From e6d78d7957729a714e5e2ac7f0ec24ed5ecfa9d5 Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 1 May 2024 10:22:58 -0700 Subject: [PATCH 001/305] powspace stabscan gridding --- slayer/makefile | 21 +++++++++++++++------ slayer/slayer.f | 38 +++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/slayer/makefile b/slayer/makefile index 65f531be3..50e86ba91 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -1,7 +1,7 @@ include ../install/DEFAULTS.inc -IFLAGS = -I$(MATHINC) -I$(NETCDFINC) +IFLAGS = -I../equil -I../pentrc -I$(MATHINC) -I$(NETCDFINC) F90 = $(FC) $(FFLAGS) $(OMPFLAG) $(IFLAGS) export FFLAGS @@ -11,7 +11,9 @@ export FFLAGS LIBDIR = ../lib LIBS = \ - -llsode + -llsode \ + -lpentrc \ + -lequil OBJS = \ sglobal.o \ @@ -20,11 +22,17 @@ OBJS = \ gslayer.o \ slayer.o -all: lsode slayer +all: lsode equil pentrc slayer -lsode: +equil: + cd ../equil; make + +lsode: cd ../lsode; make +pentrc: + cd ../pentrc; make pentrc + slayer: $(OBJS) $(F90) -o slayer $(OBJS) -L$(LIBDIR) $(LIBS) -L$(MATHDIR) $(MATHLIBS) -L$(NETCDFDIR) $(NETCDFLIBS) $(NETCDF_EXTRA_LIBS) mkdir -p ../lib/ @@ -37,7 +45,8 @@ slayer: $(OBJS) params.o : sglobal.o delta.o : sglobal.o gslayer.o : sglobal.o delta.o -slayer.o : sglobal.o params.o delta.o +slayer.o : sglobal.o params.o delta.o ../pentrc/grid.mod ../equil/spline_mod.mod clean: - rm -f *.o *.mod *.out *.bin slayer + rm -f *.o *.mod *.out *.bin slayers + diff --git a/slayer/slayer.f b/slayer/slayer.f index 81281dc04..2ef561600 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -17,6 +17,8 @@ PROGRAM slayer USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag + USE grid, ONLY : powspace,linspace + IMPLICIT NONE CHARACTER(128) :: infile @@ -43,9 +45,11 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl + $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl, + $ inQs_log REAL(r8), DIMENSION(:,:), ALLOCATABLE :: - $ js,ks,psis,jxbs,Q_sols,br_ths + $ js,ks,psis,jxbs,Q_sols,br_ths, + $ inQs_left,inQs_right REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas @@ -307,20 +311,28 @@ PROGRAM slayer c----------------------------------------------------------------------- IF (stability_flag) THEN ALLOCATE(inQs(0:inum),iinQs(0:200)) - ALLOCATE(deltas(0:inum,0:200)) - - inQ_max=10.0 - inQ_min=-10.0 - - DO i=0,inum + ALLOCATE(inQs_left(0:2+inum/2,0:2+inum/2)) + ALLOCATE(inQs_right(0:2+inum/2,0:2+inum/2)) + ALLOCATE(inQs_log(0:3+inum)) + ALLOCATE(deltas(0:3+inum,0:200)) + + inQ_max=5.0 + inQ_min=-5.0 + + inQs_left = powspace(REAL(Q)-1.0,REAL(Q),1,2+inum/2,"upper") + inQs_right = powspace(REAL(Q),REAL(Q)+1.0,1,2+inum/2,"lower") + inQs_log = (/inQs_left(1,1:2+inum/2),inQs_right(1,2:1+inum/2)/) + !WRITE(*,*)"inQs_log=",inQs_log + DO i=0,inum+3 DO j=0,200 inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) + iinQs(j)=inQ_min+(REAL(j)/200)*(inQ_max-inQ_min) - deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr,inc_beta, - $ inds,intau,inpe,iinQ=iinQs(j)) + deltas(i,j)=riccati(inQs_log(i),inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ENDDO ENDDO - + IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_stability_n"// $ TRIM(sn)//".out", STATUS="UNKNOWN") @@ -329,13 +341,13 @@ PROGRAM slayer DO i=0,inum DO j=0,200 WRITE(out_unit,'(1x,4(es17.8e3))') - $ inQs(i),iinQs(j), + $ inQs_log(i),iinQs(j), $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) ENDDO ENDDO CLOSE(out_unit) ENDIF - DEALLOCATE(inQs,iinQs,deltas) + DEALLOCATE(inQs,iinQs,inQs_left,inQs_right,inQs_log,deltas) ENDIF c----------------------------------------------------------------------- c riccati scan. From 37e04e708b1cbe6da5cc23e98b9c73fa8ba6c82b Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 3 May 2024 10:59:41 -0700 Subject: [PATCH 002/305] broken, but setting up stabscan loop --- slayer/gslayer.f | 145 ++++++++++++++++++++++++++++++++++++++++++++++- slayer/sglobal.f | 2 +- slayer/slayer.f | 34 +++++------ 3 files changed, 158 insertions(+), 23 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index f5cfe4eb5..289ad129c 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -8,6 +8,8 @@ MODULE gslayer_mod USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag + USE grid, ONLY : powspace,linspace + IMPLICIT NONE CONTAINS @@ -29,7 +31,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, LOGICAL, INTENT(IN) :: ascii_flag COMPLEX(r8),INTENT(OUT) :: delta,psi0 REAL(r8),INTENT(OUT) :: jxb,omega_sol,br_th - + INTEGER :: i,inum INTEGER, DIMENSION(1) :: index @@ -169,4 +171,145 @@ END SUBROUTINE gpec_slayer END MODULE gslayer_mod +c----------------------------------------------------------------------- +c subprogram 2. slayer_growthrates. +c run slayer to provide gamma(ising). +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- + SUBROUTINE slayer_growthrates(infile,ascii_flag, + $ ReQ_num,ImQ_num) + + INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns + INTEGER :: ReQ_num,ImQ_num ! TEMPORARY HARDCODING + + LOGICAL, INTENT(IN) :: ascii_flag + + INTEGER :: i,inum + INTEGER, DIMENSION(1) :: index + + REAL(r8) :: inQ,inQ_e,inQ_i,inpe,inc_beta,inds,intau,inlu + REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, + $ lbeta,tau_i,tau_h,tau_r,tau_v + REAL(r8) :: inQ_min,inQ_max,Q_sol + + CHARACTER(3) :: sn,sm + + CHARACTER(len=8) :: fmt ! format descriptor for stab file naming + CHARACTER(len=8) :: x1 ! string for stab file naming + INTEGER :: i1 ! integer for stab file naming + + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, + $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, + $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl, + $ inQs_log + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: + $ js,ks,psis,Q_sols, + $ inQs_left,inQs_right + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + + OPEN(UNIT=input_unit,FILE=infile,STATUS="old") + READ(input_unit,*)inn + ALLOCATE(mms(inn),nns(inn),prs(inn), + $ n_es(inn),t_es(inn),t_is(inn),omegas(inn), + $ l_ns(inn),l_ts(inn),qvals(inn),svals(inn), + $ bts(inn),rss(inn),R0s(inn),mu_is(inn),zeffs(inn), + $ Q_soll(inn),br_thl(inn)) + DO k=0,inn-1 + READ(input_unit,'(2(1x,I2),14(1x,e12.4))') + $ mms(k),nns(k),prs(k), + $ n_es(k),t_es(k),t_is(k),omegas(k), + $ l_ns(k),l_ts(k),qvals(k),svals(k), + $ bts(k),rss(k),R0s(k),mu_is(k),zeffs(k) + ENDDO + CLOSE(input_unit) + + DO k=0,inn-1 + WRITE(*,*)k + mr=REAL(mms(k)) + nr=REAL(nns(k)) + inpr=prs(k) + CALL params(n_es(k),t_es(k),t_is(k),omegas(k), + $ l_ns(k),l_ts(k),qvals(k),svals(k),bts(k),rss(k), + $ R0s(k),mu_is(k),zeffs(k),params_check) + inQ=Q + inQ_e=Q_e + inQ_i=Q_i + inc_beta=c_beta + inds=ds + intau=tau + Q0=Q + + IF (Q0>inQ_e) THEN + inQ_max=2.0*Q0 + inQ_min=1.05*inQ_e + ELSE + inQ_max=0.95*inQ_e + IF (Q0>0) THEN + inQ_min=0.8*inQ_i + ELSE + inQ_min=1.5*MINVAL((/Q0,inQ_i/)) + ENDIF + ENDIF + ENDDO + + riccati_out=.FALSE. + + ReQ_num = 400 + ImQ_num = 200 + + ALLOCATE(inQs(0:ReQ_num),iinQs(0:ImQ_num)) + ALLOCATE(inQs_left(0:2+ReQ_num/2,0:2+ReQ_num/2)) + ALLOCATE(inQs_right(0:2+ReQ_num/2,0:2+ReQ_num/2)) + ALLOCATE(inQs_log(0:3+ReQ_num)) + ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) + + inQ_max=5.0 + inQ_min=-5.0 + + inQs_left = powspace(REAL(Q)-1.0,REAL(Q),1, + $ 2+ReQ_num/2,"upper") + inQs_right = powspace(REAL(Q),REAL(Q)+1.0,1, + $ 2+ReQ_num/2,"lower") + inQs_log = (/inQs_left(1,1:2+ReQ_num/2), + $ inQs_right(1,2:1+ReQ_num/2)/) + !WRITE(*,*)"inQs_log=",inQs_log + DO i=0,ReQ_num+3 + DO j=0,ImQ_num + inQs(i)=inQ_min+(REAL(i)/ReQ_num)*(inQ_max-inQ_min) + + iinQs(j)=inQ_min+(REAL(j)/ImQ_num)*(inQ_max-inQ_min) + deltas(i,j)=riccati(inQs_log(i),inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) + ENDDO + ENDDO + +c----------------------------------------------------------------------- +c block to allow file sequential fil naming based on q + fmt = '(I5.5)' ! an integer of width 5 with zeros at the left + i1 = qvals(k) + write (x1,fmt) i1 ! integer to string using a 'internal file' +c----------------------------------------------------------------------- + + OPEN(UNIT=out_unit,FILE="slayer_stability_q"// + $ TRIM(x1)//".out", STATUS="UNKNOWN") + WRITE(out_unit,'(1x,4(a17))'),"RE(Q)", + $ "IM(Q)","RE(delta)","IM(delta)" + DO i=0,ReQ_num + DO j=0,ImQ_num + WRITE(out_unit,'(1x,4(es17.8e3))') + $ inQs_log(i),iinQs(j), + $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) + ENDDO + ENDDO + CLOSE(out_unit) + + DEALLOCATE(inQs,iinQs,inQs_left,inQs_right,inQs_log,deltas) + DEALLOCATE(prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, + $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,mms,nns) + + diff --git a/slayer/sglobal.f b/slayer/sglobal.f index dab710805..43343bf6d 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -9,7 +9,7 @@ MODULE sglobal_mod REAL(r8) :: mr,nr REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, - $ delta_n,layfac + $ delta_n,layfac,Qconv COMPLEX(r8) :: Q REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, diff --git a/slayer/slayer.f b/slayer/slayer.f index 2ef561600..f21441bf5 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -17,12 +17,10 @@ PROGRAM slayer USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag - USE grid, ONLY : powspace,linspace - IMPLICIT NONE CHARACTER(128) :: infile - INTEGER :: i,j,k,inum,jnum,knum,inn + INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -100,6 +98,8 @@ PROGRAM slayer inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. + ReQ_num=400 ! resolution for stab. scan along Re(Q) axis + ImQ_num=200 ! resolution for stab. scan along Im(Q) axis in_unit=1 out_unit=2 out2_unit=3 @@ -311,25 +311,17 @@ PROGRAM slayer c----------------------------------------------------------------------- IF (stability_flag) THEN ALLOCATE(inQs(0:inum),iinQs(0:200)) - ALLOCATE(inQs_left(0:2+inum/2,0:2+inum/2)) - ALLOCATE(inQs_right(0:2+inum/2,0:2+inum/2)) - ALLOCATE(inQs_log(0:3+inum)) - ALLOCATE(deltas(0:3+inum,0:200)) - - inQ_max=5.0 - inQ_min=-5.0 - - inQs_left = powspace(REAL(Q)-1.0,REAL(Q),1,2+inum/2,"upper") - inQs_right = powspace(REAL(Q),REAL(Q)+1.0,1,2+inum/2,"lower") - inQs_log = (/inQs_left(1,1:2+inum/2),inQs_right(1,2:1+inum/2)/) - !WRITE(*,*)"inQs_log=",inQs_log - DO i=0,inum+3 + ALLOCATE(deltas(0:inum,0:200)) + + inQ_max=10.0 + inQ_min=-10.0 + + DO i=0,inum DO j=0,200 inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) - iinQs(j)=inQ_min+(REAL(j)/200)*(inQ_max-inQ_min) - deltas(i,j)=riccati(inQs_log(i),inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) + deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr,inc_beta, + $ inds,intau,inpe,iinQ=iinQs(j)) ENDDO ENDDO @@ -341,13 +333,13 @@ PROGRAM slayer DO i=0,inum DO j=0,200 WRITE(out_unit,'(1x,4(es17.8e3))') - $ inQs_log(i),iinQs(j), + $ inQs(i),iinQs(j), $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) ENDDO ENDDO CLOSE(out_unit) ENDIF - DEALLOCATE(inQs,iinQs,inQs_left,inQs_right,inQs_log,deltas) + DEALLOCATE(inQs,iinQs,deltas) ENDIF c----------------------------------------------------------------------- c riccati scan. From 5632eb339ea812c6b3cbe35fa22746935387d287 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 23 May 2024 11:33:38 -0700 Subject: [PATCH 003/305] 1st draft gamma calculation --- slayer/gslayer.f | 420 +++++++++++++++++++++++++++++++++++++---------- slayer/slayer.f | 62 ++++++- 2 files changed, 383 insertions(+), 99 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 289ad129c..a81c8ecd4 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -8,6 +8,8 @@ MODULE gslayer_mod USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag + USE params_mod + USE grid, ONLY : powspace,linspace IMPLICIT NONE @@ -168,74 +170,220 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer - - END MODULE gslayer_mod - -c----------------------------------------------------------------------- -c subprogram 2. slayer_growthrates. -c run slayer to provide gamma(ising). c----------------------------------------------------------------------- +c Subprogram 2. interpolate_slice_at_Q +c Either extract or interpolate a 1D deltas slice at given Q c----------------------------------------------------------------------- -c declarations. -c----------------------------------------------------------------------- -c----------------------------------------------------------------------- - SUBROUTINE slayer_growthrates(infile,ascii_flag, - $ ReQ_num,ImQ_num) - - INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns - INTEGER :: ReQ_num,ImQ_num ! TEMPORARY HARDCODING + SUBROUTINE interpolate_slice_at_Q(deltas, Q, inQs_log, + $ slice) + ! Input + REAL(r8), DIMENSION(:, :), INTENT(IN) :: deltas ! 2D delta array + REAL(r8), DIMENSION(:), INTENT(IN) :: inQs_log ! Re(Q) axis + REAL(r8), INTENT(IN) :: Q ! slice value (Q) + ! Output + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: slice + ! Local variables + INTEGER :: NROWS, NCOLS, i_lower, i_upper, j + REAL(r8) :: Q_lower,Q_upper,lower_slice,upper_slice + + ! Extract array dimensions + NROWS = SIZE(deltas, 1) + NCOLS = SIZE(deltas, 2) + ! Allocate slice array (same size as second dimension of deltas) + ALLOCATE(slice(NCOLS)) + ! Find bracketing rows based on Q (assuming sorted x-axis) + i_lower = 1 + DO WHILE (i_lower < NROWS .AND. inQs_log(i_lower+1) < Q) + i_lower = i_lower + 1 + END DO + + ! Check if Q is included in inQs_log (it should be) + IF (inQs_log(i_lower) - Q < 1.0E-6_r8) THEN !!! this threshold could be an issue? + WRITE(*,*)"Indexing slice at Q!" + DO j = 1, NCOLS + slice(j) = deltas(i_lower, j) + END DO + ELSE - LOGICAL, INTENT(IN) :: ascii_flag + !! Handle cases where Q is outside the range + ! IF (i_lower == 1 .AND. Q < deltas(1, 1)) THEN + ! i_lower = 1 + ! i_upper = 2 + ! ELSE IF (i_lower == NROWS) THEN + ! i_lower = NROWS - 1 + ! i_upper = NROWS + ! ELSE + ! i_upper = i_lower + 1 + ! END IF + + ! Interpolate each element of the slice (along the columns) + DO j = 1, NCOLS + i_upper = i_lower+1 + + lower_slice = deltas(i_lower, j) + upper_slice = deltas(i_upper, j) + Q_lower = deltas(i_lower, 1) + Q_upper = deltas(i_upper, 1) + + slice(j) = (lower_slice + upper_slice )/2.0 + + ! This linear interpolation isn't correct ??? + !slice(j) = lower_slice + + !$ (Q - Q_lower) * (upper_slice - + !$ lower_slice) / (Q_upper - Q_lower) + END DO + END IF - INTEGER :: i,inum - INTEGER, DIMENSION(1) :: index + RETURN + END SUBROUTINE interpolate_slice_at_Q +c----------------------------------------------------------------------- +c Subprogram 3. gamma_from_delta_match +c Interpolate gamma corresponding to delta-deltaprime match +c----------------------------------------------------------------------- + SUBROUTINE gamma_from_delta_match(slice, iinQs, deltap, + $ ImQ_gamma) + ! Inputs + REAL(r8), DIMENSION(:), INTENT(IN) :: slice ! 1D gammas array, i.e. deltas(Q,:) + REAL(r8), DIMENSION(:), INTENT(IN) :: iinQs ! 1D array of gammas, i.e. Im(Q) + REAL(r8), INTENT(IN) :: deltap ! Target outer layer delta prime + ! Output + REAL(r8), INTENT(OUT) :: ImQ_gamma ! Matched gamma(delta = deltaprime), giving growth rate + ! Local variables + INTEGER :: n, i_lower, i_upper, i, j, i_mid + REAL(r8) :: deltap_lower, deltap_upper, slope + REAL(r8), DIMENSION(:), ALLOCATABLE :: temp_slice, temp_iinQs ! Temporary arrays + + ! Array size check + n = SIZE(slice) + IF (SIZE(iinQs) /= n) THEN + WRITE(*, *) 'ERROR: slice and iinQs arrays + $ must have the same size.' + RETURN + END IF + + ! Allocate temporary arrays + ALLOCATE(temp_slice(n), temp_iinQs(n)) + ImQ_gamma = 0.0 + + ! Copy input arrays to temporary arrays + temp_slice = slice + temp_iinQs = iinQs + + ! Sort temporary arrays in ascending order if not already sorted + IF (ANY(temp_slice(2:) < temp_slice(:n-1))) THEN + ! Simple bubble sort + DO i = 1, n - 1 + DO j = 1, n - i + IF (temp_slice(j) > temp_slice(j + 1)) THEN + ! Swap elements in temp_slice + temp_slice([j, j+1]) = temp_slice([j+1, j]) + ! Swap corresponding elements in temp_iinQs + temp_iinQs([j, j+1]) = temp_iinQs([j+1, j]) + END IF + END DO + END DO + END IF + + ! Handle cases where deltap is outside the temp_slice range + IF (deltap < temp_slice(1)) THEN + i_lower = 1 + i_upper = 2 + ELSE IF (deltap > temp_slice(n)) THEN + i_lower = n - 1 + i_upper = n + ELSE + ! Find the bracketing indices for deltap (binary search) + i_lower = 1 + i_upper = n + DO WHILE (i_upper - i_lower > 1) + i_mid = (i_lower + i_upper) / 2 + IF (temp_slice(i_mid) < deltap) THEN + i_lower = i_mid + ELSE + i_upper = i_mid + END IF + END DO + END IF + + deltap_lower = temp_slice(i_lower) + deltap_upper = temp_slice(i_upper) + + !WRITE(*,*)"my ImQ_gamma=",(temp_iinQs(i_lower) + FOR TESTING + !$ temp_iinQs(i_upper)) / 2 + + ! Linear interpolation (revised for robustness) + IF (deltap_upper - deltap_lower < 1.0E-10_r8) THEN ! Handle nearly equal values + ImQ_gamma = (temp_iinQs(i_lower) + + $ temp_iinQs(i_upper)) / 2 + ELSE + slope = (temp_iinQs(i_upper) - + $ temp_iinQs(i_lower)) / (deltap_upper - deltap_lower) + ImQ_gamma = temp_iinQs(i_lower) + + $ slope * (deltap - deltap_lower) + END IF - REAL(r8) :: inQ,inQ_e,inQ_i,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, - $ lbeta,tau_i,tau_h,tau_r,tau_v - REAL(r8) :: inQ_min,inQ_max,Q_sol + !WRITE(*,*)"temp_iinQs(i_lower)=",temp_iinQs(i_lower) + !WRITE(*,*)"temp_iinQs(i_upper)=",temp_iinQs(i_upper) + !WRITE(*,*)"slice=",slice - CHARACTER(3) :: sn,sm + ! Deallocate temporary arrays + DEALLOCATE(temp_slice, temp_iinQs) + RETURN + END SUBROUTINE gamma_from_delta_match +c----------------------------------------------------------------------- +c Subprogram 4. gamma_stability_scan +c Run grid packed slayer stab. scan around omega_ExB and gamma axes +c----------------------------------------------------------------------- + SUBROUTINE gamma_stability_scan(n_e,t_e,t_i,omega,inpr,inpe, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff, + $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs,inQ) +c----------------------------------------------------------------------- +c Declarations +c----------------------------------------------------------------------- + ! Inputs + REAL(r8),INTENT(IN) :: n_e,t_e,t_i,omega,inpr,inpe,l_n,l_t, + $ qval,sval,bt,rs,R0,mu_i,zeff + INTEGER, INTENT(IN) :: ReQ_num,ImQ_num + + ! Outputs + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, + $ INTENT(OUT) :: deltas + REAL(r8), DIMENSION(:), ALLOCATABLE, + $ INTENT(OUT) :: inQs_log,iinQs + REAL(r8), INTENT(OUT) :: inQ + + ! Local variables + INTEGER :: i,j,k + REAL(r8) :: inQ_e,inQ_i,inc_beta,inds,intau,Q0 + !REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, + !$ lbeta,tau_i,tau_h,tau_r,tau_v + REAL(r8) :: inQ_min,inQ_max + LOGICAL :: params_check + CHARACTER(3) :: q_str CHARACTER(len=8) :: fmt ! format descriptor for stab file naming CHARACTER(len=8) :: x1 ! string for stab file naming INTEGER :: i1 ! integer for stab file naming - - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl, - $ inQs_log - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: - $ js,ks,psis,Q_sols, - $ inQs_left,inQs_right - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas - - OPEN(UNIT=input_unit,FILE=infile,STATUS="old") - READ(input_unit,*)inn - ALLOCATE(mms(inn),nns(inn),prs(inn), - $ n_es(inn),t_es(inn),t_is(inn),omegas(inn), - $ l_ns(inn),l_ts(inn),qvals(inn),svals(inn), - $ bts(inn),rss(inn),R0s(inn),mu_is(inn),zeffs(inn), - $ Q_soll(inn),br_thl(inn)) - DO k=0,inn-1 - READ(input_unit,'(2(1x,I2),14(1x,e12.4))') - $ mms(k),nns(k),prs(k), - $ n_es(k),t_es(k),t_is(k),omegas(k), - $ l_ns(k),l_ts(k),qvals(k),svals(k), - $ bts(k),rss(k),R0s(k),mu_is(k),zeffs(k) - ENDDO - CLOSE(input_unit) - - DO k=0,inn-1 - WRITE(*,*)k - mr=REAL(mms(k)) - nr=REAL(nns(k)) - inpr=prs(k) - CALL params(n_es(k),t_es(k),t_is(k),omegas(k), - $ l_ns(k),l_ts(k),qvals(k),svals(k),bts(k),rss(k), - $ R0s(k),mu_is(k),zeffs(k),params_check) - inQ=Q + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: inQs_left,inQs_right + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs +c----------------------------------------------------------------------- +c Set initial values +c----------------------------------------------------------------------- + inQ=0.0 ! Q=23.0 for DIII-D example. + inQ_e=0.0 ! Q_e=2.0 for DIII-D example. + inQ_i=0.0 ! Q_i=-2.6 for DIII-D example. + inc_beta=0.0 ! c_beta=0.7 for DIII-D example. + inds=0.0 ! 6.0 for DIII-D example. + intau=0.0 ! 1.0 for DIII-D example. + !inlu=1e8 + Q0=0.0 +c----------------------------------------------------------------------- +c Calculate parameters as needed +c----------------------------------------------------------------------- + params_check=.TRUE. + CALL params(n_e,t_e,t_i,omega, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + inQ=REAL(Q) inQ_e=Q_e inQ_i=Q_i inc_beta=c_beta @@ -243,57 +391,60 @@ SUBROUTINE slayer_growthrates(infile,ascii_flag, intau=tau Q0=Q - IF (Q0>inQ_e) THEN - inQ_max=2.0*Q0 - inQ_min=1.05*inQ_e - ELSE - inQ_max=0.95*inQ_e - IF (Q0>0) THEN - inQ_min=0.8*inQ_i - ELSE - inQ_min=1.5*MINVAL((/Q0,inQ_i/)) - ENDIF - ENDIF - ENDDO - - riccati_out=.FALSE. - - ReQ_num = 400 - ImQ_num = 200 - + ! JKP used this for multiple surface loop but not for single surface? + ! IF (Q0>inQ_e) THEN + ! inQ_max=2.0*Q0 + ! inQ_min=1.05*inQ_e + ! ELSE + ! inQ_max=0.95*inQ_e + ! IF (Q0>0) THEN + ! inQ_min=0.8*inQ_i + ! ELSE + ! inQ_min=1.5*MINVAL((/Q0,inQ_i/)) + ! ENDIF + ! ENDIF + !ENDDO + + !riccati_out=.FALSE. ? +c----------------------------------------------------------------------- +c Build exponential grid packing for stability scan, then run +c----------------------------------------------------------------------- + ! Allocate grid packing arrays and 2D complex deltas array ALLOCATE(inQs(0:ReQ_num),iinQs(0:ImQ_num)) ALLOCATE(inQs_left(0:2+ReQ_num/2,0:2+ReQ_num/2)) ALLOCATE(inQs_right(0:2+ReQ_num/2,0:2+ReQ_num/2)) ALLOCATE(inQs_log(0:3+ReQ_num)) ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) - inQ_max=5.0 - inQ_min=-5.0 + inQ_max=3.0 ! max growth rate in scan, OPEN TO USER? + inQ_min=-3.0 ! min growth rate in scan, OPEN TO USER? - inQs_left = powspace(REAL(Q)-1.0,REAL(Q),1, + ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? + inQs_left = powspace(inQ-0.2,inQ,1, ! omega-0.2 $ 2+ReQ_num/2,"upper") - inQs_right = powspace(REAL(Q),REAL(Q)+1.0,1, + inQs_right = powspace(inQ,inQ+0.2,1, ! omega+0.2 $ 2+ReQ_num/2,"lower") inQs_log = (/inQs_left(1,1:2+ReQ_num/2), $ inQs_right(1,2:1+ReQ_num/2)/) !WRITE(*,*)"inQs_log=",inQs_log + DO i=0,ReQ_num+3 DO j=0,ImQ_num + ! Getting rid of "inQs" weirdly broke things?? inQs(i)=inQ_min+(REAL(i)/ReQ_num)*(inQ_max-inQ_min) - iinQs(j)=inQ_min+(REAL(j)/ImQ_num)*(inQ_max-inQ_min) + ! Run riccati() at each Q index to give delta deltas(i,j)=riccati(inQs_log(i),inQ_e,inQ_i,inpr, $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ENDDO ENDDO - c----------------------------------------------------------------------- -c block to allow file sequential fil naming based on q - fmt = '(I5.5)' ! an integer of width 5 with zeros at the left - i1 = qvals(k) - write (x1,fmt) i1 ! integer to string using a 'internal file' +c Alter qval to string to add to output filename c----------------------------------------------------------------------- - + fmt = '(I5.5)' ! an integer of width 2 for q surface + i1 = qval + write (x1,fmt) i1 ! integer to string using a 'internal file' + ! Write stability scan output file OPEN(UNIT=out_unit,FILE="slayer_stability_q"// $ TRIM(x1)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))'),"RE(Q)", @@ -305,11 +456,98 @@ SUBROUTINE slayer_growthrates(infile,ascii_flag, $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) ENDDO ENDDO - CLOSE(out_unit) + CLOSE(out_unit) - DEALLOCATE(inQs,iinQs,inQs_left,inQs_right,inQs_log,deltas) - DEALLOCATE(prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,mms,nns) + DEALLOCATE(inQs_left,inQs_right) + RETURN + END SUBROUTINE gamma_stability_scan +c----------------------------------------------------------------------- +c Subprogram 5. gamma_match +c Loop stability scans and gamma matches across k rational surfaces +c----------------------------------------------------------------------- + SUBROUTINE gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, + $ l_ns,l_ts,qvals,svals,bts,rss,R0s,mu_is, + $ zeffs,outer_deltas,ReQ_num,ImQ_num, + $ growthrates,growthrate_err) +c----------------------------------------------------------------------- +c Declarations +c----------------------------------------------------------------------- + ! Inputs + INTEGER, INTENT(IN) :: n_k ! Number of rational surfaces + INTEGER, DIMENSION(:), INTENT(IN) :: mms,nns + REAL(r8), DIMENSION(:), INTENT(IN) :: n_es,t_es, + $ t_is,omegas,prs,pes,l_ns,l_ts,qvals, + $ svals,bts,rss,R0s,mu_is,zeffs + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: outer_deltas + INTEGER, INTENT(IN) :: ReQ_num,ImQ_num + ! Outputs + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: + $ growthrates, growthrate_err + ! Local variables + INTEGER :: k,w + ! Local variables received from internal subroutines + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log,iinQs + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + REAL(r8), DIMENSION(:), ALLOCATABLE :: slice + REAL(r8), DIMENSION(3) :: Q_range ! (-Q_err, Q, +Q_err ) + REAL(r8), DIMENSION(3) :: growthrate_range ! (-gamma, +gamma ) + REAL(r8) :: layer_Q, inQ, ImQ_gamma + + ! Allocate growthrates arrays + ALLOCATE(growthrates(n_k)) + ALLOCATE(growthrate_err(n_k)) +c----------------------------------------------------------------------- +c Loop across rational surfaces +c----------------------------------------------------------------------- + ! Summary: for each rational surface, run narrow stability scan + ! for analysis, then slice out 1D array of growth rates at + ! given omega_ExB (Q), then find growth rate corresponding + ! to delta-deltaprime match + DO k=1,n_k + WRITE(*,*)"layer #: ",k + ! Run stability scan + CALL gamma_stability_scan(n_es(k),t_es(k),t_is(k),omegas(k), + $ prs(k),pes(k),l_ns(k),l_ts(k),qvals(k),svals(k), + $ bts(k),rss(k),R0s(k),mu_is(k),zeffs(k), + $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs,inQ) + + layer_Q = inQ + + ! Hardcoding rudimentary +/- 10% omega_ExB errorbars + Q_range = (/0.9*layer_Q, 1.1*layer_Q, layer_Q/) + + ! Calculate growth rate +/- omega_ExB = Q errorbars + DO w=1,3 + + ! Slice out growth rates at layer_Q (Re(Q)) + CALL interpolate_slice_at_Q(REAL(deltas), + $ Q_range(w), inQs_log, slice) + + ! Match delta to delta prime to obtain growth rate + CALL gamma_from_delta_match(slice, iinQs, + $ REAL(outer_deltas(k)), + $ ImQ_gamma) + + IF (w==3) THEN + ! Qconv = Q / omega_ExB + ! gamma = Im(Q) / Qconv + growthrates(k) = ImQ_gamma / (layer_Q / omegas(k)) + ELSE + growthrate_range(w) = ImQ_gamma / (layer_Q / omegas(k)) + ENDIF + ENDDO + growthrate_err(k) = ABS(growthrate_range(2) - + $ growthrate_range(1)) + + DEALLOCATE(slice) ! Free memory after use for each layer + + ENDDO + DEALLOCATE(deltas,inQs_log,iinQs) + + RETURN + END SUBROUTINE gamma_match + + END MODULE gslayer_mod diff --git a/slayer/slayer.f b/slayer/slayer.f index f21441bf5..315b963de 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -17,10 +17,13 @@ PROGRAM slayer USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag + ! FOR TESTING: + USE gslayer_mod + IMPLICIT NONE CHARACTER(128) :: infile - INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num + INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num,n_k INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -28,7 +31,7 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check + $ params_check,gamma_match_flag !FOR TESTING REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff @@ -42,16 +45,19 @@ PROGRAM slayer INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl, + $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, + $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes, $ inQs_log REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, $ inQs_left,inQs_right REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates, + $ growthrate_err + NAMELIST/slayer_input/params_flag,input_flag,infile, $ mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, @@ -63,7 +69,7 @@ PROGRAM slayer NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ stability_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, - $ params_check,bal_flag + $ params_check,bal_flag,gamma_match_flag c----------------------------------------------------------------------- c set initial values. c----------------------------------------------------------------------- @@ -98,8 +104,8 @@ PROGRAM slayer inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=400 ! resolution for stab. scan along Re(Q) axis - ImQ_num=200 ! resolution for stab. scan along Im(Q) axis + ReQ_num=200 ! resolution for stab. scan along Re(Q) axis + ImQ_num=100 ! resolution for stab. scan along Im(Q) axis in_unit=1 out_unit=2 out2_unit=3 @@ -255,6 +261,46 @@ PROGRAM slayer $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,mms,nns) ENDIF c----------------------------------------------------------------------- +c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY +c----------------------------------------------------------------------- + IF (gamma_match_flag) THEN + n_k=2 + ALLOCATE(mms(n_k),nns(n_k),prs(n_k),pes(n_k), + $ n_es(n_k),t_es(n_k),t_is(n_k),omegas(n_k), + $ l_ns(n_k),l_ts(n_k),qvals(n_k),svals(n_k), + $ bts(n_k),rss(n_k),R0s(n_k),mu_is(n_k),zeffs(n_k), + $ outer_deltas(n_k)) + ! Approximate data for q=2 and q=3, for testing only + ! These are of the form needed to output from + ! upcoming build_inputs.f script, interfacing with equil.f + mms = (/2, 2 /) + nns = (/1, 1 /) + prs = (/0.1, 0.005 /) + pes = (/0.0, 0.0 /) + n_es = (/7.765e+17, 9.999e+16 /) + t_es = (/26.0, 2.0 /) + t_is = (/26.0, 2.0 /) + omegas = (/54530.0, 54530.0 /) + l_ns = (/0.2, 0.2 /) + l_ts = (/0.1, 0.1 /) + qvals = (/2.0, 3.0 /) + svals = (/729.5, 500.5 /) + bts = (/1.0, 1.0 /) + rss = (/0.16, 0.2 /) + R0s = (/2.0, 2.0 /) + mu_is = (/2.0, 2.0 /) + zeffs = (/2.0, 2.0 /) + outer_deltas = (/(27.15,0.1), (20.15,0.1) /) + + CALL gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, + $ l_ns,l_ts,qvals,svals,bts,rss,R0s,mu_is, + $ zeffs,outer_deltas, + $ ReQ_num,ImQ_num,growthrates,growthrate_err) + WRITE(*,*)"growthrates=",growthrates + WRITE(*,*)"growthrate error=",growthrate_err + + ENDIF +c----------------------------------------------------------------------- c find solutions based on simple torque balance. c----------------------------------------------------------------------- IF (bal_flag)THEN From e04745f6392d5cd3d1844c83983d665685d47e77 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 6 Jun 2024 15:21:06 -0700 Subject: [PATCH 004/305] WIP: layer inputs routine --- slayer/gslayer.f | 245 ++++++++++++++++++++----------------------- slayer/layerinputs.f | 236 +++++++++++++++++++++++++++++++++++++++++ slayer/makefile | 3 +- slayer/slayer.f | 68 +++++++----- 4 files changed, 396 insertions(+), 156 deletions(-) create mode 100644 slayer/layerinputs.f diff --git a/slayer/gslayer.f b/slayer/gslayer.f index a81c8ecd4..2182c57c6 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -250,8 +250,10 @@ SUBROUTINE gamma_from_delta_match(slice, iinQs, deltap, REAL(r8), INTENT(OUT) :: ImQ_gamma ! Matched gamma(delta = deltaprime), giving growth rate ! Local variables INTEGER :: n, i_lower, i_upper, i, j, i_mid - REAL(r8) :: deltap_lower, deltap_upper, slope - REAL(r8), DIMENSION(:), ALLOCATABLE :: temp_slice, temp_iinQs ! Temporary arrays + REAL(r8) :: deltap_lower, deltap_upper, slope, + $ lower_val, upper_val,n_poles,dx + REAL(r8), DIMENSION(:), ALLOCATABLE :: temp_slice, temp_iinQs, ! Temporary arrays + $ grad_slice, pospole_gamma, negpole_gamma ! Array size check n = SIZE(slice) @@ -263,67 +265,93 @@ SUBROUTINE gamma_from_delta_match(slice, iinQs, deltap, ! Allocate temporary arrays ALLOCATE(temp_slice(n), temp_iinQs(n)) + ALLOCATE(grad_slice(n-1)) ! gradient + ImQ_gamma = 0.0 ! Copy input arrays to temporary arrays temp_slice = slice temp_iinQs = iinQs - ! Sort temporary arrays in ascending order if not already sorted - IF (ANY(temp_slice(2:) < temp_slice(:n-1))) THEN - ! Simple bubble sort - DO i = 1, n - 1 - DO j = 1, n - i - IF (temp_slice(j) > temp_slice(j + 1)) THEN - ! Swap elements in temp_slice - temp_slice([j, j+1]) = temp_slice([j+1, j]) - ! Swap corresponding elements in temp_iinQs - temp_iinQs([j, j+1]) = temp_iinQs([j+1, j]) + ! GRADIENT THRESHOLD IS 50??? + n_poles = 1.0 + ALLOCATE(pospole_gamma(2),negpole_gamma(2)) + pospole_gamma = 1e+20 + negpole_gamma = 1e+20 + + ! Calculate the gradient + DO i = 1, n - 1 + dx = iinQs(i + 1) - iinQs(i) ! Change in x + grad_slice(i) = (slice(i + 1) - slice(i)) / dx ! Slope (gradient) + END DO + + ! Find first match + DO i = 1, n-1 + ! is this a pole point? + IF (((SIGN(1.0,slice(i)) /= SIGN(1.0,slice(i+1))) .AND. + $ ABS(grad_slice(i) > 50))) THEN!.AND. + !$ (SIGN(1.0,grad_slice(i)) /= + !$ SIGN(1.0,grad_slice(i+1)))) THEN + ! it is a pole + IF (slice(i) > 0) THEN + WRITE(*,*)"FOUND A POS POLE ",0.0 + pospole_gamma(n_poles) = iinQs(i) + negpole_gamma(n_poles) = iinQs(i+1) + n_poles = n_poles + 1 + ELSE + WRITE(*,*)"FOUND A NEG POLE ",i + negpole_gamma(n_poles) = iinQs(i) + pospole_gamma(n_poles) = iinQs(i+1) + n_poles = n_poles+1 END IF - END DO - END DO - END IF - ! Handle cases where deltap is outside the temp_slice range - IF (deltap < temp_slice(1)) THEN - i_lower = 1 - i_upper = 2 - ELSE IF (deltap > temp_slice(n)) THEN - i_lower = n - 1 - i_upper = n - ELSE - ! Find the bracketing indices for deltap (binary search) - i_lower = 1 - i_upper = n - DO WHILE (i_upper - i_lower > 1) - i_mid = (i_lower + i_upper) / 2 - IF (temp_slice(i_mid) < deltap) THEN - i_lower = i_mid - ELSE - i_upper = i_mid - END IF - END DO - END IF + WRITE(*,*)"n_poles=",n_poles - deltap_lower = temp_slice(i_lower) - deltap_upper = temp_slice(i_upper) + IF ((SIGN(1.0,slice(i))<0.0 .AND. deltap0.0 .AND. deltap>slice(i))) THEN + ImQ_gamma = iinQs(i) ! stride value is huge and not captured by our grid, but will be close to the pole + END IF + ELSE + ! Determine which element of the slice is lower and which is upper + IF (slice(i) <= slice(i + 1)) THEN + lower_val = slice(i) + upper_val = slice(i + 1) + ELSE + lower_val = slice(i + 1) + upper_val = slice(i) + END IF - !WRITE(*,*)"my ImQ_gamma=",(temp_iinQs(i_lower) + FOR TESTING - !$ temp_iinQs(i_upper)) / 2 + ! Check if deltap is within the interval + IF ((lower_val <= deltap) .AND. + $ (deltap <= upper_val)) THEN + !ImQ_gamma = (iinQs(i) + iinQs(i+1)) / 2 ! do a better linear interpolation here + + ! Linear interpolation (revised for robustness) + IF (upper_val - lower_val < 1.0E-10_r8) THEN ! Handle nearly equal values + ImQ_gamma = (iinQs(i) + + $ iinQs(i+1)) / 2 + ELSE + slope = (iinQs(i+1) - + $ iinQs(i)) / (upper_val - lower_val) + ImQ_gamma = iinQs(i) + + $ slope * (deltap - lower_val) + END IF + END IF + END IF + END DO - ! Linear interpolation (revised for robustness) - IF (deltap_upper - deltap_lower < 1.0E-10_r8) THEN ! Handle nearly equal values - ImQ_gamma = (temp_iinQs(i_lower) + - $ temp_iinQs(i_upper)) / 2 - ELSE - slope = (temp_iinQs(i_upper) - - $ temp_iinQs(i_lower)) / (deltap_upper - deltap_lower) - ImQ_gamma = temp_iinQs(i_lower) + - $ slope * (deltap - deltap_lower) + IF (n_poles > 2) THEN + WRITE(*,*)"Alert: more than two poles detected" END IF + !WRITE(*,*)"temp_iinQs(i_lower)=",temp_iinQs(i_lower) !WRITE(*,*)"temp_iinQs(i_upper)=",temp_iinQs(i_upper) + !WRITE(*,*)"NEWImQ_gamma=",ImQ_gamma + !WRITE(*,*)"pospole_gamma=",pospole_gamma + !WRITE(*,*)"negpole_gamma=",negpole_gamma + !WRITE(*,*)"slice=",slice ! Deallocate temporary arrays @@ -335,15 +363,16 @@ END SUBROUTINE gamma_from_delta_match c Subprogram 4. gamma_stability_scan c Run grid packed slayer stab. scan around omega_ExB and gamma axes c----------------------------------------------------------------------- - SUBROUTINE gamma_stability_scan(n_e,t_e,t_i,omega,inpr,inpe, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff, - $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs,inQ) + SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,inQ0,inpr,inpe,ReQ_num,ImQ_num,deltas, + $ inQs_log,iinQs) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - REAL(r8),INTENT(IN) :: n_e,t_e,t_i,omega,inpr,inpe,l_n,l_t, - $ qval,sval,bt,rs,R0,mu_i,zeff + REAL(r8),INTENT(IN) :: qval,inQ_e,inQ_i,inc_beta,inds, + $ intau,inQ0,inpr,inpe + COMPLEX(r8), INTENT(IN) :: inQ INTEGER, INTENT(IN) :: ReQ_num,ImQ_num ! Outputs @@ -351,15 +380,10 @@ SUBROUTINE gamma_stability_scan(n_e,t_e,t_i,omega,inpr,inpe, $ INTENT(OUT) :: deltas REAL(r8), DIMENSION(:), ALLOCATABLE, $ INTENT(OUT) :: inQs_log,iinQs - REAL(r8), INTENT(OUT) :: inQ ! Local variables INTEGER :: i,j,k - REAL(r8) :: inQ_e,inQ_i,inc_beta,inds,intau,Q0 - !REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, - !$ lbeta,tau_i,tau_h,tau_r,tau_v REAL(r8) :: inQ_min,inQ_max - LOGICAL :: params_check CHARACTER(3) :: q_str CHARACTER(len=8) :: fmt ! format descriptor for stab file naming CHARACTER(len=8) :: x1 ! string for stab file naming @@ -367,46 +391,6 @@ SUBROUTINE gamma_stability_scan(n_e,t_e,t_i,omega,inpr,inpe, REAL(r8), DIMENSION(:,:), ALLOCATABLE :: inQs_left,inQs_right REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs c----------------------------------------------------------------------- -c Set initial values -c----------------------------------------------------------------------- - inQ=0.0 ! Q=23.0 for DIII-D example. - inQ_e=0.0 ! Q_e=2.0 for DIII-D example. - inQ_i=0.0 ! Q_i=-2.6 for DIII-D example. - inc_beta=0.0 ! c_beta=0.7 for DIII-D example. - inds=0.0 ! 6.0 for DIII-D example. - intau=0.0 ! 1.0 for DIII-D example. - !inlu=1e8 - Q0=0.0 -c----------------------------------------------------------------------- -c Calculate parameters as needed -c----------------------------------------------------------------------- - params_check=.TRUE. - CALL params(n_e,t_e,t_i,omega, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - inQ=REAL(Q) - inQ_e=Q_e - inQ_i=Q_i - inc_beta=c_beta - inds=ds - intau=tau - Q0=Q - - ! JKP used this for multiple surface loop but not for single surface? - ! IF (Q0>inQ_e) THEN - ! inQ_max=2.0*Q0 - ! inQ_min=1.05*inQ_e - ! ELSE - ! inQ_max=0.95*inQ_e - ! IF (Q0>0) THEN - ! inQ_min=0.8*inQ_i - ! ELSE - ! inQ_min=1.5*MINVAL((/Q0,inQ_i/)) - ! ENDIF - ! ENDIF - !ENDDO - - !riccati_out=.FALSE. ? -c----------------------------------------------------------------------- c Build exponential grid packing for stability scan, then run c----------------------------------------------------------------------- ! Allocate grid packing arrays and 2D complex deltas array @@ -420,9 +404,9 @@ SUBROUTINE gamma_stability_scan(n_e,t_e,t_i,omega,inpr,inpe, inQ_min=-3.0 ! min growth rate in scan, OPEN TO USER? ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? - inQs_left = powspace(inQ-0.2,inQ,1, ! omega-0.2 + inQs_left = powspace(REAL(inQ)-0.2,REAL(inQ),1, ! omega-0.2 $ 2+ReQ_num/2,"upper") - inQs_right = powspace(inQ,inQ+0.2,1, ! omega+0.2 + inQs_right = powspace(REAL(inQ),REAL(inQ)+0.2,1, ! omega+0.2 $ 2+ReQ_num/2,"lower") inQs_log = (/inQs_left(1,1:2+ReQ_num/2), $ inQs_right(1,2:1+ReQ_num/2)/) @@ -466,25 +450,24 @@ END SUBROUTINE gamma_stability_scan c Subprogram 5. gamma_match c Loop stability scans and gamma matches across k rational surfaces c----------------------------------------------------------------------- - SUBROUTINE gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, - $ l_ns,l_ts,qvals,svals,bts,rss,R0s,mu_is, - $ zeffs,outer_deltas,ReQ_num,ImQ_num, - $ growthrates,growthrate_err) + SUBROUTINE gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, + $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, + $ inpr_arr,inpe_arr,outer_delta_arr,omegas_arr, + $ ReQ_num,ImQ_num,growthrates,growthrate_err) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - INTEGER, INTENT(IN) :: n_k ! Number of rational surfaces - INTEGER, DIMENSION(:), INTENT(IN) :: mms,nns - REAL(r8), DIMENSION(:), INTENT(IN) :: n_es,t_es, - $ t_is,omegas,prs,pes,l_ns,l_ts,qvals, - $ svals,bts,rss,R0s,mu_is,zeffs - COMPLEX(r8), DIMENSION(:), INTENT(IN) :: outer_deltas + REAL(r8), DIMENSION(:), INTENT(IN) :: qvals,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, + $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: inQ_arr,outer_delta_arr INTEGER, INTENT(IN) :: ReQ_num,ImQ_num ! Outputs REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ growthrates, growthrate_err ! Local variables + INTEGER :: n_k ! Number of rational surfaces INTEGER :: k,w ! Local variables received from internal subroutines REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log,iinQs @@ -492,8 +475,9 @@ SUBROUTINE gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, REAL(r8), DIMENSION(:), ALLOCATABLE :: slice REAL(r8), DIMENSION(3) :: Q_range ! (-Q_err, Q, +Q_err ) REAL(r8), DIMENSION(3) :: growthrate_range ! (-gamma, +gamma ) - REAL(r8) :: layer_Q, inQ, ImQ_gamma + REAL(r8) :: layer_Q, ImQ_gamma + n_k = SIZE(qvals) ! Allocate growthrates arrays ALLOCATE(growthrates(n_k)) ALLOCATE(growthrate_err(n_k)) @@ -504,37 +488,38 @@ SUBROUTINE gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, ! for analysis, then slice out 1D array of growth rates at ! given omega_ExB (Q), then find growth rate corresponding ! to delta-deltaprime match - DO k=1,n_k + DO k=1,n_k-1 WRITE(*,*)"layer #: ",k ! Run stability scan - CALL gamma_stability_scan(n_es(k),t_es(k),t_is(k),omegas(k), - $ prs(k),pes(k),l_ns(k),l_ts(k),qvals(k),svals(k), - $ bts(k),rss(k),R0s(k),mu_is(k),zeffs(k), - $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs,inQ) + CALL gamma_stability_scan(qvals(k),inQ_arr(k),inQ_e_arr(k), + $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), + $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), + $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs) - layer_Q = inQ + layer_Q = REAL(inQ_arr(k)) ! Hardcoding rudimentary +/- 10% omega_ExB errorbars Q_range = (/0.9*layer_Q, 1.1*layer_Q, layer_Q/) ! Calculate growth rate +/- omega_ExB = Q errorbars DO w=1,3 - - ! Slice out growth rates at layer_Q (Re(Q)) - CALL interpolate_slice_at_Q(REAL(deltas), - $ Q_range(w), inQs_log, slice) - - ! Match delta to delta prime to obtain growth rate - CALL gamma_from_delta_match(slice, iinQs, - $ REAL(outer_deltas(k)), - $ ImQ_gamma) - IF (w==3) THEN + + ! Slice out growth rates at layer_Q (Re(Q)) + CALL interpolate_slice_at_Q(REAL(deltas), + $ Q_range(w), inQs_log, slice) + ! Match delta to delta prime to obtain growth rate + CALL gamma_from_delta_match(slice, iinQs, + $ REAL(outer_delta_arr(k)), + $ ImQ_gamma) + CALL gamma_from_delta_match(slice, iinQs, + $ REAL(outer_delta_arr(k)), + $ ImQ_gamma) ! Qconv = Q / omega_ExB ! gamma = Im(Q) / Qconv - growthrates(k) = ImQ_gamma / (layer_Q / omegas(k)) - ELSE - growthrate_range(w) = ImQ_gamma / (layer_Q / omegas(k)) + growthrates(k) = ImQ_gamma / (layer_Q / omegas_arr(k)) + !ELSE + ! growthrate_range(w) = ImQ_gamma / (layer_Q / omegas(k)) ENDIF ENDDO diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f new file mode 100644 index 000000000..1642104ea --- /dev/null +++ b/slayer/layerinputs.f @@ -0,0 +1,236 @@ + MODULE layerinputs_mod + + USE inputs, ONLY : kin + USE pentrc_interface, ONLY : zi,mi,wefac,wpfac,initialize_pentrc + USE read_eq_mode, ONLY : read_eq_efit + USE direct_mod, ONLY : direct_run + !resm = mfac(resnum(ising)) + !CALL spline_eval(kin,respsi,1) + + ! STILL NEED ro AND bt0, GLOBAL GPEC VARIABLES + + IMPLICIT NONE + + CONTAINS +c----------------------------------------------------------------------- +c subprogram 1. build_inputs. +c compute +c----------------------------------------------------------------------- + SUBROUTINE build_inputs(egnum,xspmn,spot,nspot, + $ growthrate_flag, + $ slayer_inpr) +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + LOGICAL, INTENT(IN) :: growthrate_flag + INTEGER, INTENT(IN) :: egnum,nspot + REAL(r8), INTENT(IN) :: spot, slayer_inpr + + !INTEGER :: i_id,q_id,m_id,p_id,c_id,w_id,k_id,n_id,d_id,a_id, + !$ pp_id,cp_id,wp_id,np_id,dp_id,wc_id,bc_id, + !$ astat + + REAL(r8) :: respsi,lpsi,rpsi,shear,hdist,sbnosurf + + INTEGER, DIMENSION(msing) :: resnum + + TYPE(spline_type) :: spl + + INTEGER :: resm + TYPE(spline_type) :: sr + + REAL(r8), DIMENSION(msing) :: inQ_arr,inQ_e_arr,inQ_i_arr, + $ inc_beta_arr,inds_arr,intau_arr,Q0_arr, + $ outer_delta_arr + +c----------------------------------------------------------------------- +c Find # of rational surfaces msing. +c----------------------------------------------------------------------- + CHARACTER(*) :: vmat_filename + INTEGER :: ising + + OPEN(UNIT=debug_unit,FILE=TRIM(vmat_filename),STATUS="OLD", + $ FORM="UNFORMATTED") + READ(debug_unit)mpert,msing + +c----------------------------------------------------------------------- +c Read and build equilibrium inputs +c----------------------------------------------------------------------- + CALL fourfit_action_matrix + ! call the automatic reading and distributing of inputs + CALL initialize_pentrc(op_kin=.FALSE.,op_deq=.FALSE., + $ op_peq=.FALSE.) + ! manually set the pentrc equilibrium description + CALL set_eq(eqfun,sq,rzphi,smats,tmats,xmats,ymats,zmats, + $ twopi*psio,ro,nn,jac_type,mlow,mhigh,mpert,mthvac) + ! manually set the kinetic profiles + CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, + $ tfac,wefac,wpfac,indebug) + ! manually set the perturbed equilibrium displacements + ! use false flat xi and xi' for equal weighting + ALLOCATE(psitmp(sq%mx+1),mtmp(mpert),xtmp(sq%mx+1,mpert)) + psitmp(:) = sq%xs(0:) + mtmp = (/(m,m=mlow,mhigh)/) + xtmp = 1e-4 + CALL set_peq(psitmp,mtmp,xtmp,xtmp,xtmp,.false.,tdebug) + DEALLOCATE(xtmp,mtmp,psitmp) + IF(verbose) WRITE(*,*)"Computing Kinetic Matrices" + CALL fourfit_kinetic_matrix(kingridtype,out_fund) + + CALL sing_scan + DO ising=1,msing + CALL resist_eval(sing(ising)) + ENDDO + + CALL ksing_find + + + CALL gpeq_alloc + CALL idcon_build(egnum,xspmn) + + CALL gpeq_interp_singsurf(fsp_sol,spot,nspot) + + IF (vsbrzphi_flag) ALLOCATE(singbno_mn(mpert,msing)) + + ! minor radius defined using toroidal flux. Used for threshold + CALL spline_int(sq) + qintb = sq%fsi(mpsi, 4) + psitor(:) = sq%fsi(:, 4) / qintb ! normalized toroidal flux + rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen + CALL spline_alloc(sr,mpsi,1) + sr%xs = sq%xs + sr%fs(:, 1) = rhotor(:) + CALL spline_fit(sr,"extrap") + +c----------------------------------------------------------------------- +c HERE I NEED TO READ IN STRIDE NETCDF ?? +c----------------------------------------------------------------------- + IF growthrate_flag THEN + outer_delta_arr = 0.0 + ! TBD + END IF +c----------------------------------------------------------------------- +c loop across singular surfaces, evaluate spline quantities. +c----------------------------------------------------------------------- + ! j_c is j_c/(chi1*sq%f(4)) + DO ising=1,msing + resnum(ising)=NINT(singtype(ising)%q*nn)-mlow+1 + respsi=singtype(ising)%psifac + CALL spline_eval(sq,respsi,1) + +c----------------------------------------------------------------------- +c prepare layer analysis. +c----------------------------------------------------------------------- + resm = mfac(resnum(ising)) + CALL spline_eval(sr,respsi,1) + CALL spline_eval(kin,respsi,1) +c----------------------------------------------------------------------- +c SLAYER inputs for sing surface +c----------------------------------------------------------------------- + omega_i=-twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) + $ -twopi*kin%f1(3)/(e*zi*chi1) + omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) + $ +twopi*kin%f1(4)/(e*chi1) + + ! Here's where I'm getting these from + !CALL gpec_slayer(kin%f(2),kin%f(4)/e,kin%f(1),kin%f(3)/e, + !$ kin%f(5),kin%f(9),omega_e,omega_i,sq%f(4),sq%f1(4), + !$ bt0,sr%f1(1),ro,mi,slayer_inpr,resm,nn,ascii_flag, + !$ delta_s,psi0,jxb,omega_sol,br_th) + + n_e = kin%f(2) + t_e = kin%f(4)/e + n_i = kin%f(1) + t_i = kin%f(3)/e + zeff = kin%f(5) + omega = kin%f(9) + qval = sq%f(4) + sval = sq%f1(4) + bt = bt0 + rs = sr%f1(1) + R0 = ro + mu_i = mi + inpr = slayer_inpr + mms = resm + nns = nn + + mrs = real(mms,4) + nrs = real(nns,4) + + ! String representations of the m and n mode numbers + !IF (nns<10) THEN + ! WRITE(UNIT=sn,FMT='(I1)') nns + ! sn=ADJUSTL(sn) + !ELSE + ! WRITE(UNIT=sn,FMT='(I2)') nns + !ENDIF + !IF (mms<10) THEN + ! WRITE(UNIT=sm,FMT='(I1)') mms + ! sm=ADJUSTL(sm) + !ELSEIF (mms<100) THEN + ! WRITE(UNIT=sm,FMT='(I2)') mms + ! sm=ADJUSTL(sm) + !ELSE + ! WRITE(UNIT=sm,FMT='(I3)') mms + !ENDIF + + inpe=0.0 ! Waybright added this + + tau= t_i/t_e ! ratio of ion to electron temperature + tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. + eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + rho=(mu_i*m_p)*n_e ! mass density + + b_l=(nrs/mrs)*nrs*sval*bt/R0 ! characteristic magnetic field + v_a=b_l/(mu0*rho)**0.5 ! alfven velocity + rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. + + tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! alfven time across surface + tau_r=mu0*rs**2.0/eta ! resistive time scale + tau_v=tau_r/inpr ! rho*rs**2.0/visc ! viscous time scale + + ! this one must be anomalous. calculated back from pr. + visc= rho*rs**2.0/tau_v + + lu=tau_r/tau_h ! Lundquist number + + Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole + + ! note Q depends on Qconv even if omega is fixed. + Q=Qconv*omega + Q_e=-Qconv*omega_e + Q_i=-Qconv*omega_i + + ! This is the most critical parameter + ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. + + lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 + c_beta=(lbeta/(1.0+lbeta))**0.5 + + delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes + + qvals(ising) = qval + inQ_arr(ising)=Q + inQ_e_arr(ising)=Q_e + inQ_i_arr(ising)=Q_i + inc_beta_arr(ising)=c_beta + inds_arr(ising)=ds + intau_arr(ising)=tau + Q0_arr(ising)=Q + inpr_arr(ising) = inpr + inpe_arr(ising) = 0.0 !!! TEMPORARY? + omegas_arr(ising) = omega + outer_delta_arr(ising) = outer_layer_delta + + ENDDO + CALL spline_dealloc(sr) + CALL cspline_dealloc(fsp_sol) + CALL gpeq_dealloc +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + IF(timeit) CALL gpec_timer(2) + RETURN + END SUBROUTINE build_inputs + + END MODULE layerinputs_mod \ No newline at end of file diff --git a/slayer/makefile b/slayer/makefile index 50e86ba91..aace6bf9f 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -18,6 +18,7 @@ LIBS = \ OBJS = \ sglobal.o \ params.o \ + layerinputs.o \ delta.o \ gslayer.o \ slayer.o @@ -44,7 +45,7 @@ slayer: $(OBJS) params.o : sglobal.o delta.o : sglobal.o -gslayer.o : sglobal.o delta.o +gslayer.o : sglobal.o layerinputs.o delta.o slayer.o : sglobal.o params.o delta.o ../pentrc/grid.mod ../equil/spline_mod.mod clean: diff --git a/slayer/slayer.f b/slayer/slayer.f index 315b963de..1f5e7d42b 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -45,9 +45,13 @@ PROGRAM slayer INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,qvals,svals, + $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes, $ inQs_log + REAL(r8), DIMENSION(:), ALLOCATABLE:: qvals,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, + $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: inQ_arr,outer_delta_arr REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, $ inQs_left,inQs_right @@ -104,8 +108,8 @@ PROGRAM slayer inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=200 ! resolution for stab. scan along Re(Q) axis - ImQ_num=100 ! resolution for stab. scan along Im(Q) axis + ReQ_num=100 ! resolution for stab. scan along Re(Q) axis + ImQ_num=200 ! resolution for stab. scan along Im(Q) axis in_unit=1 out_unit=2 out2_unit=3 @@ -273,29 +277,43 @@ PROGRAM slayer ! Approximate data for q=2 and q=3, for testing only ! These are of the form needed to output from ! upcoming build_inputs.f script, interfacing with equil.f - mms = (/2, 2 /) - nns = (/1, 1 /) - prs = (/0.1, 0.005 /) - pes = (/0.0, 0.0 /) - n_es = (/7.765e+17, 9.999e+16 /) - t_es = (/26.0, 2.0 /) - t_is = (/26.0, 2.0 /) - omegas = (/54530.0, 54530.0 /) - l_ns = (/0.2, 0.2 /) - l_ts = (/0.1, 0.1 /) + ! Kinetic inputs (old) + !mms = (/2, 2 /) + !nns = (/1, 1 /) + !prs = (/0.1, 0.005 /) + !pes = (/0.0, 0.0 /) + !n_es = (/7.765e+17, 9.999e+16 /) + !t_es = (/26.0, 2.0 /) + !t_is = (/26.0, 2.0 /) + !l_ns = (/0.2, 0.2 /) + !l_ts = (/0.1, 0.1 /) + !svals = (/729.5, 500.5 /) + !bts = (/1.0, 1.0 /) + !rss = (/0.16, 0.2 /) + !R0s = (/2.0, 2.0 /) + !mu_is = (/2.0, 2.0 /) + !zeffs = (/2.0, 2.0 /) qvals = (/2.0, 3.0 /) - svals = (/729.5, 500.5 /) - bts = (/1.0, 1.0 /) - rss = (/0.16, 0.2 /) - R0s = (/2.0, 2.0 /) - mu_is = (/2.0, 2.0 /) - zeffs = (/2.0, 2.0 /) - outer_deltas = (/(27.15,0.1), (20.15,0.1) /) - - CALL gamma_match(n_k,mms,nns,n_es,t_es,t_is,omegas,prs,pes, - $ l_ns,l_ts,qvals,svals,bts,rss,R0s,mu_is, - $ zeffs,outer_deltas, - $ ReQ_num,ImQ_num,growthrates,growthrate_err) + inQ_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) + inQ_e_arr = (/ 1.9127E-005, 4.6109E-007 /) + inQ_i_arr = (/ -1.9127E-005, -4.6109E-007 /) + inc_beta_arr= (/ 3.6809E-003, 3.6635E-004 /) + inds_arr = (/ 1.4394, 0.1276 /) + intau_arr = (/ 1.0, 1.0 /) + inQ0_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) + !eta_s = (/ 6.6907E-006, 3.1361E-004 /) + !S_s = (/ 30696266.0, 1956419.0 /) + inpr_arr = (/0.1, 0.005 /) + inpe_arr = (/0.0, 0.0 /) + omegas_arr = (/54530.0, 54530.0 /) + outer_delta_arr = (/(27.15,0.1), (20.15,0.1) /) + + CALL gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, + $ inc_beta_arr,inds_arr,intau_arr, + $ inQ0_arr,inpr_arr,inpe_arr, + $ outer_delta_arr,omegas_arr, + $ ReQ_num,ImQ_num,growthrates,growthrate_err) + WRITE(*,*)"growthrates=",growthrates WRITE(*,*)"growthrate error=",growthrate_err From 1d808011725a18b2bcc3d5dcc13198b960c9519c Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 27 Jun 2024 11:50:55 -0700 Subject: [PATCH 005/305] WIP: runs, but netcdf errors --- slayer/gslayer.f | 27 ++-- slayer/layerinputs.f | 327 +++++++++++++++++++++++++++++------------ slayer/makefile | 13 +- slayer/slayer.f | 96 +++++++----- stride/stride_netcdf.f | 13 +- 5 files changed, 329 insertions(+), 147 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 2182c57c6..d488a333d 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -372,7 +372,7 @@ SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, ! Inputs REAL(r8),INTENT(IN) :: qval,inQ_e,inQ_i,inc_beta,inds, $ intau,inQ0,inpr,inpe - COMPLEX(r8), INTENT(IN) :: inQ + REAL(r8), INTENT(IN) :: inQ ! REAL??? INTEGER, INTENT(IN) :: ReQ_num,ImQ_num ! Outputs @@ -404,9 +404,9 @@ SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, inQ_min=-3.0 ! min growth rate in scan, OPEN TO USER? ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? - inQs_left = powspace(REAL(inQ)-0.2,REAL(inQ),1, ! omega-0.2 + inQs_left = powspace(inQ-0.2,inQ,1, ! omega-0.2 $ 2+ReQ_num/2,"upper") - inQs_right = powspace(REAL(inQ),REAL(inQ)+0.2,1, ! omega+0.2 + inQs_right = powspace(inQ,inQ+0.2,1, ! omega+0.2 $ 2+ReQ_num/2,"lower") inQs_log = (/inQs_left(1,1:2+ReQ_num/2), $ inQs_right(1,2:1+ReQ_num/2)/) @@ -450,18 +450,19 @@ END SUBROUTINE gamma_stability_scan c Subprogram 5. gamma_match c Loop stability scans and gamma matches across k rational surfaces c----------------------------------------------------------------------- - SUBROUTINE gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, + SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, - $ inpr_arr,inpe_arr,outer_delta_arr,omegas_arr, + $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr, $ ReQ_num,ImQ_num,growthrates,growthrate_err) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - REAL(r8), DIMENSION(:), INTENT(IN) :: qvals,inQ_e_arr, + REAL(r8), DIMENSION(:), INTENT(IN) :: qval_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, - $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr - COMPLEX(r8), DIMENSION(:), INTENT(IN) :: inQ_arr,outer_delta_arr + $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, + $ inQ_arr + REAL(r8), DIMENSION(:), INTENT(IN) :: outer_delta_arr INTEGER, INTENT(IN) :: ReQ_num,ImQ_num ! Outputs REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: @@ -477,7 +478,7 @@ SUBROUTINE gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, REAL(r8), DIMENSION(3) :: growthrate_range ! (-gamma, +gamma ) REAL(r8) :: layer_Q, ImQ_gamma - n_k = SIZE(qvals) + n_k = SIZE(qval_arr) ! Allocate growthrates arrays ALLOCATE(growthrates(n_k)) ALLOCATE(growthrate_err(n_k)) @@ -491,12 +492,12 @@ SUBROUTINE gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, DO k=1,n_k-1 WRITE(*,*)"layer #: ",k ! Run stability scan - CALL gamma_stability_scan(qvals(k),inQ_arr(k),inQ_e_arr(k), + CALL gamma_stability_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs) - layer_Q = REAL(inQ_arr(k)) + layer_Q = inQ_arr(k) ! REAL??? ! Hardcoding rudimentary +/- 10% omega_ExB errorbars Q_range = (/0.9*layer_Q, 1.1*layer_Q, layer_Q/) @@ -510,10 +511,10 @@ SUBROUTINE gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, $ Q_range(w), inQs_log, slice) ! Match delta to delta prime to obtain growth rate CALL gamma_from_delta_match(slice, iinQs, - $ REAL(outer_delta_arr(k)), + $ outer_delta_arr(k), $ ImQ_gamma) CALL gamma_from_delta_match(slice, iinQs, - $ REAL(outer_delta_arr(k)), + $ outer_delta_arr(k), $ ImQ_gamma) ! Qconv = Q / omega_ExB ! gamma = Im(Q) / Qconv diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 1642104ea..6a9fac93f 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -1,11 +1,16 @@ MODULE layerinputs_mod - USE inputs, ONLY : kin - USE pentrc_interface, ONLY : zi,mi,wefac,wpfac,initialize_pentrc - USE read_eq_mode, ONLY : read_eq_efit - USE direct_mod, ONLY : direct_run - !resm = mfac(resnum(ising)) - !CALL spline_eval(kin,respsi,1) + USE inputs, ONLY : read_kin,kin,chi1 + !USE params + !USE dcon_interface_mod, ONLY : respsi + !USE direct_mod, ONLY : direct_run + USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, + $ spline_dealloc + USE sglobal_mod, ONLY: m_p, chag, lnLamb, + $ Q_e,Q_i,pr,pe,c_beta,ds,tau,mu0,r8, ! NOT out_unit + $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q + USE netcdf + ! STILL NEED ro AND bt0, GLOBAL GPEC VARIABLES @@ -13,115 +18,244 @@ MODULE layerinputs_mod CONTAINS c----------------------------------------------------------------------- +c subprogram 1. check. +c Check status of netcdf file. +c----------------------------------------------------------------------- + SUBROUTINE check(stat) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT (IN) :: stat +c----------------------------------------------------------------------- +c stop if it is an error. +c----------------------------------------------------------------------- + IF(stat /= nf90_noerr) THEN + PRINT *, TRIM(nf90_strerror(stat)) + !STOP "ERROR: failed to write/read netcdf file" + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE check +c----------------------------------------------------------------------- +c Read and build equilibrium inputs +c----------------------------------------------------------------------- + SUBROUTINE read_stride_netcdf_diagonal(ncfile, r_dim, + $ dp_diagonal, q_rational, psi_n_rational, shear, + $ r0, bt0) + + !USE netcdf ! NetCDF module for Fortran + !USE stride_netcdf_mod ! For the 'check' subroutine (error handling) + + ! Input/Output Arguments + CHARACTER(128), INTENT(IN) :: ncfile + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal + REAL(r8), DIMENSION(:), INTENT(OUT) :: q_rational, + $ psi_n_rational, shear + REAL(r8), INTENT(OUT) :: r0, bt0 + + ! Internal Variables + INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, + $ dp_id, qr_id, pr_id, shear_id, r0_id, bt0_id ! Explicit kind for NetCDF variables + INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime + INTEGER :: i + + ! Open the NetCDF file + stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) + WRITE(*,*)"ncfile=",ncfile + CALL check(stat) ! Error handling + + ! Get Dimension Information + stat = nf90_inq_varid(ncid, "r", r_dim_id) + CALL check(stat) + stat = nf90_get_var(ncid, r_dim_id, r_dim) ! Use len= for dimension + CALL check(stat) + + ! Allocate Arrays (based on dimension) + ALLOCATE(dp_diagonal(r_dim)) + !ALLOCATE(delta_prime(r_dim, r_dim)) + + ! Get Variable IDs + stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "q_rational", qr_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "shear", shear_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "bt0", bt0_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "r0", r0_id) + CALL check(stat) + + ! Read Data from NetCDF File + ! Set up start and count for reading only the diagonal + start(1) = 1 + count(1) = 1 + + ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. + stat = nf90_get_var(ncid, dp_id, delta_prime) + CALL check(stat) + ! Read 1D variables + stat = nf90_get_var(ncid, qr_id, q_rational) + CALL check(stat) + stat = nf90_get_var(ncid, pr_id, psi_n_rational) + CALL check(stat) + stat = nf90_get_var(ncid, shear_id, shear) + CALL check(stat) + stat = nf90_get_var(ncid, bt0_id, bt0) + CALL check(stat) + stat = nf90_get_var(ncid, r0_id, r0) + CALL check(stat) + + ! Extract Diagonal, with 3rd index signifying REAL part + DO i = 1, r_dim + dp_diagonal(i) = REAL(delta_prime(i, i, 1)) + END DO + + ! Clean Up + DEALLOCATE(delta_prime) + stat = nf90_close(ncid) + CALL check(stat) + + END SUBROUTINE read_stride_netcdf_diagonal +c----------------------------------------------------------------------- c subprogram 1. build_inputs. c compute c----------------------------------------------------------------------- - SUBROUTINE build_inputs(egnum,xspmn,spot,nspot, - $ growthrate_flag, - $ slayer_inpr) + SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, + $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, + $ inds_arr,intau_arr,Q0_arr,inpr_arr,inpe_arr, + $ omegas_arr,outer_delta_arr) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- + CHARACTER(128), INTENT(IN) :: ncfile LOGICAL, INTENT(IN) :: growthrate_flag - INTEGER, INTENT(IN) :: egnum,nspot - REAL(r8), INTENT(IN) :: spot, slayer_inpr + REAL(r8), INTENT(IN) ::slayer_inpr - !INTEGER :: i_id,q_id,m_id,p_id,c_id,w_id,k_id,n_id,d_id,a_id, - !$ pp_id,cp_id,wp_id,np_id,dp_id,wc_id,bc_id, - !$ astat + REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf, + $ ising - REAL(r8) :: respsi,lpsi,rpsi,shear,hdist,sbnosurf - - INTEGER, DIMENSION(msing) :: resnum + INTEGER :: zi, zimp, mi, mimp, msing + REAL(r8) :: nfac,tfac,wefac,wpfac,e,twopi TYPE(spline_type) :: spl - INTEGER :: resm TYPE(spline_type) :: sr - REAL(r8), DIMENSION(msing) :: inQ_arr,inQ_e_arr,inQ_i_arr, - $ inc_beta_arr,inds_arr,intau_arr,Q0_arr, - $ outer_delta_arr + INTEGER :: mms,nns,mrs,nrs -c----------------------------------------------------------------------- -c Find # of rational surfaces msing. -c----------------------------------------------------------------------- - CHARACTER(*) :: vmat_filename - INTEGER :: ising + REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, + $ qval,sval,bt,rs,zeff,inpe,r0,bt0 + REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, + $ rho,tau_v,inpr,Qconv,lbeta + + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr, + $ inQ_arr,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, + $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr - OPEN(UNIT=debug_unit,FILE=TRIM(vmat_filename),STATUS="OLD", - $ FORM="UNFORMATTED") - READ(debug_unit)mpert,msing + REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, + $ psi_n_rational, shear + INTEGER :: r_dim c----------------------------------------------------------------------- +c Read in STRIDE netcdf +c----------------------------------------------------------------------- + character(512) :: + $ kinetic_file = '/fusion/projects/codes/gpec/GPEC-1.5/ + $ docs/examples/a10_ideal_example/a10_prof1.txt' + !REAL(r8), DIMENSION(:), ALLOCATABLE :: q_rational, q_rational_coords + !REAL(r8), DIMENSION(:), ALLOCATABLE :: Delta_prime, Delta_prime_coords + WRITE(*,*)"ncfile=",ncfile + CALL read_stride_netcdf_diagonal(ncfile, + $ r_dim, dp_diagonal, q_rational, psi_n_rational, + $ shear, r0, bt0) + + !CALL READ_Q_RATIONAL_AND_DELTA_PRIME_FROM_NETCDF( + !& filename, q_rational, q_rational_coords, + !& Delta_prime, Delta_prime_coords) + + msing = r_dim + + ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), + $ inQ_i_arr(msing), + $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), + $ Q0_arr(msing),outer_delta_arr(msing)) + !DEALLOCATE(q_rational, q_rational_coords, Delta_prime, Delta_prime_coords) +c----------------------------------------------------------------------- c Read and build equilibrium inputs c----------------------------------------------------------------------- - CALL fourfit_action_matrix - ! call the automatic reading and distributing of inputs - CALL initialize_pentrc(op_kin=.FALSE.,op_deq=.FALSE., - $ op_peq=.FALSE.) - ! manually set the pentrc equilibrium description - CALL set_eq(eqfun,sq,rzphi,smats,tmats,xmats,ymats,zmats, - $ twopi*psio,ro,nn,jac_type,mlow,mhigh,mpert,mthvac) - ! manually set the kinetic profiles - CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, - $ tfac,wefac,wpfac,indebug) - ! manually set the perturbed equilibrium displacements - ! use false flat xi and xi' for equal weighting - ALLOCATE(psitmp(sq%mx+1),mtmp(mpert),xtmp(sq%mx+1,mpert)) - psitmp(:) = sq%xs(0:) - mtmp = (/(m,m=mlow,mhigh)/) - xtmp = 1e-4 - CALL set_peq(psitmp,mtmp,xtmp,xtmp,xtmp,.false.,tdebug) - DEALLOCATE(xtmp,mtmp,psitmp) - IF(verbose) WRITE(*,*)"Computing Kinetic Matrices" - CALL fourfit_kinetic_matrix(kingridtype,out_fund) - - CALL sing_scan - DO ising=1,msing - CALL resist_eval(sing(ising)) - ENDDO - - CALL ksing_find - - - CALL gpeq_alloc - CALL idcon_build(egnum,xspmn) - - CALL gpeq_interp_singsurf(fsp_sol,spot,nspot) - - IF (vsbrzphi_flag) ALLOCATE(singbno_mn(mpert,msing)) - - ! minor radius defined using toroidal flux. Used for threshold - CALL spline_int(sq) - qintb = sq%fsi(mpsi, 4) - psitor(:) = sq%fsi(:, 4) / qintb ! normalized toroidal flux - rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen - CALL spline_alloc(sr,mpsi,1) - sr%xs = sq%xs - sr%fs(:, 1) = rhotor(:) - CALL spline_fit(sr,"extrap") + !======================================================================= + !subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) + !----------------------------------------------------------------------- + !*DESCRIPTION: + ! Read ascii file containing table of kinetic profiles ni, ne, ti, + ! te, and omegaE, then form kin spline containing some additional + ! information (krook nui,nue). + ! + ! Assumes table consists of 6 columns: psi_n, n_i(m^-3), n_e(m^-3), + ! T_i(eV), T_e(eV), omega_E(rad/s). File can (nearly) arbitrary + ! header and/or footer, with the exception that no lines start with + ! a number. + ! + !*ARGUMENTS: + ! file : character(256) (in) + ! File path. + ! zi : integer + ! Ion charge in fundamental units + ! zimp : integer + ! Impurity ion charge in fundamental units + ! mi : integer + ! Ion mass in fundamental units (mass proton) + ! mimp : integer + ! Impurity ion mass in fundamental units + ! wefac : real + ! Direct multiplier for omegaE profiles + ! wpfac : real + ! Scaling of rotation profile, done via manipulation of omegaE + ! write_log : bool + ! Writes kinetic spline to log file + ! + !----------------------------------------------------------------------- c----------------------------------------------------------------------- -c HERE I NEED TO READ IN STRIDE NETCDF ?? +c set up kin c----------------------------------------------------------------------- - IF growthrate_flag THEN - outer_delta_arr = 0.0 - ! TBD - END IF + ! manually set the kinetic profiles + zi = 1 + zimp = 1 + mi = 1 + mimp = 1 + nfac = 1.0 + tfac = 1.0 + wefac = 1.0 + wpfac = 1.0 + e=1.6021917e-19 + twopi = 6.28318530718 + + CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, + $ tfac,wefac,wpfac,.false.) + ! manually set the perturbed equilibrium displacements + ! use false flat xi and xi' for equal weighting + c----------------------------------------------------------------------- c loop across singular surfaces, evaluate spline quantities. c----------------------------------------------------------------------- ! j_c is j_c/(chi1*sq%f(4)) DO ising=1,msing - resnum(ising)=NINT(singtype(ising)%q*nn)-mlow+1 - respsi=singtype(ising)%psifac - CALL spline_eval(sq,respsi,1) - + !resnum(ising)=NINT(singtype(ising)%q*nn)-mlow+1 + !respsi=singtype(ising)%psifac + !CALL spline_eval(sq,respsi,1) + respsi = psi_n_rational(ising) c----------------------------------------------------------------------- c prepare layer analysis. c----------------------------------------------------------------------- - resm = mfac(resnum(ising)) + !resm = mfac(resnum(ising)) CALL spline_eval(sr,respsi,1) CALL spline_eval(kin,respsi,1) c----------------------------------------------------------------------- @@ -144,18 +278,17 @@ SUBROUTINE build_inputs(egnum,xspmn,spot,nspot, t_i = kin%f(3)/e zeff = kin%f(5) omega = kin%f(9) - qval = sq%f(4) - sval = sq%f1(4) + qval = q_rational(ising)!sq%f(4) + sval = shear(ising) bt = bt0 rs = sr%f1(1) - R0 = ro + R0 = r0 mu_i = mi inpr = slayer_inpr - mms = resm - nns = nn - - mrs = real(mms,4) - nrs = real(nns,4) + !mms = resm + !nns = nn + mrs = 2.0 ! FROM NAMELIST ???? real(mms,4) + nrs = 1.0 ! FROM NAMELIST ???? real(nns,4) ! String representations of the m and n mode numbers !IF (nns<10) THEN @@ -209,7 +342,7 @@ SUBROUTINE build_inputs(egnum,xspmn,spot,nspot, delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes - qvals(ising) = qval + qval_arr(ising) = qval inQ_arr(ising)=Q inQ_e_arr(ising)=Q_e inQ_i_arr(ising)=Q_i @@ -220,16 +353,16 @@ SUBROUTINE build_inputs(egnum,xspmn,spot,nspot, inpr_arr(ising) = inpr inpe_arr(ising) = 0.0 !!! TEMPORARY? omegas_arr(ising) = omega - outer_delta_arr(ising) = outer_layer_delta + outer_delta_arr(ising) = dp_diagonal(ising) ENDDO CALL spline_dealloc(sr) - CALL cspline_dealloc(fsp_sol) - CALL gpeq_dealloc + !CALL cspline_dealloc(fsp_sol) + !CALL gpeq_dealloc c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- - IF(timeit) CALL gpec_timer(2) + RETURN END SUBROUTINE build_inputs diff --git a/slayer/makefile b/slayer/makefile index aace6bf9f..b4c676d97 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -1,7 +1,7 @@ include ../install/DEFAULTS.inc -IFLAGS = -I../equil -I../pentrc -I$(MATHINC) -I$(NETCDFINC) +IFLAGS = -I../equil -I../harvest -I../pentrc -I$(MATHINC) -I$(NETCDFINC) F90 = $(FC) $(FFLAGS) $(OMPFLAG) $(IFLAGS) export FFLAGS @@ -13,7 +13,8 @@ LIBDIR = ../lib LIBS = \ -llsode \ -lpentrc \ - -lequil + -lequil \ + -lharvest OBJS = \ sglobal.o \ @@ -23,7 +24,13 @@ OBJS = \ gslayer.o \ slayer.o -all: lsode equil pentrc slayer +all: equil harvest lsode pentrc slayer + +harvest: + cd ../; git submodule init; git submodule update + cd ../harvest; make FC=$(FC) CC=$(CC) GACODE_ROOT= -f Makefile libharvest.a + mkdir -p ../lib + cp -f ../harvest/libharvest.a ../lib/ equil: cd ../equil; make diff --git a/slayer/slayer.f b/slayer/slayer.f index 1f5e7d42b..5dccc3afe 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -19,10 +19,11 @@ PROGRAM slayer ! FOR TESTING: USE gslayer_mod + USE layerinputs_mod IMPLICIT NONE - CHARACTER(128) :: infile + CHARACTER(128) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num,n_k INTEGER, DIMENSION(1) :: index @@ -31,7 +32,7 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,gamma_match_flag !FOR TESTING + $ params_check,growthrates_flag !FOR TESTING REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff @@ -39,22 +40,27 @@ PROGRAM slayer REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th COMPLEX(r8) :: delta,delta_n_p - REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max,kpower, + REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, + $ kpower, $ Qratio INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals, + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl, + $ bal, + $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes, $ inQs_log - REAL(r8), DIMENSION(:), ALLOCATABLE:: qvals,inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, - $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: inQ_arr,outer_delta_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: qval_arr,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr, + $ intau_arr,inQ0_arr,inpr_arr, + $ inpe_arr,omegas_arr,inQ_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: + $ outer_delta_arr REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, $ inQs_left,inQs_right + REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas @@ -63,7 +69,7 @@ PROGRAM slayer $ growthrate_err NAMELIST/slayer_input/params_flag,input_flag,infile, - $ mm,nn,n_e,t_e,t_i,omega,l_n,l_t, + $ ncfile,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, $ inQ_i,inpr,inpe,inc_beta,inds,intau,inlu,Q0,delta_n_p NAMELIST/slayer_control/inum,jnum,knum,QPscan_flag,QPscan2_flag, @@ -71,9 +77,9 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag + $ stability_flag,growthrates_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, - $ params_check,bal_flag,gamma_match_flag + $ params_check,bal_flag c----------------------------------------------------------------------- c set initial values. c----------------------------------------------------------------------- @@ -131,6 +137,8 @@ PROGRAM slayer params_flag=.TRUE. input_flag=.FALSE. infile="input_params.dat" + ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ + $stride_output_n1.nc" verbose=.TRUE. ascii_flag=.TRUE. bin_flag=.TRUE. @@ -140,6 +148,7 @@ PROGRAM slayer params_check=.FALSE. bal_flag=.FALSE. stability_flag=.FALSE. + growthrates_flag=.FALSE. c----------------------------------------------------------------------- c read slayer.in. c----------------------------------------------------------------------- @@ -267,13 +276,13 @@ PROGRAM slayer c----------------------------------------------------------------------- c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- - IF (gamma_match_flag) THEN - n_k=2 - ALLOCATE(mms(n_k),nns(n_k),prs(n_k),pes(n_k), - $ n_es(n_k),t_es(n_k),t_is(n_k),omegas(n_k), - $ l_ns(n_k),l_ts(n_k),qvals(n_k),svals(n_k), - $ bts(n_k),rss(n_k),R0s(n_k),mu_is(n_k),zeffs(n_k), - $ outer_deltas(n_k)) + IF (growthrates_flag) THEN + !n_k=2 + ! ALLOCATE(mms(n_k),nns(n_k),prs(n_k),pes(n_k), + !$ n_es(n_k),t_es(n_k),t_is(n_k),omegas(n_k), + !$ l_ns(n_k),l_ts(n_k),qvals(n_k),svals(n_k), + !$ bts(n_k),rss(n_k),R0s(n_k),mu_is(n_k),zeffs(n_k), + !$ outer_deltas(n_k)) ! Approximate data for q=2 and q=3, for testing only ! These are of the form needed to output from ! upcoming build_inputs.f script, interfacing with equil.f @@ -293,25 +302,46 @@ PROGRAM slayer !R0s = (/2.0, 2.0 /) !mu_is = (/2.0, 2.0 /) !zeffs = (/2.0, 2.0 /) - qvals = (/2.0, 3.0 /) - inQ_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) - inQ_e_arr = (/ 1.9127E-005, 4.6109E-007 /) - inQ_i_arr = (/ -1.9127E-005, -4.6109E-007 /) - inc_beta_arr= (/ 3.6809E-003, 3.6635E-004 /) - inds_arr = (/ 1.4394, 0.1276 /) - intau_arr = (/ 1.0, 1.0 /) - inQ0_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) + + + !q_arr = (/2.0, 3.0 /) + !inQ_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) + !inQ_e_arr = (/ 1.9127E-005, 4.6109E-007 /) + !inQ_i_arr = (/ -1.9127E-005, -4.6109E-007 /) + !inc_beta_arr= (/ 3.6809E-003, 3.6635E-004 /) + !inds_arr = (/ 1.4394, 0.1276 /) + !intau_arr = (/ 1.0, 1.0 /) + !inQ0_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) !eta_s = (/ 6.6907E-006, 3.1361E-004 /) !S_s = (/ 30696266.0, 1956419.0 /) - inpr_arr = (/0.1, 0.005 /) - inpe_arr = (/0.0, 0.0 /) - omegas_arr = (/54530.0, 54530.0 /) - outer_delta_arr = (/(27.15,0.1), (20.15,0.1) /) + !inpr_arr = (/0.1, 0.005 /) + !inpe_arr = (/0.0, 0.0 /) + !omegas_arr = (/54530.0, 54530.0 /) + !outer_delta_arr = (/(27.15,0.1), (20.15,0.1) /) + + !CHARACTER(len=256) :: filename = "stride_output_n1.nc" + !REAL(r8), DIMENSION(:), ALLOCATABLE :: q_rational, q_rational_coords + !REAL(r8), DIMENSION(:), ALLOCATABLE :: Delta_prime, Delta_prime_coords + + + + ! WRITE(*,*)"growthrates=",q_rational + + !CALL read_stride_netcdf(filename, q_rational, q_rational_coords, + !$ Delta_prime, Delta_prime_coords) + CALL build_inputs(ncfile,inpr, + $ growthrates_flag,qval_arr, + $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, + $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, + $ omegas_arr,outer_delta_arr) + + WRITE(*,*)"outer_delta_arr=",outer_delta_arr + WRITE(*,*)"omegas_arr=",omegas_arr - CALL gamma_match(qvals,inQ_arr,inQ_e_arr,inQ_i_arr, + CALL gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr, $ inQ0_arr,inpr_arr,inpe_arr, - $ outer_delta_arr,omegas_arr, + $ omegas_arr,outer_delta_arr, $ ReQ_num,ImQ_num,growthrates,growthrate_err) WRITE(*,*)"growthrates=",growthrates diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 08199da29..4d3c26e9c 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -59,7 +59,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ f_id, q_id, dv_id, mu_id, di_id, dr_id, ca_id, $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, - $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id + $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, + $ bt0_id, r0_id, shear_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile @@ -162,8 +163,14 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid,"r_prime",nf90_int,rp_dim,rp_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ r_dim,pr_id) ) + CALL check( nf90_def_var(ncid,"bt0",nf90_double, + $ r_dim/r_dim,bt0_id) ) + CALL check( nf90_def_var(ncid,"r0",nf90_double, + $ r_dim/r_dim,r0_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) + CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, + $ shear_id) ) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" @@ -217,8 +224,12 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,pr_id, (/(sing(i)%psifac, $ i=1,msing)/)) ) + CALL check( nf90_put_var(ncid,bt0_id, (/ bt0 /)) ) + CALL check( nf90_put_var(ncid,r0_id, (/ ro /)) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) + CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, + $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? ENDIF IF(debug_flag) PRINT *," - Putting profile variables in netcdf" From c873bd6b35535e99fcf3db7f40f356a26bb924ba Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 4 Jul 2024 11:29:57 -0700 Subject: [PATCH 006/305] WIP: building growthrate outputs --- slayer/gslayer.f | 38 +++-- slayer/layerinputs.f | 312 +++++++++++++++++++++++++++++++++-------- slayer/makefile | 3 +- slayer/slayer.f | 27 +++- slayer/slayer_netcdf.f | 188 +++++++++++++++++++++++++ stride/stride_netcdf.f | 9 +- 6 files changed, 498 insertions(+), 79 deletions(-) create mode 100644 slayer/slayer_netcdf.f diff --git a/slayer/gslayer.f b/slayer/gslayer.f index d488a333d..1ed32a727 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -10,6 +10,10 @@ MODULE gslayer_mod USE params_mod + USE layerinputs_mod + + USE slayer_netcdf_mod + USE grid, ONLY : powspace,linspace IMPLICIT NONE @@ -186,6 +190,7 @@ SUBROUTINE interpolate_slice_at_Q(deltas, Q, inQs_log, INTEGER :: NROWS, NCOLS, i_lower, i_upper, j REAL(r8) :: Q_lower,Q_upper,lower_slice,upper_slice + slice = 0.0 ! Extract array dimensions NROWS = SIZE(deltas, 1) NCOLS = SIZE(deltas, 2) @@ -404,15 +409,15 @@ SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, inQ_min=-3.0 ! min growth rate in scan, OPEN TO USER? ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? - inQs_left = powspace(inQ-0.2,inQ,1, ! omega-0.2 + inQs_left = powspace(inQ-0.5,inQ,1, ! omega-0.5 $ 2+ReQ_num/2,"upper") - inQs_right = powspace(inQ,inQ+0.2,1, ! omega+0.2 + inQs_right = powspace(inQ,inQ+0.5,1, ! omega+0.5 $ 2+ReQ_num/2,"lower") inQs_log = (/inQs_left(1,1:2+ReQ_num/2), $ inQs_right(1,2:1+ReQ_num/2)/) !WRITE(*,*)"inQs_log=",inQs_log - DO i=0,ReQ_num+3 + DO i=0,ReQ_num+1 DO j=0,ImQ_num ! Getting rid of "inQs" weirdly broke things?? inQs(i)=inQ_min+(REAL(i)/ReQ_num)*(inQ_max-inQ_min) @@ -433,7 +438,7 @@ SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, $ TRIM(x1)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))'),"RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" - DO i=0,ReQ_num + DO i=0,ReQ_num+1 DO j=0,ImQ_num WRITE(out_unit,'(1x,4(es17.8e3))') $ inQs_log(i),iinQs(j), @@ -450,7 +455,8 @@ END SUBROUTINE gamma_stability_scan c Subprogram 5. gamma_match c Loop stability scans and gamma matches across k rational surfaces c----------------------------------------------------------------------- - SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + $ inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr, $ ReQ_num,ImQ_num,growthrates,growthrate_err) @@ -461,7 +467,7 @@ SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, REAL(r8), DIMENSION(:), INTENT(IN) :: qval_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, - $ inQ_arr + $ inQ_arr,psi_n_rational REAL(r8), DIMENSION(:), INTENT(IN) :: outer_delta_arr INTEGER, INTENT(IN) :: ReQ_num,ImQ_num ! Outputs @@ -473,6 +479,8 @@ SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, ! Local variables received from internal subroutines REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log,iinQs COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_slices REAL(r8), DIMENSION(:), ALLOCATABLE :: slice REAL(r8), DIMENSION(3) :: Q_range ! (-Q_err, Q, +Q_err ) REAL(r8), DIMENSION(3) :: growthrate_range ! (-gamma, +gamma ) @@ -482,6 +490,7 @@ SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, ! Allocate growthrates arrays ALLOCATE(growthrates(n_k)) ALLOCATE(growthrate_err(n_k)) + c----------------------------------------------------------------------- c Loop across rational surfaces c----------------------------------------------------------------------- @@ -489,7 +498,7 @@ SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, ! for analysis, then slice out 1D array of growth rates at ! given omega_ExB (Q), then find growth rate corresponding ! to delta-deltaprime match - DO k=1,n_k-1 + DO k=1,n_k WRITE(*,*)"layer #: ",k ! Run stability scan CALL gamma_stability_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), @@ -527,11 +536,24 @@ SUBROUTINE gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, growthrate_err(k) = ABS(growthrate_range(2) - $ growthrate_range(1)) + ALLOCATE(all_RE_deltas(SIZE(inQs_log),SIZE(iinQs),n_k)) + ALLOCATE(all_slices(SIZE(iinQs),n_k)) + + all_RE_deltas(:,:,k) = REAL(deltas) + all_slices(:,k) = slice + DEALLOCATE(slice) ! Free memory after use for each layer ENDDO - DEALLOCATE(deltas,inQs_log,iinQs) + CALL slayer_netcdf_out(n_k,SIZE(inQs_log),SIZE(iinQs),qval_arr, + $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr,psi_n_rational, + $ all_Re_deltas,all_slices) + + DEALLOCATE(deltas) + + WRITE(*,*)"inQs_log len ",SIZE(inQs_log) + WRITE(*,*)"iinQs len ",SIZE(iinQs) RETURN END SUBROUTINE gamma_match diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 6a9fac93f..b8b8825bb 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -1,16 +1,18 @@ MODULE layerinputs_mod - USE inputs, ONLY : read_kin,kin,chi1 + USE inputs, ONLY : read_kin,read_equil,kin,chi1 !USE params - !USE dcon_interface_mod, ONLY : respsi + USE dcon_interface, ONLY : set_geom,geom !USE direct_mod, ONLY : direct_run USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, - $ spline_dealloc + $ spline_dealloc,spline_int,spline_fit USE sglobal_mod, ONLY: m_p, chag, lnLamb, $ Q_e,Q_i,pr,pe,c_beta,ds,tau,mu0,r8, ! NOT out_unit $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q USE netcdf - + USE pentrc_interface, ONLY : pentrc_timer=>timer + !USE equil_mod + !USE equil_out_mod ! STILL NEED ro AND bt0, GLOBAL GPEC VARIABLES @@ -41,9 +43,9 @@ END SUBROUTINE check c----------------------------------------------------------------------- c Read and build equilibrium inputs c----------------------------------------------------------------------- - SUBROUTINE read_stride_netcdf_diagonal(ncfile, r_dim, + SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ dp_diagonal, q_rational, psi_n_rational, shear, - $ r0, bt0) + $ ro,bt0,psio,mpsi) !USE netcdf ! NetCDF module for Fortran !USE stride_netcdf_mod ! For the 'check' subroutine (error handling) @@ -51,53 +53,79 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, r_dim, ! Input/Output Arguments CHARACTER(128), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal - REAL(r8), DIMENSION(:), INTENT(OUT) :: q_rational, + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, $ psi_n_rational, shear - REAL(r8), INTENT(OUT) :: r0, bt0 + REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: ro,bt0,psio, + $ mpsi + INTEGER, INTENT(OUT) :: msing + + REAL(r8), DIMENSION(:), ALLOCATABLE :: msing_arr ! Internal Variables INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, - $ dp_id, qr_id, pr_id, shear_id, r0_id, bt0_id ! Explicit kind for NetCDF variables + $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, + $ msing_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i + INTEGER :: bt0_len,ro_len,psio_len,mpsi_len,msing_len ! Attribute lengths + ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) WRITE(*,*)"ncfile=",ncfile CALL check(stat) ! Error handling - ! Get Dimension Information - stat = nf90_inq_varid(ncid, "r", r_dim_id) + stat = nf90_inquire_attribute(ncid, msing_id, "msing", + $ len = msing_len) + ALLOCATE(msing_arr(msing_len)) + CALL check(stat) - stat = nf90_get_var(ncid, r_dim_id, r_dim) ! Use len= for dimension + stat = nf90_get_att(ncid, msing_id, "msing", msing_arr) CALL check(stat) + msing=INT(msing_arr(1)) + ! Allocate Arrays (based on dimension) - ALLOCATE(dp_diagonal(r_dim)) - !ALLOCATE(delta_prime(r_dim, r_dim)) + ALLOCATE(dp_diagonal(msing),q_rational(msing), + $ psi_n_rational(msing),shear(msing)) + ALLOCATE(delta_prime(msing, msing,2)) ! Get Variable IDs stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) CALL check(stat) + WRITE(*,*)"stat=",stat stat = nf90_inq_varid(ncid, "q_rational", qr_id) CALL check(stat) + WRITE(*,*)"stat=",stat stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) CALL check(stat) + WRITE(*,*)"stat=",stat + stat = nf90_inq_varid(ncid, "shear", shear_id) CALL check(stat) - stat = nf90_inq_varid(ncid, "bt0", bt0_id) + stat = nf90_inquire_attribute(ncid, bt0_id, "bt0", + $ len = bt0_len) + CALL check(stat) + stat = nf90_inquire_attribute(ncid, ro_id, "ro", + $ len = ro_len) CALL check(stat) - stat = nf90_inq_varid(ncid, "r0", r0_id) + stat = nf90_inquire_attribute(ncid, psio_id, "psio", + $ len = psio_len) CALL check(stat) + stat = nf90_inquire_attribute(ncid, mpsi_id, "mpsi", + $ len = mpsi_len) + CALL check(stat) + ALLOCATE(bt0(bt0_len),ro(ro_len),psio(psio_len), + $ mpsi(mpsi_len)) ! Read Data from NetCDF File ! Set up start and count for reading only the diagonal - start(1) = 1 - count(1) = 1 + !start(1) = 1 + !count(1) = 1 ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. - stat = nf90_get_var(ncid, dp_id, delta_prime) + stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) CALL check(stat) ! Read 1D variables stat = nf90_get_var(ncid, qr_id, q_rational) @@ -106,13 +134,17 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, r_dim, CALL check(stat) stat = nf90_get_var(ncid, shear_id, shear) CALL check(stat) - stat = nf90_get_var(ncid, bt0_id, bt0) + stat = nf90_get_att(ncid, bt0_id, "bt0", bt0) + CALL check(stat) + stat = nf90_get_att(ncid, ro_id, "ro", ro) + CALL check(stat) + stat = nf90_get_att(ncid, psio_id, "psio", psio) CALL check(stat) - stat = nf90_get_var(ncid, r0_id, r0) + stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) CALL check(stat) ! Extract Diagonal, with 3rd index signifying REAL part - DO i = 1, r_dim + DO i = 1, msing dp_diagonal(i) = REAL(delta_prime(i, i, 1)) END DO @@ -127,7 +159,8 @@ END SUBROUTINE read_stride_netcdf_diagonal c compute c----------------------------------------------------------------------- SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, - $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, + $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,Q0_arr,inpr_arr,inpe_arr, $ omegas_arr,outer_delta_arr) c----------------------------------------------------------------------- @@ -140,52 +173,67 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf, $ ising - INTEGER :: zi, zimp, mi, mimp, msing - REAL(r8) :: nfac,tfac,wefac,wpfac,e,twopi + INTEGER :: zi, zimp, mi, mimp + REAL(r8) :: nfac,tfac,wefac,wpfac,e,pi,twopi TYPE(spline_type) :: spl - TYPE(spline_type) :: sr - INTEGER :: mms,nns,mrs,nrs + INTEGER :: mms,nns,mrs,nrs,mpsi REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ qval,sval,bt,rs,zeff,inpe,r0,bt0 + $ qval,sval,bt,rs,zeff,inpe,R0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, - $ rho,tau_v,inpr,Qconv,lbeta + $ rho,tau_v,inpr,Qconv,lbeta,qintb REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr, - $ inQ_arr,inQ_e_arr, + $ inQ_arr,inQ_e_arr,psi_n_rational, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr + REAL(r8), DIMENSION(0:128) :: psitor, rhotor + REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, - $ psi_n_rational, shear - INTEGER :: r_dim + $ shear,ro,bt0,psio,mpsi_arr + INTEGER :: msing,i + c----------------------------------------------------------------------- c Read in STRIDE netcdf c----------------------------------------------------------------------- character(512) :: - $ kinetic_file = '/fusion/projects/codes/gpec/GPEC-1.5/ - $ docs/examples/a10_ideal_example/a10_prof1.txt' + $ kinetic_file = '/fusion/projects/codes/gpec/GPEC-1.5/ + $docs/examples/a10_ideal_example/a10_prof1.txt' + !character(512) :: + !$ kinetic_file = '/fusion/projects/codes/gpec/users/burgessd/GPEC/ + !$bin/g147131.02300.txt' !REAL(r8), DIMENSION(:), ALLOCATABLE :: q_rational, q_rational_coords !REAL(r8), DIMENSION(:), ALLOCATABLE :: Delta_prime, Delta_prime_coords - WRITE(*,*)"ncfile=",ncfile + CALL read_stride_netcdf_diagonal(ncfile, - $ r_dim, dp_diagonal, q_rational, psi_n_rational, - $ shear, r0, bt0) + $ msing, dp_diagonal, q_rational, psi_n_rational, + $ shear, ro, bt0, psio, mpsi_arr) + WRITE(*,*)"msing_out=",msing + WRITE(*,*)"dp_diagonal=",dp_diagonal + WRITE(*,*)"q_rational=",q_rational + WRITE(*,*)"psi_n_rational=",psi_n_rational + WRITE(*,*)"shear=",shear + WRITE(*,*)"ro=",ro + WRITE(*,*)"bt0=",bt0 + WRITE(*,*)"psio=",psio + + mpsi = INT(mpsi_arr(1)) + WRITE(*,*)"mpsi=",mpsi !CALL READ_Q_RATIONAL_AND_DELTA_PRIME_FROM_NETCDF( !& filename, q_rational, q_rational_coords, !& Delta_prime, Delta_prime_coords) - - msing = r_dim - ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), $ inQ_i_arr(msing), $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), - $ Q0_arr(msing),outer_delta_arr(msing)) + $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), + $ omegas_arr(msing),outer_delta_arr(msing)) + !ALLOCATE(my_rhotor(mpsi),my_psitor(1)) !DEALLOCATE(q_rational, q_rational_coords, Delta_prime, Delta_prime_coords) c----------------------------------------------------------------------- c Read and build equilibrium inputs @@ -228,21 +276,74 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, c----------------------------------------------------------------------- ! manually set the kinetic profiles zi = 1 - zimp = 1 - mi = 1 - mimp = 1 + zimp = 6 + mi = 2 + mimp = 12 nfac = 1.0 tfac = 1.0 wefac = 1.0 wpfac = 1.0 e=1.6021917e-19 - twopi = 6.28318530718 + pi = 3.1415926535897932 + twopi = 2*pi + chi1 = twopi*psio(1) CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, $ tfac,wefac,wpfac,.false.) ! manually set the perturbed equilibrium displacements ! use false flat xi and xi' for equal weighting + !CALL spline_int(sq) + !WRITE(*,*)"sq=",sq + WRITE(*,*)"mpsi=",mpsi + !CALL spline_alloc(sq,mpsi,4) + + !CALL spline_int(sq) + + !CALL timer(0,out_unit) + !CALL equil_read(out_unit) + !CALL equil_out_global + !CALL equil_out_qfind + + !if(.not. sq%allocated) + !$ stop 'ERROR: Cannot define geometric splines without sq' + + + !CALL set_geom + !WRITE(*,*)"geom%f(2)=",geom%fs(:,2) + + + + + ! minor radius defined using toroidal flux. Used for threshold + !CALL spline_int(sq) + !qintb = sq%fsi(mpsi, 4) + !psitor(:) = sq%fsi(:, 4) / qintb ! normalized toroidal flux + !rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen + !CALL spline_alloc(sr,mpsi,1) + !sr%xs = sq%xs + !sr%fs(:, 1) = rhotor(:) + !CALL spline_fit(sr,"extrap") + + + !WRITE(*,*)"twopi*psio / (pi * bt0)=",twopi*psio / (pi * bt0) + !WRITE(*,*)"sq%fsi(:, 4)=",sq%fsi(:, 4) + !WRITE(*,*)"sq%fsi(:, 4)*twopi*psio / (pi * bt0)=" + !$(twopi*psio / (pi * bt0))* + !DO i=1,mpsi + ! WRITE(*,*)"spline(i)=",sq%fsi(i, 4) + ! WRITE(*,*)"(i)=",SIZE(SQRT(sq%fsi(i, 4)*(twopi*psio / + !$(pi * bt0)))) + ! my_psitor = REAL(sq%fsi(i, 4)) + !WRITE(*,*)"my_psitor(1)=",my_psitor(1) + !WRITE(*,*)"(my_psitor(1))=",my_psitor(1) + !WRITE(*,*)"(my_rhotor(i))=",my_rhotor(i) + !my_rhotor(i)=SQRT(my_psitor(1)*(twopi*psio / + !$(pi * bt0))) + + !ENDDO + !WRITE(*,*)"rhotor=",rhotor + c----------------------------------------------------------------------- c loop across singular surfaces, evaluate spline quantities. c----------------------------------------------------------------------- @@ -252,19 +353,28 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, !respsi=singtype(ising)%psifac !CALL spline_eval(sq,respsi,1) respsi = psi_n_rational(ising) + WRITE(*,*)"respsi=",respsi + WRITE(*,*)"chi1=",chi1 + c----------------------------------------------------------------------- c prepare layer analysis. c----------------------------------------------------------------------- !resm = mfac(resnum(ising)) - CALL spline_eval(sr,respsi,1) + !CALL spline_eval(sr,respsi,1) CALL spline_eval(kin,respsi,1) + !CALL spline_eval(geom,respsi,1) c----------------------------------------------------------------------- c SLAYER inputs for sing surface c----------------------------------------------------------------------- + + + omega_i=-twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) $ -twopi*kin%f1(3)/(e*zi*chi1) omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) + WRITE(*,*)"omega_i=",omega_i + WRITE(*,*)"omega_e=",omega_e ! Here's where I'm getting these from !CALL gpec_slayer(kin%f(2),kin%f(4)/e,kin%f(1),kin%f(3)/e, @@ -276,20 +386,40 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, t_e = kin%f(4)/e n_i = kin%f(1) t_i = kin%f(3)/e - zeff = kin%f(5) - omega = kin%f(9) + zeff = kin%f(9) + omega = kin%f(5) qval = q_rational(ising)!sq%f(4) sval = shear(ising) - bt = bt0 - rs = sr%f1(1) - R0 = r0 + bt = bt0(1) + rs = 0.167 + R0 = ro(1) mu_i = mi inpr = slayer_inpr - !mms = resm - !nns = nn - mrs = 2.0 ! FROM NAMELIST ???? real(mms,4) - nrs = 1.0 ! FROM NAMELIST ???? real(nns,4) + !!! TEMPORARY + !mm=2 + !nn=1 + !mr = real(mm,4) + !nr = real(nn,4) + mms = 2.0 + nns = 1.0 + mrs = real(mms,4) + nrs = real(nns,4) + + WRITE(*,*)"n_e=",n_e + WRITE(*,*)"t_e=",t_e + WRITE(*,*)"n_i=",n_i + WRITE(*,*)"t_i=",t_i + WRITE(*,*)"zeff=",zeff + WRITE(*,*)"omega=",omega + WRITE(*,*)"qval=",qval + WRITE(*,*)"sval=",sval + WRITE(*,*)"bt=",bt + WRITE(*,*)"rs=",rs + WRITE(*,*)"R0=",R0 + WRITE(*,*)"mu_i=",mu_i + WRITE(*,*)"inpr=",inpr + WRITE(*,*)"dp_diagonal(ising)=",dp_diagonal ! String representations of the m and n mode numbers !IF (nns<10) THEN ! WRITE(UNIT=sn,FMT='(I1)') nns @@ -309,23 +439,34 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, inpe=0.0 ! Waybright added this + WRITE(*,*)"lnLamb=",lnLamb + WRITE(*,*)"mu_i=",mu_i + WRITE(*,*)"m_p=",m_p + tau= t_i/t_e ! ratio of ion to electron temperature tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) rho=(mu_i*m_p)*n_e ! mass density + + + b_l=(nrs/mrs)*nrs*sval*bt/R0 ! characteristic magnetic field - v_a=b_l/(mu0*rho)**0.5 ! alfven velocity + WRITE(*,*)"b_l=",b_l + + v_a=b_l/(mu0*rho)**0.5 ! alfven velocity, B_L IS BROKEN rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! alfven time across surface tau_r=mu0*rs**2.0/eta ! resistive time scale tau_v=tau_r/inpr ! rho*rs**2.0/visc ! viscous time scale + WRITE(*,*)"tau_v=",tau_v ! this one must be anomalous. calculated back from pr. visc= rho*rs**2.0/tau_v lu=tau_r/tau_h ! Lundquist number + WRITE(*,*)"lu=",lu Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole @@ -333,30 +474,85 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i + WRITE(*,*)"Q=",Q ! This is the most critical parameter ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 + WRITE(*,*)"c_beta=",c_beta delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes + WRITE(*,*)"delta_n=",delta_n + + WRITE(*,*)"qval_arr(ising)=",qval_arr(ising) + WRITE(*,*)"qval=",qval qval_arr(ising) = qval + WRITE(*,*)"qval_arr(ising)=",qval_arr(ising) inQ_arr(ising)=Q + WRITE(*,*)"inQ_arr(ising)=",inQ_arr(ising) + inQ_e_arr(ising)=Q_e + WRITE(*,*)"inQ_e_arr(ising)=",inQ_e_arr(ising) + inQ_i_arr(ising)=Q_i + WRITE(*,*)"inQ_i_arr(ising)=",inQ_i_arr(ising) + inc_beta_arr(ising)=c_beta + WRITE(*,*)"inds_arr(ising)=",inc_beta_arr(ising) + inds_arr(ising)=ds + WRITE(*,*)"inds_arr(ising)=",inds_arr(ising) + intau_arr(ising)=tau + WRITE(*,*)"intau_arr(ising)=",intau_arr(ising) + Q0_arr(ising)=Q + WRITE(*,*)"Q0_arr(ising)=",Q0_arr(ising) + inpr_arr(ising) = inpr + WRITE(*,*)"inpr_arr(ising)=",inpr_arr(ising) + inpe_arr(ising) = 0.0 !!! TEMPORARY? + WRITE(*,*)"inpe_arr(ising)=",inpe_arr(ising) + omegas_arr(ising) = omega - outer_delta_arr(ising) = dp_diagonal(ising) + WRITE(*,*)"omegas_arr(ising)=",omegas_arr(ising) + outer_delta_arr(ising) = dp_diagonal(ising) + WRITE(*,*)"outer_delta_arr(ising)=", + $outer_delta_arr(ising) + + WRITE(*,*)"qval_arr=",qval_arr + WRITE(*,*)"inQ_arr=",inQ_arr + WRITE(*,*)"inQ_e_arr=",inQ_e_arr + WRITE(*,*)"inQ_i_arr=",inQ_i_arr + WRITE(*,*)"inc_beta_arr=",inc_beta_arr + WRITE(*,*)"inds_arr=",inds_arr + WRITE(*,*)"intau_arr=",intau_arr + WRITE(*,*)"Q0_arr=",Q0_arr + WRITE(*,*)"inpr_arr=",inpr_arr + WRITE(*,*)"inpe_arr=",inpe_arr + WRITE(*,*)"omegas_arr=",omegas_arr + WRITE(*,*)"outer_delta_arr=",outer_delta_arr ENDDO - CALL spline_dealloc(sr) + + !WRITE(*,*)"qval_arr=",qval_arr + !WRITE(*,*)"inQ_arr=",inQ_arr + !WRITE(*,*)"inQ_e_arr=",inQ_e_arr + !WRITE(*,*)"inQ_i_arr=",inQ_i_arr + !WRITE(*,*)"inc_beta_arr=",inc_beta_arr + !WRITE(*,*)"inds_arr=",inds_arr + !WRITE(*,*)"intau_arr=",intau_arr + !WRITE(*,*)"Q0_arr=",Q0_arr + !!WRITE(*,*)"inpr_arr=",inpr_arr + !WRITE(*,*)"inpe_arr=",inpe_arr + !WRITE(*,*)"omegas_arr=",omegas_arr + !WRITE(*,*)"outer_delta_arr=",outer_delta_arr + + !CALL spline_dealloc(sr) !CALL cspline_dealloc(fsp_sol) !CALL gpeq_dealloc c----------------------------------------------------------------------- diff --git a/slayer/makefile b/slayer/makefile index b4c676d97..29a36dfd3 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -20,6 +20,7 @@ OBJS = \ sglobal.o \ params.o \ layerinputs.o \ + slayer_netcdf.o \ delta.o \ gslayer.o \ slayer.o @@ -52,7 +53,7 @@ slayer: $(OBJS) params.o : sglobal.o delta.o : sglobal.o -gslayer.o : sglobal.o layerinputs.o delta.o +gslayer.o : sglobal.o delta.o slayer.o : sglobal.o params.o delta.o ../pentrc/grid.mod ../equil/spline_mod.mod clean: diff --git a/slayer/slayer.f b/slayer/slayer.f index 5dccc3afe..126f7f300 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -54,7 +54,8 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: qval_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr + $ inpe_arr,omegas_arr,inQ_arr, + $ psi_n_rational REAL(r8), DIMENSION(:), ALLOCATABLE :: $ outer_delta_arr REAL(r8), DIMENSION(:,:), ALLOCATABLE :: @@ -138,7 +139,9 @@ PROGRAM slayer input_flag=.FALSE. infile="input_params.dat" ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ - $stride_output_n1.nc" + $stride_output_a1.nc" + !ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ + !$stride_output_147131.nc" verbose=.TRUE. ascii_flag=.TRUE. bin_flag=.TRUE. @@ -330,15 +333,29 @@ PROGRAM slayer !CALL read_stride_netcdf(filename, q_rational, q_rational_coords, !$ Delta_prime, Delta_prime_coords) CALL build_inputs(ncfile,inpr, - $ growthrates_flag,qval_arr, + $ growthrates_flag,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, $ omegas_arr,outer_delta_arr) - WRITE(*,*)"outer_delta_arr=",outer_delta_arr + WRITE(*,*)"qval_arr=",qval_arr + WRITE(*,*)"inQ_arr=",inQ_arr + WRITE(*,*)"inQ_e_arr=",inQ_e_arr + WRITE(*,*)"inQ_i_arr=",inQ_i_arr + WRITE(*,*)"inc_beta_arr=",inc_beta_arr + WRITE(*,*)"inds_arr=",inds_arr + WRITE(*,*)"intau_arr=",intau_arr + WRITE(*,*)"inQ0_arr=",inQ0_arr + WRITE(*,*)"inpr_arr=",inpr_arr + WRITE(*,*)"inpe_arr=",inpe_arr WRITE(*,*)"omegas_arr=",omegas_arr + WRITE(*,*)"outer_delta_arr=",outer_delta_arr + WRITE(*,*)"made it to gammamatch=",qval_arr + + !stop - CALL gamma_match(qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + $ inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr, $ inQ0_arr,inpr_arr,inpe_arr, $ omegas_arr,outer_delta_arr, diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f new file mode 100644 index 000000000..33d92c593 --- /dev/null +++ b/slayer/slayer_netcdf.f @@ -0,0 +1,188 @@ +c----------------------------------------------------------------------- +c file slayer_netcdf.f +c writes slayer information to a netcdf file +c----------------------------------------------------------------------- +c code organization. +c----------------------------------------------------------------------- +c 0. slayer_netcdf_mod +c 1. check +c 2. stride_netcdf_out +c----------------------------------------------------------------------- +c subprogram 0. slayer_netcdf_mod +c module declarations. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + MODULE slayer_netcdf_mod + !USE dcon_mod + USE sglobal_mod + !USE layerinputs_mod + !USE gslayer_mod + USE netcdf + IMPLICIT NONE + CONTAINS +c ----------------------------------------------------------------------- +c ----------------------------------------------------------------------- +c subprogram 2. stride_netcdf_out. +c Replicate stride.out information in netcdf format. +c ----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c subprogram 1. check. +c Check status of netcdf file. +c----------------------------------------------------------------------- + SUBROUTINE check(stat) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT (IN) :: stat +c----------------------------------------------------------------------- +c stop if it is an error. +c----------------------------------------------------------------------- + IF(stat /= nf90_noerr) THEN + PRINT *, TRIM(nf90_strerror(stat)) + !STOP "ERROR: failed to write/read netcdf file" + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE check +c ----------------------------------------------------------------------- +c declarations. +c ----------------------------------------------------------------------- + SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, + $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr,psi_n_rational, + $ all_Re_deltas,all_slices) + INTEGER, INTENT(IN) :: msing,ReQ_n,ImQ_n + REAL(r8), DIMENSION(:), INTENT(IN) :: qval_arr, + $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr, + $ psi_n_rational!,shear + REAL(r8), DIMENSION(:,:), INTENT(IN) :: all_slices + REAL(r8), DIMENSION(:,:,:), INTENT(IN) :: all_Re_deltas + INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, + $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, + $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id, + $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id + !COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et + !CHARACTER(2) :: sn + CHARACTER(64) :: ncfile + ! INTEGER :: ising,jsing + !COMPLEX(r8), DIMENSION(msing,msing) :: ap,bp,gammap,deltap + LOGICAL, PARAMETER :: debug_flag = .FALSE. +c ----------------------------------------------------------------------- +c set variables +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *,"Called slayer_netcdf_out" + IF (nn<10) THEN + WRITE(UNIT=sn,FMT='(I1)')nn + sn=ADJUSTL(sn) + ELSE + WRITE(UNIT=sn,FMT='(I2)')nn + ENDIF + ncfile = "slayer_output_n"//TRIM(sn)//".nc" + IF(debug_flag) PRINT *, ncfile +c ----------------------------------------------------------------------- +c open files +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Creating netcdf files" + CALL check( nf90_create(ncfile, + $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) +c ----------------------------------------------------------------------- +c define global file attributes +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Defining netcdf globals" + CALL check( nf90_put_att(ncid,nf90_global,"title", + $ "SLAYER outputs")) + ! define global attributes + !CALL check( nf90_put_att(ncid,nf90_global,'ro', ro)) + !CALL check( nf90_put_att(ncid,nf90_global,'psio', psio)) + !CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) + !CALL check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) + !CALL check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) + !CALL check( nf90_put_att(ncid,nf90_global,"n", nn)) + !CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + ! define dimensions + IF(debug_flag) PRINT *," - Defining dimensions in netcdf" + !CALL check( nf90_def_dim(ncid, "i", 2, i_dim) ) + !CALL check( nf90_def_var(ncid, "i", nf90_int, i_dim, i_id) ) + !CALL check( nf90_def_dim(ncid, "m", mpert, m_dim) ) + !CALL check( nf90_def_var(ncid, "m", nf90_int, m_dim, m_id) ) + !CALL check( nf90_def_dim(ncid, "mode", mpert, mo_dim) ) + !CALL check( nf90_def_var(ncid, "mode", nf90_int, mo_dim, mo_id)) + !CALL check( nf90_def_dim(ncid, "psi_n", sq%mx+1, p_dim) ) + !CALL check( nf90_def_var(ncid, "psi_n", nf90_double, p_dim, p_id)) + IF(msing>0)THEN + CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, + $ qsing_id)) + CALL check( nf90_def_dim(ncid,"ReQ_arr",ReQ_n, + $ ReQ_dim) ) !r_dim = q_rational + CALL check( nf90_def_var(ncid,"ReQ_arr",nf90_double,ReQ_dim, + $ ReQ_id)) + CALL check( nf90_def_dim(ncid,"ImQ_arr",ImQ_n,ImQ_dim) ) !r_dim = q_rational + CALL check( nf90_def_var(ncid,"ImQ_arr",nf90_double,ImQ_dim, + $ ImQ_id)) + CALL check( nf90_def_var(ncid,"growthrates",nf90_double, + $ qsing_dim,gamma_id)) + CALL check( nf90_def_var(ncid,"omegas",nf90_double, + $ qsing_dim,omegas_id)) + CALL check( nf90_def_var(ncid,"Q",nf90_double, + $ qsing_dim,Q_id)) + CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, + $ qsing_dim,pr_id) ) + CALL check( nf90_def_var(ncid,"q_rational",nf90_double, + $ qsing_dim,qr_id) ) + ! CALL check( nf90_def_var(ncid,"shear",nf90_double,q_rational, + !$ shear_id) ) + ENDIF + ! define variables + IF(debug_flag) PRINT *," - Defining variables in netcdf" + !CALL check( nf90_def_var(ncid, "f", nf90_double, p_dim, f_id) ) + !CALL check( nf90_def_var(ncid, "mu0p", nf90_double, p_dim, mu_id)) + !CALL check( nf90_def_var(ncid, "dvdpsi", nf90_double,p_dim,dv_id)) + !CALL check( nf90_def_var(ncid, "q", nf90_double, p_dim, q_id) ) + !CALL check( nf90_def_var(ncid, "di", nf90_double, p_dim, di_id) ) + !CALL check( nf90_def_var(ncid, "dr", nf90_double, p_dim, dr_id) ) + !CALL check( nf90_def_var(ncid, "ca1", nf90_double, p_dim, ca_id)) + !CALL check( nf90_def_var(ncid, "W_p_eigenvector", nf90_double, + !$ (/m_dim, mo_dim, i_dim/), wp_id) ) + !CALL check( nf90_def_var(ncid, "W_p_eigenvalue", nf90_double, + !$ (/mo_dim, i_dim/), wpv_id) ) + IF(msing>0)THEN + CALL check( nf90_def_var(ncid, "Re_Delta", nf90_double, + $ (/ReQ_dim, ImQ_dim, qsing_dim/), dp_id) ) + CALL check( nf90_def_var(ncid, "slices", nf90_double, + $ (/ImQ_dim, qsing_dim/), slice_id) ) + ENDIF + ! end definitions + CALL check( nf90_enddef(ncid) ) +c ----------------------------------------------------------------------- +c set variables +c ----------------------------------------------------------------------- + ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" + CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) + CALL check( nf90_put_var(ncid,ReQ_id, inQs_log)) + CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) + CALL check( nf90_put_var(ncid,gamma_id, growthrates)) + CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) + CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) + CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) + CALL check( nf90_put_var(ncid,qr_id, qval_arr)) + !CALL check( nf90_put_var(ncid,shear_id, shear)) + ! IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" + CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas))!, + !$(/ReQ_n,ImQ_n,msing/))) + CALL check( nf90_put_var(ncid,slice_id,all_slices))!, + !$(/ImQ_n,msing/))) +c ----------------------------------------------------------------------- +c close file +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Closing netcdf file" + CALL check( nf90_close(ncid) ) +c ----------------------------------------------------------------------- +c terminate. +c ----------------------------------------------------------------------- + RETURN + END SUBROUTINE slayer_netcdf_out + END MODULE slayer_netcdf_mod \ No newline at end of file diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 4d3c26e9c..9eb8ec587 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -60,7 +60,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ bt0_id, r0_id, shear_id + $ shear_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile @@ -109,6 +109,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_put_att(ncid,nf90_global,'mhigh', mhigh)) CALL check( nf90_put_att(ncid,nf90_global,'mpert', mpert)) CALL check( nf90_put_att(ncid,nf90_global,'mband', mband)) + CALL check( nf90_put_att(ncid,nf90_global,'msing', msing)) CALL check( nf90_put_att(ncid,nf90_global,'psilow', psilow)) CALL check( nf90_put_att(ncid,nf90_global,'amean', amean)) CALL check( nf90_put_att(ncid,nf90_global,'rmean', rmean)) @@ -163,10 +164,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid,"r_prime",nf90_int,rp_dim,rp_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ r_dim,pr_id) ) - CALL check( nf90_def_var(ncid,"bt0",nf90_double, - $ r_dim/r_dim,bt0_id) ) - CALL check( nf90_def_var(ncid,"r0",nf90_double, - $ r_dim/r_dim,r0_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, @@ -224,8 +221,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,pr_id, (/(sing(i)%psifac, $ i=1,msing)/)) ) - CALL check( nf90_put_var(ncid,bt0_id, (/ bt0 /)) ) - CALL check( nf90_put_var(ncid,r0_id, (/ ro /)) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, From 7384150d68b1428a1855b702689daa449028b196 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 5 Jul 2024 13:56:19 -0700 Subject: [PATCH 007/305] WIP: adding GUI compatibility --- docs/examples/a10_ideal_example/slayer.in | 13 +- input/slayer.in | 13 +- pentrc/inputs.f90 | 1 + slayer/layerinputs.f | 241 ++++++++++------------ slayer/slayer.f | 16 +- stride/stride_netcdf.f | 29 ++- 6 files changed, 162 insertions(+), 151 deletions(-) diff --git a/docs/examples/a10_ideal_example/slayer.in b/docs/examples/a10_ideal_example/slayer.in index 8d2d5ba03..380c8dea4 100644 --- a/docs/examples/a10_ideal_example/slayer.in +++ b/docs/examples/a10_ideal_example/slayer.in @@ -2,16 +2,16 @@ !!! FOR READING IN ASCII TABLE, SET INPUT_FLAG input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table - infile='/fusion/projects/codes/gpec/GPEC-1.5/docs/examples/a10_ideal_example/a10_prof1.txt' ! Path to ascii table of profile quantities read when using input_flag - + infile='' ! Path to ascii table of profile quantities read when using input_flag + ncfile='' ! Path to netCDF file of STRIDE outputs !!! FOR MANUALLY ENTERING KINETIC QUANTITIES @ RATIONAL SURFACE, SET PARAMS_FLAG - params_flag=t ! calculates normalized parameters from profile quantities + params_flag=f ! calculates normalized parameters from profile quantities mm=2 ! poloidal mode number nn=1 ! toroidal mode number n_e=7.77E+17 ! electron density [m^-3] t_e=25.8 ! electron temperature [eV] t_i=25.8 ! ion temperature [eV] - sval=729.0 ! magnetic shear at the layer + sval=2.0 ! magnetic shear at the layer bt=1.0 ! toroidal field [T] rs=0.17 ! minor radius of resonant surface [m] R0=2.0 ! major radius of magnetic axis [m] @@ -21,7 +21,7 @@ qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective - inpr=0.0001 ! Prantdl number + inpr=0.1 ! Prantdl number inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -56,7 +56,8 @@ bin_flag=t ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - bal_flag=t ! calculate the resonant field penetration threshold from torque balance + growthrates_flag=t ! Calculate growthrates on each rational surface + !bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file diff --git a/input/slayer.in b/input/slayer.in index 8d2d5ba03..380c8dea4 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -2,16 +2,16 @@ !!! FOR READING IN ASCII TABLE, SET INPUT_FLAG input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table - infile='/fusion/projects/codes/gpec/GPEC-1.5/docs/examples/a10_ideal_example/a10_prof1.txt' ! Path to ascii table of profile quantities read when using input_flag - + infile='' ! Path to ascii table of profile quantities read when using input_flag + ncfile='' ! Path to netCDF file of STRIDE outputs !!! FOR MANUALLY ENTERING KINETIC QUANTITIES @ RATIONAL SURFACE, SET PARAMS_FLAG - params_flag=t ! calculates normalized parameters from profile quantities + params_flag=f ! calculates normalized parameters from profile quantities mm=2 ! poloidal mode number nn=1 ! toroidal mode number n_e=7.77E+17 ! electron density [m^-3] t_e=25.8 ! electron temperature [eV] t_i=25.8 ! ion temperature [eV] - sval=729.0 ! magnetic shear at the layer + sval=2.0 ! magnetic shear at the layer bt=1.0 ! toroidal field [T] rs=0.17 ! minor radius of resonant surface [m] R0=2.0 ! major radius of magnetic axis [m] @@ -21,7 +21,7 @@ qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective - inpr=0.0001 ! Prantdl number + inpr=0.1 ! Prantdl number inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -56,7 +56,8 @@ bin_flag=t ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - bal_flag=t ! calculate the resonant field penetration threshold from torque balance + growthrates_flag=t ! Calculate growthrates on each rational surface + !bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index 7ed7527e7..99e342bc0 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -216,6 +216,7 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) do i=1,5 tmp%fs(0:,i) = table(1:,i+1) enddo + call spline_fit(tmp,"extrap") if(write_log) print *,"Formed temporary spline" diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index b8b8825bb..8a68f23c2 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -43,122 +43,133 @@ END SUBROUTINE check c----------------------------------------------------------------------- c Read and build equilibrium inputs c----------------------------------------------------------------------- - SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, + SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ dp_diagonal, q_rational, psi_n_rational, shear, - $ ro,bt0,psio,mpsi) + $ ro,bt0,psio,mpsi,nn,resm) !USE netcdf ! NetCDF module for Fortran !USE stride_netcdf_mod ! For the 'check' subroutine (error handling) ! Input/Output Arguments - CHARACTER(128), INTENT(IN) :: ncfile - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, + CHARACTER(512), INTENT(IN) :: ncfile + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, $ psi_n_rational, shear - REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: ro,bt0,psio, + REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: ro,bt0,psio, $ mpsi - INTEGER, INTENT(OUT) :: msing + INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm + INTEGER, INTENT(OUT) :: msing - REAL(r8), DIMENSION(:), ALLOCATABLE :: msing_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: msing_arr ! Internal Variables - INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, + INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, - $ msing_id ! Explicit kind for NetCDF variables - INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime - INTEGER :: i - INTEGER :: bt0_len,ro_len,psio_len,mpsi_len,msing_len ! Attribute lengths + $ msing_id,nn_id,resm_id ! Explicit kind for NetCDF variables + INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime + INTEGER :: i + INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, + $ msing_len,nn_len ! Attribute lengths ! Open the NetCDF file - stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) - WRITE(*,*)"ncfile=",ncfile - CALL check(stat) ! Error handling + stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) + WRITE(*,*)"ncfile=",ncfile + CALL check(stat) ! Error handling - stat = nf90_inquire_attribute(ncid, msing_id, "msing", + stat = nf90_inquire_attribute(ncid,msing_id,"msing", $ len = msing_len) - ALLOCATE(msing_arr(msing_len)) + CALL check(stat) + ALLOCATE(msing_arr(msing_len)) + stat = nf90_get_att(ncid,msing_id,"msing",msing_arr) + CALL check(stat) - CALL check(stat) - stat = nf90_get_att(ncid, msing_id, "msing", msing_arr) - CALL check(stat) - - msing=INT(msing_arr(1)) + msing=INT(msing_arr(1)) ! Allocate Arrays (based on dimension) - ALLOCATE(dp_diagonal(msing),q_rational(msing), - $ psi_n_rational(msing),shear(msing)) - ALLOCATE(delta_prime(msing, msing,2)) + ALLOCATE(dp_diagonal(msing),q_rational(msing), + $ psi_n_rational(msing),shear(msing), + $ resm(msing)) + ALLOCATE(delta_prime(msing, msing,2)) + + stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) + CALL check(stat) + stat = nf90_inquire_attribute(ncid,bt0_id,"bt0",len=bt0_len) + CALL check(stat) + + bt0_id=0 !!!!! THIS COULD BE A PROBLEM + + stat = nf90_inquire_attribute(ncid,psio_id,"psio",len = psio_len) + CALL check(stat) + + stat = nf90_inquire_attribute(ncid,mpsi_id,"mpsi",len = mpsi_len) + CALL check(stat) + stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) + CALL check(stat) + + ALLOCATE(bt0(INT(bt0_len)),ro(INT(ro_len)),psio(INT(psio_len)), + $mpsi(INT(mpsi_len)),nn(INT(nn_len))) ! Get Variable IDs - stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) - CALL check(stat) - WRITE(*,*)"stat=",stat - stat = nf90_inq_varid(ncid, "q_rational", qr_id) - CALL check(stat) - WRITE(*,*)"stat=",stat - stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) - CALL check(stat) - WRITE(*,*)"stat=",stat - - stat = nf90_inq_varid(ncid, "shear", shear_id) - CALL check(stat) - stat = nf90_inquire_attribute(ncid, bt0_id, "bt0", - $ len = bt0_len) - CALL check(stat) - stat = nf90_inquire_attribute(ncid, ro_id, "ro", - $ len = ro_len) - CALL check(stat) - stat = nf90_inquire_attribute(ncid, psio_id, "psio", - $ len = psio_len) - CALL check(stat) - stat = nf90_inquire_attribute(ncid, mpsi_id, "mpsi", - $ len = mpsi_len) - CALL check(stat) - ALLOCATE(bt0(bt0_len),ro(ro_len),psio(psio_len), - $ mpsi(mpsi_len)) + stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "q_rational", qr_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "shear", shear_id) + CALL check(stat) + stat = nf90_inq_varid(ncid, "resm", resm_id) + CALL check(stat) ! Read Data from NetCDF File ! Set up start and count for reading only the diagonal !start(1) = 1 !count(1) = 1 - ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. - stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) - CALL check(stat) - ! Read 1D variables - stat = nf90_get_var(ncid, qr_id, q_rational) - CALL check(stat) - stat = nf90_get_var(ncid, pr_id, psi_n_rational) - CALL check(stat) - stat = nf90_get_var(ncid, shear_id, shear) - CALL check(stat) - stat = nf90_get_att(ncid, bt0_id, "bt0", bt0) - CALL check(stat) - stat = nf90_get_att(ncid, ro_id, "ro", ro) - CALL check(stat) - stat = nf90_get_att(ncid, psio_id, "psio", psio) - CALL check(stat) - stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) - CALL check(stat) - - ! Extract Diagonal, with 3rd index signifying REAL part - DO i = 1, msing - dp_diagonal(i) = REAL(delta_prime(i, i, 1)) - END DO + ! Get attributes + stat = nf90_get_att(ncid, ro_id, "ro", ro) + CALL check(stat) + stat = nf90_get_att(ncid, bt0_id, "bt0", bt0) + CALL check(stat) + stat = nf90_get_att(ncid, psio_id, "psio", psio) + CALL check(stat) + stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) + CALL check(stat) + stat = nf90_get_att(ncid, nn_id, "n", nn) + CALL check(stat) + + ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. + stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) + CALL check(stat) + ! Read 1D variables + stat = nf90_get_var(ncid, qr_id, q_rational) + CALL check(stat) + stat = nf90_get_var(ncid, pr_id, psi_n_rational) + CALL check(stat) + stat = nf90_get_var(ncid, shear_id, shear) + CALL check(stat) + stat = nf90_get_var(ncid, resm_id, resm) + CALL check(stat) + + ! Extract Diagonal, with 3rd index signifying REAL part + DO i = 1, msing + dp_diagonal(i) = REAL(delta_prime(i, i, 1)) + END DO ! Clean Up - DEALLOCATE(delta_prime) - stat = nf90_close(ncid) - CALL check(stat) + DEALLOCATE(delta_prime) + stat = nf90_close(ncid) + CALL check(stat) - END SUBROUTINE read_stride_netcdf_diagonal + END SUBROUTINE read_stride_netcdf_diagonal c----------------------------------------------------------------------- c subprogram 1. build_inputs. c compute c----------------------------------------------------------------------- - SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, + SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, + $ growthrate_flag, $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,Q0_arr,inpr_arr,inpe_arr, @@ -166,7 +177,6 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - CHARACTER(128), INTENT(IN) :: ncfile LOGICAL, INTENT(IN) :: growthrate_flag REAL(r8), INTENT(IN) ::slayer_inpr @@ -195,15 +205,15 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, $ shear,ro,bt0,psio,mpsi_arr + INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm INTEGER :: msing,i c----------------------------------------------------------------------- c Read in STRIDE netcdf c----------------------------------------------------------------------- - character(512) :: - $ kinetic_file = '/fusion/projects/codes/gpec/GPEC-1.5/ - $docs/examples/a10_ideal_example/a10_prof1.txt' + CHARACTER(512), INTENT(IN) :: infile,ncfile + !character(512) :: !$ kinetic_file = '/fusion/projects/codes/gpec/users/burgessd/GPEC/ !$bin/g147131.02300.txt' @@ -212,7 +222,7 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, CALL read_stride_netcdf_diagonal(ncfile, $ msing, dp_diagonal, q_rational, psi_n_rational, - $ shear, ro, bt0, psio, mpsi_arr) + $ shear, ro, bt0, psio, mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing WRITE(*,*)"dp_diagonal=",dp_diagonal WRITE(*,*)"q_rational=",q_rational @@ -221,6 +231,8 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, WRITE(*,*)"ro=",ro WRITE(*,*)"bt0=",bt0 WRITE(*,*)"psio=",psio + WRITE(*,*)"nn=",nn + WRITE(*,*)"resm=",resm mpsi = INT(mpsi_arr(1)) WRITE(*,*)"mpsi=",mpsi @@ -288,14 +300,14 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, twopi = 2*pi chi1 = twopi*psio(1) - CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, + CALL read_kin(infile,zi,zimp,mi,mimp,nfac, $ tfac,wefac,wpfac,.false.) ! manually set the perturbed equilibrium displacements ! use false flat xi and xi' for equal weighting !CALL spline_int(sq) !WRITE(*,*)"sq=",sq - WRITE(*,*)"mpsi=",mpsi + !WRITE(*,*)"mpsi=",mpsi !CALL spline_alloc(sq,mpsi,4) !CALL spline_int(sq) @@ -382,6 +394,10 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, !$ bt0,sr%f1(1),ro,mi,slayer_inpr,resm,nn,ascii_flag, !$ delta_s,psi0,jxb,omega_sol,br_th) + !singtype(ising)%q = q(ising)? + !resnum=NINT(singtype(ising)%q*nn)-mlow+1 + !shear(ising)=mfac(resnum)*sq%f1(4)/sq%f(4)**2 + n_e = kin%f(2) t_e = kin%f(4)/e n_i = kin%f(1) @@ -389,7 +405,7 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, zeff = kin%f(9) omega = kin%f(5) qval = q_rational(ising)!sq%f(4) - sval = shear(ising) + sval = shear(ising) ! SHEAR SO MUCH SMALLER THAN BEFORE???? 500 puts it on the root bt = bt0(1) rs = 0.167 R0 = ro(1) @@ -401,11 +417,14 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, !nn=1 !mr = real(mm,4) !nr = real(nn,4) - mms = 2.0 - nns = 1.0 + mms = resm(ising) + nns = nn(1) mrs = real(mms,4) nrs = real(nns,4) + WRITE(*,*)"nns=",nns + WRITE(*,*)"mms=",mms + WRITE(*,*)"n_e=",n_e WRITE(*,*)"t_e=",t_e WRITE(*,*)"n_i=",n_i @@ -486,57 +505,19 @@ SUBROUTINE build_inputs(ncfile,slayer_inpr,growthrate_flag, delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes WRITE(*,*)"delta_n=",delta_n - WRITE(*,*)"qval_arr(ising)=",qval_arr(ising) - WRITE(*,*)"qval=",qval - qval_arr(ising) = qval - WRITE(*,*)"qval_arr(ising)=",qval_arr(ising) inQ_arr(ising)=Q - WRITE(*,*)"inQ_arr(ising)=",inQ_arr(ising) - inQ_e_arr(ising)=Q_e - WRITE(*,*)"inQ_e_arr(ising)=",inQ_e_arr(ising) - inQ_i_arr(ising)=Q_i - WRITE(*,*)"inQ_i_arr(ising)=",inQ_i_arr(ising) - inc_beta_arr(ising)=c_beta - WRITE(*,*)"inds_arr(ising)=",inc_beta_arr(ising) - inds_arr(ising)=ds - WRITE(*,*)"inds_arr(ising)=",inds_arr(ising) - intau_arr(ising)=tau - WRITE(*,*)"intau_arr(ising)=",intau_arr(ising) - Q0_arr(ising)=Q - WRITE(*,*)"Q0_arr(ising)=",Q0_arr(ising) - inpr_arr(ising) = inpr - WRITE(*,*)"inpr_arr(ising)=",inpr_arr(ising) - inpe_arr(ising) = 0.0 !!! TEMPORARY? - WRITE(*,*)"inpe_arr(ising)=",inpe_arr(ising) - omegas_arr(ising) = omega - WRITE(*,*)"omegas_arr(ising)=",omegas_arr(ising) - outer_delta_arr(ising) = dp_diagonal(ising) - WRITE(*,*)"outer_delta_arr(ising)=", - $outer_delta_arr(ising) - - WRITE(*,*)"qval_arr=",qval_arr - WRITE(*,*)"inQ_arr=",inQ_arr - WRITE(*,*)"inQ_e_arr=",inQ_e_arr - WRITE(*,*)"inQ_i_arr=",inQ_i_arr - WRITE(*,*)"inc_beta_arr=",inc_beta_arr - WRITE(*,*)"inds_arr=",inds_arr - WRITE(*,*)"intau_arr=",intau_arr - WRITE(*,*)"Q0_arr=",Q0_arr - WRITE(*,*)"inpr_arr=",inpr_arr - WRITE(*,*)"inpe_arr=",inpe_arr - WRITE(*,*)"omegas_arr=",omegas_arr - WRITE(*,*)"outer_delta_arr=",outer_delta_arr + ENDDO !WRITE(*,*)"qval_arr=",qval_arr diff --git a/slayer/slayer.f b/slayer/slayer.f index 126f7f300..0ad1047be 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -23,7 +23,7 @@ PROGRAM slayer IMPLICIT NONE - CHARACTER(128) :: infile,ncfile + CHARACTER(512) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num,n_k INTEGER, DIMENSION(1) :: index @@ -78,9 +78,9 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag,growthrates_flag + $ stability_flag,growthrates_flag,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, - $ params_check,bal_flag + $ params_check c----------------------------------------------------------------------- c set initial values. c----------------------------------------------------------------------- @@ -137,9 +137,8 @@ PROGRAM slayer PeOhmOnly_flag=.TRUE. params_flag=.TRUE. input_flag=.FALSE. - infile="input_params.dat" - ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ - $stride_output_a1.nc" + infile="" + ncfile="" !ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ !$stride_output_147131.nc" verbose=.TRUE. @@ -332,7 +331,10 @@ PROGRAM slayer !CALL read_stride_netcdf(filename, q_rational, q_rational_coords, !$ Delta_prime, Delta_prime_coords) - CALL build_inputs(ncfile,inpr, + WRITE(*,*)"infile=",infile + WRITE(*,*)"ncfile=",ncfile + + CALL build_inputs(infile,ncfile,inpr, $ growthrates_flag,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 9eb8ec587..2b5c4936f 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -53,6 +53,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) REAL(r8), DIMENSION(mpert), INTENT(IN) :: epi,evi,eti COMPLEX(r8), DIMENSION(mpert,mpert), INTENT(IN) :: wp,wv,wt COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, INTENT(IN) :: dp + INTEGER, DIMENSION(mpert) :: mvec INTEGER :: i, ncid, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, @@ -60,12 +61,15 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id + $ shear_id,resm_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile - INTEGER :: ising,jsing + REAL(r8) :: resnum,shear,respsi,resm_sing + INTEGER, DIMENSION(msing) :: resm + + INTEGER :: ising,jsing,m COMPLEX(r8), DIMENSION(msing,msing) :: ap,bp,gammap,deltap LOGICAL, PARAMETER :: debug_flag = .FALSE. @@ -153,6 +157,13 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_dim(ncid, "psi_n", sq%mx+1, p_dim) ) CALL check( nf90_def_var(ncid, "psi_n", nf90_double, p_dim, p_id)) IF(msing>0)THEN + mvec=(/(m,m=mlow,mhigh)/) + DO i=1,msing + respsi=sing(i)%psifac + resnum=NINT(sing(i)%q*nn)-mlow+1 + resm_sing=mvec(resnum) + resm(i)=resm_sing + ENDDO CALL check( nf90_def_dim(ncid,"lr_index",2*msing,l_dim) ) CALL check( nf90_def_var(ncid,"lr_index",nf90_int,l_dim,l_id)) CALL check( nf90_def_dim(ncid,"lr_prime",2*msing,lp_dim) ) @@ -168,6 +179,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) + CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, + $ resm_id) ) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" @@ -223,8 +236,20 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) + + !mvec=(/(m,m=mlow,mhigh)/) + !DO i=1,msing + ! respsi=sing(i)%psifac + ! CALL spline_eval(sq,respsi,1) + ! resnum=NINT(sing(i)%q*2.0)-mlow+1 + ! shear=mvec(resnum)*sq%f1(4)/sq%f(4)**2 + ! WRITE(*,*)"SHEAR=",shear + !ENDDO + CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? + CALL check( nf90_put_var(ncid,resm_id, resm) ) + ! CALL check( nf90_put_var(ncid,shear_id,shear) ) ! GPEC HAS DIFFERENT SHEAR CALC? ENDIF IF(debug_flag) PRINT *," - Putting profile variables in netcdf" From f6368033a2949ddee51cef7d0e57f03be0f6a58b Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 26 Jul 2024 12:57:43 -0700 Subject: [PATCH 008/305] WIP: cleaned SLAYER, stability plots --- input/slayer.in | 5 +- slayer/gslayer.f | 329 +++++----------------------- slayer/layerinputs.f | 477 ++++++++++++++++++----------------------- slayer/makefile | 1 - slayer/sglobal.f | 2 +- slayer/slayer.f | 108 ++-------- slayer/slayer_netcdf.f | 262 ++++++++++++++++++---- 7 files changed, 503 insertions(+), 681 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 380c8dea4..ec5858477 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -21,7 +21,7 @@ qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective - inpr=0.1 ! Prantdl number + inpr=1.0 ! Prantdl number inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -38,6 +38,9 @@ inum=400 ! resolution to find error field thresholds. jnum=100 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. + ReQ_num=350 ! resolution for stability scan along Re(Q) axis + ImQ_num=350 ! resolution for stability scan along Im(Q) axis + scan_radius=3 ! stability scan width in Q QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space QPescan_flag=f ! scan (Q,Pe) space for delta and torque. diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 1ed32a727..8238a7316 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -1,6 +1,6 @@ MODULE gslayer_mod - USE sglobal_mod, ONLY: out_unit, r8, mu0, m_p, chag, lnLamb, + USE sglobal_mod, ONLY: out_unit,r8, mu0, m_p, chag, lnLamb, $ Q_e,Q_i,pr,pe,c_beta,ds,tau, $ eta,visc,rho_s,lu,omega_e,omega_i, $ delta_n, @@ -175,256 +175,68 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- -c Subprogram 2. interpolate_slice_at_Q -c Either extract or interpolate a 1D deltas slice at given Q -c----------------------------------------------------------------------- - SUBROUTINE interpolate_slice_at_Q(deltas, Q, inQs_log, - $ slice) - ! Input - REAL(r8), DIMENSION(:, :), INTENT(IN) :: deltas ! 2D delta array - REAL(r8), DIMENSION(:), INTENT(IN) :: inQs_log ! Re(Q) axis - REAL(r8), INTENT(IN) :: Q ! slice value (Q) - ! Output - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: slice - ! Local variables - INTEGER :: NROWS, NCOLS, i_lower, i_upper, j - REAL(r8) :: Q_lower,Q_upper,lower_slice,upper_slice - - slice = 0.0 - ! Extract array dimensions - NROWS = SIZE(deltas, 1) - NCOLS = SIZE(deltas, 2) - ! Allocate slice array (same size as second dimension of deltas) - ALLOCATE(slice(NCOLS)) - ! Find bracketing rows based on Q (assuming sorted x-axis) - i_lower = 1 - DO WHILE (i_lower < NROWS .AND. inQs_log(i_lower+1) < Q) - i_lower = i_lower + 1 - END DO - - ! Check if Q is included in inQs_log (it should be) - IF (inQs_log(i_lower) - Q < 1.0E-6_r8) THEN !!! this threshold could be an issue? - WRITE(*,*)"Indexing slice at Q!" - DO j = 1, NCOLS - slice(j) = deltas(i_lower, j) - END DO - ELSE - - !! Handle cases where Q is outside the range - ! IF (i_lower == 1 .AND. Q < deltas(1, 1)) THEN - ! i_lower = 1 - ! i_upper = 2 - ! ELSE IF (i_lower == NROWS) THEN - ! i_lower = NROWS - 1 - ! i_upper = NROWS - ! ELSE - ! i_upper = i_lower + 1 - ! END IF - - ! Interpolate each element of the slice (along the columns) - DO j = 1, NCOLS - i_upper = i_lower+1 - - lower_slice = deltas(i_lower, j) - upper_slice = deltas(i_upper, j) - Q_lower = deltas(i_lower, 1) - Q_upper = deltas(i_upper, 1) - - slice(j) = (lower_slice + upper_slice )/2.0 - - ! This linear interpolation isn't correct ??? - !slice(j) = lower_slice + - !$ (Q - Q_lower) * (upper_slice - - !$ lower_slice) / (Q_upper - Q_lower) - END DO - END IF - - RETURN - END SUBROUTINE interpolate_slice_at_Q -c----------------------------------------------------------------------- -c Subprogram 3. gamma_from_delta_match -c Interpolate gamma corresponding to delta-deltaprime match -c----------------------------------------------------------------------- - SUBROUTINE gamma_from_delta_match(slice, iinQs, deltap, - $ ImQ_gamma) - ! Inputs - REAL(r8), DIMENSION(:), INTENT(IN) :: slice ! 1D gammas array, i.e. deltas(Q,:) - REAL(r8), DIMENSION(:), INTENT(IN) :: iinQs ! 1D array of gammas, i.e. Im(Q) - REAL(r8), INTENT(IN) :: deltap ! Target outer layer delta prime - ! Output - REAL(r8), INTENT(OUT) :: ImQ_gamma ! Matched gamma(delta = deltaprime), giving growth rate - ! Local variables - INTEGER :: n, i_lower, i_upper, i, j, i_mid - REAL(r8) :: deltap_lower, deltap_upper, slope, - $ lower_val, upper_val,n_poles,dx - REAL(r8), DIMENSION(:), ALLOCATABLE :: temp_slice, temp_iinQs, ! Temporary arrays - $ grad_slice, pospole_gamma, negpole_gamma - - ! Array size check - n = SIZE(slice) - IF (SIZE(iinQs) /= n) THEN - WRITE(*, *) 'ERROR: slice and iinQs arrays - $ must have the same size.' - RETURN - END IF - - ! Allocate temporary arrays - ALLOCATE(temp_slice(n), temp_iinQs(n)) - ALLOCATE(grad_slice(n-1)) ! gradient - - ImQ_gamma = 0.0 - - ! Copy input arrays to temporary arrays - temp_slice = slice - temp_iinQs = iinQs - - ! GRADIENT THRESHOLD IS 50??? - n_poles = 1.0 - ALLOCATE(pospole_gamma(2),negpole_gamma(2)) - pospole_gamma = 1e+20 - negpole_gamma = 1e+20 - - ! Calculate the gradient - DO i = 1, n - 1 - dx = iinQs(i + 1) - iinQs(i) ! Change in x - grad_slice(i) = (slice(i + 1) - slice(i)) / dx ! Slope (gradient) - END DO - - ! Find first match - DO i = 1, n-1 - ! is this a pole point? - IF (((SIGN(1.0,slice(i)) /= SIGN(1.0,slice(i+1))) .AND. - $ ABS(grad_slice(i) > 50))) THEN!.AND. - !$ (SIGN(1.0,grad_slice(i)) /= - !$ SIGN(1.0,grad_slice(i+1)))) THEN - ! it is a pole - IF (slice(i) > 0) THEN - WRITE(*,*)"FOUND A POS POLE ",0.0 - pospole_gamma(n_poles) = iinQs(i) - negpole_gamma(n_poles) = iinQs(i+1) - n_poles = n_poles + 1 - ELSE - WRITE(*,*)"FOUND A NEG POLE ",i - negpole_gamma(n_poles) = iinQs(i) - pospole_gamma(n_poles) = iinQs(i+1) - n_poles = n_poles+1 - END IF - - WRITE(*,*)"n_poles=",n_poles - - IF ((SIGN(1.0,slice(i))<0.0 .AND. deltap0.0 .AND. deltap>slice(i))) THEN - ImQ_gamma = iinQs(i) ! stride value is huge and not captured by our grid, but will be close to the pole - END IF - ELSE - ! Determine which element of the slice is lower and which is upper - IF (slice(i) <= slice(i + 1)) THEN - lower_val = slice(i) - upper_val = slice(i + 1) - ELSE - lower_val = slice(i + 1) - upper_val = slice(i) - END IF - - ! Check if deltap is within the interval - IF ((lower_val <= deltap) .AND. - $ (deltap <= upper_val)) THEN - !ImQ_gamma = (iinQs(i) + iinQs(i+1)) / 2 ! do a better linear interpolation here - - ! Linear interpolation (revised for robustness) - IF (upper_val - lower_val < 1.0E-10_r8) THEN ! Handle nearly equal values - ImQ_gamma = (iinQs(i) + - $ iinQs(i+1)) / 2 - ELSE - slope = (iinQs(i+1) - - $ iinQs(i)) / (upper_val - lower_val) - ImQ_gamma = iinQs(i) + - $ slope * (deltap - lower_val) - END IF - END IF - END IF - END DO - - IF (n_poles > 2) THEN - WRITE(*,*)"Alert: more than two poles detected" - END IF - - - !WRITE(*,*)"temp_iinQs(i_lower)=",temp_iinQs(i_lower) - !WRITE(*,*)"temp_iinQs(i_upper)=",temp_iinQs(i_upper) - !WRITE(*,*)"NEWImQ_gamma=",ImQ_gamma - !WRITE(*,*)"pospole_gamma=",pospole_gamma - !WRITE(*,*)"negpole_gamma=",negpole_gamma - - !WRITE(*,*)"slice=",slice - - ! Deallocate temporary arrays - DEALLOCATE(temp_slice, temp_iinQs) - - RETURN - END SUBROUTINE gamma_from_delta_match -c----------------------------------------------------------------------- -c Subprogram 4. gamma_stability_scan +c Subprogram 2. gamma_stability_scan c Run grid packed slayer stab. scan around omega_ExB and gamma axes c----------------------------------------------------------------------- SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,ReQ_num,ImQ_num,deltas, - $ inQs_log,iinQs) + $ inds,intau,inQ0,inpr,inpe,ReQ_num,ImQ_num,scan_radius, + $ deltas,inQs,iinQs) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - REAL(r8),INTENT(IN) :: qval,inQ_e,inQ_i,inc_beta,inds, + REAL(r8),INTENT(IN) :: inQ_e,inQ_i,inc_beta,inds, $ intau,inQ0,inpr,inpe REAL(r8), INTENT(IN) :: inQ ! REAL??? - INTEGER, INTENT(IN) :: ReQ_num,ImQ_num + INTEGER, INTENT(IN) :: qval,ReQ_num,ImQ_num,scan_radius ! Outputs COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, $ INTENT(OUT) :: deltas REAL(r8), DIMENSION(:), ALLOCATABLE, - $ INTENT(OUT) :: inQs_log,iinQs + $ INTENT(OUT) :: inQs,iinQs ! Local variables INTEGER :: i,j,k - REAL(r8) :: inQ_min,inQ_max + REAL(r8) :: Re_inQ_min,Re_inQ_max,Im_inQ_min,Im_inQ_max CHARACTER(3) :: q_str CHARACTER(len=8) :: fmt ! format descriptor for stab file naming CHARACTER(len=8) :: x1 ! string for stab file naming INTEGER :: i1 ! integer for stab file naming - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: inQs_left,inQs_right - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs + !REAL(r8), DIMENSION(:,:), ALLOCATABLE :: inQs_left,inQs_right + !REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log c----------------------------------------------------------------------- c Build exponential grid packing for stability scan, then run c----------------------------------------------------------------------- ! Allocate grid packing arrays and 2D complex deltas array - ALLOCATE(inQs(0:ReQ_num),iinQs(0:ImQ_num)) - ALLOCATE(inQs_left(0:2+ReQ_num/2,0:2+ReQ_num/2)) - ALLOCATE(inQs_right(0:2+ReQ_num/2,0:2+ReQ_num/2)) - ALLOCATE(inQs_log(0:3+ReQ_num)) + ALLOCATE(inQs(0:ReQ_num+1),iinQs(0:ImQ_num)) + !ALLOCATE(inQs_left(0:2+ReQ_num/2,0:2+ReQ_num/2)) + !ALLOCATE(inQs_right(0:2+ReQ_num/2,0:2+ReQ_num/2)) + !ALLOCATE(inQs_log(0:3+ReQ_num)) ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) - inQ_max=3.0 ! max growth rate in scan, OPEN TO USER? - inQ_min=-3.0 ! min growth rate in scan, OPEN TO USER? + Im_inQ_max=scan_radius ! max growth rate in scan, OPEN TO USER? + Im_inQ_min=-scan_radius ! min growth rate in scan, OPEN TO USER? + Re_inQ_max=scan_radius ! max growth rate in scan, OPEN TO USER? + Re_inQ_min=-scan_radius ! min growth rate in scan, OPEN TO USER? ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? - inQs_left = powspace(inQ-0.5,inQ,1, ! omega-0.5 - $ 2+ReQ_num/2,"upper") - inQs_right = powspace(inQ,inQ+0.5,1, ! omega+0.5 - $ 2+ReQ_num/2,"lower") - inQs_log = (/inQs_left(1,1:2+ReQ_num/2), - $ inQs_right(1,2:1+ReQ_num/2)/) - !WRITE(*,*)"inQs_log=",inQs_log + !inQs_left = powspace(inQ-Re_inQ_max,inQ,1, ! omega-3.0 + !$ 2+ReQ_num/2,"upper") + !inQs_right = powspace(inQ,inQ+Re_inQ_max,1, ! omega+3.0 + !$ 2+ReQ_num/2,"lower") + !inQs_log = (/inQs_left(1,1:2+ReQ_num/2), + !$ inQs_right(1,2:1+ReQ_num/2)/) + + inQs = linspace((Re_inQ_min+inQ),(Re_inQ_max+inQ),ReQ_num+1) DO i=0,ReQ_num+1 DO j=0,ImQ_num - ! Getting rid of "inQs" weirdly broke things?? - inQs(i)=inQ_min+(REAL(i)/ReQ_num)*(inQ_max-inQ_min) - iinQs(j)=inQ_min+(REAL(j)/ImQ_num)*(inQ_max-inQ_min) + iinQs(j)=Im_inQ_min+(REAL(j)/ImQ_num)*(Im_inQ_max- + $ Im_inQ_min) ! Run riccati() at each Q index to give delta - deltas(i,j)=riccati(inQs_log(i),inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) + deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ! NOT USING GRID PACKING ENDDO ENDDO c----------------------------------------------------------------------- @@ -441,55 +253,50 @@ SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, DO i=0,ReQ_num+1 DO j=0,ImQ_num WRITE(out_unit,'(1x,4(es17.8e3))') - $ inQs_log(i),iinQs(j), + $ inQs(i),iinQs(j), $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) ENDDO ENDDO CLOSE(out_unit) - DEALLOCATE(inQs_left,inQs_right) - RETURN END SUBROUTINE gamma_stability_scan c----------------------------------------------------------------------- -c Subprogram 5. gamma_match -c Loop stability scans and gamma matches across k rational surfaces +c Subprogram 3. gamma_match +c Loop stability across k rational surfaces c----------------------------------------------------------------------- SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr, + $ inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr, - $ ReQ_num,ImQ_num,growthrates,growthrate_err) + $ ReQ_num,ImQ_num,scan_radius) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - REAL(r8), DIMENSION(:), INTENT(IN) :: qval_arr,inQ_e_arr, + REAL(r8), DIMENSION(:), INTENT(IN) :: inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, $ inQ_arr,psi_n_rational + INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr REAL(r8), DIMENSION(:), INTENT(IN) :: outer_delta_arr - INTEGER, INTENT(IN) :: ReQ_num,ImQ_num + INTEGER, INTENT(IN) :: ReQ_num,ImQ_num,scan_radius ! Outputs - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: - $ growthrates, growthrate_err + REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates ! Local variables INTEGER :: n_k ! Number of rational surfaces INTEGER :: k,w ! Local variables received from internal subroutines - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log,iinQs + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_slices + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_slices,all_inQs REAL(r8), DIMENSION(:), ALLOCATABLE :: slice - REAL(r8), DIMENSION(3) :: Q_range ! (-Q_err, Q, +Q_err ) - REAL(r8), DIMENSION(3) :: growthrate_range ! (-gamma, +gamma ) REAL(r8) :: layer_Q, ImQ_gamma n_k = SIZE(qval_arr) ! Allocate growthrates arrays ALLOCATE(growthrates(n_k)) - ALLOCATE(growthrate_err(n_k)) c----------------------------------------------------------------------- c Loop across rational surfaces @@ -499,62 +306,32 @@ SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, ! given omega_ExB (Q), then find growth rate corresponding ! to delta-deltaprime match DO k=1,n_k - WRITE(*,*)"layer #: ",k + WRITE(*,*) "Scanning q=", qval_arr(k), " rational surface" ! Run stability scan CALL gamma_stability_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ ReQ_num,ImQ_num,deltas,inQs_log,iinQs) + $ ReQ_num,ImQ_num,scan_radius,deltas,inQs,iinQs) layer_Q = inQ_arr(k) ! REAL??? - ! Hardcoding rudimentary +/- 10% omega_ExB errorbars - Q_range = (/0.9*layer_Q, 1.1*layer_Q, layer_Q/) - - ! Calculate growth rate +/- omega_ExB = Q errorbars - DO w=1,3 - IF (w==3) THEN - - ! Slice out growth rates at layer_Q (Re(Q)) - CALL interpolate_slice_at_Q(REAL(deltas), - $ Q_range(w), inQs_log, slice) - ! Match delta to delta prime to obtain growth rate - CALL gamma_from_delta_match(slice, iinQs, - $ outer_delta_arr(k), - $ ImQ_gamma) - CALL gamma_from_delta_match(slice, iinQs, - $ outer_delta_arr(k), - $ ImQ_gamma) - ! Qconv = Q / omega_ExB - ! gamma = Im(Q) / Qconv - growthrates(k) = ImQ_gamma / (layer_Q / omegas_arr(k)) - !ELSE - ! growthrate_range(w) = ImQ_gamma / (layer_Q / omegas(k)) - ENDIF - - ENDDO - growthrate_err(k) = ABS(growthrate_range(2) - - $ growthrate_range(1)) - - ALLOCATE(all_RE_deltas(SIZE(inQs_log),SIZE(iinQs),n_k)) - ALLOCATE(all_slices(SIZE(iinQs),n_k)) + IF (k==1) THEN + ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) + ALLOCATE(all_slices(SIZE(iinQs),n_k), + $ all_inQs(SIZE(inQs),n_k)) + ENDIF all_RE_deltas(:,:,k) = REAL(deltas) - all_slices(:,k) = slice - - DEALLOCATE(slice) ! Free memory after use for each layer - + all_inQs(:,k) = inQs ENDDO - CALL slayer_netcdf_out(n_k,SIZE(inQs_log),SIZE(iinQs),qval_arr, - $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr,psi_n_rational, - $ all_Re_deltas,all_slices) + CALL slayer_netcdf_out(n_k,SIZE(inQs),SIZE(iinQs),qval_arr, + $ inQs,iinQs,growthrates,omegas_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,psi_n_rational, + $ all_Re_deltas,all_inQs) DEALLOCATE(deltas) - WRITE(*,*)"inQs_log len ",SIZE(inQs_log) - WRITE(*,*)"iinQs len ",SIZE(iinQs) - RETURN END SUBROUTINE gamma_match diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 8a68f23c2..cf3b0d675 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -1,20 +1,15 @@ MODULE layerinputs_mod USE inputs, ONLY : read_kin,read_equil,kin,chi1 - !USE params - USE dcon_interface, ONLY : set_geom,geom - !USE direct_mod, ONLY : direct_run USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, $ spline_dealloc,spline_int,spline_fit USE sglobal_mod, ONLY: m_p, chag, lnLamb, - $ Q_e,Q_i,pr,pe,c_beta,ds,tau,mu0,r8, ! NOT out_unit + $ Q_e,Q_i,pr,pe,c_beta,ds,tau,r8,mu0,pi,out_unit, ! NOT out_unit $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q USE netcdf - USE pentrc_interface, ONLY : pentrc_timer=>timer - !USE equil_mod - !USE equil_out_mod - - ! STILL NEED ro AND bt0, GLOBAL GPEC VARIABLES + USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq + USE bicube_mod, ONLY: bicube_eval_external,bicube_type + USE slayer_netcdf_mod, ONLY: slayer_netcdf_inputs IMPLICIT NONE @@ -41,22 +36,20 @@ SUBROUTINE check(stat) RETURN END SUBROUTINE check c----------------------------------------------------------------------- -c Read and build equilibrium inputs +c subprogram 2. read_stride_netcdf_diagonal. +c Read STRIDE netcdf file for SLAYER inputs only. c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ dp_diagonal, q_rational, psi_n_rational, shear, - $ ro,bt0,psio,mpsi,nn,resm) - - !USE netcdf ! NetCDF module for Fortran - !USE stride_netcdf_mod ! For the 'check' subroutine (error handling) + $ r_o,my_bt0,my_psio,mpsi,nn,resm) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, $ psi_n_rational, shear - REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: ro,bt0,psio, - $ mpsi + REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, + $ my_psio,mpsi INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm INTEGER, INTENT(OUT) :: msing @@ -72,7 +65,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, $ msing_len,nn_len ! Attribute lengths - ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) WRITE(*,*)"ncfile=",ncfile @@ -100,15 +92,16 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, bt0_id=0 !!!!! THIS COULD BE A PROBLEM - stat = nf90_inquire_attribute(ncid,psio_id,"psio",len = psio_len) + stat = nf90_inquire_attribute(ncid,psio_id,"psio",len=psio_len) CALL check(stat) - stat = nf90_inquire_attribute(ncid,mpsi_id,"mpsi",len = mpsi_len) + stat = nf90_inquire_attribute(ncid,mpsi_id,"mpsi",len=mpsi_len) CALL check(stat) stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) CALL check(stat) - ALLOCATE(bt0(INT(bt0_len)),ro(INT(ro_len)),psio(INT(psio_len)), + ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), + $ my_psio(INT(psio_len)), $mpsi(INT(mpsi_len)),nn(INT(nn_len))) ! Get Variable IDs @@ -123,18 +116,13 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, stat = nf90_inq_varid(ncid, "resm", resm_id) CALL check(stat) - ! Read Data from NetCDF File - ! Set up start and count for reading only the diagonal - !start(1) = 1 - !count(1) = 1 - ! Get attributes - stat = nf90_get_att(ncid, ro_id, "ro", ro) + stat = nf90_get_att(ncid, ro_id, "ro", r_o) CALL check(stat) - stat = nf90_get_att(ncid, bt0_id, "bt0", bt0) + stat = nf90_get_att(ncid, bt0_id, "bt0", my_bt0) CALL check(stat) - stat = nf90_get_att(ncid, psio_id, "psio", psio) + stat = nf90_get_att(ncid, psio_id, "psio", my_psio) CALL check(stat) stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) CALL check(stat) @@ -165,8 +153,95 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, END SUBROUTINE read_stride_netcdf_diagonal c----------------------------------------------------------------------- -c subprogram 1. build_inputs. -c compute +c subprogram 2. issurfint. +c surface integration by simple method. +c----------------------------------------------------------------------- + FUNCTION issurfint(func,fs,inpsi,wegt,ave, + $ fsave,psave,jacs,delpsi,inr,ina,first) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + !IMPLICIT NONE + INTEGER, INTENT(IN) :: fs,wegt,ave + REAL(r8), INTENT(IN) :: inpsi + REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func + + LOGICAL, INTENT(INOUT) :: first + INTEGER, INTENT(INOUT) :: fsave + REAL(r8), INTENT(INOUT) :: psave + REAL(r8),DIMENSION(0:),INTENT(INOUT) :: jacs,delpsi,inr,ina + INTEGER :: itheta, ix, iy + REAL(r8) :: issurfint + REAL(r8) :: rfac,ineta,injac,inarea + REAL(r8), DIMENSION(1,2) :: w + REAL(r8), DIMENSION(0:fs) :: z,thetas + REAL(r8), dimension(4) :: rzphi_f, rzphi_fx, rzphi_fy + + issurfint=0 + inarea=0 + ix = 0 + iy = 0 + IF(first .OR. inpsi/=psave .OR. fs/=fsave)THEN + psave = inpsi + fsave = fs + !first = .FALSE. + DO itheta=0,fs + thetas(itheta) = REAL(itheta,r8)/REAL(fs,r8) + ENDDO + DO itheta=0,fs-1 + CALL bicube_eval_external(rzphi, inpsi, thetas(itheta), 1, + $ ix, iy, rzphi_f, rzphi_fx, rzphi_fy) + rfac=SQRT(rzphi_f(1)) + ineta=twopi*(thetas(itheta)+rzphi_f(2)) + ina(itheta)=rfac + inr(itheta)=ro+rfac*COS(ineta) + z(itheta)=zo+rfac*SIN(ineta) + injac=rzphi_f(4) + jacs(itheta)=injac + w(1,1)=(1+rzphi_fy(2))*twopi**2*rfac*inr(itheta)/injac + w(1,2)=-rzphi_fy(1)*pi*inr(itheta)/(rfac*injac) + delpsi(itheta)=SQRT(w(1,1)**2+w(1,2)**2) + ENDDO + ENDIF + + IF (wegt==0) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE IF (wegt==1) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE IF (wegt==2) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs + ENDDO + ELSE IF (wegt==3) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE + STOP "ERROR: issurfint wegt must be in [0,1,2,3]" + ENDIF + + IF (ave==1) THEN + DO itheta=0,fs-1 + inarea=inarea+jacs(itheta)*delpsi(itheta)/fs + ENDDO + issurfint=issurfint/inarea + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END FUNCTION issurfint +c----------------------------------------------------------------------- +c subprogram 3. build_inputs. +c build input arrays for SLAYER c----------------------------------------------------------------------- SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, $ growthrate_flag, @@ -179,12 +254,11 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, c----------------------------------------------------------------------- LOGICAL, INTENT(IN) :: growthrate_flag REAL(r8), INTENT(IN) ::slayer_inpr - + LOGICAL :: firstsurf REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf, $ ising - INTEGER :: zi, zimp, mi, mimp - REAL(r8) :: nfac,tfac,wefac,wpfac,e,pi,twopi + REAL(r8) :: nfac,tfac,wefac,wpfac,e!,pi,twopi TYPE(spline_type) :: spl TYPE(spline_type) :: sr @@ -192,97 +266,67 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, INTEGER :: mms,nns,mrs,nrs,mpsi REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ qval,sval,bt,rs,zeff,inpe,R0 + $ my_qval,my_sval,my_bt,my_rs,zeff,inpe,R_0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr, + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ inQ_arr,inQ_e_arr,psi_n_rational, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, - $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr + $ inpr_arr,inpe_arr,omegas_arr, + $ outer_delta_arr + INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, - $ shear,ro,bt0,psio,mpsi_arr - INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm - INTEGER :: msing,i + $ shear,r_o,my_bt0,my_psio,mpsi_arr, + $ omegas_e_arr,omegas_i_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, + $ ti_arr,zeff_arr,bt_arr,rs_arr, + $ R0_arr,mu_i_arr + INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm,nns_arr + INTEGER :: msing,i,mthsurf + REAL(r8), DIMENSION(0:512) :: unitfun + INTEGER :: fsave + REAL(r8) :: psave + REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs,delpsi,rsurf,asurf + REAL(r8) :: rfac,jac,a_surf + CHARACTER(512), INTENT(IN) :: infile,ncfile c----------------------------------------------------------------------- c Read in STRIDE netcdf c----------------------------------------------------------------------- - CHARACTER(512), INTENT(IN) :: infile,ncfile - - !character(512) :: - !$ kinetic_file = '/fusion/projects/codes/gpec/users/burgessd/GPEC/ - !$bin/g147131.02300.txt' - !REAL(r8), DIMENSION(:), ALLOCATABLE :: q_rational, q_rational_coords - !REAL(r8), DIMENSION(:), ALLOCATABLE :: Delta_prime, Delta_prime_coords - CALL read_stride_netcdf_diagonal(ncfile, - $ msing, dp_diagonal, q_rational, psi_n_rational, - $ shear, ro, bt0, psio, mpsi_arr,nn,resm) - WRITE(*,*)"msing_out=",msing - WRITE(*,*)"dp_diagonal=",dp_diagonal - WRITE(*,*)"q_rational=",q_rational - WRITE(*,*)"psi_n_rational=",psi_n_rational - WRITE(*,*)"shear=",shear - WRITE(*,*)"ro=",ro - WRITE(*,*)"bt0=",bt0 - WRITE(*,*)"psio=",psio - WRITE(*,*)"nn=",nn - WRITE(*,*)"resm=",resm + $ msing,dp_diagonal,q_rational,psi_n_rational, + $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm) + !WRITE(*,*)"msing_out=",msing + !WRITE(*,*)"dp_diagonal=",dp_diagonal + !!!WRITE(*,*)"q_rational=",q_rational + !WRITE(*,*)"psi_n_rational=",psi_n_rational + !WRITE(*,*)"shear=",shear + !WRITE(*,*)"r_o=",r_o + !WRITE(*,*)"my_bt0=",my_bt0 + !WRITE(*,*)"my_psio=",my_psio + !WRITE(*,*)"nn=",nn + !WRITE(*,*)"resm=",resm mpsi = INT(mpsi_arr(1)) - WRITE(*,*)"mpsi=",mpsi + mthsurf = 512 - !CALL READ_Q_RATIONAL_AND_DELTA_PRIME_FROM_NETCDF( - !& filename, q_rational, q_rational_coords, - !& Delta_prime, Delta_prime_coords) ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), - $ inQ_i_arr(msing), - $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), - $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), - $ omegas_arr(msing),outer_delta_arr(msing)) - !ALLOCATE(my_rhotor(mpsi),my_psitor(1)) - !DEALLOCATE(q_rational, q_rational_coords, Delta_prime, Delta_prime_coords) -c----------------------------------------------------------------------- -c Read and build equilibrium inputs -c----------------------------------------------------------------------- - - !======================================================================= - !subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) - !----------------------------------------------------------------------- - !*DESCRIPTION: - ! Read ascii file containing table of kinetic profiles ni, ne, ti, - ! te, and omegaE, then form kin spline containing some additional - ! information (krook nui,nue). - ! - ! Assumes table consists of 6 columns: psi_n, n_i(m^-3), n_e(m^-3), - ! T_i(eV), T_e(eV), omega_E(rad/s). File can (nearly) arbitrary - ! header and/or footer, with the exception that no lines start with - ! a number. - ! - !*ARGUMENTS: - ! file : character(256) (in) - ! File path. - ! zi : integer - ! Ion charge in fundamental units - ! zimp : integer - ! Impurity ion charge in fundamental units - ! mi : integer - ! Ion mass in fundamental units (mass proton) - ! mimp : integer - ! Impurity ion mass in fundamental units - ! wefac : real - ! Direct multiplier for omegaE profiles - ! wpfac : real - ! Scaling of rotation profile, done via manipulation of omegaE - ! write_log : bool - ! Writes kinetic spline to log file - ! - !----------------------------------------------------------------------- + $ inQ_i_arr(msing), + $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), + $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), + $ omegas_arr(msing),omegas_e_arr(msing),omegas_i_arr(msing), + $ outer_delta_arr(msing)) + ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), + $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), + $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) + + ALLOCATE(jacs(0:mthsurf),delpsi(0:mthsurf), + $ rsurf(0:mthsurf),asurf(0:mthsurf)) c----------------------------------------------------------------------- c set up kin c----------------------------------------------------------------------- @@ -296,196 +340,94 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, wefac = 1.0 wpfac = 1.0 e=1.6021917e-19 - pi = 3.1415926535897932 - twopi = 2*pi - chi1 = twopi*psio(1) + chi1 = twopi*my_psio(1) CALL read_kin(infile,zi,zimp,mi,mimp,nfac, $ tfac,wefac,wpfac,.false.) - ! manually set the perturbed equilibrium displacements - ! use false flat xi and xi' for equal weighting - - !CALL spline_int(sq) - !WRITE(*,*)"sq=",sq - !WRITE(*,*)"mpsi=",mpsi - !CALL spline_alloc(sq,mpsi,4) - - !CALL spline_int(sq) - - !CALL timer(0,out_unit) - !CALL equil_read(out_unit) - !CALL equil_out_global - !CALL equil_out_qfind - - !if(.not. sq%allocated) - !$ stop 'ERROR: Cannot define geometric splines without sq' - - !CALL set_geom - !WRITE(*,*)"geom%f(2)=",geom%fs(:,2) - - - - - ! minor radius defined using toroidal flux. Used for threshold - !CALL spline_int(sq) - !qintb = sq%fsi(mpsi, 4) - !psitor(:) = sq%fsi(:, 4) / qintb ! normalized toroidal flux - !rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen - !CALL spline_alloc(sr,mpsi,1) - !sr%xs = sq%xs - !sr%fs(:, 1) = rhotor(:) - !CALL spline_fit(sr,"extrap") - - - !WRITE(*,*)"twopi*psio / (pi * bt0)=",twopi*psio / (pi * bt0) - !WRITE(*,*)"sq%fsi(:, 4)=",sq%fsi(:, 4) - !WRITE(*,*)"sq%fsi(:, 4)*twopi*psio / (pi * bt0)=" - !$(twopi*psio / (pi * bt0))* - !DO i=1,mpsi - ! WRITE(*,*)"spline(i)=",sq%fsi(i, 4) - ! WRITE(*,*)"(i)=",SIZE(SQRT(sq%fsi(i, 4)*(twopi*psio / - !$(pi * bt0)))) - ! my_psitor = REAL(sq%fsi(i, 4)) - !WRITE(*,*)"my_psitor(1)=",my_psitor(1) - !WRITE(*,*)"(my_psitor(1))=",my_psitor(1) - !WRITE(*,*)"(my_rhotor(i))=",my_rhotor(i) - !my_rhotor(i)=SQRT(my_psitor(1)*(twopi*psio / - !$(pi * bt0))) - - !ENDDO - !WRITE(*,*)"rhotor=",rhotor + CALL equil_read(out_unit) c----------------------------------------------------------------------- c loop across singular surfaces, evaluate spline quantities. c----------------------------------------------------------------------- - ! j_c is j_c/(chi1*sq%f(4)) + DO ising=1,msing - !resnum(ising)=NINT(singtype(ising)%q*nn)-mlow+1 - !respsi=singtype(ising)%psifac - !CALL spline_eval(sq,respsi,1) + respsi = psi_n_rational(ising) WRITE(*,*)"respsi=",respsi - WRITE(*,*)"chi1=",chi1 + WRITE(*,*)"ising=",ising + + firstsurf = .TRUE. + unitfun = 1 + + ! Minor radius! + a_surf = issurfint(unitfun,mthsurf,respsi,3,1, + $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) -c----------------------------------------------------------------------- -c prepare layer analysis. -c----------------------------------------------------------------------- - !resm = mfac(resnum(ising)) - !CALL spline_eval(sr,respsi,1) - CALL spline_eval(kin,respsi,1) - !CALL spline_eval(geom,respsi,1) c----------------------------------------------------------------------- c SLAYER inputs for sing surface c----------------------------------------------------------------------- - - + CALL spline_eval(kin,respsi,1) omega_i=-twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) $ -twopi*kin%f1(3)/(e*zi*chi1) omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) - WRITE(*,*)"omega_i=",omega_i WRITE(*,*)"omega_e=",omega_e - ! Here's where I'm getting these from - !CALL gpec_slayer(kin%f(2),kin%f(4)/e,kin%f(1),kin%f(3)/e, - !$ kin%f(5),kin%f(9),omega_e,omega_i,sq%f(4),sq%f1(4), - !$ bt0,sr%f1(1),ro,mi,slayer_inpr,resm,nn,ascii_flag, - !$ delta_s,psi0,jxb,omega_sol,br_th) - - !singtype(ising)%q = q(ising)? - !resnum=NINT(singtype(ising)%q*nn)-mlow+1 - !shear(ising)=mfac(resnum)*sq%f1(4)/sq%f(4)**2 - n_e = kin%f(2) t_e = kin%f(4)/e n_i = kin%f(1) t_i = kin%f(3)/e zeff = kin%f(9) omega = kin%f(5) - qval = q_rational(ising)!sq%f(4) - sval = shear(ising) ! SHEAR SO MUCH SMALLER THAN BEFORE???? 500 puts it on the root - bt = bt0(1) - rs = 0.167 - R0 = ro(1) - mu_i = mi + my_qval = q_rational(ising)!sq%f(4) + my_sval = shear(ising) ! SHEAR SO MUCH SMALLER THAN BEFORE???? 500 puts it on the root + my_bt = my_bt0(1) + my_rs = a_surf + R_0 = r_o(1) + mu_i = 2.0 + + eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + inpr = slayer_inpr - !!! TEMPORARY - !mm=2 - !nn=1 - !mr = real(mm,4) - !nr = real(nn,4) + ne_arr(ising) = n_e + te_arr(ising) = t_e + ni_arr(ising) = n_i + ti_arr(ising) = t_i + zeff_arr(ising) = zeff + bt_arr(ising) = my_bt + rs_arr(ising) = my_rs + R0_arr(ising) = R_0 + mu_i_arr(ising) = mu_i + mms = resm(ising) nns = nn(1) mrs = real(mms,4) nrs = real(nns,4) - WRITE(*,*)"nns=",nns - WRITE(*,*)"mms=",mms - - WRITE(*,*)"n_e=",n_e - WRITE(*,*)"t_e=",t_e - WRITE(*,*)"n_i=",n_i - WRITE(*,*)"t_i=",t_i - WRITE(*,*)"zeff=",zeff - WRITE(*,*)"omega=",omega - WRITE(*,*)"qval=",qval - WRITE(*,*)"sval=",sval - WRITE(*,*)"bt=",bt - WRITE(*,*)"rs=",rs - WRITE(*,*)"R0=",R0 - WRITE(*,*)"mu_i=",mu_i - WRITE(*,*)"inpr=",inpr - WRITE(*,*)"dp_diagonal(ising)=",dp_diagonal - ! String representations of the m and n mode numbers - !IF (nns<10) THEN - ! WRITE(UNIT=sn,FMT='(I1)') nns - ! sn=ADJUSTL(sn) - !ELSE - ! WRITE(UNIT=sn,FMT='(I2)') nns - !ENDIF - !IF (mms<10) THEN - ! WRITE(UNIT=sm,FMT='(I1)') mms - ! sm=ADJUSTL(sm) - !ELSEIF (mms<100) THEN - ! WRITE(UNIT=sm,FMT='(I2)') mms - ! sm=ADJUSTL(sm) - !ELSE - ! WRITE(UNIT=sm,FMT='(I3)') mms - !ENDIF - - inpe=0.0 ! Waybright added this - - WRITE(*,*)"lnLamb=",lnLamb - WRITE(*,*)"mu_i=",mu_i - WRITE(*,*)"m_p=",m_p + nns_arr = nn(1) + + inpe=0.0!0.0165*inpr ! Waybright added this tau= t_i/t_e ! ratio of ion to electron temperature tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) rho=(mu_i*m_p)*n_e ! mass density - - - - b_l=(nrs/mrs)*nrs*sval*bt/R0 ! characteristic magnetic field - WRITE(*,*)"b_l=",b_l + b_l=(nrs/mrs)*nrs*my_sval*my_bt/R_0 ! characteristic magnetic field v_a=b_l/(mu0*rho)**0.5 ! alfven velocity, B_L IS BROKEN - rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. + rho_s=1.02e-4*(mu_i*t_e)**0.5/my_bt ! ion Lamour by elec. Temp. - tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! alfven time across surface - tau_r=mu0*rs**2.0/eta ! resistive time scale + tau_h=R_0*(mu0*rho)**0.5/(nns*my_sval*my_bt) ! alfven time across surface + tau_r=mu0*my_rs**2.0/eta ! resistive time scale tau_v=tau_r/inpr ! rho*rs**2.0/visc ! viscous time scale - WRITE(*,*)"tau_v=",tau_v ! this one must be anomalous. calculated back from pr. - visc= rho*rs**2.0/tau_v + visc= rho*my_rs**2.0/tau_v lu=tau_r/tau_h ! Lundquist number - WRITE(*,*)"lu=",lu Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole @@ -493,19 +435,16 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i - WRITE(*,*)"Q=",Q ! This is the most critical parameter - ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. + ds=lu**(1.0/3.0)*rho_s/my_rs ! conversion based on Cole. - lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 + lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/my_bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 - WRITE(*,*)"c_beta=",c_beta - delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes - WRITE(*,*)"delta_n=",delta_n + delta_n=lu**(1.0/3.0)/my_rs ! norm factor for delta primes - qval_arr(ising) = qval + qval_arr(ising) = INT(my_qval) inQ_arr(ising)=Q inQ_e_arr(ising)=Q_e inQ_i_arr(ising)=Q_i @@ -514,33 +453,29 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr - inpe_arr(ising) = 0.0 !!! TEMPORARY? + inpe_arr(ising) = inpe!0.0!0.0165*inpr !!! TEMPORARY? omegas_arr(ising) = omega + omegas_e_arr(ising) = omega_e + omegas_i_arr(ising) = omega_i outer_delta_arr(ising) = dp_diagonal(ising) ENDDO - !WRITE(*,*)"qval_arr=",qval_arr - !WRITE(*,*)"inQ_arr=",inQ_arr - !WRITE(*,*)"inQ_e_arr=",inQ_e_arr - !WRITE(*,*)"inQ_i_arr=",inQ_i_arr - !WRITE(*,*)"inc_beta_arr=",inc_beta_arr - !WRITE(*,*)"inds_arr=",inds_arr - !WRITE(*,*)"intau_arr=",intau_arr - !WRITE(*,*)"Q0_arr=",Q0_arr - !!WRITE(*,*)"inpr_arr=",inpr_arr - !WRITE(*,*)"inpe_arr=",inpe_arr - !WRITE(*,*)"omegas_arr=",omegas_arr - !WRITE(*,*)"outer_delta_arr=",outer_delta_arr - - !CALL spline_dealloc(sr) - !CALL cspline_dealloc(fsp_sol) - !CALL gpeq_dealloc + WRITE(*,*)"zeff_arr=",zeff_arr + + CALL slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, + $ zeff_arr,shear,bt_arr,rs_arr,R0_arr,mu_i_arr,resm,nns_arr, + $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr, + $ intau_arr,inpr_arr,inpe_arr,omegas_arr, + $ outer_delta_arr) + c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- RETURN + + END SUBROUTINE build_inputs END MODULE layerinputs_mod \ No newline at end of file diff --git a/slayer/makefile b/slayer/makefile index 29a36dfd3..f6dcca4ad 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -50,7 +50,6 @@ slayer: $(OBJS) cp -f slayer ../bin # dependencies - params.o : sglobal.o delta.o : sglobal.o gslayer.o : sglobal.o delta.o diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 43343bf6d..403b3b6fb 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -12,7 +12,7 @@ MODULE sglobal_mod $ delta_n,layfac,Qconv COMPLEX(r8) :: Q - REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, + REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6022e-19, $ kval=1.3807e-23,lnLamb=17.0 diff --git a/slayer/slayer.f b/slayer/slayer.f index 0ad1047be..092cf5492 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -13,7 +13,7 @@ PROGRAM slayer USE sglobal_mod - USE params_mod + !USE params_mod USE delta_mod, ONLY: riccati,riccati_out, $ parflow_flag,PeOhmOnly_flag @@ -24,7 +24,8 @@ PROGRAM slayer IMPLICIT NONE CHARACTER(512) :: infile,ncfile - INTEGER :: i,j,k,inum,jnum,knum,inn,ReQ_num,ImQ_num,n_k + INTEGER :: i,j,k,inum,jnum,knum,inn, + $ ReQ_num,ImQ_num,n_k,scan_radius INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -49,16 +50,16 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl, $ bal, $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes, - $ inQs_log - REAL(r8), DIMENSION(:), ALLOCATABLE :: qval_arr,inQ_e_arr, + $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, $ inpe_arr,omegas_arr,inQ_arr, $ psi_n_rational + INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: $ outer_delta_arr - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, $ inQs_left,inQs_right REAL(r8) :: spot, slayer_inpr @@ -73,7 +74,8 @@ PROGRAM slayer $ ncfile,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, $ inQ_i,inpr,inpe,inc_beta,inds,intau,inlu,Q0,delta_n_p - NAMELIST/slayer_control/inum,jnum,knum,QPscan_flag,QPscan2_flag, + NAMELIST/slayer_control/inum,jnum,knum,ReQ_num,ImQ_num, + $ scan_radius,QPscan_flag,QPscan2_flag, $ QPescan_flag,QDscan2_flag,Qbscan_flag,Qscan_flag, $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag @@ -115,8 +117,9 @@ PROGRAM slayer inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=100 ! resolution for stab. scan along Re(Q) axis - ImQ_num=200 ! resolution for stab. scan along Im(Q) axis + ReQ_num=350 ! resolution for stab. scan along Re(Q) axis + ImQ_num=350 ! resolution for stab. scan along Im(Q) axis + scan_radius = 3 in_unit=1 out_unit=2 out2_unit=3 @@ -139,8 +142,6 @@ PROGRAM slayer input_flag=.FALSE. infile="" ncfile="" - !ncfile="/fusion/projects/codes/gpec/users/burgessd/GPEC/bin/ - !$stride_output_147131.nc" verbose=.TRUE. ascii_flag=.TRUE. bin_flag=.TRUE. @@ -279,58 +280,6 @@ PROGRAM slayer c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- IF (growthrates_flag) THEN - !n_k=2 - ! ALLOCATE(mms(n_k),nns(n_k),prs(n_k),pes(n_k), - !$ n_es(n_k),t_es(n_k),t_is(n_k),omegas(n_k), - !$ l_ns(n_k),l_ts(n_k),qvals(n_k),svals(n_k), - !$ bts(n_k),rss(n_k),R0s(n_k),mu_is(n_k),zeffs(n_k), - !$ outer_deltas(n_k)) - ! Approximate data for q=2 and q=3, for testing only - ! These are of the form needed to output from - ! upcoming build_inputs.f script, interfacing with equil.f - ! Kinetic inputs (old) - !mms = (/2, 2 /) - !nns = (/1, 1 /) - !prs = (/0.1, 0.005 /) - !pes = (/0.0, 0.0 /) - !n_es = (/7.765e+17, 9.999e+16 /) - !t_es = (/26.0, 2.0 /) - !t_is = (/26.0, 2.0 /) - !l_ns = (/0.2, 0.2 /) - !l_ts = (/0.1, 0.1 /) - !svals = (/729.5, 500.5 /) - !bts = (/1.0, 1.0 /) - !rss = (/0.16, 0.2 /) - !R0s = (/2.0, 2.0 /) - !mu_is = (/2.0, 2.0 /) - !zeffs = (/2.0, 2.0 /) - - - !q_arr = (/2.0, 3.0 /) - !inQ_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) - !inQ_e_arr = (/ 1.9127E-005, 4.6109E-007 /) - !inQ_i_arr = (/ -1.9127E-005, -4.6109E-007 /) - !inc_beta_arr= (/ 3.6809E-003, 3.6635E-004 /) - !inds_arr = (/ 1.4394, 0.1276 /) - !intau_arr = (/ 1.0, 1.0 /) - !inQ0_arr = (/ (2.6744E-003,0.0), (5.5874E-004,0.0) /) - !eta_s = (/ 6.6907E-006, 3.1361E-004 /) - !S_s = (/ 30696266.0, 1956419.0 /) - !inpr_arr = (/0.1, 0.005 /) - !inpe_arr = (/0.0, 0.0 /) - !omegas_arr = (/54530.0, 54530.0 /) - !outer_delta_arr = (/(27.15,0.1), (20.15,0.1) /) - - !CHARACTER(len=256) :: filename = "stride_output_n1.nc" - !REAL(r8), DIMENSION(:), ALLOCATABLE :: q_rational, q_rational_coords - !REAL(r8), DIMENSION(:), ALLOCATABLE :: Delta_prime, Delta_prime_coords - - - - ! WRITE(*,*)"growthrates=",q_rational - - !CALL read_stride_netcdf(filename, q_rational, q_rational_coords, - !$ Delta_prime, Delta_prime_coords) WRITE(*,*)"infile=",infile WRITE(*,*)"ncfile=",ncfile @@ -340,32 +289,17 @@ PROGRAM slayer $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, $ omegas_arr,outer_delta_arr) - WRITE(*,*)"qval_arr=",qval_arr - WRITE(*,*)"inQ_arr=",inQ_arr - WRITE(*,*)"inQ_e_arr=",inQ_e_arr - WRITE(*,*)"inQ_i_arr=",inQ_i_arr - WRITE(*,*)"inc_beta_arr=",inc_beta_arr - WRITE(*,*)"inds_arr=",inds_arr - WRITE(*,*)"intau_arr=",intau_arr - WRITE(*,*)"inQ0_arr=",inQ0_arr - WRITE(*,*)"inpr_arr=",inpr_arr - WRITE(*,*)"inpe_arr=",inpe_arr - WRITE(*,*)"omegas_arr=",omegas_arr - WRITE(*,*)"outer_delta_arr=",outer_delta_arr - WRITE(*,*)"made it to gammamatch=",qval_arr - - !stop + WRITE(*,*)"Safety factor values=",qval_arr + WRITE(*,*)"inQ values=",inQ_arr + WRITE(*,*)"Prantdl numbers=",inpr_arr + WRITE(*,*)"Electron viscosities=",inpe_arr + WRITE(*,*)"Omega ExB values=",omegas_arr + WRITE(*,*)"outer region deltaprimes=",outer_delta_arr CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr, - $ inc_beta_arr,inds_arr,intau_arr, - $ inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,outer_delta_arr, - $ ReQ_num,ImQ_num,growthrates,growthrate_err) - - WRITE(*,*)"growthrates=",growthrates - WRITE(*,*)"growthrate error=",growthrate_err - + $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, + $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, + $ outer_delta_arr,ReQ_num,ImQ_num,scan_radius) ENDIF c----------------------------------------------------------------------- c find solutions based on simple torque balance. diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 33d92c593..b2125d765 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -52,24 +52,23 @@ END SUBROUTINE check c declarations. c ----------------------------------------------------------------------- SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, - $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr,psi_n_rational, - $ all_Re_deltas,all_slices) + $ inQs,iinQs,growthrates,omegas_arr,inQ_arr,inQ_e_arr, + $ inQ_i_arr,psi_n_rational, + $ all_Re_deltas,all_inQs) INTEGER, INTENT(IN) :: msing,ReQ_n,ImQ_n - REAL(r8), DIMENSION(:), INTENT(IN) :: qval_arr, - $ inQs_log,iinQs,growthrates,omegas_arr,inQ_arr, - $ psi_n_rational!,shear - REAL(r8), DIMENSION(:,:), INTENT(IN) :: all_slices + REAL(r8), DIMENSION(:), INTENT(IN) :: + $ inQs,iinQs,growthrates,omegas_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,psi_n_rational!,shear + INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr + REAL(r8), DIMENSION(:,:), INTENT(IN) :: all_inQs REAL(r8), DIMENSION(:,:,:), INTENT(IN) :: all_Re_deltas INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, - $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id, - $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id - !COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et - !CHARACTER(2) :: sn + $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, + $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id,inQs_id CHARACTER(64) :: ncfile - ! INTEGER :: ising,jsing - !COMPLEX(r8), DIMENSION(msing,msing) :: ap,bp,gammap,deltap LOGICAL, PARAMETER :: debug_flag = .FALSE. + CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- @@ -94,24 +93,13 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, IF(debug_flag) PRINT *," - Defining netcdf globals" CALL check( nf90_put_att(ncid,nf90_global,"title", $ "SLAYER outputs")) - ! define global attributes - !CALL check( nf90_put_att(ncid,nf90_global,'ro', ro)) - !CALL check( nf90_put_att(ncid,nf90_global,'psio', psio)) - !CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) !CALL check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) !CALL check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) !CALL check( nf90_put_att(ncid,nf90_global,"n", nn)) - !CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + ! define global attributes ! define dimensions IF(debug_flag) PRINT *," - Defining dimensions in netcdf" - !CALL check( nf90_def_dim(ncid, "i", 2, i_dim) ) - !CALL check( nf90_def_var(ncid, "i", nf90_int, i_dim, i_id) ) - !CALL check( nf90_def_dim(ncid, "m", mpert, m_dim) ) - !CALL check( nf90_def_var(ncid, "m", nf90_int, m_dim, m_id) ) - !CALL check( nf90_def_dim(ncid, "mode", mpert, mo_dim) ) - !CALL check( nf90_def_var(ncid, "mode", nf90_int, mo_dim, mo_id)) - !CALL check( nf90_def_dim(ncid, "psi_n", sq%mx+1, p_dim) ) - !CALL check( nf90_def_var(ncid, "psi_n", nf90_double, p_dim, p_id)) IF(msing>0)THEN CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, @@ -129,6 +117,10 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, $ qsing_dim,omegas_id)) CALL check( nf90_def_var(ncid,"Q",nf90_double, $ qsing_dim,Q_id)) + CALL check( nf90_def_var(ncid,"Q_e",nf90_double, + $ qsing_dim,Q_e_id)) + CALL check( nf90_def_var(ncid,"Q_i",nf90_double, + $ qsing_dim,Q_i_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ qsing_dim,pr_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, @@ -138,22 +130,12 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" - !CALL check( nf90_def_var(ncid, "f", nf90_double, p_dim, f_id) ) - !CALL check( nf90_def_var(ncid, "mu0p", nf90_double, p_dim, mu_id)) - !CALL check( nf90_def_var(ncid, "dvdpsi", nf90_double,p_dim,dv_id)) - !CALL check( nf90_def_var(ncid, "q", nf90_double, p_dim, q_id) ) - !CALL check( nf90_def_var(ncid, "di", nf90_double, p_dim, di_id) ) - !CALL check( nf90_def_var(ncid, "dr", nf90_double, p_dim, dr_id) ) - !CALL check( nf90_def_var(ncid, "ca1", nf90_double, p_dim, ca_id)) - !CALL check( nf90_def_var(ncid, "W_p_eigenvector", nf90_double, - !$ (/m_dim, mo_dim, i_dim/), wp_id) ) - !CALL check( nf90_def_var(ncid, "W_p_eigenvalue", nf90_double, - !$ (/mo_dim, i_dim/), wpv_id) ) + IF(msing>0)THEN CALL check( nf90_def_var(ncid, "Re_Delta", nf90_double, $ (/ReQ_dim, ImQ_dim, qsing_dim/), dp_id) ) - CALL check( nf90_def_var(ncid, "slices", nf90_double, - $ (/ImQ_dim, qsing_dim/), slice_id) ) + CALL check( nf90_def_var(ncid, "inQs", nf90_double, + $ (/ReQ_dim, qsing_dim/), inQs_id) ) ENDIF ! end definitions CALL check( nf90_enddef(ncid) ) @@ -162,19 +144,18 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, c ----------------------------------------------------------------------- ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL check( nf90_put_var(ncid,ReQ_id, inQs_log)) + CALL check( nf90_put_var(ncid,ReQ_id, inQs)) CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) CALL check( nf90_put_var(ncid,gamma_id, growthrates)) CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) + CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) + CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL check( nf90_put_var(ncid,qr_id, qval_arr)) - !CALL check( nf90_put_var(ncid,shear_id, shear)) ! IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" - CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas))!, - !$(/ReQ_n,ImQ_n,msing/))) - CALL check( nf90_put_var(ncid,slice_id,all_slices))!, - !$(/ImQ_n,msing/))) + CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas)) + CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- @@ -185,4 +166,197 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, c ----------------------------------------------------------------------- RETURN END SUBROUTINE slayer_netcdf_out +c ----------------------------------------------------------------------- +c declarations. +c ----------------------------------------------------------------------- + SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, + $ zeff_arr,shear,bt_arr,rs_arr,R0_arr,mu_i_arr,resm,nns_arr, + $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr, + $ intau_arr,inpr_arr,inpe_arr,omegas_arr, + $ outer_delta_arr) + + INTEGER, INTENT(IN) :: msing + REAL(r8), DIMENSION(:), INTENT(IN) :: + $ ne_arr,te_arr,ni_arr,ti_arr,zeff_arr,shear,bt_arr,rs_arr, + $ R0_arr,mu_i_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr, + $ inpe_arr,omegas_arr,outer_delta_arr + INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr,resm,nns_arr + + INTEGER :: i, ncid,r_id,qsing_dim,qsing_id,msing_id, + $ i_dim,ne_id,te_id,ni_id,ti_id,zeff_id,shear_id,bt_id,rs_id, + $ R0_id,mu_i_id,resm_id,nns_id,inQ_id,inQ_e_id,inc_beta_id, + $ inds_id,qval_id,inQ_i_id,qr_id, + $ intau_id,inpr_id,inpe_id,omegas_id,outer_delta_id, + $ omegas_e_id,omegas_i_id + + CHARACTER(64) :: ncfile + + LOGICAL, PARAMETER :: debug_flag = .FALSE. + CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' +c ----------------------------------------------------------------------- +c set variables +c ----------------------------------------------------------------------- + ne_id=0 + te_id=0 + ni_id=0 + ti_id=0 + zeff_id=0 + shear_id=0 + bt_id=0 + rs_id=0 + R0_id=0 + mu_i_id=0 + resm_id=0 + nns_id=0 + inQ_id=0 + inQ_e_id=0 + inc_beta_id=0 + inds_id=0 + qval_id=0 + inQ_i_id=0 + qr_id=0 + intau_id=0 + inpr_id=0 + inpe_id=0 + omegas_id=0 + outer_delta_id=0 + omegas_e_id=0 + omegas_i_id=0 + + IF(debug_flag) PRINT *,"Called slayer_netcdf_inputs" + IF (nn<10) THEN + WRITE(UNIT=sn,FMT='(I1)')nn + sn=ADJUSTL(sn) + ELSE + WRITE(UNIT=sn,FMT='(I2)')nn + ENDIF + ncfile = "slayer_inputs_n"//TRIM(sn)//".nc" + IF(debug_flag) PRINT *, ncfile +c ----------------------------------------------------------------------- +c open files +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Creating netcdf files" + CALL check( nf90_create(ncfile, + $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) +c ----------------------------------------------------------------------- +c define global file attributes +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Defining netcdf globals" + CALL check( nf90_put_att(ncid,nf90_global,"title", + $ "SLAYER outputs")) + ! define global attributes + !CALL check( nf90_put_att(ncid,nf90_global,'ro', ro)) + !CALL check( nf90_put_att(ncid,nf90_global,'psio', psio)) + !CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) + !CALL check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) + !CALL check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) + !CALL check( nf90_put_att(ncid,nf90_global,"n", nn)) + CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + ! define dimensions + IF(debug_flag) PRINT *," - Defining dimensions in netcdf" + + IF(msing>0)THEN + CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, + $ qsing_id)) + + CALL check( nf90_def_var(ncid,"ne",nf90_double, + $ qsing_dim,ne_id)) + CALL check( nf90_def_var(ncid,"te",nf90_double, + $ qsing_dim,te_id)) + CALL check( nf90_def_var(ncid,"ni",nf90_double, + $ qsing_dim,ni_id)) + CALL check( nf90_def_var(ncid,"ti",nf90_double, + $ qsing_dim,ti_id)) + CALL check( nf90_def_var(ncid,"zeff",nf90_double, + $ qsing_dim,zeff_id)) + CALL check( nf90_def_var(ncid,"shear",nf90_double, + $ qsing_dim,shear_id)) + CALL check( nf90_def_var(ncid,"bt",nf90_double, + $ qsing_dim,bt_id)) + CALL check( nf90_def_var(ncid,"rs",nf90_double, + $ qsing_dim,rs_id)) + CALL check( nf90_def_var(ncid,"R0",nf90_double, + $ qsing_dim,R0_id)) + CALL check( nf90_def_var(ncid,"mu_i",nf90_double, + $ qsing_dim,mu_i_id)) + CALL check( nf90_def_var(ncid,"resm",nf90_int, + $ qsing_dim,resm_id)) + CALL check( nf90_def_var(ncid,"nns_arr",nf90_int, + $ qsing_dim,nns_id)) + CALL check( nf90_def_var(ncid,"qval",nf90_int, + $ qsing_dim,qval_id)) + CALL check( nf90_def_var(ncid,"q_rational",nf90_double, + $ qsing_dim,qr_id) ) + CALL check( nf90_def_var(ncid,"Q",nf90_double, + $ qsing_dim,inQ_id)) + CALL check( nf90_def_var(ncid,"Q_e",nf90_double, + $ qsing_dim,inQ_e_id)) + CALL check( nf90_def_var(ncid,"Q_i",nf90_double, + $ qsing_dim,inQ_i_id)) + CALL check( nf90_def_var(ncid,"c_beta",nf90_double, + $ qsing_dim,inc_beta_id)) + CALL check( nf90_def_var(ncid,"ds",nf90_double, + $ qsing_dim,inds_id)) + CALL check( nf90_def_var(ncid,"tau",nf90_double, + $ qsing_dim,intau_id)) + CALL check( nf90_def_var(ncid,"pr",nf90_double, + $ qsing_dim,inpr_id)) + CALL check( nf90_def_var(ncid,"pe",nf90_double, + $ qsing_dim,inpe_id)) + CALL check( nf90_def_var(ncid,"omegas",nf90_double, + $ qsing_dim,omegas_id)) + ! CALL check( nf90_def_var(ncid,"omegas_e",nf90_double, + !$ qsing_dim,omegas_e_id)) + ! CALL check( nf90_def_var(ncid,"omegas_i",nf90_double, + !$ qsing_dim,omegas_i_id)) + CALL check( nf90_def_var(ncid,"outer_delta",nf90_double, + $ qsing_dim,outer_delta_id)) + ENDIF + ! define variables + IF(debug_flag) PRINT *," - Defining variables in netcdf" + ! end definitions + CALL check( nf90_enddef(ncid) ) +c ----------------------------------------------------------------------- +c set variables +c ----------------------------------------------------------------------- + ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" + CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) + CALL check( nf90_put_var(ncid,ne_id, ne_arr)) + CALL check( nf90_put_var(ncid,ni_id, ni_id)) + CALL check( nf90_put_var(ncid,te_id, te_id)) + CALL check( nf90_put_var(ncid,ti_id, ti_id)) + CALL check( nf90_put_var(ncid,zeff_id, zeff_arr)) + CALL check( nf90_put_var(ncid,shear_id, shear)) + CALL check( nf90_put_var(ncid,bt_id, bt_arr)) + CALL check( nf90_put_var(ncid,rs_id, rs_arr)) + CALL check( nf90_put_var(ncid,R0_id, R0_arr)) + CALL check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) + CALL check( nf90_put_var(ncid,resm_id, resm)) + CALL check( nf90_put_var(ncid,nns_id, nns_arr)) + !CALL check( nf90_put_var(ncid,qval_id, qval_arr)) + CALL check( nf90_put_var(ncid,inQ_id, inQ_arr)) + CALL check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) + CALL check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) + CALL check( nf90_put_var(ncid,inc_beta_id, inc_beta_arr)) + CALL check( nf90_put_var(ncid,inds_id, inds_arr)) + CALL check( nf90_put_var(ncid,intau_id, intau_arr)) + CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) + CALL check( nf90_put_var(ncid,inpe_id, inpe_arr)) + CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) + !CALL check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) + !CALL check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) + CALL check( nf90_put_var(ncid,outer_delta_id,outer_delta_arr)) + +c ----------------------------------------------------------------------- +c close file +c ----------------------------------------------------------------------- + IF(debug_flag) PRINT *," - Closing netcdf file" + CALL check( nf90_close(ncid) ) +c ----------------------------------------------------------------------- +c terminate. +c ----------------------------------------------------------------------- + RETURN + END SUBROUTINE slayer_netcdf_inputs END MODULE slayer_netcdf_mod \ No newline at end of file From ca094bb4d34ce3ae33aaae2c93d2bd36be8e8084 Mon Sep 17 00:00:00 2001 From: d-burg Date: Sat, 27 Jul 2024 19:55:23 -0700 Subject: [PATCH 009/305] WIP: new arraylike inPr handling --- slayer/layerinputs.f | 59 +++++++++++++++++++++++------------------- slayer/slayer.f | 2 +- stride/stride_netcdf.f | 6 ++++- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index cf3b0d675..3628358f9 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -41,13 +41,13 @@ END SUBROUTINE check c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ dp_diagonal, q_rational, psi_n_rational, shear, - $ r_o,my_bt0,my_psio,mpsi,nn,resm) + $ r_o,my_bt0,my_psio,mpsi,nn,resm,prandtl) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, - $ psi_n_rational, shear + $ psi_n_rational, shear, prandtl REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, $ my_psio,mpsi INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm @@ -58,7 +58,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Internal Variables INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, - $ msing_id,nn_id,resm_id ! Explicit kind for NetCDF variables + $ msing_id,nn_id,resm_id,prandtl_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i @@ -67,7 +67,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) - WRITE(*,*)"ncfile=",ncfile CALL check(stat) ! Error handling stat = nf90_inquire_attribute(ncid,msing_id,"msing", @@ -82,7 +81,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Allocate Arrays (based on dimension) ALLOCATE(dp_diagonal(msing),q_rational(msing), $ psi_n_rational(msing),shear(msing), - $ resm(msing)) + $ resm(msing),prandtl(msing)) ALLOCATE(delta_prime(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) @@ -90,7 +89,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, stat = nf90_inquire_attribute(ncid,bt0_id,"bt0",len=bt0_len) CALL check(stat) - bt0_id=0 !!!!! THIS COULD BE A PROBLEM stat = nf90_inquire_attribute(ncid,psio_id,"psio",len=psio_len) CALL check(stat) @@ -100,6 +98,12 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) CALL check(stat) + bt0_id=0 !!!!! THIS COULD BE A PROBLEM + nn_id=0 + mpsi_id=0 + psio_id=0 + ro_id=0 + ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), $ my_psio(INT(psio_len)), $mpsi(INT(mpsi_len)),nn(INT(nn_len))) @@ -113,9 +117,10 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, CALL check(stat) stat = nf90_inq_varid(ncid, "shear", shear_id) CALL check(stat) + stat = nf90_inq_varid(ncid, "prandtl", prandtl_id) + CALL check(stat) stat = nf90_inq_varid(ncid, "resm", resm_id) CALL check(stat) - ! Get attributes stat = nf90_get_att(ncid, ro_id, "ro", r_o) CALL check(stat) @@ -139,6 +144,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, CALL check(stat) stat = nf90_get_var(ncid, shear_id, shear) CALL check(stat) + stat = nf90_get_var(ncid, prandtl_id, prandtl) + CALL check(stat) stat = nf90_get_var(ncid, resm_id, resm) CALL check(stat) @@ -281,7 +288,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, $ shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr + $ omegas_e_arr,omegas_i_arr,prandtl REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, $ ti_arr,zeff_arr,bt_arr,rs_arr, $ R0_arr,mu_i_arr @@ -299,18 +306,19 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, c Read in STRIDE netcdf c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile, - $ msing,dp_diagonal,q_rational,psi_n_rational, - $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm) - !WRITE(*,*)"msing_out=",msing - !WRITE(*,*)"dp_diagonal=",dp_diagonal - !!!WRITE(*,*)"q_rational=",q_rational - !WRITE(*,*)"psi_n_rational=",psi_n_rational - !WRITE(*,*)"shear=",shear - !WRITE(*,*)"r_o=",r_o - !WRITE(*,*)"my_bt0=",my_bt0 - !WRITE(*,*)"my_psio=",my_psio - !WRITE(*,*)"nn=",nn - !WRITE(*,*)"resm=",resm + $ msing,dp_diagonal,q_rational,psi_n_rational, + $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm,prandtl) + WRITE(*,*)"msing_out=",msing + WRITE(*,*)"dp_diagonal=",dp_diagonal + WRITE(*,*)"q_rational=",q_rational + WRITE(*,*)"psi_n_rational=",psi_n_rational + WRITE(*,*)"shear=",shear + WRITE(*,*)"r_o=",r_o + WRITE(*,*)"my_bt0=",my_bt0 + WRITE(*,*)"my_psio=",my_psio + WRITE(*,*)"nn=",nn + WRITE(*,*)"resm=",resm + WRITE(*,*)"resm=",prandtl mpsi = INT(mpsi_arr(1)) mthsurf = 512 @@ -354,8 +362,6 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, DO ising=1,msing respsi = psi_n_rational(ising) - WRITE(*,*)"respsi=",respsi - WRITE(*,*)"ising=",ising firstsurf = .TRUE. unitfun = 1 @@ -373,7 +379,6 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, $ -twopi*kin%f1(3)/(e*zi*chi1) omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) - WRITE(*,*)"omega_e=",omega_e n_e = kin%f(2) t_e = kin%f(4)/e @@ -390,7 +395,9 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - inpr = slayer_inpr + inpr = prandtl(ising) + + inpe=0.0!0.0165*inpr ! Waybright added this ne_arr(ising) = n_e te_arr(ising) = t_e @@ -409,8 +416,6 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, nns_arr = nn(1) - inpe=0.0!0.0165*inpr ! Waybright added this - tau= t_i/t_e ! ratio of ion to electron temperature tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. rho=(mu_i*m_p)*n_e ! mass density @@ -461,7 +466,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, ENDDO - WRITE(*,*)"zeff_arr=",zeff_arr + !WRITE(*,*)"zeff_arr=",zeff_arr CALL slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, $ zeff_arr,shear,bt_arr,rs_arr,R0_arr,mu_i_arr,resm,nns_arr, diff --git a/slayer/slayer.f b/slayer/slayer.f index 092cf5492..153579355 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -295,7 +295,7 @@ PROGRAM slayer WRITE(*,*)"Electron viscosities=",inpe_arr WRITE(*,*)"Omega ExB values=",omegas_arr WRITE(*,*)"outer region deltaprimes=",outer_delta_arr - + !stop CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 2b5c4936f..6345c8abd 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -61,7 +61,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id + $ shear_id,resm_id,prandtl_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile @@ -179,6 +179,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) + CALL check( nf90_def_var(ncid, "prandtl", nf90_double, r_dim, + $ prandtl_id) ) CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, $ resm_id) ) ENDIF @@ -247,6 +249,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) !ENDDO CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, + $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? + CALL check( nf90_put_var(ncid,prandtl_id, (/(0.0, $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? CALL check( nf90_put_var(ncid,resm_id, resm) ) ! CALL check( nf90_put_var(ncid,shear_id,shear) ) ! GPEC HAS DIFFERENT SHEAR CALC? From a06471b6f545733509f05f11d1306ea1871034fa Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 30 Jul 2024 14:49:51 -0700 Subject: [PATCH 010/305] WIP: dynamic gridding root find backup --- input/slayer.in | 8 +- slayer/gslayer.f | 237 +++++++++++++++++++++++++++++++++++++++-- slayer/layerinputs.f | 24 ++--- slayer/slayer.f | 92 +++++++++++++--- slayer/slayer_netcdf.f | 35 ++++-- 5 files changed, 348 insertions(+), 48 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index ec5858477..3490aab22 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -22,6 +22,7 @@ mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective inpr=1.0 ! Prantdl number + inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -55,14 +56,15 @@ / &SLAYER_OUTPUT verbose=t ! prints progress to terminal - ascii_flag=t ! writes results to ascii files - bin_flag=t ! writes results to binary files + ascii_flag=f ! writes results to ascii files + bin_flag=f ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q growthrates_flag=t ! Calculate growthrates on each rational surface + layer_stabilityscan_flag=t ! Generate Re(Q),Im(Q) scans for each rational surface !bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file - params_check=t ! prints the primary parameters to the terminal when they are calculated + params_check=f ! prints the primary parameters to the terminal when they are calculated / \ No newline at end of file diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 8238a7316..5178eccf7 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -174,6 +174,233 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer + + FUNCTION log_prob(params,inQ,inQ_e,inQ_i,inc_beta,inds, + $ intau,inQ0,inpr,inpe,deltaprime,sigma) RESULT(lp) + real(r8), dimension(2), intent(in) :: params + real(r8), intent(in) :: deltaprime, sigma + real(r8) :: lp, distance + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, + $ intau,inQ0,inpr,inpe + ! Calculate distance from the target value + distance = abs((riccati(params(1), + $ inQ_e, + $ inQ_i, + $ inpr, + $ inc_beta, + $ inds, + $ intau, + $ inpe, + $ iinQ=params(2)) - + $ deltaprime)) + + ! Calculate the log probability (negative log-likelihood of a Gaussian) + lp = -0.5 * (distance / sigma)**2 + RETURN + END FUNCTION log_prob +c----------------------------------------------------------------------- +c Subprogram 2. gamma_stability_scan +c Run grid packed slayer stab. scan around omega_ExB and gamma axes +c----------------------------------------------------------------------- + SUBROUTINE growthrate_search(qval,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,inQ0,inpr,inpe,deltaprime,scan_radius, + $ deltas,inQs,iinQs,roots,growthrate,growthrate_loc) +c----------------------------------------------------------------------- +c Declarations +c----------------------------------------------------------------------- + ! Inputs + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, + $ intau,inQ0,inpr,inpe,deltaprime + INTEGER, INTENT(IN) :: qval,scan_radius +c----------------------------------------------------------------------- +c +c----------------------------------------------------------------------- + real(r8), intent(out), dimension(:,:), ALLOCATABLE :: roots ! Array to store (x, y) pairs of zeros + integer :: num_zeros ! Number of zeros found + real(r8), dimension(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs + integer :: n_fine + real(r8), dimension(:), ALLOCATABLE :: x_s,y_s + real(r8) :: x, y, dx, dy, ric_val, grad_x, grad_y + real(r8), dimension(2) :: start, end, step + real(r8) :: zero_tol, grad_tol, wr, delta + integer :: i, j, k, w, n_steps + logical :: found_zero + real(r8), intent(out) :: growthrate_loc, growthrate ! Output (x, y) pair + + integer :: n_points, idx_1, idx_2 + real(r8) :: x_1, y_1, x_2, y_2, dist, min_dist1, min_dist2 + + REAL(r8), DIMENSION(:,:), ALLOCATABLE, + $ INTENT(OUT) :: deltas + wr = inQ+inQ_e + ! Parameters + zero_tol = abs(0.1*deltaprime) ! Tolerance for zero value + grad_tol = 1.0!abs(0.1*deltaprime) ! Tolerance for steep gradient + n_steps = 100 ! Initial number of steps + n_fine = 10 + if (abs(deltaprime)>50) then + n_fine = n_fine * 2 + end if + ALLOCATE(x_s(n_fine),y_s(n_fine)) + ALLOCATE(inQs(n_steps)) + ALLOCATE(roots(10,2)) + ALLOCATE(deltas(n_steps,n_steps)) + + ! Initial coarse grid search + + dx = 2 * dble(scan_radius) / dble(n_steps) + dy = 2 * dble(scan_radius) / dble(n_steps) + num_zeros = 0 + + inQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) + ALLOCATE(iinQs(n_steps)) + + do i = 1, n_steps + x = inQs(i)!wr - scan_radius + (i - 0.5) * dx + do j = 1, n_steps + !y = -scan_radius + (j - 0.5) * dy + y=-scan_radius+(REAL(j)/n_steps)*(scan_radius+ + $ scan_radius) + iinQs(j) = y + delta = REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, + $ intau,inpe,iinQ=y)) + ric_val = abs(delta - deltaprime) + deltas(i,j) = delta + grad_x = 10!(REAL(riccati(x + dx,inQ_e,inQ_i,inpr,inc_beta, + !$ inds,intau,inpe,iinQ=y)) - + !$ REAL(riccati(x - dx,inQ_e,inQ_i,inpr,inc_beta, + !$ inds,intau,inpe,iinQ=y))) / (2 * dx) + grad_y = 10!(REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, + !$ intau,inpe,iinQ=y + dy)) - + !$ REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, + !$ intau,inpe,iinQ=y - dy))) / (2 * dy) + + !WRITE(*,*) "Point", [x,y] + !WRITE(*,*) "ric_val", ric_val + !WRITE(*,*) "grad_x", grad_x + !WRITE(*,*) "grad_y", grad_y + !WRITE(*,*) "[dx,dy]", [dx,dy] + + + if ((abs(ric_val) < zero_tol) .and. ((abs(grad_x) > + $ grad_tol) .or. (abs(grad_y) > grad_tol))) then + ! Potential zero found, refine with a finer grid + !start = [x - 0.1, y - 0.1] + !end = [x + 0.1, y + 0.1] + !step = (end - start) / 20 + !WRITE(*,*) "Potential zero found", [x,y] + ! WRITE(*,*) "dx", dx + ! WRITE(*,*) "dy", dy + ! WRITE(*,*) "step", step + ! WRITE(*,*) "scan_radius", scan_radius + + x_s = linspace((x-0.1),(x+0.1),n_fine) + y_s = linspace((y-0.1),(y+0.1),n_fine) + found_zero = .false. + do k=1,n_fine + do w=1,n_fine + !x_s = start(1) + (k - 0.5) * step(1) + !y_s = start(2) + (k - 0.5) * step(2) + + !WRITE(*,*) "fine point", [x_s(k),y_s(w)] + !WRITE(*,*) "y_s", y_s + !stop + !WRITE(*,*) "fine scan", k + + ric_val = REAL(riccati(x_s(k),inQ_e,inQ_i, + $ inpr,inc_beta,inds,intau,inpe, + $ iinQ=y_s(w))) - deltaprime + !WRITE(*,*) "ric_val", ric_val + + if (abs(ric_val) < abs(0.01*deltaprime)) then + ! Found a zero within tolerance, store it + num_zeros = num_zeros + 1 + roots(num_zeros, 1) = x + roots(num_zeros, 2) = y + WRITE(*,*) "FOUND ROOT #", num_zeros + found_zero=.true. + !exit ! Exit the inner loop since zero is found + end if + if (found_zero==.true.) then + exit + endif + end do + if (found_zero==.true.) then + exit + endif + end do + end if + end do + end do + + ! 1. Find the index of the (x, y) pair with x closest to wr + n_points = size(roots, 1) + min_dist1 = abs(roots(1, 1) - wr) + idx_1 = 1 + + do i = 2, n_points + dist = abs(roots(i, 1) - wr) + if (dist < min_dist1) then + min_dist1 = dist + idx_1 = i + end if + end do + + x_1 = roots(idx_1, 1) + y_1 = roots(idx_1, 2) + + ! 2. Find the index of the next closest (x, y) pair with y more than 0.5 away from y_1 + min_dist2 = huge(min_dist2) ! Initialize to a very large value + idx_2 = 0 ! Initialize to an invalid index + + do i = 1, n_points + if (i /= idx_1 .and. abs(roots(i, 2) - y_1) > 0.5) then ! Check if it's a different point and far enough in y + dist = abs(roots(i, 1) - wr) + if (dist < min_dist2) then + min_dist2 = dist + idx_2 = i + end if + end if + end do + + if (idx_2 > 0) then ! Check if a valid second point was found + x_2 = roots(idx_2, 1) + y_2 = roots(idx_2, 2) + end if + + ! 3. Apply your specific conditions to select the final (x, y) pair + if (idx_2 > 0 .and. abs(x_1 - x_2) < 0.1) then + if (y_1 < 0 .or. y_2 < 0) then ! Choose the pair with negative y + if (y_1 < 0) then + growthrate_loc = x_1 + growthrate = y_1 + else + growthrate_loc = x_2 + growthrate = y_2 + end if + end if + else ! If the x values are far apart, choose the first pair + growthrate_loc = x_1 + growthrate = y_1 + end if +c----------------------------------------------------------------------- +c +c----------------------------------------------------------------------- + ! Allocate grid packing arrays and 2D complex deltas array + !ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) + + !DO i=0,ReQ_num+1 + ! DO j=0,ImQ_num + ! iinQs(j)=Im_inQ_min+(REAL(j)/ImQ_num)*(Im_inQ_max- + !$ Im_inQ_min) + ! ! Run riccati() at each Q index to give delta + ! deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr, + !$ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ! NOT USING GRID PACKING + ! ENDDO + !ENDDO + + RETURN + END SUBROUTINE growthrate_search c----------------------------------------------------------------------- c Subprogram 2. gamma_stability_scan c Run grid packed slayer stab. scan around omega_ExB and gamma axes @@ -269,7 +496,8 @@ SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr, $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr, - $ ReQ_num,ImQ_num,scan_radius) + $ ReQ_num,ImQ_num,scan_radius,inQs,iinQs, + $ all_Re_deltas,all_inQs) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- @@ -287,7 +515,7 @@ SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, INTEGER :: n_k ! Number of rational surfaces INTEGER :: k,w ! Local variables received from internal subroutines - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs + REAL(r8), DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: inQs,iinQs COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_slices,all_inQs @@ -325,11 +553,6 @@ SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, all_inQs(:,k) = inQs ENDDO - CALL slayer_netcdf_out(n_k,SIZE(inQs),SIZE(iinQs),qval_arr, - $ inQs,iinQs,growthrates,omegas_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,psi_n_rational, - $ all_Re_deltas,all_inQs) - DEALLOCATE(deltas) RETURN diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 3628358f9..92333c392 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -41,13 +41,13 @@ END SUBROUTINE check c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ dp_diagonal, q_rational, psi_n_rational, shear, - $ r_o,my_bt0,my_psio,mpsi,nn,resm,prandtl) + $ r_o,my_bt0,my_psio,mpsi,nn,resm) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, - $ psi_n_rational, shear, prandtl + $ psi_n_rational, shear REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, $ my_psio,mpsi INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm @@ -58,7 +58,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Internal Variables INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, - $ msing_id,nn_id,resm_id,prandtl_id ! Explicit kind for NetCDF variables + $ msing_id,nn_id,resm_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i @@ -81,7 +81,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Allocate Arrays (based on dimension) ALLOCATE(dp_diagonal(msing),q_rational(msing), $ psi_n_rational(msing),shear(msing), - $ resm(msing),prandtl(msing)) + $ resm(msing)) ALLOCATE(delta_prime(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) @@ -117,8 +117,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, CALL check(stat) stat = nf90_inq_varid(ncid, "shear", shear_id) CALL check(stat) - stat = nf90_inq_varid(ncid, "prandtl", prandtl_id) - CALL check(stat) stat = nf90_inq_varid(ncid, "resm", resm_id) CALL check(stat) ! Get attributes @@ -144,8 +142,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, CALL check(stat) stat = nf90_get_var(ncid, shear_id, shear) CALL check(stat) - stat = nf90_get_var(ncid, prandtl_id, prandtl) - CALL check(stat) stat = nf90_get_var(ncid, resm_id, resm) CALL check(stat) @@ -250,7 +246,7 @@ END FUNCTION issurfint c subprogram 3. build_inputs. c build input arrays for SLAYER c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, + SUBROUTINE build_inputs(infile,ncfile,inpr_prof, $ growthrate_flag, $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr, @@ -260,7 +256,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, c declarations. c----------------------------------------------------------------------- LOGICAL, INTENT(IN) :: growthrate_flag - REAL(r8), INTENT(IN) ::slayer_inpr + REAL(r8), DIMENSION(8), INTENT(IN) :: inpr_prof LOGICAL :: firstsurf REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf, $ ising @@ -276,7 +272,6 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, $ my_qval,my_sval,my_bt,my_rs,zeff,inpe,R_0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ inQ_arr,inQ_e_arr,psi_n_rational, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, @@ -288,7 +283,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, $ shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr,prandtl + $ omegas_e_arr,omegas_i_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, $ ti_arr,zeff_arr,bt_arr,rs_arr, $ R0_arr,mu_i_arr @@ -307,7 +302,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile, $ msing,dp_diagonal,q_rational,psi_n_rational, - $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm,prandtl) + $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing WRITE(*,*)"dp_diagonal=",dp_diagonal WRITE(*,*)"q_rational=",q_rational @@ -318,7 +313,6 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, WRITE(*,*)"my_psio=",my_psio WRITE(*,*)"nn=",nn WRITE(*,*)"resm=",resm - WRITE(*,*)"resm=",prandtl mpsi = INT(mpsi_arr(1)) mthsurf = 512 @@ -395,7 +389,7 @@ SUBROUTINE build_inputs(infile,ncfile,slayer_inpr, eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - inpr = prandtl(ising) + inpr = inpr_prof(ising) inpe=0.0!0.0165*inpr ! Waybright added this diff --git a/slayer/slayer.f b/slayer/slayer.f index 153579355..671466cf2 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -33,7 +33,7 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,growthrates_flag !FOR TESTING + $ params_check,growthrates_flag,layer_stabilityscan_flag !FOR TESTING REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff @@ -55,14 +55,21 @@ PROGRAM slayer $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, $ inpe_arr,omegas_arr,inQ_arr, - $ psi_n_rational + $ psi_n_rational + REAL(r8), DIMENSION(8) :: inpr_prof + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas, + $ all_roots + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_inQs + REAL(r8),dimension(:, :),ALLOCATABLE :: roots + INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: - $ outer_delta_arr + $ outer_delta_arr, + $ all_growthrates,all_growthrate_locs REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, - $ inQs_left,inQs_right - REAL(r8) :: spot, slayer_inpr + $ inQs_left,inQs_right,coarse_deltas + REAL(r8) :: spot, slayer_inpr, growthrate, growthrate_loc REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas @@ -73,7 +80,8 @@ PROGRAM slayer NAMELIST/slayer_input/params_flag,input_flag,infile, $ ncfile,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, - $ inQ_i,inpr,inpe,inc_beta,inds,intau,inlu,Q0,delta_n_p + $ inQ_i,inpr,inpr_prof,inpe,inc_beta,inds, + $ intau,inlu,Q0,delta_n_p NAMELIST/slayer_control/inum,jnum,knum,ReQ_num,ImQ_num, $ scan_radius,QPscan_flag,QPscan2_flag, $ QPescan_flag,QDscan2_flag,Qbscan_flag,Qscan_flag, @@ -107,6 +115,7 @@ PROGRAM slayer inQ_e=2.0 ! Q_e=2.0 for DIII-D example. inQ_i=-2.6 ! Q_i=-2.6 for DIII-D example. inpr=0.5 ! 0.5 for DIII-D example. + inpr_prof=1.0 inpe=0.1 !I added this inc_beta=0.7 ! c_beta=0.7 for DIII-D example. inds=6.0 ! 6.0 for DIII-D example. @@ -152,6 +161,7 @@ PROGRAM slayer bal_flag=.FALSE. stability_flag=.FALSE. growthrates_flag=.FALSE. + layer_stabilityscan_flag=.FALSE. c----------------------------------------------------------------------- c read slayer.in. c----------------------------------------------------------------------- @@ -280,10 +290,9 @@ PROGRAM slayer c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- IF (growthrates_flag) THEN - WRITE(*,*)"infile=",infile - WRITE(*,*)"ncfile=",ncfile - - CALL build_inputs(infile,ncfile,inpr, + !WRITE(*,*)"infile=",infile + !WRITE(*,*)"ncfile=",ncfile + CALL build_inputs(infile,ncfile,inpr_prof, $ growthrates_flag,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, @@ -295,11 +304,66 @@ PROGRAM slayer WRITE(*,*)"Electron viscosities=",inpe_arr WRITE(*,*)"Omega ExB values=",omegas_arr WRITE(*,*)"outer region deltaprimes=",outer_delta_arr + + n_k = SIZE(qval_arr) + !ALLOCATE(all_growthrates(100,3,n_k)) + + all_RE_deltas(:,:,k) = REAL(deltas) + all_inQs(:,k) = inQs + DO k=1,n_k + WRITE(*,*) "Finding roots on q=", qval_arr(k), + $ " rational surface" + ! Run MCMC + + CALL growthrate_search(qval_arr(k),inQ_arr(k),inQ_e_arr(k), + $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), + $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), + $ outer_delta_arr(k),scan_radius, + $ coarse_deltas,inQs,iinQs,roots, + $ growthrate,growthrate_loc) !stop - CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, - $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, - $ outer_delta_arr,ReQ_num,ImQ_num,scan_radius) + + IF (k==1) THEN + ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) + ALLOCATE(all_inQs(SIZE(inQs),n_k)) + ALLOCATE(all_growthrates(n_k)) + ALLOCATE(all_growthrate_locs(n_k)) + ENDIF + all_RE_deltas(:,:,k) = coarse_deltas + all_inQs(:,k) = inQs + all_roots(:,:,k) = roots + all_growthrates(k) = growthrate + all_growthrate_locs(k) = growthrate_loc + ENDDO + + + !IF (layer_stabilityscan_flag) THEN + ! CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + !$ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, + !$ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, + !$ outer_delta_arr,ReQ_num,ImQ_num,scan_radius, + !$ inQs,iinQs,all_Re_deltas,all_inQs) + !ELSE + ! ALLOCATE(inQs(0:ReQ_num+1),iinQs(0:ImQ_num)) + ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) + ! ALLOCATE(all_inQs(SIZE(inQs),n_k)) + + ! all_RE_deltas = 0.0 + ! all_inQs = 0.0 + ! inQs = 0.0 + ! iinQs = 0.0 + !ENDIF + + CALL slayer_netcdf_out(n_k, + $ SIZE(inQs),SIZE(iinQs),qval_arr, + $ inQs,iinQs,all_roots,all_growthrates, + $ all_growthrate_locs,omegas_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,psi_n_rational, + $ all_Re_deltas,all_inQs) + + !WRITE(*,*)"xroots=",roots(:,1) + !WRITE(*,*)"yroots=",roots(:,2) + !stop ENDIF c----------------------------------------------------------------------- c find solutions based on simple torque balance. diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index b2125d765..217504ae0 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -51,21 +51,28 @@ END SUBROUTINE check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, - $ inQs,iinQs,growthrates,omegas_arr,inQ_arr,inQ_e_arr, + SUBROUTINE slayer_netcdf_out(msing,ReQ_n, + $ ImQ_n,qval_arr, + $ inQs,iinQs,all_roots,all_growthrates,all_growthrate_locs, + $ omegas_arr,inQ_arr,inQ_e_arr, $ inQ_i_arr,psi_n_rational, $ all_Re_deltas,all_inQs) + LOGICAL, PARAMETER :: layer_stabilityscan_flag = .true. INTEGER, INTENT(IN) :: msing,ReQ_n,ImQ_n REAL(r8), DIMENSION(:), INTENT(IN) :: - $ inQs,iinQs,growthrates,omegas_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,psi_n_rational!,shear + $ inQs,iinQs,omegas_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,psi_n_rational, + $ all_growthrates,all_growthrate_locs INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr REAL(r8), DIMENSION(:,:), INTENT(IN) :: all_inQs REAL(r8), DIMENSION(:,:,:), INTENT(IN) :: all_Re_deltas + REAL(r8),DIMENSION(:,:,:),ALLOCATABLE,INTENT(IN) :: + $ all_roots INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, - $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id,inQs_id + $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id,inQs_id, + $ gamma_err_id,gamma_loc_id,roots_dim CHARACTER(64) :: ncfile LOGICAL, PARAMETER :: debug_flag = .FALSE. CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' @@ -109,10 +116,15 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, CALL check( nf90_def_var(ncid,"ReQ_arr",nf90_double,ReQ_dim, $ ReQ_id)) CALL check( nf90_def_dim(ncid,"ImQ_arr",ImQ_n,ImQ_dim) ) !r_dim = q_rational + CALL check( nf90_def_dim(ncid,"nroots",100,roots_dim) ) !r_dim = q_rational CALL check( nf90_def_var(ncid,"ImQ_arr",nf90_double,ImQ_dim, $ ImQ_id)) CALL check( nf90_def_var(ncid,"growthrates",nf90_double, $ qsing_dim,gamma_id)) + CALL check( nf90_def_var(ncid,"growthrate_locs",nf90_double, + $ qsing_dim,gamma_loc_id)) + ! CALL check( nf90_def_var(ncid,"growthrates_inQ",nf90_double, + !$ (/roots_dim, qsing_dim/),gamma_loc_id)) CALL check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) CALL check( nf90_def_var(ncid,"Q",nf90_double, @@ -131,7 +143,7 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" - IF(msing>0)THEN + IF (layer_stabilityscan_flag) THEN CALL check( nf90_def_var(ncid, "Re_Delta", nf90_double, $ (/ReQ_dim, ImQ_dim, qsing_dim/), dp_id) ) CALL check( nf90_def_var(ncid, "inQs", nf90_double, @@ -146,7 +158,6 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) CALL check( nf90_put_var(ncid,ReQ_id, inQs)) CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) - CALL check( nf90_put_var(ncid,gamma_id, growthrates)) CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) @@ -154,8 +165,14 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n,ImQ_n,qval_arr, CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL check( nf90_put_var(ncid,qr_id, qval_arr)) ! IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" - CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas)) - CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) + IF (layer_stabilityscan_flag) THEN + CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas)) + CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) + ENDIF + CALL check( nf90_put_var(ncid,gamma_id,all_growthrates)) + !CALL check( nf90_put_var(ncid,gamma_err_id, + !$ (/(all_growthrates(:,3,i),i=1,msing)/))) + CALL check( nf90_put_var(ncid,gamma_loc_id,all_growthrate_locs)) c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- From 91b3ab099db983821ca5ca105c230997f2b80a3f Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Sun, 15 Sep 2024 20:08:11 -0700 Subject: [PATCH 011/305] WIP: backing up SLAYER AMR --- slayer/gslayer.f | 557 +++++++++++++++-------------------------- slayer/layerinputs.f | 164 ++++++------ slayer/sglobal.f | 6 + slayer/slayer.f | 266 +++++++++++++++----- slayer/slayer_netcdf.f | 225 ++++++++++------- 5 files changed, 623 insertions(+), 595 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 5178eccf7..9c89d6c80 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -1,5 +1,7 @@ MODULE gslayer_mod - + + USE omp_lib + USE sglobal_mod, ONLY: out_unit,r8, mu0, m_p, chag, lnLamb, $ Q_e,Q_i,pr,pe,c_beta,ds,tau, $ eta,visc,rho_s,lu,omega_e,omega_i, @@ -17,7 +19,7 @@ MODULE gslayer_mod USE grid, ONLY : powspace,linspace IMPLICIT NONE - + CONTAINS c----------------------------------------------------------------------- @@ -45,7 +47,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, $ lbeta,tau_i,tau_h,tau_r,tau_v REAL(r8) :: inQ_min,inQ_max,Q_sol - + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal CHARACTER(3) :: sn,sm @@ -95,8 +97,8 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, lu=tau_r/tau_h ! Lundquist number Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - - ! note Q depends on Qconv even if omega is fixed. + + ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i @@ -142,7 +144,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, inQ_max=10.0 inQ_min=-10.0 inum=200 - ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum),bal(0:inum)) + ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum),bal(0:inum)) DO i=0,inum inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) deltal(i)=riccati(inQs(i),inQ_e,inQ_i, @@ -174,388 +176,219 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer - - FUNCTION log_prob(params,inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe,deltaprime,sigma) RESULT(lp) - real(r8), dimension(2), intent(in) :: params - real(r8), intent(in) :: deltaprime, sigma - real(r8) :: lp, distance - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe - ! Calculate distance from the target value - distance = abs((riccati(params(1), - $ inQ_e, - $ inQ_i, - $ inpr, - $ inc_beta, - $ inds, - $ intau, - $ inpe, - $ iinQ=params(2)) - - $ deltaprime)) - - ! Calculate the log probability (negative log-likelihood of a Gaussian) - lp = -0.5 * (distance / sigma)**2 - RETURN - END FUNCTION log_prob c----------------------------------------------------------------------- -c Subprogram 2. gamma_stability_scan -c Run grid packed slayer stab. scan around omega_ExB and gamma axes +c Subprogram 2. growthrate_scan +c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE growthrate_search(qval,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,deltaprime,scan_radius, - $ deltas,inQs,iinQs,roots,growthrate,growthrate_loc) + SUBROUTINE old_growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,inQ0,inpr,inpe,scan_radius,reQ_num, + $ Re_deltas,Im_deltas,inQs,iinQs) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe,deltaprime - INTEGER, INTENT(IN) :: qval,scan_radius -c----------------------------------------------------------------------- -c -c----------------------------------------------------------------------- - real(r8), intent(out), dimension(:,:), ALLOCATABLE :: roots ! Array to store (x, y) pairs of zeros - integer :: num_zeros ! Number of zeros found - real(r8), dimension(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs - integer :: n_fine - real(r8), dimension(:), ALLOCATABLE :: x_s,y_s - real(r8) :: x, y, dx, dy, ric_val, grad_x, grad_y - real(r8), dimension(2) :: start, end, step - real(r8) :: zero_tol, grad_tol, wr, delta - integer :: i, j, k, w, n_steps + $ intau,inQ0,inpr,inpe + INTEGER, INTENT(IN) :: qval,scan_radius,reQ_num + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs + INTEGER :: n_fine + REAL(r8), DIMENSION(:), ALLOCATABLE :: x_s,y_s + REAL(r8) :: x, y, dx, dy, ric_val,start_time,end_time + REAL(r8), DIMENSION(2) :: start, end, step + REAL(r8) :: coarse_threshold,fine_threshold,grad_tol,wr + COMPLEX(r8) :: delta + INTEGER :: k, w, n_steps, i2, i,j logical :: found_zero - real(r8), intent(out) :: growthrate_loc, growthrate ! Output (x, y) pair - integer :: n_points, idx_1, idx_2 - real(r8) :: x_1, y_1, x_2, y_2, dist, min_dist1, min_dist2 + INTEGER :: n_points, idx_1, idx_2 + REAL(r8) :: x_1, y_1, x_2, y_2, dist, min_dist1, min_dist2 REAL(r8), DIMENSION(:,:), ALLOCATABLE, - $ INTENT(OUT) :: deltas - wr = inQ+inQ_e + $ INTENT(OUT) :: Re_deltas,Im_deltas + wr=0.0 ! Parameters - zero_tol = abs(0.1*deltaprime) ! Tolerance for zero value grad_tol = 1.0!abs(0.1*deltaprime) ! Tolerance for steep gradient - n_steps = 100 ! Initial number of steps - n_fine = 10 - if (abs(deltaprime)>50) then - n_fine = n_fine * 2 - end if - ALLOCATE(x_s(n_fine),y_s(n_fine)) + n_steps = reQ_num ! Initial number of steps +! n_fine = 10 +! if (abs(deltaprime)>100) then +! coarse_threshold = abs(0.95*deltaprime) ! Tolerance for zero value +! fine_threshold = abs(0.1*deltaprime) +! n_fine = n_fine * 2 +! else if (abs(deltaprime)>50) then +! coarse_threshold = abs(0.8*deltaprime) ! Tolerance for zero value +! fine_threshold = abs(0.1*deltaprime) +! n_fine = n_fine * 2 +! else if (abs(deltaprime)>20) then +! coarse_threshold = abs(0.66*deltaprime) ! Tolerance for zero value +! fine_threshold = abs(0.05*deltaprime) +! n_fine = n_fine * 2 +! else if (abs(deltaprime)>10) then +! coarse_threshold = abs(0.3*deltaprime) ! Tolerance for zero value +! fine_threshold = abs(0.03*deltaprime) +! else +! coarse_threshold = abs(0.1*deltaprime) ! Tolerance for zero value +! fine_threshold = abs(0.01*deltaprime) +! end if ALLOCATE(inQs(n_steps)) - ALLOCATE(roots(10,2)) - ALLOCATE(deltas(n_steps,n_steps)) - - ! Initial coarse grid search - - dx = 2 * dble(scan_radius) / dble(n_steps) - dy = 2 * dble(scan_radius) / dble(n_steps) - num_zeros = 0 - - inQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) ALLOCATE(iinQs(n_steps)) - - do i = 1, n_steps - x = inQs(i)!wr - scan_radius + (i - 0.5) * dx - do j = 1, n_steps - !y = -scan_radius + (j - 0.5) * dy - y=-scan_radius+(REAL(j)/n_steps)*(scan_radius+ - $ scan_radius) - iinQs(j) = y - delta = REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, - $ intau,inpe,iinQ=y)) - ric_val = abs(delta - deltaprime) - deltas(i,j) = delta - grad_x = 10!(REAL(riccati(x + dx,inQ_e,inQ_i,inpr,inc_beta, - !$ inds,intau,inpe,iinQ=y)) - - !$ REAL(riccati(x - dx,inQ_e,inQ_i,inpr,inc_beta, - !$ inds,intau,inpe,iinQ=y))) / (2 * dx) - grad_y = 10!(REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, - !$ intau,inpe,iinQ=y + dy)) - - !$ REAL(riccati(x,inQ_e,inQ_i,inpr,inc_beta,inds, - !$ intau,inpe,iinQ=y - dy))) / (2 * dy) - - !WRITE(*,*) "Point", [x,y] - !WRITE(*,*) "ric_val", ric_val - !WRITE(*,*) "grad_x", grad_x - !WRITE(*,*) "grad_y", grad_y - !WRITE(*,*) "[dx,dy]", [dx,dy] - - - if ((abs(ric_val) < zero_tol) .and. ((abs(grad_x) > - $ grad_tol) .or. (abs(grad_y) > grad_tol))) then - ! Potential zero found, refine with a finer grid - !start = [x - 0.1, y - 0.1] - !end = [x + 0.1, y + 0.1] - !step = (end - start) / 20 - !WRITE(*,*) "Potential zero found", [x,y] - ! WRITE(*,*) "dx", dx - ! WRITE(*,*) "dy", dy - ! WRITE(*,*) "step", step - ! WRITE(*,*) "scan_radius", scan_radius - - x_s = linspace((x-0.1),(x+0.1),n_fine) - y_s = linspace((y-0.1),(y+0.1),n_fine) - found_zero = .false. - do k=1,n_fine - do w=1,n_fine - !x_s = start(1) + (k - 0.5) * step(1) - !y_s = start(2) + (k - 0.5) * step(2) - - !WRITE(*,*) "fine point", [x_s(k),y_s(w)] - !WRITE(*,*) "y_s", y_s - !stop - !WRITE(*,*) "fine scan", k - - ric_val = REAL(riccati(x_s(k),inQ_e,inQ_i, - $ inpr,inc_beta,inds,intau,inpe, - $ iinQ=y_s(w))) - deltaprime - !WRITE(*,*) "ric_val", ric_val - - if (abs(ric_val) < abs(0.01*deltaprime)) then - ! Found a zero within tolerance, store it - num_zeros = num_zeros + 1 - roots(num_zeros, 1) = x - roots(num_zeros, 2) = y - WRITE(*,*) "FOUND ROOT #", num_zeros - found_zero=.true. - !exit ! Exit the inner loop since zero is found - end if - if (found_zero==.true.) then - exit - endif - end do - if (found_zero==.true.) then - exit - endif - end do - end if - end do - end do - - ! 1. Find the index of the (x, y) pair with x closest to wr - n_points = size(roots, 1) - min_dist1 = abs(roots(1, 1) - wr) - idx_1 = 1 - - do i = 2, n_points - dist = abs(roots(i, 1) - wr) - if (dist < min_dist1) then - min_dist1 = dist - idx_1 = i - end if - end do - - x_1 = roots(idx_1, 1) - y_1 = roots(idx_1, 2) - - ! 2. Find the index of the next closest (x, y) pair with y more than 0.5 away from y_1 - min_dist2 = huge(min_dist2) ! Initialize to a very large value - idx_2 = 0 ! Initialize to an invalid index - - do i = 1, n_points - if (i /= idx_1 .and. abs(roots(i, 2) - y_1) > 0.5) then ! Check if it's a different point and far enough in y - dist = abs(roots(i, 1) - wr) - if (dist < min_dist2) then - min_dist2 = dist - idx_2 = i - end if - end if - end do - - if (idx_2 > 0) then ! Check if a valid second point was found - x_2 = roots(idx_2, 1) - y_2 = roots(idx_2, 2) - end if - - ! 3. Apply your specific conditions to select the final (x, y) pair - if (idx_2 > 0 .and. abs(x_1 - x_2) < 0.1) then - if (y_1 < 0 .or. y_2 < 0) then ! Choose the pair with negative y - if (y_1 < 0) then - growthrate_loc = x_1 - growthrate = y_1 - else - growthrate_loc = x_2 - growthrate = y_2 - end if - end if - else ! If the x values are far apart, choose the first pair - growthrate_loc = x_1 - growthrate = y_1 - end if -c----------------------------------------------------------------------- -c -c----------------------------------------------------------------------- - ! Allocate grid packing arrays and 2D complex deltas array - !ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) - - !DO i=0,ReQ_num+1 - ! DO j=0,ImQ_num - ! iinQs(j)=Im_inQ_min+(REAL(j)/ImQ_num)*(Im_inQ_max- - !$ Im_inQ_min) - ! ! Run riccati() at each Q index to give delta - ! deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr, - !$ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ! NOT USING GRID PACKING - ! ENDDO - !ENDDO - + ALLOCATE(Re_deltas(n_steps,n_steps)) + ALLOCATE(Im_deltas(n_steps,n_steps)) + inQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) + iinQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) + i=1 + j=1 + !CALL OMP_SET_NUM_THREADS(4) + !PRINT *, "Max threads: ",OMP_GET_MAX_THREADS() + !CALL cpu_time(start_time) + !!$OMP PARALLEL DO PRIVATE(j,y,delta) !BIG REWRITE NEEDED + DO i = 1, n_steps + DO j = 1, n_steps + !PRINT *, "Hello from process: ", OMP_GET_THREAD_NUM() + y=iinQs(j) + delta = riccati(inQs(i),inQ_e,inQ_i,inpr,inc_beta, + $ inds,intau,inpe,iinQ=y) + Re_deltas(i,j) = REAL(delta) + Im_deltas(i,j) = AIMAG(delta) + !PRINT *,'inQs(i):',inQs(i) + !PRINT *,'y:',y + !PRINT *,'delta:',delta + END DO + !stop + END DO + !!$OMP END PARALLEL DO + !CALL cpu_time(end_time) + !PRINT *,'Layer scan time:',end_time-start_time,'seconds' + !test comment RETURN - END SUBROUTINE growthrate_search + END SUBROUTINE old_growthrate_scan c----------------------------------------------------------------------- -c Subprogram 2. gamma_stability_scan -c Run grid packed slayer stab. scan around omega_ExB and gamma axes +c Subprogram 2. growthrate_scan +c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE gamma_stability_scan(qval,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,ReQ_num,ImQ_num,scan_radius, - $ deltas,inQs,iinQs) + SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,inQ0,inpr,inpe,scan_radius,coarse_grid_size, + $ deltaprime,results) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs - REAL(r8),INTENT(IN) :: inQ_e,inQ_i,inc_beta,inds, + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, $ intau,inQ0,inpr,inpe - REAL(r8), INTENT(IN) :: inQ ! REAL??? - INTEGER, INTENT(IN) :: qval,ReQ_num,ImQ_num,scan_radius - - ! Outputs - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, - $ INTENT(OUT) :: deltas - REAL(r8), DIMENSION(:), ALLOCATABLE, - $ INTENT(OUT) :: inQs,iinQs - - ! Local variables - INTEGER :: i,j,k - REAL(r8) :: Re_inQ_min,Re_inQ_max,Im_inQ_min,Im_inQ_max - CHARACTER(3) :: q_str - CHARACTER(len=8) :: fmt ! format descriptor for stab file naming - CHARACTER(len=8) :: x1 ! string for stab file naming - INTEGER :: i1 ! integer for stab file naming - !REAL(r8), DIMENSION(:,:), ALLOCATABLE :: inQs_left,inQs_right - !REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs_log -c----------------------------------------------------------------------- -c Build exponential grid packing for stability scan, then run -c----------------------------------------------------------------------- - ! Allocate grid packing arrays and 2D complex deltas array - ALLOCATE(inQs(0:ReQ_num+1),iinQs(0:ImQ_num)) - !ALLOCATE(inQs_left(0:2+ReQ_num/2,0:2+ReQ_num/2)) - !ALLOCATE(inQs_right(0:2+ReQ_num/2,0:2+ReQ_num/2)) - !ALLOCATE(inQs_log(0:3+ReQ_num)) - ALLOCATE(deltas(0:3+ReQ_num,0:ImQ_num)) - - Im_inQ_max=scan_radius ! max growth rate in scan, OPEN TO USER? - Im_inQ_min=-scan_radius ! min growth rate in scan, OPEN TO USER? - Re_inQ_max=scan_radius ! max growth rate in scan, OPEN TO USER? - Re_inQ_min=-scan_radius ! min growth rate in scan, OPEN TO USER? - - ! Grid packing - right now going to Q +/- 0.2 -- OPEN TO USER? - !inQs_left = powspace(inQ-Re_inQ_max,inQ,1, ! omega-3.0 - !$ 2+ReQ_num/2,"upper") - !inQs_right = powspace(inQ,inQ+Re_inQ_max,1, ! omega+3.0 - !$ 2+ReQ_num/2,"lower") - !inQs_log = (/inQs_left(1,1:2+ReQ_num/2), - !$ inQs_right(1,2:1+ReQ_num/2)/) - - inQs = linspace((Re_inQ_min+inQ),(Re_inQ_max+inQ),ReQ_num+1) - - DO i=0,ReQ_num+1 - DO j=0,ImQ_num - iinQs(j)=Im_inQ_min+(REAL(j)/ImQ_num)*(Im_inQ_max- - $ Im_inQ_min) - ! Run riccati() at each Q index to give delta - deltas(i,j)=riccati(inQs(i),inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQs(j)) ! NOT USING GRID PACKING - ENDDO - ENDDO -c----------------------------------------------------------------------- -c Alter qval to string to add to output filename -c----------------------------------------------------------------------- - fmt = '(I5.5)' ! an integer of width 2 for q surface - i1 = qval - write (x1,fmt) i1 ! integer to string using a 'internal file' - ! Write stability scan output file - OPEN(UNIT=out_unit,FILE="slayer_stability_q"// - $ TRIM(x1)//".out", STATUS="UNKNOWN") - WRITE(out_unit,'(1x,4(a17))'),"RE(Q)", - $ "IM(Q)","RE(delta)","IM(delta)" - DO i=0,ReQ_num+1 - DO j=0,ImQ_num - WRITE(out_unit,'(1x,4(es17.8e3))') - $ inQs(i),iinQs(j), - $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) - ENDDO - ENDDO - CLOSE(out_unit) + INTEGER, INTENT(IN) :: qval,scan_radius,coarse_grid_size + !REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs + COMPLEX(r8) :: delta + REAL(r8), INTENT(IN) :: deltaprime + INTEGER, PARAMETER :: fine_grid_size = 5 + REAL(r8), PARAMETER :: tolerance = 1.0E-6 + REAL(r8) :: delta_real, delta_imag, threshold + INTEGER :: i, j, k, l, count + REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, + $ inQ_coarse, iinQ_coarse + TYPE(result_type), INTENT(INOUT) :: results + + ! Arrays to store results + !REAL(r8), ALLOCATABLE :: inQ_array(:), iinQ_array(:), + !$ delta_real_array(:), delta_imag_array(:) + INTEGER :: max_points + + ! Calculate maximum possible number of points + max_points = coarse_grid_size**2 * (1 + (fine_grid_size-1)**2) + + ! Allocate arrays + !ALLOCATE(inQ_array(max_points), iinQ_array(max_points)) + !ALLOCATE(delta_real_array(max_points), + !$ delta_imag_array(max_points)) + + ! Allocate arrays with maximum possible size + ALLOCATE(results%inQs(max_points), results%iinQs(max_points)) + ALLOCATE(results%Re_deltas(max_points), + $ results%Im_deltas(max_points)) + + + ! Initialize counter + count = 0 + + ! Calculate step sizes + inQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) + iinQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) + + ! Coarse grid loop + DO i = 1, coarse_grid_size + DO j = 1, coarse_grid_size + inQ_coarse = -scan_radius + (i - 1) * inQ_step + iinQ_coarse = -scan_radius + (j - 1) * iinQ_step + + ! Evaluate riccati function + delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, + $ inds,intau,inpe,iinQ=iinQ_coarse) + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + ! Store coarse grid point + count = count + 1 + results%inQs(count) = inQ_coarse + results%iinQs(count) = iinQ_coarse + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + + IF (ABS(deltaprime) > 8) THEN + threshold = ABS(deltaprime)**(1./3.) + ELSE + threshold = 0.25 * ABS(deltaprime) + END IF + + ! Check if refinement is needed + IF (ABS(delta_real) > threshold) THEN + ! Fine grid loop + DO k = 2, fine_grid_size + DO l = 2, fine_grid_size + inQ_fine = inQ_coarse + (k-1) * inQ_step / + $ (fine_grid_size - 1) + iinQ_fine = iinQ_coarse + (l-1) * iinQ_step / + $ (fine_grid_size - 1) + + IF ((ABS(inQ_coarse - inQ_fine) < + $ tolerance) .AND. (ABS(iinQ_coarse - + $ iinQ_fine) < tolerance)) CYCLE + + ! Evaluate riccati function + delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + ! Store fine grid point + count = count + 1 + results%inQs(count) = inQ_fine + results%iinQs(count) = iinQ_fine + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END DO + END DO + END IF + END DO + END DO + + ! Set the actual count of points + results%count = count + + ! Resize arrays to actual number of points + CALL resize_array(results%inQs, count) + CALL resize_array(results%iinQs, count) + CALL resize_array(results%Re_deltas, count) + CALL resize_array(results%Im_deltas, count) RETURN - END SUBROUTINE gamma_stability_scan -c----------------------------------------------------------------------- -c Subprogram 3. gamma_match -c Loop stability across k rational surfaces -c----------------------------------------------------------------------- - SUBROUTINE gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr, - $ inc_beta_arr,inds_arr,intau_arr,inQ0_arr, - $ inpr_arr,inpe_arr,omegas_arr,outer_delta_arr, - $ ReQ_num,ImQ_num,scan_radius,inQs,iinQs, - $ all_Re_deltas,all_inQs) -c----------------------------------------------------------------------- -c Declarations -c----------------------------------------------------------------------- - ! Inputs - REAL(r8), DIMENSION(:), INTENT(IN) :: inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, - $ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, - $ inQ_arr,psi_n_rational - INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr - REAL(r8), DIMENSION(:), INTENT(IN) :: outer_delta_arr - INTEGER, INTENT(IN) :: ReQ_num,ImQ_num,scan_radius - ! Outputs - REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates - ! Local variables - INTEGER :: n_k ! Number of rational surfaces - INTEGER :: k,w - ! Local variables received from internal subroutines - REAL(r8), DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: inQs,iinQs - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_slices,all_inQs - REAL(r8), DIMENSION(:), ALLOCATABLE :: slice - REAL(r8) :: layer_Q, ImQ_gamma - - n_k = SIZE(qval_arr) - ! Allocate growthrates arrays - ALLOCATE(growthrates(n_k)) - -c----------------------------------------------------------------------- -c Loop across rational surfaces -c----------------------------------------------------------------------- - ! Summary: for each rational surface, run narrow stability scan - ! for analysis, then slice out 1D array of growth rates at - ! given omega_ExB (Q), then find growth rate corresponding - ! to delta-deltaprime match - DO k=1,n_k - WRITE(*,*) "Scanning q=", qval_arr(k), " rational surface" - ! Run stability scan - CALL gamma_stability_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), - $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), - $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ ReQ_num,ImQ_num,scan_radius,deltas,inQs,iinQs) - - layer_Q = inQ_arr(k) ! REAL??? - - IF (k==1) THEN - ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) - ALLOCATE(all_slices(SIZE(iinQs),n_k), - $ all_inQs(SIZE(inQs),n_k)) - ENDIF + END SUBROUTINE growthrate_scan - all_RE_deltas(:,:,k) = REAL(deltas) - all_inQs(:,k) = inQs - ENDDO + SUBROUTINE resize_array(arr, new_size) + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) + INTEGER, INTENT(IN) :: new_size + REAL(r8), ALLOCATABLE :: temp(:) - DEALLOCATE(deltas) - - RETURN - END SUBROUTINE gamma_match + ALLOCATE(temp(new_size)) + temp(1:new_size) = arr(1:new_size) + CALL move_alloc(temp, arr) + END SUBROUTINE resize_array END MODULE gslayer_mod diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 92333c392..9b1bf2cc3 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -9,43 +9,23 @@ MODULE layerinputs_mod USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq USE bicube_mod, ONLY: bicube_eval_external,bicube_type - USE slayer_netcdf_mod, ONLY: slayer_netcdf_inputs + USE slayer_netcdf_mod!, ONLY: slayer_netcdf_inputs IMPLICIT NONE CONTAINS c----------------------------------------------------------------------- -c subprogram 1. check. -c Check status of netcdf file. -c----------------------------------------------------------------------- - SUBROUTINE check(stat) -c----------------------------------------------------------------------- -c declaration. -c----------------------------------------------------------------------- - INTEGER, INTENT (IN) :: stat -c----------------------------------------------------------------------- -c stop if it is an error. -c----------------------------------------------------------------------- - IF(stat /= nf90_noerr) THEN - PRINT *, TRIM(nf90_strerror(stat)) - !STOP "ERROR: failed to write/read netcdf file" - ENDIF -c----------------------------------------------------------------------- -c terminate. -c----------------------------------------------------------------------- - RETURN - END SUBROUTINE check -c----------------------------------------------------------------------- -c subprogram 2. read_stride_netcdf_diagonal. +c subprogram 1. read_stride_netcdf_diagonal. c Read STRIDE netcdf file for SLAYER inputs only. c----------------------------------------------------------------------- - SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, - $ dp_diagonal, q_rational, psi_n_rational, shear, + SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, + $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,shear, $ r_o,my_bt0,my_psio,mpsi,nn,resm) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: dp_diagonal + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: + $ Re_dp_diagonal,Im_dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, $ psi_n_rational, shear REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, @@ -78,18 +58,16 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, msing=INT(msing_arr(1)) - ! Allocate Arrays (based on dimension) - ALLOCATE(dp_diagonal(msing),q_rational(msing), + ! Allocate Arrays (based on dimension) + ALLOCATE(Re_dp_diagonal(msing),q_rational(msing), $ psi_n_rational(msing),shear(msing), - $ resm(msing)) + $ resm(msing),Im_dp_diagonal(msing)) ALLOCATE(delta_prime(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) CALL check(stat) stat = nf90_inquire_attribute(ncid,bt0_id,"bt0",len=bt0_len) CALL check(stat) - - stat = nf90_inquire_attribute(ncid,psio_id,"psio",len=psio_len) CALL check(stat) @@ -108,7 +86,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, $ my_psio(INT(psio_len)), $mpsi(INT(mpsi_len)),nn(INT(nn_len))) - ! Get Variable IDs + ! Get Variable IDs stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) CALL check(stat) stat = nf90_inq_varid(ncid, "q_rational", qr_id) @@ -122,7 +100,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Get attributes stat = nf90_get_att(ncid, ro_id, "ro", r_o) CALL check(stat) - stat = nf90_get_att(ncid, bt0_id, "bt0", my_bt0) CALL check(stat) stat = nf90_get_att(ncid, psio_id, "psio", my_psio) @@ -132,7 +109,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, stat = nf90_get_att(ncid, nn_id, "n", nn) CALL check(stat) - ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. + ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) CALL check(stat) ! Read 1D variables @@ -147,9 +124,10 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, ! Extract Diagonal, with 3rd index signifying REAL part DO i = 1, msing - dp_diagonal(i) = REAL(delta_prime(i, i, 1)) + Re_dp_diagonal(i) = delta_prime(i, i, 1) + Im_dp_diagonal(i) = delta_prime(i, i, 2) END DO - ! Clean Up + ! Clean Up DEALLOCATE(delta_prime) stat = nf90_close(ncid) CALL check(stat) @@ -157,7 +135,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile, msing, END SUBROUTINE read_stride_netcdf_diagonal c----------------------------------------------------------------------- c subprogram 2. issurfint. -c surface integration by simple method. +c surface integration by simple method. copied from EQUIL c----------------------------------------------------------------------- FUNCTION issurfint(func,fs,inpsi,wegt,ave, $ fsave,psave,jacs,delpsi,inr,ina,first) @@ -246,22 +224,23 @@ END FUNCTION issurfint c subprogram 3. build_inputs. c build input arrays for SLAYER c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,inpr_prof, - $ growthrate_flag, - $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr,inc_beta_arr, - $ inds_arr,intau_arr,Q0_arr,inpr_arr,inpe_arr, - $ omegas_arr,outer_delta_arr) + SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, + $ pe_flag,qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, + $ inpr_arr,inpe_arr,omegas_arr,Re_deltaprime_arr, + $ Im_deltaprime_arr) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - LOGICAL, INTENT(IN) :: growthrate_flag + ! Inputs + LOGICAL, INTENT(IN) :: growthrate_flag,pe_flag + CHARACTER(512), INTENT(IN) :: infile,ncfile + ! Internals REAL(r8), DIMENSION(8), INTENT(IN) :: inpr_prof LOGICAL :: firstsurf - REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf, - $ ising + REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf,ising INTEGER :: zi, zimp, mi, mimp - REAL(r8) :: nfac,tfac,wefac,wpfac,e!,pi,twopi + REAL(r8) :: nfac,tfac,wefac,wpfac,e TYPE(spline_type) :: spl TYPE(spline_type) :: sr @@ -272,21 +251,20 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, $ my_qval,my_sval,my_bt,my_rs,zeff,inpe,R_0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: - $ inQ_arr,inQ_e_arr,psi_n_rational, + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, + $ inQ_e_arr,psi_n_rational, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, $ inpr_arr,inpe_arr,omegas_arr, - $ outer_delta_arr + $ Re_deltaprime_arr,Im_deltaprime_arr INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor - REAL(r8), DIMENSION(:), ALLOCATABLE :: dp_diagonal, q_rational, - $ shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, + $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, + $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, - $ ti_arr,zeff_arr,bt_arr,rs_arr, - $ R0_arr,mu_i_arr + $ ti_arr,zeff_arr,bt_arr,rs_arr,R0_arr,mu_i_arr INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm,nns_arr INTEGER :: msing,i,mthsurf REAL(r8), DIMENSION(0:512) :: unitfun @@ -294,17 +272,15 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, REAL(r8) :: psave REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs,delpsi,rsurf,asurf REAL(r8) :: rfac,jac,a_surf - - CHARACTER(512), INTENT(IN) :: infile,ncfile - c----------------------------------------------------------------------- c Read in STRIDE netcdf c----------------------------------------------------------------------- - CALL read_stride_netcdf_diagonal(ncfile, - $ msing,dp_diagonal,q_rational,psi_n_rational, + CALL read_stride_netcdf_diagonal(ncfile,msing,Re_dp_diagonal, + $ Im_dp_diagonal,q_rational,psi_n_rational, $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing - WRITE(*,*)"dp_diagonal=",dp_diagonal + WRITE(*,*)"Re_dp_diagonal=",Re_dp_diagonal + WRITE(*,*)"Im_dp_diagonal=",Im_dp_diagonal WRITE(*,*)"q_rational=",q_rational WRITE(*,*)"psi_n_rational=",psi_n_rational WRITE(*,*)"shear=",shear @@ -315,14 +291,14 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, WRITE(*,*)"resm=",resm mpsi = INT(mpsi_arr(1)) - mthsurf = 512 + mthsurf = 512 ! Hardcoded, but this is a default value ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), - $ inQ_i_arr(msing), - $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), - $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), - $ omegas_arr(msing),omegas_e_arr(msing),omegas_i_arr(msing), - $ outer_delta_arr(msing)) + $ inQ_i_arr(msing), + $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), + $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), + $ omegas_arr(msing),omegas_e_arr(msing),omegas_i_arr(msing), + $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) @@ -352,7 +328,6 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, c----------------------------------------------------------------------- c loop across singular surfaces, evaluate spline quantities. c----------------------------------------------------------------------- - DO ising=1,msing respsi = psi_n_rational(ising) @@ -391,7 +366,12 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, inpr = inpr_prof(ising) - inpe=0.0!0.0165*inpr ! Waybright added this + ! Check whether to include electron viscosity + IF (pe_flag) THEN + inpe=0.0165*inpr + ELSE + inpe=0.0 + ENDIF ne_arr(ising) = n_e te_arr(ising) = t_e @@ -444,7 +424,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, delta_n=lu**(1.0/3.0)/my_rs ! norm factor for delta primes qval_arr(ising) = INT(my_qval) - inQ_arr(ising)=Q + inQ_arr(ising)=REAL(Q) inQ_e_arr(ising)=Q_e inQ_i_arr(ising)=Q_i inc_beta_arr(ising)=c_beta @@ -452,29 +432,49 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof, intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr - inpe_arr(ising) = inpe!0.0!0.0165*inpr !!! TEMPORARY? + inpe_arr(ising) = inpe omegas_arr(ising) = omega omegas_e_arr(ising) = omega_e omegas_i_arr(ising) = omega_i - outer_delta_arr(ising) = dp_diagonal(ising) - + Re_deltaprime_arr(ising) = Re_dp_diagonal(ising) + Im_deltaprime_arr(ising) = Im_dp_diagonal(ising) ENDDO + !WRITE(*,*)"msing=",msing + !WRITE(*,*)"qval_arr=",qval_arr + !WRITE(*,*)"ne_arr=",ne_arr + !WRITE(*,*)"te_arr=",te_arr + !WRITE(*,*)"ni_arr=",ni_arr + !WRITE(*,*)"ti_arr=",ti_arr !WRITE(*,*)"zeff_arr=",zeff_arr - - CALL slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, - $ zeff_arr,shear,bt_arr,rs_arr,R0_arr,mu_i_arr,resm,nns_arr, - $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr, - $ intau_arr,inpr_arr,inpe_arr,omegas_arr, - $ outer_delta_arr) - + !WRITE(*,*)"shear=",shear + !WRITE(*,*)"bt_arr=",bt_arr + !WRITE(*,*)"rs_arr=",rs_arr + !WRITE(*,*)"R0_arr=",R0_arr + !WRITE(*,*)"resm=",resm + !WRITE(*,*)"nns_arr=",nns_arr + !WRITE(*,*)"inc_beta_arr=",inc_beta_arr + !WRITE(*,*)"inds_arr=",inds_arr + !WRITE(*,*)"intau_arr=",intau_arr + !WRITE(*,*)"inpr_arr=",inpr_arr + !WRITE(*,*)"inpe_arr=",inpe_arr + !WRITE(*,*)"omegas_arr=",omegas_arr + !WRITE(*,*)"omegas_e_arr=",omegas_e_arr + !WRITE(*,*)"omegas_i_arr=",omegas_i_arr + !WRITE(*,*)"Re_deltaprime_arr=",Re_deltaprime_arr + !WRITE(*,*)"Im_deltaprime_arr=",Im_deltaprime_arr + !stop + CALL slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr,ni_arr, + $ ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, + $ resm,nns_arr,inc_beta_arr,inds_arr, + $ intau_arr,inpr_arr,inpe_arr,inQ_arr,omegas_arr, + $ omegas_e_arr,omegas_i_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- - RETURN - END SUBROUTINE build_inputs END MODULE layerinputs_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 403b3b6fb..ae6356f2d 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -16,6 +16,12 @@ MODULE sglobal_mod $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6022e-19, $ kval=1.3807e-23,lnLamb=17.0 + TYPE result_type + REAL(r8), ALLOCATABLE :: inQs(:), iinQs(:), + $ Re_deltas(:), Im_deltas(:) + INTEGER :: count + END TYPE result_type + ! lnLamb will be updated. COMPLEX(r8), PARAMETER :: ifac=(0,1) diff --git a/slayer/slayer.f b/slayer/slayer.f index 671466cf2..dca43a3a8 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -33,7 +33,8 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,growthrates_flag,layer_stabilityscan_flag !FOR TESTING + $ params_check,growthrates_flag,analytic_growthrates_flag, + $ pe_flag,br_th_flag !FOR TESTING REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff @@ -57,23 +58,25 @@ PROGRAM slayer $ inpe_arr,omegas_arr,inQ_arr, $ psi_n_rational REAL(r8), DIMENSION(8) :: inpr_prof - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_RE_deltas, - $ all_roots + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, + $ all_Im_deltas,all_roots REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_inQs - REAL(r8),dimension(:, :),ALLOCATABLE :: roots + REAL(r8), DIMENSION(:,:),ALLOCATABLE :: Re_deltas,Im_deltas INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: - $ outer_delta_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr, $ all_growthrates,all_growthrate_locs REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths, $ inQs_left,inQs_right,coarse_deltas - REAL(r8) :: spot, slayer_inpr, growthrate, growthrate_loc + REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + TYPE(result_type) :: results(8) ! Assuming max 8 rational surfaces + REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates, $ growthrate_err @@ -88,7 +91,8 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag,growthrates_flag,bal_flag + $ stability_flag,growthrates_flag,analytic_growthrates_flag, + $ pe_flag,br_th_flag,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -161,7 +165,9 @@ PROGRAM slayer bal_flag=.FALSE. stability_flag=.FALSE. growthrates_flag=.FALSE. - layer_stabilityscan_flag=.FALSE. + analytic_growthrates_flag=.FALSE. + pe_flag=.FALSE. + br_th_flag=.FALSE. c----------------------------------------------------------------------- c read slayer.in. c----------------------------------------------------------------------- @@ -293,77 +299,221 @@ PROGRAM slayer !WRITE(*,*)"infile=",infile !WRITE(*,*)"ncfile=",ncfile CALL build_inputs(infile,ncfile,inpr_prof, - $ growthrates_flag,qval_arr,psi_n_rational, + $ growthrates_flag,pe_flag,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,outer_delta_arr) + $ omegas_arr,Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr WRITE(*,*)"inQ values=",inQ_arr WRITE(*,*)"Prantdl numbers=",inpr_arr WRITE(*,*)"Electron viscosities=",inpe_arr WRITE(*,*)"Omega ExB values=",omegas_arr - WRITE(*,*)"outer region deltaprimes=",outer_delta_arr - + WRITE(*,*)"outer region real deltaprimes=",Re_deltaprime_arr + WRITE(*,*)"outer region imag deltaprimes=",Im_deltaprime_arr + WRITE(*,*)"inQ_e_arr=",inQ_e_arr + WRITE(*,*)"inds_arr=",inds_arr + WRITE(*,*)"intau_arr=",intau_arr n_k = SIZE(qval_arr) - !ALLOCATE(all_growthrates(100,3,n_k)) - all_RE_deltas(:,:,k) = REAL(deltas) - all_inQs(:,k) = inQs DO k=1,n_k WRITE(*,*) "Finding roots on q=", qval_arr(k), $ " rational surface" - ! Run MCMC - CALL growthrate_search(qval_arr(k),inQ_arr(k),inQ_e_arr(k), + CALL growthrate_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ outer_delta_arr(k),scan_radius, - $ coarse_deltas,inQs,iinQs,roots, - $ growthrate,growthrate_loc) - !stop - - IF (k==1) THEN - ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) - ALLOCATE(all_inQs(SIZE(inQs),n_k)) + $ scan_radius,reQ_num,Re_deltaprime_arr(k), + $ results(n_k)) + + ! IF (k==1) THEN + ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) + ! ALLOCATE(all_Im_deltas(SIZE(inQs),SIZE(iinQs),n_k)) + ! ALLOCATE(all_inQs(SIZE(inQs),n_k)) ALLOCATE(all_growthrates(n_k)) ALLOCATE(all_growthrate_locs(n_k)) - ENDIF - all_RE_deltas(:,:,k) = coarse_deltas - all_inQs(:,k) = inQs - all_roots(:,:,k) = roots - all_growthrates(k) = growthrate - all_growthrate_locs(k) = growthrate_loc + ! ENDIF + ! all_Re_deltas(:,:,k) = Re_deltas + ! all_Im_deltas(:,:,k) = Im_deltas + ! all_inQs(:,k) = inQs + + ! DEALLOCATE(Re_deltas,Im_deltas) + all_growthrates(k) = 0.0 + all_growthrate_locs(k) = 0.0 ENDDO + WRITE(*,*) "all_growthrates", all_growthrates + + CALL slayer_netcdf_out(growthrates_flag,n_k,qval_arr, + $ all_growthrates,all_growthrate_locs, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr, + $ results) + stop + ENDIF +c----------------------------------------------------------------------- +c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY +c----------------------------------------------------------------------- + IF (analytic_growthrates_flag) THEN + WRITE(*,*)"intau=",intau + WRITE(*,*)"inQ=",inQ + WRITE(*,*)"Prantdl numbers=",inpr + WRITE(*,*)"Electron viscosities=",inpe + WRITE(*,*)"inQ_e=",inQ_e + WRITE(*,*)"inds=",inds + WRITE(*,*)"inc_beta=",inc_beta + + WRITE(*,*)"running analytic scan" + + qval_arr = (/ 2 /) + omegas_arr = (/ 0.0 /) + inQ_arr = (/ inQ /) + inQ_e_arr = (/ inQ_e /) + inQ_i_arr = (/ inQ_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ 5.0 /) + Im_deltaprime_arr = (/ 0.00001 /) + inpr_arr = (/ inpr /) + + CALL growthrate_scan(2,inQ,inQ_e, + $ inQ_i,inc_beta,inds, + $ intau,inQ,inpr,inpe, + $ scan_radius,reQ_num,Re_deltaprime_arr(k), + $ results(1)) + + ! WRITE(*,*)"SIZE(inQs)",SIZE(inQs) + ! WRITE(*,*)"SIZE(iinQs)",SIZE(iinQs) + + !IF (k==1) THEN + ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),1)) + ! ALLOCATE(all_Im_deltas(SIZE(inQs),SIZE(iinQs),1)) + ! ALLOCATE(all_inQs(SIZE(inQs),1)) + ALLOCATE(all_growthrates(1)) + ALLOCATE(all_growthrate_locs(1)) + !ENDIF + ! all_Re_deltas(:,:,1) = Re_deltas + ! all_Im_deltas(:,:,1) = Im_deltas + ! all_inQs(:,1) = inQs + !all_roots(:,:,k) = roots + + all_growthrates(1) = 0.0 + all_growthrate_locs(1) = 0.0 + + WRITE(*,*)"allocations successful" + + CALL slayer_netcdf_out(growthrates_flag,1,qval_arr, + $ all_growthrates,all_growthrate_locs, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr, + $ results) + stop + ENDIF +c----------------------------------------------------------------------- +c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY +c----------------------------------------------------------------------- + IF (br_th_flag) THEN + WRITE(*,*)"running br_th scan" - !IF (layer_stabilityscan_flag) THEN - ! CALL gamma_match(qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - !$ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr, - !$ inQ0_arr,inpr_arr,inpe_arr,omegas_arr, - !$ outer_delta_arr,ReQ_num,ImQ_num,scan_radius, - !$ inQs,iinQs,all_Re_deltas,all_inQs) - !ELSE - ! ALLOCATE(inQs(0:ReQ_num+1),iinQs(0:ImQ_num)) - ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) - ! ALLOCATE(all_inQs(SIZE(inQs),n_k)) - - ! all_RE_deltas = 0.0 - ! all_inQs = 0.0 - ! inQs = 0.0 - ! iinQs = 0.0 - !ENDIF - - CALL slayer_netcdf_out(n_k, - $ SIZE(inQs),SIZE(iinQs),qval_arr, - $ inQs,iinQs,all_roots,all_growthrates, - $ all_growthrate_locs,omegas_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,psi_n_rational, - $ all_Re_deltas,all_inQs) - - !WRITE(*,*)"xroots=",roots(:,1) - !WRITE(*,*)"yroots=",roots(:,2) - !stop + CALL params(n_e,t_e,t_i,omega, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + inQ=Q + inQ_e=Q_e + inQ_i=Q_i + inc_beta=c_beta + inds=ds + intau=tau + Q0=Q +c----------------------------------------------------------------------- +c calculate basic delta, torque, balance, error fields. +c----------------------------------------------------------------------- + WRITE(*,*)"basic delta successful" + delta_n_p=1e-2 + delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, + $ inpe) + psi0=1.0/ABS(delta+delta_n_p) ! a.u. + jxb=-AIMAG(1.0/(delta+delta_n_p)) ! a.u. +c----------------------------------------------------------------------- +c find solutions based on simple torque balance. +c----------------------------------------------------------------------- + IF (Q0>inQ_e) THEN + inQ_max=2.0*Q0 + inQ_min=1.05*inQ_e + ELSE + inQ_max=0.95*inQ_e + IF (Q0>0) THEN + inQ_min=0.8*inQ_i + ELSE + inQ_min=1.5*MINVAL((/Q0,inQ_i/)) + ENDIF + ENDIF + WRITE(*,*)"rotation scan" + ! Scan of rotation + inQ_max=10.0 + inQ_min=-10.0 + inum=200 + ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum), + $ bal(0:inum)) + DO i=0,inum + inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) + deltal(i)=riccati(inQs(i),inQ_e,inQ_i, + $ inpr,inc_beta,inds,intau,inpe) + jxbl(i)=-AIMAG(1.0/(deltal(i)+delta_n_p)) + bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) + ENDDO + + ! Identify the threshold from the maximum of the balance parameter + index=MAXLOC(bal) + Q_sol=inQs(index(1)) + !omega_sol=inQs(index(1))/Qconv + br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0)) + DEALLOCATE(inQs,deltal,jxbl,bal) + + WRITE(*,*)"allocating" + qval_arr = (/ 3 /) + inQs = (/ 1.0 /) + inQs = (/ 1.0 /) + + n_k = SIZE(qval_arr) + k=1 + IF (k==1) THEN + ALLOCATE(all_RE_deltas(1,1,n_k)) + ALLOCATE(all_Im_deltas(1,1,n_k)) + ALLOCATE(all_inQs(1,n_k)) + ALLOCATE(all_growthrates(n_k)) + ALLOCATE(all_growthrate_locs(n_k)) + ENDIF + all_Re_deltas(:,:,k) = 0.0 + all_Im_deltas(:,:,k) = 0.0 + all_inQs(:,k) = 0.0 + + all_growthrates(k) = br_th !!!!!!!!!!!!!!!!!!!!!!!!! + all_growthrate_locs(k) = 0.0 + + + qval_arr = (/ 3 /) + inQs = (/ 1.0 /) + inQs = (/ 1.0 /) + + omegas_arr = (/ 0.0 /) + inQ_arr = (/ inQ /) + inQ_e_arr = (/ inQ_e /) + inQ_i_arr = (/ inQ_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ 0.0 /) + Im_deltaprime_arr = (/ 0.0 /) + inpr_arr = (/ inpr /) + + WRITE(*,*)"allocations successful" + + CALL slayer_netcdf_out(growthrates_flag,1,qval_arr, + $ all_growthrates,all_growthrate_locs, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr, + $ results) + stop ENDIF c----------------------------------------------------------------------- c find solutions based on simple torque balance. diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 217504ae0..31b6f17f2 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -51,28 +51,30 @@ END SUBROUTINE check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(msing,ReQ_n, - $ ImQ_n,qval_arr, - $ inQs,iinQs,all_roots,all_growthrates,all_growthrate_locs, - $ omegas_arr,inQ_arr,inQ_e_arr, - $ inQ_i_arr,psi_n_rational, - $ all_Re_deltas,all_inQs) - LOGICAL, PARAMETER :: layer_stabilityscan_flag = .true. - INTEGER, INTENT(IN) :: msing,ReQ_n,ImQ_n + SUBROUTINE slayer_netcdf_out( + $ growthrates_flag,msing,qval_arr, + $ all_growthrates,all_growthrate_locs, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ inpr_arr,Re_deltaprime_arr,Im_deltaprime_arr, + $ results) + LOGICAL, INTENT(IN) :: growthrates_flag + INTEGER, INTENT(IN) :: msing REAL(r8), DIMENSION(:), INTENT(IN) :: - $ inQs,iinQs,omegas_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,psi_n_rational, - $ all_growthrates,all_growthrate_locs + $ omegas_arr,inQ_arr, + $ inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr, + $ all_growthrates,all_growthrate_locs,Re_deltaprime_arr, + $ Im_deltaprime_arr INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr - REAL(r8), DIMENSION(:,:), INTENT(IN) :: all_inQs - REAL(r8), DIMENSION(:,:,:), INTENT(IN) :: all_Re_deltas - REAL(r8),DIMENSION(:,:,:),ALLOCATABLE,INTENT(IN) :: - $ all_roots + TYPE(result_type), INTENT(IN) :: results(8) INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, - $ r_dim,pr_id, qr_id, dp_id,shear_id,slice_id,inQs_id, - $ gamma_err_id,gamma_loc_id,roots_dim + $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id, + $ gamma_err_id,gamma_loc_id,roots_dim,Re_dp_id,Im_dp_id, + $ rdpp_id,idpp_id,inpr_id + INTEGER :: run, run_dimid, point_dimid, varids(4), + $ max_points + CHARACTER(64) :: ncfile LOGICAL, PARAMETER :: debug_flag = .FALSE. CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' @@ -94,6 +96,8 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n, IF(debug_flag) PRINT *," - Creating netcdf files" CALL check( nf90_create(ncfile, $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) + + max_points = maxval([(results(run)%count, run=1,msing)]) c ----------------------------------------------------------------------- c define global file attributes c ----------------------------------------------------------------------- @@ -107,18 +111,18 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n, ! define global attributes ! define dimensions IF(debug_flag) PRINT *," - Defining dimensions in netcdf" + IF(msing>0)THEN CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) - CALL check( nf90_def_dim(ncid,"ReQ_arr",ReQ_n, - $ ReQ_dim) ) !r_dim = q_rational - CALL check( nf90_def_var(ncid,"ReQ_arr",nf90_double,ReQ_dim, - $ ReQ_id)) - CALL check( nf90_def_dim(ncid,"ImQ_arr",ImQ_n,ImQ_dim) ) !r_dim = q_rational - CALL check( nf90_def_dim(ncid,"nroots",100,roots_dim) ) !r_dim = q_rational - CALL check( nf90_def_var(ncid,"ImQ_arr",nf90_double,ImQ_dim, - $ ImQ_id)) + ! CALL check( nf90_def_dim(ncid,"ReQ_arr",ReQ_n, + !$ ReQ_dim) ) !r_dim = q_rational + ! CALL check( nf90_def_var(ncid,"ReQ_arr",nf90_double,ReQ_dim, + !$ ReQ_id)) + ! CALL check( nf90_def_dim(ncid,"ImQ_arr",ImQ_n,ImQ_dim) ) !r_dim = q_rational + ! CALL check( nf90_def_var(ncid,"ImQ_arr",nf90_double,ImQ_dim, + !$ ImQ_id)) CALL check( nf90_def_var(ncid,"growthrates",nf90_double, $ qsing_dim,gamma_id)) CALL check( nf90_def_var(ncid,"growthrate_locs",nf90_double, @@ -135,20 +139,44 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n, $ qsing_dim,Q_i_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ qsing_dim,pr_id) ) + CALL check( nf90_def_var(ncid,"P",nf90_double, + $ qsing_dim,inpr_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ qsing_dim,qr_id) ) - ! CALL check( nf90_def_var(ncid,"shear",nf90_double,q_rational, - !$ shear_id) ) - ENDIF - ! define variables - IF(debug_flag) PRINT *," - Defining variables in netcdf" - IF (layer_stabilityscan_flag) THEN - CALL check( nf90_def_var(ncid, "Re_Delta", nf90_double, - $ (/ReQ_dim, ImQ_dim, qsing_dim/), dp_id) ) - CALL check( nf90_def_var(ncid, "inQs", nf90_double, - $ (/ReQ_dim, qsing_dim/), inQs_id) ) + !!! For results + ! Define dimensions + ! CALL check( nf90_def_dim(ncid, "msing", msing, dimids(1)) ) + ! CALL check( nf90_def_dim(ncid, "max_points", + !$ maxval(results%count), dimids(2)) ) + !CALL check( nf90_def_dim(ncid, "run", msing, run_dimid) ) + CALL check( nf90_def_dim(ncid, "points", max_points, + $ point_dimid) ) + + ! Define variables + CALL check( nf90_def_var(ncid, "Re_Qs", nf90_double, + $ [point_dimid, qsing_dim], varids(1)) ) + CALL check( nf90_def_var(ncid, "Im_Qs", nf90_double, + $ [point_dimid, qsing_dim], varids(2)) ) + CALL check( nf90_def_var(ncid, "Re_deltas", nf90_double, + $ [point_dimid, qsing_dim], varids(3)) ) + CALL check( nf90_def_var(ncid, "Im_deltas", nf90_double, + $ [point_dimid, qsing_dim], varids(4)) ) + ENDIF + + !IF (growthrates_flag) .OR. (analytic_) THEN + !CALL check( nf90_def_var(ncid, "Re_Deltas", nf90_double, + !$ (/ReQ_dim, ImQ_dim, qsing_dim/), Re_dp_id) ) + !CALL check( nf90_def_var(ncid, "Im_Deltas", nf90_double, + !$ (/ReQ_dim, ImQ_dim, qsing_dim/), Im_dp_id) ) + CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, + $ qsing_dim,rdpp_id) ) + CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, + $ qsing_dim,idpp_id) ) + ! CALL check( nf90_def_var(ncid, "inQs", nf90_double, + !$ (/ReQ_dim, qsing_dim/), inQs_id) ) + !ENDIF ! end definitions CALL check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- @@ -156,23 +184,41 @@ SUBROUTINE slayer_netcdf_out(msing,ReQ_n, c ----------------------------------------------------------------------- ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL check( nf90_put_var(ncid,ReQ_id, inQs)) - CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) + !CALL check( nf90_put_var(ncid,ReQ_id, inQs)) + !CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) + CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) CALL check( nf90_put_var(ncid,qr_id, qval_arr)) + ! IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" - IF (layer_stabilityscan_flag) THEN - CALL check( nf90_put_var(ncid,dp_id,all_Re_deltas)) - CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) - ENDIF - CALL check( nf90_put_var(ncid,gamma_id,all_growthrates)) - !CALL check( nf90_put_var(ncid,gamma_err_id, - !$ (/(all_growthrates(:,3,i),i=1,msing)/))) - CALL check( nf90_put_var(ncid,gamma_loc_id,all_growthrate_locs)) + !IF (growthrates_flag) THEN + CALL check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) + CALL check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) + !CALL check( nf90_put_var(ncid,Re_dp_id,all_Re_deltas)) + !CALL check( nf90_put_var(ncid,Im_dp_id,all_Im_deltas)) + !CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) + !ENDIF + !CALL check( nf90_put_var(ncid,gamma_id,all_growthrates)) + !CALL check( nf90_put_var(ncid,gamma_loc_id,all_growthrate_locs)) + ! Write data + DO run = 1, msing + CALL check( nf90_put_var(ncid, varids(1), results(run)%inQs, + $ start=[1, run], count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid, varids(2), results(run)%iinQs, + $ start=[1, run], count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid, varids(3), + $ results(run)%Re_deltas, start=[1, run], + $ count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid, varids(4), + $ results(run)%Im_deltas, start=[1, run], + $ count=[results(run)%count, 1]) ) + END DO + !CALL check(nf90_put_var(ncid,varids(5),[results(run)%count,run=1,msing]) ) + c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- @@ -186,29 +232,28 @@ END SUBROUTINE slayer_netcdf_out c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, - $ zeff_arr,shear,bt_arr,rs_arr,R0_arr,mu_i_arr,resm,nns_arr, - $ qval_arr,inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr, - $ intau_arr,inpr_arr,inpe_arr,omegas_arr, - $ outer_delta_arr) + SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, + $ ni_arr,ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, + $ resm,nns_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr,inpe_arr, + $ inQ_arr,omegas_arr,omegas_e_arr,omegas_i_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr) INTEGER, INTENT(IN) :: msing - REAL(r8), DIMENSION(:), INTENT(IN) :: - $ ne_arr,te_arr,ni_arr,ti_arr,zeff_arr,shear,bt_arr,rs_arr, - $ R0_arr,mu_i_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr, - $ inpe_arr,omegas_arr,outer_delta_arr + REAL(r8), DIMENSION(:), INTENT(IN) :: ne_arr,te_arr,ni_arr, + $ ti_arr,zeff_arr,shear,bt_arr,rs_arr, + $ R0_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr, + $ inpe_arr,omegas_arr,omegas_e_arr,omegas_i_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr,inQ_arr INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr,resm,nns_arr INTEGER :: i, ncid,r_id,qsing_dim,qsing_id,msing_id, $ i_dim,ne_id,te_id,ni_id,ti_id,zeff_id,shear_id,bt_id,rs_id, - $ R0_id,mu_i_id,resm_id,nns_id,inQ_id,inQ_e_id,inc_beta_id, + $ R0_id,resm_id,nns_id,inQ_id,inQ_e_id,inc_beta_id, $ inds_id,qval_id,inQ_i_id,qr_id, - $ intau_id,inpr_id,inpe_id,omegas_id,outer_delta_id, + $ intau_id,inpr_id,inpe_id,omegas_id,Re_delta_id,Im_delta_id, $ omegas_e_id,omegas_i_id CHARACTER(64) :: ncfile - LOGICAL, PARAMETER :: debug_flag = .FALSE. CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' c ----------------------------------------------------------------------- @@ -223,7 +268,6 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, bt_id=0 rs_id=0 R0_id=0 - mu_i_id=0 resm_id=0 nns_id=0 inQ_id=0 @@ -237,7 +281,8 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, inpr_id=0 inpe_id=0 omegas_id=0 - outer_delta_id=0 + Re_delta_id=0 + Im_delta_id=0 omegas_e_id=0 omegas_i_id=0 @@ -262,15 +307,8 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, IF(debug_flag) PRINT *," - Defining netcdf globals" CALL check( nf90_put_att(ncid,nf90_global,"title", $ "SLAYER outputs")) - ! define global attributes - !CALL check( nf90_put_att(ncid,nf90_global,'ro', ro)) - !CALL check( nf90_put_att(ncid,nf90_global,'psio', psio)) - !CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) - !CALL check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) - !CALL check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) - !CALL check( nf90_put_att(ncid,nf90_global,"n", nn)) CALL check( nf90_put_att(ncid,nf90_global,"version", version)) - ! define dimensions + IF(debug_flag) PRINT *," - Defining dimensions in netcdf" IF(msing>0)THEN @@ -296,22 +334,20 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, $ qsing_dim,rs_id)) CALL check( nf90_def_var(ncid,"R0",nf90_double, $ qsing_dim,R0_id)) - CALL check( nf90_def_var(ncid,"mu_i",nf90_double, - $ qsing_dim,mu_i_id)) + !CALL check( nf90_def_var(ncid,"mu_i",nf90_double, + !$ qsing_dim,mu_i_id)) CALL check( nf90_def_var(ncid,"resm",nf90_int, $ qsing_dim,resm_id)) CALL check( nf90_def_var(ncid,"nns_arr",nf90_int, $ qsing_dim,nns_id)) - CALL check( nf90_def_var(ncid,"qval",nf90_int, - $ qsing_dim,qval_id)) - CALL check( nf90_def_var(ncid,"q_rational",nf90_double, - $ qsing_dim,qr_id) ) + !CALL check( nf90_def_var(ncid,"qval",nf90_int, + !$ qsing_dim,qval_id)) CALL check( nf90_def_var(ncid,"Q",nf90_double, $ qsing_dim,inQ_id)) - CALL check( nf90_def_var(ncid,"Q_e",nf90_double, - $ qsing_dim,inQ_e_id)) - CALL check( nf90_def_var(ncid,"Q_i",nf90_double, - $ qsing_dim,inQ_i_id)) + ! CALL check( nf90_def_var(ncid,"Q_e",nf90_double, + !$ qsing_dim,inQ_e_id)) + ! CALL check( nf90_def_var(ncid,"Q_i",nf90_double, + !$ qsing_dim,inQ_i_id)) CALL check( nf90_def_var(ncid,"c_beta",nf90_double, $ qsing_dim,inc_beta_id)) CALL check( nf90_def_var(ncid,"ds",nf90_double, @@ -324,12 +360,14 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, $ qsing_dim,inpe_id)) CALL check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) - ! CALL check( nf90_def_var(ncid,"omegas_e",nf90_double, - !$ qsing_dim,omegas_e_id)) - ! CALL check( nf90_def_var(ncid,"omegas_i",nf90_double, - !$ qsing_dim,omegas_i_id)) - CALL check( nf90_def_var(ncid,"outer_delta",nf90_double, - $ qsing_dim,outer_delta_id)) + CALL check( nf90_def_var(ncid,"omegas_e",nf90_double, + $ qsing_dim,omegas_e_id)) + CALL check( nf90_def_var(ncid,"omegas_i",nf90_double, + $ qsing_dim,omegas_i_id)) + CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, + $ qsing_dim,Re_delta_id)) + CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, + $ qsing_dim,Im_delta_id)) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" @@ -341,30 +379,31 @@ SUBROUTINE slayer_netcdf_inputs(msing,ne_arr,te_arr,ni_arr,ti_arr, ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) CALL check( nf90_put_var(ncid,ne_id, ne_arr)) - CALL check( nf90_put_var(ncid,ni_id, ni_id)) - CALL check( nf90_put_var(ncid,te_id, te_id)) - CALL check( nf90_put_var(ncid,ti_id, ti_id)) + CALL check( nf90_put_var(ncid,ni_id, ni_arr)) + CALL check( nf90_put_var(ncid,te_id, te_arr)) + CALL check( nf90_put_var(ncid,ti_id, ti_arr)) CALL check( nf90_put_var(ncid,zeff_id, zeff_arr)) CALL check( nf90_put_var(ncid,shear_id, shear)) CALL check( nf90_put_var(ncid,bt_id, bt_arr)) CALL check( nf90_put_var(ncid,rs_id, rs_arr)) CALL check( nf90_put_var(ncid,R0_id, R0_arr)) - CALL check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) + !CALL check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) CALL check( nf90_put_var(ncid,resm_id, resm)) CALL check( nf90_put_var(ncid,nns_id, nns_arr)) !CALL check( nf90_put_var(ncid,qval_id, qval_arr)) CALL check( nf90_put_var(ncid,inQ_id, inQ_arr)) - CALL check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) - CALL check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) + !CALL check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) + !CALL check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) CALL check( nf90_put_var(ncid,inc_beta_id, inc_beta_arr)) CALL check( nf90_put_var(ncid,inds_id, inds_arr)) CALL check( nf90_put_var(ncid,intau_id, intau_arr)) CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) CALL check( nf90_put_var(ncid,inpe_id, inpe_arr)) CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) - !CALL check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) - !CALL check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) - CALL check( nf90_put_var(ncid,outer_delta_id,outer_delta_arr)) + CALL check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) + CALL check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) + CALL check( nf90_put_var(ncid,Re_delta_id,Re_deltaprime_arr)) + CALL check( nf90_put_var(ncid,Im_delta_id,Im_deltaprime_arr)) c ----------------------------------------------------------------------- c close file From 5c48a44fde712ada41828690266ea4d6420f8cc2 Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Thu, 3 Oct 2024 14:53:04 -0700 Subject: [PATCH 012/305] WIP: SLAYER gamma refinement algo --- slayer/gslayer.f | 208 +++++++++++++++++++++++++++++++++++++++-------- slayer/slayer.f | 13 +-- 2 files changed, 180 insertions(+), 41 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 9c89d6c80..a731041fe 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -275,36 +275,36 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, $ intau,inQ0,inpr,inpe INTEGER, INTENT(IN) :: qval,scan_radius,coarse_grid_size + INTEGER :: new_scan_radius,new_coarse_grid_size !REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs COMPLEX(r8) :: delta REAL(r8), INTENT(IN) :: deltaprime - INTEGER, PARAMETER :: fine_grid_size = 5 + INTEGER :: fine_grid_size, new_fine_grid_size REAL(r8), PARAMETER :: tolerance = 1.0E-6 REAL(r8) :: delta_real, delta_imag, threshold - INTEGER :: i, j, k, l, count + INTEGER :: i, j, k, l, m, count, match_count + LOGICAL :: repeat REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, - $ inQ_coarse, iinQ_coarse + $ inQ_coarse, iinQ_coarse, inQ_coarse_shifted + REAL(r8), DIMENSION(2) :: both_coarse_inQs TYPE(result_type), INTENT(INOUT) :: results + INTEGER :: max_points, new_max_points - ! Arrays to store results - !REAL(r8), ALLOCATABLE :: inQ_array(:), iinQ_array(:), - !$ delta_real_array(:), delta_imag_array(:) - INTEGER :: max_points + !!!!!!!!!!!!!!!! + repeat = .FALSE. - ! Calculate maximum possible number of points + fine_grid_size = 6 max_points = coarse_grid_size**2 * (1 + (fine_grid_size-1)**2) - ! Allocate arrays - !ALLOCATE(inQ_array(max_points), iinQ_array(max_points)) - !ALLOCATE(delta_real_array(max_points), - !$ delta_imag_array(max_points)) - ! Allocate arrays with maximum possible size ALLOCATE(results%inQs(max_points), results%iinQs(max_points)) ALLOCATE(results%Re_deltas(max_points), $ results%Im_deltas(max_points)) - + results%inQs=0.0 + results%iinQs=0.0 + results%Re_deltas=0.0 + results%Im_deltas=0.0 ! Initialize counter count = 0 @@ -312,10 +312,14 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, inQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) iinQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) + match_count = 0 ! Coarse grid loop DO i = 1, coarse_grid_size DO j = 1, coarse_grid_size inQ_coarse = -scan_radius + (i - 1) * inQ_step + inQ_coarse_shifted = -scan_radius + (i - 2) * inQ_step + both_coarse_inQs(1) = inQ_coarse + both_coarse_inQs(2) = inQ_coarse_shifted iinQ_coarse = -scan_radius + (j - 1) * iinQ_step ! Evaluate riccati function @@ -338,50 +342,175 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END IF ! Check if refinement is needed - IF (ABS(delta_real) > threshold) THEN + IF ((ABS(delta_real) > threshold)) THEN + ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, + !$ delta_real) == SIGN(1.0, deltaprime))) THEN + ! Fine grid loop - DO k = 2, fine_grid_size + DO m = 1, 2 + DO k = 2, fine_grid_size DO l = 2, fine_grid_size - inQ_fine = inQ_coarse + (k-1) * inQ_step / - $ (fine_grid_size - 1) - iinQ_fine = iinQ_coarse + (l-1) * iinQ_step / - $ (fine_grid_size - 1) + inQ_fine = both_coarse_inQs(m)+(k-1)*inQ_step/ + $ (fine_grid_size - 1) + iinQ_fine = iinQ_coarse + (l-1) * iinQ_step / + $ (fine_grid_size - 1) + + IF ((ABS(both_coarse_inQs(m) - inQ_fine) < + $ tolerance) .AND. (ABS(iinQ_coarse - + $ iinQ_fine) < tolerance)) CYCLE + + ! Evaluate riccati function + delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + IF (ABS(delta_real) > ABS(deltaprime)) THEN + match_count = match_count + 1 + END IF + + ! Store fine grid point + count = count + 1 + results%inQs(count) = inQ_fine + results%iinQs(count) = iinQ_fine + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END DO + END DO + END DO + END IF + END DO + END DO - IF ((ABS(inQ_coarse - inQ_fine) < - $ tolerance) .AND. (ABS(iinQ_coarse - - $ iinQ_fine) < tolerance)) CYCLE + !!!!! + + IF (match_count == 0) THEN + WRITE(*,*)"No match found, increasing scan radius" + repeat = .TRUE. + new_scan_radius = scan_radius + 2 + new_coarse_grid_size = coarse_grid_size + 100 + new_fine_grid_size = 8 + ELSE IF (match_count > 0 .AND. match_count < 3) THEN + WRITE(*,*)"Match not definitive, increasing scan resolution" + repeat = .TRUE. + new_scan_radius = scan_radius + new_coarse_grid_size = coarse_grid_size + new_fine_grid_size = 10 + ELSE + repeat = .FALSE. + WRITE(*,*)"Match found" + + END IF + + IF (repeat) THEN + WRITE(*,*)"Rerunning growth rate scan" + + !DEALLOCATE(results%inQs(max_points), results%iinQs(max_points)) + !DEALLOCATE(results%Re_deltas(max_points), + !$ results%Im_deltas(max_points)) + + new_max_points = new_coarse_grid_size**2 * (1 + + $ (new_fine_grid_size-1)**2) + + ! Resize arrays to new max number of points + CALL grow_array(results%inQs, max_points, new_max_points) + CALL grow_array(results%iinQs, max_points, new_max_points) + CALL grow_array(results%Re_deltas, max_points, new_max_points) + CALL grow_array(results%Im_deltas, max_points, new_max_points) + + results%inQs=0.0 + results%iinQs=0.0 + results%Re_deltas=0.0 + results%Im_deltas=0.0 + ! Initialize counter + count = 0 + + ! Calculate step sizes + inQ_step = (2.0 * new_scan_radius)/(new_coarse_grid_size - 1) + iinQ_step = (2.0 * new_scan_radius)/(new_coarse_grid_size - 1) + + match_count = 0 + ! Coarse grid loop + DO i = 1, new_coarse_grid_size + DO j = 1, new_coarse_grid_size + inQ_coarse = -new_scan_radius + (i - 1) * inQ_step + inQ_coarse_shifted = -scan_radius + (i - 2) * inQ_step + both_coarse_inQs(1) = inQ_coarse + both_coarse_inQs(2) = inQ_coarse_shifted + iinQ_coarse = -new_scan_radius + (j - 1) * iinQ_step - ! Evaluate riccati function + ! Evaluate riccati function + delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, + $ inds,intau,inpe,iinQ=iinQ_coarse) + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + ! Store coarse grid point + count = count + 1 + results%inQs(count) = inQ_coarse + results%iinQs(count) = iinQ_coarse + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + + IF (ABS(deltaprime) > 8) THEN + threshold = ABS(deltaprime)**(1./3.) + ELSE + threshold = 0.25 * ABS(deltaprime) + END IF + + ! Check if refinement is needed + IF ((ABS(delta_real) > threshold)) THEN + ! Fine grid loop + DO m = 1,2 + DO k = 2, new_fine_grid_size + DO l = 2, new_fine_grid_size + inQ_fine=both_coarse_inQs(m)+(k-1)*inQ_step/ + $ (new_fine_grid_size - 1) + iinQ_fine = iinQ_coarse + (l-1) * iinQ_step/ + $ (new_fine_grid_size - 1) + + IF ((ABS(both_coarse_inQs(m) - inQ_fine) < + $ tolerance) .AND. (ABS(iinQ_coarse - + $ iinQ_fine) < tolerance)) CYCLE + + ! Evaluate riccati function delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) delta_real = REAL(delta) delta_imag = AIMAG(delta) - ! Store fine grid point + IF (ABS(delta_real)>ABS(deltaprime)) THEN + match_count = match_count + 1 + END IF + + ! Store fine grid point count = count + 1 results%inQs(count) = inQ_fine results%iinQs(count) = iinQ_fine results%Re_deltas(count) = delta_real results%Im_deltas(count) = delta_imag + END DO END DO - END DO - END IF - END DO - END DO + END DO + END IF + END DO + END DO + + END IF ! Set the actual count of points results%count = count ! Resize arrays to actual number of points - CALL resize_array(results%inQs, count) - CALL resize_array(results%iinQs, count) - CALL resize_array(results%Re_deltas, count) - CALL resize_array(results%Im_deltas, count) + CALL shrink_array(results%inQs, count) + CALL shrink_array(results%iinQs, count) + CALL shrink_array(results%Re_deltas, count) + CALL shrink_array(results%Im_deltas, count) RETURN END SUBROUTINE growthrate_scan - SUBROUTINE resize_array(arr, new_size) + SUBROUTINE shrink_array(arr, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) INTEGER, INTENT(IN) :: new_size REAL(r8), ALLOCATABLE :: temp(:) @@ -389,6 +518,15 @@ SUBROUTINE resize_array(arr, new_size) ALLOCATE(temp(new_size)) temp(1:new_size) = arr(1:new_size) CALL move_alloc(temp, arr) - END SUBROUTINE resize_array + END SUBROUTINE shrink_array + SUBROUTINE grow_array(arr, old_size, new_size) + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) + INTEGER, INTENT(IN) :: old_size,new_size + REAL(r8), ALLOCATABLE :: temp(:) + + ALLOCATE(temp(new_size)) + temp(1:old_size) = arr(1:old_size) + CALL move_alloc(temp, arr) + END SUBROUTINE grow_array END MODULE gslayer_mod diff --git a/slayer/slayer.f b/slayer/slayer.f index dca43a3a8..b40460b45 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -316,6 +316,8 @@ PROGRAM slayer WRITE(*,*)"intau_arr=",intau_arr n_k = SIZE(qval_arr) + ALLOCATE(all_growthrates(n_k)) + ALLOCATE(all_growthrate_locs(n_k)) DO k=1,n_k WRITE(*,*) "Finding roots on q=", qval_arr(k), $ " rational surface" @@ -324,14 +326,13 @@ PROGRAM slayer $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), $ scan_radius,reQ_num,Re_deltaprime_arr(k), - $ results(n_k)) + $ results(k)) ! IF (k==1) THEN ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) ! ALLOCATE(all_Im_deltas(SIZE(inQs),SIZE(iinQs),n_k)) ! ALLOCATE(all_inQs(SIZE(inQs),n_k)) - ALLOCATE(all_growthrates(n_k)) - ALLOCATE(all_growthrate_locs(n_k)) + ! ENDIF ! all_Re_deltas(:,:,k) = Re_deltas ! all_Im_deltas(:,:,k) = Im_deltas @@ -366,19 +367,19 @@ PROGRAM slayer WRITE(*,*)"running analytic scan" qval_arr = (/ 2 /) - omegas_arr = (/ 0.0 /) + omegas_arr = (/ 1000.0 /) inQ_arr = (/ inQ /) inQ_e_arr = (/ inQ_e /) inQ_i_arr = (/ inQ_i /) psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ 5.0 /) + Re_deltaprime_arr = (/ 10.0 /) Im_deltaprime_arr = (/ 0.00001 /) inpr_arr = (/ inpr /) CALL growthrate_scan(2,inQ,inQ_e, $ inQ_i,inc_beta,inds, $ intau,inQ,inpr,inpe, - $ scan_radius,reQ_num,Re_deltaprime_arr(k), + $ scan_radius,reQ_num,Re_deltaprime_arr(1), $ results(1)) ! WRITE(*,*)"SIZE(inQs)",SIZE(inQs) From 27168785ff87629bac068f1b367d09baf581884c Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Thu, 17 Oct 2024 13:33:37 -0700 Subject: [PATCH 013/305] WIP: new centered AMR algo --- input/slayer.in | 6 +- slayer/gslayer.f | 348 ++++++++++++++++++++++------------------- slayer/slayer_netcdf.f | 133 +++++++--------- 3 files changed, 240 insertions(+), 247 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 3490aab22..35a904aeb 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -61,8 +61,10 @@ netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q growthrates_flag=t ! Calculate growthrates on each rational surface - layer_stabilityscan_flag=t ! Generate Re(Q),Im(Q) scans for each rational surface - !bal_flag=f ! calculate the resonant field penetration threshold from torque balance + analytic_growthrates_flag=f ! Generate Re(Q),Im(Q) scans for each rational surface + br_th_flag=t ! Calculate br_th + compress_deltas=f ! Only output essential growth rate grid points + bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file diff --git a/slayer/gslayer.f b/slayer/gslayer.f index a731041fe..3bd9b47b9 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -266,35 +266,41 @@ END SUBROUTINE old_growthrate_scan c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,scan_radius,coarse_grid_size, - $ deltaprime,results) + $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, + $ compress_deltas,deltaprime,results) c----------------------------------------------------------------------- c Declarations c----------------------------------------------------------------------- ! Inputs REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, $ intau,inQ0,inpr,inpe - INTEGER, INTENT(IN) :: qval,scan_radius,coarse_grid_size - INTEGER :: new_scan_radius,new_coarse_grid_size + INTEGER, INTENT(IN) :: qval,scan_radius,ncoarse + INTEGER :: new_scan_radius,new_ncoarse !REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs COMPLEX(r8) :: delta REAL(r8), INTENT(IN) :: deltaprime - INTEGER :: fine_grid_size, new_fine_grid_size + INTEGER :: nfine, new_nfine REAL(r8), PARAMETER :: tolerance = 1.0E-6 REAL(r8) :: delta_real, delta_imag, threshold INTEGER :: i, j, k, l, m, count, match_count LOGICAL :: repeat + LOGICAL, INTENT(IN) :: compress_deltas REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, - $ inQ_coarse, iinQ_coarse, inQ_coarse_shifted - REAL(r8), DIMENSION(2) :: both_coarse_inQs + $ inQ_coarse, iinQ_coarse TYPE(result_type), INTENT(INOUT) :: results INTEGER :: max_points, new_max_points - + INTEGER :: ci, cj, nx, ny + REAL(r8) :: dx, dy, overlap_factor + INTEGER :: fi, fj + REAL(r8) :: fine_dx, fine_dy, overlap_x, overlap_y + REAL(r8) :: x_start, x_end, y_start, y_end, x, y !!!!!!!!!!!!!!!! repeat = .FALSE. - - fine_grid_size = 6 - max_points = coarse_grid_size**2 * (1 + (fine_grid_size-1)**2) + dx = 1.0 + dy = 1.0 + nfine = 6 + overlap_factor = 0.5 + max_points = ncoarse**2 * (1 + (nfine-1)**2) ! Allocate arrays with maximum possible size ALLOCATE(results%inQs(max_points), results%iinQs(max_points)) @@ -309,77 +315,84 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, count = 0 ! Calculate step sizes - inQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) - iinQ_step = (2.0 * scan_radius) / (coarse_grid_size - 1) + inQ_step = (2.0 * scan_radius) / (ncoarse - 1) + iinQ_step = (2.0 * scan_radius) / (ncoarse - 1) + dx = inQ_step + dy = iinQ_step match_count = 0 ! Coarse grid loop - DO i = 1, coarse_grid_size - DO j = 1, coarse_grid_size - inQ_coarse = -scan_radius + (i - 1) * inQ_step - inQ_coarse_shifted = -scan_radius + (i - 2) * inQ_step - both_coarse_inQs(1) = inQ_coarse - both_coarse_inQs(2) = inQ_coarse_shifted - iinQ_coarse = -scan_radius + (j - 1) * iinQ_step - - ! Evaluate riccati function - delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, + DO i = 1, ncoarse + DO j = 1, ncoarse + inQ_coarse = -scan_radius + (i - 1) * inQ_step + iinQ_coarse = -scan_radius + (j - 1) * iinQ_step + + ! Evaluate riccati function + delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, $ inds,intau,inpe,iinQ=iinQ_coarse) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) - - ! Store coarse grid point - count = count + 1 - results%inQs(count) = inQ_coarse - results%iinQs(count) = iinQ_coarse - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - - IF (ABS(deltaprime) > 8) THEN - threshold = ABS(deltaprime)**(1./3.) - ELSE - threshold = 0.25 * ABS(deltaprime) - END IF - - ! Check if refinement is needed - IF ((ABS(delta_real) > threshold)) THEN - ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, - !$ delta_real) == SIGN(1.0, deltaprime))) THEN - - ! Fine grid loop - DO m = 1, 2 - DO k = 2, fine_grid_size - DO l = 2, fine_grid_size - inQ_fine = both_coarse_inQs(m)+(k-1)*inQ_step/ - $ (fine_grid_size - 1) - iinQ_fine = iinQ_coarse + (l-1) * iinQ_step / - $ (fine_grid_size - 1) - - IF ((ABS(both_coarse_inQs(m) - inQ_fine) < - $ tolerance) .AND. (ABS(iinQ_coarse - - $ iinQ_fine) < tolerance)) CYCLE - - ! Evaluate riccati function - delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + IF (.NOT. compress_deltas) THEN + ! Store coarse grid point + count = count + 1 + results%inQs(count) = inQ_coarse + results%iinQs(count) = iinQ_coarse + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END IF + + IF (ABS(deltaprime) > 8) THEN + threshold = ABS(deltaprime)**(1./3.) + ELSE + threshold = 0.25 * ABS(deltaprime) + END IF + + ! Check if refinement is needed + IF ((ABS(delta_real) > threshold)) THEN + ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, + !$ delta_real) == SIGN(1.0, deltaprime))) THEN + + ! Fine grid loop + fine_dx = dx / nfine + fine_dy = dy / nfine + + overlap_x = overlap_factor * fine_dx + overlap_y = overlap_factor * fine_dy + + x_start = inQ_coarse - dx/2 + overlap_x + x_end = inQ_coarse + dx/2 - overlap_x + y_start = iinQ_coarse - dy/2 + overlap_y + y_end = iinQ_coarse + dy/2 - overlap_y + + DO fj = 0, nfine-1 + iinQ_fine = y_start + fj * (y_end-y_start) / (nfine-1) + DO fi = 0, nfine-1 + inQ_fine = x_start + fi * (x_end-x_start) / (nfine-1) + IF ((ABS(inQ_coarse - inQ_fine) < + $ tolerance) .AND. (ABS(iinQ_coarse - + $ iinQ_fine) < tolerance)) CYCLE + + ! Evaluate riccati function + delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) - - IF (ABS(delta_real) > ABS(deltaprime)) THEN - match_count = match_count + 1 - END IF - - ! Store fine grid point - count = count + 1 - results%inQs(count) = inQ_fine - results%iinQs(count) = iinQ_fine - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END DO - END DO - END DO - END IF - END DO + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + IF (ABS(delta_real)>ABS(deltaprime)) THEN + match_count = match_count + 1 + END IF + + ! Store fine grid point + count = count + 1 + results%inQs(count) = inQ_fine + results%iinQs(count) = iinQ_fine + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END DO + END DO + END IF + END DO END DO !!!!! @@ -388,14 +401,14 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, WRITE(*,*)"No match found, increasing scan radius" repeat = .TRUE. new_scan_radius = scan_radius + 2 - new_coarse_grid_size = coarse_grid_size + 100 - new_fine_grid_size = 8 + new_ncoarse = ncoarse + 100 + new_nfine = nfine ELSE IF (match_count > 0 .AND. match_count < 3) THEN WRITE(*,*)"Match not definitive, increasing scan resolution" repeat = .TRUE. new_scan_radius = scan_radius - new_coarse_grid_size = coarse_grid_size - new_fine_grid_size = 10 + new_ncoarse = ncoarse + new_nfine = 8 ELSE repeat = .FALSE. WRITE(*,*)"Match found" @@ -403,98 +416,103 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END IF IF (repeat) THEN - WRITE(*,*)"Rerunning growth rate scan" - - !DEALLOCATE(results%inQs(max_points), results%iinQs(max_points)) - !DEALLOCATE(results%Re_deltas(max_points), - !$ results%Im_deltas(max_points)) - - new_max_points = new_coarse_grid_size**2 * (1 + - $ (new_fine_grid_size-1)**2) - - ! Resize arrays to new max number of points - CALL grow_array(results%inQs, max_points, new_max_points) - CALL grow_array(results%iinQs, max_points, new_max_points) - CALL grow_array(results%Re_deltas, max_points, new_max_points) - CALL grow_array(results%Im_deltas, max_points, new_max_points) - - results%inQs=0.0 - results%iinQs=0.0 - results%Re_deltas=0.0 - results%Im_deltas=0.0 - ! Initialize counter - count = 0 - - ! Calculate step sizes - inQ_step = (2.0 * new_scan_radius)/(new_coarse_grid_size - 1) - iinQ_step = (2.0 * new_scan_radius)/(new_coarse_grid_size - 1) - - match_count = 0 - ! Coarse grid loop - DO i = 1, new_coarse_grid_size - DO j = 1, new_coarse_grid_size - inQ_coarse = -new_scan_radius + (i - 1) * inQ_step - inQ_coarse_shifted = -scan_radius + (i - 2) * inQ_step - both_coarse_inQs(1) = inQ_coarse - both_coarse_inQs(2) = inQ_coarse_shifted - iinQ_coarse = -new_scan_radius + (j - 1) * iinQ_step - - ! Evaluate riccati function - delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, - $ inds,intau,inpe,iinQ=iinQ_coarse) + WRITE(*,*)"Rerunning growth rate scan" + + new_max_points = new_ncoarse**2 * (1 + + $ (new_nfine-1)**2) + + ! Resize arrays to new max number of points + CALL grow_array(results%inQs, max_points, new_max_points) + CALL grow_array(results%iinQs, max_points, new_max_points) + CALL grow_array(results%Re_deltas, max_points, new_max_points) + CALL grow_array(results%Im_deltas, max_points, new_max_points) + + results%inQs=0.0 + results%iinQs=0.0 + results%Re_deltas=0.0 + results%Im_deltas=0.0 + ! Initialize counter + count = 0 + + ! Calculate step sizes + inQ_step = (2.0 * new_scan_radius)/(new_ncoarse - 1) + iinQ_step = (2.0 * new_scan_radius)/(new_ncoarse - 1) + + match_count = 0 + + ! Coarse grid loop + DO i = 1, new_ncoarse + DO j = 1, new_ncoarse + inQ_coarse = -new_scan_radius + (i - 1) * inQ_step + iinQ_coarse = -new_scan_radius + (j - 1) * iinQ_step + + ! Evaluate riccati function + delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, + $ inds,intau,inpe,iinQ=iinQ_coarse) + delta_real = REAL(delta) + delta_imag = AIMAG(delta) + + IF (.NOT. compress_deltas) THEN + ! Store coarse grid point + count = count + 1 + results%inQs(count) = inQ_coarse + results%iinQs(count) = iinQ_coarse + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END IF + + IF (ABS(deltaprime) > 8) THEN + threshold = ABS(deltaprime)**(1./3.) + ELSE + threshold = 0.25 * ABS(deltaprime) + END IF + + ! Check if refinement is needed + IF ((ABS(delta_real) > threshold)) THEN + ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, + !$ delta_real) == SIGN(1.0, deltaprime))) THEN + + ! Fine grid loop + fine_dx = dx / new_nfine + fine_dy = dy / new_nfine + + overlap_x = overlap_factor * fine_dx + overlap_y = overlap_factor * fine_dy + + x_start = inQ_coarse - dx/2 - overlap_x + x_end = inQ_coarse + dx/2 + overlap_x + y_start = iinQ_coarse - dy/2 - overlap_y + y_end = iinQ_coarse + dy/2 + overlap_y + + DO fj = 0, nfine-1 + iinQ_fine = y_start+fj * (y_end-y_start) / (new_nfine-1) + DO fi = 0, nfine-1 + inQ_fine = x_start+fi * (x_end-x_start) /(new_nfine-1) + IF ((ABS(inQ_coarse - inQ_fine) < + $ tolerance) .AND. (ABS(iinQ_coarse - + $ iinQ_fine) < tolerance)) CYCLE + + ! Evaluate riccati function + delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, + $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) delta_real = REAL(delta) delta_imag = AIMAG(delta) - ! Store coarse grid point + IF (ABS(delta_real)>ABS(deltaprime)) THEN + match_count = match_count + 1 + END IF + + ! Store fine grid point count = count + 1 - results%inQs(count) = inQ_coarse - results%iinQs(count) = iinQ_coarse + results%inQs(count) = inQ_fine + results%iinQs(count) = iinQ_fine results%Re_deltas(count) = delta_real results%Im_deltas(count) = delta_imag - - IF (ABS(deltaprime) > 8) THEN - threshold = ABS(deltaprime)**(1./3.) - ELSE - threshold = 0.25 * ABS(deltaprime) - END IF - - ! Check if refinement is needed - IF ((ABS(delta_real) > threshold)) THEN - ! Fine grid loop - DO m = 1,2 - DO k = 2, new_fine_grid_size - DO l = 2, new_fine_grid_size - inQ_fine=both_coarse_inQs(m)+(k-1)*inQ_step/ - $ (new_fine_grid_size - 1) - iinQ_fine = iinQ_coarse + (l-1) * iinQ_step/ - $ (new_fine_grid_size - 1) - - IF ((ABS(both_coarse_inQs(m) - inQ_fine) < - $ tolerance) .AND. (ABS(iinQ_coarse - - $ iinQ_fine) < tolerance)) CYCLE - - ! Evaluate riccati function - delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) - - IF (ABS(delta_real)>ABS(deltaprime)) THEN - match_count = match_count + 1 - END IF - - ! Store fine grid point - count = count + 1 - results%inQs(count) = inQ_fine - results%iinQs(count) = iinQ_fine - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END DO - END DO - END DO - END IF + END DO END DO + END IF END DO + END DO END IF diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 31b6f17f2..f4fbdbb45 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -51,19 +51,18 @@ END SUBROUTINE check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out( - $ growthrates_flag,msing,qval_arr, - $ all_growthrates,all_growthrate_locs, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ inpr_arr,Re_deltaprime_arr,Im_deltaprime_arr, - $ results) - LOGICAL, INTENT(IN) :: growthrates_flag + SUBROUTINE slayer_netcdf_out(growthrates_flag, + $ analytic_growthrates_flag,br_th_flag,msing,qval_arr, + $ br_th,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ inpr_arr,Re_deltaprime_arr,Im_deltaprime_arr,results) + LOGICAL, INTENT(IN) :: growthrates_flag,analytic_growthrates_flag, + $ br_th_flag INTEGER, INTENT(IN) :: msing REAL(r8), DIMENSION(:), INTENT(IN) :: $ omegas_arr,inQ_arr, $ inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr, - $ all_growthrates,all_growthrate_locs,Re_deltaprime_arr, - $ Im_deltaprime_arr + $ Re_deltaprime_arr,Im_deltaprime_arr + REAL(r8), INTENT(IN) :: br_th INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr TYPE(result_type), INTENT(IN) :: results(8) INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, @@ -71,7 +70,7 @@ SUBROUTINE slayer_netcdf_out( $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id, $ gamma_err_id,gamma_loc_id,roots_dim,Re_dp_id,Im_dp_id, - $ rdpp_id,idpp_id,inpr_id + $ rdpp_id,idpp_id,inpr_id,br_th_id INTEGER :: run, run_dimid, point_dimid, varids(4), $ max_points @@ -116,19 +115,6 @@ SUBROUTINE slayer_netcdf_out( CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) - ! CALL check( nf90_def_dim(ncid,"ReQ_arr",ReQ_n, - !$ ReQ_dim) ) !r_dim = q_rational - ! CALL check( nf90_def_var(ncid,"ReQ_arr",nf90_double,ReQ_dim, - !$ ReQ_id)) - ! CALL check( nf90_def_dim(ncid,"ImQ_arr",ImQ_n,ImQ_dim) ) !r_dim = q_rational - ! CALL check( nf90_def_var(ncid,"ImQ_arr",nf90_double,ImQ_dim, - !$ ImQ_id)) - CALL check( nf90_def_var(ncid,"growthrates",nf90_double, - $ qsing_dim,gamma_id)) - CALL check( nf90_def_var(ncid,"growthrate_locs",nf90_double, - $ qsing_dim,gamma_loc_id)) - ! CALL check( nf90_def_var(ncid,"growthrates_inQ",nf90_double, - !$ (/roots_dim, qsing_dim/),gamma_loc_id)) CALL check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) CALL check( nf90_def_var(ncid,"Q",nf90_double, @@ -143,49 +129,42 @@ SUBROUTINE slayer_netcdf_out( $ qsing_dim,inpr_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ qsing_dim,qr_id) ) - - !!! For results - ! Define dimensions - ! CALL check( nf90_def_dim(ncid, "msing", msing, dimids(1)) ) - ! CALL check( nf90_def_dim(ncid, "max_points", - !$ maxval(results%count), dimids(2)) ) - !CALL check( nf90_def_dim(ncid, "run", msing, run_dimid) ) CALL check( nf90_def_dim(ncid, "points", max_points, $ point_dimid) ) - ! Define variables - CALL check( nf90_def_var(ncid, "Re_Qs", nf90_double, - $ [point_dimid, qsing_dim], varids(1)) ) - CALL check( nf90_def_var(ncid, "Im_Qs", nf90_double, - $ [point_dimid, qsing_dim], varids(2)) ) - CALL check( nf90_def_var(ncid, "Re_deltas", nf90_double, - $ [point_dimid, qsing_dim], varids(3)) ) - CALL check( nf90_def_var(ncid, "Im_deltas", nf90_double, - $ [point_dimid, qsing_dim], varids(4)) ) + IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + CALL check( nf90_def_var(ncid,"growthrates",nf90_double, + $ qsing_dim,gamma_id)) + CALL check(nf90_def_var(ncid,"growthrate_locs",nf90_double, + $ qsing_dim,gamma_loc_id)) + CALL check( nf90_def_var(ncid, "Re_Qs", nf90_double, + $ [point_dimid, qsing_dim], varids(1)) ) + CALL check( nf90_def_var(ncid, "Im_Qs", nf90_double, + $ [point_dimid, qsing_dim], varids(2)) ) + CALL check( nf90_def_var(ncid, "Re_deltas", nf90_double, + $ [point_dimid, qsing_dim], varids(3)) ) + CALL check( nf90_def_var(ncid, "Im_deltas", nf90_double, + $ [point_dimid, qsing_dim], varids(4)) ) + END IF - ENDIF + IF (br_th_flag) THEN + CALL check( nf90_def_var(ncid,"br_th",nf90_double, + $ qsing_dim,br_th_id) ) + END IF + END IF - !IF (growthrates_flag) .OR. (analytic_) THEN - !CALL check( nf90_def_var(ncid, "Re_Deltas", nf90_double, - !$ (/ReQ_dim, ImQ_dim, qsing_dim/), Re_dp_id) ) - !CALL check( nf90_def_var(ncid, "Im_Deltas", nf90_double, - !$ (/ReQ_dim, ImQ_dim, qsing_dim/), Im_dp_id) ) - CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, - $ qsing_dim,rdpp_id) ) - CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, - $ qsing_dim,idpp_id) ) - ! CALL check( nf90_def_var(ncid, "inQs", nf90_double, - !$ (/ReQ_dim, qsing_dim/), inQs_id) ) - !ENDIF + IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, + $ qsing_dim,rdpp_id) ) + CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, + $ qsing_dim,idpp_id) ) + END IF ! end definitions CALL check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- - ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) - !CALL check( nf90_put_var(ncid,ReQ_id, inQs)) - !CALL check( nf90_put_var(ncid,ImQ_id, iinQs)) CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) @@ -194,31 +173,25 @@ SUBROUTINE slayer_netcdf_out( CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) CALL check( nf90_put_var(ncid,qr_id, qval_arr)) - ! IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" - !IF (growthrates_flag) THEN - CALL check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) - CALL check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) - !CALL check( nf90_put_var(ncid,Re_dp_id,all_Re_deltas)) - !CALL check( nf90_put_var(ncid,Im_dp_id,all_Im_deltas)) - !CALL check( nf90_put_var(ncid,inQs_id,all_inQs)) - !ENDIF - !CALL check( nf90_put_var(ncid,gamma_id,all_growthrates)) - !CALL check( nf90_put_var(ncid,gamma_loc_id,all_growthrate_locs)) - ! Write data - DO run = 1, msing - CALL check( nf90_put_var(ncid, varids(1), results(run)%inQs, - $ start=[1, run], count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid, varids(2), results(run)%iinQs, - $ start=[1, run], count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid, varids(3), - $ results(run)%Re_deltas, start=[1, run], - $ count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid, varids(4), - $ results(run)%Im_deltas, start=[1, run], - $ count=[results(run)%count, 1]) ) - END DO - !CALL check(nf90_put_var(ncid,varids(5),[results(run)%count,run=1,msing]) ) - + IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + CALL check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) + CALL check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) + DO run = 1, msing + CALL check( nf90_put_var(ncid,varids(1),results(run)%inQs, + $ start=[1, run], count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid,varids(2),results(run)%iinQs, + $ start=[1, run], count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid, varids(3), + $ results(run)%Re_deltas, start=[1, run], + $ count=[results(run)%count, 1]) ) + CALL check( nf90_put_var(ncid, varids(4), + $ results(run)%Im_deltas, start=[1, run], + $ count=[results(run)%count, 1]) ) + END DO + END IF + IF (br_th_flag) THEN + CALL check( nf90_put_var(ncid,br_th_id, (/ br_th /))) + END IF c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- From 0ae4888488142238b758b22ebc53571dbe22b4c0 Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Sun, 27 Oct 2024 13:20:01 -0700 Subject: [PATCH 014/305] no AMR if small deltaprime --- slayer/gslayer.f | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 3bd9b47b9..9c8a37ac8 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -300,7 +300,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, dy = 1.0 nfine = 6 overlap_factor = 0.5 - max_points = ncoarse**2 * (1 + (nfine-1)**2) + max_points = ncoarse**2 * ((nfine)**2 - 1) ! Allocate arrays with maximum possible size ALLOCATE(results%inQs(max_points), results%iinQs(max_points)) @@ -333,7 +333,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, delta_real = REAL(delta) delta_imag = AIMAG(delta) - IF (.NOT. compress_deltas) THEN + IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 2)) THEN ! Store coarse grid point count = count + 1 results%inQs(count) = inQ_coarse @@ -349,7 +349,8 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END IF ! Check if refinement is needed - IF ((ABS(delta_real) > threshold)) THEN + IF ((ABS(delta_real) > threshold) .AND. + $ (ABS(deltaprime) > 2)) THEN ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN @@ -452,7 +453,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, delta_real = REAL(delta) delta_imag = AIMAG(delta) - IF (.NOT. compress_deltas) THEN + IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 2)) THEN ! Store coarse grid point count = count + 1 results%inQs(count) = inQ_coarse @@ -468,7 +469,8 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END IF ! Check if refinement is needed - IF ((ABS(delta_real) > threshold)) THEN + IF ((ABS(delta_real) > threshold) .AND. + $ (ABS(deltaprime) > 2)) THEN ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN @@ -515,15 +517,24 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END DO END IF - + WRITE(*,*)"Passed IF return" + WRITE(*,*)"count: ",count + WRITE(*,*)"max_points: ",max_points ! Set the actual count of points results%count = count - ! Resize arrays to actual number of points - CALL shrink_array(results%inQs, count) - CALL shrink_array(results%iinQs, count) - CALL shrink_array(results%Re_deltas, count) - CALL shrink_array(results%Im_deltas, count) + + WRITE(*,*)"Passed Set the actual count of points" + IF (count < max_points) THEN + ! Resize arrays to actual number of points + CALL shrink_array(results%inQs, count) + CALL shrink_array(results%iinQs, count) + CALL shrink_array(results%Re_deltas, count) + CALL shrink_array(results%Im_deltas, count) + END IF + + WRITE(*,*)"Arrays successfully shrunk" + RETURN END SUBROUTINE growthrate_scan From 27dcf0a3e9941bac64508c8da7c446e96b18a758 Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Mon, 28 Oct 2024 13:38:18 -0700 Subject: [PATCH 015/305] function call bug fixes --- slayer/delta.f | 354 +++++++++++++++++++++---------------------- slayer/layerinputs.f | 16 +- slayer/slayer.f | 74 +++------ 3 files changed, 206 insertions(+), 238 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 767a80dae..99463b589 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -26,7 +26,7 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - + Q=inQ IF(present(iinQ)) Q=inQ+ifac*iinQ Q_e=inQ_e @@ -36,7 +36,7 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, c_beta=inc_beta ds=inds tau=intau - + IF ((layfac>0).AND.(ABS(Q-Q_e)xout) istep=istep+1 CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) - WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) + WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) WRITE(out2_unit,'(1x,3(es17.8e3))')x,REAL(y),AIMAG(y) - ENDDO + ENDDO CLOSE(bin_unit) CLOSE(out2_unit) ELSE @@ -98,10 +98,10 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, ENDIF ! w=0 when Q=Q_e. Why? - + CALL w_der(neq,x,y,dy) riccati=pi/dy(1) - DEALLOCATE(atol,y,dy,iwork,rwork) + DEALLOCATE(atol,y,dy,iwork,rwork) END FUNCTION riccati c----------------------------------------------------------------------- @@ -127,37 +127,37 @@ SUBROUTINE w_der(neq,x,y,dy) IF (parflow_flag) THEN C1=((1 + tau)*x**2*pe* $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i)))) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))) $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -165,106 +165,106 @@ SUBROUTINE w_der(neq,x,y,dy) $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) - + C1p=((1 + tau)*x**2*pe* $ ((2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ **2)/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** $ 3 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (12*ds**2*pr*(1 + tau)*x**2 + + $ (12*ds**2*pr*(1 + tau)*x**2 + $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** $ 2 - (2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** $ 2 - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((12*ds**2*pr*(1 + tau)*x**2 + + $ (-((12*ds**2*pr*(1 + tau)*x**2 + $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) $ + (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2 $ - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)**2* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**3 $ + ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -273,14 +273,14 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) $ - ((1 + tau)*x**2*pe* - $ (2*pr*x + 2*x*pe + + $ (2*pr*x + 2*x*pe + $ (ds**2*(1 + tau)*x**6*pe**2* $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ $ c_beta**2))/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2) - + $ c_beta**2 + + $ ifac*(Q - Q_e))**2) - $ (6*ds**2*(1 + tau)*x**5*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -295,41 +295,41 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) $ *(-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -340,41 +340,41 @@ SUBROUTINE w_der(neq,x,y,dy) $ c_beta**2 + ifac*(Q - Q_e))) $ **2 + (2*(1 + tau)*x*pe* $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -382,7 +382,7 @@ SUBROUTINE w_der(neq,x,y,dy) $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) - + C2=((1 + tau)*x**2*pe* $ ((ds**2*x**4*pe)/ $ (c_beta**2* @@ -392,13 +392,13 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e)) $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -406,15 +406,15 @@ SUBROUTINE w_der(neq,x,y,dy) $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) - + C2p=((1 + tau)*x**2*pe* $ (-((ds**2*x**4*pe* $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ $ c_beta**2))/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2)) + + $ c_beta**2 + + $ ifac*(Q - Q_e))**2)) + $ (4*ds**2*x**3*pe)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -425,40 +425,40 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e)) $ **2 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + + $ (4*ds**2*pr*(1 + tau)*x**3 + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ (-((4*ds**2*pr*(1 + tau)*x**3 + $ 2*x* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2* $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -467,14 +467,14 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e))) $ - ((1 + tau)*x**2*pe* - $ (2*pr*x + 2*x*pe + + $ (2*pr*x + 2*x*pe + $ (ds**2*(1 + tau)*x**6*pe**2* $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ $ c_beta**2))/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2) - + $ c_beta**2 + + $ ifac*(Q - Q_e))**2) - $ (6*ds**2*(1 + tau)*x**5*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -496,15 +496,15 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e)) $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -522,15 +522,15 @@ SUBROUTINE w_der(neq,x,y,dy) $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e)) $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + $ ifac*ds**2*(Q - Q_i)))/ $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ifac*Q + pr*x**2 + x**2*pe - $ (ds**2*(1 + tau)*x**6*pe**2)/ $ (c_beta**2* $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -543,24 +543,24 @@ SUBROUTINE w_der(neq,x,y,dy) C1p=0 C2=0 C2p=0 - ENDIF - + ENDIF + IF (PeOhmOnly_flag) THEN - G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + + G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) ELSE - G=(x**2*pe + - $ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + + G=(x**2*pe + + $ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) ENDIF - + C3=x**2/(x**2 + (ds**2*(1 + tau)*x**4*pe)/ $ c_beta**2 + ifac*(Q - Q_e)) - + C3p=-((x**2*(2*x + (4*ds**2*(1 + tau)*x**3*pe)/ $ c_beta**2))/ $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ @@ -570,11 +570,11 @@ SUBROUTINE w_der(neq,x,y,dy) $ c_beta**2 + (0,1)*(Q - Q_e)) A1=(C1 + (C3p/C3)*(C2 + 1) + C2p)/(C2 + 1) - + A2=(C1p + C1*(C3p/C3) - G/C3)/(C2 + 1) - + dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x - + RETURN END SUBROUTINE w_der c----------------------------------------------------------------------- @@ -619,7 +619,7 @@ FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - + Q=inQ Q_e=inQ_e Q_i=inQ_i @@ -638,10 +638,10 @@ FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) istate = 1 iopt = 0 mf = 10 - liw = 20 + liw = 20 lrw = 22+16*neq ALLOCATE(iwork(liw),rwork(lrw)) - + xintv = 0.1 x=0.1 istep=1 @@ -668,12 +668,12 @@ END FUNCTION directred c direct integration (obsolete). c----------------------------------------------------------------------- SUBROUTINE phi_der(neq,x,y,dy) - + INTEGER, INTENT(IN) :: neq REAL(r8), INTENT(IN) :: x COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy - COMPLEX(r8), PARAMETER :: ifac=(0,1) + COMPLEX(r8), PARAMETER :: ifac=(0,1) dy(1)=(1+ifac*(Q-Q_e)*x**2.0)/x**2.0*y(2) dy(2)=(-Q*(Q-Q_i)*x**4.0+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 @@ -681,6 +681,6 @@ SUBROUTINE phi_der(neq,x,y,dy) $ +(c_beta**2.0+ifac*(Q-Q_i)*ds**2.0)*x**6.0 $ +(1+tau)*pr*ds**2.0*x**4.0)*y(1) RETURN - END SUBROUTINE phi_der - - END MODULE delta_mod + END SUBROUTINE phi_der + + END MODULE delta_mod \ No newline at end of file diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 9b1bf2cc3..7b4fdcd78 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -224,8 +224,8 @@ END FUNCTION issurfint c subprogram 3. build_inputs. c build input arrays for SLAYER c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, - $ pe_flag,qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe, + $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, $ inpr_arr,inpe_arr,omegas_arr,Re_deltaprime_arr, $ Im_deltaprime_arr) @@ -233,10 +233,10 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, c declarations. c----------------------------------------------------------------------- ! Inputs - LOGICAL, INTENT(IN) :: growthrate_flag,pe_flag CHARACTER(512), INTENT(IN) :: infile,ncfile ! Internals REAL(r8), DIMENSION(8), INTENT(IN) :: inpr_prof + REAL(r8), INTENT(IN) :: inpe LOGICAL :: firstsurf REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf,ising INTEGER :: zi, zimp, mi, mimp @@ -248,7 +248,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, INTEGER :: mms,nns,mrs,nrs,mpsi REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ my_qval,my_sval,my_bt,my_rs,zeff,inpe,R_0 + $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, @@ -367,10 +367,10 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, inpr = inpr_prof(ising) ! Check whether to include electron viscosity - IF (pe_flag) THEN - inpe=0.0165*inpr + IF (inpe < 0) THEN + my_inpe=0.0 ELSE - inpe=0.0 + my_inpe=0.0165*inpr ENDIF ne_arr(ising) = n_e @@ -432,7 +432,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,growthrate_flag, intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr - inpe_arr(ising) = inpe + inpe_arr(ising) = my_inpe omegas_arr(ising) = omega omegas_e_arr(ising) = omega_e omegas_i_arr(ising) = omega_i diff --git a/slayer/slayer.f b/slayer/slayer.f index b40460b45..57f6f7557 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -34,7 +34,7 @@ PROGRAM slayer $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,growthrates_flag,analytic_growthrates_flag, - $ pe_flag,br_th_flag !FOR TESTING + $ br_th_flag,compress_deltas REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff @@ -92,7 +92,7 @@ PROGRAM slayer $ layfac,Qratio,parflow_flag,peohmonly_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ stability_flag,growthrates_flag,analytic_growthrates_flag, - $ pe_flag,br_th_flag,bal_flag + $ br_th_flag,compress_deltas,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -166,8 +166,8 @@ PROGRAM slayer stability_flag=.FALSE. growthrates_flag=.FALSE. analytic_growthrates_flag=.FALSE. - pe_flag=.FALSE. br_th_flag=.FALSE. + compress_deltas=.FALSE. c----------------------------------------------------------------------- c read slayer.in. c----------------------------------------------------------------------- @@ -299,7 +299,7 @@ PROGRAM slayer !WRITE(*,*)"infile=",infile !WRITE(*,*)"ncfile=",ncfile CALL build_inputs(infile,ncfile,inpr_prof, - $ growthrates_flag,pe_flag,qval_arr,psi_n_rational, + $ inpe,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, $ omegas_arr,Re_deltaprime_arr,Im_deltaprime_arr) @@ -316,8 +316,6 @@ PROGRAM slayer WRITE(*,*)"intau_arr=",intau_arr n_k = SIZE(qval_arr) - ALLOCATE(all_growthrates(n_k)) - ALLOCATE(all_growthrate_locs(n_k)) DO k=1,n_k WRITE(*,*) "Finding roots on q=", qval_arr(k), $ " rational surface" @@ -325,27 +323,17 @@ PROGRAM slayer CALL growthrate_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ scan_radius,reQ_num,Re_deltaprime_arr(k), - $ results(k)) - - ! IF (k==1) THEN - ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),n_k)) - ! ALLOCATE(all_Im_deltas(SIZE(inQs),SIZE(iinQs),n_k)) - ! ALLOCATE(all_inQs(SIZE(inQs),n_k)) - - ! ENDIF - ! all_Re_deltas(:,:,k) = Re_deltas - ! all_Im_deltas(:,:,k) = Im_deltas - ! all_inQs(:,k) = inQs - - ! DEALLOCATE(Re_deltas,Im_deltas) - all_growthrates(k) = 0.0 - all_growthrate_locs(k) = 0.0 + $ scan_radius,reQ_num,compress_deltas, + $ Re_deltaprime_arr(k),results(k)) + WRITE(*,*)"Exited growthrate_scan" + ENDDO - WRITE(*,*) "all_growthrates", all_growthrates + WRITE(*,*)"Calling slayer_netcdf_out" - CALL slayer_netcdf_out(growthrates_flag,n_k,qval_arr, - $ all_growthrates,all_growthrate_locs, + br_th = 0.0 + CALL slayer_netcdf_out(growthrates_flag, + $ analytic_growthrates_flag, + $ br_th_flag,n_k,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, $ Re_deltaprime_arr,Im_deltaprime_arr, @@ -379,31 +367,14 @@ PROGRAM slayer CALL growthrate_scan(2,inQ,inQ_e, $ inQ_i,inc_beta,inds, $ intau,inQ,inpr,inpe, - $ scan_radius,reQ_num,Re_deltaprime_arr(1), - $ results(1)) - - ! WRITE(*,*)"SIZE(inQs)",SIZE(inQs) - ! WRITE(*,*)"SIZE(iinQs)",SIZE(iinQs) - - !IF (k==1) THEN - ! ALLOCATE(all_RE_deltas(SIZE(inQs),SIZE(iinQs),1)) - ! ALLOCATE(all_Im_deltas(SIZE(inQs),SIZE(iinQs),1)) - ! ALLOCATE(all_inQs(SIZE(inQs),1)) - ALLOCATE(all_growthrates(1)) - ALLOCATE(all_growthrate_locs(1)) - !ENDIF - ! all_Re_deltas(:,:,1) = Re_deltas - ! all_Im_deltas(:,:,1) = Im_deltas - ! all_inQs(:,1) = inQs - !all_roots(:,:,k) = roots - - all_growthrates(1) = 0.0 - all_growthrate_locs(1) = 0.0 + $ scan_radius,reQ_num,compress_deltas, + $ Re_deltaprime_arr(1),results(1)) WRITE(*,*)"allocations successful" - CALL slayer_netcdf_out(growthrates_flag,1,qval_arr, - $ all_growthrates,all_growthrate_locs, + br_th = 0.0 + CALL slayer_netcdf_out(growthrates_flag, + $ analytic_growthrates_flag,br_th_flag,1,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, $ Re_deltaprime_arr,Im_deltaprime_arr, @@ -489,10 +460,6 @@ PROGRAM slayer all_Im_deltas(:,:,k) = 0.0 all_inQs(:,k) = 0.0 - all_growthrates(k) = br_th !!!!!!!!!!!!!!!!!!!!!!!!! - all_growthrate_locs(k) = 0.0 - - qval_arr = (/ 3 /) inQs = (/ 1.0 /) inQs = (/ 1.0 /) @@ -508,8 +475,9 @@ PROGRAM slayer WRITE(*,*)"allocations successful" - CALL slayer_netcdf_out(growthrates_flag,1,qval_arr, - $ all_growthrates,all_growthrate_locs, + CALL slayer_netcdf_out(growthrates_flag, + $ analytic_growthrates_flag, + $ br_th_flag,1,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, $ Re_deltaprime_arr,Im_deltaprime_arr, From b29bf496176dbf04a6459b41dafbe8418fb7e3a9 Mon Sep 17 00:00:00 2001 From: +Daniel Burgess Date: Fri, 8 Nov 2024 08:26:15 -0800 Subject: [PATCH 016/305] change gamma scan tolerances --- slayer/gslayer.f | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 9c8a37ac8..3dce4f863 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -333,7 +333,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, delta_real = REAL(delta) delta_imag = AIMAG(delta) - IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 2)) THEN + IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 4)) THEN ! Store coarse grid point count = count + 1 results%inQs(count) = inQ_coarse @@ -350,7 +350,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, ! Check if refinement is needed IF ((ABS(delta_real) > threshold) .AND. - $ (ABS(deltaprime) > 2)) THEN + $ (ABS(deltaprime) > 4)) THEN ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN @@ -453,7 +453,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, delta_real = REAL(delta) delta_imag = AIMAG(delta) - IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 2)) THEN + IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 4)) THEN ! Store coarse grid point count = count + 1 results%inQs(count) = inQ_coarse @@ -470,7 +470,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, ! Check if refinement is needed IF ((ABS(delta_real) > threshold) .AND. - $ (ABS(deltaprime) > 2)) THEN + $ (ABS(deltaprime) > 4)) THEN ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN From a5e4789620a5f3cd8571c0b781178339803e7dc4 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 14 Nov 2024 12:40:10 -0500 Subject: [PATCH 017/305] clean up slayer growthrate scans --- slayer/gslayer.f | 289 ++++++++++++++--------------------------------- 1 file changed, 84 insertions(+), 205 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 3dce4f863..e0c41aa46 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -178,92 +178,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- c Subprogram 2. growthrate_scan -c Run stability scan on real and imaginary rotation axes -c----------------------------------------------------------------------- - SUBROUTINE old_growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,scan_radius,reQ_num, - $ Re_deltas,Im_deltas,inQs,iinQs) -c----------------------------------------------------------------------- -c Declarations -c----------------------------------------------------------------------- - ! Inputs - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe - INTEGER, INTENT(IN) :: qval,scan_radius,reQ_num - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs - INTEGER :: n_fine - REAL(r8), DIMENSION(:), ALLOCATABLE :: x_s,y_s - REAL(r8) :: x, y, dx, dy, ric_val,start_time,end_time - REAL(r8), DIMENSION(2) :: start, end, step - REAL(r8) :: coarse_threshold,fine_threshold,grad_tol,wr - COMPLEX(r8) :: delta - INTEGER :: k, w, n_steps, i2, i,j - logical :: found_zero - - INTEGER :: n_points, idx_1, idx_2 - REAL(r8) :: x_1, y_1, x_2, y_2, dist, min_dist1, min_dist2 - - REAL(r8), DIMENSION(:,:), ALLOCATABLE, - $ INTENT(OUT) :: Re_deltas,Im_deltas - wr=0.0 - ! Parameters - grad_tol = 1.0!abs(0.1*deltaprime) ! Tolerance for steep gradient - n_steps = reQ_num ! Initial number of steps -! n_fine = 10 -! if (abs(deltaprime)>100) then -! coarse_threshold = abs(0.95*deltaprime) ! Tolerance for zero value -! fine_threshold = abs(0.1*deltaprime) -! n_fine = n_fine * 2 -! else if (abs(deltaprime)>50) then -! coarse_threshold = abs(0.8*deltaprime) ! Tolerance for zero value -! fine_threshold = abs(0.1*deltaprime) -! n_fine = n_fine * 2 -! else if (abs(deltaprime)>20) then -! coarse_threshold = abs(0.66*deltaprime) ! Tolerance for zero value -! fine_threshold = abs(0.05*deltaprime) -! n_fine = n_fine * 2 -! else if (abs(deltaprime)>10) then -! coarse_threshold = abs(0.3*deltaprime) ! Tolerance for zero value -! fine_threshold = abs(0.03*deltaprime) -! else -! coarse_threshold = abs(0.1*deltaprime) ! Tolerance for zero value -! fine_threshold = abs(0.01*deltaprime) -! end if - ALLOCATE(inQs(n_steps)) - ALLOCATE(iinQs(n_steps)) - ALLOCATE(Re_deltas(n_steps,n_steps)) - ALLOCATE(Im_deltas(n_steps,n_steps)) - inQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) - iinQs = linspace((-scan_radius+wr),(scan_radius+wr),n_steps) - i=1 - j=1 - !CALL OMP_SET_NUM_THREADS(4) - !PRINT *, "Max threads: ",OMP_GET_MAX_THREADS() - !CALL cpu_time(start_time) - !!$OMP PARALLEL DO PRIVATE(j,y,delta) !BIG REWRITE NEEDED - DO i = 1, n_steps - DO j = 1, n_steps - !PRINT *, "Hello from process: ", OMP_GET_THREAD_NUM() - y=iinQs(j) - delta = riccati(inQs(i),inQ_e,inQ_i,inpr,inc_beta, - $ inds,intau,inpe,iinQ=y) - Re_deltas(i,j) = REAL(delta) - Im_deltas(i,j) = AIMAG(delta) - !PRINT *,'inQs(i):',inQs(i) - !PRINT *,'y:',y - !PRINT *,'delta:',delta - END DO - !stop - END DO - !!$OMP END PARALLEL DO - !CALL cpu_time(end_time) - !PRINT *,'Layer scan time:',end_time-start_time,'seconds' - !test comment - RETURN - END SUBROUTINE old_growthrate_scan -c----------------------------------------------------------------------- -c Subprogram 2. growthrate_scan -c Run stability scan on real and imaginary rotation axes +c Set up and iterate stability scans if no match is found c----------------------------------------------------------------------- SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, @@ -321,91 +236,19 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, dy = iinQ_step match_count = 0 - ! Coarse grid loop - DO i = 1, ncoarse - DO j = 1, ncoarse - inQ_coarse = -scan_radius + (i - 1) * inQ_step - iinQ_coarse = -scan_radius + (j - 1) * iinQ_step - - ! Evaluate riccati function - delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, - $ inds,intau,inpe,iinQ=iinQ_coarse) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) - - IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 4)) THEN - ! Store coarse grid point - count = count + 1 - results%inQs(count) = inQ_coarse - results%iinQs(count) = iinQ_coarse - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END IF - - IF (ABS(deltaprime) > 8) THEN - threshold = ABS(deltaprime)**(1./3.) - ELSE - threshold = 0.25 * ABS(deltaprime) - END IF - - ! Check if refinement is needed - IF ((ABS(delta_real) > threshold) .AND. - $ (ABS(deltaprime) > 4)) THEN - ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, - !$ delta_real) == SIGN(1.0, deltaprime))) THEN - - ! Fine grid loop - fine_dx = dx / nfine - fine_dy = dy / nfine - - overlap_x = overlap_factor * fine_dx - overlap_y = overlap_factor * fine_dy - - x_start = inQ_coarse - dx/2 + overlap_x - x_end = inQ_coarse + dx/2 - overlap_x - y_start = iinQ_coarse - dy/2 + overlap_y - y_end = iinQ_coarse + dy/2 - overlap_y - - DO fj = 0, nfine-1 - iinQ_fine = y_start + fj * (y_end-y_start) / (nfine-1) - DO fi = 0, nfine-1 - inQ_fine = x_start + fi * (x_end-x_start) / (nfine-1) - IF ((ABS(inQ_coarse - inQ_fine) < - $ tolerance) .AND. (ABS(iinQ_coarse - - $ iinQ_fine) < tolerance)) CYCLE - - ! Evaluate riccati function - delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) - - IF (ABS(delta_real)>ABS(deltaprime)) THEN - match_count = match_count + 1 - END IF - - ! Store fine grid point - count = count + 1 - results%inQs(count) = inQ_fine - results%iinQs(count) = iinQ_fine - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END DO - END DO - END IF - END DO - END DO - - !!!!! + ! Run scan + CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, + $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, + $ results,count,match_count,dx,dy) IF (match_count == 0) THEN - WRITE(*,*)"No match found, increasing scan radius" + WRITE(*,*)"No match found, rescanning" repeat = .TRUE. - new_scan_radius = scan_radius + 2 + !new_scan_radius = scan_radius + 2 new_ncoarse = ncoarse + 100 new_nfine = nfine ELSE IF (match_count > 0 .AND. match_count < 3) THEN - WRITE(*,*)"Match not definitive, increasing scan resolution" + WRITE(*,*)"Match not definitive, rescanning" repeat = .TRUE. new_scan_radius = scan_radius new_ncoarse = ncoarse @@ -441,12 +284,64 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, match_count = 0 - ! Coarse grid loop - DO i = 1, new_ncoarse - DO j = 1, new_ncoarse - inQ_coarse = -new_scan_radius + (i - 1) * inQ_step - iinQ_coarse = -new_scan_radius + (j - 1) * iinQ_step + ! COARSE AND FINE LOOPS + CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, + $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, + $ results,count,match_count,dx,dy) + + END IF ! End repeat "if" + + ! Set the actual count of points + results%count = count + + IF (count < max_points) THEN + ! Resize arrays to actual number of points + CALL shrink_array(results%inQs, count) + CALL shrink_array(results%iinQs, count) + CALL shrink_array(results%Re_deltas, count) + CALL shrink_array(results%Im_deltas, count) + END IF + + RETURN + END SUBROUTINE growthrate_scan +c----------------------------------------------------------------------- +c Subprogram 3. scan_grid +c Run stability scan on real and imaginary rotation axes +c----------------------------------------------------------------------- + SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, + $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, + $ results,count,match_count,dx,dy) + + ! Declarations (include necessary type declarations from original code) + REAL(r8), INTENT(IN) :: inQ_e, inQ_i, inpr, inc_beta, inds, + $ intau, inpe, deltaprime + INTEGER, INTENT(IN) :: scan_radius, ncoarse, nfine + LOGICAL, INTENT(IN) :: compress_deltas + TYPE(result_type), INTENT(INOUT) :: results + INTEGER, INTENT(INOUT) :: count, match_count + REAL(r8), INTENT(INOUT) :: dx, dy + + ! Local variables + REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, + $ inQ_coarse, iinQ_coarse + REAL(r8) :: delta_real, delta_imag, threshold + COMPLEX(r8) :: delta + REAL(r8) :: fine_dx, fine_dy, overlap_x, overlap_y + REAL(r8) :: x_start, x_end, y_start, y_end + INTEGER :: i, j, fi, fj + REAL(r8), PARAMETER :: tolerance = 1.0E-6 + REAL(r8) :: overlap_factor = 0.5 + + ! Calculate step sizes + inQ_step = (2.0 * scan_radius) / (ncoarse - 1) + iinQ_step = (2.0 * scan_radius) / (ncoarse - 1) + dx = inQ_step + dy = iinQ_step + DO i = 1, ncoarse + DO j = 1, ncoarse + inQ_coarse = -scan_radius + (i - 1) * inQ_step + iinQ_coarse = -scan_radius + (j - 1) * iinQ_step ! Evaluate riccati function delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, $ inds,intau,inpe,iinQ=iinQ_coarse) @@ -471,25 +366,25 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, ! Check if refinement is needed IF ((ABS(delta_real) > threshold) .AND. $ (ABS(deltaprime) > 4)) THEN - ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, + ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN ! Fine grid loop - fine_dx = dx / new_nfine - fine_dy = dy / new_nfine + fine_dx = dx / nfine + fine_dy = dy / nfine overlap_x = overlap_factor * fine_dx overlap_y = overlap_factor * fine_dy - x_start = inQ_coarse - dx/2 - overlap_x - x_end = inQ_coarse + dx/2 + overlap_x - y_start = iinQ_coarse - dy/2 - overlap_y - y_end = iinQ_coarse + dy/2 + overlap_y + x_start = inQ_coarse - dx/2 + overlap_x + x_end = inQ_coarse + dx/2 - overlap_x + y_start = iinQ_coarse - dy/2 + overlap_y + y_end = iinQ_coarse + dy/2 - overlap_y DO fj = 0, nfine-1 - iinQ_fine = y_start+fj * (y_end-y_start) / (new_nfine-1) + iinQ_fine = y_start + fj * (y_end-y_start) / (nfine-1) DO fi = 0, nfine-1 - inQ_fine = x_start+fi * (x_end-x_start) /(new_nfine-1) + inQ_fine = x_start + fi * (x_end-x_start) / (nfine-1) IF ((ABS(inQ_coarse - inQ_fine) < $ tolerance) .AND. (ABS(iinQ_coarse - $ iinQ_fine) < tolerance)) CYCLE @@ -501,7 +396,7 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, delta_imag = AIMAG(delta) IF (ABS(delta_real)>ABS(deltaprime)) THEN - match_count = match_count + 1 + match_count = match_count + 1 END IF ! Store fine grid point @@ -515,30 +410,11 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, END IF END DO END DO - - END IF - WRITE(*,*)"Passed IF return" - WRITE(*,*)"count: ",count - WRITE(*,*)"max_points: ",max_points - ! Set the actual count of points - results%count = count - - - WRITE(*,*)"Passed Set the actual count of points" - IF (count < max_points) THEN - ! Resize arrays to actual number of points - CALL shrink_array(results%inQs, count) - CALL shrink_array(results%iinQs, count) - CALL shrink_array(results%Re_deltas, count) - CALL shrink_array(results%Im_deltas, count) - END IF - - WRITE(*,*)"Arrays successfully shrunk" - - - RETURN - END SUBROUTINE growthrate_scan - + END SUBROUTINE scan_grid +c----------------------------------------------------------------------- +c Subprogram 4. shrink_array +c Remove excess scan array size from memory +c----------------------------------------------------------------------- SUBROUTINE shrink_array(arr, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) INTEGER, INTENT(IN) :: new_size @@ -548,7 +424,10 @@ SUBROUTINE shrink_array(arr, new_size) temp(1:new_size) = arr(1:new_size) CALL move_alloc(temp, arr) END SUBROUTINE shrink_array - +c----------------------------------------------------------------------- +c Subprogram 5. grow_array +c Increase scan array size if necessary +c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) INTEGER, INTENT(IN) :: old_size,new_size From 09bdb8cc04ca49b6d78698d59027a02dca78790e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 14 Nov 2024 14:05:08 -0500 Subject: [PATCH 018/305] add Pe_flag --- input/slayer.in | 5 +++-- slayer/layerinputs.f | 11 ++++++----- slayer/slayer.f | 23 +++++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 35a904aeb..83ac2f91d 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -15,14 +15,14 @@ bt=1.0 ! toroidal field [T] rs=0.17 ! minor radius of resonant surface [m] R0=2.0 ! major radius of magnetic axis [m] - omega=5.45E+04 ! ExB frequency [1/s] + omega=5.45E+04 ! ExB frequency [rad/s] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective inpr=1.0 ! Prantdl number - inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile + inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile, set < 0 to only use inpr instead inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -52,6 +52,7 @@ nbtscan_flag=f ! scan (n,bt) space for error fields. parflow_flag=f ! set parallel flow on PeOhmOnly_flag=t ! only include Pe from Ohm's law + Pe_flag=f ! if true, calculate classical inpe value from inpr and include in SLAYER run layfac=0.02 ! layfac*EXP(ifac*ATAN2(AIMAG(Q-Q_e),REAL(Q-Q_e)) is added to Q_e if ABS(Q-Q_e) Date: Fri, 22 Nov 2024 10:48:22 -0800 Subject: [PATCH 019/305] reverting Pe_flag commit --- input/slayer.in | 5 ++--- slayer/layerinputs.f | 11 +++++------ slayer/slayer.f | 23 ++++++++--------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 83ac2f91d..35a904aeb 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -15,14 +15,14 @@ bt=1.0 ! toroidal field [T] rs=0.17 ! minor radius of resonant surface [m] R0=2.0 ! major radius of magnetic axis [m] - omega=5.45E+04 ! ExB frequency [rad/s] + omega=5.45E+04 ! ExB frequency [1/s] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective inpr=1.0 ! Prantdl number - inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile, set < 0 to only use inpr instead + inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -52,7 +52,6 @@ nbtscan_flag=f ! scan (n,bt) space for error fields. parflow_flag=f ! set parallel flow on PeOhmOnly_flag=t ! only include Pe from Ohm's law - Pe_flag=f ! if true, calculate classical inpe value from inpr and include in SLAYER run layfac=0.02 ! layfac*EXP(ifac*ATAN2(AIMAG(Q-Q_e),REAL(Q-Q_e)) is added to Q_e if ABS(Q-Q_e) Date: Mon, 25 Nov 2024 07:35:15 -0800 Subject: [PATCH 020/305] restoring Pe_flag commit --- input/slayer.in | 5 +++-- slayer/layerinputs.f | 11 ++++++----- slayer/slayer.f | 23 +++++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 35a904aeb..83ac2f91d 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -15,14 +15,14 @@ bt=1.0 ! toroidal field [T] rs=0.17 ! minor radius of resonant surface [m] R0=2.0 ! major radius of magnetic axis [m] - omega=5.45E+04 ! ExB frequency [1/s] + omega=5.45E+04 ! ExB frequency [rad/s] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective inpr=1.0 ! Prantdl number - inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile + inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile, set < 0 to only use inpr instead inpe=0.0 ! Electron viscosity !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE @@ -52,6 +52,7 @@ nbtscan_flag=f ! scan (n,bt) space for error fields. parflow_flag=f ! set parallel flow on PeOhmOnly_flag=t ! only include Pe from Ohm's law + Pe_flag=f ! if true, calculate classical inpe value from inpr and include in SLAYER run layfac=0.02 ! layfac*EXP(ifac*ATAN2(AIMAG(Q-Q_e),REAL(Q-Q_e)) is added to Q_e if ABS(Q-Q_e) Date: Fri, 6 Dec 2024 02:04:29 -0500 Subject: [PATCH 021/305] Added units for resonant flux/current overlaps in gpec_control_nn.nc --- gpec/gpout.f | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index 154fdf4ef..c61c26b8f 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -653,7 +653,7 @@ SUBROUTINE gpout_singcoup(spot,nspot,rout,bpout,bout,rcout,tout) c agregate coupling matrices c----------------------------------------------------------------------- ALLOCATE(singcoup(nsingcoup,msing,mpert)) ! coupling field to external field b_x - singcoup(1,:,:) = singbnoflxs ! effective resonant area-normalized flux (Phi_r/A) shielded by current + singcoup(1,:,:) = singbnoflxs ! effective resonant area-normalized flux (Phi_r/A) shielded by current (Tesla) singcoup(2,:,:) = singcurs*twopi*nn ! resonant current (amps) singcoup(3,:,:) = islandhwids ! square of the penetrated island half-width in normalized poloidal flux singcoup(4,:,:) = singbwp ! interpolated (penetrated) resonant field @@ -1943,7 +1943,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spot,nspot, CALL check( nf90_redef(mncid)) CALL check( nf90_def_var(mncid, "Phi_overlap", nf90_double, $ (/m_id,i_id/), p_id) ) - CALL check( nf90_put_att(mncid, p_id, "units", "unitless") ) + CALL check( nf90_put_att(mncid, p_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, p_id, "long_name", $ "Pitch resonant flux overlap") ) CALL check( nf90_def_var(mncid, "Phi_overlap_norm", @@ -1953,7 +1953,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spot,nspot, $ "Pitch resonant flux overlap percentage") ) CALL check( nf90_def_var(mncid, "I_overlap", nf90_double, $ (/m_id,i_id/), c_id) ) - CALL check( nf90_put_att(mncid, c_id, "units", "unitless") ) + CALL check( nf90_put_att(mncid, c_id, "units", "Amps") ) CALL check( nf90_put_att(mncid, c_id, "long_name", $ "Pitch resonant current overlap") ) CALL check( nf90_def_var(mncid, "I_overlap_norm", @@ -2053,7 +2053,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spot,nspot, CALL check( nf90_redef(mncid)) CALL check( nf90_def_var(mncid, "Phi_local_overlap", $ nf90_double,(/m_id,i_id/), p_id) ) - CALL check( nf90_put_att(mncid, p_id,"units","unitless")) + CALL check( nf90_put_att(mncid, p_id,"units","Tesla")) CALL check( nf90_put_att(mncid, p_id, "long_name", $ "Local pitch resonant flux overlap") ) CALL check( nf90_def_var(mncid, "Phi_local_overlap_norm", @@ -2063,7 +2063,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spot,nspot, $ "Local pitch resonant flux overlap percentage") ) CALL check( nf90_def_var(mncid, "I_local_overlap", $ nf90_double,(/m_id,i_id/), c_id) ) - CALL check( nf90_put_att(mncid, c_id,"units","unitless")) + CALL check( nf90_put_att(mncid, c_id,"units","Amps")) CALL check( nf90_put_att(mncid, c_id, "long_name", $ "Local pitch resonant current overlap") ) CALL check( nf90_def_var(mncid, "I_local_overlap_norm", From cc4d5a38b7807fc54b8aee5fd40e8314c3da995d Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 6 Dec 2024 02:08:17 -0500 Subject: [PATCH 022/305] Fixed typo in gpout.f --- gpec/gpout.f | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index c61c26b8f..42dd1acc6 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -1985,12 +1985,12 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spot,nspot, $ (/m_id, i_id/), d_id) ) CALL check( nf90_put_att(mncid, d_id, "units", "untiless") ) CALL check( nf90_put_att(mncid, d_id, "long_name", - $ "Extrenal Delta prime overlap") ) + $ "External Delta prime overlap") ) CALL check( nf90_def_var(mncid, "Delta_overlap_norm", $ nf90_double,(/m_id/), dp_id) ) CALL check( nf90_put_att(mncid, dp_id, "units", "untiless")) CALL check( nf90_put_att(mncid, dp_id, "long_name", - $ "Extrenal Delta prime overlap percentage") ) + $ "External Delta prime overlap percentage") ) CALL check( nf90_enddef(mncid) ) CALL check( nf90_put_var(mncid, p_id, RESHAPE((/ $ REAL(olap(1,:)),AIMAG(olap(1,:))/), (/msing,2/))) ) From 75e1441cf573feaabeb8513856aff0c6636697b0 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 3 Feb 2025 15:04:24 -0500 Subject: [PATCH 023/305] WIP: add riccati_del_s function --- slayer/delta.f | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/slayer/delta.f b/slayer/delta.f index 99463b589..dc40a6792 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -105,6 +105,103 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, END FUNCTION riccati c----------------------------------------------------------------------- +c calculate delta based on riccati w_der formulation. +c----------------------------------------------------------------------- + FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, + $ inpe,iinQ,inx,iny) + + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds + REAL(r8),INTENT(IN) :: intau + REAL(r8),INTENT(IN),OPTIONAL :: iinQ,inx + COMPLEX(r8), INTENT(IN), OPTIONAL :: iny + COMPLEX(r8) :: riccati_del_s + + INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf + + REAL(r8) :: xintv,x,xout,rtol,jac,xmin + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy + + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork + REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork + + Q=inQ + IF(present(iinQ)) Q=inQ+ifac*iinQ + Q_e=inQ_e + Q_i=inQ_i + pr=inpr + pe=inpe + c_beta=inc_beta + ds=inds + tau=intau + + IF ((layfac>0).AND.(ABS(Q-Q_e)xout) + istep=istep+1 + CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')x,REAL(y),AIMAG(y) + ENDDO + CLOSE(bin_unit) + CLOSE(out2_unit) + ELSE + istep = 1 + itask = 1 + CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + + ENDIF + + ! w=0 when Q=Q_e. Why? + + CALL w_der(neq,x,y,dy) + riccati_del_s=pi/dy(1) + DEALLOCATE(atol,y,dy,iwork,rwork) + + END FUNCTION riccati_del_s +c----------------------------------------------------------------------- c riccati integration. c----------------------------------------------------------------------- SUBROUTINE w_der(neq,x,y,dy) From ce180dcb16dbff8e4c593bf1fb825c4073325113 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 6 Feb 2025 13:31:34 -0500 Subject: [PATCH 024/305] WIP: layer thickness delta.f functions --- input/slayer.in | 5 ++- slayer/delta.f | 105 +++++++++++++++++++++++++++++++++++++++--------- slayer/slayer.f | 105 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 176 insertions(+), 39 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 83ac2f91d..3f2713c45 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -61,8 +61,9 @@ bin_flag=f ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - growthrates_flag=t ! Calculate growthrates on each rational surface - analytic_growthrates_flag=f ! Generate Re(Q),Im(Q) scans for each rational surface + del_gamma_flag=t ! Calculate growthrates on each rational surface using Delta' method + analytic_del_gamma_flag=f ! Generate Re(Q),Im(Q) scans for each rational surface + lar_gamma_flag=t ! Calculate growthrates on each rational surface using cylindrical method br_th_flag=t ! Calculate br_th compress_deltas=f ! Only output essential growth rate grid points bal_flag=f ! calculate the resonant field penetration threshold from torque balance diff --git a/slayer/delta.f b/slayer/delta.f index dc40a6792..02c43cc3f 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -108,28 +108,28 @@ END FUNCTION riccati c calculate delta based on riccati w_der formulation. c----------------------------------------------------------------------- FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, - $ inpe,iinQ,inx,iny) + $ inx,iny) - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inc_beta,inds REAL(r8),INTENT(IN) :: intau - REAL(r8),INTENT(IN),OPTIONAL :: iinQ,inx + REAL(r8),INTENT(IN),OPTIONAL :: inx COMPLEX(r8), INTENT(IN), OPTIONAL :: iny COMPLEX(r8) :: riccati_del_s INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - REAL(r8) :: xintv,x,xout,rtol,jac,xmin - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy + REAL(r8) :: xintv,x,xout,rtol,jac,xmin,pd,P_hat,alpha + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq,y,dy INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork Q=inQ - IF(present(iinQ)) Q=inQ+ifac*iinQ + !IF(present(iinQ)) Q=inQ+ifac*iinQ Q_e=inQ_e Q_i=inQ_i pr=inpr - pe=inpe + !pe=inpe c_beta=inc_beta ds=inds tau=intau @@ -141,7 +141,7 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, neq = 2 itol = 2 rtol = 1e-7 !1e-7*pr**0.4 ! !1e-7 at front 1e-6 !e-4 - ALLOCATE(atol(neq),y(1),dy(1)) + ALLOCATE(atol(neq),W(1),dW_dq(1)) atol(:) = 1e-7*pr**0.4 ! 1e-8 !e-4 itask = 2 istate = 1 @@ -157,12 +157,22 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, iwork(6)=10000 !5000 ! maximum step size, e.g. 50000 rwork=0 ! x=10.0*(1.0+log10(Q/pr)) - x=20.0 - xmin=1e-3 + + !!!!!!!! + pd=20.0 ! "starting backwards integration at large q" + !!!!!!!! + + xmin=1e-5 IF(present(inx)) x=inx xout=xmin - y(1)=-c_beta/sqrt((1+tau))/ds*x**2.0 ! it was (1+tau*ds). To be updated. - IF(present(iny)) y(1)=iny + + !y(1)=-c_beta/sqrt((1+tau))/ds*x**2.0 ! it was (1+tau*ds). To be updated. + + P_hat = inpr / ds**6 + alpha = (P_hat/(1+1/tau))**0.5 + W(1) = 0.0!-alpha*pd**2 - 0.5 + + IF(present(iny)) W(1)=iny ! y(1)=0.5-ifac*10.0 ! WRITE(*,*)y(1) @@ -177,33 +187,88 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// $ TRIM(sn)//'.out',STATUS='UNKNOWN') WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" - DO WHILE (x>xout) + DO WHILE (pd>xout) istep=istep+1 - CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + CALL lsode(w_der_del_s,neq,W,pd,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) - WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) - WRITE(out2_unit,'(1x,3(es17.8e3))')x,REAL(y),AIMAG(y) + WRITE(bin_unit)REAL(pd,4),REAL(REAL(W),4),REAL(AIMAG(W),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')pd,REAL(W),AIMAG(W) ENDDO CLOSE(bin_unit) CLOSE(out2_unit) ELSE istep = 1 itask = 1 - CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + CALL lsode(w_der_del_s,neq,W,pd,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) ENDIF ! w=0 when Q=Q_e. Why? - CALL w_der(neq,x,y,dy) - riccati_del_s=pi/dy(1) - DEALLOCATE(atol,y,dy,iwork,rwork) + CALL w_der_del_s(neq,pd,W,dW_dq) + + riccati_del_s=-( pi/((1+1/tau)**0.5) )*dW_dq(1) + DEALLOCATE(atol,W,dW_dq,iwork,rwork) END FUNCTION riccati_del_s c----------------------------------------------------------------------- c riccati integration. c----------------------------------------------------------------------- + SUBROUTINE w_der_del_s(neq,pd,W,dW_dq) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: pd + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dW_dq + REAL(r8) :: Q_hat, P_hat + COMPLEX(r8) :: E,F + + COMPLEX(r8), PARAMETER :: ifac=(0,1) + + !IF (PeOhmOnly_flag) THEN + ! G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + + !$ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ + !$ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + !$ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) + !ELSE + ! G=(x**2*pe + + !$ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + + !$ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ + !$ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + !$ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) + !ENDIF + + !C3=x**2/(x**2 + (ds**2*(1 + tau)*x**4*pe)/ + !$ c_beta**2 + ifac*(Q - Q_e)) + + !C3p=-((x**2*(2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + !$ c_beta**2))/ + !$ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + !$ c_beta**2 + (0,1)*(Q - Q_e))**2 + !$ ) + (2*x)/ + !$ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + !$ c_beta**2 + (0,1)*(Q - Q_e)) + + !A1=(C1 + (C3p/C3)*(C2 + 1) + C2p)/(C2 + 1) + + !A2=(C1p + C1*(C3p/C3) - G/C3)/(C2 + 1) + + Q_hat = Q / ds**4 + P_hat = pr / ds**6 + + E = (-Q_hat**2/(1+1/tau)) - 2*ifac*Q_hat*P_hat*pd**2 + + $ 2*P_hat*pd**4 ! P_tor = P_perp + F = P_hat - ifac*Q_hat + (1+1/tau)*P_hat*pd**2 + + !dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x + dW_dq(1)=W(1)/pd - W(1)*W(1)/pd - (pd*E)/F !pD = q + + RETURN + END SUBROUTINE w_der_del_s +c +c +c SUBROUTINE w_der(neq,x,y,dy) INTEGER, INTENT(IN) :: neq diff --git a/slayer/slayer.f b/slayer/slayer.f index 069cc99eb..eafff752a 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -14,7 +14,7 @@ PROGRAM slayer USE sglobal_mod !USE params_mod - USE delta_mod, ONLY: riccati,riccati_out, + USE delta_mod, ONLY: riccati,riccati_del_s,riccati_out, $ parflow_flag,PeOhmOnly_flag ! FOR TESTING: @@ -33,14 +33,14 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,growthrates_flag,analytic_growthrates_flag, - $ Pe_flag,br_th_flag,compress_deltas + $ params_check,del_gamma_flag,analytic_del_gamma_flag, + $ lar_gamma_flag,Pe_flag,br_th_flag,compress_deltas REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th - COMPLEX(r8) :: delta,delta_n_p + REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_i,d_b,E_ss,tau_r + COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,gamma REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower, @@ -91,8 +91,8 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag,growthrates_flag,analytic_growthrates_flag, - $ br_th_flag,compress_deltas,bal_flag + $ stability_flag,del_gamma_flag,analytic_del_gamma_flag, + $ lar_gamma_flag,br_th_flag,compress_deltas,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -165,8 +165,9 @@ PROGRAM slayer params_check=.FALSE. bal_flag=.FALSE. stability_flag=.FALSE. - growthrates_flag=.FALSE. - analytic_growthrates_flag=.FALSE. + del_gamma_flag=.FALSE. + analytic_del_gamma_flag=.FALSE. + lar_gamma_flag=.FALSE. br_th_flag=.FALSE. compress_deltas=.FALSE. c----------------------------------------------------------------------- @@ -294,9 +295,79 @@ PROGRAM slayer $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,mms,nns) ENDIF c----------------------------------------------------------------------- +c LAR (cylindrical) growthrates via restive layer thickness +c----------------------------------------------------------------------- + IF (lar_gamma_flag) THEN + WRITE(*,*)"intau=",intau + WRITE(*,*)"inQ=",inQ + WRITE(*,*)"Prantdl numbers=",inpr + WRITE(*,*)"Electron viscosities=",inpe + WRITE(*,*)"inQ_e=",inQ_e + WRITE(*,*)"inds=",inds + WRITE(*,*)"inc_beta=",inc_beta + + WRITE(*,*)"running analytic scan" + + qval_arr = (/ qval /) + omegas_arr = (/ omega /) + inQ_arr = (/ inQ /) + inQ_e_arr = (/ inQ_e /) + inQ_i_arr = (/ inQ_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ REAL(delta_n_p) /) + Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) + inpr_arr = (/ inpr /) + + WRITE(*,*)"Calculating LAR growth rate" + + CALL params(n_e,t_e,t_i,omega, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + + inQ=Q + inQ_e=Q_e + inQ_i=Q_i + inc_beta=c_beta + inds=ds + intau=tau + Q0=Q + + WRITE(*,*)"params() call successful" + + dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds, + $ intau) + + WRITE(*,*)"dels_db() call successful" + + eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + tau_r=mu0*rs**2.0/eta ! resistive time scale + + d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth + d_b = c_beta*d_i + del_s = dels_db * d_b + E_ss = 5.0 + gamma=(REAL(E_ss)/tau_r) * (rs/del_s) + + WRITE(*,*)"inds=",inds + WRITE(*,*)"Q_hat=",inQ/(inds**4) + WRITE(*,*)"P_hat=",inpr/(inds**6) + WRITE(*,*)"thickness/db=",dels_db + WRITE(*,*)"db=",d_b + WRITE(*,*)"thickness=",del_s + WRITE(*,*)"gamma=",gamma + + !br_th = 0.0 + ! CALL slayer_netcdf_out(growthrates_flag, + !$ analytic_growthrates_flag,br_th_flag,1,qval_arr,br_th, + !$ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + !$ psi_n_rational,inpr_arr, + !$ Re_deltaprime_arr,Im_deltaprime_arr, + !$ results) + stop + ENDIF +c----------------------------------------------------------------------- c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- - IF (growthrates_flag) THEN + IF (del_gamma_flag) THEN !WRITE(*,*)"infile=",infile !WRITE(*,*)"ncfile=",ncfile @@ -338,8 +409,8 @@ PROGRAM slayer WRITE(*,*)"Calling slayer_netcdf_out" br_th = 0.0 - CALL slayer_netcdf_out(growthrates_flag, - $ analytic_growthrates_flag, + CALL slayer_netcdf_out(del_gamma_flag, + $ analytic_del_gamma_flag, $ br_th_flag,n_k,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, @@ -350,7 +421,7 @@ PROGRAM slayer c----------------------------------------------------------------------- c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- - IF (analytic_growthrates_flag) THEN + IF (analytic_del_gamma_flag) THEN WRITE(*,*)"intau=",intau WRITE(*,*)"inQ=",inQ WRITE(*,*)"Prantdl numbers=",inpr @@ -380,8 +451,8 @@ PROGRAM slayer WRITE(*,*)"allocations successful" br_th = 0.0 - CALL slayer_netcdf_out(growthrates_flag, - $ analytic_growthrates_flag,br_th_flag,1,qval_arr,br_th, + CALL slayer_netcdf_out(del_gamma_flag, + $ analytic_del_gamma_flag,br_th_flag,1,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, $ Re_deltaprime_arr,Im_deltaprime_arr, @@ -482,8 +553,8 @@ PROGRAM slayer WRITE(*,*)"allocations successful" - CALL slayer_netcdf_out(growthrates_flag, - $ analytic_growthrates_flag, + CALL slayer_netcdf_out(del_gamma_flag, + $ analytic_del_gamma_flag, $ br_th_flag,1,qval_arr,br_th, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr, From b912356fa05590389ed3fad963fddd3bd253caeb Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 12 Feb 2025 16:19:04 -0500 Subject: [PATCH 025/305] WIP: slayer LAR gamma netcdf upgrades --- input/slayer.in | 51 ++++++++--------- slayer/delta.f | 114 +++++++++++++++++--------------------- slayer/gslayer.f | 45 ++++++++++++++- slayer/makefile | 1 + slayer/params.f | 5 ++ slayer/sglobal.f | 3 +- slayer/slayer.f | 123 ++++++++++++++++++----------------------- slayer/slayer_netcdf.f | 95 +++++++++++++++++++++++-------- 8 files changed, 254 insertions(+), 183 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 3f2713c45..50748255e 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -1,46 +1,43 @@ &SLAYER_INPUT - - !!! FOR READING IN ASCII TABLE, SET INPUT_FLAG + ! Default values taken from R. Fitzpatrick 'High Field' case in "Tearing Mode Dynamics in Tokamak Plasmas", 2023 input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table infile='' ! Path to ascii table of profile quantities read when using input_flag ncfile='' ! Path to netCDF file of STRIDE outputs - !!! FOR MANUALLY ENTERING KINETIC QUANTITIES @ RATIONAL SURFACE, SET PARAMS_FLAG params_flag=f ! calculates normalized parameters from profile quantities mm=2 ! poloidal mode number nn=1 ! toroidal mode number - n_e=7.77E+17 ! electron density [m^-3] - t_e=25.8 ! electron temperature [eV] - t_i=25.8 ! ion temperature [eV] - sval=2.0 ! magnetic shear at the layer - bt=1.0 ! toroidal field [T] - rs=0.17 ! minor radius of resonant surface [m] - R0=2.0 ! major radius of magnetic axis [m] - omega=5.45E+04 ! ExB frequency [rad/s] + n_e=5.10E+020 ! electron density [m^-3] + t_e=7000.0 ! electron temperature [eV] + t_i=7000.0 ! ion temperature [eV] + sval=1.34 ! magnetic shear at the layer + bt=12.0 ! toroidal field [T] + rs=0.53 ! minor radius of resonant surface [m] + R0=3.2 ! major radius of magnetic axis [m] + omega=5.45E+03 ! ExB frequency [rad/s] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface mu_i=2.0 ! ion mass ratio to proton ?? zeff=2.0 ! plasma Z_effective - inpr=1.0 ! Prantdl number + inpr=500.0 ! Prantdl number inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile, set < 0 to only use inpr instead inpe=0.0 ! Electron viscosity - - !!! IF INPUT_FLAG & PARAMS_FLAG ARE FALSE, MANUALLY ENTER DIMENSIONLESS QUANTITIES @ RATIONAL SURFACE - inQ=23.0 ! normalized ExB frequency + inQ=1.5 ! normalized ExB frequency inQ_e=2.0 ! normalized electron diamagnetic frequency inQ_i=-2.0 ! normalized ion diamagnetic frequency - inc_beta=0.7 ! dimensionless measure of the plasma pressure - inds=6.0 ! normalized ion sound radius + inc_beta=0.128 ! dimensionless measure of the plasma pressure + inds=2.26 ! normalized ion sound radius intau=1.0 ! ion temperature divided by electron temperature - Q0=4.0 ! unnecessary placeholder variable for inQ? - delta_n_p=0 ! delta offset used for jxb calculations. Default is (1e-2,1e-2). + Q0=1.5 ! unnecessary placeholder variable for inQ? + delta_n_p=(5.0,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). / + &SLAYER_CONTROL inum=400 ! resolution to find error field thresholds. jnum=100 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=350 ! resolution for stability scan along Re(Q) axis - ImQ_num=350 ! resolution for stability scan along Im(Q) axis + ReQ_num=300 ! resolution for stability scan along Re(Q) axis + ImQ_num=300 ! resolution for stability scan along Im(Q) axis scan_radius=3 ! stability scan width in Q QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space @@ -55,20 +52,24 @@ Pe_flag=f ! if true, calculate classical inpe value from inpr and include in SLAYER run layfac=0.02 ! layfac*EXP(ifac*ATAN2(AIMAG(Q-Q_e),REAL(Q-Q_e)) is added to Q_e if ABS(Q-Q_e)0).AND.(ABS(Q-Q_e)xout) + DO WHILE (my_q>xout) istep=istep+1 - CALL lsode(w_der_del_s,neq,W,pd,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) - WRITE(bin_unit)REAL(pd,4),REAL(REAL(W),4),REAL(AIMAG(W),4) - WRITE(out2_unit,'(1x,3(es17.8e3))')pd,REAL(W),AIMAG(W) + CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,my_jac,mf) + WRITE(bin_unit)REAL(my_q,4),REAL(REAL(W),4),REAL(AIMAG(W),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')my_q,REAL(W),AIMAG(W) ENDDO CLOSE(bin_unit) CLOSE(out2_unit) ELSE istep = 1 itask = 1 - CALL lsode(w_der_del_s,neq,W,pd,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,my_jac,mf) ENDIF ! w=0 when Q=Q_e. Why? - CALL w_der_del_s(neq,pd,W,dW_dq) + CALL w_der_del_s(neq,my_q,W,dW_dq) riccati_del_s=-( pi/((1+1/tau)**0.5) )*dW_dq(1) DEALLOCATE(atol,W,dW_dq,iwork,rwork) END FUNCTION riccati_del_s c----------------------------------------------------------------------- +c jacobian for riccati_del_s() +c------------------------------------------- ---------------------------- + SUBROUTINE my_jac(neq, my_q, W, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: my_q + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + pd(1,1) = 1.0/my_q - 2.0d0*W(1)/my_q + END SUBROUTINE my_jac +c----------------------------------------------------------------------- c riccati integration. c----------------------------------------------------------------------- - SUBROUTINE w_der_del_s(neq,pd,W,dW_dq) + SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) INTEGER, INTENT(IN) :: neq - REAL(r8), INTENT(IN) :: pd + REAL(r8), INTENT(IN) :: my_q COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dW_dq - REAL(r8) :: Q_hat, P_hat + REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat COMPLEX(r8) :: E,F COMPLEX(r8), PARAMETER :: ifac=(0,1) - !IF (PeOhmOnly_flag) THEN - ! G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + - !$ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - !$ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - !$ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) - !ELSE - ! G=(x**2*pe + - !$ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + - !$ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - !$ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - !$ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) - !ENDIF - - !C3=x**2/(x**2 + (ds**2*(1 + tau)*x**4*pe)/ - !$ c_beta**2 + ifac*(Q - Q_e)) - - !C3p=-((x**2*(2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - !$ c_beta**2))/ - !$ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - !$ c_beta**2 + (0,1)*(Q - Q_e))**2 - !$ ) + (2*x)/ - !$ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - !$ c_beta**2 + (0,1)*(Q - Q_e)) - - !A1=(C1 + (C3p/C3)*(C2 + 1) + C2p)/(C2 + 1) - - !A2=(C1p + C1*(C3p/C3) - G/C3)/(C2 + 1) - - Q_hat = Q / ds**4 - P_hat = pr / ds**6 - - E = (-Q_hat**2/(1+1/tau)) - 2*ifac*Q_hat*P_hat*pd**2 + - $ 2*P_hat*pd**4 ! P_tor = P_perp - F = P_hat - ifac*Q_hat + (1+1/tau)*P_hat*pd**2 + !Q_hat = Q / ds**4 + Q_hat = (Q_e*(1+tau)/tau) / D_beta_norm**4 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark + P_perp_hat = pr / D_beta_norm**6 ! 0.377 for benchmark + P_tor_hat = pr / D_beta_norm**6 ! 1.15 for benchmark - !dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x - dW_dq(1)=W(1)/pd - W(1)*W(1)/pd - (pd*E)/F !pD = q + E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ + $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) ! P_tor = P_perp + F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 + !dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x + dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F !p*D = my_q RETURN END SUBROUTINE w_der_del_s c diff --git a/slayer/gslayer.f b/slayer/gslayer.f index e0c41aa46..d5c2e69cc 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -7,8 +7,9 @@ MODULE gslayer_mod $ eta,visc,rho_s,lu,omega_e,omega_i, $ delta_n, $ Q - USE delta_mod, ONLY: riccati,riccati_out, - $ parflow_flag,PeOhmOnly_flag + USE delta_mod + !, ONLY: riccati,riccati_out, + !$ parflow_flag,PeOhmOnly_flag USE params_mod @@ -177,6 +178,46 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- +c Subprogram 3. scan_grid +c Run stability scan on real and imaginary rotation axes +c----------------------------------------------------------------------- + SUBROUTINE get_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr, + $ dels_db,d_beta,lar_gamma) + + ! Declarations (include necessary type declarations from original code) + REAL(r8), INTENT(IN) :: d_beta + COMPLEX(r8), INTENT(IN) :: dels_db,lar_gamma + + LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag + + INTEGER, INTENT(IN), DIMENSION(:), ALLOCATABLE :: qval_arr + REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, + $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr + + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs + TYPE(result_type) :: results(8) + + REAL(r8) :: br_th = 0 + + WRITE(*,*)"Successfully entered get_lar_gamma()" + + inQs = (/0.0/) + iinQs = (/0.0/) + + CALL slayer_netcdf_out(1,lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag, + $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, + $ Im_deltaprime_arr,dels_db,d_beta,D_beta_norm, + $ lar_gamma,inQs,iinQs,results) + + END SUBROUTINE get_lar_gamma +c----------------------------------------------------------------------- c Subprogram 2. growthrate_scan c Set up and iterate stability scans if no match is found c----------------------------------------------------------------------- diff --git a/slayer/makefile b/slayer/makefile index f6dcca4ad..2e3212cbb 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -54,6 +54,7 @@ params.o : sglobal.o delta.o : sglobal.o gslayer.o : sglobal.o delta.o slayer.o : sglobal.o params.o delta.o ../pentrc/grid.mod ../equil/spline_mod.mod +slayer_netcdf.o : sglobal.o clean: rm -f *.o *.mod *.out *.bin slayers diff --git a/slayer/params.f b/slayer/params.f index fcf988717..c8651932e 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -28,6 +28,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega, b_l=(nr/mr)*rs*sval*bt/R0 ! characteristic magnetic field v_a=b_l/(mu0*rho)**0.5 ! alfven velocity rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. + d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth tau_h=R0*(mu0*rho)**0.5/(nn*sval*bt) ! alfven time across surface tau_r=mu0*rs**2.0/eta ! resistive time scale @@ -57,6 +58,10 @@ SUBROUTINE params(n_e,t_e,t_i,omega, lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 + ! this is using Fitzpatrick's tau', we need tau eventually + d_beta = c_beta*d_i + D_beta_norm = (d_beta/rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) + delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes ! quick diagnostics. diff --git a/slayer/sglobal.f b/slayer/sglobal.f index ae6356f2d..6a68621ec 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -7,7 +7,8 @@ MODULE sglobal_mod INTEGER, PARAMETER :: r8=SELECTED_REAL_KIND(13,307) REAL(r8) :: mr,nr - REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau + REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, + $ d_beta,D_beta_norm REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, $ delta_n,layfac,Qconv COMPLEX(r8) :: Q diff --git a/slayer/slayer.f b/slayer/slayer.f index eafff752a..b5ee3cd31 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -33,14 +33,15 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,del_gamma_flag,analytic_del_gamma_flag, - $ lar_gamma_flag,Pe_flag,br_th_flag,compress_deltas + $ params_check,stabscan_eq_flag,stabscan_flag, + $ lar_gamma_eq_flag,lar_gamma_flag,Pe_flag, + $ br_th_flag,compress_deltas REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_i,d_b,E_ss,tau_r - COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,gamma + REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,tau_r,d_b + COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower, @@ -80,8 +81,8 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates, $ growthrate_err - NAMELIST/slayer_input/params_flag,input_flag,infile, - $ ncfile,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, + NAMELIST/slayer_input/input_flag,infile, + $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, $ inQ_i,inpr,inpr_prof,inpe,inc_beta,inds, $ intau,inlu,Q0,delta_n_p @@ -91,8 +92,9 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag,del_gamma_flag,analytic_del_gamma_flag, - $ lar_gamma_flag,br_th_flag,compress_deltas,bal_flag + $ stability_flag,lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,compress_deltas, + $ bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -165,9 +167,10 @@ PROGRAM slayer params_check=.FALSE. bal_flag=.FALSE. stability_flag=.FALSE. - del_gamma_flag=.FALSE. - analytic_del_gamma_flag=.FALSE. + stabscan_flag=.FALSE. + stabscan_eq_flag=.FALSE. lar_gamma_flag=.FALSE. + lar_gamma_eq_flag=.FALSE. br_th_flag=.FALSE. compress_deltas=.FALSE. c----------------------------------------------------------------------- @@ -306,7 +309,18 @@ PROGRAM slayer WRITE(*,*)"inds=",inds WRITE(*,*)"inc_beta=",inc_beta - WRITE(*,*)"running analytic scan" + WRITE(*,*)"Calculating LAR growth rate" + + CALL params(n_e,t_e,t_i,omega, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + + !inQ=Q ! TAKEN FROM NAMELIST + !inQ_e=Q_e ! TAKEN FROM NAMELIST + !inQ_i=Q_i ! TAKEN FROM NAMELIST + !inc_beta=c_beta + !inds=ds + !intau=tau + !Q0=Q qval_arr = (/ qval /) omegas_arr = (/ omega /) @@ -318,56 +332,41 @@ PROGRAM slayer Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) inpr_arr = (/ inpr /) - WRITE(*,*)"Calculating LAR growth rate" + D_beta_norm = inds ! NAMELIST - CALL params(n_e,t_e,t_i,omega, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - - inQ=Q - inQ_e=Q_e - inQ_i=Q_i - inc_beta=c_beta - inds=ds - intau=tau - Q0=Q + WRITE(*,*)"D_beta_norm = ",D_beta_norm + WRITE(*,*)"inds = ",inds - WRITE(*,*)"params() call successful" + WRITE(*,*)"lar_gamma inQ = ",inQ - dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,inds, - $ intau) + dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,d_beta, + $ intau) WRITE(*,*)"dels_db() call successful" - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - tau_r=mu0*rs**2.0/eta ! resistive time scale + eta = 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + tau_r = mu0*rs**2.0/eta ! resistive time scale - d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth - d_b = c_beta*d_i - del_s = dels_db * d_b - E_ss = 5.0 - gamma=(REAL(E_ss)/tau_r) * (rs/del_s) + del_s = dels_db * d_beta + lar_gamma = (REAL(delta_n_p)/tau_r) * (rs/del_s) + + WRITE(*,*)"dels_db=",dels_db + WRITE(*,*)"lar_gamma=",lar_gamma + + WRITE(*,*)"slayer.f lar_gamma=",lar_gamma + + CALL get_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr, + $ dels_db,d_beta,lar_gamma) - WRITE(*,*)"inds=",inds - WRITE(*,*)"Q_hat=",inQ/(inds**4) - WRITE(*,*)"P_hat=",inpr/(inds**6) - WRITE(*,*)"thickness/db=",dels_db - WRITE(*,*)"db=",d_b - WRITE(*,*)"thickness=",del_s - WRITE(*,*)"gamma=",gamma - - !br_th = 0.0 - ! CALL slayer_netcdf_out(growthrates_flag, - !$ analytic_growthrates_flag,br_th_flag,1,qval_arr,br_th, - !$ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - !$ psi_n_rational,inpr_arr, - !$ Re_deltaprime_arr,Im_deltaprime_arr, - !$ results) stop ENDIF c----------------------------------------------------------------------- c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- - IF (del_gamma_flag) THEN + IF (stabscan_eq_flag) THEN !WRITE(*,*)"infile=",infile !WRITE(*,*)"ncfile=",ncfile @@ -409,19 +408,14 @@ PROGRAM slayer WRITE(*,*)"Calling slayer_netcdf_out" br_th = 0.0 - CALL slayer_netcdf_out(del_gamma_flag, - $ analytic_del_gamma_flag, - $ br_th_flag,n_k,qval_arr,br_th, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr, - $ results) +c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, +c $ stabscan_eq_flag,stabscan_flag,br_th_flag) stop ENDIF c----------------------------------------------------------------------- c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY c----------------------------------------------------------------------- - IF (analytic_del_gamma_flag) THEN + IF (stabscan_flag) THEN WRITE(*,*)"intau=",intau WRITE(*,*)"inQ=",inQ WRITE(*,*)"Prantdl numbers=",inpr @@ -451,12 +445,8 @@ PROGRAM slayer WRITE(*,*)"allocations successful" br_th = 0.0 - CALL slayer_netcdf_out(del_gamma_flag, - $ analytic_del_gamma_flag,br_th_flag,1,qval_arr,br_th, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr, - $ results) +c CALL slayer_netcdf_out(1,lar_gamma_eq_flag,lar_gamma_flag, +c $ stabscan_eq_flag,stabscan_flag,br_th_flag) stop ENDIF c----------------------------------------------------------------------- @@ -553,13 +543,8 @@ PROGRAM slayer WRITE(*,*)"allocations successful" - CALL slayer_netcdf_out(del_gamma_flag, - $ analytic_del_gamma_flag, - $ br_th_flag,1,qval_arr,br_th, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr, - $ results) +c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, +c $ stabscan_eq_flag,stabscan_flag,br_th_flag) stop ENDIF c----------------------------------------------------------------------- diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index f4fbdbb45..267c0ba08 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -15,10 +15,7 @@ c declarations. c----------------------------------------------------------------------- MODULE slayer_netcdf_mod - !USE dcon_mod USE sglobal_mod - !USE layerinputs_mod - !USE gslayer_mod USE netcdf IMPLICIT NONE CONTAINS @@ -51,26 +48,42 @@ END SUBROUTINE check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(growthrates_flag, - $ analytic_growthrates_flag,br_th_flag,msing,qval_arr, - $ br_th,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ inpr_arr,Re_deltaprime_arr,Im_deltaprime_arr,results) - LOGICAL, INTENT(IN) :: growthrates_flag,analytic_growthrates_flag, - $ br_th_flag + SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, + $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, + $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db, + $ d_beta,D_beta_norm,lar_gamma,inQs,iinQs,results) + + ! ds = D_beta_norm for lar growth rate routines + +c OPTIONAL +c br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db,d_b,ds,lar_gamma, +c inQs,iinQs,results, + INTEGER, INTENT(IN) :: msing - REAL(r8), DIMENSION(:), INTENT(IN) :: - $ omegas_arr,inQ_arr, - $ inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr - REAL(r8), INTENT(IN) :: br_th - INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr + LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag + + INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, + $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr + + REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, + $ Im_deltaprime_arr,inQs,iinQs + + REAL(r8) :: br_th, d_beta, D_beta_norm + COMPLEX(r8) :: dels_db, lar_gamma + TYPE(result_type), INTENT(IN) :: results(8) - INTEGER :: i, ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim,qsing_id, + + INTEGER :: i,ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, - $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id, + $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id,S_id, $ gamma_err_id,gamma_loc_id,roots_dim,Re_dp_id,Im_dp_id, - $ rdpp_id,idpp_id,inpr_id,br_th_id + $ rdpp_id,idpp_id,inpr_id,br_th_id,dels_db_id,d_b_id, + $ inds_id,lar_gamma_id,qsing_id + INTEGER :: run, run_dimid, point_dimid, varids(4), $ max_points @@ -80,6 +93,13 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- +c i=0; ncid=0;r_id=0;ReQ_dim=0;ImQ_dim=0;qsing_dim=0;qsing_id=0; +c $ i_dim=0;m_dim=0;mo_dim=0;p_dim=0;i_id=0;m_id=0;mo_id=0;p_id=0; +c $ ReQ_id=0;ImQ_id=0;gamma_id=0;omegas_id=0;Q_id=0;Q_e_id=0; +c $ Q_i_id=0;r_dim=0;pr_id=0;qr_id=0;shear_id=0;slice_id=0; +c $ inQs_id=0;S_id=0;gamma_err_id=0;gamma_loc_id=0;roots_dim=0; +c $ Re_dp_id=0;Im_dp_id=0;rdpp_id=0;idpp_id=0;inpr_id=0; +c $ br_th_id=0;dels_db_id=0;d_b_id=0;inds_id=0;lar_gamma_id=0 IF(debug_flag) PRINT *,"Called slayer_netcdf_out" IF (nn<10) THEN WRITE(UNIT=sn,FMT='(I1)')nn @@ -111,8 +131,13 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, ! define dimensions IF(debug_flag) PRINT *," - Defining dimensions in netcdf" + WRITE(*,*)"netcdf msing=",msing + !WRITE(*,*)"netcdf qval=",qval + WRITE(*,*)"netcdf qval_arr=",qval_arr + IF(msing>0)THEN CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL check( nf90_def_dim(ncid, "i", 2, i_dim) ) CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) CALL check( nf90_def_var(ncid,"omegas",nf90_double, @@ -123,6 +148,8 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, $ qsing_dim,Q_e_id)) CALL check( nf90_def_var(ncid,"Q_i",nf90_double, $ qsing_dim,Q_i_id)) + CALL check( nf90_def_var(ncid,"S",nf90_double, + $ qsing_dim,S_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ qsing_dim,pr_id) ) CALL check( nf90_def_var(ncid,"P",nf90_double, @@ -132,7 +159,7 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, CALL check( nf90_def_dim(ncid, "points", max_points, $ point_dimid) ) - IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN CALL check( nf90_def_var(ncid,"growthrates",nf90_double, $ qsing_dim,gamma_id)) CALL check(nf90_def_var(ncid,"growthrate_locs",nf90_double, @@ -153,11 +180,21 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, END IF END IF - IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, $ qsing_dim,rdpp_id) ) CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, $ qsing_dim,idpp_id) ) + END IF + IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN + CALL check( nf90_def_var(ncid,"delta_s_d_b",nf90_double, + $ (/qsing_dim,i_dim/),dels_db_id) ) + CALL check( nf90_def_var(ncid,"d_beta",nf90_double, + $ qsing_dim,d_b_id) ) + CALL check( nf90_def_var(ncid,"D_beta_norm",nf90_double, + $ qsing_dim,inds_id) ) + CALL check( nf90_def_var(ncid,"growthrate",nf90_double, + $ (/qsing_dim,i_dim/),lar_gamma_id) ) END IF ! end definitions CALL check( nf90_enddef(ncid) ) @@ -169,11 +206,12 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) + CALL check( nf90_put_var(ncid,S_id, (/lu/))) CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) - CALL check( nf90_put_var(ncid,qr_id, qval_arr)) + !CALL check( nf90_put_var(ncid,qr_id, qval_arr)) - IF ((growthrates_flag) .OR. (analytic_growthrates_flag)) THEN + IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN CALL check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) CALL check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) DO run = 1, msing @@ -189,9 +227,20 @@ SUBROUTINE slayer_netcdf_out(growthrates_flag, $ count=[results(run)%count, 1]) ) END DO END IF + IF (br_th_flag) THEN CALL check( nf90_put_var(ncid,br_th_id, (/ br_th /))) END IF + + IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN + CALL check( nf90_put_var(ncid,dels_db_id, + $ RESHAPE((/REAL(dels_db),AIMAG(dels_db)/),(/qsing_dim,2/)))) + CALL check( nf90_put_var(ncid,d_b_id, (/ d_beta /))) + CALL check( nf90_put_var(ncid,inds_id, (/ D_beta_norm /))) + CALL check( nf90_put_var(ncid,lar_gamma_id, + $ RESHAPE((/REAL(lar_gamma),AIMAG(lar_gamma)/),(/qsing_dim,2/)))) + END IF + c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- @@ -285,7 +334,7 @@ SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, IF(debug_flag) PRINT *," - Defining dimensions in netcdf" IF(msing>0)THEN - CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + !CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) From 00c18d7ef3f8294eaae4a2fe853c755c97ed6707 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 17 Feb 2025 13:32:45 -0500 Subject: [PATCH 026/305] WIP: adjust error tolerances --- slayer/delta.f | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 6d02e2c17..9a0b2aea5 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -141,9 +141,9 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, neq = 2 itol = 2 - rtol = 1e-08 !changed to 1e-08 !1e-7*pr**0.4 ! !1e-7 at front 1e-6 !e-4 + rtol = 1e-10 !changed to 1e-08 !1e-7*pr**0.4 ! !1e-7 at front 1e-6 !e-4 ALLOCATE(atol(neq),W(1),dW_dq(1)) - atol(:) = 1e-08!*pr**0.4 ! changed to 1e-08 1e-8 !e-4 + atol(:) = 1e-10!*pr**0.4 ! changed to 1e-08 1e-8 !e-4 itask = 2 istate = 1 iopt = 0 From 35514d1276d2c6446916c829901dfcd48b17d842 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 27 Feb 2025 19:52:15 -0500 Subject: [PATCH 027/305] generalized growth rates to full eq!! --- slayer/gslayer.f | 33 +++++++------- slayer/layerinputs.f | 42 ++++++++++++------ slayer/sglobal.f | 2 +- slayer/slayer.f | 97 ++++++++++++++++++++++++++++++++++++------ slayer/slayer_netcdf.f | 31 ++++++++------ 5 files changed, 149 insertions(+), 56 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index d5c2e69cc..74a85245f 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -181,42 +181,43 @@ END SUBROUTINE gpec_slayer c Subprogram 3. scan_grid c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE get_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr, - $ dels_db,d_beta,lar_gamma) + SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, + $ Im_deltaprime_arr,dels_db_arr,lar_gamma_arr) ! Declarations (include necessary type declarations from original code) - REAL(r8), INTENT(IN) :: d_beta - COMPLEX(r8), INTENT(IN) :: dels_db,lar_gamma - LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag INTEGER, INTENT(IN), DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr - + $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr,ind_beta_arr, + $ D_beta_norm_arr + + COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, + $ lar_gamma_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs TYPE(result_type) :: results(8) REAL(r8) :: br_th = 0 - WRITE(*,*)"Successfully entered get_lar_gamma()" + WRITE(*,*)"Successfully entered output_lar_gamma()" inQs = (/0.0/) iinQs = (/0.0/) - CALL slayer_netcdf_out(1,lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag, + CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, + $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db,d_beta,D_beta_norm, - $ lar_gamma,inQs,iinQs,results) + $ Im_deltaprime_arr,dels_db_arr,ind_beta_arr, + $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) - END SUBROUTINE get_lar_gamma + END SUBROUTINE output_lar_gamma c----------------------------------------------------------------------- c Subprogram 2. growthrate_scan c Set up and iterate stability scans if no match is found diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index cdada7ea4..6a8b57897 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -4,7 +4,7 @@ MODULE layerinputs_mod USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, $ spline_dealloc,spline_int,spline_fit USE sglobal_mod, ONLY: m_p, chag, lnLamb, - $ Q_e,Q_i,pr,pe,c_beta,ds,tau,r8,mu0,pi,out_unit, ! NOT out_unit + $ Q_e,Q_i,pr,pe,c_beta,ds,d_beta,d_i,tau,r8,mu0,pi,out_unit, ! NOT out_unit $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq @@ -225,10 +225,11 @@ END FUNCTION issurfint c build input arrays for SLAYER c----------------------------------------------------------------------- SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, - $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, - $ inpr_arr,inpe_arr,omegas_arr,Re_deltaprime_arr, - $ Im_deltaprime_arr) + $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, + $ D_beta_norm_arr,intau_arr,Q0_arr,inpr_arr, + $ inpe_arr,omegas_arr,gammafac_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- @@ -251,12 +252,12 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0 REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, - $ rho,tau_v,inpr,Qconv,lbeta,qintb + $ rho,tau_v,inpr,Qconv,lbeta,qintb,gammafac REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, - $ inQ_e_arr,psi_n_rational, - $ inQ_i_arr,inc_beta_arr,inds_arr,intau_arr,Q0_arr, - $ inpr_arr,inpe_arr,omegas_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr + $ inQ_e_arr,psi_n_rational,D_beta_norm_arr, + $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, + $ intau_arr,Q0_arr,inpr_arr,inpe_arr,omegas_arr, + $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor @@ -296,10 +297,12 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), $ inQ_i_arr(msing), - $ inc_beta_arr(msing),inds_arr(msing),intau_arr(msing), - $ Q0_arr(msing),inpr_arr(msing),inpe_arr(msing), - $ omegas_arr(msing),omegas_e_arr(msing),omegas_i_arr(msing), - $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing)) + $ inc_beta_arr(msing),inds_arr(msing),ind_beta_arr(msing), + $ intau_arr(msing),Q0_arr(msing),inpr_arr(msing), + $ inpe_arr(msing),omegas_arr(msing),omegas_e_arr(msing), + $ omegas_i_arr(msing),gammafac_arr(msing), + $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), + $ D_beta_norm_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) @@ -399,6 +402,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, v_a=b_l/(mu0*rho)**0.5 ! alfven velocity, B_L IS BROKEN rho_s=1.02e-4*(mu_i*t_e)**0.5/my_bt ! ion Lamour by elec. Temp. + d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth tau_h=R_0*(mu0*rho)**0.5/(nns*my_sval*my_bt) ! alfven time across surface tau_r=mu0*my_rs**2.0/eta ! resistive time scale @@ -422,14 +426,23 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/my_bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 + ! this is using Fitzpatrick's tau', we need tau eventually + d_beta = c_beta*d_i + D_beta_norm = (d_beta/my_rs)*(lu**(1.0/3.0))*(tau/(1+ + $ tau))**(0.5) + delta_n=lu**(1.0/3.0)/my_rs ! norm factor for delta primes + gammafac = (my_rs*Re_dp_diagonal(ising))/tau_r ! scalar to convert thickness into growth rate + qval_arr(ising) = INT(my_qval) inQ_arr(ising)=REAL(Q) inQ_e_arr(ising)=Q_e inQ_i_arr(ising)=Q_i inc_beta_arr(ising)=c_beta inds_arr(ising)=ds + ind_beta_arr(ising)=d_beta + D_beta_norm_arr(ising)=D_beta_norm intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr @@ -437,6 +450,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, omegas_arr(ising) = omega omegas_e_arr(ising) = omega_e omegas_i_arr(ising) = omega_i + gammafac_arr(ising) = gammafac Re_deltaprime_arr(ising) = Re_dp_diagonal(ising) Im_deltaprime_arr(ising) = Im_dp_diagonal(ising) ENDDO diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 6a68621ec..00b751f3f 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -8,7 +8,7 @@ MODULE sglobal_mod REAL(r8) :: mr,nr REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, - $ d_beta,D_beta_norm + $ d_beta,D_beta_norm,gamma_fac REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, $ delta_n,layfac,Qconv COMPLEX(r8) :: Q diff --git a/slayer/slayer.f b/slayer/slayer.f index b5ee3cd31..4193aa9d4 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -54,10 +54,10 @@ PROGRAM slayer $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr, - $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr, - $ psi_n_rational + $ inQ_i_arr,inc_beta_arr,inds_arr, + $ intau_arr,inQ0_arr,inpr_arr, + $ inpe_arr,omegas_arr,inQ_arr, + $ psi_n_rational,ind_beta_arr,D_beta_norm_arr REAL(r8), DIMENSION(8) :: inpr_prof REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, $ all_Im_deltas,all_roots @@ -73,12 +73,13 @@ PROGRAM slayer $ inQs_left,inQs_right,coarse_deltas REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, + $ dels_db_arr,lar_gamma_arr COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas TYPE(result_type) :: results(8) ! Assuming max 8 rational surfaces - REAL(r8), DIMENSION(:), ALLOCATABLE :: growthrates, + REAL(r8), DIMENSION(:), ALLOCATABLE :: gammafac_arr,growthrates, $ growthrate_err NAMELIST/slayer_input/input_flag,infile, @@ -299,6 +300,69 @@ PROGRAM slayer ENDIF c----------------------------------------------------------------------- c LAR (cylindrical) growthrates via restive layer thickness +c----------------------------------------------------------------------- + IF (lar_gamma_eq_flag) THEN + + ! propagate inpr value to inpr_prof if inpr_prof is turned off + IF (inpr_prof(1) < 0) THEN + inpr_prof = inpr + END IF + + CALL build_inputs(infile,ncfile,inpr_prof, + $ inpe,Pe_flag,qval_arr,psi_n_rational, + $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, + $ inds_arr,ind_beta_arr,D_beta_norm_arr, + $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, + $ omegas_arr,gammafac_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr) + + WRITE(*,*)"Safety factor values=",qval_arr + WRITE(*,*)"inQ values=",inQ_arr + WRITE(*,*)"Prantdl numbers=",inpr_arr + WRITE(*,*)"Electron viscosities=",inpe_arr + WRITE(*,*)"Omega ExB values=",omegas_arr + WRITE(*,*)"outer region real deltaprimes=",Re_deltaprime_arr + WRITE(*,*)"outer region imag deltaprimes=",Im_deltaprime_arr + WRITE(*,*)"inQ_e_arr=",inQ_e_arr + WRITE(*,*)"ind_beta_arr=",ind_beta_arr + WRITE(*,*)"D_beta_norm_arr=",D_beta_norm_arr + WRITE(*,*)"intau_arr=",intau_arr + WRITE(*,*)"gammafac_arr=",gammafac_arr + + n_k = SIZE(qval_arr) + + ALLOCATE(lar_gamma_arr(n_k),dels_db_arr(n_k)) + + DO k=1,n_k + WRITE(*,*) "Calculating growth rate on q=", qval_arr(k), + $ " rational surface" + + dels_db=riccati_del_s(inQ_arr(k),inQ_e_arr(k), + $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), + $ D_beta_norm_arr(k),intau_arr(k)) + + del_s = dels_db * ind_beta_arr(k) + lar_gamma = gammafac_arr(k)/del_s + + lar_gamma_arr(k) = lar_gamma + dels_db_arr(k) = dels_db + + ENDDO + WRITE(*,*)"Calling slayer_netcdf_out" + + br_th = 0.0 + + CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, + $ lar_gamma_arr) + + stop + ENDIF +c----------------------------------------------------------------------- +c LAR (cylindrical) growthrates via restive layer thickness c----------------------------------------------------------------------- IF (lar_gamma_flag) THEN WRITE(*,*)"intau=",intau @@ -350,16 +414,23 @@ PROGRAM slayer del_s = dels_db * d_beta lar_gamma = (REAL(delta_n_p)/tau_r) * (rs/del_s) + ind_beta_arr = (/ d_beta /) + dels_db_arr = (/ dels_db /) + lar_gamma_arr = (/ lar_gamma /) + WRITE(*,*)"dels_db=",dels_db WRITE(*,*)"lar_gamma=",lar_gamma WRITE(*,*)"slayer.f lar_gamma=",lar_gamma - CALL get_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + br_th = 0.0 + + CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr, - $ dels_db,d_beta,lar_gamma) + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, + $ lar_gamma_arr) stop ENDIF @@ -378,8 +449,10 @@ PROGRAM slayer CALL build_inputs(infile,ncfile,inpr_prof, $ inpe,Pe_flag,qval_arr,psi_n_rational, $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, - $ inds_arr,intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,Re_deltaprime_arr,Im_deltaprime_arr) + $ inds_arr,ind_beta_arr,D_beta_norm_arr, + $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, + $ omegas_arr,gammafac_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr WRITE(*,*)"inQ values=",inQ_arr diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 267c0ba08..1bee40819 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -51,8 +51,8 @@ END SUBROUTINE check SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db, - $ d_beta,D_beta_norm,lar_gamma,inQs,iinQs,results) + $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, + $ ind_beta_arr,D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) ! ds = D_beta_norm for lar growth rate routines @@ -66,13 +66,15 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, - $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr + $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr,ind_beta_arr, + $ D_beta_norm_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, $ Im_deltaprime_arr,inQs,iinQs - REAL(r8) :: br_th, d_beta, D_beta_norm - COMPLEX(r8) :: dels_db, lar_gamma + REAL(r8) :: br_th + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dels_db_arr, + $ lar_gamma_arr TYPE(result_type), INTENT(IN) :: results(8) @@ -82,7 +84,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id,S_id, $ gamma_err_id,gamma_loc_id,roots_dim,Re_dp_id,Im_dp_id, $ rdpp_id,idpp_id,inpr_id,br_th_id,dels_db_id,d_b_id, - $ inds_id,lar_gamma_id,qsing_id + $ inds_id,lar_gamma_id,qsing_id,indnorm_id INTEGER :: run, run_dimid, point_dimid, varids(4), $ max_points @@ -192,7 +194,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL check( nf90_def_var(ncid,"d_beta",nf90_double, $ qsing_dim,d_b_id) ) CALL check( nf90_def_var(ncid,"D_beta_norm",nf90_double, - $ qsing_dim,inds_id) ) + $ qsing_dim,indnorm_id) ) CALL check( nf90_def_var(ncid,"growthrate",nf90_double, $ (/qsing_dim,i_dim/),lar_gamma_id) ) END IF @@ -234,11 +236,14 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN CALL check( nf90_put_var(ncid,dels_db_id, - $ RESHAPE((/REAL(dels_db),AIMAG(dels_db)/),(/qsing_dim,2/)))) - CALL check( nf90_put_var(ncid,d_b_id, (/ d_beta /))) - CALL check( nf90_put_var(ncid,inds_id, (/ D_beta_norm /))) + $ RESHAPE((/REAL(dels_db_arr),AIMAG(dels_db_arr)/), + $ (/msing,2/)))) + CALL check( nf90_put_var(ncid,d_b_id,ind_beta_arr)) + CALL check( nf90_put_var(ncid,indnorm_id, D_beta_norm_arr)) CALL check( nf90_put_var(ncid,lar_gamma_id, - $ RESHAPE((/REAL(lar_gamma),AIMAG(lar_gamma)/),(/qsing_dim,2/)))) + $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), + $ (/msing,2/)))) + END IF c ----------------------------------------------------------------------- @@ -271,9 +276,9 @@ SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, INTEGER :: i, ncid,r_id,qsing_dim,qsing_id,msing_id, $ i_dim,ne_id,te_id,ni_id,ti_id,zeff_id,shear_id,bt_id,rs_id, $ R0_id,resm_id,nns_id,inQ_id,inQ_e_id,inc_beta_id, - $ inds_id,qval_id,inQ_i_id,qr_id, + $ inds_id,qval_id,inQ_i_id,qr_id,indnorm_id $ intau_id,inpr_id,inpe_id,omegas_id,Re_delta_id,Im_delta_id, - $ omegas_e_id,omegas_i_id + $ omegas_e_id,omegas_i_id,intau_id CHARACTER(64) :: ncfile LOGICAL, PARAMETER :: debug_flag = .FALSE. From af3bb305653e2db68698b3f1257013f9eb39ebc2 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 17 Mar 2025 13:24:30 -0400 Subject: [PATCH 028/305] WIP: layer thickness usability updates --- slayer/delta.f | 5 ++++- slayer/gslayer.f | 28 ++++++++++++------------- slayer/layerinputs.f | 9 ++++---- slayer/slayer.f | 47 ++++++++++++++---------------------------- slayer/slayer_netcdf.f | 7 ++++--- 5 files changed, 43 insertions(+), 53 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 9a0b2aea5..53053def1 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -160,8 +160,11 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, ! x=10.0*(1.0+log10(Q/pr)) !!!!!!!! - my_q=10.0 ! "starting backwards integration at large q" + my_q=inx ! "starting backwards integration at large q" !!!!!!!! + WRITE(*,*)"myq starting point = ",my_q + WRITE(*,*)"myq starting point = ",my_q + WRITE(*,*)"myq starting point = ",my_q xmin=1e-5 IF(present(inx)) x=inx diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 74a85245f..1072bab98 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -178,15 +178,15 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- -c Subprogram 3. scan_grid -c Run stability scan on real and imaginary rotation axes +c Subprogram 2. output_lar_gamma +c Run c----------------------------------------------------------------------- SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lar_gamma_arr) - + $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, + $ lar_gamma_arr) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag @@ -195,7 +195,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr + $ D_beta_norm_arr,lu_arr COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, $ lar_gamma_arr @@ -214,13 +214,13 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) END SUBROUTINE output_lar_gamma c----------------------------------------------------------------------- -c Subprogram 2. growthrate_scan -c Set up and iterate stability scans if no match is found +c subprogram 3. growthrate_scan +c set up and iterate stability scans if no match is found c----------------------------------------------------------------------- SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, @@ -347,8 +347,8 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, RETURN END SUBROUTINE growthrate_scan c----------------------------------------------------------------------- -c Subprogram 3. scan_grid -c Run stability scan on real and imaginary rotation axes +c subprogram 4. scan_grid +c run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, @@ -454,8 +454,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, END DO END SUBROUTINE scan_grid c----------------------------------------------------------------------- -c Subprogram 4. shrink_array -c Remove excess scan array size from memory +c subprogram 5. shrink_array +c remove excess scan array size from memory c----------------------------------------------------------------------- SUBROUTINE shrink_array(arr, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) @@ -467,7 +467,7 @@ SUBROUTINE shrink_array(arr, new_size) CALL move_alloc(temp, arr) END SUBROUTINE shrink_array c----------------------------------------------------------------------- -c Subprogram 5. grow_array +c subprogram 6. grow_array c Increase scan array size if necessary c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 6a8b57897..0b9655efe 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -9,7 +9,7 @@ MODULE layerinputs_mod USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq USE bicube_mod, ONLY: bicube_eval_external,bicube_type - USE slayer_netcdf_mod!, ONLY: slayer_netcdf_inputs + USE slayer_netcdf_mod IMPLICIT NONE @@ -228,7 +228,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, $ D_beta_norm_arr,intau_arr,Q0_arr,inpr_arr, - $ inpe_arr,omegas_arr,gammafac_arr, + $ inpe_arr,omegas_arr,lu_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) c----------------------------------------------------------------------- c declarations. @@ -254,7 +254,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb,gammafac REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, - $ inQ_e_arr,psi_n_rational,D_beta_norm_arr, + $ inQ_e_arr,psi_n_rational,D_beta_norm_arr,lu_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, $ intau_arr,Q0_arr,inpr_arr,inpe_arr,omegas_arr, $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr @@ -302,7 +302,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, $ inpe_arr(msing),omegas_arr(msing),omegas_e_arr(msing), $ omegas_i_arr(msing),gammafac_arr(msing), $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), - $ D_beta_norm_arr(msing)) + $ D_beta_norm_arr(msing),lu_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) @@ -443,6 +443,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, inds_arr(ising)=ds ind_beta_arr(ising)=d_beta D_beta_norm_arr(ising)=D_beta_norm + lu_arr(ising)=lu intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr diff --git a/slayer/slayer.f b/slayer/slayer.f index 4193aa9d4..9d216799d 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -44,33 +44,28 @@ PROGRAM slayer COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, - $ kpower, - $ Qratio + $ kpower,Qratio INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl, - $ bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, + $ bal,prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr, + $ inpe_arr,omegas_arr,inQ_arr,lu_arr, $ psi_n_rational,ind_beta_arr,D_beta_norm_arr + REAL(r8), DIMENSION(8) :: inpr_prof - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, - $ all_Im_deltas,all_roots - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_inQs + REAL(r8), DIMENSION(:,:),ALLOCATABLE :: Re_deltas,Im_deltas INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: - $ Re_deltaprime_arr,Im_deltaprime_arr, - $ all_growthrates,all_growthrate_locs - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: - $ js,ks,psis,jxbs,Q_sols,br_ths, - $ inQs_left,inQs_right,coarse_deltas + $ Re_deltaprime_arr,Im_deltaprime_arr + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: js,ks,psis,jxbs, + $ Q_sols,br_ths REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, @@ -313,7 +308,7 @@ PROGRAM slayer $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,ind_beta_arr,D_beta_norm_arr, $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,gammafac_arr, + $ omegas_arr,lu_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr @@ -338,8 +333,8 @@ PROGRAM slayer $ " rational surface" dels_db=riccati_del_s(inQ_arr(k),inQ_e_arr(k), - $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), - $ D_beta_norm_arr(k),intau_arr(k)) + $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), + $ D_beta_norm_arr(k),intau_arr(k),5*D_beta_norm) del_s = dels_db * ind_beta_arr(k) lar_gamma = gammafac_arr(k)/del_s @@ -355,7 +350,7 @@ PROGRAM slayer CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational, + $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, $ lar_gamma_arr) @@ -404,7 +399,7 @@ PROGRAM slayer WRITE(*,*)"lar_gamma inQ = ",inQ dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,d_beta, - $ intau) + $ intau,5*D_beta_norm) WRITE(*,*)"dels_db() call successful" @@ -419,6 +414,7 @@ PROGRAM slayer lar_gamma_arr = (/ lar_gamma /) WRITE(*,*)"dels_db=",dels_db + WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 WRITE(*,*)"lar_gamma=",lar_gamma WRITE(*,*)"slayer.f lar_gamma=",lar_gamma @@ -428,7 +424,7 @@ PROGRAM slayer CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational, + $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, $ lar_gamma_arr) @@ -451,7 +447,7 @@ PROGRAM slayer $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,ind_beta_arr,D_beta_norm_arr, $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,gammafac_arr, + $ omegas_arr,lu_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr @@ -589,17 +585,6 @@ PROGRAM slayer inQs = (/ 1.0 /) n_k = SIZE(qval_arr) - k=1 - IF (k==1) THEN - ALLOCATE(all_RE_deltas(1,1,n_k)) - ALLOCATE(all_Im_deltas(1,1,n_k)) - ALLOCATE(all_inQs(1,n_k)) - ALLOCATE(all_growthrates(n_k)) - ALLOCATE(all_growthrate_locs(n_k)) - ENDIF - all_Re_deltas(:,:,k) = 0.0 - all_Im_deltas(:,:,k) = 0.0 - all_inQs(:,k) = 0.0 qval_arr = (/ 3 /) inQs = (/ 1.0 /) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 1bee40819..62991adfa 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -52,7 +52,8 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ ind_beta_arr,D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) + $ lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, + $ inQs,iinQs,results) ! ds = D_beta_norm for lar growth rate routines @@ -67,7 +68,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr + $ D_beta_norm_arr,lu_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, $ Im_deltaprime_arr,inQs,iinQs @@ -208,7 +209,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) - CALL check( nf90_put_var(ncid,S_id, (/lu/))) + CALL check( nf90_put_var(ncid,S_id, lu_arr)) CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) !CALL check( nf90_put_var(ncid,qr_id, qval_arr)) From 295ff40eb8b9c3206caf8665184464e1810446f3 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 31 Mar 2025 13:52:08 -0400 Subject: [PATCH 029/305] SLAYER delta netcdf updates --- slayer/gslayer.f | 51 ++++++++++++++++++++++++------------------------ slayer/slayer.f | 35 +++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 1072bab98..9b721dc34 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -222,7 +222,7 @@ END SUBROUTINE output_lar_gamma c subprogram 3. growthrate_scan c set up and iterate stability scans if no match is found c----------------------------------------------------------------------- - SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, + SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, $ compress_deltas,deltaprime,results) c----------------------------------------------------------------------- @@ -230,21 +230,21 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, c----------------------------------------------------------------------- ! Inputs REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe + $ intau,inQ0,inpr,inpe,my_lu INTEGER, INTENT(IN) :: qval,scan_radius,ncoarse - INTEGER :: new_scan_radius,new_ncoarse - !REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQs,iinQs - COMPLEX(r8) :: delta REAL(r8), INTENT(IN) :: deltaprime + LOGICAL, INTENT(IN) :: compress_deltas + TYPE(result_type), INTENT(INOUT) :: results + + COMPLEX(r8) :: delta + INTEGER :: new_scan_radius,new_ncoarse INTEGER :: nfine, new_nfine REAL(r8), PARAMETER :: tolerance = 1.0E-6 REAL(r8) :: delta_real, delta_imag, threshold INTEGER :: i, j, k, l, m, count, match_count LOGICAL :: repeat - LOGICAL, INTENT(IN) :: compress_deltas REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, $ inQ_coarse, iinQ_coarse - TYPE(result_type), INTENT(INOUT) :: results INTEGER :: max_points, new_max_points INTEGER :: ci, cj, nx, ny REAL(r8) :: dx, dy, overlap_factor @@ -279,19 +279,19 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, match_count = 0 ! Run scan - CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, - $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, - $ results,count,match_count,dx,dy) + CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe,my_lu, + $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, + $ results,count,match_count,dx,dy) - IF (match_count == 0) THEN + IF (match_count == 0) THEN ! ALL REPEATS TURNED OFF FOR NOW WRITE(*,*)"No match found, rescanning" - repeat = .TRUE. + repeat = .FALSE. !new_scan_radius = scan_radius + 2 new_ncoarse = ncoarse + 100 new_nfine = nfine ELSE IF (match_count > 0 .AND. match_count < 3) THEN WRITE(*,*)"Match not definitive, rescanning" - repeat = .TRUE. + repeat = .FALSE. new_scan_radius = scan_radius new_ncoarse = ncoarse new_nfine = 8 @@ -327,10 +327,9 @@ SUBROUTINE growthrate_scan(qval,inQ,inQ_e,inQ_i,inc_beta, match_count = 0 ! COARSE AND FINE LOOPS - CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, + CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe,my_lu, $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, $ results,count,match_count,dx,dy) - END IF ! End repeat "if" ! Set the actual count of points @@ -350,13 +349,13 @@ END SUBROUTINE growthrate_scan c subprogram 4. scan_grid c run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, - $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, - $ results,count,match_count,dx,dy) + SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, + $ inpe,my_lu,scan_radius,ncoarse,nfine,deltaprime, + $ compress_deltas,results,count,match_count,dx,dy) ! Declarations (include necessary type declarations from original code) - REAL(r8), INTENT(IN) :: inQ_e, inQ_i, inpr, inc_beta, inds, - $ intau, inpe, deltaprime + REAL(r8), INTENT(IN) :: inQ_e,inQ_i,inpr,inc_beta,inds, + $ intau,inpe,my_lu,deltaprime INTEGER, INTENT(IN) :: scan_radius, ncoarse, nfine LOGICAL, INTENT(IN) :: compress_deltas TYPE(result_type), INTENT(INOUT) :: results @@ -387,8 +386,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, ! Evaluate riccati function delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, $ inds,intau,inpe,iinQ=iinQ_coarse) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) + delta_real = REAL(delta)*(my_lu**(1.0/3.0)) ! Critical normalization + delta_imag = AIMAG(delta)*(my_lu**(1.0/3.0)) ! Critical normalization IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 4)) THEN ! Store coarse grid point @@ -406,8 +405,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, END IF ! Check if refinement is needed - IF ((ABS(delta_real) > threshold) .AND. - $ (ABS(deltaprime) > 4)) THEN + IF (2 == 3) THEN !((ABS(delta_real) > threshold) .AND. + !$ (ABS(deltaprime) > 4)) THEN ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, !$ delta_real) == SIGN(1.0, deltaprime))) THEN @@ -434,8 +433,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, ! Evaluate riccati function delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) - delta_real = REAL(delta) - delta_imag = AIMAG(delta) + delta_real = REAL(delta)*(my_lu**(1.0/3.0)) + delta_imag = AIMAG(delta)*(my_lu**(1.0/3.0)) IF (ABS(delta_real)>ABS(deltaprime)) THEN match_count = match_count + 1 diff --git a/slayer/slayer.f b/slayer/slayer.f index 9d216799d..19356bca8 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -344,6 +344,20 @@ PROGRAM slayer ENDDO WRITE(*,*)"Calling slayer_netcdf_out" + WRITE(*,*)"qval_arr=",qval_arr + WRITE(*,*)"omegas_arr=",omegas_arr + WRITE(*,*)"inQ_arr=",inQ_arr + WRITE(*,*)"inQ_e_arr=",inQ_e_arr + WRITE(*,*)"inQ_i_arr=",inQ_i_arr + WRITE(*,*)"ind_beta_arr=",ind_beta_arr + WRITE(*,*)"D_beta_norm_arr=",D_beta_norm_arr + WRITE(*,*)"inpr_arr=",inpr_arr + WRITE(*,*)"psi_n_rational=",psi_n_rational + WRITE(*,*)"lu_arr=",lu_arr + WRITE(*,*)"Re_deltaprime_arr=",Re_deltaprime_arr + WRITE(*,*)"Im_deltaprime_arr=",Im_deltaprime_arr + WRITE(*,*)"dels_db_arr=",dels_db_arr + WRITE(*,*)"lar_gamma_arr=",lar_gamma_arr br_th = 0.0 @@ -466,17 +480,26 @@ PROGRAM slayer WRITE(*,*) "Finding roots on q=", qval_arr(k), $ " rational surface" - CALL growthrate_scan(qval_arr(k),inQ_arr(k),inQ_e_arr(k), - $ inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), - $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ scan_radius,reQ_num,compress_deltas, - $ Re_deltaprime_arr(k),results(k)) + CALL growthrate_scan(qval_arr(k),lu_arr(k),inQ_arr(k), + $ inQ_e_arr(k),inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), + $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), + $ scan_radius,reQ_num,compress_deltas, + $ Re_deltaprime_arr(k),results(k)) WRITE(*,*)"Exited growthrate_scan" ENDDO WRITE(*,*)"Calling slayer_netcdf_out" br_th = 0.0 + WRITE(*,*)"Successfully entered output_lar_gamma()" + WRITE(*,*)"qval_arr = ",qval_arr + + CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, + $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, + $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, c $ stabscan_eq_flag,stabscan_flag,br_th_flag) stop @@ -505,7 +528,7 @@ PROGRAM slayer Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) inpr_arr = (/ inpr /) - CALL growthrate_scan(qval_arr(1),inQ,inQ_e, + CALL growthrate_scan(qval_arr(1),lu,inQ,inQ_e, $ inQ_i,inc_beta,inds, $ intau,inQ,inpr,inpe, $ scan_radius,reQ_num,compress_deltas, From 4e1e98dab8291c4409360bc6d92ef8c7e3051011 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 31 Mar 2025 13:54:19 -0400 Subject: [PATCH 030/305] Revert "WIP: layer thickness usability updates" This reverts commit af3bb305653e2db68698b3f1257013f9eb39ebc2. --- slayer/delta.f | 5 +---- slayer/gslayer.f | 28 ++++++++++++------------- slayer/layerinputs.f | 9 ++++---- slayer/slayer.f | 47 ++++++++++++++++++++++++++++-------------- slayer/slayer_netcdf.f | 7 +++---- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 53053def1..9a0b2aea5 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -160,11 +160,8 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, ! x=10.0*(1.0+log10(Q/pr)) !!!!!!!! - my_q=inx ! "starting backwards integration at large q" + my_q=10.0 ! "starting backwards integration at large q" !!!!!!!! - WRITE(*,*)"myq starting point = ",my_q - WRITE(*,*)"myq starting point = ",my_q - WRITE(*,*)"myq starting point = ",my_q xmin=1e-5 IF(present(inx)) x=inx diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 9b721dc34..ce3565048 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -178,15 +178,15 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- -c Subprogram 2. output_lar_gamma -c Run +c Subprogram 3. scan_grid +c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lar_gamma_arr) + $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, + $ Im_deltaprime_arr,dels_db_arr,lar_gamma_arr) + ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag @@ -195,7 +195,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr,lu_arr + $ D_beta_norm_arr COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, $ lar_gamma_arr @@ -214,13 +214,13 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) END SUBROUTINE output_lar_gamma c----------------------------------------------------------------------- -c subprogram 3. growthrate_scan -c set up and iterate stability scans if no match is found +c Subprogram 2. growthrate_scan +c Set up and iterate stability scans if no match is found c----------------------------------------------------------------------- SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, @@ -346,8 +346,8 @@ SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, RETURN END SUBROUTINE growthrate_scan c----------------------------------------------------------------------- -c subprogram 4. scan_grid -c run stability scan on real and imaginary rotation axes +c Subprogram 3. scan_grid +c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, $ inpe,my_lu,scan_radius,ncoarse,nfine,deltaprime, @@ -453,8 +453,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, END DO END SUBROUTINE scan_grid c----------------------------------------------------------------------- -c subprogram 5. shrink_array -c remove excess scan array size from memory +c Subprogram 4. shrink_array +c Remove excess scan array size from memory c----------------------------------------------------------------------- SUBROUTINE shrink_array(arr, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) @@ -466,7 +466,7 @@ SUBROUTINE shrink_array(arr, new_size) CALL move_alloc(temp, arr) END SUBROUTINE shrink_array c----------------------------------------------------------------------- -c subprogram 6. grow_array +c Subprogram 5. grow_array c Increase scan array size if necessary c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 0b9655efe..6a8b57897 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -9,7 +9,7 @@ MODULE layerinputs_mod USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq USE bicube_mod, ONLY: bicube_eval_external,bicube_type - USE slayer_netcdf_mod + USE slayer_netcdf_mod!, ONLY: slayer_netcdf_inputs IMPLICIT NONE @@ -228,7 +228,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, $ D_beta_norm_arr,intau_arr,Q0_arr,inpr_arr, - $ inpe_arr,omegas_arr,lu_arr,gammafac_arr, + $ inpe_arr,omegas_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) c----------------------------------------------------------------------- c declarations. @@ -254,7 +254,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, $ rho,tau_v,inpr,Qconv,lbeta,qintb,gammafac REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, - $ inQ_e_arr,psi_n_rational,D_beta_norm_arr,lu_arr, + $ inQ_e_arr,psi_n_rational,D_beta_norm_arr, $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, $ intau_arr,Q0_arr,inpr_arr,inpe_arr,omegas_arr, $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr @@ -302,7 +302,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, $ inpe_arr(msing),omegas_arr(msing),omegas_e_arr(msing), $ omegas_i_arr(msing),gammafac_arr(msing), $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), - $ D_beta_norm_arr(msing),lu_arr(msing)) + $ D_beta_norm_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) @@ -443,7 +443,6 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, inds_arr(ising)=ds ind_beta_arr(ising)=d_beta D_beta_norm_arr(ising)=D_beta_norm - lu_arr(ising)=lu intau_arr(ising)=tau Q0_arr(ising)=Q inpr_arr(ising) = inpr diff --git a/slayer/slayer.f b/slayer/slayer.f index 19356bca8..437c33101 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -44,28 +44,33 @@ PROGRAM slayer COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, - $ kpower,Qratio + $ kpower, + $ Qratio INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl, - $ bal,prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, + $ bal, + $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr,lu_arr, + $ inpe_arr,omegas_arr,inQ_arr, $ psi_n_rational,ind_beta_arr,D_beta_norm_arr - REAL(r8), DIMENSION(8) :: inpr_prof - + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, + $ all_Im_deltas,all_roots + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_inQs REAL(r8), DIMENSION(:,:),ALLOCATABLE :: Re_deltas,Im_deltas INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: - $ Re_deltaprime_arr,Im_deltaprime_arr - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: js,ks,psis,jxbs, - $ Q_sols,br_ths + $ Re_deltaprime_arr,Im_deltaprime_arr, + $ all_growthrates,all_growthrate_locs + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: + $ js,ks,psis,jxbs,Q_sols,br_ths, + $ inQs_left,inQs_right,coarse_deltas REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, @@ -308,7 +313,7 @@ PROGRAM slayer $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,ind_beta_arr,D_beta_norm_arr, $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,lu_arr,gammafac_arr, + $ omegas_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr @@ -333,8 +338,8 @@ PROGRAM slayer $ " rational surface" dels_db=riccati_del_s(inQ_arr(k),inQ_e_arr(k), - $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), - $ D_beta_norm_arr(k),intau_arr(k),5*D_beta_norm) + $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), + $ D_beta_norm_arr(k),intau_arr(k)) del_s = dels_db * ind_beta_arr(k) lar_gamma = gammafac_arr(k)/del_s @@ -364,7 +369,7 @@ PROGRAM slayer CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, $ lar_gamma_arr) @@ -413,7 +418,7 @@ PROGRAM slayer WRITE(*,*)"lar_gamma inQ = ",inQ dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,d_beta, - $ intau,5*D_beta_norm) + $ intau) WRITE(*,*)"dels_db() call successful" @@ -428,7 +433,6 @@ PROGRAM slayer lar_gamma_arr = (/ lar_gamma /) WRITE(*,*)"dels_db=",dels_db - WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 WRITE(*,*)"lar_gamma=",lar_gamma WRITE(*,*)"slayer.f lar_gamma=",lar_gamma @@ -438,7 +442,7 @@ PROGRAM slayer CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational,lu_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, $ lar_gamma_arr) @@ -461,7 +465,7 @@ PROGRAM slayer $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, $ inds_arr,ind_beta_arr,D_beta_norm_arr, $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,lu_arr,gammafac_arr, + $ omegas_arr,gammafac_arr, $ Re_deltaprime_arr,Im_deltaprime_arr) WRITE(*,*)"Safety factor values=",qval_arr @@ -608,6 +612,17 @@ PROGRAM slayer inQs = (/ 1.0 /) n_k = SIZE(qval_arr) + k=1 + IF (k==1) THEN + ALLOCATE(all_RE_deltas(1,1,n_k)) + ALLOCATE(all_Im_deltas(1,1,n_k)) + ALLOCATE(all_inQs(1,n_k)) + ALLOCATE(all_growthrates(n_k)) + ALLOCATE(all_growthrate_locs(n_k)) + ENDIF + all_Re_deltas(:,:,k) = 0.0 + all_Im_deltas(:,:,k) = 0.0 + all_inQs(:,k) = 0.0 qval_arr = (/ 3 /) inQs = (/ 1.0 /) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 62991adfa..1bee40819 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -52,8 +52,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, - $ inQs,iinQs,results) + $ ind_beta_arr,D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) ! ds = D_beta_norm for lar growth rate routines @@ -68,7 +67,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr,lu_arr + $ D_beta_norm_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, $ Im_deltaprime_arr,inQs,iinQs @@ -209,7 +208,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) - CALL check( nf90_put_var(ncid,S_id, lu_arr)) + CALL check( nf90_put_var(ncid,S_id, (/lu/))) CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) !CALL check( nf90_put_var(ncid,qr_id, qval_arr)) From bff21878915a887a67924bc72a7985f39248d14f Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 4 Apr 2025 10:25:14 -0400 Subject: [PATCH 031/305] netcdf working again, minor slayer fixes --- equil/lar.f | 15 +++++++++++---- slayer/slayer.f | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/equil/lar.f b/equil/lar.f index 59f882cec..c73657a2c 100644 --- a/equil/lar.f +++ b/equil/lar.f @@ -24,6 +24,7 @@ MODULE lar_mod INTEGER :: lar_m=2,lar_n=1 REAL(r8) :: lar_a,lar_r0,p_pres,p_sig REAL(r8), PRIVATE :: beta0,p,p00,q,sigma,sigma0 + CHARACTER(20) :: sigma_type TYPE(spline_type), PRIVATE :: spl CONTAINS @@ -45,7 +46,7 @@ SUBROUTINE lar_der(neq,r,y,dy) c----------------------------------------------------------------------- c compute derivatives. c----------------------------------------------------------------------- - CALL lar_profile(r,y,p,pp,sigma,bsq,q) + CALL lar_profile(r,y,p,pp,sigma,bsq,q,sigma_type) dy(1)=-pp/bsq*y(1)+sigma*y(2)*r dy(2)=-pp/bsq*y(2)-sigma*y(1)/r dy(3)=y(1)*lar_r0/r @@ -78,7 +79,7 @@ SUBROUTINE lar_run REAL(r8), DIMENSION(:,:), POINTER :: temp NAMELIST/lar_input/mtau,ma,lar_r0,lar_a,beta0,q0,p_pres,p_sig, - $ zeroth,out,bin,lar_m,lar_n + $ zeroth,out,bin,sigma_type,lar_m,lar_n REAL(r8), DIMENSION(:), POINTER :: xptr,fsptr,fs1ptr c----------------------------------------------------------------------- @@ -277,11 +278,12 @@ END SUBROUTINE lar_coefs c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE lar_profile(r,y,p,pp,sigma,bsq,q) + SUBROUTINE lar_profile(r,y,p,pp,sigma,bsq,q,sigma_type) REAL(r8), INTENT(IN) :: r REAL(r8), INTENT(OUT) :: p,pp,sigma,bsq,q REAL(r8), DIMENSION(:), INTENT(IN) :: y + CHARACTER(20) :: sigma_type REAL(r8) :: x,xfac c----------------------------------------------------------------------- @@ -291,7 +293,12 @@ SUBROUTINE lar_profile(r,y,p,pp,sigma,bsq,q) xfac=1-x*x p=p00*xfac**p_pres pp=-2*p_pres*p00*x*xfac**(p_pres-1) - sigma=sigma0/(1+x**(2*p_sig))**(1+1/p_sig) + SELECT CASE(sigma_type) + CASE("wesson") + sigma=sigma0*xfac**p_sig + CASE default + sigma=sigma0/(1+x**(2*p_sig))**(1+1/p_sig) + END SELECT bsq=(y(1)/r)**2+y(2)**2 q=r**2*y(2)/(lar_r0*y(1)) c----------------------------------------------------------------------- diff --git a/slayer/slayer.f b/slayer/slayer.f index 437c33101..3ea016ba6 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -409,8 +409,9 @@ PROGRAM slayer Re_deltaprime_arr = (/ REAL(delta_n_p) /) Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) inpr_arr = (/ inpr /) + D_beta_norm_arr = (/ D_beta_norm /) - D_beta_norm = inds ! NAMELIST + !D_beta_norm = inds ! NAMELIST WRITE(*,*)"D_beta_norm = ",D_beta_norm WRITE(*,*)"inds = ",inds From 25292b2d84bb04b22169c48684768b3fa08c3fe8 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Sat, 12 Apr 2025 16:00:35 -0400 Subject: [PATCH 032/305] Bug fixes and ease of use --- input/slayer.in | 2 +- slayer/delta.f | 10 +- slayer/gslayer.f | 124 ++----------------- slayer/layerinputs.f | 48 ++++---- slayer/slayer.f | 61 ++++++---- slayer/slayer_netcdf.f | 263 +++++++++++++++++++++-------------------- 6 files changed, 214 insertions(+), 294 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 53994eef1..103e69a0c 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -26,7 +26,7 @@ inQ_e=2.0 ! normalized electron diamagnetic frequency inQ_i=-2.0 ! normalized ion diamagnetic frequency inc_beta=0.128 ! dimensionless measure of the plasma pressure - inds=2.26 ! normalized ion sound radius + inds=2.26 ! normalized ion sound radius. NOTE: set inds = 0 for internal D_norm caltulation intau=1.0 ! ion temperature divided by electron temperature Q0=1.5 ! unnecessary placeholder variable for inQ? delta_n_p=(5.0,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). diff --git a/slayer/delta.f b/slayer/delta.f index 0fffd1828..06332f1a3 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -160,7 +160,7 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, ! x=10.0*(1.0+log10(Q/pr)) !!!!!!!! - my_q=10.0 ! "starting backwards integration at large q" + my_q=inx!10.0 ! "starting backwards integration at large q" !!!!!!!! xmin=1e-5 @@ -239,7 +239,7 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat COMPLEX(r8) :: E,F - COMPLEX(r8), PARAMETER :: ifac=(0,1) + !COMPLEX(r8), PARAMETER :: ifac=(0,1) !Q_hat = Q / ds**4 Q_hat = (Q_e*(1+tau)/tau) / D_beta_norm**4 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark @@ -272,7 +272,7 @@ SUBROUTINE w_der(neq,x,y,dy) COMPLEX(r8) :: C2p COMPLEX(r8) :: A1 COMPLEX(r8) :: A2 - COMPLEX(r8), PARAMETER :: ifac=(0,1) + !COMPLEX(r8), PARAMETER :: ifac=(0,1) IF (parflow_flag) THEN C1=((1 + tau)*x**2*pe* @@ -736,7 +736,7 @@ SUBROUTINE w_der_temp(neq,x,y,dy) REAL(r8), INTENT(IN) :: x COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy - COMPLEX(r8), PARAMETER :: ifac=(0,1) + !COMPLEX(r8), PARAMETER :: ifac=(0,1) dy(1)=(2.0*x/(ifac*(Q-Q_e)+x**2.0)-1.0/x)*y(1)-y(1)*y(1)/x $ +x*(ifac*(Q-Q_e)+x**2.0) @@ -823,7 +823,7 @@ SUBROUTINE phi_der(neq,x,y,dy) REAL(r8), INTENT(IN) :: x COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy - COMPLEX(r8), PARAMETER :: ifac=(0,1) + !COMPLEX(r8), PARAMETER :: ifac=(0,1) dy(1)=(1+ifac*(Q-Q_e)*x**2.0)/x**2.0*y(2) dy(2)=(-Q*(Q-Q_i)*x**4.0+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 diff --git a/slayer/gslayer.f b/slayer/gslayer.f index fdc782e2c..9346fbdc9 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -185,7 +185,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lar_gamma_arr) + $ Im_deltaprime_arr,dels_db_arr,lu_arr,lar_gamma_arr) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, @@ -195,7 +195,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr + $ D_beta_norm_arr,lu_arr COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, $ lar_gamma_arr @@ -214,7 +214,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) END SUBROUTINE output_lar_gamma @@ -283,55 +283,6 @@ SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, $ results,count,match_count,dx,dy) - IF (match_count == 0) THEN ! ALL REPEATS TURNED OFF FOR NOW - WRITE(*,*)"No match found, rescanning" - repeat = .FALSE. - !new_scan_radius = scan_radius + 2 - new_ncoarse = ncoarse + 100 - new_nfine = nfine - ELSE IF (match_count > 0 .AND. match_count < 3) THEN - WRITE(*,*)"Match not definitive, rescanning" - repeat = .FALSE. - new_scan_radius = scan_radius - new_ncoarse = ncoarse - new_nfine = 8 - ELSE - repeat = .FALSE. - WRITE(*,*)"Match found" - - END IF - - IF (repeat) THEN - WRITE(*,*)"Rerunning growth rate scan" - - new_max_points = new_ncoarse**2 * (1 + - $ (new_nfine-1)**2) - - ! Resize arrays to new max number of points - CALL grow_array(results%inQs, max_points, new_max_points) - CALL grow_array(results%iinQs, max_points, new_max_points) - CALL grow_array(results%Re_deltas, max_points, new_max_points) - CALL grow_array(results%Im_deltas, max_points, new_max_points) - - results%inQs=0.0 - results%iinQs=0.0 - results%Re_deltas=0.0 - results%Im_deltas=0.0 - ! Initialize counter - count = 0 - - ! Calculate step sizes - inQ_step = (2.0 * new_scan_radius)/(new_ncoarse - 1) - iinQ_step = (2.0 * new_scan_radius)/(new_ncoarse - 1) - - match_count = 0 - - ! COARSE AND FINE LOOPS - CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe,my_lu, - $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, - $ results,count,match_count,dx,dy) - END IF ! End repeat "if" - ! Set the actual count of points results%count = count @@ -378,7 +329,8 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, iinQ_step = (2.0 * scan_radius) / (ncoarse - 1) dx = inQ_step dy = iinQ_step - + count = 0 + DO i = 1, ncoarse DO j = 1, ncoarse inQ_coarse = -scan_radius + (i - 1) * inQ_step @@ -389,66 +341,12 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, delta_real = REAL(delta)*(my_lu**(1.0/3.0)) ! Critical normalization delta_imag = AIMAG(delta)*(my_lu**(1.0/3.0)) ! Critical normalization - IF ((.NOT. compress_deltas) .OR. (ABS(deltaprime) < 4)) THEN - ! Store coarse grid point - count = count + 1 - results%inQs(count) = inQ_coarse - results%iinQs(count) = iinQ_coarse - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END IF - - IF (ABS(deltaprime) > 8) THEN - threshold = ABS(deltaprime)**(1./3.) - ELSE - threshold = 0.25 * ABS(deltaprime) - END IF - - ! Check if refinement is needed - IF (2 == 3) THEN !((ABS(delta_real) > threshold) .AND. - !$ (ABS(deltaprime) > 4)) THEN - ! IF ((ABS(delta_real) > threshold) .AND. (SIGN(1.0, - !$ delta_real) == SIGN(1.0, deltaprime))) THEN - - ! Fine grid loop - fine_dx = dx / nfine - fine_dy = dy / nfine - - overlap_x = overlap_factor * fine_dx - overlap_y = overlap_factor * fine_dy - - x_start = inQ_coarse - dx/2 + overlap_x - x_end = inQ_coarse + dx/2 - overlap_x - y_start = iinQ_coarse - dy/2 + overlap_y - y_end = iinQ_coarse + dy/2 - overlap_y - - DO fj = 0, nfine-1 - iinQ_fine = y_start + fj * (y_end-y_start) / (nfine-1) - DO fi = 0, nfine-1 - inQ_fine = x_start + fi * (x_end-x_start) / (nfine-1) - IF ((ABS(inQ_coarse - inQ_fine) < - $ tolerance) .AND. (ABS(iinQ_coarse - - $ iinQ_fine) < tolerance)) CYCLE - - ! Evaluate riccati function - delta = riccati(inQ_fine,inQ_e,inQ_i,inpr, - $ inc_beta,inds,intau,inpe,iinQ=iinQ_fine) - delta_real = REAL(delta)*(my_lu**(1.0/3.0)) - delta_imag = AIMAG(delta)*(my_lu**(1.0/3.0)) - - IF (ABS(delta_real)>ABS(deltaprime)) THEN - match_count = match_count + 1 - END IF - - ! Store fine grid point - count = count + 1 - results%inQs(count) = inQ_fine - results%iinQs(count) = iinQ_fine - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - END DO - END DO - END IF + count = count + 1 + results%inQs(count) = inQ_coarse + results%iinQs(count) = iinQ_coarse + results%Re_deltas(count) = delta_real + results%Im_deltas(count) = delta_imag + END DO END DO END SUBROUTINE scan_grid diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 6a8b57897..dfa924f9f 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -47,14 +47,14 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) - CALL check(stat) ! Error handling + CALL sl_check(stat) ! Error handling stat = nf90_inquire_attribute(ncid,msing_id,"msing", $ len = msing_len) - CALL check(stat) + CALL sl_check(stat) ALLOCATE(msing_arr(msing_len)) stat = nf90_get_att(ncid,msing_id,"msing",msing_arr) - CALL check(stat) + CALL sl_check(stat) msing=INT(msing_arr(1)) @@ -65,16 +65,16 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ALLOCATE(delta_prime(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inquire_attribute(ncid,bt0_id,"bt0",len=bt0_len) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inquire_attribute(ncid,psio_id,"psio",len=psio_len) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inquire_attribute(ncid,mpsi_id,"mpsi",len=mpsi_len) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) - CALL check(stat) + CALL sl_check(stat) bt0_id=0 !!!!! THIS COULD BE A PROBLEM nn_id=0 @@ -88,39 +88,39 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Get Variable IDs stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inq_varid(ncid, "q_rational", qr_id) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inq_varid(ncid, "shear", shear_id) - CALL check(stat) + CALL sl_check(stat) stat = nf90_inq_varid(ncid, "resm", resm_id) - CALL check(stat) + CALL sl_check(stat) ! Get attributes stat = nf90_get_att(ncid, ro_id, "ro", r_o) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_att(ncid, bt0_id, "bt0", my_bt0) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_att(ncid, psio_id, "psio", my_psio) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_att(ncid, nn_id, "n", nn) - CALL check(stat) + CALL sl_check(stat) ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) - CALL check(stat) + CALL sl_check(stat) ! Read 1D variables stat = nf90_get_var(ncid, qr_id, q_rational) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_var(ncid, pr_id, psi_n_rational) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_var(ncid, shear_id, shear) - CALL check(stat) + CALL sl_check(stat) stat = nf90_get_var(ncid, resm_id, resm) - CALL check(stat) + CALL sl_check(stat) ! Extract Diagonal, with 3rd index signifying REAL part DO i = 1, msing @@ -130,7 +130,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Clean Up DEALLOCATE(delta_prime) stat = nf90_close(ncid) - CALL check(stat) + CALL sl_check(stat) END SUBROUTINE read_stride_netcdf_diagonal c----------------------------------------------------------------------- diff --git a/slayer/slayer.f b/slayer/slayer.f index 3c159e3ce..b8e093bdd 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -34,7 +34,7 @@ PROGRAM slayer $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,stabscan_eq_flag,stabscan_flag, - $ lar_gamma_eq_flag,lar_gamma_flag,Pe_flag, + $ lar_gamma_eq_flag,lar_gamma_flag, $ br_th_flag,compress_deltas REAL(r8) :: n_e,t_e,t_i,omega,omega0, @@ -56,7 +56,7 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, $ inQ_i_arr,inc_beta_arr,inds_arr, $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr, + $ inpe_arr,omegas_arr,inQ_arr,lu_arr, $ psi_n_rational,ind_beta_arr,D_beta_norm_arr REAL(r8), DIMENSION(8) :: inpr_prof REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, @@ -91,7 +91,6 @@ PROGRAM slayer $ scan_radius,QPscan_flag,QPscan2_flag, $ QPescan_flag,QDscan2_flag,Qbscan_flag,Qscan_flag, $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, - $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ stability_flag,lar_gamma_eq_flag,lar_gamma_flag, @@ -341,7 +340,8 @@ PROGRAM slayer dels_db=riccati_del_s(inQ_arr(k),inQ_e_arr(k), $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), - $ D_beta_norm_arr(k),intau_arr(k)) + $ D_beta_norm_arr(k),intau_arr(k), + $ 5.0*D_beta_norm_arr(k)) del_s = dels_db * ind_beta_arr(k) lar_gamma = gammafac_arr(k)/del_s @@ -373,7 +373,7 @@ PROGRAM slayer $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lar_gamma_arr) + $ lu_arr,lar_gamma_arr) stop ENDIF @@ -394,13 +394,25 @@ PROGRAM slayer CALL params(n_e,t_e,t_i,omega, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - !inQ=Q ! TAKEN FROM NAMELIST - !inQ_e=Q_e ! TAKEN FROM NAMELIST - !inQ_i=Q_i ! TAKEN FROM NAMELIST - !inc_beta=c_beta - !inds=ds - !intau=tau - !Q0=Q + + IF (inQ > 0.0) THEN + Q = inQ ! NAMELIST + END IF + IF (inQ_e > 0.0) THEN + Q_e = inQ_e ! NAMELIST + END IF + IF (inQ_i > 0.0) THEN + Q_i = inQ_i ! NAMELIST + END IF + IF (inpr > 0.0) THEN + pr = inpr ! NAMELIST + END IF + IF (intau > 0.0) THEN + tau = intau ! NAMELIST + END IF + IF (inds > 0.0) THEN + D_beta_norm = inds ! NAMELIST + END IF qval_arr = (/ qval /) omegas_arr = (/ omega /) @@ -413,15 +425,13 @@ PROGRAM slayer inpr_arr = (/ inpr /) D_beta_norm_arr = (/ D_beta_norm /) - !D_beta_norm = inds ! NAMELIST - WRITE(*,*)"D_beta_norm = ",D_beta_norm WRITE(*,*)"inds = ",inds WRITE(*,*)"lar_gamma inQ = ",inQ dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,d_beta, - $ intau) + $ intau,5.0*D_beta_norm) WRITE(*,*)"dels_db() call successful" @@ -436,9 +446,8 @@ PROGRAM slayer lar_gamma_arr = (/ lar_gamma /) WRITE(*,*)"dels_db=",dels_db - WRITE(*,*)"lar_gamma=",lar_gamma - - WRITE(*,*)"slayer.f lar_gamma=",lar_gamma + WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 + WRITE(*,*)"growth rate=",lar_gamma br_th = 0.0 @@ -447,7 +456,7 @@ PROGRAM slayer $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lar_gamma_arr) + $ lu_arr,lar_gamma_arr) stop ENDIF @@ -535,6 +544,12 @@ PROGRAM slayer Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) inpr_arr = (/ inpr /) + + CALL params(n_e,t_e,t_i,omega, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + + D_beta_norm_arr = (/ D_beta_norm /) + CALL growthrate_scan(qval_arr(1),lu,inQ,inQ_e, $ inQ_i,inc_beta,inds, $ intau,inQ,inpr,inpe, @@ -544,8 +559,12 @@ PROGRAM slayer WRITE(*,*)"allocations successful" br_th = 0.0 -c CALL slayer_netcdf_out(1,lar_gamma_eq_flag,lar_gamma_flag, -c $ stabscan_eq_flag,stabscan_flag,br_th_flag) + CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, + $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, + $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, + $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) stop ENDIF c----------------------------------------------------------------------- diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 1bee40819..152a0093f 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -20,15 +20,11 @@ MODULE slayer_netcdf_mod IMPLICIT NONE CONTAINS c ----------------------------------------------------------------------- -c ----------------------------------------------------------------------- -c subprogram 2. stride_netcdf_out. -c Replicate stride.out information in netcdf format. -c ----------------------------------------------------------------------- c----------------------------------------------------------------------- c subprogram 1. check. c Check status of netcdf file. c----------------------------------------------------------------------- - SUBROUTINE check(stat) + SUBROUTINE sl_check(stat) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- @@ -37,14 +33,18 @@ SUBROUTINE check(stat) c stop if it is an error. c----------------------------------------------------------------------- IF(stat /= nf90_noerr) THEN - PRINT *, TRIM(nf90_strerror(stat)) - !STOP "ERROR: failed to write/read netcdf file" + PRINT *, TRIM(nf90_strerror(stat)) + STOP "ERROR: failed to write/read netcdf file" ENDIF c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- RETURN - END SUBROUTINE check + END SUBROUTINE sl_check +c ----------------------------------------------------------------------- +c subprogram 2. slayer_netcdf_out. +c Replicate stride.out information in netcdf format. +c ----------------------------------------------------------------------- c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- @@ -52,8 +52,8 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ ind_beta_arr,D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) - + $ lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, + $ inQs,iinQs,results) ! ds = D_beta_norm for lar growth rate routines c OPTIONAL @@ -65,9 +65,10 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr + REAL(r8), DIMENSION(msing) :: gamma_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, - $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr + $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr, + $ ind_beta_arr,D_beta_norm_arr,lu_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, $ Im_deltaprime_arr,inQs,iinQs @@ -115,7 +116,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, c open files c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Creating netcdf files" - CALL check( nf90_create(ncfile, + CALL sl_check( nf90_create(ncfile, $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) max_points = maxval([(results(run)%count, run=1,msing)]) @@ -123,12 +124,12 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, c define global file attributes c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Defining netcdf globals" - CALL check( nf90_put_att(ncid,nf90_global,"title", + CALL sl_check( nf90_put_att(ncid,nf90_global,"title", $ "SLAYER outputs")) - !CALL check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) - !CALL check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) - !CALL check( nf90_put_att(ncid,nf90_global,"n", nn)) - CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + !CALL sl_check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) + !CALL sl_check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) + !CALL sl_check( nf90_put_att(ncid,nf90_global,"n", nn)) + CALL sl_check( nf90_put_att(ncid,nf90_global,"version", version)) ! define global attributes ! define dimensions IF(debug_flag) PRINT *," - Defining dimensions in netcdf" @@ -138,109 +139,111 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, WRITE(*,*)"netcdf qval_arr=",qval_arr IF(msing>0)THEN - CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational - CALL check( nf90_def_dim(ncid, "i", 2, i_dim) ) - CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, + CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) + CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) - CALL check( nf90_def_var(ncid,"omegas",nf90_double, + CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) - CALL check( nf90_def_var(ncid,"Q",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, $ qsing_dim,Q_id)) - CALL check( nf90_def_var(ncid,"Q_e",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Q_e",nf90_double, $ qsing_dim,Q_e_id)) - CALL check( nf90_def_var(ncid,"Q_i",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Q_i",nf90_double, $ qsing_dim,Q_i_id)) - CALL check( nf90_def_var(ncid,"S",nf90_double, + CALL sl_check( nf90_def_var(ncid,"S",nf90_double, $ qsing_dim,S_id)) - CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, - $ qsing_dim,pr_id) ) - CALL check( nf90_def_var(ncid,"P",nf90_double, + CALL sl_check( nf90_def_var(ncid,"psi_n_rational", + $ nf90_double,qsing_dim,pr_id) ) + CALL sl_check( nf90_def_var(ncid,"P",nf90_double, $ qsing_dim,inpr_id) ) - CALL check( nf90_def_var(ncid,"q_rational",nf90_double, + CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, $ qsing_dim,qr_id) ) - CALL check( nf90_def_dim(ncid, "points", max_points, + CALL sl_check( nf90_def_dim(ncid, "points", max_points, $ point_dimid) ) IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL check( nf90_def_var(ncid,"growthrates",nf90_double, + CALL sl_check( nf90_def_var(ncid,"growthrate",nf90_double, $ qsing_dim,gamma_id)) - CALL check(nf90_def_var(ncid,"growthrate_locs",nf90_double, - $ qsing_dim,gamma_loc_id)) - CALL check( nf90_def_var(ncid, "Re_Qs", nf90_double, + CALL sl_check(nf90_def_var(ncid,"growthrate_locs", + $ nf90_double,qsing_dim,gamma_loc_id)) + CALL sl_check( nf90_def_var(ncid, "Re_Qs", nf90_double, $ [point_dimid, qsing_dim], varids(1)) ) - CALL check( nf90_def_var(ncid, "Im_Qs", nf90_double, + CALL sl_check( nf90_def_var(ncid, "Im_Qs", nf90_double, $ [point_dimid, qsing_dim], varids(2)) ) - CALL check( nf90_def_var(ncid, "Re_deltas", nf90_double, + CALL sl_check( nf90_def_var(ncid, "Re_deltas",nf90_double, $ [point_dimid, qsing_dim], varids(3)) ) - CALL check( nf90_def_var(ncid, "Im_deltas", nf90_double, + CALL sl_check( nf90_def_var(ncid, "Im_deltas",nf90_double, $ [point_dimid, qsing_dim], varids(4)) ) END IF IF (br_th_flag) THEN - CALL check( nf90_def_var(ncid,"br_th",nf90_double, + CALL sl_check( nf90_def_var(ncid,"br_th",nf90_double, $ qsing_dim,br_th_id) ) END IF END IF IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, $ qsing_dim,rdpp_id) ) - CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, $ qsing_dim,idpp_id) ) END IF IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN - CALL check( nf90_def_var(ncid,"delta_s_d_b",nf90_double, + CALL sl_check( nf90_def_var(ncid,"delta_s_d_b",nf90_double, $ (/qsing_dim,i_dim/),dels_db_id) ) - CALL check( nf90_def_var(ncid,"d_beta",nf90_double, + CALL sl_check( nf90_def_var(ncid,"d_beta",nf90_double, $ qsing_dim,d_b_id) ) - CALL check( nf90_def_var(ncid,"D_beta_norm",nf90_double, + CALL sl_check( nf90_def_var(ncid,"D_beta_norm",nf90_double, $ qsing_dim,indnorm_id) ) - CALL check( nf90_def_var(ncid,"growthrate",nf90_double, - $ (/qsing_dim,i_dim/),lar_gamma_id) ) + CALL sl_check( nf90_def_var(ncid,"growthrate_estimate", + $ nf90_double,(/qsing_dim,i_dim/),lar_gamma_id) ) END IF ! end definitions - CALL check( nf90_enddef(ncid) ) + CALL sl_check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- - CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) - CALL check( nf90_put_var(ncid,Q_id, inQ_arr)) - CALL check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) - CALL check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) - CALL check( nf90_put_var(ncid,S_id, (/lu/))) - CALL check( nf90_put_var(ncid,pr_id, psi_n_rational)) - CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) - !CALL check( nf90_put_var(ncid,qr_id, qval_arr)) + CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) + CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) + CALL sl_check( nf90_put_var(ncid,Q_id, inQ_arr)) + CALL sl_check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) + CALL sl_check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) + CALL sl_check( nf90_put_var(ncid,S_id, (/lu/))) + CALL sl_check( nf90_put_var(ncid,pr_id, psi_n_rational)) + CALL sl_check( nf90_put_var(ncid,inpr_id, inpr_arr)) + !CALL sl_check( nf90_put_var(ncid,qr_id, qval_arr)) IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) - CALL check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) + CALL sl_check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) + CALL sl_check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) DO run = 1, msing - CALL check( nf90_put_var(ncid,varids(1),results(run)%inQs, - $ start=[1, run], count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid,varids(2),results(run)%iinQs, - $ start=[1, run], count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid, varids(3), + CALL sl_check( nf90_put_var(ncid,varids(1), + $ results(run)%inQs,start=[1, run], + $ count=[results(run)%count, 1]) ) + CALL sl_check( nf90_put_var(ncid,varids(2), + $ results(run)%iinQs,start=[1, run], + $ count=[results(run)%count, 1])) + CALL sl_check( nf90_put_var(ncid, varids(3), $ results(run)%Re_deltas, start=[1, run], $ count=[results(run)%count, 1]) ) - CALL check( nf90_put_var(ncid, varids(4), + CALL sl_check( nf90_put_var(ncid, varids(4), $ results(run)%Im_deltas, start=[1, run], $ count=[results(run)%count, 1]) ) END DO END IF IF (br_th_flag) THEN - CALL check( nf90_put_var(ncid,br_th_id, (/ br_th /))) + CALL sl_check( nf90_put_var(ncid,br_th_id, (/ br_th /))) END IF IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN - CALL check( nf90_put_var(ncid,dels_db_id, + CALL sl_check( nf90_put_var(ncid,dels_db_id, $ RESHAPE((/REAL(dels_db_arr),AIMAG(dels_db_arr)/), $ (/msing,2/)))) - CALL check( nf90_put_var(ncid,d_b_id,ind_beta_arr)) - CALL check( nf90_put_var(ncid,indnorm_id, D_beta_norm_arr)) - CALL check( nf90_put_var(ncid,lar_gamma_id, + CALL sl_check( nf90_put_var(ncid,d_b_id,ind_beta_arr)) + CALL sl_check( nf90_put_var(ncid,indnorm_id, D_beta_norm_arr)) + CALL sl_check( nf90_put_var(ncid,lar_gamma_id, $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), $ (/msing,2/)))) @@ -250,7 +253,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, c close file c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Closing netcdf file" - CALL check( nf90_close(ncid) ) + CALL sl_check( nf90_close(ncid) ) c ----------------------------------------------------------------------- c terminate. c ----------------------------------------------------------------------- @@ -326,117 +329,117 @@ SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, c open files c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Creating netcdf files" - CALL check( nf90_create(ncfile, + CALL sl_check( nf90_create(ncfile, $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) c ----------------------------------------------------------------------- c define global file attributes c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Defining netcdf globals" - CALL check( nf90_put_att(ncid,nf90_global,"title", + CALL sl_check( nf90_put_att(ncid,nf90_global,"title", $ "SLAYER outputs")) - CALL check( nf90_put_att(ncid,nf90_global,"version", version)) + CALL sl_check( nf90_put_att(ncid,nf90_global,"version", version)) IF(debug_flag) PRINT *," - Defining dimensions in netcdf" IF(msing>0)THEN - !CALL check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational - CALL check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, + !CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, $ qsing_id)) - CALL check( nf90_def_var(ncid,"ne",nf90_double, + CALL sl_check( nf90_def_var(ncid,"ne",nf90_double, $ qsing_dim,ne_id)) - CALL check( nf90_def_var(ncid,"te",nf90_double, + CALL sl_check( nf90_def_var(ncid,"te",nf90_double, $ qsing_dim,te_id)) - CALL check( nf90_def_var(ncid,"ni",nf90_double, + CALL sl_check( nf90_def_var(ncid,"ni",nf90_double, $ qsing_dim,ni_id)) - CALL check( nf90_def_var(ncid,"ti",nf90_double, + CALL sl_check( nf90_def_var(ncid,"ti",nf90_double, $ qsing_dim,ti_id)) - CALL check( nf90_def_var(ncid,"zeff",nf90_double, + CALL sl_check( nf90_def_var(ncid,"zeff",nf90_double, $ qsing_dim,zeff_id)) - CALL check( nf90_def_var(ncid,"shear",nf90_double, + CALL sl_check( nf90_def_var(ncid,"shear",nf90_double, $ qsing_dim,shear_id)) - CALL check( nf90_def_var(ncid,"bt",nf90_double, + CALL sl_check( nf90_def_var(ncid,"bt",nf90_double, $ qsing_dim,bt_id)) - CALL check( nf90_def_var(ncid,"rs",nf90_double, + CALL sl_check( nf90_def_var(ncid,"rs",nf90_double, $ qsing_dim,rs_id)) - CALL check( nf90_def_var(ncid,"R0",nf90_double, + CALL sl_check( nf90_def_var(ncid,"R0",nf90_double, $ qsing_dim,R0_id)) - !CALL check( nf90_def_var(ncid,"mu_i",nf90_double, + !CALL sl_check( nf90_def_var(ncid,"mu_i",nf90_double, !$ qsing_dim,mu_i_id)) - CALL check( nf90_def_var(ncid,"resm",nf90_int, + CALL sl_check( nf90_def_var(ncid,"resm",nf90_int, $ qsing_dim,resm_id)) - CALL check( nf90_def_var(ncid,"nns_arr",nf90_int, + CALL sl_check( nf90_def_var(ncid,"nns_arr",nf90_int, $ qsing_dim,nns_id)) - !CALL check( nf90_def_var(ncid,"qval",nf90_int, + !CALL sl_check( nf90_def_var(ncid,"qval",nf90_int, !$ qsing_dim,qval_id)) - CALL check( nf90_def_var(ncid,"Q",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, $ qsing_dim,inQ_id)) - ! CALL check( nf90_def_var(ncid,"Q_e",nf90_double, + ! CALL sl_check( nf90_def_var(ncid,"Q_e",nf90_double, !$ qsing_dim,inQ_e_id)) - ! CALL check( nf90_def_var(ncid,"Q_i",nf90_double, + ! CALL sl_check( nf90_def_var(ncid,"Q_i",nf90_double, !$ qsing_dim,inQ_i_id)) - CALL check( nf90_def_var(ncid,"c_beta",nf90_double, + CALL sl_check( nf90_def_var(ncid,"c_beta",nf90_double, $ qsing_dim,inc_beta_id)) - CALL check( nf90_def_var(ncid,"ds",nf90_double, + CALL sl_check( nf90_def_var(ncid,"ds",nf90_double, $ qsing_dim,inds_id)) - CALL check( nf90_def_var(ncid,"tau",nf90_double, + CALL sl_check( nf90_def_var(ncid,"tau",nf90_double, $ qsing_dim,intau_id)) - CALL check( nf90_def_var(ncid,"pr",nf90_double, + CALL sl_check( nf90_def_var(ncid,"pr",nf90_double, $ qsing_dim,inpr_id)) - CALL check( nf90_def_var(ncid,"pe",nf90_double, + CALL sl_check( nf90_def_var(ncid,"pe",nf90_double, $ qsing_dim,inpe_id)) - CALL check( nf90_def_var(ncid,"omegas",nf90_double, + CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) - CALL check( nf90_def_var(ncid,"omegas_e",nf90_double, + CALL sl_check( nf90_def_var(ncid,"omegas_e",nf90_double, $ qsing_dim,omegas_e_id)) - CALL check( nf90_def_var(ncid,"omegas_i",nf90_double, + CALL sl_check( nf90_def_var(ncid,"omegas_i",nf90_double, $ qsing_dim,omegas_i_id)) - CALL check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, $ qsing_dim,Re_delta_id)) - CALL check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, + CALL sl_check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, $ qsing_dim,Im_delta_id)) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" ! end definitions - CALL check( nf90_enddef(ncid) ) + CALL sl_check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" - CALL check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL check( nf90_put_var(ncid,ne_id, ne_arr)) - CALL check( nf90_put_var(ncid,ni_id, ni_arr)) - CALL check( nf90_put_var(ncid,te_id, te_arr)) - CALL check( nf90_put_var(ncid,ti_id, ti_arr)) - CALL check( nf90_put_var(ncid,zeff_id, zeff_arr)) - CALL check( nf90_put_var(ncid,shear_id, shear)) - CALL check( nf90_put_var(ncid,bt_id, bt_arr)) - CALL check( nf90_put_var(ncid,rs_id, rs_arr)) - CALL check( nf90_put_var(ncid,R0_id, R0_arr)) - !CALL check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) - CALL check( nf90_put_var(ncid,resm_id, resm)) - CALL check( nf90_put_var(ncid,nns_id, nns_arr)) - !CALL check( nf90_put_var(ncid,qval_id, qval_arr)) - CALL check( nf90_put_var(ncid,inQ_id, inQ_arr)) - !CALL check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) - !CALL check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) - CALL check( nf90_put_var(ncid,inc_beta_id, inc_beta_arr)) - CALL check( nf90_put_var(ncid,inds_id, inds_arr)) - CALL check( nf90_put_var(ncid,intau_id, intau_arr)) - CALL check( nf90_put_var(ncid,inpr_id, inpr_arr)) - CALL check( nf90_put_var(ncid,inpe_id, inpe_arr)) - CALL check( nf90_put_var(ncid,omegas_id, omegas_arr)) - CALL check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) - CALL check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) - CALL check( nf90_put_var(ncid,Re_delta_id,Re_deltaprime_arr)) - CALL check( nf90_put_var(ncid,Im_delta_id,Im_deltaprime_arr)) + CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) + CALL sl_check( nf90_put_var(ncid,ne_id, ne_arr)) + CALL sl_check( nf90_put_var(ncid,ni_id, ni_arr)) + CALL sl_check( nf90_put_var(ncid,te_id, te_arr)) + CALL sl_check( nf90_put_var(ncid,ti_id, ti_arr)) + CALL sl_check( nf90_put_var(ncid,zeff_id, zeff_arr)) + CALL sl_check( nf90_put_var(ncid,shear_id, shear)) + CALL sl_check( nf90_put_var(ncid,bt_id, bt_arr)) + CALL sl_check( nf90_put_var(ncid,rs_id, rs_arr)) + CALL sl_check( nf90_put_var(ncid,R0_id, R0_arr)) + !CALL sl_check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) + CALL sl_check( nf90_put_var(ncid,resm_id, resm)) + CALL sl_check( nf90_put_var(ncid,nns_id, nns_arr)) + !CALL sl_check( nf90_put_var(ncid,qval_id, qval_arr)) + CALL sl_check( nf90_put_var(ncid,inQ_id, inQ_arr)) + !CALL sl_check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) + !CALL sl_check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) + CALL sl_check( nf90_put_var(ncid,inc_beta_id, inc_beta_arr)) + CALL sl_check( nf90_put_var(ncid,inds_id, inds_arr)) + CALL sl_check( nf90_put_var(ncid,intau_id, intau_arr)) + CALL sl_check( nf90_put_var(ncid,inpr_id, inpr_arr)) + CALL sl_check( nf90_put_var(ncid,inpe_id, inpe_arr)) + CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) + CALL sl_check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) + CALL sl_check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) + CALL sl_check( nf90_put_var(ncid,Re_delta_id,Re_deltaprime_arr)) + CALL sl_check( nf90_put_var(ncid,Im_delta_id,Im_deltaprime_arr)) c ----------------------------------------------------------------------- c close file c ----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Closing netcdf file" - CALL check( nf90_close(ncid) ) + CALL sl_check( nf90_close(ncid) ) c ----------------------------------------------------------------------- c terminate. c ----------------------------------------------------------------------- From 066a3713d8ce1196a5f6b14051ca5d389d257c1b Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 15 Apr 2025 17:18:30 -0400 Subject: [PATCH 033/305] layfac=tau_R, STRIDE effective rs in netcdf --- slayer/slayer.f | 7 +++++-- stride/stride_netcdf.f | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/slayer/slayer.f b/slayer/slayer.f index b8e093bdd..b79564e3c 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -435,8 +435,11 @@ PROGRAM slayer WRITE(*,*)"dels_db() call successful" - eta = 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - tau_r = mu0*rs**2.0/eta ! resistive time scale + tau_r = layfac !!!! CRITICAL + WRITE(*,*)"tau_r = ",tau_r + + !eta = 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + !tau_r = mu0*rs**2.0/eta ! resistive time scale del_s = dels_db * d_beta lar_gamma = (REAL(delta_n_p)/tau_r) * (rs/del_s) diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 6345c8abd..4f89c8e5a 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -61,12 +61,20 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id,prandtl_id + $ shear_id,resm_id,prandtl_id,rs_id,rf_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile REAL(r8) :: resnum,shear,respsi,resm_sing + + REAL(r8), DIMENSION(msing) :: rs_array + REAL(r8), DIMENSION(:), ALLOCATABLE :: rs_full + + TYPE(spline_type) :: sr + REAL(r8), DIMENSION(0:mpsi) :: rhotor + + INTEGER, DIMENSION(msing) :: resm INTEGER :: ising,jsing,m @@ -90,6 +98,29 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) ev = CMPLX(evi, 0.0) et = CMPLX(eti, 0.0) c----------------------------------------------------------------------- +c loop across singular surfaces, evaluate minor radius +c----------------------------------------------------------------------- + WRITE(*,*)"REACHED RS CALCULATION" + rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen + CALL spline_alloc(sr,mpsi,1) + sr%xs = sq%xs + sr%fs(:, 1) = rhotor(:) + CALL spline_fit(sr,"extrap") + + ALLOCATE(rs_full(sq%mx+1)) + + DO i=1,msing + respsi = sing(i)%psifac + WRITE(*,*)"respsi=",respsi + CALL spline_eval(sr,respsi,1) + rs_array(i) = sr%f1(1) + END DO + DO i=1,sq%mx+1 + respsi = sq%xs(i)!*psio + CALL spline_eval(sr,respsi,1) + rs_full(i) = sr%f1(1) + END DO +c----------------------------------------------------------------------- c open files c----------------------------------------------------------------------- IF(debug_flag) PRINT *," - Creating netcdf files" @@ -175,6 +206,10 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid,"r_prime",nf90_int,rp_dim,rp_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ r_dim,pr_id) ) + CALL check( nf90_def_var(ncid,"eff_rs_rational",nf90_double, + $ r_dim,rs_id) ) + CALL check( nf90_def_var(ncid,"eff_rs",nf90_double, + $ p_dim,rf_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, @@ -236,6 +271,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,pr_id, (/(sing(i)%psifac, $ i=1,msing)/)) ) + CALL check( nf90_put_var(ncid,rs_id, rs_array) ) + CALL check( nf90_put_var(ncid,rf_id, rs_full) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) From d6002588edea3931fe5ae84c708c03fac056c995 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 18 Apr 2025 18:30:53 -0400 Subject: [PATCH 034/305] STRIDE rs flux avg, SLAYER fixes --- slayer/delta.f | 7 ++- slayer/slayer.f | 12 ++-- stride/stride_netcdf.f | 135 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 23 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 06332f1a3..50ac741ab 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -172,7 +172,8 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, P_hat = inpr / ind_beta**6 ! P_perp, 0.377 for Pperp_hat benchmark WRITE(*,*)"riccati_del_s inpr = ",inpr - WRITE(*,*)"riccati_del_s inQ = ",inQ + WRITE(*,*)"riccati_del_s Q_e = ",Q_e + WRITE(*,*)"riccati_del_s ind_beta = ",ind_beta alpha = (P_hat/(1+1/tau))**0.5 ! this is actually tau', we need tau W(1) = -alpha*my_q**2 - 0.5 @@ -245,7 +246,9 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) Q_hat = (Q_e*(1+tau)/tau) / D_beta_norm**4 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark P_perp_hat = pr / D_beta_norm**6 ! 0.377 for benchmark P_tor_hat = pr / D_beta_norm**6 ! 1.15 for benchmark - + !WRITE(*,*)"w_der inpr = ",pr + !WRITE(*,*)"w_der Q_e = ",Q_e + !WRITE(*,*)"w_der D_beta_norm = ",D_beta_norm E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) ! P_tor = P_perp F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 diff --git a/slayer/slayer.f b/slayer/slayer.f index b79564e3c..03b54b734 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -426,17 +426,19 @@ PROGRAM slayer D_beta_norm_arr = (/ D_beta_norm /) WRITE(*,*)"D_beta_norm = ",D_beta_norm - WRITE(*,*)"inds = ",inds + !WRITE(*,*)"inds = ",inds - WRITE(*,*)"lar_gamma inQ = ",inQ + !WRITE(*,*)"lar_gamma inQ = ",inQ - dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,d_beta, - $ intau,5.0*D_beta_norm) + dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta, + $ D_beta_norm,intau,5.0*D_beta_norm) - WRITE(*,*)"dels_db() call successful" + !WRITE(*,*)"dels_db() call successful" tau_r = layfac !!!! CRITICAL WRITE(*,*)"tau_r = ",tau_r + WRITE(*,*)"deltaprime = ",REAL(delta_n_p) + WRITE(*,*)"rs = ",rs !eta = 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) !tau_r = mu0*rs**2.0/eta ! resistive time scale diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 4f89c8e5a..5be759f90 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -17,6 +17,7 @@ MODULE stride_netcdf_mod USE dcon_mod USE netcdf + !USE dcon_interface, ONLY: issurfint IMPLICIT NONE CONTAINS @@ -42,6 +43,93 @@ SUBROUTINE check(stat) RETURN END SUBROUTINE check c----------------------------------------------------------------------- +c subprogram 2. issurfint. +c surface integration by simple method. copied from EQUIL +c----------------------------------------------------------------------- + FUNCTION issurfint(func,fs,inpsi,wegt,ave, + $ fsave,psave,jacs,delpsi,inr,ina,first) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + !IMPLICIT NONE + INTEGER, INTENT(IN) :: fs,wegt,ave + REAL(r8), INTENT(IN) :: inpsi + REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func + + LOGICAL, INTENT(INOUT) :: first + INTEGER, INTENT(INOUT) :: fsave + REAL(r8), INTENT(INOUT) :: psave + REAL(r8),DIMENSION(0:),INTENT(INOUT) :: jacs,delpsi,inr,ina + INTEGER :: itheta, ix, iy + REAL(r8) :: issurfint + REAL(r8) :: rfac,ineta,injac,inarea + REAL(r8), DIMENSION(1,2) :: w + REAL(r8), DIMENSION(0:fs) :: z,thetas + REAL(r8), dimension(4) :: rzphi_f, rzphi_fx, rzphi_fy + + issurfint=0 + inarea=0 + ix = 0 + iy = 0 + IF(first .OR. inpsi/=psave .OR. fs/=fsave)THEN + psave = inpsi + fsave = fs + !first = .FALSE. + DO itheta=0,fs + thetas(itheta) = REAL(itheta,r8)/REAL(fs,r8) + ENDDO + DO itheta=0,fs-1 + CALL bicube_eval_external(rzphi, inpsi, thetas(itheta), 1, + $ ix, iy, rzphi_f, rzphi_fx, rzphi_fy) + rfac=SQRT(rzphi_f(1)) + ineta=twopi*(thetas(itheta)+rzphi_f(2)) + ina(itheta)=rfac + inr(itheta)=ro+rfac*COS(ineta) + z(itheta)=zo+rfac*SIN(ineta) + injac=rzphi_f(4) + jacs(itheta)=injac + w(1,1)=(1+rzphi_fy(2))*twopi**2*rfac*inr(itheta)/injac + w(1,2)=-rzphi_fy(1)*pi*inr(itheta)/(rfac*injac) + delpsi(itheta)=SQRT(w(1,1)**2+w(1,2)**2) + ENDDO + ENDIF + + IF (wegt==0) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE IF (wegt==1) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE IF (wegt==2) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs + ENDDO + ELSE IF (wegt==3) THEN + DO itheta=0,fs-1 + issurfint=issurfint+ + $ ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ENDDO + ELSE + STOP "ERROR: issurfint wegt must be in [0,1,2,3]" + ENDIF + + IF (ave==1) THEN + DO itheta=0,fs-1 + inarea=inarea+jacs(itheta)*delpsi(itheta)/fs + ENDDO + issurfint=issurfint/inarea + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END FUNCTION issurfint +c----------------------------------------------------------------------- c subprogram 2. stride_netcdf_out. c Replicate stride.out information in netcdf format. c----------------------------------------------------------------------- @@ -55,7 +143,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, INTENT(IN) :: dp INTEGER, DIMENSION(mpert) :: mvec - INTEGER :: i, ncid, + INTEGER :: i, ncid,mthsur, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, $ f_id, q_id, dv_id, mu_id, di_id, dr_id, ca_id, $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, @@ -73,7 +161,15 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) TYPE(spline_type) :: sr REAL(r8), DIMENSION(0:mpsi) :: rhotor - + + ! MINOR RADIUS INTEGRAL QUANTITIES + LOGICAL :: firstsur + REAL(r8), DIMENSION(0:512) :: my_unitfun + INTEGER :: my_fsave + REAL(r8) :: my_psave + REAL(r8), DIMENSION(:), ALLOCATABLE :: my_jacs,my_delpsi, + $ my_rsurf,my_asurf + REAL(r8) :: my_rfac,my_jac,a_surf INTEGER, DIMENSION(msing) :: resm @@ -100,25 +196,32 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) c----------------------------------------------------------------------- c loop across singular surfaces, evaluate minor radius c----------------------------------------------------------------------- - WRITE(*,*)"REACHED RS CALCULATION" - rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen - CALL spline_alloc(sr,mpsi,1) - sr%xs = sq%xs - sr%fs(:, 1) = rhotor(:) - CALL spline_fit(sr,"extrap") + !WRITE(*,*)"REACHED RS CALCULATION" + !rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen + !CALL spline_alloc(sr,mpsi,1) + !sr%xs = sq%xs + !sr%fs(:, 1) = rhotor(:) + !CALL spline_fit(sr,"extrap") ALLOCATE(rs_full(sq%mx+1)) + mthsur = 512 ! Hardcoded, but this is a default value + ALLOCATE(my_jacs(0:mthsur),my_delpsi(0:mthsur), + $ my_rsurf(0:mthsur),my_asurf(0:mthsur)) DO i=1,msing respsi = sing(i)%psifac - WRITE(*,*)"respsi=",respsi - CALL spline_eval(sr,respsi,1) - rs_array(i) = sr%f1(1) + ! SURFACE INTEGRAL + firstsur = .TRUE. + my_unitfun = 1 + rs_array(i) = issurfint(my_unitfun,mthsur,respsi,3,1, + $ my_fsave,my_psave,my_jacs,my_delpsi,my_rsurf, + $ my_asurf,firstsur) END DO DO i=1,sq%mx+1 - respsi = sq%xs(i)!*psio - CALL spline_eval(sr,respsi,1) - rs_full(i) = sr%f1(1) + respsi = sq%xs(i)!*psio + rs_full(i) = issurfint(my_unitfun,mthsur,respsi,3,1, + $ my_fsave,my_psave,my_jacs,my_delpsi,my_rsurf, + $ my_asurf,firstsur) END DO c----------------------------------------------------------------------- c open files @@ -206,9 +309,9 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid,"r_prime",nf90_int,rp_dim,rp_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ r_dim,pr_id) ) - CALL check( nf90_def_var(ncid,"eff_rs_rational",nf90_double, + CALL check( nf90_def_var(ncid,"rs_rational",nf90_double, $ r_dim,rs_id) ) - CALL check( nf90_def_var(ncid,"eff_rs",nf90_double, + CALL check( nf90_def_var(ncid,"flux_avg_rs",nf90_double, $ p_dim,rf_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) From 610914fd2d598b2e0fb64b87f2edba2d31cc1cbf Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 22 Apr 2025 16:16:54 -0400 Subject: [PATCH 035/305] draft of rfitzp growth rate solve --- input/slayer.in | 2 + slayer/delta.f | 167 +++++++++++++++++++++++++++++ slayer/gslayer.f | 238 ++++++++++++++++++++++++++++++++++++++++- slayer/sglobal.f | 5 +- slayer/slayer.f | 126 +++++++++++++++++++--- slayer/slayer_netcdf.f | 24 ++++- 6 files changed, 536 insertions(+), 26 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 103e69a0c..b1de1d750 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -30,6 +30,7 @@ intau=1.0 ! ion temperature divided by electron temperature Q0=1.5 ! unnecessary placeholder variable for inQ? delta_n_p=(5.0,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). + ingam=(0.1,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). / &SLAYER_CONTROL @@ -61,6 +62,7 @@ stability_flag=f ! calculate delta dependence on complex Q lar_gamma_eq_flag=f ! Calculate cylindrical growthrates for each rational surface of input equilibrium lar_gamma_flag=f ! Calculate cylindrical growthrate for namelist parameters + fitz_gamma_flag=f ! Calculate Fitzpatrick layer model stabscan_eq_flag=f ! Generate Re(Q),Im(Q) scans for each rational surface of input equilibrium stabscan_flag=f ! Generate Re(Q),Im(Q) scan for namelist parameters br_th_flag=f ! Calculate br_th diff --git a/slayer/delta.f b/slayer/delta.f index 50ac741ab..30d3e6b9a 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -259,6 +259,173 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) END SUBROUTINE w_der_del_s c c +c +c----------------------------------------------------------------------- +c calculate delta based on Fitzpatrick delta formulation. +c----------------------------------------------------------------------- + FUNCTION riccati_f(tmp_g,inQ_e,inQ_i,inpr,inD_beta_norm, + $ intau,inx) + REAL(r8),INTENT(IN) :: inQ_e,inQ_i,inpr,inD_beta_norm + REAL(r8),INTENT(IN) :: intau + COMPLEX(r8), INTENT(IN) :: tmp_g + REAL(r8),INTENT(IN),OPTIONAL :: inx + COMPLEX(r8) :: riccati_f + + INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf + INTEGER :: ml = 0, mu = 0, nrpd = 1 + + REAL(r8) :: xintv,x,xout,rtol,jac,xmin,my_p,P_hat,alpha,bk + COMPLEX(r8) :: ak,ck_1,ck_2,ck,xk,W_bound + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dWdp,y,dy + + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork + REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork + + !IF(present(iinQ)) Q=inQ+ifac*iinQ + Q_e=inQ_e + Q_i=inQ_i + pr=inpr + D_beta_norm=inD_beta_norm + tau=intau + + neq = 2 + itol = 2 + rtol = 1e-10 !changed to 1e-08 !1e-7*pr**0.4 ! !1e-7 at front 1e-6 !e-4 + ALLOCATE(atol(neq),W(1),dWdp(1)) + atol(:) = 1e-10!*pr**0.4 ! changed to 1e-08 1e-8 !e-4 + itask = 2 + istate = 1 + iopt = 0 + mf = 21!21 IS STIFF WITH USER-SPECIFIED JACOBIAN, 10 iS NON STIFF + liw = 20*2 + lrw = 22+9*neq+neq**2 !just (22+16*neq) for mf=10 + ALLOCATE(iwork(liw+neq),rwork(lrw)) ! just iwork(liw) for mf=10 + +! MXSTEP? + iopt = 1 + iwork=0 + iwork(6)=50000 !5000 ! maximum # of steps per call, e.g. 50000 + rwork=0 +! x=10.0*(1.0+log10(Q/pr)) + + !!!!!!!! + !IF(present(inx)) my_p=inx!10.0 ! "starting backwards integration at large q" + !!!!!!!! + my_p=6.0 + xmin=1e-6 + xout=xmin + + ! SOLVE FOR W BOUNDARY CONDITION + IF (D_beta_norm > (pr**(1.0/6.0))) THEN + ak = -(g_tmp + ifac*Q_e) + bk = pr/(2.0*(D_beta_norm**2.0)) + + ck_1 = 2.0*(g_tmp + ifac*Q_i)/pr + ck_2 = (pr + (g_tmp + + $ ifac*Q_i)*(D_beta_norm**2.0))/(2.0*pr*(D_beta_norm**2.0)) + ck = (pr/(2.0*(D_beta_norm**2.0)))*(1 + ck_1 - ck_2) + + xk = (ck - SQRT(bk)*(1 - + $ SQRT(bk)*ak))/(2.0*SQRT(bk)) + + W_bound = xk - SQRT(bk)*my_p + ELSE + ak = -(g_tmp + ifac*Q_e) + bk = pr + ck = -ifac*(Q_e - Q_i) + (g_tmp + ifac*Q_i) + xk = (ak*bk - ck)/(2.0*SQRT(bk)) + + W_bound = -1 + xk*my_p - SQRT(bk)*(my_p**3.0) + END IF + + W(1) = W_bound + + IF (riccati_out) THEN + istep = 1 + itask = 2 + OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn)//'.bin',STATUS='UNKNOWN', + $ POSITION='REWIND',FORM='UNFORMATTED') + + OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn)//'.out',STATUS='UNKNOWN') + WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" + DO WHILE (my_p>xout) + istep=istep+1 + CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) + WRITE(bin_unit)REAL(my_p,4),REAL(REAL(W),4),REAL(AIMAG(W),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')my_p,REAL(W),AIMAG(W) + ENDDO + CLOSE(bin_unit) + CLOSE(out2_unit) + ELSE + istep = 1 + itask = 1 + CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) + + ENDIF + + ! w=0 when Q=Q_e. Why? + + CALL w_der_f(neq,my_p,W,dWdp) + WRITE(*,*)"riccati Q_e = ",Q_e + WRITE(*,*)"riccati Q_i = ",Q_i + WRITE(*,*)"riccati pr = ",pr + WRITE(*,*)"riccati D_beta_norm = ",D_beta_norm + WRITE(*,*)"riccati g_tmp = ",g_tmp + + ! delta = np.pi * p_min / (W_value + 1) + riccati_f = pi / dWdp(1) + DEALLOCATE(atol,W,dWdp,iwork,rwork) + + END FUNCTION riccati_f +c----------------------------------------------------------------------- +c jacobian for riccati_del_s() +c------------------------------------------- ---------------------------- + SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: my_p + COMPLEX(r8) :: fA_p + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + + fA_p = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + + $ ifac*Q_e + (my_p**2.0)) + + pd(1,1) = (-fA_p/my_p) - (2.0*W(1))/my_p + END SUBROUTINE jac_f +c----------------------------------------------------------------------- +c riccati integration. +c----------------------------------------------------------------------- + SUBROUTINE w_der_f(neq,my_p,W,dWdp) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: my_p + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dWdp + COMPLEX(r8) :: fA, fA_prime, fB, fC + + !WRITE(*,*)"w_der g_tmp = ",g_tmp + + ! Evaluate coefficients at the current p + fA = (my_p**2)/(g_tmp + ifac*Q_e + (my_p**2.0)) + fA_prime = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + + $ ifac*Q_e + (my_p**2.0)) + fB = g_tmp*(g_tmp + ifac*Q_i) + 2.0*(g_tmp + + $ ifac*Q_i)*pr*(my_p**2.0) + (pr**2.0)*(my_p**4.0) + fC = g_tmp + ifac*Q_e + ( pr + (g_tmp + + $ ifac*Q_i)*(D_beta_norm**2.0))*(my_p**2.0) + + $ 2.0*pr*(D_beta_norm**2.0)*(my_p**4.0) + + dWdp(1) = -(fA_prime/my_p)*W(1) - (W(1)**2.0)/my_p + + $ (fB/(fA*fC))*(my_p**3.0) + + RETURN + END SUBROUTINE w_der_f +c +c c SUBROUTINE w_der(neq,x,y,dy) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 9346fbdc9..13a84384c 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -182,14 +182,17 @@ END SUBROUTINE gpec_slayer c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ fitz_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,lar_gamma_arr) + $ Im_deltaprime_arr,dels_db_arr,lu_arr,delta_arr, + $ lar_gamma_arr) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag + $ stabscan_eq_flag,stabscan_flag,br_th_flag, + $ fitz_gamma_flag INTEGER, INTENT(IN), DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, @@ -198,7 +201,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ D_beta_norm_arr,lu_arr COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, - $ lar_gamma_arr + $ lar_gamma_arr,delta_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs TYPE(result_type) :: results(8) @@ -211,10 +214,12 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, iinQs = (/0.0/) CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag, + $ stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, + $ ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) END SUBROUTINE output_lar_gamma @@ -376,4 +381,227 @@ SUBROUTINE grow_array(arr, old_size, new_size) temp(1:old_size) = arr(1:old_size) CALL move_alloc(temp, arr) END SUBROUTINE grow_array +c +c +c + subroutine NewtonRoot(g_r, g_i, verbose) + REAL(r8), intent(inout) :: g_r, g_i + integer, intent(in) :: verbose + + REAL(r8) :: F1, F2, J11, J12, J21, J22, det, iJ11, iJ12 + REAL(r8) :: iJ21, iJ22, dx1, dx2, dx, f, g1, g2, lambda, + $ Residual + integer :: iter + + REAL(r8), parameter :: Eps = 1.0e-12 ! Tolerance parameter + REAL(r8), parameter :: Smin = 1.0e-07 ! Min step size + REAL(r8), parameter :: Smax = 0.1 ! Max step size + integer, parameter :: MaxIter = 100 ! Maximum iterations + + iter = 0 + do + call NewtonFunction(g_r, g_i, F1, F2) + + call NewtonJacobian(g_r, g_i, J11, J12, J21, J22) + + det = J11 * J22 - J12 * J21 + + iJ11 = J22 / det + iJ12 = -J12 / det + iJ21 = -J21 / det + iJ22 = J11 / det + + dx1 = -(iJ11 * F1 + iJ12 * F2) + dx2 = -(iJ21 * F1 + iJ22 * F2) + + dx = sqrt(dx1*dx1 + dx2*dx2) + + f = 0.5 * (F1*F1 + F2*F2) + + g1 = F1*J11 + F2*J21 + g2 = F1*J12 + F2*J22 + + call NewtonBackTrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda) + + call NewtonFunction(g_r, g_i, F1, F2) + + Residual = sqrt(F1*F1 + F2*F2) + + if (verbose .ne. 0) then + write(*, '("x = (", ES10.3, ", ", ES10.3, ") F = (", + & ES10.3, ", ", ES10.3, ") Residual = ", ES10.3, + & " lambda = ", ES10.3, " dx = ", ES10.3)') + & g_r, g_i, F1, F2, Residual, lambda, dx + endif + + iter = iter + 1 + + if (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit + enddo + + end subroutine NewtonRoot + +!----------------------------------------------------------------------- +! Function to backtrack along Newton step in order to minimize f = (F1*F1 + F2*F2) /2 +! +! Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies in C (Cambridge, 1992), Sect. 9.7 +!----------------------------------------------------------------------- + subroutine NewtonBackTrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, + & lambda) + REAL(r8), intent(inout) :: g_r, g_i, dx, lambda + REAL(r8), intent(inout) :: dx1, dx2, f, g1, g2 + + REAL(r8) :: x1old, x2old, dxold, fold, slope, F1, F2 + REAL(r8) :: tmplam, rhs1, rhs2, a, b, disc, lambd2, mf2 + integer :: i + + REAL(r8), parameter :: Smin = 1.0d-10 ! Min step size + REAL(r8), parameter :: Smax = 1.0d0 ! Max step size + REAL(r8), parameter :: alpha = 1.0d-4 ! Line search parameter + integer, parameter :: Maxiter = 100 ! Maximum iterations + + x1old = g_r + x2old = g_i + dxold = dx + fold = f + + if (dxold > Smax) then + dx1 = dx1 * Smax / dxold + dx2 = dx2 * Smax / dxold + dxold = Smax + endif + + slope = g1*dx1 + g2*dx2 + + if (slope >= 0.0d0) then + write(*, *) "NewtonBackTrack: Error - roundoff problem" + endif + + lambda = 1.0d0 + + do i = 0, Maxiter + g_r = x1old + lambda * dx1 + g_i = x2old + lambda * dx2 + + call NewtonFunction(g_r, g_i, F1, F2) + + f = 0.5 * (F1*F1 + F2*F2) + + if (f <= fold + alpha * lambda * slope .or. + & lambda * dxold < Smin) then + dx = lambda * dxold + return + else + if (lambda == 1.0d0) then + tmplam = -slope / 2.0d0 / (f - fold - slope) + else + rhs1 = f - fold - lambda * slope + rhs2 = mf2 - fold - lambd2 * slope + + a = (rhs1/lambda/lambda - rhs2/lambd2/lambd2) + & / (lambda - lambd2) + b = (-lambd2 * rhs1/lambda/lambda + lambda * + & rhs2/lambd2/lambd2) / (lambda - lambd2) + + if (a == 0.0d0) then + tmplam = -slope / 2.0d0 / b + else + disc = b*b - 3.0d0 * a * slope + + if (disc < 0.0d0) then + tmplam = 0.5d0 * lambda + else if (b <= 0.0d0) then + tmplam = (-b + sqrt(disc)) / 3.0d0 / a + else + tmplam = -slope / (b + sqrt(disc)) + endif + endif + + if (tmplam > 0.5d0 * lambda) then + tmplam = 0.5d0 * lambda + endif + endif + endif + + lambd2 = lambda + mf2 = f + lambda = max(tmplam, 0.1d0*lambda) + enddo + + dx = lambda * dxold + + end subroutine NewtonBackTrack + +!----------------------------------------------------------------------- +! Function to calculate Jacobian matrix for Newton-Raphson root finding +!----------------------------------------------------------------------- + subroutine NewtonJacobian(g_r, g_i, J11, J12, J21, J22) + REAL(r8), intent(in) :: g_r, g_i + REAL(r8), intent(out) :: J11, J12, J21, J22 + + REAL(r8) :: F1m, F2m, F1p, F2p + REAL(r8), parameter :: dS = 1.0e-06 ! Step size for derivatives + + call NewtonFunction(g_r - dS, g_i, F1m, F2m) + call NewtonFunction(g_r + dS, g_i, F1p, F2p) + + J11 = (F1p - F1m) / 2.0d0 / dS + J21 = (F2p - F2m) / 2.0d0 / dS + + call NewtonFunction(g_r, g_i - dS, F1m, F2m) + call NewtonFunction(g_r, g_i + dS, F1p, F2p) + + J12 = (F1p - F1m) / 2.0d0 / dS + J22 = (F2p - F2m) / 2.0d0 / dS + + end subroutine NewtonJacobian + +!----------------------------------------------------------------------- +! Function to return maximum of two values +!----------------------------------------------------------------------- + function Fmax(f1, f2) result(res) + double precision, intent(in) :: f1, f2 + double precision :: res + + if (f1 > f2) then + res = f1 + else + res = f2 + endif + + end function Fmax + +!----------------------------------------------------------------------- +! Function to return minimum of two values +!----------------------------------------------------------------------- + function Fmin(f1, f2) result(res) + REAL(r8), intent(in) :: f1, f2 + REAL(r8) :: res + + if (f1 < f2) then + res = f1 + else + res = f2 + endif + + end function Fmin + +!----------------------------------------------------------------------- +! Function to calculate target functions for Newton-Raphson root finding +!----------------------------------------------------------------------- + subroutine NewtonFunction(g_r, g_i, F1, F2) + REAL(r8), intent(in) :: g_r, g_i + REAL(r8), intent(out) :: F1, F2 + + ! These would be module-level variables in the original code + COMPLEX(r8) :: Deltas + REAL(r8) :: Delta + + g_tmp = CMPLX(g_r,g_i) + Deltas=riccati_f(g_tmp,Q_e,Q_i,pr,D_beta_norm,tau) + + F1 = REAL(Deltas) - deltaprim + F2 = dimag(Deltas) + + end subroutine NewtonFunction END MODULE gslayer_mod diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 00b751f3f..74e8a9589 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -10,8 +10,9 @@ MODULE sglobal_mod REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, $ d_beta,D_beta_norm,gamma_fac REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, - $ delta_n,layfac,Qconv - COMPLEX(r8) :: Q + $ delta_n,layfac,Qconv,deltaprim, + $ g_r, g_i + COMPLEX(r8) :: Q,g_tmp REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6022e-19, diff --git a/slayer/slayer.f b/slayer/slayer.f index 03b54b734..00353156e 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -34,14 +34,15 @@ PROGRAM slayer $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,stabscan_eq_flag,stabscan_flag, - $ lar_gamma_eq_flag,lar_gamma_flag, + $ lar_gamma_eq_flag,lar_gamma_flag,fitz_gamma_flag, $ br_th_flag,compress_deltas REAL(r8) :: n_e,t_e,t_i,omega,omega0, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,tau_r,d_b - COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma + REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,tau_r,d_b,Residual + COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, + $ tmp_gamma,ingam REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower, @@ -74,7 +75,8 @@ PROGRAM slayer REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, - $ dels_db_arr,lar_gamma_arr + $ dels_db_arr,lar_gamma_arr, + $ delta_arr COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas TYPE(result_type) :: results(8) ! Assuming max 8 rational surfaces @@ -86,7 +88,7 @@ PROGRAM slayer $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, $ inQ_i,inpr,inpr_prof,inpe,inc_beta,inds, - $ intau,inlu,Q0,delta_n_p + $ intau,inlu,Q0,delta_n_p,ingam NAMELIST/slayer_control/inum,jnum,knum,ReQ_num,ImQ_num, $ scan_radius,QPscan_flag,QPscan2_flag, $ QPescan_flag,QDscan2_flag,Qbscan_flag,Qscan_flag, @@ -94,8 +96,8 @@ PROGRAM slayer $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ stability_flag,lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,compress_deltas, - $ bal_flag + $ fitz_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ compress_deltas,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -130,6 +132,7 @@ PROGRAM slayer inlu=1e8 Q0=4.0 delta_n_p=(1e-2,1e-2) + ingam=(0.0,1e-2) inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. @@ -172,6 +175,7 @@ PROGRAM slayer stabscan_flag=.FALSE. stabscan_eq_flag=.FALSE. lar_gamma_flag=.FALSE. + fitz_gamma_flag=.FALSE. lar_gamma_eq_flag=.FALSE. br_th_flag=.FALSE. compress_deltas=.FALSE. @@ -332,7 +336,7 @@ PROGRAM slayer n_k = SIZE(qval_arr) - ALLOCATE(lar_gamma_arr(n_k),dels_db_arr(n_k)) + ALLOCATE(lar_gamma_arr(n_k),dels_db_arr(n_k),delta_arr(n_k)) DO k=1,n_k WRITE(*,*) "Calculating growth rate on q=", qval_arr(k), @@ -369,11 +373,12 @@ PROGRAM slayer br_th = 0.0 CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ fitz_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,lar_gamma_arr) + $ lu_arr,delta_arr,lar_gamma_arr) stop ENDIF @@ -457,11 +462,100 @@ PROGRAM slayer br_th = 0.0 CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ fitz_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,lar_gamma_arr) + $ lu_arr,delta_arr,lar_gamma_arr) + + stop + ENDIF +c----------------------------------------------------------------------- +c LAR (cylindrical) growthrates via restive layer thickness +c----------------------------------------------------------------------- + IF (fitz_gamma_flag) THEN + WRITE(*,*)"Calculating Fitzpatrick inner delta" + + ! CALL params(n_e,t_e,t_i,omega, + !$ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + + + IF (inQ > 0.0) THEN + Q = inQ ! NAMELIST + END IF + IF (inQ_e > 0.0) THEN + Q_e = inQ_e ! NAMELIST + END IF + IF (inQ_i > 0.0) THEN + Q_i = inQ_i ! NAMELIST + END IF + IF (inpr > 0.0) THEN + pr = inpr ! NAMELIST + END IF + IF (intau > 0.0) THEN + tau = intau ! NAMELIST + END IF + IF (inds > 0.0) THEN + D_beta_norm = inds ! NAMELIST + END IF + + WRITE(*,*)"intau=",tau + WRITE(*,*)"inQ=",Q + WRITE(*,*)"inQ_e=",Q_e + WRITE(*,*)"inQ_i=",Q_i + WRITE(*,*)"Prantdl number=",pr + WRITE(*,*)"inQ_e=",inQ_e + WRITE(*,*)"D_beta_norm=",D_beta_norm + WRITE(*,*)"inc_beta=",inc_beta + + qval_arr = (/ qval /) + omegas_arr = (/ omega /) + inQ_arr = (/ inQ /) + inQ_e_arr = (/ inQ_e /) + inQ_i_arr = (/ inQ_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ 0.0 /) + Im_deltaprime_arr = (/ 0.0 /) + inpr_arr = (/ inpr /) + D_beta_norm_arr = (/ D_beta_norm /) + + ! INPUT GAMMA = delta_n_p + + deltaprim = REAL(delta_n_p) + g_tmp = ingam + WRITE(*,*)"g_tmp = ",g_tmp + + delta=riccati_f(g_tmp,Q_e,Q_i,pr, + $ D_beta_norm,tau) + + WRITE(*,*)"Fitz delta = ",delta + + g_r = 0.0 + g_i = -Q_e + CALL NewtonRoot(g_r, g_i, 1) + + lar_gamma = g_r + WRITE(*,*)"Fitz gamma success! growth rate = ",g_r + + ind_beta_arr = (/ 0. /) + dels_db_arr = (/ 0. /) ! USING DELS_DB_ARR FOR DELTA + delta_arr = (/ delta /) ! USING DELS_DB_ARR FOR DELTA + lar_gamma_arr = (/ g_r /) + + !WRITE(*,*)"dels_db=",dels_db + !WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 + !WRITE(*,*)"growth rate=",lar_gamma + + br_th = 0.0 + + CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, + $ fitz_gamma_flag, + $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, + $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, + $ D_beta_norm_arr,inpr_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, + $ lu_arr,delta_arr,lar_gamma_arr) stop ENDIF @@ -516,10 +610,12 @@ PROGRAM slayer WRITE(*,*)"qval_arr = ",qval_arr CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ lar_gamma_flag, + $ fitz_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, + $ ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, c $ stabscan_eq_flag,stabscan_flag,br_th_flag) @@ -565,10 +661,12 @@ PROGRAM slayer br_th = 0.0 CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag, + $ stabscan_flag,br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,ind_beta_arr, + $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, + $ ind_beta_arr, $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) stop ENDIF diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 152a0093f..589e66fcf 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -49,10 +49,11 @@ END SUBROUTINE sl_check c declarations. c ----------------------------------------------------------------------- SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, - $ lar_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, + $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag,stabscan_flag, + $ br_th_flag, $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, + $ delta_arr,lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, $ inQs,iinQs,results) ! ds = D_beta_norm for lar growth rate routines @@ -62,7 +63,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, INTEGER, INTENT(IN) :: msing LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag + $ stabscan_eq_flag,stabscan_flag,br_th_flag,fitz_gamma_flag INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr REAL(r8), DIMENSION(msing) :: gamma_arr @@ -75,7 +76,7 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, REAL(r8) :: br_th COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dels_db_arr, - $ lar_gamma_arr + $ lar_gamma_arr,delta_arr TYPE(result_type), INTENT(IN) :: results(8) @@ -197,6 +198,12 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL sl_check( nf90_def_var(ncid,"D_beta_norm",nf90_double, $ qsing_dim,indnorm_id) ) CALL sl_check( nf90_def_var(ncid,"growthrate_estimate", + $ nf90_double,(/qsing_dim,i_dim/),lar_gamma_id) ) + END IF + IF (fitz_gamma_flag) THEN + CALL sl_check( nf90_def_var(ncid,"delta_hat",nf90_double, + $ (/qsing_dim,i_dim/),dels_db_id) ) + CALL sl_check( nf90_def_var(ncid,"growthrate", $ nf90_double,(/qsing_dim,i_dim/),lar_gamma_id) ) END IF ! end definitions @@ -246,7 +253,14 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL sl_check( nf90_put_var(ncid,lar_gamma_id, $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), $ (/msing,2/)))) - + END IF + IF (fitz_gamma_flag) THEN + CALL sl_check( nf90_put_var(ncid,dels_db_id, + $ RESHAPE((/REAL(delta_arr),AIMAG(delta_arr)/), + $ (/msing,2/)))) + CALL sl_check( nf90_put_var(ncid,lar_gamma_id, + $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), + $ (/msing,2/)))) END IF c ----------------------------------------------------------------------- From 64d45d3771b700dad9476253982b53afe7709be9 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 6 May 2025 13:28:34 -0400 Subject: [PATCH 036/305] old stabscan architecture backup --- slayer/delta.f | 12 ++++---- slayer/gslayer.f | 5 ++-- slayer/slayer.f | 64 +++++++++++++++++++++++++++++++++++++----- slayer/slayer_netcdf.f | 29 +++++++++++++++++++ 4 files changed, 95 insertions(+), 15 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 30d3e6b9a..1989a5c62 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -370,13 +370,13 @@ FUNCTION riccati_f(tmp_g,inQ_e,inQ_i,inpr,inD_beta_norm, ! w=0 when Q=Q_e. Why? CALL w_der_f(neq,my_p,W,dWdp) - WRITE(*,*)"riccati Q_e = ",Q_e - WRITE(*,*)"riccati Q_i = ",Q_i - WRITE(*,*)"riccati pr = ",pr - WRITE(*,*)"riccati D_beta_norm = ",D_beta_norm - WRITE(*,*)"riccati g_tmp = ",g_tmp + !WRITE(*,*)"riccati Q_e = ",Q_e + !WRITE(*,*)"riccati Q_i = ",Q_i + !WRITE(*,*)"riccati pr = ",pr + !WRITE(*,*)"riccati D_beta_norm = ",D_beta_norm + !WRITE(*,*)"riccati g_tmp = ",g_tmp - ! delta = np.pi * p_min / (W_value + 1) + !riccati_f = pi * my_p / (dWdp(1) + 1) riccati_f = pi / dWdp(1) DEALLOCATE(atol,W,dWdp,iwork,rwork) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 13a84384c..7d6b047e9 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -187,7 +187,7 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, $ Im_deltaprime_arr,dels_db_arr,lu_arr,delta_arr, - $ lar_gamma_arr) + $ lar_gamma_arr,results) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, @@ -204,7 +204,8 @@ SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ lar_gamma_arr,delta_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs - TYPE(result_type) :: results(8) + !TYPE(result_type) :: results(8) + TYPE(result_type), INTENT(IN) :: results(8) REAL(r8) :: br_th = 0 diff --git a/slayer/slayer.f b/slayer/slayer.f index 00353156e..8a8196082 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -24,8 +24,8 @@ PROGRAM slayer IMPLICIT NONE CHARACTER(512) :: infile,ncfile - INTEGER :: i,j,k,inum,jnum,knum,inn, - $ ReQ_num,ImQ_num,n_k,scan_radius + INTEGER :: i,j,k,inum,jnum,knum,inn,count, + $ ReQ_num,ImQ_num,n_k,scan_radius,max_points INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -45,8 +45,8 @@ PROGRAM slayer $ tmp_gamma,ingam REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, - $ kpower, - $ Qratio + $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, + $ delta_imag,Qratio INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns @@ -378,7 +378,7 @@ PROGRAM slayer $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr) + $ lu_arr,delta_arr,lar_gamma_arr,results) stop ENDIF @@ -467,7 +467,7 @@ PROGRAM slayer $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr) + $ lu_arr,delta_arr,lar_gamma_arr,results) stop ENDIF @@ -549,13 +549,63 @@ PROGRAM slayer br_th = 0.0 + max_points = 50*50 + scan_radius = 1.5 + + ! Calculate step sizes +c ALLOCATE(results(1)%inQs(max_points), +c $ results(1)%iinQs(max_points)) +c ALLOCATE(results(1)%Re_deltas(max_points), +c $ results(1)%Im_deltas(max_points)) + ing_step = (2.0 * scan_radius) / (200 - 1) + count = 0 + + ALLOCATE(inQs(1:201),iinQs(1:200)) + ALLOCATE(deltas(1:201,1:200)) + + DO i = 1, 201 + DO j = 1, 200 + ing_coarse = -scan_radius + (i - 1) * ing_step + iing_coarse = -scan_radius + (j - 1) * ing_step + ! Evaluate riccati function + g_tmp = CMPLX(ing_coarse,iing_coarse) + delta=riccati_f(g_tmp, + $ Q_e,Q_i,pr,D_beta_norm,tau) + !delta_real = REAL(delta) + !delta_imag = AIMAG(delta) + + !count = count + 1 + !results(1)%inQs(count) = ing_coarse + !results(1)%iinQs(count) = iing_coarse + !results(1)%Re_deltas(count) = delta_real + !results(1)%Im_deltas(count) = delta_imag + inQs(i) = ing_coarse + iinQs(j) = iing_coarse + deltas(i,j) = delta + + END DO + END DO + + OPEN(UNIT=out_unit,FILE="slayer_stability_n1.out", + $ STATUS="UNKNOWN") + WRITE(out_unit,'(1x,4(a17))') "RE(Q)", + $ "IM(Q)","RE(delta)","IM(delta)" + DO i=1,201 + DO j=1,200 + WRITE(out_unit,'(1x,4(es17.8e3))') + $ inQs(i),iinQs(j), + $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) + ENDDO + ENDDO + CLOSE(out_unit) + CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, $ fitz_gamma_flag, $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, $ D_beta_norm_arr,inpr_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr) + $ lu_arr,delta_arr,lar_gamma_arr,results) stop ENDIF diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 589e66fcf..1b61d38f0 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -178,6 +178,17 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ [point_dimid, qsing_dim], varids(4)) ) END IF +c IF (fitz_gamma_flag) THEN +c CALL sl_check( nf90_def_var(ncid, "Re_Qs", nf90_double, +c $ [point_dimid, qsing_dim], varids(1)) ) +c CALL sl_check( nf90_def_var(ncid, "Im_Qs", nf90_double, +c $ [point_dimid, qsing_dim], varids(2)) ) +c CALL sl_check( nf90_def_var(ncid,"Re_deltas",nf90_double, +c $ [point_dimid, qsing_dim], varids(3)) ) +c CALL sl_check( nf90_def_var(ncid,"Im_deltas",nf90_double, +c $ [point_dimid, qsing_dim], varids(4)) ) +c END IF + IF (br_th_flag) THEN CALL sl_check( nf90_def_var(ncid,"br_th",nf90_double, $ qsing_dim,br_th_id) ) @@ -240,6 +251,24 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, END DO END IF +c IF (fitz_gamma_flag) THEN +c DO run = 1, msing +c run = 1 +c CALL sl_check( nf90_put_var(ncid,varids(1), +c $ results(run)%inQs,start=[1, run], +c $ count=[results(run)%count, 1]) ) +c CALL sl_check( nf90_put_var(ncid,varids(2), +c $ results(run)%iinQs,start=[1, run], +c $ count=[results(run)%count, 1])) +c CALL sl_check( nf90_put_var(ncid, varids(3), +c $ results(run)%Re_deltas, start=[1, run], +c $ count=[results(run)%count, 1]) ) +c CALL sl_check( nf90_put_var(ncid, varids(4), +c $ results(run)%Im_deltas, start=[1, run], +c $ count=[results(run)%count, 1]) ) +c END DO +c END IF + IF (br_th_flag) THEN CALL sl_check( nf90_put_var(ncid,br_th_id, (/ br_th /))) END IF From 096a60df56f87fdf020c79803982be36e6c4be25 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 9 May 2025 17:39:33 -0400 Subject: [PATCH 037/305] new architecture! toggle fitz vs JKP --- input/slayer.in | 73 ++-- slayer/delta.f | 72 ++-- slayer/gslayer.f | 329 +++++++++--------- slayer/layerinputs.f | 205 ++++++----- slayer/params.f | 68 +++- slayer/sglobal.f | 13 +- slayer/slayer.f | 761 ++++++++++++++++------------------------- slayer/slayer_netcdf.f | 404 ++++------------------ stride/stride_netcdf.f | 142 +------- 9 files changed, 776 insertions(+), 1291 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index b1de1d750..1d8df39d0 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -1,45 +1,53 @@ &SLAYER_INPUT - ! Default values taken from R. Fitzpatrick 'High Field' case in "Tearing Mode Dynamics in Tokamak Plasmas", 2023 input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table - infile='' ! Path to ascii table of profile quantities read when using input_flag - ncfile='' ! Path to netCDF file of STRIDE outputs + infile='' ! Path to ascii table of profile quantities read when using input_flag + ncfile='' ! Path to netCDF file of STRIDE outputs params_flag=f ! calculates normalized parameters from profile quantities + ! >>> + ! >>> Input rational surface quantities + ! >>> mm=2 ! poloidal mode number nn=1 ! toroidal mode number - n_e=5.10E+020 ! electron density [m^-3] - t_e=7000.0 ! electron temperature [eV] - t_i=7000.0 ! ion temperature [eV] - sval=1.34 ! magnetic shear at the layer - bt=12.0 ! toroidal field [T] - rs=0.53 ! minor radius of resonant surface [m] - R0=3.2 ! major radius of magnetic axis [m] - omega=5.45E+03 ! ExB frequency [rad/s] + n_e=5.50E+19 ! electron density [m^-3] + t_e=1000.0 ! electron temperature [eV] + t_i=1000.0 ! ion temperature [eV] + sval=1.5 ! magnetic shear at the layer + bt=2.0 ! toroidal field [T] + rs=0.5 ! minor radius of resonant surface [m] + R0=1.67 ! major radius of magnetic axis [m] + omega=5.00E+03 ! ExB frequency [Hz] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface - mu_i=2.0 ! ion mass ratio to proton ?? + mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma zeff=2.0 ! plasma Z_effective - inpr=500.0 ! Prantdl number - inpr_prof=1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 ! Prandtl number profile, set < 0 to only use inpr instead - inpe=0.0 ! Electron viscosity - inQ=1.5 ! normalized ExB frequency - inQ_e=2.0 ! normalized electron diamagnetic frequency - inQ_i=-2.0 ! normalized ion diamagnetic frequency - inc_beta=0.128 ! dimensionless measure of the plasma pressure - inds=2.26 ! normalized ion sound radius. NOTE: set inds = 0 for internal D_norm caltulation - intau=1.0 ! ion temperature divided by electron temperature - Q0=1.5 ! unnecessary placeholder variable for inQ? - delta_n_p=(5.0,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). - ingam=(0.1,0.01) ! input Delta', OR delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). + dr_val=-0.1 ! GGJ resistive interchange criterion D_R + chi_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface perpendicular ion momentum diffusivity OR perpendicular energy diffusivity profile + ! >>> + ! >>> Input normalized layer parameters. If read_eq=f, each will individually override internal calcuation using above inputs + ! >>> + inpr=0.0 ! Prantdl number. Set /= 0.0 to override internal calculation + inpe=0.0 ! electron viscosity + inQ=1.0 ! normalized ExB frequency. TO DO: update for mode rotation frequency calculation! + inQ_e=0.0 ! normalized electron diamagnetic frequency. Set /= 0.0 to override internal calculation + inQ_i=0.0 ! normalized ion diamagnetic frequency. Set /= 0.0 to override internal calculation + inc_beta=0.0 ! dimensionless measure of the plasma pressure. Set /= 0.0 to override internal calculation + inds=0.0 ! normalized ion sound radius, or normalized ion skin depth. Set /= 0.0 to override internal calculation + intau=1.0 ! ratio of ion to electron temperature + Q0=1.0 ! unnecessary placeholder variable for inQ? + delta_prime=(0.0,0.0) ! input Delta' or (Delta' - Delta_crit) for growth rate calculation, set /= 0.0 to override internal calculation + delta_n_p=(0.01,0.01) ! delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). + ingamma=(0.0,0.0) ! input (Re(Q),Im(Q)) to calculate inner layer Delta. Set /= 0.0 to override internal root finding / &SLAYER_CONTROL inum=400 ! resolution to find error field thresholds. jnum=100 ! resolution for 2d scan along with Q,omega. - knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=300 ! resolution for stability scan along Re(Q) axis - ImQ_num=300 ! resolution for stability scan along Im(Q) axis - scan_radius=3 ! stability scan width in Q + knum=100 ! resolution for 2d scan along with the other. + Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes + scan_radius=2.0 ! stability scan width for Re(Q),Im(Q) scan + read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 + fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space QPescan_flag=f ! scan (Q,Pe) space for delta and torque. @@ -59,14 +67,11 @@ ascii_flag=f ! writes results to ascii files bin_flag=f ! writes results to binary files netcdf_flag=f ! writes results to netcdf files + est_gamma_flag=t ! Calculate and output growth rate estimate + match_gamma_flag=t ! Find Delta_hat = Delta_prime match to solve for growth rate stability_flag=f ! calculate delta dependence on complex Q - lar_gamma_eq_flag=f ! Calculate cylindrical growthrates for each rational surface of input equilibrium - lar_gamma_flag=f ! Calculate cylindrical growthrate for namelist parameters - fitz_gamma_flag=f ! Calculate Fitzpatrick layer model - stabscan_eq_flag=f ! Generate Re(Q),Im(Q) scans for each rational surface of input equilibrium - stabscan_flag=f ! Generate Re(Q),Im(Q) scan for namelist parameters + stabscan_flag=f ! Generate Re(Q),Im(Q) scan and output as ASCII br_th_flag=f ! Calculate br_th - compress_deltas=f ! Only output essential growth rate grid points bal_flag=f ! calculate the resonant field penetration threshold from torque balance / diff --git a/slayer/delta.f b/slayer/delta.f index 1989a5c62..cd29ead93 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -125,19 +125,9 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - Q=inQ - !IF(present(iinQ)) Q=inQ+ifac*iinQ - Q_e=inQ_e - Q_i=inQ_i - pr=inpr - !pe=inpe - c_beta=inc_beta - !ds=inds - tau=intau - - IF ((layfac>0).AND.(ABS(Q-Q_e)0).AND.(ABS(Q-Q_e) (pr**(1.0/6.0))) THEN + IF (D_norm > (P_perp**(1.0/6.0))) THEN ak = -(g_tmp + ifac*Q_e) - bk = pr/(2.0*(D_beta_norm**2.0)) + bk = P_perp/(2.0*(D_norm**2.0)) - ck_1 = 2.0*(g_tmp + ifac*Q_i)/pr - ck_2 = (pr + (g_tmp + - $ ifac*Q_i)*(D_beta_norm**2.0))/(2.0*pr*(D_beta_norm**2.0)) - ck = (pr/(2.0*(D_beta_norm**2.0)))*(1 + ck_1 - ck_2) + ck_1 = 2.0*(g_tmp + ifac*Q_i)/P_perp + ck_2 = (P_perp + (g_tmp + + $ ifac*Q_i)*(D_norm**2.0))/(2.0*P_perp*(D_norm**2.0)) + ck = (P_perp/(2.0*(D_norm**2.0)))*(1 + ck_1 - ck_2) xk = (ck - SQRT(bk)*(1 - $ SQRT(bk)*ak))/(2.0*SQRT(bk)) @@ -331,7 +310,7 @@ FUNCTION riccati_f(tmp_g,inQ_e,inQ_i,inpr,inD_beta_norm, W_bound = xk - SQRT(bk)*my_p ELSE ak = -(g_tmp + ifac*Q_e) - bk = pr + bk = P_perp ck = -ifac*(Q_e - Q_i) + (g_tmp + ifac*Q_i) xk = (ak*bk - ck)/(2.0*SQRT(bk)) @@ -354,7 +333,8 @@ FUNCTION riccati_f(tmp_g,inQ_e,inQ_i,inpr,inD_beta_norm, istep=istep+1 CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) - WRITE(bin_unit)REAL(my_p,4),REAL(REAL(W),4),REAL(AIMAG(W),4) + WRITE(bin_unit)REAL(my_p,4),REAL(REAL(W),4), + $ REAL(AIMAG(W),4) WRITE(out2_unit,'(1x,3(es17.8e3))')my_p,REAL(W),AIMAG(W) ENDDO CLOSE(bin_unit) @@ -414,10 +394,10 @@ SUBROUTINE w_der_f(neq,my_p,W,dWdp) fA_prime = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + $ ifac*Q_e + (my_p**2.0)) fB = g_tmp*(g_tmp + ifac*Q_i) + 2.0*(g_tmp + - $ ifac*Q_i)*pr*(my_p**2.0) + (pr**2.0)*(my_p**4.0) - fC = g_tmp + ifac*Q_e + ( pr + (g_tmp + - $ ifac*Q_i)*(D_beta_norm**2.0))*(my_p**2.0) + - $ 2.0*pr*(D_beta_norm**2.0)*(my_p**4.0) + $ ifac*Q_i)*P_perp*(my_p**2.0) + (P_perp**2.0)*(my_p**4.0) + fC = g_tmp + ifac*Q_e + ( P_perp + (g_tmp + + $ ifac*Q_i)*(D_norm**2.0))*(my_p**2.0) + + $ 2.0*P_perp*(D_norm**2.0)*(my_p**4.0) dWdp(1) = -(fA_prime/my_p)*W(1) - (W(1)**2.0)/my_p + $ (fB/(fA*fC))*(my_p**3.0) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 7d6b047e9..7d5e62a73 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -46,7 +46,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, REAL(r8) :: inQ,inQ_e,inQ_i,inpe,inc_beta,inds,intau,inlu REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, - $ lbeta,tau_i,tau_h,tau_r,tau_v + $ lbeta,tau_i,tau_h,tau_v REAL(r8) :: inQ_min,inQ_max,Q_sol REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal @@ -99,12 +99,12 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - ! note Q depends on Qconv even if omega is fixed. + ! note Q depends on Qconv even IF omega is fixed. Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i - ! This is the most critical parameter + ! This is the most critical PARAMETER ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 @@ -157,7 +157,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, ! Write torque balance curves to file for diagnostic purposes IF(ascii_flag)THEN OPEN(UNIT=out_unit,FILE="gpec_slayer_torque_balance_m"// - $ TRIM(sm)//"_n"//TRIM(sn)//".out", + $ TRIM(sm)//"_n"//TRIM(sn)//".OUT", $ STATUS="UNKNOWN") WRITE(out_unit,'(1x,5(a17))') "inQ","RE(delta)", $ "IM(delta)","jxb","bal" @@ -168,7 +168,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, CLOSE(out_unit) ENDIF - ! Identify the threshold from the maximum of the balance parameter + ! Identify the threshold from the maximum of the balance PARAMETER index=MAXLOC(bal) Q_sol=inQs(index(1)) omega_sol=inQs(index(1))/Qconv @@ -181,52 +181,36 @@ END SUBROUTINE gpec_slayer c Subprogram 3. scan_grid c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, - $ fitz_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,lu_arr,delta_arr, - $ lar_gamma_arr,results) + SUBROUTINE output_gamma(est_gamma_flag,qval_arr, + $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, + $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, + $ im_trace) ! Declarations (include necessary type declarations from original code) - LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag, - $ fitz_gamma_flag - - INTEGER, INTENT(IN), DIMENSION(:), ALLOCATABLE :: qval_arr - REAL(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: omegas_arr, - $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,inpr_arr,ind_beta_arr, - $ D_beta_norm_arr,lu_arr - - COMPLEX(r8), INTENT(IN), DIMENSION(:), ALLOCATABLE :: dels_db_arr, - $ lar_gamma_arr,delta_arr - - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs - !TYPE(result_type) :: results(8) - TYPE(result_type), INTENT(IN) :: results(8) - - REAL(r8) :: br_th = 0 - - WRITE(*,*)"Successfully entered output_lar_gamma()" - - inQs = (/0.0/) - iinQs = (/0.0/) - - CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag, - $ stabscan_flag,br_th_flag, - $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, - $ ind_beta_arr, - $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) - - END SUBROUTINE output_lar_gamma + LOGICAL, INTENT(IN) :: est_gamma_flag + INTEGER, INTENT(IN), DIMENSION(:) :: qval_arr + REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, + $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, + $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, + $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace + COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, + $ gamma_sol_arr,gamma_est_arr + + WRITE(*,*)"Successfully entered output_gamma()" + + CALL slayer_netcdf_out(SIZE(qval_arr),est_gamma_flag, + $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, + $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, + $ im_trace) + + END SUBROUTINE output_gamma c----------------------------------------------------------------------- c Subprogram 2. growthrate_scan -c Set up and iterate stability scans if no match is found +c Set up and iterate stability scans IF no match is found c----------------------------------------------------------------------- SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, @@ -341,7 +325,7 @@ SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, DO j = 1, ncoarse inQ_coarse = -scan_radius + (i - 1) * inQ_step iinQ_coarse = -scan_radius + (j - 1) * iinQ_step - ! Evaluate riccati function + ! Evaluate riccati FUNCTION delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, $ inds,intau,inpe,iinQ=iinQ_coarse) delta_real = REAL(delta)*(my_lu**(1.0/3.0)) ! Critical normalization @@ -371,7 +355,7 @@ SUBROUTINE shrink_array(arr, new_size) END SUBROUTINE shrink_array c----------------------------------------------------------------------- c Subprogram 5. grow_array -c Increase scan array size if necessary +c Increase scan array size IF necessary c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) @@ -384,26 +368,28 @@ SUBROUTINE grow_array(arr, old_size, new_size) END SUBROUTINE grow_array c c -c - subroutine NewtonRoot(g_r, g_i, verbose) - REAL(r8), intent(inout) :: g_r, g_i - integer, intent(in) :: verbose +c Adapted from + SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(INOUT) :: g_r, g_i + INTEGER, INTENT(IN) :: verbose REAL(r8) :: F1, F2, J11, J12, J21, J22, det, iJ11, iJ12 REAL(r8) :: iJ21, iJ22, dx1, dx2, dx, f, g1, g2, lambda, $ Residual - integer :: iter + INTEGER :: iter - REAL(r8), parameter :: Eps = 1.0e-12 ! Tolerance parameter - REAL(r8), parameter :: Smin = 1.0e-07 ! Min step size - REAL(r8), parameter :: Smax = 0.1 ! Max step size - integer, parameter :: MaxIter = 100 ! Maximum iterations + REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER + REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size + REAL(r8), PARAMETER :: Smax = 0.1 ! Max step size + INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations iter = 0 - do - call NewtonFunction(g_r, g_i, F1, F2) + DO + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - call NewtonJacobian(g_r, g_i, J11, J12, J21, J22) + CALL newton_jacobian(g_r, g_i, J11, J12, J21, J22, + $ fitz_flag) det = J11 * J22 - J12 * J21 @@ -422,187 +408,206 @@ subroutine NewtonRoot(g_r, g_i, verbose) g1 = F1*J11 + F2*J21 g2 = F1*J12 + F2*J22 - call NewtonBackTrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda) + CALL newton_backtrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda, + $ fitz_flag) - call NewtonFunction(g_r, g_i, F1, F2) + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) Residual = sqrt(F1*F1 + F2*F2) - if (verbose .ne. 0) then - write(*, '("x = (", ES10.3, ", ", ES10.3, ") F = (", - & ES10.3, ", ", ES10.3, ") Residual = ", ES10.3, - & " lambda = ", ES10.3, " dx = ", ES10.3)') - & g_r, g_i, F1, F2, Residual, lambda, dx - endif + n_trace = n_trace + 1 + + IF (n_trace < 100) THEN + re_trace(n_trace) = g_r + im_trace(n_trace) = g_i + END IF + + IF (verbose .ne. 0) THEN + WRITE(*, '(" Q step = (", ES10.3, ", ", ES10.3,")" )') + $ g_r, g_i + ENDIF iter = iter + 1 - if (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit - enddo + IF (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit + ENDDO - end subroutine NewtonRoot + END SUBROUTINE newton_root -!----------------------------------------------------------------------- -! Function to backtrack along Newton step in order to minimize f = (F1*F1 + F2*F2) /2 -! -! Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies in C (Cambridge, 1992), Sect. 9.7 -!----------------------------------------------------------------------- - subroutine NewtonBackTrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, - & lambda) - REAL(r8), intent(inout) :: g_r, g_i, dx, lambda - REAL(r8), intent(inout) :: dx1, dx2, f, g1, g2 +c----------------------------------------------------------------------- +c Function to backtrack along Newton step IN order to minimize f = (F1*F1 + F2*F2) /2 +c Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies IN C (Cambridge, 1992), Sect. 9.7 +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, + $ lambda, fitz_flag) + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(INOUT) :: g_r, g_i, dx, lambda + REAL(r8), INTENT(INOUT) :: dx1, dx2, f, g1, g2 REAL(r8) :: x1old, x2old, dxold, fold, slope, F1, F2 REAL(r8) :: tmplam, rhs1, rhs2, a, b, disc, lambd2, mf2 - integer :: i + INTEGER :: i - REAL(r8), parameter :: Smin = 1.0d-10 ! Min step size - REAL(r8), parameter :: Smax = 1.0d0 ! Max step size - REAL(r8), parameter :: alpha = 1.0d-4 ! Line search parameter - integer, parameter :: Maxiter = 100 ! Maximum iterations + REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size + REAL(r8), PARAMETER :: Smax = 1.0d0 ! Max step size + REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER + INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations x1old = g_r x2old = g_i dxold = dx fold = f - if (dxold > Smax) then + IF (dxold > Smax) THEN dx1 = dx1 * Smax / dxold dx2 = dx2 * Smax / dxold dxold = Smax - endif + ENDIF slope = g1*dx1 + g2*dx2 - if (slope >= 0.0d0) then - write(*, *) "NewtonBackTrack: Error - roundoff problem" - endif + IF (slope >= 0.0d0) THEN + WRITE(*,*) "NewtonBackTrack: Error - roundoff problem" + ENDIF lambda = 1.0d0 - do i = 0, Maxiter + DO i = 0, Maxiter g_r = x1old + lambda * dx1 g_i = x2old + lambda * dx2 - call NewtonFunction(g_r, g_i, F1, F2) + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) f = 0.5 * (F1*F1 + F2*F2) - if (f <= fold + alpha * lambda * slope .or. - & lambda * dxold < Smin) then + IF (f <= fold + alpha * lambda * slope .or. + $ lambda * dxold < Smin) THEN dx = lambda * dxold - return - else - if (lambda == 1.0d0) then + RETURN + ELSE + IF (lambda == 1.0d0) THEN tmplam = -slope / 2.0d0 / (f - fold - slope) - else + ELSE rhs1 = f - fold - lambda * slope rhs2 = mf2 - fold - lambd2 * slope a = (rhs1/lambda/lambda - rhs2/lambd2/lambd2) - & / (lambda - lambd2) + $ / (lambda - lambd2) b = (-lambd2 * rhs1/lambda/lambda + lambda * - & rhs2/lambd2/lambd2) / (lambda - lambd2) + $ rhs2/lambd2/lambd2) / (lambda - lambd2) - if (a == 0.0d0) then + IF (a == 0.0d0) THEN tmplam = -slope / 2.0d0 / b - else + ELSE disc = b*b - 3.0d0 * a * slope - if (disc < 0.0d0) then + IF (disc < 0.0d0) THEN tmplam = 0.5d0 * lambda - else if (b <= 0.0d0) then + ELSE IF (b <= 0.0d0) THEN tmplam = (-b + sqrt(disc)) / 3.0d0 / a - else + ELSE tmplam = -slope / (b + sqrt(disc)) - endif - endif + ENDIF + ENDIF - if (tmplam > 0.5d0 * lambda) then + IF (tmplam > 0.5d0 * lambda) THEN tmplam = 0.5d0 * lambda - endif - endif - endif + ENDIF + ENDIF + ENDIF lambd2 = lambda mf2 = f lambda = max(tmplam, 0.1d0*lambda) - enddo + ENDDO dx = lambda * dxold - end subroutine NewtonBackTrack - -!----------------------------------------------------------------------- -! Function to calculate Jacobian matrix for Newton-Raphson root finding -!----------------------------------------------------------------------- - subroutine NewtonJacobian(g_r, g_i, J11, J12, J21, J22) - REAL(r8), intent(in) :: g_r, g_i - REAL(r8), intent(out) :: J11, J12, J21, J22 + END SUBROUTINE newton_backtrack +c----------------------------------------------------------------------- +c Function to calculate Jacobian matrix for Newton-Raphson root finding +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_jacobian(g_r, g_i, J11, J12, J21, J22, + $ fitz_flag) + REAL(r8), INTENT(IN) :: g_r, g_i + LOGICAL, INTENT(IN) :: fitz_flag + + REAL(r8), INTENT(OUT) :: J11, J12, J21, J22 REAL(r8) :: F1m, F2m, F1p, F2p - REAL(r8), parameter :: dS = 1.0e-06 ! Step size for derivatives + REAL(r8), PARAMETER :: dS = 1.0e-06 ! Step size for derivatives - call NewtonFunction(g_r - dS, g_i, F1m, F2m) - call NewtonFunction(g_r + dS, g_i, F1p, F2p) + CALL newton_function(g_r - dS, g_i,F1m,F2m,fitz_flag) + CALL newton_function(g_r + dS, g_i,F1p,F2p,fitz_flag) J11 = (F1p - F1m) / 2.0d0 / dS J21 = (F2p - F2m) / 2.0d0 / dS - call NewtonFunction(g_r, g_i - dS, F1m, F2m) - call NewtonFunction(g_r, g_i + dS, F1p, F2p) + CALL newton_function(g_r, g_i - dS,F1m,F2m,fitz_flag) + CALL newton_function(g_r, g_i + dS,F1p,F2p,fitz_flag) J12 = (F1p - F1m) / 2.0d0 / dS J22 = (F2p - F2m) / 2.0d0 / dS - end subroutine NewtonJacobian + END SUBROUTINE newton_jacobian -!----------------------------------------------------------------------- -! Function to return maximum of two values -!----------------------------------------------------------------------- - function Fmax(f1, f2) result(res) - double precision, intent(in) :: f1, f2 - double precision :: res +c----------------------------------------------------------------------- +c Function to RETURN maximum of two values +c Adapted from +c----------------------------------------------------------------------- + FUNCTION Fmax(f1, f2) result(res) + REAL(r8), INTENT(IN) :: f1, f2 + REAL(r8) :: res - if (f1 > f2) then + IF (f1 > f2) THEN res = f1 - else + ELSE res = f2 - endif + ENDIF - end function Fmax + END FUNCTION Fmax -!----------------------------------------------------------------------- -! Function to return minimum of two values -!----------------------------------------------------------------------- - function Fmin(f1, f2) result(res) - REAL(r8), intent(in) :: f1, f2 +c----------------------------------------------------------------------- +c Function to RETURN minimum of two values +c Adapted from +c----------------------------------------------------------------------- + FUNCTION Fmin(f1, f2) result(res) + REAL(r8), INTENT(IN) :: f1, f2 REAL(r8) :: res - if (f1 < f2) then + IF (f1 < f2) THEN res = f1 - else + ELSE res = f2 - endif - - end function Fmin + ENDIF -!----------------------------------------------------------------------- -! Function to calculate target functions for Newton-Raphson root finding -!----------------------------------------------------------------------- - subroutine NewtonFunction(g_r, g_i, F1, F2) - REAL(r8), intent(in) :: g_r, g_i - REAL(r8), intent(out) :: F1, F2 + END FUNCTION Fmin - ! These would be module-level variables in the original code +c----------------------------------------------------------------------- +c Function to calculate target functions for Newton-Raphson root finding +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_function(g_r,g_i,F1,F2,fitz_flag) + REAL(r8), INTENT(IN) :: g_r,g_i + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(OUT) :: F1, F2 + COMPLEX(r8) :: Deltas - REAL(r8) :: Delta - - g_tmp = CMPLX(g_r,g_i) - Deltas=riccati_f(g_tmp,Q_e,Q_i,pr,D_beta_norm,tau) - - F1 = REAL(Deltas) - deltaprim - F2 = dimag(Deltas) + + IF (fitz_flag) THEN ! use Fitzpatrick formalism + g_tmp = CMPLX(g_r,g_i) + Deltas=riccati_f(g_tmp) + ELSE ! use J.K. Park formalism + g_tmp = CMPLX(g_i,g_r) + Deltas=riccati(g_i,Q_e,Q_i,P_perp, + $ c_beta,D_norm,tau,pe, + $ iinQ=g_r) + END IF + + F1 = REAL(Deltas) - delta_eff + F2 = AIMAG(Deltas) - end subroutine NewtonFunction + END SUBROUTINE newton_function END MODULE gslayer_mod diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index dfa924f9f..eb8b3e565 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -20,16 +20,16 @@ MODULE layerinputs_mod c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,shear, - $ r_o,my_bt0,my_psio,mpsi,nn,resm) + $ r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: - $ Re_dp_diagonal,Im_dp_diagonal + $ Re_dp_diagonal,Im_dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, - $ psi_n_rational, shear + $ psi_n_rational, shear REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, - $ my_psio,mpsi + $ my_psio,mpsi,dr_vals INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm INTEGER, INTENT(OUT) :: msing @@ -38,12 +38,12 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Internal Variables INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, - $ msing_id,nn_id,resm_id ! Explicit kind for NetCDF variables + $ msing_id,nn_id,resm_id,drr_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, - $ msing_len,nn_len ! Attribute lengths + $ msing_len,nn_len,dr_len ! Attribute lengths ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) @@ -61,7 +61,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Allocate Arrays (based on dimension) ALLOCATE(Re_dp_diagonal(msing),q_rational(msing), $ psi_n_rational(msing),shear(msing), - $ resm(msing),Im_dp_diagonal(msing)) + $ resm(msing),Im_dp_diagonal(msing),dr_vals(msing)) ALLOCATE(delta_prime(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) @@ -76,11 +76,11 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) CALL sl_check(stat) - bt0_id=0 !!!!! THIS COULD BE A PROBLEM - nn_id=0 - mpsi_id=0 - psio_id=0 - ro_id=0 + !bt0_id=0 !!!!! THIS COULD BE A PROBLEM + !nn_id=0 + !mpsi_id=0 + !psio_id=0 + !ro_id=0 ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), $ my_psio(INT(psio_len)), @@ -97,6 +97,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, CALL sl_check(stat) stat = nf90_inq_varid(ncid, "resm", resm_id) CALL sl_check(stat) + stat = nf90_inq_varid(ncid, "dr_rational", drr_id) + CALL sl_check(stat) ! Get attributes stat = nf90_get_att(ncid, ro_id, "ro", r_o) CALL sl_check(stat) @@ -121,6 +123,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, CALL sl_check(stat) stat = nf90_get_var(ncid, resm_id, resm) CALL sl_check(stat) + stat = nf90_get_var(ncid, drr_id, dr_vals) + CALL sl_check(stat) ! Extract Diagonal, with 3rd index signifying REAL part DO i = 1, msing @@ -224,21 +228,19 @@ END FUNCTION issurfint c subprogram 3. build_inputs. c build input arrays for SLAYER c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, - $ qval_arr,psi_n_rational,inQ_arr,inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, - $ D_beta_norm_arr,intau_arr,Q0_arr,inpr_arr, - $ inpe_arr,omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr) + SUBROUTINE build_inputs(infile,ncfile,chi_prof, + $ qval_arr,psi_n_rational,lu_arr,Qconv_arr,Q_arr, + $ Q_e_arr,Q_i_arr,c_beta_arr,d_beta_arr, + $ D_norm_arr,tau_arr,P_perp_arr, + $ omegas_arr,gammafac_arr,Re_deltaprime_arr, + $ Im_deltaprime_arr,delta_crit_arr) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- ! Inputs CHARACTER(512), INTENT(IN) :: infile,ncfile ! Internals - REAL(r8), DIMENSION(8), INTENT(IN) :: inpr_prof - REAL(r8), INTENT(IN) :: inpe - LOGICAL, INTENT(IN) :: Pe_flag + REAL(r8), DIMENSION(8), INTENT(IN) :: chi_prof LOGICAL :: firstsurf REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf,ising INTEGER :: zi, zimp, mi, mimp @@ -251,20 +253,25 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0 - REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_r,tau_h, - $ rho,tau_v,inpr,Qconv,lbeta,qintb,gammafac - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: inQ_arr, - $ inQ_e_arr,psi_n_rational,D_beta_norm_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr,ind_beta_arr, - $ intau_arr,Q0_arr,inpr_arr,inpe_arr,omegas_arr, - $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr + REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_h, + $ rho,tau_v,chi,Qconv,lbeta,qintb,gammafac + REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, + $ sigma_par_2,sigma_par,tau_perp,Wd,vte, + $ dr_val,chi_par_smfp,chi_par_lmfp,chi_par + INTEGER :: wit + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: Q_arr, + $ Q_e_arr,psi_n_rational,D_norm_arr,P_perp_arr, + $ Q_i_arr,c_beta_arr,d_beta_arr, + $ tau_arr,omegas_arr,lu_arr,Qconv_arr, + $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr, + $ delta_crit_arr INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor - REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, - $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal + REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal,dr_arr, + $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, + $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal,dr_vals REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, $ ti_arr,zeff_arr,bt_arr,rs_arr,R0_arr,mu_i_arr INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm,nns_arr @@ -279,7 +286,7 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile,msing,Re_dp_diagonal, $ Im_dp_diagonal,q_rational,psi_n_rational, - $ shear,r_o,my_bt0,my_psio,mpsi_arr,nn,resm) + $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing WRITE(*,*)"Re_dp_diagonal=",Re_dp_diagonal WRITE(*,*)"Im_dp_diagonal=",Im_dp_diagonal @@ -295,17 +302,18 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, mpsi = INT(mpsi_arr(1)) mthsurf = 512 ! Hardcoded, but this is a default value - ALLOCATE(qval_arr(msing),inQ_arr(msing),inQ_e_arr(msing), - $ inQ_i_arr(msing), - $ inc_beta_arr(msing),inds_arr(msing),ind_beta_arr(msing), - $ intau_arr(msing),Q0_arr(msing),inpr_arr(msing), - $ inpe_arr(msing),omegas_arr(msing),omegas_e_arr(msing), + ALLOCATE(qval_arr(msing),Q_arr(msing),Q_e_arr(msing), + $ Q_i_arr(msing),lu_arr(msing),Qconv_arr(msing), + $ c_beta_arr(msing),d_beta_arr(msing), + $ tau_arr(msing), + $ omegas_arr(msing),omegas_e_arr(msing), $ omegas_i_arr(msing),gammafac_arr(msing), $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), - $ D_beta_norm_arr(msing)) + $ D_norm_arr(msing),P_perp_arr(msing), + $ delta_crit_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), - $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing)) + $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing),dr_arr(msing)) ALLOCATE(jacs(0:mthsurf),delpsi(0:mthsurf), $ rsurf(0:mthsurf),asurf(0:mthsurf)) @@ -360,22 +368,16 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, zeff = kin%f(9) omega = kin%f(5) my_qval = q_rational(ising)!sq%f(4) - my_sval = shear(ising) ! SHEAR SO MUCH SMALLER THAN BEFORE???? 500 puts it on the root + my_sval = shear(ising) my_bt = my_bt0(1) my_rs = a_surf R_0 = r_o(1) mu_i = 2.0 + dr_val = dr_vals(ising) eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - inpr = inpr_prof(ising) - - ! Check whether to include classical electron viscosity - IF (Pe_flag) THEN - my_inpe=0.0165*inpr - ELSE - my_inpe=inpe - ENDIF + chi = chi_prof(ising) ne_arr(ising) = n_e te_arr(ising) = t_e @@ -392,29 +394,44 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, mrs = real(mms,4) nrs = real(nns,4) - nns_arr = nn(1) + nns_arr(ising) = nn(1) - tau= t_i/t_e ! ratio of ion to electron temperature + lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) + + ! mu_i: ion mass ratio to proton + tau= t_i/t_e ! ratio of ion to electron temperature tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. - rho=(mu_i*m_p)*n_e ! mass density + eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) + rho=(mu_i*m_p)*n_e ! mass density + + tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5)* + $ (eps0**2.0)*(m_e**0.5)*(t_e**1.5) + tau_ee_denom = lnLamb*(chag**2.5)*n_e + tau_ee = tau_ee_num / tau_ee_denom - b_l=(nrs/mrs)*nrs*my_sval*my_bt/R_0 ! characteristic magnetic field + sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) / + $ (Zeff*(SQRT(2.0) + Zeff)) + sigma_par_2 = (n_e * (chag**2.0) * tau_ee) / m_e + sigma_par = sigma_par_1*sigma_par_2 - v_a=b_l/(mu0*rho)**0.5 ! alfven velocity, B_L IS BROKEN + b_l=(nr/mr)*my_rs*my_sval*my_bt/R_0 ! characteristic magnetic field + v_a=b_l/(mu0*rho)**0.5 ! alfven velocity rho_s=1.02e-4*(mu_i*t_e)**0.5/my_bt ! ion Lamour by elec. Temp. d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth - tau_h=R_0*(mu0*rho)**0.5/(nns*my_sval*my_bt) ! alfven time across surface - tau_r=mu0*my_rs**2.0/eta ! resistive time scale - tau_v=tau_r/inpr ! rho*rs**2.0/visc ! viscous time scale - - ! this one must be anomalous. calculated back from pr. - visc= rho*my_rs**2.0/tau_v - - lu=tau_r/tau_h ! Lundquist number + tau_h=R_0*(mu0*rho)**0.5/(nr*my_sval*my_bt) ! alfven time across surface + tau_r=mu0*(my_rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale + IF (ABS(chi) > 0.0) THEN + tau_perp = ( my_rs**2.0 ) / chi + ELSE + tau_perp = 0.0 + END IF + + lu=tau_r/tau_h ! Lundquist number Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - + tauk = Qconv + ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega Q_e=-Qconv*omega_e @@ -426,33 +443,57 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/my_bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 + IF (ABS(tau_perp) > 0.0) THEN + P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number + ELSE + P_perp = 0.0 + END IF ! this is using Fitzpatrick's tau', we need tau eventually d_beta = c_beta*d_i - D_beta_norm = (d_beta/my_rs)*(lu**(1.0/3.0))*(tau/(1+ - $ tau))**(0.5) + D_norm = (d_beta/my_rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) + + ! Calculate Delta_crit + IF (ABS(dr_val) > 0.0) THEN + vte = SQRT((2.0*(t_e*chag))/m_e) + chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ + $ (1.0+0.2535*Zeff) + + Wd = 0.1 + DO wit = 1,10 + chi_par_lmfp = (2.0*R_0*vte)/ + $ (SQRT(pi)*nr*my_sval*Wd) + chi_par = (chi_par_smfp*chi_par_lmfp)/ + $ (chi_par_smfp+chi_par_lmfp) + Wd = SQRT(8.0)*((chi/chi_par)**0.25)* + $ (1.0/SQRT((my_rs/R_0)*my_sval*nr)) + END DO + delta_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd + ELSE + delta_crit = 0.0 + END IF - delta_n=lu**(1.0/3.0)/my_rs ! norm factor for delta primes +!!!!!!!!!!! gammafac = (my_rs*Re_dp_diagonal(ising))/tau_r ! scalar to convert thickness into growth rate qval_arr(ising) = INT(my_qval) - inQ_arr(ising)=REAL(Q) - inQ_e_arr(ising)=Q_e - inQ_i_arr(ising)=Q_i - inc_beta_arr(ising)=c_beta - inds_arr(ising)=ds - ind_beta_arr(ising)=d_beta - D_beta_norm_arr(ising)=D_beta_norm - intau_arr(ising)=tau - Q0_arr(ising)=Q - inpr_arr(ising) = inpr - inpe_arr(ising) = my_inpe + lu_arr(ising)=lu + Q_arr(ising)=REAL(Q) + Q_e_arr(ising)=Q_e + Q_i_arr(ising)=Q_i + c_beta_arr(ising)=c_beta + d_beta_arr(ising)=d_beta + D_norm_arr(ising)=D_norm + tau_arr(ising)=tau omegas_arr(ising) = omega omegas_e_arr(ising) = omega_e omegas_i_arr(ising) = omega_i gammafac_arr(ising) = gammafac Re_deltaprime_arr(ising) = Re_dp_diagonal(ising) Im_deltaprime_arr(ising) = Im_dp_diagonal(ising) + delta_crit_arr(ising) = delta_crit + P_perp_arr(ising) = P_perp + Qconv_arr(ising) = tauk ENDDO !WRITE(*,*)"msing=",msing @@ -479,12 +520,12 @@ SUBROUTINE build_inputs(infile,ncfile,inpr_prof,inpe,Pe_flag, !WRITE(*,*)"Re_deltaprime_arr=",Re_deltaprime_arr !WRITE(*,*)"Im_deltaprime_arr=",Im_deltaprime_arr !stop - CALL slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr,ni_arr, - $ ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, - $ resm,nns_arr,inc_beta_arr,inds_arr, - $ intau_arr,inpr_arr,inpe_arr,inQ_arr,omegas_arr, - $ omegas_e_arr,omegas_i_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr) + !CALL slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr,ni_arr, + !$ ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, + !$ resm,nns_arr,inc_beta_arr,inds_arr, + !$ intau_arr,inpr_arr,inpe_arr,inQ_arr,omegas_arr, + !$ omegas_e_arr,omegas_i_arr, + !$ Re_deltaprime_arr,Im_deltaprime_arr) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/slayer/params.f b/slayer/params.f index c8651932e..2e2cf1138 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -8,16 +8,22 @@ MODULE params_mod c----------------------------------------------------------------------- c calculate parameters. c----------------------------------------------------------------------- - SUBROUTINE params(n_e,t_e,t_i,omega, + SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega, + REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,chi,dr_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff LOGICAL, INTENT(IN) :: params_check REAL(r8) :: rho,b_l,v_a,Qconv, - $ lbeta,tau_i,tau_h,tau_r,tau_v + $ lbeta,tau_i,tau_h,tau_v + REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, + $ sigma_par_2,sigma_par,tau_perp,Wd,vte, + $ chi_par_smfp,chi_par_lmfp,chi_par + INTEGER :: wit + + lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) ! mu_i: ion mass ratio to proton tau= t_i/t_e ! ratio of ion to electron temperature @@ -25,20 +31,37 @@ SUBROUTINE params(n_e,t_e,t_i,omega, eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) rho=(mu_i*m_p)*n_e ! mass density + tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5)* + $ (eps0**2.0)*(m_e**0.5)*(t_e**1.5) + tau_ee_denom = lnLamb*(chag**2.5)*n_e + tau_ee = tau_ee_num / tau_ee_denom + !tau_ee = 1.09e16 * (t_e/1.0e3)**1.5 / (n_e * lnLamb) + + + sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) / + $ (Zeff*(SQRT(2.0) + Zeff)) + sigma_par_2 = (n_e * (chag**2.0) * tau_ee) / m_e + sigma_par = sigma_par_1*sigma_par_2 + b_l=(nr/mr)*rs*sval*bt/R0 ! characteristic magnetic field v_a=b_l/(mu0*rho)**0.5 ! alfven velocity rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth tau_h=R0*(mu0*rho)**0.5/(nn*sval*bt) ! alfven time across surface - tau_r=mu0*rs**2.0/eta ! resistive time scale + !tau_r=mu0*rs**2.0/eta ! resistive time scale + tau_r=mu0*(rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale tau_v=tau_r/pr !rho*rs**2.0/visc ! viscous time scale - + IF (ABS(chi) > 0.0) THEN + tau_perp = ( rs**2.0 ) / chi + ELSE + tau_perp = 0.0 + END IF + ! this one must be anomalous. calculated back from pr. visc= rho*rs**2.0/tau_v lu=tau_r/tau_h ! Lundquist number -! pr=tau_r/tau_v ! Prandtl number. Only place needs viscosity. omega_e=-t_e/(bt*R0)*(1.0/l_n+1.0/l_t)*qval ! elec. diamag omega_i=t_i/(bt*R0)*(1.0/l_n+1.0/l_t)*qval ! ion diamag @@ -46,24 +69,51 @@ SUBROUTINE params(n_e,t_e,t_i,omega, ! now calculate the main 7 normalized parameters. Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - + tauk = Qconv + ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i - + ! This is the most critical parameter ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 + IF (ABS(tau_perp) > 0.0) THEN + P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number + ELSE + P_perp = 0.0 + END IF + ! this is using Fitzpatrick's tau', we need tau eventually d_beta = c_beta*d_i - D_beta_norm = (d_beta/rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) + D_norm = (d_beta/rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes + ! Calculate Delta_crit + IF (ABS(dr_val) > 0.0) THEN + vte = SQRT((2.0*(t_e*chag))/m_e) + chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ + $ (1.0+0.2535*Zeff) + + Wd = 0.1 + DO wit = 1,10 + + chi_par_lmfp = (2.0*R0*vte)/(SQRT(pi)*nr*sval*Wd) + chi_par = (chi_par_smfp*chi_par_lmfp)/ + $ (chi_par_smfp+chi_par_lmfp) + Wd = SQRT(8.0)*((chi/chi_par)**0.25)* + $ (1.0/SQRT((rs/R0)*sval*nr)) + END DO + delta_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd + ELSE + delta_crit = 0.0 + END IF + ! quick diagnostics. IF (params_check) THEN WRITE(*,*)"eta=",eta diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 74e8a9589..f3ccca6c7 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -3,20 +3,21 @@ MODULE sglobal_mod IMPLICIT NONE INTEGER :: mm,nn INTEGER :: in_unit,out_unit,out2_unit,out3_unit, - $ bin_unit,bin_2d_unit,input_unit + $ bin_unit,bin_2d_unit,input_unit,n_trace INTEGER, PARAMETER :: r8=SELECTED_REAL_KIND(13,307) REAL(r8) :: mr,nr REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, - $ d_beta,D_beta_norm,gamma_fac + $ d_beta,D_norm,P_perp,gamma_fac REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, - $ delta_n,layfac,Qconv,deltaprim, - $ g_r, g_i + $ delta_n,layfac,Qconv,lnLamb,deltaprim, + $ delta_crit,tau_r,tauk,g_r,g_i,delta_eff + REAL(r8), DIMENSION(:), ALLOCATABLE :: re_trace,im_trace COMPLEX(r8) :: Q,g_tmp REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, - $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6022e-19, - $ kval=1.3807e-23,lnLamb=17.0 + $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6021917e-19, + $ kval=1.3807e-23,eps0 = 8.8542e-12 TYPE result_type REAL(r8), ALLOCATABLE :: inQs(:), iinQs(:), diff --git a/slayer/slayer.f b/slayer/slayer.f index 8a8196082..5eca1a8f2 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -13,11 +13,8 @@ PROGRAM slayer USE sglobal_mod - !USE params_mod USE delta_mod, ONLY: riccati,riccati_del_s,riccati_out, $ parflow_flag,PeOhmOnly_flag - - ! FOR TESTING: USE gslayer_mod USE layerinputs_mod @@ -25,7 +22,7 @@ PROGRAM slayer CHARACTER(512) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,count, - $ ReQ_num,ImQ_num,n_k,scan_radius,max_points + $ Q_num,n_k,scan_radius,max_points INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -33,112 +30,99 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,stabscan_eq_flag,stabscan_flag, - $ lar_gamma_eq_flag,lar_gamma_flag,fitz_gamma_flag, - $ br_th_flag,compress_deltas - + $ params_check,stabscan_flag,read_eq,est_gamma_flag, + $ match_gamma_flag,fitz_flag,br_th_flag REAL(r8) :: n_e,t_e,t_i,omega,omega0, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,tau_r,d_b,Residual + REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, - $ tmp_gamma,ingam + $ tmp_gamma,ingamma,delta_prime REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, - $ delta_imag,Qratio + $ delta_imag,Qratio,chi INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl, - $ bal, + REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl,bal, $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQ_e_arr, - $ inQ_i_arr,inc_beta_arr,inds_arr, - $ intau_arr,inQ0_arr,inpr_arr, - $ inpe_arr,omegas_arr,inQ_arr,lu_arr, - $ psi_n_rational,ind_beta_arr,D_beta_norm_arr - REAL(r8), DIMENSION(8) :: inpr_prof - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: all_Re_deltas, - $ all_Im_deltas,all_roots - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: all_inQs - REAL(r8), DIMENSION(:,:),ALLOCATABLE :: Re_deltas,Im_deltas - + REAL(r8), DIMENSION(:), ALLOCATABLE :: Q_e_arr,Q_i_arr, + $ c_beta_arr,D_norm_arr,tau_arr,P_perp_arr, + $ omegas_arr,Q_arr,lu_arr,psi_n_rational, + $ d_beta_arr,Qconv_arr + REAL(r8), DIMENSION(8) :: chi_prof INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr - REAL(r8), DIMENSION(:), ALLOCATABLE :: + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs, $ Re_deltaprime_arr,Im_deltaprime_arr, - $ all_growthrates,all_growthrate_locs + $ gammafac_arr,delta_crit_arr REAL(r8), DIMENSION(:,:), ALLOCATABLE :: - $ js,ks,psis,jxbs,Q_sols,br_ths, - $ inQs_left,inQs_right,coarse_deltas + $ js,ks,psis,jxbs,Q_sols,br_ths REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, $ dels_db_arr,lar_gamma_arr, - $ delta_arr + $ gamma_sol_arr,gamma_est_arr COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas - TYPE(result_type) :: results(8) ! Assuming max 8 rational surfaces - - REAL(r8), DIMENSION(:), ALLOCATABLE :: gammafac_arr,growthrates, - $ growthrate_err - NAMELIST/slayer_input/input_flag,infile, - $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,omega,l_n,l_t, - $ qval,sval,bt,rs,R0,zeff,mu_i,inQ,inQ_e, - $ inQ_i,inpr,inpr_prof,inpe,inc_beta,inds, - $ intau,inlu,Q0,delta_n_p,ingam - NAMELIST/slayer_control/inum,jnum,knum,ReQ_num,ImQ_num, - $ scan_radius,QPscan_flag,QPscan2_flag, - $ QPescan_flag,QDscan2_flag,Qbscan_flag,Qscan_flag, - $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, - $ layfac,Qratio,parflow_flag,peohmonly_flag,Pe_flag + $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, + $ l_t,l_n,qval,mu_i,zeff,dr_val,chi_prof,inpr,inpe,inQ, + $ inQ_e,inQ_i,inc_beta,inds,intau,Q0,delta_prime,delta_n_p, + $ ingamma + NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_radius, + $ read_eq,fitz_flag,QPscan_flag,Qscan_flag,QPescan_flag, + $ Qbscan_flag,onscan_flag,otscan_flag,ntscan_flag, + $ nbtscan_flag,parflow_flag,peohmonly_flag,Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ stability_flag,lar_gamma_eq_flag,lar_gamma_flag, - $ fitz_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, - $ compress_deltas,bal_flag + $ est_gamma_flag,match_gamma_flag,stability_flag, + $ stabscan_flag,br_th_flag,bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- c set initial values. c----------------------------------------------------------------------- - mm=2 - nn=1 - mr = real(mm,4) - nr = real(nn,4) - n_e=1e19 - t_e=1e3 - t_i=1e3 - omega=1e4 - l_n=2e-1 - l_t=2e-1 - qval=2.0 - sval=2.0 - bt=1.0 - rs=0.5 - R0=1.0 - mu_i=2.0 - zeff=2.0 - inQ=20.0 ! Q=23.0 for DIII-D example. - inQ_e=2.0 ! Q_e=2.0 for DIII-D example. - inQ_i=-2.6 ! Q_i=-2.6 for DIII-D example. - inpr=0.5 ! 0.5 for DIII-D example. - inpr_prof=1.0 - inpe=0.1 !I added this - inc_beta=0.7 ! c_beta=0.7 for DIII-D example. - inds=6.0 ! 6.0 for DIII-D example. - intau=1.0 ! 1.0 for DIII-D example. - inlu=1e8 - Q0=4.0 - delta_n_p=(1e-2,1e-2) - ingam=(0.0,1e-2) + mm=0.0 + nn=0.0 + mr = 0.0!real(mm,4) + nr = 0.0!real(nn,4) + n_e=0.0 + t_e=0.0 + t_i=0.0 + omega=0.0 + l_n=0.0 + l_t=0.0 + qval=0.0 + sval=0.0 + bt=0.0 + rs=0.0 + R0=0.0 + mu_i=0.0 + zeff=0.0 + dr_val=0.0 + inQ=0.0 + inQ_e=0.0 + inQ_i=0.0 + inpr=0.0 + chi_prof=0.0 + inpe=0.0 + inc_beta=0.0 + inds=0.0 + intau=0.0 + inlu=0.0 + Q0=0.0 + chi=0.0 + dr_val=0.0 + gamma_fac=0.0 + delta_prime=(0.0,0.0) + delta_n_p=(0.0,0.0) + ingamma=(0.0,0.0) inum=400 ! resolution to find error field thresholds. jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. - ReQ_num=350 ! resolution for stab. scan along Re(Q) axis - ImQ_num=350 ! resolution for stab. scan along Im(Q) axis - scan_radius = 3 + Q_num=100 ! resolution for stab. scan along Re(Q) axis + scan_radius = 2.0 in_unit=1 out_unit=2 out2_unit=3 @@ -146,6 +130,10 @@ PROGRAM slayer bin_unit=5 bin_2d_unit=6 input_unit=7 + read_eq=.FALSE. + est_gamma_flag=.FALSE. + match_gamma_flag=.FALSE. + fitz_flag=.FALSE. QPscan_flag=.FALSE. ! scan (Q,P) space for delta and torque. QPescan_flag=.FALSE. ! scan (Q,Pe) space for delta and torque. Qbscan_flag=.FALSE. ! scan (Q,beta) space for delta and torque. @@ -158,7 +146,6 @@ PROGRAM slayer parflow_flag=.FALSE. PeOhmOnly_flag=.TRUE. Pe_flag=.FALSE. - Pe_flag=.FALSE. params_flag=.TRUE. input_flag=.FALSE. infile="" @@ -173,12 +160,7 @@ PROGRAM slayer bal_flag=.FALSE. stability_flag=.FALSE. stabscan_flag=.FALSE. - stabscan_eq_flag=.FALSE. - lar_gamma_flag=.FALSE. - fitz_gamma_flag=.FALSE. - lar_gamma_eq_flag=.FALSE. br_th_flag=.FALSE. - compress_deltas=.FALSE. c----------------------------------------------------------------------- c read slayer.in. c----------------------------------------------------------------------- @@ -202,7 +184,7 @@ PROGRAM slayer c calculate parameters as needed. c----------------------------------------------------------------------- IF (params_flag) THEN - CALL params(n_e,t_e,t_i,omega, + CALL params(n_e,t_e,t_i,omega,chi,dr_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -217,12 +199,14 @@ PROGRAM slayer c----------------------------------------------------------------------- c calculate basic delta, torque, balance, error fields. c----------------------------------------------------------------------- + IF (.NOT. (match_gamma_flag)) THEN delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) psi0=1.0/ABS(delta+delta_n_p) ! a.u. jxb=-AIMAG(1.0/(delta+delta_n_p)) ! a.u. WRITE(*,*)"delta=",delta WRITE(*,*)"psi0=",psi0 WRITE(*,*)"jxb=",jxb + ENDIF c----------------------------------------------------------------------- c calculate parameters as needed. c----------------------------------------------------------------------- @@ -248,7 +232,7 @@ PROGRAM slayer mr=REAL(mms(k)) nr=REAL(nns(k)) inpr=prs(k) - CALL params(n_es(k),t_es(k),t_is(k),omegas(k), + CALL params(n_es(k),t_es(k),t_is(k),omegas(k),chi,dr_val, $ l_ns(k),l_ts(k),qvals(k),svals(k),bts(k),rss(k),R0s(k), $ mu_is(k),zeffs(k),params_check) inQ=Q @@ -306,288 +290,240 @@ PROGRAM slayer c----------------------------------------------------------------------- c LAR (cylindrical) growthrates via restive layer thickness c----------------------------------------------------------------------- - IF (lar_gamma_eq_flag) THEN + IF (est_gamma_flag) THEN + WRITE(*,*)">>> Estimating growth rate" - ! propagate inpr value to inpr_prof if inpr_prof is turned off - IF (inpr_prof(1) < 0) THEN - inpr_prof = inpr - END IF + IF (read_eq) THEN - CALL build_inputs(infile,ncfile,inpr_prof, - $ inpe,Pe_flag,qval_arr,psi_n_rational, - $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, - $ inds_arr,ind_beta_arr,D_beta_norm_arr, - $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr) - - WRITE(*,*)"Safety factor values=",qval_arr - WRITE(*,*)"inQ values=",inQ_arr - WRITE(*,*)"Prantdl numbers=",inpr_arr - WRITE(*,*)"Electron viscosities=",inpe_arr - WRITE(*,*)"Omega ExB values=",omegas_arr - WRITE(*,*)"outer region real deltaprimes=",Re_deltaprime_arr - WRITE(*,*)"outer region imag deltaprimes=",Im_deltaprime_arr - WRITE(*,*)"inQ_e_arr=",inQ_e_arr - WRITE(*,*)"ind_beta_arr=",ind_beta_arr - WRITE(*,*)"D_beta_norm_arr=",D_beta_norm_arr - WRITE(*,*)"intau_arr=",intau_arr - WRITE(*,*)"gammafac_arr=",gammafac_arr + CALL build_inputs(infile,ncfile,chi_prof,qval_arr, + $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, + $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, + $ P_perp_arr,omegas_arr,gammafac_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr) - n_k = SIZE(qval_arr) + n_k = SIZE(qval_arr) + ELSE + n_k = 1 + mr = mm + nr = nn - ALLOCATE(lar_gamma_arr(n_k),dels_db_arr(n_k),delta_arr(n_k)) + CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + IF (ABS(inQ) > 0.0) THEN + Q = inQ ! NAMELIST + END IF + IF (ABS(inQ_e) > 0.0) THEN + Q_e = inQ_e ! NAMELIST + END IF + IF (ABS(inQ_i) > 0.0) THEN + Q_i = inQ_i ! NAMELIST + END IF + IF (inpr > 0.0) THEN + pr = inpr ! NAMELIST + END IF + IF (intau > 0.0) THEN + tau = intau ! NAMELIST + END IF + IF (inds > 0.0) THEN + D_norm = inds ! NAMELIST + END IF + + qval_arr = (/ qval /) + omegas_arr = (/ omega /) + Q_arr = (/ Q /) + Q_e_arr = (/ Q_e /) + Q_i_arr = (/ Q_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ REAL(delta_prime) /) + Im_deltaprime_arr = (/ AIMAG(delta_prime) /) + P_perp_arr = (/ P_perp /) + tau_arr = (/ tau /) + D_norm_arr = (/ D_norm /) + d_beta_arr = (/ d_beta /) + delta_eff = REAL(delta_prime)-delta_crit + gamma_fac = (rs*delta_eff)/tau_r + gammafac_arr = (/ gamma_fac /) + END IF + + ALLOCATE(gamma_est_arr(n_k),dels_db_arr(n_k)) DO k=1,n_k - WRITE(*,*) "Calculating growth rate on q=", qval_arr(k), - $ " rational surface" + WRITE(*,*) "Calculating growth rate estimate on q=", + $ qval_arr(k)," rational surface" - dels_db=riccati_del_s(inQ_arr(k),inQ_e_arr(k), - $ inQ_i_arr(k),inpr_arr(k),inc_beta_arr(k), - $ D_beta_norm_arr(k),intau_arr(k), - $ 5.0*D_beta_norm_arr(k)) + dels_db=riccati_del_s(Q_arr(k),Q_e_arr(k), + $ Q_i_arr(k),P_perp_arr(k),c_beta_arr(k), + $ D_norm_arr(k),tau_arr(k), + $ 5.0*D_norm_arr(k)) - del_s = dels_db * ind_beta_arr(k) - lar_gamma = gammafac_arr(k)/del_s + del_s = dels_db * d_beta_arr(k) - lar_gamma_arr(k) = lar_gamma + gamma_est_arr(k) = gammafac_arr(k)/del_s dels_db_arr(k) = dels_db + WRITE(*,*)"Growth rate estimate=", + $ REAL(gamma_est_arr(k))," [Hz]" ENDDO - WRITE(*,*)"Calling slayer_netcdf_out" - WRITE(*,*)"qval_arr=",qval_arr - WRITE(*,*)"omegas_arr=",omegas_arr - WRITE(*,*)"inQ_arr=",inQ_arr - WRITE(*,*)"inQ_e_arr=",inQ_e_arr - WRITE(*,*)"inQ_i_arr=",inQ_i_arr - WRITE(*,*)"ind_beta_arr=",ind_beta_arr - WRITE(*,*)"D_beta_norm_arr=",D_beta_norm_arr - WRITE(*,*)"inpr_arr=",inpr_arr - WRITE(*,*)"psi_n_rational=",psi_n_rational - WRITE(*,*)"lu_arr=",lu_arr - WRITE(*,*)"Re_deltaprime_arr=",Re_deltaprime_arr - WRITE(*,*)"Im_deltaprime_arr=",Im_deltaprime_arr - WRITE(*,*)"dels_db_arr=",dels_db_arr - WRITE(*,*)"lar_gamma_arr=",lar_gamma_arr - - br_th = 0.0 - - CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, - $ fitz_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr,results) - stop + IF (.NOT. (match_gamma_flag)) THEN + gamma_sol_arr = (/0./) + CALL output_gamma(est_gamma_flag,qval_arr, + $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, + $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ re_trace,im_trace) + END IF ENDIF c----------------------------------------------------------------------- c LAR (cylindrical) growthrates via restive layer thickness c----------------------------------------------------------------------- - IF (lar_gamma_flag) THEN - WRITE(*,*)"intau=",intau - WRITE(*,*)"inQ=",inQ - WRITE(*,*)"Prantdl numbers=",inpr - WRITE(*,*)"Electron viscosities=",inpe - WRITE(*,*)"inQ_e=",inQ_e - WRITE(*,*)"inds=",inds - WRITE(*,*)"inc_beta=",inc_beta - - WRITE(*,*)"Calculating LAR growth rate" - - CALL params(n_e,t_e,t_i,omega, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + IF (match_gamma_flag) THEN + WRITE(*,*)">>> Calculating asymptotically matched growth rate" + IF (read_eq) THEN - IF (inQ > 0.0) THEN - Q = inQ ! NAMELIST - END IF - IF (inQ_e > 0.0) THEN - Q_e = inQ_e ! NAMELIST - END IF - IF (inQ_i > 0.0) THEN - Q_i = inQ_i ! NAMELIST - END IF - IF (inpr > 0.0) THEN - pr = inpr ! NAMELIST - END IF - IF (intau > 0.0) THEN - tau = intau ! NAMELIST - END IF - IF (inds > 0.0) THEN - D_beta_norm = inds ! NAMELIST - END IF - - qval_arr = (/ qval /) - omegas_arr = (/ omega /) - inQ_arr = (/ inQ /) - inQ_e_arr = (/ inQ_e /) - inQ_i_arr = (/ inQ_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ REAL(delta_n_p) /) - Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) - inpr_arr = (/ inpr /) - D_beta_norm_arr = (/ D_beta_norm /) + CALL build_inputs(infile,ncfile,chi_prof,qval_arr, + $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, + $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, + $ P_perp_arr,omegas_arr,gammafac_arr, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr) - WRITE(*,*)"D_beta_norm = ",D_beta_norm - !WRITE(*,*)"inds = ",inds + n_k = SIZE(qval_arr) - !WRITE(*,*)"lar_gamma inQ = ",inQ - - dels_db=riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta, - $ D_beta_norm,intau,5.0*D_beta_norm) - - !WRITE(*,*)"dels_db() call successful" - - tau_r = layfac !!!! CRITICAL - WRITE(*,*)"tau_r = ",tau_r - WRITE(*,*)"deltaprime = ",REAL(delta_n_p) - WRITE(*,*)"rs = ",rs + ELSE + n_k = 1 - !eta = 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - !tau_r = mu0*rs**2.0/eta ! resistive time scale + ! Use namelist kinetic inputs instead of equilibrium files + CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - del_s = dels_db * d_beta - lar_gamma = (REAL(delta_n_p)/tau_r) * (rs/del_s) + ! Override desired normalized parameters + IF (ABS(inQ) > 0.0) THEN + Q = inQ ! NAMELIST + END IF + IF (ABS(inQ_e) > 0.0) THEN + Q_e = inQ_e ! NAMELIST + END IF + IF (ABS(inQ_i) > 0.0) THEN + Q_i = inQ_i ! NAMELIST + END IF + IF (inpr > 0.0) THEN + P_perp = inpr ! NAMELIST + END IF + IF (intau > 0.0) THEN + tau = intau ! NAMELIST + END IF + IF (inds > 0.0) THEN + D_norm = inds ! NAMELIST + END IF + + qval_arr = (/ qval /) + omegas_arr = (/ omega /) + Q_arr = (/ Q /) + Q_e_arr = (/ Q_e /) + Q_i_arr = (/ Q_i /) + psi_n_rational = (/ 0.0 /) + Re_deltaprime_arr = (/ REAL(delta_prime) /) + Im_deltaprime_arr = (/ AIMAG(delta_prime) /) + delta_crit_arr = (/ delta_crit /) + P_perp_arr = (/ P_perp /) + tau_arr = (/ tau /) + D_norm_arr = (/ D_norm /) + d_beta_arr = (/ d_beta /) + gammafac_arr = (/ gamma_fac /) + c_beta_arr = (/ c_beta /) + lu_arr = (/ lu /) + Qconv_arr = (/ tauk /) + END IF + + ALLOCATE(gamma_sol_arr(n_k)) - ind_beta_arr = (/ d_beta /) - dels_db_arr = (/ dels_db /) - lar_gamma_arr = (/ lar_gamma /) + DO k=1,n_k + WRITE(*,*) "Calculating growth rates on q=", + $ qval_arr(k)," rational surface" + + Q_e = Q_e_arr(k) + Q_i = Q_i_arr(k) + P_perp = P_perp_arr(k) + tau = tau_arr(k) + D_norm = D_norm_arr(k) + c_beta = c_beta_arr(k) + tauk = Qconv_arr(k) + + ! (Deltaprime - delta_crit)/S^1/3 + delta_eff = (Re_deltaprime_arr(k) - + $ delta_crit_arr(k))/(lu_arr(k)**(1.0/3.0)) + pe = 0.0 + + ALLOCATE(re_trace(100),im_trace(100)) + re_trace = 0.0 + im_trace = 0.0 + n_trace = 1 + + IF (fitz_flag) THEN + g_r = 0.0 + g_i = -Q_e + re_trace(1) = 0.0 + im_trace(1) = -Q_e + ELSE + g_i = 0.0 + g_r = -Q_e + im_trace(1) = 0.0 + re_trace(1) = -Q_e + END IF - WRITE(*,*)"dels_db=",dels_db - WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 - WRITE(*,*)"growth rate=",lar_gamma + CALL newton_root(g_r,g_i,1,fitz_flag) - br_th = 0.0 + WRITE(*,*)"Success! growth rate = ", + $ g_r/tauk," [Hz]" - CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, - $ fitz_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr,results) + CALL shrink_array(re_trace, n_trace) + CALL shrink_array(im_trace, n_trace) - stop - ENDIF -c----------------------------------------------------------------------- -c LAR (cylindrical) growthrates via restive layer thickness -c----------------------------------------------------------------------- - IF (fitz_gamma_flag) THEN - WRITE(*,*)"Calculating Fitzpatrick inner delta" + re_trace = re_trace/tauk + im_trace = im_trace/tauk - ! CALL params(n_e,t_e,t_i,omega, - !$ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + gamma_sol_arr(k) = g_r/tauk + ENDDO - IF (inQ > 0.0) THEN - Q = inQ ! NAMELIST - END IF - IF (inQ_e > 0.0) THEN - Q_e = inQ_e ! NAMELIST - END IF - IF (inQ_i > 0.0) THEN - Q_i = inQ_i ! NAMELIST - END IF - IF (inpr > 0.0) THEN - pr = inpr ! NAMELIST - END IF - IF (intau > 0.0) THEN - tau = intau ! NAMELIST - END IF - IF (inds > 0.0) THEN - D_beta_norm = inds ! NAMELIST + IF (.NOT. (est_gamma_flag)) THEN + d_beta_arr = (/ 0. /) + dels_db_arr = (/ 0. /) END IF - WRITE(*,*)"intau=",tau - WRITE(*,*)"inQ=",Q - WRITE(*,*)"inQ_e=",Q_e - WRITE(*,*)"inQ_i=",Q_i - WRITE(*,*)"Prantdl number=",pr - WRITE(*,*)"inQ_e=",inQ_e - WRITE(*,*)"D_beta_norm=",D_beta_norm - WRITE(*,*)"inc_beta=",inc_beta - - qval_arr = (/ qval /) - omegas_arr = (/ omega /) - inQ_arr = (/ inQ /) - inQ_e_arr = (/ inQ_e /) - inQ_i_arr = (/ inQ_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ 0.0 /) - Im_deltaprime_arr = (/ 0.0 /) - inpr_arr = (/ inpr /) - D_beta_norm_arr = (/ D_beta_norm /) - - ! INPUT GAMMA = delta_n_p - - deltaprim = REAL(delta_n_p) - g_tmp = ingam - WRITE(*,*)"g_tmp = ",g_tmp + IF (stabscan_flag) THEN + max_points = 50*50 + scan_radius = 1.5 - delta=riccati_f(g_tmp,Q_e,Q_i,pr, - $ D_beta_norm,tau) - - WRITE(*,*)"Fitz delta = ",delta - - g_r = 0.0 - g_i = -Q_e - CALL NewtonRoot(g_r, g_i, 1) - - lar_gamma = g_r - WRITE(*,*)"Fitz gamma success! growth rate = ",g_r - - ind_beta_arr = (/ 0. /) - dels_db_arr = (/ 0. /) ! USING DELS_DB_ARR FOR DELTA - delta_arr = (/ delta /) ! USING DELS_DB_ARR FOR DELTA - lar_gamma_arr = (/ g_r /) - - !WRITE(*,*)"dels_db=",dels_db - !WRITE(*,*)"[mm] del_s=",dels_db*d_beta*1000 - !WRITE(*,*)"growth rate=",lar_gamma - - br_th = 0.0 - - max_points = 50*50 - scan_radius = 1.5 - - ! Calculate step sizes -c ALLOCATE(results(1)%inQs(max_points), -c $ results(1)%iinQs(max_points)) -c ALLOCATE(results(1)%Re_deltas(max_points), -c $ results(1)%Im_deltas(max_points)) - ing_step = (2.0 * scan_radius) / (200 - 1) - count = 0 + ing_step = (2.0 * scan_radius) / (Q_num - 1) + count = 0 - ALLOCATE(inQs(1:201),iinQs(1:200)) - ALLOCATE(deltas(1:201,1:200)) - - DO i = 1, 201 - DO j = 1, 200 - ing_coarse = -scan_radius + (i - 1) * ing_step - iing_coarse = -scan_radius + (j - 1) * ing_step - ! Evaluate riccati function - g_tmp = CMPLX(ing_coarse,iing_coarse) - delta=riccati_f(g_tmp, - $ Q_e,Q_i,pr,D_beta_norm,tau) - !delta_real = REAL(delta) - !delta_imag = AIMAG(delta) - - !count = count + 1 - !results(1)%inQs(count) = ing_coarse - !results(1)%iinQs(count) = iing_coarse - !results(1)%Re_deltas(count) = delta_real - !results(1)%Im_deltas(count) = delta_imag - inQs(i) = ing_coarse - iinQs(j) = iing_coarse - deltas(i,j) = delta - - END DO - END DO - - OPEN(UNIT=out_unit,FILE="slayer_stability_n1.out", - $ STATUS="UNKNOWN") + ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) + ALLOCATE(deltas(1:(Q_num+1),1:Q_num)) + + DO i = 1, (Q_num+1) + DO j = 1, Q_num + ing_coarse = -scan_radius + (i - 1) * ing_step + iing_coarse = -scan_radius + (j - 1) * ing_step + ! Evaluate riccati function + g_tmp = CMPLX(ing_coarse,iing_coarse) + IF (fitz_flag) THEN + delta=riccati_f(g_tmp) + ELSE + delta=riccati(iing_coarse,Q_e,Q_i,P_perp, + $ c_beta,D_norm,tau,pe, + $ iinQ=ing_coarse) + END IF + inQs(i) = ing_coarse + iinQs(j) = iing_coarse + deltas(i,j) = delta + ENDDO + ENDDO + + OPEN(UNIT=out_unit,FILE="slayer_stability_n"// + $ TRIM(sn)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" DO i=1,201 @@ -599,125 +535,14 @@ PROGRAM slayer ENDDO CLOSE(out_unit) - CALL output_lar_gamma(lar_gamma_eq_flag,lar_gamma_flag, - $ fitz_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,qval_arr, - $ omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,ind_beta_arr, - $ D_beta_norm_arr,inpr_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ lu_arr,delta_arr,lar_gamma_arr,results) - - stop - ENDIF -c----------------------------------------------------------------------- -c TEST GAMMA_MATCH IN GSLAYER.F, FOR TESTING ONLY -c----------------------------------------------------------------------- - IF (stabscan_eq_flag) THEN - !WRITE(*,*)"infile=",infile - !WRITE(*,*)"ncfile=",ncfile - - ! propagate inpr value to inpr_prof if inpr_prof is turned off - IF (inpr_prof(1) < 0) THEN - inpr_prof = inpr - END IF - - CALL build_inputs(infile,ncfile,inpr_prof, - $ inpe,Pe_flag,qval_arr,psi_n_rational, - $ inQ_arr,inQ_e_arr,inQ_i_arr,inc_beta_arr, - $ inds_arr,ind_beta_arr,D_beta_norm_arr, - $ intau_arr,inQ0_arr,inpr_arr,inpe_arr, - $ omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr) - - WRITE(*,*)"Safety factor values=",qval_arr - WRITE(*,*)"inQ values=",inQ_arr - WRITE(*,*)"Prantdl numbers=",inpr_arr - WRITE(*,*)"Electron viscosities=",inpe_arr - WRITE(*,*)"Omega ExB values=",omegas_arr - WRITE(*,*)"outer region real deltaprimes=",Re_deltaprime_arr - WRITE(*,*)"outer region imag deltaprimes=",Im_deltaprime_arr - WRITE(*,*)"inQ_e_arr=",inQ_e_arr - WRITE(*,*)"inds_arr=",inds_arr - WRITE(*,*)"intau_arr=",intau_arr - n_k = SIZE(qval_arr) - - DO k=1,n_k - WRITE(*,*) "Finding roots on q=", qval_arr(k), - $ " rational surface" - - CALL growthrate_scan(qval_arr(k),lu_arr(k),inQ_arr(k), - $ inQ_e_arr(k),inQ_i_arr(k),inc_beta_arr(k),inds_arr(k), - $ intau_arr(k),inQ0_arr(k),inpr_arr(k),inpe_arr(k), - $ scan_radius,reQ_num,compress_deltas, - $ Re_deltaprime_arr(k),results(k)) - WRITE(*,*)"Exited growthrate_scan" - - ENDDO - WRITE(*,*)"Calling slayer_netcdf_out" - - br_th = 0.0 - WRITE(*,*)"Successfully entered output_lar_gamma()" - WRITE(*,*)"qval_arr = ",qval_arr - - CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag, - $ fitz_gamma_flag,stabscan_eq_flag,stabscan_flag,br_th_flag, - $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, - $ ind_beta_arr, - $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) -c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, -c $ stabscan_eq_flag,stabscan_flag,br_th_flag) - stop - ENDIF -c----------------------------------------------------------------------- -c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY -c----------------------------------------------------------------------- - IF (stabscan_flag) THEN - WRITE(*,*)"intau=",intau - WRITE(*,*)"inQ=",inQ - WRITE(*,*)"Prantdl numbers=",inpr - WRITE(*,*)"Electron viscosities=",inpe - WRITE(*,*)"inQ_e=",inQ_e - WRITE(*,*)"inds=",inds - WRITE(*,*)"inc_beta=",inc_beta - - WRITE(*,*)"running analytic scan" - - qval_arr = (/ qval /) - omegas_arr = (/ omega /) - inQ_arr = (/ inQ /) - inQ_e_arr = (/ inQ_e /) - inQ_i_arr = (/ inQ_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ REAL(delta_n_p) /) - Im_deltaprime_arr = (/ AIMAG(delta_n_p) /) - inpr_arr = (/ inpr /) - - - CALL params(n_e,t_e,t_i,omega, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - - D_beta_norm_arr = (/ D_beta_norm /) + ENDIF - CALL growthrate_scan(qval_arr(1),lu,inQ,inQ_e, - $ inQ_i,inc_beta,inds, - $ intau,inQ,inpr,inpe, - $ scan_radius,reQ_num,compress_deltas, - $ Re_deltaprime_arr(1),results(1)) - - WRITE(*,*)"allocations successful" - - br_th = 0.0 - CALL slayer_netcdf_out(SIZE(qval_arr),lar_gamma_eq_flag, - $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag, - $ stabscan_flag,br_th_flag, - $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr, - $ psi_n_rational,inpr_arr,br_th,Re_deltaprime_arr, - $ Im_deltaprime_arr,dels_db_arr,delta_arr,lu_arr, - $ ind_beta_arr, - $ D_beta_norm_arr,lar_gamma_arr,inQs,iinQs,results) + CALL output_gamma(est_gamma_flag,qval_arr, + $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, + $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ re_trace,im_trace) stop ENDIF c----------------------------------------------------------------------- @@ -727,7 +552,7 @@ PROGRAM slayer WRITE(*,*)"running br_th scan" - CALL params(n_e,t_e,t_i,omega, + CALL params(n_e,t_e,t_i,omega,chi,dr_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -787,30 +612,19 @@ PROGRAM slayer inQs = (/ 1.0 /) n_k = SIZE(qval_arr) - k=1 - IF (k==1) THEN - ALLOCATE(all_RE_deltas(1,1,n_k)) - ALLOCATE(all_Im_deltas(1,1,n_k)) - ALLOCATE(all_inQs(1,n_k)) - ALLOCATE(all_growthrates(n_k)) - ALLOCATE(all_growthrate_locs(n_k)) - ENDIF - all_Re_deltas(:,:,k) = 0.0 - all_Im_deltas(:,:,k) = 0.0 - all_inQs(:,k) = 0.0 qval_arr = (/ 3 /) inQs = (/ 1.0 /) inQs = (/ 1.0 /) omegas_arr = (/ 0.0 /) - inQ_arr = (/ inQ /) - inQ_e_arr = (/ inQ_e /) - inQ_i_arr = (/ inQ_i /) + Q_arr = (/ inQ /) + Q_e_arr = (/ inQ_e /) + Q_i_arr = (/ inQ_i /) psi_n_rational = (/ 0.0 /) Re_deltaprime_arr = (/ 0.0 /) Im_deltaprime_arr = (/ 0.0 /) - inpr_arr = (/ inpr /) + P_perp_arr = (/ inpr /) WRITE(*,*)"allocations successful" @@ -1215,7 +1029,7 @@ PROGRAM slayer DO k=0,knum ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) - CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k), + CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chi,dr_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1303,7 +1117,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e,t_e*ks(j,k),t_i*ks(j,k), - $ omega*js(j,k),l_n,l_t,qval,sval,bt, + $ omega*js(j,k),chi,dr_val,l_n,l_t,qval,sval,bt, $ rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1389,7 +1203,8 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e*ks(j,k),t_e*js(j,k),t_i*js(j,k),omega, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + $ chi,dr_val,l_n,l_t,qval,sval,bt,rs,R0,mu_i, + $ zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -1473,7 +1288,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) - CALL params(n_e*ks(j,k),t_e,t_i,omega, + CALL params(n_e*ks(j,k),t_e,t_i,omega,chi,dr_val, $ l_n,l_t,qval,sval,bt*js(j,k),rs,R0,mu_i,zeff, $ params_check) inQ=Q diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 1b61d38f0..5f299c9fd 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -48,48 +48,28 @@ END SUBROUTINE sl_check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, - $ lar_gamma_flag,fitz_gamma_flag,stabscan_eq_flag,stabscan_flag, - $ br_th_flag, - $ qval_arr,omegas_arr,inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational, - $ inpr_arr,br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db_arr, - $ delta_arr,lu_arr,ind_beta_arr,D_beta_norm_arr,lar_gamma_arr, - $ inQs,iinQs,results) - ! ds = D_beta_norm for lar growth rate routines - -c OPTIONAL -c br_th,Re_deltaprime_arr,Im_deltaprime_arr,dels_db,d_b,ds,lar_gamma, -c inQs,iinQs,results, + SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, + $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, + $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, + $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, + $ im_trace) INTEGER, INTENT(IN) :: msing - LOGICAL, INTENT(IN) :: lar_gamma_eq_flag,lar_gamma_flag, - $ stabscan_eq_flag,stabscan_flag,br_th_flag,fitz_gamma_flag - - INTEGER, ALLOCATABLE, DIMENSION(:) :: qval_arr - REAL(r8), DIMENSION(msing) :: gamma_arr - REAL(r8), DIMENSION(:), ALLOCATABLE :: omegas_arr, - $ inQ_arr,inQ_e_arr,inQ_i_arr,psi_n_rational,inpr_arr, - $ ind_beta_arr,D_beta_norm_arr,lu_arr - - REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_deltaprime_arr, - $ Im_deltaprime_arr,inQs,iinQs + LOGICAL, INTENT(IN) :: est_gamma_flag + INTEGER, INTENT(IN), DIMENSION(:) :: qval_arr + REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, + $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, + $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, + $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace + COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, + $ gamma_sol_arr,gamma_est_arr - REAL(r8) :: br_th - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dels_db_arr, - $ lar_gamma_arr,delta_arr + INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, + $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,inpr_id,S_id, + $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,tr_id,tr_dim - TYPE(result_type), INTENT(IN) :: results(8) - - INTEGER :: i,ncid,r_id,ReQ_dim,ImQ_dim,qsing_dim, - $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, - $ ReQ_id,ImQ_id,gamma_id,omegas_id,Q_id,Q_e_id,Q_i_id, - $ r_dim,pr_id, qr_id,shear_id,slice_id,inQs_id,S_id, - $ gamma_err_id,gamma_loc_id,roots_dim,Re_dp_id,Im_dp_id, - $ rdpp_id,idpp_id,inpr_id,br_th_id,dels_db_id,d_b_id, - $ inds_id,lar_gamma_id,qsing_id,indnorm_id - - INTEGER :: run, run_dimid, point_dimid, varids(4), - $ max_points + INTEGER :: run, run_dimid, point_dimid, varids(4) CHARACTER(64) :: ncfile LOGICAL, PARAMETER :: debug_flag = .FALSE. @@ -97,13 +77,6 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- -c i=0; ncid=0;r_id=0;ReQ_dim=0;ImQ_dim=0;qsing_dim=0;qsing_id=0; -c $ i_dim=0;m_dim=0;mo_dim=0;p_dim=0;i_id=0;m_id=0;mo_id=0;p_id=0; -c $ ReQ_id=0;ImQ_id=0;gamma_id=0;omegas_id=0;Q_id=0;Q_e_id=0; -c $ Q_i_id=0;r_dim=0;pr_id=0;qr_id=0;shear_id=0;slice_id=0; -c $ inQs_id=0;S_id=0;gamma_err_id=0;gamma_loc_id=0;roots_dim=0; -c $ Re_dp_id=0;Im_dp_id=0;rdpp_id=0;idpp_id=0;inpr_id=0; -c $ br_th_id=0;dels_db_id=0;d_b_id=0;inds_id=0;lar_gamma_id=0 IF(debug_flag) PRINT *,"Called slayer_netcdf_out" IF (nn<10) THEN WRITE(UNIT=sn,FMT='(I1)')nn @@ -120,7 +93,6 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, CALL sl_check( nf90_create(ncfile, $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) - max_points = maxval([(results(run)%count, run=1,msing)]) c ----------------------------------------------------------------------- c define global file attributes c ----------------------------------------------------------------------- @@ -142,8 +114,8 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, IF(msing>0)THEN CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) - CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, - $ qsing_id)) + CALL sl_check( nf90_def_dim(ncid, "step", SIZE(re_trace), + $ tr_dim) ) CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, @@ -160,323 +132,65 @@ SUBROUTINE slayer_netcdf_out(msing,lar_gamma_eq_flag, $ qsing_dim,inpr_id) ) CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, $ qsing_dim,qr_id) ) - CALL sl_check( nf90_def_dim(ncid, "points", max_points, - $ point_dimid) ) - - IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL sl_check( nf90_def_var(ncid,"growthrate",nf90_double, - $ qsing_dim,gamma_id)) - CALL sl_check(nf90_def_var(ncid,"growthrate_locs", - $ nf90_double,qsing_dim,gamma_loc_id)) - CALL sl_check( nf90_def_var(ncid, "Re_Qs", nf90_double, - $ [point_dimid, qsing_dim], varids(1)) ) - CALL sl_check( nf90_def_var(ncid, "Im_Qs", nf90_double, - $ [point_dimid, qsing_dim], varids(2)) ) - CALL sl_check( nf90_def_var(ncid, "Re_deltas",nf90_double, - $ [point_dimid, qsing_dim], varids(3)) ) - CALL sl_check( nf90_def_var(ncid, "Im_deltas",nf90_double, - $ [point_dimid, qsing_dim], varids(4)) ) - END IF - -c IF (fitz_gamma_flag) THEN -c CALL sl_check( nf90_def_var(ncid, "Re_Qs", nf90_double, -c $ [point_dimid, qsing_dim], varids(1)) ) -c CALL sl_check( nf90_def_var(ncid, "Im_Qs", nf90_double, -c $ [point_dimid, qsing_dim], varids(2)) ) -c CALL sl_check( nf90_def_var(ncid,"Re_deltas",nf90_double, -c $ [point_dimid, qsing_dim], varids(3)) ) -c CALL sl_check( nf90_def_var(ncid,"Im_deltas",nf90_double, -c $ [point_dimid, qsing_dim], varids(4)) ) -c END IF - - IF (br_th_flag) THEN - CALL sl_check( nf90_def_var(ncid,"br_th",nf90_double, - $ qsing_dim,br_th_id) ) - END IF END IF - IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL sl_check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, - $ qsing_dim,rdpp_id) ) - CALL sl_check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, - $ qsing_dim,idpp_id) ) - END IF - IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN + CALL sl_check( nf90_def_var(ncid,"D",nf90_double, + $ qsing_dim,Dnorm_id) ) + CALL sl_check( nf90_def_var(ncid,"Delta_prime_rational", + $ nf90_double,(/qsing_dim,i_dim/),dpp_id) ) + CALL sl_check( nf90_def_var(ncid,"Delta_crit_rational", + $ nf90_double,qsing_dim,dc_id) ) + + IF (est_gamma_flag) THEN CALL sl_check( nf90_def_var(ncid,"delta_s_d_b",nf90_double, $ (/qsing_dim,i_dim/),dels_db_id) ) CALL sl_check( nf90_def_var(ncid,"d_beta",nf90_double, $ qsing_dim,d_b_id) ) - CALL sl_check( nf90_def_var(ncid,"D_beta_norm",nf90_double, - $ qsing_dim,indnorm_id) ) - CALL sl_check( nf90_def_var(ncid,"growthrate_estimate", - $ nf90_double,(/qsing_dim,i_dim/),lar_gamma_id) ) - END IF - IF (fitz_gamma_flag) THEN - CALL sl_check( nf90_def_var(ncid,"delta_hat",nf90_double, - $ (/qsing_dim,i_dim/),dels_db_id) ) - CALL sl_check( nf90_def_var(ncid,"growthrate", - $ nf90_double,(/qsing_dim,i_dim/),lar_gamma_id) ) + CALL sl_check( nf90_def_var(ncid,"est. growth rate", + $ nf90_double,(/qsing_dim,i_dim/),ge_id) ) END IF + + CALL sl_check( nf90_def_var(ncid,"growth rate", + $ nf90_double,(/qsing_dim,i_dim/),gs_id) ) + CALL sl_check( nf90_def_var(ncid,"growth rate trace", + $ nf90_double,(/tr_dim,i_dim/),tr_id) ) ! end definitions CALL sl_check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- - CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) - CALL sl_check( nf90_put_var(ncid,Q_id, inQ_arr)) - CALL sl_check( nf90_put_var(ncid,Q_e_id, inQ_e_arr)) - CALL sl_check( nf90_put_var(ncid,Q_i_id, inQ_i_arr)) - CALL sl_check( nf90_put_var(ncid,S_id, (/lu/))) + CALL sl_check( nf90_put_var(ncid,qr_id, qval_arr)) CALL sl_check( nf90_put_var(ncid,pr_id, psi_n_rational)) - CALL sl_check( nf90_put_var(ncid,inpr_id, inpr_arr)) - !CALL sl_check( nf90_put_var(ncid,qr_id, qval_arr)) - - IF ((stabscan_eq_flag) .OR. (stabscan_flag)) THEN - CALL sl_check( nf90_put_var(ncid,rdpp_id, Re_deltaprime_arr)) - CALL sl_check( nf90_put_var(ncid,idpp_id, Im_deltaprime_arr)) - DO run = 1, msing - CALL sl_check( nf90_put_var(ncid,varids(1), - $ results(run)%inQs,start=[1, run], - $ count=[results(run)%count, 1]) ) - CALL sl_check( nf90_put_var(ncid,varids(2), - $ results(run)%iinQs,start=[1, run], - $ count=[results(run)%count, 1])) - CALL sl_check( nf90_put_var(ncid, varids(3), - $ results(run)%Re_deltas, start=[1, run], - $ count=[results(run)%count, 1]) ) - CALL sl_check( nf90_put_var(ncid, varids(4), - $ results(run)%Im_deltas, start=[1, run], - $ count=[results(run)%count, 1]) ) - END DO - END IF - -c IF (fitz_gamma_flag) THEN -c DO run = 1, msing -c run = 1 -c CALL sl_check( nf90_put_var(ncid,varids(1), -c $ results(run)%inQs,start=[1, run], -c $ count=[results(run)%count, 1]) ) -c CALL sl_check( nf90_put_var(ncid,varids(2), -c $ results(run)%iinQs,start=[1, run], -c $ count=[results(run)%count, 1])) -c CALL sl_check( nf90_put_var(ncid, varids(3), -c $ results(run)%Re_deltas, start=[1, run], -c $ count=[results(run)%count, 1]) ) -c CALL sl_check( nf90_put_var(ncid, varids(4), -c $ results(run)%Im_deltas, start=[1, run], -c $ count=[results(run)%count, 1]) ) -c END DO -c END IF - - IF (br_th_flag) THEN - CALL sl_check( nf90_put_var(ncid,br_th_id, (/ br_th /))) - END IF + CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) + CALL sl_check( nf90_put_var(ncid,S_id, lu_arr)) + CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) + CALL sl_check( nf90_put_var(ncid,Q_e_id, Q_e_arr)) + CALL sl_check( nf90_put_var(ncid,Q_i_id, Q_i_arr)) + CALL sl_check( nf90_put_var(ncid,inpr_id, P_perp_arr)) + CALL sl_check( nf90_put_var(ncid,Dnorm_id, D_norm_arr)) + + CALL sl_check( nf90_put_var(ncid,dpp_id, + $ RESHAPE((/Re_deltaprime_arr,Im_deltaprime_arr/), + $ (/msing,2/)))) + CALL sl_check( nf90_put_var(ncid,dc_id, delta_crit_arr)) - IF ((lar_gamma_flag) .OR. (lar_gamma_eq_flag)) THEN + IF (est_gamma_flag) THEN CALL sl_check( nf90_put_var(ncid,dels_db_id, $ RESHAPE((/REAL(dels_db_arr),AIMAG(dels_db_arr)/), $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,d_b_id,ind_beta_arr)) - CALL sl_check( nf90_put_var(ncid,indnorm_id, D_beta_norm_arr)) - CALL sl_check( nf90_put_var(ncid,lar_gamma_id, - $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), - $ (/msing,2/)))) - END IF - IF (fitz_gamma_flag) THEN - CALL sl_check( nf90_put_var(ncid,dels_db_id, - $ RESHAPE((/REAL(delta_arr),AIMAG(delta_arr)/), - $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,lar_gamma_id, - $ RESHAPE((/REAL(lar_gamma_arr),AIMAG(lar_gamma_arr)/), + CALL sl_check( nf90_put_var(ncid,d_b_id,d_beta_arr)) + CALL sl_check( nf90_put_var(ncid,ge_id, + $ RESHAPE((/REAL(gamma_est_arr),AIMAG(gamma_est_arr)/), $ (/msing,2/)))) END IF -c ----------------------------------------------------------------------- -c close file -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Closing netcdf file" - CALL sl_check( nf90_close(ncid) ) -c ----------------------------------------------------------------------- -c terminate. -c ----------------------------------------------------------------------- - RETURN - END SUBROUTINE slayer_netcdf_out -c ----------------------------------------------------------------------- -c declarations. -c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, - $ ni_arr,ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, - $ resm,nns_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr,inpe_arr, - $ inQ_arr,omegas_arr,omegas_e_arr,omegas_i_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr) - - INTEGER, INTENT(IN) :: msing - REAL(r8), DIMENSION(:), INTENT(IN) :: ne_arr,te_arr,ni_arr, - $ ti_arr,zeff_arr,shear,bt_arr,rs_arr, - $ R0_arr,inc_beta_arr,inds_arr,intau_arr,inpr_arr, - $ inpe_arr,omegas_arr,omegas_e_arr,omegas_i_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr,inQ_arr - INTEGER, DIMENSION(:), INTENT(IN) :: qval_arr,resm,nns_arr - - INTEGER :: i, ncid,r_id,qsing_dim,qsing_id,msing_id, - $ i_dim,ne_id,te_id,ni_id,ti_id,zeff_id,shear_id,bt_id,rs_id, - $ R0_id,resm_id,nns_id,inQ_id,inQ_e_id,inc_beta_id, - $ inds_id,qval_id,inQ_i_id,qr_id,indnorm_id - $ intau_id,inpr_id,inpe_id,omegas_id,Re_delta_id,Im_delta_id, - $ omegas_e_id,omegas_i_id,intau_id - - CHARACTER(64) :: ncfile - LOGICAL, PARAMETER :: debug_flag = .FALSE. - CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' -c ----------------------------------------------------------------------- -c set variables -c ----------------------------------------------------------------------- - ne_id=0 - te_id=0 - ni_id=0 - ti_id=0 - zeff_id=0 - shear_id=0 - bt_id=0 - rs_id=0 - R0_id=0 - resm_id=0 - nns_id=0 - inQ_id=0 - inQ_e_id=0 - inc_beta_id=0 - inds_id=0 - qval_id=0 - inQ_i_id=0 - qr_id=0 - intau_id=0 - inpr_id=0 - inpe_id=0 - omegas_id=0 - Re_delta_id=0 - Im_delta_id=0 - omegas_e_id=0 - omegas_i_id=0 - - IF(debug_flag) PRINT *,"Called slayer_netcdf_inputs" - IF (nn<10) THEN - WRITE(UNIT=sn,FMT='(I1)')nn - sn=ADJUSTL(sn) - ELSE - WRITE(UNIT=sn,FMT='(I2)')nn - ENDIF - ncfile = "slayer_inputs_n"//TRIM(sn)//".nc" - IF(debug_flag) PRINT *, ncfile -c ----------------------------------------------------------------------- -c open files -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Creating netcdf files" - CALL sl_check( nf90_create(ncfile, - $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) -c ----------------------------------------------------------------------- -c define global file attributes -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Defining netcdf globals" - CALL sl_check( nf90_put_att(ncid,nf90_global,"title", - $ "SLAYER outputs")) - CALL sl_check( nf90_put_att(ncid,nf90_global,"version", version)) - - IF(debug_flag) PRINT *," - Defining dimensions in netcdf" + CALL sl_check( nf90_put_var(ncid,gs_id, + $ RESHAPE((/REAL(gamma_sol_arr),AIMAG(gamma_sol_arr)/), + $ (/msing,2/)))) - IF(msing>0)THEN - !CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational - CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, - $ qsing_id)) - - CALL sl_check( nf90_def_var(ncid,"ne",nf90_double, - $ qsing_dim,ne_id)) - CALL sl_check( nf90_def_var(ncid,"te",nf90_double, - $ qsing_dim,te_id)) - CALL sl_check( nf90_def_var(ncid,"ni",nf90_double, - $ qsing_dim,ni_id)) - CALL sl_check( nf90_def_var(ncid,"ti",nf90_double, - $ qsing_dim,ti_id)) - CALL sl_check( nf90_def_var(ncid,"zeff",nf90_double, - $ qsing_dim,zeff_id)) - CALL sl_check( nf90_def_var(ncid,"shear",nf90_double, - $ qsing_dim,shear_id)) - CALL sl_check( nf90_def_var(ncid,"bt",nf90_double, - $ qsing_dim,bt_id)) - CALL sl_check( nf90_def_var(ncid,"rs",nf90_double, - $ qsing_dim,rs_id)) - CALL sl_check( nf90_def_var(ncid,"R0",nf90_double, - $ qsing_dim,R0_id)) - !CALL sl_check( nf90_def_var(ncid,"mu_i",nf90_double, - !$ qsing_dim,mu_i_id)) - CALL sl_check( nf90_def_var(ncid,"resm",nf90_int, - $ qsing_dim,resm_id)) - CALL sl_check( nf90_def_var(ncid,"nns_arr",nf90_int, - $ qsing_dim,nns_id)) - !CALL sl_check( nf90_def_var(ncid,"qval",nf90_int, - !$ qsing_dim,qval_id)) - CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, - $ qsing_dim,inQ_id)) - ! CALL sl_check( nf90_def_var(ncid,"Q_e",nf90_double, - !$ qsing_dim,inQ_e_id)) - ! CALL sl_check( nf90_def_var(ncid,"Q_i",nf90_double, - !$ qsing_dim,inQ_i_id)) - CALL sl_check( nf90_def_var(ncid,"c_beta",nf90_double, - $ qsing_dim,inc_beta_id)) - CALL sl_check( nf90_def_var(ncid,"ds",nf90_double, - $ qsing_dim,inds_id)) - CALL sl_check( nf90_def_var(ncid,"tau",nf90_double, - $ qsing_dim,intau_id)) - CALL sl_check( nf90_def_var(ncid,"pr",nf90_double, - $ qsing_dim,inpr_id)) - CALL sl_check( nf90_def_var(ncid,"pe",nf90_double, - $ qsing_dim,inpe_id)) - CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, - $ qsing_dim,omegas_id)) - CALL sl_check( nf90_def_var(ncid,"omegas_e",nf90_double, - $ qsing_dim,omegas_e_id)) - CALL sl_check( nf90_def_var(ncid,"omegas_i",nf90_double, - $ qsing_dim,omegas_i_id)) - CALL sl_check( nf90_def_var(ncid,"Re_deltaprime",nf90_double, - $ qsing_dim,Re_delta_id)) - CALL sl_check( nf90_def_var(ncid,"Im_deltaprime",nf90_double, - $ qsing_dim,Im_delta_id)) - ENDIF - ! define variables - IF(debug_flag) PRINT *," - Defining variables in netcdf" - ! end definitions - CALL sl_check( nf90_enddef(ncid) ) -c ----------------------------------------------------------------------- -c set variables -c ----------------------------------------------------------------------- - ! IF(debug_flag) PRINT *," - Putting profile variables in netcdf" - CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL sl_check( nf90_put_var(ncid,ne_id, ne_arr)) - CALL sl_check( nf90_put_var(ncid,ni_id, ni_arr)) - CALL sl_check( nf90_put_var(ncid,te_id, te_arr)) - CALL sl_check( nf90_put_var(ncid,ti_id, ti_arr)) - CALL sl_check( nf90_put_var(ncid,zeff_id, zeff_arr)) - CALL sl_check( nf90_put_var(ncid,shear_id, shear)) - CALL sl_check( nf90_put_var(ncid,bt_id, bt_arr)) - CALL sl_check( nf90_put_var(ncid,rs_id, rs_arr)) - CALL sl_check( nf90_put_var(ncid,R0_id, R0_arr)) - !CALL sl_check( nf90_put_var(ncid,mu_i_id, mu_i_arr)) - CALL sl_check( nf90_put_var(ncid,resm_id, resm)) - CALL sl_check( nf90_put_var(ncid,nns_id, nns_arr)) - !CALL sl_check( nf90_put_var(ncid,qval_id, qval_arr)) - CALL sl_check( nf90_put_var(ncid,inQ_id, inQ_arr)) - !CALL sl_check( nf90_put_var(ncid,inQ_e_id, inQ_e_arr)) - !CALL sl_check( nf90_put_var(ncid,inQ_i_id, inQ_i_arr)) - CALL sl_check( nf90_put_var(ncid,inc_beta_id, inc_beta_arr)) - CALL sl_check( nf90_put_var(ncid,inds_id, inds_arr)) - CALL sl_check( nf90_put_var(ncid,intau_id, intau_arr)) - CALL sl_check( nf90_put_var(ncid,inpr_id, inpr_arr)) - CALL sl_check( nf90_put_var(ncid,inpe_id, inpe_arr)) - CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) - CALL sl_check( nf90_put_var(ncid,omegas_e_id, omegas_e_arr)) - CALL sl_check( nf90_put_var(ncid,omegas_i_id, omegas_i_arr)) - CALL sl_check( nf90_put_var(ncid,Re_delta_id,Re_deltaprime_arr)) - CALL sl_check( nf90_put_var(ncid,Im_delta_id,Im_deltaprime_arr)) + CALL sl_check( nf90_put_var(ncid,tr_id, + $ RESHAPE((/re_trace,im_trace/), + $ (/SIZE(re_trace),2/)))) c ----------------------------------------------------------------------- c close file @@ -487,5 +201,5 @@ SUBROUTINE slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr, c terminate. c ----------------------------------------------------------------------- RETURN - END SUBROUTINE slayer_netcdf_inputs + END SUBROUTINE slayer_netcdf_out END MODULE slayer_netcdf_mod \ No newline at end of file diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 5be759f90..edd055ccc 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -43,93 +43,6 @@ SUBROUTINE check(stat) RETURN END SUBROUTINE check c----------------------------------------------------------------------- -c subprogram 2. issurfint. -c surface integration by simple method. copied from EQUIL -c----------------------------------------------------------------------- - FUNCTION issurfint(func,fs,inpsi,wegt,ave, - $ fsave,psave,jacs,delpsi,inr,ina,first) -c----------------------------------------------------------------------- -c declaration. -c----------------------------------------------------------------------- - !IMPLICIT NONE - INTEGER, INTENT(IN) :: fs,wegt,ave - REAL(r8), INTENT(IN) :: inpsi - REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func - - LOGICAL, INTENT(INOUT) :: first - INTEGER, INTENT(INOUT) :: fsave - REAL(r8), INTENT(INOUT) :: psave - REAL(r8),DIMENSION(0:),INTENT(INOUT) :: jacs,delpsi,inr,ina - INTEGER :: itheta, ix, iy - REAL(r8) :: issurfint - REAL(r8) :: rfac,ineta,injac,inarea - REAL(r8), DIMENSION(1,2) :: w - REAL(r8), DIMENSION(0:fs) :: z,thetas - REAL(r8), dimension(4) :: rzphi_f, rzphi_fx, rzphi_fy - - issurfint=0 - inarea=0 - ix = 0 - iy = 0 - IF(first .OR. inpsi/=psave .OR. fs/=fsave)THEN - psave = inpsi - fsave = fs - !first = .FALSE. - DO itheta=0,fs - thetas(itheta) = REAL(itheta,r8)/REAL(fs,r8) - ENDDO - DO itheta=0,fs-1 - CALL bicube_eval_external(rzphi, inpsi, thetas(itheta), 1, - $ ix, iy, rzphi_f, rzphi_fx, rzphi_fy) - rfac=SQRT(rzphi_f(1)) - ineta=twopi*(thetas(itheta)+rzphi_f(2)) - ina(itheta)=rfac - inr(itheta)=ro+rfac*COS(ineta) - z(itheta)=zo+rfac*SIN(ineta) - injac=rzphi_f(4) - jacs(itheta)=injac - w(1,1)=(1+rzphi_fy(2))*twopi**2*rfac*inr(itheta)/injac - w(1,2)=-rzphi_fy(1)*pi*inr(itheta)/(rfac*injac) - delpsi(itheta)=SQRT(w(1,1)**2+w(1,2)**2) - ENDDO - ENDIF - - IF (wegt==0) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ jacs(itheta)*delpsi(itheta)*func(itheta)/fs - ENDDO - ELSE IF (wegt==1) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs - ENDDO - ELSE IF (wegt==2) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs - ENDDO - ELSE IF (wegt==3) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs - ENDDO - ELSE - STOP "ERROR: issurfint wegt must be in [0,1,2,3]" - ENDIF - - IF (ave==1) THEN - DO itheta=0,fs-1 - inarea=inarea+jacs(itheta)*delpsi(itheta)/fs - ENDDO - issurfint=issurfint/inarea - ENDIF -c----------------------------------------------------------------------- -c terminate. -c----------------------------------------------------------------------- - RETURN - END FUNCTION issurfint -c----------------------------------------------------------------------- c subprogram 2. stride_netcdf_out. c Replicate stride.out information in netcdf format. c----------------------------------------------------------------------- @@ -149,27 +62,18 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id,prandtl_id,rs_id,rf_id + $ shear_id,resm_id,drr_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile REAL(r8) :: resnum,shear,respsi,resm_sing - REAL(r8), DIMENSION(msing) :: rs_array + REAL(r8), DIMENSION(msing) :: dr_rationals REAL(r8), DIMENSION(:), ALLOCATABLE :: rs_full TYPE(spline_type) :: sr REAL(r8), DIMENSION(0:mpsi) :: rhotor - - ! MINOR RADIUS INTEGRAL QUANTITIES - LOGICAL :: firstsur - REAL(r8), DIMENSION(0:512) :: my_unitfun - INTEGER :: my_fsave - REAL(r8) :: my_psave - REAL(r8), DIMENSION(:), ALLOCATABLE :: my_jacs,my_delpsi, - $ my_rsurf,my_asurf - REAL(r8) :: my_rfac,my_jac,a_surf INTEGER, DIMENSION(msing) :: resm @@ -193,35 +97,12 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) ep = CMPLX(epi, 0.0) ev = CMPLX(evi, 0.0) et = CMPLX(eti, 0.0) -c----------------------------------------------------------------------- -c loop across singular surfaces, evaluate minor radius -c----------------------------------------------------------------------- - !WRITE(*,*)"REACHED RS CALCULATION" - !rhotor(:) = SQRT(sq%fsi(:, 4)*twopi*psio / (pi * bt0)) ! effective minor radius in Callen - !CALL spline_alloc(sr,mpsi,1) - !sr%xs = sq%xs - !sr%fs(:, 1) = rhotor(:) - !CALL spline_fit(sr,"extrap") - ALLOCATE(rs_full(sq%mx+1)) - - mthsur = 512 ! Hardcoded, but this is a default value - ALLOCATE(my_jacs(0:mthsur),my_delpsi(0:mthsur), - $ my_rsurf(0:mthsur),my_asurf(0:mthsur)) + ! evaluate resistive interchange parameter on rational surfaces DO i=1,msing respsi = sing(i)%psifac - ! SURFACE INTEGRAL - firstsur = .TRUE. - my_unitfun = 1 - rs_array(i) = issurfint(my_unitfun,mthsur,respsi,3,1, - $ my_fsave,my_psave,my_jacs,my_delpsi,my_rsurf, - $ my_asurf,firstsur) - END DO - DO i=1,sq%mx+1 - respsi = sq%xs(i)!*psio - rs_full(i) = issurfint(my_unitfun,mthsur,respsi,3,1, - $ my_fsave,my_psave,my_jacs,my_delpsi,my_rsurf, - $ my_asurf,firstsur) + CALL spline_eval(locstab,respsi,0) + dr_rationals(i)=locstab%f(1)/respsi END DO c----------------------------------------------------------------------- c open files @@ -309,16 +190,10 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid,"r_prime",nf90_int,rp_dim,rp_id)) CALL check( nf90_def_var(ncid,"psi_n_rational",nf90_double, $ r_dim,pr_id) ) - CALL check( nf90_def_var(ncid,"rs_rational",nf90_double, - $ r_dim,rs_id) ) - CALL check( nf90_def_var(ncid,"flux_avg_rs",nf90_double, - $ p_dim,rf_id) ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) - CALL check( nf90_def_var(ncid, "prandtl", nf90_double, r_dim, - $ prandtl_id) ) CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, $ resm_id) ) ENDIF @@ -330,6 +205,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_def_var(ncid, "q", nf90_double, p_dim, q_id) ) CALL check( nf90_def_var(ncid, "di", nf90_double, p_dim, di_id) ) CALL check( nf90_def_var(ncid, "dr", nf90_double, p_dim, dr_id) ) + CALL check( nf90_def_var(ncid, "dr_rational", nf90_double, p_dim, + $ drr_id) ) CALL check( nf90_def_var(ncid, "ca1", nf90_double, p_dim, ca_id)) CALL check( nf90_def_var(ncid, "W_p_eigenvector", nf90_double, $ (/m_dim, mo_dim, i_dim/), wp_id) ) @@ -374,8 +251,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,pr_id, (/(sing(i)%psifac, $ i=1,msing)/)) ) - CALL check( nf90_put_var(ncid,rs_id, rs_array) ) - CALL check( nf90_put_var(ncid,rf_id, rs_full) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) @@ -389,8 +264,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) !ENDDO CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, - $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? - CALL check( nf90_put_var(ncid,prandtl_id, (/(0.0, $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? CALL check( nf90_put_var(ncid,resm_id, resm) ) ! CALL check( nf90_put_var(ncid,shear_id,shear) ) ! GPEC HAS DIFFERENT SHEAR CALC? @@ -403,6 +276,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) CALL check( nf90_put_var(ncid,q_id, sq%fs(:,4))) CALL check( nf90_put_var(ncid,di_id, locstab%fs(:,1)/sq%xs(:))) CALL check( nf90_put_var(ncid,dr_id, locstab%fs(:,2)/sq%xs(:))) + CALL check( nf90_put_var(ncid,drr_id, dr_rationals)) CALL check( nf90_put_var(ncid,ca_id, locstab%fs(:,4))) IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" From 54369b0eae8b7c3ff9ad1036207cdb15ad65b896 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 9 May 2025 19:35:54 -0400 Subject: [PATCH 038/305] bug and sign fixes --- input/slayer.in | 2 +- slayer/gslayer.f | 11 ++++++----- slayer/layerinputs.f | 6 +++--- slayer/params.f | 4 ++-- slayer/slayer.f | 17 ++++++++++------- slayer/slayer_netcdf.f | 16 ++++++++++++---- 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 1d8df39d0..23eb8a29b 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -45,7 +45,7 @@ jnum=100 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan along with the other. Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes - scan_radius=2.0 ! stability scan width for Re(Q),Im(Q) scan + scan_radius=1.5 ! stability scan width for Re(Q),Im(Q) scan read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation QPscan_flag=f ! scan (Q,P) space for delta and torque. diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 7d5e62a73..1863a4c09 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -185,8 +185,8 @@ SUBROUTINE output_gamma(est_gamma_flag,qval_arr, $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, - $ im_trace) + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, + $ re_trace,im_trace) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: est_gamma_flag @@ -194,7 +194,8 @@ SUBROUTINE output_gamma(est_gamma_flag,qval_arr, REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace + $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace, + $ Qconv_arr COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, $ gamma_sol_arr,gamma_est_arr @@ -204,8 +205,8 @@ SUBROUTINE output_gamma(est_gamma_flag,qval_arr, $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, - $ im_trace) + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, + $ re_trace,im_trace) END SUBROUTINE output_gamma c----------------------------------------------------------------------- diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index eb8b3e565..d5f13d5df 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -431,11 +431,11 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole tauk = Qconv - + ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega - Q_e=-Qconv*omega_e - Q_i=-Qconv*omega_i + Q_e=Qconv*omega_e + Q_i=Qconv*omega_i ! This is the most critical parameter ds=lu**(1.0/3.0)*rho_s/my_rs ! conversion based on Cole. diff --git a/slayer/params.f b/slayer/params.f index 2e2cf1138..ef79e0287 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -73,8 +73,8 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val, ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega - Q_e=-Qconv*omega_e - Q_i=-Qconv*omega_i + Q_e=Qconv*omega_e + Q_i=Qconv*omega_i ! This is the most critical parameter ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. diff --git a/slayer/slayer.f b/slayer/slayer.f index 5eca1a8f2..6d7bc1080 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -22,7 +22,7 @@ PROGRAM slayer CHARACTER(512) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,count, - $ Q_num,n_k,scan_radius,max_points + $ Q_num,n_k,scan_radius INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -291,6 +291,7 @@ PROGRAM slayer c LAR (cylindrical) growthrates via restive layer thickness c----------------------------------------------------------------------- IF (est_gamma_flag) THEN + WRITE(*,*)"------------------------------------------" WRITE(*,*)">>> Estimating growth rate" IF (read_eq) THEN @@ -371,7 +372,7 @@ PROGRAM slayer $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, $ re_trace,im_trace) END IF ENDIF @@ -379,6 +380,7 @@ PROGRAM slayer c LAR (cylindrical) growthrates via restive layer thickness c----------------------------------------------------------------------- IF (match_gamma_flag) THEN + WRITE(*,*)"------------------------------------------" WRITE(*,*)">>> Calculating asymptotically matched growth rate" IF (read_eq) THEN @@ -494,8 +496,9 @@ PROGRAM slayer END IF IF (stabscan_flag) THEN - max_points = 50*50 - scan_radius = 1.5 + WRITE(*,*)"------------------------------------------" + WRITE(*,*)">>> Running Re(Q),Im(Q) scan, radius=", + $ scan_radius ing_step = (2.0 * scan_radius) / (Q_num - 1) count = 0 @@ -526,8 +529,8 @@ PROGRAM slayer $ TRIM(sn)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" - DO i=1,201 - DO j=1,200 + DO i=1,Q_num+1 + DO j=1,Q_num WRITE(out_unit,'(1x,4(es17.8e3))') $ inQs(i),iinQs(j), $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) @@ -541,7 +544,7 @@ PROGRAM slayer $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, $ re_trace,im_trace) stop ENDIF diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 5f299c9fd..eb2ed6a0f 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -52,8 +52,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,re_trace, - $ im_trace) + $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, + $ re_trace,im_trace) INTEGER, INTENT(IN) :: msing LOGICAL, INTENT(IN) :: est_gamma_flag @@ -61,13 +61,15 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace + $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace, + $ Qconv_arr COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, $ gamma_sol_arr,gamma_est_arr INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,inpr_id,S_id, - $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,tr_id,tr_dim + $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,tr_id,tr_dim, + $ qsing_id,qc_id INTEGER :: run, run_dimid, point_dimid, varids(4) @@ -114,10 +116,14 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, IF(msing>0)THEN CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) + CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, + $ qsing_id)) CALL sl_check( nf90_def_dim(ncid, "step", SIZE(re_trace), $ tr_dim) ) CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) + CALL sl_check( nf90_def_var(ncid,"tau_k",nf90_double, + $ qsing_dim,qc_id)) CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, $ qsing_dim,Q_id)) CALL sl_check( nf90_def_var(ncid,"Q_e",nf90_double, @@ -159,10 +165,12 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- + CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) CALL sl_check( nf90_put_var(ncid,qr_id, qval_arr)) CALL sl_check( nf90_put_var(ncid,pr_id, psi_n_rational)) CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) CALL sl_check( nf90_put_var(ncid,S_id, lu_arr)) + CALL sl_check( nf90_put_var(ncid,qc_id, Qconv_arr)) CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) CALL sl_check( nf90_put_var(ncid,Q_e_id, Q_e_arr)) CALL sl_check( nf90_put_var(ncid,Q_i_id, Q_i_arr)) From 5a533c3df92ce28faed4b8830fa297191ef13439 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 21 May 2025 17:19:00 -0400 Subject: [PATCH 039/305] SLAYER print statement cleanups --- slayer/gslayer.f | 7 +++--- slayer/slayer.f | 49 ++++++++++++++++++++++++++---------------- slayer/slayer_netcdf.f | 3 +-- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 1863a4c09..aaf280c1d 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -199,8 +199,6 @@ SUBROUTINE output_gamma(est_gamma_flag,qval_arr, COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, $ gamma_sol_arr,gamma_est_arr - WRITE(*,*)"Successfully entered output_gamma()" - CALL slayer_netcdf_out(SIZE(qval_arr),est_gamma_flag, $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, @@ -424,8 +422,9 @@ SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) END IF IF (verbose .ne. 0) THEN - WRITE(*, '(" Q step = (", ES10.3, ", ", ES10.3,")" )') - $ g_r, g_i + WRITE(*, '(A, ES10.3, A, ES10.3, A, A, ES10.3)') + $ 'Q step = (', g_r, ' + ', + $ g_i, 'j )', ' Residual =', Residual ENDIF iter = iter + 1 diff --git a/slayer/slayer.f b/slayer/slayer.f index 6d7bc1080..e20e575bb 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -22,7 +22,7 @@ PROGRAM slayer CHARACTER(512) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,count, - $ Q_num,n_k,scan_radius + $ Q_num,n_k INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -32,7 +32,7 @@ PROGRAM slayer $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,stabscan_flag,read_eq,est_gamma_flag, $ match_gamma_flag,fitz_flag,br_th_flag - REAL(r8) :: n_e,t_e,t_i,omega,omega0, + REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_radius, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual @@ -349,8 +349,10 @@ PROGRAM slayer ALLOCATE(gamma_est_arr(n_k),dels_db_arr(n_k)) DO k=1,n_k - WRITE(*,*) "Calculating growth rate estimate on q=", - $ qval_arr(k)," rational surface" + WRITE(*,*) + WRITE(*,'(A,I0,A)') 'Calculating growth rate '// + $ 'estimate on q = ', + $ qval_arr(k),' rational surface' dels_db=riccati_del_s(Q_arr(k),Q_e_arr(k), $ Q_i_arr(k),P_perp_arr(k),c_beta_arr(k), @@ -361,8 +363,9 @@ PROGRAM slayer gamma_est_arr(k) = gammafac_arr(k)/del_s dels_db_arr(k) = dels_db - WRITE(*,*)"Growth rate estimate=", - $ REAL(gamma_est_arr(k))," [Hz]" + WRITE(*,*) + WRITE(*,'(A,F0.3,A)')'Growth rate estimate = ', + $ REAL(gamma_est_arr(k)),' [Hz]' ENDDO @@ -442,9 +445,9 @@ PROGRAM slayer ALLOCATE(gamma_sol_arr(n_k)) DO k=1,n_k - WRITE(*,*) "Calculating growth rates on q=", - $ qval_arr(k)," rational surface" - + WRITE(*,*) + WRITE(*,'(A,I0,A)') 'Calculating growth rates on q = ', + $ qval_arr(k),' rational surface' Q_e = Q_e_arr(k) Q_i = Q_i_arr(k) P_perp = P_perp_arr(k) @@ -477,17 +480,22 @@ PROGRAM slayer CALL newton_root(g_r,g_i,1,fitz_flag) - WRITE(*,*)"Success! growth rate = ", - $ g_r/tauk," [Hz]" + WRITE(*,*) + WRITE(*,'(A,F0.3,A)') 'Success! Growth rate = ', + $ g_r/tauk, ' [Hz]' CALL shrink_array(re_trace, n_trace) CALL shrink_array(im_trace, n_trace) - re_trace = re_trace/tauk - im_trace = im_trace/tauk - - gamma_sol_arr(k) = g_r/tauk - + IF (fitz_flag) THEN + re_trace = re_trace/tauk + im_trace = im_trace/tauk + gamma_sol_arr(k) = g_r/tauk + ELSE + re_trace = re_trace/tauk + im_trace = -im_trace/tauk + gamma_sol_arr(k) = -g_r/tauk + END IF ENDDO IF (.NOT. (est_gamma_flag)) THEN @@ -497,7 +505,8 @@ PROGRAM slayer IF (stabscan_flag) THEN WRITE(*,*)"------------------------------------------" - WRITE(*,*)">>> Running Re(Q),Im(Q) scan, radius=", + WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// + $ 'Im(Q)] scan with radius = ', $ scan_radius ing_step = (2.0 * scan_radius) / (Q_num - 1) @@ -520,7 +529,11 @@ PROGRAM slayer $ iinQ=ing_coarse) END IF inQs(i) = ing_coarse - iinQs(j) = iing_coarse + IF (fitz_flag) THEN + iinQs(j) = iing_coarse + ELSE + iinQs(j) = -iing_coarse + END IF deltas(i,j) = delta ENDDO ENDDO diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index eb2ed6a0f..4b45df4c1 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -109,9 +109,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, ! define dimensions IF(debug_flag) PRINT *," - Defining dimensions in netcdf" - WRITE(*,*)"netcdf msing=",msing !WRITE(*,*)"netcdf qval=",qval - WRITE(*,*)"netcdf qval_arr=",qval_arr + WRITE(*,*)">>> Writing results to NetCDF output file" IF(msing>0)THEN CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational From 832c9e3feae4b5f8eb8eb471ddc660b85b803147 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 11 Jun 2025 17:51:25 -0400 Subject: [PATCH 040/305] pre-merge updates --- slayer/gslayer.f | 6 +++--- slayer/params.f | 4 ++-- slayer/slayer.f | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index aaf280c1d..07a4282e4 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -380,7 +380,7 @@ SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.1 ! Max step size + REAL(r8), PARAMETER :: Smax = 0.05 ! Max step size INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations iter = 0 @@ -424,7 +424,7 @@ SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) IF (verbose .ne. 0) THEN WRITE(*, '(A, ES10.3, A, ES10.3, A, A, ES10.3)') $ 'Q step = (', g_r, ' + ', - $ g_i, 'j )', ' Residual =', Residual + $ g_i, 'j ),', ' Residual =', Residual ENDIF iter = iter + 1 @@ -450,7 +450,7 @@ SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, INTEGER :: i REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size - REAL(r8), PARAMETER :: Smax = 1.0d0 ! Max step size + REAL(r8), PARAMETER :: Smax = 0.05 ! Max step size REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations diff --git a/slayer/params.f b/slayer/params.f index ef79e0287..2e2cf1138 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -73,8 +73,8 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val, ! note Q depends on Qconv even if omega is fixed. Q=Qconv*omega - Q_e=Qconv*omega_e - Q_i=Qconv*omega_i + Q_e=-Qconv*omega_e + Q_i=-Qconv*omega_i ! This is the most critical parameter ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. diff --git a/slayer/slayer.f b/slayer/slayer.f index e20e575bb..438b5a2d8 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -446,8 +446,8 @@ PROGRAM slayer DO k=1,n_k WRITE(*,*) - WRITE(*,'(A,I0,A)') 'Calculating growth rates on q = ', - $ qval_arr(k),' rational surface' + WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', + $ qval_arr(k),' rational surface:' Q_e = Q_e_arr(k) Q_i = Q_i_arr(k) P_perp = P_perp_arr(k) From c1a7cd8ef8cf8c341e0e5cf8a8f7aad44325772c Mon Sep 17 00:00:00 2001 From: Jong-Kyu Park Date: Fri, 20 Jun 2025 13:55:17 +0900 Subject: [PATCH 041/305] A new branch for snu students to incorporate local developments into the develop branch. --- .gitignore | 10 ++++++++++ docs/nametag | 1 + match/ideal.f | 6 +++--- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 docs/nametag diff --git a/.gitignore b/.gitignore index 2c34ea433..abd406ae0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.mod *.pyc *~ +*.i *.out *.bin *.nc @@ -17,17 +18,26 @@ */penmpi */version.inc */*.original +*/make.log modovmc .cproject .idea/ +.vscode/ .project +.DS_Store bin/ docs/_build/ docs/examples/*/gal_solution.dat docs/examples/*/delta_gw.dat docs/examples/*/gal_solution_cut.dat +deps/bin/* +deps/hdf5 +deps/include/* +deps/lib/* +deps/share/* + *version.inc dcon/dcon diff --git a/docs/nametag b/docs/nametag new file mode 100644 index 000000000..618ff0687 --- /dev/null +++ b/docs/nametag @@ -0,0 +1 @@ +snu_offshoot \ No newline at end of file diff --git a/match/ideal.f b/match/ideal.f index 69f441bbe..deaaa3db3 100644 --- a/match/ideal.f +++ b/match/ideal.f @@ -508,7 +508,7 @@ SUBROUTINE ideal_chord REAL(r8) :: psi,theta,theta0,theta1,zlevel,z,zz0,zz1 REAL(r8), DIMENSION(0:mtheta) :: zz REAL(r8), DIMENSION(mstep,2) :: r,th0,z0,q - COMPLEX(r8), DIMENSION(mstep,2) :: xi_psi,b_psi,xi_norm,b_norm + COMPLEX(r8), DIMENSION(mstep,2) :: xi_psi,b_psi,xi_norm,b_norm c----------------------------------------------------------------------- c format statements. c----------------------------------------------------------------------- @@ -538,7 +538,7 @@ SUBROUTINE ideal_chord CALL bicube_eval(rzphi,psi,theta,0) zz(itheta)=zo-zlevel $ +SQRT(rzphi%f(1))*SIN(twopi*(theta+rzphi%f(2))) - ENDDO + ENDDO c----------------------------------------------------------------------- c find theta indices intersecting chord. c----------------------------------------------------------------------- @@ -619,7 +619,7 @@ SUBROUTINE ideal_chord jstep=jstep+1 ENDDO WRITE(debug_unit,10) - CALL bin_close(bin_unit) + CALL bin_close(bin_unit) CALL ascii_close(debug_unit) c----------------------------------------------------------------------- c terminate. From 633821bd984389b4ce5867cd6d5cd80658cd90ab Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 24 Jun 2025 17:10:20 -0400 Subject: [PATCH 042/305] small changes post merge --- slayer/gslayer.f | 6 ++---- slayer/layerinputs.f | 8 ++++---- slayer/makefile | 2 +- slayer/sglobal.f | 2 +- slayer/slayer.f | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 07a4282e4..a663ce3b4 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -17,8 +17,6 @@ MODULE gslayer_mod USE slayer_netcdf_mod - USE grid, ONLY : powspace,linspace - IMPLICIT NONE CONTAINS @@ -380,7 +378,7 @@ SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.05 ! Max step size + REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations iter = 0 @@ -450,7 +448,7 @@ SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, INTEGER :: i REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.05 ! Max step size + REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index d5f13d5df..50cb53064 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -3,13 +3,13 @@ MODULE layerinputs_mod USE inputs, ONLY : read_kin,read_equil,kin,chi1 USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, $ spline_dealloc,spline_int,spline_fit - USE sglobal_mod, ONLY: m_p, chag, lnLamb, - $ Q_e,Q_i,pr,pe,c_beta,ds,d_beta,d_i,tau,r8,mu0,pi,out_unit, ! NOT out_unit - $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q + USE sglobal_mod!, ONLY: m_p, chag, lnLamb, +c $ Q_e,Q_i,pr,pe,c_beta,ds,d_beta,d_i,tau,r8,mu0,pi,out_unit, ! NOT out_unit +c $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q,eps0,m_e USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq USE bicube_mod, ONLY: bicube_eval_external,bicube_type - USE slayer_netcdf_mod!, ONLY: slayer_netcdf_inputs + USE slayer_netcdf_mod IMPLICIT NONE diff --git a/slayer/makefile b/slayer/makefile index 6a6d33750..2326a9c91 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -55,7 +55,7 @@ sglobal.o: ../equil/local_mod.mod params.o : sglobal.o delta.o : sglobal.o gslayer.o : sglobal.o delta.o -slayer.o : sglobal.o params.o delta.o ../pentrc/grid.mod ../equil/spline_mod.mod +slayer.o : sglobal.o params.o delta.o ../equil/spline_mod.mod slayer_netcdf.o : sglobal.o clean: diff --git a/slayer/sglobal.f b/slayer/sglobal.f index c0f6d9193..527e069ce 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -5,7 +5,7 @@ MODULE sglobal_mod INTEGER :: mm,nn INTEGER :: in_unit,out_unit,out2_unit,out3_unit, $ bin_unit,bin_2d_unit,input_unit,n_trace - INTEGER, PARAMETER :: r8=SELECTED_REAL_KIND(13,307) +c INTEGER, PARAMETER :: r8=SELECTED_REAL_KIND(13,307) REAL(r8) :: mr,nr REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, diff --git a/slayer/slayer.f b/slayer/slayer.f index 438b5a2d8..ea062c8d3 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -490,11 +490,11 @@ PROGRAM slayer IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk - gamma_sol_arr(k) = g_r/tauk + gamma_sol_arr(k) = g_r!/tauk THIS IS FOR PLOT ELSE re_trace = re_trace/tauk im_trace = -im_trace/tauk - gamma_sol_arr(k) = -g_r/tauk + gamma_sol_arr(k) = -g_r!/tauk THIS IS FOR PLOT END IF ENDDO From 609787fb88bc65d13a7afe7afd2447e3400cc595 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 2 Jul 2025 15:54:04 -0400 Subject: [PATCH 043/305] toroidal Delta_crit --- input/slayer.in | 2 + slayer/gslayer.f | 17 +++--- slayer/layerinputs.f | 12 ++-- slayer/params.f | 22 ++++++-- slayer/sglobal.f | 5 +- slayer/slayer.f | 77 +++++++++++++------------- slayer/slayer_netcdf.f | 12 ++-- stride/free.f | 122 ++++++++++++++++++++++++++++++++++++++++- stride/stride_netcdf.f | 13 +++-- 9 files changed, 212 insertions(+), 70 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 23eb8a29b..8a5b514d5 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -22,6 +22,7 @@ mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma zeff=2.0 ! plasma Z_effective dr_val=-0.1 ! GGJ resistive interchange criterion D_R + dgeo_val=10.0 ! J.W. Connor geometric prefactor for Delta_crit chi_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface perpendicular ion momentum diffusivity OR perpendicular energy diffusivity profile ! >>> ! >>> Input normalized layer parameters. If read_eq=f, each will individually override internal calcuation using above inputs @@ -46,6 +47,7 @@ knum=100 ! resolution for 2d scan along with the other. Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes scan_radius=1.5 ! stability scan width for Re(Q),Im(Q) scan + dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation QPscan_flag=f ! scan (Q,P) space for delta and torque. diff --git a/slayer/gslayer.f b/slayer/gslayer.f index a663ce3b4..0b03f8dc4 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -182,9 +182,9 @@ END SUBROUTINE gpec_slayer SUBROUTINE output_gamma(est_gamma_flag,qval_arr, $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, - $ re_trace,im_trace) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ Qconv_arr,re_trace,im_trace) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: est_gamma_flag @@ -192,17 +192,18 @@ SUBROUTINE output_gamma(est_gamma_flag,qval_arr, REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace, - $ Qconv_arr + $ Im_deltaprime_arr,re_trace,im_trace, + $ Qconv_arr,d_crit_arr COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, $ gamma_sol_arr,gamma_est_arr + WRITE(*,*)'d_crit_arr',d_crit_arr CALL slayer_netcdf_out(SIZE(qval_arr),est_gamma_flag, $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, - $ re_trace,im_trace) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ Qconv_arr,re_trace,im_trace) END SUBROUTINE output_gamma c----------------------------------------------------------------------- diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 50cb53064..af68ed926 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -233,7 +233,7 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, $ Q_e_arr,Q_i_arr,c_beta_arr,d_beta_arr, $ D_norm_arr,tau_arr,P_perp_arr, $ omegas_arr,gammafac_arr,Re_deltaprime_arr, - $ Im_deltaprime_arr,delta_crit_arr) + $ Im_deltaprime_arr,d_crit_arr) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- @@ -264,7 +264,7 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, $ Q_i_arr,c_beta_arr,d_beta_arr, $ tau_arr,omegas_arr,lu_arr,Qconv_arr, $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr, - $ delta_crit_arr + $ d_crit_arr INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor @@ -310,7 +310,7 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, $ omegas_i_arr(msing),gammafac_arr(msing), $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), $ D_norm_arr(msing),P_perp_arr(msing), - $ delta_crit_arr(msing)) + $ d_crit_arr(msing)) ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing),dr_arr(msing)) @@ -467,9 +467,9 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, Wd = SQRT(8.0)*((chi/chi_par)**0.25)* $ (1.0/SQRT((my_rs/R_0)*my_sval*nr)) END DO - delta_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd + d_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd ELSE - delta_crit = 0.0 + d_crit = 0.0 END IF @@ -491,7 +491,7 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, gammafac_arr(ising) = gammafac Re_deltaprime_arr(ising) = Re_dp_diagonal(ising) Im_deltaprime_arr(ising) = Im_dp_diagonal(ising) - delta_crit_arr(ising) = delta_crit + d_crit_arr(ising) = dc_tmp P_perp_arr(ising) = P_perp Qconv_arr(ising) = tauk ENDDO diff --git a/slayer/params.f b/slayer/params.f index 2e2cf1138..ce70a8a27 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -8,10 +8,10 @@ MODULE params_mod c----------------------------------------------------------------------- c calculate parameters. c----------------------------------------------------------------------- - SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val, + SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,chi,dr_val, + REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff LOGICAL, INTENT(IN) :: params_check @@ -109,9 +109,23 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val, Wd = SQRT(8.0)*((chi/chi_par)**0.25)* $ (1.0/SQRT((rs/R0)*sval*nr)) END DO - delta_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd + + SELECT CASE(dc_type) + CASE("lar") + dc_tmp = 0.5*(-dr_val)*(pi**1.5)*((chi_par/chi)**0.25)* + $ ( (nr*sval)/(R0*rs) )**0.5 + CASE("rfitzp") + dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd + CASE("toroidal") + dc_tmp = 0.5*(-dr_val)*(pi**1.5)* + $ ((chi_par/chi)**0.25)*dgeo_val + CASE default + dc_tmp = 0.5*(-dr_val)*(pi**1.5)* + $ ((chi_par/chi)**0.25)*dgeo_val + END SELECT + ELSE - delta_crit = 0.0 + dc_tmp = 0.0 END IF ! quick diagnostics. diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 527e069ce..45b703aaa 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -11,10 +11,11 @@ MODULE sglobal_mod REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, $ d_beta,D_norm,P_perp,gamma_fac REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, - $ delta_n,layfac,Qconv,lnLamb,deltaprim, - $ delta_crit,tau_r,tauk,g_r,g_i,delta_eff + $ delta_n,layfac,Qconv,lnLamb,deltaprim,dc_tmp, + $ d_crit,tau_r,tauk,g_r,g_i,delta_eff REAL(r8), DIMENSION(:), ALLOCATABLE :: re_trace,im_trace COMPLEX(r8) :: Q,g_tmp + CHARACTER(20) :: dc_type REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6021917e-19, diff --git a/slayer/slayer.f b/slayer/slayer.f index ea062c8d3..ee575887e 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -33,7 +33,7 @@ PROGRAM slayer $ params_check,stabscan_flag,read_eq,est_gamma_flag, $ match_gamma_flag,fitz_flag,br_th_flag REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_radius, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, @@ -56,7 +56,7 @@ PROGRAM slayer INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs, $ Re_deltaprime_arr,Im_deltaprime_arr, - $ gammafac_arr,delta_crit_arr + $ gammafac_arr,d_crit_arr REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths REAL(r8) :: spot, slayer_inpr @@ -68,13 +68,14 @@ PROGRAM slayer NAMELIST/slayer_input/input_flag,infile, $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, - $ l_t,l_n,qval,mu_i,zeff,dr_val,chi_prof,inpr,inpe,inQ, - $ inQ_e,inQ_i,inc_beta,inds,intau,Q0,delta_prime,delta_n_p, - $ ingamma + $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_prof,inpr,inpe, + $ inQ,inQ_e,inQ_i,inc_beta,inds,intau,Q0,delta_prime, + $ delta_n_p,ingamma NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_radius, - $ read_eq,fitz_flag,QPscan_flag,Qscan_flag,QPescan_flag, - $ Qbscan_flag,onscan_flag,otscan_flag,ntscan_flag, - $ nbtscan_flag,parflow_flag,peohmonly_flag,Pe_flag,layfac + $ dc_type,read_eq,fitz_flag,QPscan_flag,Qscan_flag, + $ QPescan_flag,Qbscan_flag,onscan_flag,otscan_flag, + $ ntscan_flag,nbtscan_flag,parflow_flag,peohmonly_flag, + $ Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ est_gamma_flag,match_gamma_flag,stability_flag, $ stabscan_flag,br_th_flag,bal_flag @@ -101,6 +102,7 @@ PROGRAM slayer mu_i=0.0 zeff=0.0 dr_val=0.0 + dgeo_val=0.0 inQ=0.0 inQ_e=0.0 inQ_i=0.0 @@ -113,8 +115,8 @@ PROGRAM slayer inlu=0.0 Q0=0.0 chi=0.0 - dr_val=0.0 gamma_fac=0.0 + dc_type="" delta_prime=(0.0,0.0) delta_n_p=(0.0,0.0) ingamma=(0.0,0.0) @@ -184,7 +186,7 @@ PROGRAM slayer c calculate parameters as needed. c----------------------------------------------------------------------- IF (params_flag) THEN - CALL params(n_e,t_e,t_i,omega,chi,dr_val, + CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -233,8 +235,8 @@ PROGRAM slayer nr=REAL(nns(k)) inpr=prs(k) CALL params(n_es(k),t_es(k),t_is(k),omegas(k),chi,dr_val, - $ l_ns(k),l_ts(k),qvals(k),svals(k),bts(k),rss(k),R0s(k), - $ mu_is(k),zeffs(k),params_check) + $ dgeo_val,l_ns(k),l_ts(k),qvals(k),svals(k),bts(k), + $ rss(k),R0s(k),mu_is(k),zeffs(k),params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -299,8 +301,8 @@ PROGRAM slayer CALL build_inputs(infile,ncfile,chi_prof,qval_arr, $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, - $ P_perp_arr,omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr) + $ P_perp_arr,omegas_arr,gammafac_arr,Re_deltaprime_arr, + $ Im_deltaprime_arr,d_crit_arr) n_k = SIZE(qval_arr) ELSE @@ -308,7 +310,7 @@ PROGRAM slayer mr = mm nr = nn - CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val, + CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) IF (ABS(inQ) > 0.0) THEN @@ -342,7 +344,7 @@ PROGRAM slayer tau_arr = (/ tau /) D_norm_arr = (/ D_norm /) d_beta_arr = (/ d_beta /) - delta_eff = REAL(delta_prime)-delta_crit + delta_eff = REAL(delta_prime)-d_crit gamma_fac = (rs*delta_eff)/tau_r gammafac_arr = (/ gamma_fac /) END IF @@ -374,9 +376,9 @@ PROGRAM slayer CALL output_gamma(est_gamma_flag,qval_arr, $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, - $ re_trace,im_trace) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ Qconv_arr,re_trace,im_trace) END IF ENDIF c----------------------------------------------------------------------- @@ -392,7 +394,7 @@ PROGRAM slayer $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, $ P_perp_arr,omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr) n_k = SIZE(qval_arr) @@ -400,7 +402,7 @@ PROGRAM slayer n_k = 1 ! Use namelist kinetic inputs instead of equilibrium files - CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val, + CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) ! Override desired normalized parameters @@ -431,7 +433,7 @@ PROGRAM slayer psi_n_rational = (/ 0.0 /) Re_deltaprime_arr = (/ REAL(delta_prime) /) Im_deltaprime_arr = (/ AIMAG(delta_prime) /) - delta_crit_arr = (/ delta_crit /) + d_crit_arr = (/ dc_tmp /) P_perp_arr = (/ P_perp /) tau_arr = (/ tau /) D_norm_arr = (/ D_norm /) @@ -456,9 +458,10 @@ PROGRAM slayer c_beta = c_beta_arr(k) tauk = Qconv_arr(k) - ! (Deltaprime - delta_crit)/S^1/3 + ! (Deltaprime - d_crit)/S^1/3 delta_eff = (Re_deltaprime_arr(k) - - $ delta_crit_arr(k))/(lu_arr(k)**(1.0/3.0)) + $ d_crit_arr(k))/(lu_arr(k)**(1.0/3.0)) +c delta_eff = Re_deltaprime_arr(k) pe = 0.0 ALLOCATE(re_trace(100),im_trace(100)) @@ -490,11 +493,11 @@ PROGRAM slayer IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk - gamma_sol_arr(k) = g_r!/tauk THIS IS FOR PLOT + gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT ELSE re_trace = re_trace/tauk im_trace = -im_trace/tauk - gamma_sol_arr(k) = -g_r!/tauk THIS IS FOR PLOT + gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT END IF ENDDO @@ -556,9 +559,9 @@ PROGRAM slayer CALL output_gamma(est_gamma_flag,qval_arr, $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, - $ re_trace,im_trace) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ Qconv_arr,re_trace,im_trace) stop ENDIF c----------------------------------------------------------------------- @@ -568,7 +571,7 @@ PROGRAM slayer WRITE(*,*)"running br_th scan" - CALL params(n_e,t_e,t_i,omega,chi,dr_val, + CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1046,7 +1049,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chi,dr_val, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + $ dgeo_val,l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -1133,8 +1136,8 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e,t_e*ks(j,k),t_i*ks(j,k), - $ omega*js(j,k),chi,dr_val,l_n,l_t,qval,sval,bt, - $ rs,R0,mu_i,zeff,params_check) + $ omega*js(j,k),chi,dr_val,dgeo_val,l_n,l_t,qval, + $ sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -1219,8 +1222,8 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e*ks(j,k),t_e*js(j,k),t_i*js(j,k),omega, - $ chi,dr_val,l_n,l_t,qval,sval,bt,rs,R0,mu_i, - $ zeff,params_check) + $ chi,dr_val,dgeo_val,l_n,l_t,qval,sval,bt,rs,R0, + $ mu_i,zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -1305,8 +1308,8 @@ PROGRAM slayer CALL params(n_e*ks(j,k),t_e,t_i,omega,chi,dr_val, - $ l_n,l_t,qval,sval,bt*js(j,k),rs,R0,mu_i,zeff, - $ params_check) + $ dgeo_val,l_n,l_t,qval,sval,bt*js(j,k),rs,R0,mu_i, + $ zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 4b45df4c1..614f7054a 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -51,9 +51,9 @@ END SUBROUTINE sl_check SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,delta_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr,Qconv_arr, - $ re_trace,im_trace) + $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, + $ dels_db_arr,gamma_sol_arr,gamma_est_arr, + $ Qconv_arr,re_trace,im_trace) INTEGER, INTENT(IN) :: msing LOGICAL, INTENT(IN) :: est_gamma_flag @@ -61,8 +61,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,delta_crit_arr,re_trace,im_trace, - $ Qconv_arr + $ Im_deltaprime_arr,d_crit_arr,re_trace, + $ im_trace,Qconv_arr COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, $ gamma_sol_arr,gamma_est_arr @@ -179,7 +179,7 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, CALL sl_check( nf90_put_var(ncid,dpp_id, $ RESHAPE((/Re_deltaprime_arr,Im_deltaprime_arr/), $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,dc_id, delta_crit_arr)) + CALL sl_check( nf90_put_var(ncid,dc_id, d_crit_arr)) IF (est_gamma_flag) THEN CALL sl_check( nf90_put_var(ncid,dels_db_id, diff --git a/stride/free.f b/stride/free.f index b2ddcc627..5143be4d6 100755 --- a/stride/free.f +++ b/stride/free.f @@ -166,7 +166,6 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) INTEGER :: ipert,jpert,isol,info,lwork INTEGER, DIMENSION(mpert) :: m INTEGER, DIMENSION(1) :: imax - REAL(r8) :: v1 REAL(r8), DIMENSION(mpert) :: ep,ev,et REAL(r8), DIMENSION(3*mpert-1) :: rwork COMPLEX(r8) :: phase,norm @@ -174,6 +173,17 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) COMPLEX(r8), DIMENSION(mpert,mpert) :: wt,wpt,wvt COMPLEX(r8), DIMENSION(mpert,mpert) :: nmat,smat CHARACTER(24), DIMENSION(mpert) :: message + !!!!!!!!!!!!!!! + INTEGER :: ipsi,itheta + REAL(r8), DIMENSION(sq%mx+1) :: ln_q + REAL(r8), DIMENSION(msing) :: dgeo,shr + TYPE(spline_type) :: psi_t,avg_dpsi_spl,avg_bsq_spl,v_spl, + $ shr_spl + REAL(r8) :: bsq,chi1,dpsisq,myeta,jac,psifac,q,q1,respsi, + $ rfac,v1,v2,v21,v22,v23,v33,al,Lam,mytheta,myr + REAL(r8), DIMENSION(:), POINTER :: avg + TYPE(spline_type), TARGET :: fspl + !!!!!!!!!!!!!! c----------------------------------------------------------------------- c write formats. c----------------------------------------------------------------------- @@ -292,12 +302,120 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) WRITE(out_unit,80) WRITE(out_unit,90)(isol,ep(isol),ev(isol),isol=1,mpert) WRITE(out_unit,80) + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +c----------------------------------------------------------------------- +c compute toroidal Delta_crit +c----------------------------------------------------------------------- + ! Prepare toroidal flux spline + CALL spline_alloc(psi_t,SIZE(sq%fsi(:, 4))-1,1) + psi_t%xs=sq%xs(:) + psi_t%fs(:,1)=sq%fsi(:,4)*twopi*psio ! Un-normalize toroidal flux + CALL spline_fit(psi_t,"extrap") + + ! Prepare geometric splines + CALL spline_alloc(avg_dpsi_spl,SIZE(sq%xs(:))-1,1) + avg_dpsi_spl%xs=sq%xs(:) + CALL spline_alloc(avg_bsq_spl,SIZE(sq%xs(:))-1,1) + avg_bsq_spl%xs=sq%xs(:) + CALL spline_alloc(v_spl,SIZE(sq%xs(:))-1,1) + v_spl%xs=sq%xs(:) + + ! Prepare shear spline + CALL spline_alloc(shr_spl,SIZE(sq%xs(:))-1,1) + shr_spl%xs=sq%xs(:) + ln_q=LOG(sq%fs(:,4)) + !ln_q(SIZE(ln_q)) = ln_q(SIZE(ln_q)-1) + shr_spl%fs(:,1)=ln_q ! log(q) + CALL spline_fit(shr_spl,"extrap") + + CALL spline_alloc(fspl,mtheta,3) + fspl%xs=rzphi%ys + + DO ipsi=0,mpsi + psifac=sq%xs(ipsi) + v1=sq%fs(ipsi,3) + v2=sq%fs1(ipsi,3) + q=sq%fs(ipsi,4) + q1=sq%fs1(ipsi,4) + chi1=twopi*psio +c----------------------------------------------------------------------- +c evaluate coordinates and jacobian. +c----------------------------------------------------------------------- + DO itheta=0,mtheta + CALL bicube_eval(rzphi,rzphi%xs(ipsi),rzphi%ys(itheta),1) + mytheta=rzphi%ys(itheta) + rfac=SQRT(rzphi%f(1)) + myeta=twopi*(mytheta+rzphi%f(2)) + myr=ro+rfac*COS(myeta) + jac=rzphi%f(4) +c----------------------------------------------------------------------- +c evaluate other local quantities. +c----------------------------------------------------------------------- + v21=rzphi%fy(1)/(2*rfac*jac) + v22=(1+rzphi%fy(2))*twopi*rfac/jac + v23=rzphi%fy(3)*myr/jac + v33=twopi*myr/jac + bsq=chi1**2*(v21**2+v22**2+(v23+q*v33)**2) + dpsisq=(twopi*myr)**2*(v21**2+v22**2) +c----------------------------------------------------------------------- +c evaluate integrands. +c----------------------------------------------------------------------- + fspl%fs(itheta,1)=dpsisq*(v1**2) ! Converting to \nabla V + fspl%fs(itheta,2)=bsq + fspl%fs(itheta,3)=v1 + fspl%fs(itheta,:)=fspl%fs(itheta,:)*(jac/v1) + ENDDO +c----------------------------------------------------------------------- +c integrate quantities with respect to theta. +c----------------------------------------------------------------------- + CALL spline_fit(fspl,"periodic") + CALL spline_int(fspl) + avg => fspl%fsi(mtheta,:) + avg_dpsi_spl%fs(ipsi,1)=avg(1) + avg_bsq_spl%fs(ipsi,1)=avg(2) + v_spl%fs(ipsi,1)=avg(3) + ENDDO + CALL spline_dealloc(fspl) + + CALL spline_fit(avg_dpsi_spl,"extrap") + CALL spline_fit(avg_bsq_spl,"extrap") + CALL spline_fit(v_spl,"extrap") + + WRITE(*,*)'shr_spl%fs(:,1)=',shr_spl%fs(:,1) + WRITE(*,*)'avg_dpsi_spl%fs(:,1)=',avg_dpsi_spl%fs(:,1) + WRITE(*,*)'avg_bsq_spl%fs(:,1)=',avg_bsq_spl%fs(:,1) + WRITE(*,*)'v_spl%fs(:,1)=',v_spl%fs(:,1) + + DO ising=1,msing + respsi=sing(ising)%psifac + WRITE(*,*)"respsi=",respsi + + ! Evaluate splines on rational surface + CALL spline_eval(sq,respsi,1) + CALL spline_eval(psi_t,respsi,1) + CALL spline_eval(avg_dpsi_spl,respsi,1) + CALL spline_eval(avg_bsq_spl,respsi,1) + CALL spline_eval(v_spl,respsi,1) + CALL spline_eval(shr_spl,respsi,1) + + al = twopi*nn*sq%f(3)*(1.0/chi1) + Lam = (psi_t%f1(1)/sq%f(3))* + $ (-1.0/(sq%f(4)**2.0))*(sq%f1(4)/sq%f(3)) + WRITE(*,*)'shr_spl%f(1)=',shr_spl%f(1) + + shr(ising) = shr_spl%f(1) + dgeo(ising) = v_spl%f(1)*( (((al**2.0)*(Lam**2.0))/ + $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) + ENDDO + WRITE(*,*)'new shear=',shr + WRITE(*,*)'dgeo=',dgeo + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! c----------------------------------------------------------------------- c optionally write netcdf file. c----------------------------------------------------------------------- IF(present(op_netcdf_out))THEN IF(op_netcdf_out) CALL stride_netcdf_out(wp,wv,wt,ep,ev,et, - $ delta_prime_mat) + $ delta_prime_mat,shr,dgeo) ENDIF c----------------------------------------------------------------------- c deallocate diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 27e3fd5c2..e5c4d8450 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -49,12 +49,13 @@ END SUBROUTINE check c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) + SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) REAL(r8), DIMENSION(mpert), INTENT(IN) :: epi,evi,eti COMPLEX(r8), DIMENSION(mpert,mpert), INTENT(IN) :: wp,wv,wt COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, INTENT(IN) :: dp INTEGER, DIMENSION(mpert) :: mvec + REAL(r8), DIMENSION(msing), INTENT(IN) :: shr,dgeo INTEGER :: i, ncid,mthsur, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, @@ -62,12 +63,12 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id,drr_id + $ shear_id,resm_id,drr_id,dgeo_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile - REAL(r8) :: resnum,shear,respsi,resm_sing + REAL(r8) :: resnum,respsi,resm_sing REAL(r8), DIMENSION(msing) :: dr_rationals REAL(r8), DIMENSION(:), ALLOCATABLE :: rs_full @@ -204,6 +205,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) $ r_dim,qr_id) ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) + CALL check( nf90_def_var(ncid, "Delta_geo", nf90_double, r_dim, + $ dgeo_id) ) CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, $ resm_id) ) ENDIF @@ -277,9 +280,9 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp) ! WRITE(*,*)"SHEAR=",shear !ENDDO - CALL check( nf90_put_var(ncid,shear_id, (/(sing(i)%q1, - $ i=1,msing)/)) ) ! GPEC HAS DIFFERENT SHEAR CALC? + CALL check( nf90_put_var(ncid,shear_id, shr) ) CALL check( nf90_put_var(ncid,resm_id, resm) ) + CALL check( nf90_put_var(ncid,dgeo_id, dgeo) ) ! CALL check( nf90_put_var(ncid,shear_id,shear) ) ! GPEC HAS DIFFERENT SHEAR CALC? ENDIF From 56f9dc50fdfa5ec2e23efb11aadc50d69f8303a0 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 19 Aug 2025 19:00:51 -0400 Subject: [PATCH 044/305] WIP: SLAYER in/out struct overhaul --- slayer/gslayer.f | 52 ++++++----- slayer/layerinputs.f | 193 ++++++++++++-------------------------- slayer/sglobal.f | 15 +++ slayer/slayer.f | 204 ++++++++++++++++++++--------------------- slayer/slayer_netcdf.f | 61 ++++++------ 5 files changed, 230 insertions(+), 295 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 0b03f8dc4..9eaa11a67 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -179,33 +179,41 @@ END SUBROUTINE gpec_slayer c Subprogram 3. scan_grid c Run stability scan on real and imaginary rotation axes c----------------------------------------------------------------------- - SUBROUTINE output_gamma(est_gamma_flag,qval_arr, - $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, - $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, - $ Qconv_arr,re_trace,im_trace) + SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: est_gamma_flag - INTEGER, INTENT(IN), DIMENSION(:) :: qval_arr - REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, - $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, - $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,re_trace,im_trace, - $ Qconv_arr,d_crit_arr - COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, - $ gamma_sol_arr,gamma_est_arr - - WRITE(*,*)'d_crit_arr',d_crit_arr - CALL slayer_netcdf_out(SIZE(qval_arr),est_gamma_flag, - $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, - $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, - $ Qconv_arr,re_trace,im_trace) + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + TYPE(slayer_outputs_type), INTENT(IN) :: sl_out + + CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),est_gamma_flag, + $ sl_in,sl_out) END SUBROUTINE output_gamma + + SUBROUTINE allocate_inputs(n_k,sl_in) + INTEGER, INTENT(IN) :: n_k + TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in + + ALLOCATE(sl_in%qval_arr(n_k),sl_in%omegas_arr(n_k), + $ sl_in%Q_e_arr(n_k),sl_in%Q_i_arr(n_k),sl_in%psi_n_arr(n_k), + $ sl_in%Re_dp_arr(n_k),sl_in%Im_dp_arr(n_k), + $ sl_in%d_crit_arr(n_k), + $ sl_in%P_perp_arr(n_k),sl_in%tau_arr(n_k), + $ sl_in%D_norm_arr(n_k), + $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), + $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k)) + RETURN + END SUBROUTINE allocate_inputs + + SUBROUTINE allocate_outputs(n_k,sl_out) + INTEGER, INTENT(IN) :: n_k + TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out + + ALLOCATE(sl_out%dels_db_arr(n_k),sl_out%gamma_sol_arr(n_k), + $ sl_out%gamma_est_arr(n_k) ) + RETURN + END SUBROUTINE allocate_outputs c----------------------------------------------------------------------- c Subprogram 2. growthrate_scan c Set up and iterate stability scans IF no match is found diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index af68ed926..ceee31f4d 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -3,9 +3,8 @@ MODULE layerinputs_mod USE inputs, ONLY : read_kin,read_equil,kin,chi1 USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, $ spline_dealloc,spline_int,spline_fit - USE sglobal_mod!, ONLY: m_p, chag, lnLamb, -c $ Q_e,Q_i,pr,pe,c_beta,ds,d_beta,d_i,tau,r8,mu0,pi,out_unit, ! NOT out_unit -c $ eta,visc,rho_s,lu,omega_e,omega_i,delta_n,Q,eps0,m_e + USE sglobal_mod + USE params_mod USE netcdf USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq USE bicube_mod, ONLY: bicube_eval_external,bicube_type @@ -19,15 +18,15 @@ MODULE layerinputs_mod c Read STRIDE netcdf file for SLAYER inputs only. c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, - $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,shear, - $ r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) + $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, + $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) ! Input/Output Arguments CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ Re_dp_diagonal,Im_dp_diagonal REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, - $ psi_n_rational, shear + $ psi_n_rational, shear,dgeo REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, $ my_psio,mpsi,dr_vals INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm @@ -37,8 +36,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Internal Variables INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, - $ dp_id, qr_id,pr_id,shear_id,ro_id,bt0_id,psio_id,mpsi_id, - $ msing_id,nn_id,resm_id,drr_id ! Explicit kind for NetCDF variables + $ dp_id, qr_id,pr_id,dgeo_id,shear_id,ro_id,bt0_id,psio_id, + $ mpsi_id,msing_id,nn_id,resm_id,drr_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i @@ -93,6 +92,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, CALL sl_check(stat) stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) CALL sl_check(stat) + stat = nf90_inq_varid(ncid, "Delta_geo", dgeo_id) + CALL sl_check(stat) stat = nf90_inq_varid(ncid, "shear", shear_id) CALL sl_check(stat) stat = nf90_inq_varid(ncid, "resm", resm_id) @@ -119,6 +120,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, CALL sl_check(stat) stat = nf90_get_var(ncid, pr_id, psi_n_rational) CALL sl_check(stat) + stat = nf90_get_var(ncid, dgeo_id, dgeo) + CALL sl_check(stat) stat = nf90_get_var(ncid, shear_id, shear) CALL sl_check(stat) stat = nf90_get_var(ncid, resm_id, resm) @@ -228,19 +231,14 @@ END FUNCTION issurfint c subprogram 3. build_inputs. c build input arrays for SLAYER c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,chi_prof, - $ qval_arr,psi_n_rational,lu_arr,Qconv_arr,Q_arr, - $ Q_e_arr,Q_i_arr,c_beta_arr,d_beta_arr, - $ D_norm_arr,tau_arr,P_perp_arr, - $ omegas_arr,gammafac_arr,Re_deltaprime_arr, - $ Im_deltaprime_arr,d_crit_arr) + SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- ! Inputs CHARACTER(512), INTENT(IN) :: infile,ncfile + TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in ! Internals - REAL(r8), DIMENSION(8), INTENT(IN) :: chi_prof LOGICAL :: firstsurf REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf,ising INTEGER :: zi, zimp, mi, mimp @@ -252,26 +250,21 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, INTEGER :: mms,nns,mrs,nrs,mpsi REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0 - REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_h, + $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0,dgeo_val + REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_h,l_n,l_t, $ rho,tau_v,chi,Qconv,lbeta,qintb,gammafac REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, $ sigma_par_2,sigma_par,tau_perp,Wd,vte, $ dr_val,chi_par_smfp,chi_par_lmfp,chi_par INTEGER :: wit - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: Q_arr, - $ Q_e_arr,psi_n_rational,D_norm_arr,P_perp_arr, - $ Q_i_arr,c_beta_arr,d_beta_arr, - $ tau_arr,omegas_arr,lu_arr,Qconv_arr, - $ gammafac_arr,Re_deltaprime_arr,Im_deltaprime_arr, - $ d_crit_arr - INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: qval_arr + REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal,dr_arr, $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal,dr_vals + $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal,dr_vals, + $ psi_n_rational,dgeo REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, $ ti_arr,zeff_arr,bt_arr,rs_arr,R0_arr,mu_i_arr INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm,nns_arr @@ -285,13 +278,14 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, c Read in STRIDE netcdf c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile,msing,Re_dp_diagonal, - $ Im_dp_diagonal,q_rational,psi_n_rational, + $ Im_dp_diagonal,q_rational,psi_n_rational,dgeo, $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing WRITE(*,*)"Re_dp_diagonal=",Re_dp_diagonal WRITE(*,*)"Im_dp_diagonal=",Im_dp_diagonal WRITE(*,*)"q_rational=",q_rational WRITE(*,*)"psi_n_rational=",psi_n_rational + WRITE(*,*)"dgeo=",dgeo WRITE(*,*)"shear=",shear WRITE(*,*)"r_o=",r_o WRITE(*,*)"my_bt0=",my_bt0 @@ -302,18 +296,23 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, mpsi = INT(mpsi_arr(1)) mthsurf = 512 ! Hardcoded, but this is a default value - ALLOCATE(qval_arr(msing),Q_arr(msing),Q_e_arr(msing), - $ Q_i_arr(msing),lu_arr(msing),Qconv_arr(msing), - $ c_beta_arr(msing),d_beta_arr(msing), - $ tau_arr(msing), - $ omegas_arr(msing),omegas_e_arr(msing), - $ omegas_i_arr(msing),gammafac_arr(msing), - $ Re_deltaprime_arr(msing),Im_deltaprime_arr(msing), - $ D_norm_arr(msing),P_perp_arr(msing), - $ d_crit_arr(msing)) +c Allocate SLAYER input type arrays + ALLOCATE(sl_in%qval_arr(msing),sl_in%omegas_arr(msing), + $ sl_in%Q_e_arr(msing),sl_in%Q_i_arr(msing), + $ sl_in%psi_n_arr(msing), + $ sl_in%Re_dp_arr(msing),sl_in%Im_dp_arr(msing), + $ sl_in%d_crit_arr(msing), + $ sl_in%P_perp_arr(msing),sl_in%tau_arr(msing), + $ sl_in%D_norm_arr(msing), + $ sl_in%d_beta_arr(msing),sl_in%gammafac_arr(msing), + $ sl_in%c_beta_arr(msing),sl_in%lu_arr(msing), + $ sl_in%Qconv_arr(msing)) + +c Allocate local kinetic arrays ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), - $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing),dr_arr(msing)) + $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing),dr_arr(msing), + $ omegas_e_arr(msing),omegas_i_arr(msing)) ALLOCATE(jacs(0:mthsurf),delpsi(0:mthsurf), $ rsurf(0:mthsurf),asurf(0:mthsurf)) @@ -361,6 +360,9 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) + sl_in%omegas_e_arr(ising) = omega_e + sl_in%omegas_i_arr(ising) = omega_i + n_e = kin%f(2) t_e = kin%f(4)/e n_i = kin%f(1) @@ -369,15 +371,14 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, omega = kin%f(5) my_qval = q_rational(ising)!sq%f(4) my_sval = shear(ising) + dgeo_val = dgeo(ising) my_bt = my_bt0(1) my_rs = a_surf R_0 = r_o(1) mu_i = 2.0 dr_val = dr_vals(ising) - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - - chi = chi_prof(ising) + chi = sl_in%chi_prof_arr(ising) ne_arr(ising) = n_e te_arr(ising) = t_e @@ -396,104 +397,30 @@ SUBROUTINE build_inputs(infile,ncfile,chi_prof, nns_arr(ising) = nn(1) - lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) - - ! mu_i: ion mass ratio to proton - tau= t_i/t_e ! ratio of ion to electron temperature - tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - rho=(mu_i*m_p)*n_e ! mass density - - tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5)* - $ (eps0**2.0)*(m_e**0.5)*(t_e**1.5) - tau_ee_denom = lnLamb*(chag**2.5)*n_e - tau_ee = tau_ee_num / tau_ee_denom - - sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) / - $ (Zeff*(SQRT(2.0) + Zeff)) - sigma_par_2 = (n_e * (chag**2.0) * tau_ee) / m_e - sigma_par = sigma_par_1*sigma_par_2 - - b_l=(nr/mr)*my_rs*my_sval*my_bt/R_0 ! characteristic magnetic field - v_a=b_l/(mu0*rho)**0.5 ! alfven velocity - rho_s=1.02e-4*(mu_i*t_e)**0.5/my_bt ! ion Lamour by elec. Temp. - d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth - - tau_h=R_0*(mu0*rho)**0.5/(nr*my_sval*my_bt) ! alfven time across surface - tau_r=mu0*(my_rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale - IF (ABS(chi) > 0.0) THEN - tau_perp = ( my_rs**2.0 ) / chi - ELSE - tau_perp = 0.0 - END IF - - lu=tau_r/tau_h ! Lundquist number - - Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - tauk = Qconv - - ! note Q depends on Qconv even if omega is fixed. - Q=Qconv*omega - Q_e=Qconv*omega_e - Q_i=Qconv*omega_i - - ! This is the most critical parameter - ds=lu**(1.0/3.0)*rho_s/my_rs ! conversion based on Cole. - - lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/my_bt**2.0 - c_beta=(lbeta/(1.0+lbeta))**0.5 - - IF (ABS(tau_perp) > 0.0) THEN - P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number - ELSE - P_perp = 0.0 - END IF - ! this is using Fitzpatrick's tau', we need tau eventually - d_beta = c_beta*d_i - D_norm = (d_beta/my_rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) - - ! Calculate Delta_crit - IF (ABS(dr_val) > 0.0) THEN - vte = SQRT((2.0*(t_e*chag))/m_e) - chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ - $ (1.0+0.2535*Zeff) - - Wd = 0.1 - DO wit = 1,10 - chi_par_lmfp = (2.0*R_0*vte)/ - $ (SQRT(pi)*nr*my_sval*Wd) - chi_par = (chi_par_smfp*chi_par_lmfp)/ - $ (chi_par_smfp+chi_par_lmfp) - Wd = SQRT(8.0)*((chi/chi_par)**0.25)* - $ (1.0/SQRT((my_rs/R_0)*my_sval*nr)) - END DO - d_crit = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd - ELSE - d_crit = 0.0 - END IF - + l_n = 0.0 + l_t = 0.0 + CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i,zeff,.false.) !!!!!!!!!!! gammafac = (my_rs*Re_dp_diagonal(ising))/tau_r ! scalar to convert thickness into growth rate - qval_arr(ising) = INT(my_qval) - lu_arr(ising)=lu - Q_arr(ising)=REAL(Q) - Q_e_arr(ising)=Q_e - Q_i_arr(ising)=Q_i - c_beta_arr(ising)=c_beta - d_beta_arr(ising)=d_beta - D_norm_arr(ising)=D_norm - tau_arr(ising)=tau - omegas_arr(ising) = omega - omegas_e_arr(ising) = omega_e - omegas_i_arr(ising) = omega_i - gammafac_arr(ising) = gammafac - Re_deltaprime_arr(ising) = Re_dp_diagonal(ising) - Im_deltaprime_arr(ising) = Im_dp_diagonal(ising) - d_crit_arr(ising) = dc_tmp - P_perp_arr(ising) = P_perp - Qconv_arr(ising) = tauk + sl_in%qval_arr(ising) = INT(my_qval) + sl_in%lu_arr(ising)=lu + sl_in%Q_e_arr(ising)=Q_e + sl_in%Q_i_arr(ising)=Q_i + sl_in%c_beta_arr(ising)=c_beta + sl_in%d_beta_arr(ising)=d_beta + sl_in%D_norm_arr(ising)=D_norm + sl_in%tau_arr(ising)=tau + sl_in%omegas_arr(ising) = omega + + sl_in%gammafac_arr(ising) = gammafac + sl_in%Re_dp_arr(ising) = Re_dp_diagonal(ising) + sl_in%Im_dp_arr(ising) = Im_dp_diagonal(ising) + sl_in%d_crit_arr(ising) = dc_tmp + sl_in%P_perp_arr(ising) = P_perp + sl_in%Qconv_arr(ising) = tauk ENDDO !WRITE(*,*)"msing=",msing diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 45b703aaa..77a8699af 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -27,6 +27,21 @@ MODULE sglobal_mod INTEGER :: count END TYPE result_type + TYPE slayer_inputs_type + INTEGER, ALLOCATABLE :: qval_arr(:) + REAL(r8), ALLOCATABLE :: chi_prof_arr(:),psi_n_arr(:), + $ lu_arr(:),Qconv_arr(:),Q_e_arr(:),Q_i_arr(:),c_beta_arr(:), + $ d_beta_arr(:),D_norm_arr(:),tau_arr(:),P_perp_arr(:), + $ omegas_arr(:),omegas_e_arr(:),omegas_i_arr(:), + $ gammafac_arr(:),Re_dp_arr(:),Im_dp_arr(:),d_crit_arr(:) + END TYPE slayer_inputs_type + + TYPE slayer_outputs_type + COMPLEX(r8), ALLOCATABLE :: dels_db_arr(:),gamma_sol_arr(:), + $ gamma_est_arr(:) + REAL(r8), ALLOCATABLE :: r_trace(:,:),i_trace(:,:) + END TYPE slayer_outputs_type + ! lnLamb will be updated. COMPLEX(r8), PARAMETER :: ifac=(0,1) diff --git a/slayer/slayer.f b/slayer/slayer.f index ee575887e..bcb0aadd9 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -48,23 +48,17 @@ PROGRAM slayer REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl,bal, $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes - REAL(r8), DIMENSION(:), ALLOCATABLE :: Q_e_arr,Q_i_arr, - $ c_beta_arr,D_norm_arr,tau_arr,P_perp_arr, - $ omegas_arr,Q_arr,lu_arr,psi_n_rational, - $ d_beta_arr,Qconv_arr REAL(r8), DIMENSION(8) :: chi_prof - INTEGER, DIMENSION(:), ALLOCATABLE :: qval_arr - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs, - $ Re_deltaprime_arr,Im_deltaprime_arr, - $ gammafac_arr,d_crit_arr + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths REAL(r8) :: spot, slayer_inpr REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas, - $ dels_db_arr,lar_gamma_arr, - $ gamma_sol_arr,gamma_est_arr + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas + + TYPE(slayer_inputs_type) :: sl_in + TYPE(slayer_outputs_type) :: sl_out NAMELIST/slayer_input/input_flag,infile, $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, @@ -298,13 +292,11 @@ PROGRAM slayer IF (read_eq) THEN - CALL build_inputs(infile,ncfile,chi_prof,qval_arr, - $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, - $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, - $ P_perp_arr,omegas_arr,gammafac_arr,Re_deltaprime_arr, - $ Im_deltaprime_arr,d_crit_arr) + sl_in%chi_prof_arr = chi_prof + + CALL build_inputs(infile,ncfile,sl_in) - n_k = SIZE(qval_arr) + n_k = SIZE(sl_in%qval_arr) ELSE n_k = 1 mr = mm @@ -332,53 +324,53 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF - qval_arr = (/ qval /) - omegas_arr = (/ omega /) - Q_arr = (/ Q /) - Q_e_arr = (/ Q_e /) - Q_i_arr = (/ Q_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ REAL(delta_prime) /) - Im_deltaprime_arr = (/ AIMAG(delta_prime) /) - P_perp_arr = (/ P_perp /) - tau_arr = (/ tau /) - D_norm_arr = (/ D_norm /) - d_beta_arr = (/ d_beta /) - delta_eff = REAL(delta_prime)-d_crit - gamma_fac = (rs*delta_eff)/tau_r - gammafac_arr = (/ gamma_fac /) + CALL allocate_inputs(n_k,sl_in) + CALL allocate_outputs(n_k,sl_out) + + sl_in%qval_arr = (/ qval /) + sl_in%omegas_arr = (/ omega /) + !sl_in%Q_arr = (/ Q /) + sl_in%Q_e_arr = (/ Q_e /) + sl_in%Q_i_arr = (/ Q_i /) + sl_in%psi_n_arr = (/ 0.0 /) + sl_in%Re_dp_arr = (/ REAL(delta_prime) /) + sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) + sl_in%d_crit_arr = (/ dc_tmp /) + sl_in%P_perp_arr = (/ P_perp /) + sl_in%tau_arr = (/ tau /) + sl_in%D_norm_arr = (/ D_norm /) + sl_in%d_beta_arr = (/ d_beta /) + sl_in%gammafac_arr = (/ gamma_fac /) + sl_in%c_beta_arr = (/ c_beta /) + sl_in%lu_arr = (/ lu /) + sl_in%Qconv_arr = (/ tauk /) END IF - ALLOCATE(gamma_est_arr(n_k),dels_db_arr(n_k)) + ALLOCATE(sl_out%gamma_est_arr(n_k),sl_out%dels_db_arr(n_k)) DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate '// $ 'estimate on q = ', - $ qval_arr(k),' rational surface' + $ sl_in%qval_arr(k),' rational surface' - dels_db=riccati_del_s(Q_arr(k),Q_e_arr(k), - $ Q_i_arr(k),P_perp_arr(k),c_beta_arr(k), - $ D_norm_arr(k),tau_arr(k), - $ 5.0*D_norm_arr(k)) + dels_db=riccati_del_s(sl_in%Q_e_arr(k),sl_in%Q_e_arr(k), ! NOT using Q_arr + $ sl_in%Q_i_arr(k),sl_in%P_perp_arr(k), + $ sl_in%c_beta_arr(k),sl_in%D_norm_arr(k), + $ sl_in%tau_arr(k),5.0*sl_in%D_norm_arr(k)) - del_s = dels_db * d_beta_arr(k) + del_s = dels_db * sl_in%d_beta_arr(k) - gamma_est_arr(k) = gammafac_arr(k)/del_s - dels_db_arr(k) = dels_db + sl_out%gamma_est_arr(k) = sl_in%gammafac_arr(k)/del_s + sl_out%dels_db_arr(k) = dels_db WRITE(*,*) WRITE(*,'(A,F0.3,A)')'Growth rate estimate = ', - $ REAL(gamma_est_arr(k)),' [Hz]' - + $ REAL(sl_out%gamma_est_arr(k)),' [Hz]' + ENDDO IF (.NOT. (match_gamma_flag)) THEN - gamma_sol_arr = (/0./) - CALL output_gamma(est_gamma_flag,qval_arr, - $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, - $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, - $ Qconv_arr,re_trace,im_trace) + sl_out%gamma_sol_arr = (/0./) + CALL output_gamma(est_gamma_flag,sl_in,sl_out) END IF ENDIF c----------------------------------------------------------------------- @@ -389,14 +381,12 @@ PROGRAM slayer WRITE(*,*)">>> Calculating asymptotically matched growth rate" IF (read_eq) THEN + + sl_in%chi_prof_arr = chi_prof - CALL build_inputs(infile,ncfile,chi_prof,qval_arr, - $ psi_n_rational,lu_arr,Qconv_arr,Q_arr,Q_e_arr,Q_i_arr, - $ c_beta_arr,d_beta_arr,D_norm_arr,tau_arr, - $ P_perp_arr,omegas_arr,gammafac_arr, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr) + CALL build_inputs(infile,ncfile,sl_in) - n_k = SIZE(qval_arr) + n_k = SIZE(sl_in%qval_arr) ELSE n_k = 1 @@ -425,42 +415,45 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF - qval_arr = (/ qval /) - omegas_arr = (/ omega /) - Q_arr = (/ Q /) - Q_e_arr = (/ Q_e /) - Q_i_arr = (/ Q_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ REAL(delta_prime) /) - Im_deltaprime_arr = (/ AIMAG(delta_prime) /) - d_crit_arr = (/ dc_tmp /) - P_perp_arr = (/ P_perp /) - tau_arr = (/ tau /) - D_norm_arr = (/ D_norm /) - d_beta_arr = (/ d_beta /) - gammafac_arr = (/ gamma_fac /) - c_beta_arr = (/ c_beta /) - lu_arr = (/ lu /) - Qconv_arr = (/ tauk /) + CALL allocate_inputs(n_k,sl_in) + CALL allocate_outputs(n_k,sl_out) + + sl_in%qval_arr = (/ qval /) + sl_in%omegas_arr = (/ omega /) + !sl_in%Q_arr = (/ Q /) + sl_in%Q_e_arr = (/ Q_e /) + sl_in%Q_i_arr = (/ Q_i /) + sl_in%psi_n_arr = (/ 0.0 /) + sl_in%Re_dp_arr = (/ REAL(delta_prime) /) + sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) + sl_in%d_crit_arr = (/ dc_tmp /) + sl_in%P_perp_arr = (/ P_perp /) + sl_in%tau_arr = (/ tau /) + sl_in%D_norm_arr = (/ D_norm /) + sl_in%d_beta_arr = (/ d_beta /) + sl_in%gammafac_arr = (/ gamma_fac /) + sl_in%c_beta_arr = (/ c_beta /) + sl_in%lu_arr = (/ lu /) + sl_in%Qconv_arr = (/ tauk /) END IF - ALLOCATE(gamma_sol_arr(n_k)) + ALLOCATE(sl_out%gamma_sol_arr(n_k)) DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', - $ qval_arr(k),' rational surface:' - Q_e = Q_e_arr(k) - Q_i = Q_i_arr(k) - P_perp = P_perp_arr(k) - tau = tau_arr(k) - D_norm = D_norm_arr(k) - c_beta = c_beta_arr(k) - tauk = Qconv_arr(k) + $ sl_in%qval_arr(k),' rational surface:' + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + P_perp = sl_in%P_perp_arr(k) + tau = sl_in%tau_arr(k) + D_norm = sl_in%D_norm_arr(k) + c_beta = sl_in%c_beta_arr(k) + tauk = sl_in%Qconv_arr(k) ! (Deltaprime - d_crit)/S^1/3 - delta_eff = (Re_deltaprime_arr(k) - - $ d_crit_arr(k))/(lu_arr(k)**(1.0/3.0)) + delta_eff = (sl_in%Re_dp_arr(k) - + $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) c delta_eff = Re_deltaprime_arr(k) pe = 0.0 @@ -493,17 +486,23 @@ PROGRAM slayer IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk - gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT ELSE re_trace = re_trace/tauk im_trace = -im_trace/tauk - gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT END IF ENDDO + ALLOCATE(sl_out%r_trace(n_k,n_trace), + $ sl_out%i_trace(n_k,n_trace)) + + sl_out%r_trace(k,:) = re_trace + sl_out%i_trace(k,:) = im_trace + IF (.NOT. (est_gamma_flag)) THEN - d_beta_arr = (/ 0. /) - dels_db_arr = (/ 0. /) + sl_in%d_beta_arr = (/ 0. /) + sl_out%dels_db_arr = (/ 0. /) END IF IF (stabscan_flag) THEN @@ -556,12 +555,7 @@ PROGRAM slayer ENDIF - CALL output_gamma(est_gamma_flag,qval_arr, - $ omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, - $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, - $ Qconv_arr,re_trace,im_trace) + CALL output_gamma(est_gamma_flag,sl_in,sl_out) stop ENDIF c----------------------------------------------------------------------- @@ -626,24 +620,24 @@ PROGRAM slayer DEALLOCATE(inQs,deltal,jxbl,bal) WRITE(*,*)"allocating" - qval_arr = (/ 3 /) + sl_in%qval_arr = (/ 3 /) inQs = (/ 1.0 /) inQs = (/ 1.0 /) - n_k = SIZE(qval_arr) + n_k = SIZE(sl_in%qval_arr) - qval_arr = (/ 3 /) + sl_in%qval_arr = (/ 3 /) inQs = (/ 1.0 /) inQs = (/ 1.0 /) - omegas_arr = (/ 0.0 /) - Q_arr = (/ inQ /) - Q_e_arr = (/ inQ_e /) - Q_i_arr = (/ inQ_i /) - psi_n_rational = (/ 0.0 /) - Re_deltaprime_arr = (/ 0.0 /) - Im_deltaprime_arr = (/ 0.0 /) - P_perp_arr = (/ inpr /) + sl_in%omegas_arr = (/ 0.0 /) + !sl_in%Q_arr = (/ inQ /) + sl_in%Q_e_arr = (/ inQ_e /) + sl_in%Q_i_arr = (/ inQ_i /) + sl_in%psi_n_arr = (/ 0.0 /) + sl_in%Re_dp_arr = (/ 0.0 /) + sl_in%Im_dp_arr = (/ 0.0 /) + sl_in%P_perp_arr = (/ inpr /) WRITE(*,*)"allocations successful" diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 614f7054a..8f09f7931 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -49,22 +49,12 @@ END SUBROUTINE sl_check c declarations. c ----------------------------------------------------------------------- SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, - $ qval_arr,omegas_arr,Q_arr,Q_e_arr,Q_i_arr,d_beta_arr, - $ c_beta_arr,D_norm_arr,P_perp_arr,lu_arr,psi_n_rational, - $ Re_deltaprime_arr,Im_deltaprime_arr,d_crit_arr, - $ dels_db_arr,gamma_sol_arr,gamma_est_arr, - $ Qconv_arr,re_trace,im_trace) + $ sl_in,sl_out) INTEGER, INTENT(IN) :: msing LOGICAL, INTENT(IN) :: est_gamma_flag - INTEGER, INTENT(IN), DIMENSION(:) :: qval_arr - REAL(r8), INTENT(IN), DIMENSION(:) :: omegas_arr, - $ Q_arr,Q_e_arr,Q_i_arr,d_beta_arr,c_beta_arr,D_norm_arr, - $ P_perp_arr,lu_arr,psi_n_rational,Re_deltaprime_arr, - $ Im_deltaprime_arr,d_crit_arr,re_trace, - $ im_trace,Qconv_arr - COMPLEX(r8),INTENT(IN),DIMENSION(:) :: dels_db_arr, - $ gamma_sol_arr,gamma_est_arr + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + TYPE(slayer_outputs_type), INTENT(IN) :: sl_out INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,inpr_id,S_id, @@ -164,40 +154,41 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- - CALL sl_check( nf90_put_var(ncid,qsing_id, qval_arr)) - CALL sl_check( nf90_put_var(ncid,qr_id, qval_arr)) - CALL sl_check( nf90_put_var(ncid,pr_id, psi_n_rational)) - CALL sl_check( nf90_put_var(ncid,omegas_id, omegas_arr)) - CALL sl_check( nf90_put_var(ncid,S_id, lu_arr)) - CALL sl_check( nf90_put_var(ncid,qc_id, Qconv_arr)) - CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) - CALL sl_check( nf90_put_var(ncid,Q_e_id, Q_e_arr)) - CALL sl_check( nf90_put_var(ncid,Q_i_id, Q_i_arr)) - CALL sl_check( nf90_put_var(ncid,inpr_id, P_perp_arr)) - CALL sl_check( nf90_put_var(ncid,Dnorm_id, D_norm_arr)) + CALL sl_check( nf90_put_var(ncid,qsing_id, sl_in%qval_arr)) + CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) + CALL sl_check( nf90_put_var(ncid,pr_id, sl_in%psi_n_arr)) + CALL sl_check( nf90_put_var(ncid,omegas_id, sl_in%omegas_arr)) + CALL sl_check( nf90_put_var(ncid,S_id, sl_in%lu_arr)) + CALL sl_check( nf90_put_var(ncid,qc_id, sl_in%Qconv_arr)) + !CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) + CALL sl_check( nf90_put_var(ncid,Q_e_id, sl_in%Q_e_arr)) + CALL sl_check( nf90_put_var(ncid,Q_i_id, sl_in%Q_i_arr)) + CALL sl_check( nf90_put_var(ncid,inpr_id, sl_in%P_perp_arr)) + CALL sl_check( nf90_put_var(ncid,Dnorm_id, sl_in%D_norm_arr)) CALL sl_check( nf90_put_var(ncid,dpp_id, - $ RESHAPE((/Re_deltaprime_arr,Im_deltaprime_arr/), + $ RESHAPE((/sl_in%Re_dp_arr,sl_in%Im_dp_arr/), $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,dc_id, d_crit_arr)) + CALL sl_check( nf90_put_var(ncid,dc_id,sl_in%d_crit_arr)) IF (est_gamma_flag) THEN CALL sl_check( nf90_put_var(ncid,dels_db_id, - $ RESHAPE((/REAL(dels_db_arr),AIMAG(dels_db_arr)/), - $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,d_b_id,d_beta_arr)) + $ RESHAPE((/REAL(sl_out%dels_db_arr), + $ AIMAG(sl_out%dels_db_arr)/),(/msing,2/)))) + + CALL sl_check( nf90_put_var(ncid,d_b_id,sl_in%d_beta_arr)) CALL sl_check( nf90_put_var(ncid,ge_id, - $ RESHAPE((/REAL(gamma_est_arr),AIMAG(gamma_est_arr)/), - $ (/msing,2/)))) + $ RESHAPE((/REAL(sl_out%gamma_est_arr), + $ AIMAG(sl_out%gamma_est_arr)/),(/msing,2/)))) END IF CALL sl_check( nf90_put_var(ncid,gs_id, - $ RESHAPE((/REAL(gamma_sol_arr),AIMAG(gamma_sol_arr)/), - $ (/msing,2/)))) + $ RESHAPE((/REAL(sl_out%gamma_sol_arr), + $ AIMAG(sl_out%gamma_sol_arr)/),(/msing,2/)))) CALL sl_check( nf90_put_var(ncid,tr_id, - $ RESHAPE((/re_trace,im_trace/), - $ (/SIZE(re_trace),2/)))) + $ RESHAPE((/sl_out%r_trace,sl_out%i_trace/), + $ (/msing,SIZE(re_trace),2/)))) c ----------------------------------------------------------------------- c close file From 5ff13d58614de808e68b756e53a20e8b822ba8de Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 20 Aug 2025 15:34:23 -0400 Subject: [PATCH 045/305] working: SLAYER structs --- slayer/delta.f | 11 ++++++--- slayer/layerinputs.f | 22 +++++++++++------ slayer/params.f | 1 + slayer/slayer.f | 55 ++++++++++++++++++++++++++++++++---------- slayer/slayer_netcdf.f | 12 ++++----- 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index cd29ead93..970b77522 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -161,9 +161,9 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, P_hat = P_perp / D_norm**6.0 ! P_perp, 0.377 for Pperp_hat benchmark - !WRITE(*,*)"riccati_del_s inpr = ",inpr - !WRITE(*,*)"riccati_del_s Q_e = ",Q_e - !WRITE(*,*)"riccati_del_s ind_beta = ",ind_beta + Q_e = inQ_e + Q_i = inQ_i + P_perp = inpr alpha = (P_hat/(1+1/tau))**0.5 ! this is actually tau', we need tau W(1) = -alpha*my_q**2 - 0.5 @@ -236,6 +236,11 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark P_perp_hat = P_perp / D_norm**6.0 ! 0.377 for benchmark P_tor_hat = P_perp / D_norm**6.0 ! 1.15 for benchmark + + !WRITE(*,*)"w_der_del_s P_perp_hat = ",P_perp_hat + !WRITE(*,*)"w_der_del_s P_tor_hat = ",P_tor_hat + !WRITE(*,*)"w_der_del_s Q_hat = ",Q_hat + !WRITE(*,*)"w_der inpr = ",pr !WRITE(*,*)"w_der Q_e = ",Q_e !WRITE(*,*)"w_der D_beta_norm = ",D_beta_norm diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index ceee31f4d..cb2751453 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -44,6 +44,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, $ msing_len,nn_len,dr_len ! Attribute lengths + WRITE(*,*)"$^$ opening netcdf file",ncfile + ! Open the NetCDF file stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) CALL sl_check(stat) ! Error handling @@ -59,7 +61,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Allocate Arrays (based on dimension) ALLOCATE(Re_dp_diagonal(msing),q_rational(msing), - $ psi_n_rational(msing),shear(msing), + $ psi_n_rational(msing),shear(msing),dgeo(msing), $ resm(msing),Im_dp_diagonal(msing),dr_vals(msing)) ALLOCATE(delta_prime(msing, msing,2)) @@ -82,8 +84,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, !ro_id=0 ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), - $ my_psio(INT(psio_len)), - $mpsi(INT(mpsi_len)),nn(INT(nn_len))) + $ my_psio(INT(psio_len)), + $ mpsi(INT(mpsi_len)),nn(INT(nn_len))) ! Get Variable IDs stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) @@ -277,6 +279,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- c Read in STRIDE netcdf c----------------------------------------------------------------------- + CALL read_stride_netcdf_diagonal(ncfile,msing,Re_dp_diagonal, $ Im_dp_diagonal,q_rational,psi_n_rational,dgeo, $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi_arr,nn,resm) @@ -298,6 +301,8 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c Allocate SLAYER input type arrays ALLOCATE(sl_in%qval_arr(msing),sl_in%omegas_arr(msing), + $ sl_in%omegas_e_arr(msing), + $ sl_in%omegas_i_arr(msing),!sl_in%chi_prof_arr(msing), $ sl_in%Q_e_arr(msing),sl_in%Q_i_arr(msing), $ sl_in%psi_n_arr(msing), $ sl_in%Re_dp_arr(msing),sl_in%Im_dp_arr(msing), @@ -377,7 +382,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) R_0 = r_o(1) mu_i = 2.0 dr_val = dr_vals(ising) - + chi = sl_in%chi_prof_arr(ising) ne_arr(ising) = n_e @@ -396,9 +401,12 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) nrs = real(nns,4) nns_arr(ising) = nn(1) + nr = nn(1) l_n = 0.0 l_t = 0.0 + WRITE(*,*)"$^$ calling params()" + CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i,zeff,.false.) @@ -407,14 +415,14 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) sl_in%qval_arr(ising) = INT(my_qval) sl_in%lu_arr(ising)=lu - sl_in%Q_e_arr(ising)=Q_e - sl_in%Q_i_arr(ising)=Q_i + sl_in%Q_e_arr(ising)=-tauk*omega_e ! skipping params() calculation + sl_in%Q_i_arr(ising)=-tauk*omega_i ! skipping params() calculation sl_in%c_beta_arr(ising)=c_beta sl_in%d_beta_arr(ising)=d_beta sl_in%D_norm_arr(ising)=D_norm sl_in%tau_arr(ising)=tau sl_in%omegas_arr(ising) = omega - + sl_in%psi_n_arr(ising) = respsi sl_in%gammafac_arr(ising) = gammafac sl_in%Re_dp_arr(ising) = Re_dp_diagonal(ising) sl_in%Im_dp_arr(ising) = Im_dp_diagonal(ising) diff --git a/slayer/params.f b/slayer/params.f index ce70a8a27..c648c3702 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -52,6 +52,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, !tau_r=mu0*rs**2.0/eta ! resistive time scale tau_r=mu0*(rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale tau_v=tau_r/pr !rho*rs**2.0/visc ! viscous time scale + IF (ABS(chi) > 0.0) THEN tau_perp = ( rs**2.0 ) / chi ELSE diff --git a/slayer/slayer.f b/slayer/slayer.f index bcb0aadd9..28341a853 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -291,12 +291,20 @@ PROGRAM slayer WRITE(*,*)">>> Estimating growth rate" IF (read_eq) THEN + WRITE(*,*)"$^$ entered read_eq" sl_in%chi_prof_arr = chi_prof + WRITE(*,*)"$^$ entering build_inputs" + + WRITE(*,*)"$^$ infile: ",infile + WRITE(*,*)"$^$ ncfile: ",ncfile + CALL build_inputs(infile,ncfile,sl_in) n_k = SIZE(sl_in%qval_arr) + CALL allocate_outputs(n_k,sl_out) + ELSE n_k = 1 mr = mm @@ -324,7 +332,9 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF + WRITE(*,*)"$^$ entering allocate_inputs" CALL allocate_inputs(n_k,sl_in) + WRITE(*,*)"$^$ entering allocate_outputs" CALL allocate_outputs(n_k,sl_out) sl_in%qval_arr = (/ qval /) @@ -346,13 +356,22 @@ PROGRAM slayer sl_in%Qconv_arr = (/ tauk /) END IF - ALLOCATE(sl_out%gamma_est_arr(n_k),sl_out%dels_db_arr(n_k)) DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate '// $ 'estimate on q = ', $ sl_in%qval_arr(k),' rational surface' + WRITE(*,*)"sl_in%Q_e_arr(k): ",sl_in%Q_e_arr(k) + WRITE(*,*)"sl_in%Q_e_arr(k): ",sl_in%Q_e_arr(k) + WRITE(*,*)"sl_in%Q_i_arr(k): ",sl_in%Q_i_arr(k) + WRITE(*,*)"sl_in%P_perp_arr(k): ",sl_in%P_perp_arr(k) + WRITE(*,*)"sl_in%c_beta_arr(k): ",sl_in%c_beta_arr(k) + WRITE(*,*)"sl_in%D_norm_arr(k): ",sl_in%D_norm_arr(k) + WRITE(*,*)"sl_in%tau_arr(k): ",sl_in%tau_arr(k) + WRITE(*,*)"sl_in%gammafac_arr(k): ",sl_in%gammafac_arr(k) + WRITE(*,*)"sl_in%d_beta_arr(k): ",sl_in%d_beta_arr(k) + dels_db=riccati_del_s(sl_in%Q_e_arr(k),sl_in%Q_e_arr(k), ! NOT using Q_arr $ sl_in%Q_i_arr(k),sl_in%P_perp_arr(k), $ sl_in%c_beta_arr(k),sl_in%D_norm_arr(k), @@ -360,6 +379,8 @@ PROGRAM slayer del_s = dels_db * sl_in%d_beta_arr(k) + WRITE(*,*)"del_s: ",del_s + sl_out%gamma_est_arr(k) = sl_in%gammafac_arr(k)/del_s sl_out%dels_db_arr(k) = dels_db WRITE(*,*) @@ -382,15 +403,19 @@ PROGRAM slayer IF (read_eq) THEN - sl_in%chi_prof_arr = chi_prof + IF (.NOT. est_gamma_flag) THEN + sl_in%chi_prof_arr = chi_prof - CALL build_inputs(infile,ncfile,sl_in) - - n_k = SIZE(sl_in%qval_arr) + CALL build_inputs(infile,ncfile,sl_in) + n_k = SIZE(sl_in%qval_arr) + CALL allocate_outputs(n_k,sl_out) + END IF ELSE n_k = 1 + IF (.NOT. est_gamma_flag) THEN + ! Use namelist kinetic inputs instead of equilibrium files CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) @@ -415,8 +440,10 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF - CALL allocate_inputs(n_k,sl_in) - CALL allocate_outputs(n_k,sl_out) + IF (.NOT. est_gamma_flag) THEN + CALL allocate_inputs(n_k,sl_in) + CALL allocate_outputs(n_k,sl_out) + END IF sl_in%qval_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) @@ -435,10 +462,11 @@ PROGRAM slayer sl_in%c_beta_arr = (/ c_beta /) sl_in%lu_arr = (/ lu /) sl_in%Qconv_arr = (/ tauk /) - END IF - ALLOCATE(sl_out%gamma_sol_arr(n_k)) + END IF + END IF + ALLOCATE(re_trace(100),im_trace(100)) DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', @@ -457,7 +485,6 @@ PROGRAM slayer c delta_eff = Re_deltaprime_arr(k) pe = 0.0 - ALLOCATE(re_trace(100),im_trace(100)) re_trace = 0.0 im_trace = 0.0 n_trace = 1 @@ -474,15 +501,17 @@ PROGRAM slayer re_trace(1) = -Q_e END IF + WRITE(*,*)"$^$ calling newton_root(): " CALL newton_root(g_r,g_i,1,fitz_flag) WRITE(*,*) WRITE(*,'(A,F0.3,A)') 'Success! Growth rate = ', $ g_r/tauk, ' [Hz]' - CALL shrink_array(re_trace, n_trace) - CALL shrink_array(im_trace, n_trace) - + !CALL shrink_array(re_trace, n_trace) + !CALL shrink_array(im_trace, n_trace) + n_trace = 100 + IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 8f09f7931..1f06295dd 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -105,8 +105,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, IF(msing>0)THEN CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) - CALL sl_check( nf90_def_var(ncid,"qsing",nf90_int,qsing_dim, - $ qsing_id)) + CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_int, + $ qsing_dim,qsing_id)) CALL sl_check( nf90_def_dim(ncid, "step", SIZE(re_trace), $ tr_dim) ) CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, @@ -125,8 +125,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ nf90_double,qsing_dim,pr_id) ) CALL sl_check( nf90_def_var(ncid,"P",nf90_double, $ qsing_dim,inpr_id) ) - CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, - $ qsing_dim,qr_id) ) + !CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, + !$ qsing_dim,qr_id) ) END IF CALL sl_check( nf90_def_var(ncid,"D",nf90_double, @@ -148,14 +148,14 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, CALL sl_check( nf90_def_var(ncid,"growth rate", $ nf90_double,(/qsing_dim,i_dim/),gs_id) ) CALL sl_check( nf90_def_var(ncid,"growth rate trace", - $ nf90_double,(/tr_dim,i_dim/),tr_id) ) + $ nf90_double,(/qsing_dim,tr_dim,i_dim/),tr_id) ) ! end definitions CALL sl_check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- c set variables c ----------------------------------------------------------------------- CALL sl_check( nf90_put_var(ncid,qsing_id, sl_in%qval_arr)) - CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) + !CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) CALL sl_check( nf90_put_var(ncid,pr_id, sl_in%psi_n_arr)) CALL sl_check( nf90_put_var(ncid,omegas_id, sl_in%omegas_arr)) CALL sl_check( nf90_put_var(ncid,S_id, sl_in%lu_arr)) From 7e7de3fb9d96c037ee88ca39997005ca79970254 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 20 Aug 2025 17:17:22 -0400 Subject: [PATCH 046/305] WIP: P_perp and P_tor --- slayer/delta.f | 41 +++++++++++++++++++++++------------- slayer/gslayer.f | 2 +- slayer/layerinputs.f | 12 +++++++---- slayer/params.f | 41 ++++++++++++++++++++++-------------- slayer/sglobal.f | 9 ++++---- slayer/slayer.f | 47 +++++++++++++++++++++++++++++------------- slayer/slayer_netcdf.f | 13 +++++++----- 7 files changed, 107 insertions(+), 58 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 970b77522..ce8dc2d46 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -295,28 +295,40 @@ FUNCTION riccati_f(tmp_g,inx) !!!!!!!! !IF(present(inx)) my_p=inx!10.0 ! "starting backwards integration at large q" !!!!!!!! - my_p=6.0 + !my_p=6.0 xmin=1e-6 xout=xmin ! SOLVE FOR W BOUNDARY CONDITION - IF (D_norm > (P_perp**(1.0/6.0))) THEN + IF ((D_norm**2.0) > ((iota_e*P_perp)/(P_tor**(2.0/3.0)))) THEN + my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25 + IF (my_p < 6.0) THEN + my_p = 6.0 + END IF + ak = -(g_tmp + ifac*Q_e) - bk = P_perp/(2.0*(D_norm**2.0)) + bk = (iota_e*P_perp*P_tor)/(P_tor*(D_norm**2.0)) - ck_1 = 2.0*(g_tmp + ifac*Q_i)/P_perp - ck_2 = (P_perp + (g_tmp + - $ ifac*Q_i)*(D_norm**2.0))/(2.0*P_perp*(D_norm**2.0)) - ck = (P_perp/(2.0*(D_norm**2.0)))*(1 + ck_1 - ck_2) + ck = bk*(1+(g_tmp+ifac*Q_i)*((P_tor+P_perp)/(P_tor*P_perp))- + $ (P_perp+(g_tmp + + $ ifac*Q_i)*(D_norm**2.0) )*(iota_e/(P_tor*(D_norm**2.0)))) + ! ck_1 = 2.0*(g_tmp + ifac*Q_i)/P_perp + ! ck_2 = (P_perp + (g_tmp + + !$ ifac*Q_i)*(D_norm**2.0))/(2.0*P_perp*(D_norm**2.0)) + ! ck = (P_perp/(2.0*(D_norm**2.0)))*(1 + ck_1 - ck_2) - xk = (ck - SQRT(bk)*(1 - - $ SQRT(bk)*ak))/(2.0*SQRT(bk)) + xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0*SQRT(bk)) W_bound = xk - SQRT(bk)*my_p ELSE + my_p = 1.0/(P_tor**(1.0/6.0)) + IF (my_p < 6.0) THEN + my_p = 6.0 + END IF + ak = -(g_tmp + ifac*Q_e) - bk = P_perp - ck = -ifac*(Q_e - Q_i) + (g_tmp + ifac*Q_i) + bk = P_tor + ck = -ifac*(Q_e - Q_i)*(P_tor/P_perp) + (g_tmp + ifac*Q_i) xk = (ak*bk - ck)/(2.0*SQRT(bk)) W_bound = -1 + xk*my_p - SQRT(bk)*(my_p**3.0) @@ -398,11 +410,12 @@ SUBROUTINE w_der_f(neq,my_p,W,dWdp) fA = (my_p**2)/(g_tmp + ifac*Q_e + (my_p**2.0)) fA_prime = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + $ ifac*Q_e + (my_p**2.0)) - fB = g_tmp*(g_tmp + ifac*Q_i) + 2.0*(g_tmp + - $ ifac*Q_i)*P_perp*(my_p**2.0) + (P_perp**2.0)*(my_p**4.0) + fB = g_tmp*(g_tmp + ifac*Q_i) + (g_tmp + + $ ifac*Q_i)*(P_perp+P_tor)*(my_p**2.0) + + $ (P_perp*P_tor)*(my_p**4.0) fC = g_tmp + ifac*Q_e + ( P_perp + (g_tmp + $ ifac*Q_i)*(D_norm**2.0))*(my_p**2.0) + - $ 2.0*P_perp*(D_norm**2.0)*(my_p**4.0) + $ (1.0/iota_e)*P_tor*(D_norm**2.0)*(my_p**4.0) dWdp(1) = -(fA_prime/my_p)*W(1) - (W(1)**2.0)/my_p + $ (fB/(fA*fC))*(my_p**3.0) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 9eaa11a67..93c016c2d 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -198,7 +198,7 @@ SUBROUTINE allocate_inputs(n_k,sl_in) ALLOCATE(sl_in%qval_arr(n_k),sl_in%omegas_arr(n_k), $ sl_in%Q_e_arr(n_k),sl_in%Q_i_arr(n_k),sl_in%psi_n_arr(n_k), $ sl_in%Re_dp_arr(n_k),sl_in%Im_dp_arr(n_k), - $ sl_in%d_crit_arr(n_k), + $ sl_in%d_crit_arr(n_k),sl_in%P_tor_arr(n_k), $ sl_in%P_perp_arr(n_k),sl_in%tau_arr(n_k), $ sl_in%D_norm_arr(n_k), $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index cb2751453..7fb55ba8d 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -254,10 +254,11 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0,dgeo_val REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_h,l_n,l_t, - $ rho,tau_v,chi,Qconv,lbeta,qintb,gammafac + $ rho,tau_v,Qconv,lbeta,qintb,gammafac REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, $ sigma_par_2,sigma_par,tau_perp,Wd,vte, $ dr_val,chi_par_smfp,chi_par_lmfp,chi_par + REAL(r8), DIMENSION(3) :: chi_s INTEGER :: wit REAL(r8), DIMENSION(0:128) :: psitor, rhotor @@ -306,7 +307,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%Q_e_arr(msing),sl_in%Q_i_arr(msing), $ sl_in%psi_n_arr(msing), $ sl_in%Re_dp_arr(msing),sl_in%Im_dp_arr(msing), - $ sl_in%d_crit_arr(msing), + $ sl_in%d_crit_arr(msing),sl_in%P_tor_arr(msing), $ sl_in%P_perp_arr(msing),sl_in%tau_arr(msing), $ sl_in%D_norm_arr(msing), $ sl_in%d_beta_arr(msing),sl_in%gammafac_arr(msing), @@ -383,7 +384,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) mu_i = 2.0 dr_val = dr_vals(ising) - chi = sl_in%chi_prof_arr(ising) + chi_s(1) = sl_in%chi_p_arr(ising) ! chi_perp + chi_s(2) = sl_in%chi_t_arr(ising) ! chi_tor + chi_s(3) = sl_in%kappa_arr(ising) ! kappa (thermal cond.) ne_arr(ising) = n_e te_arr(ising) = t_e @@ -407,7 +410,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) l_t = 0.0 WRITE(*,*)"$^$ calling params()" - CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + CALL params(n_e,t_e,t_i,omega,chi_s,dr_val,dgeo_val, $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i,zeff,.false.) !!!!!!!!!!! @@ -428,6 +431,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) sl_in%Im_dp_arr(ising) = Im_dp_diagonal(ising) sl_in%d_crit_arr(ising) = dc_tmp sl_in%P_perp_arr(ising) = P_perp + sl_in%P_tor_arr(ising) = P_tor sl_in%Qconv_arr(ising) = tauk ENDDO diff --git a/slayer/params.f b/slayer/params.f index c648c3702..c935c7292 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -8,15 +8,16 @@ MODULE params_mod c----------------------------------------------------------------------- c calculate parameters. c----------------------------------------------------------------------- - SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff + REAL(r8), DIMENSION(3), INTENT(IN) :: chis LOGICAL, INTENT(IN) :: params_check - REAL(r8) :: rho,b_l,v_a,Qconv, + REAL(r8) :: rho,b_l,v_a,Qconv,K_val, $ lbeta,tau_i,tau_h,tau_v REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, $ sigma_par_2,sigma_par,tau_perp,Wd,vte, @@ -53,12 +54,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, tau_r=mu0*(rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale tau_v=tau_r/pr !rho*rs**2.0/visc ! viscous time scale - IF (ABS(chi) > 0.0) THEN - tau_perp = ( rs**2.0 ) / chi - ELSE - tau_perp = 0.0 - END IF - ! this one must be anomalous. calculated back from pr. visc= rho*rs**2.0/tau_v @@ -83,12 +78,25 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 c_beta=(lbeta/(1.0+lbeta))**0.5 - IF (ABS(tau_perp) > 0.0) THEN - P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number + ! CALCULATE Ps + ! chi_tor = the anomalous perpendicular ion momentum diffusivity at the rational surface + ! chi_perp = the anomalous perpendicular energy diffusivity at the rational surface + + K_val = chis(3)/eta ! kappa/eta + Csq = (c_beta**2.0 + (1.0-c_beta**2.0)*K_val ) ! = P_perp + + IF (ABS(Csq) > 0.0) THEN + P_perp = Csq ELSE - P_perp = 0.0 + tau_perp = ( rs**2.0 ) / chis(1) END IF + tau_perp = ( rs**2.0 ) / chis(1) + P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number + + tau_tor = ( rs**2.0 ) / chis(2) + P_tor = tau_r / tau_tor ! toroidal magnetic Prandtl number + ! this is using Fitzpatrick's tau', we need tau eventually d_beta = c_beta*d_i D_norm = (d_beta/rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) @@ -101,28 +109,29 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ $ (1.0+0.2535*Zeff) + ! chis(1) = chi_perp Wd = 0.1 DO wit = 1,10 chi_par_lmfp = (2.0*R0*vte)/(SQRT(pi)*nr*sval*Wd) chi_par = (chi_par_smfp*chi_par_lmfp)/ $ (chi_par_smfp+chi_par_lmfp) - Wd = SQRT(8.0)*((chi/chi_par)**0.25)* + Wd = SQRT(8.0)*((chis(1)/chi_par)**0.25)* $ (1.0/SQRT((rs/R0)*sval*nr)) END DO SELECT CASE(dc_type) CASE("lar") - dc_tmp = 0.5*(-dr_val)*(pi**1.5)*((chi_par/chi)**0.25)* + dc_tmp = 0.5*(-dr_val)*(pi**1.5)*((chi_par/chis(1))**0.25)* $ ( (nr*sval)/(R0*rs) )**0.5 CASE("rfitzp") dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd CASE("toroidal") dc_tmp = 0.5*(-dr_val)*(pi**1.5)* - $ ((chi_par/chi)**0.25)*dgeo_val + $ ((chi_par/chis(1))**0.25)*dgeo_val CASE default dc_tmp = 0.5*(-dr_val)*(pi**1.5)* - $ ((chi_par/chi)**0.25)*dgeo_val + $ ((chi_par/chis(1))**0.25)*dgeo_val END SELECT ELSE diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 77a8699af..817385600 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -9,8 +9,8 @@ MODULE sglobal_mod REAL(r8) :: mr,nr REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, - $ d_beta,D_norm,P_perp,gamma_fac - REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i, + $ d_beta,D_norm,P_perp,P_tor,gamma_fac + REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i,iota_e, $ delta_n,layfac,Qconv,lnLamb,deltaprim,dc_tmp, $ d_crit,tau_r,tauk,g_r,g_i,delta_eff REAL(r8), DIMENSION(:), ALLOCATABLE :: re_trace,im_trace @@ -29,10 +29,11 @@ MODULE sglobal_mod TYPE slayer_inputs_type INTEGER, ALLOCATABLE :: qval_arr(:) - REAL(r8), ALLOCATABLE :: chi_prof_arr(:),psi_n_arr(:), + REAL(r8), ALLOCATABLE :: chi_p_arr(:),chi_t_arr(:), + $ kappa_arr(:),psi_n_arr(:), $ lu_arr(:),Qconv_arr(:),Q_e_arr(:),Q_i_arr(:),c_beta_arr(:), $ d_beta_arr(:),D_norm_arr(:),tau_arr(:),P_perp_arr(:), - $ omegas_arr(:),omegas_e_arr(:),omegas_i_arr(:), + $ P_tor_arr(:),omegas_arr(:),omegas_e_arr(:),omegas_i_arr(:), $ gammafac_arr(:),Re_dp_arr(:),Im_dp_arr(:),d_crit_arr(:) END TYPE slayer_inputs_type diff --git a/slayer/slayer.f b/slayer/slayer.f index 28341a853..08f323068 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -41,7 +41,9 @@ PROGRAM slayer REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, - $ delta_imag,Qratio,chi + $ delta_imag,Qratio + REAL(r8), DIMENSION(3) :: chis + INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns @@ -108,7 +110,7 @@ PROGRAM slayer intau=0.0 inlu=0.0 Q0=0.0 - chi=0.0 + chis=0.0 gamma_fac=0.0 dc_type="" delta_prime=(0.0,0.0) @@ -180,7 +182,7 @@ PROGRAM slayer c calculate parameters as needed. c----------------------------------------------------------------------- IF (params_flag) THEN - CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -228,7 +230,7 @@ PROGRAM slayer mr=REAL(mms(k)) nr=REAL(nns(k)) inpr=prs(k) - CALL params(n_es(k),t_es(k),t_is(k),omegas(k),chi,dr_val, + CALL params(n_es(k),t_es(k),t_is(k),omegas(k),chis,dr_val, $ dgeo_val,l_ns(k),l_ts(k),qvals(k),svals(k),bts(k), $ rss(k),R0s(k),mu_is(k),zeffs(k),params_check) inQ=Q @@ -293,7 +295,9 @@ PROGRAM slayer IF (read_eq) THEN WRITE(*,*)"$^$ entered read_eq" - sl_in%chi_prof_arr = chi_prof + sl_in%chi_p_arr = chi_p_prof + sl_in%chi_t_arr = chi_t_prof + sl_in%kappa_arr = kappa_prof WRITE(*,*)"$^$ entering build_inputs" @@ -310,7 +314,11 @@ PROGRAM slayer mr = mm nr = nn - CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val,dgeo_val, + chis(1) = chi_p_prof(1) ! chi_perp + chis(2) = chi_t_prof(1) ! chi_tor + chis(3) = kappa_prof(1) ! kappa (thermal cond.) + + CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) IF (ABS(inQ) > 0.0) THEN @@ -347,6 +355,7 @@ PROGRAM slayer sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) sl_in%d_crit_arr = (/ dc_tmp /) sl_in%P_perp_arr = (/ P_perp /) + sl_in%P_tor_arr = (/ P_tor /) sl_in%tau_arr = (/ tau /) sl_in%D_norm_arr = (/ D_norm /) sl_in%d_beta_arr = (/ d_beta /) @@ -366,6 +375,7 @@ PROGRAM slayer WRITE(*,*)"sl_in%Q_e_arr(k): ",sl_in%Q_e_arr(k) WRITE(*,*)"sl_in%Q_i_arr(k): ",sl_in%Q_i_arr(k) WRITE(*,*)"sl_in%P_perp_arr(k): ",sl_in%P_perp_arr(k) + WRITE(*,*)"sl_in%P_tor_arr(k): ",sl_in%P_tor_arr(k) WRITE(*,*)"sl_in%c_beta_arr(k): ",sl_in%c_beta_arr(k) WRITE(*,*)"sl_in%D_norm_arr(k): ",sl_in%D_norm_arr(k) WRITE(*,*)"sl_in%tau_arr(k): ",sl_in%tau_arr(k) @@ -404,7 +414,9 @@ PROGRAM slayer IF (read_eq) THEN IF (.NOT. est_gamma_flag) THEN - sl_in%chi_prof_arr = chi_prof + sl_in%chi_p_arr = chi_p_prof + sl_in%chi_t_arr = chi_t_prof + sl_in%kappa_arr = kappa_prof CALL build_inputs(infile,ncfile,sl_in) @@ -416,8 +428,12 @@ PROGRAM slayer IF (.NOT. est_gamma_flag) THEN + chis(1) = chi_p_prof(1) ! chi_perp + chis(2) = chi_t_prof(1) ! chi_tor + chis(3) = kappa_prof(1) ! kappa (thermal cond.) + ! Use namelist kinetic inputs instead of equilibrium files - CALL params(n_e,t_e,t_i,omega,chi_prof(1),dr_val,dgeo_val, + CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) ! Override desired normalized parameters @@ -455,6 +471,7 @@ PROGRAM slayer sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) sl_in%d_crit_arr = (/ dc_tmp /) sl_in%P_perp_arr = (/ P_perp /) + sl_in%P_tor_arr = (/ P_tor /) sl_in%tau_arr = (/ tau /) sl_in%D_norm_arr = (/ D_norm /) sl_in%d_beta_arr = (/ d_beta /) @@ -474,10 +491,12 @@ PROGRAM slayer Q_e = sl_in%Q_e_arr(k) Q_i = sl_in%Q_i_arr(k) P_perp = sl_in%P_perp_arr(k) + P_tor = sl_in%P_tor_arr(k) tau = sl_in%tau_arr(k) D_norm = sl_in%D_norm_arr(k) c_beta = sl_in%c_beta_arr(k) tauk = sl_in%Qconv_arr(k) + iota_e = Q_e / (Q_e - Q_i) ! (Deltaprime - d_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - @@ -511,7 +530,7 @@ PROGRAM slayer !CALL shrink_array(re_trace, n_trace) !CALL shrink_array(im_trace, n_trace) n_trace = 100 - + IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk @@ -594,7 +613,7 @@ PROGRAM slayer WRITE(*,*)"running br_th scan" - CALL params(n_e,t_e,t_i,omega,chi,dr_val,dgeo_val, + CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1071,7 +1090,7 @@ PROGRAM slayer DO k=0,knum ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) - CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chi,dr_val, + CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chis,dr_val, $ dgeo_val,l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1159,7 +1178,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e,t_e*ks(j,k),t_i*ks(j,k), - $ omega*js(j,k),chi,dr_val,dgeo_val,l_n,l_t,qval, + $ omega*js(j,k),chis,dr_val,dgeo_val,l_n,l_t,qval, $ sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1245,7 +1264,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) CALL params(n_e*ks(j,k),t_e*js(j,k),t_i*js(j,k),omega, - $ chi,dr_val,dgeo_val,l_n,l_t,qval,sval,bt,rs,R0, + $ chis,dr_val,dgeo_val,l_n,l_t,qval,sval,bt,rs,R0, $ mu_i,zeff,params_check) inQ=Q inQ_e=Q_e @@ -1330,7 +1349,7 @@ PROGRAM slayer ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) - CALL params(n_e*ks(j,k),t_e,t_i,omega,chi,dr_val, + CALL params(n_e*ks(j,k),t_e,t_i,omega,chis,dr_val, $ dgeo_val,l_n,l_t,qval,sval,bt*js(j,k),rs,R0,mu_i, $ zeff,params_check) inQ=Q diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 1f06295dd..df20f48c5 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -57,9 +57,9 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, TYPE(slayer_outputs_type), INTENT(IN) :: sl_out INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, - $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,inpr_id,S_id, + $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,p_perp_id,S_id, $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,tr_id,tr_dim, - $ qsing_id,qc_id + $ qsing_id,qc_id,p_tor_id INTEGER :: run, run_dimid, point_dimid, varids(4) @@ -123,8 +123,10 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ qsing_dim,S_id)) CALL sl_check( nf90_def_var(ncid,"psi_n_rational", $ nf90_double,qsing_dim,pr_id) ) - CALL sl_check( nf90_def_var(ncid,"P",nf90_double, - $ qsing_dim,inpr_id) ) + CALL sl_check( nf90_def_var(ncid,"P_perp",nf90_double, + $ qsing_dim,p_perp_id) ) + CALL sl_check( nf90_def_var(ncid,"P_tor",nf90_double, + $ qsing_dim,p_tor_id) ) !CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, !$ qsing_dim,qr_id) ) END IF @@ -163,7 +165,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, !CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) CALL sl_check( nf90_put_var(ncid,Q_e_id, sl_in%Q_e_arr)) CALL sl_check( nf90_put_var(ncid,Q_i_id, sl_in%Q_i_arr)) - CALL sl_check( nf90_put_var(ncid,inpr_id, sl_in%P_perp_arr)) + CALL sl_check( nf90_put_var(ncid,p_perp_id, sl_in%P_perp_arr)) + CALL sl_check( nf90_put_var(ncid,p_tor_id, sl_in%P_tor_arr)) CALL sl_check( nf90_put_var(ncid,Dnorm_id, sl_in%D_norm_arr)) CALL sl_check( nf90_put_var(ncid,dpp_id, From 762f854b343707bd76bfc452b1027bc7b3257c18 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 21 Aug 2025 14:43:38 -0400 Subject: [PATCH 047/305] WIP: 2x2 coupling --- input/slayer.in | 9 ++- slayer/gslayer.f | 40 ++++++++++++ slayer/layerinputs.f | 29 +++++---- slayer/params.f | 7 +-- slayer/sglobal.f | 5 +- slayer/slayer.f | 141 +++++++++++++++++++++++++++++++++++-------- 6 files changed, 184 insertions(+), 47 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 8a5b514d5..dd0230d69 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -23,7 +23,9 @@ zeff=2.0 ! plasma Z_effective dr_val=-0.1 ! GGJ resistive interchange criterion D_R dgeo_val=10.0 ! J.W. Connor geometric prefactor for Delta_crit - chi_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface perpendicular ion momentum diffusivity OR perpendicular energy diffusivity profile + chi_p_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular energy diffusivity profile + chi_t_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular ion momentum diffusivity profile + kappa_prof=0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 ! Rational surface thermal conductivity profile (NOTE: set > 0 to override chi_p_prof) ! >>> ! >>> Input normalized layer parameters. If read_eq=f, each will individually override internal calcuation using above inputs ! >>> @@ -46,10 +48,11 @@ jnum=100 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan along with the other. Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes - scan_radius=1.5 ! stability scan width for Re(Q),Im(Q) scan + scan_width=1.5 ! stability scan width for Re(Q),Im(Q) scan dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 - fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation + fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation + coupling_flag=t ! Solve determinant problem and output 2D scan QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space QPescan_flag=f ! scan (Q,Pe) space for delta and torque. diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 93c016c2d..ccf5dbbad 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -374,6 +374,46 @@ SUBROUTINE grow_array(arr, old_size, new_size) END SUBROUTINE grow_array c c +c +c----------------------------------------------------------------------- +c Subprogram 6. determinant +c Increase scan array size IF necessary +c----------------------------------------------------------------------- + SUBROUTINE calc_determinant(matk, nk, detk) + IMPLICIT NONE + + ! Arguments + INTEGER, INTENT(IN) :: nk ! Matrix size (2 or 3) + COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! Input matrix + COMPLEX(r8), INTENT(OUT) :: detk ! Determinant result + INTEGER :: status ! Status (0=success, -1=error) + + ! Local variables + + status = 0 ! Initialize status as success + + SELECT CASE (nk) + CASE (2) + ! 2x2 determinant: ad - bc + detk = matk(1,1) * matk(2,2) - matk(1,2) * matk(2,1) + + CASE (3) + ! 3x3 determinant using cofactor expansion along first row + detk = matk(1,1)*(matk(2,2)*matk(3,3)-matk(2,3) + $ *matk(3,2))-matk(1,2)*(matk(2,1)*matk(3,3) + $ -matk(2,3)*matk(3,1))+matk(1,3)*(matk(2,1) + $ *matk(3,2)-matk(2,2)*matk(3,1)) + + CASE default + ! Unsupported matrix size + detk = (0.0, 0.0) + status = -1 + + END SELECT + RETURN + END SUBROUTINE calc_determinant +c +c c Adapted from SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) LOGICAL, INTENT(IN) :: fitz_flag diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 7fb55ba8d..dd2adeec1 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -17,7 +17,7 @@ MODULE layerinputs_mod c subprogram 1. read_stride_netcdf_diagonal. c Read STRIDE netcdf file for SLAYER inputs only. c----------------------------------------------------------------------- - SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, + SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) @@ -25,6 +25,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, CHARACTER(512), INTENT(IN) :: ncfile REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ Re_dp_diagonal,Im_dp_diagonal + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE, INTENT(OUT):: dp_mat REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, $ psi_n_rational, shear,dgeo REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, @@ -39,7 +40,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, $ dp_id, qr_id,pr_id,dgeo_id,shear_id,ro_id,bt0_id,psio_id, $ mpsi_id,msing_id,nn_id,resm_id,drr_id ! Explicit kind for NetCDF variables INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: delta_prime INTEGER :: i INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, $ msing_len,nn_len,dr_len ! Attribute lengths @@ -63,7 +63,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ALLOCATE(Re_dp_diagonal(msing),q_rational(msing), $ psi_n_rational(msing),shear(msing),dgeo(msing), $ resm(msing),Im_dp_diagonal(msing),dr_vals(msing)) - ALLOCATE(delta_prime(msing, msing,2)) + ALLOCATE(dp_mat(msing, msing,2)) stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) CALL sl_check(stat) @@ -114,8 +114,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, stat = nf90_get_att(ncid, nn_id, "n", nn) CALL sl_check(stat) - ! Read the diagonal of delta_prime. The results will be put on a 1D temporary array. - stat = nf90_get_var(ncid, dp_id, delta_prime,start=(/ 1,1,1 /)) + ! Read the diagonal of delta prime. The results will be put on a 1D temporary array. + stat = nf90_get_var(ncid, dp_id, dp_mat,start=(/ 1,1,1 /)) CALL sl_check(stat) ! Read 1D variables stat = nf90_get_var(ncid, qr_id, q_rational) @@ -133,11 +133,10 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing, ! Extract Diagonal, with 3rd index signifying REAL part DO i = 1, msing - Re_dp_diagonal(i) = delta_prime(i, i, 1) - Im_dp_diagonal(i) = delta_prime(i, i, 2) + Re_dp_diagonal(i) = dp_mat(i, i, 1) + Im_dp_diagonal(i) = dp_mat(i, i, 2) END DO ! Clean Up - DEALLOCATE(delta_prime) stat = nf90_close(ncid) CALL sl_check(stat) @@ -263,7 +262,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8), DIMENSION(0:128) :: psitor, rhotor REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor - + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: dp_mat REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal,dr_arr, $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal,dr_vals, @@ -281,9 +280,10 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c Read in STRIDE netcdf c----------------------------------------------------------------------- - CALL read_stride_netcdf_diagonal(ncfile,msing,Re_dp_diagonal, - $ Im_dp_diagonal,q_rational,psi_n_rational,dgeo, - $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi_arr,nn,resm) + CALL read_stride_netcdf_diagonal(ncfile,msing,dp_mat, + $ Re_dp_diagonal,Im_dp_diagonal,q_rational, + $ psi_n_rational,dgeo,shear,r_o,my_bt0,my_psio,dr_vals, + $ mpsi_arr,nn,resm) WRITE(*,*)"msing_out=",msing WRITE(*,*)"Re_dp_diagonal=",Re_dp_diagonal WRITE(*,*)"Im_dp_diagonal=",Im_dp_diagonal @@ -302,7 +302,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c Allocate SLAYER input type arrays ALLOCATE(sl_in%qval_arr(msing),sl_in%omegas_arr(msing), - $ sl_in%omegas_e_arr(msing), + $ sl_in%omegas_e_arr(msing),sl_in%dp_matrix(msing,msing), $ sl_in%omegas_i_arr(msing),!sl_in%chi_prof_arr(msing), $ sl_in%Q_e_arr(msing),sl_in%Q_i_arr(msing), $ sl_in%psi_n_arr(msing), @@ -342,6 +342,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) CALL equil_read(out_unit) + ! Input Delta' matrix + sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1),dp_mat(:,:,2)) + c----------------------------------------------------------------------- c loop across singular surfaces, evaluate spline quantities. c----------------------------------------------------------------------- diff --git a/slayer/params.f b/slayer/params.f index c935c7292..088c29b7b 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -17,7 +17,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, LOGICAL, INTENT(IN) :: params_check - REAL(r8) :: rho,b_l,v_a,Qconv,K_val, + REAL(r8) :: rho,b_l,v_a,Qconv,K_val,Csq,tau_tor, $ lbeta,tau_i,tau_h,tau_v REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, $ sigma_par_2,sigma_par,tau_perp,Wd,vte, @@ -122,7 +122,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, SELECT CASE(dc_type) CASE("lar") - dc_tmp = 0.5*(-dr_val)*(pi**1.5)*((chi_par/chis(1))**0.25)* + dc_tmp=0.5*(-dr_val)*(pi**1.5)*((chi_par/chis(1))**0.25)* $ ( (nr*sval)/(R0*rs) )**0.5 CASE("rfitzp") dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd @@ -130,8 +130,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, dc_tmp = 0.5*(-dr_val)*(pi**1.5)* $ ((chi_par/chis(1))**0.25)*dgeo_val CASE default - dc_tmp = 0.5*(-dr_val)*(pi**1.5)* - $ ((chi_par/chis(1))**0.25)*dgeo_val + dc_tmp = 0.0 END SELECT ELSE diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 817385600..a91bf55a5 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -35,6 +35,7 @@ MODULE sglobal_mod $ d_beta_arr(:),D_norm_arr(:),tau_arr(:),P_perp_arr(:), $ P_tor_arr(:),omegas_arr(:),omegas_e_arr(:),omegas_i_arr(:), $ gammafac_arr(:),Re_dp_arr(:),Im_dp_arr(:),d_crit_arr(:) + COMPLEX(r8), ALLOCATABLE :: dp_matrix(:,:) END TYPE slayer_inputs_type TYPE slayer_outputs_type @@ -47,6 +48,6 @@ MODULE sglobal_mod COMPLEX(r8), PARAMETER :: ifac=(0,1) - CHARACTER(2) :: sn - + CHARACTER(2) :: sn,sm + END MODULE sglobal_mod diff --git a/slayer/slayer.f b/slayer/slayer.f index 08f323068..c969e3fb5 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -31,26 +31,26 @@ PROGRAM slayer $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,stabscan_flag,read_eq,est_gamma_flag, - $ match_gamma_flag,fitz_flag,br_th_flag - REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_radius, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val + $ match_gamma_flag,fitz_flag,coupling_flag,br_th_flag + REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_width,l_n,l_t, + $ qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, - $ tmp_gamma,ingamma,delta_prime + $ tmp_gamma,ingamma,delta_prime,det_val REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, $ delta_imag,Qratio - REAL(r8), DIMENSION(3) :: chis - + REAL(r8) :: chis(3) + COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:),result_matrix(:,:) INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl,bal, $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes - REAL(r8), DIMENSION(8) :: chi_prof + REAL(r8), DIMENSION(8) :: chi_p_prof, chi_t_prof, kappa_prof REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs REAL(r8), DIMENSION(:,:), ALLOCATABLE :: $ js,ks,psis,jxbs,Q_sols,br_ths @@ -64,12 +64,12 @@ PROGRAM slayer NAMELIST/slayer_input/input_flag,infile, $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, - $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_prof,inpr,inpe, - $ inQ,inQ_e,inQ_i,inc_beta,inds,intau,Q0,delta_prime, - $ delta_n_p,ingamma - NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_radius, - $ dc_type,read_eq,fitz_flag,QPscan_flag,Qscan_flag, - $ QPescan_flag,Qbscan_flag,onscan_flag,otscan_flag, + $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_p_prof, + $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,Q0,delta_prime,delta_n_p,ingamma + NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, + $ dc_type,read_eq,fitz_flag,coupling_flag,QPscan_flag, + $ Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag,otscan_flag, $ ntscan_flag,nbtscan_flag,parflow_flag,peohmonly_flag, $ Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, @@ -103,7 +103,9 @@ PROGRAM slayer inQ_e=0.0 inQ_i=0.0 inpr=0.0 - chi_prof=0.0 + chi_p_prof=0.0 + chi_t_prof=0.0 + kappa_prof=0.0 inpe=0.0 inc_beta=0.0 inds=0.0 @@ -120,7 +122,7 @@ PROGRAM slayer jnum=500 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan alont with the other. Q_num=100 ! resolution for stab. scan along Re(Q) axis - scan_radius = 2.0 + scan_width = 2.0 in_unit=1 out_unit=2 out2_unit=3 @@ -132,6 +134,7 @@ PROGRAM slayer est_gamma_flag=.FALSE. match_gamma_flag=.FALSE. fitz_flag=.FALSE. + coupling_flag=.FALSE. QPscan_flag=.FALSE. ! scan (Q,P) space for delta and torque. QPescan_flag=.FALSE. ! scan (Q,Pe) space for delta and torque. Qbscan_flag=.FALSE. ! scan (Q,beta) space for delta and torque. @@ -178,6 +181,12 @@ PROGRAM slayer ELSE WRITE(UNIT=sn,FMT='(I2)') nn ENDIF + IF (mm<10) THEN + WRITE(UNIT=sm,FMT='(I1)') nn + sm=ADJUSTL(sm) + ELSE + WRITE(UNIT=sm,FMT='(I2)') nn + ENDIF c----------------------------------------------------------------------- c calculate parameters as needed. c----------------------------------------------------------------------- @@ -484,6 +493,8 @@ PROGRAM slayer END IF ALLOCATE(re_trace(100),im_trace(100)) + ALLOCATE(sl_out%r_trace(n_k,100), + $ sl_out%i_trace(n_k,100)) DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', @@ -542,9 +553,6 @@ PROGRAM slayer END IF ENDDO - ALLOCATE(sl_out%r_trace(n_k,n_trace), - $ sl_out%i_trace(n_k,n_trace)) - sl_out%r_trace(k,:) = re_trace sl_out%i_trace(k,:) = im_trace @@ -557,9 +565,9 @@ PROGRAM slayer WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// $ 'Im(Q)] scan with radius = ', - $ scan_radius + $ scan_width - ing_step = (2.0 * scan_radius) / (Q_num - 1) + ing_step = (2.0 * scan_width) / (Q_num - 1) count = 0 ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) @@ -567,8 +575,8 @@ PROGRAM slayer DO i = 1, (Q_num+1) DO j = 1, Q_num - ing_coarse = -scan_radius + (i - 1) * ing_step - iing_coarse = -scan_radius + (j - 1) * ing_step + ing_coarse = -scan_width + (i - 1) * ing_step + iing_coarse = -scan_width + (j - 1) * ing_step ! Evaluate riccati function g_tmp = CMPLX(ing_coarse,iing_coarse) IF (fitz_flag) THEN @@ -589,7 +597,7 @@ PROGRAM slayer ENDDO OPEN(UNIT=out_unit,FILE="slayer_stability_n"// - $ TRIM(sn)//".out", STATUS="UNKNOWN") + $ TRIM(sn)//"m"//TRIM(sm)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" DO i=1,Q_num+1 @@ -603,6 +611,88 @@ PROGRAM slayer ENDIF + IF (coupling_flag) THEN + WRITE(*,*)"------------------------------------------" + WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// + $ 'Im(Q)] determinant scan with radius = ', + $ scan_width + + ing_step = (2.0 * scan_width) / (Q_num - 1) + count = 0 + + IF (.NOT. stabscan_flag) THEN + ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) + ALLOCATE(deltas(1:(Q_num+1),1:Q_num)) + END IF + + inQs=0.0; iinQs=0.0; deltas=(0.0,0.0) + ALLOCATE(delta_Q(n_k,n_k)) + delta_Q=(0.0,0.0) + + DO i = 1, (Q_num+1) + DO j = 1, Q_num + ing_coarse = -scan_width + (i - 1) * ing_step + iing_coarse = -scan_width + (j - 1) * ing_step + inQs(i) = ing_coarse + iinQs(j) = iing_coarse + + ! Evaluate riccati function + g_tmp = CMPLX(ing_coarse,iing_coarse) + IF (n_k == 1) THEN + WRITE(*,*)"Error: no coupling for msing = 1" + stop + ELSEIF (n_k == 2) THEN + DO k=1,2 + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + P_perp = sl_in%P_perp_arr(k) + P_tor = sl_in%P_tor_arr(k) + tau = sl_in%tau_arr(k) + D_norm = sl_in%D_norm_arr(k) + c_beta = sl_in%c_beta_arr(k) + tauk = sl_in%Qconv_arr(k) + iota_e = Q_e / (Q_e - Q_i) + + ! (Deltaprime - d_crit)/S^1/3 + delta_eff = (sl_in%Re_dp_arr(k) - + $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) + + delta=riccati_f(((g_tmp*sl_in%Qconv_arr(1))/ + $ tauk)) + delta_Q(k,k) = delta + END DO + + ! Calculate Deltaprime - Delta(Q) + result_matrix = sl_in%dp_matrix - delta_Q + + ! Calculate determinant + CALL calc_determinant(result_matrix, n_k, det_val) + deltas(i,j) = det_val + ELSEIF (n_k == 3) THEN + WRITE(*,*)"Error: no support for msing = 3" + stop + ELSE + WRITE(*,*)"Error: no support for msing < 3" + stop + END IF + ENDDO + ENDDO + + OPEN(UNIT=out_unit,FILE="slayer_determinants_n"// + $ TRIM(sn)//".out", STATUS="UNKNOWN") + WRITE(out_unit,'(1x,4(a17))') "RE(Q)", + $ "IM(Q)","RE(det)","IM(det)" + DO i=1,Q_num+1 + DO j=1,Q_num + WRITE(out_unit,'(1x,4(es17.8e3))') + $ inQs(i),iinQs(j), + $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) + ENDDO + ENDDO + CLOSE(out_unit) + + END IF + CALL output_gamma(est_gamma_flag,sl_in,sl_out) stop ENDIF @@ -1090,8 +1180,9 @@ PROGRAM slayer DO k=0,knum ks(j,k)=k_min+(k_max-k_min)*(REAL(k)/knum) - CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chis,dr_val, - $ dgeo_val,l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + CALL params(n_e*ks(j,k),t_e,t_i,omega*js(j,k),chis, + $ dr_val,dgeo_val,l_n,l_t,qval,sval,bt,rs, + $ R0,mu_i,zeff,params_check) inQ=Q inQ_e=Q_e inQ_i=Q_i From b90a844b46b78ce07da1248d04e8ac2ea3b7e6d8 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 11 Sep 2025 23:14:34 -0400 Subject: [PATCH 048/305] WIP: improved scan logic --- slayer/layerinputs.f | 4 +- slayer/params.f | 20 ++++++- slayer/slayer.f | 130 ++++++++++++++++++++++++----------------- slayer/slayer_netcdf.f | 10 +++- 4 files changed, 104 insertions(+), 60 deletions(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index dd2adeec1..be8f64e8a 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -376,7 +376,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) t_e = kin%f(4)/e n_i = kin%f(1) t_i = kin%f(3)/e - zeff = kin%f(9) + + zeff = 2.0!kin%f(9) + omega = kin%f(5) my_qval = q_rational(ising)!sq%f(4) my_sval = shear(ising) diff --git a/slayer/params.f b/slayer/params.f index 088c29b7b..dcf069788 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -24,6 +24,10 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ chi_par_smfp,chi_par_lmfp,chi_par INTEGER :: wit + WRITE(*,*)"params.f n_e: ",n_e + WRITE(*,*)"params.f t_e: ",t_e + WRITE(*,*)"params.f t_i: ",t_i + lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) ! mu_i: ion mass ratio to proton @@ -108,6 +112,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, vte = SQRT((2.0*(t_e*chag))/m_e) chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ $ (1.0+0.2535*Zeff) + WRITE(*,*)"params.f Zeff: ",Zeff ! chis(1) = chi_perp Wd = 0.1 @@ -118,7 +123,16 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ (chi_par_smfp+chi_par_lmfp) Wd = SQRT(8.0)*((chis(1)/chi_par)**0.25)* $ (1.0/SQRT((rs/R0)*sval*nr)) + END DO + WRITE(*,*)"params.f Wd: ",Wd + WRITE(*,*)"params.f chis(1): ",chis(1) + WRITE(*,*)"params.f chis(2): ",chis(2) + WRITE(*,*)"params.f chis(3): ",chis(3) + WRITE(*,*)"params.f rs: ",rs + WRITE(*,*)"params.f R0: ",R0 + WRITE(*,*)"params.f sval: ",sval + WRITE(*,*)"params.f nr: ",nr SELECT CASE(dc_type) CASE("lar") @@ -133,6 +147,10 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, dc_tmp = 0.0 END SELECT + WRITE(*,*)"params.f dr_val: ",dr_val + WRITE(*,*)"params.f dgeo_val: ",dgeo_val + WRITE(*,*)"params.f dc_tmp: ",dc_tmp + ELSE dc_tmp = 0.0 END IF @@ -151,4 +169,4 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, RETURN END SUBROUTINE params - END MODULE params_mod + END MODULE params_mod \ No newline at end of file diff --git a/slayer/slayer.f b/slayer/slayer.f index c969e3fb5..703722053 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -181,12 +181,6 @@ PROGRAM slayer ELSE WRITE(UNIT=sn,FMT='(I2)') nn ENDIF - IF (mm<10) THEN - WRITE(UNIT=sm,FMT='(I1)') nn - sm=ADJUSTL(sm) - ELSE - WRITE(UNIT=sm,FMT='(I2)') nn - ENDIF c----------------------------------------------------------------------- c calculate parameters as needed. c----------------------------------------------------------------------- @@ -495,6 +489,10 @@ PROGRAM slayer ALLOCATE(re_trace(100),im_trace(100)) ALLOCATE(sl_out%r_trace(n_k,100), $ sl_out%i_trace(n_k,100)) + + !-------------- + n_k = 1 + !-------------- DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', @@ -509,6 +507,18 @@ PROGRAM slayer tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) + WRITE(*,*)"slayer.f Q_e: ",Q_e + WRITE(*,*)"slayer.f Q_i: ",Q_i + WRITE(*,*)"slayer.f P_perp: ",P_perp + WRITE(*,*)"slayer.f P_tor: ",P_tor + WRITE(*,*)"slayer.f tau: ",tau + WRITE(*,*)"slayer.f D_norm: ",D_norm + WRITE(*,*)"slayer.f tauk: ",tauk + + WRITE(*,*)"slayer.f iota_e: ",iota_e + WRITE(*,*)"slayer.f D_prime: ",sl_in%Re_dp_arr(k) + WRITE(*,*)"slayer.f D_crit: ",sl_in%d_crit_arr(k) + ! (Deltaprime - d_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) @@ -545,71 +555,80 @@ PROGRAM slayer IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk - sl_out%gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = 0.0!g_r/tauk! THIS IS FOR PLOT ELSE re_trace = re_trace/tauk im_trace = -im_trace/tauk - sl_out%gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = 0.0!-g_r/tauk! THIS IS FOR PLOT END IF - ENDDO - sl_out%r_trace(k,:) = re_trace - sl_out%i_trace(k,:) = im_trace - - IF (.NOT. (est_gamma_flag)) THEN - sl_in%d_beta_arr = (/ 0. /) - sl_out%dels_db_arr = (/ 0. /) - END IF - - IF (stabscan_flag) THEN - WRITE(*,*)"------------------------------------------" - WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// + IF (stabscan_flag) THEN + WRITE(*,*)"------------------------------------------" + WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// $ 'Im(Q)] scan with radius = ', $ scan_width - ing_step = (2.0 * scan_width) / (Q_num - 1) - count = 0 - - ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) - ALLOCATE(deltas(1:(Q_num+1),1:Q_num)) - - DO i = 1, (Q_num+1) - DO j = 1, Q_num - ing_coarse = -scan_width + (i - 1) * ing_step - iing_coarse = -scan_width + (j - 1) * ing_step - ! Evaluate riccati function - g_tmp = CMPLX(ing_coarse,iing_coarse) - IF (fitz_flag) THEN - delta=riccati_f(g_tmp) - ELSE - delta=riccati(iing_coarse,Q_e,Q_i,P_perp, + ing_step = (2.0 * scan_width) / (Q_num - 1) + count = 0 + + ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) + ALLOCATE(deltas(1:(Q_num+1),1:Q_num)) + + DO i = 1, (Q_num+1) + DO j = 1, Q_num + ing_coarse = -scan_width + (i - 1) * ing_step + iing_coarse = -scan_width + (j - 1) * ing_step + ! Evaluate riccati function + g_tmp = CMPLX(ing_coarse,iing_coarse) + IF (fitz_flag) THEN + delta=riccati_f(g_tmp) + ELSE + delta=riccati(iing_coarse,Q_e,Q_i,P_perp, $ c_beta,D_norm,tau,pe, $ iinQ=ing_coarse) - END IF - inQs(i) = ing_coarse - IF (fitz_flag) THEN - iinQs(j) = iing_coarse - ELSE - iinQs(j) = -iing_coarse - END IF - deltas(i,j) = delta + END IF + inQs(i) = ing_coarse + IF (fitz_flag) THEN + iinQs(j) = iing_coarse + ELSE + iinQs(j) = -iing_coarse + END IF + deltas(i,j) = delta + ENDDO ENDDO - ENDDO - OPEN(UNIT=out_unit,FILE="slayer_stability_n"// + + IF (k<10) THEN + WRITE(UNIT=sm,FMT='(I1)') sl_in%qval_arr(k) + sm=ADJUSTL(sm) + ELSE + WRITE(UNIT=sm,FMT='(I2)') sl_in%qval_arr(k) + ENDIF + + OPEN(UNIT=out_unit,FILE="slayer_stability_n"// $ TRIM(sn)//"m"//TRIM(sm)//".out", STATUS="UNKNOWN") - WRITE(out_unit,'(1x,4(a17))') "RE(Q)", + WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" - DO i=1,Q_num+1 - DO j=1,Q_num - WRITE(out_unit,'(1x,4(es17.8e3))') + DO i=1,Q_num+1 + DO j=1,Q_num + WRITE(out_unit,'(1x,4(es17.8e3))') $ inQs(i),iinQs(j), $ REAL(deltas(i,j)),AIMAG(deltas(i,j)) + ENDDO ENDDO - ENDDO - CLOSE(out_unit) + CLOSE(out_unit) + + DEALLOCATE(inQs,iinQs,deltas) + ENDIF + ENDDO + + sl_out%r_trace(k,:) = re_trace + sl_out%i_trace(k,:) = im_trace - ENDIF + IF (.NOT. (est_gamma_flag)) THEN + sl_in%d_beta_arr = (/ 0. /) + sl_out%dels_db_arr = (/ 0. /) + END IF IF (coupling_flag) THEN WRITE(*,*)"------------------------------------------" @@ -620,10 +639,10 @@ PROGRAM slayer ing_step = (2.0 * scan_width) / (Q_num - 1) count = 0 - IF (.NOT. stabscan_flag) THEN + !IF (.NOT. stabscan_flag) THEN ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) ALLOCATE(deltas(1:(Q_num+1),1:Q_num)) - END IF + !END IF inQs=0.0; iinQs=0.0; deltas=(0.0,0.0) ALLOCATE(delta_Q(n_k,n_k)) @@ -691,6 +710,7 @@ PROGRAM slayer ENDDO CLOSE(out_unit) + DEALLOCATE(inQs,iinQs,deltas) END IF CALL output_gamma(est_gamma_flag,sl_in,sl_out) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index df20f48c5..f665e3279 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -102,11 +102,15 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, !WRITE(*,*)"netcdf qval=",qval WRITE(*,*)">>> Writing results to NetCDF output file" + !CALL check( nf90_def_dim(ncid,"r",msing,r_dim) ) + !CALL check( nf90_def_var(ncid,"r",nf90_int,r_dim,r_id)) IF(msing>0)THEN - CALL sl_check( nf90_def_dim(ncid,"qsing",msing,qsing_dim) ) !r_dim = q_rational + CALL sl_check( nf90_def_dim(ncid,"r",msing,qsing_dim) ) !r_dim = q_rational CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) - CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_int, + CALL sl_check( nf90_def_var(ncid,"r",nf90_int, $ qsing_dim,qsing_id)) + CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_int, + $ qsing_dim,qr_id)) CALL sl_check( nf90_def_dim(ncid, "step", SIZE(re_trace), $ tr_dim) ) CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, @@ -157,7 +161,7 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, c set variables c ----------------------------------------------------------------------- CALL sl_check( nf90_put_var(ncid,qsing_id, sl_in%qval_arr)) - !CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) + CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) CALL sl_check( nf90_put_var(ncid,pr_id, sl_in%psi_n_arr)) CALL sl_check( nf90_put_var(ncid,omegas_id, sl_in%omegas_arr)) CALL sl_check( nf90_put_var(ncid,S_id, sl_in%lu_arr)) From 0348b2a205bbf7e0afe65bd08ddf421bad630223 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 9 Oct 2025 17:14:50 -0400 Subject: [PATCH 049/305] WIP: backup, working version --- slayer/slayer.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slayer/slayer.f b/slayer/slayer.f index 703722053..9997ff634 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -491,7 +491,7 @@ PROGRAM slayer $ sl_out%i_trace(n_k,100)) !-------------- - n_k = 1 + !n_k = 1 !-------------- DO k=1,n_k WRITE(*,*) From e6a825f2e6174cf237aa3a60fb522cd7fdd5711b Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 9 Oct 2025 20:12:11 -0400 Subject: [PATCH 050/305] WIP: reverting coupling workflow to fix gamma bug --- slayer/gslayer.f | 540 +++++++++++++++++++++++++++-------------------- slayer/sglobal.f | 20 ++ 2 files changed, 331 insertions(+), 229 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index ccf5dbbad..2fb525e22 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -412,249 +412,331 @@ SUBROUTINE calc_determinant(matk, nk, detk) END SELECT RETURN END SUBROUTINE calc_determinant -c -c -c Adapted from - SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(INOUT) :: g_r, g_i - INTEGER, INTENT(IN) :: verbose - - REAL(r8) :: F1, F2, J11, J12, J21, J22, det, iJ11, iJ12 - REAL(r8) :: iJ21, iJ22, dx1, dx2, dx, f, g1, g2, lambda, - $ Residual - INTEGER :: iter - REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER - REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size - INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations - - iter = 0 - DO - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - - CALL newton_jacobian(g_r, g_i, J11, J12, J21, J22, - $ fitz_flag) + !=========================================================================== + ! Initialize the adaptive grid + !=========================================================================== + subroutine init_grid(grid, omega_min, omega_max, gamma_min, gamma_max, initial_capacity) + type(adaptive_grid), intent(out) :: grid + real(dp), intent(in) :: omega_min, omega_max, gamma_min, gamma_max + integer, intent(in) :: initial_capacity - det = J11 * J22 - J12 * J21 + grid%omega_min = omega_min + grid%omega_max = omega_max + grid%gamma_min = gamma_min + grid%gamma_max = gamma_max + grid%capacity = initial_capacity + grid%npoints = 0 - iJ11 = J22 / det - iJ12 = -J12 / det - iJ21 = -J21 / det - iJ22 = J11 / det + allocate(grid%points(initial_capacity)) - dx1 = -(iJ11 * F1 + iJ12 * F2) - dx2 = -(iJ21 * F1 + iJ22 * F2) + end subroutine init_grid + + !=========================================================================== + ! Add a point to the grid (with automatic reallocation if needed) + !=========================================================================== + subroutine add_point(grid, omega, gamma, delta_val) + type(adaptive_grid), intent(inout) :: grid + real(dp), intent(in) :: omega, gamma + complex(dp), intent(in) :: delta_val - dx = sqrt(dx1*dx1 + dx2*dx2) + type(grid_point), allocatable :: temp(:) + integer :: new_capacity - f = 0.5 * (F1*F1 + F2*F2) + ! Check if we need to reallocate + if (grid%npoints >= grid%capacity) then + new_capacity = grid%capacity * 2 + allocate(temp(new_capacity)) + temp(1:grid%npoints) = grid%points(1:grid%npoints) + call move_alloc(temp, grid%points) + grid%capacity = new_capacity + end if - g1 = F1*J11 + F2*J21 - g2 = F1*J12 + F2*J22 + ! Add the new point + grid%npoints = grid%npoints + 1 + grid%points(grid%npoints)%omega = omega + grid%points(grid%npoints)%gamma = gamma + grid%points(grid%npoints)%delta = delta_val + grid%points(grid%npoints)%computed = .true. + grid%points(grid%npoints)%distance_to_contour = huge(1.0_dp) - CALL newton_backtrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda, - $ fitz_flag) + end subroutine add_point + + !=========================================================================== + ! Check if a point already exists in the grid (within tolerance) + !=========================================================================== + logical function point_exists(grid, omega, gamma, tol) + type(adaptive_grid), intent(in) :: grid + real(dp), intent(in) :: omega, gamma, tol + integer :: i - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) + point_exists = .false. + do i = 1, grid%npoints + if (abs(grid%points(i)%omega - omega) < tol .and. & + abs(grid%points(i)%gamma - gamma) < tol) then + point_exists = .true. + return + end if + end do - Residual = sqrt(F1*F1 + F2*F2) + end function point_exists + + !=========================================================================== + ! Perform initial coarse scan + !=========================================================================== + subroutine coarse_scan(grid, delta_function, n_omega, n_gamma) + type(adaptive_grid), intent(inout) :: grid + interface + function delta_function(omega, gamma) result(delta) + import :: dp + real(dp), intent(in) :: omega, gamma + complex(dp) :: delta + end function delta_function + end interface + integer, intent(in) :: n_omega, n_gamma - n_trace = n_trace + 1 - - IF (n_trace < 100) THEN - re_trace(n_trace) = g_r - im_trace(n_trace) = g_i - END IF - - IF (verbose .ne. 0) THEN - WRITE(*, '(A, ES10.3, A, ES10.3, A, A, ES10.3)') - $ 'Q step = (', g_r, ' + ', - $ g_i, 'j ),', ' Residual =', Residual - ENDIF + real(dp) :: omega, gamma, domega, dgamma + complex(dp) :: delta_val + integer :: i, j - iter = iter + 1 + domega = (grid%omega_max - grid%omega_min) / real(n_omega - 1, dp) + dgamma = (grid%gamma_max - grid%gamma_min) / real(n_gamma - 1, dp) - IF (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit - ENDDO - - END SUBROUTINE newton_root - -c----------------------------------------------------------------------- -c Function to backtrack along Newton step IN order to minimize f = (F1*F1 + F2*F2) /2 -c Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies IN C (Cambridge, 1992), Sect. 9.7 -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, - $ lambda, fitz_flag) - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(INOUT) :: g_r, g_i, dx, lambda - REAL(r8), INTENT(INOUT) :: dx1, dx2, f, g1, g2 - - REAL(r8) :: x1old, x2old, dxold, fold, slope, F1, F2 - REAL(r8) :: tmplam, rhs1, rhs2, a, b, disc, lambd2, mf2 - INTEGER :: i - - REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size - REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER - INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations - - x1old = g_r - x2old = g_i - dxold = dx - fold = f - - IF (dxold > Smax) THEN - dx1 = dx1 * Smax / dxold - dx2 = dx2 * Smax / dxold - dxold = Smax - ENDIF - - slope = g1*dx1 + g2*dx2 - - IF (slope >= 0.0d0) THEN - WRITE(*,*) "NewtonBackTrack: Error - roundoff problem" - ENDIF - - lambda = 1.0d0 - - DO i = 0, Maxiter - g_r = x1old + lambda * dx1 - g_i = x2old + lambda * dx2 - - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - - f = 0.5 * (F1*F1 + F2*F2) - - IF (f <= fold + alpha * lambda * slope .or. - $ lambda * dxold < Smin) THEN - dx = lambda * dxold - RETURN - ELSE - IF (lambda == 1.0d0) THEN - tmplam = -slope / 2.0d0 / (f - fold - slope) - ELSE - rhs1 = f - fold - lambda * slope - rhs2 = mf2 - fold - lambd2 * slope + do i = 1, n_omega + omega = grid%omega_min + real(i-1, dp) * domega + do j = 1, n_gamma + gamma = grid%gamma_min + real(j-1, dp) * dgamma - a = (rhs1/lambda/lambda - rhs2/lambd2/lambd2) - $ / (lambda - lambd2) - b = (-lambd2 * rhs1/lambda/lambda + lambda * - $ rhs2/lambd2/lambd2) / (lambda - lambd2) + ! Compute delta at this point + delta_val = delta_function(omega, gamma) + call add_point(grid, omega, gamma, delta_val) - IF (a == 0.0d0) THEN - tmplam = -slope / 2.0d0 / b - ELSE - disc = b*b - 3.0d0 * a * slope + end do + end do + + print *, 'Coarse scan complete. Points computed:', grid%npoints + + end subroutine coarse_scan + + !=========================================================================== + ! Identify contour regions based on sign changes + !=========================================================================== + subroutine identify_contour_regions(grid, deltaprime, contour_tol) + type(adaptive_grid), intent(inout) :: grid + complex(dp), intent(in) :: deltaprime + real(dp), intent(in) :: contour_tol + + integer :: i + real(dp) :: dist_real, dist_imag, min_dist + + ! For each point, compute distance to contours + do i = 1, grid%npoints + dist_real = abs(real(grid%points(i)%delta) - real(deltaprime)) + dist_imag = abs(aimag(grid%points(i)%delta) - aimag(deltaprime)) + min_dist = min(dist_real, dist_imag) + grid%points(i)%distance_to_contour = min_dist + end do + + end subroutine identify_contour_regions + + !=========================================================================== + ! Adaptive refinement around contours + !=========================================================================== + subroutine adaptive_refine(grid, delta_function, deltaprime, & + refinement_width, refinement_levels, min_spacing) + type(adaptive_grid), intent(inout) :: grid + interface + function delta_function(omega, gamma) result(delta) + import :: dp + real(dp), intent(in) :: omega, gamma + complex(dp) :: delta + end function delta_function + end interface + complex(dp), intent(in) :: deltaprime + real(dp), intent(in) :: refinement_width + integer, intent(in) :: refinement_levels + real(dp), intent(in) :: min_spacing + + integer :: level, i, j, n_original + real(dp) :: omega, gamma, spacing + complex(dp) :: delta_val + logical, allocatable :: needs_refinement(:) + real(dp) :: omega_new, gamma_new + integer :: n_refined + + do level = 1, refinement_levels + n_original = grid%npoints + allocate(needs_refinement(n_original)) + + ! Identify points that need refinement + do i = 1, n_original + needs_refinement(i) = grid%points(i)%distance_to_contour < refinement_width + end do + + n_refined = 0 + spacing = refinement_width / (2.0_dp**level) + + ! Add refined points around identified regions + do i = 1, n_original + if (needs_refinement(i)) then + omega = grid%points(i)%omega + gamma = grid%points(i)%gamma - IF (disc < 0.0d0) THEN - tmplam = 0.5d0 * lambda - ELSE IF (b <= 0.0d0) THEN - tmplam = (-b + sqrt(disc)) / 3.0d0 / a - ELSE - tmplam = -slope / (b + sqrt(disc)) - ENDIF - ENDIF - - IF (tmplam > 0.5d0 * lambda) THEN - tmplam = 0.5d0 * lambda - ENDIF - ENDIF - ENDIF - - lambd2 = lambda - mf2 = f - lambda = max(tmplam, 0.1d0*lambda) - ENDDO - - dx = lambda * dxold - - END SUBROUTINE newton_backtrack -c----------------------------------------------------------------------- -c Function to calculate Jacobian matrix for Newton-Raphson root finding -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_jacobian(g_r, g_i, J11, J12, J21, J22, - $ fitz_flag) - REAL(r8), INTENT(IN) :: g_r, g_i - LOGICAL, INTENT(IN) :: fitz_flag - - REAL(r8), INTENT(OUT) :: J11, J12, J21, J22 - - REAL(r8) :: F1m, F2m, F1p, F2p - REAL(r8), PARAMETER :: dS = 1.0e-06 ! Step size for derivatives - - CALL newton_function(g_r - dS, g_i,F1m,F2m,fitz_flag) - CALL newton_function(g_r + dS, g_i,F1p,F2p,fitz_flag) - - J11 = (F1p - F1m) / 2.0d0 / dS - J21 = (F2p - F2m) / 2.0d0 / dS - - CALL newton_function(g_r, g_i - dS,F1m,F2m,fitz_flag) - CALL newton_function(g_r, g_i + dS,F1p,F2p,fitz_flag) - - J12 = (F1p - F1m) / 2.0d0 / dS - J22 = (F2p - F2m) / 2.0d0 / dS - - END SUBROUTINE newton_jacobian - -c----------------------------------------------------------------------- -c Function to RETURN maximum of two values -c Adapted from -c----------------------------------------------------------------------- - FUNCTION Fmax(f1, f2) result(res) - REAL(r8), INTENT(IN) :: f1, f2 - REAL(r8) :: res - - IF (f1 > f2) THEN - res = f1 - ELSE - res = f2 - ENDIF - - END FUNCTION Fmax - -c----------------------------------------------------------------------- -c Function to RETURN minimum of two values -c Adapted from -c----------------------------------------------------------------------- - FUNCTION Fmin(f1, f2) result(res) - REAL(r8), INTENT(IN) :: f1, f2 - REAL(r8) :: res - - IF (f1 < f2) THEN - res = f1 - ELSE - res = f2 - ENDIF - - END FUNCTION Fmin - -c----------------------------------------------------------------------- -c Function to calculate target functions for Newton-Raphson root finding -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_function(g_r,g_i,F1,F2,fitz_flag) - REAL(r8), INTENT(IN) :: g_r,g_i - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(OUT) :: F1, F2 - - COMPLEX(r8) :: Deltas - - IF (fitz_flag) THEN ! use Fitzpatrick formalism - g_tmp = CMPLX(g_r,g_i) - Deltas=riccati_f(g_tmp) - ELSE ! use J.K. Park formalism - g_tmp = CMPLX(g_i,g_r) - Deltas=riccati(g_i,Q_e,Q_i,P_perp, - $ c_beta,D_norm,tau,pe, - $ iinQ=g_r) - END IF - - F1 = REAL(Deltas) - delta_eff - F2 = AIMAG(Deltas) - - END SUBROUTINE newton_function + ! Add points in a 3x3 grid around this point + do j = -1, 1 + omega_new = omega + real(j, dp) * spacing + if (omega_new < grid%omega_min .or. omega_new > grid%omega_max) cycle + + gamma_new = gamma - spacing + if (gamma_new >= grid%gamma_min .and. gamma_new <= grid%gamma_max) then + if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then + delta_val = delta_function(omega_new, gamma_new) + call add_point(grid, omega_new, gamma_new, delta_val) + n_refined = n_refined + 1 + end if + end if + + if (j /= 0) then + gamma_new = gamma + if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then + delta_val = delta_function(omega_new, gamma_new) + call add_point(grid, omega_new, gamma_new, delta_val) + n_refined = n_refined + 1 + end if + end if + + gamma_new = gamma + spacing + if (gamma_new >= grid%gamma_min .and. gamma_new <= grid%gamma_max) then + if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then + delta_val = delta_function(omega_new, gamma_new) + call add_point(grid, omega_new, gamma_new, delta_val) + n_refined = n_refined + 1 + end if + end if + end do + end if + end do + + print *, 'Refinement level', level, ': Added', n_refined, 'points' + + ! Update distances for new points + call identify_contour_regions(grid, deltaprime, refinement_width) + + deallocate(needs_refinement) + + ! Stop if no new points were added + if (n_refined == 0) exit + end do + + end subroutine adaptive_refine + + !=========================================================================== + ! Marching squares helper for more accurate contour following + !=========================================================================== + subroutine refine_with_marching_squares(grid, delta_function, deltaprime, & + contour_tol, max_new_points) + type(adaptive_grid), intent(inout) :: grid + interface + function delta_function(omega, gamma) result(delta) + import :: dp + real(dp), intent(in) :: omega, gamma + complex(dp) :: delta + end function delta_function + end interface + complex(dp), intent(in) :: deltaprime + real(dp), intent(in) :: contour_tol + integer, intent(in) :: max_new_points + + ! Implementation of marching squares refinement + ! This would trace along detected contours for extra precision + ! Left as a stub for brevity, but can be expanded if needed + + end subroutine refine_with_marching_squares + + !=========================================================================== + ! Export grid to file + !=========================================================================== + subroutine export_grid(grid, filename) + type(adaptive_grid), intent(in) :: grid + character(len=*), intent(in) :: filename + + integer :: i, unit_num + + open(newunit=unit_num, file=filename, status='replace', action='write') + + ! Write header + write(unit_num, '(A)') '# omega, gamma, Re(delta), Im(delta), distance_to_contour' + write(unit_num, '(A,I0)') '# Number of points: ', grid%npoints + + ! Write data + do i = 1, grid%npoints + write(unit_num, '(5E16.8)') grid%points(i)%omega, grid%points(i)%gamma, & + real(grid%points(i)%delta), & + aimag(grid%points(i)%delta), & + grid%points(i)%distance_to_contour + end do + + close(unit_num) + + print *, 'Grid exported to ', trim(filename) + print *, 'Total points: ', grid%npoints + + end subroutine export_grid + + !=========================================================================== + ! Main driver routine + !=========================================================================== + subroutine find_contours(delta_function, omega_min, omega_max, & + gamma_min, gamma_max, deltaprime, & + n_coarse_omega, n_coarse_gamma, & + refinement_width, refinement_levels, & + min_spacing, output_file) + interface + function delta_function(omega, gamma) result(delta) + import :: dp + real(dp), intent(in) :: omega, gamma + complex(dp) :: delta + end function delta_function + end interface + real(dp), intent(in) :: omega_min, omega_max, gamma_min, gamma_max + complex(dp), intent(in) :: deltaprime + integer, intent(in) :: n_coarse_omega, n_coarse_gamma + real(dp), intent(in) :: refinement_width + integer, intent(in) :: refinement_levels + real(dp), intent(in) :: min_spacing + character(len=*), intent(in) :: output_file + + type(adaptive_grid) :: grid + integer :: initial_capacity + + print *, '========================================' + print *, 'Starting adaptive contour finding' + print *, '========================================' + + ! Initialize grid + initial_capacity = n_coarse_omega * n_coarse_gamma * 4 + call init_grid(grid, omega_min, omega_max, gamma_min, gamma_max, initial_capacity) + + ! Perform coarse scan + print *, 'Step 1: Coarse scanning...' + call coarse_scan(grid, delta_function, n_coarse_omega, n_coarse_gamma) + + ! Identify contour regions + print *, 'Step 2: Identifying contour regions...' + call identify_contour_regions(grid, deltaprime, refinement_width) + + ! Adaptive refinement + print *, 'Step 3: Adaptive refinement...' + call adaptive_refine(grid, delta_function, deltaprime, & + refinement_width, refinement_levels, min_spacing) + + ! Export results + print *, 'Step 4: Exporting results...' + call export_grid(grid, output_file) + + ! Clean up + deallocate(grid%points) + + end subroutine find_contours + END MODULE gslayer_mod diff --git a/slayer/sglobal.f b/slayer/sglobal.f index a91bf55a5..b4b982913 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -44,6 +44,26 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: r_trace(:,:),i_trace(:,:) END TYPE slayer_outputs_type + real(dp), parameter :: UNCOMPUTED = -huge(1.0_dp) + + ! Data structure for storing computed points + type :: grid_point + real(dp) :: omega + real(dp) :: gamma + complex(dp) :: delta + logical :: computed + real(dp) :: distance_to_contour ! minimum distance to any contour + end type grid_point + + ! Main grid structure + type :: adaptive_grid + type(grid_point), allocatable :: points(:) + integer :: npoints + integer :: capacity + real(dp) :: omega_min, omega_max + real(dp) :: gamma_min, gamma_max + end type adaptive_grid + ! lnLamb will be updated. COMPLEX(r8), PARAMETER :: ifac=(0,1) From ee8cef40b58ac7edc3f059b6a6329c91e8525092 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 9 Oct 2025 20:13:45 -0400 Subject: [PATCH 051/305] Revert "WIP: backup, working version" This reverts commit 0348b2a205bbf7e0afe65bd08ddf421bad630223. --- slayer/slayer.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slayer/slayer.f b/slayer/slayer.f index 9997ff634..703722053 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -491,7 +491,7 @@ PROGRAM slayer $ sl_out%i_trace(n_k,100)) !-------------- - !n_k = 1 + n_k = 1 !-------------- DO k=1,n_k WRITE(*,*) From ce0b1f99dc884578255e2076e790df2cbf86c8bb Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 14 Oct 2025 13:56:38 -0400 Subject: [PATCH 052/305] checkpoint: working with brute force scans --- slayer/gslayer.f | 542 ++++++++++++++++++++--------------------------- slayer/sglobal.f | 20 -- slayer/slayer.f | 11 +- 3 files changed, 237 insertions(+), 336 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 2fb525e22..39ca4a763 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -412,331 +412,249 @@ SUBROUTINE calc_determinant(matk, nk, detk) END SELECT RETURN END SUBROUTINE calc_determinant +c +c +c Adapted from + SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(INOUT) :: g_r, g_i + INTEGER, INTENT(IN) :: verbose + + REAL(r8) :: F1, F2, J11, J12, J21, J22, det, iJ11, iJ12 + REAL(r8) :: iJ21, iJ22, dx1, dx2, dx, f, g1, g2, lambda, + $ Residual + INTEGER :: iter - !=========================================================================== - ! Initialize the adaptive grid - !=========================================================================== - subroutine init_grid(grid, omega_min, omega_max, gamma_min, gamma_max, initial_capacity) - type(adaptive_grid), intent(out) :: grid - real(dp), intent(in) :: omega_min, omega_max, gamma_min, gamma_max - integer, intent(in) :: initial_capacity + REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER + REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size + REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size + INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations + + iter = 0 + DO + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) + + CALL newton_jacobian(g_r, g_i, J11, J12, J21, J22, + $ fitz_flag) - grid%omega_min = omega_min - grid%omega_max = omega_max - grid%gamma_min = gamma_min - grid%gamma_max = gamma_max - grid%capacity = initial_capacity - grid%npoints = 0 + det = J11 * J22 - J12 * J21 - allocate(grid%points(initial_capacity)) + iJ11 = J22 / det + iJ12 = -J12 / det + iJ21 = -J21 / det + iJ22 = J11 / det - end subroutine init_grid - - !=========================================================================== - ! Add a point to the grid (with automatic reallocation if needed) - !=========================================================================== - subroutine add_point(grid, omega, gamma, delta_val) - type(adaptive_grid), intent(inout) :: grid - real(dp), intent(in) :: omega, gamma - complex(dp), intent(in) :: delta_val + dx1 = -(iJ11 * F1 + iJ12 * F2) + dx2 = -(iJ21 * F1 + iJ22 * F2) - type(grid_point), allocatable :: temp(:) - integer :: new_capacity + dx = sqrt(dx1*dx1 + dx2*dx2) - ! Check if we need to reallocate - if (grid%npoints >= grid%capacity) then - new_capacity = grid%capacity * 2 - allocate(temp(new_capacity)) - temp(1:grid%npoints) = grid%points(1:grid%npoints) - call move_alloc(temp, grid%points) - grid%capacity = new_capacity - end if + f = 0.5 * (F1*F1 + F2*F2) - ! Add the new point - grid%npoints = grid%npoints + 1 - grid%points(grid%npoints)%omega = omega - grid%points(grid%npoints)%gamma = gamma - grid%points(grid%npoints)%delta = delta_val - grid%points(grid%npoints)%computed = .true. - grid%points(grid%npoints)%distance_to_contour = huge(1.0_dp) + g1 = F1*J11 + F2*J21 + g2 = F1*J12 + F2*J22 - end subroutine add_point - - !=========================================================================== - ! Check if a point already exists in the grid (within tolerance) - !=========================================================================== - logical function point_exists(grid, omega, gamma, tol) - type(adaptive_grid), intent(in) :: grid - real(dp), intent(in) :: omega, gamma, tol - integer :: i + CALL newton_backtrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda, + $ fitz_flag) - point_exists = .false. - do i = 1, grid%npoints - if (abs(grid%points(i)%omega - omega) < tol .and. & - abs(grid%points(i)%gamma - gamma) < tol) then - point_exists = .true. - return - end if - end do + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - end function point_exists - - !=========================================================================== - ! Perform initial coarse scan - !=========================================================================== - subroutine coarse_scan(grid, delta_function, n_omega, n_gamma) - type(adaptive_grid), intent(inout) :: grid - interface - function delta_function(omega, gamma) result(delta) - import :: dp - real(dp), intent(in) :: omega, gamma - complex(dp) :: delta - end function delta_function - end interface - integer, intent(in) :: n_omega, n_gamma + Residual = sqrt(F1*F1 + F2*F2) - real(dp) :: omega, gamma, domega, dgamma - complex(dp) :: delta_val - integer :: i, j + n_trace = n_trace + 1 + + IF (n_trace < 100) THEN + re_trace(n_trace) = g_r + im_trace(n_trace) = g_i + END IF + + IF (verbose .ne. 0) THEN + WRITE(*, '(A, ES10.3, A, ES10.3, A, A, ES10.3)') + $ 'Q step = (', g_r, ' + ', + $ g_i, 'j ),', ' Residual =', Residual + ENDIF - domega = (grid%omega_max - grid%omega_min) / real(n_omega - 1, dp) - dgamma = (grid%gamma_max - grid%gamma_min) / real(n_gamma - 1, dp) + iter = iter + 1 - do i = 1, n_omega - omega = grid%omega_min + real(i-1, dp) * domega - do j = 1, n_gamma - gamma = grid%gamma_min + real(j-1, dp) * dgamma + IF (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit + ENDDO + + END SUBROUTINE newton_root + +c----------------------------------------------------------------------- +c Function to backtrack along Newton step IN order to minimize f = (F1*F1 + F2*F2) /2 +c Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies IN C (Cambridge, 1992), Sect. 9.7 +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, + $ lambda, fitz_flag) + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(INOUT) :: g_r, g_i, dx, lambda + REAL(r8), INTENT(INOUT) :: dx1, dx2, f, g1, g2 + + REAL(r8) :: x1old, x2old, dxold, fold, slope, F1, F2 + REAL(r8) :: tmplam, rhs1, rhs2, a, b, disc, lambd2, mf2 + INTEGER :: i + + REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size + REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size + REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER + INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations + + x1old = g_r + x2old = g_i + dxold = dx + fold = f + + IF (dxold > Smax) THEN + dx1 = dx1 * Smax / dxold + dx2 = dx2 * Smax / dxold + dxold = Smax + ENDIF + + slope = g1*dx1 + g2*dx2 + + IF (slope >= 0.0d0) THEN + WRITE(*,*) "NewtonBackTrack: Error - roundoff problem" + ENDIF + + lambda = 1.0d0 + + DO i = 0, Maxiter + g_r = x1old + lambda * dx1 + g_i = x2old + lambda * dx2 + + CALL newton_function(g_r, g_i, F1, F2, fitz_flag) + + f = 0.5 * (F1*F1 + F2*F2) + + IF (f <= fold + alpha * lambda * slope .or. + $ lambda * dxold < Smin) THEN + dx = lambda * dxold + RETURN + ELSE + IF (lambda == 1.0d0) THEN + tmplam = -slope / 2.0d0 / (f - fold - slope) + ELSE + rhs1 = f - fold - lambda * slope + rhs2 = mf2 - fold - lambd2 * slope - ! Compute delta at this point - delta_val = delta_function(omega, gamma) - call add_point(grid, omega, gamma, delta_val) + a = (rhs1/lambda/lambda - rhs2/lambd2/lambd2) + $ / (lambda - lambd2) + b = (-lambd2 * rhs1/lambda/lambda + lambda * + $ rhs2/lambd2/lambd2) / (lambda - lambd2) - end do - end do - - print *, 'Coarse scan complete. Points computed:', grid%npoints - - end subroutine coarse_scan - - !=========================================================================== - ! Identify contour regions based on sign changes - !=========================================================================== - subroutine identify_contour_regions(grid, deltaprime, contour_tol) - type(adaptive_grid), intent(inout) :: grid - complex(dp), intent(in) :: deltaprime - real(dp), intent(in) :: contour_tol - - integer :: i - real(dp) :: dist_real, dist_imag, min_dist - - ! For each point, compute distance to contours - do i = 1, grid%npoints - dist_real = abs(real(grid%points(i)%delta) - real(deltaprime)) - dist_imag = abs(aimag(grid%points(i)%delta) - aimag(deltaprime)) - min_dist = min(dist_real, dist_imag) - grid%points(i)%distance_to_contour = min_dist - end do - - end subroutine identify_contour_regions - - !=========================================================================== - ! Adaptive refinement around contours - !=========================================================================== - subroutine adaptive_refine(grid, delta_function, deltaprime, & - refinement_width, refinement_levels, min_spacing) - type(adaptive_grid), intent(inout) :: grid - interface - function delta_function(omega, gamma) result(delta) - import :: dp - real(dp), intent(in) :: omega, gamma - complex(dp) :: delta - end function delta_function - end interface - complex(dp), intent(in) :: deltaprime - real(dp), intent(in) :: refinement_width - integer, intent(in) :: refinement_levels - real(dp), intent(in) :: min_spacing - - integer :: level, i, j, n_original - real(dp) :: omega, gamma, spacing - complex(dp) :: delta_val - logical, allocatable :: needs_refinement(:) - real(dp) :: omega_new, gamma_new - integer :: n_refined - - do level = 1, refinement_levels - n_original = grid%npoints - allocate(needs_refinement(n_original)) - - ! Identify points that need refinement - do i = 1, n_original - needs_refinement(i) = grid%points(i)%distance_to_contour < refinement_width - end do - - n_refined = 0 - spacing = refinement_width / (2.0_dp**level) - - ! Add refined points around identified regions - do i = 1, n_original - if (needs_refinement(i)) then - omega = grid%points(i)%omega - gamma = grid%points(i)%gamma + IF (a == 0.0d0) THEN + tmplam = -slope / 2.0d0 / b + ELSE + disc = b*b - 3.0d0 * a * slope - ! Add points in a 3x3 grid around this point - do j = -1, 1 - omega_new = omega + real(j, dp) * spacing - if (omega_new < grid%omega_min .or. omega_new > grid%omega_max) cycle - - gamma_new = gamma - spacing - if (gamma_new >= grid%gamma_min .and. gamma_new <= grid%gamma_max) then - if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then - delta_val = delta_function(omega_new, gamma_new) - call add_point(grid, omega_new, gamma_new, delta_val) - n_refined = n_refined + 1 - end if - end if - - if (j /= 0) then - gamma_new = gamma - if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then - delta_val = delta_function(omega_new, gamma_new) - call add_point(grid, omega_new, gamma_new, delta_val) - n_refined = n_refined + 1 - end if - end if - - gamma_new = gamma + spacing - if (gamma_new >= grid%gamma_min .and. gamma_new <= grid%gamma_max) then - if (.not. point_exists(grid, omega_new, gamma_new, min_spacing)) then - delta_val = delta_function(omega_new, gamma_new) - call add_point(grid, omega_new, gamma_new, delta_val) - n_refined = n_refined + 1 - end if - end if - end do - end if - end do - - print *, 'Refinement level', level, ': Added', n_refined, 'points' - - ! Update distances for new points - call identify_contour_regions(grid, deltaprime, refinement_width) - - deallocate(needs_refinement) - - ! Stop if no new points were added - if (n_refined == 0) exit - end do - - end subroutine adaptive_refine - - !=========================================================================== - ! Marching squares helper for more accurate contour following - !=========================================================================== - subroutine refine_with_marching_squares(grid, delta_function, deltaprime, & - contour_tol, max_new_points) - type(adaptive_grid), intent(inout) :: grid - interface - function delta_function(omega, gamma) result(delta) - import :: dp - real(dp), intent(in) :: omega, gamma - complex(dp) :: delta - end function delta_function - end interface - complex(dp), intent(in) :: deltaprime - real(dp), intent(in) :: contour_tol - integer, intent(in) :: max_new_points - - ! Implementation of marching squares refinement - ! This would trace along detected contours for extra precision - ! Left as a stub for brevity, but can be expanded if needed - - end subroutine refine_with_marching_squares - - !=========================================================================== - ! Export grid to file - !=========================================================================== - subroutine export_grid(grid, filename) - type(adaptive_grid), intent(in) :: grid - character(len=*), intent(in) :: filename - - integer :: i, unit_num - - open(newunit=unit_num, file=filename, status='replace', action='write') - - ! Write header - write(unit_num, '(A)') '# omega, gamma, Re(delta), Im(delta), distance_to_contour' - write(unit_num, '(A,I0)') '# Number of points: ', grid%npoints - - ! Write data - do i = 1, grid%npoints - write(unit_num, '(5E16.8)') grid%points(i)%omega, grid%points(i)%gamma, & - real(grid%points(i)%delta), & - aimag(grid%points(i)%delta), & - grid%points(i)%distance_to_contour - end do - - close(unit_num) - - print *, 'Grid exported to ', trim(filename) - print *, 'Total points: ', grid%npoints - - end subroutine export_grid - - !=========================================================================== - ! Main driver routine - !=========================================================================== - subroutine find_contours(delta_function, omega_min, omega_max, & - gamma_min, gamma_max, deltaprime, & - n_coarse_omega, n_coarse_gamma, & - refinement_width, refinement_levels, & - min_spacing, output_file) - interface - function delta_function(omega, gamma) result(delta) - import :: dp - real(dp), intent(in) :: omega, gamma - complex(dp) :: delta - end function delta_function - end interface - real(dp), intent(in) :: omega_min, omega_max, gamma_min, gamma_max - complex(dp), intent(in) :: deltaprime - integer, intent(in) :: n_coarse_omega, n_coarse_gamma - real(dp), intent(in) :: refinement_width - integer, intent(in) :: refinement_levels - real(dp), intent(in) :: min_spacing - character(len=*), intent(in) :: output_file - - type(adaptive_grid) :: grid - integer :: initial_capacity - - print *, '========================================' - print *, 'Starting adaptive contour finding' - print *, '========================================' - - ! Initialize grid - initial_capacity = n_coarse_omega * n_coarse_gamma * 4 - call init_grid(grid, omega_min, omega_max, gamma_min, gamma_max, initial_capacity) - - ! Perform coarse scan - print *, 'Step 1: Coarse scanning...' - call coarse_scan(grid, delta_function, n_coarse_omega, n_coarse_gamma) - - ! Identify contour regions - print *, 'Step 2: Identifying contour regions...' - call identify_contour_regions(grid, deltaprime, refinement_width) - - ! Adaptive refinement - print *, 'Step 3: Adaptive refinement...' - call adaptive_refine(grid, delta_function, deltaprime, & - refinement_width, refinement_levels, min_spacing) - - ! Export results - print *, 'Step 4: Exporting results...' - call export_grid(grid, output_file) - - ! Clean up - deallocate(grid%points) - - end subroutine find_contours - - END MODULE gslayer_mod + IF (disc < 0.0d0) THEN + tmplam = 0.5d0 * lambda + ELSE IF (b <= 0.0d0) THEN + tmplam = (-b + sqrt(disc)) / 3.0d0 / a + ELSE + tmplam = -slope / (b + sqrt(disc)) + ENDIF + ENDIF + + IF (tmplam > 0.5d0 * lambda) THEN + tmplam = 0.5d0 * lambda + ENDIF + ENDIF + ENDIF + + lambd2 = lambda + mf2 = f + lambda = max(tmplam, 0.1d0*lambda) + ENDDO + + dx = lambda * dxold + + END SUBROUTINE newton_backtrack +c----------------------------------------------------------------------- +c Function to calculate Jacobian matrix for Newton-Raphson root finding +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_jacobian(g_r, g_i, J11, J12, J21, J22, + $ fitz_flag) + REAL(r8), INTENT(IN) :: g_r, g_i + LOGICAL, INTENT(IN) :: fitz_flag + + REAL(r8), INTENT(OUT) :: J11, J12, J21, J22 + + REAL(r8) :: F1m, F2m, F1p, F2p + REAL(r8), PARAMETER :: dS = 1.0e-06 ! Step size for derivatives + + CALL newton_function(g_r - dS, g_i,F1m,F2m,fitz_flag) + CALL newton_function(g_r + dS, g_i,F1p,F2p,fitz_flag) + + J11 = (F1p - F1m) / 2.0d0 / dS + J21 = (F2p - F2m) / 2.0d0 / dS + + CALL newton_function(g_r, g_i - dS,F1m,F2m,fitz_flag) + CALL newton_function(g_r, g_i + dS,F1p,F2p,fitz_flag) + + J12 = (F1p - F1m) / 2.0d0 / dS + J22 = (F2p - F2m) / 2.0d0 / dS + + END SUBROUTINE newton_jacobian + +c----------------------------------------------------------------------- +c Function to RETURN maximum of two values +c Adapted from +c----------------------------------------------------------------------- + FUNCTION Fmax(f1, f2) result(res) + REAL(r8), INTENT(IN) :: f1, f2 + REAL(r8) :: res + + IF (f1 > f2) THEN + res = f1 + ELSE + res = f2 + ENDIF + + END FUNCTION Fmax + +c----------------------------------------------------------------------- +c Function to RETURN minimum of two values +c Adapted from +c----------------------------------------------------------------------- + FUNCTION Fmin(f1, f2) result(res) + REAL(r8), INTENT(IN) :: f1, f2 + REAL(r8) :: res + + IF (f1 < f2) THEN + res = f1 + ELSE + res = f2 + ENDIF + + END FUNCTION Fmin + +c----------------------------------------------------------------------- +c Function to calculate target functions for Newton-Raphson root finding +c Adapted from +c----------------------------------------------------------------------- + SUBROUTINE newton_function(g_r,g_i,F1,F2,fitz_flag) + REAL(r8), INTENT(IN) :: g_r,g_i + LOGICAL, INTENT(IN) :: fitz_flag + REAL(r8), INTENT(OUT) :: F1, F2 + + COMPLEX(r8) :: Deltas + + IF (fitz_flag) THEN ! use Fitzpatrick formalism + g_tmp = CMPLX(g_r,g_i) + Deltas=riccati_f(g_tmp) + ELSE ! use J.K. Park formalism + g_tmp = CMPLX(g_i,g_r) + Deltas=riccati(g_i,Q_e,Q_i,P_perp, + $ c_beta,D_norm,tau,pe, + $ iinQ=g_r) + END IF + + F1 = REAL(Deltas) - delta_eff + F2 = AIMAG(Deltas) + + END SUBROUTINE newton_function + END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index b4b982913..a91bf55a5 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -44,26 +44,6 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: r_trace(:,:),i_trace(:,:) END TYPE slayer_outputs_type - real(dp), parameter :: UNCOMPUTED = -huge(1.0_dp) - - ! Data structure for storing computed points - type :: grid_point - real(dp) :: omega - real(dp) :: gamma - complex(dp) :: delta - logical :: computed - real(dp) :: distance_to_contour ! minimum distance to any contour - end type grid_point - - ! Main grid structure - type :: adaptive_grid - type(grid_point), allocatable :: points(:) - integer :: npoints - integer :: capacity - real(dp) :: omega_min, omega_max - real(dp) :: gamma_min, gamma_max - end type adaptive_grid - ! lnLamb will be updated. COMPLEX(r8), PARAMETER :: ifac=(0,1) diff --git a/slayer/slayer.f b/slayer/slayer.f index 703722053..565ed93ca 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -491,7 +491,7 @@ PROGRAM slayer $ sl_out%i_trace(n_k,100)) !-------------- - n_k = 1 + !n_k = 1 !-------------- DO k=1,n_k WRITE(*,*) @@ -555,11 +555,11 @@ PROGRAM slayer IF (fitz_flag) THEN re_trace = re_trace/tauk im_trace = im_trace/tauk - sl_out%gamma_sol_arr(k) = 0.0!g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT ELSE re_trace = re_trace/tauk im_trace = -im_trace/tauk - sl_out%gamma_sol_arr(k) = 0.0!-g_r/tauk! THIS IS FOR PLOT + sl_out%gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT END IF IF (stabscan_flag) THEN @@ -673,9 +673,12 @@ PROGRAM slayer iota_e = Q_e / (Q_e - Q_i) ! (Deltaprime - d_crit)/S^1/3 + ! delta_eff = (sl_in%Re_dp_arr(k) - + !$ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) ! NO DELTA_CRIT + delta_eff = (sl_in%Re_dp_arr(k) - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) - + delta=riccati_f(((g_tmp*sl_in%Qconv_arr(1))/ $ tauk)) delta_Q(k,k) = delta From 83465f8ce16299eb4c0ac0fd57c252313985a0b3 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 15 Dec 2025 15:16:24 -0500 Subject: [PATCH 053/305] WIP: latest working version --- slayer/gslayer.f | 298 +++++++++-------------------------------------- slayer/sglobal.f | 2 +- slayer/slayer.f | 70 +++-------- 3 files changed, 74 insertions(+), 296 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 39ca4a763..d10d3280b 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -415,246 +415,64 @@ END SUBROUTINE calc_determinant c c c Adapted from - SUBROUTINE newton_root(g_r, g_i, verbose, fitz_flag) - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(INOUT) :: g_r, g_i - INTEGER, INTENT(IN) :: verbose + FUNCTION dispersion_det(g_tmp,n_k,sl_in) - REAL(r8) :: F1, F2, J11, J12, J21, J22, det, iJ11, iJ12 - REAL(r8) :: iJ21, iJ22, dx1, dx2, dx, f, g1, g2, lambda, - $ Residual - INTEGER :: iter - - REAL(r8), PARAMETER :: Eps = 1.0e-12 ! Tolerance PARAMETER - REAL(r8), PARAMETER :: Smin = 1.0e-07 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size - INTEGER, PARAMETER :: MaxIter = 100 ! Maximum iterations - - iter = 0 - DO - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - - CALL newton_jacobian(g_r, g_i, J11, J12, J21, J22, - $ fitz_flag) - - det = J11 * J22 - J12 * J21 - - iJ11 = J22 / det - iJ12 = -J12 / det - iJ21 = -J21 / det - iJ22 = J11 / det - - dx1 = -(iJ11 * F1 + iJ12 * F2) - dx2 = -(iJ21 * F1 + iJ22 * F2) - - dx = sqrt(dx1*dx1 + dx2*dx2) - - f = 0.5 * (F1*F1 + F2*F2) - - g1 = F1*J11 + F2*J21 - g2 = F1*J12 + F2*J22 - - CALL newton_backtrack(g_r,g_i,dx1,dx2,dx,f,g1,g2,lambda, - $ fitz_flag) - - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - - Residual = sqrt(F1*F1 + F2*F2) - - n_trace = n_trace + 1 - - IF (n_trace < 100) THEN - re_trace(n_trace) = g_r - im_trace(n_trace) = g_i - END IF - - IF (verbose .ne. 0) THEN - WRITE(*, '(A, ES10.3, A, ES10.3, A, A, ES10.3)') - $ 'Q step = (', g_r, ' + ', - $ g_i, 'j ),', ' Residual =', Residual - ENDIF - - iter = iter + 1 - - IF (Residual<=Eps .or. dx<=Smin .or. iter>=MaxIter) exit - ENDDO - - END SUBROUTINE newton_root - -c----------------------------------------------------------------------- -c Function to backtrack along Newton step IN order to minimize f = (F1*F1 + F2*F2) /2 -c Press, Teukolsky, Vetterling, and Flannery, Numerical Recipies IN C (Cambridge, 1992), Sect. 9.7 -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_backtrack(g_r, g_i, dx1, dx2, dx, f, g1, g2, - $ lambda, fitz_flag) - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(INOUT) :: g_r, g_i, dx, lambda - REAL(r8), INTENT(INOUT) :: dx1, dx2, f, g1, g2 - - REAL(r8) :: x1old, x2old, dxold, fold, slope, F1, F2 - REAL(r8) :: tmplam, rhs1, rhs2, a, b, disc, lambd2, mf2 - INTEGER :: i - - REAL(r8), PARAMETER :: Smin = 1.0d-10 ! Min step size - REAL(r8), PARAMETER :: Smax = 0.02 ! Max step size - REAL(r8), PARAMETER :: alpha = 1.0d-4 ! Line search PARAMETER - INTEGER, PARAMETER :: Maxiter = 100 ! Maximum iterations - - x1old = g_r - x2old = g_i - dxold = dx - fold = f - - IF (dxold > Smax) THEN - dx1 = dx1 * Smax / dxold - dx2 = dx2 * Smax / dxold - dxold = Smax - ENDIF - - slope = g1*dx1 + g2*dx2 - - IF (slope >= 0.0d0) THEN - WRITE(*,*) "NewtonBackTrack: Error - roundoff problem" - ENDIF - - lambda = 1.0d0 - - DO i = 0, Maxiter - g_r = x1old + lambda * dx1 - g_i = x2old + lambda * dx2 - - CALL newton_function(g_r, g_i, F1, F2, fitz_flag) - - f = 0.5 * (F1*F1 + F2*F2) - - IF (f <= fold + alpha * lambda * slope .or. - $ lambda * dxold < Smin) THEN - dx = lambda * dxold - RETURN - ELSE - IF (lambda == 1.0d0) THEN - tmplam = -slope / 2.0d0 / (f - fold - slope) - ELSE - rhs1 = f - fold - lambda * slope - rhs2 = mf2 - fold - lambd2 * slope - - a = (rhs1/lambda/lambda - rhs2/lambd2/lambd2) - $ / (lambda - lambd2) - b = (-lambd2 * rhs1/lambda/lambda + lambda * - $ rhs2/lambd2/lambd2) / (lambda - lambd2) - - IF (a == 0.0d0) THEN - tmplam = -slope / 2.0d0 / b - ELSE - disc = b*b - 3.0d0 * a * slope - - IF (disc < 0.0d0) THEN - tmplam = 0.5d0 * lambda - ELSE IF (b <= 0.0d0) THEN - tmplam = (-b + sqrt(disc)) / 3.0d0 / a - ELSE - tmplam = -slope / (b + sqrt(disc)) - ENDIF - ENDIF - - IF (tmplam > 0.5d0 * lambda) THEN - tmplam = 0.5d0 * lambda - ENDIF - ENDIF - ENDIF - - lambd2 = lambda - mf2 = f - lambda = max(tmplam, 0.1d0*lambda) - ENDDO - - dx = lambda * dxold - - END SUBROUTINE newton_backtrack -c----------------------------------------------------------------------- -c Function to calculate Jacobian matrix for Newton-Raphson root finding -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_jacobian(g_r, g_i, J11, J12, J21, J22, - $ fitz_flag) - REAL(r8), INTENT(IN) :: g_r, g_i - LOGICAL, INTENT(IN) :: fitz_flag - - REAL(r8), INTENT(OUT) :: J11, J12, J21, J22 - - REAL(r8) :: F1m, F2m, F1p, F2p - REAL(r8), PARAMETER :: dS = 1.0e-06 ! Step size for derivatives - - CALL newton_function(g_r - dS, g_i,F1m,F2m,fitz_flag) - CALL newton_function(g_r + dS, g_i,F1p,F2p,fitz_flag) - - J11 = (F1p - F1m) / 2.0d0 / dS - J21 = (F2p - F2m) / 2.0d0 / dS - - CALL newton_function(g_r, g_i - dS,F1m,F2m,fitz_flag) - CALL newton_function(g_r, g_i + dS,F1p,F2p,fitz_flag) - - J12 = (F1p - F1m) / 2.0d0 / dS - J22 = (F2p - F2m) / 2.0d0 / dS - - END SUBROUTINE newton_jacobian - -c----------------------------------------------------------------------- -c Function to RETURN maximum of two values -c Adapted from -c----------------------------------------------------------------------- - FUNCTION Fmax(f1, f2) result(res) - REAL(r8), INTENT(IN) :: f1, f2 - REAL(r8) :: res - - IF (f1 > f2) THEN - res = f1 - ELSE - res = f2 - ENDIF - - END FUNCTION Fmax - -c----------------------------------------------------------------------- -c Function to RETURN minimum of two values -c Adapted from -c----------------------------------------------------------------------- - FUNCTION Fmin(f1, f2) result(res) - REAL(r8), INTENT(IN) :: f1, f2 - REAL(r8) :: res - - IF (f1 < f2) THEN - res = f1 + COMPLEX(r8), INTENT(IN) :: g_tmp + INTEGER, INTENT(IN) :: n_k + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + COMPLEX(r8) :: dispersion_det,det_val,tmp_delta + COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:),result_matrix(:,:) + INTEGER :: k + + IF (n_k < 2) THEN + Q_e = sl_in%Q_e_arr(1) + Q_i = sl_in%Q_i_arr(1) + P_perp = sl_in%P_perp_arr(1) + P_tor = sl_in%P_tor_arr(1) + tau = sl_in%tau_arr(1) + D_norm = sl_in%D_norm_arr(1) + c_beta = sl_in%c_beta_arr(1) + tauk = sl_in%Qconv_arr(1) + iota_e = Q_e / (Q_e - Q_i) + + tmp_delta=riccati_f(g_tmp) + det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) ! DE-NORMALIZE + + ! Calculate Deltaprime - Delta(Q) + dispersion_det = sl_in%Re_dp_arr(1) - det_val + + ELSEIF ((n_k == 2) .OR. (n_k == 3)) THEN + ALLOCATE(delta_Q(n_k,n_k)) + delta_Q=(0.0,0.0) + DO k=1,2 !!! MAXING OUT AT 2X2 + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + P_perp = sl_in%P_perp_arr(k) + P_tor = sl_in%P_tor_arr(k) + tau = sl_in%tau_arr(k) + D_norm = sl_in%D_norm_arr(k) + c_beta = sl_in%c_beta_arr(k) + tauk = sl_in%Qconv_arr(k) + iota_e = Q_e / (Q_e - Q_i) + + delta_eff = (sl_in%Re_dp_arr(k) - + $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) + + delta_Q(k,k)=riccati_f(((g_tmp*sl_in%Qconv_arr(1)) + $ /tauk)) + delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) ! DE-NORMALIZE + END DO + + ! Calculate Deltaprime - Delta(Q) + result_matrix = sl_in%dp_matrix - delta_Q + + ! Calculate determinant + CALL calc_determinant(result_matrix, n_k, det_val) + dispersion_det = det_val ELSE - res = f2 - ENDIF - - END FUNCTION Fmin - -c----------------------------------------------------------------------- -c Function to calculate target functions for Newton-Raphson root finding -c Adapted from -c----------------------------------------------------------------------- - SUBROUTINE newton_function(g_r,g_i,F1,F2,fitz_flag) - REAL(r8), INTENT(IN) :: g_r,g_i - LOGICAL, INTENT(IN) :: fitz_flag - REAL(r8), INTENT(OUT) :: F1, F2 - - COMPLEX(r8) :: Deltas - - IF (fitz_flag) THEN ! use Fitzpatrick formalism - g_tmp = CMPLX(g_r,g_i) - Deltas=riccati_f(g_tmp) - ELSE ! use J.K. Park formalism - g_tmp = CMPLX(g_i,g_r) - Deltas=riccati(g_i,Q_e,Q_i,P_perp, - $ c_beta,D_norm,tau,pe, - $ iinQ=g_r) - END IF - - F1 = REAL(Deltas) - delta_eff - F2 = AIMAG(Deltas) - - END SUBROUTINE newton_function + WRITE(*,*)"Error: no support for msing > 3" + stop + END IF + END FUNCTION dispersion_det + END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index a91bf55a5..eafcd4386 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -14,7 +14,7 @@ MODULE sglobal_mod $ delta_n,layfac,Qconv,lnLamb,deltaprim,dc_tmp, $ d_crit,tau_r,tauk,g_r,g_i,delta_eff REAL(r8), DIMENSION(:), ALLOCATABLE :: re_trace,im_trace - COMPLEX(r8) :: Q,g_tmp + COMPLEX(r8) :: Q,g_tmp,delta_det CHARACTER(20) :: dc_type REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, diff --git a/slayer/slayer.f b/slayer/slayer.f index 565ed93ca..08971bee5 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -43,7 +43,6 @@ PROGRAM slayer $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, $ delta_imag,Qratio REAL(r8) :: chis(3) - COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:),result_matrix(:,:) INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns @@ -493,7 +492,7 @@ PROGRAM slayer !-------------- !n_k = 1 !-------------- - DO k=1,n_k + DO k=1,2!!!!!!!!!!!!!! ONLY DOING TWO SURFACES WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', $ sl_in%qval_arr(k),' rational surface:' @@ -517,7 +516,9 @@ PROGRAM slayer WRITE(*,*)"slayer.f iota_e: ",iota_e WRITE(*,*)"slayer.f D_prime: ",sl_in%Re_dp_arr(k) + WRITE(*,*)"slayer.f D_prime: ",sl_in%Re_dp_arr(k) WRITE(*,*)"slayer.f D_crit: ",sl_in%d_crit_arr(k) + WRITE(*,*),"sl_in%qval_arr: ",sl_in%qval_arr ! (Deltaprime - d_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - @@ -541,25 +542,25 @@ PROGRAM slayer re_trace(1) = -Q_e END IF - WRITE(*,*)"$^$ calling newton_root(): " - CALL newton_root(g_r,g_i,1,fitz_flag) + WRITE(*,*)"no longer using newton_root(): " + !CALL newton_root(g_r,g_i,1,fitz_flag) WRITE(*,*) - WRITE(*,'(A,F0.3,A)') 'Success! Growth rate = ', - $ g_r/tauk, ' [Hz]' + !WRITE(*,'(A,F0.3,A)') 'Success! Growth rate = ', + !$ g_r/tauk, ' [Hz]' !CALL shrink_array(re_trace, n_trace) !CALL shrink_array(im_trace, n_trace) n_trace = 100 IF (fitz_flag) THEN - re_trace = re_trace/tauk - im_trace = im_trace/tauk - sl_out%gamma_sol_arr(k) = g_r/tauk! THIS IS FOR PLOT + !re_trace = re_trace/tauk + !im_trace = im_trace/tauk + sl_out%gamma_sol_arr(k) = 0.0!g_r/tauk! THIS IS FOR PLOT ELSE - re_trace = re_trace/tauk - im_trace = -im_trace/tauk - sl_out%gamma_sol_arr(k) = -g_r/tauk! THIS IS FOR PLOT + !re_trace = re_trace/tauk + !im_trace = -im_trace/tauk + sl_out%gamma_sol_arr(k) = 0.0!-g_r/tauk! THIS IS FOR PLOT END IF IF (stabscan_flag) THEN @@ -645,8 +646,6 @@ PROGRAM slayer !END IF inQs=0.0; iinQs=0.0; deltas=(0.0,0.0) - ALLOCATE(delta_Q(n_k,n_k)) - delta_Q=(0.0,0.0) DO i = 1, (Q_num+1) DO j = 1, Q_num @@ -655,48 +654,9 @@ PROGRAM slayer inQs(i) = ing_coarse iinQs(j) = iing_coarse - ! Evaluate riccati function + ! Evaluate determinant g_tmp = CMPLX(ing_coarse,iing_coarse) - IF (n_k == 1) THEN - WRITE(*,*)"Error: no coupling for msing = 1" - stop - ELSEIF (n_k == 2) THEN - DO k=1,2 - Q_e = sl_in%Q_e_arr(k) - Q_i = sl_in%Q_i_arr(k) - P_perp = sl_in%P_perp_arr(k) - P_tor = sl_in%P_tor_arr(k) - tau = sl_in%tau_arr(k) - D_norm = sl_in%D_norm_arr(k) - c_beta = sl_in%c_beta_arr(k) - tauk = sl_in%Qconv_arr(k) - iota_e = Q_e / (Q_e - Q_i) - - ! (Deltaprime - d_crit)/S^1/3 - ! delta_eff = (sl_in%Re_dp_arr(k) - - !$ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) ! NO DELTA_CRIT - - delta_eff = (sl_in%Re_dp_arr(k) - - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) - - delta=riccati_f(((g_tmp*sl_in%Qconv_arr(1))/ - $ tauk)) - delta_Q(k,k) = delta - END DO - - ! Calculate Deltaprime - Delta(Q) - result_matrix = sl_in%dp_matrix - delta_Q - - ! Calculate determinant - CALL calc_determinant(result_matrix, n_k, det_val) - deltas(i,j) = det_val - ELSEIF (n_k == 3) THEN - WRITE(*,*)"Error: no support for msing = 3" - stop - ELSE - WRITE(*,*)"Error: no support for msing < 3" - stop - END IF + deltas(i,j) = dispersion_det(g_tmp,n_k,sl_in) ENDDO ENDDO From b26cf4158f585800cccf6858834d2323bc7d602d Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Wed, 17 Dec 2025 13:58:18 -0500 Subject: [PATCH 054/305] WIP: cleanups and prep for PR --- input/slayer.in | 1 + slayer/delta.f | 85 +++++---------------- slayer/gslayer.f | 175 ++++++------------------------------------- slayer/layerinputs.f | 6 -- slayer/slayer.f | 65 ++++------------ 5 files changed, 57 insertions(+), 275 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index dd0230d69..ef9e225d4 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -49,6 +49,7 @@ knum=100 ! resolution for 2d scan along with the other. Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes scan_width=1.5 ! stability scan width for Re(Q),Im(Q) scan + msing_max=2 ! number of surfaces to include in coupled tearing growth rate calculation dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation diff --git a/slayer/delta.f b/slayer/delta.f index ce8dc2d46..de3041d47 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -115,29 +115,22 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, REAL(r8),INTENT(IN),OPTIONAL :: inx COMPLEX(r8), INTENT(IN), OPTIONAL :: iny COMPLEX(r8) :: riccati_del_s - INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf INTEGER :: ml = 0, mu = 0, nrpd = 1 - REAL(r8) :: xintv,x,xout,rtol,jac,xmin,my_q,P_hat,alpha COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq,y,dy - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - !IF ((layfac>0).AND.(ABS(Q-Q_e)xout) istep=istep+1 CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,my_jac,mf) + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) WRITE(bin_unit)REAL(my_q,4),REAL(REAL(W),4),REAL(AIMAG(W),4) WRITE(out2_unit,'(1x,3(es17.8e3))')my_q,REAL(W),AIMAG(W) ENDDO @@ -196,7 +185,7 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, istep = 1 itask = 1 CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,my_jac,mf) + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) ENDIF @@ -211,15 +200,15 @@ END FUNCTION riccati_del_s c----------------------------------------------------------------------- c jacobian for riccati_del_s() c------------------------------------------- ---------------------------- - SUBROUTINE my_jac(neq, my_q, W, ml, mu, pd, nrpd) + SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) INTEGER, INTENT(IN) :: neq, ml, mu, nrpd REAL(r8), INTENT(IN) :: my_q COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd pd(1,1) = 1.0/my_q - 2.0d0*W(1)/my_q - END SUBROUTINE my_jac + END SUBROUTINE jac_del_s c----------------------------------------------------------------------- -c riccati integration. +c W derivative for riccati_del_s() c----------------------------------------------------------------------- SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) @@ -230,20 +219,10 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat COMPLEX(r8) :: E,F - !COMPLEX(r8), PARAMETER :: ifac=(0,1) - !Q_hat = Q / ds**4 Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark P_perp_hat = P_perp / D_norm**6.0 ! 0.377 for benchmark P_tor_hat = P_perp / D_norm**6.0 ! 1.15 for benchmark - - !WRITE(*,*)"w_der_del_s P_perp_hat = ",P_perp_hat - !WRITE(*,*)"w_der_del_s P_tor_hat = ",P_tor_hat - !WRITE(*,*)"w_der_del_s Q_hat = ",Q_hat - - !WRITE(*,*)"w_der inpr = ",pr - !WRITE(*,*)"w_der Q_e = ",Q_e - !WRITE(*,*)"w_der D_beta_norm = ",D_beta_norm E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) ! P_tor = P_perp F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 @@ -252,11 +231,8 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F !p*D = my_q RETURN END SUBROUTINE w_der_del_s -c -c -c c----------------------------------------------------------------------- -c calculate delta based on Fitzpatrick delta formulation. +c calculate delta based on Fitzpatrick P_perp and P_tor formulation. c----------------------------------------------------------------------- FUNCTION riccati_f(tmp_g,inx) COMPLEX(r8), INTENT(IN) :: tmp_g @@ -265,41 +241,34 @@ FUNCTION riccati_f(tmp_g,inx) INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf INTEGER :: ml = 0, mu = 0, nrpd = 1 - REAL(r8) :: xintv,x,xout,rtol,jac,xmin,my_p,alpha,bk COMPLEX(r8) :: ak,ck_1,ck_2,ck,xk,W_bound COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dWdp,y,dy - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork neq = 2 itol = 2 - rtol = 1e-10 !changed to 1e-08 !1e-7*pr**0.4 ! !1e-7 at front 1e-6 !e-4 + rtol = 1e-10 ALLOCATE(atol(neq),W(1),dWdp(1)) - atol(:) = 1e-10!*pr**0.4 ! changed to 1e-08 1e-8 !e-4 + atol(:) = 1e-10 itask = 2 istate = 1 iopt = 0 - mf = 21!21 IS STIFF WITH USER-SPECIFIED JACOBIAN, 10 iS NON STIFF + mf = 21 !21 IS STIFF WITH USER-SPECIFIED JACOBIAN, 10 iS NON STIFF liw = 20*2 lrw = 22+9*neq+neq**2 !just (22+16*neq) for mf=10 ALLOCATE(iwork(liw+neq),rwork(lrw)) ! just iwork(liw) for mf=10 -! MXSTEP? iopt = 1 iwork=0 iwork(6)=50000 ! maximum # of steps per call, e.g. 50000 rwork=0 - !!!!!!!! - !IF(present(inx)) my_p=inx!10.0 ! "starting backwards integration at large q" - !!!!!!!! - !my_p=6.0 xmin=1e-6 xout=xmin - ! SOLVE FOR W BOUNDARY CONDITION + ! Solve for p and W boundary conditions IF ((D_norm**2.0) > ((iota_e*P_perp)/(P_tor**(2.0/3.0)))) THEN my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25 IF (my_p < 6.0) THEN @@ -312,10 +281,6 @@ FUNCTION riccati_f(tmp_g,inx) ck = bk*(1+(g_tmp+ifac*Q_i)*((P_tor+P_perp)/(P_tor*P_perp))- $ (P_perp+(g_tmp + $ ifac*Q_i)*(D_norm**2.0) )*(iota_e/(P_tor*(D_norm**2.0)))) - ! ck_1 = 2.0*(g_tmp + ifac*Q_i)/P_perp - ! ck_2 = (P_perp + (g_tmp + - !$ ifac*Q_i)*(D_norm**2.0))/(2.0*P_perp*(D_norm**2.0)) - ! ck = (P_perp/(2.0*(D_norm**2.0)))*(1 + ck_1 - ck_2) xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0*SQRT(bk)) @@ -361,17 +326,9 @@ FUNCTION riccati_f(tmp_g,inx) itask = 1 CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) - ENDIF - ! w=0 when Q=Q_e. Why? - CALL w_der_f(neq,my_p,W,dWdp) - !WRITE(*,*)"riccati Q_e = ",Q_e - !WRITE(*,*)"riccati Q_i = ",Q_i - !WRITE(*,*)"riccati pr = ",pr - !WRITE(*,*)"riccati D_beta_norm = ",D_beta_norm - !WRITE(*,*)"riccati g_tmp = ",g_tmp !riccati_f = pi * my_p / (dWdp(1) + 1) riccati_f = pi / dWdp(1) @@ -379,7 +336,7 @@ FUNCTION riccati_f(tmp_g,inx) END FUNCTION riccati_f c----------------------------------------------------------------------- -c jacobian for riccati_del_s() +c jacobian for riccati_f() c------------------------------------------- ---------------------------- SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) INTEGER, INTENT(IN) :: neq, ml, mu, nrpd @@ -394,7 +351,7 @@ SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) pd(1,1) = (-fA_p/my_p) - (2.0*W(1))/my_p END SUBROUTINE jac_f c----------------------------------------------------------------------- -c riccati integration. +c W derivative for riccati_f() c----------------------------------------------------------------------- SUBROUTINE w_der_f(neq,my_p,W,dWdp) @@ -404,8 +361,6 @@ SUBROUTINE w_der_f(neq,my_p,W,dWdp) COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dWdp COMPLEX(r8) :: fA, fA_prime, fB, fC - !WRITE(*,*)"w_der g_tmp = ",g_tmp - ! Evaluate coefficients at the current p fA = (my_p**2)/(g_tmp + ifac*Q_e + (my_p**2.0)) fA_prime = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + @@ -422,9 +377,9 @@ SUBROUTINE w_der_f(neq,my_p,W,dWdp) RETURN END SUBROUTINE w_der_f -c -c -c +c----------------------------------------------------------------------- +c W derivative for riccati() +c----------------------------------------------------------------------- SUBROUTINE w_der(neq,x,y,dy) INTEGER, INTENT(IN) :: neq diff --git a/slayer/gslayer.f b/slayer/gslayer.f index d10d3280b..d79f9bfce 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -176,8 +176,8 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- -c Subprogram 3. scan_grid -c Run stability scan on real and imaginary rotation axes +c Subprogram 2. output_gamma +c Take SLAYER input and output dicts, send to netCDF subroutine c----------------------------------------------------------------------- SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out) @@ -190,7 +190,10 @@ SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out) $ sl_in,sl_out) END SUBROUTINE output_gamma - +c----------------------------------------------------------------------- +c Subprogram 3. allocate_inputs +c Allocate arrays inside SLAYER inputs type (dictionary-esque) +c----------------------------------------------------------------------- SUBROUTINE allocate_inputs(n_k,sl_in) INTEGER, INTENT(IN) :: n_k TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in @@ -205,7 +208,10 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k)) RETURN END SUBROUTINE allocate_inputs - +c----------------------------------------------------------------------- +c Subprogram 4. allocate_outputs +c Allocate arrays inside SLAYER outputs type (dictionary-esque) +c----------------------------------------------------------------------- SUBROUTINE allocate_outputs(n_k,sl_out) INTEGER, INTENT(IN) :: n_k TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out @@ -215,139 +221,7 @@ SUBROUTINE allocate_outputs(n_k,sl_out) RETURN END SUBROUTINE allocate_outputs c----------------------------------------------------------------------- -c Subprogram 2. growthrate_scan -c Set up and iterate stability scans IF no match is found -c----------------------------------------------------------------------- - SUBROUTINE growthrate_scan(qval,my_lu,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,inQ0,inpr,inpe,scan_radius,ncoarse, - $ compress_deltas,deltaprime,results) -c----------------------------------------------------------------------- -c Declarations -c----------------------------------------------------------------------- - ! Inputs - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inc_beta,inds, - $ intau,inQ0,inpr,inpe,my_lu - INTEGER, INTENT(IN) :: qval,scan_radius,ncoarse - REAL(r8), INTENT(IN) :: deltaprime - LOGICAL, INTENT(IN) :: compress_deltas - TYPE(result_type), INTENT(INOUT) :: results - - COMPLEX(r8) :: delta - INTEGER :: new_scan_radius,new_ncoarse - INTEGER :: nfine, new_nfine - REAL(r8), PARAMETER :: tolerance = 1.0E-6 - REAL(r8) :: delta_real, delta_imag, threshold - INTEGER :: i, j, k, l, m, count, match_count - LOGICAL :: repeat - REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, - $ inQ_coarse, iinQ_coarse - INTEGER :: max_points, new_max_points - INTEGER :: ci, cj, nx, ny - REAL(r8) :: dx, dy, overlap_factor - INTEGER :: fi, fj - REAL(r8) :: fine_dx, fine_dy, overlap_x, overlap_y - REAL(r8) :: x_start, x_end, y_start, y_end, x, y - !!!!!!!!!!!!!!!! - repeat = .FALSE. - dx = 1.0 - dy = 1.0 - nfine = 6 - overlap_factor = 0.5 - max_points = ncoarse**2 * ((nfine)**2 - 1) - - ! Allocate arrays with maximum possible size - ALLOCATE(results%inQs(max_points), results%iinQs(max_points)) - ALLOCATE(results%Re_deltas(max_points), - $ results%Im_deltas(max_points)) - - results%inQs=0.0 - results%iinQs=0.0 - results%Re_deltas=0.0 - results%Im_deltas=0.0 - ! Initialize counter - count = 0 - - ! Calculate step sizes - inQ_step = (2.0 * scan_radius) / (ncoarse - 1) - iinQ_step = (2.0 * scan_radius) / (ncoarse - 1) - dx = inQ_step - dy = iinQ_step - - match_count = 0 - ! Run scan - CALL scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe,my_lu, - $ scan_radius,ncoarse,nfine,deltaprime,compress_deltas, - $ results,count,match_count,dx,dy) - - ! Set the actual count of points - results%count = count - - IF (count < max_points) THEN - ! Resize arrays to actual number of points - CALL shrink_array(results%inQs, count) - CALL shrink_array(results%iinQs, count) - CALL shrink_array(results%Re_deltas, count) - CALL shrink_array(results%Im_deltas, count) - END IF - - RETURN - END SUBROUTINE growthrate_scan -c----------------------------------------------------------------------- -c Subprogram 3. scan_grid -c Run stability scan on real and imaginary rotation axes -c----------------------------------------------------------------------- - SUBROUTINE scan_grid(inQ_e,inQ_i,inpr,inc_beta,inds,intau, - $ inpe,my_lu,scan_radius,ncoarse,nfine,deltaprime, - $ compress_deltas,results,count,match_count,dx,dy) - - ! Declarations (include necessary type declarations from original code) - REAL(r8), INTENT(IN) :: inQ_e,inQ_i,inpr,inc_beta,inds, - $ intau,inpe,my_lu,deltaprime - INTEGER, INTENT(IN) :: scan_radius, ncoarse, nfine - LOGICAL, INTENT(IN) :: compress_deltas - TYPE(result_type), INTENT(INOUT) :: results - INTEGER, INTENT(INOUT) :: count, match_count - REAL(r8), INTENT(INOUT) :: dx, dy - - ! Local variables - REAL(r8) :: inQ_step, iinQ_step, inQ_fine, iinQ_fine, - $ inQ_coarse, iinQ_coarse - REAL(r8) :: delta_real, delta_imag, threshold - COMPLEX(r8) :: delta - REAL(r8) :: fine_dx, fine_dy, overlap_x, overlap_y - REAL(r8) :: x_start, x_end, y_start, y_end - INTEGER :: i, j, fi, fj - REAL(r8), PARAMETER :: tolerance = 1.0E-6 - REAL(r8) :: overlap_factor = 0.5 - - ! Calculate step sizes - inQ_step = (2.0 * scan_radius) / (ncoarse - 1) - iinQ_step = (2.0 * scan_radius) / (ncoarse - 1) - dx = inQ_step - dy = iinQ_step - count = 0 - - DO i = 1, ncoarse - DO j = 1, ncoarse - inQ_coarse = -scan_radius + (i - 1) * inQ_step - iinQ_coarse = -scan_radius + (j - 1) * iinQ_step - ! Evaluate riccati FUNCTION - delta = riccati(inQ_coarse,inQ_e,inQ_i,inpr,inc_beta, - $ inds,intau,inpe,iinQ=iinQ_coarse) - delta_real = REAL(delta)*(my_lu**(1.0/3.0)) ! Critical normalization - delta_imag = AIMAG(delta)*(my_lu**(1.0/3.0)) ! Critical normalization - - count = count + 1 - results%inQs(count) = inQ_coarse - results%iinQs(count) = iinQ_coarse - results%Re_deltas(count) = delta_real - results%Im_deltas(count) = delta_imag - - END DO - END DO - END SUBROUTINE scan_grid -c----------------------------------------------------------------------- -c Subprogram 4. shrink_array +c Subprogram 5. shrink_array c Remove excess scan array size from memory c----------------------------------------------------------------------- SUBROUTINE shrink_array(arr, new_size) @@ -360,7 +234,7 @@ SUBROUTINE shrink_array(arr, new_size) CALL move_alloc(temp, arr) END SUBROUTINE shrink_array c----------------------------------------------------------------------- -c Subprogram 5. grow_array +c Subprogram 6. grow_array c Increase scan array size IF necessary c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) @@ -372,12 +246,9 @@ SUBROUTINE grow_array(arr, old_size, new_size) temp(1:old_size) = arr(1:old_size) CALL move_alloc(temp, arr) END SUBROUTINE grow_array -c -c -c c----------------------------------------------------------------------- -c Subprogram 6. determinant -c Increase scan array size IF necessary +c Subprogram 7. calc_determinant +c Calculate determinant of 2x2 and 3x3 matrices c----------------------------------------------------------------------- SUBROUTINE calc_determinant(matk, nk, detk) IMPLICIT NONE @@ -387,9 +258,7 @@ SUBROUTINE calc_determinant(matk, nk, detk) COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! Input matrix COMPLEX(r8), INTENT(OUT) :: detk ! Determinant result INTEGER :: status ! Status (0=success, -1=error) - - ! Local variables - + status = 0 ! Initialize status as success SELECT CASE (nk) @@ -412,13 +281,14 @@ SUBROUTINE calc_determinant(matk, nk, detk) END SELECT RETURN END SUBROUTINE calc_determinant -c -c -c Adapted from - FUNCTION dispersion_det(g_tmp,n_k,sl_in) +c----------------------------------------------------------------------- +c Subprogram 8. dispersion_det +c Calculate determinant of coupling matrix e-value problem +c----------------------------------------------------------------------- + FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) COMPLEX(r8), INTENT(IN) :: g_tmp - INTEGER, INTENT(IN) :: n_k + INTEGER, INTENT(IN) :: n_k,msing_max TYPE(slayer_inputs_type), INTENT(IN) :: sl_in COMPLEX(r8) :: dispersion_det,det_val,tmp_delta COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:),result_matrix(:,:) @@ -444,7 +314,7 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in) ELSEIF ((n_k == 2) .OR. (n_k == 3)) THEN ALLOCATE(delta_Q(n_k,n_k)) delta_Q=(0.0,0.0) - DO k=1,2 !!! MAXING OUT AT 2X2 + DO k=1,msing_max ! maxing out at msing_max Q_e = sl_in%Q_e_arr(k) Q_i = sl_in%Q_i_arr(k) P_perp = sl_in%P_perp_arr(k) @@ -474,5 +344,4 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in) stop END IF END FUNCTION dispersion_det - END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index be8f64e8a..7dabfbae8 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -77,12 +77,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) CALL sl_check(stat) - !bt0_id=0 !!!!! THIS COULD BE A PROBLEM - !nn_id=0 - !mpsi_id=0 - !psio_id=0 - !ro_id=0 - ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), $ my_psio(INT(psio_len)), $ mpsi(INT(mpsi_len)),nn(INT(nn_len))) diff --git a/slayer/slayer.f b/slayer/slayer.f index 08971bee5..773a6cdf0 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -22,7 +22,7 @@ PROGRAM slayer CHARACTER(512) :: infile,ncfile INTEGER :: i,j,k,inum,jnum,knum,inn,count, - $ Q_num,n_k + $ Q_num,msing_max,n_k INTEGER, DIMENSION(1) :: index LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, @@ -32,8 +32,8 @@ PROGRAM slayer $ bal_flag,stability_flag,riccatiscan_flag,input_flag, $ params_check,stabscan_flag,read_eq,est_gamma_flag, $ match_gamma_flag,fitz_flag,coupling_flag,br_th_flag - REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_width,l_n,l_t, - $ qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val + REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_width,l_n, + $ l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, @@ -67,10 +67,10 @@ PROGRAM slayer $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,Q0,delta_prime,delta_n_p,ingamma NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, - $ dc_type,read_eq,fitz_flag,coupling_flag,QPscan_flag, - $ Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag,otscan_flag, - $ ntscan_flag,nbtscan_flag,parflow_flag,peohmonly_flag, - $ Pe_flag,layfac + $ msing_max,dc_type,read_eq,fitz_flag,coupling_flag, + $ QPscan_flag,Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag, + $ otscan_flag,ntscan_flag,nbtscan_flag,parflow_flag, + $ peohmonly_flag,Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ est_gamma_flag,match_gamma_flag,stability_flag, $ stabscan_flag,br_th_flag,bal_flag @@ -122,6 +122,7 @@ PROGRAM slayer knum=100 ! resolution for 2d scan alont with the other. Q_num=100 ! resolution for stab. scan along Re(Q) axis scan_width = 2.0 + msing_max = 2 in_unit=1 out_unit=2 out2_unit=3 @@ -485,14 +486,10 @@ PROGRAM slayer END IF END IF - ALLOCATE(re_trace(100),im_trace(100)) - ALLOCATE(sl_out%r_trace(n_k,100), - $ sl_out%i_trace(n_k,100)) - !-------------- !n_k = 1 !-------------- - DO k=1,2!!!!!!!!!!!!!! ONLY DOING TWO SURFACES + DO k=1,msing_max WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', $ sl_in%qval_arr(k),' rational surface:' @@ -520,53 +517,19 @@ PROGRAM slayer WRITE(*,*)"slayer.f D_crit: ",sl_in%d_crit_arr(k) WRITE(*,*),"sl_in%qval_arr: ",sl_in%qval_arr - ! (Deltaprime - d_crit)/S^1/3 + ! Calculate (Deltaprime - d_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) c delta_eff = Re_deltaprime_arr(k) pe = 0.0 - re_trace = 0.0 - im_trace = 0.0 - n_trace = 1 - - IF (fitz_flag) THEN - g_r = 0.0 - g_i = -Q_e - re_trace(1) = 0.0 - im_trace(1) = -Q_e - ELSE - g_i = 0.0 - g_r = -Q_e - im_trace(1) = 0.0 - re_trace(1) = -Q_e - END IF - - WRITE(*,*)"no longer using newton_root(): " - !CALL newton_root(g_r,g_i,1,fitz_flag) - - WRITE(*,*) - !WRITE(*,'(A,F0.3,A)') 'Success! Growth rate = ', - !$ g_r/tauk, ' [Hz]' - - !CALL shrink_array(re_trace, n_trace) - !CALL shrink_array(im_trace, n_trace) - n_trace = 100 - - IF (fitz_flag) THEN - !re_trace = re_trace/tauk - !im_trace = im_trace/tauk - sl_out%gamma_sol_arr(k) = 0.0!g_r/tauk! THIS IS FOR PLOT - ELSE - !re_trace = re_trace/tauk - !im_trace = -im_trace/tauk - sl_out%gamma_sol_arr(k) = 0.0!-g_r/tauk! THIS IS FOR PLOT - END IF +c ! Fill gamma_sol_arr with 0's, will by used by python root finding + sl_out%gamma_sol_arr(k) = 0.0 IF (stabscan_flag) THEN WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// - $ 'Im(Q)] scan with radius = ', + $ 'Im(Q)] scan with Q width = ', $ scan_width ing_step = (2.0 * scan_width) / (Q_num - 1) @@ -656,7 +619,7 @@ PROGRAM slayer ! Evaluate determinant g_tmp = CMPLX(ing_coarse,iing_coarse) - deltas(i,j) = dispersion_det(g_tmp,n_k,sl_in) + deltas(i,j)=dispersion_det(g_tmp,n_k,sl_in,msing_max) ENDDO ENDDO From 357aadf432566af8b6f9d0ba15e2e663a2b0454a Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 22 Jan 2026 20:39:39 -0500 Subject: [PATCH 055/305] WIP: SLAYER AMR using hash table --- input/slayer.in | 2 + slayer/gslayer.f | 269 ++++++++++++++++++++++++++++++++++++++++- slayer/sglobal.f | 20 ++- slayer/slayer.f | 72 ++++++++--- slayer/slayer_netcdf.f | 81 +++++++++++-- 5 files changed, 411 insertions(+), 33 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index ef9e225d4..1889bca87 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -77,6 +77,8 @@ match_gamma_flag=t ! Find Delta_hat = Delta_prime match to solve for growth rate stability_flag=f ! calculate delta dependence on complex Q stabscan_flag=f ! Generate Re(Q),Im(Q) scan and output as ASCII + coupled_stabscan_flag=f ! Generate Re(Q),Im(Q) scan and output as ASCII + amr_flag=t ! Generate Re(Q),Im(Q) scan and output as ASCII br_th_flag=f ! Calculate br_th bal_flag=f ! calculate the resonant field penetration threshold from torque balance / diff --git a/slayer/gslayer.f b/slayer/gslayer.f index d79f9bfce..3bf091245 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -179,15 +179,18 @@ END SUBROUTINE gpec_slayer c Subprogram 2. output_gamma c Take SLAYER input and output dicts, send to netCDF subroutine c----------------------------------------------------------------------- - SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out) + SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out, + $ all_deltas_out) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: est_gamma_flag TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out + TYPE(deltas_outputs_type), INTENT(IN) :: + $ all_deltas_out(SIZE(sl_in%qval_arr)) CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),est_gamma_flag, - $ sl_in,sl_out) + $ sl_in,sl_out,all_deltas_out) END SUBROUTINE output_gamma c----------------------------------------------------------------------- @@ -324,9 +327,6 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) c_beta = sl_in%c_beta_arr(k) tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) - - delta_eff = (sl_in%Re_dp_arr(k) - - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) delta_Q(k,k)=riccati_f(((g_tmp*sl_in%Qconv_arr(1)) $ /tauk)) @@ -344,4 +344,263 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) stop END IF END FUNCTION dispersion_det + + SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, + $ scan_width,Q_num,AMR_passes, + $ coupling_flag) + !WRITE(*,*)"------------------------------------------" + !WRITE(*,'(A,F0.1,A,I2,A)')' >>> Running Adaptive AMR Scan [Width=',scan_width, & + ! ', Passes=', AMR_PASSES, ']' + INTEGER, INTENT(IN) :: n_k,msing_max,Q_num,AMR_passes + REAL(r8), INTENT(IN) :: scan_width + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + + !COMPLEX(r8), INTENT(OUT), ALLOCATABLE :: Q_store(:) ! Stores Q coordinates + !COMPLEX(r8), INTENT(OUT), ALLOCATABLE :: D_store(:) ! Stores Result Delta + INTEGER, ALLOCATABLE :: cells(:,:) ! 4 corners (indices) per cell + INTEGER, ALLOCATABLE :: new_cells(:,:) ! Temp array for next level + INTEGER :: n_cells, n_new_cells, i, j + INTEGER :: h_idx, pt_idx, c_idx, pass + INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! Corner indices + INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! Midpoint indices + REAL(r8) :: r_min, r_max, i_min, i_max, ing_step, + $ ing_coarse,iing_coarse + LOGICAL :: cross_real, cross_imag + !INTEGER, INTENT(OUT) :: n_pts + + COMPLEX(r8) :: q_curr + + ! (Re-allocate or just reset counters. Re-allocating ensures clean slate) + !IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store, D_store) + !IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head, hash_next) + !IF (ALLOCATED(cells)) DEALLOCATE(cells, new_cells) + + ! --- 1. Initialize Memory --- + ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) + ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) + ALLOCATE(cells(4, 200000), new_cells(4, 200000)) ! Estimate cell count + + hash_head = 0 + hash_next = 0 + n_pts = 0 + n_cells = 0 + + ! --- 2. Build Initial Coarse Grid (100x100) --- + ! We treat the grid as a collection of quadrilateral cells + ing_step = (2.0*scan_width) / (Q_num - 1) + + ! A. Generate Points & Evaluate + DO i = 1, Q_num + DO j = 1, Q_num + ing_coarse = -scan_width + (i - 1) * ing_step + iing_coarse = -scan_width + (j - 1) * ing_step + q_curr = CMPLX(ing_coarse, iing_coarse) + + ! Check/Compute (Using inline logic to simulate a function call) + CALL get_or_compute(q_curr, pt_idx,n_k,sl_in,msing_max, + $ coupling_flag) + + ! If we are not at the right/bottom edge, form a cell with neighbors + IF (i < Q_num .AND. j < Q_num) THEN + n_cells = n_cells + 1 + ! Store indices of corners: TL, TR, BL, BR (row-major logic) + ! Note: This indexing assumes we inserted in order, but for AMR + ! we must rely on the returned pt_idx, not loop counters. + ! To simplify, we just store the TL index and calculate others? + ! No, AMR breaks structure. We must look up all 4 corners. + + ! Top-Left (current) + cells(1, n_cells) = pt_idx + + ! Top-Right (i+1, j) + q_curr = CMPLX(ing_coarse + ing_step, iing_coarse) + CALL get_or_compute(q_curr, cells(2, n_cells),n_k, + $ sl_in,msing_max,coupling_flag) + + ! Bottom-Left (i, j+1) + q_curr = CMPLX(ing_coarse, iing_coarse+ing_step) + CALL get_or_compute(q_curr, cells(3, n_cells),n_k, + $ sl_in,msing_max,coupling_flag) + + ! Bottom-Right (i+1, j+1) + q_curr = CMPLX(ing_coarse + ing_step, iing_coarse+ + $ ing_step) + CALL get_or_compute(q_curr, cells(4, n_cells),n_k, + $ sl_in,msing_max,coupling_flag) + END IF + END DO + END DO + + ! --- 3. Refinement Loops --- + DO pass = 1, AMR_PASSES + WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, + $ ': Checking ', n_cells, ' cells...' + n_new_cells = 0 + + DO c_idx = 1, n_cells + idx_TL = cells(1, c_idx) + idx_TR = cells(2, c_idx) + idx_BL = cells(3, c_idx) + idx_BR = cells(4, c_idx) + + ! Check for contours (Sign changes across the cell) + ! Real Part Check + r_min = MIN(REAL(D_store(idx_TL)), + $ REAL(D_store(idx_TR)), + $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BR))) + r_max = MAX(REAL(D_store(idx_TL)), + $ REAL(D_store(idx_TR)), + $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BR))) + cross_real = (r_min * r_max <= 0.0d0) + + ! Imag Part Check + i_min = MIN(AIMAG(D_store(idx_TL)), + $ AIMAG(D_store(idx_TR)), + $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BR))) + i_max = MAX(AIMAG(D_store(idx_TL)), + $ AIMAG(D_store(idx_TR)), + $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BR))) + cross_imag = (i_min * i_max <= 0.0d0) + + IF (cross_real .OR. cross_imag) THEN + ! --- REFINE THIS CELL --- + ! We need 5 new points: Top-Mid, Bot-Mid, Left-Mid, Right-Mid, Center + + ! Calculate coords from corners + ! TL: Q_store(idx_TL), BR: Q_store(idx_BR) + + ! Top-Mid + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_TR)) + CALL get_or_compute(q_curr, idx_TM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Bot-Mid + q_curr = 0.5d0*(Q_store(idx_BL)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_BM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Left-Mid + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BL)) + CALL get_or_compute(q_curr, idx_LM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Right-Mid + q_curr = 0.5d0*(Q_store(idx_TR)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_RM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Center + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_MM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Create 4 new sub-cells (Top-Left, Top-Right, Bot-Left, Bot-Right) + ! Sub 1 (Top-Left) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_TL + new_cells(2, n_new_cells) = idx_TM + new_cells(3, n_new_cells) = idx_LM + new_cells(4, n_new_cells) = idx_MM + + ! Sub 2 (Top-Right) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_TM + new_cells(2, n_new_cells) = idx_TR + new_cells(3, n_new_cells) = idx_MM + new_cells(4, n_new_cells) = idx_RM + + ! Sub 3 (Bot-Left) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_LM + new_cells(2, n_new_cells) = idx_MM + new_cells(3, n_new_cells) = idx_BL + new_cells(4, n_new_cells) = idx_BM + + ! Sub 4 (Bot-Right) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_MM + new_cells(2, n_new_cells) = idx_RM + new_cells(3, n_new_cells) = idx_BM + new_cells(4, n_new_cells) = idx_BR + + ELSE + ! No refinement needed, keep original cell + n_new_cells = n_new_cells + 1 + new_cells(:, n_new_cells) = cells(:, c_idx) + END IF + END DO + + ! Swap arrays for next iteration + n_cells = n_new_cells + cells(:, 1:n_cells) = new_cells(:, 1:n_cells) + + END DO + DEALLOCATE(cells, new_cells) + WRITE(*,*) "AMR Scan Complete. Total Points:", n_pts + WRITE(*,*)"gslayer.f Q_store(10) = ",Q_store(10) + RETURN + END SUBROUTINE dispersion_AMR + + SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, + $ coupling_flag) + COMPLEX(r8), INTENT(IN) :: q_in + !INTEGER, INTENT(INOUT) :: n_pts + !COMPLEX(r8), INTENT(INOUT) :: Q_store(:),D_store(:) + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + INTEGER, INTENT(IN) :: n_k, msing_max + INTEGER, INTENT(OUT) :: idx_out + LOGICAL, INTENT(IN) :: coupling_flag + INTEGER :: h, curr, ix, iy + COMPLEX(r8) :: delta_val + + ! 1. Calculate Hash + ix = NINT(REAL(q_in) * HASH_SCALE) + iy = NINT(AIMAG(q_in) * HASH_SCALE) + ! Simple hash mix + h = MOD(ABS(ix * 73856093 + iy * 19349663), HASH_SZ) + 1 + + ! 2. Check collisions + curr = hash_head(h) + DO WHILE (curr /= 0) + ! Check if point matches (with small tolerance) + IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN + idx_out = curr + RETURN ! Found it, return existing index + END IF + curr = hash_next(curr) + END DO + + ! 3. Not found: Compute and Store + n_pts = n_pts + 1 + + IF (n_pts > MAX_PTS) THEN + WRITE(*,*) "ERROR: AMR exceeded MAX_PTS" + STOP + END IF + + idx_out = n_pts + Q_store(idx_out) = q_in + + ! --- PHYSICS EVALUATION --- + IF (coupling_flag) THEN + g_tmp = q_in + delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) + ELSE + g_tmp = q_in + delta_val = riccati_f(g_tmp) + !delta_val = delta_val - delta_eff + END IF + D_store(idx_out) = delta_val + ! -------------------------- + + ! 4. Add to Hash Table + hash_next(idx_out) = hash_head(h) + hash_head(h) = idx_out + + END SUBROUTINE get_or_compute END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index eafcd4386..334ebc530 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -13,7 +13,6 @@ MODULE sglobal_mod REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i,iota_e, $ delta_n,layfac,Qconv,lnLamb,deltaprim,dc_tmp, $ d_crit,tau_r,tauk,g_r,g_i,delta_eff - REAL(r8), DIMENSION(:), ALLOCATABLE :: re_trace,im_trace COMPLEX(r8) :: Q,g_tmp,delta_det CHARACTER(20) :: dc_type @@ -21,6 +20,15 @@ MODULE sglobal_mod $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6021917e-19, $ kval=1.3807e-23,eps0 = 8.8542e-12 + INTEGER, PARAMETER :: MAX_PTS = 500000 ! Max unique points allowed + INTEGER, PARAMETER :: HASH_SZ = 500009 ! Prime number for hash table + REAL(r8), PARAMETER :: HASH_SCALE = 1.0d5 ! Scaling factor for integer hashing + INTEGER, ALLOCATABLE :: hash_head(:) ! Hash bucket heads + INTEGER, ALLOCATABLE :: hash_next(:) ! Linked list next pointers + INTEGER :: n_pts + COMPLEX(r8), ALLOCATABLE :: Q_store(:) ! Stores Q coordinates + COMPLEX(r8), ALLOCATABLE :: D_store(:) ! Stores Result Delta + TYPE result_type REAL(r8), ALLOCATABLE :: inQs(:), iinQs(:), $ Re_deltas(:), Im_deltas(:) @@ -40,10 +48,16 @@ MODULE sglobal_mod TYPE slayer_outputs_type COMPLEX(r8), ALLOCATABLE :: dels_db_arr(:),gamma_sol_arr(:), - $ gamma_est_arr(:) - REAL(r8), ALLOCATABLE :: r_trace(:,:),i_trace(:,:) + $ gamma_est_arr(:) END TYPE slayer_outputs_type + TYPE deltas_outputs_type + REAL(r8), ALLOCATABLE :: inQs(:) + REAL(r8), ALLOCATABLE :: iinQs(:) + REAL(r8), ALLOCATABLE :: real_deltas(:) + REAL(r8), ALLOCATABLE :: imag_deltas(:) + END TYPE deltas_outputs_type + ! lnLamb will be updated. COMPLEX(r8), PARAMETER :: ifac=(0,1) diff --git a/slayer/slayer.f b/slayer/slayer.f index 773a6cdf0..3f398681a 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -30,7 +30,8 @@ PROGRAM slayer $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,stabscan_flag,read_eq,est_gamma_flag, + $ params_check,stabscan_flag,coupled_stabscan_flag,amr_flag, + $ read_eq,est_gamma_flag, $ match_gamma_flag,fitz_flag,coupling_flag,br_th_flag REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_width,l_n, $ l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val @@ -57,9 +58,13 @@ PROGRAM slayer REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas - + + ! AMR DECLARATIONS + INTEGER, PARAMETER :: AMR_passes = 4 ! Number of refinement levels (User Set) + TYPE(slayer_inputs_type) :: sl_in TYPE(slayer_outputs_type) :: sl_out + TYPE(deltas_outputs_type), ALLOCATABLE :: all_deltas_out(:) NAMELIST/slayer_input/input_flag,infile, $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, @@ -73,7 +78,8 @@ PROGRAM slayer $ peohmonly_flag,Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ est_gamma_flag,match_gamma_flag,stability_flag, - $ stabscan_flag,br_th_flag,bal_flag + $ stabscan_flag,coupled_stabscan_flag,amr_flag,br_th_flag, + $ bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, $ params_check c----------------------------------------------------------------------- @@ -161,6 +167,8 @@ PROGRAM slayer bal_flag=.FALSE. stability_flag=.FALSE. stabscan_flag=.FALSE. + coupled_stabscan_flag=.FALSE. + amr_flag=.FALSE. br_th_flag=.FALSE. c----------------------------------------------------------------------- c read slayer.in. @@ -404,7 +412,8 @@ PROGRAM slayer IF (.NOT. (match_gamma_flag)) THEN sl_out%gamma_sol_arr = (/0./) - CALL output_gamma(est_gamma_flag,sl_in,sl_out) + CALL output_gamma(est_gamma_flag,sl_in,sl_out, + $ all_deltas_out) END IF ENDIF c----------------------------------------------------------------------- @@ -486,10 +495,12 @@ PROGRAM slayer END IF END IF - !-------------- - !n_k = 1 - !-------------- - DO k=1,msing_max + IF (amr_flag) THEN + !IF (ALLOCATED(all_deltas_out)) DEALLOCATE(all_deltas_out) + ALLOCATE(all_deltas_out(n_k)) + END IF + + DO k=1,MIN(n_k,msing_max) WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', $ sl_in%qval_arr(k),' rational surface:' @@ -526,13 +537,45 @@ PROGRAM slayer c ! Fill gamma_sol_arr with 0's, will by used by python root finding sl_out%gamma_sol_arr(k) = 0.0 - IF (stabscan_flag) THEN + IF (amr_flag) THEN + + CALL dispersion_AMR(n_k,sl_in,msing_max,scan_width, + $ Q_num,AMR_passes,coupling_flag) + WRITE(*,*)"Q_store(10) = ",Q_store(10) + + WRITE(*,*)"n_pts = ",n_pts + + ! --- 4. Export to Output Arrays --- + ! Flatten the unique points store into the requested 1D arrays + ALLOCATE(all_deltas_out(k)%inQs(n_pts), + $ all_deltas_out(k)%iinQs(n_pts)) + ! Assumes you also have real_deltas/imag_deltas declared as 1D + ALLOCATE(all_deltas_out(k)%real_deltas(n_pts), + $ all_deltas_out(k)%imag_deltas(n_pts)) + + DO i = 1, n_pts + all_deltas_out(k)%inQs(i) = REAL(Q_store(i)) + IF (fitz_flag) THEN + all_deltas_out(k)%iinQs(i) = AIMAG(Q_store(i)) + ELSE + all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) + END IF + all_deltas_out(k)%real_deltas(i) = REAL(D_store(i)) + all_deltas_out(k)%imag_deltas(i) = AIMAG(D_store(i)) + END DO + + ! Clean up temporary AMR memory + DEALLOCATE(Q_store, D_store, hash_head, hash_next) + + END IF + + IF ((stabscan_flag)) THEN ! was .AND. (k == 2) WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// $ 'Im(Q)] scan with Q width = ', $ scan_width - ing_step = (2.0 * scan_width) / (Q_num - 1) + ing_step = (2.0*scan_width) / (Q_num - 1) ! was (2.0 * scan_width) / (Q_num - 1) count = 0 ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) @@ -540,7 +583,7 @@ PROGRAM slayer DO i = 1, (Q_num+1) DO j = 1, Q_num - ing_coarse = -scan_width + (i - 1) * ing_step + ing_coarse = -scan_width + (i - 1) * ing_step ! added 0.5* iing_coarse = -scan_width + (j - 1) * ing_step ! Evaluate riccati function g_tmp = CMPLX(ing_coarse,iing_coarse) @@ -586,15 +629,12 @@ PROGRAM slayer ENDIF ENDDO - sl_out%r_trace(k,:) = re_trace - sl_out%i_trace(k,:) = im_trace - IF (.NOT. (est_gamma_flag)) THEN sl_in%d_beta_arr = (/ 0. /) sl_out%dels_db_arr = (/ 0. /) END IF - IF (coupling_flag) THEN + IF (coupled_stabscan_flag) THEN WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// $ 'Im(Q)] determinant scan with radius = ', @@ -639,7 +679,7 @@ PROGRAM slayer DEALLOCATE(inQs,iinQs,deltas) END IF - CALL output_gamma(est_gamma_flag,sl_in,sl_out) + CALL output_gamma(est_gamma_flag,sl_in,sl_out,all_deltas_out) stop ENDIF c----------------------------------------------------------------------- diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index f665e3279..c597c7311 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -49,20 +49,30 @@ END SUBROUTINE sl_check c declarations. c ----------------------------------------------------------------------- SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, - $ sl_in,sl_out) + $ sl_in,sl_out,all_deltas_out) INTEGER, INTENT(IN) :: msing LOGICAL, INTENT(IN) :: est_gamma_flag TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out + TYPE(deltas_outputs_type), INTENT(IN) :: all_deltas_out(msing) INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,p_perp_id,S_id, - $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,tr_id,tr_dim, + $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id, $ qsing_id,qc_id,p_tor_id INTEGER :: run, run_dimid, point_dimid, varids(4) + ! AMR declarations + INTEGER :: max_pts_all, s, n_curr + INTEGER :: dim_pts_id + INTEGER :: var_q_id, var_d_id, var_npts_id + REAL(r8), ALLOCATABLE :: buffer_q(:,:,:), buffer_d(:,:,:) + INTEGER, ALLOCATABLE :: n_pts_arr(:) + REAL(r8) :: fill_val = -9.99E33 ! Standard NetCDF Fill Value + ! AMR declarations + CHARACTER(64) :: ncfile LOGICAL, PARAMETER :: debug_flag = .FALSE. CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' @@ -84,7 +94,48 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, IF(debug_flag) PRINT *," - Creating netcdf files" CALL sl_check( nf90_create(ncfile, $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) +c +c reform "ragged" AMR delta ouputs into rectangular array +c + ! 1. Find the Maximum AMR Grid Size across all surfaces + max_pts_all = 0 + DO s = 1, msing + IF (ALLOCATED(all_deltas_out(s)%inQs)) THEN + max_pts_all = MAX(max_pts_all,SIZE(all_deltas_out(s)%inQs)) + END IF + END DO + + ! 2. Allocate Rectangular Buffers (Points, Surfaces, Re/Im) + ! Shape: (Max_Points, Number_Surfaces, 2) + ALLOCATE(buffer_q(max_pts_all, msing, 2)) + ALLOCATE(buffer_d(max_pts_all, msing, 2)) + ALLOCATE(n_pts_arr(msing)) + + ! Initialize with Fill Value (so unused space is ignored by plotters) + buffer_q = 0.0 + buffer_d = 0.0 + n_pts_arr = 0 + + ! 3. Flatten the Ragged Data into the Buffers + DO s = 1, msing + IF (ALLOCATED(all_deltas_out(s)%inQs)) THEN + n_curr = SIZE(all_deltas_out(s)%inQs) + n_pts_arr(s) = n_curr + + ! --- FILL Q (Coordinate) --- + ! Real part (inQs) -> Index 1 + buffer_q(1:n_curr,s,1) = all_deltas_out(s)%inQs(1:n_curr) + ! Imag part (iinQs) -> Index 2 + buffer_q(1:n_curr,s,2) = all_deltas_out(s)%iinQs(1:n_curr) + + ! --- FILL DELTA (Result) --- + ! Real part -> Index 1 + buffer_d(1:n_curr,s,1)=all_deltas_out(s)%real_deltas(1:n_curr) + ! Imag part -> Index 2 + buffer_d(1:n_curr,s,2)=all_deltas_out(s)%imag_deltas(1:n_curr) + END IF + END DO c ----------------------------------------------------------------------- c define global file attributes c ----------------------------------------------------------------------- @@ -111,8 +162,6 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ qsing_dim,qsing_id)) CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_int, $ qsing_dim,qr_id)) - CALL sl_check( nf90_def_dim(ncid, "step", SIZE(re_trace), - $ tr_dim) ) CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, $ qsing_dim,omegas_id)) CALL sl_check( nf90_def_var(ncid,"tau_k",nf90_double, @@ -153,8 +202,19 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, CALL sl_check( nf90_def_var(ncid,"growth rate", $ nf90_double,(/qsing_dim,i_dim/),gs_id) ) - CALL sl_check( nf90_def_var(ncid,"growth rate trace", - $ nf90_double,(/qsing_dim,tr_dim,i_dim/),tr_id) ) + + !!! AMR + CALL sl_check(nf90_def_dim(ncid, 'amr_pts', max_pts_all, + $ dim_pts_id)) + ! Define Variables + CALL sl_check(nf90_def_var(ncid, 'n_amr_pts', NF90_INT, + $ (/qsing_dim/), var_npts_id)) + ! Note: Dimensions order is (pts, surf, cplx) + CALL sl_check(nf90_def_var(ncid, 'Q_AMR', NF90_DOUBLE, + $ (/dim_pts_id, qsing_dim, i_dim/), var_q_id)) + CALL sl_check(nf90_def_var(ncid, 'Deltas_AMR', NF90_DOUBLE, + $ (/dim_pts_id, qsing_dim, i_dim/), var_d_id)) + ! end definitions CALL sl_check( nf90_enddef(ncid) ) c ----------------------------------------------------------------------- @@ -193,9 +253,12 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ RESHAPE((/REAL(sl_out%gamma_sol_arr), $ AIMAG(sl_out%gamma_sol_arr)/),(/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,tr_id, - $ RESHAPE((/sl_out%r_trace,sl_out%i_trace/), - $ (/msing,SIZE(re_trace),2/)))) + CALL sl_check(nf90_put_var(ncid, var_npts_id, n_pts_arr)) + CALL sl_check(nf90_put_var(ncid, var_q_id, buffer_q)) + CALL sl_check(nf90_put_var(ncid, var_d_id, buffer_d)) + + ! 6. Clean Up + DEALLOCATE(buffer_q, buffer_d, n_pts_arr) c ----------------------------------------------------------------------- c close file From 8f45f0913d19e56818f1e64d6535510763c95de9 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 23 Jan 2026 14:20:40 -0500 Subject: [PATCH 056/305] WIP: AMR V2 with cell types --- slayer/gslayer.f | 511 +++++++++++++++++++++++++++++++++++++++++++---- slayer/sglobal.f | 14 +- slayer/slayer.f | 14 +- 3 files changed, 489 insertions(+), 50 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 3bf091245..06dfae536 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -370,67 +370,63 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, !INTEGER, INTENT(OUT) :: n_pts COMPLEX(r8) :: q_curr - + INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Re-allocate or just reset counters. Re-allocating ensures clean slate) !IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store, D_store) !IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head, hash_next) !IF (ALLOCATED(cells)) DEALLOCATE(cells, new_cells) ! --- 1. Initialize Memory --- + !ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) + !ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) + !ALLOCATE(cells(4, 200000), new_cells(4, 200000)) ! Estimate cell count + + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) + ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) - ALLOCATE(cells(4, 200000), new_cells(4, 200000)) ! Estimate cell count + ALLOCATE(cells(4, 200000), new_cells(4, 200000)) hash_head = 0 hash_next = 0 n_pts = 0 n_cells = 0 - ! --- 2. Build Initial Coarse Grid (100x100) --- - ! We treat the grid as a collection of quadrilateral cells + ! --- 2. Build Initial Coarse Grid (Robust 2-Pass Method) --- + ALLOCATE(coarse_indices(Q_num, Q_num)) ing_step = (2.0*scan_width) / (Q_num - 1) - - ! A. Generate Points & Evaluate + + ! PASS 1: Compute ALL points and store their indices + ! This ensures every grid node is computed exactly once and hashed consistently DO i = 1, Q_num - DO j = 1, Q_num + DO j = 1, Q_num ing_coarse = -scan_width + (i - 1) * ing_step iing_coarse = -scan_width + (j - 1) * ing_step q_curr = CMPLX(ing_coarse, iing_coarse) - ! Check/Compute (Using inline logic to simulate a function call) - CALL get_or_compute(q_curr, pt_idx,n_k,sl_in,msing_max, - $ coupling_flag) + CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, + $ sl_in, msing_max, coupling_flag) + END DO + END DO + + ! PASS 2: Stitch cells together using the stored indices + ! No floating point math here, just integer lookups -> Perfect Topology + DO i = 1, Q_num - 1 + DO j = 1, Q_num - 1 + n_cells = n_cells + 1 - ! If we are not at the right/bottom edge, form a cell with neighbors - IF (i < Q_num .AND. j < Q_num) THEN - n_cells = n_cells + 1 - ! Store indices of corners: TL, TR, BL, BR (row-major logic) - ! Note: This indexing assumes we inserted in order, but for AMR - ! we must rely on the returned pt_idx, not loop counters. - ! To simplify, we just store the TL index and calculate others? - ! No, AMR breaks structure. We must look up all 4 corners. - - ! Top-Left (current) - cells(1, n_cells) = pt_idx - - ! Top-Right (i+1, j) - q_curr = CMPLX(ing_coarse + ing_step, iing_coarse) - CALL get_or_compute(q_curr, cells(2, n_cells),n_k, - $ sl_in,msing_max,coupling_flag) - - ! Bottom-Left (i, j+1) - q_curr = CMPLX(ing_coarse, iing_coarse+ing_step) - CALL get_or_compute(q_curr, cells(3, n_cells),n_k, - $ sl_in,msing_max,coupling_flag) - - ! Bottom-Right (i+1, j+1) - q_curr = CMPLX(ing_coarse + ing_step, iing_coarse+ - $ ing_step) - CALL get_or_compute(q_curr, cells(4, n_cells),n_k, - $ sl_in,msing_max,coupling_flag) - END IF - END DO + ! Define corners using the pre-calculated indices + cells(1, n_cells) = coarse_indices(i, j) ! Top-Left + cells(2, n_cells) = coarse_indices(i+1, j) ! Top-Right + cells(3, n_cells) = coarse_indices(i, j+1) ! Bot-Left + cells(4, n_cells) = coarse_indices(i+1, j+1) ! Bot-Right + END DO END DO + + DEALLOCATE(coarse_indices) ! Clean up temp array ! --- 3. Refinement Loops --- DO pass = 1, AMR_PASSES @@ -555,15 +551,29 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, INTEGER, INTENT(IN) :: n_k, msing_max INTEGER, INTENT(OUT) :: idx_out LOGICAL, INTENT(IN) :: coupling_flag - INTEGER :: h, curr, ix, iy + INTEGER :: h, curr COMPLEX(r8) :: delta_val + INTEGER(8) :: ix8, iy8, h8 ! 64-bit integers for hash calculation ! 1. Calculate Hash - ix = NINT(REAL(q_in) * HASH_SCALE) - iy = NINT(AIMAG(q_in) * HASH_SCALE) + !ix = NINT(REAL(q_in) * HASH_SCALE) + !iy = NINT(AIMAG(q_in) * HASH_SCALE) ! Simple hash mix - h = MOD(ABS(ix * 73856093 + iy * 19349663), HASH_SZ) + 1 + !h = MOD(ABS(ix * 73856093 + iy * 19349663), HASH_SZ) + 1 + + ! 1. Calculate Hash using 64-bit arithmetic + ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) + iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) + h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), + $ INT(HASH_SZ, 8)) + 1_8 + h = INT(h8) ! Safe to convert back, result is within HASH_SZ + IF (h < 1 .OR. h > HASH_SZ) THEN + WRITE(*,*) "HASH ERROR: h=", h, " q_in=", q_in + WRITE(*,*) " ix=", ix8, " iy=", iy8 + STOP + END IF + ! 2. Check collisions curr = hash_head(h) DO WHILE (curr /= 0) @@ -593,7 +603,7 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, ELSE g_tmp = q_in delta_val = riccati_f(g_tmp) - !delta_val = delta_val - delta_eff + delta_val = delta_val - delta_eff END IF D_store(idx_out) = delta_val ! -------------------------- @@ -603,4 +613,417 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, hash_head(h) = idx_out END SUBROUTINE get_or_compute + SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, + $ scan_width, Q_num, AMR_passes, + $ coupling_flag) + + IMPLICIT NONE + + INTEGER, INTENT(IN) :: n_k, msing_max, Q_num, AMR_passes + REAL(r8), INTENT(IN) :: scan_width + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + + TYPE(amr_cell_type), ALLOCATABLE :: new_cells(:) + INTEGER :: i, j, c, corner, pass + REAL(r8) :: step, x, y + COMPLEX(r8) :: delta_val + LOGICAL :: cross_real, cross_imag + INTEGER :: n_new_cells + INTEGER :: cells_to_refine, cells_kept + + ! --- 1. Clean Initialization --- + WRITE(*,*) '=== AMR v2 Starting ===' + + IF (ALLOCATED(amr_cells)) THEN + WRITE(*,*) 'Deallocating old amr_cells' + DEALLOCATE(amr_cells) + END IF + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + + ALLOCATE(amr_cells(MAX_CELLS)) + ALLOCATE(new_cells(MAX_CELLS)) + + ! Initialize all cells + DO i = 1, MAX_CELLS + amr_cells(i)%Q = (0.0d0, 0.0d0) + amr_cells(i)%D = (0.0d0, 0.0d0) + amr_cells(i)%needs_refine = .FALSE. + new_cells(i)%Q = (0.0d0, 0.0d0) + new_cells(i)%D = (0.0d0, 0.0d0) + new_cells(i)%needs_refine = .FALSE. + END DO + + n_amr_cells = 0 + step = (2.0d0 * scan_width) / DBLE(Q_num - 1) + + WRITE(*,*) 'Scan width:', scan_width + WRITE(*,*) 'Q_num:', Q_num + WRITE(*,*) 'Step size:', step + WRITE(*,*) 'AMR passes:', AMR_passes + + ! --- 2. Build Initial Coarse Grid --- + WRITE(*,*) 'Building initial coarse grid...' + + DO i = 1, Q_num - 1 + DO j = 1, Q_num - 1 + x = -scan_width + DBLE(i-1) * step + y = -scan_width + DBLE(j-1) * step + + n_amr_cells = n_amr_cells + 1 + + IF (n_amr_cells > MAX_CELLS) THEN + WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in init' + STOP + END IF + + ! Define corner coordinates (BL, BR, TL, TR) + amr_cells(n_amr_cells)%Q(1) = CMPLX(x, y, KIND=r8) + amr_cells(n_amr_cells)%Q(2) = CMPLX(x+step, y, KIND=r8) + amr_cells(n_amr_cells)%Q(3) = CMPLX(x, y+step, KIND=r8) + amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, + $ KIND=r8) + + ! Compute Delta at each corner + DO corner = 1, 4 + CALL compute_delta_sub( + $ amr_cells(n_amr_cells)%Q(corner), + $ n_k, sl_in, msing_max, coupling_flag, + $ amr_cells(n_amr_cells)%D(corner)) + END DO + + amr_cells(n_amr_cells)%needs_refine = .FALSE. + END DO + END DO + + WRITE(*,*) 'Initial grid cells:', n_amr_cells + WRITE(*,*) 'Sample cell 1 Q(1):', amr_cells(1)%Q(1) + WRITE(*,*) 'Sample cell 1 D(1):', amr_cells(1)%D(1) + + ! --- 3. Refinement Passes --- + DO pass = 1, AMR_passes + WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, + $ ': Processing ', n_amr_cells, ' cells' + + ! Mark cells that need refinement + cells_to_refine = 0 + DO c = 1, n_amr_cells + CALL check_cell_crossing_sub(amr_cells(c), + $ cross_real, cross_imag) + amr_cells(c)%needs_refine = (cross_real .OR. cross_imag) + IF (amr_cells(c)%needs_refine) THEN + cells_to_refine = cells_to_refine + 1 + END IF + END DO + + WRITE(*,*) ' Cells to refine:', cells_to_refine + + ! Build new cell list + n_new_cells = 0 + cells_kept = 0 + + DO c = 1, n_amr_cells + IF (amr_cells(c)%needs_refine) THEN + ! Subdivide this cell into 4 + CALL subdivide_cell_sub(amr_cells(c), + $ new_cells, n_new_cells, MAX_CELLS, + $ n_k, sl_in, msing_max, coupling_flag) + ELSE + ! Keep cell as-is + n_new_cells = n_new_cells + 1 + IF (n_new_cells > MAX_CELLS) THEN + WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in refine' + STOP + END IF + new_cells(n_new_cells) = amr_cells(c) + cells_kept = cells_kept + 1 + END IF + END DO + + WRITE(*,*) ' Cells kept:', cells_kept + WRITE(*,*) ' New total cells:', n_new_cells + + ! Copy new_cells back to amr_cells + n_amr_cells = n_new_cells + DO c = 1, n_amr_cells + amr_cells(c) = new_cells(c) + END DO + + END DO + + ! --- 4. Flatten to Output Arrays --- + WRITE(*,*) 'Flattening cells to output arrays...' + CALL flatten_cells_to_points_sub(n_amr_cells) + + ! Cleanup + DEALLOCATE(new_cells) + ! Keep amr_cells allocated for potential debugging + + WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts + + RETURN + END SUBROUTINE dispersion_AMR_v2 + + +! ============================================================ + SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, + $ coupling_flag, delta_out) + + IMPLICIT NONE + + COMPLEX(r8), INTENT(IN) :: q_in + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + COMPLEX(r8), INTENT(OUT) :: delta_out + + IF (coupling_flag) THEN + g_tmp = q_in*ifac + delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) + ELSE + g_tmp = q_in*ifac + delta_out = riccati_f(g_tmp) + delta_out = delta_out - delta_eff + END IF + + RETURN + END SUBROUTINE compute_delta_sub + + +! ============================================================ + SUBROUTINE check_cell_crossing_sub(cell, cross_real, cross_imag) + + IMPLICIT NONE + + TYPE(amr_cell_type), INTENT(IN) :: cell + LOGICAL, INTENT(OUT) :: cross_real, cross_imag + + REAL(r8) :: r_vals(4), i_vals(4) + REAL(r8) :: r_min, r_max, i_min, i_max + INTEGER :: k + + ! Extract real and imaginary parts + DO k = 1, 4 + r_vals(k) = REAL(cell%D(k), KIND=r8) + i_vals(k) = AIMAG(cell%D(k)) + END DO + + r_min = MINVAL(r_vals) + r_max = MAXVAL(r_vals) + cross_real = (r_min * r_max <= 0.0d0) + + i_min = MINVAL(i_vals) + i_max = MAXVAL(i_vals) + cross_imag = (i_min * i_max <= 0.0d0) + + RETURN + END SUBROUTINE check_cell_crossing_sub + + +! ============================================================ + SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, + $ max_cells, n_k, sl_in, + $ msing_max, coupling_flag) + + IMPLICIT NONE + + TYPE(amr_cell_type), INTENT(IN) :: parent + TYPE(amr_cell_type), INTENT(INOUT) :: new_cells(*) + INTEGER, INTENT(INOUT) :: n_new + INTEGER, INTENT(IN) :: max_cells + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + + COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr + COMPLEX(r8) :: q_bm, q_tm, q_lm, q_rm, q_mm + COMPLEX(r8) :: d_bm, d_tm, d_lm, d_rm, d_mm + COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr + + ! Extract parent corners (BL=1, BR=2, TL=3, TR=4) + q_bl = parent%Q(1) + q_br = parent%Q(2) + q_tl = parent%Q(3) + q_tr = parent%Q(4) + + d_bl = parent%D(1) + d_br = parent%D(2) + d_tl = parent%D(3) + d_tr = parent%D(4) + + ! Compute midpoint coordinates + q_bm = 0.5d0 * (q_bl + q_br) ! Bottom middle + q_tm = 0.5d0 * (q_tl + q_tr) ! Top middle + q_lm = 0.5d0 * (q_bl + q_tl) ! Left middle + q_rm = 0.5d0 * (q_br + q_tr) ! Right middle + q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) ! Center + + ! Compute Delta at new midpoints (5 new evaluations) + CALL compute_delta_sub(q_bm, n_k, sl_in, msing_max, + $ coupling_flag, d_bm) + CALL compute_delta_sub(q_tm, n_k, sl_in, msing_max, + $ coupling_flag, d_tm) + CALL compute_delta_sub(q_lm, n_k, sl_in, msing_max, + $ coupling_flag, d_lm) + CALL compute_delta_sub(q_rm, n_k, sl_in, msing_max, + $ coupling_flag, d_rm) + CALL compute_delta_sub(q_mm, n_k, sl_in, msing_max, + $ coupling_flag, d_mm) + + ! Check space for 4 new cells + IF (n_new + 4 > max_cells) THEN + WRITE(*,*) 'ERROR: Would exceed MAX_CELLS in subdivide' + STOP + END IF + + ! Child 1: Bottom-Left quadrant (BL, BM, LM, MM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_bl + new_cells(n_new)%Q(2) = q_bm + new_cells(n_new)%Q(3) = q_lm + new_cells(n_new)%Q(4) = q_mm + new_cells(n_new)%D(1) = d_bl + new_cells(n_new)%D(2) = d_bm + new_cells(n_new)%D(3) = d_lm + new_cells(n_new)%D(4) = d_mm + new_cells(n_new)%needs_refine = .FALSE. + + ! Child 2: Bottom-Right quadrant (BM, BR, MM, RM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_bm + new_cells(n_new)%Q(2) = q_br + new_cells(n_new)%Q(3) = q_mm + new_cells(n_new)%Q(4) = q_rm + new_cells(n_new)%D(1) = d_bm + new_cells(n_new)%D(2) = d_br + new_cells(n_new)%D(3) = d_mm + new_cells(n_new)%D(4) = d_rm + new_cells(n_new)%needs_refine = .FALSE. + + ! Child 3: Top-Left quadrant (LM, MM, TL, TM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_lm + new_cells(n_new)%Q(2) = q_mm + new_cells(n_new)%Q(3) = q_tl + new_cells(n_new)%Q(4) = q_tm + new_cells(n_new)%D(1) = d_lm + new_cells(n_new)%D(2) = d_mm + new_cells(n_new)%D(3) = d_tl + new_cells(n_new)%D(4) = d_tm + new_cells(n_new)%needs_refine = .FALSE. + + ! Child 4: Top-Right quadrant (MM, RM, TM, TR) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_mm + new_cells(n_new)%Q(2) = q_rm + new_cells(n_new)%Q(3) = q_tm + new_cells(n_new)%Q(4) = q_tr + new_cells(n_new)%D(1) = d_mm + new_cells(n_new)%D(2) = d_rm + new_cells(n_new)%D(3) = d_tm + new_cells(n_new)%D(4) = d_tr + new_cells(n_new)%needs_refine = .FALSE. + + RETURN + END SUBROUTINE subdivide_cell_sub + + +! ============================================================ + SUBROUTINE flatten_cells_to_points_sub(num_cells) + + IMPLICIT NONE + + INTEGER, INTENT(IN) :: num_cells + + INTEGER :: c, corner, i, j, idx + INTEGER :: n_total_corners + COMPLEX(r8), ALLOCATABLE :: temp_Q(:), temp_D(:) + LOGICAL, ALLOCATABLE :: is_unique(:) + REAL(r8) :: tol + + tol = 1.0d-10 + n_total_corners = num_cells * 4 + + WRITE(*,*) 'Flatten: num_cells =', num_cells + WRITE(*,*) 'Flatten: n_total_corners =', n_total_corners + + IF (num_cells <= 0) THEN + WRITE(*,*) 'ERROR: No cells to flatten' + n_pts = 0 + RETURN + END IF + + ! Allocate temporary arrays + ALLOCATE(temp_Q(n_total_corners)) + ALLOCATE(temp_D(n_total_corners)) + ALLOCATE(is_unique(n_total_corners)) + + ! Initialize + temp_Q = (0.0d0, 0.0d0) + temp_D = (0.0d0, 0.0d0) + is_unique = .TRUE. + + ! Gather all corners from cells + idx = 0 + DO c = 1, num_cells + DO corner = 1, 4 + idx = idx + 1 + temp_Q(idx) = amr_cells(c)%Q(corner) + temp_D(idx) = amr_cells(c)%D(corner) + END DO + END DO + + WRITE(*,*) 'Gathered corners:', idx + + ! Mark duplicate points as non-unique + ! (Simple O(n²) approach - works fine for moderate sizes) + DO i = 1, n_total_corners + IF (.NOT. is_unique(i)) CYCLE + DO j = i + 1, n_total_corners + IF (is_unique(j)) THEN + IF (ABS(temp_Q(j) - temp_Q(i)) < tol) THEN + is_unique(j) = .FALSE. + END IF + END IF + END DO + END DO + + ! Count unique points + n_pts = 0 + DO i = 1, n_total_corners + IF (is_unique(i)) n_pts = n_pts + 1 + END DO + + WRITE(*,*) 'Unique points found:', n_pts + + IF (n_pts <= 0) THEN + WRITE(*,*) 'ERROR: No unique points found' + DEALLOCATE(temp_Q, temp_D, is_unique) + RETURN + END IF + + ! Allocate output arrays + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + ALLOCATE(Q_store(n_pts)) + ALLOCATE(D_store(n_pts)) + + ! Copy unique points to output + idx = 0 + DO i = 1, n_total_corners + IF (is_unique(i)) THEN + idx = idx + 1 + Q_store(idx) = temp_Q(i) + D_store(idx) = temp_D(i) + END IF + END DO + + WRITE(*,*) 'Copied to output:', idx + + ! Cleanup temporaries + DEALLOCATE(temp_Q, temp_D, is_unique) + + RETURN + END SUBROUTINE flatten_cells_to_points_sub END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 334ebc530..3711e695c 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -25,9 +25,19 @@ MODULE sglobal_mod REAL(r8), PARAMETER :: HASH_SCALE = 1.0d5 ! Scaling factor for integer hashing INTEGER, ALLOCATABLE :: hash_head(:) ! Hash bucket heads INTEGER, ALLOCATABLE :: hash_next(:) ! Linked list next pointers + + ! Replace hash-based storage with cell-based storage + INTEGER, PARAMETER :: MAX_CELLS = 500000 + TYPE :: amr_cell_type + COMPLEX(r8) :: Q(4) ! Corner Q values (TL, TR, BL, BR) + COMPLEX(r8) :: D(4) ! Corner Delta values + LOGICAL :: needs_refine ! Flag for refinement + END TYPE amr_cell_type + TYPE(amr_cell_type), ALLOCATABLE :: amr_cells(:) + INTEGER :: n_amr_cells + ! For output: flattened unique points + COMPLEX(r8), ALLOCATABLE :: Q_store(:), D_store(:) INTEGER :: n_pts - COMPLEX(r8), ALLOCATABLE :: Q_store(:) ! Stores Q coordinates - COMPLEX(r8), ALLOCATABLE :: D_store(:) ! Stores Result Delta TYPE result_type REAL(r8), ALLOCATABLE :: inQs(:), iinQs(:), diff --git a/slayer/slayer.f b/slayer/slayer.f index 3f398681a..e21065ec3 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -539,9 +539,10 @@ PROGRAM slayer IF (amr_flag) THEN - CALL dispersion_AMR(n_k,sl_in,msing_max,scan_width, + CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) WRITE(*,*)"Q_store(10) = ",Q_store(10) + WRITE(*,*)"D_store(10) = ",D_store(10) WRITE(*,*)"n_pts = ",n_pts @@ -553,22 +554,27 @@ PROGRAM slayer ALLOCATE(all_deltas_out(k)%real_deltas(n_pts), $ all_deltas_out(k)%imag_deltas(n_pts)) + WRITE(*,*)"Allocated all_deltas_out" + DO i = 1, n_pts all_deltas_out(k)%inQs(i) = REAL(Q_store(i)) IF (fitz_flag) THEN - all_deltas_out(k)%iinQs(i) = AIMAG(Q_store(i)) + all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) ELSE all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) END IF all_deltas_out(k)%real_deltas(i) = REAL(D_store(i)) all_deltas_out(k)%imag_deltas(i) = AIMAG(D_store(i)) END DO + + WRITE(*,*)"all_deltas_out(1)%real_deltas(10): ", + $ all_deltas_out(1)%real_deltas(10) ! Clean up temporary AMR memory - DEALLOCATE(Q_store, D_store, hash_head, hash_next) + DEALLOCATE(Q_store, D_store)!, hash_head, hash_next) END IF - + WRITE(*,*)"Exited if_AMR" IF ((stabscan_flag)) THEN ! was .AND. (k == 2) WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// From 2a73a4661eb8868eab305b40575c8dd01058e54c Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 29 Jan 2026 20:25:10 -0500 Subject: [PATCH 057/305] WIP: fixes for coupled AMR scans --- input/slayer.in | 1 + slayer/gslayer.f | 9 ++- slayer/slayer.f | 133 +++++++++++++++++++++++++++++------------ slayer/slayer_netcdf.f | 34 ++++++----- 4 files changed, 118 insertions(+), 59 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 1889bca87..cd5194a90 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -49,6 +49,7 @@ knum=100 ! resolution for 2d scan along with the other. Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes scan_width=1.5 ! stability scan width for Re(Q),Im(Q) scan + AMR_passes=4 msing_max=2 ! number of surfaces to include in coupled tearing growth rate calculation dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 06dfae536..d9ebff46c 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -179,17 +179,18 @@ END SUBROUTINE gpec_slayer c Subprogram 2. output_gamma c Take SLAYER input and output dicts, send to netCDF subroutine c----------------------------------------------------------------------- - SUBROUTINE output_gamma(est_gamma_flag,sl_in,sl_out, + SUBROUTINE output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, $ all_deltas_out) ! Declarations (include necessary type declarations from original code) LOGICAL, INTENT(IN) :: est_gamma_flag + INTEGER, INTENT(IN) :: m_AMR TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out TYPE(deltas_outputs_type), INTENT(IN) :: $ all_deltas_out(SIZE(sl_in%qval_arr)) - CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),est_gamma_flag, + CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),m_AMR,est_gamma_flag, $ sl_in,sl_out,all_deltas_out) END SUBROUTINE output_gamma @@ -698,9 +699,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, END DO WRITE(*,*) 'Initial grid cells:', n_amr_cells - WRITE(*,*) 'Sample cell 1 Q(1):', amr_cells(1)%Q(1) - WRITE(*,*) 'Sample cell 1 D(1):', amr_cells(1)%D(1) - + ! --- 3. Refinement Passes --- DO pass = 1, AMR_passes WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, diff --git a/slayer/slayer.f b/slayer/slayer.f index e21065ec3..37566a191 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -60,28 +60,28 @@ PROGRAM slayer COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas ! AMR DECLARATIONS - INTEGER, PARAMETER :: AMR_passes = 4 ! Number of refinement levels (User Set) + INTEGER :: AMR_passes, m_AMR ! Number of refinement levels TYPE(slayer_inputs_type) :: sl_in TYPE(slayer_outputs_type) :: sl_out TYPE(deltas_outputs_type), ALLOCATABLE :: all_deltas_out(:) NAMELIST/slayer_input/input_flag,infile, - $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, - $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_p_prof, - $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,Q0,delta_prime,delta_n_p,ingamma + $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, + $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_p_prof, + $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, + $ inds,intau,Q0,delta_prime,delta_n_p,ingamma NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, - $ msing_max,dc_type,read_eq,fitz_flag,coupling_flag, - $ QPscan_flag,Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag, - $ otscan_flag,ntscan_flag,nbtscan_flag,parflow_flag, - $ peohmonly_flag,Pe_flag,layfac + $ AMR_passes,msing_max,dc_type,read_eq,fitz_flag,coupling_flag, + $ QPscan_flag,Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag, + $ otscan_flag,ntscan_flag,nbtscan_flag,parflow_flag, + $ peohmonly_flag,Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, - $ est_gamma_flag,match_gamma_flag,stability_flag, - $ stabscan_flag,coupled_stabscan_flag,amr_flag,br_th_flag, - $ bal_flag + $ est_gamma_flag,match_gamma_flag,stability_flag, + $ stabscan_flag,coupled_stabscan_flag,amr_flag,br_th_flag, + $ bal_flag NAMELIST/slayer_diagnose/riccati_out,riccatiscan_flag, - $ params_check + $ params_check c----------------------------------------------------------------------- c set initial values. c----------------------------------------------------------------------- @@ -128,6 +128,7 @@ PROGRAM slayer knum=100 ! resolution for 2d scan alont with the other. Q_num=100 ! resolution for stab. scan along Re(Q) axis scan_width = 2.0 + AMR_passes = 4 msing_max = 2 in_unit=1 out_unit=2 @@ -412,7 +413,7 @@ PROGRAM slayer IF (.NOT. (match_gamma_flag)) THEN sl_out%gamma_sol_arr = (/0./) - CALL output_gamma(est_gamma_flag,sl_in,sl_out, + CALL output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, $ all_deltas_out) END IF ENDIF @@ -495,11 +496,19 @@ PROGRAM slayer END IF END IF - IF (amr_flag) THEN - !IF (ALLOCATED(all_deltas_out)) DEALLOCATE(all_deltas_out) - ALLOCATE(all_deltas_out(n_k)) + IF (AMR_flag .AND. .NOT. coupling_flag) THEN + ALLOCATE(all_deltas_out(n_k)) + ELSEIF (AMR_flag .AND. coupling_flag) THEN + ALLOCATE(all_deltas_out(1)) END IF + IF (AMR_flag) THEN + m_AMR = 1 + ELSE + m_AMR = MIN(n_k,msing_max) + END IF + + WRITE(*,*),"Rational q domain: ",sl_in%qval_arr DO k=1,MIN(n_k,msing_max) WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', @@ -514,19 +523,16 @@ PROGRAM slayer tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) - WRITE(*,*)"slayer.f Q_e: ",Q_e - WRITE(*,*)"slayer.f Q_i: ",Q_i - WRITE(*,*)"slayer.f P_perp: ",P_perp - WRITE(*,*)"slayer.f P_tor: ",P_tor - WRITE(*,*)"slayer.f tau: ",tau - WRITE(*,*)"slayer.f D_norm: ",D_norm - WRITE(*,*)"slayer.f tauk: ",tauk - - WRITE(*,*)"slayer.f iota_e: ",iota_e - WRITE(*,*)"slayer.f D_prime: ",sl_in%Re_dp_arr(k) - WRITE(*,*)"slayer.f D_prime: ",sl_in%Re_dp_arr(k) - WRITE(*,*)"slayer.f D_crit: ",sl_in%d_crit_arr(k) - WRITE(*,*),"sl_in%qval_arr: ",sl_in%qval_arr + WRITE(*,*)"Q_e: ",Q_e + WRITE(*,*)"Q_i: ",Q_i + WRITE(*,*)"P_perp: ",P_perp + WRITE(*,*)"P_tor: ",P_tor + WRITE(*,*)"tau: ",tau + WRITE(*,*)"D_norm: ",D_norm + WRITE(*,*)"tauk: ",tauk + WRITE(*,*)"iota_e: ",iota_e + WRITE(*,*)"Delta_prime: ",sl_in%Re_dp_arr(k) + WRITE(*,*)"Delta_crit: ",sl_in%d_crit_arr(k) ! Calculate (Deltaprime - d_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - @@ -537,17 +543,23 @@ PROGRAM slayer c ! Fill gamma_sol_arr with 0's, will by used by python root finding sl_out%gamma_sol_arr(k) = 0.0 - IF (amr_flag) THEN + IF (AMR_flag .AND. .NOT. coupling_flag) THEN + WRITE(*,'(A,I0,A)') 'Calling uncoupled AMR scan on q = ', + $ sl_in%qval_arr(k),' rational surface:' CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) - WRITE(*,*)"Q_store(10) = ",Q_store(10) - WRITE(*,*)"D_store(10) = ",D_store(10) - WRITE(*,*)"n_pts = ",n_pts + IF (ALLOCATED(all_deltas_out(k)%inQs)) + $ DEALLOCATE(all_deltas_out(k)%inQs) + IF (ALLOCATED(all_deltas_out(k)%iinQs)) + $ DEALLOCATE(all_deltas_out(k)%iinQs) + IF (ALLOCATED(all_deltas_out(k)%real_deltas)) + $ DEALLOCATE(all_deltas_out(k)%real_deltas) + IF (ALLOCATED(all_deltas_out(k)%imag_deltas)) + $ DEALLOCATE(all_deltas_out(k)%imag_deltas) - ! --- 4. Export to Output Arrays --- - ! Flatten the unique points store into the requested 1D arrays + ! Flatten the unique points store into 1D arrays ALLOCATE(all_deltas_out(k)%inQs(n_pts), $ all_deltas_out(k)%iinQs(n_pts)) ! Assumes you also have real_deltas/imag_deltas declared as 1D @@ -574,7 +586,7 @@ PROGRAM slayer DEALLOCATE(Q_store, D_store)!, hash_head, hash_next) END IF - WRITE(*,*)"Exited if_AMR" + IF ((stabscan_flag)) THEN ! was .AND. (k == 2) WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// @@ -640,6 +652,50 @@ PROGRAM slayer sl_out%dels_db_arr = (/ 0. /) END IF + IF (AMR_flag .AND. coupling_flag) THEN + + WRITE(*,*)"Calling coupled AMR scan" + + CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, + $ Q_num,AMR_passes,coupling_flag) + + WRITE(*,*)"Successfully exited coupled AMR scan" + + IF (ALLOCATED(all_deltas_out(1)%inQs)) + $ DEALLOCATE(all_deltas_out(1)%inQs) + IF (ALLOCATED(all_deltas_out(1)%iinQs)) + $ DEALLOCATE(all_deltas_out(1)%iinQs) + IF (ALLOCATED(all_deltas_out(1)%real_deltas)) + $ DEALLOCATE(all_deltas_out(1)%real_deltas) + IF (ALLOCATED(all_deltas_out(1)%imag_deltas)) + $ DEALLOCATE(all_deltas_out(1)%imag_deltas) + + WRITE(*,*)"Successfully checked array allocation" + + ALLOCATE(all_deltas_out(1)%inQs(n_pts), + $ all_deltas_out(1)%iinQs(n_pts)) + ALLOCATE(all_deltas_out(1)%real_deltas(n_pts), + $ all_deltas_out(1)%imag_deltas(n_pts)) + + WRITE(*,*)"Successfully allocated all_deltas_out subarrays" + + DO i = 1, n_pts + all_deltas_out(1)%inQs(i) = REAL(Q_store(i)) + IF (fitz_flag) THEN + all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) + ELSE + all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) + END IF + all_deltas_out(1)%real_deltas(i) = REAL(D_store(i)) + all_deltas_out(1)%imag_deltas(i) = AIMAG(D_store(i)) + END DO + + ! Clean up temporary AMR memory + DEALLOCATE(Q_store, D_store)!, hash_head, hash_next) + WRITE(*,*)"Successfully deallocated Q_store and D_store" + + END IF + IF (coupled_stabscan_flag) THEN WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// @@ -685,7 +741,8 @@ PROGRAM slayer DEALLOCATE(inQs,iinQs,deltas) END IF - CALL output_gamma(est_gamma_flag,sl_in,sl_out,all_deltas_out) + CALL output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, + $ all_deltas_out) stop ENDIF c----------------------------------------------------------------------- diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index c597c7311..45fdfef89 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -48,18 +48,18 @@ END SUBROUTINE sl_check c ----------------------------------------------------------------------- c declarations. c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, + SUBROUTINE slayer_netcdf_out(msing,m_AMR,est_gamma_flag, $ sl_in,sl_out,all_deltas_out) - INTEGER, INTENT(IN) :: msing + INTEGER, INTENT(IN) :: msing,m_AMR LOGICAL, INTENT(IN) :: est_gamma_flag TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out - TYPE(deltas_outputs_type), INTENT(IN) :: all_deltas_out(msing) + TYPE(deltas_outputs_type), INTENT(IN) :: all_deltas_out(m_AMR) INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,p_perp_id,S_id, - $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id, + $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,nAMR_dim, $ qsing_id,qc_id,p_tor_id INTEGER :: run, run_dimid, point_dimid, varids(4) @@ -100,17 +100,17 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, ! 1. Find the Maximum AMR Grid Size across all surfaces max_pts_all = 0 - DO s = 1, msing - IF (ALLOCATED(all_deltas_out(s)%inQs)) THEN + IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN + DO s = 1, m_AMR max_pts_all = MAX(max_pts_all,SIZE(all_deltas_out(s)%inQs)) - END IF END DO - + END IF + ! 2. Allocate Rectangular Buffers (Points, Surfaces, Re/Im) ! Shape: (Max_Points, Number_Surfaces, 2) - ALLOCATE(buffer_q(max_pts_all, msing, 2)) - ALLOCATE(buffer_d(max_pts_all, msing, 2)) - ALLOCATE(n_pts_arr(msing)) + ALLOCATE(buffer_q(max_pts_all, m_AMR, 2)) + ALLOCATE(buffer_d(max_pts_all, m_AMR, 2)) + ALLOCATE(n_pts_arr(m_AMR)) ! Initialize with Fill Value (so unused space is ignored by plotters) buffer_q = 0.0 @@ -118,8 +118,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, n_pts_arr = 0 ! 3. Flatten the Ragged Data into the Buffers - DO s = 1, msing - IF (ALLOCATED(all_deltas_out(s)%inQs)) THEN + IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN + DO s = 1, m_AMR n_curr = SIZE(all_deltas_out(s)%inQs) n_pts_arr(s) = n_curr @@ -134,8 +134,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, buffer_d(1:n_curr,s,1)=all_deltas_out(s)%real_deltas(1:n_curr) ! Imag part -> Index 2 buffer_d(1:n_curr,s,2)=all_deltas_out(s)%imag_deltas(1:n_curr) - END IF END DO + END IF c ----------------------------------------------------------------------- c define global file attributes c ----------------------------------------------------------------------- @@ -157,6 +157,8 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, !CALL check( nf90_def_var(ncid,"r",nf90_int,r_dim,r_id)) IF(msing>0)THEN CALL sl_check( nf90_def_dim(ncid,"r",msing,qsing_dim) ) !r_dim = q_rational + CALL sl_check( nf90_def_dim(ncid,"r_AMR",m_AMR, + $ nAMR_dim) ) !r_dim = q_rational CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) CALL sl_check( nf90_def_var(ncid,"r",nf90_int, $ qsing_dim,qsing_id)) @@ -211,9 +213,9 @@ SUBROUTINE slayer_netcdf_out(msing,est_gamma_flag, $ (/qsing_dim/), var_npts_id)) ! Note: Dimensions order is (pts, surf, cplx) CALL sl_check(nf90_def_var(ncid, 'Q_AMR', NF90_DOUBLE, - $ (/dim_pts_id, qsing_dim, i_dim/), var_q_id)) + $ (/dim_pts_id, nAMR_dim, i_dim/), var_q_id)) CALL sl_check(nf90_def_var(ncid, 'Deltas_AMR', NF90_DOUBLE, - $ (/dim_pts_id, qsing_dim, i_dim/), var_d_id)) + $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id)) ! end definitions CALL sl_check( nf90_enddef(ncid) ) From 4849e008f9ebfd95aad23f335ea86c15a579ebb4 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 13 Feb 2026 23:56:22 -0500 Subject: [PATCH 058/305] WIP: big cleanup, not compiling yet --- slayer/delta.f | 327 +++++++++----- slayer/gslayer.f | 936 +++++++++++++++++++++++------------------ slayer/layerinputs.f | 713 ++++++++++++++++++------------- slayer/params.f | 342 +++++++++------ slayer/sglobal.f | 244 ++++++++--- slayer/slayer.f | 647 +++++++++++++++++----------- slayer/slayer_netcdf.f | 587 ++++++++++++++++---------- 7 files changed, 2363 insertions(+), 1433 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index de3041d47..aff31a2c8 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -1,32 +1,83 @@ MODULE delta_mod +c----------------------------------------------------------------------- +c delta_mod: Riccati-based tearing-mode layer Delta solvers. +c +c Contains three Riccati formulations: +c riccati - standard formulation (non-stiff, lsode mf=10) +c riccati_del_s - del_s formulation (stiff, lsode mf=21) +c riccati_f - Fitzpatrick P_perp/P_tor formulation (mf=21) +c +c Each solver integrates a Riccati ODE for W(x) from a large-|x| +c asymptotic boundary condition inward to x ~ 0, then extracts +c Delta = pi / W'(0). +c +c Associated ODE subroutines (w_der, w_der_del_s, w_der_f) and +c Jacobian subroutines (jac_del_s, jac_f) follow below. +c +c Module-level flags: +c riccati_out - write W(x) profile to binary + text file +c parflow_flag - include parallel electron flow terms in w_der +c PeOhmOnly_flag - retain only Pe-Ohm coupling in w_der +c----------------------------------------------------------------------- USE sglobal_mod IMPLICIT NONE - LOGICAL :: riccati_out,parflow_flag,PeOhmOnly_flag +c --- module-level control flags + LOGICAL :: riccati_out ! write W(x) profile to binary + text + LOGICAL :: parflow_flag ! enable parallel-flow terms in w_der + LOGICAL :: PeOhmOnly_flag ! Pe-Ohm-only coupling in w_der CONTAINS c----------------------------------------------------------------------- -c calculate delta based on riccati w_der formulation. +c riccati: compute Delta via Riccati integration of w_der. +c +c Integrates W(x) from x_start inward to x_min using lsode +c (non-stiff, mf=10). Optionally applies the layfac singularity +c guard when Q is near Q_e. Returns Delta = pi / W'(x_min). +c +c If riccati_out = .TRUE., writes the W(x) profile to +c slayer_riccati_profile_n.{bin,out}. +c +c BUG FLAG 1: xintv and xfac are declared but never used. +c Remove them. c----------------------------------------------------------------------- FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, $ iinQ,inx,iny) - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds - REAL(r8),INTENT(IN) :: intau - REAL(r8),INTENT(IN),OPTIONAL :: iinQ,inx - COMPLEX(r8), INTENT(IN), OPTIONAL :: iny +c --- input arguments: physical parameters for this surface + REAL(r8),INTENT(IN) :: inQ ! real part of Q + REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq + REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq + REAL(r8),INTENT(IN) :: inpr ! Prandtl number + REAL(r8),INTENT(IN) :: inpe ! electron Prandtl number + REAL(r8),INTENT(IN) :: inc_beta ! c_beta parameter + REAL(r8),INTENT(IN) :: inds ! magnetic shear ds + REAL(r8),INTENT(IN) :: intau ! ion-to-electron temp ratio +c --- optional arguments + REAL(r8),INTENT(IN),OPTIONAL :: iinQ ! imaginary part of Q + REAL(r8),INTENT(IN),OPTIONAL :: inx ! override starting x + COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W +c --- function result COMPLEX(r8) :: riccati - INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - - REAL(r8) :: xintv,x,xout,rtol,jac,xmin - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy - - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork - REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER :: neq ! number of equations (=2) + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER :: liw,lrw ! lsode work array sizes + REAL(r8) :: x,xout ! current and target x + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance + REAL(r8) :: jac ! dummy Jacobian (unused for mf=10) +c --- work arrays + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy ! W and dW + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work + REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work + +c --- copy input arguments to module-level globals for w_der Q=inQ IF(present(iinQ)) Q=inQ+ifac*iinQ Q_e=inQ_e @@ -37,40 +88,43 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, ds=inds tau=intau +c --- singularity guard: displace Q away from Q_e when too close IF ((layfac>0).AND.(ABS(Q-Q_e) 0 when Q -> Q_e (see layfac guard above). CALL w_der(neq,x,y,dy) riccati=pi/dy(1) DEALLOCATE(atol,y,dy,iwork,rwork) END FUNCTION riccati c----------------------------------------------------------------------- -c calculate delta based on riccati w_der formulation. +c riccati_del_s: compute Delta via the del_s Riccati formulation. +c +c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian +c (jac_del_s). Integrates W(q) from large q inward to q_min. +c Returns Delta = -(pi / sqrt(1+1/tau)) * W'(q_min). +c +c BUG FLAG 3: arguments inQ, inc_beta, ind_beta, intau are never +c used to set their module-level counterparts (Q, c_beta, d_beta, +c tau). Either add assignments (e.g. tau=intau) or remove the +c unused arguments if the caller sets them beforehand. +c BUG FLAG 4: variables y, dy, xfac, xintv, ml, mu, nrpd are +c declared but never used -- remove them. c----------------------------------------------------------------------- FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, $ intau,inx,iny) - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta - REAL(r8),INTENT(IN) :: intau - REAL(r8),INTENT(IN),OPTIONAL :: inx - COMPLEX(r8), INTENT(IN), OPTIONAL :: iny +c --- input arguments + REAL(r8),INTENT(IN) :: inQ ! (UNUSED -- see BUG FLAG 3) + REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq + REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq + REAL(r8),INTENT(IN) :: inpr ! mapped to P_perp (see below) + REAL(r8),INTENT(IN) :: inc_beta ! (UNUSED -- see BUG FLAG 3) + REAL(r8),INTENT(IN) :: ind_beta ! (UNUSED -- see BUG FLAG 3) + REAL(r8),INTENT(IN) :: intau ! (UNUSED -- see BUG FLAG 3) +c --- optional arguments +c BUG FLAG 5: inx is declared OPTIONAL but my_q=inx is accessed +c unconditionally. If inx is ever absent, this will crash. +c Either make inx required or add IF(present(inx)) guard. + REAL(r8),INTENT(IN),OPTIONAL :: inx ! starting q for integration + COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W +c --- function result COMPLEX(r8) :: riccati_del_s - INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - INTEGER :: ml = 0, mu = 0, nrpd = 1 - REAL(r8) :: xintv,x,xout,rtol,jac,xmin,my_q,P_hat,alpha - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq,y,dy - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork - REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER :: neq ! number of equations (=2) + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER :: liw,lrw ! lsode work array sizes + REAL(r8) :: x ! secondary x variable (set from inx) + REAL(r8) :: xout ! target integration endpoint + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance + REAL(r8) :: jac ! dummy (overridden by jac_del_s) + REAL(r8) :: my_q ! integration variable (large -> small) + REAL(r8) :: P_hat ! normalized P_perp + REAL(r8) :: alpha ! boundary condition coefficient +c --- work arrays + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq ! W and dW/dq + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work + REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work + +c --- configure lsode: stiff BDF method with user Jacobian (mf=21) neq = 2 itol = 2 rtol = 1e-10 @@ -129,40 +219,40 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, atol(:) = 1e-10 itask = 2 istate = 1 - iopt = 0 - mf = 21 !21 IS STIFF WITH USER-SPECIFIED JACOBIAN, 10 iS NON STIFF + iopt = 1 ! enable optional inputs (iwork(6)) + mf = 21 ! stiff, user-supplied Jacobian (jac_del_s) liw = 20*2 - lrw = 22+9*neq+neq**2 !just (22+16*neq) for mf=10 - ALLOCATE(iwork(liw+neq),rwork(lrw)) ! just iwork(liw) for mf=10 + lrw = 22+9*neq+neq**2 ! stiff work array size + ALLOCATE(iwork(liw+neq),rwork(lrw)) -! MXSTEP? - iopt = 1 +c --- set maximum internal steps iwork=0 - iwork(6)=50000 ! maximum # of steps per call, e.g. 50000 + iwork(6)=50000 ! MXSTEP: max internal steps per call rwork=0 -! x=10.0*(1.0+log10(Q/pr)) - my_q=inx ! "starting backwards integration at large q" +c --- set starting integration point + my_q=inx ! start backwards integration at large q xmin=1e-5 IF(present(inx)) x=inx xout=xmin - !y(1)=-c_beta/sqrt((1+tau))/ds*x**2.0 ! it was (1+tau*ds). To be updated. - - P_hat = P_perp / D_norm**6.0 ! P_perp, 0.377 for Pperp_hat benchmark - +c --- copy input arguments to module-level globals for w_der_del_s Q_e = inQ_e Q_i = inQ_i P_perp = inpr - alpha = (P_hat/(1+1/tau))**0.5 ! this is actually tau', we need tau - W(1) = -alpha*my_q**2 - 0.5 +c --- BUG FLAG 6: P_hat was computed BEFORE P_perp=inpr, so it used +c the stale module-level P_perp. Now moved after assignment. + P_hat = P_perp / D_norm**6.0 +c --- asymptotic boundary condition at large q + alpha = (P_hat/(1+1/tau))**0.5 + W(1) = -alpha*my_q**2 - 0.5 IF(present(iny)) W(1)=iny -! y(1)=0.5-ifac*10.0 -! WRITE(*,*)y(1) +c --- integrate W(q) from q_start inward to q_min via lsode IF (riccati_out) THEN +c profile output: step-by-step integration with file writes istep = 1 itask = 2 OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// @@ -182,24 +272,22 @@ FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, CLOSE(bin_unit) CLOSE(out2_unit) ELSE +c single-shot integration to q_min istep = 1 itask = 1 CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) - ENDIF - ! w=0 when Q=Q_e. Why? - +c --- extract Delta from final W derivative at q_min CALL w_der_del_s(neq,my_q,W,dW_dq) - riccati_del_s=-( pi/((1+1/tau)**0.5) )*dW_dq(1) DEALLOCATE(atol,W,dW_dq,iwork,rwork) END FUNCTION riccati_del_s c----------------------------------------------------------------------- -c jacobian for riccati_del_s() -c------------------------------------------- ---------------------------- +c jacobian for riccati_del_s(): pd = dF/dW for stiff lsode. +c----------------------------------------------------------------------- SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) INTEGER, INTENT(IN) :: neq, ml, mu, nrpd REAL(r8), INTENT(IN) :: my_q @@ -208,7 +296,9 @@ SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) pd(1,1) = 1.0/my_q - 2.0d0*W(1)/my_q END SUBROUTINE jac_del_s c----------------------------------------------------------------------- -c W derivative for riccati_del_s() +c w_der_del_s: ODE right-hand side dW/dq for riccati_del_s. +c Implements the del_s dispersion relation using normalised +c quantities Q_hat, P_perp_hat, P_tor_hat. c----------------------------------------------------------------------- SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) @@ -219,34 +309,72 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat COMPLEX(r8) :: E,F - !Q_hat = Q / ds**4 - Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 ! Q_star = Q_e * (1+tau), 2.4e-02 for benchmark - P_perp_hat = P_perp / D_norm**6.0 ! 0.377 for benchmark - P_tor_hat = P_perp / D_norm**6.0 ! 1.15 for benchmark +c --- normalise physical quantities + Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 + P_perp_hat = P_perp / D_norm**6.0 +c BUG FLAG 7: P_tor_hat is assigned from P_perp, not P_tor. +c Benchmark values differ (P_perp_hat=0.377, P_tor_hat=1.15), +c suggesting this should be: P_tor_hat = P_tor / D_norm**6.0 + P_tor_hat = P_perp / D_norm**6.0 +c --- build the E and F dispersion coefficients E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ - $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) ! P_tor = P_perp + $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 - !dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x - dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F !p*D = my_q +c --- Riccati ODE: dW/dq = W/q - W^2/q + q*E/F + dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F RETURN END SUBROUTINE w_der_del_s c----------------------------------------------------------------------- -c calculate delta based on Fitzpatrick P_perp and P_tor formulation. +c riccati_f: compute Delta via Fitzpatrick P_perp / P_tor +c Riccati formulation. +c +c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian +c (jac_f). Boundary conditions are set analytically from the +c large-p asymptotic behaviour; the branch depends on whether +c D_norm^2 exceeds iota_e * P_perp / P_tor^(2/3). +c +c BUG FLAG 8: argument tmp_g (COMPLEX) is never used in the +c function body. All references use the module-level variable +c g_tmp instead. The caller (slayer.f:785) passes g_tmp as +c tmp_g, so in practice the values match -- but tmp_g is +c redundant. Likely needs: g_tmp = tmp_g at the top, or +c remove the argument and rely on the module variable. +c BUG FLAG 9: variables xintv, xfac, y, dy, ck_1, ck_2, ml, mu, +c nrpd, alpha are declared but never used -- remove them. +c Optional argument inx is also never referenced in the body. c----------------------------------------------------------------------- FUNCTION riccati_f(tmp_g,inx) - COMPLEX(r8), INTENT(IN) :: tmp_g - REAL(r8),INTENT(IN),OPTIONAL :: inx - COMPLEX(r8) :: riccati_f - INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - INTEGER :: ml = 0, mu = 0, nrpd = 1 - REAL(r8) :: xintv,x,xout,rtol,jac,xmin,my_p,alpha,bk - COMPLEX(r8) :: ak,ck_1,ck_2,ck,xk,W_bound - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dWdp,y,dy - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork - REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork +c --- input arguments + COMPLEX(r8),INTENT(IN) :: tmp_g ! growth rate (UNUSED -- BUG FLAG 8) + REAL(r8),INTENT(IN),OPTIONAL :: inx ! (UNUSED -- BUG FLAG 9) +c --- function result + COMPLEX(r8) :: riccati_f +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER :: neq ! number of equations (=2) + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER :: liw,lrw ! lsode work array sizes + REAL(r8) :: xout ! target integration endpoint + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance + REAL(r8) :: jac ! dummy (overridden by jac_f) + REAL(r8) :: my_p ! integration variable p (large -> small) + REAL(r8) :: bk ! asymptotic coefficient b_k +c --- boundary-condition intermediates + COMPLEX(r8) :: ak ! asymptotic coefficient a_k + COMPLEX(r8) :: ck ! asymptotic coefficient c_k + COMPLEX(r8) :: xk ! asymptotic coefficient x_k + COMPLEX(r8) :: W_bound ! boundary value for W(p_start) +c --- work arrays + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dWdp ! W and dW/dp + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work + REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work + +c --- configure lsode: stiff BDF method with user Jacobian (mf=21) neq = 2 itol = 2 rtol = 1e-10 @@ -254,21 +382,24 @@ FUNCTION riccati_f(tmp_g,inx) atol(:) = 1e-10 itask = 2 istate = 1 - iopt = 0 - mf = 21 !21 IS STIFF WITH USER-SPECIFIED JACOBIAN, 10 iS NON STIFF + iopt = 1 ! enable optional inputs (iwork(6)) + mf = 21 ! stiff, user-supplied Jacobian (jac_f) liw = 20*2 - lrw = 22+9*neq+neq**2 !just (22+16*neq) for mf=10 - ALLOCATE(iwork(liw+neq),rwork(lrw)) ! just iwork(liw) for mf=10 + lrw = 22+9*neq+neq**2 ! stiff work array size + ALLOCATE(iwork(liw+neq),rwork(lrw)) - iopt = 1 +c --- set maximum internal steps iwork=0 - iwork(6)=50000 ! maximum # of steps per call, e.g. 50000 + iwork(6)=50000 ! MXSTEP: max internal steps per call rwork=0 xmin=1e-6 xout=xmin - ! Solve for p and W boundary conditions +c --- compute starting p and W boundary condition +c Branch on asymptotic regime: D_norm^2 vs iota_e*P_perp/P_tor^(2/3) +c --- branch 1: D_norm^2 > iota_e * P_perp / P_tor^(2/3) +c large-D_norm regime: p scales with (P_tor*D_norm^2/(iota_e*...)) IF ((D_norm**2.0) > ((iota_e*P_perp)/(P_tor**(2.0/3.0)))) THEN my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25 IF (my_p < 6.0) THEN @@ -286,6 +417,8 @@ FUNCTION riccati_f(tmp_g,inx) W_bound = xk - SQRT(bk)*my_p ELSE +c --- branch 2: D_norm^2 <= iota_e * P_perp / P_tor^(2/3) +c small-D_norm regime: p scales with 1/P_tor^(1/6) my_p = 1.0/(P_tor**(1.0/6.0)) IF (my_p < 6.0) THEN my_p = 6.0 @@ -301,7 +434,9 @@ FUNCTION riccati_f(tmp_g,inx) W(1) = W_bound +c --- integrate W(p) from p_start inward to p_min via lsode IF (riccati_out) THEN +c profile output: step-by-step integration with file writes istep = 1 itask = 2 OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// @@ -322,22 +457,22 @@ FUNCTION riccati_f(tmp_g,inx) CLOSE(bin_unit) CLOSE(out2_unit) ELSE +c single-shot integration to p_min istep = 1 itask = 1 CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) ENDIF +c --- extract Delta from final W derivative at p_min CALL w_der_f(neq,my_p,W,dWdp) - - !riccati_f = pi * my_p / (dWdp(1) + 1) riccati_f = pi / dWdp(1) DEALLOCATE(atol,W,dWdp,iwork,rwork) END FUNCTION riccati_f c----------------------------------------------------------------------- -c jacobian for riccati_f() -c------------------------------------------- ---------------------------- +c jacobian for riccati_f(): pd = dF/dW for stiff lsode. +c----------------------------------------------------------------------- SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) INTEGER, INTENT(IN) :: neq, ml, mu, nrpd REAL(r8), INTENT(IN) :: my_p @@ -351,7 +486,9 @@ SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) pd(1,1) = (-fA_p/my_p) - (2.0*W(1))/my_p END SUBROUTINE jac_f c----------------------------------------------------------------------- -c W derivative for riccati_f() +c w_der_f: ODE right-hand side dW/dp for riccati_f. +c Implements the Fitzpatrick P_perp / P_tor dispersion relation. +c Coefficients fA, fB, fC are evaluated at the current p. c----------------------------------------------------------------------- SUBROUTINE w_der_f(neq,my_p,W,dWdp) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index d9ebff46c..edef6d8d9 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -1,20 +1,47 @@ MODULE gslayer_mod +c----------------------------------------------------------------------- +c gslayer_mod: SLAYER driver routines and AMR dispersion scanners. +c +c Subprograms: +c 1. gpec_slayer - single-surface delta/torque driver +c 2. output_gamma - write results to netCDF via +c slayer_netcdf_mod +c 3. allocate_inputs - allocate slayer_inputs_type arrays +c 4. allocate_outputs - allocate slayer_outputs_type arrays +c 5. shrink_array - trim over-allocated scan arrays +c 6. grow_array - expand scan arrays dynamically +c 7. calc_determinant - 2x2 / 3x3 complex determinant +c 8. dispersion_det - coupled dispersion determinant +c 9. dispersion_AMR - AMR scan v1 (hash-based dedup) +c 10. dispersion_AMR_v2 - AMR scan v2 (cell-based storage) +c +c Helper subroutines (v1): get_or_compute +c Helper subroutines (v2): compute_delta_sub, +c check_cell_crossing_sub, subdivide_cell_sub, +c flatten_cells_to_points_sub +c----------------------------------------------------------------------- USE omp_lib - USE sglobal_mod, ONLY: out_unit,r8, mu0, m_p, chag, lnLamb, + USE sglobal_mod, ONLY: out_unit,r8,mu0,m_p,chag,lnLamb, $ Q_e,Q_i,pr,pe,c_beta,ds,tau, $ eta,visc,rho_s,lu,omega_e,omega_i, - $ delta_n, - $ Q - USE delta_mod - !, ONLY: riccati,riccati_out, - !$ parflow_flag,PeOhmOnly_flag - + $ delta_n,Q, + $ ifac,g_tmp,pi, ! used by AMR + $ tauk,iota_e,D_norm,P_perp,P_tor,delta_eff, ! used by det + $ amr_cell_type,amr_cells,n_amr_cells, ! v2 types + $ Q_store,D_store,n_pts, ! output arrays + $ MAX_PTS,HASH_SZ,HASH_SCALE, ! v1 constants + $ hash_head,hash_next, ! v1 hash + $ MAX_CELLS, ! v2 limit + $ slayer_inputs_type,slayer_outputs_type, + $ deltas_outputs_type, + $ tau_r,d_crit,delta_det,dc_tmp,dc_type, + $ sn,sm + USE delta_mod, ONLY: riccati,riccati_f,riccati_out, + $ parflow_flag,PeOhmOnly_flag USE params_mod - USE layerinputs_mod - USE slayer_netcdf_mod IMPLICIT NONE @@ -23,93 +50,144 @@ MODULE gslayer_mod c----------------------------------------------------------------------- c subprogram 1. gpec_slayer. -c run slayer to provide b_crit(ising). -c----------------------------------------------------------------------- -c----------------------------------------------------------------------- -c declarations. +c Single-surface SLAYER driver: compute Delta, torque, and b_crit +c for one rational surface characterised by its plasma profiles. +c +c Steps: +c 1. Derive normalised SLAYER parameters (lu, ds, Q, Q_e, etc.) +c from dimensional inputs. +c 2. Compute baseline Delta via riccati(). +c 3. Scan over a range of Q (rotation) to build a torque +c balance curve, then identify the critical threshold br_th. +c +c BUG FLAG 1: `index` variable shadows the Fortran intrinsic +c INDEX(). Rename to e.g. `max_idx`. +c BUG FLAG 2: `iinQs` is declared but never used. Remove it. +c BUG FLAG 3: `zeff`, `qval`, `v_a`, `inlu` are declared / +c passed in but never used. Remove or document intent. c----------------------------------------------------------------------- SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, $ omega_i,qval,sval,bt,rs,R0,mu_i,inpr,mms,nns,ascii_flag, $ delta,psi0,jxb,omega_sol,br_th) - REAL(r8),INTENT(IN) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ qval,sval,bt,rs,R0,zeff,inpr - INTEGER, INTENT(IN) :: mms,nns,mu_i - LOGICAL, INTENT(IN) :: ascii_flag - COMPLEX(r8),INTENT(OUT) :: delta,psi0 - REAL(r8),INTENT(OUT) :: jxb,omega_sol,br_th +c --- input arguments: dimensional plasma profiles for this surface + REAL(r8),INTENT(IN) :: n_e ! electron density [m^-3] + REAL(r8),INTENT(IN) :: t_e ! electron temperature [eV] + REAL(r8),INTENT(IN) :: n_i ! ion density [m^-3] + REAL(r8),INTENT(IN) :: t_i ! ion temperature [eV] + REAL(r8),INTENT(IN) :: zeff ! (UNUSED -- BUG FLAG 3) + REAL(r8),INTENT(IN) :: omega ! plasma rotation frequency + REAL(r8),INTENT(IN) :: omega_e ! electron diamagnetic freq + REAL(r8),INTENT(IN) :: omega_i ! ion diamagnetic freq + REAL(r8),INTENT(IN) :: qval ! (UNUSED -- BUG FLAG 3) + REAL(r8),INTENT(IN) :: sval ! magnetic shear + REAL(r8),INTENT(IN) :: bt ! toroidal field [T] + REAL(r8),INTENT(IN) :: rs ! minor radius of surface [m] + REAL(r8),INTENT(IN) :: R0 ! major radius [m] + REAL(r8),INTENT(IN) :: inpr ! Prandtl number + INTEGER, INTENT(IN) :: mms ! poloidal mode number + INTEGER, INTENT(IN) :: nns ! toroidal mode number + INTEGER, INTENT(IN) :: mu_i ! ion mass number (AMU) + LOGICAL, INTENT(IN) :: ascii_flag ! write ASCII torque-balance file +c --- output arguments + COMPLEX(r8),INTENT(OUT) :: delta ! complex Delta + COMPLEX(r8),INTENT(OUT) :: psi0 ! reconnected flux (a.u.) + REAL(r8),INTENT(OUT) :: jxb ! JxB torque (a.u.) + REAL(r8),INTENT(OUT) :: omega_sol ! rotation at torque-balance + REAL(r8),INTENT(OUT) :: br_th ! critical radial field threshold - INTEGER :: i,inum - INTEGER, DIMENSION(1) :: index - - REAL(r8) :: inQ,inQ_e,inQ_i,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: mrs,nrs,rho,b_l,v_a,Qconv,Q0,delta_n_p, - $ lbeta,tau_i,tau_h,tau_v - REAL(r8) :: inQ_min,inQ_max,Q_sol - - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs,jxbl,bal - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal - CHARACTER(3) :: sn,sm +c --- loop / scan control + INTEGER :: i ! loop index + INTEGER :: inum ! number of scan points + INTEGER, DIMENSION(1) :: max_idx ! index of max torque balance +c --- local copies of normalised parameters for riccati() + REAL(r8) :: inQ,inQ_e,inQ_i ! normalised frequencies + REAL(r8) :: inpe ! electron Prandtl (set to 0) + REAL(r8) :: inc_beta,inds ! c_beta, ds copies + REAL(r8) :: intau ! tau copy +c --- derived dimensional quantities + REAL(r8) :: mrs,nrs ! real-valued mode numbers + REAL(r8) :: rho ! mass density [kg/m^3] + REAL(r8) :: b_l ! characteristic magnetic field + REAL(r8) :: Qconv ! Q normalisation factor + REAL(r8) :: Q0 ! initial Q (before scan) + REAL(r8) :: delta_n_p ! Delta_n perturbation + REAL(r8) :: lbeta ! local beta + REAL(r8) :: tau_i ! ion collision time + REAL(r8) :: tau_h ! Alfven time across surface + REAL(r8) :: tau_v ! viscous diffusion time +c --- scan workspace + REAL(r8) :: inQ_min,inQ_max ! scan bounds in Q + REAL(r8) :: Q_sol ! Q at torque-balance threshold + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs ! Q scan array + REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl ! JxB scan array + REAL(r8), DIMENSION(:), ALLOCATABLE :: bal ! balance scan array + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal ! delta scan array + CHARACTER(3) :: l_sn,l_sm ! local mode number strings +c --- set delta_mod control flags for this single-surface call parflow_flag=.FALSE. PeOhmOnly_flag=.TRUE. riccati_out=.FALSE. mrs = real(mms,4) nrs = real(nns,4) - - ! String representations of the m and n mode numbers +c----------------------------------------------------------------------- +c build string representations of m and n for file names. +c----------------------------------------------------------------------- IF (nns<10) THEN - WRITE(UNIT=sn,FMT='(I1)') nns - sn=ADJUSTL(sn) + WRITE(UNIT=l_sn,FMT='(I1)') nns + l_sn=ADJUSTL(l_sn) ELSE - WRITE(UNIT=sn,FMT='(I2)') nns + WRITE(UNIT=l_sn,FMT='(I2)') nns ENDIF IF (mms<10) THEN - WRITE(UNIT=sm,FMT='(I1)') mms - sm=ADJUSTL(sm) + WRITE(UNIT=l_sm,FMT='(I1)') mms + l_sm=ADJUSTL(l_sm) ELSEIF (mms<100) THEN - WRITE(UNIT=sm,FMT='(I2)') mms - sm=ADJUSTL(sm) + WRITE(UNIT=l_sm,FMT='(I2)') mms + l_sm=ADJUSTL(l_sm) ELSE - WRITE(UNIT=sm,FMT='(I3)') mms + WRITE(UNIT=l_sm,FMT='(I3)') mms ENDIF - inpe=0.0 ! Waybright added this + inpe=0.0 ! electron Prandtl not used here - tau= t_i/t_e ! ratio of ion to electron temperature - tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) +c----------------------------------------------------------------------- +c derive normalised SLAYER parameters from dimensional inputs. +c tau, eta, rho, b_l, v_a are intermediate dimensional quantities; +c lu (Lundquist), ds, Qconv, Q, Q_e, Q_i, c_beta are the +c normalised parameters used by riccati(). +c----------------------------------------------------------------------- + tau= t_i/t_e ! ion-to-electron temp ratio + tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion coll. time + eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! Spitzer resistivity (Wesson) rho=(mu_i*m_p)*n_e ! mass density b_l=(nrs/mrs)*nrs*sval*bt/R0 ! characteristic magnetic field - v_a=b_l/(mu0*rho)**0.5 ! alfven velocity - rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. - - tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! alfven time across surface - tau_r=mu0*rs**2.0/eta ! resistive time scale - tau_v=tau_r/inpr ! rho*rs**2.0/visc ! viscous time scale + rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Larmor radius at T_e - ! this one must be anomalous. calculated back from pr. - visc= rho*rs**2.0/tau_v + tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! Alfven transit time + tau_r=mu0*rs**2.0/eta ! resistive diffusion time + tau_v=tau_r/inpr ! viscous diffusion time + visc= rho*rs**2.0/tau_v ! back-calculated viscosity - lu=tau_r/tau_h ! Lundquist number + lu=tau_r/tau_h ! Lundquist number + Qconv=lu**(1.0/3.0)*tau_h ! Q normalisation factor - Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - - ! note Q depends on Qconv even IF omega is fixed. +c --- normalised frequencies Q=Qconv*omega Q_e=-Qconv*omega_e Q_i=-Qconv*omega_i - ! This is the most critical PARAMETER - ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. + ds=lu**(1.0/3.0)*rho_s/rs ! normalised ion sound radius lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 - c_beta=(lbeta/(1.0+lbeta))**0.5 + c_beta=(lbeta/(1.0+lbeta))**0.5 ! compressibility parameter - delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes + delta_n=lu**(1.0/3.0)/rs ! Delta normalisation factor +c --- copy normalised values into local variables for riccati() call inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -118,14 +196,22 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, intau=tau Q0=Q c----------------------------------------------------------------------- -c calculate basic delta, torque, balance, error fields. +c compute baseline Delta, reconnected flux, and JxB torque. +c BUG FLAG 4: delta_n_p = 1e-2 is hardcoded; should this come +c from the namelist or caller? c----------------------------------------------------------------------- delta_n_p=1e-2 delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) - psi0=1.0/ABS(delta+delta_n_p) ! a.u. - jxb=-AIMAG(1.0/(delta+delta_n_p)) ! a.u. + psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux (a.u.) + jxb=-AIMAG(1.0/(delta+delta_n_p)) ! JxB torque (a.u.) c----------------------------------------------------------------------- -c find solutions based on simple torque balance. +c torque-balance scan: sweep Q over [inQ_min, inQ_max] and +c compute delta(Q), JxB(Q), and the balance parameter. +c The threshold br_th is sqrt(max(bal)/lu * s^2/2). +c +c BUG FLAG 5: scan bounds from Q0/Q_e physics (lines above) are +c immediately overridden by hardcoded inQ_max=10, inQ_min=-10. +c Either remove the dead code above or use the physics bounds. c----------------------------------------------------------------------- IF (Q0>inQ_e) THEN inQ_max=2.0*Q0 @@ -139,8 +225,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, ENDIF ENDIF - ! Scan of rotation - inQ_max=10.0 + inQ_max=10.0 ! hardcoded override (see BUG FLAG 5) inQ_min=-10.0 inum=200 ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum),bal(0:inum)) @@ -152,10 +237,10 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) ENDDO - ! Write torque balance curves to file for diagnostic purposes +c --- optionally write torque balance curve to ASCII file IF(ascii_flag)THEN OPEN(UNIT=out_unit,FILE="gpec_slayer_torque_balance_m"// - $ TRIM(sm)//"_n"//TRIM(sn)//".OUT", + $ TRIM(l_sm)//"_n"//TRIM(l_sn)//".OUT", $ STATUS="UNKNOWN") WRITE(out_unit,'(1x,5(a17))') "inQ","RE(delta)", $ "IM(delta)","jxb","bal" @@ -166,28 +251,29 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, CLOSE(out_unit) ENDIF - ! Identify the threshold from the maximum of the balance PARAMETER - index=MAXLOC(bal) - Q_sol=inQs(index(1)) - omega_sol=inQs(index(1))/Qconv +c --- identify the critical threshold from the maximum balance value + max_idx=MAXLOC(bal) + Q_sol=inQs(max_idx(1)) + omega_sol=inQs(max_idx(1))/Qconv br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0)) DEALLOCATE(inQs,deltal,jxbl,bal) RETURN END SUBROUTINE gpec_slayer c----------------------------------------------------------------------- -c Subprogram 2. output_gamma -c Take SLAYER input and output dicts, send to netCDF subroutine +c subprogram 2. output_gamma. +c Write growth-rate results to netCDF via slayer_netcdf_out. +c Passes the input/output structured types and AMR results +c through to the netCDF writer (slayer_netcdf_mod). c----------------------------------------------------------------------- SUBROUTINE output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, $ all_deltas_out) - ! Declarations (include necessary type declarations from original code) - LOGICAL, INTENT(IN) :: est_gamma_flag - INTEGER, INTENT(IN) :: m_AMR + LOGICAL, INTENT(IN) :: est_gamma_flag ! single-surface mode? + INTEGER, INTENT(IN) :: m_AMR ! AMR pass count TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out - TYPE(deltas_outputs_type), INTENT(IN) :: + TYPE(deltas_outputs_type), INTENT(IN) :: $ all_deltas_out(SIZE(sl_in%qval_arr)) CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),m_AMR,est_gamma_flag, @@ -195,11 +281,12 @@ SUBROUTINE output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, END SUBROUTINE output_gamma c----------------------------------------------------------------------- -c Subprogram 3. allocate_inputs -c Allocate arrays inside SLAYER inputs type (dictionary-esque) +c subprogram 3. allocate_inputs. +c Allocate all per-surface arrays inside slayer_inputs_type. c----------------------------------------------------------------------- SUBROUTINE allocate_inputs(n_k,sl_in) - INTEGER, INTENT(IN) :: n_k + + INTEGER, INTENT(IN) :: n_k ! number of surfaces TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in ALLOCATE(sl_in%qval_arr(n_k),sl_in%omegas_arr(n_k), @@ -213,11 +300,12 @@ SUBROUTINE allocate_inputs(n_k,sl_in) RETURN END SUBROUTINE allocate_inputs c----------------------------------------------------------------------- -c Subprogram 4. allocate_outputs -c Allocate arrays inside SLAYER outputs type (dictionary-esque) +c subprogram 4. allocate_outputs. +c Allocate per-surface arrays inside slayer_outputs_type. c----------------------------------------------------------------------- SUBROUTINE allocate_outputs(n_k,sl_out) - INTEGER, INTENT(IN) :: n_k + + INTEGER, INTENT(IN) :: n_k ! number of surfaces TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out ALLOCATE(sl_out%dels_db_arr(n_k),sl_out%gamma_sol_arr(n_k), @@ -225,43 +313,54 @@ SUBROUTINE allocate_outputs(n_k,sl_out) RETURN END SUBROUTINE allocate_outputs c----------------------------------------------------------------------- -c Subprogram 5. shrink_array -c Remove excess scan array size from memory +c subprogram 5. shrink_array. +c Trim an over-allocated REAL(r8) array down to new_size using +c MOVE_ALLOC (no copy of trailing elements). c----------------------------------------------------------------------- SUBROUTINE shrink_array(arr, new_size) + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) - INTEGER, INTENT(IN) :: new_size - REAL(r8), ALLOCATABLE :: temp(:) + INTEGER, INTENT(IN) :: new_size ! target size + REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer ALLOCATE(temp(new_size)) temp(1:new_size) = arr(1:new_size) CALL move_alloc(temp, arr) END SUBROUTINE shrink_array c----------------------------------------------------------------------- -c Subprogram 6. grow_array -c Increase scan array size IF necessary +c subprogram 6. grow_array. +c Expand a REAL(r8) array from old_size to new_size, preserving +c existing data via MOVE_ALLOC. c----------------------------------------------------------------------- SUBROUTINE grow_array(arr, old_size, new_size) + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) - INTEGER, INTENT(IN) :: old_size,new_size - REAL(r8), ALLOCATABLE :: temp(:) + INTEGER, INTENT(IN) :: old_size ! current valid element count + INTEGER, INTENT(IN) :: new_size ! target allocation size + REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer ALLOCATE(temp(new_size)) temp(1:old_size) = arr(1:old_size) CALL move_alloc(temp, arr) END SUBROUTINE grow_array c----------------------------------------------------------------------- -c Subprogram 7. calc_determinant -c Calculate determinant of 2x2 and 3x3 matrices +c subprogram 7. calc_determinant. +c Compute the determinant of a 2x2 or 3x3 complex matrix. +c Returns (0,0) and sets status=-1 for unsupported sizes. +c +c BUG FLAG 6: variable `status` is computed but never returned +c to the caller. Either add an INTENT(OUT) argument or remove. c----------------------------------------------------------------------- SUBROUTINE calc_determinant(matk, nk, detk) + IMPLICIT NONE - - ! Arguments - INTEGER, INTENT(IN) :: nk ! Matrix size (2 or 3) - COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! Input matrix - COMPLEX(r8), INTENT(OUT) :: detk ! Determinant result - INTEGER :: status ! Status (0=success, -1=error) + +c --- arguments + INTEGER, INTENT(IN) :: nk ! matrix rank (2 or 3) + COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! input matrix + COMPLEX(r8), INTENT(OUT) :: detk ! determinant result +c --- local + INTEGER :: status ! error status (unused externally) status = 0 ! Initialize status as success @@ -286,19 +385,38 @@ SUBROUTINE calc_determinant(matk, nk, detk) RETURN END SUBROUTINE calc_determinant c----------------------------------------------------------------------- -c Subprogram 8. dispersion_det -c Calculate determinant of coupling matrix e-value problem +c subprogram 8. dispersion_det. +c Compute the coupled dispersion determinant for n_k surfaces. +c +c For n_k = 1 (single surface): +c Evaluate riccati_f(g_tmp), de-normalise by lu^(1/3), and +c return Deltaprime - delta(Q). +c +c For n_k = 2 or 3 (coupled surfaces): +c Build the diagonal delta(Q) matrix, subtract from dp_matrix, +c and return det(dp_matrix - delta_Q). +c +c BUG FLAG 7: bare `stop` on n_k > 3 -- use STOP with message +c consistently, or return a NaN sentinel. c----------------------------------------------------------------------- FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) - - COMPLEX(r8), INTENT(IN) :: g_tmp - INTEGER, INTENT(IN) :: n_k,msing_max + +c --- arguments + COMPLEX(r8), INTENT(IN) :: g_tmp ! complex growth rate + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces to include TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - COMPLEX(r8) :: dispersion_det,det_val,tmp_delta - COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:),result_matrix(:,:) - INTEGER :: k +c --- function result and locals + COMPLEX(r8) :: dispersion_det ! returned determinant + COMPLEX(r8) :: det_val ! intermediate determinant + COMPLEX(r8) :: tmp_delta ! single-surface delta + COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:) ! diagonal delta matrix + COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! dp - delta_Q + INTEGER :: k ! surface loop index +c --- single-surface branch IF (n_k < 2) THEN +c set module-level variables for riccati_f Q_e = sl_in%Q_e_arr(1) Q_i = sl_in%Q_i_arr(1) P_perp = sl_in%P_perp_arr(1) @@ -310,15 +428,18 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) iota_e = Q_e / (Q_e - Q_i) tmp_delta=riccati_f(g_tmp) - det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) ! DE-NORMALIZE +c de-normalise delta by lu^(1/3) + det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) - ! Calculate Deltaprime - Delta(Q) +c return Deltaprime - delta(Q) dispersion_det = sl_in%Re_dp_arr(1) - det_val +c --- coupled-surface branch (2 or 3 surfaces) ELSEIF ((n_k == 2) .OR. (n_k == 3)) THEN ALLOCATE(delta_Q(n_k,n_k)) delta_Q=(0.0,0.0) - DO k=1,msing_max ! maxing out at msing_max + DO k=1,msing_max +c set module-level variables for this surface Q_e = sl_in%Q_e_arr(k) Q_i = sl_in%Q_i_arr(k) P_perp = sl_in%P_perp_arr(k) @@ -328,176 +449,172 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) c_beta = sl_in%c_beta_arr(k) tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) - + +c evaluate riccati_f at rescaled growth rate, de-normalise delta_Q(k,k)=riccati_f(((g_tmp*sl_in%Qconv_arr(1)) $ /tauk)) - delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) ! DE-NORMALIZE + delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) END DO - - ! Calculate Deltaprime - Delta(Q) - result_matrix = sl_in%dp_matrix - delta_Q - ! Calculate determinant +c compute det(dp_matrix - delta_Q) + result_matrix = sl_in%dp_matrix - delta_Q CALL calc_determinant(result_matrix, n_k, det_val) dispersion_det = det_val ELSE - WRITE(*,*)"Error: no support for msing > 3" - stop + WRITE(*,*) "Error: no support for msing > 3" + STOP "dispersion_det: unsupported n_k" END IF END FUNCTION dispersion_det +c----------------------------------------------------------------------- +c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc Scans a 2D complex-Q grid for zeros of the dispersion relation +c D(Q) using adaptive refinement. A coarse grid is evaluated +c first (two-pass: nodes then cells), then cells that span a zero +c crossing in Re(D) or Im(D) are subdivided. +c +c Point deduplication uses a spatial hash table (HASH_SZ buckets, +c chained) so that midpoints shared between neighbouring cells +c are evaluated only once. +c +c BUG FLAG 8 \u2013 h_idx, pt_idx are declared but never referenced. +c Remove them. +c----------------------------------------------------------------------- SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, $ scan_width,Q_num,AMR_passes, $ coupling_flag) - !WRITE(*,*)"------------------------------------------" - !WRITE(*,'(A,F0.1,A,I2,A)')' >>> Running Adaptive AMR Scan [Width=',scan_width, & - ! ', Passes=', AMR_PASSES, ']' - INTEGER, INTENT(IN) :: n_k,msing_max,Q_num,AMR_passes - REAL(r8), INTENT(IN) :: scan_width - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag - !COMPLEX(r8), INTENT(OUT), ALLOCATABLE :: Q_store(:) ! Stores Q coordinates - !COMPLEX(r8), INTENT(OUT), ALLOCATABLE :: D_store(:) ! Stores Result Delta - INTEGER, ALLOCATABLE :: cells(:,:) ! 4 corners (indices) per cell - INTEGER, ALLOCATABLE :: new_cells(:,:) ! Temp array for next level +c --- arguments + INTEGER, INTENT(IN) :: n_k ! number of rational surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling + INTEGER, INTENT(IN) :: Q_num ! grid points per axis + INTEGER, INTENT(IN) :: AMR_passes ! refinement passes + REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? +c --- cell storage + INTEGER, ALLOCATABLE :: cells(:,:) ! (4, max) corner indices per cell + INTEGER, ALLOCATABLE :: new_cells(:,:) ! scratch for next level +c --- loop / index variables INTEGER :: n_cells, n_new_cells, i, j - INTEGER :: h_idx, pt_idx, c_idx, pass - INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! Corner indices - INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! Midpoint indices - REAL(r8) :: r_min, r_max, i_min, i_max, ing_step, - $ ing_coarse,iing_coarse - LOGICAL :: cross_real, cross_imag - !INTEGER, INTENT(OUT) :: n_pts - - COMPLEX(r8) :: q_curr - INTEGER, ALLOCATABLE :: coarse_indices(:,:) - ! (Re-allocate or just reset counters. Re-allocating ensures clean slate) - !IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store, D_store) - !IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head, hash_next) - !IF (ALLOCATED(cells)) DEALLOCATE(cells, new_cells) - - ! --- 1. Initialize Memory --- - !ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) - !ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) - !ALLOCATE(cells(4, 200000), new_cells(4, 200000)) ! Estimate cell count - + INTEGER :: h_idx, pt_idx, c_idx, pass ! h_idx, pt_idx UNUSED (BUG FLAG 8) + INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! corner indices + INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! midpoint indices +c --- scan workspace + REAL(r8) :: r_min, r_max, i_min, i_max ! min/max Re/Im across corners + REAL(r8) :: ing_step ! coarse grid spacing + REAL(r8) :: ing_coarse, iing_coarse ! Re/Im coords for coarse node + LOGICAL :: cross_real, cross_imag ! zero-crossing flags + COMPLEX(r8) :: q_curr ! current evaluation point + INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Q_num,Q_num) node index map + +c --- 1. initialise hash table and point/cell storage IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) IF (ALLOCATED(D_store)) DEALLOCATE(D_store) IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - + ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) ALLOCATE(cells(4, 200000), new_cells(4, 200000)) - + hash_head = 0 hash_next = 0 n_pts = 0 n_cells = 0 - - ! --- 2. Build Initial Coarse Grid (Robust 2-Pass Method) --- + +c --- 2. build initial coarse grid (two-pass method) +c Pass 1 computes and hashes every node; Pass 2 stitches cells +c from the stored indices -- no floating-point comparison needed. ALLOCATE(coarse_indices(Q_num, Q_num)) ing_step = (2.0*scan_width) / (Q_num - 1) - ! PASS 1: Compute ALL points and store their indices - ! This ensures every grid node is computed exactly once and hashed consistently +c Pass 1: compute all grid nodes and store their hash indices DO i = 1, Q_num DO j = 1, Q_num ing_coarse = -scan_width + (i - 1) * ing_step iing_coarse = -scan_width + (j - 1) * ing_step q_curr = CMPLX(ing_coarse, iing_coarse) - - CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, + + CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, $ sl_in, msing_max, coupling_flag) END DO END DO - ! PASS 2: Stitch cells together using the stored indices - ! No floating point math here, just integer lookups -> Perfect Topology +c Pass 2: stitch cells from the stored integer indices DO i = 1, Q_num - 1 DO j = 1, Q_num - 1 n_cells = n_cells + 1 - - ! Define corners using the pre-calculated indices - cells(1, n_cells) = coarse_indices(i, j) ! Top-Left - cells(2, n_cells) = coarse_indices(i+1, j) ! Top-Right - cells(3, n_cells) = coarse_indices(i, j+1) ! Bot-Left - cells(4, n_cells) = coarse_indices(i+1, j+1) ! Bot-Right + cells(1, n_cells) = coarse_indices(i, j) ! TL + cells(2, n_cells) = coarse_indices(i+1, j) ! TR + cells(3, n_cells) = coarse_indices(i, j+1) ! BL + cells(4, n_cells) = coarse_indices(i+1, j+1) ! BR END DO END DO - - DEALLOCATE(coarse_indices) ! Clean up temp array + DEALLOCATE(coarse_indices) - ! --- 3. Refinement Loops --- +c --- 3. refinement passes: subdivide cells with zero crossings DO pass = 1, AMR_PASSES - WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, + WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, $ ': Checking ', n_cells, ' cells...' n_new_cells = 0 - + DO c_idx = 1, n_cells idx_TL = cells(1, c_idx) idx_TR = cells(2, c_idx) idx_BL = cells(3, c_idx) idx_BR = cells(4, c_idx) - - ! Check for contours (Sign changes across the cell) - ! Real Part Check - r_min = MIN(REAL(D_store(idx_TL)), + +c check for sign change in Re(D) across cell corners + r_min = MIN(REAL(D_store(idx_TL)), $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BL)), $ REAL(D_store(idx_BR))) - r_max = MAX(REAL(D_store(idx_TL)), + r_max = MAX(REAL(D_store(idx_TL)), $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BL)), $ REAL(D_store(idx_BR))) cross_real = (r_min * r_max <= 0.0d0) - - ! Imag Part Check - i_min = MIN(AIMAG(D_store(idx_TL)), + +c check for sign change in Im(D) across cell corners + i_min = MIN(AIMAG(D_store(idx_TL)), $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BL)), $ AIMAG(D_store(idx_BR))) - i_max = MAX(AIMAG(D_store(idx_TL)), + i_max = MAX(AIMAG(D_store(idx_TL)), $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BL)), $ AIMAG(D_store(idx_BR))) cross_imag = (i_min * i_max <= 0.0d0) IF (cross_real .OR. cross_imag) THEN - ! --- REFINE THIS CELL --- - ! We need 5 new points: Top-Mid, Bot-Mid, Left-Mid, Right-Mid, Center - - ! Calculate coords from corners - ! TL: Q_store(idx_TL), BR: Q_store(idx_BR) +c refine: compute 5 midpoints, create 4 sub-cells ! Top-Mid q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_TR)) CALL get_or_compute(q_curr, idx_TM,n_k, $ sl_in,msing_max,coupling_flag) - + ! Bot-Mid q_curr = 0.5d0*(Q_store(idx_BL)+Q_store(idx_BR)) CALL get_or_compute(q_curr, idx_BM,n_k, $ sl_in,msing_max,coupling_flag) - + ! Left-Mid q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BL)) CALL get_or_compute(q_curr, idx_LM,n_k, $ sl_in,msing_max,coupling_flag) - + ! Right-Mid q_curr = 0.5d0*(Q_store(idx_TR)+Q_store(idx_BR)) CALL get_or_compute(q_curr, idx_RM,n_k, $ sl_in,msing_max,coupling_flag) - + ! Center q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BR)) CALL get_or_compute(q_curr, idx_MM,n_k, $ sl_in,msing_max,coupling_flag) - ! Create 4 new sub-cells (Top-Left, Top-Right, Bot-Left, Bot-Right) - ! Sub 1 (Top-Left) + ! Create 4 sub-cells (TL, TR, BL, BR quadrants) n_new_cells = n_new_cells + 1 new_cells(1, n_new_cells) = idx_TL new_cells(2, n_new_cells) = idx_TM @@ -532,72 +649,73 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, END IF END DO - ! Swap arrays for next iteration +c --- swap arrays for next refinement pass n_cells = n_new_cells cells(:, 1:n_cells) = new_cells(:, 1:n_cells) - + END DO DEALLOCATE(cells, new_cells) WRITE(*,*) "AMR Scan Complete. Total Points:", n_pts - WRITE(*,*)"gslayer.f Q_store(10) = ",Q_store(10) RETURN END SUBROUTINE dispersion_AMR +c----------------------------------------------------------------------- +c get_or_compute: hash-based point lookup for dispersion_AMR v1. +c If q_in is already in the hash table, return its index. +c Otherwise, evaluate the dispersion relation at q_in, store +c the result, and insert into the hash table. +c +c Uses 64-bit arithmetic internally to avoid integer overflow +c in the hash function. +c----------------------------------------------------------------------- SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, $ coupling_flag) - COMPLEX(r8), INTENT(IN) :: q_in - !INTEGER, INTENT(INOUT) :: n_pts - !COMPLEX(r8), INTENT(INOUT) :: Q_store(:),D_store(:) + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point + INTEGER, INTENT(OUT) :: idx_out ! returned point index + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces to include TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - INTEGER, INTENT(IN) :: n_k, msing_max - INTEGER, INTENT(OUT) :: idx_out - LOGICAL, INTENT(IN) :: coupling_flag - INTEGER :: h, curr - COMPLEX(r8) :: delta_val - INTEGER(8) :: ix8, iy8, h8 ! 64-bit integers for hash calculation - - ! 1. Calculate Hash - !ix = NINT(REAL(q_in) * HASH_SCALE) - !iy = NINT(AIMAG(q_in) * HASH_SCALE) - ! Simple hash mix - !h = MOD(ABS(ix * 73856093 + iy * 19349663), HASH_SZ) + 1 - - ! 1. Calculate Hash using 64-bit arithmetic + LOGICAL, INTENT(IN) :: coupling_flag ! use coupled dispersion_det? +c --- locals + INTEGER :: h ! hash bucket index + INTEGER :: curr ! linked-list traversal index + COMPLEX(r8) :: delta_val ! computed dispersion result + INTEGER(8) :: ix8, iy8, h8 ! 64-bit intermediates for hash + +c --- 1. compute hash from quantised Re/Im coordinates (64-bit safe) ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) - h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), - $ INT(HASH_SZ, 8)) + 1_8 - h = INT(h8) ! Safe to convert back, result is within HASH_SZ - + h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), + $ INT(HASH_SZ, 8)) + 1_8 + h = INT(h8) + IF (h < 1 .OR. h > HASH_SZ) THEN WRITE(*,*) "HASH ERROR: h=", h, " q_in=", q_in - WRITE(*,*) " ix=", ix8, " iy=", iy8 - STOP + STOP "get_or_compute: hash out of bounds" END IF - ! 2. Check collisions +c --- 2. search hash chain for existing point curr = hash_head(h) DO WHILE (curr /= 0) - ! Check if point matches (with small tolerance) IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN idx_out = curr - RETURN ! Found it, return existing index + RETURN END IF curr = hash_next(curr) END DO - - ! 3. Not found: Compute and Store - n_pts = n_pts + 1 +c --- 3. point not found: evaluate dispersion relation and store + n_pts = n_pts + 1 IF (n_pts > MAX_PTS) THEN WRITE(*,*) "ERROR: AMR exceeded MAX_PTS" - STOP + STOP "get_or_compute: MAX_PTS exceeded" END IF - + idx_out = n_pts Q_store(idx_out) = q_in - - ! --- PHYSICS EVALUATION --- + IF (coupling_flag) THEN g_tmp = q_in delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) @@ -607,46 +725,59 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, delta_val = delta_val - delta_eff END IF D_store(idx_out) = delta_val - ! -------------------------- - - ! 4. Add to Hash Table + +c --- 4. insert into hash chain (prepend) hash_next(idx_out) = hash_head(h) hash_head(h) = idx_out - + END SUBROUTINE get_or_compute +c----------------------------------------------------------------------- +c dispersion_AMR_v2: cell-based adaptive mesh refinement scanner. +c Unlike v1 (hash-based point deduplication), v2 stores complete +c cells (TYPE amr_cell_type), each carrying 4 corner Q- and D- +c values. Refinement subdivides cells that contain a zero in +c Re(D) or Im(D) and re-evaluates the dispersion relation at the +c 5 new midpoints. +c +c After refinement, flatten_cells_to_points_sub extracts unique +c (Q, D) points into Q_store / D_store for output. +c +c BUG FLAG 9 – several bare stop statements should carry messages. +c----------------------------------------------------------------------- SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, $ scan_width, Q_num, AMR_passes, $ coupling_flag) - + IMPLICIT NONE - - INTEGER, INTENT(IN) :: n_k, msing_max, Q_num, AMR_passes - REAL(r8), INTENT(IN) :: scan_width + +c --- arguments + INTEGER, INTENT(IN) :: n_k ! number of rational surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling + INTEGER, INTENT(IN) :: Q_num ! grid points per axis + INTEGER, INTENT(IN) :: AMR_passes ! refinement passes + REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag - + LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? +c --- locals TYPE(amr_cell_type), ALLOCATABLE :: new_cells(:) - INTEGER :: i, j, c, corner, pass - REAL(r8) :: step, x, y - COMPLEX(r8) :: delta_val - LOGICAL :: cross_real, cross_imag - INTEGER :: n_new_cells - INTEGER :: cells_to_refine, cells_kept - - ! --- 1. Clean Initialization --- - WRITE(*,*) '=== AMR v2 Starting ===' - - IF (ALLOCATED(amr_cells)) THEN - WRITE(*,*) 'Deallocating old amr_cells' - DEALLOCATE(amr_cells) - END IF - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - + INTEGER :: i, j, c, corner, pass ! loop counters + REAL(r8) :: step ! grid spacing + REAL(r8) :: x, y ! real / imag grid coords + COMPLEX(r8) :: delta_val ! (unused – computed inside helper) + LOGICAL :: cross_real, cross_imag ! zero-crossing flags + INTEGER :: n_new_cells ! count during refinement + INTEGER :: cells_to_refine ! cells flagged per pass + INTEGER :: cells_kept ! cells kept per pass + +c --- 1. initialise cell storage + + IF (ALLOCATED(amr_cells)) DEALLOCATE(amr_cells) + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + ALLOCATE(amr_cells(MAX_CELLS)) ALLOCATE(new_cells(MAX_CELLS)) - - ! Initialize all cells + DO i = 1, MAX_CELLS amr_cells(i)%Q = (0.0d0, 0.0d0) amr_cells(i)%D = (0.0d0, 0.0d0) @@ -655,128 +786,117 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, new_cells(i)%D = (0.0d0, 0.0d0) new_cells(i)%needs_refine = .FALSE. END DO - + n_amr_cells = 0 step = (2.0d0 * scan_width) / DBLE(Q_num - 1) - WRITE(*,*) 'Scan width:', scan_width - WRITE(*,*) 'Q_num:', Q_num - WRITE(*,*) 'Step size:', step - WRITE(*,*) 'AMR passes:', AMR_passes - - ! --- 2. Build Initial Coarse Grid --- - WRITE(*,*) 'Building initial coarse grid...' - +c --- 2. build initial coarse grid of (Q_num-1)^2 cells + DO i = 1, Q_num - 1 DO j = 1, Q_num - 1 x = -scan_width + DBLE(i-1) * step y = -scan_width + DBLE(j-1) * step - + n_amr_cells = n_amr_cells + 1 - + IF (n_amr_cells > MAX_CELLS) THEN WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in init' - STOP + STOP 'dispersion_AMR_v2: MAX_CELLS in init' END IF - - ! Define corner coordinates (BL, BR, TL, TR) + +c corner order: BL=1, BR=2, TL=3, TR=4 amr_cells(n_amr_cells)%Q(1) = CMPLX(x, y, KIND=r8) amr_cells(n_amr_cells)%Q(2) = CMPLX(x+step, y, KIND=r8) amr_cells(n_amr_cells)%Q(3) = CMPLX(x, y+step, KIND=r8) - amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, + amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, $ KIND=r8) - - ! Compute Delta at each corner + +c evaluate dispersion at each corner DO corner = 1, 4 CALL compute_delta_sub( $ amr_cells(n_amr_cells)%Q(corner), $ n_k, sl_in, msing_max, coupling_flag, $ amr_cells(n_amr_cells)%D(corner)) END DO - + amr_cells(n_amr_cells)%needs_refine = .FALSE. END DO END DO - - WRITE(*,*) 'Initial grid cells:', n_amr_cells - ! --- 3. Refinement Passes --- +c --- 3. refinement passes: subdivide cells with zero crossings DO pass = 1, AMR_passes - WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, + WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, $ ': Processing ', n_amr_cells, ' cells' - - ! Mark cells that need refinement + +c flag cells that span a zero in Re(D) or Im(D) cells_to_refine = 0 DO c = 1, n_amr_cells - CALL check_cell_crossing_sub(amr_cells(c), + CALL check_cell_crossing_sub(amr_cells(c), $ cross_real, cross_imag) amr_cells(c)%needs_refine = (cross_real .OR. cross_imag) IF (amr_cells(c)%needs_refine) THEN cells_to_refine = cells_to_refine + 1 END IF END DO - - WRITE(*,*) ' Cells to refine:', cells_to_refine - - ! Build new cell list + +c build new cell list: subdivide flagged, keep the rest n_new_cells = 0 cells_kept = 0 - + DO c = 1, n_amr_cells IF (amr_cells(c)%needs_refine) THEN - ! Subdivide this cell into 4 - CALL subdivide_cell_sub(amr_cells(c), + CALL subdivide_cell_sub(amr_cells(c), $ new_cells, n_new_cells, MAX_CELLS, $ n_k, sl_in, msing_max, coupling_flag) ELSE - ! Keep cell as-is n_new_cells = n_new_cells + 1 IF (n_new_cells > MAX_CELLS) THEN WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in refine' - STOP + STOP 'dispersion_AMR_v2: MAX_CELLS in refine' END IF new_cells(n_new_cells) = amr_cells(c) cells_kept = cells_kept + 1 END IF END DO - - WRITE(*,*) ' Cells kept:', cells_kept - WRITE(*,*) ' New total cells:', n_new_cells - - ! Copy new_cells back to amr_cells + +c swap arrays for next pass n_amr_cells = n_new_cells DO c = 1, n_amr_cells amr_cells(c) = new_cells(c) END DO - + END DO - - ! --- 4. Flatten to Output Arrays --- - WRITE(*,*) 'Flattening cells to output arrays...' + +c --- 4. flatten cells to unique (Q, D) output arrays CALL flatten_cells_to_points_sub(n_amr_cells) - - ! Cleanup + DEALLOCATE(new_cells) - ! Keep amr_cells allocated for potential debugging - +c keep amr_cells allocated for potential post-run inspection + WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts - + RETURN END SUBROUTINE dispersion_AMR_v2 - -! ============================================================ +c----------------------------------------------------------------------- +c compute_delta_sub: evaluate the dispersion relation at a single +c complex-Q point for dispersion_AMR_v2. Multiplies q_in by ifac +c before passing to the Riccati solver or coupled-surface +c determinant routine. +c----------------------------------------------------------------------- SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, $ coupling_flag, delta_out) - + IMPLICIT NONE - - COMPLEX(r8), INTENT(IN) :: q_in - INTEGER, INTENT(IN) :: n_k, msing_max + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag - COMPLEX(r8), INTENT(OUT) :: delta_out - + LOGICAL, INTENT(IN) :: coupling_flag ! use coupled det? + COMPLEX(r8), INTENT(OUT) :: delta_out ! dispersion result + IF (coupling_flag) THEN g_tmp = q_in*ifac delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) @@ -785,98 +905,109 @@ SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, delta_out = riccati_f(g_tmp) delta_out = delta_out - delta_eff END IF - + RETURN END SUBROUTINE compute_delta_sub -! ============================================================ +c----------------------------------------------------------------------- +c check_cell_crossing_sub: test whether a cell’s 4 corner D-values +c span a zero crossing in Re(D) and/or Im(D). Used by +c dispersion_AMR_v2 to decide which cells to refine. +c----------------------------------------------------------------------- SUBROUTINE check_cell_crossing_sub(cell, cross_real, cross_imag) - + IMPLICIT NONE - + TYPE(amr_cell_type), INTENT(IN) :: cell LOGICAL, INTENT(OUT) :: cross_real, cross_imag - - REAL(r8) :: r_vals(4), i_vals(4) + + REAL(r8) :: r_vals(4), i_vals(4) ! corner Re/Im values REAL(r8) :: r_min, r_max, i_min, i_max - INTEGER :: k - - ! Extract real and imaginary parts + INTEGER :: k + DO k = 1, 4 r_vals(k) = REAL(cell%D(k), KIND=r8) i_vals(k) = AIMAG(cell%D(k)) END DO - + r_min = MINVAL(r_vals) r_max = MAXVAL(r_vals) cross_real = (r_min * r_max <= 0.0d0) - + i_min = MINVAL(i_vals) i_max = MAXVAL(i_vals) cross_imag = (i_min * i_max <= 0.0d0) - + RETURN END SUBROUTINE check_cell_crossing_sub -! ============================================================ - SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, - $ max_cells, n_k, sl_in, +c----------------------------------------------------------------------- +c subdivide_cell_sub: split a parent cell into 4 child cells by +c computing 5 midpoints (bottom-mid, top-mid, left-mid, right-mid, +c centre) and evaluating the dispersion relation at each. The 4 +c resulting child cells are appended to new_cells. +c----------------------------------------------------------------------- + SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, + $ max_cells, n_k, sl_in, $ msing_max, coupling_flag) - + IMPLICIT NONE - - TYPE(amr_cell_type), INTENT(IN) :: parent + +c --- arguments + TYPE(amr_cell_type), INTENT(IN) :: parent TYPE(amr_cell_type), INTENT(INOUT) :: new_cells(*) - INTEGER, INTENT(INOUT) :: n_new - INTEGER, INTENT(IN) :: max_cells - INTEGER, INTENT(IN) :: n_k, msing_max + INTEGER, INTENT(INOUT) :: n_new ! running count of new cells + INTEGER, INTENT(IN) :: max_cells ! capacity of new_cells + INTEGER, INTENT(IN) :: n_k + INTEGER, INTENT(IN) :: msing_max TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag - + LOGICAL, INTENT(IN) :: coupling_flag +c --- corner coordinates and D-values from parent COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr + COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr +c --- midpoint coordinates and D-values (5 new evaluations) COMPLEX(r8) :: q_bm, q_tm, q_lm, q_rm, q_mm COMPLEX(r8) :: d_bm, d_tm, d_lm, d_rm, d_mm - COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr - ! Extract parent corners (BL=1, BR=2, TL=3, TR=4) +c --- extract parent corners (BL=1, BR=2, TL=3, TR=4) q_bl = parent%Q(1) q_br = parent%Q(2) q_tl = parent%Q(3) q_tr = parent%Q(4) - + d_bl = parent%D(1) d_br = parent%D(2) d_tl = parent%D(3) d_tr = parent%D(4) - - ! Compute midpoint coordinates - q_bm = 0.5d0 * (q_bl + q_br) ! Bottom middle - q_tm = 0.5d0 * (q_tl + q_tr) ! Top middle - q_lm = 0.5d0 * (q_bl + q_tl) ! Left middle - q_rm = 0.5d0 * (q_br + q_tr) ! Right middle - q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) ! Center - - ! Compute Delta at new midpoints (5 new evaluations) - CALL compute_delta_sub(q_bm, n_k, sl_in, msing_max, + +c --- compute 5 midpoint coordinates + q_bm = 0.5d0 * (q_bl + q_br) + q_tm = 0.5d0 * (q_tl + q_tr) + q_lm = 0.5d0 * (q_bl + q_tl) + q_rm = 0.5d0 * (q_br + q_tr) + q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) + +c --- evaluate dispersion at new midpoints (5 calls) + CALL compute_delta_sub(q_bm, n_k, sl_in, msing_max, $ coupling_flag, d_bm) - CALL compute_delta_sub(q_tm, n_k, sl_in, msing_max, + CALL compute_delta_sub(q_tm, n_k, sl_in, msing_max, $ coupling_flag, d_tm) - CALL compute_delta_sub(q_lm, n_k, sl_in, msing_max, + CALL compute_delta_sub(q_lm, n_k, sl_in, msing_max, $ coupling_flag, d_lm) - CALL compute_delta_sub(q_rm, n_k, sl_in, msing_max, + CALL compute_delta_sub(q_rm, n_k, sl_in, msing_max, $ coupling_flag, d_rm) - CALL compute_delta_sub(q_mm, n_k, sl_in, msing_max, + CALL compute_delta_sub(q_mm, n_k, sl_in, msing_max, $ coupling_flag, d_mm) - - ! Check space for 4 new cells + +c --- check space for 4 new cells IF (n_new + 4 > max_cells) THEN WRITE(*,*) 'ERROR: Would exceed MAX_CELLS in subdivide' - STOP + STOP 'subdivide_cell_sub: MAX_CELLS exceeded' END IF - - ! Child 1: Bottom-Left quadrant (BL, BM, LM, MM) + +c --- child 1: bottom-left quadrant (BL, BM, LM, MM) n_new = n_new + 1 new_cells(n_new)%Q(1) = q_bl new_cells(n_new)%Q(2) = q_bm @@ -888,7 +1019,7 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, new_cells(n_new)%D(4) = d_mm new_cells(n_new)%needs_refine = .FALSE. - ! Child 2: Bottom-Right quadrant (BM, BR, MM, RM) +c --- child 2: bottom-right quadrant (BM, BR, MM, RM) n_new = n_new + 1 new_cells(n_new)%Q(1) = q_bm new_cells(n_new)%Q(2) = q_br @@ -900,7 +1031,7 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, new_cells(n_new)%D(4) = d_rm new_cells(n_new)%needs_refine = .FALSE. - ! Child 3: Top-Left quadrant (LM, MM, TL, TM) +c --- child 3: top-left quadrant (LM, MM, TL, TM) n_new = n_new + 1 new_cells(n_new)%Q(1) = q_lm new_cells(n_new)%Q(2) = q_mm @@ -912,7 +1043,7 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, new_cells(n_new)%D(4) = d_tm new_cells(n_new)%needs_refine = .FALSE. - ! Child 4: Top-Right quadrant (MM, RM, TM, TR) +c --- child 4: top-right quadrant (MM, RM, TM, TR) n_new = n_new + 1 new_cells(n_new)%Q(1) = q_mm new_cells(n_new)%Q(2) = q_rm @@ -928,42 +1059,48 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, END SUBROUTINE subdivide_cell_sub -! ============================================================ +c----------------------------------------------------------------------- +c flatten_cells_to_points_sub: extract unique (Q, D) points from +c the cell array into the module-level Q_store / D_store arrays. +c Uses a brute-force O(n^2) duplicate check which is acceptable +c for moderate cell counts; could be replaced by a hash set for +c very large scans. +c +c BUG FLAG 10 – n_total_corners could exceed MAX_PTS for large +c grids. No guard is present. +c----------------------------------------------------------------------- SUBROUTINE flatten_cells_to_points_sub(num_cells) - + IMPLICIT NONE - - INTEGER, INTENT(IN) :: num_cells - + +c --- arguments + INTEGER, INTENT(IN) :: num_cells ! number of cells to flatten +c --- locals INTEGER :: c, corner, i, j, idx - INTEGER :: n_total_corners - COMPLEX(r8), ALLOCATABLE :: temp_Q(:), temp_D(:) - LOGICAL, ALLOCATABLE :: is_unique(:) - REAL(r8) :: tol - + INTEGER :: n_total_corners ! = num_cells * 4 + COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! all corner Q-values + COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! all corner D-values + LOGICAL, ALLOCATABLE :: is_unique(:) ! dedup mask + REAL(r8) :: tol ! duplicate tolerance + tol = 1.0d-10 n_total_corners = num_cells * 4 - - WRITE(*,*) 'Flatten: num_cells =', num_cells - WRITE(*,*) 'Flatten: n_total_corners =', n_total_corners - + IF (num_cells <= 0) THEN WRITE(*,*) 'ERROR: No cells to flatten' n_pts = 0 RETURN END IF - - ! Allocate temporary arrays + +c --- gather all corners from cells ALLOCATE(temp_Q(n_total_corners)) ALLOCATE(temp_D(n_total_corners)) ALLOCATE(is_unique(n_total_corners)) - - ! Initialize + temp_Q = (0.0d0, 0.0d0) temp_D = (0.0d0, 0.0d0) is_unique = .TRUE. - - ! Gather all corners from cells + idx = 0 DO c = 1, num_cells DO corner = 1, 4 @@ -972,11 +1109,8 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) temp_D(idx) = amr_cells(c)%D(corner) END DO END DO - - WRITE(*,*) 'Gathered corners:', idx - - ! Mark duplicate points as non-unique - ! (Simple O(n²) approach - works fine for moderate sizes) + +c --- mark duplicates (O(n^2) pairwise comparison) DO i = 1, n_total_corners IF (.NOT. is_unique(i)) CYCLE DO j = i + 1, n_total_corners @@ -987,28 +1121,25 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) END IF END DO END DO - - ! Count unique points + +c --- count unique points n_pts = 0 DO i = 1, n_total_corners IF (is_unique(i)) n_pts = n_pts + 1 END DO - - WRITE(*,*) 'Unique points found:', n_pts - + IF (n_pts <= 0) THEN WRITE(*,*) 'ERROR: No unique points found' DEALLOCATE(temp_Q, temp_D, is_unique) RETURN END IF - - ! Allocate output arrays + +c --- copy unique points to module-level output arrays IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) IF (ALLOCATED(D_store)) DEALLOCATE(D_store) ALLOCATE(Q_store(n_pts)) ALLOCATE(D_store(n_pts)) - - ! Copy unique points to output + idx = 0 DO i = 1, n_total_corners IF (is_unique(i)) THEN @@ -1017,12 +1148,9 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) D_store(idx) = temp_D(i) END IF END DO - - WRITE(*,*) 'Copied to output:', idx - - ! Cleanup temporaries + DEALLOCATE(temp_Q, temp_D, is_unique) - + RETURN END SUBROUTINE flatten_cells_to_points_sub END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 7dabfbae8..c9756bce0 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -1,117 +1,161 @@ +c======================================================================= +c MODULE layerinputs_mod +c +c Reads STRIDE NetCDF output and constructs the per-surface +c input arrays required by the SLAYER layer-physics solver. +c +c Subprograms contained: +c 1. read_stride_netcdf_diagonal -- read STRIDE NetCDF, extract +c Deltaprime diagonal, geometry, and equilibrium scalars. +c 2. issurfint -- surface integral by simple +c quadrature (adapted from EQUIL). +c 3. build_inputs -- master driver: reads kinetic +c profiles, evaluates params(), and populates +c slayer_inputs_type for every rational surface. +c======================================================================= MODULE layerinputs_mod - USE inputs, ONLY : read_kin,read_equil,kin,chi1 - USE spline_mod, ONLY : spline_alloc,spline_eval,spline_type, - $ spline_dealloc,spline_int,spline_fit - USE sglobal_mod - USE params_mod - USE netcdf - USE equil_mod, ONLY: equil_read,rzphi,twopi,ro,zo,sq - USE bicube_mod, ONLY: bicube_eval_external,bicube_type - USE slayer_netcdf_mod + USE inputs, ONLY : read_kin, read_equil, kin, chi1 + USE spline_mod, ONLY : spline_alloc, spline_eval, spline_type, + $ spline_dealloc, spline_int, spline_fit + USE sglobal_mod ! SLAYER global scalars, types, constants + USE params_mod ! params() -- compute derived layer params + USE netcdf ! NetCDF Fortran bindings + USE equil_mod, ONLY : equil_read, rzphi, twopi, ro, zo, sq + USE bicube_mod, ONLY : bicube_eval_external, bicube_type + USE slayer_netcdf_mod ! sl_check(), SLAYER NetCDF output IMPLICIT NONE CONTAINS c----------------------------------------------------------------------- c subprogram 1. read_stride_netcdf_diagonal. -c Read STRIDE netcdf file for SLAYER inputs only. +c Read the STRIDE NetCDF file and extract the Deltaprime matrix, +c rational-surface geometry (q, psi_n, shear, dgeo, dr), and +c equilibrium scalars (R0, B_t0, psi0, m_psi, n, resm). +c +c BUG FLAG 1 -- NetCDF variable IDs (dp_id, qr_id, ...) are +c declared but never initialised via nf90_inq_varid before +c being passed to nf90_inquire_attribute. The attribute +c reads for ro, bt0, psio, mpsi, n use *_id variables that +c are still zero at that point; the calls appear to succeed +c only because the NetCDF library treats the id argument as +c a global-attribute flag when it is NF90_GLOBAL (=0). +c Suggested fix: either replace the id arguments with +c NF90_GLOBAL explicitly, or move the nf90_inq_varid calls +c above the nf90_inquire_attribute calls. c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) - ! Input/Output Arguments - CHARACTER(512), INTENT(IN) :: ncfile +c --- arguments (all INTENT(OUT) except ncfile) + CHARACTER(512), INTENT(IN) :: ncfile ! path to STRIDE NetCDF + INTEGER, INTENT(OUT) :: msing ! number of singular surfaces + REAL(r8), DIMENSION(:,:,:), ALLOCATABLE, INTENT(OUT) :: dp_mat + ! Deltaprime matrix (msing x msing x 2) REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: - $ Re_dp_diagonal,Im_dp_diagonal - REAL(r8), DIMENSION(:,:,:), ALLOCATABLE, INTENT(OUT):: dp_mat - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: q_rational, - $ psi_n_rational, shear,dgeo - REAL(r8), DIMENSION(:),ALLOCATABLE,INTENT(OUT) :: r_o,my_bt0, - $ my_psio,mpsi,dr_vals - INTEGER, DIMENSION(:), ALLOCATABLE,INTENT(OUT) :: nn,resm - INTEGER, INTENT(OUT) :: msing - - REAL(r8), DIMENSION(:), ALLOCATABLE :: msing_arr - - ! Internal Variables - INTEGER(kind=nf90_int) :: ncid, stat, r_dim_id, r_dim, - $ dp_id, qr_id,pr_id,dgeo_id,shear_id,ro_id,bt0_id,psio_id, - $ mpsi_id,msing_id,nn_id,resm_id,drr_id ! Explicit kind for NetCDF variables - INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! Explicit kind for NetCDF variables - INTEGER :: i - INTEGER :: bt0_len,ro_len,psio_len,mpsi_len, - $ msing_len,nn_len,dr_len ! Attribute lengths - - WRITE(*,*)"$^$ opening netcdf file",ncfile - - ! Open the NetCDF file - stat = nf90_open(path=ncfile,mode=NF90_WRITE,ncid=ncid) - CALL sl_check(stat) ! Error handling - - stat = nf90_inquire_attribute(ncid,msing_id,"msing", + $ Re_dp_diagonal, ! Re diag(Deltaprime) + $ Im_dp_diagonal ! Im diag(Deltaprime) + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: + $ q_rational, ! rational-surface q values + $ psi_n_rational, ! normalised psi at each surface + $ shear, ! magnetic shear + $ dgeo ! geometric delta (Shafranov shift) + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: + $ r_o, ! major radius R0 + $ my_bt0, ! toroidal field B_t0 + $ my_psio, ! poloidal flux psi_0 + $ mpsi, ! poloidal mode number array + $ dr_vals ! radial width dr at each surface + INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: + $ nn, ! toroidal mode number(s) + $ resm ! resonant poloidal mode numbers + +c --- locals + REAL(r8), DIMENSION(:), ALLOCATABLE :: msing_arr ! temp for reading msing attribute + INTEGER(kind=nf90_int) :: ncid, stat ! NetCDF file id / return status + INTEGER(kind=nf90_int) :: r_dim_id, r_dim ! dimension id / length (unused) + INTEGER(kind=nf90_int) :: dp_id, qr_id, pr_id ! variable ids + INTEGER(kind=nf90_int) :: dgeo_id, shear_id ! variable ids + INTEGER(kind=nf90_int) :: ro_id, bt0_id, psio_id ! attribute ids (see BUG FLAG 1) + INTEGER(kind=nf90_int) :: mpsi_id, msing_id ! attribute ids + INTEGER(kind=nf90_int) :: nn_id, resm_id, drr_id ! variable / attribute ids + INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! NetCDF hyperslab + INTEGER :: i ! loop index + INTEGER :: bt0_len, ro_len, psio_len ! attribute lengths + INTEGER :: mpsi_len, msing_len, nn_len, dr_len ! attribute lengths + +c----------------------------------------------------------------------- +c open the STRIDE NetCDF file and read dimension / attribute data. +c----------------------------------------------------------------------- + WRITE(*,*) '$^$ opening netcdf file', ncfile + + stat = nf90_open(path=ncfile, mode=NF90_WRITE, ncid=ncid) + CALL sl_check(stat) + +c --- read msing (number of singular surfaces) from global attribute + stat = nf90_inquire_attribute(ncid, msing_id, 'msing', $ len = msing_len) CALL sl_check(stat) ALLOCATE(msing_arr(msing_len)) - stat = nf90_get_att(ncid,msing_id,"msing",msing_arr) + stat = nf90_get_att(ncid, msing_id, 'msing', msing_arr) CALL sl_check(stat) + msing = INT(msing_arr(1)) - msing=INT(msing_arr(1)) - - ! Allocate Arrays (based on dimension) - ALLOCATE(Re_dp_diagonal(msing),q_rational(msing), - $ psi_n_rational(msing),shear(msing),dgeo(msing), - $ resm(msing),Im_dp_diagonal(msing),dr_vals(msing)) - ALLOCATE(dp_mat(msing, msing,2)) +c --- allocate output arrays sized by msing + ALLOCATE(Re_dp_diagonal(msing), q_rational(msing), + $ psi_n_rational(msing), shear(msing), dgeo(msing), + $ resm(msing), Im_dp_diagonal(msing), dr_vals(msing)) + ALLOCATE(dp_mat(msing, msing, 2)) - stat = nf90_inquire_attribute(ncid,ro_id,"ro",len = ro_len) +c --- read lengths of scalar / small-array global attributes + stat = nf90_inquire_attribute(ncid, ro_id, 'ro', len=ro_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid,bt0_id,"bt0",len=bt0_len) + stat = nf90_inquire_attribute(ncid, bt0_id, 'bt0', len=bt0_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid,psio_id,"psio",len=psio_len) + stat = nf90_inquire_attribute(ncid, psio_id, 'psio', len=psio_len) CALL sl_check(stat) - - stat = nf90_inquire_attribute(ncid,mpsi_id,"mpsi",len=mpsi_len) + stat = nf90_inquire_attribute(ncid, mpsi_id, 'mpsi', len=mpsi_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid,nn_id,"n",len = nn_len) + stat = nf90_inquire_attribute(ncid, nn_id, 'n', len=nn_len) CALL sl_check(stat) - ALLOCATE(my_bt0(INT(bt0_len)),r_o(INT(ro_len)), + ALLOCATE(my_bt0(INT(bt0_len)), r_o(INT(ro_len)), $ my_psio(INT(psio_len)), - $ mpsi(INT(mpsi_len)),nn(INT(nn_len))) + $ mpsi(INT(mpsi_len)), nn(INT(nn_len))) - ! Get Variable IDs - stat = nf90_inq_varid(ncid, "Delta_prime", dp_id) +c --- obtain NetCDF variable IDs + stat = nf90_inq_varid(ncid, 'Delta_prime', dp_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "q_rational", qr_id) + stat = nf90_inq_varid(ncid, 'q_rational', qr_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "psi_n_rational", pr_id) + stat = nf90_inq_varid(ncid, 'psi_n_rational', pr_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "Delta_geo", dgeo_id) + stat = nf90_inq_varid(ncid, 'Delta_geo', dgeo_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "shear", shear_id) + stat = nf90_inq_varid(ncid, 'shear', shear_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "resm", resm_id) + stat = nf90_inq_varid(ncid, 'resm', resm_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, "dr_rational", drr_id) + stat = nf90_inq_varid(ncid, 'dr_rational', drr_id) CALL sl_check(stat) - ! Get attributes - stat = nf90_get_att(ncid, ro_id, "ro", r_o) + +c --- read global attributes (equilibrium scalars) + stat = nf90_get_att(ncid, ro_id, 'ro', r_o) CALL sl_check(stat) - stat = nf90_get_att(ncid, bt0_id, "bt0", my_bt0) + stat = nf90_get_att(ncid, bt0_id, 'bt0', my_bt0) CALL sl_check(stat) - stat = nf90_get_att(ncid, psio_id, "psio", my_psio) + stat = nf90_get_att(ncid, psio_id, 'psio', my_psio) CALL sl_check(stat) - stat = nf90_get_att(ncid, mpsi_id, "mpsi", mpsi) + stat = nf90_get_att(ncid, mpsi_id, 'mpsi', mpsi) CALL sl_check(stat) - stat = nf90_get_att(ncid, nn_id, "n", nn) + stat = nf90_get_att(ncid, nn_id, 'n', nn) CALL sl_check(stat) - ! Read the diagonal of delta prime. The results will be put on a 1D temporary array. - stat = nf90_get_var(ncid, dp_id, dp_mat,start=(/ 1,1,1 /)) +c --- read variable data: Deltaprime matrix and 1-D surface arrays + stat = nf90_get_var(ncid, dp_id, dp_mat, start=(/1,1,1/)) CALL sl_check(stat) - ! Read 1D variables stat = nf90_get_var(ncid, qr_id, q_rational) CALL sl_check(stat) stat = nf90_get_var(ncid, pr_id, psi_n_rational) @@ -125,97 +169,139 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, stat = nf90_get_var(ncid, drr_id, dr_vals) CALL sl_check(stat) - ! Extract Diagonal, with 3rd index signifying REAL part +c --- extract diagonal of the complex Deltaprime matrix DO i = 1, msing Re_dp_diagonal(i) = dp_mat(i, i, 1) Im_dp_diagonal(i) = dp_mat(i, i, 2) END DO - ! Clean Up + +c --- close file stat = nf90_close(ncid) CALL sl_check(stat) END SUBROUTINE read_stride_netcdf_diagonal c----------------------------------------------------------------------- c subprogram 2. issurfint. -c surface integration by simple method. copied from EQUIL +c Surface integral by simple quadrature, adapted from EQUIL. +c Computes int f(theta) * W(theta) d(theta) where W depends +c on the weight flag `wegt`: +c 0 = jac * |grad psi| +c 1 = R * jac * |grad psi| (R-weighted) +c 2 = jac * |grad psi| / R (1/R-weighted) +c 3 = a * jac * |grad psi| (minor-radius weighted) +c If ave==1, the result is divided by the unweighted surface area. +c +c Geometry is cached via first/fsave/psave to avoid recomputing +c Jacobians when the same surface is queried repeatedly. +c +c BUG FLAG 2 -- `first = .FALSE.` is commented out (line after +c `fsave = fs`). The caching logic therefore never sets +c first=.FALSE., so geometry is recomputed on every call +c even when psi and fs are unchanged. Uncomment the line +c or remove the caching branch entirely. +c +c BUG FLAG 3 -- `z` (toroidal-Z coordinate) is computed inside +c the geometry loop but never used outside it. Remove or +c document its intended purpose. c----------------------------------------------------------------------- FUNCTION issurfint(func,fs,inpsi,wegt,ave, $ fsave,psave,jacs,delpsi,inr,ina,first) c----------------------------------------------------------------------- -c declaration. +c declarations. c----------------------------------------------------------------------- - !IMPLICIT NONE - INTEGER, INTENT(IN) :: fs,wegt,ave - REAL(r8), INTENT(IN) :: inpsi - REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func - - LOGICAL, INTENT(INOUT) :: first - INTEGER, INTENT(INOUT) :: fsave - REAL(r8), INTENT(INOUT) :: psave - REAL(r8),DIMENSION(0:),INTENT(INOUT) :: jacs,delpsi,inr,ina - INTEGER :: itheta, ix, iy +c --- arguments + INTEGER, INTENT(IN) :: fs ! number of poloidal segments + INTEGER, INTENT(IN) :: wegt ! weight flag (0..3) + INTEGER, INTENT(IN) :: ave ! 1 = return surface average + REAL(r8), INTENT(IN) :: inpsi ! normalised psi of surface + REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func ! integrand array + + LOGICAL, INTENT(INOUT) :: first ! .TRUE. on first call (see BUG FLAG 2) + INTEGER, INTENT(INOUT) :: fsave ! cached fs + REAL(r8), INTENT(INOUT) :: psave ! cached psi + REAL(r8), DIMENSION(0:), INTENT(INOUT) :: jacs ! Jacobian cache + REAL(r8), DIMENSION(0:), INTENT(INOUT) :: delpsi ! |grad psi| cache + REAL(r8), DIMENSION(0:), INTENT(INOUT) :: inr ! R(theta) cache + REAL(r8), DIMENSION(0:), INTENT(INOUT) :: ina ! a(theta) cache +c --- return value REAL(r8) :: issurfint - REAL(r8) :: rfac,ineta,injac,inarea - REAL(r8), DIMENSION(1,2) :: w - REAL(r8), DIMENSION(0:fs) :: z,thetas - REAL(r8), dimension(4) :: rzphi_f, rzphi_fx, rzphi_fy - - issurfint=0 - inarea=0 +c --- locals + INTEGER :: itheta ! poloidal loop index + INTEGER :: ix, iy ! bicube grid hints + REAL(r8) :: rfac, ineta, injac, inarea ! geometry intermediates + REAL(r8), DIMENSION(1,2) :: w ! gradient components + REAL(r8), DIMENSION(0:fs) :: z ! Z coords (UNUSED -- BUG FLAG 3) + REAL(r8), DIMENSION(0:fs) :: thetas ! normalised theta grid + REAL(r8), DIMENSION(4) :: rzphi_f, rzphi_fx, rzphi_fy + ! bicube_eval_external outputs +c----------------------------------------------------------------------- +c compute / cache geometry if surface changed. +c [bicube_eval_external]: external bicubic interpolation from EQUIL. +c----------------------------------------------------------------------- + issurfint = 0 + inarea = 0 ix = 0 iy = 0 - IF(first .OR. inpsi/=psave .OR. fs/=fsave)THEN + + IF (first .OR. inpsi /= psave .OR. fs /= fsave) THEN psave = inpsi fsave = fs - !first = .FALSE. - DO itheta=0,fs - thetas(itheta) = REAL(itheta,r8)/REAL(fs,r8) + !first = .FALSE. ! BUG FLAG 2: should be uncommented + DO itheta = 0, fs + thetas(itheta) = REAL(itheta, r8) / REAL(fs, r8) ENDDO - DO itheta=0,fs-1 + DO itheta = 0, fs-1 CALL bicube_eval_external(rzphi, inpsi, thetas(itheta), 1, $ ix, iy, rzphi_f, rzphi_fx, rzphi_fy) - rfac=SQRT(rzphi_f(1)) - ineta=twopi*(thetas(itheta)+rzphi_f(2)) - ina(itheta)=rfac - inr(itheta)=ro+rfac*COS(ineta) - z(itheta)=zo+rfac*SIN(ineta) - injac=rzphi_f(4) - jacs(itheta)=injac - w(1,1)=(1+rzphi_fy(2))*twopi**2*rfac*inr(itheta)/injac - w(1,2)=-rzphi_fy(1)*pi*inr(itheta)/(rfac*injac) - delpsi(itheta)=SQRT(w(1,1)**2+w(1,2)**2) + rfac = SQRT(rzphi_f(1)) + ineta = twopi * (thetas(itheta) + rzphi_f(2)) + ina(itheta) = rfac + inr(itheta) = ro + rfac * COS(ineta) + z(itheta) = zo + rfac * SIN(ineta) + injac = rzphi_f(4) + jacs(itheta) = injac +c gradient magnitude: |grad psi| from metric components + w(1,1) = (1 + rzphi_fy(2)) * twopi**2 + $ * rfac * inr(itheta) / injac + w(1,2) = -rzphi_fy(1) * pi * inr(itheta) + $ / (rfac * injac) + delpsi(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) ENDDO ENDIF - - IF (wegt==0) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ jacs(itheta)*delpsi(itheta)*func(itheta)/fs +c----------------------------------------------------------------------- +c perform weighted surface integral based on wegt flag. +c----------------------------------------------------------------------- + IF (wegt == 0) THEN + DO itheta = 0, fs-1 + issurfint = issurfint + $ + jacs(itheta)*delpsi(itheta)*func(itheta)/fs ENDDO - ELSE IF (wegt==1) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ELSE IF (wegt == 1) THEN + DO itheta = 0, fs-1 + issurfint = issurfint + $ + inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs ENDDO - ELSE IF (wegt==2) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs + ELSE IF (wegt == 2) THEN + DO itheta = 0, fs-1 + issurfint = issurfint + $ + jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs ENDDO - ELSE IF (wegt==3) THEN - DO itheta=0,fs-1 - issurfint=issurfint+ - $ ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + ELSE IF (wegt == 3) THEN + DO itheta = 0, fs-1 + issurfint = issurfint + $ + ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs ENDDO ELSE - STOP "ERROR: issurfint wegt must be in [0,1,2,3]" + STOP 'ERROR: issurfint wegt must be in [0,1,2,3]' ENDIF - - IF (ave==1) THEN - DO itheta=0,fs-1 - inarea=inarea+jacs(itheta)*delpsi(itheta)/fs +c----------------------------------------------------------------------- +c optionally normalise by unweighted surface area. +c----------------------------------------------------------------------- + IF (ave == 1) THEN + DO itheta = 0, fs-1 + inarea = inarea + jacs(itheta)*delpsi(itheta)/fs ENDDO - issurfint=issurfint/inarea + issurfint = issurfint / inarea ENDIF c----------------------------------------------------------------------- c terminate. @@ -224,246 +310,273 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, END FUNCTION issurfint c----------------------------------------------------------------------- c subprogram 3. build_inputs. -c build input arrays for SLAYER +c Master driver: reads the STRIDE NetCDF file, sets up kinetic +c profiles from the input file, reads the equilibrium, and then +c loops over every singular surface to compute derived layer +c parameters via params() and populate slayer_inputs_type. +c +c BUG FLAG 4 -- many local scalars declared here (lpsi, rpsi, +c hdist, sbnosurf, spl, sr, my_inpe, tau_i, b_l, v_a, tau_h, +c rho, tau_v, Qconv, lbeta, qintb, tau_ee_num..chi_par, +c psitor, rhotor, my_rhotor, my_psitor, rfac, jac, wit) +c are never used. They appear to be left over from an earlier +c version. Remove to reduce confusion. +c +c BUG FLAG 5 -- `ising` is declared REAL(r8) but is used as a +c DO-loop index (integer context). This works in Fortran but +c is non-standard in F90+ and may fail with strict compilers. +c Declare as INTEGER. +c +c BUG FLAG 6 -- `zeff = 2.0` is hardcoded on every surface +c (the kin%f(9) alternative is commented out). If the +c kinetic file provides Z_eff, this should use it. +c +c BUG FLAG 7 -- `mrs` and `nrs` are assigned +c `real(mms,4)` / `real(nns,4)` (single precision) but are +c declared INTEGER, so the float is silently truncated. +c They are also never used afterwards. Remove or fix. +c +c BUG FLAG 8 -- the local arrays ne_arr, te_arr, ... mu_i_arr, +c nns_arr, dr_arr, omegas_e_arr, omegas_i_arr are populated +c inside the loop but never used outside it (the old +c slayer_netcdf_inputs call is commented out). Remove or +c gate behind a diagnostic flag. c----------------------------------------------------------------------- SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - ! Inputs - CHARACTER(512), INTENT(IN) :: infile,ncfile +c --- arguments + CHARACTER(512), INTENT(IN) :: infile ! kinetic input file path + CHARACTER(512), INTENT(IN) :: ncfile ! STRIDE NetCDF file path TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in - ! Internals - LOGICAL :: firstsurf - REAL(r8) :: respsi,lpsi,rpsi,hdist,sbnosurf,ising - INTEGER :: zi, zimp, mi, mimp - REAL(r8) :: nfac,tfac,wefac,wpfac,e - +c --- surface-loop control + LOGICAL :: firstsurf ! first-call flag for issurfint + REAL(r8) :: respsi ! normalised psi at current surface + REAL(r8) :: ising ! loop index (BUG FLAG 5: should be INTEGER) +c --- unused scalars (BUG FLAG 4 -- remove) + REAL(r8) :: lpsi, rpsi, hdist, sbnosurf +c --- kinetic profile parameters (for read_kin) + INTEGER :: zi, zimp, mi, mimp ! charge/mass species ids + REAL(r8) :: nfac, tfac, wefac, wpfac ! profile scale factors + REAL(r8) :: e ! elementary charge [C] +c --- unused spline temporaries (BUG FLAG 4) TYPE(spline_type) :: spl TYPE(spline_type) :: sr - - INTEGER :: mms,nns,mrs,nrs,mpsi - - REAL(r8) :: n_e,t_e,n_i,t_i,omega,omega_e,omega_i, - $ my_qval,my_sval,my_bt,my_rs,my_inpe,zeff,R_0,dgeo_val - REAL(r8) :: mu_i,tau_i,b_l,v_a,tau_h,l_n,l_t, - $ rho,tau_v,Qconv,lbeta,qintb,gammafac - REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, - $ sigma_par_2,sigma_par,tau_perp,Wd,vte, - $ dr_val,chi_par_smfp,chi_par_lmfp,chi_par - REAL(r8), DIMENSION(3) :: chi_s - INTEGER :: wit - +c --- mode number workspace + INTEGER :: mms, nns ! poloidal / toroidal mode nums + INTEGER :: mrs, nrs ! UNUSED, wrongly typed (BUG FLAG 7) + INTEGER :: mpsi ! poloidal-flux index from attr +c --- local plasma quantities at current surface + REAL(r8) :: n_e, t_e, n_i, t_i ! densities [m^-3], temperatures [eV] + REAL(r8) :: omega, omega_e, omega_i ! toroidal & diamagnetic freqs [rad/s] + REAL(r8) :: my_qval, my_sval ! safety factor, magnetic shear + REAL(r8) :: my_bt, my_rs, R_0 ! toroidal field, minor radius, major radius + REAL(r8) :: my_inpe ! UNUSED (BUG FLAG 4) + REAL(r8) :: zeff ! effective charge (hardcoded -- BUG FLAG 6) + REAL(r8) :: dgeo_val ! geometric delta (Shafranov) + REAL(r8) :: mu_i ! ion mass ratio to proton + REAL(r8) :: dr_val ! radial width dr at surface + REAL(r8) :: l_n, l_t ! density / temperature gradient lengths + REAL(r8) :: gammafac ! growth-rate conversion factor + REAL(r8), DIMENSION(3) :: chi_s ! chi_perp, chi_tor, kappa +c --- unused derived quantities (BUG FLAG 4 -- left over from params duplication) + REAL(r8) :: tau_i, b_l, v_a, tau_h, rho, tau_v + REAL(r8) :: Qconv, lbeta, qintb + REAL(r8) :: tau_ee_num, tau_ee_denom, tau_ee + REAL(r8) :: sigma_par_1, sigma_par_2, sigma_par + REAL(r8) :: tau_perp, Wd, vte + REAL(r8) :: chi_par_smfp, chi_par_lmfp, chi_par + INTEGER :: wit +c --- unused flux-coordinate arrays (BUG FLAG 4) REAL(r8), DIMENSION(0:128) :: psitor, rhotor - REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor,my_psitor + REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor, my_psitor +c --- STRIDE data (from read_stride_netcdf_diagonal) REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: dp_mat - REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal,dr_arr, - $ q_rational,shear,r_o,my_bt0,my_psio,mpsi_arr, - $ omegas_e_arr,omegas_i_arr,Im_dp_diagonal,dr_vals, - $ psi_n_rational,dgeo - REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr,te_arr,ni_arr, - $ ti_arr,zeff_arr,bt_arr,rs_arr,R0_arr,mu_i_arr - INTEGER,DIMENSION(:),ALLOCATABLE :: nn,resm,nns_arr - INTEGER :: msing,i,mthsurf - REAL(r8), DIMENSION(0:512) :: unitfun - INTEGER :: fsave - REAL(r8) :: psave - REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs,delpsi,rsurf,asurf - REAL(r8) :: rfac,jac,a_surf -c----------------------------------------------------------------------- -c Read in STRIDE netcdf + REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, + $ Im_dp_diagonal, q_rational, psi_n_rational, + $ shear, dgeo, r_o, my_bt0, my_psio, mpsi_arr, + $ dr_vals, dr_arr +c --- local per-surface kinetic arrays (UNUSED outside loop -- BUG FLAG 8) + REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr, te_arr, + $ ni_arr, ti_arr, zeff_arr, bt_arr, rs_arr, R0_arr, + $ mu_i_arr, omegas_e_arr, omegas_i_arr + INTEGER, DIMENSION(:), ALLOCATABLE :: nn, resm, nns_arr +c --- surface-integral workspace + INTEGER :: msing, i, mthsurf ! surface count, loop idx, theta pts + REAL(r8), DIMENSION(0:512) :: unitfun ! unit function for area integral + INTEGER :: fsave ! cached fs for issurfint + REAL(r8) :: psave ! cached psi for issurfint + REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs, delpsi, rsurf, asurf + REAL(r8) :: rfac, jac, a_surf ! rfac/jac UNUSED (BUG FLAG 4) +c----------------------------------------------------------------------- +c read STRIDE NetCDF: Deltaprime matrix, geometry, equilibrium scalars. c----------------------------------------------------------------------- - CALL read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational, $ psi_n_rational,dgeo,shear,r_o,my_bt0,my_psio,dr_vals, $ mpsi_arr,nn,resm) - WRITE(*,*)"msing_out=",msing - WRITE(*,*)"Re_dp_diagonal=",Re_dp_diagonal - WRITE(*,*)"Im_dp_diagonal=",Im_dp_diagonal - WRITE(*,*)"q_rational=",q_rational - WRITE(*,*)"psi_n_rational=",psi_n_rational - WRITE(*,*)"dgeo=",dgeo - WRITE(*,*)"shear=",shear - WRITE(*,*)"r_o=",r_o - WRITE(*,*)"my_bt0=",my_bt0 - WRITE(*,*)"my_psio=",my_psio - WRITE(*,*)"nn=",nn - WRITE(*,*)"resm=",resm mpsi = INT(mpsi_arr(1)) - mthsurf = 512 ! Hardcoded, but this is a default value + mthsurf = 512 ! poloidal segments for surface integral -c Allocate SLAYER input type arrays - ALLOCATE(sl_in%qval_arr(msing),sl_in%omegas_arr(msing), - $ sl_in%omegas_e_arr(msing),sl_in%dp_matrix(msing,msing), - $ sl_in%omegas_i_arr(msing),!sl_in%chi_prof_arr(msing), - $ sl_in%Q_e_arr(msing),sl_in%Q_i_arr(msing), +c --- allocate slayer_inputs_type arrays (one entry per surface) + ALLOCATE(sl_in%qval_arr(msing), sl_in%omegas_arr(msing), + $ sl_in%omegas_e_arr(msing), sl_in%dp_matrix(msing,msing), + $ sl_in%omegas_i_arr(msing), + $ sl_in%Q_e_arr(msing), sl_in%Q_i_arr(msing), $ sl_in%psi_n_arr(msing), - $ sl_in%Re_dp_arr(msing),sl_in%Im_dp_arr(msing), - $ sl_in%d_crit_arr(msing),sl_in%P_tor_arr(msing), - $ sl_in%P_perp_arr(msing),sl_in%tau_arr(msing), + $ sl_in%Re_dp_arr(msing), sl_in%Im_dp_arr(msing), + $ sl_in%d_crit_arr(msing), sl_in%P_tor_arr(msing), + $ sl_in%P_perp_arr(msing), sl_in%tau_arr(msing), $ sl_in%D_norm_arr(msing), - $ sl_in%d_beta_arr(msing),sl_in%gammafac_arr(msing), - $ sl_in%c_beta_arr(msing),sl_in%lu_arr(msing), + $ sl_in%d_beta_arr(msing), sl_in%gammafac_arr(msing), + $ sl_in%c_beta_arr(msing), sl_in%lu_arr(msing), $ sl_in%Qconv_arr(msing)) -c Allocate local kinetic arrays - ALLOCATE(ne_arr(msing),te_arr(msing),ni_arr(msing), - $ ti_arr(msing),zeff_arr(msing),bt_arr(msing),rs_arr(msing), - $ R0_arr(msing),mu_i_arr(msing),nns_arr(msing),dr_arr(msing), - $ omegas_e_arr(msing),omegas_i_arr(msing)) +c --- allocate local kinetic arrays (diagnostic, BUG FLAG 8) + ALLOCATE(ne_arr(msing), te_arr(msing), ni_arr(msing), + $ ti_arr(msing), zeff_arr(msing), bt_arr(msing), + $ rs_arr(msing), R0_arr(msing), mu_i_arr(msing), + $ nns_arr(msing), dr_arr(msing), + $ omegas_e_arr(msing), omegas_i_arr(msing)) - ALLOCATE(jacs(0:mthsurf),delpsi(0:mthsurf), - $ rsurf(0:mthsurf),asurf(0:mthsurf)) + ALLOCATE(jacs(0:mthsurf), delpsi(0:mthsurf), + $ rsurf(0:mthsurf), asurf(0:mthsurf)) c----------------------------------------------------------------------- -c set up kin +c set up kinetic profiles and equilibrium. +c [read_kin]: external, reads kinetic input file into kin spline. +c [equil_read]: external, reads equilibrium into EQUIL module. c----------------------------------------------------------------------- - ! manually set the kinetic profiles - zi = 1 - zimp = 6 - mi = 2 - mimp = 12 - nfac = 1.0 - tfac = 1.0 + zi = 1 ! main-ion charge + zimp = 6 ! impurity charge (carbon) + mi = 2 ! main-ion mass (deuterium) + mimp = 12 ! impurity mass + nfac = 1.0 + tfac = 1.0 wefac = 1.0 wpfac = 1.0 - e=1.6021917e-19 - chi1 = twopi*my_psio(1) + e = 1.6021917e-19 ! elementary charge [C] + chi1 = twopi * my_psio(1) ! total poloidal flux (module-level) CALL read_kin(infile,zi,zimp,mi,mimp,nfac, $ tfac,wefac,wpfac,.false.) CALL equil_read(out_unit) - ! Input Delta' matrix - sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1),dp_mat(:,:,2)) +c store full complex Deltaprime matrix in sl_in + sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1), dp_mat(:,:,2)) c----------------------------------------------------------------------- -c loop across singular surfaces, evaluate spline quantities. +c loop over singular surfaces: evaluate kinetic/equilibrium +c quantities via spline interpolation, compute derived layer +c parameters via params(), and populate sl_in arrays. c----------------------------------------------------------------------- - DO ising=1,msing - - respsi = psi_n_rational(ising) + DO ising = 1, msing + respsi = psi_n_rational(ising) ! normalised psi firstsurf = .TRUE. unitfun = 1 - ! Minor radius! +c compute flux-surface-averaged minor radius a_surf = issurfint(unitfun,mthsurf,respsi,3,1, $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) c----------------------------------------------------------------------- -c SLAYER inputs for sing surface +c evaluate kinetic splines at this surface. +c [spline_eval]: external, evaluates kin spline at respsi. +c kin%f(1..5) = n_i, n_e, t_i, t_e, omega (SI units) +c kin%f1(1..5) = d/d(psi_n) of the above c----------------------------------------------------------------------- CALL spline_eval(kin,respsi,1) - omega_i=-twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) - $ -twopi*kin%f1(3)/(e*zi*chi1) - omega_e=twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) - $ +twopi*kin%f1(4)/(e*chi1) +c diamagnetic frequencies (rad/s) + omega_i = -twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) + $ -twopi*kin%f1(3)/(e*zi*chi1) + omega_e = twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) + $ +twopi*kin%f1(4)/(e*chi1) sl_in%omegas_e_arr(ising) = omega_e sl_in%omegas_i_arr(ising) = omega_i +c extract local plasma quantities from spline n_e = kin%f(2) - t_e = kin%f(4)/e + t_e = kin%f(4) / e ! convert J -> eV n_i = kin%f(1) - t_i = kin%f(3)/e + t_i = kin%f(3) / e - zeff = 2.0!kin%f(9) - - omega = kin%f(5) - my_qval = q_rational(ising)!sq%f(4) - my_sval = shear(ising) + zeff = 2.0 ! hardcoded (BUG FLAG 6) + + omega = kin%f(5) + my_qval = q_rational(ising) + my_sval = shear(ising) dgeo_val = dgeo(ising) - my_bt = my_bt0(1) - my_rs = a_surf - R_0 = r_o(1) - mu_i = 2.0 - dr_val = dr_vals(ising) - + my_bt = my_bt0(1) + my_rs = a_surf + R_0 = r_o(1) + mu_i = 2.0 ! deuterium + dr_val = dr_vals(ising) + +c transport coefficients from caller-provided arrays chi_s(1) = sl_in%chi_p_arr(ising) ! chi_perp chi_s(2) = sl_in%chi_t_arr(ising) ! chi_tor chi_s(3) = sl_in%kappa_arr(ising) ! kappa (thermal cond.) - ne_arr(ising) = n_e - te_arr(ising) = t_e - ni_arr(ising) = n_i - ti_arr(ising) = t_i +c store local kinetic arrays (BUG FLAG 8: unused) + ne_arr(ising) = n_e + te_arr(ising) = t_e + ni_arr(ising) = n_i + ti_arr(ising) = t_i zeff_arr(ising) = zeff - bt_arr(ising) = my_bt - rs_arr(ising) = my_rs - R0_arr(ising) = R_0 + bt_arr(ising) = my_bt + rs_arr(ising) = my_rs + R0_arr(ising) = R_0 mu_i_arr(ising) = mu_i mms = resm(ising) nns = nn(1) - mrs = real(mms,4) - nrs = real(nns,4) - + mrs = real(mms, 4) ! BUG FLAG 7: float -> integer truncation + nrs = real(nns, 4) nns_arr(ising) = nn(1) - nr = nn(1) + nr = nn(1) ! module-level toroidal mode number l_n = 0.0 l_t = 0.0 - WRITE(*,*)"$^$ calling params()" +c----------------------------------------------------------------------- +c compute derived layer parameters. +c [params]: external (params_mod), sets module-level globals +c tau, tau_r, tauk, lu, c_beta, d_beta, D_norm, P_perp, +c P_tor, dc_tmp, etc. in sglobal_mod. +c----------------------------------------------------------------------- CALL params(n_e,t_e,t_i,omega,chi_s,dr_val,dgeo_val, - $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i,zeff,.false.) - -!!!!!!!!!!! - gammafac = (my_rs*Re_dp_diagonal(ising))/tau_r ! scalar to convert thickness into growth rate - - sl_in%qval_arr(ising) = INT(my_qval) - sl_in%lu_arr(ising)=lu - sl_in%Q_e_arr(ising)=-tauk*omega_e ! skipping params() calculation - sl_in%Q_i_arr(ising)=-tauk*omega_i ! skipping params() calculation - sl_in%c_beta_arr(ising)=c_beta - sl_in%d_beta_arr(ising)=d_beta - sl_in%D_norm_arr(ising)=D_norm - sl_in%tau_arr(ising)=tau - sl_in%omegas_arr(ising) = omega - sl_in%psi_n_arr(ising) = respsi - sl_in%gammafac_arr(ising) = gammafac - sl_in%Re_dp_arr(ising) = Re_dp_diagonal(ising) - sl_in%Im_dp_arr(ising) = Im_dp_diagonal(ising) - sl_in%d_crit_arr(ising) = dc_tmp - sl_in%P_perp_arr(ising) = P_perp - sl_in%P_tor_arr(ising) = P_tor - sl_in%Qconv_arr(ising) = tauk - ENDDO + $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i, + $ zeff,.false.) + +c growth-rate conversion factor: Deltaprime -> gamma + gammafac = (my_rs * Re_dp_diagonal(ising)) / tau_r - !WRITE(*,*)"msing=",msing - !WRITE(*,*)"qval_arr=",qval_arr - !WRITE(*,*)"ne_arr=",ne_arr - !WRITE(*,*)"te_arr=",te_arr - !WRITE(*,*)"ni_arr=",ni_arr - !WRITE(*,*)"ti_arr=",ti_arr - !WRITE(*,*)"zeff_arr=",zeff_arr - !WRITE(*,*)"shear=",shear - !WRITE(*,*)"bt_arr=",bt_arr - !WRITE(*,*)"rs_arr=",rs_arr - !WRITE(*,*)"R0_arr=",R0_arr - !WRITE(*,*)"resm=",resm - !WRITE(*,*)"nns_arr=",nns_arr - !WRITE(*,*)"inc_beta_arr=",inc_beta_arr - !WRITE(*,*)"inds_arr=",inds_arr - !WRITE(*,*)"intau_arr=",intau_arr - !WRITE(*,*)"inpr_arr=",inpr_arr - !WRITE(*,*)"inpe_arr=",inpe_arr - !WRITE(*,*)"omegas_arr=",omegas_arr - !WRITE(*,*)"omegas_e_arr=",omegas_e_arr - !WRITE(*,*)"omegas_i_arr=",omegas_i_arr - !WRITE(*,*)"Re_deltaprime_arr=",Re_deltaprime_arr - !WRITE(*,*)"Im_deltaprime_arr=",Im_deltaprime_arr - !stop - !CALL slayer_netcdf_inputs(msing,qval_arr,ne_arr,te_arr,ni_arr, - !$ ti_arr,zeff_arr,shear,bt_arr,rs_arr,R0_arr, - !$ resm,nns_arr,inc_beta_arr,inds_arr, - !$ intau_arr,inpr_arr,inpe_arr,inQ_arr,omegas_arr, - !$ omegas_e_arr,omegas_i_arr, - !$ Re_deltaprime_arr,Im_deltaprime_arr) +c----------------------------------------------------------------------- +c populate sl_in for this surface from params() globals. +c----------------------------------------------------------------------- + sl_in%qval_arr(ising) = INT(my_qval) + sl_in%lu_arr(ising) = lu + sl_in%Q_e_arr(ising) = -tauk * omega_e + sl_in%Q_i_arr(ising) = -tauk * omega_i + sl_in%c_beta_arr(ising) = c_beta + sl_in%d_beta_arr(ising) = d_beta + sl_in%D_norm_arr(ising) = D_norm + sl_in%tau_arr(ising) = tau + sl_in%omegas_arr(ising) = omega + sl_in%psi_n_arr(ising) = respsi + sl_in%gammafac_arr(ising)= gammafac + sl_in%Re_dp_arr(ising) = Re_dp_diagonal(ising) + sl_in%Im_dp_arr(ising) = Im_dp_diagonal(ising) + sl_in%d_crit_arr(ising) = dc_tmp + sl_in%P_perp_arr(ising) = P_perp + sl_in%P_tor_arr(ising) = P_tor + sl_in%Qconv_arr(ising) = tauk + ENDDO c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/slayer/params.f b/slayer/params.f index dcf069788..c68e0032e 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -1,172 +1,264 @@ +c======================================================================= +c MODULE params_mod +c +c Computes the normalised layer-physics parameters needed by the +c SLAYER dispersion solver from dimensional equilibrium and +c kinetic-profile inputs. +c +c Subprograms contained: +c 1. params -- derive all normalised quantities (Q, ds, c_beta, +c D_norm, P_perp, P_tor, lu, delta_crit, ...) for a +c single rational surface and store them in sglobal_mod. +c======================================================================= MODULE params_mod - USE sglobal_mod + USE sglobal_mod ! SLAYER global scalars, types, constants IMPLICIT NONE CONTAINS c----------------------------------------------------------------------- -c calculate parameters. +c subprogram 1. params. +c Compute all derived layer-physics parameters for a single +c rational surface from dimensional equilibrium / kinetic inputs. +c Results are written to module-level variables in sglobal_mod +c (tau, tau_r, tauk, lu, Q, Q_e, Q_i, ds, c_beta, d_beta, +c D_norm, P_perp, P_tor, delta_n, dc_tmp, eta, visc, rho_s, ...). +c +c BUG FLAG 1 -- `pr` (magnetic Prandtl number) and `pe` are read +c from sglobal_mod but never set within this routine. They must +c be initialised elsewhere before calling params(), otherwise +c `tau_v = tau_r / pr` will divide by zero or garbage. +c Suggested fix: add pr/pe as INTENT(IN) arguments, or +c document the required initialisation order. +c +c BUG FLAG 2 -- Several debug WRITE statements print to stdout +c unconditionally on every call. For a public release these +c should either be removed or guarded behind `params_check`. c----------------------------------------------------------------------- SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - REAL(r8), INTENT(IN) :: n_e,t_e,t_i,omega,dr_val,dgeo_val, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff +c --- arguments + REAL(r8), INTENT(IN) :: n_e ! electron density [m^-3] + REAL(r8), INTENT(IN) :: t_e ! electron temperature [eV] + REAL(r8), INTENT(IN) :: t_i ! ion temperature [eV] + REAL(r8), INTENT(IN) :: omega ! toroidal rotation [rad/s] + REAL(r8), INTENT(IN) :: dr_val ! radial width dr at surface + REAL(r8), INTENT(IN) :: dgeo_val ! geometric delta (Shafranov shift factor) + REAL(r8), INTENT(IN) :: l_n ! density gradient length + REAL(r8), INTENT(IN) :: l_t ! temperature gradient length + REAL(r8), INTENT(IN) :: qval ! safety factor + REAL(r8), INTENT(IN) :: sval ! magnetic shear + REAL(r8), INTENT(IN) :: bt ! toroidal field [T] + REAL(r8), INTENT(IN) :: rs ! minor radius [m] + REAL(r8), INTENT(IN) :: R0 ! major radius [m] + REAL(r8), INTENT(IN) :: mu_i ! ion mass ratio to proton + REAL(r8), INTENT(IN) :: zeff ! effective charge REAL(r8), DIMENSION(3), INTENT(IN) :: chis + ! (1) chi_perp [m^2/s] + ! (2) chi_tor [m^2/s] + ! (3) kappa [m^2/s] + LOGICAL, INTENT(IN) :: params_check ! .TRUE. = print diagnostics + +c --- local variables: basic plasma + REAL(r8) :: rho ! mass density [kg/m^3] + REAL(r8) :: b_l ! characteristic magnetic field [T] + REAL(r8) :: v_a ! Alfvén velocity [m/s] + REAL(r8) :: lbeta ! local beta-related quantity +c --- local variables: electron collision time (Braginskii) + REAL(r8) :: tau_ee_num ! numerator of tau_ee formula + REAL(r8) :: tau_ee_denom ! denominator of tau_ee formula + REAL(r8) :: tau_ee ! electron-electron collision time [s] +c --- local variables: parallel conductivity (Spitzer-Härm) + REAL(r8) :: sigma_par_1 ! neoclassical correction factor + REAL(r8) :: sigma_par_2 ! classical conductivity [1/(Ohm*m)] + REAL(r8) :: sigma_par ! parallel conductivity [1/(Ohm*m)] +c --- local variables: timescales + REAL(r8) :: tau_i ! ion collision time [s] + REAL(r8) :: tau_h ! Alfvén transit time [s] + REAL(r8) :: tau_v ! viscous time [s] + REAL(r8) :: tau_tor ! toroidal diffusion time [s] + REAL(r8) :: tau_perp ! perp. diffusion time [s] +c --- local variables: delta_crit iteration + REAL(r8) :: vte ! thermal electron speed [m/s] + REAL(r8) :: chi_par_smfp ! chi_par in short mfp limit + REAL(r8) :: chi_par_lmfp ! chi_par in long mfp limit + REAL(r8) :: chi_par ! effective parallel thermal cond. + REAL(r8) :: Wd ! magnetic island width proxy + INTEGER :: wit ! iteration counter +c --- local variables: unused intermediates + REAL(r8) :: Qconv ! (shadowed by module-level Qconv) + REAL(r8) :: K_val, Csq ! kappa/eta and composite quantity - LOGICAL, INTENT(IN) :: params_check - - REAL(r8) :: rho,b_l,v_a,Qconv,K_val,Csq,tau_tor, - $ lbeta,tau_i,tau_h,tau_v - REAL(r8) :: tau_ee_num,tau_ee_denom,tau_ee,sigma_par_1, - $ sigma_par_2,sigma_par,tau_perp,Wd,vte, - $ chi_par_smfp,chi_par_lmfp,chi_par - INTEGER :: wit - - WRITE(*,*)"params.f n_e: ",n_e - WRITE(*,*)"params.f t_e: ",t_e - WRITE(*,*)"params.f t_i: ",t_i - +c----------------------------------------------------------------------- +c Coulomb logarithm, basic plasma quantities, and Spitzer +c resistivity. +c----------------------------------------------------------------------- lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) - ! mu_i: ion mass ratio to proton - tau= t_i/t_e ! ratio of ion to electron temperature - tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5/(n_e*lnLamb) ! ion colls. - eta= 1.65e-9*lnLamb/(t_e/1e3)**1.5 ! spitzer resistivity (wesson) - rho=(mu_i*m_p)*n_e ! mass density + tau = t_i / t_e ! T_i / T_e + tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5 + $ / (n_e*lnLamb) ! ion collision time [s] + eta = 1.65e-9*lnLamb / (t_e/1e3)**1.5 ! Spitzer resistivity (Wesson) + rho = (mu_i*m_p)*n_e ! mass density [kg/m^3] - tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5)* - $ (eps0**2.0)*(m_e**0.5)*(t_e**1.5) +c----------------------------------------------------------------------- +c Electron-electron collision time (Braginskii) and Spitzer-Härm +c parallel conductivity. +c----------------------------------------------------------------------- + tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5) + $ *(eps0**2.0)*(m_e**0.5)*(t_e**1.5) tau_ee_denom = lnLamb*(chag**2.5)*n_e - tau_ee = tau_ee_num / tau_ee_denom - !tau_ee = 1.09e16 * (t_e/1.0e3)**1.5 / (n_e * lnLamb) - + tau_ee = tau_ee_num / tau_ee_denom - sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) / - $ (Zeff*(SQRT(2.0) + Zeff)) + sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) + $ / (Zeff*(SQRT(2.0) + Zeff)) sigma_par_2 = (n_e * (chag**2.0) * tau_ee) / m_e - sigma_par = sigma_par_1*sigma_par_2 + sigma_par = sigma_par_1 * sigma_par_2 - b_l=(nr/mr)*rs*sval*bt/R0 ! characteristic magnetic field - v_a=b_l/(mu0*rho)**0.5 ! alfven velocity - rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Lamour by elec. Temp. - d_i = ( (mu_i*m_p)/(n_e * (chag**2) * mu0) )**0.5 ! collisionless ion skin depth +c----------------------------------------------------------------------- +c Characteristic field, Alfvén speed, length scales, and +c fundamental timescales. +c Note: mr, nr (rational m/n) and nn (toroidal mode number) are +c module-level variables set before calling params(). +c----------------------------------------------------------------------- + b_l = (nr/mr)*rs*sval*bt/R0 ! characteristic B [T] + v_a = b_l / (mu0*rho)**0.5 ! Alfvén velocity [m/s] + rho_s = 1.02e-4*(mu_i*t_e)**0.5 / bt ! ion Larmor at T_e [m] + d_i = ( (mu_i*m_p) / (n_e*(chag**2)*mu0) )**0.5 + ! ion skin depth [m] - tau_h=R0*(mu0*rho)**0.5/(nn*sval*bt) ! alfven time across surface - !tau_r=mu0*rs**2.0/eta ! resistive time scale - tau_r=mu0*(rs**2.0)*(sigma_par) ! R. Fitzpatrick resistive time scale - tau_v=tau_r/pr !rho*rs**2.0/visc ! viscous time scale + tau_h = R0*(mu0*rho)**0.5 / (nn*sval*bt) ! Alfvén time [s] + tau_r = mu0*(rs**2.0)*sigma_par ! resistive time [s] (Fitzpatrick) + tau_v = tau_r / pr ! viscous time [s] (BUG FLAG 1) - ! this one must be anomalous. calculated back from pr. - visc= rho*rs**2.0/tau_v - - lu=tau_r/tau_h ! Lundquist number - - omega_e=-t_e/(bt*R0)*(1.0/l_n+1.0/l_t)*qval ! elec. diamag - omega_i=t_i/(bt*R0)*(1.0/l_n+1.0/l_t)*qval ! ion diamag +c back-compute anomalous viscosity from tau_v + visc = rho*rs**2.0 / tau_v - ! now calculate the main 7 normalized parameters. +c Lundquist number + lu = tau_r / tau_h + +c----------------------------------------------------------------------- +c Diamagnetic frequencies and normalised Q parameters. +c Qconv converts dimensional frequencies to the normalised Q +c used in the SLAYER dispersion relation (Cole scaling). +c----------------------------------------------------------------------- + omega_e = -t_e/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval ! electron diamagnetic [rad/s] + omega_i = t_i/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval ! ion diamagnetic [rad/s] - Qconv=lu**(1.0/3.0)*tau_h ! conversion to Qs based on Cole - tauk = Qconv + Qconv = lu**(1.0/3.0) * tau_h ! frequency normalisation (Cole) + tauk = Qconv ! stored in sglobal_mod - ! note Q depends on Qconv even if omega is fixed. - Q=Qconv*omega - Q_e=-Qconv*omega_e - Q_i=-Qconv*omega_i - - ! This is the most critical parameter - ds=lu**(1.0/3.0)*rho_s/rs ! conversion based on Cole. + Q = Qconv * omega ! normalised rotation frequency + Q_e = -Qconv * omega_e ! normalised electron diamagnetic + Q_i = -Qconv * omega_i ! normalised ion diamagnetic - lbeta=(5.0/3.0)*mu0*n_e*chag*(t_e+t_i)/bt**2.0 - c_beta=(lbeta/(1.0+lbeta))**0.5 +c normalised ion Larmor radius (critical stability parameter) + ds = lu**(1.0/3.0) * rho_s / rs - ! CALCULATE Ps - ! chi_tor = the anomalous perpendicular ion momentum diffusivity at the rational surface - ! chi_perp = the anomalous perpendicular energy diffusivity at the rational surface +c----------------------------------------------------------------------- +c Plasma beta and Prandtl-number-like transport ratios. +c +c BUG FLAG 3 -- The K_val/Csq block (kappa-based P_perp) is +c immediately overridden by the tau_perp definition below it, +c so K_val and the IF(ABS(Csq)>0) branch are dead code. +c Either remove the dead block or delete the override. +c----------------------------------------------------------------------- + lbeta = (5.0/3.0)*mu0*n_e*chag*(t_e+t_i) / bt**2.0 + c_beta = (lbeta / (1.0+lbeta))**0.5 - K_val = chis(3)/eta ! kappa/eta - Csq = (c_beta**2.0 + (1.0-c_beta**2.0)*K_val ) ! = P_perp +c --- dead code begin (BUG FLAG 3) --- + K_val = chis(3) / eta + Csq = c_beta**2.0 + (1.0 - c_beta**2.0)*K_val IF (ABS(Csq) > 0.0) THEN P_perp = Csq ELSE - tau_perp = ( rs**2.0 ) / chis(1) + tau_perp = (rs**2.0) / chis(1) END IF +c --- dead code end (BUG FLAG 3) --- - tau_perp = ( rs**2.0 ) / chis(1) - P_perp = tau_r / tau_perp ! perpendicular magnetic Prandtl number +c effective perpendicular and toroidal Prandtl numbers + tau_perp = (rs**2.0) / chis(1) + P_perp = tau_r / tau_perp ! perp magnetic Prandtl number - tau_tor = ( rs**2.0 ) / chis(2) - P_tor = tau_r / tau_tor ! toroidal magnetic Prandtl number + tau_tor = (rs**2.0) / chis(2) + P_tor = tau_r / tau_tor ! toroidal magnetic Prandtl number - ! this is using Fitzpatrick's tau', we need tau eventually - d_beta = c_beta*d_i - D_norm = (d_beta/rs)*(lu**(1.0/3.0))*(tau/(1+tau))**(0.5) +c----------------------------------------------------------------------- +c Normalised beta-related width and Delta norm factor. +c----------------------------------------------------------------------- +c d_beta uses Fitzpatrick's definition (tau' form) + d_beta = c_beta * d_i + D_norm = (d_beta/rs) * lu**(1.0/3.0) + $ * (tau/(1+tau))**(0.5) - delta_n=lu**(1.0/3.0)/rs ! norm factor for delta primes + delta_n = lu**(1.0/3.0) / rs ! normalisation for Delta values - ! Calculate Delta_crit +c----------------------------------------------------------------------- +c Critical Deltaprime (dc_tmp) via iterative chi_parallel calculation. +c The island-width Wd is iterated 10 times to converge the +c short-mfp / long-mfp interpolation for chi_parallel. +c dc_type (from sglobal_mod) selects the formula: +c 'lar' -- cylindrical (Lutjens) +c 'rfitzp' -- R. Fitzpatrick +c 'toroidal' -- toroidal geometry using dgeo_val +c default -- dc_tmp = 0 +c +c BUG FLAG 4 -- iteration count 10 is hardcoded with no +c convergence check. Consider adding a tolerance test. +c----------------------------------------------------------------------- IF (ABS(dr_val) > 0.0) THEN - vte = SQRT((2.0*(t_e*chag))/m_e) - chi_par_smfp = (1.581*tau_ee*(vte**2.0))/ - $ (1.0+0.2535*Zeff) - WRITE(*,*)"params.f Zeff: ",Zeff - - ! chis(1) = chi_perp - Wd = 0.1 - DO wit = 1,10 - - chi_par_lmfp = (2.0*R0*vte)/(SQRT(pi)*nr*sval*Wd) - chi_par = (chi_par_smfp*chi_par_lmfp)/ - $ (chi_par_smfp+chi_par_lmfp) - Wd = SQRT(8.0)*((chis(1)/chi_par)**0.25)* - $ (1.0/SQRT((rs/R0)*sval*nr)) - - END DO - WRITE(*,*)"params.f Wd: ",Wd - WRITE(*,*)"params.f chis(1): ",chis(1) - WRITE(*,*)"params.f chis(2): ",chis(2) - WRITE(*,*)"params.f chis(3): ",chis(3) - WRITE(*,*)"params.f rs: ",rs - WRITE(*,*)"params.f R0: ",R0 - WRITE(*,*)"params.f sval: ",sval - WRITE(*,*)"params.f nr: ",nr - - SELECT CASE(dc_type) - CASE("lar") - dc_tmp=0.5*(-dr_val)*(pi**1.5)*((chi_par/chis(1))**0.25)* - $ ( (nr*sval)/(R0*rs) )**0.5 - CASE("rfitzp") - dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val)/Wd - CASE("toroidal") - dc_tmp = 0.5*(-dr_val)*(pi**1.5)* - $ ((chi_par/chis(1))**0.25)*dgeo_val - CASE default - dc_tmp = 0.0 - END SELECT - - WRITE(*,*)"params.f dr_val: ",dr_val - WRITE(*,*)"params.f dgeo_val: ",dgeo_val - WRITE(*,*)"params.f dc_tmp: ",dc_tmp + + vte = SQRT((2.0*(t_e*chag)) / m_e) + chi_par_smfp = (1.581*tau_ee*(vte**2.0)) + $ / (1.0 + 0.2535*Zeff) + + Wd = 0.1 ! initial guess + DO wit = 1, 10 + chi_par_lmfp = (2.0*R0*vte) + $ / (SQRT(pi)*nr*sval*Wd) + chi_par = (chi_par_smfp*chi_par_lmfp) + $ / (chi_par_smfp + chi_par_lmfp) + Wd = SQRT(8.0)*((chis(1)/chi_par)**0.25) + $ * (1.0/SQRT((rs/R0)*sval*nr)) + END DO + + SELECT CASE(dc_type) + CASE('lar') + dc_tmp = 0.5*(-dr_val)*(pi**1.5) + $ *((chi_par/chis(1))**0.25) + $ *( (nr*sval)/(R0*rs) )**0.5 + CASE('rfitzp') + dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val) / Wd + CASE('toroidal') + dc_tmp = 0.5*(-dr_val)*(pi**1.5) + $ *((chi_par/chis(1))**0.25)*dgeo_val + CASE default + dc_tmp = 0.0 + END SELECT ELSE - dc_tmp = 0.0 + dc_tmp = 0.0 END IF - ! quick diagnostics. +c----------------------------------------------------------------------- +c optional diagnostics (guarded by params_check flag). +c----------------------------------------------------------------------- IF (params_check) THEN - WRITE(*,*)"eta=",eta - WRITE(*,*)"S=",lu - WRITE(*,*)"Q=",Q - WRITE(*,*)"Q_e=",Q_e - WRITE(*,*)"Q_i=",Q_i - WRITE(*,*)"ds=",ds - WRITE(*,*)"c_beta=",c_beta + WRITE(*,*) 'eta = ', eta + WRITE(*,*) 'S = ', lu + WRITE(*,*) 'Q = ', Q + WRITE(*,*) 'Q_e = ', Q_e + WRITE(*,*) 'Q_i = ', Q_i + WRITE(*,*) 'ds = ', ds + WRITE(*,*) 'c_beta = ', c_beta ENDIF - +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- RETURN - END SUBROUTINE params - + END SUBROUTINE params + END MODULE params_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 3711e695c..7c94683c9 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -1,77 +1,207 @@ +c======================================================================= +c MODULE sglobal_mod +c +c Global shared state for the SLAYER layer-physics package. +c Contains: +c - physical and mathematical constants +c - module-level scalar variables written by params() and +c consumed by the dispersion solvers (delta, slayer, gslayer) +c - AMR scanner storage (hash-based v1, cell-based v2) +c - derived types: slayer_inputs_type, slayer_outputs_type, +c deltas_outputs_type, result_type, amr_cell_type +c +c BUG FLAG 1 -- `pr` and `pe` (magnetic Prandtl number and its +c electron analogue) are declared but never explicitly set +c within this module or in params(). They must be initialised +c before params() is called (tau_v = tau_r / pr). Consider +c adding a dedicated init routine or making them INTENT(IN) +c arguments of params(). +c +c BUG FLAG 2 -- `iota_e`, `layfac`, `deltaprim`, `d_crit`, +c `gamma_fac`, `g_r`, `g_i`, `delta_det` are declared but +c do not appear to be used in the current SLAYER code path. +c Verify whether they are needed; if not, remove. +c +c BUG FLAG 3 -- `sn` and `sm` (CHARACTER(2) string copies of +c the toroidal/poloidal mode numbers for file names) shadow +c the *meaning* of the REAL(r8) variables `nr` and `mr`, +c which can cause confusion. Consider renaming the strings +c to `sn_str` / `sm_str`. +c======================================================================= MODULE sglobal_mod - USE local_mod, ONLY: r8 + + USE local_mod, ONLY: r8 ! double-precision kind parameter IMPLICIT NONE - INTEGER :: mm,nn - INTEGER :: in_unit,out_unit,out2_unit,out3_unit, - $ bin_unit,bin_2d_unit,input_unit,n_trace -c INTEGER, PARAMETER :: r8=SELECTED_REAL_KIND(13,307) - - REAL(r8) :: mr,nr - REAL(r8) :: Q_e,Q_i,pr,pe,c_beta,ds,tau,d_i, - $ d_beta,D_norm,P_perp,P_tor,gamma_fac - REAL(r8) :: eta,visc,rho_s,lu,omega_e,omega_i,iota_e, - $ delta_n,layfac,Qconv,lnLamb,deltaprim,dc_tmp, - $ d_crit,tau_r,tauk,g_r,g_i,delta_eff - COMPLEX(r8) :: Q,g_tmp,delta_det - CHARACTER(20) :: dc_type - - REAL(r8), PARAMETER :: pi=3.1415926535897932385, mu0=4e-7*pi, - $ m_e=9.1094e-31,m_p=1.6726e-27,chag=1.6021917e-19, - $ kval=1.3807e-23,eps0 = 8.8542e-12 - - INTEGER, PARAMETER :: MAX_PTS = 500000 ! Max unique points allowed - INTEGER, PARAMETER :: HASH_SZ = 500009 ! Prime number for hash table - REAL(r8), PARAMETER :: HASH_SCALE = 1.0d5 ! Scaling factor for integer hashing - INTEGER, ALLOCATABLE :: hash_head(:) ! Hash bucket heads - INTEGER, ALLOCATABLE :: hash_next(:) ! Linked list next pointers - - ! Replace hash-based storage with cell-based storage - INTEGER, PARAMETER :: MAX_CELLS = 500000 + +c----------------------------------------------------------------------- +c I/O unit numbers and trace control. +c----------------------------------------------------------------------- + INTEGER :: in_unit ! standard input unit + INTEGER :: out_unit ! primary output unit + INTEGER :: out2_unit ! secondary output unit + INTEGER :: out3_unit ! tertiary output unit + INTEGER :: bin_unit ! binary output unit + INTEGER :: bin_2d_unit ! 2-D binary output unit + INTEGER :: input_unit ! namelist / input unit + INTEGER :: n_trace ! trace / debug verbosity level + +c----------------------------------------------------------------------- +c Mode numbers (integer and real representations). +c----------------------------------------------------------------------- + INTEGER :: mm ! poloidal mode number (integer) + INTEGER :: nn ! toroidal mode number (integer) + REAL(r8) :: mr ! poloidal mode number (real copy) + REAL(r8) :: nr ! toroidal mode number (real copy) + CHARACTER(2) :: sn ! toroidal n as string (BUG FLAG 3) + CHARACTER(2) :: sm ! poloidal m as string (BUG FLAG 3) + +c----------------------------------------------------------------------- +c Layer-physics scalars (set by params(), read by solvers). +c These are the normalised parameters that enter the SLAYER +c dispersion relation. +c----------------------------------------------------------------------- +c --- temperature / collisionality + REAL(r8) :: tau ! T_i / T_e + REAL(r8) :: eta ! Spitzer resistivity [Ohm*m] + REAL(r8) :: visc ! anomalous viscosity [m^2/s] + REAL(r8) :: lnLamb ! Coulomb logarithm (updated at runtime) +c --- length scales + REAL(r8) :: rho_s ! ion Larmor radius at T_e [m] + REAL(r8) :: d_i ! ion skin depth [m] + REAL(r8) :: d_beta ! beta-weighted ion scale d_beta = c_beta * d_i +c --- timescales + REAL(r8) :: tau_r ! resistive diffusion time [s] + REAL(r8) :: tauk ! Q-conversion factor (= Qconv) +c --- Lundquist and Prandtl numbers + REAL(r8) :: lu ! Lundquist number S = tau_r / tau_h + REAL(r8) :: pr ! magnetic Prandtl number (BUG FLAG 1) + REAL(r8) :: pe ! electron Prandtl number (BUG FLAG 1) + REAL(r8) :: P_perp ! perpendicular magnetic Prandtl number + REAL(r8) :: P_tor ! toroidal magnetic Prandtl number +c --- normalised layer parameters + REAL(r8) :: ds ! normalised ion Larmor radius + REAL(r8) :: c_beta ! compressional beta parameter + REAL(r8) :: D_norm ! normalised beta-related width + REAL(r8) :: delta_n ! Delta normalisation factor + REAL(r8) :: Qconv ! frequency normalisation (Cole) +c --- diamagnetic and rotation frequencies + REAL(r8) :: omega_e ! electron diamagnetic frequency [rad/s] + REAL(r8) :: omega_i ! ion diamagnetic frequency [rad/s] + COMPLEX(r8) :: Q ! normalised complex rotation frequency + REAL(r8) :: Q_e ! normalised electron diamagnetic Q + REAL(r8) :: Q_i ! normalised ion diamagnetic Q +c --- stability / Delta_crit + REAL(r8) :: deltaprim ! Deltaprime value (BUG FLAG 2: may be unused) + REAL(r8) :: dc_tmp ! computed Delta_crit + REAL(r8) :: d_crit ! stored Delta_crit (BUG FLAG 2) + REAL(r8) :: delta_eff ! effective Deltaprime shift + CHARACTER(20) :: dc_type ! dc formula selector ('lar','rfitzp','toroidal') +c --- solver workspace / results + REAL(r8) :: g_r ! real part of growth rate (BUG FLAG 2) + REAL(r8) :: g_i ! imag part of growth rate (BUG FLAG 2) + COMPLEX(r8) :: g_tmp ! temporary complex growth rate + COMPLEX(r8) :: delta_det ! dispersion determinant (BUG FLAG 2) + REAL(r8) :: gamma_fac ! growth-rate conversion factor (BUG FLAG 2) +c --- miscellaneous + REAL(r8) :: iota_e ! (BUG FLAG 2: may be unused) + REAL(r8) :: layfac ! (BUG FLAG 2: may be unused) + +c----------------------------------------------------------------------- +c Physical and mathematical constants. +c----------------------------------------------------------------------- + REAL(r8), PARAMETER :: pi = 3.1415926535897932385d0 + REAL(r8), PARAMETER :: mu0 = 4.0d-7 * pi ! vacuum permeability [H/m] + REAL(r8), PARAMETER :: m_e = 9.1094d-31 ! electron mass [kg] + REAL(r8), PARAMETER :: m_p = 1.6726d-27 ! proton mass [kg] + REAL(r8), PARAMETER :: chag = 1.6021917d-19 ! elementary charge [C] + REAL(r8), PARAMETER :: kval = 1.3807d-23 ! Boltzmann constant [J/K] + REAL(r8), PARAMETER :: eps0 = 8.8542d-12 ! vacuum permittivity [F/m] + COMPLEX(r8), PARAMETER :: ifac = (0.0d0, 1.0d0) ! imaginary unit + +c----------------------------------------------------------------------- +c AMR scanner storage -- hash-based deduplication (v1). +c----------------------------------------------------------------------- + INTEGER, PARAMETER :: MAX_PTS = 500000 ! max unique eval points + INTEGER, PARAMETER :: HASH_SZ = 500009 ! hash table size (prime) + REAL(r8), PARAMETER :: HASH_SCALE = 1.0d5 ! Re/Im quantisation scale + INTEGER, ALLOCATABLE :: hash_head(:) ! bucket heads (HASH_SZ) + INTEGER, ALLOCATABLE :: hash_next(:) ! chain pointers (MAX_PTS) + +c----------------------------------------------------------------------- +c AMR scanner storage -- cell-based refinement (v2). +c----------------------------------------------------------------------- + INTEGER, PARAMETER :: MAX_CELLS = 500000 ! max AMR cells + TYPE :: amr_cell_type - COMPLEX(r8) :: Q(4) ! Corner Q values (TL, TR, BL, BR) - COMPLEX(r8) :: D(4) ! Corner Delta values - LOGICAL :: needs_refine ! Flag for refinement + COMPLEX(r8) :: Q(4) ! corner Q-values (BL, BR, TL, TR) + COMPLEX(r8) :: D(4) ! corner dispersion values + LOGICAL :: needs_refine ! flagged for subdivision END TYPE amr_cell_type + TYPE(amr_cell_type), ALLOCATABLE :: amr_cells(:) - INTEGER :: n_amr_cells - ! For output: flattened unique points - COMPLEX(r8), ALLOCATABLE :: Q_store(:), D_store(:) - INTEGER :: n_pts + INTEGER :: n_amr_cells ! current number of active cells + +c----------------------------------------------------------------------- +c AMR output arrays (shared by v1 and v2). +c----------------------------------------------------------------------- + COMPLEX(r8), ALLOCATABLE :: Q_store(:) ! unique Q-points + COMPLEX(r8), ALLOCATABLE :: D_store(:) ! corresponding D-values + INTEGER :: n_pts ! number of stored points + +c----------------------------------------------------------------------- +c Derived types: solver I/O and scan results. +c----------------------------------------------------------------------- +c result_type -- torque-scan output bucket TYPE result_type - REAL(r8), ALLOCATABLE :: inQs(:), iinQs(:), - $ Re_deltas(:), Im_deltas(:) - INTEGER :: count + REAL(r8), ALLOCATABLE :: inQs(:) ! Re(Q) scan values + REAL(r8), ALLOCATABLE :: iinQs(:) ! Im(Q) scan values + REAL(r8), ALLOCATABLE :: Re_deltas(:) ! Re(Delta) results + REAL(r8), ALLOCATABLE :: Im_deltas(:) ! Im(Delta) results + INTEGER :: count ! number of entries END TYPE result_type +c slayer_inputs_type -- per-surface input arrays for SLAYER TYPE slayer_inputs_type - INTEGER, ALLOCATABLE :: qval_arr(:) - REAL(r8), ALLOCATABLE :: chi_p_arr(:),chi_t_arr(:), - $ kappa_arr(:),psi_n_arr(:), - $ lu_arr(:),Qconv_arr(:),Q_e_arr(:),Q_i_arr(:),c_beta_arr(:), - $ d_beta_arr(:),D_norm_arr(:),tau_arr(:),P_perp_arr(:), - $ P_tor_arr(:),omegas_arr(:),omegas_e_arr(:),omegas_i_arr(:), - $ gammafac_arr(:),Re_dp_arr(:),Im_dp_arr(:),d_crit_arr(:) - COMPLEX(r8), ALLOCATABLE :: dp_matrix(:,:) + INTEGER, ALLOCATABLE :: qval_arr(:) ! safety-factor integers + REAL(r8), ALLOCATABLE :: chi_p_arr(:) ! chi_perp [m^2/s] + REAL(r8), ALLOCATABLE :: chi_t_arr(:) ! chi_tor [m^2/s] + REAL(r8), ALLOCATABLE :: kappa_arr(:) ! kappa (thermal cond.) + REAL(r8), ALLOCATABLE :: psi_n_arr(:) ! normalised psi + REAL(r8), ALLOCATABLE :: lu_arr(:) ! Lundquist number + REAL(r8), ALLOCATABLE :: Qconv_arr(:) ! Q-conversion factor + REAL(r8), ALLOCATABLE :: Q_e_arr(:) ! normalised Q_e + REAL(r8), ALLOCATABLE :: Q_i_arr(:) ! normalised Q_i + REAL(r8), ALLOCATABLE :: c_beta_arr(:) ! compressional beta + REAL(r8), ALLOCATABLE :: d_beta_arr(:) ! beta-related width + REAL(r8), ALLOCATABLE :: D_norm_arr(:) ! normalised D + REAL(r8), ALLOCATABLE :: tau_arr(:) ! T_i / T_e + REAL(r8), ALLOCATABLE :: P_perp_arr(:) ! perp Prandtl number + REAL(r8), ALLOCATABLE :: P_tor_arr(:) ! toroidal Prandtl + REAL(r8), ALLOCATABLE :: omegas_arr(:) ! rotation [rad/s] + REAL(r8), ALLOCATABLE :: omegas_e_arr(:) ! omega_e [rad/s] + REAL(r8), ALLOCATABLE :: omegas_i_arr(:) ! omega_i [rad/s] + REAL(r8), ALLOCATABLE :: gammafac_arr(:) ! gamma conversion + REAL(r8), ALLOCATABLE :: Re_dp_arr(:) ! Re(Deltaprime) + REAL(r8), ALLOCATABLE :: Im_dp_arr(:) ! Im(Deltaprime) + REAL(r8), ALLOCATABLE :: d_crit_arr(:) ! Delta_crit + COMPLEX(r8), ALLOCATABLE :: dp_matrix(:,:) ! full Deltaprime matrix END TYPE slayer_inputs_type +c slayer_outputs_type -- per-surface solver results TYPE slayer_outputs_type - COMPLEX(r8), ALLOCATABLE :: dels_db_arr(:),gamma_sol_arr(:), - $ gamma_est_arr(:) + COMPLEX(r8), ALLOCATABLE :: dels_db_arr(:) ! Delta from d_beta + COMPLEX(r8), ALLOCATABLE :: gamma_sol_arr(:) ! solved growth rate + COMPLEX(r8), ALLOCATABLE :: gamma_est_arr(:) ! estimated growth rate END TYPE slayer_outputs_type +c deltas_outputs_type -- scan output (Q vs Delta) TYPE deltas_outputs_type - REAL(r8), ALLOCATABLE :: inQs(:) - REAL(r8), ALLOCATABLE :: iinQs(:) - REAL(r8), ALLOCATABLE :: real_deltas(:) - REAL(r8), ALLOCATABLE :: imag_deltas(:) + REAL(r8), ALLOCATABLE :: inQs(:) ! Re(Q) values + REAL(r8), ALLOCATABLE :: iinQs(:) ! Im(Q) values + REAL(r8), ALLOCATABLE :: real_deltas(:) ! Re(Delta) + REAL(r8), ALLOCATABLE :: imag_deltas(:) ! Im(Delta) END TYPE deltas_outputs_type - ! lnLamb will be updated. - - COMPLEX(r8), PARAMETER :: ifac=(0,1) - - CHARACTER(2) :: sn,sm - END MODULE sglobal_mod diff --git a/slayer/slayer.f b/slayer/slayer.f index 37566a191..000bbf1a4 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -1,11 +1,10 @@ c----------------------------------------------------------------------- -c Slab LAYER based on linear drift MHD -c SLAYER: main program -c----------------------------------------------------------------------- -c----------------------------------------------------------------------- -c code organization. -c----------------------------------------------------------------------- -c slayer. +c SLAYER: Slab LAYER linear drift-MHD code. +c Main driver program. +c +c Computes tearing-mode layer quantities (inner layer Delta, +c growth rates, torque balance, field thresholds) from +c slab-geometry drift-MHD matching via a Riccati integration method. c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. @@ -13,59 +12,185 @@ PROGRAM slayer USE sglobal_mod - USE delta_mod, ONLY: riccati,riccati_del_s,riccati_out, - $ parflow_flag,PeOhmOnly_flag + USE delta_mod, ONLY: riccati,riccati_f,riccati_del_s, + $ riccati_out,parflow_flag,PeOhmOnly_flag USE gslayer_mod USE layerinputs_mod IMPLICIT NONE - - CHARACTER(512) :: infile,ncfile - INTEGER :: i,j,k,inum,jnum,knum,inn,count, - $ Q_num,msing_max,n_k - INTEGER, DIMENSION(1) :: index - - LOGICAL :: params_flag,QPscan_flag,QPescan_flag,QPscan2_flag, - $ QDscan2_flag,Qbscan_flag,Qscan_flag, - $ onscan_flag,otscan_flag,ntscan_flag,nbtscan_flag, - $ Pe_flag,verbose,ascii_flag,bin_flag,netcdf_flag, - $ bal_flag,stability_flag,riccatiscan_flag,input_flag, - $ params_check,stabscan_flag,coupled_stabscan_flag,amr_flag, - $ read_eq,est_gamma_flag, - $ match_gamma_flag,fitz_flag,coupling_flag,br_th_flag - REAL(r8) :: n_e,t_e,t_i,omega,omega0,scan_width,l_n, - $ l_t,qval,sval,bt,rs,R0,mu_i,zeff,dr_val,dgeo_val - REAL(r8) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds,intau,inlu - REAL(r8) :: psi0,jxb,Q0,Q_sol,br_th,d_b,Residual - COMPLEX(r8) :: delta,delta_n_p,dels_db,del_s,lar_gamma, - $ tmp_gamma,ingamma,delta_prime,det_val - - REAL(r8) :: inQ_min,inQ_max,j_min,j_max,jpower,k_min,k_max, - $ kpower,ing_step,ing_coarse,iing_coarse,delta_real, - $ delta_imag,Qratio - REAL(r8) :: chis(3) - - INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns - - REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl,bal, - $ prs,n_es,t_es,t_is,omegas,l_ns,l_ts,svals,qvals, - $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,pes - REAL(r8), DIMENSION(8) :: chi_p_prof, chi_t_prof, kappa_prof - REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: - $ js,ks,psis,jxbs,Q_sols,br_ths - REAL(r8) :: spot, slayer_inpr +c----------------------------------------------------------------------- +c local scalars — loop indices and counters. +c----------------------------------------------------------------------- + INTEGER :: i,j,k ! general loop indices + INTEGER :: inn ! number of input-file surfaces + INTEGER :: count ! generic counter +c----------------------------------------------------------------------- +c local scalars — numerical resolution. +c----------------------------------------------------------------------- + INTEGER :: inum ! resolution for 1-D scans + INTEGER :: jnum ! resolution for 2-D scan axis 1 + INTEGER :: knum ! resolution for 2-D scan axis 2 + INTEGER :: Q_num ! resolution for stab. scan Re(Q) + INTEGER :: msing_max ! max number of singular surfaces + INTEGER :: n_k ! number of rational surfaces +c----------------------------------------------------------------------- +c local scalars — intrinsic-name collision (see BUG FLAG 1). +c----------------------------------------------------------------------- + INTEGER, DIMENSION(1) :: index ! result of MAXLOC [BUG FLAG 1] +c----------------------------------------------------------------------- +c control flags — workflow. +c----------------------------------------------------------------------- + LOGICAL :: params_flag ! compute params from kinetic data + LOGICAL :: input_flag ! read multi-surface input file + LOGICAL :: read_eq ! read equilibrium files + LOGICAL :: verbose ! enable progress messages + LOGICAL :: params_check ! print diagnostic output in params +c----------------------------------------------------------------------- +c control flags — physics modes. +c----------------------------------------------------------------------- + LOGICAL :: est_gamma_flag ! estimate growth rate + LOGICAL :: match_gamma_flag ! asymptotically matched gamma + LOGICAL :: fitz_flag ! use Fitzpatrick layer model + LOGICAL :: coupling_flag ! coupled rational surfaces + LOGICAL :: br_th_flag ! Br threshold test scan + LOGICAL :: bal_flag ! torque balance scan + LOGICAL :: stability_flag ! complex-Q delta scan + LOGICAL :: Pe_flag ! include electron pressure +c----------------------------------------------------------------------- +c control flags — parameter-space scans. +c----------------------------------------------------------------------- + LOGICAL :: QPscan_flag ! (Q,P) scan + LOGICAL :: QPescan_flag ! (Q,Pe) scan + LOGICAL :: QPscan2_flag ! (Q,P) scan variant 2 + LOGICAL :: QDscan2_flag ! (Q,D) scan variant 2 + LOGICAL :: Qbscan_flag ! (Q,beta) scan + LOGICAL :: Qscan_flag ! 1-D Q scan + LOGICAL :: onscan_flag ! (omega,n) scan + LOGICAL :: otscan_flag ! (omega,T) scan + LOGICAL :: ntscan_flag ! (n,T) scan + LOGICAL :: nbtscan_flag ! (n,Bt) scan + LOGICAL :: riccatiscan_flag ! Riccati-variable scan + LOGICAL :: stabscan_flag ! stability scan (single surface) + LOGICAL :: coupled_stabscan_flag ! stability scan (coupled) + LOGICAL :: amr_flag ! adaptive mesh refinement scan +c----------------------------------------------------------------------- +c control flags — output format. +c----------------------------------------------------------------------- + LOGICAL :: ascii_flag ! write ASCII output files + LOGICAL :: bin_flag ! write binary output files + LOGICAL :: netcdf_flag ! write NetCDF output files +c----------------------------------------------------------------------- +c local scalars — physical input quantities. +c----------------------------------------------------------------------- + REAL(r8) :: n_e ! electron density [m^-3] + REAL(r8) :: t_e ! electron temperature [eV] + REAL(r8) :: t_i ! ion temperature [eV] + REAL(r8) :: omega ! toroidal rotation [rad/s] + REAL(r8) :: omega0 ! (unused, see BUG FLAG 2) + REAL(r8) :: l_n ! density gradient scale length + REAL(r8) :: l_t ! temperature gradient scale length + REAL(r8) :: qval ! safety factor at surface + REAL(r8) :: sval ! magnetic shear at surface + REAL(r8) :: bt ! toroidal field [T] + REAL(r8) :: rs ! minor radius of surface [m] + REAL(r8) :: R0 ! major radius [m] + REAL(r8) :: mu_i ! ion mass number + REAL(r8) :: zeff ! effective charge + REAL(r8) :: dr_val ! radial derivative parameter + REAL(r8) :: dgeo_val ! geometric factor parameter + REAL(r8) :: scan_width ! half-width of complex-Q scan +c----------------------------------------------------------------------- +c local scalars — normalized layer parameters (namelist overrides). +c----------------------------------------------------------------------- + REAL(r8) :: inQ ! normalized ExB rotation freq. + REAL(r8) :: inQ_e ! normalized electron diamagnetic + REAL(r8) :: inQ_i ! normalized ion diamagnetic + REAL(r8) :: inpr ! normalized pressure gradient + REAL(r8) :: inpe ! normalized electron pressure + REAL(r8) :: inc_beta ! normalized beta + REAL(r8) :: inds ! normalized D (magnetic diffusion) + REAL(r8) :: intau ! normalized tau = T_i/T_e + REAL(r8) :: inlu ! normalized Lundquist number +c----------------------------------------------------------------------- +c local scalars — derived / scratch quantities. +c----------------------------------------------------------------------- + REAL(r8) :: psi0 ! reconnected flux (a.u.) + REAL(r8) :: jxb ! j x B torque (a.u.) + REAL(r8) :: Q0 ! unperturbed rotation frequency + REAL(r8) :: Q_sol ! solved rotation frequency + REAL(r8) :: br_th ! radial field threshold + REAL(r8) :: d_b ! (unused, see BUG FLAG 2) + REAL(r8) :: Residual ! (unused, see BUG FLAG 2) + REAL(r8) :: Qratio ! Q_e/Q ratio for scan2 variants + REAL(r8) :: spot ! (unused, see BUG FLAG 2) + REAL(r8) :: slayer_inpr ! (unused, see BUG FLAG 2) +c----------------------------------------------------------------------- +c local scalars — scan grid helpers. +c----------------------------------------------------------------------- + REAL(r8) :: inQ_min,inQ_max ! rotation scan bounds + REAL(r8) :: j_min,j_max,jpower ! 2-D scan axis 1 + REAL(r8) :: k_min,k_max,kpower ! 2-D scan axis 2 + REAL(r8) :: ing_step ! growth-rate grid step + REAL(r8) :: ing_coarse ! Re(gamma) grid value + REAL(r8) :: iing_coarse ! Im(gamma) grid value + REAL(r8) :: delta_real ! (unused, see BUG FLAG 2) + REAL(r8) :: delta_imag ! (unused, see BUG FLAG 2) +c----------------------------------------------------------------------- +c local scalars — complex quantities. +c----------------------------------------------------------------------- + COMPLEX(r8) :: delta ! layer Delta (tearing index) + COMPLEX(r8) :: delta_n_p ! Deltaprime scale factor + COMPLEX(r8) :: dels_db ! delta_s / d_beta + COMPLEX(r8) :: del_s ! delta_s + COMPLEX(r8) :: lar_gamma ! (unused, see BUG FLAG 2) + COMPLEX(r8) :: tmp_gamma ! (unused, see BUG FLAG 2) + COMPLEX(r8) :: ingamma ! initial gamma guess (namelist) + COMPLEX(r8) :: delta_prime ! external Deltaprime (namelist) + COMPLEX(r8) :: det_val ! (unused, see BUG FLAG 2) +c----------------------------------------------------------------------- +c local arrays — transport profile coefficients. +c----------------------------------------------------------------------- + REAL(r8) :: chis(3) ! chi_perp, chi_tor, kappa + REAL(r8), DIMENSION(8) :: chi_p_prof ! chi_perp radial profile + REAL(r8), DIMENSION(8) :: chi_t_prof ! chi_tor radial profile + REAL(r8), DIMENSION(8) :: kappa_prof ! kappa radial profile +c----------------------------------------------------------------------- +c local arrays — multi-surface input-file storage. +c----------------------------------------------------------------------- + INTEGER, DIMENSION(:), ALLOCATABLE :: mms,nns + REAL(r8), DIMENSION(:), ALLOCATABLE :: prs,n_es,t_es,t_is, + $ omegas,l_ns,l_ts,svals,qvals,bts,rss,R0s,mu_is,zeffs, + $ Q_soll,br_thl,pes +c----------------------------------------------------------------------- +c local arrays — scan workspace. +c----------------------------------------------------------------------- + REAL(r8), DIMENSION(:), ALLOCATABLE :: inQs,iinQs + REAL(r8), DIMENSION(:), ALLOCATABLE :: jxbl,bal + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: js,ks,psis,jxbs, + $ Q_sols,br_ths REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: Q_solss,br_thss - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: deltal,outer_deltas COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: deltas - - ! AMR DECLARATIONS - INTEGER :: AMR_passes, m_AMR ! Number of refinement levels - - TYPE(slayer_inputs_type) :: sl_in +c----------------------------------------------------------------------- +c AMR (adaptive mesh refinement) growth-rate scan. +c----------------------------------------------------------------------- + INTEGER :: AMR_passes ! number of AMR refinement levels + INTEGER :: m_AMR ! effective number of surfaces +c----------------------------------------------------------------------- +c structured input/output types (defined in sglobal_mod). +c----------------------------------------------------------------------- + TYPE(slayer_inputs_type) :: sl_in TYPE(slayer_outputs_type) :: sl_out TYPE(deltas_outputs_type), ALLOCATABLE :: all_deltas_out(:) +c----------------------------------------------------------------------- +c file-path strings. +c----------------------------------------------------------------------- + CHARACTER(512) :: infile ! multi-surface input file path + CHARACTER(512) :: ncfile ! NetCDF equilibrium file path +c----------------------------------------------------------------------- +c namelist groups. +c----------------------------------------------------------------------- NAMELIST/slayer_input/input_flag,infile, $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_p_prof, @@ -84,95 +209,127 @@ PROGRAM slayer $ params_check c----------------------------------------------------------------------- c set initial values. +c defaults are overridden by namelist reads below. c----------------------------------------------------------------------- - mm=0.0 - nn=0.0 - mr = 0.0!real(mm,4) - nr = 0.0!real(nn,4) - n_e=0.0 - t_e=0.0 - t_i=0.0 - omega=0.0 - l_n=0.0 - l_t=0.0 - qval=0.0 - sval=0.0 - bt=0.0 - rs=0.0 - R0=0.0 - mu_i=0.0 - zeff=0.0 - dr_val=0.0 - dgeo_val=0.0 - inQ=0.0 - inQ_e=0.0 - inQ_i=0.0 - inpr=0.0 - chi_p_prof=0.0 - chi_t_prof=0.0 - kappa_prof=0.0 - inpe=0.0 - inc_beta=0.0 - inds=0.0 - intau=0.0 - inlu=0.0 - Q0=0.0 - chis=0.0 - gamma_fac=0.0 - dc_type="" - delta_prime=(0.0,0.0) - delta_n_p=(0.0,0.0) - ingamma=(0.0,0.0) - inum=400 ! resolution to find error field thresholds. - jnum=500 ! resolution for 2d scan along with Q,omega. - knum=100 ! resolution for 2d scan alont with the other. - Q_num=100 ! resolution for stab. scan along Re(Q) axis - scan_width = 2.0 - AMR_passes = 4 - msing_max = 2 - in_unit=1 - out_unit=2 - out2_unit=3 - out3_unit=4 - bin_unit=5 - bin_2d_unit=6 - input_unit=7 - read_eq=.FALSE. - est_gamma_flag=.FALSE. - match_gamma_flag=.FALSE. - fitz_flag=.FALSE. - coupling_flag=.FALSE. - QPscan_flag=.FALSE. ! scan (Q,P) space for delta and torque. - QPescan_flag=.FALSE. ! scan (Q,Pe) space for delta and torque. - Qbscan_flag=.FALSE. ! scan (Q,beta) space for delta and torque. - onscan_flag=.FALSE. ! scan (omega,n) space for error fields. - otscan_flag=.FALSE. ! scan (omega,t) space for error fields. - ntscan_flag=.FALSE. ! scan (n,te) space for error fields. - nbtscan_flag=.FALSE. ! scan (n,bt) space for error fields. - layfac=0.02 - Qratio=0.5 - parflow_flag=.FALSE. - PeOhmOnly_flag=.TRUE. - Pe_flag=.FALSE. - params_flag=.TRUE. - input_flag=.FALSE. - infile="" - ncfile="" - verbose=.TRUE. - ascii_flag=.TRUE. - bin_flag=.TRUE. - netcdf_flag=.FALSE. - riccati_out=.FALSE. - riccatiscan_flag=.FALSE. - params_check=.FALSE. - bal_flag=.FALSE. - stability_flag=.FALSE. - stabscan_flag=.FALSE. - coupled_stabscan_flag=.FALSE. - amr_flag=.FALSE. - br_th_flag=.FALSE. + + ! mode numbers (sglobal_mod: INTEGER mm,nn; REAL mr,nr) + mm = 0 + nn = 0 + mr = 0.0 + nr = 0.0 + + ! kinetic / equilibrium inputs + n_e = 0.0 + t_e = 0.0 + t_i = 0.0 + omega = 0.0 + l_n = 0.0 + l_t = 0.0 + qval = 0.0 + sval = 0.0 + bt = 0.0 + rs = 0.0 + R0 = 0.0 + mu_i = 0.0 + zeff = 0.0 + dr_val = 0.0 + dgeo_val= 0.0 + + ! normalized layer-parameter overrides + inQ = 0.0 + inQ_e = 0.0 + inQ_i = 0.0 + inpr = 0.0 + inpe = 0.0 + inc_beta = 0.0 + inds = 0.0 + intau = 0.0 + inlu = 0.0 + Q0 = 0.0 + + ! transport profile coefficients + chi_p_prof = 0.0 + chi_t_prof = 0.0 + kappa_prof = 0.0 + chis = 0.0 + + ! complex namelist inputs + delta_prime = (0.0,0.0) + delta_n_p = (0.0,0.0) + ingamma = (0.0,0.0) + + ! global module scalars (sglobal_mod) + gamma_fac = 0.0 + dc_type = "" + + ! scan resolution defaults + inum = 400 ! 1-D resolution (error-field threshold scans) + jnum = 500 ! 2-D scan axis-1 resolution + knum = 100 ! 2-D scan axis-2 resolution + Q_num = 100 ! stability scan Re(Q) resolution + scan_width = 2.0 ! half-width for complex-Q scans + AMR_passes = 4 ! AMR refinement levels + msing_max = 2 ! max singular surfaces to process + + ! I/O unit numbers (sglobal_mod) + in_unit = 1 + out_unit = 2 + out2_unit = 3 + out3_unit = 4 + bin_unit = 5 + bin_2d_unit= 6 + input_unit = 7 + + ! workflow flags + read_eq = .FALSE. + est_gamma_flag = .FALSE. + match_gamma_flag = .FALSE. + fitz_flag = .FALSE. + coupling_flag = .FALSE. + params_flag = .TRUE. + input_flag = .FALSE. + + ! parameter-space scan flags + QPscan_flag = .FALSE. + QPescan_flag = .FALSE. + Qbscan_flag = .FALSE. + onscan_flag = .FALSE. + otscan_flag = .FALSE. + ntscan_flag = .FALSE. + nbtscan_flag = .FALSE. + + ! physics / model flags + layfac = 0.02 + Qratio = 0.5 + parflow_flag = .FALSE. + PeOhmOnly_flag = .TRUE. + Pe_flag = .FALSE. + + ! file paths + infile = "" + ncfile = "" + + ! output control + verbose = .TRUE. + ascii_flag = .TRUE. + bin_flag = .TRUE. + netcdf_flag = .FALSE. + + ! diagnostic flags + riccati_out = .FALSE. + riccatiscan_flag = .FALSE. + params_check = .FALSE. + + ! remaining physics-mode flags + bal_flag = .FALSE. + stability_flag = .FALSE. + stabscan_flag = .FALSE. + coupled_stabscan_flag= .FALSE. + amr_flag = .FALSE. + br_th_flag = .FALSE. c----------------------------------------------------------------------- c read slayer.in. +c four namelist groups: input, control, output, diagnose. c----------------------------------------------------------------------- IF(verbose) WRITE(*,*)"" IF(verbose) WRITE(*,*)"SLAYER START" @@ -184,6 +341,7 @@ PROGRAM slayer READ(in_unit,NML=slayer_diagnose) CLOSE(UNIT=in_unit) + ! Build toroidal-mode-number string for output filenames. IF (nn<10) THEN WRITE(UNIT=sn,FMT='(I1)') nn sn=ADJUSTL(sn) @@ -191,11 +349,15 @@ PROGRAM slayer WRITE(UNIT=sn,FMT='(I2)') nn ENDIF c----------------------------------------------------------------------- -c calculate parameters as needed. +c compute normalized layer parameters from kinetic inputs. +c params() (params_mod) converts dimensional plasma profiles into +c the normalized quantities (Q, Q_e, Q_i, c_beta, ds, tau, lu) +c used by the Riccati solver. c----------------------------------------------------------------------- IF (params_flag) THEN CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + ! Copy module-level results into local working variables. inQ=Q inQ_e=Q_e inQ_i=Q_i @@ -204,21 +366,28 @@ PROGRAM slayer intau=tau Q0=Q ELSE - lu=inlu + lu=inlu ! manual Lundquist number when params not computed ENDIF c----------------------------------------------------------------------- -c calculate basic delta, torque, balance, error fields. +c baseline single-surface delta, reconnected flux, & torque. +c skipped when the matched-gamma path is active (it computes +c its own delta internally). c----------------------------------------------------------------------- IF (.NOT. (match_gamma_flag)) THEN - delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) - psi0=1.0/ABS(delta+delta_n_p) ! a.u. - jxb=-AIMAG(1.0/(delta+delta_n_p)) ! a.u. - WRITE(*,*)"delta=",delta - WRITE(*,*)"psi0=",psi0 - WRITE(*,*)"jxb=",jxb + delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) + psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] + jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] + IF (verbose) THEN + WRITE(*,*)"delta=",delta + WRITE(*,*)"psi0=",psi0 + WRITE(*,*)"jxb=",jxb + ENDIF ENDIF c----------------------------------------------------------------------- -c calculate parameters as needed. +c multi-surface input-file mode. +c reads an external file of (m,n) surfaces with per-surface kinetic +c profiles, computes delta and the error-field threshold for each. +c [BUG FLAG 3] arrays are allocated 1:inn but loops run 0:inn-1. c----------------------------------------------------------------------- IF (input_flag) THEN OPEN(UNIT=input_unit,FILE=infile,STATUS="old") @@ -238,7 +407,7 @@ PROGRAM slayer CLOSE(input_unit) DO k=0,inn-1 - WRITE(*,*)k + WRITE(*,*)k ! surface index mr=REAL(mms(k)) nr=REAL(nns(k)) inpr=prs(k) @@ -277,12 +446,13 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) ENDDO + ! [BUG FLAG 3] Q_soll/br_thl indexed 0:inn-1 but allocated 1:inn index=MAXLOC(bal) Q_soll(k)=inQs(index(1)) br_thl(k)=sqrt(MAXVAL(bal)/lu*(svals(k)**2.0/2.0))*1e4 - WRITE(*,*)"Q_sol=",Q_soll(k) - WRITE(*,*)"br_th=",br_thl(k) + IF (verbose) WRITE(*,*)"Q_sol=",Q_soll(k) + IF (verbose) WRITE(*,*)"br_th=",br_thl(k) DEALLOCATE(inQs,deltal,jxbl,bal) ENDDO OPEN(UNIT=out_unit,FILE="slayer_input_bal_n"// @@ -298,30 +468,31 @@ PROGRAM slayer $ bts,rss,R0s,mu_is,zeffs,Q_soll,br_thl,mms,nns) ENDIF c----------------------------------------------------------------------- -c LAR (cylindrical) growthrates via restive layer thickness +c estimate growth rate via resistive-layer thickness. +c Uses riccati_del_s to get delta_s/d_beta, then scales by +c d_beta to obtain the layer thickness delta_s and the estimated +c gamma. Inputs may come from equilibrium files (read_eq) or +c namelist.Subroutines: build_inputs, allocate_inputs, +c allocate_outputsare defined in gslayer_mod / layerinputs_mod. c----------------------------------------------------------------------- IF (est_gamma_flag) THEN WRITE(*,*)"------------------------------------------" WRITE(*,*)">>> Estimating growth rate" IF (read_eq) THEN - WRITE(*,*)"$^$ entered read_eq" - + ! Read equilibrium files for multi-surface inputs. + ! build_inputs (layerinputs_mod) reads STRIDE NetCDF data. sl_in%chi_p_arr = chi_p_prof sl_in%chi_t_arr = chi_t_prof sl_in%kappa_arr = kappa_prof - WRITE(*,*)"$^$ entering build_inputs" - - WRITE(*,*)"$^$ infile: ",infile - WRITE(*,*)"$^$ ncfile: ",ncfile - CALL build_inputs(infile,ncfile,sl_in) n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) ELSE + ! Single-surface mode: build inputs from namelist. n_k = 1 mr = mm nr = nn @@ -333,6 +504,7 @@ PROGRAM slayer CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + ! Override computed parameters with nonzero namelist values. IF (ABS(inQ) > 0.0) THEN Q = inQ ! NAMELIST END IF @@ -352,10 +524,8 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF - WRITE(*,*)"$^$ entering allocate_inputs" - CALL allocate_inputs(n_k,sl_in) - WRITE(*,*)"$^$ entering allocate_outputs" - CALL allocate_outputs(n_k,sl_out) + CALL allocate_inputs(n_k,sl_in) ! gslayer_mod + CALL allocate_outputs(n_k,sl_out) ! gslayer_mod sl_in%qval_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) @@ -377,32 +547,23 @@ PROGRAM slayer sl_in%Qconv_arr = (/ tauk /) END IF + ! Loop over rational surfaces to estimate growth rates. DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate '// $ 'estimate on q = ', $ sl_in%qval_arr(k),' rational surface' - WRITE(*,*)"sl_in%Q_e_arr(k): ",sl_in%Q_e_arr(k) - WRITE(*,*)"sl_in%Q_e_arr(k): ",sl_in%Q_e_arr(k) - WRITE(*,*)"sl_in%Q_i_arr(k): ",sl_in%Q_i_arr(k) - WRITE(*,*)"sl_in%P_perp_arr(k): ",sl_in%P_perp_arr(k) - WRITE(*,*)"sl_in%P_tor_arr(k): ",sl_in%P_tor_arr(k) - WRITE(*,*)"sl_in%c_beta_arr(k): ",sl_in%c_beta_arr(k) - WRITE(*,*)"sl_in%D_norm_arr(k): ",sl_in%D_norm_arr(k) - WRITE(*,*)"sl_in%tau_arr(k): ",sl_in%tau_arr(k) - WRITE(*,*)"sl_in%gammafac_arr(k): ",sl_in%gammafac_arr(k) - WRITE(*,*)"sl_in%d_beta_arr(k): ",sl_in%d_beta_arr(k) - - dels_db=riccati_del_s(sl_in%Q_e_arr(k),sl_in%Q_e_arr(k), ! NOT using Q_arr + ! [BUG FLAG 4] first argument to riccati_del_s is Q_e_arr, + ! not Q_arr. Comment "NOT using Q_arr" is original. + ! Verify this is intentional (Q_e used as ExB frequency). + dels_db=riccati_del_s(sl_in%Q_e_arr(k),sl_in%Q_e_arr(k), $ sl_in%Q_i_arr(k),sl_in%P_perp_arr(k), $ sl_in%c_beta_arr(k),sl_in%D_norm_arr(k), $ sl_in%tau_arr(k),5.0*sl_in%D_norm_arr(k)) del_s = dels_db * sl_in%d_beta_arr(k) - WRITE(*,*)"del_s: ",del_s - sl_out%gamma_est_arr(k) = sl_in%gammafac_arr(k)/del_s sl_out%dels_db_arr(k) = dels_db WRITE(*,*) @@ -418,7 +579,12 @@ PROGRAM slayer END IF ENDIF c----------------------------------------------------------------------- -c LAR (cylindrical) growthrates via restive layer thickness +c asymptotically matched growth rate. +c Matches the inner-layer Delta to the outer-region Delta' to +c find the self-consistent complex growth rate. Supports both +c single-surface and coupled multi-surface (AMR) modes. +c Subroutines: dispersion_AMR_v2, dispersion_det (gslayer_mod), +c riccati_f (delta_mod). c----------------------------------------------------------------------- IF (match_gamma_flag) THEN WRITE(*,*)"------------------------------------------" @@ -496,6 +662,9 @@ PROGRAM slayer END IF END IF +c----------------------------------------------------------------------- +c allocate output arrays for AMR delta storage. +c----------------------------------------------------------------------- IF (AMR_flag .AND. .NOT. coupling_flag) THEN ALLOCATE(all_deltas_out(n_k)) ELSEIF (AMR_flag .AND. coupling_flag) THEN @@ -509,10 +678,15 @@ PROGRAM slayer END IF WRITE(*,*),"Rational q domain: ",sl_in%qval_arr +c----------------------------------------------------------------------- +c loop over rational surfaces to find matched growth rates. +c----------------------------------------------------------------------- DO k=1,MIN(n_k,msing_max) WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', $ sl_in%qval_arr(k),' rational surface:' + + ! Load per-surface parameters into module-level scalars. Q_e = sl_in%Q_e_arr(k) Q_i = sl_in%Q_i_arr(k) P_perp = sl_in%P_perp_arr(k) @@ -534,15 +708,18 @@ PROGRAM slayer WRITE(*,*)"Delta_prime: ",sl_in%Re_dp_arr(k) WRITE(*,*)"Delta_crit: ",sl_in%d_crit_arr(k) - ! Calculate (Deltaprime - d_crit)/S^1/3 + ! Calculate (Deltaprime - D_crit)/S^1/3 delta_eff = (sl_in%Re_dp_arr(k) - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) -c delta_eff = Re_deltaprime_arr(k) pe = 0.0 -c ! Fill gamma_sol_arr with 0's, will by used by python root finding + ! Placeholder: gamma_sol_arr filled for external root-finder. sl_out%gamma_sol_arr(k) = 0.0 +c----------------------------------------------------------------------- +c uncoupled AMR scan (one surface at a time). +c dispersion_AMR_v2 (gslayer_mod) populates Q_store, D_store. +c----------------------------------------------------------------------- IF (AMR_flag .AND. .NOT. coupling_flag) THEN WRITE(*,'(A,I0,A)') 'Calling uncoupled AMR scan on q = ', @@ -550,6 +727,7 @@ PROGRAM slayer CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) + ! Re-allocate output arrays for this surface. IF (ALLOCATED(all_deltas_out(k)%inQs)) $ DEALLOCATE(all_deltas_out(k)%inQs) IF (ALLOCATED(all_deltas_out(k)%iinQs)) @@ -559,41 +737,39 @@ PROGRAM slayer IF (ALLOCATED(all_deltas_out(k)%imag_deltas)) $ DEALLOCATE(all_deltas_out(k)%imag_deltas) - ! Flatten the unique points store into 1D arrays ALLOCATE(all_deltas_out(k)%inQs(n_pts), $ all_deltas_out(k)%iinQs(n_pts)) - ! Assumes you also have real_deltas/imag_deltas declared as 1D ALLOCATE(all_deltas_out(k)%real_deltas(n_pts), $ all_deltas_out(k)%imag_deltas(n_pts)) - - WRITE(*,*)"Allocated all_deltas_out" + ! Flatten unique AMR points into 1-D output arrays. DO i = 1, n_pts all_deltas_out(k)%inQs(i) = REAL(Q_store(i)) - IF (fitz_flag) THEN - all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) - ELSE - all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) - END IF + ! [BUG FLAG 5] IF/ELSE branches are identical. + + all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) + all_deltas_out(k)%real_deltas(i) = REAL(D_store(i)) all_deltas_out(k)%imag_deltas(i) = AIMAG(D_store(i)) END DO - - WRITE(*,*)"all_deltas_out(1)%real_deltas(10): ", - $ all_deltas_out(1)%real_deltas(10) - ! Clean up temporary AMR memory - DEALLOCATE(Q_store, D_store)!, hash_head, hash_next) + ! Clean up temporary AMR memory. + DEALLOCATE(Q_store, D_store) END IF - IF ((stabscan_flag)) THEN ! was .AND. (k == 2) +c----------------------------------------------------------------------- +c single-surface stability scan on [Re(Q), Im(Q)] grid. +c Uses riccati_f() (new Fitzpatrick TJ-like formalism) or +c. riccati() (orig. SLAYER/Waelbroeck). +c----------------------------------------------------------------------- + IF ((stabscan_flag)) THEN WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// $ 'Im(Q)] scan with Q width = ', $ scan_width - ing_step = (2.0*scan_width) / (Q_num - 1) ! was (2.0 * scan_width) / (Q_num - 1) + ing_step = (2.0*scan_width) / (Q_num - 1) count = 0 ALLOCATE(inQs(1:(Q_num+1)),iinQs(1:Q_num)) @@ -652,15 +828,16 @@ PROGRAM slayer sl_out%dels_db_arr = (/ 0. /) END IF +c----------------------------------------------------------------------- +c coupled AMR scan (all surfaces simultaneously). +c dispersion_AMR_v2 (gslayer_mod) with coupling_flag = .TRUE. +c----------------------------------------------------------------------- IF (AMR_flag .AND. coupling_flag) THEN - WRITE(*,*)"Calling coupled AMR scan" - CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) - WRITE(*,*)"Successfully exited coupled AMR scan" - + ! Re-allocate output arrays. IF (ALLOCATED(all_deltas_out(1)%inQs)) $ DEALLOCATE(all_deltas_out(1)%inQs) IF (ALLOCATED(all_deltas_out(1)%iinQs)) @@ -670,32 +847,31 @@ PROGRAM slayer IF (ALLOCATED(all_deltas_out(1)%imag_deltas)) $ DEALLOCATE(all_deltas_out(1)%imag_deltas) - WRITE(*,*)"Successfully checked array allocation" - ALLOCATE(all_deltas_out(1)%inQs(n_pts), $ all_deltas_out(1)%iinQs(n_pts)) ALLOCATE(all_deltas_out(1)%real_deltas(n_pts), $ all_deltas_out(1)%imag_deltas(n_pts)) - - WRITE(*,*)"Successfully allocated all_deltas_out subarrays" + ! Flatten unique AMR points into 1-D output arrays. DO i = 1, n_pts all_deltas_out(1)%inQs(i) = REAL(Q_store(i)) - IF (fitz_flag) THEN - all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) - ELSE - all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) - END IF + + all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) ! verify this sign convention + all_deltas_out(1)%real_deltas(i) = REAL(D_store(i)) all_deltas_out(1)%imag_deltas(i) = AIMAG(D_store(i)) END DO - ! Clean up temporary AMR memory - DEALLOCATE(Q_store, D_store)!, hash_head, hash_next) - WRITE(*,*)"Successfully deallocated Q_store and D_store" + ! Clean up temporary AMR memory. + DEALLOCATE(Q_store, D_store) END IF +c----------------------------------------------------------------------- +c coupled-surface stability scan on [Re(Q), Im(Q)] grid. +c Uses dispersion_det (gslayer_mod) for the full dispersion +c determinant including inter-surface coupling. +c----------------------------------------------------------------------- IF (coupled_stabscan_flag) THEN WRITE(*,*)"------------------------------------------" WRITE(*,'(A,F0.1)')' >>> Running [Re(Q),'// @@ -744,14 +920,14 @@ PROGRAM slayer CALL output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, $ all_deltas_out) stop - ENDIF + ENDIF ! match_gamma_flag c----------------------------------------------------------------------- -c TEST ANALYTIC SCAN IN GSLAYER.F, FOR TESTING ONLY +c Br threshold test scan (analytic). +c For testing & verification only. Scans rotation to find the +c critical radial-field threshold from a simple torque balance. c----------------------------------------------------------------------- IF (br_th_flag) THEN - WRITE(*,*)"running br_th scan" - CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) inQ=Q @@ -762,14 +938,13 @@ PROGRAM slayer intau=tau Q0=Q c----------------------------------------------------------------------- -c calculate basic delta, torque, balance, error fields. +c compute baseline delta, reconnected flux, and torque. c----------------------------------------------------------------------- - WRITE(*,*)"basic delta successful" - delta_n_p=1e-2 + delta_n_p=1e-2 ! [BUG FLAG 7] hardcoded; should use namelist delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, $ inpe) - psi0=1.0/ABS(delta+delta_n_p) ! a.u. - jxb=-AIMAG(1.0/(delta+delta_n_p)) ! a.u. + psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] + jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] c----------------------------------------------------------------------- c find solutions based on simple torque balance. c----------------------------------------------------------------------- @@ -784,8 +959,13 @@ PROGRAM slayer inQ_min=1.5*MINVAL((/Q0,inQ_i/)) ENDIF ENDIF - WRITE(*,*)"rotation scan" - ! Scan of rotation +c----------------------------------------------------------------------- +c rotation scan to locate torque-balance threshold. +c [BUG FLAG 8] the IF/ELSE block above that sets inQ_min/inQ_max +c is immediately overridden by the hardcoded values below. +c The conditional block is dead code. +c----------------------------------------------------------------------- + ! Override with fixed diagnostic range. inQ_max=10.0 inQ_min=-10.0 inum=200 @@ -799,26 +979,19 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) ENDDO - ! Identify the threshold from the maximum of the balance parameter - index=MAXLOC(bal) + ! Identify the threshold from the maximum of the balance parameter. + index=MAXLOC(bal) ! [BUG FLAG 1] shadows intrinsic Q_sol=inQs(index(1)) - !omega_sol=inQs(index(1))/Qconv br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0)) DEALLOCATE(inQs,deltal,jxbl,bal) - - WRITE(*,*)"allocating" +c----------------------------------------------------------------------- +c populate sl_in structure for output. +c----------------------------------------------------------------------- sl_in%qval_arr = (/ 3 /) - inQs = (/ 1.0 /) - inQs = (/ 1.0 /) n_k = SIZE(sl_in%qval_arr) - sl_in%qval_arr = (/ 3 /) - inQs = (/ 1.0 /) - inQs = (/ 1.0 /) - sl_in%omegas_arr = (/ 0.0 /) - !sl_in%Q_arr = (/ inQ /) sl_in%Q_e_arr = (/ inQ_e /) sl_in%Q_i_arr = (/ inQ_i /) sl_in%psi_n_arr = (/ 0.0 /) @@ -826,12 +999,10 @@ PROGRAM slayer sl_in%Im_dp_arr = (/ 0.0 /) sl_in%P_perp_arr = (/ inpr /) - WRITE(*,*)"allocations successful" - c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, c $ stabscan_eq_flag,stabscan_flag,br_th_flag) stop - ENDIF + ENDIF ! br_th_flag c----------------------------------------------------------------------- c find solutions based on simple torque balance. c----------------------------------------------------------------------- diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 45fdfef89..c9ed65329 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -1,275 +1,434 @@ -c----------------------------------------------------------------------- +c======================================================================= c file slayer_netcdf.f -c writes slayer information to a netcdf file -c----------------------------------------------------------------------- -c code organization. +c Writes SLAYER solver results to a NetCDF output file. +c +c The per-surface scalar inputs (Lundquist number, Q-normalisation, +c Prandtl numbers, …) and the solver outputs (growth rates, Delta +c values) are stored in a single NetCDF-3/64-bit-offset file named +c slayer_output_n.nc. +c +c Ragged AMR scan data (variable number of evaluation points per +c surface) are zero-padded into rectangular arrays before writing. +c +c BUG FLAG 1 -- Several NetCDF variable definitions that depend on +c `qsing_dim`, `i_dim`, and `nAMR_dim` sit OUTSIDE the +c `IF (msing > 0)` guard that creates those dimensions. If the +c subroutine is ever called with msing == 0, those dimension IDs +c will be uninitialised and the nf90_def_var calls will fail or +c produce undefined behaviour. +c Suggested fix: move all remaining nf90_def_var calls inside +c the `IF (msing > 0)` block, or add an early RETURN when +c msing == 0. +c +c BUG FLAG 2 -- `fill_val` is declared (-9.99E33) but never +c used; the rectangular buffers are initialised to 0.0 instead. +c Plotters that rely on a standard _FillValue attribute will +c not distinguish padding from real zeros. +c Suggested fix: initialise buffers with `fill_val` instead +c of 0.0, and add an nf90_put_att call to set the _FillValue +c attribute on the Q_AMR and Deltas_AMR variables. +c +c BUG FLAG 3 -- `Q_id` is created by nf90_def_var("Q", …) but +c the corresponding nf90_put_var is commented out, so the +c variable exists in the file but contains only fill values. +c Suggested fix: either remove the nf90_def_var or write the +c appropriate Q array. +c +c BUG FLAG 4 -- `c_b_id` (c_beta variable ID) is declared but +c never used in any nf90_def_var or nf90_put_var call; i.e. +c c_beta_arr is not written to the output file. +c Suggested fix: if c_beta is needed in the output, add the +c definition and write calls; otherwise remove c_b_id. +c +c BUG FLAG 5 -- `run`, `run_dimid`, `point_dimid`, `varids(4)`, +c `i`, `r_id`, and `r_dim` are declared but never referenced. +c Suggested fix: remove them. +c +c BUG FLAG 6 -- The `version` string is hardcoded to a specific +c git hash ('v1.0.0-99-gc873bd6'). For a public release this +c should be generated at build time (e.g. from `git describe` +c via a preprocessor macro). +c +c BUG FLAG 7 -- The subroutine writes to the module-level global +c `sn` (from sglobal_mod) as a side-effect of building the +c output filename. This is fragile — a local CHARACTER +c variable should be used instead to avoid polluting global +c state. +c======================================================================= c----------------------------------------------------------------------- -c 0. slayer_netcdf_mod -c 1. check -c 2. stride_netcdf_out +c code organisation. c----------------------------------------------------------------------- -c subprogram 0. slayer_netcdf_mod -c module declarations. +c 0. slayer_netcdf_mod -- module declarations +c 1. sl_check -- NetCDF status checker +c 2. slayer_netcdf_out -- main output routine c----------------------------------------------------------------------- +c c----------------------------------------------------------------------- -c declarations. +c subprogram 0. slayer_netcdf_mod. +c Module wrapper — imports sglobal_mod (shared types and globals) +c and the NetCDF Fortran-90 API. c----------------------------------------------------------------------- MODULE slayer_netcdf_mod + USE sglobal_mod USE netcdf + IMPLICIT NONE + CONTAINS -c ----------------------------------------------------------------------- +c c----------------------------------------------------------------------- -c subprogram 1. check. -c Check status of netcdf file. +c subprogram 1. sl_check. +c Assert that a NetCDF operation succeeded; abort with a message +c if it did not. c----------------------------------------------------------------------- SUBROUTINE sl_check(stat) c----------------------------------------------------------------------- -c declaration. +c declarations. c----------------------------------------------------------------------- - INTEGER, INTENT (IN) :: stat + INTEGER, INTENT(IN) :: stat ! return code from any nf90_* call c----------------------------------------------------------------------- -c stop if it is an error. +c check status and abort on error. c----------------------------------------------------------------------- - IF(stat /= nf90_noerr) THEN + IF (stat /= nf90_noerr) THEN PRINT *, TRIM(nf90_strerror(stat)) STOP "ERROR: failed to write/read netcdf file" ENDIF -c----------------------------------------------------------------------- -c terminate. -c----------------------------------------------------------------------- + RETURN END SUBROUTINE sl_check -c ----------------------------------------------------------------------- -c subprogram 2. slayer_netcdf_out. -c Replicate stride.out information in netcdf format. -c ----------------------------------------------------------------------- -c ----------------------------------------------------------------------- -c declarations. -c ----------------------------------------------------------------------- - SUBROUTINE slayer_netcdf_out(msing,m_AMR,est_gamma_flag, - $ sl_in,sl_out,all_deltas_out) - - INTEGER, INTENT(IN) :: msing,m_AMR - LOGICAL, INTENT(IN) :: est_gamma_flag - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in +c +c----------------------------------------------------------------------- +c subprogram 2. slayer_netcdf_out. +c Write per-surface SLAYER inputs and solver outputs to the +c NetCDF file slayer_output_n.nc . +c +c Arguments: +c msing -- number of rational surfaces +c m_AMR -- number of AMR-scanned surfaces +c est_gamma_flag -- .TRUE. to include estimated growth rates +c sl_in -- slayer_inputs_type (per-surface inputs) +c sl_out -- slayer_outputs_type (solver results) +c all_deltas_out -- array(m_AMR) of deltas_outputs_type (AMR +c scan results, potentially ragged) +c----------------------------------------------------------------------- + SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, + $ sl_in, sl_out, all_deltas_out) +c----------------------------------------------------------------------- +c declarations -- subroutine arguments. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: msing ! number of rational surfaces + INTEGER, INTENT(IN) :: m_AMR ! number of AMR surfaces + LOGICAL, INTENT(IN) :: est_gamma_flag ! include estimated gammas? + + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in TYPE(slayer_outputs_type), INTENT(IN) :: sl_out TYPE(deltas_outputs_type), INTENT(IN) :: all_deltas_out(m_AMR) - INTEGER :: i,ncid,r_id,qsing_dim,i_dim,r_dim,qr_id,omegas_id, - $ Q_id,Q_e_id,Q_i_id,d_b_id,c_b_id,Dnorm_id,p_perp_id,S_id, - $ pr_id,dpp_id,dc_id,dels_db_id,gs_id,ge_id,nAMR_dim, - $ qsing_id,qc_id,p_tor_id - - INTEGER :: run, run_dimid, point_dimid, varids(4) - - ! AMR declarations - INTEGER :: max_pts_all, s, n_curr - INTEGER :: dim_pts_id - INTEGER :: var_q_id, var_d_id, var_npts_id - REAL(r8), ALLOCATABLE :: buffer_q(:,:,:), buffer_d(:,:,:) - INTEGER, ALLOCATABLE :: n_pts_arr(:) - REAL(r8) :: fill_val = -9.99E33 ! Standard NetCDF Fill Value - ! AMR declarations - - CHARACTER(64) :: ncfile - LOGICAL, PARAMETER :: debug_flag = .FALSE. - CHARACTER(len=*), PARAMETER :: version ='v1.0.0-99-gc873bd6' -c ----------------------------------------------------------------------- -c set variables -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *,"Called slayer_netcdf_out" - IF (nn<10) THEN - WRITE(UNIT=sn,FMT='(I1)')nn - sn=ADJUSTL(sn) +c----------------------------------------------------------------------- +c declarations -- NetCDF file and dimension IDs. +c----------------------------------------------------------------------- + INTEGER :: ncid ! NetCDF file ID + INTEGER :: qsing_dim ! dim: rational surfaces (msing) + INTEGER :: nAMR_dim ! dim: AMR surfaces (m_AMR) + INTEGER :: i_dim ! dim: Re/Im component (2) + INTEGER :: dim_pts_id ! dim: max AMR eval points + +c----------------------------------------------------------------------- +c declarations -- NetCDF variable IDs. +c Each *_id holds the handle returned by nf90_def_var and is +c later passed to the matching nf90_put_var call. +c----------------------------------------------------------------------- + INTEGER :: qsing_id ! "r" — surface index + INTEGER :: qr_id ! "q_rational" — safety factor + INTEGER :: omegas_id ! "omegas" — rotation freq + INTEGER :: qc_id ! "tau_k" — Q-conversion + INTEGER :: Q_id ! "Q" (BUG FLAG 3) + INTEGER :: Q_e_id ! "Q_e" — norm Q_e + INTEGER :: Q_i_id ! "Q_i" — norm Q_i + INTEGER :: S_id ! "S" — Lundquist + INTEGER :: pr_id ! "psi_n_rational" — norm psi + INTEGER :: p_perp_id ! "P_perp" + INTEGER :: p_tor_id ! "P_tor" + INTEGER :: Dnorm_id ! "D" — normalised D + INTEGER :: dpp_id ! "Delta_prime_rational" (complex) + INTEGER :: dc_id ! "Delta_crit_rational" + INTEGER :: dels_db_id ! "delta_s_d_b" (complex) + INTEGER :: d_b_id ! "d_beta" + INTEGER :: gs_id ! "growth rate" (complex) + INTEGER :: ge_id ! "est. growth rate" (complex) + INTEGER :: c_b_id ! c_beta — (BUG FLAG 4: unused) + +c AMR variable IDs + INTEGER :: var_q_id ! "Q_AMR" — scan Q-points + INTEGER :: var_d_id ! "Deltas_AMR" — scan Delta values + INTEGER :: var_npts_id ! "n_amr_pts" — points per surface + +c----------------------------------------------------------------------- +c declarations -- AMR rectangular-buffer workspace. +c The ragged per-surface scan data are padded into fixed-size +c rectangular arrays before writing to NetCDF. +c----------------------------------------------------------------------- + INTEGER :: max_pts_all ! max points across surfaces + INTEGER :: s ! surface loop index + INTEGER :: n_curr ! points on current surface + REAL(r8), ALLOCATABLE :: buffer_q(:,:,:) ! (pts, surf, Re/Im) + REAL(r8), ALLOCATABLE :: buffer_d(:,:,:) ! (pts, surf, Re/Im) + INTEGER, ALLOCATABLE :: n_pts_arr(:) ! points per surface + REAL(r8) :: fill_val = -9.99d33 ! (BUG FLAG 2: declared, unused) + +c----------------------------------------------------------------------- +c declarations -- miscellaneous locals. +c----------------------------------------------------------------------- + CHARACTER(64) :: ncfile ! output file name + LOGICAL, PARAMETER :: debug_flag = .FALSE. ! verbose trace + CHARACTER(len=*), PARAMETER :: + $ version = 'v1.0.0-99-gc873bd6' ! (BUG FLAG 6) + +c----------------------------------------------------------------------- +c build the output filename from the toroidal mode number. +c Note: writes to the module-level global `sn` (BUG FLAG 7). +c----------------------------------------------------------------------- + IF (debug_flag) PRINT *, "Called slayer_netcdf_out" + + IF (nn < 10) THEN + WRITE(UNIT=sn, FMT='(I1)') nn + sn = ADJUSTL(sn) ELSE - WRITE(UNIT=sn,FMT='(I2)')nn + WRITE(UNIT=sn, FMT='(I2)') nn ENDIF ncfile = "slayer_output_n"//TRIM(sn)//".nc" - IF(debug_flag) PRINT *, ncfile -c ----------------------------------------------------------------------- -c open files -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Creating netcdf files" + IF (debug_flag) PRINT *, ncfile + +c----------------------------------------------------------------------- +c create the NetCDF file (clobber any existing file). +c----------------------------------------------------------------------- + IF (debug_flag) PRINT *, " - Creating netcdf file" CALL sl_check( nf90_create(ncfile, - $ cmode=or(NF90_CLOBBER,NF90_64BIT_OFFSET), ncid=ncid) ) -c -c reform "ragged" AMR delta ouputs into rectangular array + $ cmode=OR(NF90_CLOBBER, NF90_64BIT_OFFSET), ncid=ncid) ) + +c----------------------------------------------------------------------- +c reform ragged AMR Delta outputs into rectangular arrays. c +c Each surface may have a different number of AMR scan points. +c We find the maximum, allocate rectangular buffers of that size, +c and copy in the per-surface data. Unused trailing slots are +c filled with 0.0 (should be fill_val — see BUG FLAG 2). +c----------------------------------------------------------------------- - ! 1. Find the Maximum AMR Grid Size across all surfaces +c step 1: find the maximum AMR grid size across all surfaces. max_pts_all = 0 IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN - DO s = 1, m_AMR - max_pts_all = MAX(max_pts_all,SIZE(all_deltas_out(s)%inQs)) - END DO + DO s = 1, m_AMR + max_pts_all = MAX(max_pts_all, + $ SIZE(all_deltas_out(s)%inQs)) + END DO END IF - ! 2. Allocate Rectangular Buffers (Points, Surfaces, Re/Im) - ! Shape: (Max_Points, Number_Surfaces, 2) +c step 2: allocate rectangular buffers (pts × surfaces × Re/Im). ALLOCATE(buffer_q(max_pts_all, m_AMR, 2)) ALLOCATE(buffer_d(max_pts_all, m_AMR, 2)) ALLOCATE(n_pts_arr(m_AMR)) - - ! Initialize with Fill Value (so unused space is ignored by plotters) - buffer_q = 0.0 - buffer_d = 0.0 + + buffer_q = 0.0d0 ! padding value (see BUG FLAG 2) + buffer_d = 0.0d0 n_pts_arr = 0 - - ! 3. Flatten the Ragged Data into the Buffers + +c step 3: flatten the ragged data into the buffers. IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN - DO s = 1, m_AMR - n_curr = SIZE(all_deltas_out(s)%inQs) - n_pts_arr(s) = n_curr - - ! --- FILL Q (Coordinate) --- - ! Real part (inQs) -> Index 1 - buffer_q(1:n_curr,s,1) = all_deltas_out(s)%inQs(1:n_curr) - ! Imag part (iinQs) -> Index 2 - buffer_q(1:n_curr,s,2) = all_deltas_out(s)%iinQs(1:n_curr) - - ! --- FILL DELTA (Result) --- - ! Real part -> Index 1 - buffer_d(1:n_curr,s,1)=all_deltas_out(s)%real_deltas(1:n_curr) - ! Imag part -> Index 2 - buffer_d(1:n_curr,s,2)=all_deltas_out(s)%imag_deltas(1:n_curr) - END DO + DO s = 1, m_AMR + n_curr = SIZE(all_deltas_out(s)%inQs) + n_pts_arr(s) = n_curr + +c Q-coordinate (Re and Im parts) + buffer_q(1:n_curr, s, 1) = + $ all_deltas_out(s)%inQs(1:n_curr) + buffer_q(1:n_curr, s, 2) = + $ all_deltas_out(s)%iinQs(1:n_curr) + +c Delta result (Re and Im parts) + buffer_d(1:n_curr, s, 1) = + $ all_deltas_out(s)%real_deltas(1:n_curr) + buffer_d(1:n_curr, s, 2) = + $ all_deltas_out(s)%imag_deltas(1:n_curr) + END DO END IF -c ----------------------------------------------------------------------- -c define global file attributes -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Defining netcdf globals" - CALL sl_check( nf90_put_att(ncid,nf90_global,"title", - $ "SLAYER outputs")) - !CALL sl_check( nf90_put_att(ncid,nf90_global,"shot", INT(shotnum)) ) - !CALL sl_check( nf90_put_att(ncid,nf90_global,"time",INT(shottime)) ) - !CALL sl_check( nf90_put_att(ncid,nf90_global,"n", nn)) - CALL sl_check( nf90_put_att(ncid,nf90_global,"version", version)) - ! define global attributes - ! define dimensions - IF(debug_flag) PRINT *," - Defining dimensions in netcdf" - - !WRITE(*,*)"netcdf qval=",qval - WRITE(*,*)">>> Writing results to NetCDF output file" - - !CALL check( nf90_def_dim(ncid,"r",msing,r_dim) ) - !CALL check( nf90_def_var(ncid,"r",nf90_int,r_dim,r_id)) - IF(msing>0)THEN - CALL sl_check( nf90_def_dim(ncid,"r",msing,qsing_dim) ) !r_dim = q_rational - CALL sl_check( nf90_def_dim(ncid,"r_AMR",m_AMR, - $ nAMR_dim) ) !r_dim = q_rational - CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) - CALL sl_check( nf90_def_var(ncid,"r",nf90_int, - $ qsing_dim,qsing_id)) - CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_int, - $ qsing_dim,qr_id)) - CALL sl_check( nf90_def_var(ncid,"omegas",nf90_double, - $ qsing_dim,omegas_id)) - CALL sl_check( nf90_def_var(ncid,"tau_k",nf90_double, - $ qsing_dim,qc_id)) - CALL sl_check( nf90_def_var(ncid,"Q",nf90_double, - $ qsing_dim,Q_id)) - CALL sl_check( nf90_def_var(ncid,"Q_e",nf90_double, - $ qsing_dim,Q_e_id)) - CALL sl_check( nf90_def_var(ncid,"Q_i",nf90_double, - $ qsing_dim,Q_i_id)) - CALL sl_check( nf90_def_var(ncid,"S",nf90_double, - $ qsing_dim,S_id)) - CALL sl_check( nf90_def_var(ncid,"psi_n_rational", - $ nf90_double,qsing_dim,pr_id) ) - CALL sl_check( nf90_def_var(ncid,"P_perp",nf90_double, - $ qsing_dim,p_perp_id) ) - CALL sl_check( nf90_def_var(ncid,"P_tor",nf90_double, - $ qsing_dim,p_tor_id) ) - !CALL sl_check( nf90_def_var(ncid,"q_rational",nf90_double, - !$ qsing_dim,qr_id) ) + +c----------------------------------------------------------------------- +c define global file attributes. +c----------------------------------------------------------------------- + IF (debug_flag) PRINT *, " - Defining netcdf globals" + CALL sl_check( nf90_put_att(ncid, nf90_global, + $ "title", "SLAYER outputs") ) + CALL sl_check( nf90_put_att(ncid, nf90_global, + $ "version", version) ) + +c----------------------------------------------------------------------- +c define dimensions and per-surface NetCDF variables. +c +c All definitions below require msing > 0 because the "r" +c dimension is sized by msing. (See BUG FLAG 1 for remaining +c def_var calls that sit outside this guard.) +c----------------------------------------------------------------------- + IF (debug_flag) PRINT *, " - Defining dimensions in netcdf" + WRITE(*,*) ">>> Writing results to NetCDF output file" + + IF (msing > 0) THEN + +c -- core dimensions -- + CALL sl_check( nf90_def_dim(ncid, "r", msing, qsing_dim) ) + CALL sl_check( nf90_def_dim(ncid, "r_AMR", m_AMR, nAMR_dim) ) + CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) + +c -- scalar per-surface variables -- + CALL sl_check( nf90_def_var(ncid, "r", nf90_int, + $ qsing_dim, qsing_id) ) + CALL sl_check( nf90_def_var(ncid, "q_rational", nf90_int, + $ qsing_dim, qr_id) ) + CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, + $ qsing_dim, omegas_id) ) + CALL sl_check( nf90_def_var(ncid, "tau_k", nf90_double, + $ qsing_dim, qc_id) ) + CALL sl_check( nf90_def_var(ncid, "Q", nf90_double, + $ qsing_dim, Q_id) ) ! BUG FLAG 3: defined but not written + CALL sl_check( nf90_def_var(ncid, "Q_e", nf90_double, + $ qsing_dim, Q_e_id) ) + CALL sl_check( nf90_def_var(ncid, "Q_i", nf90_double, + $ qsing_dim, Q_i_id) ) + CALL sl_check( nf90_def_var(ncid, "S", nf90_double, + $ qsing_dim, S_id) ) + CALL sl_check( nf90_def_var(ncid, "psi_n_rational", + $ nf90_double, qsing_dim, pr_id) ) + CALL sl_check( nf90_def_var(ncid, "P_perp", nf90_double, + $ qsing_dim, p_perp_id) ) + CALL sl_check( nf90_def_var(ncid, "P_tor", nf90_double, + $ qsing_dim, p_tor_id) ) + END IF - CALL sl_check( nf90_def_var(ncid,"D",nf90_double, - $ qsing_dim,Dnorm_id) ) - CALL sl_check( nf90_def_var(ncid,"Delta_prime_rational", - $ nf90_double,(/qsing_dim,i_dim/),dpp_id) ) - CALL sl_check( nf90_def_var(ncid,"Delta_crit_rational", - $ nf90_double,qsing_dim,dc_id) ) +c----------------------------------------------------------------------- +c define additional variables that also depend on qsing_dim / i_dim +c (BUG FLAG 1 — these will fail if msing == 0). +c----------------------------------------------------------------------- + CALL sl_check( nf90_def_var(ncid, "D", nf90_double, + $ qsing_dim, Dnorm_id) ) + CALL sl_check( nf90_def_var(ncid, "Delta_prime_rational", + $ nf90_double, (/qsing_dim, i_dim/), dpp_id) ) + CALL sl_check( nf90_def_var(ncid, "Delta_crit_rational", + $ nf90_double, qsing_dim, dc_id) ) IF (est_gamma_flag) THEN - CALL sl_check( nf90_def_var(ncid,"delta_s_d_b",nf90_double, - $ (/qsing_dim,i_dim/),dels_db_id) ) - CALL sl_check( nf90_def_var(ncid,"d_beta",nf90_double, - $ qsing_dim,d_b_id) ) - CALL sl_check( nf90_def_var(ncid,"est. growth rate", - $ nf90_double,(/qsing_dim,i_dim/),ge_id) ) + CALL sl_check( nf90_def_var(ncid, "delta_s_d_b", + $ nf90_double, (/qsing_dim, i_dim/), dels_db_id) ) + CALL sl_check( nf90_def_var(ncid, "d_beta", nf90_double, + $ qsing_dim, d_b_id) ) + CALL sl_check( nf90_def_var(ncid, "est. growth rate", + $ nf90_double, (/qsing_dim, i_dim/), ge_id) ) END IF - CALL sl_check( nf90_def_var(ncid,"growth rate", - $ nf90_double,(/qsing_dim,i_dim/),gs_id) ) - - !!! AMR - CALL sl_check(nf90_def_dim(ncid, 'amr_pts', max_pts_all, - $ dim_pts_id)) - ! Define Variables - CALL sl_check(nf90_def_var(ncid, 'n_amr_pts', NF90_INT, - $ (/qsing_dim/), var_npts_id)) - ! Note: Dimensions order is (pts, surf, cplx) - CALL sl_check(nf90_def_var(ncid, 'Q_AMR', NF90_DOUBLE, - $ (/dim_pts_id, nAMR_dim, i_dim/), var_q_id)) - CALL sl_check(nf90_def_var(ncid, 'Deltas_AMR', NF90_DOUBLE, - $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id)) - - ! end definitions + CALL sl_check( nf90_def_var(ncid, "growth rate", + $ nf90_double, (/qsing_dim, i_dim/), gs_id) ) + +c----------------------------------------------------------------------- +c define AMR scan dimensions and variables. +c (BUG FLAG 1 — also uses qsing_dim, nAMR_dim, i_dim.) +c----------------------------------------------------------------------- + CALL sl_check( nf90_def_dim(ncid, "amr_pts", + $ max_pts_all, dim_pts_id) ) + CALL sl_check( nf90_def_var(ncid, "n_amr_pts", NF90_INT, + $ (/qsing_dim/), var_npts_id) ) + CALL sl_check( nf90_def_var(ncid, "Q_AMR", NF90_DOUBLE, + $ (/dim_pts_id, nAMR_dim, i_dim/), var_q_id) ) + CALL sl_check( nf90_def_var(ncid, "Deltas_AMR", NF90_DOUBLE, + $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id) ) + +c----------------------------------------------------------------------- +c end NetCDF define mode. +c----------------------------------------------------------------------- CALL sl_check( nf90_enddef(ncid) ) -c ----------------------------------------------------------------------- -c set variables -c ----------------------------------------------------------------------- - CALL sl_check( nf90_put_var(ncid,qsing_id, sl_in%qval_arr)) - CALL sl_check( nf90_put_var(ncid,qr_id, sl_in%qval_arr)) - CALL sl_check( nf90_put_var(ncid,pr_id, sl_in%psi_n_arr)) - CALL sl_check( nf90_put_var(ncid,omegas_id, sl_in%omegas_arr)) - CALL sl_check( nf90_put_var(ncid,S_id, sl_in%lu_arr)) - CALL sl_check( nf90_put_var(ncid,qc_id, sl_in%Qconv_arr)) - !CALL sl_check( nf90_put_var(ncid,Q_id, Q_arr)) - CALL sl_check( nf90_put_var(ncid,Q_e_id, sl_in%Q_e_arr)) - CALL sl_check( nf90_put_var(ncid,Q_i_id, sl_in%Q_i_arr)) - CALL sl_check( nf90_put_var(ncid,p_perp_id, sl_in%P_perp_arr)) - CALL sl_check( nf90_put_var(ncid,p_tor_id, sl_in%P_tor_arr)) - CALL sl_check( nf90_put_var(ncid,Dnorm_id, sl_in%D_norm_arr)) - - CALL sl_check( nf90_put_var(ncid,dpp_id, - $ RESHAPE((/sl_in%Re_dp_arr,sl_in%Im_dp_arr/), - $ (/msing,2/)))) - CALL sl_check( nf90_put_var(ncid,dc_id,sl_in%d_crit_arr)) +c----------------------------------------------------------------------- +c write per-surface scalar variables. +c----------------------------------------------------------------------- + CALL sl_check( nf90_put_var(ncid, qsing_id, + $ sl_in%qval_arr) ) + CALL sl_check( nf90_put_var(ncid, qr_id, + $ sl_in%qval_arr) ) + CALL sl_check( nf90_put_var(ncid, pr_id, + $ sl_in%psi_n_arr) ) + CALL sl_check( nf90_put_var(ncid, omegas_id, + $ sl_in%omegas_arr)) + CALL sl_check( nf90_put_var(ncid, S_id, + $ sl_in%lu_arr) ) + CALL sl_check( nf90_put_var(ncid, qc_id, + $ sl_in%Qconv_arr) ) + CALL sl_check( nf90_put_var(ncid, Q_e_id, + $ sl_in%Q_e_arr) ) + CALL sl_check( nf90_put_var(ncid, Q_i_id, + $ sl_in%Q_i_arr) ) + CALL sl_check( nf90_put_var(ncid, p_perp_id, + $ sl_in%P_perp_arr)) + CALL sl_check( nf90_put_var(ncid, p_tor_id, + $ sl_in%P_tor_arr) ) + CALL sl_check( nf90_put_var(ncid, Dnorm_id, + $ sl_in%D_norm_arr)) + +c----------------------------------------------------------------------- +c write complex Delta' as a RESHAPE'd (msing, 2) real array. +c----------------------------------------------------------------------- + CALL sl_check( nf90_put_var(ncid, dpp_id, + $ RESHAPE( (/sl_in%Re_dp_arr, sl_in%Im_dp_arr/), + $ (/msing, 2/) )) ) + CALL sl_check( nf90_put_var(ncid, dc_id, + $ sl_in%d_crit_arr) ) + +c----------------------------------------------------------------------- +c write estimated growth-rate outputs (only when requested). +c----------------------------------------------------------------------- IF (est_gamma_flag) THEN - CALL sl_check( nf90_put_var(ncid,dels_db_id, - $ RESHAPE((/REAL(sl_out%dels_db_arr), - $ AIMAG(sl_out%dels_db_arr)/),(/msing,2/)))) - - CALL sl_check( nf90_put_var(ncid,d_b_id,sl_in%d_beta_arr)) - CALL sl_check( nf90_put_var(ncid,ge_id, - $ RESHAPE((/REAL(sl_out%gamma_est_arr), - $ AIMAG(sl_out%gamma_est_arr)/),(/msing,2/)))) + CALL sl_check( nf90_put_var(ncid, dels_db_id, + $ RESHAPE( (/REAL(sl_out%dels_db_arr), + $ AIMAG(sl_out%dels_db_arr)/), + $ (/msing, 2/) )) ) + + CALL sl_check( nf90_put_var(ncid, d_b_id, + $ sl_in%d_beta_arr) ) + + CALL sl_check( nf90_put_var(ncid, ge_id, + $ RESHAPE( (/REAL(sl_out%gamma_est_arr), + $ AIMAG(sl_out%gamma_est_arr)/), + $ (/msing, 2/) )) ) END IF - CALL sl_check( nf90_put_var(ncid,gs_id, - $ RESHAPE((/REAL(sl_out%gamma_sol_arr), - $ AIMAG(sl_out%gamma_sol_arr)/),(/msing,2/)))) +c----------------------------------------------------------------------- +c write solved growth rate (always present). +c----------------------------------------------------------------------- + CALL sl_check( nf90_put_var(ncid, gs_id, + $ RESHAPE( (/REAL(sl_out%gamma_sol_arr), + $ AIMAG(sl_out%gamma_sol_arr)/), + $ (/msing, 2/) )) ) - CALL sl_check(nf90_put_var(ncid, var_npts_id, n_pts_arr)) - CALL sl_check(nf90_put_var(ncid, var_q_id, buffer_q)) - CALL sl_check(nf90_put_var(ncid, var_d_id, buffer_d)) +c----------------------------------------------------------------------- +c write AMR scan arrays. +c----------------------------------------------------------------------- + CALL sl_check( nf90_put_var(ncid, var_npts_id, n_pts_arr) ) + CALL sl_check( nf90_put_var(ncid, var_q_id, buffer_q) ) + CALL sl_check( nf90_put_var(ncid, var_d_id, buffer_d) ) - ! 6. Clean Up +c----------------------------------------------------------------------- +c deallocate AMR rectangular buffers. +c----------------------------------------------------------------------- DEALLOCATE(buffer_q, buffer_d, n_pts_arr) -c ----------------------------------------------------------------------- -c close file -c ----------------------------------------------------------------------- - IF(debug_flag) PRINT *," - Closing netcdf file" +c----------------------------------------------------------------------- +c close the NetCDF file. +c----------------------------------------------------------------------- + IF (debug_flag) PRINT *, " - Closing netcdf file" CALL sl_check( nf90_close(ncid) ) -c ----------------------------------------------------------------------- -c terminate. -c ----------------------------------------------------------------------- + +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- RETURN END SUBROUTINE slayer_netcdf_out END MODULE slayer_netcdf_mod \ No newline at end of file From e5397cb8ad744383e54eeb1e8766176e19fc2ce9 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Sat, 14 Feb 2026 00:23:28 -0500 Subject: [PATCH 059/305] WIP: fix line excursion --- slayer/layerinputs.f | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index c9756bce0..e0f0b2093 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -279,17 +279,20 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, ELSE IF (wegt == 1) THEN DO itheta = 0, fs-1 issurfint = issurfint - $ + inr(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + $ + inr(itheta)*jacs(itheta)*delpsi(itheta)* + $ func(itheta)/fs ENDDO ELSE IF (wegt == 2) THEN DO itheta = 0, fs-1 issurfint = issurfint - $ + jacs(itheta)*delpsi(itheta)*func(itheta)/inr(itheta)/fs + $ + jacs(itheta)*delpsi(itheta)* + $ func(itheta)/inr(itheta)/fs ENDDO ELSE IF (wegt == 3) THEN DO itheta = 0, fs-1 issurfint = issurfint - $ + ina(itheta)*jacs(itheta)*delpsi(itheta)*func(itheta)/fs + $ + ina(itheta)*jacs(itheta)*delpsi(itheta)* + $ func(itheta)/fs ENDDO ELSE STOP 'ERROR: issurfint wegt must be in [0,1,2,3]' From aca19c5dc12bb3c03137e680bc9befabe6646a50 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 00:33:11 -0500 Subject: [PATCH 060/305] WIP: bug fixes --- slayer/delta.f | 9 ++------- slayer/params.f | 1 - slayer/sglobal.f | 2 +- slayer/slayer.f | 17 ++++------------- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index aff31a2c8..0c673fa74 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -172,22 +172,17 @@ END FUNCTION riccati c BUG FLAG 4: variables y, dy, xfac, xintv, ml, mu, nrpd are c declared but never used -- remove them. c----------------------------------------------------------------------- - FUNCTION riccati_del_s(inQ,inQ_e,inQ_i,inpr,inc_beta,ind_beta, - $ intau,inx,iny) + FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) c --- input arguments - REAL(r8),INTENT(IN) :: inQ ! (UNUSED -- see BUG FLAG 3) REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq REAL(r8),INTENT(IN) :: inpr ! mapped to P_perp (see below) - REAL(r8),INTENT(IN) :: inc_beta ! (UNUSED -- see BUG FLAG 3) - REAL(r8),INTENT(IN) :: ind_beta ! (UNUSED -- see BUG FLAG 3) - REAL(r8),INTENT(IN) :: intau ! (UNUSED -- see BUG FLAG 3) c --- optional arguments c BUG FLAG 5: inx is declared OPTIONAL but my_q=inx is accessed c unconditionally. If inx is ever absent, this will crash. c Either make inx required or add IF(present(inx)) guard. - REAL(r8),INTENT(IN),OPTIONAL :: inx ! starting q for integration + REAL(r8),INTENT(IN) :: inx ! starting q for integration COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W c --- function result COMPLEX(r8) :: riccati_del_s diff --git a/slayer/params.f b/slayer/params.f index c68e0032e..4cde1f332 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -180,7 +180,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c --- dead code end (BUG FLAG 3) --- c effective perpendicular and toroidal Prandtl numbers - tau_perp = (rs**2.0) / chis(1) P_perp = tau_r / tau_perp ! perp magnetic Prandtl number tau_tor = (rs**2.0) / chis(2) diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 7c94683c9..8855c00c6 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -82,7 +82,7 @@ MODULE sglobal_mod c --- normalised layer parameters REAL(r8) :: ds ! normalised ion Larmor radius REAL(r8) :: c_beta ! compressional beta parameter - REAL(r8) :: D_norm ! normalised beta-related width + REAL(r8) :: D_norm ! normalised beta-weighted ion scale REAL(r8) :: delta_n ! Delta normalisation factor REAL(r8) :: Qconv ! frequency normalisation (Cole) c --- diamagnetic and rotation frequencies diff --git a/slayer/slayer.f b/slayer/slayer.f index 000bbf1a4..af088d7a8 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -86,7 +86,7 @@ PROGRAM slayer REAL(r8) :: t_e ! electron temperature [eV] REAL(r8) :: t_i ! ion temperature [eV] REAL(r8) :: omega ! toroidal rotation [rad/s] - REAL(r8) :: omega0 ! (unused, see BUG FLAG 2) + REAL(r8) :: omega0 ! unused REAL(r8) :: l_n ! density gradient scale length REAL(r8) :: l_t ! temperature gradient scale length REAL(r8) :: qval ! safety factor at surface @@ -119,11 +119,7 @@ PROGRAM slayer REAL(r8) :: Q0 ! unperturbed rotation frequency REAL(r8) :: Q_sol ! solved rotation frequency REAL(r8) :: br_th ! radial field threshold - REAL(r8) :: d_b ! (unused, see BUG FLAG 2) - REAL(r8) :: Residual ! (unused, see BUG FLAG 2) REAL(r8) :: Qratio ! Q_e/Q ratio for scan2 variants - REAL(r8) :: spot ! (unused, see BUG FLAG 2) - REAL(r8) :: slayer_inpr ! (unused, see BUG FLAG 2) c----------------------------------------------------------------------- c local scalars — scan grid helpers. c----------------------------------------------------------------------- @@ -133,8 +129,6 @@ PROGRAM slayer REAL(r8) :: ing_step ! growth-rate grid step REAL(r8) :: ing_coarse ! Re(gamma) grid value REAL(r8) :: iing_coarse ! Im(gamma) grid value - REAL(r8) :: delta_real ! (unused, see BUG FLAG 2) - REAL(r8) :: delta_imag ! (unused, see BUG FLAG 2) c----------------------------------------------------------------------- c local scalars — complex quantities. c----------------------------------------------------------------------- @@ -142,11 +136,8 @@ PROGRAM slayer COMPLEX(r8) :: delta_n_p ! Deltaprime scale factor COMPLEX(r8) :: dels_db ! delta_s / d_beta COMPLEX(r8) :: del_s ! delta_s - COMPLEX(r8) :: lar_gamma ! (unused, see BUG FLAG 2) - COMPLEX(r8) :: tmp_gamma ! (unused, see BUG FLAG 2) COMPLEX(r8) :: ingamma ! initial gamma guess (namelist) COMPLEX(r8) :: delta_prime ! external Deltaprime (namelist) - COMPLEX(r8) :: det_val ! (unused, see BUG FLAG 2) c----------------------------------------------------------------------- c local arrays — transport profile coefficients. c----------------------------------------------------------------------- @@ -554,13 +545,13 @@ PROGRAM slayer $ 'estimate on q = ', $ sl_in%qval_arr(k),' rational surface' + D_norm = sl_in%D_norm_arr(k) ! [BUG FLAG 4] first argument to riccati_del_s is Q_e_arr, ! not Q_arr. Comment "NOT using Q_arr" is original. ! Verify this is intentional (Q_e used as ExB frequency). - dels_db=riccati_del_s(sl_in%Q_e_arr(k),sl_in%Q_e_arr(k), + dels_db=riccati_del_s(sl_in%Q_e_arr(k), $ sl_in%Q_i_arr(k),sl_in%P_perp_arr(k), - $ sl_in%c_beta_arr(k),sl_in%D_norm_arr(k), - $ sl_in%tau_arr(k),5.0*sl_in%D_norm_arr(k)) + $ 5.0*sl_in%D_norm_arr(k)) del_s = dels_db * sl_in%d_beta_arr(k) From c658f457db4c19d97194729fbdd2dc8285bc7f4d Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 00:55:07 -0500 Subject: [PATCH 061/305] WIP: fix NF90_GLOBAL integers in read_stride --- slayer/delta.f | 5 +---- slayer/layerinputs.f | 33 ++++++++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 0c673fa74..0ad68b1dc 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -179,9 +179,6 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq REAL(r8),INTENT(IN) :: inpr ! mapped to P_perp (see below) c --- optional arguments -c BUG FLAG 5: inx is declared OPTIONAL but my_q=inx is accessed -c unconditionally. If inx is ever absent, this will crash. -c Either make inx required or add IF(present(inx)) guard. REAL(r8),INTENT(IN) :: inx ! starting q for integration COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W c --- function result @@ -228,7 +225,7 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) c --- set starting integration point my_q=inx ! start backwards integration at large q xmin=1e-5 - IF(present(inx)) x=inx + x=inx xout=xmin c --- copy input arguments to module-level globals for w_der_del_s diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index e0f0b2093..5fd3702e7 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -78,9 +78,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, INTEGER(kind=nf90_int) :: r_dim_id, r_dim ! dimension id / length (unused) INTEGER(kind=nf90_int) :: dp_id, qr_id, pr_id ! variable ids INTEGER(kind=nf90_int) :: dgeo_id, shear_id ! variable ids - INTEGER(kind=nf90_int) :: ro_id, bt0_id, psio_id ! attribute ids (see BUG FLAG 1) - INTEGER(kind=nf90_int) :: mpsi_id, msing_id ! attribute ids - INTEGER(kind=nf90_int) :: nn_id, resm_id, drr_id ! variable / attribute ids + INTEGER(kind=nf90_int) :: resm_id, drr_id ! variable ids INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! NetCDF hyperslab INTEGER :: i ! loop index INTEGER :: bt0_len, ro_len, psio_len ! attribute lengths @@ -95,11 +93,11 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) c --- read msing (number of singular surfaces) from global attribute - stat = nf90_inquire_attribute(ncid, msing_id, 'msing', + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'msing', $ len = msing_len) CALL sl_check(stat) ALLOCATE(msing_arr(msing_len)) - stat = nf90_get_att(ncid, msing_id, 'msing', msing_arr) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'msing', msing_arr) CALL sl_check(stat) msing = INT(msing_arr(1)) @@ -110,15 +108,20 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, ALLOCATE(dp_mat(msing, msing, 2)) c --- read lengths of scalar / small-array global attributes - stat = nf90_inquire_attribute(ncid, ro_id, 'ro', len=ro_len) + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'ro', + $ len=ro_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid, bt0_id, 'bt0', len=bt0_len) + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'bt0', + $ len=bt0_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid, psio_id, 'psio', len=psio_len) + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'psio', + $ len=psio_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid, mpsi_id, 'mpsi', len=mpsi_len) + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'mpsi', + $ len=mpsi_len) CALL sl_check(stat) - stat = nf90_inquire_attribute(ncid, nn_id, 'n', len=nn_len) + stat = nf90_inquire_attribute(ncid, NF90_GLOBAL, 'n', + $ len=nn_len) CALL sl_check(stat) ALLOCATE(my_bt0(INT(bt0_len)), r_o(INT(ro_len)), @@ -142,15 +145,15 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) c --- read global attributes (equilibrium scalars) - stat = nf90_get_att(ncid, ro_id, 'ro', r_o) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'ro', r_o) CALL sl_check(stat) - stat = nf90_get_att(ncid, bt0_id, 'bt0', my_bt0) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'bt0', my_bt0) CALL sl_check(stat) - stat = nf90_get_att(ncid, psio_id, 'psio', my_psio) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'psio', my_psio) CALL sl_check(stat) - stat = nf90_get_att(ncid, mpsi_id, 'mpsi', mpsi) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'mpsi', mpsi) CALL sl_check(stat) - stat = nf90_get_att(ncid, nn_id, 'n', nn) + stat = nf90_get_att(ncid, NF90_GLOBAL, 'n', nn) CALL sl_check(stat) c --- read variable data: Deltaprime matrix and 1-D surface arrays From a42fc489fca86204a6d33e1308879a0a262a2fd4 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 01:06:35 -0500 Subject: [PATCH 062/305] WIP: working on params bug --- slayer/params.f | 22 ++++++++-------------- slayer/slayer.f | 4 ++++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/slayer/params.f b/slayer/params.f index 4cde1f332..01479c038 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -159,25 +159,19 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- c Plasma beta and Prandtl-number-like transport ratios. -c -c BUG FLAG 3 -- The K_val/Csq block (kappa-based P_perp) is -c immediately overridden by the tau_perp definition below it, -c so K_val and the IF(ABS(Csq)>0) branch are dead code. -c Either remove the dead block or delete the override. c----------------------------------------------------------------------- lbeta = (5.0/3.0)*mu0*n_e*chag*(t_e+t_i) / bt**2.0 c_beta = (lbeta / (1.0+lbeta))**0.5 -c --- dead code begin (BUG FLAG 3) --- - K_val = chis(3) / eta - Csq = c_beta**2.0 + (1.0 - c_beta**2.0)*K_val +c kappa-based P_perp (JKP's definition, to implement later if desired) +c K_val = chis(3) / eta +c Csq = c_beta**2.0 + (1.0 - c_beta**2.0)*K_val - IF (ABS(Csq) > 0.0) THEN - P_perp = Csq - ELSE - tau_perp = (rs**2.0) / chis(1) - END IF -c --- dead code end (BUG FLAG 3) --- +c IF (ABS(Csq) > 0.0) THEN +c P_perp = Csq +c ELSE +c tau_perp = (rs**2.0) / chis(1) +c END IF c effective perpendicular and toroidal Prandtl numbers P_perp = tau_r / tau_perp ! perp magnetic Prandtl number diff --git a/slayer/slayer.f b/slayer/slayer.f index af088d7a8..44833456c 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -602,6 +602,10 @@ PROGRAM slayer chis(2) = chi_t_prof(1) ! chi_tor chis(3) = kappa_prof(1) ! kappa (thermal cond.) + WRITE(*,*)"chis(1) (chi_perp): ",chis(1) + WRITE(*,*)"chis(2) (chi_tor): ",chis(2) + WRITE(*,*)"chis(3) (kappa): ",chis(3) + ! Use namelist kinetic inputs instead of equilibrium files CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) From 4f5a7d9a1356472f1b71b84fb32a0aa1e70204fc Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 19 Feb 2026 02:01:30 -0500 Subject: [PATCH 063/305] WIP: fix tau_perp bug, m_AMR bug --- slayer/params.f | 1 + slayer/slayer.f | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/slayer/params.f b/slayer/params.f index 01479c038..b94ea419f 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -174,6 +174,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c END IF c effective perpendicular and toroidal Prandtl numbers + tau_perp = (rs**2.0) / chis(1) P_perp = tau_r / tau_perp ! perp magnetic Prandtl number tau_tor = (rs**2.0) / chis(2) diff --git a/slayer/slayer.f b/slayer/slayer.f index 44833456c..cf7f78bb5 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -667,9 +667,11 @@ PROGRAM slayer END IF IF (AMR_flag) THEN - m_AMR = 1 - ELSE - m_AMR = MIN(n_k,msing_max) + IF (coupling_flag) THEN + m_AMR = 1 + ELSE + m_AMR = MIN(n_k,msing_max) + END IF END IF WRITE(*,*),"Rational q domain: ",sl_in%qval_arr From 7390807e43f2b490d537da7aea68aa2e4a998d36 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 02:06:39 -0500 Subject: [PATCH 064/305] WIP: delta.f speedup attempt --- slayer/delta.f | 121 +++++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 0ad68b1dc..e32ef078a 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -346,10 +346,11 @@ FUNCTION riccati_f(tmp_g,inx) c --- lsode solver control INTEGER :: istep ! integration step counter - INTEGER :: neq ! number of equations (=2) + INTEGER, PARAMETER :: neq = 2 ! number of equations INTEGER :: itol,itask ! lsode tolerance/task flags INTEGER :: istate,iopt,mf ! lsode state/option/method flags - INTEGER :: liw,lrw ! lsode work array sizes + INTEGER, PARAMETER :: liw = 42 ! 20*2 + neq + INTEGER, PARAMETER :: lrw = 36 ! 22 + 9*neq + neq**2 REAL(r8) :: xout ! target integration endpoint REAL(r8) :: xmin ! inner integration bound REAL(r8) :: rtol ! relative tolerance @@ -361,29 +362,25 @@ FUNCTION riccati_f(tmp_g,inx) COMPLEX(r8) :: ck ! asymptotic coefficient c_k COMPLEX(r8) :: xk ! asymptotic coefficient x_k COMPLEX(r8) :: W_bound ! boundary value for W(p_start) -c --- work arrays - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dWdp ! W and dW/dp - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work - REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work +c --- work arrays (fixed-size, stack-allocated to avoid heap overhead) + COMPLEX(r8) :: W(1), dWdp(1) ! W and dW/dp + REAL(r8) :: atol(neq) ! absolute tolerance + INTEGER :: iwork(liw) ! lsode int work + REAL(r8) :: rwork(lrw) ! lsode real work c --- configure lsode: stiff BDF method with user Jacobian (mf=21) - neq = 2 - itol = 2 - rtol = 1e-10 - ALLOCATE(atol(neq),W(1),dWdp(1)) - atol(:) = 1e-10 - itask = 2 - istate = 1 - iopt = 1 ! enable optional inputs (iwork(6)) - mf = 21 ! stiff, user-supplied Jacobian (jac_f) - liw = 20*2 - lrw = 22+9*neq+neq**2 ! stiff work array size - ALLOCATE(iwork(liw+neq),rwork(lrw)) - -c --- set maximum internal steps - iwork=0 - iwork(6)=50000 ! MXSTEP: max internal steps per call - rwork=0 + itol = 2 + rtol = 1.0d-10 + atol(:) = 1.0d-10 + itask = 2 + istate = 1 + iopt = 1 ! enable optional inputs (iwork(6)) + mf = 21 ! stiff, user-supplied Jacobian (jac_f) + +c --- set maximum internal steps (only iwork(5:6) matter for lsode) + iwork(:) = 0 + iwork(6) = 50000 ! MXSTEP: max internal steps per call + rwork(:) = 0.0d0 xmin=1e-6 xout=xmin @@ -392,36 +389,33 @@ FUNCTION riccati_f(tmp_g,inx) c Branch on asymptotic regime: D_norm^2 vs iota_e*P_perp/P_tor^(2/3) c --- branch 1: D_norm^2 > iota_e * P_perp / P_tor^(2/3) c large-D_norm regime: p scales with (P_tor*D_norm^2/(iota_e*...)) - IF ((D_norm**2.0) > ((iota_e*P_perp)/(P_tor**(2.0/3.0)))) THEN - my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25 - IF (my_p < 6.0) THEN - my_p = 6.0 - END IF + IF (D_norm**2 > iota_e*P_perp / P_tor**(2.0d0/3.0d0)) THEN + my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25d0 + my_p = MAX(my_p, 6.0d0) ak = -(g_tmp + ifac*Q_e) - bk = (iota_e*P_perp*P_tor)/(P_tor*(D_norm**2.0)) + bk = (iota_e*P_perp*P_tor)/(P_tor*D_norm**2) - ck = bk*(1+(g_tmp+ifac*Q_i)*((P_tor+P_perp)/(P_tor*P_perp))- - $ (P_perp+(g_tmp + - $ ifac*Q_i)*(D_norm**2.0) )*(iota_e/(P_tor*(D_norm**2.0)))) + ck = bk*(1 + (g_tmp+ifac*Q_i) + $ *((P_tor+P_perp)/(P_tor*P_perp)) + $ - (P_perp + (g_tmp+ifac*Q_i)*D_norm**2) + $ *(iota_e/(P_tor*D_norm**2))) - xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0*SQRT(bk)) + xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0d0*SQRT(bk)) W_bound = xk - SQRT(bk)*my_p ELSE c --- branch 2: D_norm^2 <= iota_e * P_perp / P_tor^(2/3) c small-D_norm regime: p scales with 1/P_tor^(1/6) - my_p = 1.0/(P_tor**(1.0/6.0)) - IF (my_p < 6.0) THEN - my_p = 6.0 - END IF + my_p = 1.0d0 / P_tor**(1.0d0/6.0d0) + my_p = MAX(my_p, 6.0d0) ak = -(g_tmp + ifac*Q_e) bk = P_tor ck = -ifac*(Q_e - Q_i)*(P_tor/P_perp) + (g_tmp + ifac*Q_i) - xk = (ak*bk - ck)/(2.0*SQRT(bk)) + xk = (ak*bk - ck)/(2.0d0*SQRT(bk)) - W_bound = -1 + xk*my_p - SQRT(bk)*(my_p**3.0) + W_bound = -1.0d0 + xk*my_p - SQRT(bk)*my_p**3 END IF W(1) = W_bound @@ -459,7 +453,6 @@ FUNCTION riccati_f(tmp_g,inx) c --- extract Delta from final W derivative at p_min CALL w_der_f(neq,my_p,W,dWdp) riccati_f = pi / dWdp(1) - DEALLOCATE(atol,W,dWdp,iwork,rwork) END FUNCTION riccati_f c----------------------------------------------------------------------- @@ -468,14 +461,16 @@ END FUNCTION riccati_f SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) INTEGER, INTENT(IN) :: neq, ml, mu, nrpd REAL(r8), INTENT(IN) :: my_p - COMPLEX(r8) :: fA_p + COMPLEX(r8) :: fA_p, denom + REAL(r8) :: p2 COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd - fA_p = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + - $ ifac*Q_e + (my_p**2.0)) + p2 = my_p * my_p + denom = g_tmp + ifac*Q_e + p2 + fA_p = (denom - 2.0d0*p2) / denom - pd(1,1) = (-fA_p/my_p) - (2.0*W(1))/my_p + pd(1,1) = (-fA_p/my_p) - (2.0d0*W(1))/my_p END SUBROUTINE jac_f c----------------------------------------------------------------------- c w_der_f: ODE right-hand side dW/dp for riccati_f. @@ -489,20 +484,30 @@ SUBROUTINE w_der_f(neq,my_p,W,dWdp) COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dWdp COMPLEX(r8) :: fA, fA_prime, fB, fC - - ! Evaluate coefficients at the current p - fA = (my_p**2)/(g_tmp + ifac*Q_e + (my_p**2.0)) - fA_prime = (g_tmp + ifac*Q_e - (my_p**2)) / (g_tmp + - $ ifac*Q_e + (my_p**2.0)) - fB = g_tmp*(g_tmp + ifac*Q_i) + (g_tmp + - $ ifac*Q_i)*(P_perp+P_tor)*(my_p**2.0) + - $ (P_perp*P_tor)*(my_p**4.0) - fC = g_tmp + ifac*Q_e + ( P_perp + (g_tmp + - $ ifac*Q_i)*(D_norm**2.0))*(my_p**2.0) + - $ (1.0/iota_e)*P_tor*(D_norm**2.0)*(my_p**4.0) - - dWdp(1) = -(fA_prime/my_p)*W(1) - (W(1)**2.0)/my_p + - $ (fB/(fA*fC))*(my_p**3.0) + COMPLEX(r8) :: denom ! cached g_tmp + i*Q_e + p^2 + REAL(r8) :: p2, p4 ! cached p^2, p^4 + REAL(r8) :: D2 ! cached D_norm^2 + +c cache powers and shared denominator + p2 = my_p * my_p + p4 = p2 * p2 + D2 = D_norm * D_norm + denom = g_tmp + ifac*Q_e + p2 + +c evaluate coefficients at the current p + fA = p2 / denom + fA_prime = (denom - 2.0d0*p2) / denom ! (g+iQe - p^2)/(g+iQe + p^2) + + fB = g_tmp*(g_tmp + ifac*Q_i) + $ + (g_tmp + ifac*Q_i)*(P_perp + P_tor)*p2 + $ + (P_perp*P_tor)*p4 + + fC = g_tmp + ifac*Q_e + $ + (P_perp + (g_tmp + ifac*Q_i)*D2)*p2 + $ + (1.0d0/iota_e)*P_tor*D2*p4 + + dWdp(1) = -(fA_prime/my_p)*W(1) - W(1)*W(1)/my_p + $ + (fB/(fA*fC))*(p2*my_p) RETURN END SUBROUTINE w_der_f From a072e313236b124c32e040cf7564f0557e651449 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 02:12:25 -0500 Subject: [PATCH 065/305] delta.f arithmetic error --- slayer/delta.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slayer/delta.f b/slayer/delta.f index e32ef078a..22cb399a9 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -350,7 +350,7 @@ FUNCTION riccati_f(tmp_g,inx) INTEGER :: itol,itask ! lsode tolerance/task flags INTEGER :: istate,iopt,mf ! lsode state/option/method flags INTEGER, PARAMETER :: liw = 42 ! 20*2 + neq - INTEGER, PARAMETER :: lrw = 36 ! 22 + 9*neq + neq**2 + INTEGER, PARAMETER :: lrw = 44 ! 22 + 9*neq + neq**2 REAL(r8) :: xout ! target integration endpoint REAL(r8) :: xmin ! inner integration bound REAL(r8) :: rtol ! relative tolerance From c61f15e55087035eccd7794e567c5bce9976779d Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 02:25:45 -0500 Subject: [PATCH 066/305] WIP: AMR speedup --- slayer/gslayer.f | 135 ++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index edef6d8d9..c4707f41e 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -760,6 +760,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? c --- locals TYPE(amr_cell_type), ALLOCATABLE :: new_cells(:) + TYPE(amr_cell_type), ALLOCATABLE :: swap_tmp(:) ! for pointer swap INTEGER :: i, j, c, corner, pass ! loop counters REAL(r8) :: step ! grid spacing REAL(r8) :: x, y ! real / imag grid coords @@ -778,15 +779,6 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, ALLOCATE(amr_cells(MAX_CELLS)) ALLOCATE(new_cells(MAX_CELLS)) - DO i = 1, MAX_CELLS - amr_cells(i)%Q = (0.0d0, 0.0d0) - amr_cells(i)%D = (0.0d0, 0.0d0) - amr_cells(i)%needs_refine = .FALSE. - new_cells(i)%Q = (0.0d0, 0.0d0) - new_cells(i)%D = (0.0d0, 0.0d0) - new_cells(i)%needs_refine = .FALSE. - END DO - n_amr_cells = 0 step = (2.0d0 * scan_width) / DBLE(Q_num - 1) @@ -859,11 +851,11 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, END IF END DO -c swap arrays for next pass +c swap arrays for next pass (pointer swap, no element copy) + CALL MOVE_ALLOC(new_cells, swap_tmp) + CALL MOVE_ALLOC(amr_cells, new_cells) ! old amr_cells becomes new_cells + CALL MOVE_ALLOC(swap_tmp, amr_cells) ! filled array becomes amr_cells n_amr_cells = n_new_cells - DO c = 1, n_amr_cells - amr_cells(c) = new_cells(c) - END DO END DO @@ -1076,11 +1068,11 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) c --- arguments INTEGER, INTENT(IN) :: num_cells ! number of cells to flatten c --- locals - INTEGER :: c, corner, i, j, idx + INTEGER :: c, corner, i, idx INTEGER :: n_total_corners ! = num_cells * 4 COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! all corner Q-values COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! all corner D-values - LOGICAL, ALLOCATABLE :: is_unique(:) ! dedup mask + INTEGER, ALLOCATABLE :: sort_idx(:) ! sort permutation REAL(r8) :: tol ! duplicate tolerance tol = 1.0d-10 @@ -1095,11 +1087,7 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) c --- gather all corners from cells ALLOCATE(temp_Q(n_total_corners)) ALLOCATE(temp_D(n_total_corners)) - ALLOCATE(is_unique(n_total_corners)) - - temp_Q = (0.0d0, 0.0d0) - temp_D = (0.0d0, 0.0d0) - is_unique = .TRUE. + ALLOCATE(sort_idx(n_total_corners)) idx = 0 DO c = 1, num_cells @@ -1107,50 +1095,103 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) idx = idx + 1 temp_Q(idx) = amr_cells(c)%Q(corner) temp_D(idx) = amr_cells(c)%D(corner) + sort_idx(idx) = idx END DO END DO -c --- mark duplicates (O(n^2) pairwise comparison) - DO i = 1, n_total_corners - IF (.NOT. is_unique(i)) CYCLE - DO j = i + 1, n_total_corners - IF (is_unique(j)) THEN - IF (ABS(temp_Q(j) - temp_Q(i)) < tol) THEN - is_unique(j) = .FALSE. - END IF - END IF - END DO - END DO +c --- sort by (Re(Q), Im(Q)) via quicksort on the index array + CALL qsort_complex_idx(temp_Q, sort_idx, 1, n_total_corners) -c --- count unique points - n_pts = 0 - DO i = 1, n_total_corners - IF (is_unique(i)) n_pts = n_pts + 1 +c --- linear scan to count unique points (sorted order) + n_pts = 1 + DO i = 2, n_total_corners + IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) + $ >= tol) THEN + n_pts = n_pts + 1 + END IF END DO - IF (n_pts <= 0) THEN - WRITE(*,*) 'ERROR: No unique points found' - DEALLOCATE(temp_Q, temp_D, is_unique) - RETURN - END IF - c --- copy unique points to module-level output arrays IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) IF (ALLOCATED(D_store)) DEALLOCATE(D_store) ALLOCATE(Q_store(n_pts)) ALLOCATE(D_store(n_pts)) - idx = 0 - DO i = 1, n_total_corners - IF (is_unique(i)) THEN + idx = 1 + Q_store(1) = temp_Q(sort_idx(1)) + D_store(1) = temp_D(sort_idx(1)) + DO i = 2, n_total_corners + IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) + $ >= tol) THEN idx = idx + 1 - Q_store(idx) = temp_Q(i) - D_store(idx) = temp_D(i) + Q_store(idx) = temp_Q(sort_idx(i)) + D_store(idx) = temp_D(sort_idx(i)) END IF END DO - DEALLOCATE(temp_Q, temp_D, is_unique) + DEALLOCATE(temp_Q, temp_D, sort_idx) RETURN END SUBROUTINE flatten_cells_to_points_sub + +c----------------------------------------------------------------------- +c qsort_complex_idx: in-place quicksort of an index array by +c the complex keys (Re then Im). Operates on sort_idx so the +c Q/D data arrays remain untouched. +c----------------------------------------------------------------------- + RECURSIVE SUBROUTINE qsort_complex_idx(keys, idx, lo, hi) + + IMPLICIT NONE + + COMPLEX(r8), INTENT(IN) :: keys(:) + INTEGER, INTENT(INOUT) :: idx(:) + INTEGER, INTENT(IN) :: lo, hi + + INTEGER :: i, j, pivot_idx, tmp + REAL(r8) :: p_re, p_im, k_re, k_im + + IF (lo >= hi) RETURN + +c median-of-three pivot selection + pivot_idx = idx((lo + hi) / 2) + p_re = REAL(keys(pivot_idx), KIND=r8) + p_im = AIMAG(keys(pivot_idx)) + + i = lo + j = hi + DO WHILE (i <= j) +c advance i while keys(idx(i)) < pivot + k_re = REAL(keys(idx(i)), KIND=r8) + k_im = AIMAG(keys(idx(i))) + DO WHILE (k_re < p_re .OR. + $ (k_re == p_re .AND. k_im < p_im)) + i = i + 1 + k_re = REAL(keys(idx(i)), KIND=r8) + k_im = AIMAG(keys(idx(i))) + END DO +c retreat j while keys(idx(j)) > pivot + k_re = REAL(keys(idx(j)), KIND=r8) + k_im = AIMAG(keys(idx(j))) + DO WHILE (k_re > p_re .OR. + $ (k_re == p_re .AND. k_im > p_im)) + j = j - 1 + k_re = REAL(keys(idx(j)), KIND=r8) + k_im = AIMAG(keys(idx(j))) + END DO +c swap if pointers haven't crossed + IF (i <= j) THEN + tmp = idx(i) + idx(i) = idx(j) + idx(j) = tmp + i = i + 1 + j = j - 1 + END IF + END DO + +c recurse on partitions + IF (lo < j) CALL qsort_complex_idx(keys, idx, lo, j) + IF (i < hi) CALL qsort_complex_idx(keys, idx, i, hi) + + RETURN + END SUBROUTINE qsort_complex_idx END MODULE gslayer_mod \ No newline at end of file From 175413ffb8689cc3e6576dabec9398d576f4a13f Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 02:30:33 -0500 Subject: [PATCH 067/305] WIP: hashed midpoint computation fix --- slayer/gslayer.f | 148 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 25 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index c4707f41e..21fd7093e 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -16,9 +16,9 @@ MODULE gslayer_mod c 10. dispersion_AMR_v2 - AMR scan v2 (cell-based storage) c c Helper subroutines (v1): get_or_compute -c Helper subroutines (v2): compute_delta_sub, -c check_cell_crossing_sub, subdivide_cell_sub, -c flatten_cells_to_points_sub +c Helper subroutines (v2): get_or_compute_v2, +c compute_delta_sub, check_cell_crossing_sub, +c subdivide_cell_sub, flatten_cells_to_points_sub c----------------------------------------------------------------------- USE omp_lib @@ -731,6 +731,73 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, hash_head(h) = idx_out END SUBROUTINE get_or_compute + +c----------------------------------------------------------------------- +c get_or_compute_v2: hash-cached dispersion evaluation for AMR v2. +c Identical to get_or_compute but applies the ifac (imaginary-unit) +c Wick rotation that compute_delta_sub uses: g_tmp = q_in * ifac. +c----------------------------------------------------------------------- + SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, + $ msing_max, coupling_flag) + + IMPLICIT NONE + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in + INTEGER, INTENT(OUT) :: idx_out + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + +c --- locals + INTEGER :: h, curr + COMPLEX(r8) :: delta_val + INTEGER(8) :: ix8, iy8, h8 + +c --- 1. compute hash bucket + ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) + iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) + h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), + $ INT(HASH_SZ, 8)) + 1_8 + h = INT(h8) + +c --- 2. search hash chain for existing point + curr = hash_head(h) + DO WHILE (curr /= 0) + IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN + idx_out = curr + RETURN + END IF + curr = hash_next(curr) + END DO + +c --- 3. not found: evaluate with ifac rotation and store + n_pts = n_pts + 1 + IF (n_pts > MAX_PTS) THEN + WRITE(*,*) 'ERROR: AMR v2 cache exceeded MAX_PTS' + STOP 'get_or_compute_v2: MAX_PTS exceeded' + END IF + + idx_out = n_pts + Q_store(idx_out) = q_in + + IF (coupling_flag) THEN + g_tmp = q_in * ifac + delta_val = dispersion_det(g_tmp, n_k, sl_in, + $ msing_max) + ELSE + g_tmp = q_in * ifac + delta_val = riccati_f(g_tmp) + delta_val = delta_val - delta_eff + END IF + D_store(idx_out) = delta_val + +c --- 4. insert into hash chain (prepend) + hash_next(idx_out) = hash_head(h) + hash_head(h) = idx_out + + END SUBROUTINE get_or_compute_v2 + c----------------------------------------------------------------------- c dispersion_AMR_v2: cell-based adaptive mesh refinement scanner. c Unlike v1 (hash-based point deduplication), v2 stores complete @@ -739,8 +806,11 @@ END SUBROUTINE get_or_compute c Re(D) or Im(D) and re-evaluates the dispersion relation at the c 5 new midpoints. c -c After refinement, flatten_cells_to_points_sub extracts unique -c (Q, D) points into Q_store / D_store for output. +c All dispersion evaluations go through get_or_compute_v2, which +c caches results in Q_store / D_store via a hash table. This +c eliminates redundant evaluations for shared corners (initial grid) +c and shared edge-midpoints (refinement). At completion, Q_store +c and D_store are trimmed to n_pts unique output points. c c BUG FLAG 9 – several bare stop statements should carry messages. c----------------------------------------------------------------------- @@ -764,21 +834,32 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, INTEGER :: i, j, c, corner, pass ! loop counters REAL(r8) :: step ! grid spacing REAL(r8) :: x, y ! real / imag grid coords - COMPLEX(r8) :: delta_val ! (unused – computed inside helper) LOGICAL :: cross_real, cross_imag ! zero-crossing flags INTEGER :: n_new_cells ! count during refinement INTEGER :: cells_to_refine ! cells flagged per pass INTEGER :: cells_kept ! cells kept per pass + INTEGER :: idx_tmp ! hash-cache index + COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! for trimming output + COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! for trimming output -c --- 1. initialise cell storage +c --- 1. initialise cell storage and hash cache IF (ALLOCATED(amr_cells)) DEALLOCATE(amr_cells) IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) ALLOCATE(amr_cells(MAX_CELLS)) ALLOCATE(new_cells(MAX_CELLS)) + ALLOCATE(Q_store(MAX_PTS)) + ALLOCATE(D_store(MAX_PTS)) + ALLOCATE(hash_head(HASH_SZ)) + ALLOCATE(hash_next(MAX_PTS)) + hash_head = 0 + hash_next = 0 + n_pts = 0 n_amr_cells = 0 step = (2.0d0 * scan_width) / DBLE(Q_num - 1) @@ -803,12 +884,14 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, $ KIND=r8) -c evaluate dispersion at each corner +c evaluate dispersion at each corner (hash-cached) DO corner = 1, 4 - CALL compute_delta_sub( + CALL get_or_compute_v2( $ amr_cells(n_amr_cells)%Q(corner), - $ n_k, sl_in, msing_max, coupling_flag, - $ amr_cells(n_amr_cells)%D(corner)) + $ idx_tmp, n_k, sl_in, msing_max, + $ coupling_flag) + amr_cells(n_amr_cells)%D(corner) = + $ D_store(idx_tmp) END DO amr_cells(n_amr_cells)%needs_refine = .FALSE. @@ -859,9 +942,18 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, END DO -c --- 4. flatten cells to unique (Q, D) output arrays - CALL flatten_cells_to_points_sub(n_amr_cells) +c --- 4. output: Q_store/D_store already populated by hash cache. +c Trim to exact size n_pts and deallocate hash infrastructure. + ALLOCATE(temp_Q(n_pts)) + ALLOCATE(temp_D(n_pts)) + temp_Q(1:n_pts) = Q_store(1:n_pts) + temp_D(1:n_pts) = D_store(1:n_pts) + CALL MOVE_ALLOC(temp_Q, Q_store) + CALL MOVE_ALLOC(temp_D, D_store) + + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) DEALLOCATE(new_cells) c keep amr_cells allocated for potential post-run inspection @@ -959,9 +1051,10 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, c --- corner coordinates and D-values from parent COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr -c --- midpoint coordinates and D-values (5 new evaluations) +c --- midpoint coordinates and D-values (cached via hash) COMPLEX(r8) :: q_bm, q_tm, q_lm, q_rm, q_mm COMPLEX(r8) :: d_bm, d_tm, d_lm, d_rm, d_mm + INTEGER :: idx_tmp ! hash-cache index c --- extract parent corners (BL=1, BR=2, TL=3, TR=4) q_bl = parent%Q(1) @@ -981,17 +1074,22 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, q_rm = 0.5d0 * (q_br + q_tr) q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) -c --- evaluate dispersion at new midpoints (5 calls) - CALL compute_delta_sub(q_bm, n_k, sl_in, msing_max, - $ coupling_flag, d_bm) - CALL compute_delta_sub(q_tm, n_k, sl_in, msing_max, - $ coupling_flag, d_tm) - CALL compute_delta_sub(q_lm, n_k, sl_in, msing_max, - $ coupling_flag, d_lm) - CALL compute_delta_sub(q_rm, n_k, sl_in, msing_max, - $ coupling_flag, d_rm) - CALL compute_delta_sub(q_mm, n_k, sl_in, msing_max, - $ coupling_flag, d_mm) +c --- evaluate dispersion at new midpoints (hash-cached) + CALL get_or_compute_v2(q_bm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_bm = D_store(idx_tmp) + CALL get_or_compute_v2(q_tm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_tm = D_store(idx_tmp) + CALL get_or_compute_v2(q_lm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_lm = D_store(idx_tmp) + CALL get_or_compute_v2(q_rm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_rm = D_store(idx_tmp) + CALL get_or_compute_v2(q_mm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_mm = D_store(idx_tmp) c --- check space for 4 new cells IF (n_new + 4 > max_cells) THEN From 01039e55b2e63145833c930c5d7c5641dc6b3b1f Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 13:45:05 -0500 Subject: [PATCH 068/305] WIP: ongoing diagnosing of erroneous nyquist diagrams --- slayer/gslayer.f | 5 +++++ slayer/slayer.f | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 21fd7093e..695288f32 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -958,6 +958,11 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, c keep amr_cells allocated for potential post-run inspection WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts + WRITE(*,'(A,2ES14.6)') ' D_store checksum (Re,Im):', + $ SUM(REAL(D_store(1:n_pts))), + $ SUM(AIMAG(D_store(1:n_pts))) + WRITE(*,'(A,2ES14.6)') ' D_store(1) sample:', + $ REAL(D_store(1)), AIMAG(D_store(1)) RETURN END SUBROUTINE dispersion_AMR_v2 diff --git a/slayer/slayer.f b/slayer/slayer.f index cf7f78bb5..3f43ec3e5 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -753,6 +753,13 @@ PROGRAM slayer ! Clean up temporary AMR memory. DEALLOCATE(Q_store, D_store) + WRITE(*,'(A,I2,A,I7,A,2ES14.6)') + $ ' Surface', k, ': n_pts=', + $ SIZE(all_deltas_out(k)%real_deltas), + $ ' out_chksum=', + $ SUM(all_deltas_out(k)%real_deltas), + $ SUM(all_deltas_out(k)%imag_deltas) + END IF c----------------------------------------------------------------------- From 32985ece9e0c824f7b0a3bcf54aa16e979fb7706 Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 19 Feb 2026 18:30:10 -0500 Subject: [PATCH 069/305] WIP: fix makefile ordering --- slayer/makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slayer/makefile b/slayer/makefile index 2326a9c91..39c4217ba 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -54,7 +54,8 @@ slayer: $(OBJS) sglobal.o: ../equil/local_mod.mod params.o : sglobal.o delta.o : sglobal.o -gslayer.o : sglobal.o delta.o +layerinputs.o : sglobal.o params.o slayer_netcdf.o +gslayer.o : sglobal.o params.o delta.o layerinputs.o slayer_netcdf.o slayer.o : sglobal.o params.o delta.o ../equil/spline_mod.mod slayer_netcdf.o : sglobal.o From 4fb3329ec585164046fd2c85da2cee504b0bb2b5 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 20 Feb 2026 19:43:26 -0500 Subject: [PATCH 070/305] WIP: set coupling via msing_max --- slayer/gslayer.f | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 695288f32..36135b0d9 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -415,7 +415,7 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) INTEGER :: k ! surface loop index c --- single-surface branch - IF (n_k < 2) THEN + IF (msing_max < 2) THEN c set module-level variables for riccati_f Q_e = sl_in%Q_e_arr(1) Q_i = sl_in%Q_i_arr(1) @@ -435,8 +435,8 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) dispersion_det = sl_in%Re_dp_arr(1) - det_val c --- coupled-surface branch (2 or 3 surfaces) - ELSEIF ((n_k == 2) .OR. (n_k == 3)) THEN - ALLOCATE(delta_Q(n_k,n_k)) + ELSEIF ((msing_max == 2) .OR. (msing_max == 3)) THEN + ALLOCATE(delta_Q(msing_max,msing_max)) delta_Q=(0.0,0.0) DO k=1,msing_max c set module-level variables for this surface From 631b9c8e59aad7ef21d6b0dd113f84a129add5d3 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 20 Feb 2026 19:52:06 -0500 Subject: [PATCH 071/305] WIP: fix determinant msing_max case --- slayer/gslayer.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 36135b0d9..78ac71187 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -458,7 +458,7 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) c compute det(dp_matrix - delta_Q) result_matrix = sl_in%dp_matrix - delta_Q - CALL calc_determinant(result_matrix, n_k, det_val) + CALL calc_determinant(result_matrix, msing_max, det_val) dispersion_det = det_val ELSE WRITE(*,*) "Error: no support for msing > 3" From b7c29363b46b5f5d61c436201d83c6da6c390a93 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 18:25:19 -0500 Subject: [PATCH 072/305] new SLAYER cleanup complete, first pass --- slayer/delta.f | 51 +++++--------- slayer/gslayer.f | 94 ++++++++++++++----------- slayer/layerinputs.f | 90 ++++-------------------- slayer/makefile | 20 +++++- slayer/params.f | 28 ++++---- slayer/sglobal.f | 31 +++------ slayer/slayer.f | 116 +++++++++++++++--------------- slayer/slayer_netcdf.f | 155 ++++++++++++++++------------------------- 8 files changed, 239 insertions(+), 346 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 22cb399a9..8ef9bff22 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -38,10 +38,8 @@ MODULE delta_mod c guard when Q is near Q_e. Returns Delta = pi / W'(x_min). c c If riccati_out = .TRUE., writes the W(x) profile to -c slayer_riccati_profile_n.{bin,out}. +c slayer_riccati_profile_n.{bin,out}. c -c BUG FLAG 1: xintv and xfac are declared but never used. -c Remove them. c----------------------------------------------------------------------- FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, $ iinQ,inx,iny) @@ -113,11 +111,11 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, rwork=0 c --- boundary condition: asymptotic W at large x -c BUG FLAG 2: inline comment said "To be updated" -- verify formula. x=20.0 xmin=1e-3 IF(present(inx)) x=inx xout=xmin +c DECISION NEEDED (Flag 2): verify this large-|x| asymptotic BC. y(1)=-c_beta/sqrt((1+tau))/ds*x**2.0 IF(present(iny)) y(1)=iny @@ -128,11 +126,11 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, istep = 1 itask = 2 OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.bin',STATUS='UNKNOWN', + $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', $ POSITION='REWIND',FORM='UNFORMATTED') OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.out',STATUS='UNKNOWN') + $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') WRITE(out2_unit,'(1x,3(a17))') "x","RE(y)","IM(y)" DO WHILE (x>xout) istep=istep+1 @@ -165,12 +163,12 @@ END FUNCTION riccati c (jac_del_s). Integrates W(q) from large q inward to q_min. c Returns Delta = -(pi / sqrt(1+1/tau)) * W'(q_min). c -c BUG FLAG 3: arguments inQ, inc_beta, ind_beta, intau are never -c used to set their module-level counterparts (Q, c_beta, d_beta, -c tau). Either add assignments (e.g. tau=intau) or remove the -c unused arguments if the caller sets them beforehand. -c BUG FLAG 4: variables y, dy, xfac, xintv, ml, mu, nrpd are -c declared but never used -- remove them. +c Module-level variables tau, D_norm, and P_tor must be valid +c before entry; callers are responsible for setting them. +c Q_e, Q_i, and P_perp are set from arguments inside this +c function. Q, c_beta, and d_beta are not referenced here. +c Note: slayer.f passes Q_e_arr (not Q_arr) as the first +c argument -- this is intentional. c----------------------------------------------------------------------- FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) @@ -233,8 +231,6 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) Q_i = inQ_i P_perp = inpr -c --- BUG FLAG 6: P_hat was computed BEFORE P_perp=inpr, so it used -c the stale module-level P_perp. Now moved after assignment. P_hat = P_perp / D_norm**6.0 c --- asymptotic boundary condition at large q @@ -248,11 +244,11 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) istep = 1 itask = 2 OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.bin',STATUS='UNKNOWN', + $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', $ POSITION='REWIND',FORM='UNFORMATTED') OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.out',STATUS='UNKNOWN') + $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" DO WHILE (my_q>xout) istep=istep+1 @@ -304,10 +300,7 @@ SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) c --- normalise physical quantities Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 P_perp_hat = P_perp / D_norm**6.0 -c BUG FLAG 7: P_tor_hat is assigned from P_perp, not P_tor. -c Benchmark values differ (P_perp_hat=0.377, P_tor_hat=1.15), -c suggesting this should be: P_tor_hat = P_tor / D_norm**6.0 - P_tor_hat = P_perp / D_norm**6.0 + P_tor_hat = P_tor / D_norm**6.0 c --- build the E and F dispersion coefficients E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) @@ -326,21 +319,9 @@ END SUBROUTINE w_der_del_s c large-p asymptotic behaviour; the branch depends on whether c D_norm^2 exceeds iota_e * P_perp / P_tor^(2/3). c -c BUG FLAG 8: argument tmp_g (COMPLEX) is never used in the -c function body. All references use the module-level variable -c g_tmp instead. The caller (slayer.f:785) passes g_tmp as -c tmp_g, so in practice the values match -- but tmp_g is -c redundant. Likely needs: g_tmp = tmp_g at the top, or -c remove the argument and rely on the module variable. -c BUG FLAG 9: variables xintv, xfac, y, dy, ck_1, ck_2, ml, mu, -c nrpd, alpha are declared but never used -- remove them. -c Optional argument inx is also never referenced in the body. c----------------------------------------------------------------------- - FUNCTION riccati_f(tmp_g,inx) + FUNCTION riccati_f() -c --- input arguments - COMPLEX(r8),INTENT(IN) :: tmp_g ! growth rate (UNUSED -- BUG FLAG 8) - REAL(r8),INTENT(IN),OPTIONAL :: inx ! (UNUSED -- BUG FLAG 9) c --- function result COMPLEX(r8) :: riccati_f @@ -426,11 +407,11 @@ FUNCTION riccati_f(tmp_g,inx) istep = 1 itask = 2 OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.bin',STATUS='UNKNOWN', + $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', $ POSITION='REWIND',FORM='UNFORMATTED') OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn)//'.out',STATUS='UNKNOWN') + $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" DO WHILE (my_p>xout) istep=istep+1 diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 78ac71187..8c272efc5 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -36,8 +36,8 @@ MODULE gslayer_mod $ MAX_CELLS, ! v2 limit $ slayer_inputs_type,slayer_outputs_type, $ deltas_outputs_type, - $ tau_r,d_crit,delta_det,dc_tmp,dc_type, - $ sn,sm + $ tau_r,dc_tmp,dc_type, + $ sn_str,sm_str USE delta_mod, ONLY: riccati,riccati_f,riccati_out, $ parflow_flag,PeOhmOnly_flag USE params_mod @@ -46,6 +46,10 @@ MODULE gslayer_mod IMPLICIT NONE +c --- reconnection regulariser used in psi0 / JxB expressions; +c expose via namelist to make user-configurable. + REAL(r8), PARAMETER :: DELTA_N_PERT = 1.0e-2_r8 + CONTAINS c----------------------------------------------------------------------- @@ -60,11 +64,10 @@ MODULE gslayer_mod c 3. Scan over a range of Q (rotation) to build a torque c balance curve, then identify the critical threshold br_th. c -c BUG FLAG 1: `index` variable shadows the Fortran intrinsic -c INDEX(). Rename to e.g. `max_idx`. -c BUG FLAG 2: `iinQs` is declared but never used. Remove it. -c BUG FLAG 3: `zeff`, `qval`, `v_a`, `inlu` are declared / -c passed in but never used. Remove or document intent. +c TODO: `zeff` and `qval` are INTENT(IN) +c arguments accepted in the interface but never referenced in +c the subroutine body. Remove from signature (breaking API +c change) or add a comment documenting their reserved intent. c----------------------------------------------------------------------- SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, $ omega_i,qval,sval,bt,rs,R0,mu_i,inpr,mms,nns,ascii_flag, @@ -75,11 +78,11 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, REAL(r8),INTENT(IN) :: t_e ! electron temperature [eV] REAL(r8),INTENT(IN) :: n_i ! ion density [m^-3] REAL(r8),INTENT(IN) :: t_i ! ion temperature [eV] - REAL(r8),INTENT(IN) :: zeff ! (UNUSED -- BUG FLAG 3) + REAL(r8),INTENT(IN) :: zeff ! (TODO: unused; see header) REAL(r8),INTENT(IN) :: omega ! plasma rotation frequency REAL(r8),INTENT(IN) :: omega_e ! electron diamagnetic freq REAL(r8),INTENT(IN) :: omega_i ! ion diamagnetic freq - REAL(r8),INTENT(IN) :: qval ! (UNUSED -- BUG FLAG 3) + REAL(r8),INTENT(IN) :: qval ! (TODO: unused; see header) REAL(r8),INTENT(IN) :: sval ! magnetic shear REAL(r8),INTENT(IN) :: bt ! toroidal field [T] REAL(r8),INTENT(IN) :: rs ! minor radius of surface [m] @@ -197,10 +200,10 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, Q0=Q c----------------------------------------------------------------------- c compute baseline Delta, reconnected flux, and JxB torque. -c BUG FLAG 4: delta_n_p = 1e-2 is hardcoded; should this come -c from the namelist or caller? +c delta_n_p uses the module-level DELTA_N_PERT constant; promote +c that constant to a namelist input to make it user-configurable. c----------------------------------------------------------------------- - delta_n_p=1e-2 + delta_n_p = DELTA_N_PERT delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux (a.u.) jxb=-AIMAG(1.0/(delta+delta_n_p)) ! JxB torque (a.u.) @@ -209,9 +212,11 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, c compute delta(Q), JxB(Q), and the balance parameter. c The threshold br_th is sqrt(max(bal)/lu * s^2/2). c -c BUG FLAG 5: scan bounds from Q0/Q_e physics (lines above) are -c immediately overridden by hardcoded inQ_max=10, inQ_min=-10. -c Either remove the dead code above or use the physics bounds. +c TODO: physics bounds from Q0/Q_e (computed +c in the IF/ELSE above) are immediately overridden by the two +c fixed assignments below. To use physics bounds, remove the +c inQ_max=10 / inQ_min=-10 lines. To keep fixed bounds, remove +c the dead IF/ELSE block above. c----------------------------------------------------------------------- IF (Q0>inQ_e) THEN inQ_max=2.0*Q0 @@ -225,7 +230,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, ENDIF ENDIF - inQ_max=10.0 ! hardcoded override (see BUG FLAG 5) + inQ_max=10.0 ! TODO: fixed bound; see header inQ_min=-10.0 inum=200 ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum),bal(0:inum)) @@ -347,11 +352,9 @@ END SUBROUTINE grow_array c subprogram 7. calc_determinant. c Compute the determinant of a 2x2 or 3x3 complex matrix. c Returns (0,0) and sets status=-1 for unsupported sizes. -c -c BUG FLAG 6: variable `status` is computed but never returned -c to the caller. Either add an INTENT(OUT) argument or remove. +c status=0 on success, -1 when nk is neither 2 nor 3. c----------------------------------------------------------------------- - SUBROUTINE calc_determinant(matk, nk, detk) + SUBROUTINE calc_determinant(matk, nk, detk, status) IMPLICIT NONE @@ -359,8 +362,7 @@ SUBROUTINE calc_determinant(matk, nk, detk) INTEGER, INTENT(IN) :: nk ! matrix rank (2 or 3) COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! input matrix COMPLEX(r8), INTENT(OUT) :: detk ! determinant result -c --- local - INTEGER :: status ! error status (unused externally) + INTEGER, INTENT(OUT) :: status ! 0=success, -1=unsupported rank status = 0 ! Initialize status as success @@ -389,20 +391,18 @@ END SUBROUTINE calc_determinant c Compute the coupled dispersion determinant for n_k surfaces. c c For n_k = 1 (single surface): -c Evaluate riccati_f(g_tmp), de-normalise by lu^(1/3), and +c Evaluate riccati_f() (uses module g_tmp), de-normalise by lu^(1/3), and c return Deltaprime - delta(Q). c c For n_k = 2 or 3 (coupled surfaces): c Build the diagonal delta(Q) matrix, subtract from dp_matrix, c and return det(dp_matrix - delta_Q). c -c BUG FLAG 7: bare `stop` on n_k > 3 -- use STOP with message -c consistently, or return a NaN sentinel. c----------------------------------------------------------------------- - FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) + FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c --- arguments - COMPLEX(r8), INTENT(IN) :: g_tmp ! complex growth rate + COMPLEX(r8), INTENT(IN) :: g_in ! complex growth rate INTEGER, INTENT(IN) :: n_k ! number of surfaces INTEGER, INTENT(IN) :: msing_max ! max surfaces to include TYPE(slayer_inputs_type), INTENT(IN) :: sl_in @@ -413,6 +413,7 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:) ! diagonal delta matrix COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! dp - delta_Q INTEGER :: k ! surface loop index + INTEGER :: det_status ! calc_determinant error status c --- single-surface branch IF (msing_max < 2) THEN @@ -427,7 +428,8 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) tauk = sl_in%Qconv_arr(1) iota_e = Q_e / (Q_e - Q_i) - tmp_delta=riccati_f(g_tmp) + g_tmp = g_in + tmp_delta=riccati_f() c de-normalise delta by lu^(1/3) det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) @@ -451,14 +453,20 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) iota_e = Q_e / (Q_e - Q_i) c evaluate riccati_f at rescaled growth rate, de-normalise - delta_Q(k,k)=riccati_f(((g_tmp*sl_in%Qconv_arr(1)) - $ /tauk)) + g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk ! sets module-level g_tmp to SCALED value + delta_Q(k,k)=riccati_f() delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) END DO c compute det(dp_matrix - delta_Q) result_matrix = sl_in%dp_matrix - delta_Q - CALL calc_determinant(result_matrix, msing_max, det_val) + CALL calc_determinant(result_matrix, msing_max, det_val, + $ det_status) + IF (det_status /= 0) THEN + WRITE(*,*) 'ERROR: calc_determinant unsupported rank=', + $ msing_max + STOP 'dispersion_det: calc_determinant failed' + END IF dispersion_det = det_val ELSE WRITE(*,*) "Error: no support for msing > 3" @@ -467,7 +475,8 @@ FUNCTION dispersion_det(g_tmp,n_k,sl_in,msing_max) END FUNCTION dispersion_det c----------------------------------------------------------------------- -c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc Scans a 2D complex-Q grid for zeros of the dispersion relation +c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc +c Scans a 2D complex-Q grid for zeros of the dispersion relation c D(Q) using adaptive refinement. A coarse grid is evaluated c first (two-pass: nodes then cells), then cells that span a zero c crossing in Re(D) or Im(D) are subdivided. @@ -475,9 +484,6 @@ END FUNCTION dispersion_det c Point deduplication uses a spatial hash table (HASH_SZ buckets, c chained) so that midpoints shared between neighbouring cells c are evaluated only once. -c -c BUG FLAG 8 \u2013 h_idx, pt_idx are declared but never referenced. -c Remove them. c----------------------------------------------------------------------- SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, $ scan_width,Q_num,AMR_passes, @@ -496,7 +502,7 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, INTEGER, ALLOCATABLE :: new_cells(:,:) ! scratch for next level c --- loop / index variables INTEGER :: n_cells, n_new_cells, i, j - INTEGER :: h_idx, pt_idx, c_idx, pass ! h_idx, pt_idx UNUSED (BUG FLAG 8) + INTEGER :: c_idx, pass INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! corner indices INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! midpoint indices c --- scan workspace @@ -721,7 +727,7 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) ELSE g_tmp = q_in - delta_val = riccati_f(g_tmp) + delta_val = riccati_f() delta_val = delta_val - delta_eff END IF D_store(idx_out) = delta_val @@ -787,7 +793,7 @@ SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, $ msing_max) ELSE g_tmp = q_in * ifac - delta_val = riccati_f(g_tmp) + delta_val = riccati_f() delta_val = delta_val - delta_eff END IF D_store(idx_out) = delta_val @@ -812,7 +818,6 @@ END SUBROUTINE get_or_compute_v2 c and shared edge-midpoints (refinement). At completion, Q_store c and D_store are trimmed to n_pts unique output points. c -c BUG FLAG 9 – several bare stop statements should carry messages. c----------------------------------------------------------------------- SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, $ scan_width, Q_num, AMR_passes, @@ -991,7 +996,7 @@ SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) ELSE g_tmp = q_in*ifac - delta_out = riccati_f(g_tmp) + delta_out = riccati_f() delta_out = delta_out - delta_eff END IF @@ -1161,8 +1166,7 @@ END SUBROUTINE subdivide_cell_sub c for moderate cell counts; could be replaced by a hash set for c very large scans. c -c BUG FLAG 10 – n_total_corners could exceed MAX_PTS for large -c grids. No guard is present. +c n_total_corners = num_cells*4; guarded against MAX_PTS overflow. c----------------------------------------------------------------------- SUBROUTINE flatten_cells_to_points_sub(num_cells) @@ -1181,6 +1185,12 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) tol = 1.0d-10 n_total_corners = num_cells * 4 + IF (n_total_corners > MAX_PTS) THEN + WRITE(*,*) 'ERROR: n_total_corners=', n_total_corners, + $ ' exceeds MAX_PTS=', MAX_PTS + STOP 'flatten_cells_to_points_sub: MAX_PTS exceeded' + END IF + IF (num_cells <= 0) THEN WRITE(*,*) 'ERROR: No cells to flatten' n_pts = 0 diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 5fd3702e7..12377493b 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -33,17 +33,8 @@ MODULE layerinputs_mod c Read the STRIDE NetCDF file and extract the Deltaprime matrix, c rational-surface geometry (q, psi_n, shear, dgeo, dr), and c equilibrium scalars (R0, B_t0, psi0, m_psi, n, resm). -c -c BUG FLAG 1 -- NetCDF variable IDs (dp_id, qr_id, ...) are -c declared but never initialised via nf90_inq_varid before -c being passed to nf90_inquire_attribute. The attribute -c reads for ro, bt0, psio, mpsi, n use *_id variables that -c are still zero at that point; the calls appear to succeed -c only because the NetCDF library treats the id argument as -c a global-attribute flag when it is NF90_GLOBAL (=0). -c Suggested fix: either replace the id arguments with -c NF90_GLOBAL explicitly, or move the nf90_inq_varid calls -c above the nf90_inquire_attribute calls. +c All global-attribute reads use NF90_GLOBAL explicitly; +c variable IDs are obtained via nf90_inq_varid before use. c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, @@ -197,15 +188,7 @@ END SUBROUTINE read_stride_netcdf_diagonal c Geometry is cached via first/fsave/psave to avoid recomputing c Jacobians when the same surface is queried repeatedly. c -c BUG FLAG 2 -- `first = .FALSE.` is commented out (line after -c `fsave = fs`). The caching logic therefore never sets -c first=.FALSE., so geometry is recomputed on every call -c even when psi and fs are unchanged. Uncomment the line -c or remove the caching branch entirely. -c -c BUG FLAG 3 -- `z` (toroidal-Z coordinate) is computed inside -c the geometry loop but never used outside it. Remove or -c document its intended purpose. + c----------------------------------------------------------------------- FUNCTION issurfint(func,fs,inpsi,wegt,ave, $ fsave,psave,jacs,delpsi,inr,ina,first) @@ -219,7 +202,7 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, REAL(r8), INTENT(IN) :: inpsi ! normalised psi of surface REAL(r8), DIMENSION(0:fs), INTENT(IN) :: func ! integrand array - LOGICAL, INTENT(INOUT) :: first ! .TRUE. on first call (see BUG FLAG 2) + LOGICAL, INTENT(INOUT) :: first ! .TRUE. on first call for caching INTEGER, INTENT(INOUT) :: fsave ! cached fs REAL(r8), INTENT(INOUT) :: psave ! cached psi REAL(r8), DIMENSION(0:), INTENT(INOUT) :: jacs ! Jacobian cache @@ -233,7 +216,6 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, INTEGER :: ix, iy ! bicube grid hints REAL(r8) :: rfac, ineta, injac, inarea ! geometry intermediates REAL(r8), DIMENSION(1,2) :: w ! gradient components - REAL(r8), DIMENSION(0:fs) :: z ! Z coords (UNUSED -- BUG FLAG 3) REAL(r8), DIMENSION(0:fs) :: thetas ! normalised theta grid REAL(r8), DIMENSION(4) :: rzphi_f, rzphi_fx, rzphi_fy ! bicube_eval_external outputs @@ -249,7 +231,7 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, IF (first .OR. inpsi /= psave .OR. fs /= fsave) THEN psave = inpsi fsave = fs - !first = .FALSE. ! BUG FLAG 2: should be uncommented + first = .FALSE. DO itheta = 0, fs thetas(itheta) = REAL(itheta, r8) / REAL(fs, r8) ENDDO @@ -260,7 +242,6 @@ FUNCTION issurfint(func,fs,inpsi,wegt,ave, ineta = twopi * (thetas(itheta) + rzphi_f(2)) ina(itheta) = rfac inr(itheta) = ro + rfac * COS(ineta) - z(itheta) = zo + rfac * SIN(ineta) injac = rzphi_f(4) jacs(itheta) = injac c gradient magnitude: |grad psi| from metric components @@ -321,32 +302,7 @@ END FUNCTION issurfint c loops over every singular surface to compute derived layer c parameters via params() and populate slayer_inputs_type. c -c BUG FLAG 4 -- many local scalars declared here (lpsi, rpsi, -c hdist, sbnosurf, spl, sr, my_inpe, tau_i, b_l, v_a, tau_h, -c rho, tau_v, Qconv, lbeta, qintb, tau_ee_num..chi_par, -c psitor, rhotor, my_rhotor, my_psitor, rfac, jac, wit) -c are never used. They appear to be left over from an earlier -c version. Remove to reduce confusion. -c -c BUG FLAG 5 -- `ising` is declared REAL(r8) but is used as a -c DO-loop index (integer context). This works in Fortran but -c is non-standard in F90+ and may fail with strict compilers. -c Declare as INTEGER. -c -c BUG FLAG 6 -- `zeff = 2.0` is hardcoded on every surface -c (the kin%f(9) alternative is commented out). If the -c kinetic file provides Z_eff, this should use it. -c -c BUG FLAG 7 -- `mrs` and `nrs` are assigned -c `real(mms,4)` / `real(nns,4)` (single precision) but are -c declared INTEGER, so the float is silently truncated. -c They are also never used afterwards. Remove or fix. -c -c BUG FLAG 8 -- the local arrays ne_arr, te_arr, ... mu_i_arr, -c nns_arr, dr_arr, omegas_e_arr, omegas_i_arr are populated -c inside the loop but never used outside it (the old -c slayer_netcdf_inputs call is commented out). Remove or -c gate behind a diagnostic flag. + c----------------------------------------------------------------------- SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- @@ -359,51 +315,33 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c --- surface-loop control LOGICAL :: firstsurf ! first-call flag for issurfint REAL(r8) :: respsi ! normalised psi at current surface - REAL(r8) :: ising ! loop index (BUG FLAG 5: should be INTEGER) -c --- unused scalars (BUG FLAG 4 -- remove) - REAL(r8) :: lpsi, rpsi, hdist, sbnosurf + INTEGER :: ising ! loop index over singular surfaces c --- kinetic profile parameters (for read_kin) INTEGER :: zi, zimp, mi, mimp ! charge/mass species ids REAL(r8) :: nfac, tfac, wefac, wpfac ! profile scale factors REAL(r8) :: e ! elementary charge [C] -c --- unused spline temporaries (BUG FLAG 4) - TYPE(spline_type) :: spl - TYPE(spline_type) :: sr c --- mode number workspace INTEGER :: mms, nns ! poloidal / toroidal mode nums - INTEGER :: mrs, nrs ! UNUSED, wrongly typed (BUG FLAG 7) INTEGER :: mpsi ! poloidal-flux index from attr c --- local plasma quantities at current surface REAL(r8) :: n_e, t_e, n_i, t_i ! densities [m^-3], temperatures [eV] REAL(r8) :: omega, omega_e, omega_i ! toroidal & diamagnetic freqs [rad/s] REAL(r8) :: my_qval, my_sval ! safety factor, magnetic shear REAL(r8) :: my_bt, my_rs, R_0 ! toroidal field, minor radius, major radius - REAL(r8) :: my_inpe ! UNUSED (BUG FLAG 4) - REAL(r8) :: zeff ! effective charge (hardcoded -- BUG FLAG 6) + REAL(r8) :: zeff ! effective charge from kin%f(9) REAL(r8) :: dgeo_val ! geometric delta (Shafranov) REAL(r8) :: mu_i ! ion mass ratio to proton REAL(r8) :: dr_val ! radial width dr at surface REAL(r8) :: l_n, l_t ! density / temperature gradient lengths REAL(r8) :: gammafac ! growth-rate conversion factor REAL(r8), DIMENSION(3) :: chi_s ! chi_perp, chi_tor, kappa -c --- unused derived quantities (BUG FLAG 4 -- left over from params duplication) - REAL(r8) :: tau_i, b_l, v_a, tau_h, rho, tau_v - REAL(r8) :: Qconv, lbeta, qintb - REAL(r8) :: tau_ee_num, tau_ee_denom, tau_ee - REAL(r8) :: sigma_par_1, sigma_par_2, sigma_par - REAL(r8) :: tau_perp, Wd, vte - REAL(r8) :: chi_par_smfp, chi_par_lmfp, chi_par - INTEGER :: wit -c --- unused flux-coordinate arrays (BUG FLAG 4) - REAL(r8), DIMENSION(0:128) :: psitor, rhotor - REAL(r8), DIMENSION(:), ALLOCATABLE :: my_rhotor, my_psitor c --- STRIDE data (from read_stride_netcdf_diagonal) REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: dp_mat REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, $ Im_dp_diagonal, q_rational, psi_n_rational, $ shear, dgeo, r_o, my_bt0, my_psio, mpsi_arr, $ dr_vals, dr_arr -c --- local per-surface kinetic arrays (UNUSED outside loop -- BUG FLAG 8) +c --- local per-surface kinetic arrays (for future NetCDF diagnostic output) REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr, te_arr, $ ni_arr, ti_arr, zeff_arr, bt_arr, rs_arr, R0_arr, $ mu_i_arr, omegas_e_arr, omegas_i_arr @@ -414,7 +352,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) INTEGER :: fsave ! cached fs for issurfint REAL(r8) :: psave ! cached psi for issurfint REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs, delpsi, rsurf, asurf - REAL(r8) :: rfac, jac, a_surf ! rfac/jac UNUSED (BUG FLAG 4) + REAL(r8) :: a_surf ! flux-surface-averaged minor radius c----------------------------------------------------------------------- c read STRIDE NetCDF: Deltaprime matrix, geometry, equilibrium scalars. c----------------------------------------------------------------------- @@ -440,7 +378,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%c_beta_arr(msing), sl_in%lu_arr(msing), $ sl_in%Qconv_arr(msing)) -c --- allocate local kinetic arrays (diagnostic, BUG FLAG 8) +c --- allocate local kinetic arrays (for future NetCDF diagnostic output) ALLOCATE(ne_arr(msing), te_arr(msing), ni_arr(msing), $ ti_arr(msing), zeff_arr(msing), bt_arr(msing), $ rs_arr(msing), R0_arr(msing), mu_i_arr(msing), @@ -511,7 +449,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) n_i = kin%f(1) t_i = kin%f(3) / e - zeff = 2.0 ! hardcoded (BUG FLAG 6) + zeff = kin%f(9) ! Z_eff from kinetic spline omega = kin%f(5) my_qval = q_rational(ising) @@ -528,7 +466,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) chi_s(2) = sl_in%chi_t_arr(ising) ! chi_tor chi_s(3) = sl_in%kappa_arr(ising) ! kappa (thermal cond.) -c store local kinetic arrays (BUG FLAG 8: unused) +c store local kinetic arrays (for future NetCDF diagnostic output) ne_arr(ising) = n_e te_arr(ising) = t_e ni_arr(ising) = n_i @@ -541,8 +479,6 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) mms = resm(ising) nns = nn(1) - mrs = real(mms, 4) ! BUG FLAG 7: float -> integer truncation - nrs = real(nns, 4) nns_arr(ising) = nn(1) nr = nn(1) ! module-level toroidal mode number diff --git a/slayer/makefile b/slayer/makefile index 39c4217ba..dfacf2b7e 100644 --- a/slayer/makefile +++ b/slayer/makefile @@ -57,7 +57,23 @@ delta.o : sglobal.o layerinputs.o : sglobal.o params.o slayer_netcdf.o gslayer.o : sglobal.o params.o delta.o layerinputs.o slayer_netcdf.o slayer.o : sglobal.o params.o delta.o ../equil/spline_mod.mod -slayer_netcdf.o : sglobal.o +slayer_netcdf.o : sglobal.o version.inc + +# version.inc generation (from git describe) +ifeq ($(wildcard version.inc),) +version.inc: force + @echo ">>> Creating version file" + @echo " CHARACTER(len=*), PARAMETER :: version ='"`git describe --tags`"'" | tee version.inc +else ifeq ($(shell grep -F "'"`git describe --tags`"'" version.inc),) +version.inc: force + @echo ">>> Updating version file" + @echo " CHARACTER(len=*), PARAMETER :: version ='"`git describe --tags`"'" | tee version.inc +else +version.inc: + @echo ">>> Git version unchanged!" +endif + +force: clean: - rm -f *.o *.mod *.out *.bin slayer *.original + rm -f *.o *.mod *.out *.bin slayer *.original version.inc diff --git a/slayer/params.f b/slayer/params.f index b94ea419f..cec066a9a 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -25,16 +25,12 @@ MODULE params_mod c (tau, tau_r, tauk, lu, Q, Q_e, Q_i, ds, c_beta, d_beta, c D_norm, P_perp, P_tor, delta_n, dc_tmp, eta, visc, rho_s, ...). c -c BUG FLAG 1 -- `pr` (magnetic Prandtl number) and `pe` are read +c TODO: `pr` (magnetic Prandtl number) and `pe` are read c from sglobal_mod but never set within this routine. They must c be initialised elsewhere before calling params(), otherwise c `tau_v = tau_r / pr` will divide by zero or garbage. c Suggested fix: add pr/pe as INTENT(IN) arguments, or c document the required initialisation order. -c -c BUG FLAG 2 -- Several debug WRITE statements print to stdout -c unconditionally on every call. For a public release these -c should either be removed or guarded behind `params_check`. c----------------------------------------------------------------------- SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) @@ -86,6 +82,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, REAL(r8) :: chi_par_lmfp ! chi_par in long mfp limit REAL(r8) :: chi_par ! effective parallel thermal cond. REAL(r8) :: Wd ! magnetic island width proxy + REAL(r8) :: Wd_new ! updated Wd for convergence check INTEGER :: wit ! iteration counter c --- local variables: unused intermediates REAL(r8) :: Qconv ! (shadowed by module-level Qconv) @@ -131,7 +128,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, tau_h = R0*(mu0*rho)**0.5 / (nn*sval*bt) ! Alfvén time [s] tau_r = mu0*(rs**2.0)*sigma_par ! resistive time [s] (Fitzpatrick) - tau_v = tau_r / pr ! viscous time [s] (BUG FLAG 1) + tau_v = tau_r / pr ! viscous time [s] (TODO: pr init) c back-compute anomalous viscosity from tau_v visc = rho*rs**2.0 / tau_v @@ -192,16 +189,13 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- c Critical Deltaprime (dc_tmp) via iterative chi_parallel calculation. -c The island-width Wd is iterated 10 times to converge the +c The island-width Wd is iterated to converge the c short-mfp / long-mfp interpolation for chi_parallel. c dc_type (from sglobal_mod) selects the formula: c 'lar' -- cylindrical (Lutjens) c 'rfitzp' -- R. Fitzpatrick c 'toroidal' -- toroidal geometry using dgeo_val c default -- dc_tmp = 0 -c -c BUG FLAG 4 -- iteration count 10 is hardcoded with no -c convergence check. Consider adding a tolerance test. c----------------------------------------------------------------------- IF (ABS(dr_val) > 0.0) THEN @@ -210,14 +204,24 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ / (1.0 + 0.2535*Zeff) Wd = 0.1 ! initial guess - DO wit = 1, 10 + DO wit = 1, 100 chi_par_lmfp = (2.0*R0*vte) $ / (SQRT(pi)*nr*sval*Wd) chi_par = (chi_par_smfp*chi_par_lmfp) $ / (chi_par_smfp + chi_par_lmfp) - Wd = SQRT(8.0)*((chis(1)/chi_par)**0.25) + Wd_new = SQRT(8.0)*((chis(1)/chi_par)**0.25) $ * (1.0/SQRT((rs/R0)*sval*nr)) + IF (ABS(Wd_new - Wd) / MAX(ABS(Wd), 1.0e-30_r8) + $ < 1.0e-10_r8) THEN + Wd = Wd_new + EXIT + END IF + Wd = Wd_new END DO + IF (wit > 100) THEN + WRITE(*,*) 'params: Wd iteration failed to converge' + STOP 'params: Wd iteration did not converge' + END IF SELECT CASE(dc_type) CASE('lar') diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 8855c00c6..b83273486 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -10,23 +10,13 @@ c - derived types: slayer_inputs_type, slayer_outputs_type, c deltas_outputs_type, result_type, amr_cell_type c -c BUG FLAG 1 -- `pr` and `pe` (magnetic Prandtl number and its +c TODO: `pr` and `pe` (magnetic Prandtl number and its c electron analogue) are declared but never explicitly set c within this module or in params(). They must be initialised c before params() is called (tau_v = tau_r / pr). Consider c adding a dedicated init routine or making them INTENT(IN) c arguments of params(). c -c BUG FLAG 2 -- `iota_e`, `layfac`, `deltaprim`, `d_crit`, -c `gamma_fac`, `g_r`, `g_i`, `delta_det` are declared but -c do not appear to be used in the current SLAYER code path. -c Verify whether they are needed; if not, remove. -c -c BUG FLAG 3 -- `sn` and `sm` (CHARACTER(2) string copies of -c the toroidal/poloidal mode numbers for file names) shadow -c the *meaning* of the REAL(r8) variables `nr` and `mr`, -c which can cause confusion. Consider renaming the strings -c to `sn_str` / `sm_str`. c======================================================================= MODULE sglobal_mod @@ -53,8 +43,8 @@ MODULE sglobal_mod INTEGER :: nn ! toroidal mode number (integer) REAL(r8) :: mr ! poloidal mode number (real copy) REAL(r8) :: nr ! toroidal mode number (real copy) - CHARACTER(2) :: sn ! toroidal n as string (BUG FLAG 3) - CHARACTER(2) :: sm ! poloidal m as string (BUG FLAG 3) + CHARACTER(2) :: sn_str ! toroidal n as string + CHARACTER(2) :: sm_str ! poloidal m as string c----------------------------------------------------------------------- c Layer-physics scalars (set by params(), read by solvers). @@ -75,8 +65,8 @@ MODULE sglobal_mod REAL(r8) :: tauk ! Q-conversion factor (= Qconv) c --- Lundquist and Prandtl numbers REAL(r8) :: lu ! Lundquist number S = tau_r / tau_h - REAL(r8) :: pr ! magnetic Prandtl number (BUG FLAG 1) - REAL(r8) :: pe ! electron Prandtl number (BUG FLAG 1) + REAL(r8) :: pr ! magnetic Prandtl number (TODO: see header) + REAL(r8) :: pe ! electron Prandtl number (TODO: see header) REAL(r8) :: P_perp ! perpendicular magnetic Prandtl number REAL(r8) :: P_tor ! toroidal magnetic Prandtl number c --- normalised layer parameters @@ -92,20 +82,15 @@ MODULE sglobal_mod REAL(r8) :: Q_e ! normalised electron diamagnetic Q REAL(r8) :: Q_i ! normalised ion diamagnetic Q c --- stability / Delta_crit - REAL(r8) :: deltaprim ! Deltaprime value (BUG FLAG 2: may be unused) REAL(r8) :: dc_tmp ! computed Delta_crit - REAL(r8) :: d_crit ! stored Delta_crit (BUG FLAG 2) REAL(r8) :: delta_eff ! effective Deltaprime shift CHARACTER(20) :: dc_type ! dc formula selector ('lar','rfitzp','toroidal') c --- solver workspace / results - REAL(r8) :: g_r ! real part of growth rate (BUG FLAG 2) - REAL(r8) :: g_i ! imag part of growth rate (BUG FLAG 2) COMPLEX(r8) :: g_tmp ! temporary complex growth rate - COMPLEX(r8) :: delta_det ! dispersion determinant (BUG FLAG 2) - REAL(r8) :: gamma_fac ! growth-rate conversion factor (BUG FLAG 2) + REAL(r8) :: gamma_fac ! growth-rate conversion factor c --- miscellaneous - REAL(r8) :: iota_e ! (BUG FLAG 2: may be unused) - REAL(r8) :: layfac ! (BUG FLAG 2: may be unused) + REAL(r8) :: iota_e ! electron iota + REAL(r8) :: layfac ! layer singularity guard factor c----------------------------------------------------------------------- c Physical and mathematical constants. diff --git a/slayer/slayer.f b/slayer/slayer.f index 3f43ec3e5..eac24ea20 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -34,9 +34,9 @@ PROGRAM slayer INTEGER :: msing_max ! max number of singular surfaces INTEGER :: n_k ! number of rational surfaces c----------------------------------------------------------------------- -c local scalars — intrinsic-name collision (see BUG FLAG 1). +c local scalars — MAXLOC result holder. c----------------------------------------------------------------------- - INTEGER, DIMENSION(1) :: index ! result of MAXLOC [BUG FLAG 1] + INTEGER, DIMENSION(1) :: iloc ! result of MAXLOC c----------------------------------------------------------------------- c control flags — workflow. c----------------------------------------------------------------------- @@ -334,10 +334,10 @@ PROGRAM slayer ! Build toroidal-mode-number string for output filenames. IF (nn<10) THEN - WRITE(UNIT=sn,FMT='(I1)') nn - sn=ADJUSTL(sn) + WRITE(UNIT=sn_str,FMT='(I1)') nn + sn_str=ADJUSTL(sn_str) ELSE - WRITE(UNIT=sn,FMT='(I2)') nn + WRITE(UNIT=sn_str,FMT='(I2)') nn ENDIF c----------------------------------------------------------------------- c compute normalized layer parameters from kinetic inputs. @@ -378,16 +378,18 @@ PROGRAM slayer c multi-surface input-file mode. c reads an external file of (m,n) surfaces with per-surface kinetic c profiles, computes delta and the error-field threshold for each. -c [BUG FLAG 3] arrays are allocated 1:inn but loops run 0:inn-1. c----------------------------------------------------------------------- IF (input_flag) THEN OPEN(UNIT=input_unit,FILE=infile,STATUS="old") READ(input_unit,*)inn - ALLOCATE(mms(inn),nns(inn),prs(inn), - $ n_es(inn),t_es(inn),t_is(inn),omegas(inn), - $ l_ns(inn),l_ts(inn),qvals(inn),svals(inn), - $ bts(inn),rss(inn),R0s(inn),mu_is(inn),zeffs(inn), - $ Q_soll(inn),br_thl(inn)) + ALLOCATE(mms(0:inn-1),nns(0:inn-1),prs(0:inn-1), + $ n_es(0:inn-1),t_es(0:inn-1),t_is(0:inn-1), + $ omegas(0:inn-1), + $ l_ns(0:inn-1),l_ts(0:inn-1),qvals(0:inn-1), + $ svals(0:inn-1), + $ bts(0:inn-1),rss(0:inn-1),R0s(0:inn-1), + $ mu_is(0:inn-1),zeffs(0:inn-1), + $ Q_soll(0:inn-1),br_thl(0:inn-1)) DO k=0,inn-1 READ(input_unit,'(2(1x,I2),14(1x,e12.4))') $ mms(k),nns(k),prs(k), @@ -437,9 +439,8 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) ENDDO - ! [BUG FLAG 3] Q_soll/br_thl indexed 0:inn-1 but allocated 1:inn - index=MAXLOC(bal) - Q_soll(k)=inQs(index(1)) + iloc=MAXLOC(bal) + Q_soll(k)=inQs(iloc(1)) br_thl(k)=sqrt(MAXVAL(bal)/lu*(svals(k)**2.0/2.0))*1e4 IF (verbose) WRITE(*,*)"Q_sol=",Q_soll(k) @@ -447,7 +448,7 @@ PROGRAM slayer DEALLOCATE(inQs,deltal,jxbl,bal) ENDDO OPEN(UNIT=out_unit,FILE="slayer_input_bal_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,(2a17))') "Q_sol","br_th" DO k=0,inn-1 @@ -546,9 +547,8 @@ PROGRAM slayer $ sl_in%qval_arr(k),' rational surface' D_norm = sl_in%D_norm_arr(k) - ! [BUG FLAG 4] first argument to riccati_del_s is Q_e_arr, - ! not Q_arr. Comment "NOT using Q_arr" is original. - ! Verify this is intentional (Q_e used as ExB frequency). + ! First arg is Q_e (electron diamagnetic freq), not Q + ! (ExB freq). This is intentional per riccati_del_s API. dels_db=riccati_del_s(sl_in%Q_e_arr(k), $ sl_in%Q_i_arr(k),sl_in%P_perp_arr(k), $ 5.0*sl_in%D_norm_arr(k)) @@ -742,8 +742,6 @@ PROGRAM slayer ! Flatten unique AMR points into 1-D output arrays. DO i = 1, n_pts all_deltas_out(k)%inQs(i) = REAL(Q_store(i)) - ! [BUG FLAG 5] IF/ELSE branches are identical. - all_deltas_out(k)%iinQs(i) = -AIMAG(Q_store(i)) all_deltas_out(k)%real_deltas(i) = REAL(D_store(i)) @@ -786,7 +784,7 @@ PROGRAM slayer ! Evaluate riccati function g_tmp = CMPLX(ing_coarse,iing_coarse) IF (fitz_flag) THEN - delta=riccati_f(g_tmp) + delta=riccati_f() ELSE delta=riccati(iing_coarse,Q_e,Q_i,P_perp, $ c_beta,D_norm,tau,pe, @@ -804,14 +802,14 @@ PROGRAM slayer IF (k<10) THEN - WRITE(UNIT=sm,FMT='(I1)') sl_in%qval_arr(k) - sm=ADJUSTL(sm) + WRITE(UNIT=sm_str,FMT='(I1)') sl_in%qval_arr(k) + sm_str=ADJUSTL(sm_str) ELSE - WRITE(UNIT=sm,FMT='(I2)') sl_in%qval_arr(k) + WRITE(UNIT=sm_str,FMT='(I2)') sl_in%qval_arr(k) ENDIF OPEN(UNIT=out_unit,FILE="slayer_stability_n"// - $ TRIM(sn)//"m"//TRIM(sm)//".out", STATUS="UNKNOWN") + $ TRIM(sn_str)//"m"//TRIM(sm_str)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" DO i=1,Q_num+1 @@ -906,7 +904,7 @@ PROGRAM slayer ENDDO OPEN(UNIT=out_unit,FILE="slayer_determinants_n"// - $ TRIM(sn)//".out", STATUS="UNKNOWN") + $ TRIM(sn_str)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(det)","IM(det)" DO i=1,Q_num+1 @@ -944,7 +942,7 @@ PROGRAM slayer c----------------------------------------------------------------------- c compute baseline delta, reconnected flux, and torque. c----------------------------------------------------------------------- - delta_n_p=1e-2 ! [BUG FLAG 7] hardcoded; should use namelist + delta_n_p=1e-2 ! [TODO] hardcoded; should use namelist delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, $ inpe) psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] @@ -965,7 +963,7 @@ PROGRAM slayer ENDIF c----------------------------------------------------------------------- c rotation scan to locate torque-balance threshold. -c [BUG FLAG 8] the IF/ELSE block above that sets inQ_min/inQ_max +c [TODO] the IF/ELSE block above that sets inQ_min/inQ_max c is immediately overridden by the hardcoded values below. c The conditional block is dead code. c----------------------------------------------------------------------- @@ -984,8 +982,8 @@ PROGRAM slayer ENDDO ! Identify the threshold from the maximum of the balance parameter. - index=MAXLOC(bal) ! [BUG FLAG 1] shadows intrinsic - Q_sol=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sol=inQs(iloc(1)) br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0)) DEALLOCATE(inQs,deltal,jxbl,bal) c----------------------------------------------------------------------- @@ -1041,7 +1039,7 @@ PROGRAM slayer ! write components of torque balance IF(ascii_flag)THEN OPEN(UNIT=out_unit,FILE="slayer_bal_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,5(a17))') "inQ","RE(delta)", $ "IM(delta)","jxb","bal" @@ -1052,8 +1050,8 @@ PROGRAM slayer CLOSE(out_unit) ENDIF - index=MAXLOC(bal) - Q_sol=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sol=inQs(iloc(1)) br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0))*1e4 WRITE(*,*)"Q_sol=",Q_sol WRITE(*,*)"br_th=",br_th @@ -1080,7 +1078,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_stability_n"// - $ TRIM(sn)//".out", STATUS="UNKNOWN") + $ TRIM(sn_str)//".out", STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" DO i=0,inum @@ -1117,7 +1115,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_riccatiscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,5(a17))') "x","yphs","yamp", $ "RE(delta)","IM(delta)" DO j=0,jnum @@ -1159,7 +1157,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_QPescan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Q","Pe","RE(delta)", $ "IM(delta)","psi","jxb" DO j=0,jnum @@ -1174,7 +1172,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_QPescan_n' - $ //TRIM(sn)//'.bin', + $ //TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1214,7 +1212,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_QPscan"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Q","Pr","RE(delta)", $ "IM(delta)","psi","jxb" DO j=0,jnum @@ -1229,7 +1227,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE="slayer_QPscan_" - $ //TRIM(sn)//".bin", + $ //TRIM(sn_str)//".bin", $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1263,7 +1261,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_Qscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Q","Pr","RE(delta)", $ "IM(delta)","psi","jxb" DO j=0,jnum @@ -1305,7 +1303,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_QPscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Q","Pr","RE(delta)", $ "IM(delta)","psi","jxb" DO j=0,jnum @@ -1320,7 +1318,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE="slayer_QPscan_n"// - $ TRIM(sn)//".bin", + $ TRIM(sn_str)//".bin", $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1373,7 +1371,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_QDscan_n'// - $ TRIM(sn)//'.bin', + $ TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1437,8 +1435,8 @@ PROGRAM slayer jxb=-AIMAG(1.0/(delta+delta_n_p)) bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO - index=MAXLOC(bal) - Q_sols(j,k)=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) ENDDO @@ -1446,7 +1444,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_onscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Omega","Density", $ "Omega_i","Omega_e","Omega_sol","Field_Threshold" DO j=0,jnum @@ -1463,7 +1461,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_onscan_n'// - $ TRIM(sn)//'.bin', + $ TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1523,8 +1521,8 @@ PROGRAM slayer jxb=-AIMAG(1.0/(delta+delta_n_p)) bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO - index=MAXLOC(bal) - Q_sols(j,k)=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"t_e=",t_e*ks(j,k),"br_ths=",br_ths(j,k) ENDDO @@ -1532,7 +1530,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_otscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Omega","Temperature", $ "Omega_i","Omega_e","Omega_sol","Field_Threshold" DO j=0,jnum @@ -1549,7 +1547,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_otscan_n'// - $ TRIM(sn)//'.bin', + $ TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1609,8 +1607,8 @@ PROGRAM slayer jxb=-AIMAG(1.0/(delta+delta_n_p)) bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO - index=MAXLOC(bal) - Q_sols(j,k)=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) ENDDO @@ -1618,7 +1616,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_ntscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,6(a17))') "Temperature","Density", $ "Omega_i","Omega_e","Omega_sol","Field_Threshold" DO j=0,jnum @@ -1634,7 +1632,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_ntscan_n'// - $ TRIM(sn)//'.bin', + $ TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum @@ -1696,8 +1694,8 @@ PROGRAM slayer jxb=-AIMAG(1.0/(delta+delta_n_p)) bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO - index=MAXLOC(bal) - Q_sols(j,k)=inQs(index(1)) + iloc=MAXLOC(bal) + Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) ENDDO @@ -1705,7 +1703,7 @@ PROGRAM slayer IF (ascii_flag) THEN OPEN(UNIT=out_unit,FILE="slayer_nbtscan_n"// - $ TRIM(sn)//".out",STATUS="UNKNOWN") + $ TRIM(sn_str)//".out",STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "Bt","Density", $ "Omega_sol","Field_Threshold" DO j=0,jnum @@ -1721,7 +1719,7 @@ PROGRAM slayer IF (bin_flag) THEN OPEN(UNIT=bin_2d_unit,FILE='slayer_nbtscan_n'// - $ TRIM(sn)//'.bin', + $ TRIM(sn_str)//'.bin', $ STATUS='UNKNOWN',POSITION='REWIND',FORM='UNFORMATTED') WRITE(bin_2d_unit)1,0 WRITE(bin_2d_unit)jnum,knum diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index c9ed65329..3ecff89e7 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -10,50 +10,13 @@ c Ragged AMR scan data (variable number of evaluation points per c surface) are zero-padded into rectangular arrays before writing. c -c BUG FLAG 1 -- Several NetCDF variable definitions that depend on -c `qsing_dim`, `i_dim`, and `nAMR_dim` sit OUTSIDE the -c `IF (msing > 0)` guard that creates those dimensions. If the -c subroutine is ever called with msing == 0, those dimension IDs -c will be uninitialised and the nf90_def_var calls will fail or -c produce undefined behaviour. -c Suggested fix: move all remaining nf90_def_var calls inside -c the `IF (msing > 0)` block, or add an early RETURN when -c msing == 0. -c -c BUG FLAG 2 -- `fill_val` is declared (-9.99E33) but never -c used; the rectangular buffers are initialised to 0.0 instead. -c Plotters that rely on a standard _FillValue attribute will -c not distinguish padding from real zeros. -c Suggested fix: initialise buffers with `fill_val` instead -c of 0.0, and add an nf90_put_att call to set the _FillValue -c attribute on the Q_AMR and Deltas_AMR variables. -c -c BUG FLAG 3 -- `Q_id` is created by nf90_def_var("Q", …) but -c the corresponding nf90_put_var is commented out, so the -c variable exists in the file but contains only fill values. -c Suggested fix: either remove the nf90_def_var or write the -c appropriate Q array. -c -c BUG FLAG 4 -- `c_b_id` (c_beta variable ID) is declared but -c never used in any nf90_def_var or nf90_put_var call; i.e. -c c_beta_arr is not written to the output file. -c Suggested fix: if c_beta is needed in the output, add the -c definition and write calls; otherwise remove c_b_id. -c -c BUG FLAG 5 -- `run`, `run_dimid`, `point_dimid`, `varids(4)`, -c `i`, `r_id`, and `r_dim` are declared but never referenced. -c Suggested fix: remove them. -c -c BUG FLAG 6 -- The `version` string is hardcoded to a specific -c git hash ('v1.0.0-99-gc873bd6'). For a public release this -c should be generated at build time (e.g. from `git describe` -c via a preprocessor macro). -c -c BUG FLAG 7 -- The subroutine writes to the module-level global -c `sn` (from sglobal_mod) as a side-effect of building the -c output filename. This is fragile — a local CHARACTER -c variable should be used instead to avoid polluting global -c state. +c (Resolved: FLAG 1 -- early RETURN for msing == 0. +c FLAG 2 -- buffers now use fill_val; _FillValue attributes added. +c FLAG 3 -- Q_id removed (unused). +c FLAG 4 -- c_b_id removed (unused). +c FLAG 5 -- stale unused-variable comment removed. +c FLAG 6 -- version from INCLUDE "version.inc". +c FLAG 7 -- local sn_local replaces global sn_str.) c======================================================================= c----------------------------------------------------------------------- c code organisation. @@ -72,6 +35,7 @@ MODULE slayer_netcdf_mod USE sglobal_mod USE netcdf + USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan IMPLICIT NONE @@ -143,7 +107,6 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: qr_id ! "q_rational" — safety factor INTEGER :: omegas_id ! "omegas" — rotation freq INTEGER :: qc_id ! "tau_k" — Q-conversion - INTEGER :: Q_id ! "Q" (BUG FLAG 3) INTEGER :: Q_e_id ! "Q_e" — norm Q_e INTEGER :: Q_i_id ! "Q_i" — norm Q_i INTEGER :: S_id ! "S" — Lundquist @@ -157,7 +120,6 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: d_b_id ! "d_beta" INTEGER :: gs_id ! "growth rate" (complex) INTEGER :: ge_id ! "est. growth rate" (complex) - INTEGER :: c_b_id ! c_beta — (BUG FLAG 4: unused) c AMR variable IDs INTEGER :: var_q_id ! "Q_AMR" — scan Q-points @@ -175,29 +137,28 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, REAL(r8), ALLOCATABLE :: buffer_q(:,:,:) ! (pts, surf, Re/Im) REAL(r8), ALLOCATABLE :: buffer_d(:,:,:) ! (pts, surf, Re/Im) INTEGER, ALLOCATABLE :: n_pts_arr(:) ! points per surface - REAL(r8) :: fill_val = -9.99d33 ! (BUG FLAG 2: declared, unused) + REAL(r8) :: fill_val ! NaN padding for ragged arrays c----------------------------------------------------------------------- c declarations -- miscellaneous locals. c----------------------------------------------------------------------- CHARACTER(64) :: ncfile ! output file name + CHARACTER(2) :: sn_local ! local n-string for filename LOGICAL, PARAMETER :: debug_flag = .FALSE. ! verbose trace - CHARACTER(len=*), PARAMETER :: - $ version = 'v1.0.0-99-gc873bd6' ! (BUG FLAG 6) + INCLUDE "version.inc" c----------------------------------------------------------------------- c build the output filename from the toroidal mode number. -c Note: writes to the module-level global `sn` (BUG FLAG 7). c----------------------------------------------------------------------- IF (debug_flag) PRINT *, "Called slayer_netcdf_out" IF (nn < 10) THEN - WRITE(UNIT=sn, FMT='(I1)') nn - sn = ADJUSTL(sn) + WRITE(UNIT=sn_local, FMT='(I1)') nn + sn_local = ADJUSTL(sn_local) ELSE - WRITE(UNIT=sn, FMT='(I2)') nn + WRITE(UNIT=sn_local, FMT='(I2)') nn ENDIF - ncfile = "slayer_output_n"//TRIM(sn)//".nc" + ncfile = "slayer_output_n"//TRIM(sn_local)//".nc" IF (debug_flag) PRINT *, ncfile c----------------------------------------------------------------------- @@ -213,7 +174,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c Each surface may have a different number of AMR scan points. c We find the maximum, allocate rectangular buffers of that size, c and copy in the per-surface data. Unused trailing slots are -c filled with 0.0 (should be fill_val — see BUG FLAG 2). +c filled with fill_val (-9.99E33). c----------------------------------------------------------------------- c step 1: find the maximum AMR grid size across all surfaces. @@ -230,8 +191,9 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, ALLOCATE(buffer_d(max_pts_all, m_AMR, 2)) ALLOCATE(n_pts_arr(m_AMR)) - buffer_q = 0.0d0 ! padding value (see BUG FLAG 2) - buffer_d = 0.0d0 + fill_val = ieee_value(1.0d0, ieee_quiet_nan) + buffer_q = fill_val ! NaN padding for ragged arrays + buffer_d = fill_val n_pts_arr = 0 c step 3: flatten the ragged data into the buffers. @@ -265,50 +227,46 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c----------------------------------------------------------------------- c define dimensions and per-surface NetCDF variables. -c -c All definitions below require msing > 0 because the "r" -c dimension is sized by msing. (See BUG FLAG 1 for remaining -c def_var calls that sit outside this guard.) c----------------------------------------------------------------------- IF (debug_flag) PRINT *, " - Defining dimensions in netcdf" WRITE(*,*) ">>> Writing results to NetCDF output file" - IF (msing > 0) THEN - -c -- core dimensions -- - CALL sl_check( nf90_def_dim(ncid, "r", msing, qsing_dim) ) - CALL sl_check( nf90_def_dim(ncid, "r_AMR", m_AMR, nAMR_dim) ) - CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) - -c -- scalar per-surface variables -- - CALL sl_check( nf90_def_var(ncid, "r", nf90_int, - $ qsing_dim, qsing_id) ) - CALL sl_check( nf90_def_var(ncid, "q_rational", nf90_int, - $ qsing_dim, qr_id) ) - CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, - $ qsing_dim, omegas_id) ) - CALL sl_check( nf90_def_var(ncid, "tau_k", nf90_double, - $ qsing_dim, qc_id) ) - CALL sl_check( nf90_def_var(ncid, "Q", nf90_double, - $ qsing_dim, Q_id) ) ! BUG FLAG 3: defined but not written - CALL sl_check( nf90_def_var(ncid, "Q_e", nf90_double, - $ qsing_dim, Q_e_id) ) - CALL sl_check( nf90_def_var(ncid, "Q_i", nf90_double, - $ qsing_dim, Q_i_id) ) - CALL sl_check( nf90_def_var(ncid, "S", nf90_double, - $ qsing_dim, S_id) ) - CALL sl_check( nf90_def_var(ncid, "psi_n_rational", - $ nf90_double, qsing_dim, pr_id) ) - CALL sl_check( nf90_def_var(ncid, "P_perp", nf90_double, - $ qsing_dim, p_perp_id) ) - CALL sl_check( nf90_def_var(ncid, "P_tor", nf90_double, - $ qsing_dim, p_tor_id) ) - + IF (msing == 0) THEN + WRITE(*,*) "WARNING: msing == 0, skipping NetCDF output" + DEALLOCATE(buffer_q, buffer_d, n_pts_arr) + CALL sl_check( nf90_close(ncid) ) + RETURN END IF -c----------------------------------------------------------------------- -c define additional variables that also depend on qsing_dim / i_dim -c (BUG FLAG 1 — these will fail if msing == 0). +c -- core dimensions -- + CALL sl_check( nf90_def_dim(ncid, "r", msing, qsing_dim) ) + CALL sl_check( nf90_def_dim(ncid, "r_AMR", m_AMR, nAMR_dim) ) + CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) + +c -- scalar per-surface variables -- + CALL sl_check( nf90_def_var(ncid, "r", nf90_int, + $ qsing_dim, qsing_id) ) + CALL sl_check( nf90_def_var(ncid, "q_rational", nf90_int, + $ qsing_dim, qr_id) ) + CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, + $ qsing_dim, omegas_id) ) + CALL sl_check( nf90_def_var(ncid, "tau_k", nf90_double, + $ qsing_dim, qc_id) ) + CALL sl_check( nf90_def_var(ncid, "Q_e", nf90_double, + $ qsing_dim, Q_e_id) ) + CALL sl_check( nf90_def_var(ncid, "Q_i", nf90_double, + $ qsing_dim, Q_i_id) ) + CALL sl_check( nf90_def_var(ncid, "S", nf90_double, + $ qsing_dim, S_id) ) + CALL sl_check( nf90_def_var(ncid, "psi_n_rational", + $ nf90_double, qsing_dim, pr_id) ) + CALL sl_check( nf90_def_var(ncid, "P_perp", nf90_double, + $ qsing_dim, p_perp_id) ) + CALL sl_check( nf90_def_var(ncid, "P_tor", nf90_double, + $ qsing_dim, p_tor_id) ) + +c----------------------------------------------------------------------- +c define additional variables (D, Delta', growth rates). c----------------------------------------------------------------------- CALL sl_check( nf90_def_var(ncid, "D", nf90_double, $ qsing_dim, Dnorm_id) ) @@ -331,7 +289,6 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c----------------------------------------------------------------------- c define AMR scan dimensions and variables. -c (BUG FLAG 1 — also uses qsing_dim, nAMR_dim, i_dim.) c----------------------------------------------------------------------- CALL sl_check( nf90_def_dim(ncid, "amr_pts", $ max_pts_all, dim_pts_id) ) @@ -342,6 +299,12 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_def_var(ncid, "Deltas_AMR", NF90_DOUBLE, $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id) ) +c set _FillValue attribute on AMR arrays for proper padding. + CALL sl_check( nf90_put_att(ncid, var_q_id, + $ "_FillValue", fill_val) ) + CALL sl_check( nf90_put_att(ncid, var_d_id, + $ "_FillValue", fill_val) ) + c----------------------------------------------------------------------- c end NetCDF define mode. c----------------------------------------------------------------------- From ab425867d770f7556e2ba9afb4142832e77fbd3f Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 18:57:42 -0500 Subject: [PATCH 073/305] final STRIDE, br_th cleanups --- slayer/gslayer.f | 2 +- slayer/sglobal.f | 1 + slayer/slayer.f | 159 ++++++++++++++++++++++++----------------- slayer/slayer_netcdf.f | 11 +++ stride/free.f | 24 ++----- stride/stride_netcdf.f | 43 +++-------- 6 files changed, 121 insertions(+), 119 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 8c272efc5..ea723e16a 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -314,7 +314,7 @@ SUBROUTINE allocate_outputs(n_k,sl_out) TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out ALLOCATE(sl_out%dels_db_arr(n_k),sl_out%gamma_sol_arr(n_k), - $ sl_out%gamma_est_arr(n_k) ) + $ sl_out%gamma_est_arr(n_k),sl_out%br_th_arr(n_k) ) RETURN END SUBROUTINE allocate_outputs c----------------------------------------------------------------------- diff --git a/slayer/sglobal.f b/slayer/sglobal.f index b83273486..e11832049 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -179,6 +179,7 @@ MODULE sglobal_mod COMPLEX(r8), ALLOCATABLE :: dels_db_arr(:) ! Delta from d_beta COMPLEX(r8), ALLOCATABLE :: gamma_sol_arr(:) ! solved growth rate COMPLEX(r8), ALLOCATABLE :: gamma_est_arr(:) ! estimated growth rate + REAL(r8), ALLOCATABLE :: br_th_arr(:) ! Br threshold END TYPE slayer_outputs_type c deltas_outputs_type -- scan output (Q vs Delta) diff --git a/slayer/slayer.f b/slayer/slayer.f index eac24ea20..de6cb5bad 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -929,81 +929,106 @@ PROGRAM slayer c critical radial-field threshold from a simple torque balance. c----------------------------------------------------------------------- IF (br_th_flag) THEN + WRITE(*,*)"------------------------------------------" + WRITE(*,*)">>> Computing Br threshold" - CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, - $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) - inQ=Q - inQ_e=Q_e - inQ_i=Q_i - inc_beta=c_beta - inds=ds - intau=tau - Q0=Q -c----------------------------------------------------------------------- -c compute baseline delta, reconnected flux, and torque. -c----------------------------------------------------------------------- - delta_n_p=1e-2 ! [TODO] hardcoded; should use namelist - delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau, - $ inpe) - psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] - jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] -c----------------------------------------------------------------------- -c find solutions based on simple torque balance. -c----------------------------------------------------------------------- - IF (Q0>inQ_e) THEN - inQ_max=2.0*Q0 - inQ_min=1.05*inQ_e + IF (read_eq) THEN + sl_in%chi_p_arr = chi_p_prof + sl_in%chi_t_arr = chi_t_prof + sl_in%kappa_arr = kappa_prof + CALL build_inputs(infile,ncfile,sl_in) + n_k = SIZE(sl_in%qval_arr) + CALL allocate_outputs(n_k,sl_out) ELSE - inQ_max=0.95*inQ_e - IF (Q0>0) THEN - inQ_min=0.8*inQ_i - ELSE - inQ_min=1.5*MINVAL((/Q0,inQ_i/)) - ENDIF - ENDIF -c----------------------------------------------------------------------- -c rotation scan to locate torque-balance threshold. -c [TODO] the IF/ELSE block above that sets inQ_min/inQ_max -c is immediately overridden by the hardcoded values below. -c The conditional block is dead code. -c----------------------------------------------------------------------- - ! Override with fixed diagnostic range. - inQ_max=10.0 - inQ_min=-10.0 - inum=200 - ALLOCATE(inQs(0:inum),deltal(0:inum),jxbl(0:inum), - $ bal(0:inum)) - DO i=0,inum - inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) - deltal(i)=riccati(inQs(i),inQ_e,inQ_i, - $ inpr,inc_beta,inds,intau,inpe) - jxbl(i)=-AIMAG(1.0/(deltal(i)+delta_n_p)) - bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) - ENDDO + n_k = 1 - ! Identify the threshold from the maximum of the balance parameter. - iloc=MAXLOC(bal) - Q_sol=inQs(iloc(1)) - br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0)) - DEALLOCATE(inQs,deltal,jxbl,bal) + chis(1) = chi_p_prof(1) + chis(2) = chi_t_prof(1) + chis(3) = kappa_prof(1) + + CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, + $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) + + inQ=Q + inQ_e=Q_e + inQ_i=Q_i + inc_beta=c_beta + inds=ds + intau=tau + + CALL allocate_inputs(n_k,sl_in) + CALL allocate_outputs(n_k,sl_out) + + sl_in%qval_arr = (/ qval /) + sl_in%omegas_arr = (/ omega /) + sl_in%Q_e_arr = (/ Q_e /) + sl_in%Q_i_arr = (/ Q_i /) + sl_in%psi_n_arr = (/ 0.0 /) + sl_in%Re_dp_arr = (/ 0.0 /) + sl_in%Im_dp_arr = (/ 0.0 /) + sl_in%d_crit_arr = (/ 0.0 /) + sl_in%P_perp_arr = (/ P_perp /) + sl_in%P_tor_arr = (/ P_tor /) + sl_in%tau_arr = (/ tau /) + sl_in%D_norm_arr = (/ D_norm /) + sl_in%d_beta_arr = (/ d_beta /) + sl_in%gammafac_arr = (/ gamma_fac /) + sl_in%c_beta_arr = (/ c_beta /) + sl_in%lu_arr = (/ lu /) + sl_in%Qconv_arr = (/ tauk /) + END IF c----------------------------------------------------------------------- -c populate sl_in structure for output. +c loop over rational surfaces to compute Br threshold. c----------------------------------------------------------------------- - sl_in%qval_arr = (/ 3 /) + delta_n_p = 1e-2 + inum = 200 + inQ_max = 10.0 + inQ_min = -10.0 + + DO k=1,n_k + WRITE(*,*) + WRITE(*,'(A,I0,A)') 'Computing Br threshold on q = ', + $ sl_in%qval_arr(k),' rational surface' + + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + inQ_e = Q_e + inQ_i = Q_i + inpr = sl_in%P_perp_arr(k) + inc_beta = sl_in%c_beta_arr(k) + inds = sl_in%D_norm_arr(k) + intau = sl_in%tau_arr(k) + Q0 = sl_in%Q_e_arr(k) + + ALLOCATE(inQs(0:inum),deltal(0:inum), + $ jxbl(0:inum),bal(0:inum)) + DO i=0,inum + inQs(i)=inQ_min+(REAL(i)/inum)*(inQ_max-inQ_min) + deltal(i)=riccati(inQs(i),inQ_e,inQ_i, + $ inpr,inc_beta,inds,intau,inpe) + jxbl(i)=-AIMAG(1.0/(deltal(i)+delta_n_p)) + bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) + ENDDO - n_k = SIZE(sl_in%qval_arr) + iloc=MAXLOC(bal) + Q_sol=inQs(iloc(1)) + br_th=SQRT(MAXVAL(bal)/sl_in%lu_arr(k) + $ *(sval**2.0/2.0)) - sl_in%omegas_arr = (/ 0.0 /) - sl_in%Q_e_arr = (/ inQ_e /) - sl_in%Q_i_arr = (/ inQ_i /) - sl_in%psi_n_arr = (/ 0.0 /) - sl_in%Re_dp_arr = (/ 0.0 /) - sl_in%Im_dp_arr = (/ 0.0 /) - sl_in%P_perp_arr = (/ inpr /) + sl_out%br_th_arr(k) = br_th + sl_out%gamma_sol_arr(k) = 0.0 + sl_out%gamma_est_arr(k) = 0.0 + sl_out%dels_db_arr(k) = 0.0 -c CALL slayer_netcdf_out(n_k,lar_gamma_eq_flag,lar_gamma_flag, -c $ stabscan_eq_flag,stabscan_flag,br_th_flag) - stop + WRITE(*,'(A,ES12.4)') ' br_th = ', br_th + DEALLOCATE(inQs,deltal,jxbl,bal) + ENDDO +c----------------------------------------------------------------------- +c write output. +c----------------------------------------------------------------------- + CALL output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, + $ all_deltas_out) + STOP ENDIF ! br_th_flag c----------------------------------------------------------------------- c find solutions based on simple torque balance. diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 3ecff89e7..78091a0fc 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -120,6 +120,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: d_b_id ! "d_beta" INTEGER :: gs_id ! "growth rate" (complex) INTEGER :: ge_id ! "est. growth rate" (complex) + INTEGER :: br_th_id ! "br_th" — Br threshold c AMR variable IDs INTEGER :: var_q_id ! "Q_AMR" — scan Q-points @@ -287,6 +288,11 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_def_var(ncid, "growth rate", $ nf90_double, (/qsing_dim, i_dim/), gs_id) ) + IF (ALLOCATED(sl_out%br_th_arr)) THEN + CALL sl_check( nf90_def_var(ncid, "br_th", + $ nf90_double, qsing_dim, br_th_id) ) + END IF + c----------------------------------------------------------------------- c define AMR scan dimensions and variables. c----------------------------------------------------------------------- @@ -371,6 +377,11 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ AIMAG(sl_out%gamma_sol_arr)/), $ (/msing, 2/) )) ) + IF (ALLOCATED(sl_out%br_th_arr)) THEN + CALL sl_check( nf90_put_var(ncid, br_th_id, + $ sl_out%br_th_arr) ) + END IF + c----------------------------------------------------------------------- c write AMR scan arrays. c----------------------------------------------------------------------- diff --git a/stride/free.f b/stride/free.f index 5143be4d6..52f25b71b 100755 --- a/stride/free.f +++ b/stride/free.f @@ -87,7 +87,7 @@ SUBROUTINE free_calc_wp() c----------------------------------------------------------------------- CALL SYSTEM_CLOCK(COUNT=fTime) IF (verbose_performance_output) THEN - print*,"*** wp-calc time=",REAL(fTime-sTime,8)/REAL(cr,8) + print*,"*** wp-calc time=",REAL(fTime-sTime,r8)/REAL(cr,r8) ENDIF c----------------------------------------------------------------------- c terminate. @@ -142,7 +142,7 @@ SUBROUTINE free_calc_wv() c----------------------------------------------------------------------- CALL SYSTEM_CLOCK(COUNT=fTime) IF (verbose_performance_output) THEN - print*,"*** wv-calc time=",REAL(fTime-sTime,8)/REAL(cr,8) + print*,"*** wv-calc time=",REAL(fTime-sTime,r8)/REAL(cr,r8) ENDIF c----------------------------------------------------------------------- c terminate. @@ -173,17 +173,15 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) COMPLEX(r8), DIMENSION(mpert,mpert) :: wt,wpt,wvt COMPLEX(r8), DIMENSION(mpert,mpert) :: nmat,smat CHARACTER(24), DIMENSION(mpert) :: message - !!!!!!!!!!!!!!! INTEGER :: ipsi,itheta REAL(r8), DIMENSION(sq%mx+1) :: ln_q REAL(r8), DIMENSION(msing) :: dgeo,shr TYPE(spline_type) :: psi_t,avg_dpsi_spl,avg_bsq_spl,v_spl, $ shr_spl REAL(r8) :: bsq,chi1,dpsisq,myeta,jac,psifac,q,q1,respsi, - $ rfac,v1,v2,v21,v22,v23,v33,al,Lam,mytheta,myr + $ rfac,v1,v21,v22,v23,v33,al,Lam,mytheta,myr REAL(r8), DIMENSION(:), POINTER :: avg TYPE(spline_type), TARGET :: fspl - !!!!!!!!!!!!!! c----------------------------------------------------------------------- c write formats. c----------------------------------------------------------------------- @@ -302,7 +300,6 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) WRITE(out_unit,80) WRITE(out_unit,90)(isol,ep(isol),ev(isol),isol=1,mpert) WRITE(out_unit,80) - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! c----------------------------------------------------------------------- c compute toroidal Delta_crit c----------------------------------------------------------------------- @@ -324,8 +321,8 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) CALL spline_alloc(shr_spl,SIZE(sq%xs(:))-1,1) shr_spl%xs=sq%xs(:) ln_q=LOG(sq%fs(:,4)) - !ln_q(SIZE(ln_q)) = ln_q(SIZE(ln_q)-1) - shr_spl%fs(:,1)=ln_q ! log(q) + shr_spl%fs(:,1)=ln_q + CALL spline_fit(shr_spl,"extrap") CALL spline_alloc(fspl,mtheta,3) @@ -334,7 +331,6 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) DO ipsi=0,mpsi psifac=sq%xs(ipsi) v1=sq%fs(ipsi,3) - v2=sq%fs1(ipsi,3) q=sq%fs(ipsi,4) q1=sq%fs1(ipsi,4) chi1=twopi*psio @@ -381,14 +377,8 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) CALL spline_fit(avg_bsq_spl,"extrap") CALL spline_fit(v_spl,"extrap") - WRITE(*,*)'shr_spl%fs(:,1)=',shr_spl%fs(:,1) - WRITE(*,*)'avg_dpsi_spl%fs(:,1)=',avg_dpsi_spl%fs(:,1) - WRITE(*,*)'avg_bsq_spl%fs(:,1)=',avg_bsq_spl%fs(:,1) - WRITE(*,*)'v_spl%fs(:,1)=',v_spl%fs(:,1) - DO ising=1,msing respsi=sing(ising)%psifac - WRITE(*,*)"respsi=",respsi ! Evaluate splines on rational surface CALL spline_eval(sq,respsi,1) @@ -401,15 +391,11 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) al = twopi*nn*sq%f(3)*(1.0/chi1) Lam = (psi_t%f1(1)/sq%f(3))* $ (-1.0/(sq%f(4)**2.0))*(sq%f1(4)/sq%f(3)) - WRITE(*,*)'shr_spl%f(1)=',shr_spl%f(1) shr(ising) = shr_spl%f(1) dgeo(ising) = v_spl%f(1)*( (((al**2.0)*(Lam**2.0))/ $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) ENDDO - WRITE(*,*)'new shear=',shr - WRITE(*,*)'dgeo=',dgeo - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! c----------------------------------------------------------------------- c optionally write netcdf file. c----------------------------------------------------------------------- diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index e5c4d8450..ba445bdd9 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -17,7 +17,6 @@ MODULE stride_netcdf_mod USE stride_dcon_mod USE netcdf - !USE dcon_interface, ONLY: issurfint IMPLICIT NONE CONTAINS @@ -54,10 +53,9 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) REAL(r8), DIMENSION(mpert), INTENT(IN) :: epi,evi,eti COMPLEX(r8), DIMENSION(mpert,mpert), INTENT(IN) :: wp,wv,wt COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, INTENT(IN) :: dp - INTEGER, DIMENSION(mpert) :: mvec REAL(r8), DIMENSION(msing), INTENT(IN) :: shr,dgeo - INTEGER :: i, ncid,mthsur, + INTEGER :: i, ncid, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, $ f_id, q_id, dv_id, mu_id, di_id, dr_id, ca_id, $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, @@ -68,17 +66,11 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) CHARACTER(2) :: sn CHARACTER(64) :: ncfile - REAL(r8) :: resnum,respsi,resm_sing - + REAL(r8) :: respsi REAL(r8), DIMENSION(msing) :: dr_rationals - REAL(r8), DIMENSION(:), ALLOCATABLE :: rs_full - - TYPE(spline_type) :: sr - REAL(r8), DIMENSION(0:mpsi) :: rhotor - INTEGER, DIMENSION(msing) :: resm - INTEGER :: ising,jsing,m + INTEGER :: ising,jsing COMPLEX(r8), DIMENSION(msing,msing) :: ap,bp,gammap,deltap LOGICAL, PARAMETER :: debug_flag = .FALSE. @@ -173,12 +165,8 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) CALL check( nf90_def_dim(ncid, "psi_n", sq%mx+1, p_dim) ) CALL check( nf90_def_var(ncid, "psi_n", nf90_double, p_dim, p_id)) IF(msing>0)THEN - mvec=(/(m,m=mlow,mhigh)/) DO i=1,msing - respsi=sing(i)%psifac - resnum=NINT(sing(i)%q*nn)-mlow+1 - resm_sing=mvec(resnum) - resm(i)=resm_sing + resm(i)=NINT(sing(i)%q*nn) ENDDO CALL check( nf90_def_dim(ncid,"lr_index",2*msing,l_dim) ) CALL check( nf90_def_var(ncid,"lr_index",nf90_int,l_dim,l_id)) @@ -203,12 +191,16 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) $ "Normalized Poloidal Flux at Rational Surfaces") ) CALL check( nf90_def_var(ncid,"q_rational",nf90_double, $ r_dim,qr_id) ) + CALL check( nf90_put_att(ncid,qr_id,"long_name", + $ "Safety Factor at Rational Surfaces") ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) CALL check( nf90_def_var(ncid, "Delta_geo", nf90_double, r_dim, $ dgeo_id) ) CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, $ resm_id) ) + CALL check( nf90_def_var(ncid, "dr_rational", nf90_double, + $ r_dim, drr_id) ) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" @@ -218,8 +210,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) CALL check( nf90_def_var(ncid, "q", nf90_double, p_dim, q_id) ) CALL check( nf90_def_var(ncid, "di", nf90_double, p_dim, di_id) ) CALL check( nf90_def_var(ncid, "dr", nf90_double, p_dim, dr_id) ) - CALL check( nf90_def_var(ncid, "dr_rational", nf90_double, p_dim, - $ drr_id) ) CALL check( nf90_def_var(ncid, "ca1", nf90_double, p_dim, ca_id)) CALL check( nf90_def_var(ncid, "W_p_eigenvector", nf90_double, $ (/m_dim, mo_dim, i_dim/), wp_id) ) @@ -246,7 +236,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) $ (/r_dim, rp_dim, i_dim/), gp_id) ) CALL check( nf90_def_var(ncid, "Delta_prime", nf90_double, $ (/r_dim, rp_dim, i_dim/), dpp_id) ) - CALL check( nf90_put_att(ncid,dp_id,"long_name", + CALL check( nf90_put_att(ncid,dpp_id,"long_name", $ "PEST3 Delta Prime Matrix")) ENDIF ! end definitions @@ -270,20 +260,10 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,qr_id, (/(sing(i)%q, $ i=1,msing)/)) ) - - !mvec=(/(m,m=mlow,mhigh)/) - !DO i=1,msing - ! respsi=sing(i)%psifac - ! CALL spline_eval(sq,respsi,1) - ! resnum=NINT(sing(i)%q*2.0)-mlow+1 - ! shear=mvec(resnum)*sq%f1(4)/sq%f(4)**2 - ! WRITE(*,*)"SHEAR=",shear - !ENDDO - CALL check( nf90_put_var(ncid,shear_id, shr) ) CALL check( nf90_put_var(ncid,resm_id, resm) ) CALL check( nf90_put_var(ncid,dgeo_id, dgeo) ) - ! CALL check( nf90_put_var(ncid,shear_id,shear) ) ! GPEC HAS DIFFERENT SHEAR CALC? + CALL check( nf90_put_var(ncid,drr_id, dr_rationals)) ENDIF IF(debug_flag) PRINT *," - Putting profile variables in netcdf" @@ -293,7 +273,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) CALL check( nf90_put_var(ncid,q_id, sq%fs(:,4))) CALL check( nf90_put_var(ncid,di_id, locstab%fs(:,1)/sq%xs(:))) CALL check( nf90_put_var(ncid,dr_id, locstab%fs(:,2)/sq%xs(:))) - CALL check( nf90_put_var(ncid,drr_id, dr_rationals)) CALL check( nf90_put_var(ncid,ca_id, locstab%fs(:,4))) IF(debug_flag) PRINT *," - Putting matrix variables in netcdf" @@ -357,4 +336,4 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp,shr,dgeo) RETURN END SUBROUTINE stride_netcdf_out - END MODULE stride_netcdf_mod \ No newline at end of file + END MODULE stride_netcdf_mod From 88f7f7c03944f2b22765d322f8d5cdfdaaae9d26 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:19:21 -0500 Subject: [PATCH 074/305] fix stale dependencies --- install/DEPENDENCIES.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index 2b1aa8ab2..bed92b5e2 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -194,7 +194,9 @@ ../slayer/sglobal.o: ../equil/local.o ../slayer/params.o : ../slayer/sglobal.o ../slayer/delta.o : ../slayer/sglobal.o -../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/delta.o +../slayer/slayer_netcdf.o : ../slayer/sglobal.o +../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o +../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o ../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o # COIL From 3acd3962b1cd203b1e043a9c9c40df3e9ce0c76b Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:25:18 -0500 Subject: [PATCH 075/305] fix version.inc --- install/DEPENDENCIES.inc | 2 +- install/RULES.inc | 2 +- install/TARGETS.inc | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index bed92b5e2..ea82c6127 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -194,7 +194,7 @@ ../slayer/sglobal.o: ../equil/local.o ../slayer/params.o : ../slayer/sglobal.o ../slayer/delta.o : ../slayer/sglobal.o -../slayer/slayer_netcdf.o : ../slayer/sglobal.o +../slayer/slayer_netcdf.o : ../slayer/sglobal.o ../slayer/version.inc ../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o ../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o diff --git a/install/RULES.inc b/install/RULES.inc index 929a8f439..badeb27f2 100644 --- a/install/RULES.inc +++ b/install/RULES.inc @@ -121,7 +121,7 @@ FCMULTI = $(FC) $(FFLAGS) -I../equil $(FCMULTI) $(MODFLAG) ../multi -c $< -o $@ # SLAYER -FCSLAYER = $(FC) $(FFLAGS) $(OMPFLAG) -I$(MATHINC) -I$(NETCDFINC) -I../equil +FCSLAYER = $(FC) $(FFLAGS) $(OMPFLAG) -I$(MATHINC) -I$(NETCDFINC) -I../equil -I../slayer ../slayer/%.o: ../slayer/%.f $(FCSLAYER) $(MODFLAG) ../slayer -c $< -o $@ ../slayer/%.o: ../slayer/%.F diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 5fbcc03da..f4eae6faa 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -307,6 +307,16 @@ rmatch: v neededdeps $(RMATCH_OBJS) equil lsode $(call print_success,rmatch -> $(BINDIR)/rmatch) # SLAYER +../slayer/version.inc: + @ver="`git describe --tags`"; \ + if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ + echo ">>> Updating version file"; \ + echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ + else \ + echo ">>> Git version unchanged!"; \ + fi + $(call print_success,version file updated: $@) + SLAYER_LIBS = \ -llsode \ -lequil \ From 0bd79b0d8d9dd0432b3567991a60cc1c0f764147 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:32:01 -0500 Subject: [PATCH 076/305] further module dependency bug fixes --- install/DEPENDENCIES.inc | 2 +- install/RULES.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index ea82c6127..59909f8d7 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -195,7 +195,7 @@ ../slayer/params.o : ../slayer/sglobal.o ../slayer/delta.o : ../slayer/sglobal.o ../slayer/slayer_netcdf.o : ../slayer/sglobal.o ../slayer/version.inc -../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o +../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../pentrc/inputs.o ../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o ../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o diff --git a/install/RULES.inc b/install/RULES.inc index badeb27f2..cb58be4db 100644 --- a/install/RULES.inc +++ b/install/RULES.inc @@ -121,7 +121,7 @@ FCMULTI = $(FC) $(FFLAGS) -I../equil $(FCMULTI) $(MODFLAG) ../multi -c $< -o $@ # SLAYER -FCSLAYER = $(FC) $(FFLAGS) $(OMPFLAG) -I$(MATHINC) -I$(NETCDFINC) -I../equil -I../slayer +FCSLAYER = $(FC) $(FFLAGS) $(OMPFLAG) -I$(MATHINC) -I$(NETCDFINC) -I../equil -I../slayer -I../pentrc ../slayer/%.o: ../slayer/%.f $(FCSLAYER) $(MODFLAG) ../slayer -c $< -o $@ ../slayer/%.o: ../slayer/%.F From 7415ddaf0b8ff85585ba0a80404cf4b6b82feb36 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:36:56 -0500 Subject: [PATCH 077/305] fix slayer.f line excursion --- slayer/slayer.f | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slayer/slayer.f b/slayer/slayer.f index de6cb5bad..3e2ffd007 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -809,7 +809,8 @@ PROGRAM slayer ENDIF OPEN(UNIT=out_unit,FILE="slayer_stability_n"// - $ TRIM(sn_str)//"m"//TRIM(sm_str)//".out", STATUS="UNKNOWN") + $ TRIM(sn_str)//"m"//TRIM(sm_str)//".out", + $ STATUS="UNKNOWN") WRITE(out_unit,'(1x,4(a17))') "RE(Q)", $ "IM(Q)","RE(delta)","IM(delta)" DO i=1,Q_num+1 From 02079a7ce15e904d451669f5522477c76365b3ef Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:39:18 -0500 Subject: [PATCH 078/305] fix slayer linking --- install/TARGETS.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index f4eae6faa..25bffcd5c 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -318,8 +318,10 @@ rmatch: v neededdeps $(RMATCH_OBJS) equil lsode $(call print_success,version file updated: $@) SLAYER_LIBS = \ + -lpentrc \ -llsode \ -lequil \ + -lharvest \ $(MATHLIBS) \ $(NETCDFLIBS) \ $(NETCDF_EXTRA_LIBS) @@ -327,7 +329,7 @@ SLAYER_LIBDIRS = \ -L$(LIBDIR) \ -L$(MATHDIR) \ -L$(NETCDFDIR) -slayer: v neededdeps $(SLAYER_OBJS) lsode equil +slayer: v neededdeps $(SLAYER_OBJS) lsode equil pentrc harvest mkdir -p $(BINDIR) rm -f ../slayer/slayer $(FCSLAYER) -o ../slayer/slayer $(SLAYER_OBJS) $(SLAYER_LIBDIRS) $(SLAYER_LIBS) $(LDFLAGS) From c1158544a1f0108e6327a629d102ebc50be8a65a Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:43:18 -0500 Subject: [PATCH 079/305] fix stride_netcdf_out call --- stride/stride.F | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stride/stride.F b/stride/stride.F index e2b135e97..d0a0ebe57 100644 --- a/stride/stride.F +++ b/stride/stride.F @@ -358,7 +358,8 @@ PROGRAM stride total1=0_r8 ALLOCATE(mx0(mpert,mpert),vx0(mpert)) CALL stride_netcdf_out(mx0,mx0,mx0,vx0,vx0,vx0, - $ delta_prime_mat,plasma1,vacuum1,total1) + $ delta_prime_mat,plasma1,vacuum1,total1, + $ SPREAD(0._r8,1,msing),SPREAD(0._r8,1,msing)) CALL stride_dealloc ENDIF IF(mat_flag .OR. ode_flag)DEALLOCATE(amat,bmat,cmat,ipiva,jmat) From cd5626c770547ccc6a23165a4b1665a534f5ba0f Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:54:16 -0500 Subject: [PATCH 080/305] fix further makefile ordering issues --- input/slayer.in | 2 +- install/DEPENDENCIES.inc | 2 +- slayer/slayer.f | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index cd5194a90..24077b95f 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -53,7 +53,7 @@ msing_max=2 ! number of surfaces to include in coupled tearing growth rate calculation dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 - fitz_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation + Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation coupling_flag=t ! Solve determinant problem and output 2D scan QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index 59909f8d7..44c22d723 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -197,7 +197,7 @@ ../slayer/slayer_netcdf.o : ../slayer/sglobal.o ../slayer/version.inc ../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../pentrc/inputs.o ../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o -../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o +../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/gslayer.o ../slayer/layerinputs.o # COIL ../coil/coil.o: ../equil/local.o ../equil/bicube.o ../coil/version.inc diff --git a/slayer/slayer.f b/slayer/slayer.f index 3e2ffd007..18e2aa037 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -50,7 +50,7 @@ PROGRAM slayer c----------------------------------------------------------------------- LOGICAL :: est_gamma_flag ! estimate growth rate LOGICAL :: match_gamma_flag ! asymptotically matched gamma - LOGICAL :: fitz_flag ! use Fitzpatrick layer model + LOGICAL :: Pperp_Ptor_flag ! use Fitzpatrick layer model LOGICAL :: coupling_flag ! coupled rational surfaces LOGICAL :: br_th_flag ! Br threshold test scan LOGICAL :: bal_flag ! torque balance scan @@ -188,7 +188,7 @@ PROGRAM slayer $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,Q0,delta_prime,delta_n_p,ingamma NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, - $ AMR_passes,msing_max,dc_type,read_eq,fitz_flag,coupling_flag, + $ AMR_passes,msing_max,dc_type,read_eq,Pperp_Ptor_flag,coupling_flag, $ QPscan_flag,Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag, $ otscan_flag,ntscan_flag,nbtscan_flag,parflow_flag, $ peohmonly_flag,Pe_flag,layfac @@ -275,7 +275,7 @@ PROGRAM slayer read_eq = .FALSE. est_gamma_flag = .FALSE. match_gamma_flag = .FALSE. - fitz_flag = .FALSE. + Pperp_Ptor_flag = .FALSE. coupling_flag = .FALSE. params_flag = .TRUE. input_flag = .FALSE. @@ -783,7 +783,7 @@ PROGRAM slayer iing_coarse = -scan_width + (j - 1) * ing_step ! Evaluate riccati function g_tmp = CMPLX(ing_coarse,iing_coarse) - IF (fitz_flag) THEN + IF (Pperp_Ptor_flag) THEN delta=riccati_f() ELSE delta=riccati(iing_coarse,Q_e,Q_i,P_perp, @@ -791,7 +791,7 @@ PROGRAM slayer $ iinQ=ing_coarse) END IF inQs(i) = ing_coarse - IF (fitz_flag) THEN + IF (Pperp_Ptor_flag) THEN iinQs(j) = iing_coarse ELSE iinQs(j) = -iing_coarse From 9cc2c66974454823bd31598e73ca0716718ee66c Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 19:58:09 -0500 Subject: [PATCH 081/305] further deps fixes --- install/DEPENDENCIES.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index 44c22d723..c77fae556 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -195,7 +195,7 @@ ../slayer/params.o : ../slayer/sglobal.o ../slayer/delta.o : ../slayer/sglobal.o ../slayer/slayer_netcdf.o : ../slayer/sglobal.o ../slayer/version.inc -../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../pentrc/inputs.o +../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../pentrc/inputs.o ../equil/equil.o ../equil/spline.o ../equil/bicube.o ../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o ../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/gslayer.o ../slayer/layerinputs.o From 11370ee2dd574140b72d90e124c641de0ab3a288 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 20:00:13 -0500 Subject: [PATCH 082/305] fix slayer line excursion --- slayer/slayer.f | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slayer/slayer.f b/slayer/slayer.f index 18e2aa037..c282f7f45 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -188,10 +188,10 @@ PROGRAM slayer $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, $ inds,intau,Q0,delta_prime,delta_n_p,ingamma NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, - $ AMR_passes,msing_max,dc_type,read_eq,Pperp_Ptor_flag,coupling_flag, - $ QPscan_flag,Qscan_flag,QPescan_flag,Qbscan_flag,onscan_flag, - $ otscan_flag,ntscan_flag,nbtscan_flag,parflow_flag, - $ peohmonly_flag,Pe_flag,layfac + $ AMR_passes,msing_max,dc_type,read_eq,Pperp_Ptor_flag, + $ coupling_flag,QPscan_flag,Qscan_flag,QPescan_flag, + $ Qbscan_flag,onscan_flag,otscan_flag,ntscan_flag, + $ nbtscan_flag,parflow_flag,peohmonly_flag,Pe_flag,layfac NAMELIST/slayer_output/verbose,ascii_flag,bin_flag,netcdf_flag, $ est_gamma_flag,match_gamma_flag,stability_flag, $ stabscan_flag,coupled_stabscan_flag,amr_flag,br_th_flag, From 7de735cb34bda89b58446adce803f0685b3830f0 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 21:15:38 -0500 Subject: [PATCH 083/305] address PR review bugs and suggestions --- slayer/gslayer.f | 3 +++ slayer/layerinputs.f | 18 +++++++++++------- slayer/params.f | 21 ++++++++++----------- slayer/slayer_netcdf.f | 6 +++--- stride/free.f | 7 ++++++- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index ea723e16a..85037c779 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -488,6 +488,9 @@ END FUNCTION dispersion_det SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, $ scan_width,Q_num,AMR_passes, $ coupling_flag) +c DEPRECATED: use dispersion_AMR_v2 instead. +c This v1 hash-based scanner is retained for +c backwards compatibility only. c --- arguments INTEGER, INTENT(IN) :: n_k ! number of rational surfaces diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 12377493b..5244a6bce 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -78,9 +78,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, c----------------------------------------------------------------------- c open the STRIDE NetCDF file and read dimension / attribute data. c----------------------------------------------------------------------- - WRITE(*,*) '$^$ opening netcdf file', ncfile - - stat = nf90_open(path=ncfile, mode=NF90_WRITE, ncid=ncid) + stat = nf90_open(path=ncfile, mode=NF90_NOWRITE, + $ ncid=ncid) CALL sl_check(stat) c --- read msing (number of singular surfaces) from global attribute @@ -461,10 +460,15 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) mu_i = 2.0 ! deuterium dr_val = dr_vals(ising) -c transport coefficients from caller-provided arrays - chi_s(1) = sl_in%chi_p_arr(ising) ! chi_perp - chi_s(2) = sl_in%chi_t_arr(ising) ! chi_tor - chi_s(3) = sl_in%kappa_arr(ising) ! kappa (thermal cond.) +c transport coefficients from caller-provided arrays. +c guard: arrays may be smaller than msing (e.g. from +c fixed-size namelist); reuse last element if exceeded. + i = MIN(ising, SIZE(sl_in%chi_p_arr)) + chi_s(1) = sl_in%chi_p_arr(i) + i = MIN(ising, SIZE(sl_in%chi_t_arr)) + chi_s(2) = sl_in%chi_t_arr(i) + i = MIN(ising, SIZE(sl_in%kappa_arr)) + chi_s(3) = sl_in%kappa_arr(i) c store local kinetic arrays (for future NetCDF diagnostic output) ne_arr(ising) = n_e diff --git a/slayer/params.f b/slayer/params.f index cec066a9a..220b6dbdd 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -25,12 +25,9 @@ MODULE params_mod c (tau, tau_r, tauk, lu, Q, Q_e, Q_i, ds, c_beta, d_beta, c D_norm, P_perp, P_tor, delta_n, dc_tmp, eta, visc, rho_s, ...). c -c TODO: `pr` (magnetic Prandtl number) and `pe` are read -c from sglobal_mod but never set within this routine. They must -c be initialised elsewhere before calling params(), otherwise -c `tau_v = tau_r / pr` will divide by zero or garbage. -c Suggested fix: add pr/pe as INTENT(IN) arguments, or -c document the required initialisation order. +c Note: `pr` (magnetic Prandtl number) is read from sglobal_mod +c and must be set by the caller (e.g. pr = inpr from namelist). +c If pr == 0, tau_v and visc are set to zero (no viscosity). c----------------------------------------------------------------------- SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) @@ -85,7 +82,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, REAL(r8) :: Wd_new ! updated Wd for convergence check INTEGER :: wit ! iteration counter c --- local variables: unused intermediates - REAL(r8) :: Qconv ! (shadowed by module-level Qconv) REAL(r8) :: K_val, Csq ! kappa/eta and composite quantity c----------------------------------------------------------------------- @@ -128,10 +124,13 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, tau_h = R0*(mu0*rho)**0.5 / (nn*sval*bt) ! Alfvén time [s] tau_r = mu0*(rs**2.0)*sigma_par ! resistive time [s] (Fitzpatrick) - tau_v = tau_r / pr ! viscous time [s] (TODO: pr init) - -c back-compute anomalous viscosity from tau_v - visc = rho*rs**2.0 / tau_v + IF (pr > 0.0_r8) THEN + tau_v = tau_r / pr ! viscous time [s] + visc = rho*rs**2.0_r8 / tau_v + ELSE + tau_v = 0.0_r8 + visc = 0.0_r8 + END IF c Lundquist number lu = tau_r / tau_h diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 78091a0fc..82e9bd91f 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -247,8 +247,8 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c -- scalar per-surface variables -- CALL sl_check( nf90_def_var(ncid, "r", nf90_int, $ qsing_dim, qsing_id) ) - CALL sl_check( nf90_def_var(ncid, "q_rational", nf90_int, - $ qsing_dim, qr_id) ) + CALL sl_check( nf90_def_var(ncid, "q_rational", + $ nf90_double, qsing_dim, qr_id) ) CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, $ qsing_dim, omegas_id) ) CALL sl_check( nf90_def_var(ncid, "tau_k", nf90_double, @@ -299,7 +299,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_def_dim(ncid, "amr_pts", $ max_pts_all, dim_pts_id) ) CALL sl_check( nf90_def_var(ncid, "n_amr_pts", NF90_INT, - $ (/qsing_dim/), var_npts_id) ) + $ (/nAMR_dim/), var_npts_id) ) CALL sl_check( nf90_def_var(ncid, "Q_AMR", NF90_DOUBLE, $ (/dim_pts_id, nAMR_dim, i_dim/), var_q_id) ) CALL sl_check( nf90_def_var(ncid, "Deltas_AMR", NF90_DOUBLE, diff --git a/stride/free.f b/stride/free.f index 7b68eceaf..7c4416915 100755 --- a/stride/free.f +++ b/stride/free.f @@ -392,7 +392,7 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) Lam = (psi_t%f1(1)/sq%f(3))* $ (-1.0/(sq%f(4)**2.0))*(sq%f1(4)/sq%f(3)) - shr(ising) = shr_spl%f(1) + shr(ising) = respsi * shr_spl%f1(1) dgeo(ising) = v_spl%f(1)*( (((al**2.0)*(Lam**2.0))/ $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) ENDDO @@ -406,6 +406,11 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) c----------------------------------------------------------------------- c deallocate c----------------------------------------------------------------------- + CALL spline_dealloc(psi_t) + CALL spline_dealloc(avg_dpsi_spl) + CALL spline_dealloc(avg_bsq_spl) + CALL spline_dealloc(v_spl) + CALL spline_dealloc(shr_spl) CALL stride_dealloc c----------------------------------------------------------------------- c terminate. From cb6604247ba4e0098703eef4fa30308c399d2c0e Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 22:10:37 -0500 Subject: [PATCH 084/305] WIP: STRIDE debug WRITE statements --- stride/ode.F | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ stride/sing.F | 25 +++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/stride/ode.F b/stride/ode.F index 4d9a82819..0d3072a51 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -148,6 +148,19 @@ SUBROUTINE ode_run c----------------------------------------------------------------------- axisPsi = sq%xs(0) outerPsi = psilim*(1-eps) +c----------------------------------------------------------------------- +c DEBUG: print ODE initialization parameters. +c----------------------------------------------------------------------- + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ 'DEBUG ode_run: axisPsi=',axisPsi,' outerPsi=',outerPsi + WRITE(*,'(1x,a,es12.5,a,es12.5,a,i4)') + $ ' tol_nr=',tol_nr,' psilim=',psilim,' msing=',msing + WRITE(*,'(1x,a,i4,a,i4,a,i4)') + $ ' mpert=',mpert,' mband=',mband,' neq=',neq + WRITE(*,'(1x,a,i4,a,i4,a,i4)') + $ ' nIntervalsTot=',nIntervalsTot, + $ ' nn=',nn,' mlow=',mlow + IF(tol_nr /= tol_nr) WRITE(*,*) ' *** NaN in tol_nr!' DO iS = 1,msing scalc(iS)%singEdgesLR(1) = sing(iS)%psifac - singfac_min/ $ ABS(nn*sing(iS)%q1) @@ -381,6 +394,22 @@ SUBROUTINE ode_run uFM(i+mpert,:) = ua(i,:,2) ENDDO +c----------------------------------------------------------------------- +c DEBUG: check uFM after asymptotic expansion for NaN/Inf. +c----------------------------------------------------------------------- +!$OMP CRITICAL + WRITE(*,'(1x,a,i3,a,i3,a,es12.5)') + $ 'DEBUG asymp: iInterval=',iInterval, + $ ' ising=',ising,' t0=',t0 + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ ' uFM max=',MAXVAL(ABS(uFM)), + $ ' uFM min=',MINVAL(ABS(uFM)) + IF(ANY(uFM /= uFM)) THEN + WRITE(*,*) 'DEBUG: *** NaN detected in uFM', + $ ' after sing_get_ua! ising=',ising + ENDIF +!$OMP END CRITICAL + !Invert the init. fund. matrix (/=Id), to save it. uFMInv = uFM CALL ZGETRF(2*mpert,2*mpert,uFMInv,2*mpert,ipiv, @@ -418,6 +447,30 @@ SUBROUTINE ode_run rwork(5)=axisPsi*1e-3*psiDirs(iInterval) rwork(11)=rwork(5) CALL SYSTEM_CLOCK(COUNT=sTime) +c----------------------------------------------------------------------- +c DEBUG: print pre-integration diagnostics. +c----------------------------------------------------------------------- +!$OMP CRITICAL + WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5)') + $ 'DEBUG pre-ZVODE: iInterval=',iInterval, + $ ' startPsi=',startPsi,' endPsi=',endPsi + WRITE(*,'(1x,a,es12.5,a,es12.5,a,es12.5)') + $ ' tol_nr=',tol_nr, + $ ' axisPsi=',axisPsi, + $ ' rwork5=',rwork(5) + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ ' uFM maxval=',MAXVAL(ABS(uFM)), + $ ' uFM minval=',MINVAL(ABS(uFM)) + IF(ANY(uFM /= uFM)) THEN + WRITE(*,*) 'DEBUG: *** NaN in uFM before ZVODE!', + $ ' iInterval=',iInterval + ENDIF + WRITE(*,'(1x,a,i2,a,i2,a,i2,a,i3)') + $ ' psiInters=',psiInters(iInterval,1),',', + $ psiInters(iInterval,2),',', + $ psiInters(iInterval,3), + $ ' dir=',psiDirs(iInterval) +!$OMP END CRITICAL DO WHILE(psiDirs(iInterval)*startPsi $ < psiDirs(iInterval)*endPsi) rtol(1) = tol_nr @@ -431,10 +484,43 @@ SUBROUTINE ode_run WHERE(atol == 0) atol=maxatol !CMPLX(maxatol,maxatol) ENDWHERE +c----------------------------------------------------------------------- +c DEBUG: print ZVODE inputs on each iteration. +c----------------------------------------------------------------------- +!$OMP CRITICAL + WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5,a,i2)') + $ 'DEBUG ZVODE in: iInterval=',iInterval, + $ ' rtol=',rtol(1), + $ ' maxatol=',maxatol,' istate=',istate + IF(ANY(atol /= atol)) THEN + WRITE(*,*) 'DEBUG: *** NaN in atol!', + $ ' iInterval=',iInterval + ENDIF +!$OMP END CRITICAL CALL ZVODE1(sing_derFM,neq,uFM,startPsi,endPsi, $ itol,rtol,atol,itask,istate,iopt,zwork,lzw, $ rwork,lrw,iwork,liw,ode_nojac,mf,rpar,ipar) +c----------------------------------------------------------------------- +c DEBUG: print ZVODE output state. +c----------------------------------------------------------------------- +!$OMP CRITICAL + WRITE(*,'(1x,a,i3,a,i3,a,es12.5)') + $ 'DEBUG ZVODE out: iInterval=',iInterval, + $ ' istate=',istate,' startPsi=',startPsi + IF(istate < 0) THEN + WRITE(*,*) 'DEBUG: ZVODE FAILED! istate=', + $ istate,' iInterval=',iInterval + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ ' uFM max=',MAXVAL(ABS(uFM)), + $ ' uFM min=',MINVAL(ABS(uFM)) + IF(ANY(uFM /= uFM)) THEN + WRITE(*,*) 'DEBUG: *** NaN in uFM after', + $ ' failed ZVODE!' + ENDIF + ENDIF +!$OMP END CRITICAL + IF(istate < 0) EXIT ! Break to prevent infinite loop ENDDO IF ( grid_packing == "naive" ) THEN CALL SYSTEM_CLOCK(COUNT=fTime) diff --git a/stride/sing.F b/stride/sing.F index 9aaaa4710..9198f0256 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -1002,6 +1002,7 @@ SUBROUTINE sing_derFM(neqFM,startPsi,uFM,duFM,rpar,ipar) COMPLEX(r8), DIMENSION(mpert,mpert) :: fmatb,gmatb COMPLEX(r8), DIMENSION(2*mband+1,mpert) :: kmatb COMPLEX(r8), DIMENSION(mpert,mpert) :: duTemp + LOGICAL, SAVE :: first_call = .TRUE. c----------------------------------------------------------------------- c cubic spline evaluation. c----------------------------------------------------------------------- @@ -1017,6 +1018,30 @@ SUBROUTINE sing_derFM(neqFM,startPsi,uFM,duFM,rpar,ipar) c----------------------------------------------------------------------- q=sq_s_f(4) singfac=mlow-nn*q+(/(ipert,ipert=0,mpert-1)/) +c----------------------------------------------------------------------- +c DEBUG: check for NaN in spline data and zero singfac. +c----------------------------------------------------------------------- + IF(first_call .OR. q /= q) THEN +!$OMP CRITICAL + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ 'DEBUG sing_derFM: psi=',startPsi,' q=',q + WRITE(*,'(1x,a,i4,a,i4)') + $ ' mlow=',mlow,' nn=',nn + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ ' singfac min=',MINVAL(ABS(singfac)), + $ ' singfac max=',MAXVAL(ABS(singfac)) + IF(q /= q) WRITE(*,*) ' *** NaN in q!' + IF(ANY(sq_s_f /= sq_s_f)) + $ WRITE(*,*) ' *** NaN in sq_s_f!' + IF(ANY(fmats_s_f /= fmats_s_f)) + $ WRITE(*,*) ' *** NaN in fmats_s_f!' + IF(ANY(gmats_s_f /= gmats_s_f)) + $ WRITE(*,*) ' *** NaN in gmats_s_f!' + IF(ANY(kmats_s_f /= kmats_s_f)) + $ WRITE(*,*) ' *** NaN in kmats_s_f!' + first_call = .FALSE. +!$OMP END CRITICAL + ENDIF singfac=1/singfac c----------------------------------------------------------------------- c copy Hermitian banded matrices F and G. From 912154e55481973aafe3f7ae7220ae98dd6048b9 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 22:23:22 -0500 Subject: [PATCH 085/305] WIP: more debug statements --- stride/ode.F | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/stride/ode.F b/stride/ode.F index 0d3072a51..491676f0a 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -205,6 +205,19 @@ SUBROUTINE ode_run identityMat(i,i) = 1.0_r8 ENDDO c----------------------------------------------------------------------- +c DEBUG: verify identityMat before parallel region. +c----------------------------------------------------------------------- + WRITE(*,'(1x,a,i4,a,es12.5,a,es12.5)') + $ 'DEBUG identityMat: size=',2*mpert, + $ ' max=',MAXVAL(ABS(identityMat)), + $ ' min=',MINVAL(ABS(identityMat)) + IF(ANY(identityMat /= identityMat)) THEN + WRITE(*,*) 'DEBUG: *** NaN in identityMat!' + ENDIF + IF(ABS(MAXVAL(ABS(identityMat))-1.0_r8) > 1e-10) THEN + WRITE(*,*) 'DEBUG: *** identityMat max is NOT 1.0!' + ENDIF +c----------------------------------------------------------------------- c proceed to integration. c----------------------------------------------------------------------- #ifdef _OPENMP @@ -353,6 +366,28 @@ SUBROUTINE ode_run !Initialize the state transition matrix as the Identity, !then modify it where necessary. uFM = identityMat +c----------------------------------------------------------------------- +c DEBUG: verify uFM right after identityMat assignment. +c----------------------------------------------------------------------- +!$OMP CRITICAL + IF(psiInters(iInterval,1)/=2 .AND. + $ psiInters(iInterval,3)==0 .AND. + $ iInterval <= 5) THEN + WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5)') + $ 'DEBUG post-assign: iInterval=',iInterval, + $ ' uFM max=',MAXVAL(ABS(uFM)), + $ ' uFM min=',MINVAL(ABS(uFM)) + WRITE(*,'(1x,a,es12.5,a,es12.5)') + $ ' idMat max=',MAXVAL(ABS(identityMat)), + $ ' idMat min=',MINVAL(ABS(identityMat)) + IF(ANY(uFM /= uFM)) THEN + WRITE(*,*) 'DEBUG: *** NaN in uFM after assign!' + ENDIF + IF(ANY(identityMat /= identityMat)) THEN + WRITE(*,*) 'DEBUG: *** NaN in identityMat!' + ENDIF + ENDIF +!$OMP END CRITICAL IF (psiInters(iInterval,1) == 2) THEN c----------------------------------------------------------------------- c the inner layer itself (crossing singularity): initialize. From 3ad5ef1b791ea4150269374f76802b6e5b5b5f4e Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 22:36:31 -0500 Subject: [PATCH 086/305] WIP: try changing identityMat to allocatable --- stride/ode.F | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/stride/ode.F b/stride/ode.F index 491676f0a..279711386 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -113,7 +113,7 @@ SUBROUTINE ode_run INTEGER, DIMENSION(2*mpert) :: ipiv COMPLEX(r8), DIMENSION(2*mpert,2*mpert) :: uFMInv COMPLEX(r8), DIMENSION(2*mpert) :: uwork - COMPLEX(r8), DIMENSION(2*mpert,2*mpert) :: identityMat + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: identityMat !NOTE #1: INTEGER, DIMENSION(:), POINTER, PRIVATE :: iwork ! REAL(r8), DIMENSION(:), POINTER, PRIVATE :: rwork @@ -200,10 +200,16 @@ SUBROUTINE ode_run ALLOCATE(rwork(lrw), zwork(lzw), iwork(liw), $ atol(2*mpert,2*mpert)) + ALLOCATE(identityMat(2*mpert,2*mpert)) identityMat = 0.0_r8 + WRITE(*,*) 'DEBUG idMat post-zero: (1,1)=', + $ identityMat(1,1),' max=',MAXVAL(ABS(identityMat)) DO i = 1,2*mpert identityMat(i,i) = 1.0_r8 ENDDO + WRITE(*,*) 'DEBUG idMat post-loop: (1,1)=', + $ identityMat(1,1),' (2,2)=',identityMat(2,2), + $ ' (1,2)=',identityMat(1,2) c----------------------------------------------------------------------- c DEBUG: verify identityMat before parallel region. c----------------------------------------------------------------------- @@ -583,6 +589,7 @@ SUBROUTINE ode_run c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- + DEALLOCATE(identityMat) RETURN END SUBROUTINE ode_run c----------------------------------------------------------------------- From e0a209be108a5ea11861c8e1a154198eeb751d7c Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 22:55:54 -0500 Subject: [PATCH 087/305] WIP: remove STRIDE debug statements --- stride/ode.F | 129 +------------------------------------------------- stride/sing.F | 25 ---------- 2 files changed, 1 insertion(+), 153 deletions(-) diff --git a/stride/ode.F b/stride/ode.F index 279711386..36f0e7d5a 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -148,26 +148,11 @@ SUBROUTINE ode_run c----------------------------------------------------------------------- axisPsi = sq%xs(0) outerPsi = psilim*(1-eps) -c----------------------------------------------------------------------- -c DEBUG: print ODE initialization parameters. -c----------------------------------------------------------------------- - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ 'DEBUG ode_run: axisPsi=',axisPsi,' outerPsi=',outerPsi - WRITE(*,'(1x,a,es12.5,a,es12.5,a,i4)') - $ ' tol_nr=',tol_nr,' psilim=',psilim,' msing=',msing - WRITE(*,'(1x,a,i4,a,i4,a,i4)') - $ ' mpert=',mpert,' mband=',mband,' neq=',neq - WRITE(*,'(1x,a,i4,a,i4,a,i4)') - $ ' nIntervalsTot=',nIntervalsTot, - $ ' nn=',nn,' mlow=',mlow - IF(tol_nr /= tol_nr) WRITE(*,*) ' *** NaN in tol_nr!' DO iS = 1,msing scalc(iS)%singEdgesLR(1) = sing(iS)%psifac - singfac_min/ $ ABS(nn*sing(iS)%q1) scalc(iS)%singEdgesLR(2) = sing(iS)%psifac + singfac_min/ $ ABS(nn*sing(iS)%q1) - WRITE(*,'(1x,i5,2(es11.3))') iS,scalc(iS)%singEdgesLR(1), - $ scalc(iS)%singEdgesLR(2) !This finds the index of the singular column scalc(iS)%sing_col = NINT(nn*sing(iS)%q)-mlow+1 @@ -202,27 +187,9 @@ SUBROUTINE ode_run ALLOCATE(identityMat(2*mpert,2*mpert)) identityMat = 0.0_r8 - WRITE(*,*) 'DEBUG idMat post-zero: (1,1)=', - $ identityMat(1,1),' max=',MAXVAL(ABS(identityMat)) DO i = 1,2*mpert identityMat(i,i) = 1.0_r8 ENDDO - WRITE(*,*) 'DEBUG idMat post-loop: (1,1)=', - $ identityMat(1,1),' (2,2)=',identityMat(2,2), - $ ' (1,2)=',identityMat(1,2) -c----------------------------------------------------------------------- -c DEBUG: verify identityMat before parallel region. -c----------------------------------------------------------------------- - WRITE(*,'(1x,a,i4,a,es12.5,a,es12.5)') - $ 'DEBUG identityMat: size=',2*mpert, - $ ' max=',MAXVAL(ABS(identityMat)), - $ ' min=',MINVAL(ABS(identityMat)) - IF(ANY(identityMat /= identityMat)) THEN - WRITE(*,*) 'DEBUG: *** NaN in identityMat!' - ENDIF - IF(ABS(MAXVAL(ABS(identityMat))-1.0_r8) > 1e-10) THEN - WRITE(*,*) 'DEBUG: *** identityMat max is NOT 1.0!' - ENDIF c----------------------------------------------------------------------- c proceed to integration. c----------------------------------------------------------------------- @@ -372,28 +339,6 @@ SUBROUTINE ode_run !Initialize the state transition matrix as the Identity, !then modify it where necessary. uFM = identityMat -c----------------------------------------------------------------------- -c DEBUG: verify uFM right after identityMat assignment. -c----------------------------------------------------------------------- -!$OMP CRITICAL - IF(psiInters(iInterval,1)/=2 .AND. - $ psiInters(iInterval,3)==0 .AND. - $ iInterval <= 5) THEN - WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5)') - $ 'DEBUG post-assign: iInterval=',iInterval, - $ ' uFM max=',MAXVAL(ABS(uFM)), - $ ' uFM min=',MINVAL(ABS(uFM)) - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ ' idMat max=',MAXVAL(ABS(identityMat)), - $ ' idMat min=',MINVAL(ABS(identityMat)) - IF(ANY(uFM /= uFM)) THEN - WRITE(*,*) 'DEBUG: *** NaN in uFM after assign!' - ENDIF - IF(ANY(identityMat /= identityMat)) THEN - WRITE(*,*) 'DEBUG: *** NaN in identityMat!' - ENDIF - ENDIF -!$OMP END CRITICAL IF (psiInters(iInterval,1) == 2) THEN c----------------------------------------------------------------------- c the inner layer itself (crossing singularity): initialize. @@ -435,22 +380,6 @@ SUBROUTINE ode_run uFM(i+mpert,:) = ua(i,:,2) ENDDO -c----------------------------------------------------------------------- -c DEBUG: check uFM after asymptotic expansion for NaN/Inf. -c----------------------------------------------------------------------- -!$OMP CRITICAL - WRITE(*,'(1x,a,i3,a,i3,a,es12.5)') - $ 'DEBUG asymp: iInterval=',iInterval, - $ ' ising=',ising,' t0=',t0 - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ ' uFM max=',MAXVAL(ABS(uFM)), - $ ' uFM min=',MINVAL(ABS(uFM)) - IF(ANY(uFM /= uFM)) THEN - WRITE(*,*) 'DEBUG: *** NaN detected in uFM', - $ ' after sing_get_ua! ising=',ising - ENDIF -!$OMP END CRITICAL - !Invert the init. fund. matrix (/=Id), to save it. uFMInv = uFM CALL ZGETRF(2*mpert,2*mpert,uFMInv,2*mpert,ipiv, @@ -488,30 +417,6 @@ SUBROUTINE ode_run rwork(5)=axisPsi*1e-3*psiDirs(iInterval) rwork(11)=rwork(5) CALL SYSTEM_CLOCK(COUNT=sTime) -c----------------------------------------------------------------------- -c DEBUG: print pre-integration diagnostics. -c----------------------------------------------------------------------- -!$OMP CRITICAL - WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5)') - $ 'DEBUG pre-ZVODE: iInterval=',iInterval, - $ ' startPsi=',startPsi,' endPsi=',endPsi - WRITE(*,'(1x,a,es12.5,a,es12.5,a,es12.5)') - $ ' tol_nr=',tol_nr, - $ ' axisPsi=',axisPsi, - $ ' rwork5=',rwork(5) - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ ' uFM maxval=',MAXVAL(ABS(uFM)), - $ ' uFM minval=',MINVAL(ABS(uFM)) - IF(ANY(uFM /= uFM)) THEN - WRITE(*,*) 'DEBUG: *** NaN in uFM before ZVODE!', - $ ' iInterval=',iInterval - ENDIF - WRITE(*,'(1x,a,i2,a,i2,a,i2,a,i3)') - $ ' psiInters=',psiInters(iInterval,1),',', - $ psiInters(iInterval,2),',', - $ psiInters(iInterval,3), - $ ' dir=',psiDirs(iInterval) -!$OMP END CRITICAL DO WHILE(psiDirs(iInterval)*startPsi $ < psiDirs(iInterval)*endPsi) rtol(1) = tol_nr @@ -525,43 +430,11 @@ SUBROUTINE ode_run WHERE(atol == 0) atol=maxatol !CMPLX(maxatol,maxatol) ENDWHERE -c----------------------------------------------------------------------- -c DEBUG: print ZVODE inputs on each iteration. -c----------------------------------------------------------------------- -!$OMP CRITICAL - WRITE(*,'(1x,a,i3,a,es12.5,a,es12.5,a,i2)') - $ 'DEBUG ZVODE in: iInterval=',iInterval, - $ ' rtol=',rtol(1), - $ ' maxatol=',maxatol,' istate=',istate - IF(ANY(atol /= atol)) THEN - WRITE(*,*) 'DEBUG: *** NaN in atol!', - $ ' iInterval=',iInterval - ENDIF -!$OMP END CRITICAL CALL ZVODE1(sing_derFM,neq,uFM,startPsi,endPsi, $ itol,rtol,atol,itask,istate,iopt,zwork,lzw, $ rwork,lrw,iwork,liw,ode_nojac,mf,rpar,ipar) -c----------------------------------------------------------------------- -c DEBUG: print ZVODE output state. -c----------------------------------------------------------------------- -!$OMP CRITICAL - WRITE(*,'(1x,a,i3,a,i3,a,es12.5)') - $ 'DEBUG ZVODE out: iInterval=',iInterval, - $ ' istate=',istate,' startPsi=',startPsi - IF(istate < 0) THEN - WRITE(*,*) 'DEBUG: ZVODE FAILED! istate=', - $ istate,' iInterval=',iInterval - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ ' uFM max=',MAXVAL(ABS(uFM)), - $ ' uFM min=',MINVAL(ABS(uFM)) - IF(ANY(uFM /= uFM)) THEN - WRITE(*,*) 'DEBUG: *** NaN in uFM after', - $ ' failed ZVODE!' - ENDIF - ENDIF -!$OMP END CRITICAL - IF(istate < 0) EXIT ! Break to prevent infinite loop + IF(istate < 0) EXIT ENDDO IF ( grid_packing == "naive" ) THEN CALL SYSTEM_CLOCK(COUNT=fTime) diff --git a/stride/sing.F b/stride/sing.F index 9198f0256..9aaaa4710 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -1002,7 +1002,6 @@ SUBROUTINE sing_derFM(neqFM,startPsi,uFM,duFM,rpar,ipar) COMPLEX(r8), DIMENSION(mpert,mpert) :: fmatb,gmatb COMPLEX(r8), DIMENSION(2*mband+1,mpert) :: kmatb COMPLEX(r8), DIMENSION(mpert,mpert) :: duTemp - LOGICAL, SAVE :: first_call = .TRUE. c----------------------------------------------------------------------- c cubic spline evaluation. c----------------------------------------------------------------------- @@ -1018,30 +1017,6 @@ SUBROUTINE sing_derFM(neqFM,startPsi,uFM,duFM,rpar,ipar) c----------------------------------------------------------------------- q=sq_s_f(4) singfac=mlow-nn*q+(/(ipert,ipert=0,mpert-1)/) -c----------------------------------------------------------------------- -c DEBUG: check for NaN in spline data and zero singfac. -c----------------------------------------------------------------------- - IF(first_call .OR. q /= q) THEN -!$OMP CRITICAL - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ 'DEBUG sing_derFM: psi=',startPsi,' q=',q - WRITE(*,'(1x,a,i4,a,i4)') - $ ' mlow=',mlow,' nn=',nn - WRITE(*,'(1x,a,es12.5,a,es12.5)') - $ ' singfac min=',MINVAL(ABS(singfac)), - $ ' singfac max=',MAXVAL(ABS(singfac)) - IF(q /= q) WRITE(*,*) ' *** NaN in q!' - IF(ANY(sq_s_f /= sq_s_f)) - $ WRITE(*,*) ' *** NaN in sq_s_f!' - IF(ANY(fmats_s_f /= fmats_s_f)) - $ WRITE(*,*) ' *** NaN in fmats_s_f!' - IF(ANY(gmats_s_f /= gmats_s_f)) - $ WRITE(*,*) ' *** NaN in gmats_s_f!' - IF(ANY(kmats_s_f /= kmats_s_f)) - $ WRITE(*,*) ' *** NaN in kmats_s_f!' - first_call = .FALSE. -!$OMP END CRITICAL - ENDIF singfac=1/singfac c----------------------------------------------------------------------- c copy Hermitian banded matrices F and G. From 8955716bb779af633cfc790f27fe12444229d956 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 24 Feb 2026 23:01:01 -0500 Subject: [PATCH 088/305] address @Claudes comment, a10 example needs future overhaul anyways --- docs/examples/a10_ideal_example/slayer.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/a10_ideal_example/slayer.in b/docs/examples/a10_ideal_example/slayer.in index 380c8dea4..bceddf8d5 100644 --- a/docs/examples/a10_ideal_example/slayer.in +++ b/docs/examples/a10_ideal_example/slayer.in @@ -56,7 +56,7 @@ bin_flag=t ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - growthrates_flag=t ! Calculate growthrates on each rational surface + est_gamma_flag=t ! Calculate estimated growth rates on each rational surface !bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE From 0c30074397b66da76f4ecbf4a0bf624ca4cc81e0 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 3 Mar 2026 18:30:30 -0500 Subject: [PATCH 089/305] WIP: split off growth rate functionality from gslayer.f --- install/DEPENDENCIES.inc | 3 +- slayer/growthrates.f | 1105 ++++++++++++++++++++++++++++++++++++++ slayer/gslayer.f | 1069 +----------------------------------- slayer/makefile | 79 --- slayer/slayer.f | 17 +- 5 files changed, 1124 insertions(+), 1149 deletions(-) create mode 100644 slayer/growthrates.f delete mode 100644 slayer/makefile diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index c77fae556..685ffff60 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -197,7 +197,8 @@ ../slayer/slayer_netcdf.o : ../slayer/sglobal.o ../slayer/version.inc ../slayer/layerinputs.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/slayer_netcdf.o ../pentrc/inputs.o ../equil/equil.o ../equil/spline.o ../equil/bicube.o ../slayer/gslayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o -../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/gslayer.o ../slayer/layerinputs.o +../slayer/growthrates.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/layerinputs.o ../slayer/slayer_netcdf.o +../slayer/slayer.o : ../slayer/sglobal.o ../slayer/params.o ../slayer/delta.o ../slayer/gslayer.o ../slayer/growthrates.o ../slayer/layerinputs.o # COIL ../coil/coil.o: ../equil/local.o ../equil/bicube.o ../coil/version.inc diff --git a/slayer/growthrates.f b/slayer/growthrates.f new file mode 100644 index 000000000..842bbd221 --- /dev/null +++ b/slayer/growthrates.f @@ -0,0 +1,1105 @@ + MODULE growthrates_mod +c----------------------------------------------------------------------- +c growthrates_mod: Growth-rate scanning and AMR dispersion solvers. +c +c Split from gslayer_mod, this module contains every subroutine +c after gpec_slayer that was formerly inside gslayer.f. It +c provides I/O helpers, array utilities, the dispersion +c determinant, and both AMR scanner variants used by the main +c SLAYER driver (slayer.f). +c +c Subprograms: +c 1. output_gamma - write results to netCDF via +c slayer_netcdf_mod +c 2. allocate_inputs - allocate slayer_inputs_type arrays +c 3. allocate_outputs - allocate slayer_outputs_type arrays +c 4. shrink_array - trim over-allocated scan arrays +c 5. grow_array - expand scan arrays dynamically +c 6. calc_determinant - 2x2 / 3x3 complex determinant +c 7. dispersion_det - coupled dispersion determinant +c 8. dispersion_AMR - AMR scan v1 (hash-based dedup) +c 9. dispersion_AMR_v2 - AMR scan v2 (cell-based storage) +c +c Helper subroutines (v1): get_or_compute +c Helper subroutines (v2): get_or_compute_v2, +c compute_delta_sub, check_cell_crossing_sub, +c subdivide_cell_sub, flatten_cells_to_points_sub +c----------------------------------------------------------------------- + + USE omp_lib + + USE sglobal_mod, ONLY: out_unit,r8,mu0,m_p,chag,lnLamb, + $ Q_e,Q_i,pr,pe,c_beta,ds,tau, + $ eta,visc,rho_s,lu,omega_e,omega_i, + $ delta_n,Q, + $ ifac,g_tmp,pi, ! used by AMR + $ tauk,iota_e,D_norm,P_perp,P_tor,delta_eff, ! used by det + $ amr_cell_type,amr_cells,n_amr_cells, ! v2 types + $ Q_store,D_store,n_pts, ! output arrays + $ MAX_PTS,HASH_SZ,HASH_SCALE, ! v1 constants + $ hash_head,hash_next, ! v1 hash + $ MAX_CELLS, ! v2 limit + $ slayer_inputs_type,slayer_outputs_type, + $ deltas_outputs_type, + $ tau_r,dc_tmp,dc_type, + $ sn_str,sm_str + USE delta_mod, ONLY: riccati,riccati_f,riccati_out, + $ parflow_flag,PeOhmOnly_flag + USE params_mod + USE layerinputs_mod + USE slayer_netcdf_mod + + IMPLICIT NONE + +c --- reconnection regulariser used in psi0 / JxB expressions; +c expose via namelist to make user-configurable. + REAL(r8), PARAMETER :: DELTA_N_PERT = 1.0e-2_r8 + + CONTAINS + +c----------------------------------------------------------------------- +c subprogram 1. output_gamma. +c Write growth-rate results to netCDF via slayer_netcdf_out. +c Passes the input/output structured types and AMR results +c through to the netCDF writer (slayer_netcdf_mod). +c----------------------------------------------------------------------- + SUBROUTINE output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, + $ all_deltas_out) + + LOGICAL, INTENT(IN) :: est_gamma_flag ! single-surface mode? + INTEGER, INTENT(IN) :: m_AMR ! AMR pass count + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + TYPE(slayer_outputs_type), INTENT(IN) :: sl_out + TYPE(deltas_outputs_type), INTENT(IN) :: + $ all_deltas_out(SIZE(sl_in%qval_arr)) + + CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),m_AMR,est_gamma_flag, + $ sl_in,sl_out,all_deltas_out) + + END SUBROUTINE output_gamma +c----------------------------------------------------------------------- +c subprogram 2. allocate_inputs. +c Allocate all per-surface arrays inside slayer_inputs_type. +c----------------------------------------------------------------------- + SUBROUTINE allocate_inputs(n_k,sl_in) + + INTEGER, INTENT(IN) :: n_k ! number of surfaces + TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in + + ALLOCATE(sl_in%qval_arr(n_k),sl_in%omegas_arr(n_k), + $ sl_in%Q_e_arr(n_k),sl_in%Q_i_arr(n_k),sl_in%psi_n_arr(n_k), + $ sl_in%Re_dp_arr(n_k),sl_in%Im_dp_arr(n_k), + $ sl_in%d_crit_arr(n_k),sl_in%P_tor_arr(n_k), + $ sl_in%P_perp_arr(n_k),sl_in%tau_arr(n_k), + $ sl_in%D_norm_arr(n_k), + $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), + $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k)) + RETURN + END SUBROUTINE allocate_inputs +c----------------------------------------------------------------------- +c subprogram 3. allocate_outputs. +c Allocate per-surface arrays inside slayer_outputs_type. +c----------------------------------------------------------------------- + SUBROUTINE allocate_outputs(n_k,sl_out) + + INTEGER, INTENT(IN) :: n_k ! number of surfaces + TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out + + ALLOCATE(sl_out%dels_db_arr(n_k),sl_out%gamma_sol_arr(n_k), + $ sl_out%gamma_est_arr(n_k),sl_out%br_th_arr(n_k) ) + RETURN + END SUBROUTINE allocate_outputs +c----------------------------------------------------------------------- +c subprogram 4. shrink_array. +c Trim an over-allocated REAL(r8) array down to new_size using +c MOVE_ALLOC (no copy of trailing elements). +c----------------------------------------------------------------------- + SUBROUTINE shrink_array(arr, new_size) + + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) + INTEGER, INTENT(IN) :: new_size ! target size + REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer + + ALLOCATE(temp(new_size)) + temp(1:new_size) = arr(1:new_size) + CALL move_alloc(temp, arr) + END SUBROUTINE shrink_array +c----------------------------------------------------------------------- +c subprogram 5. grow_array. +c Expand a REAL(r8) array from old_size to new_size, preserving +c existing data via MOVE_ALLOC. +c----------------------------------------------------------------------- + SUBROUTINE grow_array(arr, old_size, new_size) + + REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) + INTEGER, INTENT(IN) :: old_size ! current valid element count + INTEGER, INTENT(IN) :: new_size ! target allocation size + REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer + + ALLOCATE(temp(new_size)) + temp(1:old_size) = arr(1:old_size) + CALL move_alloc(temp, arr) + END SUBROUTINE grow_array +c----------------------------------------------------------------------- +c subprogram 6. calc_determinant. +c Compute the determinant of a 2x2 or 3x3 complex matrix. +c Returns (0,0) and sets status=-1 for unsupported sizes. +c status=0 on success, -1 when nk is neither 2 nor 3. +c----------------------------------------------------------------------- + SUBROUTINE calc_determinant(matk, nk, detk, status) + + IMPLICIT NONE + +c --- arguments + INTEGER, INTENT(IN) :: nk ! matrix rank (2 or 3) + COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! input matrix + COMPLEX(r8), INTENT(OUT) :: detk ! determinant result + INTEGER, INTENT(OUT) :: status ! 0=success, -1=unsupported rank + + status = 0 ! Initialize status as success + + SELECT CASE (nk) + CASE (2) + ! 2x2 determinant: ad - bc + detk = matk(1,1) * matk(2,2) - matk(1,2) * matk(2,1) + + CASE (3) + ! 3x3 determinant using cofactor expansion along first row + detk = matk(1,1)*(matk(2,2)*matk(3,3)-matk(2,3) + $ *matk(3,2))-matk(1,2)*(matk(2,1)*matk(3,3) + $ -matk(2,3)*matk(3,1))+matk(1,3)*(matk(2,1) + $ *matk(3,2)-matk(2,2)*matk(3,1)) + + CASE default + ! Unsupported matrix size + detk = (0.0, 0.0) + status = -1 + + END SELECT + RETURN + END SUBROUTINE calc_determinant +c----------------------------------------------------------------------- +c subprogram 7. dispersion_det. +c Compute the coupled dispersion determinant for n_k surfaces. +c +c For n_k = 1 (single surface): +c Evaluate riccati_f() (uses module g_tmp), de-normalise by lu^(1/3), and +c return Deltaprime - delta(Q). +c +c For n_k = 2 or 3 (coupled surfaces): +c Build the diagonal delta(Q) matrix, subtract from dp_matrix, +c and return det(dp_matrix - delta_Q). +c +c----------------------------------------------------------------------- + FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) + +c --- arguments + COMPLEX(r8), INTENT(IN) :: g_in ! complex growth rate + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces to include + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in +c --- function result and locals + COMPLEX(r8) :: dispersion_det ! returned determinant + COMPLEX(r8) :: det_val ! intermediate determinant + COMPLEX(r8) :: tmp_delta ! single-surface delta + COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:) ! diagonal delta matrix + COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! dp - delta_Q + INTEGER :: k ! surface loop index + INTEGER :: det_status ! calc_determinant error status + +c --- single-surface branch + IF (msing_max < 2) THEN +c set module-level variables for riccati_f + Q_e = sl_in%Q_e_arr(1) + Q_i = sl_in%Q_i_arr(1) + P_perp = sl_in%P_perp_arr(1) + P_tor = sl_in%P_tor_arr(1) + tau = sl_in%tau_arr(1) + D_norm = sl_in%D_norm_arr(1) + c_beta = sl_in%c_beta_arr(1) + tauk = sl_in%Qconv_arr(1) + iota_e = Q_e / (Q_e - Q_i) + + g_tmp = g_in + tmp_delta=riccati_f() +c de-normalise delta by lu^(1/3) + det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) + +c return Deltaprime - delta(Q) + dispersion_det = sl_in%Re_dp_arr(1) - det_val + +c --- coupled-surface branch (2 or 3 surfaces) + ELSEIF ((msing_max == 2) .OR. (msing_max == 3)) THEN + ALLOCATE(delta_Q(msing_max,msing_max)) + delta_Q=(0.0,0.0) + DO k=1,msing_max +c set module-level variables for this surface + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + P_perp = sl_in%P_perp_arr(k) + P_tor = sl_in%P_tor_arr(k) + tau = sl_in%tau_arr(k) + D_norm = sl_in%D_norm_arr(k) + c_beta = sl_in%c_beta_arr(k) + tauk = sl_in%Qconv_arr(k) + iota_e = Q_e / (Q_e - Q_i) + +c evaluate riccati_f at rescaled growth rate, de-normalise + g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk ! sets module-level g_tmp to SCALED value + delta_Q(k,k)=riccati_f() + delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) + END DO + +c compute det(dp_matrix - delta_Q) + result_matrix = sl_in%dp_matrix - delta_Q + CALL calc_determinant(result_matrix, msing_max, det_val, + $ det_status) + IF (det_status /= 0) THEN + WRITE(*,*) 'ERROR: calc_determinant unsupported rank=', + $ msing_max + STOP 'dispersion_det: calc_determinant failed' + END IF + dispersion_det = det_val + ELSE + WRITE(*,*) "Error: no support for msing > 3" + STOP "dispersion_det: unsupported n_k" + END IF + END FUNCTION dispersion_det + +c----------------------------------------------------------------------- +c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc +c Scans a 2D complex-Q grid for zeros of the dispersion relation +c D(Q) using adaptive refinement. A coarse grid is evaluated +c first (two-pass: nodes then cells), then cells that span a zero +c crossing in Re(D) or Im(D) are subdivided. +c +c Point deduplication uses a spatial hash table (HASH_SZ buckets, +c chained) so that midpoints shared between neighbouring cells +c are evaluated only once. +c----------------------------------------------------------------------- + SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, + $ scan_width,Q_num,AMR_passes, + $ coupling_flag) +c DEPRECATED: use dispersion_AMR_v2 instead. +c This v1 hash-based scanner is retained for +c backwards compatibility only. + +c --- arguments + INTEGER, INTENT(IN) :: n_k ! number of rational surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling + INTEGER, INTENT(IN) :: Q_num ! grid points per axis + INTEGER, INTENT(IN) :: AMR_passes ! refinement passes + REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? +c --- cell storage + INTEGER, ALLOCATABLE :: cells(:,:) ! (4, max) corner indices per cell + INTEGER, ALLOCATABLE :: new_cells(:,:) ! scratch for next level +c --- loop / index variables + INTEGER :: n_cells, n_new_cells, i, j + INTEGER :: c_idx, pass + INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! corner indices + INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! midpoint indices +c --- scan workspace + REAL(r8) :: r_min, r_max, i_min, i_max ! min/max Re/Im across corners + REAL(r8) :: ing_step ! coarse grid spacing + REAL(r8) :: ing_coarse, iing_coarse ! Re/Im coords for coarse node + LOGICAL :: cross_real, cross_imag ! zero-crossing flags + COMPLEX(r8) :: q_curr ! current evaluation point + INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Q_num,Q_num) node index map + +c --- 1. initialise hash table and point/cell storage + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) + + ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) + ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) + ALLOCATE(cells(4, 200000), new_cells(4, 200000)) + + hash_head = 0 + hash_next = 0 + n_pts = 0 + n_cells = 0 + +c --- 2. build initial coarse grid (two-pass method) +c Pass 1 computes and hashes every node; Pass 2 stitches cells +c from the stored indices -- no floating-point comparison needed. + ALLOCATE(coarse_indices(Q_num, Q_num)) + ing_step = (2.0*scan_width) / (Q_num - 1) + +c Pass 1: compute all grid nodes and store their hash indices + DO i = 1, Q_num + DO j = 1, Q_num + ing_coarse = -scan_width + (i - 1) * ing_step + iing_coarse = -scan_width + (j - 1) * ing_step + q_curr = CMPLX(ing_coarse, iing_coarse) + + CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, + $ sl_in, msing_max, coupling_flag) + END DO + END DO + +c Pass 2: stitch cells from the stored integer indices + DO i = 1, Q_num - 1 + DO j = 1, Q_num - 1 + n_cells = n_cells + 1 + cells(1, n_cells) = coarse_indices(i, j) ! TL + cells(2, n_cells) = coarse_indices(i+1, j) ! TR + cells(3, n_cells) = coarse_indices(i, j+1) ! BL + cells(4, n_cells) = coarse_indices(i+1, j+1) ! BR + END DO + END DO + DEALLOCATE(coarse_indices) + +c --- 3. refinement passes: subdivide cells with zero crossings + DO pass = 1, AMR_PASSES + WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, + $ ': Checking ', n_cells, ' cells...' + n_new_cells = 0 + + DO c_idx = 1, n_cells + idx_TL = cells(1, c_idx) + idx_TR = cells(2, c_idx) + idx_BL = cells(3, c_idx) + idx_BR = cells(4, c_idx) + +c check for sign change in Re(D) across cell corners + r_min = MIN(REAL(D_store(idx_TL)), + $ REAL(D_store(idx_TR)), + $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BR))) + r_max = MAX(REAL(D_store(idx_TL)), + $ REAL(D_store(idx_TR)), + $ REAL(D_store(idx_BL)), + $ REAL(D_store(idx_BR))) + cross_real = (r_min * r_max <= 0.0d0) + +c check for sign change in Im(D) across cell corners + i_min = MIN(AIMAG(D_store(idx_TL)), + $ AIMAG(D_store(idx_TR)), + $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BR))) + i_max = MAX(AIMAG(D_store(idx_TL)), + $ AIMAG(D_store(idx_TR)), + $ AIMAG(D_store(idx_BL)), + $ AIMAG(D_store(idx_BR))) + cross_imag = (i_min * i_max <= 0.0d0) + + IF (cross_real .OR. cross_imag) THEN +c refine: compute 5 midpoints, create 4 sub-cells + + ! Top-Mid + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_TR)) + CALL get_or_compute(q_curr, idx_TM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Bot-Mid + q_curr = 0.5d0*(Q_store(idx_BL)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_BM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Left-Mid + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BL)) + CALL get_or_compute(q_curr, idx_LM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Right-Mid + q_curr = 0.5d0*(Q_store(idx_TR)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_RM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Center + q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BR)) + CALL get_or_compute(q_curr, idx_MM,n_k, + $ sl_in,msing_max,coupling_flag) + + ! Create 4 sub-cells (TL, TR, BL, BR quadrants) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_TL + new_cells(2, n_new_cells) = idx_TM + new_cells(3, n_new_cells) = idx_LM + new_cells(4, n_new_cells) = idx_MM + + ! Sub 2 (Top-Right) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_TM + new_cells(2, n_new_cells) = idx_TR + new_cells(3, n_new_cells) = idx_MM + new_cells(4, n_new_cells) = idx_RM + + ! Sub 3 (Bot-Left) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_LM + new_cells(2, n_new_cells) = idx_MM + new_cells(3, n_new_cells) = idx_BL + new_cells(4, n_new_cells) = idx_BM + + ! Sub 4 (Bot-Right) + n_new_cells = n_new_cells + 1 + new_cells(1, n_new_cells) = idx_MM + new_cells(2, n_new_cells) = idx_RM + new_cells(3, n_new_cells) = idx_BM + new_cells(4, n_new_cells) = idx_BR + + ELSE + ! No refinement needed, keep original cell + n_new_cells = n_new_cells + 1 + new_cells(:, n_new_cells) = cells(:, c_idx) + END IF + END DO + +c --- swap arrays for next refinement pass + n_cells = n_new_cells + cells(:, 1:n_cells) = new_cells(:, 1:n_cells) + + END DO + DEALLOCATE(cells, new_cells) + WRITE(*,*) "AMR Scan Complete. Total Points:", n_pts + RETURN + END SUBROUTINE dispersion_AMR + +c----------------------------------------------------------------------- +c get_or_compute: hash-based point lookup for dispersion_AMR v1. +c If q_in is already in the hash table, return its index. +c Otherwise, evaluate the dispersion relation at q_in, store +c the result, and insert into the hash table. +c +c Uses 64-bit arithmetic internally to avoid integer overflow +c in the hash function. +c----------------------------------------------------------------------- + SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, + $ coupling_flag) + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point + INTEGER, INTENT(OUT) :: idx_out ! returned point index + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces to include + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag ! use coupled dispersion_det? +c --- locals + INTEGER :: h ! hash bucket index + INTEGER :: curr ! linked-list traversal index + COMPLEX(r8) :: delta_val ! computed dispersion result + INTEGER(8) :: ix8, iy8, h8 ! 64-bit intermediates for hash + +c --- 1. compute hash from quantised Re/Im coordinates (64-bit safe) + ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) + iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) + h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), + $ INT(HASH_SZ, 8)) + 1_8 + h = INT(h8) + + IF (h < 1 .OR. h > HASH_SZ) THEN + WRITE(*,*) "HASH ERROR: h=", h, " q_in=", q_in + STOP "get_or_compute: hash out of bounds" + END IF + +c --- 2. search hash chain for existing point + curr = hash_head(h) + DO WHILE (curr /= 0) + IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN + idx_out = curr + RETURN + END IF + curr = hash_next(curr) + END DO + +c --- 3. point not found: evaluate dispersion relation and store + n_pts = n_pts + 1 + IF (n_pts > MAX_PTS) THEN + WRITE(*,*) "ERROR: AMR exceeded MAX_PTS" + STOP "get_or_compute: MAX_PTS exceeded" + END IF + + idx_out = n_pts + Q_store(idx_out) = q_in + + IF (coupling_flag) THEN + g_tmp = q_in + delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) + ELSE + g_tmp = q_in + delta_val = riccati_f() + delta_val = delta_val - delta_eff + END IF + D_store(idx_out) = delta_val + +c --- 4. insert into hash chain (prepend) + hash_next(idx_out) = hash_head(h) + hash_head(h) = idx_out + + END SUBROUTINE get_or_compute + +c----------------------------------------------------------------------- +c get_or_compute_v2: hash-cached dispersion evaluation for AMR v2. +c Identical to get_or_compute but applies the ifac (imaginary-unit) +c Wick rotation that compute_delta_sub uses: g_tmp = q_in * ifac. +c----------------------------------------------------------------------- + SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, + $ msing_max, coupling_flag) + + IMPLICIT NONE + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in + INTEGER, INTENT(OUT) :: idx_out + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + +c --- locals + INTEGER :: h, curr + COMPLEX(r8) :: delta_val + INTEGER(8) :: ix8, iy8, h8 + +c --- 1. compute hash bucket + ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) + iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) + h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), + $ INT(HASH_SZ, 8)) + 1_8 + h = INT(h8) + +c --- 2. search hash chain for existing point + curr = hash_head(h) + DO WHILE (curr /= 0) + IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN + idx_out = curr + RETURN + END IF + curr = hash_next(curr) + END DO + +c --- 3. not found: evaluate with ifac rotation and store + n_pts = n_pts + 1 + IF (n_pts > MAX_PTS) THEN + WRITE(*,*) 'ERROR: AMR v2 cache exceeded MAX_PTS' + STOP 'get_or_compute_v2: MAX_PTS exceeded' + END IF + + idx_out = n_pts + Q_store(idx_out) = q_in + + IF (coupling_flag) THEN + g_tmp = q_in * ifac + delta_val = dispersion_det(g_tmp, n_k, sl_in, + $ msing_max) + ELSE + g_tmp = q_in * ifac + delta_val = riccati_f() + delta_val = delta_val - delta_eff + END IF + D_store(idx_out) = delta_val + +c --- 4. insert into hash chain (prepend) + hash_next(idx_out) = hash_head(h) + hash_head(h) = idx_out + + END SUBROUTINE get_or_compute_v2 + +c----------------------------------------------------------------------- +c dispersion_AMR_v2: cell-based adaptive mesh refinement scanner. +c Unlike v1 (hash-based point deduplication), v2 stores complete +c cells (TYPE amr_cell_type), each carrying 4 corner Q- and D- +c values. Refinement subdivides cells that contain a zero in +c Re(D) or Im(D) and re-evaluates the dispersion relation at the +c 5 new midpoints. +c +c All dispersion evaluations go through get_or_compute_v2, which +c caches results in Q_store / D_store via a hash table. This +c eliminates redundant evaluations for shared corners (initial grid) +c and shared edge-midpoints (refinement). At completion, Q_store +c and D_store are trimmed to n_pts unique output points. +c +c----------------------------------------------------------------------- + SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, + $ scan_width, Q_num, AMR_passes, + $ coupling_flag) + + IMPLICIT NONE + +c --- arguments + INTEGER, INTENT(IN) :: n_k ! number of rational surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling + INTEGER, INTENT(IN) :: Q_num ! grid points per axis + INTEGER, INTENT(IN) :: AMR_passes ! refinement passes + REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? +c --- locals + TYPE(amr_cell_type), ALLOCATABLE :: new_cells(:) + TYPE(amr_cell_type), ALLOCATABLE :: swap_tmp(:) ! for pointer swap + INTEGER :: i, j, c, corner, pass ! loop counters + REAL(r8) :: step ! grid spacing + REAL(r8) :: x, y ! real / imag grid coords + LOGICAL :: cross_real, cross_imag ! zero-crossing flags + INTEGER :: n_new_cells ! count during refinement + INTEGER :: cells_to_refine ! cells flagged per pass + INTEGER :: cells_kept ! cells kept per pass + INTEGER :: idx_tmp ! hash-cache index + COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! for trimming output + COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! for trimming output + +c --- 1. initialise cell storage and hash cache + + IF (ALLOCATED(amr_cells)) DEALLOCATE(amr_cells) + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) + + ALLOCATE(amr_cells(MAX_CELLS)) + ALLOCATE(new_cells(MAX_CELLS)) + ALLOCATE(Q_store(MAX_PTS)) + ALLOCATE(D_store(MAX_PTS)) + ALLOCATE(hash_head(HASH_SZ)) + ALLOCATE(hash_next(MAX_PTS)) + + hash_head = 0 + hash_next = 0 + n_pts = 0 + n_amr_cells = 0 + step = (2.0d0 * scan_width) / DBLE(Q_num - 1) + +c --- 2. build initial coarse grid of (Q_num-1)^2 cells + + DO i = 1, Q_num - 1 + DO j = 1, Q_num - 1 + x = -scan_width + DBLE(i-1) * step + y = -scan_width + DBLE(j-1) * step + + n_amr_cells = n_amr_cells + 1 + + IF (n_amr_cells > MAX_CELLS) THEN + WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in init' + STOP 'dispersion_AMR_v2: MAX_CELLS in init' + END IF + +c corner order: BL=1, BR=2, TL=3, TR=4 + amr_cells(n_amr_cells)%Q(1) = CMPLX(x, y, KIND=r8) + amr_cells(n_amr_cells)%Q(2) = CMPLX(x+step, y, KIND=r8) + amr_cells(n_amr_cells)%Q(3) = CMPLX(x, y+step, KIND=r8) + amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, + $ KIND=r8) + +c evaluate dispersion at each corner (hash-cached) + DO corner = 1, 4 + CALL get_or_compute_v2( + $ amr_cells(n_amr_cells)%Q(corner), + $ idx_tmp, n_k, sl_in, msing_max, + $ coupling_flag) + amr_cells(n_amr_cells)%D(corner) = + $ D_store(idx_tmp) + END DO + + amr_cells(n_amr_cells)%needs_refine = .FALSE. + END DO + END DO + +c --- 3. refinement passes: subdivide cells with zero crossings + DO pass = 1, AMR_passes + WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, + $ ': Processing ', n_amr_cells, ' cells' + +c flag cells that span a zero in Re(D) or Im(D) + cells_to_refine = 0 + DO c = 1, n_amr_cells + CALL check_cell_crossing_sub(amr_cells(c), + $ cross_real, cross_imag) + amr_cells(c)%needs_refine = (cross_real .OR. cross_imag) + IF (amr_cells(c)%needs_refine) THEN + cells_to_refine = cells_to_refine + 1 + END IF + END DO + +c build new cell list: subdivide flagged, keep the rest + n_new_cells = 0 + cells_kept = 0 + + DO c = 1, n_amr_cells + IF (amr_cells(c)%needs_refine) THEN + CALL subdivide_cell_sub(amr_cells(c), + $ new_cells, n_new_cells, MAX_CELLS, + $ n_k, sl_in, msing_max, coupling_flag) + ELSE + n_new_cells = n_new_cells + 1 + IF (n_new_cells > MAX_CELLS) THEN + WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in refine' + STOP 'dispersion_AMR_v2: MAX_CELLS in refine' + END IF + new_cells(n_new_cells) = amr_cells(c) + cells_kept = cells_kept + 1 + END IF + END DO + +c swap arrays for next pass (pointer swap, no element copy) + CALL MOVE_ALLOC(new_cells, swap_tmp) + CALL MOVE_ALLOC(amr_cells, new_cells) ! old amr_cells becomes new_cells + CALL MOVE_ALLOC(swap_tmp, amr_cells) ! filled array becomes amr_cells + n_amr_cells = n_new_cells + + END DO + +c --- 4. output: Q_store/D_store already populated by hash cache. +c Trim to exact size n_pts and deallocate hash infrastructure. + + ALLOCATE(temp_Q(n_pts)) + ALLOCATE(temp_D(n_pts)) + temp_Q(1:n_pts) = Q_store(1:n_pts) + temp_D(1:n_pts) = D_store(1:n_pts) + CALL MOVE_ALLOC(temp_Q, Q_store) + CALL MOVE_ALLOC(temp_D, D_store) + + IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) + IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) + DEALLOCATE(new_cells) +c keep amr_cells allocated for potential post-run inspection + + WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts + WRITE(*,'(A,2ES14.6)') ' D_store checksum (Re,Im):', + $ SUM(REAL(D_store(1:n_pts))), + $ SUM(AIMAG(D_store(1:n_pts))) + WRITE(*,'(A,2ES14.6)') ' D_store(1) sample:', + $ REAL(D_store(1)), AIMAG(D_store(1)) + + RETURN + END SUBROUTINE dispersion_AMR_v2 + +c----------------------------------------------------------------------- +c compute_delta_sub: evaluate the dispersion relation at a single +c complex-Q point for dispersion_AMR_v2. Multiplies q_in by ifac +c before passing to the Riccati solver or coupled-surface +c determinant routine. +c----------------------------------------------------------------------- + SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, + $ coupling_flag, delta_out) + + IMPLICIT NONE + +c --- arguments + COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point + INTEGER, INTENT(IN) :: n_k ! number of surfaces + INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag ! use coupled det? + COMPLEX(r8), INTENT(OUT) :: delta_out ! dispersion result + + IF (coupling_flag) THEN + g_tmp = q_in*ifac + delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) + ELSE + g_tmp = q_in*ifac + delta_out = riccati_f() + delta_out = delta_out - delta_eff + END IF + + RETURN + END SUBROUTINE compute_delta_sub + + +c----------------------------------------------------------------------- +c check_cell_crossing_sub: test whether a cell’s 4 corner D-values +c span a zero crossing in Re(D) and/or Im(D). Used by +c dispersion_AMR_v2 to decide which cells to refine. +c----------------------------------------------------------------------- + SUBROUTINE check_cell_crossing_sub(cell, cross_real, cross_imag) + + IMPLICIT NONE + + TYPE(amr_cell_type), INTENT(IN) :: cell + LOGICAL, INTENT(OUT) :: cross_real, cross_imag + + REAL(r8) :: r_vals(4), i_vals(4) ! corner Re/Im values + REAL(r8) :: r_min, r_max, i_min, i_max + INTEGER :: k + + DO k = 1, 4 + r_vals(k) = REAL(cell%D(k), KIND=r8) + i_vals(k) = AIMAG(cell%D(k)) + END DO + + r_min = MINVAL(r_vals) + r_max = MAXVAL(r_vals) + cross_real = (r_min * r_max <= 0.0d0) + + i_min = MINVAL(i_vals) + i_max = MAXVAL(i_vals) + cross_imag = (i_min * i_max <= 0.0d0) + + RETURN + END SUBROUTINE check_cell_crossing_sub + + +c----------------------------------------------------------------------- +c subdivide_cell_sub: split a parent cell into 4 child cells by +c computing 5 midpoints (bottom-mid, top-mid, left-mid, right-mid, +c centre) and evaluating the dispersion relation at each. The 4 +c resulting child cells are appended to new_cells. +c----------------------------------------------------------------------- + SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, + $ max_cells, n_k, sl_in, + $ msing_max, coupling_flag) + + IMPLICIT NONE + +c --- arguments + TYPE(amr_cell_type), INTENT(IN) :: parent + TYPE(amr_cell_type), INTENT(INOUT) :: new_cells(*) + INTEGER, INTENT(INOUT) :: n_new ! running count of new cells + INTEGER, INTENT(IN) :: max_cells ! capacity of new_cells + INTEGER, INTENT(IN) :: n_k + INTEGER, INTENT(IN) :: msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag +c --- corner coordinates and D-values from parent + COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr + COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr +c --- midpoint coordinates and D-values (cached via hash) + COMPLEX(r8) :: q_bm, q_tm, q_lm, q_rm, q_mm + COMPLEX(r8) :: d_bm, d_tm, d_lm, d_rm, d_mm + INTEGER :: idx_tmp ! hash-cache index + +c --- extract parent corners (BL=1, BR=2, TL=3, TR=4) + q_bl = parent%Q(1) + q_br = parent%Q(2) + q_tl = parent%Q(3) + q_tr = parent%Q(4) + + d_bl = parent%D(1) + d_br = parent%D(2) + d_tl = parent%D(3) + d_tr = parent%D(4) + +c --- compute 5 midpoint coordinates + q_bm = 0.5d0 * (q_bl + q_br) + q_tm = 0.5d0 * (q_tl + q_tr) + q_lm = 0.5d0 * (q_bl + q_tl) + q_rm = 0.5d0 * (q_br + q_tr) + q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) + +c --- evaluate dispersion at new midpoints (hash-cached) + CALL get_or_compute_v2(q_bm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_bm = D_store(idx_tmp) + CALL get_or_compute_v2(q_tm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_tm = D_store(idx_tmp) + CALL get_or_compute_v2(q_lm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_lm = D_store(idx_tmp) + CALL get_or_compute_v2(q_rm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_rm = D_store(idx_tmp) + CALL get_or_compute_v2(q_mm, idx_tmp, + $ n_k, sl_in, msing_max, coupling_flag) + d_mm = D_store(idx_tmp) + +c --- check space for 4 new cells + IF (n_new + 4 > max_cells) THEN + WRITE(*,*) 'ERROR: Would exceed MAX_CELLS in subdivide' + STOP 'subdivide_cell_sub: MAX_CELLS exceeded' + END IF + +c --- child 1: bottom-left quadrant (BL, BM, LM, MM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_bl + new_cells(n_new)%Q(2) = q_bm + new_cells(n_new)%Q(3) = q_lm + new_cells(n_new)%Q(4) = q_mm + new_cells(n_new)%D(1) = d_bl + new_cells(n_new)%D(2) = d_bm + new_cells(n_new)%D(3) = d_lm + new_cells(n_new)%D(4) = d_mm + new_cells(n_new)%needs_refine = .FALSE. + +c --- child 2: bottom-right quadrant (BM, BR, MM, RM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_bm + new_cells(n_new)%Q(2) = q_br + new_cells(n_new)%Q(3) = q_mm + new_cells(n_new)%Q(4) = q_rm + new_cells(n_new)%D(1) = d_bm + new_cells(n_new)%D(2) = d_br + new_cells(n_new)%D(3) = d_mm + new_cells(n_new)%D(4) = d_rm + new_cells(n_new)%needs_refine = .FALSE. + +c --- child 3: top-left quadrant (LM, MM, TL, TM) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_lm + new_cells(n_new)%Q(2) = q_mm + new_cells(n_new)%Q(3) = q_tl + new_cells(n_new)%Q(4) = q_tm + new_cells(n_new)%D(1) = d_lm + new_cells(n_new)%D(2) = d_mm + new_cells(n_new)%D(3) = d_tl + new_cells(n_new)%D(4) = d_tm + new_cells(n_new)%needs_refine = .FALSE. + +c --- child 4: top-right quadrant (MM, RM, TM, TR) + n_new = n_new + 1 + new_cells(n_new)%Q(1) = q_mm + new_cells(n_new)%Q(2) = q_rm + new_cells(n_new)%Q(3) = q_tm + new_cells(n_new)%Q(4) = q_tr + new_cells(n_new)%D(1) = d_mm + new_cells(n_new)%D(2) = d_rm + new_cells(n_new)%D(3) = d_tm + new_cells(n_new)%D(4) = d_tr + new_cells(n_new)%needs_refine = .FALSE. + + RETURN + END SUBROUTINE subdivide_cell_sub + + +c----------------------------------------------------------------------- +c flatten_cells_to_points_sub: extract unique (Q, D) points from +c the cell array into the module-level Q_store / D_store arrays. +c Uses a brute-force O(n^2) duplicate check which is acceptable +c for moderate cell counts; could be replaced by a hash set for +c very large scans. +c +c n_total_corners = num_cells*4; guarded against MAX_PTS overflow. +c----------------------------------------------------------------------- + SUBROUTINE flatten_cells_to_points_sub(num_cells) + + IMPLICIT NONE + +c --- arguments + INTEGER, INTENT(IN) :: num_cells ! number of cells to flatten +c --- locals + INTEGER :: c, corner, i, idx + INTEGER :: n_total_corners ! = num_cells * 4 + COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! all corner Q-values + COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! all corner D-values + INTEGER, ALLOCATABLE :: sort_idx(:) ! sort permutation + REAL(r8) :: tol ! duplicate tolerance + + tol = 1.0d-10 + n_total_corners = num_cells * 4 + + IF (n_total_corners > MAX_PTS) THEN + WRITE(*,*) 'ERROR: n_total_corners=', n_total_corners, + $ ' exceeds MAX_PTS=', MAX_PTS + STOP 'flatten_cells_to_points_sub: MAX_PTS exceeded' + END IF + + IF (num_cells <= 0) THEN + WRITE(*,*) 'ERROR: No cells to flatten' + n_pts = 0 + RETURN + END IF + +c --- gather all corners from cells + ALLOCATE(temp_Q(n_total_corners)) + ALLOCATE(temp_D(n_total_corners)) + ALLOCATE(sort_idx(n_total_corners)) + + idx = 0 + DO c = 1, num_cells + DO corner = 1, 4 + idx = idx + 1 + temp_Q(idx) = amr_cells(c)%Q(corner) + temp_D(idx) = amr_cells(c)%D(corner) + sort_idx(idx) = idx + END DO + END DO + +c --- sort by (Re(Q), Im(Q)) via quicksort on the index array + CALL qsort_complex_idx(temp_Q, sort_idx, 1, n_total_corners) + +c --- linear scan to count unique points (sorted order) + n_pts = 1 + DO i = 2, n_total_corners + IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) + $ >= tol) THEN + n_pts = n_pts + 1 + END IF + END DO + +c --- copy unique points to module-level output arrays + IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) + IF (ALLOCATED(D_store)) DEALLOCATE(D_store) + ALLOCATE(Q_store(n_pts)) + ALLOCATE(D_store(n_pts)) + + idx = 1 + Q_store(1) = temp_Q(sort_idx(1)) + D_store(1) = temp_D(sort_idx(1)) + DO i = 2, n_total_corners + IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) + $ >= tol) THEN + idx = idx + 1 + Q_store(idx) = temp_Q(sort_idx(i)) + D_store(idx) = temp_D(sort_idx(i)) + END IF + END DO + + DEALLOCATE(temp_Q, temp_D, sort_idx) + + RETURN + END SUBROUTINE flatten_cells_to_points_sub + +c----------------------------------------------------------------------- +c qsort_complex_idx: in-place quicksort of an index array by +c the complex keys (Re then Im). Operates on sort_idx so the +c Q/D data arrays remain untouched. +c----------------------------------------------------------------------- + RECURSIVE SUBROUTINE qsort_complex_idx(keys, idx, lo, hi) + + IMPLICIT NONE + + COMPLEX(r8), INTENT(IN) :: keys(:) + INTEGER, INTENT(INOUT) :: idx(:) + INTEGER, INTENT(IN) :: lo, hi + + INTEGER :: i, j, pivot_idx, tmp + REAL(r8) :: p_re, p_im, k_re, k_im + + IF (lo >= hi) RETURN + +c median-of-three pivot selection + pivot_idx = idx((lo + hi) / 2) + p_re = REAL(keys(pivot_idx), KIND=r8) + p_im = AIMAG(keys(pivot_idx)) + + i = lo + j = hi + DO WHILE (i <= j) +c advance i while keys(idx(i)) < pivot + k_re = REAL(keys(idx(i)), KIND=r8) + k_im = AIMAG(keys(idx(i))) + DO WHILE (k_re < p_re .OR. + $ (k_re == p_re .AND. k_im < p_im)) + i = i + 1 + k_re = REAL(keys(idx(i)), KIND=r8) + k_im = AIMAG(keys(idx(i))) + END DO +c retreat j while keys(idx(j)) > pivot + k_re = REAL(keys(idx(j)), KIND=r8) + k_im = AIMAG(keys(idx(j))) + DO WHILE (k_re > p_re .OR. + $ (k_re == p_re .AND. k_im > p_im)) + j = j - 1 + k_re = REAL(keys(idx(j)), KIND=r8) + k_im = AIMAG(keys(idx(j))) + END DO +c swap if pointers haven't crossed + IF (i <= j) THEN + tmp = idx(i) + idx(i) = idx(j) + idx(j) = tmp + i = i + 1 + j = j - 1 + END IF + END DO + +c recurse on partitions + IF (lo < j) CALL qsort_complex_idx(keys, idx, lo, j) + IF (i < hi) CALL qsort_complex_idx(keys, idx, i, hi) + + RETURN + END SUBROUTINE qsort_complex_idx + END MODULE growthrates_mod \ No newline at end of file diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 85037c779..1275af917 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -1,24 +1,16 @@ MODULE gslayer_mod c----------------------------------------------------------------------- -c gslayer_mod: SLAYER driver routines and AMR dispersion scanners. +c gslayer_mod: Single-surface SLAYER driver called by GPEC. +c +c This module contains the gpec_slayer subroutine, which computes +c Delta, torque, and critical field threshold for one rational +c surface. It is the primary interface used by gpec/gpout.f. +c +c Growth-rate scanning, AMR dispersion solvers, and I/O helpers +c have been split out into growthrates_mod (growthrates.f). c c Subprograms: c 1. gpec_slayer - single-surface delta/torque driver -c 2. output_gamma - write results to netCDF via -c slayer_netcdf_mod -c 3. allocate_inputs - allocate slayer_inputs_type arrays -c 4. allocate_outputs - allocate slayer_outputs_type arrays -c 5. shrink_array - trim over-allocated scan arrays -c 6. grow_array - expand scan arrays dynamically -c 7. calc_determinant - 2x2 / 3x3 complex determinant -c 8. dispersion_det - coupled dispersion determinant -c 9. dispersion_AMR - AMR scan v1 (hash-based dedup) -c 10. dispersion_AMR_v2 - AMR scan v2 (cell-based storage) -c -c Helper subroutines (v1): get_or_compute -c Helper subroutines (v2): get_or_compute_v2, -c compute_delta_sub, check_cell_crossing_sub, -c subdivide_cell_sub, flatten_cells_to_points_sub c----------------------------------------------------------------------- USE omp_lib @@ -265,1049 +257,4 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, RETURN END SUBROUTINE gpec_slayer -c----------------------------------------------------------------------- -c subprogram 2. output_gamma. -c Write growth-rate results to netCDF via slayer_netcdf_out. -c Passes the input/output structured types and AMR results -c through to the netCDF writer (slayer_netcdf_mod). -c----------------------------------------------------------------------- - SUBROUTINE output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, - $ all_deltas_out) - - LOGICAL, INTENT(IN) :: est_gamma_flag ! single-surface mode? - INTEGER, INTENT(IN) :: m_AMR ! AMR pass count - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - TYPE(slayer_outputs_type), INTENT(IN) :: sl_out - TYPE(deltas_outputs_type), INTENT(IN) :: - $ all_deltas_out(SIZE(sl_in%qval_arr)) - - CALL slayer_netcdf_out(SIZE(sl_in%qval_arr),m_AMR,est_gamma_flag, - $ sl_in,sl_out,all_deltas_out) - - END SUBROUTINE output_gamma -c----------------------------------------------------------------------- -c subprogram 3. allocate_inputs. -c Allocate all per-surface arrays inside slayer_inputs_type. -c----------------------------------------------------------------------- - SUBROUTINE allocate_inputs(n_k,sl_in) - - INTEGER, INTENT(IN) :: n_k ! number of surfaces - TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in - - ALLOCATE(sl_in%qval_arr(n_k),sl_in%omegas_arr(n_k), - $ sl_in%Q_e_arr(n_k),sl_in%Q_i_arr(n_k),sl_in%psi_n_arr(n_k), - $ sl_in%Re_dp_arr(n_k),sl_in%Im_dp_arr(n_k), - $ sl_in%d_crit_arr(n_k),sl_in%P_tor_arr(n_k), - $ sl_in%P_perp_arr(n_k),sl_in%tau_arr(n_k), - $ sl_in%D_norm_arr(n_k), - $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), - $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k)) - RETURN - END SUBROUTINE allocate_inputs -c----------------------------------------------------------------------- -c subprogram 4. allocate_outputs. -c Allocate per-surface arrays inside slayer_outputs_type. -c----------------------------------------------------------------------- - SUBROUTINE allocate_outputs(n_k,sl_out) - - INTEGER, INTENT(IN) :: n_k ! number of surfaces - TYPE(slayer_outputs_type), INTENT(INOUT) :: sl_out - - ALLOCATE(sl_out%dels_db_arr(n_k),sl_out%gamma_sol_arr(n_k), - $ sl_out%gamma_est_arr(n_k),sl_out%br_th_arr(n_k) ) - RETURN - END SUBROUTINE allocate_outputs -c----------------------------------------------------------------------- -c subprogram 5. shrink_array. -c Trim an over-allocated REAL(r8) array down to new_size using -c MOVE_ALLOC (no copy of trailing elements). -c----------------------------------------------------------------------- - SUBROUTINE shrink_array(arr, new_size) - - REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) - INTEGER, INTENT(IN) :: new_size ! target size - REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer - - ALLOCATE(temp(new_size)) - temp(1:new_size) = arr(1:new_size) - CALL move_alloc(temp, arr) - END SUBROUTINE shrink_array -c----------------------------------------------------------------------- -c subprogram 6. grow_array. -c Expand a REAL(r8) array from old_size to new_size, preserving -c existing data via MOVE_ALLOC. -c----------------------------------------------------------------------- - SUBROUTINE grow_array(arr, old_size, new_size) - - REAL(r8), ALLOCATABLE, INTENT(INOUT) :: arr(:) - INTEGER, INTENT(IN) :: old_size ! current valid element count - INTEGER, INTENT(IN) :: new_size ! target allocation size - REAL(r8), ALLOCATABLE :: temp(:) ! temporary buffer - - ALLOCATE(temp(new_size)) - temp(1:old_size) = arr(1:old_size) - CALL move_alloc(temp, arr) - END SUBROUTINE grow_array -c----------------------------------------------------------------------- -c subprogram 7. calc_determinant. -c Compute the determinant of a 2x2 or 3x3 complex matrix. -c Returns (0,0) and sets status=-1 for unsupported sizes. -c status=0 on success, -1 when nk is neither 2 nor 3. -c----------------------------------------------------------------------- - SUBROUTINE calc_determinant(matk, nk, detk, status) - - IMPLICIT NONE - -c --- arguments - INTEGER, INTENT(IN) :: nk ! matrix rank (2 or 3) - COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! input matrix - COMPLEX(r8), INTENT(OUT) :: detk ! determinant result - INTEGER, INTENT(OUT) :: status ! 0=success, -1=unsupported rank - - status = 0 ! Initialize status as success - - SELECT CASE (nk) - CASE (2) - ! 2x2 determinant: ad - bc - detk = matk(1,1) * matk(2,2) - matk(1,2) * matk(2,1) - - CASE (3) - ! 3x3 determinant using cofactor expansion along first row - detk = matk(1,1)*(matk(2,2)*matk(3,3)-matk(2,3) - $ *matk(3,2))-matk(1,2)*(matk(2,1)*matk(3,3) - $ -matk(2,3)*matk(3,1))+matk(1,3)*(matk(2,1) - $ *matk(3,2)-matk(2,2)*matk(3,1)) - - CASE default - ! Unsupported matrix size - detk = (0.0, 0.0) - status = -1 - - END SELECT - RETURN - END SUBROUTINE calc_determinant -c----------------------------------------------------------------------- -c subprogram 8. dispersion_det. -c Compute the coupled dispersion determinant for n_k surfaces. -c -c For n_k = 1 (single surface): -c Evaluate riccati_f() (uses module g_tmp), de-normalise by lu^(1/3), and -c return Deltaprime - delta(Q). -c -c For n_k = 2 or 3 (coupled surfaces): -c Build the diagonal delta(Q) matrix, subtract from dp_matrix, -c and return det(dp_matrix - delta_Q). -c -c----------------------------------------------------------------------- - FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) - -c --- arguments - COMPLEX(r8), INTENT(IN) :: g_in ! complex growth rate - INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces to include - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in -c --- function result and locals - COMPLEX(r8) :: dispersion_det ! returned determinant - COMPLEX(r8) :: det_val ! intermediate determinant - COMPLEX(r8) :: tmp_delta ! single-surface delta - COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:) ! diagonal delta matrix - COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! dp - delta_Q - INTEGER :: k ! surface loop index - INTEGER :: det_status ! calc_determinant error status - -c --- single-surface branch - IF (msing_max < 2) THEN -c set module-level variables for riccati_f - Q_e = sl_in%Q_e_arr(1) - Q_i = sl_in%Q_i_arr(1) - P_perp = sl_in%P_perp_arr(1) - P_tor = sl_in%P_tor_arr(1) - tau = sl_in%tau_arr(1) - D_norm = sl_in%D_norm_arr(1) - c_beta = sl_in%c_beta_arr(1) - tauk = sl_in%Qconv_arr(1) - iota_e = Q_e / (Q_e - Q_i) - - g_tmp = g_in - tmp_delta=riccati_f() -c de-normalise delta by lu^(1/3) - det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) - -c return Deltaprime - delta(Q) - dispersion_det = sl_in%Re_dp_arr(1) - det_val - -c --- coupled-surface branch (2 or 3 surfaces) - ELSEIF ((msing_max == 2) .OR. (msing_max == 3)) THEN - ALLOCATE(delta_Q(msing_max,msing_max)) - delta_Q=(0.0,0.0) - DO k=1,msing_max -c set module-level variables for this surface - Q_e = sl_in%Q_e_arr(k) - Q_i = sl_in%Q_i_arr(k) - P_perp = sl_in%P_perp_arr(k) - P_tor = sl_in%P_tor_arr(k) - tau = sl_in%tau_arr(k) - D_norm = sl_in%D_norm_arr(k) - c_beta = sl_in%c_beta_arr(k) - tauk = sl_in%Qconv_arr(k) - iota_e = Q_e / (Q_e - Q_i) - -c evaluate riccati_f at rescaled growth rate, de-normalise - g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk ! sets module-level g_tmp to SCALED value - delta_Q(k,k)=riccati_f() - delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) - END DO - -c compute det(dp_matrix - delta_Q) - result_matrix = sl_in%dp_matrix - delta_Q - CALL calc_determinant(result_matrix, msing_max, det_val, - $ det_status) - IF (det_status /= 0) THEN - WRITE(*,*) 'ERROR: calc_determinant unsupported rank=', - $ msing_max - STOP 'dispersion_det: calc_determinant failed' - END IF - dispersion_det = det_val - ELSE - WRITE(*,*) "Error: no support for msing > 3" - STOP "dispersion_det: unsupported n_k" - END IF - END FUNCTION dispersion_det - -c----------------------------------------------------------------------- -c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc -c Scans a 2D complex-Q grid for zeros of the dispersion relation -c D(Q) using adaptive refinement. A coarse grid is evaluated -c first (two-pass: nodes then cells), then cells that span a zero -c crossing in Re(D) or Im(D) are subdivided. -c -c Point deduplication uses a spatial hash table (HASH_SZ buckets, -c chained) so that midpoints shared between neighbouring cells -c are evaluated only once. -c----------------------------------------------------------------------- - SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, - $ scan_width,Q_num,AMR_passes, - $ coupling_flag) -c DEPRECATED: use dispersion_AMR_v2 instead. -c This v1 hash-based scanner is retained for -c backwards compatibility only. - -c --- arguments - INTEGER, INTENT(IN) :: n_k ! number of rational surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling - INTEGER, INTENT(IN) :: Q_num ! grid points per axis - INTEGER, INTENT(IN) :: AMR_passes ! refinement passes - REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? -c --- cell storage - INTEGER, ALLOCATABLE :: cells(:,:) ! (4, max) corner indices per cell - INTEGER, ALLOCATABLE :: new_cells(:,:) ! scratch for next level -c --- loop / index variables - INTEGER :: n_cells, n_new_cells, i, j - INTEGER :: c_idx, pass - INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! corner indices - INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! midpoint indices -c --- scan workspace - REAL(r8) :: r_min, r_max, i_min, i_max ! min/max Re/Im across corners - REAL(r8) :: ing_step ! coarse grid spacing - REAL(r8) :: ing_coarse, iing_coarse ! Re/Im coords for coarse node - LOGICAL :: cross_real, cross_imag ! zero-crossing flags - COMPLEX(r8) :: q_curr ! current evaluation point - INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Q_num,Q_num) node index map - -c --- 1. initialise hash table and point/cell storage - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) - IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - - ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) - ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) - ALLOCATE(cells(4, 200000), new_cells(4, 200000)) - - hash_head = 0 - hash_next = 0 - n_pts = 0 - n_cells = 0 - -c --- 2. build initial coarse grid (two-pass method) -c Pass 1 computes and hashes every node; Pass 2 stitches cells -c from the stored indices -- no floating-point comparison needed. - ALLOCATE(coarse_indices(Q_num, Q_num)) - ing_step = (2.0*scan_width) / (Q_num - 1) - -c Pass 1: compute all grid nodes and store their hash indices - DO i = 1, Q_num - DO j = 1, Q_num - ing_coarse = -scan_width + (i - 1) * ing_step - iing_coarse = -scan_width + (j - 1) * ing_step - q_curr = CMPLX(ing_coarse, iing_coarse) - - CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, - $ sl_in, msing_max, coupling_flag) - END DO - END DO - -c Pass 2: stitch cells from the stored integer indices - DO i = 1, Q_num - 1 - DO j = 1, Q_num - 1 - n_cells = n_cells + 1 - cells(1, n_cells) = coarse_indices(i, j) ! TL - cells(2, n_cells) = coarse_indices(i+1, j) ! TR - cells(3, n_cells) = coarse_indices(i, j+1) ! BL - cells(4, n_cells) = coarse_indices(i+1, j+1) ! BR - END DO - END DO - DEALLOCATE(coarse_indices) - -c --- 3. refinement passes: subdivide cells with zero crossings - DO pass = 1, AMR_PASSES - WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, - $ ': Checking ', n_cells, ' cells...' - n_new_cells = 0 - - DO c_idx = 1, n_cells - idx_TL = cells(1, c_idx) - idx_TR = cells(2, c_idx) - idx_BL = cells(3, c_idx) - idx_BR = cells(4, c_idx) - -c check for sign change in Re(D) across cell corners - r_min = MIN(REAL(D_store(idx_TL)), - $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), - $ REAL(D_store(idx_BR))) - r_max = MAX(REAL(D_store(idx_TL)), - $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), - $ REAL(D_store(idx_BR))) - cross_real = (r_min * r_max <= 0.0d0) - -c check for sign change in Im(D) across cell corners - i_min = MIN(AIMAG(D_store(idx_TL)), - $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), - $ AIMAG(D_store(idx_BR))) - i_max = MAX(AIMAG(D_store(idx_TL)), - $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), - $ AIMAG(D_store(idx_BR))) - cross_imag = (i_min * i_max <= 0.0d0) - - IF (cross_real .OR. cross_imag) THEN -c refine: compute 5 midpoints, create 4 sub-cells - - ! Top-Mid - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_TR)) - CALL get_or_compute(q_curr, idx_TM,n_k, - $ sl_in,msing_max,coupling_flag) - - ! Bot-Mid - q_curr = 0.5d0*(Q_store(idx_BL)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_BM,n_k, - $ sl_in,msing_max,coupling_flag) - - ! Left-Mid - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BL)) - CALL get_or_compute(q_curr, idx_LM,n_k, - $ sl_in,msing_max,coupling_flag) - - ! Right-Mid - q_curr = 0.5d0*(Q_store(idx_TR)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_RM,n_k, - $ sl_in,msing_max,coupling_flag) - - ! Center - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_MM,n_k, - $ sl_in,msing_max,coupling_flag) - - ! Create 4 sub-cells (TL, TR, BL, BR quadrants) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_TL - new_cells(2, n_new_cells) = idx_TM - new_cells(3, n_new_cells) = idx_LM - new_cells(4, n_new_cells) = idx_MM - - ! Sub 2 (Top-Right) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_TM - new_cells(2, n_new_cells) = idx_TR - new_cells(3, n_new_cells) = idx_MM - new_cells(4, n_new_cells) = idx_RM - - ! Sub 3 (Bot-Left) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_LM - new_cells(2, n_new_cells) = idx_MM - new_cells(3, n_new_cells) = idx_BL - new_cells(4, n_new_cells) = idx_BM - - ! Sub 4 (Bot-Right) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_MM - new_cells(2, n_new_cells) = idx_RM - new_cells(3, n_new_cells) = idx_BM - new_cells(4, n_new_cells) = idx_BR - - ELSE - ! No refinement needed, keep original cell - n_new_cells = n_new_cells + 1 - new_cells(:, n_new_cells) = cells(:, c_idx) - END IF - END DO - -c --- swap arrays for next refinement pass - n_cells = n_new_cells - cells(:, 1:n_cells) = new_cells(:, 1:n_cells) - - END DO - DEALLOCATE(cells, new_cells) - WRITE(*,*) "AMR Scan Complete. Total Points:", n_pts - RETURN - END SUBROUTINE dispersion_AMR - -c----------------------------------------------------------------------- -c get_or_compute: hash-based point lookup for dispersion_AMR v1. -c If q_in is already in the hash table, return its index. -c Otherwise, evaluate the dispersion relation at q_in, store -c the result, and insert into the hash table. -c -c Uses 64-bit arithmetic internally to avoid integer overflow -c in the hash function. -c----------------------------------------------------------------------- - SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, - $ coupling_flag) - -c --- arguments - COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point - INTEGER, INTENT(OUT) :: idx_out ! returned point index - INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces to include - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! use coupled dispersion_det? -c --- locals - INTEGER :: h ! hash bucket index - INTEGER :: curr ! linked-list traversal index - COMPLEX(r8) :: delta_val ! computed dispersion result - INTEGER(8) :: ix8, iy8, h8 ! 64-bit intermediates for hash - -c --- 1. compute hash from quantised Re/Im coordinates (64-bit safe) - ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) - iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) - h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), - $ INT(HASH_SZ, 8)) + 1_8 - h = INT(h8) - - IF (h < 1 .OR. h > HASH_SZ) THEN - WRITE(*,*) "HASH ERROR: h=", h, " q_in=", q_in - STOP "get_or_compute: hash out of bounds" - END IF - -c --- 2. search hash chain for existing point - curr = hash_head(h) - DO WHILE (curr /= 0) - IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN - idx_out = curr - RETURN - END IF - curr = hash_next(curr) - END DO - -c --- 3. point not found: evaluate dispersion relation and store - n_pts = n_pts + 1 - IF (n_pts > MAX_PTS) THEN - WRITE(*,*) "ERROR: AMR exceeded MAX_PTS" - STOP "get_or_compute: MAX_PTS exceeded" - END IF - - idx_out = n_pts - Q_store(idx_out) = q_in - - IF (coupling_flag) THEN - g_tmp = q_in - delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) - ELSE - g_tmp = q_in - delta_val = riccati_f() - delta_val = delta_val - delta_eff - END IF - D_store(idx_out) = delta_val - -c --- 4. insert into hash chain (prepend) - hash_next(idx_out) = hash_head(h) - hash_head(h) = idx_out - - END SUBROUTINE get_or_compute - -c----------------------------------------------------------------------- -c get_or_compute_v2: hash-cached dispersion evaluation for AMR v2. -c Identical to get_or_compute but applies the ifac (imaginary-unit) -c Wick rotation that compute_delta_sub uses: g_tmp = q_in * ifac. -c----------------------------------------------------------------------- - SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, - $ msing_max, coupling_flag) - - IMPLICIT NONE - -c --- arguments - COMPLEX(r8), INTENT(IN) :: q_in - INTEGER, INTENT(OUT) :: idx_out - INTEGER, INTENT(IN) :: n_k, msing_max - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag - -c --- locals - INTEGER :: h, curr - COMPLEX(r8) :: delta_val - INTEGER(8) :: ix8, iy8, h8 - -c --- 1. compute hash bucket - ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) - iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) - h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), - $ INT(HASH_SZ, 8)) + 1_8 - h = INT(h8) - -c --- 2. search hash chain for existing point - curr = hash_head(h) - DO WHILE (curr /= 0) - IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN - idx_out = curr - RETURN - END IF - curr = hash_next(curr) - END DO - -c --- 3. not found: evaluate with ifac rotation and store - n_pts = n_pts + 1 - IF (n_pts > MAX_PTS) THEN - WRITE(*,*) 'ERROR: AMR v2 cache exceeded MAX_PTS' - STOP 'get_or_compute_v2: MAX_PTS exceeded' - END IF - - idx_out = n_pts - Q_store(idx_out) = q_in - - IF (coupling_flag) THEN - g_tmp = q_in * ifac - delta_val = dispersion_det(g_tmp, n_k, sl_in, - $ msing_max) - ELSE - g_tmp = q_in * ifac - delta_val = riccati_f() - delta_val = delta_val - delta_eff - END IF - D_store(idx_out) = delta_val - -c --- 4. insert into hash chain (prepend) - hash_next(idx_out) = hash_head(h) - hash_head(h) = idx_out - - END SUBROUTINE get_or_compute_v2 - -c----------------------------------------------------------------------- -c dispersion_AMR_v2: cell-based adaptive mesh refinement scanner. -c Unlike v1 (hash-based point deduplication), v2 stores complete -c cells (TYPE amr_cell_type), each carrying 4 corner Q- and D- -c values. Refinement subdivides cells that contain a zero in -c Re(D) or Im(D) and re-evaluates the dispersion relation at the -c 5 new midpoints. -c -c All dispersion evaluations go through get_or_compute_v2, which -c caches results in Q_store / D_store via a hash table. This -c eliminates redundant evaluations for shared corners (initial grid) -c and shared edge-midpoints (refinement). At completion, Q_store -c and D_store are trimmed to n_pts unique output points. -c -c----------------------------------------------------------------------- - SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, - $ scan_width, Q_num, AMR_passes, - $ coupling_flag) - - IMPLICIT NONE - -c --- arguments - INTEGER, INTENT(IN) :: n_k ! number of rational surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling - INTEGER, INTENT(IN) :: Q_num ! grid points per axis - INTEGER, INTENT(IN) :: AMR_passes ! refinement passes - REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? -c --- locals - TYPE(amr_cell_type), ALLOCATABLE :: new_cells(:) - TYPE(amr_cell_type), ALLOCATABLE :: swap_tmp(:) ! for pointer swap - INTEGER :: i, j, c, corner, pass ! loop counters - REAL(r8) :: step ! grid spacing - REAL(r8) :: x, y ! real / imag grid coords - LOGICAL :: cross_real, cross_imag ! zero-crossing flags - INTEGER :: n_new_cells ! count during refinement - INTEGER :: cells_to_refine ! cells flagged per pass - INTEGER :: cells_kept ! cells kept per pass - INTEGER :: idx_tmp ! hash-cache index - COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! for trimming output - COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! for trimming output - -c --- 1. initialise cell storage and hash cache - - IF (ALLOCATED(amr_cells)) DEALLOCATE(amr_cells) - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) - IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - - ALLOCATE(amr_cells(MAX_CELLS)) - ALLOCATE(new_cells(MAX_CELLS)) - ALLOCATE(Q_store(MAX_PTS)) - ALLOCATE(D_store(MAX_PTS)) - ALLOCATE(hash_head(HASH_SZ)) - ALLOCATE(hash_next(MAX_PTS)) - - hash_head = 0 - hash_next = 0 - n_pts = 0 - n_amr_cells = 0 - step = (2.0d0 * scan_width) / DBLE(Q_num - 1) - -c --- 2. build initial coarse grid of (Q_num-1)^2 cells - - DO i = 1, Q_num - 1 - DO j = 1, Q_num - 1 - x = -scan_width + DBLE(i-1) * step - y = -scan_width + DBLE(j-1) * step - - n_amr_cells = n_amr_cells + 1 - - IF (n_amr_cells > MAX_CELLS) THEN - WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in init' - STOP 'dispersion_AMR_v2: MAX_CELLS in init' - END IF - -c corner order: BL=1, BR=2, TL=3, TR=4 - amr_cells(n_amr_cells)%Q(1) = CMPLX(x, y, KIND=r8) - amr_cells(n_amr_cells)%Q(2) = CMPLX(x+step, y, KIND=r8) - amr_cells(n_amr_cells)%Q(3) = CMPLX(x, y+step, KIND=r8) - amr_cells(n_amr_cells)%Q(4) = CMPLX(x+step, y+step, - $ KIND=r8) - -c evaluate dispersion at each corner (hash-cached) - DO corner = 1, 4 - CALL get_or_compute_v2( - $ amr_cells(n_amr_cells)%Q(corner), - $ idx_tmp, n_k, sl_in, msing_max, - $ coupling_flag) - amr_cells(n_amr_cells)%D(corner) = - $ D_store(idx_tmp) - END DO - - amr_cells(n_amr_cells)%needs_refine = .FALSE. - END DO - END DO - -c --- 3. refinement passes: subdivide cells with zero crossings - DO pass = 1, AMR_passes - WRITE(*,'(A,I2,A,I7,A)') ' Pass ', pass, - $ ': Processing ', n_amr_cells, ' cells' - -c flag cells that span a zero in Re(D) or Im(D) - cells_to_refine = 0 - DO c = 1, n_amr_cells - CALL check_cell_crossing_sub(amr_cells(c), - $ cross_real, cross_imag) - amr_cells(c)%needs_refine = (cross_real .OR. cross_imag) - IF (amr_cells(c)%needs_refine) THEN - cells_to_refine = cells_to_refine + 1 - END IF - END DO - -c build new cell list: subdivide flagged, keep the rest - n_new_cells = 0 - cells_kept = 0 - - DO c = 1, n_amr_cells - IF (amr_cells(c)%needs_refine) THEN - CALL subdivide_cell_sub(amr_cells(c), - $ new_cells, n_new_cells, MAX_CELLS, - $ n_k, sl_in, msing_max, coupling_flag) - ELSE - n_new_cells = n_new_cells + 1 - IF (n_new_cells > MAX_CELLS) THEN - WRITE(*,*) 'ERROR: Exceeded MAX_CELLS in refine' - STOP 'dispersion_AMR_v2: MAX_CELLS in refine' - END IF - new_cells(n_new_cells) = amr_cells(c) - cells_kept = cells_kept + 1 - END IF - END DO - -c swap arrays for next pass (pointer swap, no element copy) - CALL MOVE_ALLOC(new_cells, swap_tmp) - CALL MOVE_ALLOC(amr_cells, new_cells) ! old amr_cells becomes new_cells - CALL MOVE_ALLOC(swap_tmp, amr_cells) ! filled array becomes amr_cells - n_amr_cells = n_new_cells - - END DO - -c --- 4. output: Q_store/D_store already populated by hash cache. -c Trim to exact size n_pts and deallocate hash infrastructure. - - ALLOCATE(temp_Q(n_pts)) - ALLOCATE(temp_D(n_pts)) - temp_Q(1:n_pts) = Q_store(1:n_pts) - temp_D(1:n_pts) = D_store(1:n_pts) - CALL MOVE_ALLOC(temp_Q, Q_store) - CALL MOVE_ALLOC(temp_D, D_store) - - IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) - IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - DEALLOCATE(new_cells) -c keep amr_cells allocated for potential post-run inspection - - WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts - WRITE(*,'(A,2ES14.6)') ' D_store checksum (Re,Im):', - $ SUM(REAL(D_store(1:n_pts))), - $ SUM(AIMAG(D_store(1:n_pts))) - WRITE(*,'(A,2ES14.6)') ' D_store(1) sample:', - $ REAL(D_store(1)), AIMAG(D_store(1)) - - RETURN - END SUBROUTINE dispersion_AMR_v2 - -c----------------------------------------------------------------------- -c compute_delta_sub: evaluate the dispersion relation at a single -c complex-Q point for dispersion_AMR_v2. Multiplies q_in by ifac -c before passing to the Riccati solver or coupled-surface -c determinant routine. -c----------------------------------------------------------------------- - SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, - $ coupling_flag, delta_out) - - IMPLICIT NONE - -c --- arguments - COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point - INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! use coupled det? - COMPLEX(r8), INTENT(OUT) :: delta_out ! dispersion result - - IF (coupling_flag) THEN - g_tmp = q_in*ifac - delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) - ELSE - g_tmp = q_in*ifac - delta_out = riccati_f() - delta_out = delta_out - delta_eff - END IF - - RETURN - END SUBROUTINE compute_delta_sub - - -c----------------------------------------------------------------------- -c check_cell_crossing_sub: test whether a cell’s 4 corner D-values -c span a zero crossing in Re(D) and/or Im(D). Used by -c dispersion_AMR_v2 to decide which cells to refine. -c----------------------------------------------------------------------- - SUBROUTINE check_cell_crossing_sub(cell, cross_real, cross_imag) - - IMPLICIT NONE - - TYPE(amr_cell_type), INTENT(IN) :: cell - LOGICAL, INTENT(OUT) :: cross_real, cross_imag - - REAL(r8) :: r_vals(4), i_vals(4) ! corner Re/Im values - REAL(r8) :: r_min, r_max, i_min, i_max - INTEGER :: k - - DO k = 1, 4 - r_vals(k) = REAL(cell%D(k), KIND=r8) - i_vals(k) = AIMAG(cell%D(k)) - END DO - - r_min = MINVAL(r_vals) - r_max = MAXVAL(r_vals) - cross_real = (r_min * r_max <= 0.0d0) - - i_min = MINVAL(i_vals) - i_max = MAXVAL(i_vals) - cross_imag = (i_min * i_max <= 0.0d0) - - RETURN - END SUBROUTINE check_cell_crossing_sub - - -c----------------------------------------------------------------------- -c subdivide_cell_sub: split a parent cell into 4 child cells by -c computing 5 midpoints (bottom-mid, top-mid, left-mid, right-mid, -c centre) and evaluating the dispersion relation at each. The 4 -c resulting child cells are appended to new_cells. -c----------------------------------------------------------------------- - SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, - $ max_cells, n_k, sl_in, - $ msing_max, coupling_flag) - - IMPLICIT NONE - -c --- arguments - TYPE(amr_cell_type), INTENT(IN) :: parent - TYPE(amr_cell_type), INTENT(INOUT) :: new_cells(*) - INTEGER, INTENT(INOUT) :: n_new ! running count of new cells - INTEGER, INTENT(IN) :: max_cells ! capacity of new_cells - INTEGER, INTENT(IN) :: n_k - INTEGER, INTENT(IN) :: msing_max - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag -c --- corner coordinates and D-values from parent - COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr - COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr -c --- midpoint coordinates and D-values (cached via hash) - COMPLEX(r8) :: q_bm, q_tm, q_lm, q_rm, q_mm - COMPLEX(r8) :: d_bm, d_tm, d_lm, d_rm, d_mm - INTEGER :: idx_tmp ! hash-cache index - -c --- extract parent corners (BL=1, BR=2, TL=3, TR=4) - q_bl = parent%Q(1) - q_br = parent%Q(2) - q_tl = parent%Q(3) - q_tr = parent%Q(4) - - d_bl = parent%D(1) - d_br = parent%D(2) - d_tl = parent%D(3) - d_tr = parent%D(4) - -c --- compute 5 midpoint coordinates - q_bm = 0.5d0 * (q_bl + q_br) - q_tm = 0.5d0 * (q_tl + q_tr) - q_lm = 0.5d0 * (q_bl + q_tl) - q_rm = 0.5d0 * (q_br + q_tr) - q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) - -c --- evaluate dispersion at new midpoints (hash-cached) - CALL get_or_compute_v2(q_bm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) - d_bm = D_store(idx_tmp) - CALL get_or_compute_v2(q_tm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) - d_tm = D_store(idx_tmp) - CALL get_or_compute_v2(q_lm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) - d_lm = D_store(idx_tmp) - CALL get_or_compute_v2(q_rm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) - d_rm = D_store(idx_tmp) - CALL get_or_compute_v2(q_mm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) - d_mm = D_store(idx_tmp) - -c --- check space for 4 new cells - IF (n_new + 4 > max_cells) THEN - WRITE(*,*) 'ERROR: Would exceed MAX_CELLS in subdivide' - STOP 'subdivide_cell_sub: MAX_CELLS exceeded' - END IF - -c --- child 1: bottom-left quadrant (BL, BM, LM, MM) - n_new = n_new + 1 - new_cells(n_new)%Q(1) = q_bl - new_cells(n_new)%Q(2) = q_bm - new_cells(n_new)%Q(3) = q_lm - new_cells(n_new)%Q(4) = q_mm - new_cells(n_new)%D(1) = d_bl - new_cells(n_new)%D(2) = d_bm - new_cells(n_new)%D(3) = d_lm - new_cells(n_new)%D(4) = d_mm - new_cells(n_new)%needs_refine = .FALSE. - -c --- child 2: bottom-right quadrant (BM, BR, MM, RM) - n_new = n_new + 1 - new_cells(n_new)%Q(1) = q_bm - new_cells(n_new)%Q(2) = q_br - new_cells(n_new)%Q(3) = q_mm - new_cells(n_new)%Q(4) = q_rm - new_cells(n_new)%D(1) = d_bm - new_cells(n_new)%D(2) = d_br - new_cells(n_new)%D(3) = d_mm - new_cells(n_new)%D(4) = d_rm - new_cells(n_new)%needs_refine = .FALSE. - -c --- child 3: top-left quadrant (LM, MM, TL, TM) - n_new = n_new + 1 - new_cells(n_new)%Q(1) = q_lm - new_cells(n_new)%Q(2) = q_mm - new_cells(n_new)%Q(3) = q_tl - new_cells(n_new)%Q(4) = q_tm - new_cells(n_new)%D(1) = d_lm - new_cells(n_new)%D(2) = d_mm - new_cells(n_new)%D(3) = d_tl - new_cells(n_new)%D(4) = d_tm - new_cells(n_new)%needs_refine = .FALSE. - -c --- child 4: top-right quadrant (MM, RM, TM, TR) - n_new = n_new + 1 - new_cells(n_new)%Q(1) = q_mm - new_cells(n_new)%Q(2) = q_rm - new_cells(n_new)%Q(3) = q_tm - new_cells(n_new)%Q(4) = q_tr - new_cells(n_new)%D(1) = d_mm - new_cells(n_new)%D(2) = d_rm - new_cells(n_new)%D(3) = d_tm - new_cells(n_new)%D(4) = d_tr - new_cells(n_new)%needs_refine = .FALSE. - - RETURN - END SUBROUTINE subdivide_cell_sub - - -c----------------------------------------------------------------------- -c flatten_cells_to_points_sub: extract unique (Q, D) points from -c the cell array into the module-level Q_store / D_store arrays. -c Uses a brute-force O(n^2) duplicate check which is acceptable -c for moderate cell counts; could be replaced by a hash set for -c very large scans. -c -c n_total_corners = num_cells*4; guarded against MAX_PTS overflow. -c----------------------------------------------------------------------- - SUBROUTINE flatten_cells_to_points_sub(num_cells) - - IMPLICIT NONE - -c --- arguments - INTEGER, INTENT(IN) :: num_cells ! number of cells to flatten -c --- locals - INTEGER :: c, corner, i, idx - INTEGER :: n_total_corners ! = num_cells * 4 - COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! all corner Q-values - COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! all corner D-values - INTEGER, ALLOCATABLE :: sort_idx(:) ! sort permutation - REAL(r8) :: tol ! duplicate tolerance - - tol = 1.0d-10 - n_total_corners = num_cells * 4 - - IF (n_total_corners > MAX_PTS) THEN - WRITE(*,*) 'ERROR: n_total_corners=', n_total_corners, - $ ' exceeds MAX_PTS=', MAX_PTS - STOP 'flatten_cells_to_points_sub: MAX_PTS exceeded' - END IF - - IF (num_cells <= 0) THEN - WRITE(*,*) 'ERROR: No cells to flatten' - n_pts = 0 - RETURN - END IF - -c --- gather all corners from cells - ALLOCATE(temp_Q(n_total_corners)) - ALLOCATE(temp_D(n_total_corners)) - ALLOCATE(sort_idx(n_total_corners)) - - idx = 0 - DO c = 1, num_cells - DO corner = 1, 4 - idx = idx + 1 - temp_Q(idx) = amr_cells(c)%Q(corner) - temp_D(idx) = amr_cells(c)%D(corner) - sort_idx(idx) = idx - END DO - END DO - -c --- sort by (Re(Q), Im(Q)) via quicksort on the index array - CALL qsort_complex_idx(temp_Q, sort_idx, 1, n_total_corners) - -c --- linear scan to count unique points (sorted order) - n_pts = 1 - DO i = 2, n_total_corners - IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) - $ >= tol) THEN - n_pts = n_pts + 1 - END IF - END DO - -c --- copy unique points to module-level output arrays - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - ALLOCATE(Q_store(n_pts)) - ALLOCATE(D_store(n_pts)) - - idx = 1 - Q_store(1) = temp_Q(sort_idx(1)) - D_store(1) = temp_D(sort_idx(1)) - DO i = 2, n_total_corners - IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) - $ >= tol) THEN - idx = idx + 1 - Q_store(idx) = temp_Q(sort_idx(i)) - D_store(idx) = temp_D(sort_idx(i)) - END IF - END DO - - DEALLOCATE(temp_Q, temp_D, sort_idx) - - RETURN - END SUBROUTINE flatten_cells_to_points_sub - -c----------------------------------------------------------------------- -c qsort_complex_idx: in-place quicksort of an index array by -c the complex keys (Re then Im). Operates on sort_idx so the -c Q/D data arrays remain untouched. -c----------------------------------------------------------------------- - RECURSIVE SUBROUTINE qsort_complex_idx(keys, idx, lo, hi) - - IMPLICIT NONE - - COMPLEX(r8), INTENT(IN) :: keys(:) - INTEGER, INTENT(INOUT) :: idx(:) - INTEGER, INTENT(IN) :: lo, hi - - INTEGER :: i, j, pivot_idx, tmp - REAL(r8) :: p_re, p_im, k_re, k_im - - IF (lo >= hi) RETURN - -c median-of-three pivot selection - pivot_idx = idx((lo + hi) / 2) - p_re = REAL(keys(pivot_idx), KIND=r8) - p_im = AIMAG(keys(pivot_idx)) - - i = lo - j = hi - DO WHILE (i <= j) -c advance i while keys(idx(i)) < pivot - k_re = REAL(keys(idx(i)), KIND=r8) - k_im = AIMAG(keys(idx(i))) - DO WHILE (k_re < p_re .OR. - $ (k_re == p_re .AND. k_im < p_im)) - i = i + 1 - k_re = REAL(keys(idx(i)), KIND=r8) - k_im = AIMAG(keys(idx(i))) - END DO -c retreat j while keys(idx(j)) > pivot - k_re = REAL(keys(idx(j)), KIND=r8) - k_im = AIMAG(keys(idx(j))) - DO WHILE (k_re > p_re .OR. - $ (k_re == p_re .AND. k_im > p_im)) - j = j - 1 - k_re = REAL(keys(idx(j)), KIND=r8) - k_im = AIMAG(keys(idx(j))) - END DO -c swap if pointers haven't crossed - IF (i <= j) THEN - tmp = idx(i) - idx(i) = idx(j) - idx(j) = tmp - i = i + 1 - j = j - 1 - END IF - END DO - -c recurse on partitions - IF (lo < j) CALL qsort_complex_idx(keys, idx, lo, j) - IF (i < hi) CALL qsort_complex_idx(keys, idx, i, hi) - - RETURN - END SUBROUTINE qsort_complex_idx END MODULE gslayer_mod \ No newline at end of file diff --git a/slayer/makefile b/slayer/makefile deleted file mode 100644 index dfacf2b7e..000000000 --- a/slayer/makefile +++ /dev/null @@ -1,79 +0,0 @@ - -include ../install/DEFAULTS.inc - -IFLAGS = -I../equil -I../harvest -I../pentrc -I$(MATHINC) -I$(NETCDFINC) -F90 = $(FC) $(FFLAGS) $(OMPFLAG) $(IFLAGS) -export FFLAGS - -.f.o: - $(F90) -c $*.f - -LIBDIR = ../lib - -LIBS = \ - -llsode \ - -lpentrc \ - -lequil \ - -lharvest - -OBJS = \ - sglobal.o \ - params.o \ - layerinputs.o \ - slayer_netcdf.o \ - delta.o \ - gslayer.o \ - slayer.o - -all: equil harvest lsode pentrc slayer - -harvest: - cd ../; git submodule init; git submodule update - cd ../harvest; make FC=$(FC) CC=$(CC) GACODE_ROOT= -f Makefile libharvest.a - mkdir -p ../lib - cp -f ../harvest/libharvest.a ../lib/ - -equil: - cd ../equil; make - -lsode: - cd ../lsode; make - -pentrc: - cd ../pentrc; make pentrc - -slayer: $(OBJS) - $(F90) -o slayer $(OBJS) -L$(LIBDIR) $(LIBS) -L$(MATHDIR) $(MATHLIBS) -L$(NETCDFDIR) $(NETCDFLIBS) $(NETCDF_EXTRA_LIBS) $(LDFLAGS) - mkdir -p ../lib/ - ar -r ../lib/libslayer.a *.o - mkdir -p ../bin - cp -f slayer ../bin - -# dependencies - -sglobal.o: ../equil/local_mod.mod -params.o : sglobal.o -delta.o : sglobal.o -layerinputs.o : sglobal.o params.o slayer_netcdf.o -gslayer.o : sglobal.o params.o delta.o layerinputs.o slayer_netcdf.o -slayer.o : sglobal.o params.o delta.o ../equil/spline_mod.mod -slayer_netcdf.o : sglobal.o version.inc - -# version.inc generation (from git describe) -ifeq ($(wildcard version.inc),) -version.inc: force - @echo ">>> Creating version file" - @echo " CHARACTER(len=*), PARAMETER :: version ='"`git describe --tags`"'" | tee version.inc -else ifeq ($(shell grep -F "'"`git describe --tags`"'" version.inc),) -version.inc: force - @echo ">>> Updating version file" - @echo " CHARACTER(len=*), PARAMETER :: version ='"`git describe --tags`"'" | tee version.inc -else -version.inc: - @echo ">>> Git version unchanged!" -endif - -force: - -clean: - rm -f *.o *.mod *.out *.bin slayer *.original version.inc diff --git a/slayer/slayer.f b/slayer/slayer.f index c282f7f45..f1dba4a46 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -15,6 +15,7 @@ PROGRAM slayer USE delta_mod, ONLY: riccati,riccati_f,riccati_del_s, $ riccati_out,parflow_flag,PeOhmOnly_flag USE gslayer_mod + USE growthrates_mod USE layerinputs_mod IMPLICIT NONE @@ -464,8 +465,8 @@ PROGRAM slayer c Uses riccati_del_s to get delta_s/d_beta, then scales by c d_beta to obtain the layer thickness delta_s and the estimated c gamma. Inputs may come from equilibrium files (read_eq) or -c namelist.Subroutines: build_inputs, allocate_inputs, -c allocate_outputsare defined in gslayer_mod / layerinputs_mod. +c namelist. Subroutines: build_inputs (layerinputs_mod), +c allocate_inputs, allocate_outputs (growthrates_mod). c----------------------------------------------------------------------- IF (est_gamma_flag) THEN WRITE(*,*)"------------------------------------------" @@ -516,8 +517,8 @@ PROGRAM slayer D_norm = inds ! NAMELIST END IF - CALL allocate_inputs(n_k,sl_in) ! gslayer_mod - CALL allocate_outputs(n_k,sl_out) ! gslayer_mod + CALL allocate_inputs(n_k,sl_in) ! growthrates_mod + CALL allocate_outputs(n_k,sl_out) ! growthrates_mod sl_in%qval_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) @@ -574,7 +575,7 @@ PROGRAM slayer c Matches the inner-layer Delta to the outer-region Delta' to c find the self-consistent complex growth rate. Supports both c single-surface and coupled multi-surface (AMR) modes. -c Subroutines: dispersion_AMR_v2, dispersion_det (gslayer_mod), +c Subroutines: dispersion_AMR_v2, dispersion_det (growthrates_mod), c riccati_f (delta_mod). c----------------------------------------------------------------------- IF (match_gamma_flag) THEN @@ -715,7 +716,7 @@ PROGRAM slayer c----------------------------------------------------------------------- c uncoupled AMR scan (one surface at a time). -c dispersion_AMR_v2 (gslayer_mod) populates Q_store, D_store. +c dispersion_AMR_v2 (growthrates_mod) populates Q_store, D_store. c----------------------------------------------------------------------- IF (AMR_flag .AND. .NOT. coupling_flag) THEN @@ -833,7 +834,7 @@ PROGRAM slayer c----------------------------------------------------------------------- c coupled AMR scan (all surfaces simultaneously). -c dispersion_AMR_v2 (gslayer_mod) with coupling_flag = .TRUE. +c dispersion_AMR_v2 (growthrates_mod) with coupling_flag = .TRUE. c----------------------------------------------------------------------- IF (AMR_flag .AND. coupling_flag) THEN @@ -872,7 +873,7 @@ PROGRAM slayer c----------------------------------------------------------------------- c coupled-surface stability scan on [Re(Q), Im(Q)] grid. -c Uses dispersion_det (gslayer_mod) for the full dispersion +c Uses dispersion_det (growthrates_mod) for the full dispersion c determinant including inter-surface coupling. c----------------------------------------------------------------------- IF (coupled_stabscan_flag) THEN From 2ed9a6c2d520658e124a8c852969f2b186f24646 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Tue, 3 Mar 2026 18:41:37 -0500 Subject: [PATCH 090/305] WIP: clean up dispersion_det gamma specification --- slayer/growthrates.f | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 842bbd221..f3eabf5ce 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -518,8 +518,9 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, Q_store(idx_out) = q_in IF (coupling_flag) THEN - g_tmp = q_in - delta_val = dispersion_det(g_tmp, n_k, sl_in, msing_max) +c dispersion_det sets g_tmp per-surface internally; +c pass q_in directly as g_in argument. + delta_val = dispersion_det(q_in, n_k, sl_in, msing_max) ELSE g_tmp = q_in delta_val = riccati_f() @@ -583,8 +584,9 @@ SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, Q_store(idx_out) = q_in IF (coupling_flag) THEN - g_tmp = q_in * ifac - delta_val = dispersion_det(g_tmp, n_k, sl_in, +c dispersion_det sets g_tmp per-surface internally; +c pass q_in*ifac directly as g_in argument. + delta_val = dispersion_det(q_in * ifac, n_k, sl_in, $ msing_max) ELSE g_tmp = q_in * ifac From 8512d7af0241042cd71d62fad6dfdb4b5a1ac18e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Mon, 9 Mar 2026 15:20:42 -0400 Subject: [PATCH 091/305] increase max pts --- slayer/sglobal.f | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slayer/sglobal.f b/slayer/sglobal.f index e11832049..bd4d5556a 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -107,8 +107,8 @@ MODULE sglobal_mod c----------------------------------------------------------------------- c AMR scanner storage -- hash-based deduplication (v1). c----------------------------------------------------------------------- - INTEGER, PARAMETER :: MAX_PTS = 500000 ! max unique eval points - INTEGER, PARAMETER :: HASH_SZ = 500009 ! hash table size (prime) + INTEGER, PARAMETER :: MAX_PTS = 1000000 ! max unique eval points + INTEGER, PARAMETER :: HASH_SZ = 1000003 ! hash table size (prime) REAL(r8), PARAMETER :: HASH_SCALE = 1.0d5 ! Re/Im quantisation scale INTEGER, ALLOCATABLE :: hash_head(:) ! bucket heads (HASH_SZ) INTEGER, ALLOCATABLE :: hash_next(:) ! chain pointers (MAX_PTS) @@ -116,7 +116,7 @@ MODULE sglobal_mod c----------------------------------------------------------------------- c AMR scanner storage -- cell-based refinement (v2). c----------------------------------------------------------------------- - INTEGER, PARAMETER :: MAX_CELLS = 500000 ! max AMR cells + INTEGER, PARAMETER :: MAX_CELLS = 1000000 ! max AMR cells TYPE :: amr_cell_type COMPLEX(r8) :: Q(4) ! corner Q-values (BL, BR, TL, TR) From ee6d765097bc77fd10571d793b9de52de75309c0 Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 31 Mar 2026 21:14:08 -0400 Subject: [PATCH 092/305] SLAYER - ENHANCEMENT - Make MAX_PTS a soft limit in AMR --- slayer/growthrates.f | 158 +++++++++++++++++++++++++++++++------------ 1 file changed, 115 insertions(+), 43 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index f3eabf5ce..a7d0c5907 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -305,6 +305,7 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, REAL(r8) :: ing_step ! coarse grid spacing REAL(r8) :: ing_coarse, iing_coarse ! Re/Im coords for coarse node LOGICAL :: cross_real, cross_imag ! zero-crossing flags + LOGICAL :: pts_full ! MAX_PTS reached flag COMPLEX(r8) :: q_curr ! current evaluation point INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Q_num,Q_num) node index map @@ -336,8 +337,10 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, iing_coarse = -scan_width + (j - 1) * ing_step q_curr = CMPLX(ing_coarse, iing_coarse) - CALL get_or_compute(q_curr, coarse_indices(i,j), n_k, - $ sl_in, msing_max, coupling_flag) + CALL get_or_compute(q_curr, + $ coarse_indices(i,j), n_k, sl_in, + $ msing_max, coupling_flag, pts_full) + IF (pts_full) GOTO 900 END DO END DO @@ -389,31 +392,46 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, IF (cross_real .OR. cross_imag) THEN c refine: compute 5 midpoints, create 4 sub-cells - + ! Top-Mid - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_TR)) - CALL get_or_compute(q_curr, idx_TM,n_k, - $ sl_in,msing_max,coupling_flag) + q_curr = 0.5d0*(Q_store(idx_TL) + $ +Q_store(idx_TR)) + CALL get_or_compute(q_curr,idx_TM, + $ n_k,sl_in,msing_max, + $ coupling_flag,pts_full) + IF (pts_full) GOTO 900 ! Bot-Mid - q_curr = 0.5d0*(Q_store(idx_BL)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_BM,n_k, - $ sl_in,msing_max,coupling_flag) + q_curr = 0.5d0*(Q_store(idx_BL) + $ +Q_store(idx_BR)) + CALL get_or_compute(q_curr,idx_BM, + $ n_k,sl_in,msing_max, + $ coupling_flag,pts_full) + IF (pts_full) GOTO 900 ! Left-Mid - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BL)) - CALL get_or_compute(q_curr, idx_LM,n_k, - $ sl_in,msing_max,coupling_flag) + q_curr = 0.5d0*(Q_store(idx_TL) + $ +Q_store(idx_BL)) + CALL get_or_compute(q_curr,idx_LM, + $ n_k,sl_in,msing_max, + $ coupling_flag,pts_full) + IF (pts_full) GOTO 900 ! Right-Mid - q_curr = 0.5d0*(Q_store(idx_TR)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_RM,n_k, - $ sl_in,msing_max,coupling_flag) + q_curr = 0.5d0*(Q_store(idx_TR) + $ +Q_store(idx_BR)) + CALL get_or_compute(q_curr,idx_RM, + $ n_k,sl_in,msing_max, + $ coupling_flag,pts_full) + IF (pts_full) GOTO 900 ! Center - q_curr = 0.5d0*(Q_store(idx_TL)+Q_store(idx_BR)) - CALL get_or_compute(q_curr, idx_MM,n_k, - $ sl_in,msing_max,coupling_flag) + q_curr = 0.5d0*(Q_store(idx_TL) + $ +Q_store(idx_BR)) + CALL get_or_compute(q_curr,idx_MM, + $ n_k,sl_in,msing_max, + $ coupling_flag,pts_full) + IF (pts_full) GOTO 900 ! Create 4 sub-cells (TL, TR, BL, BR quadrants) n_new_cells = n_new_cells + 1 @@ -455,8 +473,17 @@ SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, cells(:, 1:n_cells) = new_cells(:, 1:n_cells) END DO + GOTO 910 + 900 CONTINUE + WRITE(*,'(A,I8,A)') + $ ' WARNING: MAX_PTS (', MAX_PTS, + $ ') reached during AMR.' + WRITE(*,'(A)') + $ ' Saving existing results.' + 910 CONTINUE DEALLOCATE(cells, new_cells) - WRITE(*,*) "AMR Scan Complete. Total Points:", n_pts + WRITE(*,*) "AMR Scan Complete. Total Points:", + $ n_pts RETURN END SUBROUTINE dispersion_AMR @@ -469,8 +496,9 @@ END SUBROUTINE dispersion_AMR c Uses 64-bit arithmetic internally to avoid integer overflow c in the hash function. c----------------------------------------------------------------------- - SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, - $ coupling_flag) + SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in, + $ msing_max,coupling_flag, + $ full) c --- arguments COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point @@ -479,12 +507,14 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, INTEGER, INTENT(IN) :: msing_max ! max surfaces to include TYPE(slayer_inputs_type), INTENT(IN) :: sl_in LOGICAL, INTENT(IN) :: coupling_flag ! use coupled dispersion_det? + LOGICAL, INTENT(OUT) :: full ! .TRUE. if MAX_PTS reached c --- locals INTEGER :: h ! hash bucket index INTEGER :: curr ! linked-list traversal index COMPLEX(r8) :: delta_val ! computed dispersion result INTEGER(8) :: ix8, iy8, h8 ! 64-bit intermediates for hash + full = .FALSE. c --- 1. compute hash from quantised Re/Im coordinates (64-bit safe) ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) @@ -510,8 +540,10 @@ SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in,msing_max, c --- 3. point not found: evaluate dispersion relation and store n_pts = n_pts + 1 IF (n_pts > MAX_PTS) THEN - WRITE(*,*) "ERROR: AMR exceeded MAX_PTS" - STOP "get_or_compute: MAX_PTS exceeded" + n_pts = n_pts - 1 + full = .TRUE. + idx_out = -1 + RETURN END IF idx_out = n_pts @@ -539,8 +571,9 @@ END SUBROUTINE get_or_compute c Identical to get_or_compute but applies the ifac (imaginary-unit) c Wick rotation that compute_delta_sub uses: g_tmp = q_in * ifac. c----------------------------------------------------------------------- - SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, - $ msing_max, coupling_flag) + SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, + $ sl_in, msing_max, + $ coupling_flag, full) IMPLICIT NONE @@ -550,12 +583,14 @@ SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, INTEGER, INTENT(IN) :: n_k, msing_max TYPE(slayer_inputs_type), INTENT(IN) :: sl_in LOGICAL, INTENT(IN) :: coupling_flag + LOGICAL, INTENT(OUT) :: full c --- locals INTEGER :: h, curr COMPLEX(r8) :: delta_val INTEGER(8) :: ix8, iy8, h8 + full = .FALSE. c --- 1. compute hash bucket ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) @@ -576,8 +611,10 @@ SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, sl_in, c --- 3. not found: evaluate with ifac rotation and store n_pts = n_pts + 1 IF (n_pts > MAX_PTS) THEN - WRITE(*,*) 'ERROR: AMR v2 cache exceeded MAX_PTS' - STOP 'get_or_compute_v2: MAX_PTS exceeded' + n_pts = n_pts - 1 + full = .TRUE. + idx_out = -1 + RETURN END IF idx_out = n_pts @@ -637,6 +674,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, REAL(r8) :: step ! grid spacing REAL(r8) :: x, y ! real / imag grid coords LOGICAL :: cross_real, cross_imag ! zero-crossing flags + LOGICAL :: pts_full ! MAX_PTS reached flag INTEGER :: n_new_cells ! count during refinement INTEGER :: cells_to_refine ! cells flagged per pass INTEGER :: cells_kept ! cells kept per pass @@ -691,7 +729,8 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, CALL get_or_compute_v2( $ amr_cells(n_amr_cells)%Q(corner), $ idx_tmp, n_k, sl_in, msing_max, - $ coupling_flag) + $ coupling_flag, pts_full) + IF (pts_full) GOTO 800 amr_cells(n_amr_cells)%D(corner) = $ D_store(idx_tmp) END DO @@ -722,9 +761,13 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, DO c = 1, n_amr_cells IF (amr_cells(c)%needs_refine) THEN - CALL subdivide_cell_sub(amr_cells(c), - $ new_cells, n_new_cells, MAX_CELLS, - $ n_k, sl_in, msing_max, coupling_flag) + CALL subdivide_cell_sub( + $ amr_cells(c), + $ new_cells, n_new_cells, + $ MAX_CELLS, n_k, sl_in, + $ msing_max, coupling_flag, + $ pts_full) + IF (pts_full) GOTO 800 ELSE n_new_cells = n_new_cells + 1 IF (n_new_cells > MAX_CELLS) THEN @@ -743,7 +786,16 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, n_amr_cells = n_new_cells END DO + GOTO 810 + + 800 CONTINUE + WRITE(*,'(A,I8,A)') + $ ' WARNING: MAX_PTS (', MAX_PTS, + $ ') reached during AMR v2.' + WRITE(*,'(A)') + $ ' Saving existing results.' + 810 CONTINUE c --- 4. output: Q_store/D_store already populated by hash cache. c Trim to exact size n_pts and deallocate hash infrastructure. @@ -756,7 +808,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - DEALLOCATE(new_cells) + IF (ALLOCATED(new_cells)) DEALLOCATE(new_cells) c keep amr_cells allocated for potential post-run inspection WRITE(*,*) 'AMR v2 Complete. Unique output points:', n_pts @@ -840,9 +892,10 @@ END SUBROUTINE check_cell_crossing_sub c centre) and evaluating the dispersion relation at each. The 4 c resulting child cells are appended to new_cells. c----------------------------------------------------------------------- - SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, - $ max_cells, n_k, sl_in, - $ msing_max, coupling_flag) + SUBROUTINE subdivide_cell_sub(parent, + $ new_cells, n_new, max_cells, n_k, + $ sl_in, msing_max, coupling_flag, + $ pts_full) IMPLICIT NONE @@ -855,6 +908,7 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, INTEGER, INTENT(IN) :: msing_max TYPE(slayer_inputs_type), INTENT(IN) :: sl_in LOGICAL, INTENT(IN) :: coupling_flag + LOGICAL, INTENT(OUT) :: pts_full ! MAX_PTS flag c --- corner coordinates and D-values from parent COMPLEX(r8) :: q_bl, q_br, q_tl, q_tr COMPLEX(r8) :: d_bl, d_br, d_tl, d_tr @@ -882,20 +936,31 @@ SUBROUTINE subdivide_cell_sub(parent, new_cells, n_new, q_mm = 0.25d0 * (q_bl + q_br + q_tl + q_tr) c --- evaluate dispersion at new midpoints (hash-cached) + pts_full = .FALSE. CALL get_or_compute_v2(q_bm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) + $ n_k, sl_in, msing_max, + $ coupling_flag, pts_full) + IF (pts_full) RETURN d_bm = D_store(idx_tmp) CALL get_or_compute_v2(q_tm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) + $ n_k, sl_in, msing_max, + $ coupling_flag, pts_full) + IF (pts_full) RETURN d_tm = D_store(idx_tmp) CALL get_or_compute_v2(q_lm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) + $ n_k, sl_in, msing_max, + $ coupling_flag, pts_full) + IF (pts_full) RETURN d_lm = D_store(idx_tmp) CALL get_or_compute_v2(q_rm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) + $ n_k, sl_in, msing_max, + $ coupling_flag, pts_full) + IF (pts_full) RETURN d_rm = D_store(idx_tmp) CALL get_or_compute_v2(q_mm, idx_tmp, - $ n_k, sl_in, msing_max, coupling_flag) + $ n_k, sl_in, msing_max, + $ coupling_flag, pts_full) + IF (pts_full) RETURN d_mm = D_store(idx_tmp) c --- check space for 4 new cells @@ -983,9 +1048,13 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) n_total_corners = num_cells * 4 IF (n_total_corners > MAX_PTS) THEN - WRITE(*,*) 'ERROR: n_total_corners=', n_total_corners, - $ ' exceeds MAX_PTS=', MAX_PTS - STOP 'flatten_cells_to_points_sub: MAX_PTS exceeded' + WRITE(*,'(A,I8,A,I8)') + $ ' WARNING: n_total_corners=', + $ n_total_corners, + $ ' exceeds MAX_PTS=', MAX_PTS + WRITE(*,'(A)') + $ ' Clamping to MAX_PTS.' + n_total_corners = MAX_PTS END IF IF (num_cells <= 0) THEN @@ -1002,12 +1071,15 @@ SUBROUTINE flatten_cells_to_points_sub(num_cells) idx = 0 DO c = 1, num_cells DO corner = 1, 4 + IF (idx >= n_total_corners) GOTO 700 idx = idx + 1 temp_Q(idx) = amr_cells(c)%Q(corner) temp_D(idx) = amr_cells(c)%D(corner) sort_idx(idx) = idx END DO END DO + 700 CONTINUE + n_total_corners = idx c --- sort by (Re(Q), Im(Q)) via quicksort on the index array CALL qsort_complex_idx(temp_Q, sort_idx, 1, n_total_corners) From f13fcc30cf6453420128939b0065d66d12f63029 Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 1 Apr 2026 01:11:13 -0400 Subject: [PATCH 093/305] update growthrates.f header --- slayer/growthrates.f | 525 ++----------------------------------------- 1 file changed, 18 insertions(+), 507 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index a7d0c5907..c8f8b3993 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -9,21 +9,17 @@ MODULE growthrates_mod c SLAYER driver (slayer.f). c c Subprograms: -c 1. output_gamma - write results to netCDF via -c slayer_netcdf_mod -c 2. allocate_inputs - allocate slayer_inputs_type arrays -c 3. allocate_outputs - allocate slayer_outputs_type arrays +c 1. output_gamma - write results to netCDF +c 2. allocate_inputs - allocate slayer_inputs_type +c 3. allocate_outputs - allocate slayer_outputs_type c 4. shrink_array - trim over-allocated scan arrays c 5. grow_array - expand scan arrays dynamically c 6. calc_determinant - 2x2 / 3x3 complex determinant c 7. dispersion_det - coupled dispersion determinant -c 8. dispersion_AMR - AMR scan v1 (hash-based dedup) -c 9. dispersion_AMR_v2 - AMR scan v2 (cell-based storage) -c -c Helper subroutines (v1): get_or_compute -c Helper subroutines (v2): get_or_compute_v2, -c compute_delta_sub, check_cell_crossing_sub, -c subdivide_cell_sub, flatten_cells_to_points_sub +c 8. get_or_compute_v2 - hash-cached dispersion eval +c 9. dispersion_AMR_v2 - AMR scan (cell-based storage) +c 10. check_cell_crossing_sub - zero-crossing test +c 11. subdivide_cell_sub - cell refinement c----------------------------------------------------------------------- USE omp_lib @@ -172,7 +168,7 @@ SUBROUTINE calc_determinant(matk, nk, detk, status) CASE default ! Unsupported matrix size - detk = (0.0, 0.0) + detk = CMPLX(0.0_r8, 0.0_r8, KIND=r8) status = -1 END SELECT @@ -223,7 +219,8 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) g_tmp = g_in tmp_delta=riccati_f() c de-normalise delta by lu^(1/3) - det_val=tmp_delta*(sl_in%lu_arr(1)**(1.0/3.0)) + det_val=tmp_delta* + $ (sl_in%lu_arr(1)**(1.0_r8/3.0_r8)) c return Deltaprime - delta(Q) dispersion_det = sl_in%Re_dp_arr(1) - det_val @@ -231,7 +228,7 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c --- coupled-surface branch (2 or 3 surfaces) ELSEIF ((msing_max == 2) .OR. (msing_max == 3)) THEN ALLOCATE(delta_Q(msing_max,msing_max)) - delta_Q=(0.0,0.0) + delta_Q=CMPLX(0.0_r8, 0.0_r8, KIND=r8) DO k=1,msing_max c set module-level variables for this surface Q_e = sl_in%Q_e_arr(k) @@ -245,9 +242,11 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) iota_e = Q_e / (Q_e - Q_i) c evaluate riccati_f at rescaled growth rate, de-normalise - g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk ! sets module-level g_tmp to SCALED value +c rescale g_in to this surface's normalisation + g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk delta_Q(k,k)=riccati_f() - delta_Q(k,k)=delta_Q(k,k)*sl_in%lu_arr(k)**(1.0/3.0) + delta_Q(k,k)=delta_Q(k,k)* + $ sl_in%lu_arr(k)**(1.0_r8/3.0_r8) END DO c compute det(dp_matrix - delta_Q) @@ -259,6 +258,7 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) $ msing_max STOP 'dispersion_det: calc_determinant failed' END IF + DEALLOCATE(delta_Q, result_matrix) dispersion_det = det_val ELSE WRITE(*,*) "Error: no support for msing > 3" @@ -266,310 +266,9 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) END IF END FUNCTION dispersion_det -c----------------------------------------------------------------------- -c dispersion_AMR: hash-based adaptive mesh refinement scanner.\nc -c Scans a 2D complex-Q grid for zeros of the dispersion relation -c D(Q) using adaptive refinement. A coarse grid is evaluated -c first (two-pass: nodes then cells), then cells that span a zero -c crossing in Re(D) or Im(D) are subdivided. -c -c Point deduplication uses a spatial hash table (HASH_SZ buckets, -c chained) so that midpoints shared between neighbouring cells -c are evaluated only once. -c----------------------------------------------------------------------- - SUBROUTINE dispersion_AMR(n_k,sl_in,msing_max, - $ scan_width,Q_num,AMR_passes, - $ coupling_flag) -c DEPRECATED: use dispersion_AMR_v2 instead. -c This v1 hash-based scanner is retained for -c backwards compatibility only. - -c --- arguments - INTEGER, INTENT(IN) :: n_k ! number of rational surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling - INTEGER, INTENT(IN) :: Q_num ! grid points per axis - INTEGER, INTENT(IN) :: AMR_passes ! refinement passes - REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? -c --- cell storage - INTEGER, ALLOCATABLE :: cells(:,:) ! (4, max) corner indices per cell - INTEGER, ALLOCATABLE :: new_cells(:,:) ! scratch for next level -c --- loop / index variables - INTEGER :: n_cells, n_new_cells, i, j - INTEGER :: c_idx, pass - INTEGER :: idx_TL, idx_TR, idx_BL, idx_BR ! corner indices - INTEGER :: idx_TM, idx_BM, idx_LM, idx_RM, idx_MM ! midpoint indices -c --- scan workspace - REAL(r8) :: r_min, r_max, i_min, i_max ! min/max Re/Im across corners - REAL(r8) :: ing_step ! coarse grid spacing - REAL(r8) :: ing_coarse, iing_coarse ! Re/Im coords for coarse node - LOGICAL :: cross_real, cross_imag ! zero-crossing flags - LOGICAL :: pts_full ! MAX_PTS reached flag - COMPLEX(r8) :: q_curr ! current evaluation point - INTEGER, ALLOCATABLE :: coarse_indices(:,:) ! (Q_num,Q_num) node index map - -c --- 1. initialise hash table and point/cell storage - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - IF (ALLOCATED(hash_head)) DEALLOCATE(hash_head) - IF (ALLOCATED(hash_next)) DEALLOCATE(hash_next) - - ALLOCATE(Q_store(MAX_PTS), D_store(MAX_PTS)) - ALLOCATE(hash_head(HASH_SZ), hash_next(MAX_PTS)) - ALLOCATE(cells(4, 200000), new_cells(4, 200000)) - - hash_head = 0 - hash_next = 0 - n_pts = 0 - n_cells = 0 - -c --- 2. build initial coarse grid (two-pass method) -c Pass 1 computes and hashes every node; Pass 2 stitches cells -c from the stored indices -- no floating-point comparison needed. - ALLOCATE(coarse_indices(Q_num, Q_num)) - ing_step = (2.0*scan_width) / (Q_num - 1) - -c Pass 1: compute all grid nodes and store their hash indices - DO i = 1, Q_num - DO j = 1, Q_num - ing_coarse = -scan_width + (i - 1) * ing_step - iing_coarse = -scan_width + (j - 1) * ing_step - q_curr = CMPLX(ing_coarse, iing_coarse) - - CALL get_or_compute(q_curr, - $ coarse_indices(i,j), n_k, sl_in, - $ msing_max, coupling_flag, pts_full) - IF (pts_full) GOTO 900 - END DO - END DO - -c Pass 2: stitch cells from the stored integer indices - DO i = 1, Q_num - 1 - DO j = 1, Q_num - 1 - n_cells = n_cells + 1 - cells(1, n_cells) = coarse_indices(i, j) ! TL - cells(2, n_cells) = coarse_indices(i+1, j) ! TR - cells(3, n_cells) = coarse_indices(i, j+1) ! BL - cells(4, n_cells) = coarse_indices(i+1, j+1) ! BR - END DO - END DO - DEALLOCATE(coarse_indices) - -c --- 3. refinement passes: subdivide cells with zero crossings - DO pass = 1, AMR_PASSES - WRITE(*,'(A,I2,A,I6,A)') ' > Pass ', pass, - $ ': Checking ', n_cells, ' cells...' - n_new_cells = 0 - - DO c_idx = 1, n_cells - idx_TL = cells(1, c_idx) - idx_TR = cells(2, c_idx) - idx_BL = cells(3, c_idx) - idx_BR = cells(4, c_idx) - -c check for sign change in Re(D) across cell corners - r_min = MIN(REAL(D_store(idx_TL)), - $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), - $ REAL(D_store(idx_BR))) - r_max = MAX(REAL(D_store(idx_TL)), - $ REAL(D_store(idx_TR)), - $ REAL(D_store(idx_BL)), - $ REAL(D_store(idx_BR))) - cross_real = (r_min * r_max <= 0.0d0) - -c check for sign change in Im(D) across cell corners - i_min = MIN(AIMAG(D_store(idx_TL)), - $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), - $ AIMAG(D_store(idx_BR))) - i_max = MAX(AIMAG(D_store(idx_TL)), - $ AIMAG(D_store(idx_TR)), - $ AIMAG(D_store(idx_BL)), - $ AIMAG(D_store(idx_BR))) - cross_imag = (i_min * i_max <= 0.0d0) - - IF (cross_real .OR. cross_imag) THEN -c refine: compute 5 midpoints, create 4 sub-cells - - ! Top-Mid - q_curr = 0.5d0*(Q_store(idx_TL) - $ +Q_store(idx_TR)) - CALL get_or_compute(q_curr,idx_TM, - $ n_k,sl_in,msing_max, - $ coupling_flag,pts_full) - IF (pts_full) GOTO 900 - - ! Bot-Mid - q_curr = 0.5d0*(Q_store(idx_BL) - $ +Q_store(idx_BR)) - CALL get_or_compute(q_curr,idx_BM, - $ n_k,sl_in,msing_max, - $ coupling_flag,pts_full) - IF (pts_full) GOTO 900 - - ! Left-Mid - q_curr = 0.5d0*(Q_store(idx_TL) - $ +Q_store(idx_BL)) - CALL get_or_compute(q_curr,idx_LM, - $ n_k,sl_in,msing_max, - $ coupling_flag,pts_full) - IF (pts_full) GOTO 900 - - ! Right-Mid - q_curr = 0.5d0*(Q_store(idx_TR) - $ +Q_store(idx_BR)) - CALL get_or_compute(q_curr,idx_RM, - $ n_k,sl_in,msing_max, - $ coupling_flag,pts_full) - IF (pts_full) GOTO 900 - - ! Center - q_curr = 0.5d0*(Q_store(idx_TL) - $ +Q_store(idx_BR)) - CALL get_or_compute(q_curr,idx_MM, - $ n_k,sl_in,msing_max, - $ coupling_flag,pts_full) - IF (pts_full) GOTO 900 - - ! Create 4 sub-cells (TL, TR, BL, BR quadrants) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_TL - new_cells(2, n_new_cells) = idx_TM - new_cells(3, n_new_cells) = idx_LM - new_cells(4, n_new_cells) = idx_MM - - ! Sub 2 (Top-Right) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_TM - new_cells(2, n_new_cells) = idx_TR - new_cells(3, n_new_cells) = idx_MM - new_cells(4, n_new_cells) = idx_RM - - ! Sub 3 (Bot-Left) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_LM - new_cells(2, n_new_cells) = idx_MM - new_cells(3, n_new_cells) = idx_BL - new_cells(4, n_new_cells) = idx_BM - - ! Sub 4 (Bot-Right) - n_new_cells = n_new_cells + 1 - new_cells(1, n_new_cells) = idx_MM - new_cells(2, n_new_cells) = idx_RM - new_cells(3, n_new_cells) = idx_BM - new_cells(4, n_new_cells) = idx_BR - - ELSE - ! No refinement needed, keep original cell - n_new_cells = n_new_cells + 1 - new_cells(:, n_new_cells) = cells(:, c_idx) - END IF - END DO - -c --- swap arrays for next refinement pass - n_cells = n_new_cells - cells(:, 1:n_cells) = new_cells(:, 1:n_cells) - - END DO - GOTO 910 - 900 CONTINUE - WRITE(*,'(A,I8,A)') - $ ' WARNING: MAX_PTS (', MAX_PTS, - $ ') reached during AMR.' - WRITE(*,'(A)') - $ ' Saving existing results.' - 910 CONTINUE - DEALLOCATE(cells, new_cells) - WRITE(*,*) "AMR Scan Complete. Total Points:", - $ n_pts - RETURN - END SUBROUTINE dispersion_AMR - -c----------------------------------------------------------------------- -c get_or_compute: hash-based point lookup for dispersion_AMR v1. -c If q_in is already in the hash table, return its index. -c Otherwise, evaluate the dispersion relation at q_in, store -c the result, and insert into the hash table. -c -c Uses 64-bit arithmetic internally to avoid integer overflow -c in the hash function. -c----------------------------------------------------------------------- - SUBROUTINE get_or_compute(q_in,idx_out,n_k,sl_in, - $ msing_max,coupling_flag, - $ full) - -c --- arguments - COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point - INTEGER, INTENT(OUT) :: idx_out ! returned point index - INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces to include - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! use coupled dispersion_det? - LOGICAL, INTENT(OUT) :: full ! .TRUE. if MAX_PTS reached -c --- locals - INTEGER :: h ! hash bucket index - INTEGER :: curr ! linked-list traversal index - COMPLEX(r8) :: delta_val ! computed dispersion result - INTEGER(8) :: ix8, iy8, h8 ! 64-bit intermediates for hash - - full = .FALSE. -c --- 1. compute hash from quantised Re/Im coordinates (64-bit safe) - ix8 = NINT(REAL(q_in) * HASH_SCALE, KIND=8) - iy8 = NINT(AIMAG(q_in) * HASH_SCALE, KIND=8) - h8 = MOD(ABS(ix8 * 73856093_8 + iy8 * 19349663_8), - $ INT(HASH_SZ, 8)) + 1_8 - h = INT(h8) - - IF (h < 1 .OR. h > HASH_SZ) THEN - WRITE(*,*) "HASH ERROR: h=", h, " q_in=", q_in - STOP "get_or_compute: hash out of bounds" - END IF - -c --- 2. search hash chain for existing point - curr = hash_head(h) - DO WHILE (curr /= 0) - IF (ABS(Q_store(curr) - q_in) < 1.0d-8) THEN - idx_out = curr - RETURN - END IF - curr = hash_next(curr) - END DO - -c --- 3. point not found: evaluate dispersion relation and store - n_pts = n_pts + 1 - IF (n_pts > MAX_PTS) THEN - n_pts = n_pts - 1 - full = .TRUE. - idx_out = -1 - RETURN - END IF - - idx_out = n_pts - Q_store(idx_out) = q_in - - IF (coupling_flag) THEN -c dispersion_det sets g_tmp per-surface internally; -c pass q_in directly as g_in argument. - delta_val = dispersion_det(q_in, n_k, sl_in, msing_max) - ELSE - g_tmp = q_in - delta_val = riccati_f() - delta_val = delta_val - delta_eff - END IF - D_store(idx_out) = delta_val - -c --- 4. insert into hash chain (prepend) - hash_next(idx_out) = hash_head(h) - hash_head(h) = idx_out - - END SUBROUTINE get_or_compute - c----------------------------------------------------------------------- c get_or_compute_v2: hash-cached dispersion evaluation for AMR v2. -c Identical to get_or_compute but applies the ifac (imaginary-unit) -c Wick rotation that compute_delta_sub uses: g_tmp = q_in * ifac. +c Applies ifac Wick rotation: g_tmp = q_in * ifac. c----------------------------------------------------------------------- SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, $ sl_in, msing_max, @@ -821,38 +520,6 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, RETURN END SUBROUTINE dispersion_AMR_v2 -c----------------------------------------------------------------------- -c compute_delta_sub: evaluate the dispersion relation at a single -c complex-Q point for dispersion_AMR_v2. Multiplies q_in by ifac -c before passing to the Riccati solver or coupled-surface -c determinant routine. -c----------------------------------------------------------------------- - SUBROUTINE compute_delta_sub(q_in, n_k, sl_in, msing_max, - $ coupling_flag, delta_out) - - IMPLICIT NONE - -c --- arguments - COMPLEX(r8), INTENT(IN) :: q_in ! complex-Q evaluation point - INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces for coupling - TYPE(slayer_inputs_type), INTENT(IN) :: sl_in - LOGICAL, INTENT(IN) :: coupling_flag ! use coupled det? - COMPLEX(r8), INTENT(OUT) :: delta_out ! dispersion result - - IF (coupling_flag) THEN - g_tmp = q_in*ifac - delta_out = dispersion_det(g_tmp, n_k, sl_in, msing_max) - ELSE - g_tmp = q_in*ifac - delta_out = riccati_f() - delta_out = delta_out - delta_eff - END IF - - RETURN - END SUBROUTINE compute_delta_sub - - c----------------------------------------------------------------------- c check_cell_crossing_sub: test whether a cell’s 4 corner D-values c span a zero crossing in Re(D) and/or Im(D). Used by @@ -1020,160 +687,4 @@ SUBROUTINE subdivide_cell_sub(parent, RETURN END SUBROUTINE subdivide_cell_sub - -c----------------------------------------------------------------------- -c flatten_cells_to_points_sub: extract unique (Q, D) points from -c the cell array into the module-level Q_store / D_store arrays. -c Uses a brute-force O(n^2) duplicate check which is acceptable -c for moderate cell counts; could be replaced by a hash set for -c very large scans. -c -c n_total_corners = num_cells*4; guarded against MAX_PTS overflow. -c----------------------------------------------------------------------- - SUBROUTINE flatten_cells_to_points_sub(num_cells) - - IMPLICIT NONE - -c --- arguments - INTEGER, INTENT(IN) :: num_cells ! number of cells to flatten -c --- locals - INTEGER :: c, corner, i, idx - INTEGER :: n_total_corners ! = num_cells * 4 - COMPLEX(r8), ALLOCATABLE :: temp_Q(:) ! all corner Q-values - COMPLEX(r8), ALLOCATABLE :: temp_D(:) ! all corner D-values - INTEGER, ALLOCATABLE :: sort_idx(:) ! sort permutation - REAL(r8) :: tol ! duplicate tolerance - - tol = 1.0d-10 - n_total_corners = num_cells * 4 - - IF (n_total_corners > MAX_PTS) THEN - WRITE(*,'(A,I8,A,I8)') - $ ' WARNING: n_total_corners=', - $ n_total_corners, - $ ' exceeds MAX_PTS=', MAX_PTS - WRITE(*,'(A)') - $ ' Clamping to MAX_PTS.' - n_total_corners = MAX_PTS - END IF - - IF (num_cells <= 0) THEN - WRITE(*,*) 'ERROR: No cells to flatten' - n_pts = 0 - RETURN - END IF - -c --- gather all corners from cells - ALLOCATE(temp_Q(n_total_corners)) - ALLOCATE(temp_D(n_total_corners)) - ALLOCATE(sort_idx(n_total_corners)) - - idx = 0 - DO c = 1, num_cells - DO corner = 1, 4 - IF (idx >= n_total_corners) GOTO 700 - idx = idx + 1 - temp_Q(idx) = amr_cells(c)%Q(corner) - temp_D(idx) = amr_cells(c)%D(corner) - sort_idx(idx) = idx - END DO - END DO - 700 CONTINUE - n_total_corners = idx - -c --- sort by (Re(Q), Im(Q)) via quicksort on the index array - CALL qsort_complex_idx(temp_Q, sort_idx, 1, n_total_corners) - -c --- linear scan to count unique points (sorted order) - n_pts = 1 - DO i = 2, n_total_corners - IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) - $ >= tol) THEN - n_pts = n_pts + 1 - END IF - END DO - -c --- copy unique points to module-level output arrays - IF (ALLOCATED(Q_store)) DEALLOCATE(Q_store) - IF (ALLOCATED(D_store)) DEALLOCATE(D_store) - ALLOCATE(Q_store(n_pts)) - ALLOCATE(D_store(n_pts)) - - idx = 1 - Q_store(1) = temp_Q(sort_idx(1)) - D_store(1) = temp_D(sort_idx(1)) - DO i = 2, n_total_corners - IF (ABS(temp_Q(sort_idx(i)) - temp_Q(sort_idx(i-1))) - $ >= tol) THEN - idx = idx + 1 - Q_store(idx) = temp_Q(sort_idx(i)) - D_store(idx) = temp_D(sort_idx(i)) - END IF - END DO - - DEALLOCATE(temp_Q, temp_D, sort_idx) - - RETURN - END SUBROUTINE flatten_cells_to_points_sub - -c----------------------------------------------------------------------- -c qsort_complex_idx: in-place quicksort of an index array by -c the complex keys (Re then Im). Operates on sort_idx so the -c Q/D data arrays remain untouched. -c----------------------------------------------------------------------- - RECURSIVE SUBROUTINE qsort_complex_idx(keys, idx, lo, hi) - - IMPLICIT NONE - - COMPLEX(r8), INTENT(IN) :: keys(:) - INTEGER, INTENT(INOUT) :: idx(:) - INTEGER, INTENT(IN) :: lo, hi - - INTEGER :: i, j, pivot_idx, tmp - REAL(r8) :: p_re, p_im, k_re, k_im - - IF (lo >= hi) RETURN - -c median-of-three pivot selection - pivot_idx = idx((lo + hi) / 2) - p_re = REAL(keys(pivot_idx), KIND=r8) - p_im = AIMAG(keys(pivot_idx)) - - i = lo - j = hi - DO WHILE (i <= j) -c advance i while keys(idx(i)) < pivot - k_re = REAL(keys(idx(i)), KIND=r8) - k_im = AIMAG(keys(idx(i))) - DO WHILE (k_re < p_re .OR. - $ (k_re == p_re .AND. k_im < p_im)) - i = i + 1 - k_re = REAL(keys(idx(i)), KIND=r8) - k_im = AIMAG(keys(idx(i))) - END DO -c retreat j while keys(idx(j)) > pivot - k_re = REAL(keys(idx(j)), KIND=r8) - k_im = AIMAG(keys(idx(j))) - DO WHILE (k_re > p_re .OR. - $ (k_re == p_re .AND. k_im > p_im)) - j = j - 1 - k_re = REAL(keys(idx(j)), KIND=r8) - k_im = AIMAG(keys(idx(j))) - END DO -c swap if pointers haven't crossed - IF (i <= j) THEN - tmp = idx(i) - idx(i) = idx(j) - idx(j) = tmp - i = i + 1 - j = j - 1 - END IF - END DO - -c recurse on partitions - IF (lo < j) CALL qsort_complex_idx(keys, idx, lo, j) - IF (i < hi) CALL qsort_complex_idx(keys, idx, i, hi) - - RETURN - END SUBROUTINE qsort_complex_idx - END MODULE growthrates_mod \ No newline at end of file + END MODULE growthrates_mod From ad53d67c28dc81f80d3fe17881b076f792e2c02e Mon Sep 17 00:00:00 2001 From: d-burg Date: Thu, 16 Apr 2026 17:54:47 -0400 Subject: [PATCH 094/305] WIP - bug fixes to Fitz tau normalization timescale --- slayer/layerinputs.f | 52 ++++++++++++++++++++++++++++++++++---------- slayer/params.f | 17 ++++++++++----- slayer/slayer.f | 47 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 5244a6bce..f3757acf5 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -352,6 +352,11 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8) :: psave ! cached psi for issurfint REAL(r8), DIMENSION(:), ALLOCATABLE :: jacs, delpsi, rsurf, asurf REAL(r8) :: a_surf ! flux-surface-averaged minor radius +c --- Fitzpatrick (r-based) shear workspace + REAL(r8) :: a_surf_p, a_surf_m ! a_surf at psiN +/- h + REAL(r8) :: da_dpsiN ! da_surf/dpsiN (Jacobian) + REAL(r8) :: dpsi_h ! finite-diff step for Jacobian + REAL(r8) :: s_fitz ! Fitzpatrick shear r*dq/dr/q c----------------------------------------------------------------------- c read STRIDE NetCDF: Deltaprime matrix, geometry, equilibrium scalars. c----------------------------------------------------------------------- @@ -410,6 +415,8 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c store full complex Deltaprime matrix in sl_in sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1), dp_mat(:,:,2)) + dpsi_h = 0.002 ! finite-difference step for Jacobian + c----------------------------------------------------------------------- c loop over singular surfaces: evaluate kinetic/equilibrium c quantities via spline interpolation, compute derived layer @@ -425,6 +432,17 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) a_surf = issurfint(unitfun,mthsurf,respsi,3,1, $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) +c compute Jacobian da_surf/dpsiN by central difference + a_surf_p = issurfint(unitfun,mthsurf, + $ MIN(respsi+dpsi_h, REAL(1.0,r8)),3,1, + $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) + a_surf_m = issurfint(unitfun,mthsurf, + $ MAX(respsi-dpsi_h, REAL(0.001,r8)),3,1, + $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) + da_dpsiN = (a_surf_p - a_surf_m) + $ / (MIN(respsi+dpsi_h, REAL(1.0,r8)) + $ - MAX(respsi-dpsi_h, REAL(0.001,r8))) + c----------------------------------------------------------------------- c evaluate kinetic splines at this surface. c [spline_eval]: external, evaluates kin spline at respsi. @@ -433,12 +451,12 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- CALL spline_eval(kin,respsi,1) -c diamagnetic frequencies (rad/s) - omega_i = -twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) - $ -twopi*kin%f1(3)/(e*zi*chi1) +c diamagnetic frequencies (rad/s) from GPEC kinetic splines. +c These compute the ELECTRON diamagnetic frequency directly. omega_e = twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) - + omega_i = -twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) + $ -twopi*kin%f1(3)/(e*zi*chi1) sl_in%omegas_e_arr(ising) = omega_e sl_in%omegas_i_arr(ising) = omega_i @@ -448,7 +466,12 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) n_i = kin%f(1) t_i = kin%f(3) / e - zeff = kin%f(9) ! Z_eff from kinetic spline +c Z_eff: kinetic spline value may be incorrect if ni=ne in gpeckf +c (quasi-neutrality assumption makes Zeff=1). Override to 2.0 +c for deuterium plasma with carbon impurities (matching TJ). +c TODO: fix gpeckf generation to include proper ni for Zeff, +c or read Zeff from a namelist parameter. + zeff = 2.0 omega = kin%f(5) my_qval = q_rational(ising) @@ -460,6 +483,10 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) mu_i = 2.0 ! deuterium dr_val = dr_vals(ising) +c convert STRIDE shear (psiN-based) to Fitzpatrick shear (r-based). +c s_Fitz = s_psiN * r_s / (psiN * da_surf/dpsiN) + s_fitz = my_sval * my_rs / (respsi * da_dpsiN) + c transport coefficients from caller-provided arrays. c guard: arrays may be smaller than msing (e.g. from c fixed-size namelist); reuse last element if exceeded. @@ -490,13 +517,14 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) l_t = 0.0 c----------------------------------------------------------------------- -c compute derived layer parameters. -c [params]: external (params_mod), sets module-level globals -c tau, tau_r, tauk, lu, c_beta, d_beta, D_norm, P_perp, -c P_tor, dc_tmp, etc. in sglobal_mod. +c compute derived layer parameters using Fitzpatrick (r-based) +c shear. params() sees s_fitz as `sval`, so lu, tauk, D_norm, +c dc_tmp, etc. are all Fitzpatrick-consistent. Gradient lengths +c are zero here; params() skips its own omega_e/omega_i and we +c set Q_e/Q_i below from the spline-derived frequencies. c----------------------------------------------------------------------- CALL params(n_e,t_e,t_i,omega,chi_s,dr_val,dgeo_val, - $ l_n,l_t,my_qval,my_sval,my_bt,my_rs,R_0,mu_i, + $ l_n,l_t,my_qval,s_fitz,my_bt,my_rs,R_0,mu_i, $ zeff,.false.) c growth-rate conversion factor: Deltaprime -> gamma @@ -504,11 +532,13 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- c populate sl_in for this surface from params() globals. +c Q_e/Q_i use spline-derived omega_e/omega_i normalised by +c tauk (= Fitzpatrick S^(1/3) * tau_H, from params()). c----------------------------------------------------------------------- sl_in%qval_arr(ising) = INT(my_qval) sl_in%lu_arr(ising) = lu sl_in%Q_e_arr(ising) = -tauk * omega_e - sl_in%Q_i_arr(ising) = -tauk * omega_i + sl_in%Q_i_arr(ising) = tauk * omega_i sl_in%c_beta_arr(ising) = c_beta sl_in%d_beta_arr(ising) = d_beta sl_in%D_norm_arr(ising) = D_norm diff --git a/slayer/params.f b/slayer/params.f index 220b6dbdd..3324165e3 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -140,15 +140,20 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c Qconv converts dimensional frequencies to the normalised Q c used in the SLAYER dispersion relation (Cole scaling). c----------------------------------------------------------------------- - omega_e = -t_e/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval ! electron diamagnetic [rad/s] - omega_i = t_i/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval ! ion diamagnetic [rad/s] - Qconv = lu**(1.0/3.0) * tau_h ! frequency normalisation (Cole) tauk = Qconv ! stored in sglobal_mod + Q = Qconv * omega ! normalised rotation frequency - Q = Qconv * omega ! normalised rotation frequency - Q_e = -Qconv * omega_e ! normalised electron diamagnetic - Q_i = -Qconv * omega_i ! normalised ion diamagnetic +c Diamagnetic frequencies require gradient lengths; skip when the +c caller does not provide them (l_n <= 0 or l_t <= 0). Callers that +c compute omega_e/omega_i by other means must set Q_e and Q_i +c themselves afterwards. + IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN + omega_e = -t_e/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval + omega_i = t_i/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval + Q_e = -Qconv * omega_e + Q_i = -Qconv * omega_i + END IF c normalised ion Larmor radius (critical stability parameter) ds = lu**(1.0/3.0) * rho_s / rs diff --git a/slayer/slayer.f b/slayer/slayer.f index f1dba4a46..fbc66e435 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -106,6 +106,8 @@ PROGRAM slayer REAL(r8) :: inQ ! normalized ExB rotation freq. REAL(r8) :: inQ_e ! normalized electron diamagnetic REAL(r8) :: inQ_i ! normalized ion diamagnetic + REAL(r8) :: inQ_e_ovr(8) ! per-surface Q_e overrides (read_eq) + REAL(r8) :: inQ_i_ovr(8) ! per-surface Q_i overrides (read_eq) REAL(r8) :: inpr ! normalized pressure gradient REAL(r8) :: inpe ! normalized electron pressure REAL(r8) :: inc_beta ! normalized beta @@ -187,7 +189,8 @@ PROGRAM slayer $ ncfile,params_flag,mm,nn,n_e,t_e,t_i,sval,bt,rs,R0,omega, $ l_t,l_n,qval,mu_i,zeff,dr_val,dgeo_val,chi_p_prof, $ chi_t_prof,kappa_prof,inpr,inpe,inQ,inQ_e,inQ_i,inc_beta, - $ inds,intau,Q0,delta_prime,delta_n_p,ingamma + $ inds,intau,Q0,delta_prime,delta_n_p,ingamma, + $ inQ_e_ovr,inQ_i_ovr NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, $ AMR_passes,msing_max,dc_type,read_eq,Pperp_Ptor_flag, $ coupling_flag,QPscan_flag,Qscan_flag,QPescan_flag, @@ -231,6 +234,8 @@ PROGRAM slayer inQ = 0.0 inQ_e = 0.0 inQ_i = 0.0 + inQ_e_ovr = 0.0 + inQ_i_ovr = 0.0 inpr = 0.0 inpe = 0.0 inc_beta = 0.0 @@ -347,6 +352,11 @@ PROGRAM slayer c used by the Riccati solver. c----------------------------------------------------------------------- IF (params_flag) THEN + nr = nn ! ensure toroidal mode number is set for Wd iteration + mr = mm ! ensure poloidal mode number is set + chis(1) = chi_p_prof(1) ! chi_perp + chis(2) = chi_t_prof(1) ! chi_tor + chis(3) = kappa_prof(1) ! kappa CALL params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ l_n,l_t,qval,sval,bt,rs,R0,mu_i,zeff,params_check) ! Copy module-level results into local working variables. @@ -484,6 +494,30 @@ PROGRAM slayer n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) +c Override Q_e/Q_i with namelist values if nonzero. +c inQ_e overrides all surfaces; per-surface arrays take +c precedence (surface k gets inQ_e_ovr(k) if nonzero). + IF (ABS(inQ_e) > 0.0) THEN + WRITE(*,*) 'Overriding all Q_e with inQ_e=',inQ_e + sl_in%Q_e_arr(:) = inQ_e + END IF + IF (ABS(inQ_i) > 0.0) THEN + WRITE(*,*) 'Overriding all Q_i with inQ_i=',inQ_i + sl_in%Q_i_arr(:) = inQ_i + END IF + DO k = 1, MIN(n_k, SIZE(inQ_e_ovr)) + IF (ABS(inQ_e_ovr(k)) > 0.0) THEN + WRITE(*,*) ' Q_e override surface',k,':', + $ sl_in%Q_e_arr(k),' ->',inQ_e_ovr(k) + sl_in%Q_e_arr(k) = inQ_e_ovr(k) + END IF + END DO + DO k = 1, MIN(n_k, SIZE(inQ_i_ovr)) + IF (ABS(inQ_i_ovr(k)) > 0.0) THEN + sl_in%Q_i_arr(k) = inQ_i_ovr(k) + END IF + END DO + ELSE ! Single-surface mode: build inputs from namelist. n_k = 1 @@ -594,6 +628,17 @@ PROGRAM slayer n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) END IF +c Apply Q_e/Q_i overrides (same logic as est_gamma path) + DO k = 1, MIN(n_k, SIZE(inQ_e_ovr)) + IF (ABS(inQ_e_ovr(k)) > 0.0) THEN + sl_in%Q_e_arr(k) = inQ_e_ovr(k) + END IF + END DO + DO k = 1, MIN(n_k, SIZE(inQ_i_ovr)) + IF (ABS(inQ_i_ovr(k)) > 0.0) THEN + sl_in%Q_i_arr(k) = inQ_i_ovr(k) + END IF + END DO ELSE n_k = 1 From 8d4a86dde0581c7adb72c6cc743616a8a629f1ea Mon Sep 17 00:00:00 2001 From: d-burg Date: Sat, 18 Apr 2026 00:29:32 -0400 Subject: [PATCH 095/305] correct prefactors for toroidal delta_crit --- stride/free.f | 53 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/stride/free.f b/stride/free.f index 7c4416915..043007b1f 100755 --- a/stride/free.f +++ b/stride/free.f @@ -177,9 +177,9 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) REAL(r8), DIMENSION(sq%mx+1) :: ln_q REAL(r8), DIMENSION(msing) :: dgeo,shr TYPE(spline_type) :: psi_t,avg_dpsi_spl,avg_bsq_spl,v_spl, - $ shr_spl + $ vol_spl,shr_spl REAL(r8) :: bsq,chi1,dpsisq,myeta,jac,psifac,q,q1,respsi, - $ rfac,v1,v21,v22,v23,v33,al,Lam,mytheta,myr + $ rfac,v1,v21,v22,v23,v33,al,Lam,mytheta,myr,V_s REAL(r8), DIMENSION(:), POINTER :: avg TYPE(spline_type), TARGET :: fspl c----------------------------------------------------------------------- @@ -316,6 +316,11 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) avg_bsq_spl%xs=sq%xs(:) CALL spline_alloc(v_spl,SIZE(sq%xs(:))-1,1) v_spl%xs=sq%xs(:) + ! Cumulative volume V(psi_N) [m^3] = integral of dV/dpsi from axis. + CALL spline_alloc(vol_spl,SIZE(sq%xs(:))-1,1) + vol_spl%xs=sq%xs(:) + vol_spl%fs(:,1)=sq%fsi(:,3) + CALL spline_fit(vol_spl,"extrap") ! Prepare shear spline CALL spline_alloc(shr_spl,SIZE(sq%xs(:))-1,1) @@ -386,16 +391,50 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) CALL spline_eval(avg_dpsi_spl,respsi,1) CALL spline_eval(avg_bsq_spl,respsi,1) CALL spline_eval(v_spl,respsi,1) + CALL spline_eval(vol_spl,respsi,1) CALL spline_eval(shr_spl,respsi,1) - al = twopi*nn*sq%f(3)*(1.0/chi1) - Lam = (psi_t%f1(1)/sq%f(3))* - $ (-1.0/(sq%f(4)**2.0))*(sq%f1(4)/sq%f(3)) +c Connor et al. 2015 (PPCF 57 065001) eq. (59): +c Delta_crit = (pi^{3/2}/2) (chi_par/chi_perp)^{1/4} V_s +c * [alpha^2 Lambda^2/(<|grad V|^2>)]^{1/4} +c * (-D_R) +c Identify dgeo = V_s * [alpha^2 Lambda^2 +c /(<|grad V|^2>)]^{1/4}. +c +c Connor's alpha has dimension m^3/Wb: the phase 2 pi i n u / q +c must be dimensionless with u = psi_pol'(V) * theta (Hamada), +c which has units Wb/m^3, so alpha = (2 pi n / q) * v1_SI with +c v1_SI = dV/dpsi_pol [m^3/Wb]. +c Lambda = -q'(V) * (psi_pol'(V))^2 (SI: Wb^2/m^9) +c = -psio^2 * (dq/dpsi_N) / (dV/dpsi_N)^3 +c where psio is total poloidal flux at edge (Wb). +c V_s = V(psi_N_res) (m^3) +c STRIDE's sq uses psi_N (psifac); convert via psi_pol = psi_N * psio. +c sq%f(3) = dV / d(psi_N) [m^3] +c sq%f1(4) = dq / d(psi_N) [dimensionless] +c psio = chi1/(2 pi) [Wb] (total poloidal flux) +c v1_SI = sq%f(3)/psio [m^3/Wb] + V_s = vol_spl%f(1) + q = sq%f(4) + + ! alpha = (2 pi n / q) * v1_SI [m^3/Wb] + al = twopi * nn / q * sq%f(3) / psio + + ! Lambda = -psio^2 * (dq/dpsi_N) / (dV/dpsi_N)^3 [Wb^2/m^9] + Lam = -(psio**2.0) * sq%f1(4) / (sq%f(3)**3.0) shr(ising) = respsi * shr_spl%f1(1) - dgeo(ising) = v_spl%f(1)*( (((al**2.0)*(Lam**2.0))/ - $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) +c Final 2 sqrt(2 pi q) factor bridges Connor's Hamada-0-to-1 +c convention (eq. 59) to the Fitzpatrick/PEST3 r_s-normalized +c convention used by STRIDE's Delta_prime (PEST3 matrix) and +c SLAYER's rfitzp dc_type. In the LAR limit this reduces +c dgeo -> sqrt(ns/(R r_s)), matching Connor eq. (61) times r_s. + dgeo(ising) = 2.0d0*SQRT(twopi*q) * V_s + $ * ( (((al**2.0)*(Lam**2.0))/ + $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) ENDDO + ! Deallocate the new cumulative volume spline + CALL spline_dealloc(vol_spl) c----------------------------------------------------------------------- c optionally write netcdf file. c----------------------------------------------------------------------- From 77d3419967a8f8ae06a345e10c455130bca8aa4b Mon Sep 17 00:00:00 2001 From: d-burg Date: Sat, 18 Apr 2026 12:29:50 -0400 Subject: [PATCH 096/305] add no-op delta_c matrix to determinant --- slayer/growthrates.f | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index c8f8b3993..fbfcbb1a0 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -249,8 +249,20 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) $ sl_in%lu_arr(k)**(1.0_r8/3.0_r8) END DO -c compute det(dp_matrix - delta_Q) +c Coupled dispersion: det(Delta' - diag(Delta_c) - delta_Q). +c Delta_crit is a *local* layer-intrinsic offset at each +c rational surface, so it modifies only the diagonal of +c Delta'; outer-region off-diagonal couplings are untouched. +c This reduces to the single-surface matching +c (Delta'_kk - Delta_crit_k) = S^(1/3) * Delta_s +c on the diagonal. When dc_type='none', d_crit_arr is zero +c and the subtraction is a no-op, so this is backward +c compatible with the original coupled behaviour. result_matrix = sl_in%dp_matrix - delta_Q + DO k = 1, msing_max + result_matrix(k,k) = result_matrix(k,k) + $ - CMPLX(sl_in%d_crit_arr(k), 0.0_r8, KIND=r8) + END DO CALL calc_determinant(result_matrix, msing_max, det_val, $ det_status) IF (det_status /= 0) THEN From c834fbc3800e42d59c006945fceeff0c1e76afee Mon Sep 17 00:00:00 2001 From: d-burg Date: Sun, 19 Apr 2026 14:06:16 -0400 Subject: [PATCH 097/305] fix Q_e sign error --- slayer/layerinputs.f | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index f3757acf5..e9b790654 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -537,8 +537,14 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c----------------------------------------------------------------------- sl_in%qval_arr(ising) = INT(my_qval) sl_in%lu_arr(ising) = lu +c Use the same -tauk prefactor for both so opposite-signed +c spline omega_e, omega_i (electron vs ion diamagnetic +c frequencies) produce opposite-signed Q_e, Q_i. Matches +c params.f convention: Q_e = -Qconv*omega_e, Q_i = -Qconv*omega_i. +c Using +tauk on Q_i cancels the physical sign and drives +c Q_e - Q_i -> 0 for ni=ne, Ti=Te (iota_e blows up). sl_in%Q_e_arr(ising) = -tauk * omega_e - sl_in%Q_i_arr(ising) = tauk * omega_i + sl_in%Q_i_arr(ising) = -tauk * omega_i sl_in%c_beta_arr(ising) = c_beta sl_in%d_beta_arr(ising) = d_beta sl_in%D_norm_arr(ising) = D_norm From 335ce314c8dc16a14fa3f70713bde831771cb78a Mon Sep 17 00:00:00 2001 From: d-burg Date: Sat, 25 Apr 2026 19:16:26 -0400 Subject: [PATCH 098/305] WIP - SLAYER + RMATCH benchmarking patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SLAYER: - growthrates.f: set pr=P_perp in dispersion_det (both single-surface and coupled branches) so riccati_f's atol = 1e-7*pr**0.4 doesn't inherit a stale/zero module-global. Without this, DIII-D (read_eq=t, coupling_flag=t, msing>1) hits an infinite LSODE 'too much accuracy requested for precision of machine, R1=NaN' loop in the AMR scan. - layerinputs.f: add per-surface print of psi_N-based vs Fitzpatrick (r-based) shear conversion for benchmarking against Julia's build_slayer_inputs. RMATCH: - match.f: add detgrid_flag + match_detgrid subroutine for a 2D complex-Q grid scan of match_delta(Q, mat). Writes detgrid.out and detgrid.bin for apples-to-apples comparison vs Julia's AMR scan of the same coupled GGJ dispersion. Adds delta_per_surface.out per-call dump in match_delta when model='deltac' for direct Δ(Q,ising) comparison. Co-Authored-By: Claude Opus 4.7 (1M context) --- rmatch/match.f | 74 ++++++++++++++++++++++++++++++++++++++++++-- slayer/growthrates.f | 6 ++++ slayer/layerinputs.f | 14 ++++++++- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/rmatch/match.f b/rmatch/match.f index be4fa63ac..8b9890646 100644 --- a/rmatch/match.f +++ b/rmatch/match.f @@ -92,12 +92,16 @@ MODULE match_mod $ deltac_flag=.FALSE.,deltaj_flag=.FALSE., $ match_flag=.FALSE. LOGICAL :: bin_rpecsol=.FALSE.,out_rpecsol=.FALSE. + LOGICAL :: detgrid_flag=.FALSE. CHARACTER(10) :: model="deltac" INTEGER :: msing,totmsing,nstep=32,qscan_ising=1 INTEGER :: scan_nstep, scan_estep INTEGER :: nroot=1,iroot,totnsol,ising_output=1,itermax=500 + INTEGER :: detgrid_nre=64, detgrid_nim=64 REAL(r8) :: eta(20),dlim=1000,massden(20),rotation(20)=0,ntor=1 REAL(r8) :: scan_x0,scan_x1,relax_fac,scan_e0,scan_e1 + REAL(r8) :: detgrid_re_min=-1.0, detgrid_re_max=1.0, + $ detgrid_im_min=-1.0, detgrid_im_max=1.0 REAL(r8), DIMENSION(:), ALLOCATABLE :: taur_save REAL(r8), DIMENSION(:), ALLOCATABLE :: zo_out,zi_in COMPLEX(r8) :: initguess @@ -137,7 +141,10 @@ SUBROUTINE match_run $ deflate,nroot,match_flag,ising_output, $ match_sol,matrix_diagnose,fulldomain, $ coil,itermax,relax_fac,init_scan_flag, - $ scan_e0,scan_e1,eqscan_flag,scan_estep + $ scan_e0,scan_e1,eqscan_flag,scan_estep, + $ detgrid_flag,detgrid_nre,detgrid_nim, + $ detgrid_re_min,detgrid_re_max, + $ detgrid_im_min,detgrid_im_max NAMELIST/rmatch_output/ bin_rpecsol,out_rpecsol NAMELIST/nyquist_input/nyquist 10 FORMAT(1x,"Eigenvalue=",1p,2e11.3) @@ -246,7 +253,12 @@ SUBROUTINE match_run c----------------------------------------------------------------------- c scan eigen value (Q) for different inner models. c----------------------------------------------------------------------- - IF(qscan_flag) CALL match_qscan + IF(qscan_flag) CALL match_qscan +c----------------------------------------------------------------------- +c 2D complex-Q grid scan of the full match_delta determinant +c (patch added for Julia↔Fortran apples-to-apples comparison). +c----------------------------------------------------------------------- + IF(detgrid_flag) CALL match_detgrid c----------------------------------------------------------------------- c nyquist plot. c----------------------------------------------------------------------- @@ -475,6 +487,17 @@ FUNCTION match_delta(guess,mat) RESULT(det) zi_in(ising)=0 q_in(ising)=q_deltac sol=0 +c -- PATCH (Julia/Fortran benchmark) -- +c Dump per-surface Δ for each match_delta call so we can compare +c Julia's Galerkin output to Fortran's at the same Q. Writes to +c delta_per_surface.out (appended each call; nuke before a run). + OPEN(UNIT=42, FILE='delta_per_surface.out', + $ POSITION='APPEND') + WRITE(42,'(2e22.14, i5, 4e22.14)') + $ REAL(guess_modify), AIMAG(guess_modify), ising, + $ REAL(deltar(ising,1)), AIMAG(deltar(ising,1)), + $ REAL(deltar(ising,2)), AIMAG(deltar(ising,2)) + CLOSE(UNIT=42) END SELECT c----------------------------------------------------------------------- c construct the matching matrix. @@ -963,7 +986,52 @@ SUBROUTINE match_qscan c----------------------------------------------------------------------- CALL program_stop("Normal termination for q scan.") END SUBROUTINE match_qscan - + +c----------------------------------------------------------------------- +c subprogram 7b. match_detgrid. +c scan match_delta on a 2D Q_re × Q_im grid and write an ascii dump +c (detgrid.out, format "qre qim re_det im_det") plus a binary +c (detgrid.bin). Patch added for Julia↔Fortran apples-to-apples +c comparison of the coupled GGJ dispersion relation. +c----------------------------------------------------------------------- + SUBROUTINE match_detgrid + INTEGER :: ire, iim + REAL(r8) :: qre, qim, dre, dim + COMPLEX(r8) :: guess, det + COMPLEX(r8), DIMENSION(4*msing,4*msing) :: mat + 10 FORMAT(1p,4e20.11) + WRITE(*,*) "DETGRID: 2D scan of match_delta(Q, mat)" + WRITE(*,'(2x,a,i4,a,i4)') "nre=",detgrid_nre," nim=",detgrid_nim + WRITE(*,'(2x,a,2e12.3)') "Re(Q) range:", + $ detgrid_re_min, detgrid_re_max + WRITE(*,'(2x,a,2e12.3)') "Im(Q) range:", + $ detgrid_im_min, detgrid_im_max + OPEN(UNIT=bin_unit,FILE="detgrid.bin",STATUS="REPLACE", + $ FORM="UNFORMATTED") + CALL ascii_open(out_unit,"detgrid.out","REPLACE") + WRITE(out_unit,'(a)') "# Q_re Q_im Re(det) Im(det)" + WRITE(bin_unit) detgrid_nre, detgrid_nim, msing + dre = (detgrid_re_max - detgrid_re_min) / MAX(1, detgrid_nre - 1) + dim = (detgrid_im_max - detgrid_im_min) / MAX(1, detgrid_nim - 1) + DO iim = 1, detgrid_nim + qim = detgrid_im_min + (iim-1) * dim + DO ire = 1, detgrid_nre + qre = detgrid_re_min + (ire-1) * dre + guess = CMPLX(qre, qim, r8) + det = match_delta(guess, mat) + WRITE(out_unit,10) qre, qim, REAL(det,r8), AIMAG(det) + WRITE(bin_unit) REAL(qre,4), REAL(qim,4), + $ REAL(REAL(det,r8),4), REAL(AIMAG(det),4) + ENDDO + IF (MOD(iim, MAX(1,detgrid_nim/10)) == 0) + $ WRITE(*,'(4x,a,i4,a,i4)') "row ",iim," /",detgrid_nim + ENDDO + CALL ascii_close(out_unit) + WRITE(bin_unit) + CLOSE(UNIT=bin_unit) + WRITE(*,*) "DETGRID: wrote detgrid.out and detgrid.bin" + CALL program_stop("Normal termination for detgrid scan.") + END SUBROUTINE match_detgrid c----------------------------------------------------------------------- c subprogram 8. match_delta_jardin. c finite differential method of GGJ. diff --git a/slayer/growthrates.f b/slayer/growthrates.f index fbfcbb1a0..469541046 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -215,6 +215,9 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c_beta = sl_in%c_beta_arr(1) tauk = sl_in%Qconv_arr(1) iota_e = Q_e / (Q_e - Q_i) +c BENCHMARK PATCH: ensure pr is set so riccati_f's +c atol = 1e-7*pr**0.4 does not inherit a stale/zero module value. + pr = P_perp g_tmp = g_in tmp_delta=riccati_f() @@ -240,6 +243,9 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c_beta = sl_in%c_beta_arr(k) tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) +c BENCHMARK PATCH: ensure pr is set so riccati_f's +c atol = 1e-7*pr**0.4 does not inherit a stale/zero module value. + pr = P_perp c evaluate riccati_f at rescaled growth rate, de-normalise c rescale g_in to this surface's normalisation diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index e9b790654..ffedd8191 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -484,8 +484,20 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) dr_val = dr_vals(ising) c convert STRIDE shear (psiN-based) to Fitzpatrick shear (r-based). -c s_Fitz = s_psiN * r_s / (psiN * da_surf/dpsiN) +c STRIDE's shear(ising) = psi_N * d(ln q)/d(psi_N) (psi_N-based). +c Fitzpatrick's rfitzp formula needs s = (r/q) * dq/dr (r-based). +c Chain-rule: s_Fitz = s_psiN * r_s / (psi_N * da/dpsi_N). +c Typical magnitudes: s_psiN ~ 0.3-0.5 mid-plasma, while s_Fitz +c ~ 0.7-1.5 -- r-based shear is usually LARGER than psi_N-based +c because (r/psi_N) > (dr/dpsi_N) inside the mid-radius region. s_fitz = my_sval * my_rs / (respsi * da_dpsiN) + WRITE(*,'(A,I2,A,F7.4,A,F6.3,A,F8.4,A,F8.4,A,F7.3,A,F7.3)') + $ ' [layerinputs] q=', resm(ising), + $ ' psiN=', respsi, + $ ' r_s=', my_rs, + $ ' da/dpsiN=', da_dpsiN, + $ ' s_psiN=', my_sval, + $ ' -> s_Fitz=', s_fitz c transport coefficients from caller-provided arrays. c guard: arrays may be smaller than msing (e.g. from From 9ea6367b1a56694de1d2c14c5965f156cade0125 Mon Sep 17 00:00:00 2001 From: d-burg Date: Sun, 26 Apr 2026 03:47:48 -0400 Subject: [PATCH 099/305] SLAYER - NEW FEATURE - Allow STRIDE msing > coupled-dispersion msing_max MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch growthrates.f::dispersion_det to take only the (1:msing_max, 1:msing_max) sub-block of sl_in%dp_matrix before forming the coupled dispersion residual. Required for the DIII-D 147131 case where sas_flag=t, dmlim=0.2, qhigh=1e3 makes STRIDE produce a 4×4 Δ' matrix (q=2,3,4,5) but the SLAYER coupled-dispersion code only supports msing_max ≤ 3 (calc_determinant is hard-coded for 2x2 / 3x3). Coupled Δ_crit (dc_type=rfitzp) now works end-to-end across STRIDE, SLAYER, rdcon, and rmatch on the DIII-D benchmark. The dc_type='rfitzp' case visibly damps the coupled growth rate vs dc_type='none' on both Julia and Fortran SLAYER panels (different pole arrangement between the codes, traced to STRIDE's misnamed `dr_rational` variable storing the Mercier index D_I instead of the resistive interchange D_R = E + F + H² that the rfitzp formula expects — flagged for a separate fix). Verified at: CTM-processing/julia_vs_fortran/rmatch_eigenvalue_benchmark/cases/diiid/ --- slayer/growthrates.f | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 469541046..502fffae7 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -264,7 +264,15 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c on the diagonal. When dc_type='none', d_crit_arr is zero c and the subtraction is a no-op, so this is backward c compatible with the original coupled behaviour. - result_matrix = sl_in%dp_matrix - delta_Q +c +c BENCHMARK PATCH: take only the (1:msing_max, 1:msing_max) +c sub-block of dp_matrix so callers can compute the coupled +c dispersion on a subset of the rational surfaces that STRIDE +c actually found. This is needed when STRIDE finds 4+ surfaces +c (e.g. DIII-D 147131 with sas_flag=t, qhigh=1e3) but the +c coupled-dispersion code only supports msing_max ≤ 3. + result_matrix = sl_in%dp_matrix(1:msing_max,1:msing_max) + $ - delta_Q DO k = 1, msing_max result_matrix(k,k) = result_matrix(k,k) $ - CMPLX(sl_in%d_crit_arr(k), 0.0_r8, KIND=r8) From c0988c61c73443530fc55b6e418168626f65baa4 Mon Sep 17 00:00:00 2001 From: d-burg Date: Mon, 1 Jun 2026 14:46:43 -0400 Subject: [PATCH 100/305] fix dr_rationals to use locstab%f(2) index Co-Authored-By: Claude Opus 4.8 (1M context) --- stride/stride_netcdf.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index a7ea49168..0d7d8781d 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -97,7 +97,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, DO i=1,msing respsi = sing(i)%psifac CALL spline_eval(locstab,respsi,0) - dr_rationals(i)=locstab%f(1)/respsi + dr_rationals(i)=locstab%f(2)/respsi END DO c----------------------------------------------------------------------- c open files From f8bd9a943abbdaf7be3ee05809c7f5715e071f91 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 21 Jun 2026 21:12:44 -0700 Subject: [PATCH 101/305] Zeff profile read-in and spline evaluation --- rdcon/dcon.f | 3 ++- rdcon/dcon_mod.f | 4 +++- rdcon/mercier.f | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index a8e056246..32fdf0125 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -226,7 +226,8 @@ SUBROUTINE dcon_run $ tol_nr,tol_r,crossover,ucrit,singfac_min,singfac_max, $ cyl_flag,dmlim,lim_flag,sas_flag,sing_order,sort_type, $ gal_flag,regrid_flag,sing1_flag,qlow,qhigh, - $ sing_order_ceiling,degen_tol,coil,Zeff,reform_eq_with_psilim + $ sing_order_ceiling,degen_tol,coil,reform_eq_with_psilim, + $ Zeff, psi_N_Zeff NAMELIST/rdcon_output/interp,crit_break,out_bal1, $ bin_bal1,out_bal2,bin_bal2,out_metric,bin_metric,out_fmat, $ bin_fmat,out_gmat,bin_gmat,out_kmat,bin_kmat,out_sol, diff --git a/rdcon/dcon_mod.f b/rdcon/dcon_mod.f index 99bf7444c..02c9ce502 100644 --- a/rdcon/dcon_mod.f +++ b/rdcon/dcon_mod.f @@ -80,7 +80,9 @@ MODULE rdcon_mod $ sing_start=0,nzero=0 REAL(r8) :: thmax0=1,ucrit=1e4,tol_r=1e-5,tol_nr=1e-5, $ crossover=1e-2,mthsurf0=1,prefac=1.0, - $ plasma1=0.0,vacuum1=0.0,total1=0.0,Zeff=1.52 + $ plasma1=0.0,vacuum1=0.0,total1=0.0 + + REAL(r8), DIMENSION(:), ALLOCATABLE :: Zeff, psi_N_Zeff REAL(r8) :: dpsi_intvl=0.1,dpsi1_intvl=0.1 TYPE(spline_type) :: locstab, mreterms diff --git a/rdcon/mercier.f b/rdcon/mercier.f index f86faf262..4bcf0b109 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -37,12 +37,20 @@ SUBROUTINE mercier_scan $ eps_eff REAL(r8), DIMENSION(:), POINTER :: avg REAL(r8), DIMENSION(2) :: rmax_loc, rmin_loc, zmax_loc, zmin_loc - TYPE(spline_type), TARGET :: ff + TYPE(spline_type), TARGET :: ff, Zeff_spline c----------------------------------------------------------------------- c prepare spline types. c----------------------------------------------------------------------- IF(MRE_flag)THEN CALL spline_alloc(ff,mtheta,22) + IF(ALLOCATED(Zeff) .AND. ALLOCATED(psi_N_Zeff))THEN + CALL spline_alloc(Zeff_spline,SIZE(psi_N_),1) + Zeff_spline%xs=psi_N_Zeff + Zeff_spline%fs(:,1)=Zeff + CALL spline_fit(Zeff_spline,"extrap") + ELSE + CALL program_stop("Provide Zeff profile values") + ENDIF ELSE CALL spline_alloc(ff,mtheta,5) ENDIF @@ -207,8 +215,9 @@ SUBROUTINE mercier_scan c----------------------------------------------------------------------- ! mu_e_on_nu_e=(ftr/(1.d0-ftr))*(1.d0+0.533d0/Zeff) ! ^ Taking banana limit of eq. B17 (& B14) in Callen - mufrac=ftr*(1.d0+0.533d0/Zeff)/ - $ ((1.d0-ftr)+ftr*(1.d0+0.533d0/Zeff)) + CALL spline_eval(Zeff_spline,psifac,0) + mufrac=ftr*(1.d0+0.533d0/Zeff_spline%f(1))/ + $ ((1.d0-ftr)+ftr*(1.d0+0.533d0/Zeff_spline%f(1))) c----------------------------------------------------------------------- c evaluate geometric prefactors of MRE stability terms from c Hegna 1999 https://doi.org/10.1063/1.873661 From ccb52e6d74552a86d05e0b13e07fd13127f4bdbf Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 21 Jun 2026 21:12:52 -0700 Subject: [PATCH 102/305] Zeff profile printing --- rdcon/rdcon_netcdf.f | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rdcon/rdcon_netcdf.f b/rdcon/rdcon_netcdf.f index 1b2c52d34..0534e7a10 100644 --- a/rdcon/rdcon_netcdf.f +++ b/rdcon/rdcon_netcdf.f @@ -130,7 +130,11 @@ SUBROUTINE rdcon_netcdf_out(wp,wv,wt,wt0,ep,ev,et) CALL check( nf90_put_att(ncid,nf90_global,'betat', betat)) CALL check( nf90_put_att(ncid,nf90_global,'betan', betan)) CALL check( nf90_put_att(ncid,nf90_global,'volume', volume)) - CALL check( nf90_put_att(ncid,nf90_global,'Zeff', Zeff)) + IF(ALLOCATED(Zeff) .AND. ALLOCATED(psi_N_Zeff))THEN + CALL check( nf90_put_att(ncid,nf90_global,'Zeff', Zeff)) + CALL check( nf90_put_att(ncid,nf90_global,'psi_N_Zeff', + $ psi_N_Zeff)) + ENDIF CALL check( nf90_put_att(ncid,nf90_global,'bwall', bwall)) CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) CALL check( nf90_put_att(ncid,nf90_global,'q0', q0)) From 182190e5ceb1b0f017944d3c08a55523a5d04732 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 21 Jun 2026 21:20:11 -0700 Subject: [PATCH 103/305] bugfix --- rdcon/mercier.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdcon/mercier.f b/rdcon/mercier.f index 4bcf0b109..bd4713a42 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -44,7 +44,7 @@ SUBROUTINE mercier_scan IF(MRE_flag)THEN CALL spline_alloc(ff,mtheta,22) IF(ALLOCATED(Zeff) .AND. ALLOCATED(psi_N_Zeff))THEN - CALL spline_alloc(Zeff_spline,SIZE(psi_N_),1) + CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff),1) Zeff_spline%xs=psi_N_Zeff Zeff_spline%fs(:,1)=Zeff CALL spline_fit(Zeff_spline,"extrap") From 9fe031d4ea96758c6cf7e0d042043b37ffbbc30a Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 21 Jun 2026 22:53:42 -0700 Subject: [PATCH 104/305] Adding 'read_var_len' subroutine to read in variable length arrays --- equil/local.f | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/equil/local.f b/equil/local.f index b4e25380d..fb7aba804 100644 --- a/equil/local.f +++ b/equil/local.f @@ -13,6 +13,7 @@ c 5. ascii_close. c 6. program_stop. c 7. floored_log. +c 8. read_var_len. c----------------------------------------------------------------------- c subprogram 0. local. c module declarations. @@ -259,4 +260,49 @@ FUNCTION floored_log(u) RESULT(ulog) c----------------------------------------------------------------------- RETURN END FUNCTION floored_log +c----------------------------------------------------------------------- +c subprogram 8. read_var_len. +c allocates variable length arrays from set-length read-in arrays. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE read_var_len(x_in, y_in, x_out, y_out) + + IMPLICIT NONE + + REAL(r8), DIMENSION(:), INTENT(IN) :: x_in, y_in + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: x_out, y_out + INTEGER :: n, i + REAL(r8) :: default_value_x,default_value_y +c----------------------------------------------------------------------- +c assume last values of x_in, y_in are sentinals +c----------------------------------------------------------------------- + default_value_x = x_in(SIZE(x_in)) + default_value_y = y_in(SIZE(y_in)) +c----------------------------------------------------------------------- +c count leading non-sentinel values in x_in. +c----------------------------------------------------------------------- + n = 0 + DO i = 1, SIZE(x_in) - 1 + IF (x_in(i) /= default_value_x) THEN + n = n + 1 + ELSE + EXIT + END IF + END DO +c----------------------------------------------------------------------- +c allocate output arrays and copy if values exist. +c----------------------------------------------------------------------- + IF (n > 0) THEN + ALLOCATE(x_out(n)) + ALLOCATE(y_out(n)) + x_out(1:n) = x_in(1:n) + y_out(1:n) = y_in(1:n) + END IF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE read_var_len END MODULE local_mod From 4e1c8e23b1e58137d7b921477e81f5dd3e79b2ce Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 21 Jun 2026 22:54:13 -0700 Subject: [PATCH 105/305] Using read_var_len to allocate internal variables Zeff_, psi_N_Zeff_ --- rdcon/dcon.f | 5 ++++- rdcon/dcon_mod.f | 3 ++- rdcon/mercier.f | 8 ++++---- rdcon/rdcon_netcdf.f | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index 32fdf0125..8852772bb 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -261,7 +261,10 @@ SUBROUTINE dcon_run CALL gal_read_input ENDIF CALL ascii_close(in_unit) - +c----------------------------------------------------------------------- +c Allocate variable-length Zeff input profile. +c----------------------------------------------------------------------- + CALL read_var_len(Zeff,psi_N_Zeff,Zeff_,psi_N_Zeff_) c----------------------------------------------------------------------- c open output files, read, process, and diagnose equilibrium. c----------------------------------------------------------------------- diff --git a/rdcon/dcon_mod.f b/rdcon/dcon_mod.f index 02c9ce502..47604f187 100644 --- a/rdcon/dcon_mod.f +++ b/rdcon/dcon_mod.f @@ -82,7 +82,8 @@ MODULE rdcon_mod $ crossover=1e-2,mthsurf0=1,prefac=1.0, $ plasma1=0.0,vacuum1=0.0,total1=0.0 - REAL(r8), DIMENSION(:), ALLOCATABLE :: Zeff, psi_N_Zeff + REAL(r8), DIMENSION(:), ALLOCATABLE :: Zeff_, psi_N_Zeff_ + REAL(r8), DIMENSION(1000) :: Zeff=-1.0, psi_N_Zeff=-1.0 ! read-in REAL(r8) :: dpsi_intvl=0.1,dpsi1_intvl=0.1 TYPE(spline_type) :: locstab, mreterms diff --git a/rdcon/mercier.f b/rdcon/mercier.f index bd4713a42..cff17260b 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -43,10 +43,10 @@ SUBROUTINE mercier_scan c----------------------------------------------------------------------- IF(MRE_flag)THEN CALL spline_alloc(ff,mtheta,22) - IF(ALLOCATED(Zeff) .AND. ALLOCATED(psi_N_Zeff))THEN - CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff),1) - Zeff_spline%xs=psi_N_Zeff - Zeff_spline%fs(:,1)=Zeff + IF(ALLOCATED(Zeff_) .AND. ALLOCATED(psi_N_Zeff_))THEN + CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_),1) + Zeff_spline%xs=psi_N_Zeff_ + Zeff_spline%fs(:,1)=Zeff_ CALL spline_fit(Zeff_spline,"extrap") ELSE CALL program_stop("Provide Zeff profile values") diff --git a/rdcon/rdcon_netcdf.f b/rdcon/rdcon_netcdf.f index 0534e7a10..0733d3f77 100644 --- a/rdcon/rdcon_netcdf.f +++ b/rdcon/rdcon_netcdf.f @@ -130,10 +130,10 @@ SUBROUTINE rdcon_netcdf_out(wp,wv,wt,wt0,ep,ev,et) CALL check( nf90_put_att(ncid,nf90_global,'betat', betat)) CALL check( nf90_put_att(ncid,nf90_global,'betan', betan)) CALL check( nf90_put_att(ncid,nf90_global,'volume', volume)) - IF(ALLOCATED(Zeff) .AND. ALLOCATED(psi_N_Zeff))THEN - CALL check( nf90_put_att(ncid,nf90_global,'Zeff', Zeff)) + IF(ALLOCATED(Zeff_) .AND. ALLOCATED(psi_N_Zeff_))THEN + CALL check( nf90_put_att(ncid,nf90_global,'Zeff', Zeff_)) CALL check( nf90_put_att(ncid,nf90_global,'psi_N_Zeff', - $ psi_N_Zeff)) + $ psi_N_Zeff_)) ENDIF CALL check( nf90_put_att(ncid,nf90_global,'bwall', bwall)) CALL check( nf90_put_att(ncid,nf90_global,'bt0', bt0)) From 4584a7affc94d47940082467a20e50c9c25c8af2 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 3 Jul 2026 11:26:36 -0700 Subject: [PATCH 106/305] Making variable array read-in to treat each array separately. Adding explitcit indexing control to match Zeff spline assignment --- equil/local.f | 19 ++++++++----------- rdcon/dcon.f | 6 ++---- rdcon/dcon_mod.f | 2 +- rdcon/mercier.f | 2 +- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/equil/local.f b/equil/local.f index fb7aba804..0cd9c436c 100644 --- a/equil/local.f +++ b/equil/local.f @@ -267,38 +267,35 @@ END FUNCTION floored_log c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE read_var_len(x_in, y_in, x_out, y_out) + SUBROUTINE read_var_len(x_in, x_out) IMPLICIT NONE - REAL(r8), DIMENSION(:), INTENT(IN) :: x_in, y_in - REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: x_out, y_out + REAL(r8), DIMENSION(:), INTENT(IN) :: x_in ! Indexes from 1... + REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: x_out INTEGER :: n, i - REAL(r8) :: default_value_x,default_value_y + REAL(r8) :: default_value c----------------------------------------------------------------------- -c assume last values of x_in, y_in are sentinals +c assume last value of x_in is a sentinal c----------------------------------------------------------------------- - default_value_x = x_in(SIZE(x_in)) - default_value_y = y_in(SIZE(y_in)) + default_value = x_in(SIZE(x_in)) c----------------------------------------------------------------------- c count leading non-sentinel values in x_in. c----------------------------------------------------------------------- n = 0 DO i = 1, SIZE(x_in) - 1 - IF (x_in(i) /= default_value_x) THEN + IF (x_in(i) /= default_value) THEN n = n + 1 ELSE EXIT END IF END DO c----------------------------------------------------------------------- -c allocate output arrays and copy if values exist. +c allocate output array and copy if values exist. c----------------------------------------------------------------------- IF (n > 0) THEN ALLOCATE(x_out(n)) - ALLOCATE(y_out(n)) x_out(1:n) = x_in(1:n) - y_out(1:n) = y_in(1:n) END IF c----------------------------------------------------------------------- c terminate. diff --git a/rdcon/dcon.f b/rdcon/dcon.f index 8852772bb..ec13c5d87 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -262,10 +262,6 @@ SUBROUTINE dcon_run ENDIF CALL ascii_close(in_unit) c----------------------------------------------------------------------- -c Allocate variable-length Zeff input profile. -c----------------------------------------------------------------------- - CALL read_var_len(Zeff,psi_N_Zeff,Zeff_,psi_N_Zeff_) -c----------------------------------------------------------------------- c open output files, read, process, and diagnose equilibrium. c----------------------------------------------------------------------- CALL ascii_open(out_unit,"dcon.out","REPLACE") @@ -343,6 +339,8 @@ SUBROUTINE dcon_run c optionally compute modified Rutherford equation (MRE) terms. c----------------------------------------------------------------------- IF(MRE_flag)THEN + CALL read_var_len(Zeff,Zeff_) + CALL read_var_len(psi_N_Zeff,psi_N_Zeff_) CALL spline_alloc(mreterms,mpsi,30) mreterms%xs=sq%xs mreterms%fs=0 diff --git a/rdcon/dcon_mod.f b/rdcon/dcon_mod.f index 47604f187..74c161d1c 100644 --- a/rdcon/dcon_mod.f +++ b/rdcon/dcon_mod.f @@ -83,7 +83,7 @@ MODULE rdcon_mod $ plasma1=0.0,vacuum1=0.0,total1=0.0 REAL(r8), DIMENSION(:), ALLOCATABLE :: Zeff_, psi_N_Zeff_ - REAL(r8), DIMENSION(1000) :: Zeff=-1.0, psi_N_Zeff=-1.0 ! read-in + REAL(r8), DIMENSION(1:1000) :: Zeff=-1.0, psi_N_Zeff=-1.0 REAL(r8) :: dpsi_intvl=0.1,dpsi1_intvl=0.1 TYPE(spline_type) :: locstab, mreterms diff --git a/rdcon/mercier.f b/rdcon/mercier.f index cff17260b..2f2f7e4df 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -44,7 +44,7 @@ SUBROUTINE mercier_scan IF(MRE_flag)THEN CALL spline_alloc(ff,mtheta,22) IF(ALLOCATED(Zeff_) .AND. ALLOCATED(psi_N_Zeff_))THEN - CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_),1) + CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_)-1,1) Zeff_spline%xs=psi_N_Zeff_ Zeff_spline%fs(:,1)=Zeff_ CALL spline_fit(Zeff_spline,"extrap") From b3d713405e38a9791c4c02dffbf8a6cad6da164e Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 3 Jul 2026 11:26:57 -0700 Subject: [PATCH 107/305] Adding spline cleanup --- rdcon/dcon.f | 5 +++++ rdcon/mercier.f | 1 + 2 files changed, 6 insertions(+) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index ec13c5d87..ee948fbca 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -77,6 +77,11 @@ SUBROUTINE dcon_dealloc(opt) DEALLOCATE(cellinfos%icell,cellinfos%iintvl, $ cellinfos%etypes,cellinfos%etypes_int,cellinfos%x1,cellinfos%x2) ENDIF + IF(MRE_flag)THEN + CALL spline_dealloc(mreterms) + DEALLOCATE(Zeff_) + DEALLOCATE(psi_N_Zeff_) + ENDIF c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/rdcon/mercier.f b/rdcon/mercier.f index 2f2f7e4df..b3e03ed23 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -288,6 +288,7 @@ SUBROUTINE mercier_scan 120 FORMAT(19(E30.15,1X)) ENDDO CALL spline_dealloc(ff) + IF(MRE_flag) CALL spline_dealloc(Zeff_spline) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- From 572462737a2686cdb1e95778678d1b2a4a5b791e Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 3 Jul 2026 11:27:19 -0700 Subject: [PATCH 108/305] turning off MRE_flag, geom_flag defaults --- docs/examples/DIIID_resistive_example/rdcon.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/DIIID_resistive_example/rdcon.in b/docs/examples/DIIID_resistive_example/rdcon.in index eef0ddbf1..36aebd085 100644 --- a/docs/examples/DIIID_resistive_example/rdcon.in +++ b/docs/examples/DIIID_resistive_example/rdcon.in @@ -75,8 +75,8 @@ bin_bal1=f ! Binary output for bal_flag poloidal functions out_bal2=f ! Ascii output for bal_flag functions bin_bal2=f ! Binary output for bal_flag functions - MRE_flag=t ! Netcdf output for various modified Rutherford equation terms (defined in mercier.f) - geom_flag=t ! Netcdf output for more flux-surface averaged terms (defined in mercier.f). To use, must set MRE_flag = t + MRE_flag=f ! Netcdf output for various modified Rutherford equation terms (defined in mercier.f) + geom_flag=f ! Netcdf output for more flux-surface averaged terms (defined in mercier.f). To use, must set MRE_flag = t / &UA_DIAGNOSE_LIST From 985c69e5959034947af9c2c0a228188bcbcc9660 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 11 Mar 2026 14:36:31 +0900 Subject: [PATCH 109/305] Remove nametag file from documentation (cherry picked from commit 7ffde747ab34995e44688cb0954a6ae352c49e6d) --- docs/nametag | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/nametag diff --git a/docs/nametag b/docs/nametag deleted file mode 100644 index 618ff0687..000000000 --- a/docs/nametag +++ /dev/null @@ -1 +0,0 @@ -snu_offshoot \ No newline at end of file From d21b863ce815271f66f8f9c37e54868a902dcd71 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 13 Mar 2026 11:53:11 +0900 Subject: [PATCH 110/305] GPEC - WIP - add curvature, shear, K value calculation for recon_flag (cherry picked from commit 4a146f21e01c700f56130d41672c5951b920beb6) --- gpec/gpec.f | 9 +- gpec/gpeq.f | 416 +++++++++++++++++++++++++++++++++++++++++++++--- gpec/gpglobal.F | 11 ++ gpec/gpout.f | 108 +++++++++++++ input/gpec.in | 1 + 5 files changed, 520 insertions(+), 25 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index 6e1eba66c..5ff5ab9eb 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -37,7 +37,7 @@ PROGRAM gpec_main $ arbsurf_flag,angles_flag,surfmode_flag,rzpgrid_flag, $ singcurs_flag,m3d_flag,cas3d_flag,test_flag,nrzeq_flag, $ arzphifun_flag,xbrzphifun_flag,pmodbmn_flag,xclebsch_flag, - $ filter_flag,gal_flag,delpsi_flag, + $ filter_flag,gal_flag,delpsi_flag,recon_flag, $ singthresh_callen_flag,singthresh_slayer_flag,singthresh_flag LOGICAL, DIMENSION(100) :: ss_flag COMPLEX(r8), DIMENSION(:), POINTER :: finmn,foutmn,xspmn, @@ -70,7 +70,7 @@ PROGRAM gpec_main $ xclebsch_flag,pbrzphi_flag,verbose,max_linesout,filter_flag, $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, - $ singthresh_slayer_inpr,out_ahg2msc + $ singthresh_slayer_inpr,out_ahg2msc,recon_flag NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -194,6 +194,7 @@ PROGRAM gpec_main test_flag=.FALSE. eigm_flag=.FALSE. mutual_test_flag=.FALSE. + recon_flag=.FALSE. majr=10.0 minr=1.0 @@ -677,7 +678,9 @@ PROGRAM gpec_main IF (arzphifun_flag) THEN CALL gpout_arzphifun(mode,xspmn) ENDIF - + IF (recon_flag) THEN + CALL gpout_recon(mode,xspmn) + ENDIF c----------------------------------------------------------------------- c diagnose. c----------------------------------------------------------------------- diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 6d4b30c3c..30ecc0f8c 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -14,17 +14,23 @@ c 6. gpeq_parallel c 7. gpeq_rzphi c 8. gpeq_surface -c 9. gpeq_fcoords -c 10. gpeq_fcoordsout -c 11. gpeq_bcoords -c 12. gpeq_bcoordsout -c 13. gpeq_weight -c 14. gpeq_rzpgrid -c 15. gpeq_rzpdiv -c 16. gpeq_alloc -c 17. gpeq_dealloc -c 18. gpeq_interp_singsurf -c 19. gpeq_interp_sol +c 9. gpeq_epf (reconstruction: C vector for EPF) +c 10. gpeq_dst (reconstruction: C vector for DST) +c 11. gpeq_shear (reconstruction: magnetic shear) +c 12. gpeq_curvature (reconstruction: curvature) +c 13. gpeq_K (reconstruction: Bernstein K quantity) +c 14. gpeq_terms (reconstruction: integration terms) +c 15. gpeq_fcoords +c 16. gpeq_fcoordsout +c 17. gpeq_bcoords +c 18. gpeq_bcoordsout +c 19. gpeq_weight +c 20. gpeq_rzpgrid +c 21. gpeq_rzpdiv +c 22. gpeq_alloc +c 23. gpeq_dealloc +c 24. gpeq_interp_singsurf +c 25. gpeq_interp_sol c----------------------------------------------------------------------- c subprogram 0. gpeq_mod. c module declarations. @@ -639,7 +645,373 @@ SUBROUTINE gpeq_surface(psi) RETURN END SUBROUTINE gpeq_surface c----------------------------------------------------------------------- -c subprogram 9. gpeq_fcoords. +c subprogram 9. gpeq_epf. +c compute C vector (covariant + contravariant) for EPF calculation. +c +c Stores mode-space coefficients for all psi levels: +c C_psi_mn(mpert, 0:mpsi) - covariant psi component +c C_theta_mn(mpert, 0:mpsi) - covariant theta component +c C_zeta_mn(mpert, 0:mpsi) - covariant zeta component +c C1_mn(mpert, 0:mpsi) - contravariant scalar C_1 +c C2_mn(mpert, 0:mpsi) - contravariant scalar C_2 +c C3_mn(mpert, 0:mpsi) - contravariant scalar C_3 +c +c INPUT: psi (flux coordinate) +c ipsi (psi index for storage) +c +c REFERENCES: +c - Q = ξ × (∇ × B) in energy principle +c - C = Q + ξ·n̂ (μ₀ j × n̂) where n̂ = ∇ψ/|∇ψ| +c - Formulas from main.tex section "Reconstruction v1" +c----------------------------------------------------------------------- + SUBROUTINE gpeq_epf(psi, ipsi) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + INTEGER, INTENT(IN) :: ipsi + + INTEGER :: itheta, ipert, jpert, m, m1, dm + + REAL(r8) :: q, q1, f1raw, p1, chi1, jac, delpsi + REAL(r8) :: eta, rfac + REAL(r8), DIMENSION(0:mthsurf) :: jacs, dphi, r_vec, z_vec + +c Metric tensor (COMPLEX - see gpeq_cova) + COMPLEX(r8), DIMENSION(-mband:mband) :: g11, g12, g22, g23, + $ g31, g33 + +c Eigenfunction components in spatial representation + COMPLEX(r8), DIMENSION(0:mthsurf) :: xi_psi_fun, xi_s_fun, + $ xwp_fun, xmt_fun + +c C vector components (covariant) in spatial rep + COMPLEX(r8), DIMENSION(0:mthsurf) :: C_psi_fun, C_theta_fun, + $ C_zeta_fun, C1_fun, C2_fun, C3_fun + +c Supporting functions + REAL(r8), DIMENSION(0:mthsurf) :: delpsi_arr + + IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi +c----------------------------------------------------------------------- +c 0) Setup: evaluate equilibrium data at this psi. +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + q = sq%f(4) + q1 = sq%f1(4) + f1raw = sq%f1(1) + p1 = sq%f1(2) + chi1 = psio * twopi + +c Get metric tensor (same as gpeq_cova) + CALL cspline_eval(metric%cs, psi, 0) + g11(0:-mband:-1) = metric%cs%f(1:mband+1) + g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) + g33(0:-mband:-1) = metric%cs%f(4*mband+6:5*mband+6) + g23(0:-mband:-1) = metric%cs%f(3*mband+4:4*mband+4) + g31(0:-mband:-1) = metric%cs%f(4*mband+5:5*mband+5) + g12(0:-mband:-1) = metric%cs%f(5*mband+6:6*mband+6) + +c Fill upper half of metric arrays (conjugate symmetry) + g11(1:mband) = CONJG(g11(-1:-mband:-1)) + g22(1:mband) = CONJG(g22(-1:-mband:-1)) + g33(1:mband) = CONJG(g33(-1:-mband:-1)) + g23(1:mband) = CONJG(g23(-1:-mband:-1)) + g31(1:mband) = CONJG(g31(-1:-mband:-1)) + g12(1:mband) = CONJG(g12(-1:-mband:-1)) + +c----------------------------------------------------------------------- +c 1) Compute theta grid: |∇ψ|² and coordinate values. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + jac = rzphi%f(4) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_vec(itheta) = ro + rfac*COS(eta) + z_vec(itheta) = zo + rfac*SIN(eta) + jacs(itheta) = jac + dphi(itheta) = rzphi%f(3) + +c Compute |∇ψ|² from contravariant metric + w(1,1) = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_vec(itheta) + $ /jac + w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) + delpsi_arr(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) + ENDDO + +c----------------------------------------------------------------------- +c 2) Reconstruct eigenfunctions in spatial representation. +c Use iscdftb: mode-space coefficients → spatial functions +c----------------------------------------------------------------------- + CALL iscdftb(mfac, mpert, xi_psi_fun, mthsurf, xsp_mn) + CALL iscdftb(mfac, mpert, xi_s_fun, mthsurf, xss_mn) + CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) + CALL iscdftb(mfac, mpert, xmt_fun, mthsurf, xmt_mn) + +c----------------------------------------------------------------------- +c 3) Construct C vectors in spatial representation. +c For now: simplified version using existing eigenfunctions. +c Full version would include current coupling terms. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + C_psi_fun(itheta) = xwp_fun(itheta) + C_theta_fun(itheta) = xmt_fun(itheta) + C_zeta_fun(itheta) = CMPLX(0.0_r8, 0.0_r8, r8) + +c Contravariant representation (same as covariant for now) + C1_fun(itheta) = C_psi_fun(itheta) + C2_fun(itheta) = C_theta_fun(itheta) + C3_fun(itheta) = C_zeta_fun(itheta) + ENDDO + +c----------------------------------------------------------------------- +c 4) Fourier transform C components to mode-space and store. +c Use iscdftf: spatial functions → mode-space coefficients +c----------------------------------------------------------------------- + CALL iscdftf(mfac, mpert, C_psi_fun, mthsurf, + $ C_psi_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, C_theta_fun, mthsurf, + $ C_theta_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, C_zeta_fun, mthsurf, + $ C_zeta_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, C1_fun, mthsurf, + $ C1_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, C2_fun, mthsurf, + $ C2_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, C3_fun, mthsurf, + $ C3_mn(1:mpert, ipsi)) + + IF(debug_flag) PRINT *, "->Leaving gpeq_epf at ipsi=", ipsi +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_epf +c----------------------------------------------------------------------- +c subprogram 10. gpeq_dst. +c compute DST mode coefficients and spatial functions. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_dst(psi, ipsi) + REAL(r8), INTENT(IN) :: psi + INTEGER, INTENT(IN) :: ipsi +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_dst +c----------------------------------------------------------------------- +c subprogram 11. gpeq_shear. +c compute magnetic shear mode coefficients and spatial functions. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_shear(psi, shear_mn, shear_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: shear_mn + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) + $ :: shear_fun + + INTEGER :: itheta, ipert, jpert, dm, m1 + REAL(r8) :: q, q1, eta, r, rfac + REAL(r8), DIMENSION(0:mthsurf) :: theta_vals + COMPLEX(r8), DIMENSION(-mband:mband) :: jmat, jmat1 + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_theta_fun + + CALL spline_eval(sq, psi, 1) + q = sq%f(4) + q1 = sq%f1(4) + CALL cspline_eval(metric%cs, psi, 0) + + jmat(0:-mband:-1) = metric%cs%f(6*mband+7:7*mband+7) + jmat1(0:-mband:-1) = metric%cs%f(7*mband+8:8*mband+8) + jmat(1:mband) = CONJG(jmat(-1:-mband:-1)) + jmat1(1:mband) = CONJG(jmat1(-1:-mband:-1)) + +c----------------------------------------------------------------------- +c compute shear in mode space from metric tensors and solutions. +c----------------------------------------------------------------------- + ipert = 0 + shear_mn = 0.0_r8 + DO m1 = mlow, mhigh + ipert = ipert + 1 + DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) + jpert = ipert + dm + shear_mn(ipert) = shear_mn(ipert) + $ + (twopi**2/jac) * (q1 + $ + (jmat(dm)*xwp_mn(jpert) - q*jmat1(dm)*xwp_mn(jpert))/ + $ q) + ENDDO + ENDDO + +c----------------------------------------------------------------------- +c convert to spatial functions if requested. +c----------------------------------------------------------------------- + IF (PRESENT(shear_fun)) THEN + CALL iscdftb(mfac, mpert, shear_theta_fun, mthsurf, + $ shear_mn) + shear_fun = shear_theta_fun + ENDIF + + RETURN + END SUBROUTINE gpeq_shear +c----------------------------------------------------------------------- +c subprogram 12. gpeq_curvature. +c compute curvature mode coefficients and spatial functions. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_curvature(psi, curv_mn, curv_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: curv_mn + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) + $ :: curv_fun + + INTEGER :: itheta, ipert, jpert, dm, m1 + REAL(r8) :: q, p1, chi1, eta, r, rfac, bsq_val + COMPLEX(r8), DIMENSION(-mband:mband) :: g22, g23, g33 + COMPLEX(r8), DIMENSION(0:mthsurf) :: curv_theta_fun + COMPLEX(r8), DIMENSION(mpert) :: bvt_local, bvz_local + + chi1 = psio * twopi + + CALL spline_eval(sq, psi, 1) + q = sq%f(4) + p1 = sq%f1(2) + CALL cspline_eval(metric%cs, psi, 0) + + g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) + g23(0:-mband:-1) = metric%cs%f(3*mband+3:4*mband+3) + g33(0:-mband:-1) = metric%cs%f(4*mband+4:5*mband+4) + g22(1:mband) = CONJG(g22(-1:-mband:-1)) + g23(1:mband) = CONJG(g23(-1:-mband:-1)) + g33(1:mband) = CONJG(g33(-1:-mband:-1)) + +c----------------------------------------------------------------------- +c compute covariant components needed for curvature. +c----------------------------------------------------------------------- + ipert = 0 + bvt_local = 0.0_r8 + bvz_local = 0.0_r8 + curv_mn = 0.0_r8 + DO m1 = mlow, mhigh + ipert = ipert + 1 + DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) + jpert = ipert + dm + bvt_local(ipert) = bvt_local(ipert) + $ + g22(dm)*bmt_mn(jpert) + g23(dm)*bmz_mn(jpert) + bvz_local(ipert) = bvz_local(ipert) + $ + g23(dm)*bmt_mn(jpert) + g33(dm)*bmz_mn(jpert) + ENDDO + curv_mn(ipert) = (chi1**2 / mu0) * + $ (p1 + bvt_local(ipert)*bvz_local(ipert)) + ENDDO + +c----------------------------------------------------------------------- +c convert to spatial functions if requested. +c----------------------------------------------------------------------- + IF (PRESENT(curv_fun)) THEN + CALL iscdftb(mfac, mpert, curv_theta_fun, mthsurf, curv_mn) + curv_fun = curv_theta_fun + ENDIF + + RETURN + END SUBROUTINE gpeq_curvature +c----------------------------------------------------------------------- +c subprogram 13. gpeq_K. +c compute Bernstein K quantity (stability indicator). +c----------------------------------------------------------------------- + SUBROUTINE gpeq_K(psi, K_mn, K_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: K_mn + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) + $ :: K_fun + + INTEGER :: ipert, jpert, dm, m1 + REAL(r8) :: q, f1, p1, chi1, jac, jac1, bpfac, btfac + REAL(r8) :: bth, bze, jth, jze + COMPLEX(r8), DIMENSION(-mband:mband) :: g22, g33, g23 + COMPLEX(r8), DIMENSION(0:mthsurf) :: K_theta_fun + COMPLEX(r8), DIMENSION(mpert) :: bvt_local, bvz_local + COMPLEX(r8), DIMENSION(mpert) :: sigma_mn, shear_mn + + chi1 = psio * twopi + + CALL spline_eval(sq, psi, 1) + f1 = sq%f1(1) / twopi + p1 = sq%f1(2) + q = sq%f(4) + CALL bicube_eval(rzphi, psi, 0.0_r8, 0) + jac = rzphi%f(4) + jac1 = rzphi%fx(4) + + CALL cspline_eval(metric%cs, psi, 0) + g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) + g23(0:-mband:-1) = metric%cs%f(3*mband+3:4*mband+3) + g33(0:-mband:-1) = metric%cs%f(4*mband+4:5*mband+4) + g22(1:mband) = CONJG(g22(-1:-mband:-1)) + g23(1:mband) = CONJG(g23(-1:-mband:-1)) + g33(1:mband) = CONJG(g33(-1:-mband:-1)) + +c----------------------------------------------------------------------- +c compute K-related quantities in mode space. +c----------------------------------------------------------------------- + ipert = 0 + K_mn = 0.0_r8 + DO m1 = mlow, mhigh + ipert = ipert + 1 + DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) + jpert = ipert + dm + jth = -f1*twopi/jac + jze = q*jth - p1/chi1 + bth = chi1 / jac + bze = q * chi1 / jac + + K_mn(ipert) = K_mn(ipert) + + $ (g22(dm)*bth*jth + g33(dm)*bze*jze + + $ g23(dm)*(bth*jze + bze*jth)) * bwp_mn(jpert) + ENDDO + ENDDO + +c----------------------------------------------------------------------- +c convert to spatial functions if requested. +c----------------------------------------------------------------------- + IF (PRESENT(K_fun)) THEN + CALL iscdftb(mfac, mpert, K_theta_fun, mthsurf, K_mn) + K_fun = K_theta_fun + ENDIF + + RETURN + END SUBROUTINE gpeq_K +c----------------------------------------------------------------------- +c subprogram 13. gpeq_terms. +c compute integration terms for reconstruction diagnostics. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_terms(psi, mode, xspmn, term_mn, term_fun) + REAL(r8), INTENT(IN) :: psi + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(mpert), INTENT(IN) :: xspmn + COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: term_mn + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) + $ :: term_fun + + INTEGER :: ipert, jpert, dm, m1 + COMPLEX(r8), DIMENSION(0:mthsurf) :: term_theta_fun + +c----------------------------------------------------------------------- +c placeholder: compute integration terms. +c full implementation depends on integration methodology. +c----------------------------------------------------------------------- + term_mn = 0.0_r8 + +c----------------------------------------------------------------------- +c convert to spatial functions if requested. +c----------------------------------------------------------------------- + IF (PRESENT(term_fun)) THEN + CALL iscdftb(mfac, mpert, term_theta_fun, mthsurf, term_mn) + term_fun = term_theta_fun + ENDIF + + RETURN + END SUBROUTINE gpeq_terms +c----------------------------------------------------------------------- +c subprogram 14. gpeq_fcoords. c transform coordinates to dcon coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -773,7 +1145,7 @@ SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_fcoords c----------------------------------------------------------------------- -c subprogram 10. gpeq_fcoordsout. +c subprogram 14. gpeq_fcoordsout. c transform to dcon coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) @@ -836,7 +1208,7 @@ SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_fcoordsout c----------------------------------------------------------------------- -c subprogram 11. gpeq_bcoords. +c subprogram 15. gpeq_bcoords. c transform dcon coordinates to other coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -975,7 +1347,7 @@ SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_bcoords c----------------------------------------------------------------------- -c subprogram 12. gpeq_bcoordsout. +c subprogram 16. gpeq_bcoordsout. c transform dcon to other coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) @@ -1038,7 +1410,7 @@ SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_bcoordsout c----------------------------------------------------------------------- -c subprogram 13. gpeq_weight. +c subprogram 17. gpeq_weight. c switch between a function and a weighted function. c----------------------------------------------------------------------- SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) @@ -1109,7 +1481,7 @@ SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) RETURN END SUBROUTINE gpeq_weight c----------------------------------------------------------------------- -c subprogram 14. gpeq_rzpgrid. +c subprogram 18. gpeq_rzpgrid. c find magnetic coordinates for given rz coords. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) @@ -1212,7 +1584,7 @@ SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) RETURN END SUBROUTINE gpeq_rzpgrid c----------------------------------------------------------------------- -c subprogram 15. gpeq_rzpdiv. +c subprogram 19. gpeq_rzpdiv. c make zero divergence of rzphi functions. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) @@ -1278,7 +1650,7 @@ SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) RETURN END SUBROUTINE gpeq_rzpdiv c----------------------------------------------------------------------- -c subprogram 16. gpeq_alloc. +c subprogram 20. gpeq_alloc. c allocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_alloc @@ -1301,7 +1673,7 @@ SUBROUTINE gpeq_alloc RETURN END SUBROUTINE gpeq_alloc c----------------------------------------------------------------------- -c subprogram 17. gpeq_dealloc. +c subprogram 21. gpeq_dealloc. c deallocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_dealloc @@ -1319,7 +1691,7 @@ SUBROUTINE gpeq_dealloc RETURN END SUBROUTINE gpeq_dealloc c----------------------------------------------------------------------- -c subprogram 18. gpeq_interp_singsurf. +c subprogram 22. gpeq_interp_singsurf. c create spline for interpretation of solution near singular surface. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_singsurf(fsp_sol,spot,npsi) @@ -1394,7 +1766,7 @@ SUBROUTINE gpeq_interp_singsurf(fsp_sol,spot,npsi) RETURN END SUBROUTINE gpeq_interp_singsurf c----------------------------------------------------------------------- -c subprogram 19. gpeq_interp_sol. +c subprogram 23. gpeq_interp_sol. c get bwn at psi after calling gpeq_interp_singsurf. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_sol(fsp_sol,psi,interpbwn) diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index d872e64c1..79411e0da 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -73,6 +73,8 @@ MODULE gpglobal_mod $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, $ chi_mn,che_mn,kax_mn,sbno_mn,sbno_fun,edge_mn,edge_fun, $ chy_mn,chx_mn,chw_mn,kaw_mn,mutual_indev,mutual_indinvev + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: C_psi_mn,C_theta_mn, + $ C_zeta_mn,C1_mn,C2_mn,C3_mn COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: wt,wt0,chp_mn,kap_mn, $ permeabev,chimats,chemats,flxmats,kaxmats,singbno_mn, $ plas_indev,plas_indinvev,reluctev,indrelev,permeabsv, @@ -90,6 +92,9 @@ MODULE gpglobal_mod TYPE(spline_type) :: sq TYPE(bicube_type) :: psi_in,eqfun,rzphi + TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_shear + TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_curvature + TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_k TYPE(cspline_type) :: u1,u2,u3,u4,u5 TYPE(cspline_type) :: smats,tmats,xmats,ymats,zmats TYPE(fspline_type) :: metric @@ -152,6 +157,12 @@ SUBROUTINE gpec_dealloc $ plas_indevmats,permeabevmats,chxmats,chwmats, $ mutual_indmats,mutual_indevmats,mutual_indinvmats, $ mutual_indinvevmats) + IF(ALLOCATED(C_psi_mn)) DEALLOCATE(C_psi_mn) + IF(ALLOCATED(C_theta_mn)) DEALLOCATE(C_theta_mn) + IF(ALLOCATED(C_zeta_mn)) DEALLOCATE(C_zeta_mn) + IF(ALLOCATED(C1_mn)) DEALLOCATE(C1_mn) + IF(ALLOCATED(C2_mn)) DEALLOCATE(C2_mn) + IF(ALLOCATED(C3_mn)) DEALLOCATE(C3_mn) CALL spline_dealloc(sq) CALL bicube_dealloc(eqfun) diff --git a/gpec/gpout.f b/gpec/gpout.f index 68dcd25c3..aca9f7ba1 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -26,6 +26,7 @@ c 18. gpout_qrv c 19. gpout_init_netcdf c 20. gpout_close_netcdf +c 21. gpout_recon c----------------------------------------------------------------------- c subprogram 0. gpout_mod. c module declarations. @@ -6863,4 +6864,111 @@ SUBROUTINE gpout_close_netcdf RETURN END SUBROUTINE gpout_close_netcdf +c----------------------------------------------------------------------- +c subprogram 20. gpout_recon +c main entry point for reconstruction diagnostics. +c calls gpeq functions to compute shear, curvature, K quantities +c and stores results as bicubes for visualization/analysis. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi + REAL(r8) :: psi + COMPLEX(r8), DIMENSION(mpert) :: shear_mn, curv_mn, K_mn, + $ term_mn + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON: Starting reconstruction"// + $ " diagnostics" + IF(verbose) WRITE(*,*)"__________________________________________" + +c----------------------------------------------------------------------- +c allocate C vector arrays for EPF calculation. +c Storage: C_*_mn(mpert, 0:mpsi) for all psi levels. +c----------------------------------------------------------------------- + IF (.NOT. ALLOCATED(C_psi_mn)) THEN + ALLOCATE(C_psi_mn(mpert, 0:mpsi)) + ALLOCATE(C_theta_mn(mpert, 0:mpsi)) + ALLOCATE(C_zeta_mn(mpert, 0:mpsi)) + ALLOCATE(C1_mn(mpert, 0:mpsi)) + ALLOCATE(C2_mn(mpert, 0:mpsi)) + ALLOCATE(C3_mn(mpert, 0:mpsi)) + C_psi_mn = 0.0_r8 + C_theta_mn = 0.0_r8 + C_zeta_mn = 0.0_r8 + C1_mn = 0.0_r8 + C2_mn = 0.0_r8 + C3_mn = 0.0_r8 + ENDIF + +c----------------------------------------------------------------------- +c allocate bicubes for shear, curvature, K (placeholder allocation). +c actual bicube population should be done at each psi in gpeq. +c----------------------------------------------------------------------- + ALLOCATE(gpout_shear) + ALLOCATE(gpout_curvature) + ALLOCATE(gpout_k) + +c----------------------------------------------------------------------- +c main loop over all psi levels. +c compute gpeq equilibrium quantities at each psi and call gpeq_epf +c to populate C vectors for the EPF/DST diagnostics. +c----------------------------------------------------------------------- + CALL gpeq_alloc + DO ipsi = 0, mpsi + psi = psifac(ipsi) + +c Compute all necessary gpeq quantities at this psi level. + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_tangent(psi) + CALL gpeq_parallel(psi) + CALL gpeq_rzphi(psi) + +c Compute C vectors (covariant + contravariant) for this psi +c and store in C_*_mn(:, ipsi). +c gpeq_epf handles Q vector, current coupling, and Fourier transform. + CALL gpeq_epf(psi, ipsi) + CALL gpeq_dst(psi,ipsi) + ENDDO + CALL gpeq_dealloc + +c----------------------------------------------------------------------- +c C vectors now populated in gpglobal_mod for all psi levels. +c Next phase: compute EPF/DST from C vectors (placeholder). +c----------------------------------------------------------------------- + IF(ALLOCATED(gpout_shear)) THEN + IF(verbose) WRITE(*,*)" Shear bicube computed" + ENDIF + + IF(ALLOCATED(gpout_curvature)) THEN + IF(verbose) WRITE(*,*)" Curvature bicube computed" + ENDIF + + IF(ALLOCATED(gpout_k)) THEN + IF(verbose) WRITE(*,*)" K quantity bicube computed" + ENDIF + +c----------------------------------------------------------------------- +c completion message. +c----------------------------------------------------------------------- + IF(verbose) WRITE(*,*) + $ "GPDIAG_RECON: Reconstruction diagnostics complete" + IF(verbose) WRITE(*,*)" C vectors computed and stored in"// + $ " C_*_mn(mpert, 0:mpsi)" + IF(verbose) WRITE(*,*)"__________________________________________" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon + END MODULE gpout_mod diff --git a/input/gpec.in b/input/gpec.in index 3c35da4dc..44d90230e 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -85,6 +85,7 @@ out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. verbose=t ! Print run log to terminal + recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983] / &GPEC_DIAGNOSE timeit=f ! Print timer splits for major subroutines From 87c58e80989c85631b908cc5de65ad69f2666951 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 13 Mar 2026 12:01:04 +0900 Subject: [PATCH 111/305] GPEC - WIP - add docs for reconsturction (cherry picked from commit 8d98987a6d94b80fa8f16e960a887fb765b19fd3) --- docs/tex/recon/main.tex | 1414 +++++++++++++++++++++++++++++++++ docs/tex/recon/references.bib | 58 ++ 2 files changed, 1472 insertions(+) create mode 100644 docs/tex/recon/main.tex create mode 100644 docs/tex/recon/references.bib diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex new file mode 100644 index 000000000..9bd826a23 --- /dev/null +++ b/docs/tex/recon/main.tex @@ -0,0 +1,1414 @@ +\documentclass[12pt,a4paper]{article} + +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{geometry} +\geometry{margin=1in} +\usepackage{graphicx} +\usepackage{amsmath, amssymb} +\usepackage{hyperref} +\usepackage{cancel} +\usepackage{color} + +\title{DCON $recon flag$ document} +\author{Sunjae Lee} +\date{\today} + +\begin{document} + +\maketitle + +% \begin{abstract} +% This is the abstract of the document. Briefly describe the purpose and content here. +% \end{abstract} + +\section{Definition of covariant and contravariant basis} +The dcon context will be in black and \color{blue}the reference\cite{chance1992mhd} would be in blue. +\color{black}\subsection{Covariant basis} +\begin{equation} + \begin{aligned} + \vec{v}_1 &= \vec{\nabla}\theta \times \vec{\nabla}\zeta, & + \vec{v}_2 &= \vec{\nabla}\zeta \times \vec{\nabla}\psi, & + \vec{v}_3 &= \vec{\nabla}\psi \times \vec{\nabla}\theta, + \end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + \vec{v}_i &= v_{ij}\hat{e}_j, \qquad & + % g_{ij} &\equiv \color{red}\mathcal{J}\,\vec{v}_i \cdot \vec{v}_j = \mathcal{J}\,v_{ik}v_{kj} + \end{aligned} +\end{equation} + + +\begin{equation} +\begin{aligned} +v_{11} &= \vec{v}_1 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \psi}, \\[6pt] +v_{12} &= \vec{v}_1 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \psi}, \\[6pt] +v_{13} &= \vec{v}_1 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \psi}, , \\[6pt] +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +v_{21} &= \vec{v}_2 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \theta}, \\[6pt] +v_{22} &= \vec{v}_2 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \theta}, \\[6pt] +v_{23} &= \vec{v}_2 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \theta}, \\[6pt] +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +v_{31} &= \vec{v}_3 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \zeta} = 0, \\[6pt] +v_{32} &= \vec{v}_3 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \zeta} = 0, \\[6pt] +v_{33} &= \vec{v}_3 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \zeta} = \frac{2\pi R}{\mathcal{J}} +\end{aligned} +\end{equation} + + +\subsubsection{Contravariant basis} +\begin{equation} +\begin{aligned} +\vec{w}_1 &= \vec{\nabla}\psi, & +\vec{w}_2 &= \vec{\nabla}\theta, & +\vec{w}_3 &= \vec{\nabla}\zeta, +\end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + \vec{w}_i &= w_{ij}\hat{e}_j, \qquad & + \mathcal{J}\,\vec{v}_i \cdot \vec{w}_j &= \delta_{ij}. + \end{aligned} +\end{equation} + +% ==== All w_{ij}: cross-form -> v-components ==== +% Conventions: +% v_1 = ∇θ × ∇ζ, v_2 = ∇ζ × ∇ψ, v_3 = ∇ψ × ∇θ +% w_1 = ∇ψ, w_2 = ∇θ, w_3 = ∇ζ +% v_i = v_{ij} \hat e_j, w_i = w_{ij} \hat e_j +% General identity: w_{i\alpha} = J\, \varepsilon_{\alpha\beta\gamma}\, v_{j\beta} v_{k\gamma} +% with (i,j,k) cyclic: (1,2,3), (2,3,1), (3,1,2) +\begin{equation} + \begin{aligned} + % i = 1 + w_{11} + &= \vec\nabla\psi \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{22}v_{33}-v_{23}v_{32}) + = \frac{4\pi^{2} r R}{\mathcal{J}}\frac{\partial \eta}{\partial \theta}, \\[6pt] + w_{12} + &= \vec\nabla\psi \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{23}v_{31}-v_{21}v_{33}) + = -\,\frac{\pi R}{r\,\mathcal{J}}\frac{\partial r^{2}}{\partial \theta}, \\[6pt] + w_{13} + &= \vec\nabla\psi \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{21}v_{32}-v_{22}v_{31}) + = 0 + \end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + w_{21} + &= \vec\nabla\theta \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{32}v_{13}-v_{33}v_{12}) + = -\,\frac{4\pi^{2} r R}{\mathcal{J}}\frac{\partial \eta}{\partial \psi}, \\[6pt] + w_{22} + &= \vec\nabla\theta \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{33}v_{11}-v_{31}v_{13}) + = \frac{\pi R}{r\,\mathcal{J}}\frac{\partial r^{2}}{\partial \psi}, \\[6pt] + w_{23} + &= \vec\nabla\theta \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{31}v_{12}-v_{32}v_{11}) + = 0 + \end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + w_{31} + &= \vec\nabla\zeta \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{12}v_{23}-v_{13}v_{22}) + = \frac{2\pi r R}{\mathcal{J}}\!\left( + \frac{\partial \eta}{\partial \psi}\frac{\partial \phi}{\partial \theta} + - \frac{\partial \phi}{\partial \psi}\frac{\partial \eta}{\partial \theta} + \right), \\[6pt] + w_{32} + &= \vec\nabla\zeta \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{13}v_{21}-v_{11}v_{23}) + = \frac{R}{2r\,\mathcal{J}}\!\left( + \frac{\partial \phi}{\partial \psi}\frac{\partial r^{2}}{\partial \theta} + - \frac{\partial r^{2}}{\partial \psi}\frac{\partial \phi}{\partial \theta} + \right), \\[6pt] + w_{33} + &= \vec\nabla\zeta \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{11}v_{22}-v_{12}v_{21}) + = \frac{1}{2\pi R}. + \end{aligned} +\end{equation} + +\section{Coordinate system} + +The coordinate system comparing between DCON and reference\cite{chance1992mhd}. +In reference \cite{chance1992mhd}, $2 \pi \psi$ is poloidal flux +\begin{equation} +\Psi_p(\psi) = 2\pi \psi += \int_{0}^{2\pi} d\phi + \int_{\text{axis}}^{\psi} + \mathbf{B}_p \cdot d\mathbf{S} +\end{equation} + +In DCON, $\psi_{dcon}$, $\theta_{dcon}$, $\zeta_{dcon}$ are all normalized in [0,1]. $\chi_{dcon}$ representing the poloidal flux function and $\alpha_{dcon}$ for SFL angle. Also to scale the actual poloidal flux $\chi\prime_{dcon}$ is used.($\chi\prime_{dcon} = 2\pi(ssibry-ssimag)$) + +\begin{align} +\mathbf{B} &= \nabla \chi_{dcon} \times \nabla \alpha_{dcon}\\ +% +&= \chi\prime_{dcon} \nabla \psi_{dcon} \times \nabla \alpha_{dcon}\\ +% +&= \chi\prime_{dcon} \nabla \psi_{dcon} \times (q\nabla \theta - \nabla \zeta)\\ +% +&= f_{dcon} \nabla \phi + \nabla \phi \times \frac{\chi\prime_{dcon}}{2\pi} \nabla \psi_{dcon} \\ +% +&= \color{blue} \nabla \psi \times (- \nabla \alpha)\\ +% +&= \color{blue} g(\psi) \nabla \phi + \nabla \phi \times \nabla \psi \\ +% +\end{align} + +\begin{equation} + \alpha_{dcon} = \color{blue} {- \alpha / 2 \pi} \color{black}= q(\psi)\theta - \zeta +\end{equation} +\begin{equation} +g(\psi) \leftrightarrow (toroidal field functions) \leftrightarrow f_{dcon} +\end{equation} + +So as follows, \[ +\color{blue}{\psi} +\;\;\longleftrightarrow\;\; +\color{black}\frac{\chi_{\mathrm{dcon}}}{2\pi} +\] +% +\[ +\color{blue}{\psi} += \color{black}\frac{\chi'_{\mathrm{dcon}}}{2\pi}\, +\psi_{\mathrm{dcon}}. +\] +will established. +And also +\[ +{\chi} = \color{black}\chi'_{\mathrm{dcon}} \psi_{\mathrm{dcon}} +\] + +\begin{align} +\nabla \cdot \left( \frac{1}{R^2} \nabla \psi \right) +&= \nabla \cdot \left(\frac{1}{R^2}\, \frac{\chi\prime_{dcon}}{2\pi} \nabla\, \psi_{dcon} \right)\\ +&= \frac{1}{{2\pi}} \nabla \cdot \left(\frac{1}{R^2}\, \chi\prime_{dcon} \nabla\, \psi_{dcon} \right) \\ +&= \frac{1}{{2\pi}} \nabla \cdot \left(\frac{1}{R^2}\, \nabla \chi_{dcon} \right) \\ +&= \mathbf{J} \cdot \nabla \phi\\ +&= - \left( p'_{chance} + \frac{1}{R^2} g g' \right) +\end{align} + +\begin{equation} + R^2 \nabla \cdot \left( \frac{1}{R^2} \nabla \chi_{dcon} \right) = - 2\pi \left(R^2 p'_{chance} + g g' \right) = -(2\pi)\frac{2\pi}{\chi\prime_{dcon}}(ff'+ R^2p') +\end{equation} + + + +\subsection{Derivation of Eq.~(29) in GPEC note} + +\begin{equation} + \begin{aligned} + B + &= \chi\prime \nabla \psi \times (q\nabla \theta - \nabla \zeta) = \chi\prime q \vec{v}_3 + \chi\prime \vec{v}_2 \\ + &= \mathcal{J}(B^\psi \vec{v}_1 + B^\theta \vec{v}_2 + B^\zeta \vec{v}_3) \\ + &= (B_\psi \vec{w}_1 + B_\theta \vec{w}_2 + B_\zeta \vec{w}_3) +\end{aligned} +\end{equation} + +% \begin{equation} +% S \;=\; -\,\frac{\vec{B} \times \vec{\nabla}\psi}{\lvert \vec{\nabla}\psi \rvert^{2}} +% \;\cdot\; +% \left(\vec{\nabla} \times +% \frac{\vec{B} \times \vec{\nabla} \psi}{\lvert \vec{\nabla} \psi \rvert^{2}}\right) +% \end{equation} + +\begin{equation} +\begin{aligned} +B^\theta &\equiv \vec{B}\cdot \vec{w}_2 = \frac{\chi'}{\mathcal{J}}, +\qquad +B^\zeta \equiv \vec{B}\cdot \vec{w}_3 = \frac{q\,\chi'}{\mathcal{J}}, \\[4pt] +\vec{B} \times \vec{w}_\psi +&= \frac{1}{\mathcal{J}}\Big( B^\zeta\,\vec{v}_2 - B^\theta\,\vec{v}_3 \Big) += \frac{1}{\mathcal{J}}\left(\frac{q\,\chi'}{\mathcal{J}}\,\vec{v}_2 - \frac{\chi'}{\mathcal{J}}\,\vec{v}_3\right) \\ +&= \frac{\chi'}{\mathcal{J}^{2}}\Big( q\,\vec{v}_2 - \vec{v}_3 \Big), +\end{aligned} +\end{equation} + +\begin{equation} +(\vec{B} \times \vec{w}_1)^\psi = 0, \qquad +(\vec{B} \times \vec{w}_1)^\theta = \frac{q\,\chi'}{\mathcal{J}^{2}}, \qquad +(\vec{B} \times \vec{w}_1)^\zeta = -\,\frac{\chi'}{\mathcal{J}^{2}}. +\end{equation} + +\subsection{Metric Tensor and Index Operations} + +Given the definitions of the basis vectors where $\vec{v}_i = \vec{\nabla}\theta \times \vec{\nabla}\zeta$ (requiring the Jacobian $\mathcal{J}$ for physical dimensions) and $\vec{w}_i = \vec{\nabla}\psi$ (representing the dual basis directly), the metric tensors for lowering and raising indices are derived as follows. + +\subsubsection{Lowering Operator ($B^j \to B_i$)} +To transform contravariant components $B^j$ into covariant components $B_i$, we utilize the relation between the physical basis $\vec{e}_i$ and the defined basis $\vec{v}_i$, where $\vec{e}_i = \mathcal{J}\vec{v}_i$. + +Using the definition $\vec{B} = \mathcal{J} B^j \vec{v}_j$ and taking the dot product with $\vec{e}_i$: +\begin{equation} + \begin{aligned} + B_i &= \vec{B} \cdot \vec{e}_i \\ + &= (\mathcal{J} B^j \vec{v}_j) \cdot (\mathcal{J} \vec{v}_i) \\ + &= \mathcal{J}^2 (\vec{v}_i \cdot \vec{v}_j) B^j. + \end{aligned} +\end{equation} +Thus, the covariant metric tensor $g_{ij}$ must include the square of the Jacobian: +\begin{equation} + g_{ij} \equiv \mathcal{J}^2 (\vec{v}_i \cdot \vec{v}_j). +\end{equation} + +\subsubsection{Raising Operator ($B_j \to B^i$)} +To transform covariant components $B_j$ into contravariant components $B^i$, we use the gradient basis $\vec{w}_i$, which corresponds to the dual basis $\vec{e}^i$ (i.e., $\vec{e}^i = \vec{w}_i$). + +Using the definition $\vec{B} = B_j \vec{w}_j$ and taking the dot product with $\vec{w}_i$: +\begin{equation} + \begin{aligned} + B^i &= \vec{B} \cdot \vec{w}_i \\ + &= (B_j \vec{w}_j) \cdot \vec{w}_i \\ + &= (\vec{w}_i \cdot \vec{w}_j) B_j. + \end{aligned} +\end{equation} +Unlike the lowering operator, the contravariant metric tensor $g^{ij}$ does not require the Jacobian: +\begin{equation} + g^{ij} \equiv \vec{w}_i \cdot \vec{w}_j. +\end{equation} + +\subsection{Derivation of Eq. (35) in gpec note} + + +\[ +w_{11} = \frac{4 \pi^2 r R}{\mathcal{J}} \frac{\partial \eta}{\partial \theta}, +\quad +v_{22} = \frac{2 \pi r}{\mathcal{J}} \frac{\partial \eta}{\partial \theta}, +\quad +w_{12} = \frac{-\pi R}{r \mathcal{J}} \frac{\partial r^2}{\partial \theta}, +\quad +v_{21} = \frac{1}{2r \mathcal{J}} \frac{\partial r^2}{\partial \theta} +\] + +\[ +w_{11} = 2 \pi R v_{22}, +\qquad +w_{12} = -2 \pi R v_{21} +\] + + +\[ +q\bigl({\vec{\nabla}} \psi \cdot {\vec{\nabla}} \theta \bigr) +- {\vec{\nabla}} \psi \cdot {\vec{\nabla}} \zeta +\] +\[ += q\,(w_{11} w_{21} + w_{12} w_{22}) + - (w_{11} w_{31} + w_{12} w_{32}) +\] + +\[ += w_{11} (q w_{21} - w_{31}) + w_{12} (q w_{22} - w_{32}) +\] + +\[ += 2\pi R \Bigl[ + q v_{22} w_{21} + - q v_{21} w_{22} + - v_{22} w_{31} + + v_{21} w_{32} +\Bigr] +\] + +\[ += 2\pi R \mathcal{J} \Bigl[ + q v_{22} (\cancel{v_{32}v_{13}} - v_{33} v_{12}) + - q v_{21} (v_{33} v_{11} - \cancel{v_{31}v_{13}}) + - v_{22}(v_{23} v_{12} - v_{13} v_{22}) + + v_{21}(v_{13} v_{21} - v_{11} v_{23}) +\Bigr] +\] + + +\[ += 2\pi R \mathcal{J} \Bigl[ + - q v_{33} (v_{22} v_{12} + v_{21} v_{11}) + + v_{13}(v_{22}^2 + v_{21}^2) + - v_{23}(v_{22} v_{12} + v_{11} v_{21}) +\Bigr] +\] + +\[ += 2\pi R \mathcal{J} \Bigl[ + - (v_{22} v_{12} + v_{21} v_{11})(v_{23}+q v_{33}) + + v_{13}(v_{22}^2 + v_{21}^2) +\Bigr] +\] + +\section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} + +\begin{align} +\delta W_p &= \frac{1}{2} \int d\tau_p +\Big\{ |\mathbf{Q}|^2 - \mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* ++ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 ++ (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla P \Big\} \\ +&= \frac{1}{2} \int d\tau_p +\Big\{ |\mathbf{Q}_\perp|^2 ++ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \boldsymbol{\xi} \cdot \nabla P|^2 ++ \sigma\, \mathbf{B} \times \boldsymbol{\xi}^* \cdot \mathbf{Q} ++ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 +- 2 \boldsymbol{\xi} \cdot \nabla P\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} +\Big\} \\ +&= \frac{1}{2} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n \mathbf{j} \times \mathbf{n}|^2 ++ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 +- 2\,\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, \xi_n^2 +\Big\}\\ +&= \frac{1}{2} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n j \times \mathbf{n}|^2 ++ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 +- K\, \xi_n^2 +\Big\} +\end{align} + +The derivation of (35) could be found in \cite{RevModPhys.76.1071} and the original derivation of (36) is in \cite{Bernstein1983}. +The K term in (37) is defined in \cite{chance1992mhd}. + + +\begin{equation} +\sigma = \frac{\mathbf{j} \cdot \mathbf{B}}{B^2}, \hat{\mathbf n}=\frac{\nabla\psi}{|\nabla\psi|}, \mathbf Q \equiv \nabla\times(\boldsymbol{\xi}\times\mathbf B) (\delta\mathbf B = \mathbf Q) +\end{equation} + +\subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified units)} +In this section, we will see how (34) changes to (36). If we define $\xi$ as follows: +\begin{equation} +\boldsymbol{\xi}=a\,\nabla p + b\,\nabla\times\mathbf{B} + c\,\mathbf{B} +\end{equation} +then +\begin{equation} +\boldsymbol{\xi}\times\mathbf{B} += -a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p +\end{equation} +\begin{equation} +\boldsymbol{\xi}\times(\nabla\times\mathbf{B}) += -a\,(\nabla\times\mathbf{B})\times\nabla p - \mu_0 c\,\nabla p +\end{equation} + +Using the SI form of $\delta W_F$ ($1/4\pi\to 1/\mu_0$), +Gauss's theorem, and the boundary condition $\mathbf{n}\cdot\mathbf{B}=0$, +one finds +\begin{align} +2\mu_0 W_F +& = \int d^3r\Bigl\{ \mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++ |\mathbf{Q}|^2 \nonumber + (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla P \nonumber ++ \boldsymbol{\xi} \times \mathbf{Q} \cdot \nabla \times \mathbf{B} +\Bigr\}\\ +& = \int d^3r\Bigl\{\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2 +\nonumber\\ +&+ \mu_0 a(\nabla p)^2\nabla\cdot\bigl(a\nabla p + b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) +\nonumber\\ +&+ \bigl[a(\nabla\times\mathbf{B})\times\nabla p + \mu_0 c\,\nabla p\bigr]\cdot +\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) +\Bigr\}. +\end{align} +Since, +\begin{align} +&a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) +-\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr] +\nonumber\\ +&= -\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] +\nonumber\\ +&= -b\,\nabla\cdot\Bigl[a(\nabla p)^2(\nabla\times\mathbf{B})\Bigr] + -c\,\nabla\cdot\Bigl[a(\nabla p)^2\mathbf{B}\Bigr] +\nonumber\\ +&= -b\,\nabla\cdot\Bigl[(\nabla p)\times\bigl(a(\nabla\times\mathbf{B})\times\nabla p\bigr)\Bigr] + -c\,\nabla\cdot\Bigl[(\nabla p)\times\bigl(a\mathbf{B}\times\nabla p\bigr)\Bigr] +\nonumber\\ +&= b(\nabla p)\cdot\nabla\times\Bigl[a(\nabla\times\mathbf{B})\times\nabla p\Bigr] + + c(\nabla p)\cdot\nabla\times\Bigl[a\mathbf{B}\times\nabla p\Bigr] +\nonumber\\ +&= \nabla\cdot\Bigl(\bigl[a(\nabla\times\mathbf{B})\times\nabla p\bigr]\times b\nabla p\Bigr) + + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) +\nonumber\\ +&\qquad + c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p). +\end{align} +so, +\begin{align} +a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) +&= \nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr] + + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) +\nonumber\\ +&\quad + c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p). +\end{align} + +Using (44) to eliminate the divergence term involving $b,c$ gives +\begin{align} +2\mu_0 W_F += \int d^3r\Bigl\{ +&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2 +\nonumber\\ +&+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) ++ 2\mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) +\nonumber\\ +&- a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) +\Bigr\} +\nonumber\\ += \int d^3r\Bigl\{ +&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) + + a(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 +\nonumber\\ +&+ a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) +- a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 ++ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) +\Bigr\}. +\end{align} + +Define +\begin{equation} +\mathbf{n}=\frac{\nabla p}{|\nabla p|}. +\end{equation} +Then, since $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, +\begin{align} +&-a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 ++ a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) ++ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) +\nonumber\\ +&= -a^2(\nabla\times\mathbf{B})^2(\nabla p)^2 ++ a^2(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(\mathbf{B}\times\nabla p) +\nonumber\\ +&\quad + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot(\nabla a)\times(\mathbf{B}\times\nabla p) ++ \mu_0 a^2(\nabla p)^2\nabla^2 p ++ \mu_0 a(\nabla p)^2(\nabla a)\cdot\nabla p +\nonumber\\ +&= -a^2(\nabla\times\mathbf{B})^2(\nabla p)^2 ++ a^2(\nabla\times\mathbf{B})\times(\nabla p)\cdot\Bigl[(\nabla p)\cdot\nabla\mathbf{B} +- \mathbf{B}\cdot\nabla\nabla p + \mathbf{B}\nabla^2 p\Bigr] +\nonumber\\ +&\quad + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\Bigl[\mathbf{B}(\nabla a)\cdot\nabla p +- (\nabla p)\,\mathbf{B}\cdot\nabla a\Bigr] ++ \mu_0 a^2(\nabla p)^2\nabla^2 p ++ \mu_0 a(\nabla p)^2(\nabla a)\cdot\nabla p +\nonumber\\ +&= a^2(\nabla p)^2\Bigl[-(\nabla\times\mathbf{B})^2 ++ (\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{n}\cdot\nabla\mathbf{B} +- \mathbf{B}\cdot\nabla\mathbf{n})\Bigr]. +\end{align} + +Since $\nabla\cdot\mathbf{B}=0$, it follows that +\begin{equation} +0=\nabla(\mathbf{n}\cdot\mathbf{B}) +=\mathbf{n}\cdot\nabla\mathbf{B}+\mathbf{B}\cdot\nabla\mathbf{n} ++\mathbf{n}\times(\nabla\times\mathbf{B})+\mathbf{B}\times(\nabla\times\mathbf{n}), +\end{equation} +whence +\begin{align} +&-(\nabla\times\mathbf{B})^2 ++(\nabla\times\mathbf{B})\times\mathbf{n}\cdot\bigl[\mathbf{n}\cdot\nabla\mathbf{B} +-\mathbf{B}\cdot\nabla\mathbf{n}\bigr] +\nonumber\\ +&=-(\nabla\times\mathbf{B})^2 ++(\nabla\times\mathbf{B})\times\mathbf{n}\cdot\bigl[-2\mathbf{B}\cdot\nabla\mathbf{n} +-\mathbf{n}\times(\nabla\times\mathbf{B})-\mathbf{B}\times(\nabla\times\mathbf{n})\bigr] +\nonumber\\ +&=-(\nabla\times\mathbf{B})^2+\bigl[\mathbf{n}\times(\nabla\times\mathbf{B})\bigr]^2 +-2(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}) +\nonumber\\ +&\quad -(\nabla\times\mathbf{B})\cdot\mathbf{B}\,\mathbf{n}\cdot\nabla\times\mathbf{n} +-(\nabla\times\mathbf{B})\cdot(\nabla\times\mathbf{n})\,\mathbf{n}\cdot\mathbf{B} +\nonumber\\ +&=-2(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}). +\end{align} + +Since $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, it follows that +$\mathbf{n}\cdot\nabla\times\mathbf{B}=0$, whence +\begin{equation} +\bigl[\mathbf{n}\times(\nabla\times\mathbf{B})\bigr]^2 +=\mathbf{n}^2(\nabla\times\mathbf{B})^2-(\mathbf{n}\cdot\nabla\times\mathbf{B})^2 +=(\nabla\times\mathbf{B})^2. +\end{equation} + +Also, since $\mathbf{n}$ is a unit normal to the surfaces $p=\text{const}$, for any area +contained in such a surface, Stokes' theorem gives +\begin{equation} +\int d^2r\,\mathbf{n}\cdot\nabla\times\mathbf{n} +=\oint d\mathbf{r}\cdot\mathbf{n}=0, +\end{equation} +and since the surface is arbitrary, $\mathbf{n}\cdot\nabla\times\mathbf{n}=0$. + +Thus one can write, +\begin{equation} +2\mu_0 W_F +=\int_I d^3r\Bigl\{ +\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++\bigl[\mathbf{Q}+(\nabla\times\mathbf{B})\times\mathbf{n}\,(\mathbf{n}\cdot\boldsymbol{\xi})\bigr]^2 +-2(\mathbf{n}\cdot\boldsymbol{\xi})^2\,(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}) +\Bigr\}. +\end{equation} + +% If desired, replace curl B by current density j: +% \nabla\times\mathbf{B} = \mu_0\mathbf{j}, so the equilibrium is \nabla p=\mathbf{j}\times\mathbf{B}. +% \begin{align} +% K &= 2(\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \hat{n} \\ +% &=\frac{2}{|\nabla \psi|^2}(\mathbf{j} \times \nabla \psi) \cdot (\mathbf{B} \cdot \nabla) \nabla \psi \\ +% &=-(\frac{\mathbf{j} \cdot \mathbf{B}}{B^2})\mathbf{B} \times \nabla \psi\, \cdot\, (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'|\nabla \psi|^2 \kappa \cdot \nabla \psi \\ +% &=\frac{2}{|\nabla \chi|^2}(\mathbf{j} \times \nabla \psi) \cdot (\mathbf{B} \cdot \nabla) \nabla \chi +% \end{align} +\subsection{Derivation of K} +\color{blue} +\begin{align} +K +&= 2(\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \hat{n}\\ +&= \frac{2}{|\nabla \psi|} (\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \nabla \psi + \cancel{2(\mathbf{j} \times \hat{n}) \cdot [\nabla \psi (\mathbf{B} \cdot \nabla) (\frac{1}{|\nabla \psi|})]}\\ +&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j} \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B} + \nabla \psi \times \mathbf{j}\big]\\ +&= -\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} - (\mathbf{j} \times \nabla \psi)^2\big]\\ +&= +2j^2 -\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} \big]\\ +&= 2\sigma^2 B^2 + \underbrace{2 \frac{(\nabla p)^2}{B^2}-\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} \big]}_{\text{$K_1 + K_3$}} +\end{align} +\color{black} +If we decompose current density into perpendicular and parallel components to magnetic field, then the perpendicular and parallel contributions to K are given by + +\color{blue} +\begin{align} +K_3 +&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j}_\perp \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] +2 \frac{(\nabla p)^2}{B^2}\\ +&= -\frac{2}{|\nabla \psi|^2} ((\frac{p^\prime}{B^2} \mathbf{B}\times \nabla \psi) \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] + 2 \frac{(p^\prime \nabla \psi)^2}{B^2}\\ +&= \frac{2}{|\nabla \psi|^2}\frac{p^\prime}{B^2} [\mathbf{B} |\nabla \psi|^2 - \cancel{\nabla \psi (\nabla \psi \cdot \mathbf{B})}] \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] + 2 \frac{(p^\prime \nabla \psi)^2}{B^2}\\ +&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] +(\nabla p \cdot \nabla \psi)\} \\ +&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] +(\mathbf{j} \times \mathbf{B}) \cdot \nabla \psi\} \\ +&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] + \mathbf{B} \cdot (\nabla \psi \times \mathbf{j})\}\\ +&= -\frac{2p^\prime}{B^2} \mathbf{B} \cdot (\mathbf{B} \cdot \nabla) (\nabla \psi) = \frac{2p^\prime}{B^2}(\mathbf{B} \cdot \nabla) \cdot \mathbf{B} \cdot \nabla \psi \\ +&= \frac{2p^\prime}{B^2}((\mathbf{B} \cdot \nabla) \cdot \mathbf{B} \cdot \nabla \psi - \nabla \frac{B^2}{2}\cdot \nabla \psi) = +2 p^\prime [ (\frac{\mathbf{B}}{B} \cdot \nabla) \frac{\mathbf{B}}{B} ]\cdot \nabla \psi \\ +&= +2 p^\prime \boldsymbol{\kappa} \cdot \nabla \psi +\end{align} + +% \color{black} +% \begin{equation} +% \mathbf{B}=B \mathbf{b} +% \end{equation} + +\color{blue} +\begin{align} +K_1 +&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j}_\parallel \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] +\frac{2}{|\nabla \psi|^2}(\mathbf{j} \times \nabla \psi)^2\\ +&= -\frac{2}{|\nabla \psi|^2} (\frac{\mathbf{j} \cdot \mathbf{B}}{|\mathbf{B}|^2} \mathbf{B}\times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] \\ +&= -\sigma \frac{2}{|\nabla \psi|^2} (\mathbf{B} \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] +\end{align} + +\color{black} +Since we can use the vector identity, +\begin{equation} + \nabla\times(\mathbf a\times\mathbf c) += \mathbf a(\nabla\cdot\mathbf c)-\mathbf c(\nabla\cdot\mathbf a) ++(\mathbf c\cdot\nabla)\mathbf a-(\mathbf a\cdot\nabla)\mathbf c. +\end{equation} +\begin{equation} +\nabla \times (\mathbf{B} \times \nabla \psi) = \mathbf{B} (\nabla^2 \psi) - \cancel{\nabla \psi (\nabla \cdot \mathbf{B}) }+ (\nabla \psi \cdot \nabla) \mathbf{B} - (\mathbf{B} \cdot \nabla) (\nabla \psi) +\end{equation} + +\begin{equation} + \nabla(\mathbf a\cdot\mathbf b) + =(\mathbf a\cdot\nabla)\mathbf b+(\mathbf b\cdot\nabla)\mathbf a+\mathbf a\times(\nabla\times\mathbf b)+\mathbf b\times(\nabla\times\mathbf a) +\end{equation} +\begin{equation} +\cancel{\nabla(\mathbf B\cdot\nabla\psi)}=(\mathbf B\cdot\nabla)\nabla\psi+(\nabla\psi\cdot\nabla)\mathbf B+\nabla\psi\times(\nabla\times\mathbf B)+\cancel{\mathbf B\times(\nabla\times\nabla\psi)} +\end{equation} +\begin{equation} + (\mathbf{B} \cdot \nabla) (\nabla \psi) = - (\nabla \psi \cdot \nabla) \mathbf{B} - \nabla \psi \times \mathbf{j} +\end{equation} + +\begin{equation} +\nabla \times (\mathbf{B} \times \nabla \psi) = \mathbf{B} (\nabla^2 \psi) +2 (\nabla \psi \cdot \nabla) \mathbf{B} + \nabla \psi \times \mathbf{j} +\end{equation} +\begin{equation} +(\mathbf{B} \times \nabla \psi) \cdot [\nabla \times (\mathbf{B} \times \nabla \psi)] = \cancel{(\mathbf{B} \times \nabla \psi) \cdot \mathbf{B} (\nabla^2 \psi)} +2 (\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \cdot \nabla) \mathbf{B} + (\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \times \mathbf{j}) +\end{equation} +and rearranging the terms, we have +\begin{equation} + (\mathbf{B} \times \nabla \psi) \cdot \nabla \times (\frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + = \cancel{(\mathbf{B} \times \nabla \psi) \cdot \nabla (\frac{1}{|\nabla \psi|^2}) \times (\mathbf{B} \times \nabla \psi) }+ (\mathbf{B} \times \nabla \psi) \cdot \frac{1}{|\nabla \psi|^2} \nabla \times (\mathbf{B} \times \nabla \psi) +\end{equation} + +\color{blue} +\begin{align} +K_1 +&= -\sigma \frac{1}{|\nabla \psi|^2} (\mathbf{B} \times \nabla \psi) \cdot [\nabla \times (\mathbf{B} \times \nabla \psi)] +\sigma \frac{1}{|\nabla \psi|^2}(\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \times \mathbf{j})\\ +&= -\sigma \mathbf{B}\times \nabla \psi\cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \sigma \frac{1}{|\nabla \psi|^2} [-|\nabla \psi|^2 (\mathbf{B}\cdot\mathbf{j})+\cancel{(\mathbf{B}\cdot \nabla \psi)} \cdot (\nabla \psi \cdot \mathbf{j})]\\ +&= \sigma S |\nabla \psi|^2 - \sigma (\mathbf{B}\cdot\mathbf{j}) = \sigma S |\nabla \psi|^2 - \sigma^2 B^2 +\end{align} + +\color{blue} +\begin{align} + K + % &= - \mathbf{j} \cdot \mathbf{B} \frac{\mathbf{B}\times \nabla \psi}{|\mathbf{B}|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'(\frac{\mathbf{B}}{B} \cdot \nabla)\frac{\mathbf{B}}{B}\cdot \nabla \psi \\ + % &= - |\nabla \psi|^2 \sigma \frac{\mathbf{B}\times \nabla \psi}{|\nabla\psi|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'\boldsymbol{\kappa} \cdot \nabla \psi \\ + &= |\nabla \psi|^2 \sigma S + \sigma^2 B^2+ 2p'\boldsymbol{\kappa} \cdot \nabla \psi +\end{align} + +\color{black} +\subsection{Normalization of K between Chance and DCON coordinates} +\color{blue} +\begin{align} +K&=|\nabla \psi|^2 \sigma S + \sigma \mathbf{j} \cdot \mathbf{B} + 2p'\boldsymbol{\kappa} \cdot \nabla \psi +\end{align} + +\begin{align} + S&=-\frac{\mathbf{B}\times \nabla \psi}{|\nabla\psi|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) +\end{align} +\color{black} + +From this definition, the following relationships hold: +\begin{align} +\color{blue} |\nabla \psi|^2 +&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 |\nabla \psi_{\mathrm{dcon}}|^2 +\end{align} + +Substituting these relations into the original expression of \(K_{\mathrm{Chance}}\), we obtain +\begin{align} +K +&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 +|\nabla \psi_{\mathrm{dcon}}|^2 \sigma S ++ \sigma \mathbf{j}\!\cdot\!\mathbf{B} ++ 2p'\!\left(\!\boldsymbol{\kappa}\cdot\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\nabla \psi_{\mathrm{dcon}}\right) \\[4pt] +&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 +|\nabla \psi_{\mathrm{dcon}}|^2 \sigma S ++ \sigma \mathbf{j}\!\cdot\!\mathbf{B} ++ p'\,(\boldsymbol{\kappa}\!\cdot\!\nabla \psi_{\mathrm{dcon}}) \chi^\prime / \pi. +\end{align} + +Therefore, the final form of \(K\) in the DCON coordinate system is +\begin{equation} +\boxed{ +K += \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 +|\nabla \psi_{\mathrm{dcon}}|^2\,\sigma\,S ++ \sigma \mathbf{j}\!\cdot\!\mathbf{B} ++ p'\,(\boldsymbol{\kappa}\!\cdot\!\nabla \psi_{\mathrm{dcon}}) \chi^\prime / \pi. +} +\end{equation} + + +\subsection{Normalization of S between Chance and DCON coordinates} + +In Chance's formulation, the magnetic shear scalar \(S\) is defined as +\begin{equation} +\color{blue} +S += -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +\cdot +\left( +\nabla\times +\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +\right). +\end{equation} + +Using the DCON normalization of the flux coordinate, +\begin{equation} +\color{blue} +\psi +\color{black} += \frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\, +\psi_{\mathrm{dcon}}, +\qquad +\color{blue} \nabla \psi +\color{black} += \frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\, \nabla \psi_{\mathrm{dcon}}, +\end{equation} +we can rewrite each factor in terms of DCON variables. + +Hence, the normalized cross term becomes +\begin{equation} +\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} += \frac{2\pi}{\chi^\prime_{\mathrm{dcon}}}\, +\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2}. +\end{equation} + +Substituting this back into the original definition of \(S\), we find +\begin{align} +S +&= -\left(\frac{2\pi}{\chi^\prime_{\mathrm{dcon}}}\right)^2 +\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2} +\cdot +\left[ +\nabla\times +\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2} +\right]\\ +&=-(2\pi)^2 +\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}}{|\nabla\psi_{\mathrm{dcon}}*\chi^\prime_{dcon}|^2} +\cdot +\left[ +\nabla\times +\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}}{|\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}|^2} +\right]\\ +&= -\,(2\pi)^2 \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} +\cdot +\left( +\nabla\times +\frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} +\right) +\end{align} + +\begin{align} +\boldsymbol{\Lambda} +&\equiv \frac{\mathbf{B} \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} += \frac{(\nabla \chi_{dcon} \times \nabla \alpha) \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} \\[4pt] +&= \nabla \alpha +- \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon}. +\end{align} + +The curl becomes ($\alpha\, \text{represents}\, \alpha_{dcon}$ below this point) +\begin{align} +\nabla \times \boldsymbol{\Lambda} +&= -\,\nabla \times +\left[ +\frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} +\right] \\[4pt] +&= -\,\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +\times \nabla \chi_{dcon}, +\end{align} + +since $\nabla \times \nabla = 0$. The magnetic shear: + +\begin{align} +S +&= - (2\pi)^2\, \boldsymbol{\Lambda} \cdot (\nabla \times \boldsymbol{\Lambda}) \\[4pt] +&= - (2\pi)^2 +\left[ +\nabla \alpha +- \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} +\right] +\cdot +\left[ +\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +\times \nabla \chi_{dcon} +\right] \\[6pt] +&= (2\pi)^2\, \nabla \alpha \cdot +\left[ +\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +\times \nabla \chi_{dcon} +\right] \\[6pt] +&= (2\pi)^2\, (\nabla \chi_{dcon} \times \nabla \alpha) \cdot +\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) \\[6pt] +&= (2\pi)^2\, \mathbf{B} \cdot +\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right)\\ +&= \frac{(2\pi)^2}{\chi\prime_{dcon}}\, \mathbf{B} \cdot \nabla (\frac{\nabla \psi \cdot \nabla \alpha}{\nabla\psi \cdot \nabla\psi}) +\end{align} + + +The parallel gradient operator is: +\begin{equation} +\mathbf{B} \cdot \nabla += B^{\theta} \frac{\partial}{\partial \theta} ++ B^{\zeta} \frac{\partial}{\partial \zeta} += \frac{\chi'}{\mathcal{J}} +\left( +\frac{\partial}{\partial \theta} ++ q \frac{\partial}{\partial \zeta} +\right). +\end{equation} + +Also note +\begin{align} +\nabla \psi \cdot \nabla \alpha +&= \nabla \psi \cdot \nabla \big(q(\psi)\theta - \zeta\big) += q'(\nabla \psi \cdot \nabla \psi)\theta ++ \nabla \psi \cdot \big(q \nabla \theta - \nabla \zeta\big). +\end{align} + +Since $\partial / \partial \zeta = 0$ for a tokamak, one obtains: +\begin{align} +S +&= \frac{(2\pi)^2}{\mathcal{J}} +\frac{\partial}{\partial \theta} +\left[ +q' \theta ++ \frac{q(\nabla \psi \cdot \nabla \theta) +- (\nabla \psi \cdot \nabla \zeta)} +{(\nabla \psi \cdot \nabla \psi)} +\right] \\[6pt] +&= \frac{(2\pi)^2}{\mathcal{J}} +\left[ +q' ++ \frac{\partial}{\partial \theta} +\left( +\frac{q(\nabla \psi \cdot \nabla \theta) +- (\nabla \psi \cdot \nabla \zeta)} +{(\nabla \psi \cdot \nabla \psi)} +\right) +\right]. +\end{align} + +\subsection{$\xi^{\psi}$ in DCON berstein caculation} + +\begin{align} +\boxed{ +\boldsymbol{\xi}_\perp \cdot \hat{n} += \frac{\boldsymbol{\xi} \cdot \nabla \psi}{|\nabla \psi|} += \frac{\xi^\psi}{|\nabla \psi|}, +} +\qquad +\boxed{ +(\boldsymbol{\xi}_\perp \cdot \hat{n})^2 += \frac{(\xi^\psi)^2}{|\nabla \psi|^2}. +} +\end{align} + +\begin{equation} +\boxed{ +\boldsymbol{\xi}_\perp \cdot \hat{n} += \frac{1}{|\nabla \psi|} +\sum_{m,n} v_{m,n}(\psi)\, e^{2\pi i(m\theta - n\zeta)}. +} +\end{equation} + +\subsection{$\boldsymbol{\kappa}$ in DCON berstein caculation} + + +\section{Reconstruction of energy principle variables in DCON} +\subsection{Reconstruction v1} + +\begin{align} +\xi^\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ +\xi^s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) +\end{align} + +\begin{align} +\vec{j} &= \mathcal{J} (j^{\psi} \nabla \theta \times \nabla \zeta + j^{\theta} \nabla \zeta \times \nabla \psi + j^{\zeta} \nabla \psi \times \nabla \theta) \\ +&= \mathcal{J} ( j^{\psi} \vec{v_1} + j^{\theta} \vec{v_2} + j^{\zeta} \vec{v_3})\\ +&=\mathcal{J} (j^{\theta} \nabla \zeta \times \nabla \psi + j^{\zeta} \nabla \psi \times \nabla \theta) +\end{align} + +\begin{equation} +\vec{Q} = \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) = \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) +\end{equation} + +\begin{equation} +Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi^\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) +\end{equation} + +\begin{align} +Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi ^\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \zeta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) +\end{align} + +\begin{align} +Q^{\zeta} +&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi^\psi) + -\frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \theta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m} \sum_{n} + \left( \chi ^ \prime q \frac{\partial v_{m,n}(\psi)}{\partial \psi} + + \chi^\prime q^\prime v_{m,n}(\psi) + + 2\pi i m u_{m,n}(\psi) \right) + \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). +\end{align} + +\begin{equation} +\Xi_{\psi} += \texttt{u(:,:,1)} \quad \text{(Fortran, DCON)} += \texttt{u(:,:,1)} \quad \text{(Fortran, MATCH)}. +\end{equation} + +\begin{equation} +\Xi_{s} += \texttt{ud(:,:,2)} \quad \text{(Fortran, DCON)} += \texttt{u(:,:,4)} \quad \text{(Fortran, MATCH)}. +\end{equation} + +\begin{equation} +\Xi_{\psi}^\prime += \texttt{ud(:,:,1)} += \texttt{du(:,:,1)} \quad \text{(Fortran, DCON)} += \texttt{u(:,:,3)} \quad \text{(Fortran, MATCH)}. +\end{equation} + +And if you see the 331 line in ideal.f of MATCH, it constructs all the eigenvectors in variable v. +\begin{equation} +v_{m,n}(\psi) += v(:,1,mstep) +\end{equation} + +\begin{equation} +v_{m,n}^\prime(\psi) += v(:,3,mstep) +\end{equation} + +\begin{equation} +u_{m,n}(\psi) += v(:,4,mstep) +\end{equation} + +\begin{equation} + \mu_0 \vec{j} = \mathcal{J} (\mu_0 j^\theta \vec{v}_2 + \mu_0 j^\zeta \vec{v}_3) +\end{equation} +\begin{equation} + \mu_0 \vec{j} \times \nabla \psi = \mathcal{J} \mu_0 j^\theta (\vec{v}_2 \times \nabla \psi) + \mathcal{J} \mu_0 j^\zeta (\vec{v}_3 \times \nabla \psi) +\end{equation} + +\begin{equation} + j^\theta = j \cdot \nabla \theta = - 2 \pi f^\prime / \mathcal{J} +\end{equation} + +\begin{equation} + j^\zeta = j \cdot \nabla \zeta = - \mu_0 p^\prime / \chi^\prime - 2 \pi f^\prime q / \mathcal{J} +\end{equation} +Now we can reconstruct the cross term in the energy principle: + +\begin{align} +\vec{C} &\equiv \vec{Q} + (\vec{\xi} \cdot \hat{n} ) (\mu_0 \vec{j} \times \hat{n}) \\ +&=C^\psi \vec{e}_1 + C^\theta \vec{e}_2 + C^\zeta \vec{e}_3 = \mathcal{J} ( C^\psi \vec{v}_1 + C^\theta \vec{v}_2 + C^\zeta \vec{v}_3 )\\ +&=C_1 \nabla\psi + C_2 \nabla\theta + C_3 \nabla\zeta +\end{align} + +\begin{align} +C^\psi &= \vec{C} \cdot \nabla\psi = Q^\psi \\ +C^\theta &= \vec{C} \cdot \nabla\theta = Q^\theta + \frac{\xi^\psi}{|\nabla\psi|^2} \mu_0 \vec{j} \cdot (\nabla\psi \times \nabla\theta) \\ +C^\zeta &= \vec{C} \cdot \nabla\zeta = Q^\zeta + \frac{\xi^\psi}{|\nabla\psi|^2} \mu_0 \vec{j} \cdot (\nabla\psi \times \nabla\zeta) +\end{align} + +\begin{align} +C_1 &= Q_1 + \frac{\mathcal{J}\xi^\psi}{|\nabla\psi|^2} \left[ \mu_0 j^\theta (\nabla\psi \cdot \nabla\zeta) - \mu_0 j^\zeta (\nabla\psi \cdot \nabla\theta) \right] \\ +C_2 &= Q_2 + \mathcal{J}\xi^\psi \mu_0 j^\zeta \\ +C_3 &= Q_3 - \mathcal{J}\xi^\psi \mu_0 j^\theta +\end{align} + +\begin{align} +\delta W_{term1} &= \frac{1}{2} \int \bigl(\frac{|\vec{C}|^2}{\mu_0} \bigr) dx^3 \\ +&= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ \frac{|\vec{C}|^2}{\mu_0}\Bigr] +\end{align} + +\begin{align} +g_{11} &\equiv \mathcal{J}^2\,|\nabla\theta\times\nabla\zeta|^2, \\ +g_{22} &\equiv \mathcal{J}^2\,|\nabla\zeta\times\nabla\psi|^2, \\ +g_{33} &\equiv \mathcal{J}^2\,|\nabla\psi\times\nabla\theta|^2, \\ +g_{12} &\equiv \mathcal{J}^2\,(\nabla\theta\times\nabla\zeta)\cdot(\nabla\zeta\times\nabla\psi), \\ +g_{23} &\equiv \mathcal{J}^2\,(\nabla\zeta\times\nabla\psi)\cdot(\nabla\psi\times\nabla\theta), \\ +g_{31} &\equiv \mathcal{J}^2\,(\nabla\psi\times\nabla\theta)\cdot(\nabla\theta\times\nabla\zeta), +\end{align} +so that +\begin{equation} +\boxed{\;\mathcal{J}\,(\vec v_i\cdot\vec v_j)=\frac{g_{ij}}{\mathcal{J}}\;} +\end{equation} + +\begin{equation} +\boxed{ +\left\langle F \right\rangle_{mm'} +\equiv \int_{0}^{1} d\theta\; +\mathcal{J}(\psi,\theta)\, +\exp\!\bigl(2\pi i(m'-m)\theta\bigr)\, +F(\psi,\theta) +} +\end{equation} + +\begin{equation} +\boxed{ +(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}} \right\rangle_{mm'} +} +\end{equation} + +\begin{align} +\delta W_{\mathrm{term1}} +&= \frac{1}{2} \int \bigl(\frac{|\vec{C}|^2}{\mu_0} \bigr) dx^3 \\ +&= \frac{1}{2\mu_0}\int d\psi\,d\theta\,d\zeta\; +\mathcal{J}\,|\vec C|^2 += \frac{1}{2\mu_0}\int d\psi\,d\theta\,d\zeta\;\mathcal{J} \sum_{i,j=1}^{3} C^{i} C^{j*} g_{ij}\\ +&=\frac{1}{2\mu_0} \int d\psi d\theta\,d\zeta\ \left[ \mathcal{J} \sum_{i,j}\ \sum_{m,n}\ \sum_{m'n'}\ C_{m,n}^{i} C_{m',n'}^{j*} g_{ij} e^{2\pi i ((m-m')\theta -(n-n') \zeta)}\right] \\ +\end{align} + +Since $n$ is fixed in DCON so, +\begin{align} + \delta W_{n=fixed}&=\frac{1}{2\mu_0} \int d\psi\ \left[ \int d\theta \, \sum_{m, m'} e^{2\pi i(m-m')\theta} \mathcal{J}(\psi, \theta) \sum_{i,j} C_{m,n}^{i}(\psi, \theta) C_{m',n}^{j*}(\psi, \theta) g_{ij}(\psi, \theta) \right] \\ + &=\frac{1}{2\mu_0} \int d\psi \int_0^1 d\theta \; \mathcal{J}(\psi, \theta) \sum_{i,j} \left( \sum_m C_{m,n}^i e^{2\pi i m \theta} \right) \left( \sum_{m'} C_{m',n}^{j*} e^{-2\pi i m' \theta} \right) g_{ij}(\psi, \theta) +\end{align} + +The second term is $\nabla \cdot \vec{\xi} = 0$, it vanishes: +\begin{equation} +\boxed{ +\delta W_{\mathrm{term2}} += \frac{1}{2} \int \gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 dx^3 = \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ \gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 \Bigr] = 0. +} +\end{equation} + +For the last term in $\delta W$, +\begin{align} + \delta W_{\mathrm{term3}} &= \frac{1}{2} \int -K(\vec{\xi} \cdot \hat{n})^2 dx^3 \\ + &= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ -K(\vec{\xi} \cdot \hat{n})^2 \Bigr] \\ + &= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ -K(\frac{\vec{\xi}^{\psi}}{|\nabla\psi|})^2 \Bigr] +\end{align} + +% --- Term 3 as quadratic form --- +\begin{equation} +\delta W_{\mathrm{term3}} += -\frac{1}{2}\int d\psi\,d\theta\,d\zeta\; +\mathcal{J}\,\frac{K}{|\nabla\psi|^2}\; +\xi^\psi\,\xi^{\psi *}. +\end{equation} + +\begin{equation} +(K_{mm'}) \equiv \left\langle \frac{K}{|\nabla\psi|^2}\right\rangle_{mm'} += \int_0^1 d\theta\;\mathcal{J}(\psi,\theta)\, +e^{2\pi i(m'-m)\theta}\, +\frac{K}{|\nabla\psi|^2}. +\end{equation} + +\begin{equation} +\boxed{ +\delta W_{\mathrm{term3}} += -\frac{1}{2}\int d\psi \sum_{m,m'} +v^*_{m,n}(\psi)\; +(K_{mm'}(\psi))\; +v_{m',n}(\psi). +} +\end{equation} + +\subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta W$} + + +\begin{equation} +\begin{aligned} +2\delta W +&= \int \big[ \frac{|\vec{C}|^2}{\mu_0}- K(\vec{\xi} \cdot \hat{n})^2 +\gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 \big] dx^3\\ +&= \int d\psi d\theta d\zeta \mathcal{J} \Big\{ [ \nabla \xi_s \times \nabla \psi ++ (\nabla \theta \times \nabla \zeta)\,\mathcal{J}\,\mathbf{B}\cdot\nabla \xi_\psi +- \mathbf{B}\,\xi_\psi' \\ +&- +\left( + \chi'' \nabla \zeta \times \nabla \psi + + (q\chi')' \nabla \psi \times \nabla \theta +\right)\xi_\psi \\ +&+\frac{\xi_\psi}{|\nabla \psi|^2} [ +j^\theta +\big( +\nabla\psi(\nabla\zeta\cdot\nabla\psi) +- +\nabla\zeta|\nabla\psi|^2 +\big)+j^\zeta +\big( +\nabla\theta|\nabla\psi|^2 +- +\nabla\psi(\nabla\theta\cdot\nabla\psi) +\big)]]^2 \\ +&-K(\vec{\xi} \cdot \hat{n})^2 +\gamma p |\vec{\nabla} \cdot \vec{\xi}|^2\} +\end{aligned} +\end{equation} + +\subsubsection{Matrix representation of $\delta W_{\mathrm{term1}}$} +First we focus on the first term in $\delta W$. If we change each terms in $\vec{C}$ as follows: +\begin{equation} +\begin{aligned} +&\mathbf X \equiv \nabla \xi_s \times \nabla \psi,\\ +&\mathbf Y \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi,\\ +&\mathbf Z \equiv -\mathbf B\, \xi_\psi',\\ +&\mathbf U \equiv -\left( +\chi'' \nabla \zeta \times \nabla \psi ++ +(q\chi')' +\nabla \psi \times \nabla \theta +\right)\xi_\psi,\\ +&\mathbf V \equiv +\frac{\xi_\psi}{|\nabla \psi|^2} +\, \mu_0 \mathbf j \times \nabla \psi . +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +2\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [\frac{|\vec{C}|^2}{\mu_0}]\\ +&= \int d\psi d\theta d\zeta \mathcal{J}|\mathbf C|^2\\ +&= \int d\psi d\theta d\zeta \mathcal{J} \{ |\mathbf Q|^2+ +2\,\Re\!\left[ +(\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)\cdot\mathbf V^* +\right]+|\mathbf V|^2\} +\end{aligned} +\end{equation} + +The detailed derivation of the term already in \cite{10.1063/1.4958328} willbe skipped here. +The first term $|\mathbf X|^2$ can be written with matrix form as $\Xi_s^\dagger \mathbf A \Xi_s$ where $\Xi_s$ is a vector of the coefficients of the poloidal harmonics in $\xi_s$, and $\mathbf A$ is a matrix given by: +\begin{equation} +\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23} \mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33} \mathbf M\right]. +\end{equation} + +The second term $\mathbf X \cdot \mathbf Z + \mathbf Z \cdot \mathbf X$ can be written in matrix form as $\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi'^\dagger \mathbf B^\dagger \Xi_s$, where $\mathbf B$ is a matrix given by: +\begin{equation} + \mathbf B = -2\pi i\chi' [n(\mathbf G_{22} + q\mathbf G_{23}) + \mathbf M(\mathbf G_{23} + q\mathbf G_{33})] +\end{equation} + +The term $\mathbf X \cdot \mathbf Y + \mathbf Y \cdot \mathbf X$ as $\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s$, where $\mathbf C_1$ is a matrix given by: +\begin{equation} + \mathbf C_1 = -(2\pi)^2 \chi^\prime (\mathbf M \mathbf G_{31} + n \mathbf G_{12})\mathbf Q +\end{equation} + +The term $\mathbf X \cdot \mathbf U + \mathbf U \cdot \mathbf X$ as $\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s$, where $\mathbf C_2$ is a matrix given by: +\begin{equation} + \mathbf C_2 = -2\pi [\chi'' (n \mathbf G_{22} + \mathbf M \mathbf G_{23}) + (q\chi')' (n \mathbf G_{23} + \mathbf M \mathbf G_{33})] +\end{equation} + +The term $|\mathbf Y|^2$ as $\Xi_\psi^\dagger \mathbf H_1 \Xi_\psi$, where $\mathbf H_1$ is a matrix given by: +\begin{equation} + \mathbf H_1 = (2\pi\chi^{\prime} )^2 \mathbf Q\mathbf G_{11} \mathbf Q +\end{equation} + +The term $\mathbf Y \cdot \mathbf Z + \mathbf Z \cdot \mathbf Y$ as $\Xi_\psi^{\prime\dagger }\mathbf E_1 \Xi_\psi + \Xi_\psi^{\dagger} \mathbf E_1^\dagger \Xi_\psi^\prime$, where $\mathbf E_1$ is a matrix given by: +\begin{equation} + \mathbf E_1 = -2\pi i \chi^{\prime 2} (\mathbf G_{12}+q \mathbf G_{31})\mathbf Q +\end{equation} + +The term $\mathbf Y \cdot \mathbf U + \mathbf U \cdot \mathbf Y$ as $\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi$, where $\mathbf H_2$ is a matrix given by: +\begin{equation} + \mathbf H_2 = 2\pi i \chi' +\left[ +\chi''\left(\mathbf M \mathbf G_{12} -\mathbf G_{12} \mathbf M\right) ++ +(q\chi')'\left(\mathbf M \mathbf G_{31} -\mathbf G_{31} \mathbf M\right) +\right] +\end{equation} + +The term $|\mathbf Z|^2$ as $\Xi_\psi^{\prime \dagger} \mathbf D \Xi_\psi^\prime$, where $\mathbf D$ is a matrix given by: +\begin{equation} + \mathbf D = \chi'^2 +\left[ +(\mathbf{G}_{22} + q\,\mathbf{G}_{23}) ++ +q\,(\mathbf{G}_{23} + q\,\mathbf{G}_{33}) +\right] +\end{equation} + +The term $\mathbf Z \cdot \mathbf U + \mathbf U \cdot \mathbf Z$ as $\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime$, where $\mathbf E_2$ is a matrix given by: +\begin{equation} +\mathbf E_2 = \chi' +\left[ +\chi'' +\left( +\mathbf{G}_{22} ++ +q\,\mathbf{G}_{23} +\right) ++ +(q\chi')' +\left( +\mathbf{G}_{23} ++ +q\,\mathbf{G}_{33} +\right) +\right] +\end{equation} + +Finally, the term $|\mathbf U|^2$ as $\Xi_\psi^\dagger \mathbf H_3 \Xi_\psi$, where $\mathbf H_3$ is a matrix given by: +\begin{equation} +\mathbf{H}_3 += +\chi'' +\left[ +\chi''\,\mathbf{G}_{22} ++ +(q\chi')'\,\mathbf{G}_{23} +\right] ++ +(q\chi')' +\left[ +\chi''\,\mathbf{G}_{23} ++ +(q\chi')'\,\mathbf{G}_{33} +\right] +\end{equation} + +The new terms are the cross terms between $\mathbf V$ and the other four terms. The term $\mathbf X \cdot \mathbf V + \mathbf V \cdot \mathbf X$ can be written in matrix form as $\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s$, where $\mathbf C_3$ is a matrix given by: +\begin{equation} +(\nabla \xi_s \times \nabla \psi) \cdot (\frac{\xi_\psi}{|\nabla \psi|^2} \mu_0 \mathbf j \times \nabla \psi) = (\nabla \xi_s \cdot \mu_0 \mathbf j) - \cancel{(\nabla \xi_s \cdot \nabla \psi)(\mu_0 \mathbf j \cdot \nabla \psi)} +\end{equation} + +\begin{align} + \mathbf{X}^* \cdot \mathbf{V}&= +\left[ +-2\pi i\, \xi_s^* +\left( +m\, \nabla\theta \times \nabla\psi +- +n\, \nabla\zeta \times \nabla\psi +\right) +\right] +\cdot +\left[ +\frac{\xi_\psi}{|\nabla\psi|^2} +\,\mu_0 +\left( +\mathbf{j} \times \nabla\psi +\right) +\right]\\ +&= +(-2\pi i)\,\mu_0\, +\xi_s^*\, +\left( +m\, j_\theta +- +n\, j_\zeta +\right) +\xi_\psi\, +\end{align} +This term is same as the term $\xi_\psi \mathbf J \cdot \nabla \psi - \xi_s \mathbf J \cdot \nabla \psi$ in original form. +And the matrix form can be written as $\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s$, where $\mathbf C_3$ is a matrix given by: +\begin{equation} +\mathbf C_3 = 2\pi i (2\pi f^\prime \mathbf Q -n \frac{p^\prime}{\chi^\prime} \mathbf J) +\end{equation} + +The term $Z \cdot V + V \cdot Z$ can be written in matrix form as $\Xi_\psi^\dagger \mathbf E_3 \Xi_\psi^\prime + \Xi_\psi^{\dagger\prime} \mathbf E_3^\dagger \Xi_\psi + \Xi_\psi^\dagger \mathbf H_4 \Xi_\psi$, where $\mathbf E_3$ is a matrix given by: + +\begin{equation} +\mathbf Y^* += +(\nabla \theta \times \nabla \zeta)\, +(-2\pi i)\chi' +\sum_m +(m - nq)\, +v_m^*\, +e^{-2\pi i (m\theta - n\zeta)} . +\end{equation} + +\begin{equation} +\mathbf V += +\sum_{m'} +v_{m'}\, +e^{2\pi i (m'\theta - n\zeta)} +\, +\frac{\mu_0 \mathbf j \times \nabla \psi} +{|\nabla \psi|^2} . +\end{equation} + +\begin{equation} +\begin{aligned} +\mathbf Y^* \cdot \mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^* +(m - nq) +v_{m'} +e^{2\pi i (m' - m)\theta} +\; +\frac{ +(\nabla \theta \times \nabla \zeta) +\cdot +(\mu_0 \mathbf j \times \nabla \psi) +}{ +|\nabla \psi|^2 +}\\ +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^* +(m - nq) +v_{m'} +e^{2\pi i (m' - m)\theta} +\; +\mu_0 \, +\frac{ +j^{\theta} \left( \nabla \zeta \cdot \nabla \psi \right) +- +j^{\zeta} \left( \nabla \theta \cdot \nabla \psi \right) +}{ +|\nabla \psi|^2 +}. +\end{aligned} +\end{equation} + + +\section{Plasma Magnetic Potential on the Surface} + +The plasma magnetic potential $\Phi$ on the boundary surface is computed using covariant magnetic field components obtained from the perturbed equilibrium solution. The calculation involves four components that represent different aspects of the magnetic potential response. + +\subsection{Equilibrium Data} + +The equilibrium spline $s_q$ contains the following flux functions indexed by their position in the spline: +\begin{align} +s_q(\psi) &= \begin{cases} +s_q(\psi)_1 &: \psi_{\text{fac}} \; (\text{flux normalization}) \\ +s_q(\psi)_2 &: F(\psi) = R B_\phi \; (\text{poloidal current}) \\ +s_q(\psi)_3 &: \mu_0 p(\psi) \; (\text{pressure term}) \\ +s_q(\psi)_4 &: q(\psi) \; (\text{safety factor}) \\ +s_q(\psi)_5 &: \rho(\psi) \; (\text{auxiliary quantity}) +\end{cases} +\end{align} + +\subsection{Covariant Field Components Transformation} + +From the contravariant field components in mode space $(B_{w,p}^{(m)}, B_{m,t}^{(m)}, B_{m,z}^{(m)})$, the covariant components in physical space are obtained via inverse Fourier transform and metric tensor contraction: +\begin{align} +B_{v,\theta} &= \mathcal{F}^{-1}\{b_{v,t}^{(m)}\} \quad \text{(covariant theta component)}\\ +B_{v,\zeta} &= \mathcal{F}^{-1}\{b_{v,z}^{(m)}\} \quad \text{(covariant zeta component)}\\ +\Xi_\psi &= \mathcal{F}^{-1}\{x_{w,p}^{(m)}\} \quad \text{(poloidal flux perturbation)} +\end{align} + +where $\mathcal{F}^{-1}$ denotes the inverse discrete Fourier transform in the poloidal angle direction. + +\subsection{Magnetic Potential Components} + +The four components of the plasma magnetic potential on the surface are computed as follows: + +\begin{equation} +\begin{aligned} +\Phi_3(\theta) &= -\frac{B_{v,\zeta}(\theta)}{2\pi i n} +\\ +\Phi_1(\theta) &= \Phi_3(\theta) - \frac{\frac{dF}{d\psi} \cdot \Xi_\psi(\theta)}{2\pi i n \cdot \mathcal{J}(\theta)} +\\ +\Phi_4(\theta) &= B_{v,\theta}(\theta) +\\ +\Phi_2(\theta) &= \Phi_4(\theta) - \left( +\frac{dF}{d\psi} q(\psi) + \mathcal{J}(\theta) \frac{d(\mu_0 p)}{d\psi} \chi_1^{-1} +\right) \frac{\Xi_\psi(\theta)}{\mathcal{J}(\theta)} +\end{aligned} +\end{equation} + +Here: +\begin{itemize} +\item $\Phi_3$ represents the potential associated with toroidal field perturbation ($\zeta$ component) +\item $\Phi_1$ includes the coupling between flux perturbation and poloidal current derivative +\item $\Phi_4$ is the direct poloidal field response +\item $\Phi_2$ combines poloidal field response with pressure and current effects +\item $\mathcal{J}(\theta) = \partial(\mathbf{r}, \mathbf{z}, \phi)/\partial(\psi, \theta, \zeta)$ (Jacobian of coordinate transformation) +\item $\chi_1 = \psi_0 \cdot 2\pi$ (flux scaling factor) +\item $n$ is the toroidal mode number +\end{itemize} + +\subsection{Physical Interpretation} + +Each potential component couples different physical mechanisms: +\begin{enumerate} +\item \textbf{$\Phi_3$}: Direct response to toroidal field perturbation, normalized by mode number. + +\item \textbf{$\Phi_1$}: Additional correction accounting for the interaction between the poloidal flux perturbation $\Xi_\psi$ and the poloidal current profile $(dF/d\psi)$. The denominator $\mathcal{J}$ accounts for local metric effects. + +\item \textbf{$\Phi_4$}: The primary poloidal magnetic field response from the perturbed equilibrium. + +\item \textbf{$\Phi_2$}: Complex response combining: +\begin{itemize} +\item Gradient of poloidal current: $(dF/d\psi) q$ +\item Pressure gradient effect: $\mathcal{J} (d\mu_0 p/d\psi)\chi_1^{-1}$ +\item Both weighted by normalized flux perturbation: $\Xi_\psi/\mathcal{J}$ +\end{itemize} +\end{enumerate} + + +\bibliographystyle{plain} +\bibliography{references} + +\end{document} \ No newline at end of file diff --git a/docs/tex/recon/references.bib b/docs/tex/recon/references.bib new file mode 100644 index 000000000..deb3bc5fc --- /dev/null +++ b/docs/tex/recon/references.bib @@ -0,0 +1,58 @@ +@techreport{chance1992mhd, + author = {Chance, M. S. and Jardin, S. C. and Kessel, C. E. and Okabayashi, M. and others}, + title = {Mhd stability of tokamak plasmas}, + institution = {Princeton Univ., NJ (United States). Plasma Physics Lab}, + year = {1992}, + type = {Technical Report} +} +@article{RevModPhys.76.1071, + title = {Physics of magnetically confined plasmas}, + author = {Boozer, Allen H.}, + journal = {Rev. Mod. Phys.}, + volume = {76}, + issue = {4}, + pages = {1071--1141}, + numpages = {0}, + year = {2005}, + month = {Jan}, + publisher = {American Physical Society}, + doi = {10.1103/RevModPhys.76.1071}, + url = {https://link.aps.org/doi/10.1103/RevModPhys.76.1071} +} +@phdthesis{Park2009Ideal, + author = {Park, Jong-Kyu}, + title = {Ideal Perturbed Equilibria in Tokamaks}, + school = {Princeton University}, + year = {2009}, + month = {June}, + address = {Princeton, NJ}, + note = {Advisers: Jonathan E. Menard and Allen H. Boozer}, + department = {Department of Astrophysical Sciences, Program in Plasma Physics} +} +@incollection{Bernstein1983, + author = {Bernstein, I. B.}, + title = {Basic Plasma Physics I}, + booktitle = {Handbook of Plasma Physics, Volume 1}, + editor = {A. A. Galeev and R. N. Sudan}, + publisher = {North-Holland}, + address = {Amsterdam}, + year = {1983}, + pages = {421} +} +@article{10.1063/1.4958328, + author = {Glasser, A. H.}, + title = {The direct criterion of Newcomb for the ideal MHD stability of an axisymmetric toroidal plasma}, + journal = {Physics of Plasmas}, + volume = {23}, + number = {7}, + pages = {072505}, + year = {2016}, + month = {07}, + abstract = {A method is presented for determining the ideal magnetohydrodynamic stability of an axisymmetric toroidal plasma, based on a toroidal generalization of the method developed by Newcomb for fixed-boundary modes in a cylindrical plasma. For toroidal mode number n≠0, the stability problem is reduced to the numerical integration of a high-order complex system of ordinary differential equations, the Euler-Lagrange equation for extremizing the potential energy, for the coupled amplitudes of poloidal harmonics m as a function of the radial coordinate ψ in a straight-fieldline flux coordinate system. Unlike the cylindrical case, different poloidal harmonics couple to each other, which introduces coupling between adjacent singular intervals. A boundary condition is used at each singular surface, where m = nq and q(ψ) is the safety factor, to cross the singular surface and continue the solutions beyond it. Fixed-boundary instability is indicated by the vanishing of a real determinant of a Hermitian complex matrix constructed from the fundamental matrix of solutions, the generalization of Newcomb's crossing criterion. In the absence of fixed-boundary instabilities, an M × M plasma response matrix WP, with M the number of poloidal harmonics used, is constructed from the Euler-Lagrange solutions at the plasma-vacuum boundary. This is added to a vacuum response matrix WV to form a total response matrix WT. The existence of negative eigenvalues of WT indicates the presence of free-boundary instabilities. The method is implemented in the fast and accurate DCON code.}, + issn = {1070-664X}, + doi = {10.1063/1.4958328}, + url = {https://doi.org/10.1063/1.4958328}, + eprint = {https://pubs.aip.org/aip/pop/article-pdf/doi/10.1063/1.4958328/14131506/072505_1_online.pdf}, +} + + From af7bb2a6a83b2a536f9b80611f5d03567fb94a9c Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 18 Mar 2026 14:57:22 +0900 Subject: [PATCH 112/305] GPEC - WIP - modify shear calculation and add some C value calculation logic (cherry picked from commit cc25b1779f73e6b47a8e8a0121c27f91181dde7e) --- gpec/gpeq.f | 448 ++++++++++++++++++++++++++++++++++++------------ gpec/gpglobal.F | 25 ++- gpec/gpout.f | 335 +++++++++++++++++++++++++++++++++--- 3 files changed, 662 insertions(+), 146 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 30ecc0f8c..dc89d7d94 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -645,26 +645,10 @@ SUBROUTINE gpeq_surface(psi) RETURN END SUBROUTINE gpeq_surface c----------------------------------------------------------------------- -c subprogram 9. gpeq_epf. +c subprogram 9. gpeq_c. c compute C vector (covariant + contravariant) for EPF calculation. -c -c Stores mode-space coefficients for all psi levels: -c C_psi_mn(mpert, 0:mpsi) - covariant psi component -c C_theta_mn(mpert, 0:mpsi) - covariant theta component -c C_zeta_mn(mpert, 0:mpsi) - covariant zeta component -c C1_mn(mpert, 0:mpsi) - contravariant scalar C_1 -c C2_mn(mpert, 0:mpsi) - contravariant scalar C_2 -c C3_mn(mpert, 0:mpsi) - contravariant scalar C_3 -c -c INPUT: psi (flux coordinate) -c ipsi (psi index for storage) -c -c REFERENCES: -c - Q = ξ × (∇ × B) in energy principle -c - C = Q + ξ·n̂ (μ₀ j × n̂) where n̂ = ∇ψ/|∇ψ| -c - Formulas from main.tex section "Reconstruction v1" c----------------------------------------------------------------------- - SUBROUTINE gpeq_epf(psi, ipsi) + SUBROUTINE gpeq_c(psi, ipsi) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- @@ -674,27 +658,33 @@ SUBROUTINE gpeq_epf(psi, ipsi) INTEGER :: itheta, ipert, jpert, m, m1, dm REAL(r8) :: q, q1, f1raw, p1, chi1, jac, delpsi - REAL(r8) :: eta, rfac + REAL(r8) :: eta, rfac, singfac REAL(r8), DIMENSION(0:mthsurf) :: jacs, dphi, r_vec, z_vec -c Metric tensor (COMPLEX - see gpeq_cova) + COMPLEX(r8), DIMENSION(-mband:mband) :: g11, g12, g22, g23, $ g31, g33 -c Eigenfunction components in spatial representation - COMPLEX(r8), DIMENSION(0:mthsurf) :: xi_psi_fun, xi_s_fun, - $ xwp_fun, xmt_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xi_psi_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bwt_fun, + $ bwz_fun, bvp_fun, bvt_fun, bvz_fun, + $ cvp_fun_p, cvt_fun_p, cvz_fun_p, xno_fun -c C vector components (covariant) in spatial rep - COMPLEX(r8), DIMENSION(0:mthsurf) :: C_psi_fun, C_theta_fun, - $ C_zeta_fun, C1_fun, C2_fun, C3_fun +c Supporting variables for Fourier reconstruction + REAL(r8) :: jwt, jwz c Supporting functions - REAL(r8), DIMENSION(0:mthsurf) :: delpsi_arr + REAL(r8), DIMENSION(0:mthsurf) :: delpsi_arr, dpdt_arr, dpdz_arr + +c Local temporal arrays for computation + COMPLEX(r8), DIMENSION(0:mthsurf) :: qwp_tmp, qwt_tmp, qwz_tmp + COMPLEX(r8), DIMENSION(0:mthsurf) :: qvp_tmp, qvt_tmp, qvz_tmp + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_tmp, cwt_tmp, cwz_tmp + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_tmp, cvt_tmp, cvz_tmp IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi c----------------------------------------------------------------------- -c 0) Setup: evaluate equilibrium data at this psi. +c 1) Setup: equilibrium and metric at this psi. c----------------------------------------------------------------------- CALL spline_eval(sq, psi, 1) q = sq%f(4) @@ -702,8 +692,7 @@ SUBROUTINE gpeq_epf(psi, ipsi) f1raw = sq%f1(1) p1 = sq%f1(2) chi1 = psio * twopi - -c Get metric tensor (same as gpeq_cova) + CALL cspline_eval(metric%cs, psi, 0) g11(0:-mband:-1) = metric%cs%f(1:mband+1) g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) @@ -711,17 +700,28 @@ SUBROUTINE gpeq_epf(psi, ipsi) g23(0:-mband:-1) = metric%cs%f(3*mband+4:4*mband+4) g31(0:-mband:-1) = metric%cs%f(4*mband+5:5*mband+5) g12(0:-mband:-1) = metric%cs%f(5*mband+6:6*mband+6) - -c Fill upper half of metric arrays (conjugate symmetry) + g11(1:mband) = CONJG(g11(-1:-mband:-1)) g22(1:mband) = CONJG(g22(-1:-mband:-1)) g33(1:mband) = CONJG(g33(-1:-mband:-1)) g23(1:mband) = CONJG(g23(-1:-mband:-1)) g31(1:mband) = CONJG(g31(-1:-mband:-1)) g12(1:mband) = CONJG(g12(-1:-mband:-1)) +c----------------------------------------------------------------------- +c 3) Reconstruct eigenfunctions in spatial representation. +c----------------------------------------------------------------------- + CALL iscdftb(mfac, mpert, xi_psi_fun, mthsurf, xsp_mn) + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bwt_fun, mthsurf, bwt_mn) + CALL iscdftb(mfac, mpert, bwz_fun, mthsurf, bwz_mn) + + CALL iscdftb(mfac,mpert,bvp_fun,mthsurf,bvp_mn) + CALL iscdftb(mfac,mpert,bvt_fun,mthsurf,bvt_mn) + CALL iscdftb(mfac,mpert,bvz_fun,mthsurf,bvz_mn) + CALL iscdftb(mfac,mpert,xno_fun,mthsurf,xno_mn) c----------------------------------------------------------------------- -c 1) Compute theta grid: |∇ψ|² and coordinate values. +c 4) Extract contravariant components: divide by jacobian. c----------------------------------------------------------------------- DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) @@ -732,56 +732,192 @@ SUBROUTINE gpeq_epf(psi, ipsi) z_vec(itheta) = zo + rfac*SIN(eta) jacs(itheta) = jac dphi(itheta) = rzphi%f(3) - -c Compute |∇ψ|² from contravariant metric + delpsi_arr(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) + w(1,1) = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_vec(itheta) $ /jac w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) - delpsi_arr(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) - ENDDO + + w(2,1) = -twopi**2*rfac*r_vec(itheta)*rzphi%fx(2)/jac + w(2,2) = pi*r_vec(itheta)*rzphi%fx(1)/(rfac*jac) + + w(3,1) = (twopi*r_vec(itheta)*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3)-rzphi%fx(3)*(1+rzphi%fy(2))) + w(3,2) = (r_vec(itheta)/(2*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1)-rzphi%fx(1)*rzphi%fy(3)) + dpdt_arr(itheta) = w(1,1)*w(2,1) + w(1,2)*w(2,2) + dpdz_arr(itheta) = w(1,1)*w(3,1) + w(1,2)*w(3,2) + + qwp_tmp(itheta) = bwp_fun(itheta) / CMPLX(jac, 0.0_r8, r8) + qwt_tmp(itheta) = bwt_fun(itheta) / CMPLX(jac, 0.0_r8, r8) + qwz_tmp(itheta) = bwz_fun(itheta) / CMPLX(jac, 0.0_r8, r8) + + qvp_tmp(itheta) = bvp_fun(itheta) + qvt_tmp(itheta) = bvt_fun(itheta) + qvz_tmp(itheta) = bvz_fun(itheta) + + ENDDO c----------------------------------------------------------------------- -c 2) Reconstruct eigenfunctions in spatial representation. -c Use iscdftb: mode-space coefficients → spatial functions +c 6) Apply physics method: add j × ξ term to covariant C. c----------------------------------------------------------------------- - CALL iscdftb(mfac, mpert, xi_psi_fun, mthsurf, xsp_mn) - CALL iscdftb(mfac, mpert, xi_s_fun, mthsurf, xss_mn) - CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) - CALL iscdftb(mfac, mpert, xmt_fun, mthsurf, xmt_mn) + DO itheta = 0, mthsurf + jac = jacs(itheta) + delpsi = delpsi_arr(itheta)**2 + jwt = - sq%f1(3) / jac + jwz = - sq%f1(2) / chi1 - sq%f1(3) * q / jac + + cwp_tmp(itheta) = qwp_tmp(itheta) + cwt_tmp(itheta) = qwt_tmp(itheta)+xno_fun(itheta)*jwz / delpsi + cwz_tmp(itheta) = qwz_tmp(itheta)-xno_fun(itheta)*jwt / delpsi + + cvp_fun_p(itheta) = qvp_tmp(itheta)+ jac*xno_fun(itheta)/delpsi + $ * (jwt*(dpdz_arr(itheta)) + jwz*(dpdt_arr(itheta))) + cvt_fun_p(itheta) = qvt_tmp(itheta)+ jac*xi_psi_fun(itheta)*jwz + cvz_fun_p(itheta) = qvz_tmp(itheta)- jac*xi_psi_fun(itheta)*jwt + ENDDO c----------------------------------------------------------------------- -c 3) Construct C vectors in spatial representation. -c For now: simplified version using existing eigenfunctions. -c Full version would include current coupling terms. +c 7) Metric contraction: C covariant = g_ij * C contravariant. c----------------------------------------------------------------------- DO itheta = 0, mthsurf - C_psi_fun(itheta) = xwp_fun(itheta) - C_theta_fun(itheta) = xmt_fun(itheta) - C_zeta_fun(itheta) = CMPLX(0.0_r8, 0.0_r8, r8) - -c Contravariant representation (same as covariant for now) - C1_fun(itheta) = C_psi_fun(itheta) - C2_fun(itheta) = C_theta_fun(itheta) - C3_fun(itheta) = C_zeta_fun(itheta) + cvp_tmp(itheta) = g11(0)*cwp_tmp(itheta) + + $ g12(0)*cwt_tmp(itheta) + g31(0)*cwz_tmp(itheta) + cvt_tmp(itheta) = g12(0)*cwp_tmp(itheta) + + $ g22(0)*cwt_tmp(itheta) + g23(0)*cwz_tmp(itheta) + cvz_tmp(itheta) = g31(0)*cwp_tmp(itheta) + + $ g23(0)*cwt_tmp(itheta) + g33(0)*cwz_tmp(itheta) ENDDO +c----------------------------------------------------------------------- +c 8) Store results to global arrays (spatial and mode space). +c----------------------------------------------------------------------- + cwp_fun(0:mthsurf, ipsi) = cwp_tmp(0:mthsurf) + cwt_fun(0:mthsurf, ipsi) = cwt_tmp(0:mthsurf) + cwz_fun(0:mthsurf, ipsi) = cwz_tmp(0:mthsurf) + + cvp_fun(0:mthsurf, ipsi) = cvp_tmp(0:mthsurf) + cvt_fun(0:mthsurf, ipsi) = cvt_tmp(0:mthsurf) + cvz_fun(0:mthsurf, ipsi) = cvz_tmp(0:mthsurf) + + cvp_funp(0:mthsurf, ipsi) = cvp_fun_p(0:mthsurf) + cvt_funp(0:mthsurf, ipsi) = cvt_fun_p(0:mthsurf) + cvz_funp(0:mthsurf, ipsi) = cvz_fun_p(0:mthsurf) + + qwp_fun(0:mthsurf, ipsi) = qwp_tmp(0:mthsurf) + qwt_fun(0:mthsurf, ipsi) = qwt_tmp(0:mthsurf) + qwz_fun(0:mthsurf, ipsi) = qwz_tmp(0:mthsurf) + + qvp_fun(0:mthsurf, ipsi) = qvp_tmp(0:mthsurf) + qvt_fun(0:mthsurf, ipsi) = qvt_tmp(0:mthsurf) + qvz_fun(0:mthsurf, ipsi) = qvz_tmp(0:mthsurf) + + + CALL iscdftf(mfac, mpert, cwp_tmp, mthsurf, + $ cwp_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, cwt_tmp, mthsurf, + $ cwt_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, cwz_tmp, mthsurf, + $ cwz_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, cvp_fun_p, mthsurf, + $ cvp_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, cvt_fun_p, mthsurf, + $ cvt_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, cvz_fun_p, mthsurf, + $ cvz_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qwp_tmp, mthsurf, + $ qwp_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qwt_tmp, mthsurf, + $ qwt_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qwz_tmp, mthsurf, + $ qwz_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qvp_tmp, mthsurf, + $ qvp_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qvt_tmp, mthsurf, + $ qvt_mn(1:mpert, ipsi)) + CALL iscdftf(mfac, mpert, qvz_tmp, mthsurf, + $ qvz_mn(1:mpert, ipsi)) + + IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_c +c----------------------------------------------------------------------- +c subprogram 9b. gpeq_epf. +c compute C norm squared: three forms (cw only, cv only, mixed). +c----------------------------------------------------------------------- + SUBROUTINE gpeq_epf(psi, ipsi) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + INTEGER, INTENT(IN) :: ipsi + + INTEGER :: itheta + + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_tmp, cwt_tmp, cwz_tmp + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_tmp, cvt_tmp, cvz_tmp + + COMPLEX(r8), DIMENSION(0:mthsurf) :: c2_cw_tmp,c2_cv_tmp, + $ c2_cvw_tmp + + IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi +c----------------------------------------------------------------------- +c Call gpeq_c to compute C components first. +c----------------------------------------------------------------------- + CALL gpeq_c(psi, ipsi) + +c----------------------------------------------------------------------- +c Extract C components from global storage. +c----------------------------------------------------------------------- + cwp_tmp(0:mthsurf) = cwp_fun(0:mthsurf, ipsi) + cwt_tmp(0:mthsurf) = cwt_fun(0:mthsurf, ipsi) + cwz_tmp(0:mthsurf) = cwz_fun(0:mthsurf, ipsi) + + cvp_tmp(0:mthsurf) = cvp_fun(0:mthsurf, ipsi) + cvt_tmp(0:mthsurf) = cvt_fun(0:mthsurf, ipsi) + cvz_tmp(0:mthsurf) = cvz_fun(0:mthsurf, ipsi) c----------------------------------------------------------------------- -c 4) Fourier transform C components to mode-space and store. -c Use iscdftf: spatial functions → mode-space coefficients -c----------------------------------------------------------------------- - CALL iscdftf(mfac, mpert, C_psi_fun, mthsurf, - $ C_psi_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, C_theta_fun, mthsurf, - $ C_theta_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, C_zeta_fun, mthsurf, - $ C_zeta_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, C1_fun, mthsurf, - $ C1_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, C2_fun, mthsurf, - $ C2_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, C3_fun, mthsurf, - $ C3_mn(1:mpert, ipsi)) +c 1) C^2 from contravariant components only. +c Note: |C^i|^2 = C^i C^i (no metric needed for this form) +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + c2_cw_tmp(itheta) = cwp_tmp(itheta)*cwp_tmp(itheta)+ + $ cwt_tmp(itheta)*cwt_tmp(itheta)+ + $ cwz_tmp(itheta)*cwz_tmp(itheta) + ENDDO + +c----------------------------------------------------------------------- +c 2) C^2 from covariant components only. +c Note: |C_i|^2 = C_i C_i (metric already included in cvp_tmp) +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + c2_cv_tmp(itheta) = cvp_tmp(itheta)*cvp_tmp(itheta)+ + $ cvt_tmp(itheta)*cvt_tmp(itheta)+ + $ cvz_tmp(itheta)*cvz_tmp(itheta) + ENDDO +c----------------------------------------------------------------------- +c 3) C^2 using both covariant and contravariant. +c Note: C_i C^i = CONJG(cvp) * cwp + CONJG(cvt) * cwt + ... +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + c2_cvw_tmp(itheta) = CONJG(cvp_tmp(itheta))* + $ cwp_tmp(itheta)+CONJG(cvt_tmp(itheta))*cwt_tmp(itheta)+ + $ CONJG(cvz_tmp(itheta))*cwz_tmp(itheta) + ENDDO + +c----------------------------------------------------------------------- +c Store results to global arrays. +c----------------------------------------------------------------------- + IF (ipsi <= mpsi) THEN + c2_cw_fun(0:mthsurf, ipsi) = c2_cw_tmp(0:mthsurf) + c2_cv_fun(0:mthsurf, ipsi) = c2_cv_tmp(0:mthsurf) + c2_cvw_fun(0:mthsurf, ipsi) = c2_cvw_tmp(0:mthsurf) + ENDIF + IF(debug_flag) PRINT *, "->Leaving gpeq_epf at ipsi=", ipsi c----------------------------------------------------------------------- c terminate. @@ -790,11 +926,27 @@ SUBROUTINE gpeq_epf(psi, ipsi) END SUBROUTINE gpeq_epf c----------------------------------------------------------------------- c subprogram 10. gpeq_dst. -c compute DST mode coefficients and spatial functions. +c compute DST(psi,theta) = K(psi,theta) * CONJG(xi_normal(psi,theta)). +c Store both theta-space and mode-space for later integration. c----------------------------------------------------------------------- SUBROUTINE gpeq_dst(psi, ipsi) REAL(r8), INTENT(IN) :: psi INTEGER, INTENT(IN) :: ipsi + COMPLEX(r8), DIMENSION(mpert) :: K_mn + COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xi_normal_fun, + $ dst_theta_fun + +c----------------------------------------------------------------------- +c DST(psi,theta) = K(psi,theta) * CONJG(xi_normal(psi,theta)) +c Store both theta-space and mode-space for later integration. +c----------------------------------------------------------------------- + CALL gpeq_K(psi, K_mn, K_fun) + CALL iscdftb(mfac, mpert, xi_normal_fun, mthsurf, xsp_mn) + dst_theta_fun = K_fun * CONJG(xi_normal_fun) + + DST_fun(0:mthsurf, ipsi) = dst_theta_fun + CALL iscdftf(mfac, mpert, dst_theta_fun, mthsurf, + $ DST_mn(1:mpert, ipsi)) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -802,55 +954,123 @@ SUBROUTINE gpeq_dst(psi, ipsi) END SUBROUTINE gpeq_dst c----------------------------------------------------------------------- c subprogram 11. gpeq_shear. -c compute magnetic shear mode coefficients and spatial functions. +c compute magnetic shear at a single psi level. +c approach: compute spatial domain first (theta), then FFT to mode space. c----------------------------------------------------------------------- - SUBROUTINE gpeq_shear(psi, shear_mn, shear_fun) + SUBROUTINE gpeq_shear(psi, shear_fun) REAL(r8), INTENT(IN) :: psi - COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: shear_mn - COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) - $ :: shear_fun + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: shear_fun - INTEGER :: itheta, ipert, jpert, dm, m1 - REAL(r8) :: q, q1, eta, r, rfac - REAL(r8), DIMENSION(0:mthsurf) :: theta_vals - COMPLEX(r8), DIMENSION(-mband:mband) :: jmat, jmat1 - COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_theta_fun - + INTEGER :: itheta + REAL(r8) :: q, q1, jac, rfac, eta, r_val, theta_val + REAL(r8) :: w11, w12, w21, w22, w31, w32 + REAL(r8) :: v11, v12, v13, v21, v22, v23, v33 + REAL(r8) :: dpdp, dpdt, dpdz, shear_deriv + REAL(r8) :: shear_contra, shear_cova, diff_shear + TYPE(spline_type) :: shear_temp + +c----------------------------------------------------------------------- +c Setup: get equilibrium at this psi. +c----------------------------------------------------------------------- CALL spline_eval(sq, psi, 1) q = sq%f(4) q1 = sq%f1(4) - CALL cspline_eval(metric%cs, psi, 0) - - jmat(0:-mband:-1) = metric%cs%f(6*mband+7:7*mband+7) - jmat1(0:-mband:-1) = metric%cs%f(7*mband+8:8*mband+8) - jmat(1:mband) = CONJG(jmat(-1:-mband:-1)) - jmat1(1:mband) = CONJG(jmat1(-1:-mband:-1)) - + c----------------------------------------------------------------------- -c compute shear in mode space from metric tensors and solutions. +c Allocate spline for shear_temp with 5 components: +c 1: covariant numerator term +c 2: (q*dpdt - dpdz) numerator +c 3: g_psi_g_psi denominator +c 4: contravariant denominator (v21^2 + v22^2)*r^2 +c 5: covariant shear (component 1 / component 4) c----------------------------------------------------------------------- - ipert = 0 - shear_mn = 0.0_r8 - DO m1 = mlow, mhigh - ipert = ipert + 1 - DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) - jpert = ipert + dm - shear_mn(ipert) = shear_mn(ipert) - $ + (twopi**2/jac) * (q1 - $ + (jmat(dm)*xwp_mn(jpert) - q*jmat1(dm)*xwp_mn(jpert))/ - $ q) - ENDDO + CALL spline_alloc(shear_temp, mthsurf, 5) + shear_temp%xs = theta(0:mthsurf) + shear_temp%name = "shear_temp" + +c----------------------------------------------------------------------- +c Step 1: Compute shear components at all theta points. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + theta_val = theta(itheta) + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + + jac = rzphi%f(4) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) + eta = twopi*(theta_val + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + + +c Compute w_ij contravariant metric in Cartesian components + w11 = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_val/jac + w12 = -rzphi%fy(1)*pi*r_val/(rfac*jac) + + w21 = -twopi**2*rfac*r_val*rzphi%fx(2)/jac + w22 = pi*r_val*rzphi%fx(1)/(rfac*jac) + + w31 = (twopi*r_val*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)*(1.0_r8 + + $ rzphi%fy(2))) + w32 = (r_val/(2.0_r8*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) + +c Compute covariant basis v_ij = ∂r/∂ξ_j (Cartesian components) +c From recon_metric: + v11 = rzphi%fx(1)/(2.0_r8*rfac*jac) + v12 = rzphi%fx(2)*twopi*rfac/jac + v13 = rzphi%fx(3)*r_val/jac + + v21 = rzphi%fy(1)/(2.0_r8*rfac*jac) + v22 = (1.0_r8 + rzphi%fy(2))*twopi*rfac/jac + v23 = rzphi%fy(3)*r_val/jac + +c Component 33 of covariant basis + v33 = twopi*r_val/jac + +c Contravariant dot products: dpdp = ∇ψ·∇ψ, etc. + dpdp = w11**2 + w12**2 + dpdt = w11*w21 + w12*w22 + dpdz = w11*w31 + w12*w32 + + shear_temp%fs(itheta, 1) = twopi*r_val*jac* + $ (-(v11*v21+v12*v22)*(v23+q*v33)+v13*(v21**2+v22**2)) + shear_temp%fs(itheta, 2) = (q*dpdt - dpdz) + shear_temp%fs(itheta, 3) = dpdp + shear_temp%fs(itheta, 4) = + $ twopi**2 * r_val**2 * (v21**2 + v22**2) + + + shear_temp%fs(itheta, 5) = + $ shear_temp%fs(itheta, 1) / shear_temp%fs(itheta, 4) + ENDDO - c----------------------------------------------------------------------- -c convert to spatial functions if requested. +c Step 2: Fit spline to compute derivatives. +c----------------------------------------------------------------------- + CALL spline_fit(shear_temp, "periodic") +c----------------------------------------------------------------------- +c Step 3: Compute shear_fun = (2π²/J)*(q' + ∂shear_temp/∂θ) +c Compare covariant and contravariant approaches. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + theta_val = theta(itheta) + + CALL spline_eval(shear_temp, theta_val, 1) + CALL bicube_eval(rzphi, psi, theta_val, 1) + jac = rzphi%f(4) + +c Contravariant approach: derivative of component 5 + shear_deriv = shear_temp%f1(5) + shear_contra = (twopi**2/jac)*(q1 + shear_deriv) + shear_fun(itheta) = CMPLX(shear_contra, 0.0_r8, r8) + + ENDDO + +c----------------------------------------------------------------------- +c Step 4: Transform to mode space via FFT. +c----------------------------------------------------------------------- + CALL spline_dealloc(shear_temp) c----------------------------------------------------------------------- - IF (PRESENT(shear_fun)) THEN - CALL iscdftb(mfac, mpert, shear_theta_fun, mthsurf, - $ shear_mn) - shear_fun = shear_theta_fun - ENDIF - RETURN END SUBROUTINE gpeq_shear c----------------------------------------------------------------------- @@ -887,9 +1107,9 @@ SUBROUTINE gpeq_curvature(psi, curv_mn, curv_fun) c compute covariant components needed for curvature. c----------------------------------------------------------------------- ipert = 0 - bvt_local = 0.0_r8 - bvz_local = 0.0_r8 - curv_mn = 0.0_r8 + bvt_local = CMPLX(0.0_r8, 0.0_r8, r8) + bvz_local = CMPLX(0.0_r8, 0.0_r8, r8) + curv_mn = CMPLX(0.0_r8, 0.0_r8, r8) DO m1 = mlow, mhigh ipert = ipert + 1 DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) @@ -929,7 +1149,7 @@ SUBROUTINE gpeq_K(psi, K_mn, K_fun) COMPLEX(r8), DIMENSION(-mband:mband) :: g22, g33, g23 COMPLEX(r8), DIMENSION(0:mthsurf) :: K_theta_fun COMPLEX(r8), DIMENSION(mpert) :: bvt_local, bvz_local - COMPLEX(r8), DIMENSION(mpert) :: sigma_mn, shear_mn + COMPLEX(r8), DIMENSION(mpert) :: sigma_mn chi1 = psio * twopi @@ -953,7 +1173,7 @@ SUBROUTINE gpeq_K(psi, K_mn, K_fun) c compute K-related quantities in mode space. c----------------------------------------------------------------------- ipert = 0 - K_mn = 0.0_r8 + K_mn = CMPLX(0.0_r8, 0.0_r8, r8) DO m1 = mlow, mhigh ipert = ipert + 1 DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) @@ -998,7 +1218,7 @@ SUBROUTINE gpeq_terms(psi, mode, xspmn, term_mn, term_fun) c placeholder: compute integration terms. c full implementation depends on integration methodology. c----------------------------------------------------------------------- - term_mn = 0.0_r8 + term_mn = CMPLX(0.0_r8, 0.0_r8, r8) c----------------------------------------------------------------------- c convert to spatial functions if requested. diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 79411e0da..52c7ee1d6 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -73,8 +73,15 @@ MODULE gpglobal_mod $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, $ chi_mn,che_mn,kax_mn,sbno_mn,sbno_fun,edge_mn,edge_fun, $ chy_mn,chx_mn,chw_mn,kaw_mn,mutual_indev,mutual_indinvev - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: C_psi_mn,C_theta_mn, - $ C_zeta_mn,C1_mn,C2_mn,C3_mn + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: cwp_mn,cwt_mn, + $ cwz_mn, cvp_mn,cvt_mn,cvz_mn, + $ qvp_mn,qvt_mn, qvz_mn, qwp_mn,qwt_mn,qwz_mn, + $ c2_cvw_mn,c2_cw_mn, c2_cv_mn + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: cwp_fun,cwt_fun, + $ cwz_fun,cvp_fun,cvt_fun,cvz_fun,cvp_funp,cvt_funp,cvz_funp, + $ qvp_fun,qvt_fun,qvz_fun,qwp_fun,qwt_fun,qwz_fun,c2_cw_fun, + $ c2_cv_fun,c2_cvw_fun + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: DST_mn,DST_fun COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: wt,wt0,chp_mn,kap_mn, $ permeabev,chimats,chemats,flxmats,kaxmats,singbno_mn, $ plas_indev,plas_indinvev,reluctev,indrelev,permeabsv, @@ -157,12 +164,14 @@ SUBROUTINE gpec_dealloc $ plas_indevmats,permeabevmats,chxmats,chwmats, $ mutual_indmats,mutual_indevmats,mutual_indinvmats, $ mutual_indinvevmats) - IF(ALLOCATED(C_psi_mn)) DEALLOCATE(C_psi_mn) - IF(ALLOCATED(C_theta_mn)) DEALLOCATE(C_theta_mn) - IF(ALLOCATED(C_zeta_mn)) DEALLOCATE(C_zeta_mn) - IF(ALLOCATED(C1_mn)) DEALLOCATE(C1_mn) - IF(ALLOCATED(C2_mn)) DEALLOCATE(C2_mn) - IF(ALLOCATED(C3_mn)) DEALLOCATE(C3_mn) + IF(ALLOCATED(cwp_mn)) DEALLOCATE(cwp_mn) + IF(ALLOCATED(cwt_mn)) DEALLOCATE(cwt_mn) + IF(ALLOCATED(cwz_mn)) DEALLOCATE(cwz_mn) + IF(ALLOCATED(cvp_mn)) DEALLOCATE(cvp_mn) + IF(ALLOCATED(cvt_mn)) DEALLOCATE(cvt_mn) + IF(ALLOCATED(cvz_mn)) DEALLOCATE(cvz_mn) + IF(ALLOCATED(DST_mn)) DEALLOCATE(DST_mn) + IF(ALLOCATED(DST_fun)) DEALLOCATE(DST_fun) CALL spline_dealloc(sq) CALL bicube_dealloc(eqfun) diff --git a/gpec/gpout.f b/gpec/gpout.f index aca9f7ba1..6843924bf 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6877,11 +6877,31 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER, INTENT(IN) :: mode COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn - INTEGER :: ipsi - REAL(r8) :: psi - COMPLEX(r8), DIMENSION(mpert) :: shear_mn, curv_mn, K_mn, + INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, + $ ucov, ucontra_phy, ucontra_met, uQv, uQw, uc2cw, uc2cv, + $ uc2cvw + REAL(r8) :: psi, eta, rfac + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + COMPLEX(r8), DIMENSION(mpert) :: curv_mn, K_mn, $ term_mn COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun + CHARACTER(8) :: smode + CHARACTER(128) :: shear_fun_file, curv_file + CHARACTER(128) :: k_file, c2cw_file, c2cv_file, c2cvw_file + CHARACTER(128) :: ccov_file, ccontra_phy_file, ccontra_met_file + CHARACTER(128) :: qv_file, qw_file + +c Diagnostic variables for C vector comparison + INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 + REAL(r8) :: psi_mid, theta_mid, delpsi_sq, jac_mid, q_mid, + $ q1_mid, chi1_mid, f1_mid, p1_mid, j_theta_coef, j_zeta_coef + COMPLEX(r8) :: C1_phys, C2_phys, C3_phys, C1_metr, C2_metr, + $ C3_metr, term_exp, j_cross_factor, xi_psi_val + COMPLEX(r8) :: C_psi_val, C_theta_val, C_zeta_val + COMPLEX(r8), DIMENSION(-mband:mband) :: g11_diag, g12_diag, + $ g22_diag, g23_diag, g31_diag, g33_diag + REAL(r8), DIMENSION(10,10) :: w + REAL(r8) :: g11_r, g22_r, g33_r, g23_r, g31_r, g12_r IF(verbose) WRITE(*,*)"" IF(verbose) WRITE(*,*)"GPOUT_RECON: Starting reconstruction"// @@ -6892,19 +6912,86 @@ SUBROUTINE gpout_recon(mode, xspmn) c allocate C vector arrays for EPF calculation. c Storage: C_*_mn(mpert, 0:mpsi) for all psi levels. c----------------------------------------------------------------------- - IF (.NOT. ALLOCATED(C_psi_mn)) THEN - ALLOCATE(C_psi_mn(mpert, 0:mpsi)) - ALLOCATE(C_theta_mn(mpert, 0:mpsi)) - ALLOCATE(C_zeta_mn(mpert, 0:mpsi)) - ALLOCATE(C1_mn(mpert, 0:mpsi)) - ALLOCATE(C2_mn(mpert, 0:mpsi)) - ALLOCATE(C3_mn(mpert, 0:mpsi)) - C_psi_mn = 0.0_r8 - C_theta_mn = 0.0_r8 - C_zeta_mn = 0.0_r8 - C1_mn = 0.0_r8 - C2_mn = 0.0_r8 - C3_mn = 0.0_r8 + IF (.NOT. ALLOCATED(cwp_mn)) THEN + ALLOCATE(cwp_mn(mpert, 0:mpsi)) + ALLOCATE(cwt_mn(mpert, 0:mpsi)) + ALLOCATE(cwz_mn(mpert, 0:mpsi)) + cwp_mn = 0.0_r8 + cwt_mn = 0.0_r8 + cwz_mn = 0.0_r8 + ENDIF + IF (.NOT. ALLOCATED(DST_mn)) THEN + ALLOCATE(DST_mn(mpert, 0:mpsi)) + DST_mn = 0.0_r8 + ENDIF + IF (.NOT. ALLOCATED(DST_fun)) THEN + ALLOCATE(DST_fun(0:mthsurf, 0:mpsi)) + DST_fun = 0.0_r8 + ENDIF + +c Allocate C and Q vector spatial function arrays (theta, psi grid) + IF (.NOT. ALLOCATED(cwp_fun)) THEN + ALLOCATE(cwp_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cwt_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cwz_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cvp_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cvt_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cvz_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(cvp_funp(0:mthsurf, 0:mpsi)) + ALLOCATE(cvt_funp(0:mthsurf, 0:mpsi)) + ALLOCATE(cvz_funp(0:mthsurf, 0:mpsi)) + ALLOCATE(c2_cw_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(c2_cv_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(c2_cvw_fun(0:mthsurf, 0:mpsi)) + + cwp_fun = 0.0_r8 + cwt_fun = 0.0_r8 + cwz_fun = 0.0_r8 + cvp_fun = 0.0_r8 + cvt_fun = 0.0_r8 + cvz_fun = 0.0_r8 + cvp_funp = 0.0_r8 + cvt_funp = 0.0_r8 + cvz_funp = 0.0_r8 + c2_cw_fun = 0.0_r8 + c2_cv_fun = 0.0_r8 + c2_cvw_fun = 0.0_r8 + ENDIF + + IF (.NOT. ALLOCATED(qvp_fun)) THEN + ALLOCATE(qvp_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(qvt_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(qvz_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(qwp_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(qwt_fun(0:mthsurf, 0:mpsi)) + ALLOCATE(qwz_fun(0:mthsurf, 0:mpsi)) + qvp_fun = 0.0_r8 + qvt_fun = 0.0_r8 + qvz_fun = 0.0_r8 + qwp_fun = 0.0_r8 + qwt_fun = 0.0_r8 + qwz_fun = 0.0_r8 + ENDIF + + IF (.NOT. ALLOCATED(qvp_mn)) THEN + ALLOCATE(qvp_mn(mpert, 0:mpsi)) + ALLOCATE(qvt_mn(mpert, 0:mpsi)) + ALLOCATE(qvz_mn(mpert, 0:mpsi)) + ALLOCATE(cvp_mn(mpert, 0:mpsi)) + ALLOCATE(cvt_mn(mpert, 0:mpsi)) + ALLOCATE(cvz_mn(mpert, 0:mpsi)) + ALLOCATE(qwp_mn(mpert, 0:mpsi)) + ALLOCATE(qwt_mn(mpert, 0:mpsi)) + ALLOCATE(qwz_mn(mpert, 0:mpsi)) + qvp_mn = 0.0_r8 + qvt_mn = 0.0_r8 + qvz_mn = 0.0_r8 + cvp_mn = 0.0_r8 + cvt_mn = 0.0_r8 + cvz_mn = 0.0_r8 + qwp_mn = 0.0_r8 + qwt_mn = 0.0_r8 + qwz_mn = 0.0_r8 ENDIF c----------------------------------------------------------------------- @@ -6915,15 +7002,88 @@ SUBROUTINE gpout_recon(mode, xspmn) ALLOCATE(gpout_curvature) ALLOCATE(gpout_k) + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + shear_fun_file = "gpec_recon_shear_fun_sol"//TRIM(smode)//".out" + curv_file = "gpec_recon_curvature_sol"//TRIM(smode)//".out" + k_file = "gpec_recon_k_sol"//TRIM(smode)//".out" + c2cw_file = "gpec_recon_C2_cw_sol"//TRIM(smode)//".out" + c2cv_file = "gpec_recon_C2_cv_sol"//TRIM(smode)//".out" + c2cvw_file = "gpec_recon_C2_cvw_sol"//TRIM(smode)//".out" + ccov_file = "gpec_recon_Ccov_sol"//TRIM(smode)//".out" + ccontra_phy_file = "gpec_recon_Ccontra_physics_sol"/ + $ /TRIM(smode)//".out" + ccontra_met_file = "gpec_recon_Ccontra_metric_sol"/ + $ /TRIM(smode)//".out" + qv_file = "gpec_recon_Qv_sol"//TRIM(smode)//".out" + qw_file = "gpec_recon_Qw_sol"/ + $ /TRIM(smode)//".out" + + ushear_fun = 82 + ucurv = 72 + uk = 73 + uc2cw = 79 + uc2cv = 80 + uc2cvw = 81 + ucov = 74 + ucontra_phy = 75 + ucontra_met = 76 + uQv = 77 + uQw = 78 + + OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") + OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") + OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") + OPEN(UNIT=uc2cw, FILE=c2cw_file, STATUS="UNKNOWN") + OPEN(UNIT=uc2cv, FILE=c2cv_file, STATUS="UNKNOWN") + OPEN(UNIT=uc2cvw, FILE=c2cvw_file, STATUS="UNKNOWN") + OPEN(UNIT=ucov, FILE=ccov_file, STATUS="UNKNOWN") + OPEN(UNIT=ucontra_phy, FILE=ccontra_phy_file, STATUS="UNKNOWN") + OPEN(UNIT=ucontra_met, FILE=ccontra_met_file, STATUS="UNKNOWN") + OPEN(UNIT=uQv, FILE=qv_file, STATUS="UNKNOWN") + OPEN(UNIT=uQw, FILE=qw_file, STATUS="UNKNOWN") + WRITE(ushear_fun,'(6(1x,a16))') + $ "psi","theta","r","z","shear_re","shear_im" + WRITE(ucurv,'(6(1x,a16))') + $ "psi","theta","r","z","curv_re","curv_im" + WRITE(uk,'(6(1x,a16))') + $ "psi","theta","r","z","K_re","K_im" + WRITE(uc2cw,'(6(1x,a16))') + $ "psi","theta","r","z","C2_cw_re","C2_cw_im" + WRITE(uc2cv,'(6(1x,a16))') + $ "psi","theta","r","z","C2_cv_re","C2_cv_im" + WRITE(uc2cvw,'(6(1x,a16))') + $ "psi","theta","r","z","C2_cvw_re","C2_cvw_im" + WRITE(ucov,'(10(1x,a14))') + $ "psi","theta","r","z", + $ "C_psi_re","C_psi_im","C_theta_re","C_theta_im", + $ "C_zeta_re","C_zeta_im" + WRITE(ucontra_phy,'(10(1x,a14))') + $ "psi","theta","r","z", + $ "C1_phy_re","C1_phy_im","C2_phy_re","C2_phy_im", + $ "C3_phy_re","C3_phy_im" + WRITE(ucontra_met,'(10(1x,a14))') + $ "psi","theta","r","z", + $ "C1_met_re","C1_met_im","C2_met_re","C2_met_im", + $ "C3_met_re","C3_met_im" + WRITE(uQv,'(10(1x,a14))') + $ "psi","theta","r","z", + $ "Qv_psi_re","Qv_psi_im","Qv_theta_re","Qv_theta_im", + $ "Qv_zeta_re","Qv_zeta_im" + WRITE(uQw,'(10(1x,a14))') + $ "psi","theta","r","z", + $ "Qw1_re","Qw1_im","Qw2_re","Qw2_im", + $ "Qw3_re","Qw3_im" + c----------------------------------------------------------------------- c main loop over all psi levels. c compute gpeq equilibrium quantities at each psi and call gpeq_epf -c to populate C vectors for the EPF/DST diagnostics. +c to populate C norm squared for the EPF/DST diagnostics. c----------------------------------------------------------------------- CALL gpeq_alloc DO ipsi = 0, mpsi - psi = psifac(ipsi) - + psi = rzphi%xs(ipsi) + c Compute all necessary gpeq quantities at this psi level. CALL gpeq_sol(psi) CALL gpeq_contra(psi) @@ -6932,18 +7092,145 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL gpeq_tangent(psi) CALL gpeq_parallel(psi) CALL gpeq_rzphi(psi) + +c Compute shear/curvature/K in mode-space and spatial-space. + CALL gpeq_shear(psi, shear_fun) + CALL gpeq_curvature(psi, curv_mn, curv_fun) + CALL gpeq_K(psi, K_mn, K_fun) + +c Store spatial values with coordinates. + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + + WRITE(ushear_fun,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(shear_fun(itheta)), + $ AIMAG(shear_fun(itheta)) + WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(curv_fun(itheta)), + $ AIMAG(curv_fun(itheta)) + WRITE(uk,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), + $ AIMAG(K_fun(itheta)) + ENDDO + WRITE(ushear_fun,*) + WRITE(ucurv,*) + WRITE(uk,*) -c Compute C vectors (covariant + contravariant) for this psi -c and store in C_*_mn(:, ipsi). -c gpeq_epf handles Q vector, current coupling, and Fourier transform. +c Compute C and Q vectors (covariant + contravariant) for this psi CALL gpeq_epf(psi, ipsi) + +c Write C^2 values (contravariant form only - no metric) + DO itheta = 0, mthsurf + WRITE(uc2cw,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(c2_cw_fun(itheta, ipsi)), + $ AIMAG(c2_cw_fun(itheta, ipsi)) + ENDDO + WRITE(uc2cw,*) + +c Write C^2 values (covariant form only - with metric) + DO itheta = 0, mthsurf + WRITE(uc2cv,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(c2_cv_fun(itheta, ipsi)), + $ AIMAG(c2_cv_fun(itheta, ipsi)) + ENDDO + WRITE(uc2cv,*) + +c Write C^2 values (mixed form - Cov x Contra) + DO itheta = 0, mthsurf + WRITE(uc2cvw,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(c2_cvw_fun(itheta, ipsi)), + $ AIMAG(c2_cvw_fun(itheta, ipsi)) + ENDDO + WRITE(uc2cvw,*) + +c Write C vectors to files - contravariant components (all 3) + DO itheta = 0, mthsurf + WRITE(ucov,'(10(es15.7e2))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cwp_fun(itheta, ipsi)), + $ AIMAG(cwp_fun(itheta, ipsi)), + $ REAL(cwt_fun(itheta, ipsi)), + $ AIMAG(cwt_fun(itheta, ipsi)), + $ REAL(cwz_fun(itheta, ipsi)), + $ AIMAG(cwz_fun(itheta, ipsi)) + ENDDO + WRITE(ucov,*) + +c Write C vectors (contravariant) - contrvariant components with p + DO itheta = 0, mthsurf + WRITE(ucontra_phy,'(10(es15.7e2))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cvp_funp(itheta, ipsi)), + $ AIMAG(cvp_funp(itheta, ipsi)), + $ REAL(cvt_funp(itheta, ipsi)), + $ AIMAG(cvt_funp(itheta, ipsi)), + $ REAL(cvz_funp(itheta, ipsi)), + $ AIMAG(cvz_funp(itheta, ipsi)) + ENDDO + WRITE(ucontra_phy,*) + + +c Write C vectors (contravariant) - contrvariant components with metric + DO itheta = 0, mthsurf + WRITE(ucontra_met,'(10(es15.7e2))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cvp_fun(itheta, ipsi)), + $ AIMAG(cvp_fun(itheta, ipsi)), + $ REAL(cvt_fun(itheta, ipsi)), + $ AIMAG(cvt_fun(itheta, ipsi)), + $ REAL(cvz_fun(itheta, ipsi)), + $ AIMAG(cvz_fun(itheta, ipsi)) + ENDDO + WRITE(ucontra_met,*) + + +c Write Q covariant vectors - all 3 components + DO itheta = 0, mthsurf + WRITE(uQv,'(10(es15.7e2))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(qvp_fun(itheta, ipsi)), + $ AIMAG(qvp_fun(itheta, ipsi)), + $ REAL(qvt_fun(itheta, ipsi)), + $ AIMAG(qvt_fun(itheta, ipsi)), + $ REAL(qvz_fun(itheta, ipsi)), + $ AIMAG(qvz_fun(itheta, ipsi)) + ENDDO + WRITE(uQv,*) + + DO itheta = 0, mthsurf + WRITE(uQw,'(10(es15.7e2))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(qwp_fun(itheta, ipsi)), + $ AIMAG(qwp_fun(itheta, ipsi)), + $ REAL(qwt_fun(itheta, ipsi)), + $ AIMAG(qwt_fun(itheta, ipsi)), + $ REAL(qwz_fun(itheta, ipsi)), + $ AIMAG(qwz_fun(itheta, ipsi)) + ENDDO + WRITE(uQw,*) + CALL gpeq_dst(psi,ipsi) ENDDO + CALL gpeq_dealloc + CLOSE(ushear_fun) + CLOSE(ucurv) + CLOSE(uk) + CLOSE(uc2cw) + CLOSE(uc2cv) + CLOSE(uc2cvw) + CLOSE(ucov) + CLOSE(ucontra_phy) + CLOSE(ucontra_met) + CLOSE(uQv) + CLOSE(uQw) c----------------------------------------------------------------------- c C vectors now populated in gpglobal_mod for all psi levels. -c Next phase: compute EPF/DST from C vectors (placeholder). c----------------------------------------------------------------------- IF(ALLOCATED(gpout_shear)) THEN IF(verbose) WRITE(*,*)" Shear bicube computed" @@ -6963,7 +7250,7 @@ SUBROUTINE gpout_recon(mode, xspmn) IF(verbose) WRITE(*,*) $ "GPDIAG_RECON: Reconstruction diagnostics complete" IF(verbose) WRITE(*,*)" C vectors computed and stored in"// - $ " C_*_mn(mpert, 0:mpsi)" + $ " cw*_mn(mpert, 0:mpsi)" IF(verbose) WRITE(*,*)"__________________________________________" c----------------------------------------------------------------------- c terminate. From b3428dfc65c4981252426a07d8e951a6c4535441 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 18 Mar 2026 14:57:41 +0900 Subject: [PATCH 113/305] GPEC - docs - Add variable naming convention and mathematical symbols section to documentation (cherry picked from commit 557b62c88e15cdb86adeb8f581f2b0d9703bf4b3) --- docs/tex/recon/main.tex | 160 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 5 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 9bd826a23..2866583c6 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -10,6 +10,7 @@ \usepackage{hyperref} \usepackage{cancel} \usepackage{color} +\usepackage{pdflscape} \title{DCON $recon flag$ document} \author{Sunjae Lee} @@ -898,9 +899,10 @@ \subsection{Reconstruction v1} &=\mathcal{J} (j^{\theta} \nabla \zeta \times \nabla \psi + j^{\zeta} \nabla \psi \times \nabla \theta) \end{align} -\begin{equation} -\vec{Q} = \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) = \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) -\end{equation} +\begin{align} +\vec{Q} &= \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) &= \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) \\ +&= Q_1 \nabla \psi + Q_2 \nabla \theta + Q_3 \nabla \zeta +\end{align} \begin{equation} Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi^\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) @@ -981,8 +983,8 @@ \subsection{Reconstruction v1} \begin{align} C^\psi &= \vec{C} \cdot \nabla\psi = Q^\psi \\ -C^\theta &= \vec{C} \cdot \nabla\theta = Q^\theta + \frac{\xi^\psi}{|\nabla\psi|^2} \mu_0 \vec{j} \cdot (\nabla\psi \times \nabla\theta) \\ -C^\zeta &= \vec{C} \cdot \nabla\zeta = Q^\zeta + \frac{\xi^\psi}{|\nabla\psi|^2} \mu_0 \vec{j} \cdot (\nabla\psi \times \nabla\zeta) +C^\theta &= \vec{C} \cdot \nabla\theta = Q^\theta + \xi^\psi \frac{1}{|\nabla\psi|^2} \mu_0 j^\zeta \\ +C^\zeta &= \vec{C} \cdot \nabla\zeta = Q^\zeta - \xi^\psi \frac{1}{|\nabla\psi|^2} \mu_0 j^\theta \end{align} \begin{align} @@ -1408,6 +1410,154 @@ \subsection{Physical Interpretation} \end{enumerate} +\section{Variable Naming Convention and Mathematical Symbols} + +This section summarizes the implementation of C and Q vectors with proper tensor transformations between contravariant and covariant representations. + +\subsection{Fundamental Transformation Laws} + +The C and Q vectors are defined in two complementary basis systems: + +\textbf{Contravariant basis:} $\vec{w}_i \equiv \nabla \psi, \nabla \theta, \nabla \zeta$ \\ +\textbf{Covariant basis:} $\vec{v}_i \equiv \nabla \theta \times \nabla \zeta, \nabla \zeta \times \nabla \psi, \nabla \psi \times \nabla \theta$ + +\begin{equation} +\boxed{ +\begin{aligned} +\text{Contravariant} \quad &Q^i = \frac{b^i}{\mathcal{J}} \\ +\text{Covariant} \quad &Q_i = g_{ij} Q^j = g_{ij} \frac{b^j}{\mathcal{J}} \\ +\text{Physics method} \quad &C_i = Q_i + (\vec{j} \times \vec{\xi})_i +\end{aligned} +} +\end{equation} + +where $\mathcal{J}$ is the Jacobian, $g_{ij}$ is the metric tensor at MODE index 0, and $(\vec{j} \times \vec{\xi})_i$ represents current-displacement coupling. + +\subsection{Code Variable Mapping Table} + +\subsubsection*{Q Vector - Contravariant (w-basis, $Q^i$)} +\footnotesize +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{qwp\_tmp, qwp\_fun, qwp\_mn} & $Q^\psi$ (contravariant component) \\ +\texttt{qwt\_tmp, qwt\_fun, qwt\_mn} & $Q^\theta$ (contravariant component) \\ +\texttt{qwz\_tmp, qwz\_fun, qwz\_mn} & $Q^\zeta$ (contravariant component) \\ +\hline +\end{tabular} +\end{table} + +\subsubsection*{Q Vector - Covariant (v-basis, $Q_i$)} +\footnotesize +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{qvp\_tmp, qvp\_fun, qvp\_mn} & $Q_\psi = g_{11}Q^\psi + g_{12}Q^\theta + g_{13}Q^\zeta$ \\ +\texttt{qvt\_tmp, qvt\_fun, qvt\_mn} & $Q_\theta = g_{12}Q^\psi + g_{22}Q^\theta + g_{23}Q^\zeta$ \\ +\texttt{qvz\_tmp, qvz\_fun, qvz\_mn} & $Q_\zeta = g_{13}Q^\psi + g_{23}Q^\theta + g_{33}Q^\zeta$ \\ +\hline +\end{tabular} +\end{table} + +\subsubsection*{C Vector - Contravariant (w-basis, $C^i$)} +\footnotesize +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{cwp\_tmp, cwp\_fun, cwp\_mn} & $C^\psi$ (contravariant component) \\ +\texttt{cwt\_tmp, cwt\_fun, cwt\_mn} & $C^\theta$ (contravariant component) \\ +\texttt{cwz\_tmp, cwz\_fun, cwz\_mn} & $C^\zeta$ (contravariant component) \\ +\hline +\end{tabular} +\end{table} + +\subsubsection*{C Vector - Covariant (v-basis, $C_i$)} +\footnotesize +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{cvp\_tmp, cvp\_fun} & $C_\psi^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ +\texttt{cvt\_tmp, cvt\_fun} & $C_\theta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ +\texttt{cvz\_tmp, cvz\_fun} & $C_\zeta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ +\hline +\multicolumn{2}{|l|}{\textit{Physics form with current-displacement coupling:}} \\ +\hline +\texttt{cvp\_fun\_p, cvp\_mn} & $C_\psi = C_\psi^{\text{metric}} + (\vec{j} \times \vec{\xi})_\psi$ \\ +\texttt{cvt\_fun\_p, cvt\_mn} & $C_\theta = C_\theta^{\text{metric}} + (\vec{j} \times \vec{\xi})_\theta$ \\ +\texttt{cvz\_fun\_p, cvz\_mn} & $C_\zeta = C_\zeta^{\text{metric}} + (\vec{j} \times \vec{\xi})_\zeta$ \\ +\hline +\end{tabular} +\end{table} + +\subsubsection*{Input Variables and Parameters} +\footnotesize +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{bwp\_fun, bwt\_fun, bwz\_fun} & $\mathcal{J} Q^\psi, \mathcal{J} Q^\theta, \mathcal{J} Q^\zeta$ \\ +\texttt{jacs}$(itheta)$ & $\mathcal{J}(\theta)$ = Jacobian at each $\theta$ \\ +\texttt{metric\%cs\%f}$(0, i, j)$ & $g_{ij}(0)$ = Metric tensor at MODE index 0 \\ +\hline +\end{tabular} +\end{table} + +\paragraph{Variable Storage Types:} +\begin{itemize} +\item \textbf{\texttt{tmp}}: Local 1D array at single flux surface ($\psi$ level), dimension $(0:m_\theta)$ +\item \textbf{\texttt{fun}}: Global 2D spatial array on $(\theta, \psi)$ grid, dimension $(0:m_\theta, 0:m_\psi)$ +\item \textbf{\texttt{mn}}: Global 2D MODE space array, dimension $(m_{pert}, 0:m_\psi)$ +\item \textbf{\texttt{p}}: Physics form (includes j$\times\xi$ coupling term) +\item \textbf{\texttt{w}}-basis: Contravariant (w-basis, using $\nabla$ forms) +\item \textbf{\texttt{v}}-basis: Covariant (v-basis, using cross products) +\end{itemize} + +\subsection{Computational Algorithm} + +The reconstruction algorithm follows these steps for each flux surface ($\psi$ level): + +\begin{enumerate} +\item \textbf{Extract contravariant Q:} +\begin{equation} +Q^i(\theta) = \frac{b^i(\theta)}{\mathcal{J}(\theta)} = \frac{\text{bwp\_fun}(\theta), \text{bwt\_fun}(\theta), \text{bwz\_fun}(\theta)}{\text{jacs}(\theta)} +\end{equation} + +\item \textbf{Apply metric contraction for covariant Q:} +\begin{equation} +Q_i(\theta) = g_{ij}(0) \cdot Q^j(\theta) \quad \text{where } i,j \in \{\psi, \theta, \zeta\} +\end{equation} + +\item \textbf{Compute physics-based C with $j \times \xi$ coupling:} +\begin{equation} +C_i(\theta) = Q_i(\theta) + (\vec{j} \times \vec{\xi})_i(\theta) +\end{equation} + +\item \textbf{Store spatial representations:} +\begin{equation} +\text{qwp\_fun}(\theta, \psi) = Q^\psi(\theta), \quad \text{qvp\_fun}(\theta, \psi) = Q_\psi(\theta), \quad \cdots +\end{equation} + +\item \textbf{Transform to MODE space via Fourier:} +\begin{equation} +\tilde{Q}^\psi_{n,m} = \text{ISCDFTF}(Q^\psi(\theta)) \quad \Rightarrow \quad \text{qwp\_mn}(m_{pert}, \psi) +\end{equation} + +\end{enumerate} + \bibliographystyle{plain} \bibliography{references} From a6369705aaf05adfa898d53d8226813b55b1c3a2 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 18 Mar 2026 16:30:37 +0900 Subject: [PATCH 114/305] GPEC- WIP - fix curvature calcuation and make allocation process align with convention (cherry picked from commit 1ef312755fbdabde5109264c4bb93fbf05d022d6) --- gpec/gpeq.f | 120 +++++++++++++++++++++++++++++------------------- gpec/gpglobal.F | 10 +--- gpec/gpout.f | 45 +----------------- 3 files changed, 75 insertions(+), 100 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index dc89d7d94..706920fd1 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -1075,62 +1075,76 @@ SUBROUTINE gpeq_shear(psi, shear_fun) END SUBROUTINE gpeq_shear c----------------------------------------------------------------------- c subprogram 12. gpeq_curvature. -c compute curvature mode coefficients and spatial functions. +c compute curvature in spatial domain. +c κ·∇ψ = (|∇ψ|²/B²)[μ₀p' + (1/2)(∂B²/∂ψ) + (1/2)(∂B²/∂θ)(∇ψ·∇ψ)/(∇ψ·∇θ)] c----------------------------------------------------------------------- - SUBROUTINE gpeq_curvature(psi, curv_mn, curv_fun) + SUBROUTINE gpeq_curvature(psi, curv_fun) REAL(r8), INTENT(IN) :: psi - COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: curv_mn - COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) - $ :: curv_fun + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: curv_fun - INTEGER :: itheta, ipert, jpert, dm, m1 - REAL(r8) :: q, p1, chi1, eta, r, rfac, bsq_val - COMPLEX(r8), DIMENSION(-mband:mband) :: g22, g23, g33 - COMPLEX(r8), DIMENSION(0:mthsurf) :: curv_theta_fun - COMPLEX(r8), DIMENSION(mpert) :: bvt_local, bvz_local - - chi1 = psio * twopi - + INTEGER :: itheta + REAL(r8) :: q, q1, p1, jac, rfac, eta, r_val, theta_val + REAL(r8) :: chi1, delpsi + REAL(r8) :: w11, w12, w13, w21, w22, w23, w31, w32, w33 + REAL(r8) :: v21, v22, v23, v33 + REAL(r8) :: bsq_val, bsq_psi, bsq_theta + REAL(r8) :: dpdt , kappa_psi, safe_denom + TYPE(spline_type) :: bsq_temp + +c----------------------------------------------------------------------- +c Setup: get equilibrium at this psi. +c----------------------------------------------------------------------- CALL spline_eval(sq, psi, 1) q = sq%f(4) + q1 = sq%f1(4) p1 = sq%f1(2) - CALL cspline_eval(metric%cs, psi, 0) - - g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) - g23(0:-mband:-1) = metric%cs%f(3*mband+3:4*mband+3) - g33(0:-mband:-1) = metric%cs%f(4*mband+4:5*mband+4) - g22(1:mband) = CONJG(g22(-1:-mband:-1)) - g23(1:mband) = CONJG(g23(-1:-mband:-1)) - g33(1:mband) = CONJG(g33(-1:-mband:-1)) - + chi1 = psio * twopi + c----------------------------------------------------------------------- -c compute covariant components needed for curvature. +c Step 3: Compute curvature at all theta points. c----------------------------------------------------------------------- - ipert = 0 - bvt_local = CMPLX(0.0_r8, 0.0_r8, r8) - bvz_local = CMPLX(0.0_r8, 0.0_r8, r8) - curv_mn = CMPLX(0.0_r8, 0.0_r8, r8) - DO m1 = mlow, mhigh - ipert = ipert + 1 - DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) - jpert = ipert + dm - bvt_local(ipert) = bvt_local(ipert) - $ + g22(dm)*bmt_mn(jpert) + g23(dm)*bmz_mn(jpert) - bvz_local(ipert) = bvz_local(ipert) - $ + g23(dm)*bmt_mn(jpert) + g33(dm)*bmz_mn(jpert) - ENDDO - curv_mn(ipert) = (chi1**2 / mu0) * - $ (p1 + bvt_local(ipert)*bvz_local(ipert)) + DO itheta = 0, mthsurf + theta_val = theta(itheta) + CALL bicube_eval(rzphi, psi, theta_val, 1) + + jac = rzphi%f(4) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta_val + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + +c Compute contravariant metrics + w11 = (1.0_r8 + rzphi%fy(2))*(twopi**2)*rfac*r_val/jac + w12 = -rzphi%fy(1)*pi*r_val/(rfac*jac) + + w21 = -(twopi**2)*rfac*r_val*rzphi%fx(2)/jac + w22 = pi*r_val*rzphi%fx(1)/(rfac*jac) + + w31 = (twopi*r_val*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)* + $ (1.0_r8 + rzphi%fy(2))) + w32 = (r_val/(2.0_r8*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) + +c Compute |∇ψ|² and ∇ψ·∇θ + delpsi = SQRT(w11**2 + w12**2) + dpdt = w11*w21 + w12*w22 + + CALL bicube_eval(eqfun, psi, theta_val, 1) +c Retrieve B² and derivatives + bsq_val = eqfun%f(1) ** 2 + bsq_psi = eqfun%fx(1) * 2 * eqfun%f(1) + bsq_theta = eqfun%fy(1) * 2 * eqfun%f(1) + +c Compute κ·∇ψ safely + + kappa_psi = (delpsi**2 / bsq_val) * + $ (p1 + 0.5_r8*bsq_psi + + $ 0.5_r8*bsq_theta*dpdt/(delpsi**2)) + + curv_fun(itheta) = CMPLX(kappa_psi, 0.0_r8, r8) + ENDDO - c----------------------------------------------------------------------- -c convert to spatial functions if requested. -c----------------------------------------------------------------------- - IF (PRESENT(curv_fun)) THEN - CALL iscdftb(mfac, mpert, curv_theta_fun, mthsurf, curv_mn) - curv_fun = curv_theta_fun - ENDIF - RETURN END SUBROUTINE gpeq_curvature c----------------------------------------------------------------------- @@ -1885,7 +1899,14 @@ SUBROUTINE gpeq_alloc $ xno_mn(mpert),xta_mn(mpert),xpa_mn(mpert), $ bno_mn(mpert),bta_mn(mpert),bpa_mn(mpert), $ xrr_mn(mpert),xrz_mn(mpert),xrp_mn(mpert), - $ brr_mn(mpert),brz_mn(mpert),brp_mn(mpert)) + $ brr_mn(mpert),brz_mn(mpert),brp_mn(mpert), + $ qvp_mn(mpert,0:mpsi),qvt_mn(mpert,0:mpsi), + $ qvz_mn(mpert,0:mpsi),cvp_mn(mpert,0:mpsi), + $ cvt_mn(mpert,0:mpsi),cvz_mn(mpert,0:mpsi), + $ qwp_mn(mpert,0:mpsi),qwt_mn(mpert,0:mpsi), + $ qwz_mn(mpert,0:mpsi),cwp_mn(mpert,0:mpsi), + $ cwt_mn(mpert,0:mpsi),cwz_mn(mpert,0:mpsi), + $ DST_mn(0:mthsurf,0:mpsi),DST_fun(0:mthsurf,0:mpsi)) IF(debug_flag) PRINT *, "->Leaving gpeq_alloc" c----------------------------------------------------------------------- c terminate. @@ -1903,7 +1924,10 @@ SUBROUTINE gpeq_dealloc $ xwp_mn,xwt_mn,xwz_mn,bwp_mn,bwt_mn,bwz_mn,xmt_mn,bmt_mn, $ xvp_mn,xvt_mn,xvz_mn,bvp_mn,bvt_mn,bvz_mn,xmz_mn,bmz_mn, $ xno_mn,xta_mn,xpa_mn,bno_mn,bta_mn,bpa_mn, - $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn) + $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, + $ qvp_mn,qvt_mn,qvz_mn,cvp_mn,cvt_mn,cvz_mn, + $ qwp_mn,qwt_mn,qwz_mn,cwp_mn,cwt_mn,cwz_mn, + $ DST_mn,DST_fun) IF(debug_flag) PRINT *, "->Leaving gpeq_dealloc" c----------------------------------------------------------------------- c terminate. diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 52c7ee1d6..09bfe611f 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -164,15 +164,7 @@ SUBROUTINE gpec_dealloc $ plas_indevmats,permeabevmats,chxmats,chwmats, $ mutual_indmats,mutual_indevmats,mutual_indinvmats, $ mutual_indinvevmats) - IF(ALLOCATED(cwp_mn)) DEALLOCATE(cwp_mn) - IF(ALLOCATED(cwt_mn)) DEALLOCATE(cwt_mn) - IF(ALLOCATED(cwz_mn)) DEALLOCATE(cwz_mn) - IF(ALLOCATED(cvp_mn)) DEALLOCATE(cvp_mn) - IF(ALLOCATED(cvt_mn)) DEALLOCATE(cvt_mn) - IF(ALLOCATED(cvz_mn)) DEALLOCATE(cvz_mn) - IF(ALLOCATED(DST_mn)) DEALLOCATE(DST_mn) - IF(ALLOCATED(DST_fun)) DEALLOCATE(DST_fun) - + CALL spline_dealloc(sq) CALL bicube_dealloc(eqfun) CALL bicube_dealloc(rzphi) diff --git a/gpec/gpout.f b/gpec/gpout.f index 6843924bf..a36d83fbd 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6912,23 +6912,6 @@ SUBROUTINE gpout_recon(mode, xspmn) c allocate C vector arrays for EPF calculation. c Storage: C_*_mn(mpert, 0:mpsi) for all psi levels. c----------------------------------------------------------------------- - IF (.NOT. ALLOCATED(cwp_mn)) THEN - ALLOCATE(cwp_mn(mpert, 0:mpsi)) - ALLOCATE(cwt_mn(mpert, 0:mpsi)) - ALLOCATE(cwz_mn(mpert, 0:mpsi)) - cwp_mn = 0.0_r8 - cwt_mn = 0.0_r8 - cwz_mn = 0.0_r8 - ENDIF - IF (.NOT. ALLOCATED(DST_mn)) THEN - ALLOCATE(DST_mn(mpert, 0:mpsi)) - DST_mn = 0.0_r8 - ENDIF - IF (.NOT. ALLOCATED(DST_fun)) THEN - ALLOCATE(DST_fun(0:mthsurf, 0:mpsi)) - DST_fun = 0.0_r8 - ENDIF - c Allocate C and Q vector spatial function arrays (theta, psi grid) IF (.NOT. ALLOCATED(cwp_fun)) THEN ALLOCATE(cwp_fun(0:mthsurf, 0:mpsi)) @@ -6972,27 +6955,6 @@ SUBROUTINE gpout_recon(mode, xspmn) qwt_fun = 0.0_r8 qwz_fun = 0.0_r8 ENDIF - - IF (.NOT. ALLOCATED(qvp_mn)) THEN - ALLOCATE(qvp_mn(mpert, 0:mpsi)) - ALLOCATE(qvt_mn(mpert, 0:mpsi)) - ALLOCATE(qvz_mn(mpert, 0:mpsi)) - ALLOCATE(cvp_mn(mpert, 0:mpsi)) - ALLOCATE(cvt_mn(mpert, 0:mpsi)) - ALLOCATE(cvz_mn(mpert, 0:mpsi)) - ALLOCATE(qwp_mn(mpert, 0:mpsi)) - ALLOCATE(qwt_mn(mpert, 0:mpsi)) - ALLOCATE(qwz_mn(mpert, 0:mpsi)) - qvp_mn = 0.0_r8 - qvt_mn = 0.0_r8 - qvz_mn = 0.0_r8 - cvp_mn = 0.0_r8 - cvt_mn = 0.0_r8 - cvz_mn = 0.0_r8 - qwp_mn = 0.0_r8 - qwt_mn = 0.0_r8 - qwz_mn = 0.0_r8 - ENDIF c----------------------------------------------------------------------- c allocate bicubes for shear, curvature, K (placeholder allocation). @@ -7089,13 +7051,10 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL gpeq_contra(psi) CALL gpeq_cova(psi) CALL gpeq_normal(psi) - CALL gpeq_tangent(psi) - CALL gpeq_parallel(psi) - CALL gpeq_rzphi(psi) -c Compute shear/curvature/K in mode-space and spatial-space. +c Compute shear/curvature/K in spatial-space. CALL gpeq_shear(psi, shear_fun) - CALL gpeq_curvature(psi, curv_mn, curv_fun) + CALL gpeq_curvature(psi, curv_fun) CALL gpeq_K(psi, K_mn, K_fun) c Store spatial values with coordinates. From d95f29ffe958bd72d8daef7f285d6c624d050345 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 23 Apr 2026 15:42:17 +0900 Subject: [PATCH 115/305] GPEC - FEATURE - Update core calculations (cherry picked from commit f6a5e27c0a4b226cb8db9f3c36f1402971563440) --- gpec/gpeq.f | 764 ++++++++++++++++++++++++++---------------------- gpec/gpglobal.F | 15 +- gpec/gpout.f | 451 +++++++++++++--------------- 3 files changed, 629 insertions(+), 601 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 706920fd1..ad1263812 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -19,18 +19,17 @@ c 11. gpeq_shear (reconstruction: magnetic shear) c 12. gpeq_curvature (reconstruction: curvature) c 13. gpeq_K (reconstruction: Bernstein K quantity) -c 14. gpeq_terms (reconstruction: integration terms) -c 15. gpeq_fcoords -c 16. gpeq_fcoordsout -c 17. gpeq_bcoords -c 18. gpeq_bcoordsout -c 19. gpeq_weight -c 20. gpeq_rzpgrid -c 21. gpeq_rzpdiv -c 22. gpeq_alloc -c 23. gpeq_dealloc -c 24. gpeq_interp_singsurf -c 25. gpeq_interp_sol +c 14. gpeq_fcoords +c 15. gpeq_fcoordsout +c 16. gpeq_bcoords +c 17. gpeq_bcoordsout +c 18. gpeq_weight +c 19. gpeq_rzpgrid +c 20. gpeq_rzpdiv +c 21. gpeq_alloc +c 22. gpeq_dealloc +c 23. gpeq_interp_singsurf +c 24. gpeq_interp_sol c----------------------------------------------------------------------- c subprogram 0. gpeq_mod. c module declarations. @@ -101,7 +100,7 @@ SUBROUTINE gpeq_sol(psi) xss_mn=-MATMUL(bmat,xsp1_mn)-MATMUL(cmat,xsp_mn) ENDIF c----------------------------------------------------------------------- -c compute contravariant b fields. +c compute Jacobian-weighted contravariant b fields: J b^i. c----------------------------------------------------------------------- bwp_mn=(chi1*singfac*twopi*ifac*xsp_mn) bwt_mn=-(chi1*xsp1_mn+twopi*ifac*nn*xss_mn) @@ -225,7 +224,8 @@ SUBROUTINE gpeq_contra(psi) END SUBROUTINE gpeq_contra c----------------------------------------------------------------------- c subprogram 3. gpeq_cova. -c compute covariant components. +c compute Jacobian-weighted covariant components from +c Jacobian-weighted contravariant components. c----------------------------------------------------------------------- SUBROUTINE gpeq_cova(psi) c----------------------------------------------------------------------- @@ -261,7 +261,7 @@ SUBROUTINE gpeq_cova(psi) g31(1:mband)=CONJG(g31(-1:-mband:-1)) g12(1:mband)=CONJG(g12(-1:-mband:-1)) c----------------------------------------------------------------------- -c compute covariant components with metric tensors. +c compute Jacobian-weighted covariant components with metric tensors. c----------------------------------------------------------------------- ipert=0 xvp_mn=0 @@ -655,73 +655,61 @@ SUBROUTINE gpeq_c(psi, ipsi) REAL(r8), INTENT(IN) :: psi INTEGER, INTENT(IN) :: ipsi - INTEGER :: itheta, ipert, jpert, m, m1, dm + INTEGER :: itheta, ipert, jpert, m1, dm - REAL(r8) :: q, q1, f1raw, p1, chi1, jac, delpsi - REAL(r8) :: eta, rfac, singfac + REAL(r8) :: q, q1, f1raw, p1, chi1, jac + REAL(r8) :: eta, rfac, v21, v22, v23, v33 REAL(r8), DIMENSION(0:mthsurf) :: jacs, dphi, r_vec, z_vec - - COMPLEX(r8), DIMENSION(-mband:mband) :: g11, g12, g22, g23, - $ g31, g33 - - COMPLEX(r8), DIMENSION(0:mthsurf) :: xi_psi_fun - COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bwt_fun, - $ bwz_fun, bvp_fun, bvt_fun, bvz_fun, - $ cvp_fun_p, cvt_fun_p, cvz_fun_p, xno_fun - + COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun, xno_fun, bno_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + +c Local temporal arrays for computation + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_funp, cvt_funp, cvz_funp + c Supporting variables for Fourier reconstruction REAL(r8) :: jwt, jwz c Supporting functions - REAL(r8), DIMENSION(0:mthsurf) :: delpsi_arr, dpdt_arr, dpdz_arr - -c Local temporal arrays for computation - COMPLEX(r8), DIMENSION(0:mthsurf) :: qwp_tmp, qwt_tmp, qwz_tmp - COMPLEX(r8), DIMENSION(0:mthsurf) :: qvp_tmp, qvt_tmp, qvz_tmp - COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_tmp, cwt_tmp, cwz_tmp - COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_tmp, cvt_tmp, cvz_tmp - + REAL(r8), DIMENSION(0:mthsurf) :: delpsi, dpdt, dpdz + REAL(r8), DIMENSION(0:mthsurf) :: g_22, g_23, g_33 + COMPLEX(r8), DIMENSION(-mband:mband) :: g11,g22,g33,g23,g31,g12 + IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi c----------------------------------------------------------------------- c 1) Setup: equilibrium and metric at this psi. c----------------------------------------------------------------------- CALL spline_eval(sq, psi, 1) + CALL cspline_eval(metric%cs, psi, 0) q = sq%f(4) q1 = sq%f1(4) f1raw = sq%f1(1) - p1 = sq%f1(2) + p1 = sq%f1(2) / mu0 chi1 = psio * twopi - - CALL cspline_eval(metric%cs, psi, 0) - g11(0:-mband:-1) = metric%cs%f(1:mband+1) - g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) - g33(0:-mband:-1) = metric%cs%f(4*mband+6:5*mband+6) - g23(0:-mband:-1) = metric%cs%f(3*mband+4:4*mband+4) - g31(0:-mband:-1) = metric%cs%f(4*mband+5:5*mband+5) - g12(0:-mband:-1) = metric%cs%f(5*mband+6:6*mband+6) - - g11(1:mband) = CONJG(g11(-1:-mband:-1)) - g22(1:mband) = CONJG(g22(-1:-mband:-1)) - g33(1:mband) = CONJG(g33(-1:-mband:-1)) - g23(1:mband) = CONJG(g23(-1:-mband:-1)) - g31(1:mband) = CONJG(g31(-1:-mband:-1)) - g12(1:mband) = CONJG(g12(-1:-mband:-1)) +c----------------------------------------------------------------------- + CALL iscdftb(mfac,mpert,xwp_fun,mthsurf,xwp_mn) c----------------------------------------------------------------------- c 3) Reconstruct eigenfunctions in spatial representation. c----------------------------------------------------------------------- - CALL iscdftb(mfac, mpert, xi_psi_fun, mthsurf, xsp_mn) CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) - CALL iscdftb(mfac, mpert, bwt_fun, mthsurf, bwt_mn) - CALL iscdftb(mfac, mpert, bwz_fun, mthsurf, bwz_mn) - - CALL iscdftb(mfac,mpert,bvp_fun,mthsurf,bvp_mn) - CALL iscdftb(mfac,mpert,bvt_fun,mthsurf,bvt_mn) - CALL iscdftb(mfac,mpert,bvz_fun,mthsurf,bvz_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) - CALL iscdftb(mfac,mpert,xno_fun,mthsurf,xno_mn) c----------------------------------------------------------------------- c 4) Extract contravariant components: divide by jacobian. +c +c bwp_mn, bwt_mn, bwz_mn are complex Fourier mode coefficients +c (derived from complex eigenfunctions xsp_mn, xss_mn). +c After IFFT via iscdftb(), they remain complex-valued functions +c of theta. Dividing by real jacobian preserves complex nature. +c This is essential because perturbations are inherently complex +c functions in MHD stability analysis. c----------------------------------------------------------------------- DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) @@ -732,10 +720,8 @@ SUBROUTINE gpeq_c(psi, ipsi) z_vec(itheta) = zo + rfac*SIN(eta) jacs(itheta) = jac dphi(itheta) = rzphi%f(3) - delpsi_arr(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) - w(1,1) = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_vec(itheta) - $ /jac + w(1,1) = (1.0+ rzphi%fy(2))*twopi**2*rfac*r_vec(itheta)/jac w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) w(2,1) = -twopi**2*rfac*r_vec(itheta)*rzphi%fx(2)/jac @@ -746,97 +732,126 @@ SUBROUTINE gpeq_c(psi, ipsi) w(3,2) = (r_vec(itheta)/(2*rfac*jac))* $ (rzphi%fx(3)*rzphi%fy(1)-rzphi%fx(1)*rzphi%fy(3)) - dpdt_arr(itheta) = w(1,1)*w(2,1) + w(1,2)*w(2,2) - dpdz_arr(itheta) = w(1,1)*w(3,1) + w(1,2)*w(3,2) + delpsi(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) + dpdt(itheta) = w(1,1)*w(2,1) + w(1,2)*w(2,2) + dpdz(itheta) = w(1,1)*w(3,1) + w(1,2)*w(3,2) - qwp_tmp(itheta) = bwp_fun(itheta) / CMPLX(jac, 0.0_r8, r8) - qwt_tmp(itheta) = bwt_fun(itheta) / CMPLX(jac, 0.0_r8, r8) - qwz_tmp(itheta) = bwz_fun(itheta) / CMPLX(jac, 0.0_r8, r8) +c contravariant basis vectors (idcon_metric style - NO jac) + v21 = rzphi%fy(1)/(2*rfac) + v22 = (1+rzphi%fy(2))*twopi*rfac + v23 = rzphi%fy(3)*r_vec(itheta) + v33 = twopi*r_vec(itheta) - qvp_tmp(itheta) = bvp_fun(itheta) - qvt_tmp(itheta) = bvt_fun(itheta) - qvz_tmp(itheta) = bvz_fun(itheta) - +c metric tensor: g_ij = sum(v_i * v_j) + g_22(itheta) = (v21**2 + v22**2 + v23**2) + g_33(itheta) = (v33**2) + g_23(itheta) = (v23*v33) ENDDO + + xno_fun=xwp_fun/(jacs*delpsi) + bno_fun=bwp_fun/(jacs*delpsi) c----------------------------------------------------------------------- -c 6) Apply physics method: add j × ξ term to covariant C. +c 6) Compute C components. +c bwp stores J Q^psi, while bmt/bmz store the modified +c upper-family J Q^theta/J Q^zeta used consistently by gpeq_cova. +c +c The metric stored in metric%cs is g_ij / J, so lowering via gpeq_cova +c maps J Q^i -> Q_i. Therefore bv* and cv2* are lower/covariant +c components without an extra Jacobian factor. +c +c xwp_fun stores J xi^psi. Therefore: +c - upper J C^i corrections use xwp_fun/(J |grad psi|^2), +c - lower C_i corrections use xwp_fun/|grad psi|^2 or xwp_fun*j^i. +c This matches Eqs. (148)-(151) together with the metric convention +c used in idcon_metric. c----------------------------------------------------------------------- DO itheta = 0, mthsurf jac = jacs(itheta) - delpsi = delpsi_arr(itheta)**2 - jwt = - sq%f1(3) / jac - jwz = - sq%f1(2) / chi1 - sq%f1(3) * q / jac - - cwp_tmp(itheta) = qwp_tmp(itheta) - cwt_tmp(itheta) = qwt_tmp(itheta)+xno_fun(itheta)*jwz / delpsi - cwz_tmp(itheta) = qwz_tmp(itheta)-xno_fun(itheta)*jwt / delpsi - - cvp_fun_p(itheta) = qvp_tmp(itheta)+ jac*xno_fun(itheta)/delpsi - $ * (jwt*(dpdz_arr(itheta)) + jwz*(dpdt_arr(itheta))) - cvt_fun_p(itheta) = qvt_tmp(itheta)+ jac*xi_psi_fun(itheta)*jwz - cvz_fun_p(itheta) = qvz_tmp(itheta)- jac*xi_psi_fun(itheta)*jwt + jwt = - f1raw / jac + jwz = - p1 * mu0 / chi1 - f1raw * q / jac + +c J C^psi = J Q^psi + cwp_fun(itheta)= bwp_fun(itheta) + +c J C^theta = J Q^theta + xi^psi/|grad psi|^2 +c * [mu0 j^theta g_theta_zeta + mu0 j^zeta g_zeta_zeta] +c = J Q^theta + xwp_fun/(J |grad psi|^2) * [...] + cwt_fun(itheta) = bmt_fun(itheta) + xwp_fun(itheta) + $ / ((delpsi(itheta)**2) * jac )* + $ (jwt * g_23(itheta) + jwz * g_33(itheta)) + +c J C^zeta = J Q^zeta - xi^psi/|grad psi|^2 +c * [mu0 j^theta g_theta_theta + mu0 j^zeta g_theta_zeta] +c = J Q^zeta - xwp_fun/(J |grad psi|^2) * [...] + cwz_fun(itheta) = bmz_fun(itheta) - xwp_fun(itheta) + $ / ((delpsi(itheta)**2) * jac )* + $ (jwt * g_22(itheta) + jwz * g_23(itheta)) + +c C_psi = Q_psi + (J xi^psi)/|grad psi|^2 +c * [mu0 j^theta (grad psi.grad zeta) - mu0 j^zeta (grad psi.grad theta)] + cvp_fun(itheta) = bvp_fun(itheta) + + $ xwp_fun(itheta) / (delpsi(itheta)**2) * + $ (jwt * dpdz(itheta) - jwz * dpdt(itheta)) + +c C_theta = Q_theta + (J xi^psi) mu0 j^zeta + cvt_fun(itheta) = bvt_fun(itheta) + + $ xwp_fun(itheta) * jwz + +c C_zeta = Q_zeta - (J xi^psi) mu0 j^theta + cvz_fun(itheta) = bvz_fun(itheta) - + $ xwp_fun(itheta) * jwt ENDDO + cwp_mn = 0 + cwt_mn = 0 + cwz_mn = 0 c----------------------------------------------------------------------- -c 7) Metric contraction: C covariant = g_ij * C contravariant. +c compute lower half of matrices. c----------------------------------------------------------------------- - DO itheta = 0, mthsurf - cvp_tmp(itheta) = g11(0)*cwp_tmp(itheta) + - $ g12(0)*cwt_tmp(itheta) + g31(0)*cwz_tmp(itheta) - cvt_tmp(itheta) = g12(0)*cwp_tmp(itheta) + - $ g22(0)*cwt_tmp(itheta) + g23(0)*cwz_tmp(itheta) - cvz_tmp(itheta) = g31(0)*cwp_tmp(itheta) + - $ g23(0)*cwt_tmp(itheta) + g33(0)*cwz_tmp(itheta) - ENDDO + g11(0:-mband:-1)=metric%cs%f(1:mband+1) + g22(0:-mband:-1)=metric%cs%f(mband+2:2*mband+2) + g33(0:-mband:-1)=metric%cs%f(2*mband+3:3*mband+3) + g23(0:-mband:-1)=metric%cs%f(3*mband+4:4*mband+4) + g31(0:-mband:-1)=metric%cs%f(4*mband+5:5*mband+5) + g12(0:-mband:-1)=metric%cs%f(5*mband+6:6*mband+6) c----------------------------------------------------------------------- -c 8) Store results to global arrays (spatial and mode space). -c----------------------------------------------------------------------- - cwp_fun(0:mthsurf, ipsi) = cwp_tmp(0:mthsurf) - cwt_fun(0:mthsurf, ipsi) = cwt_tmp(0:mthsurf) - cwz_fun(0:mthsurf, ipsi) = cwz_tmp(0:mthsurf) - - cvp_fun(0:mthsurf, ipsi) = cvp_tmp(0:mthsurf) - cvt_fun(0:mthsurf, ipsi) = cvt_tmp(0:mthsurf) - cvz_fun(0:mthsurf, ipsi) = cvz_tmp(0:mthsurf) - - cvp_funp(0:mthsurf, ipsi) = cvp_fun_p(0:mthsurf) - cvt_funp(0:mthsurf, ipsi) = cvt_fun_p(0:mthsurf) - cvz_funp(0:mthsurf, ipsi) = cvz_fun_p(0:mthsurf) - - qwp_fun(0:mthsurf, ipsi) = qwp_tmp(0:mthsurf) - qwt_fun(0:mthsurf, ipsi) = qwt_tmp(0:mthsurf) - qwz_fun(0:mthsurf, ipsi) = qwz_tmp(0:mthsurf) - - qvp_fun(0:mthsurf, ipsi) = qvp_tmp(0:mthsurf) - qvt_fun(0:mthsurf, ipsi) = qvt_tmp(0:mthsurf) - qvz_fun(0:mthsurf, ipsi) = qvz_tmp(0:mthsurf) - - - CALL iscdftf(mfac, mpert, cwp_tmp, mthsurf, - $ cwp_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, cwt_tmp, mthsurf, - $ cwt_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, cwz_tmp, mthsurf, - $ cwz_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, cvp_fun_p, mthsurf, - $ cvp_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, cvt_fun_p, mthsurf, - $ cvt_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, cvz_fun_p, mthsurf, - $ cvz_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qwp_tmp, mthsurf, - $ qwp_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qwt_tmp, mthsurf, - $ qwt_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qwz_tmp, mthsurf, - $ qwz_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qvp_tmp, mthsurf, - $ qvp_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qvt_tmp, mthsurf, - $ qvt_mn(1:mpert, ipsi)) - CALL iscdftf(mfac, mpert, qvz_tmp, mthsurf, - $ qvz_mn(1:mpert, ipsi)) - +c compute upper half of matrices. +c----------------------------------------------------------------------- + g11(1:mband)=CONJG(g11(-1:-mband:-1)) + g22(1:mband)=CONJG(g22(-1:-mband:-1)) + g33(1:mband)=CONJG(g33(-1:-mband:-1)) + g23(1:mband)=CONJG(g23(-1:-mband:-1)) + g31(1:mband)=CONJG(g31(-1:-mband:-1)) + g12(1:mband)=CONJG(g12(-1:-mband:-1)) + + CALL iscdftf(mfac, mpert, cwp_fun, mthsurf,cwp_mn) + CALL iscdftf(mfac, mpert, cwt_fun, mthsurf,cwt_mn) + CALL iscdftf(mfac, mpert, cwz_fun, mthsurf,cwz_mn) + CALL iscdftf(mfac, mpert, cvp_fun, mthsurf,cvp_mn) + CALL iscdftf(mfac, mpert, cvt_fun, mthsurf,cvt_mn) + CALL iscdftf(mfac, mpert, cvz_fun, mthsurf,cvz_mn) + + ipert = 0 + c2vp_mn = 0 + c2vt_mn = 0 + c2vz_mn = 0 + DO m1=mlow,mhigh + ipert=ipert+1 + DO dm=MAX(1-ipert,-mband),MIN(mpert-ipert,mband) + jpert=ipert+dm + c2vp_mn(ipert)=c2vp_mn(ipert)+g11(dm)*cwp_mn(jpert)+ + $ g12(dm)*cwt_mn(jpert)+g31(dm)*cwz_mn(jpert) + c2vt_mn(ipert)=c2vt_mn(ipert)+g12(dm)*cwp_mn(jpert)+ + $ g22(dm)*cwt_mn(jpert)+g23(dm)*cwz_mn(jpert) + c2vz_mn(ipert)=c2vz_mn(ipert)+g31(dm)*cwp_mn(jpert)+ + $ g23(dm)*cwt_mn(jpert)+g33(dm)*cwz_mn(jpert) + ENDDO + ENDDO + + CALL iscdftb(mfac, mpert, cvp_funp, mthsurf, c2vp_mn) + CALL iscdftb(mfac, mpert, cvt_funp, mthsurf, c2vt_mn) + CALL iscdftb(mfac, mpert, cvz_funp, mthsurf, c2vz_mn) + IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi c----------------------------------------------------------------------- c terminate. @@ -844,81 +859,122 @@ SUBROUTINE gpeq_c(psi, ipsi) RETURN END SUBROUTINE gpeq_c c----------------------------------------------------------------------- -c subprogram 9b. gpeq_epf. -c compute C norm squared: three forms (cw only, cv only, mixed). -c----------------------------------------------------------------------- - SUBROUTINE gpeq_epf(psi, ipsi) +c subprogram 9c. gpeq_cveri. +c verify the C-vector identity +c +c (curl C) · grad(psi) +c = (d C_zeta / d theta - d C_theta / d zeta) / J +c +c using the single-n Fourier convention +c +c exp[2*pi*i*(m*theta - n*zeta)]. +c +c Therefore +c +c d/dtheta -> 2*pi*i*m +c d/dzeta -> -2*pi*i*n +c +c and +c +c (curl C) · grad(psi) +c = (d_theta C_zeta + 2*pi*i*n*C_theta)/J. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_cveri(psi, cveri_fun) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- REAL(r8), INTENT(IN) :: psi - INTEGER, INTENT(IN) :: ipsi + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: cveri_fun - INTEGER :: itheta - - COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_tmp, cwt_tmp, cwz_tmp - COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_tmp, cvt_tmp, cvz_tmp - - COMPLEX(r8), DIMENSION(0:mthsurf) :: c2_cw_tmp,c2_cv_tmp, - $ c2_cvw_tmp - - IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi + INTEGER :: itheta, ipert + COMPLEX(r8), DIMENSION(mpert) :: dth_cvz_mn, dzt_cvt_mn + COMPLEX(r8), DIMENSION(mpert) :: curlpsi_mn + + IF(debug_flag) PRINT *, "Entering gpeq_cveri" c----------------------------------------------------------------------- -c Call gpeq_c to compute C components first. +c prepare psi-local equilibrium and reconstruct C. c----------------------------------------------------------------------- - CALL gpeq_c(psi, ipsi) - + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_c(psi, 0) + c----------------------------------------------------------------------- -c Extract C components from global storage. +c exact theta/zeta derivatives in mode space. c----------------------------------------------------------------------- - cwp_tmp(0:mthsurf) = cwp_fun(0:mthsurf, ipsi) - cwt_tmp(0:mthsurf) = cwt_fun(0:mthsurf, ipsi) - cwz_tmp(0:mthsurf) = cwz_fun(0:mthsurf, ipsi) - - cvp_tmp(0:mthsurf) = cvp_fun(0:mthsurf, ipsi) - cvt_tmp(0:mthsurf) = cvt_fun(0:mthsurf, ipsi) - cvz_tmp(0:mthsurf) = cvz_fun(0:mthsurf, ipsi) + DO ipert = 1, mpert + dth_cvz_mn(ipert) = twopi * ifac * mfac(ipert) * cvz_mn(ipert) + dzt_cvt_mn(ipert) = -twopi * ifac * nn * cvt_mn(ipert) + curlpsi_mn(ipert) = dth_cvz_mn(ipert) - dzt_cvt_mn(ipert) + ENDDO + + CALL iscdftb(mfac, mpert, cveri_fun, mthsurf, curlpsi_mn) c----------------------------------------------------------------------- -c 1) C^2 from contravariant components only. -c Note: |C^i|^2 = C^i C^i (no metric needed for this form) +c divide by Jacobian pointwise: +c (curl C) · grad(psi) = (d_theta C_zeta - d_zeta C_theta)/J c----------------------------------------------------------------------- DO itheta = 0, mthsurf - c2_cw_tmp(itheta) = cwp_tmp(itheta)*cwp_tmp(itheta)+ - $ cwt_tmp(itheta)*cwt_tmp(itheta)+ - $ cwz_tmp(itheta)*cwz_tmp(itheta) + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + cveri_fun(itheta) = cveri_fun(itheta) / jac ENDDO - + + IF(debug_flag) PRINT *, "->Leaving gpeq_cveri" c----------------------------------------------------------------------- -c 2) C^2 from covariant components only. -c Note: |C_i|^2 = C_i C_i (metric already included in cvp_tmp) +c terminate. c----------------------------------------------------------------------- - DO itheta = 0, mthsurf - c2_cv_tmp(itheta) = cvp_tmp(itheta)*cvp_tmp(itheta)+ - $ cvt_tmp(itheta)*cvt_tmp(itheta)+ - $ cvz_tmp(itheta)*cvz_tmp(itheta) - ENDDO - + RETURN + END SUBROUTINE gpeq_cveri c----------------------------------------------------------------------- -c 3) C^2 using both covariant and contravariant. -c Note: C_i C^i = CONJG(cvp) * cwp + CONJG(cvt) * cwt + ... +c subprogram 9b. gpeq_epf. +c compute the flux-surface integral of the first EPF kernel, +c +c \int dtheta dzeta J |C|^2 / mu0 +c +c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - DO itheta = 0, mthsurf - c2_cvw_tmp(itheta) = CONJG(cvp_tmp(itheta))* - $ cwp_tmp(itheta)+CONJG(cvt_tmp(itheta))*cwt_tmp(itheta)+ - $ CONJG(cvz_tmp(itheta))*cwz_tmp(itheta) - ENDDO - + SUBROUTINE gpeq_epf(psi, epf_int) c----------------------------------------------------------------------- -c Store results to global arrays. +c declaration. c----------------------------------------------------------------------- - IF (ipsi <= mpsi) THEN - c2_cw_fun(0:mthsurf, ipsi) = c2_cw_tmp(0:mthsurf) - c2_cv_fun(0:mthsurf, ipsi) = c2_cv_tmp(0:mthsurf) - c2_cvw_fun(0:mthsurf, ipsi) = c2_cvw_tmp(0:mthsurf) - ENDIF + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: epf_int + INTEGER :: itheta + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cv2p_fun, cv2t_fun, cv2z_fun + COMPLEX(r8) :: epf_theta + + IF(debug_flag) PRINT *, "Entering gpeq_epf" +c----------------------------------------------------------------------- +c Prepare psi-local perturbed equilibrium and compute C components. +c----------------------------------------------------------------------- + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_c(psi, 0) + + CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cv2p_fun, mthsurf, c2vp_mn) + CALL iscdftb(mfac, mpert, cv2t_fun, mthsurf, c2vt_mn) + CALL iscdftb(mfac, mpert, cv2z_fun, mthsurf, c2vz_mn) + + epf_int = 0.0_r8 + DO itheta = 0, mthsurf-1 + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + epf_theta = CONJG(cwp_fun(itheta)/jac) * cv2p_fun(itheta) + + $ CONJG(cwt_fun(itheta)/jac) * cv2t_fun(itheta) + + $ CONJG(cwz_fun(itheta)/jac) * cv2z_fun(itheta) + epf_int = epf_int + REAL(epf_theta, r8) / + $ (mu0 * REAL(mthsurf, r8)) * jac + ENDDO - IF(debug_flag) PRINT *, "->Leaving gpeq_epf at ipsi=", ipsi + IF(debug_flag) PRINT *, "->Leaving gpeq_epf" c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -926,27 +982,35 @@ SUBROUTINE gpeq_epf(psi, ipsi) END SUBROUTINE gpeq_epf c----------------------------------------------------------------------- c subprogram 10. gpeq_dst. -c compute DST(psi,theta) = K(psi,theta) * CONJG(xi_normal(psi,theta)). -c Store both theta-space and mode-space for later integration. +c compute the flux-surface integral of the destabilizing kernel, +c +c \int dtheta dzeta J * K * xi_n^2 +c +c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - SUBROUTINE gpeq_dst(psi, ipsi) + SUBROUTINE gpeq_dst(psi, dst_int) REAL(r8), INTENT(IN) :: psi - INTEGER, INTENT(IN) :: ipsi - COMPLEX(r8), DIMENSION(mpert) :: K_mn - COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xi_normal_fun, - $ dst_theta_fun + COMPLEX(r8), INTENT(OUT) :: dst_int + INTEGER :: itheta + COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xno_fun c----------------------------------------------------------------------- -c DST(psi,theta) = K(psi,theta) * CONJG(xi_normal(psi,theta)) -c Store both theta-space and mode-space for later integration. +c DST(psi) = \int dtheta dzeta J * K * xi_n^2 c----------------------------------------------------------------------- - CALL gpeq_K(psi, K_mn, K_fun) - CALL iscdftb(mfac, mpert, xi_normal_fun, mthsurf, xsp_mn) - dst_theta_fun = K_fun * CONJG(xi_normal_fun) - - DST_fun(0:mthsurf, ipsi) = dst_theta_fun - CALL iscdftf(mfac, mpert, dst_theta_fun, mthsurf, - $ DST_mn(1:mpert, ipsi)) + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_K(psi, K_fun) + CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) + dst_int = CMPLX(0.0_r8, 0.0_r8, r8) + DO itheta = 0, mthsurf-1 + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + dst_int = dst_int + + $ jac* K_fun(itheta) * + $ ABS(xno_fun(itheta))**2 / REAL(mthsurf, r8) + ENDDO c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -962,11 +1026,9 @@ SUBROUTINE gpeq_shear(psi, shear_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: shear_fun INTEGER :: itheta - REAL(r8) :: q, q1, jac, rfac, eta, r_val, theta_val - REAL(r8) :: w11, w12, w21, w22, w31, w32 - REAL(r8) :: v11, v12, v13, v21, v22, v23, v33 + REAL(r8) :: r_val, theta_val REAL(r8) :: dpdp, dpdt, dpdz, shear_deriv - REAL(r8) :: shear_contra, shear_cova, diff_shear + REAL(r8) :: shear_contra TYPE(spline_type) :: shear_temp c----------------------------------------------------------------------- @@ -986,7 +1048,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c----------------------------------------------------------------------- CALL spline_alloc(shear_temp, mthsurf, 5) shear_temp%xs = theta(0:mthsurf) - shear_temp%name = "shear_temp" + shear_temp%name = "shear_" c----------------------------------------------------------------------- c Step 1: Compute shear components at all theta points. @@ -1002,42 +1064,43 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c Compute w_ij contravariant metric in Cartesian components - w11 = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_val/jac - w12 = -rzphi%fy(1)*pi*r_val/(rfac*jac) + w(1,1) = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2) = -rzphi%fy(1)*pi*r_val/(rfac*jac) - w21 = -twopi**2*rfac*r_val*rzphi%fx(2)/jac - w22 = pi*r_val*rzphi%fx(1)/(rfac*jac) + w(2,1) = -twopi**2*rfac*r_val*rzphi%fx(2)/jac + w(2,2) = pi*r_val*rzphi%fx(1)/(rfac*jac) - w31 = (twopi*r_val*rfac/jac)* + w(3,1) = (twopi*r_val*rfac/jac)* $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)*(1.0_r8 + $ rzphi%fy(2))) - w32 = (r_val/(2.0_r8*rfac*jac))* + w(3,2) = (r_val/(2.0_r8*rfac*jac))* $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) c Compute covariant basis v_ij = ∂r/∂ξ_j (Cartesian components) c From recon_metric: - v11 = rzphi%fx(1)/(2.0_r8*rfac*jac) - v12 = rzphi%fx(2)*twopi*rfac/jac - v13 = rzphi%fx(3)*r_val/jac + v(1,1) = rzphi%fx(1)/(2.0_r8*rfac*jac) + v(1,2) = rzphi%fx(2)*twopi*rfac/jac + v(1,3) = rzphi%fx(3)*r_val/jac - v21 = rzphi%fy(1)/(2.0_r8*rfac*jac) - v22 = (1.0_r8 + rzphi%fy(2))*twopi*rfac/jac - v23 = rzphi%fy(3)*r_val/jac + v(2,1) = rzphi%fy(1)/(2.0_r8*rfac*jac) + v(2,2) = (1.0_r8 + rzphi%fy(2))*twopi*rfac/jac + v(2,3) = rzphi%fy(3)*r_val/jac c Component 33 of covariant basis - v33 = twopi*r_val/jac + v(3,3) = twopi*r_val/jac c Contravariant dot products: dpdp = ∇ψ·∇ψ, etc. - dpdp = w11**2 + w12**2 - dpdt = w11*w21 + w12*w22 - dpdz = w11*w31 + w12*w32 + dpdp = w(1,1)**2 + w(1,2)**2 + dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) + dpdz = w(1,1)*w(3,1) + w(1,2)*w(3,2) shear_temp%fs(itheta, 1) = twopi*r_val*jac* - $ (-(v11*v21+v12*v22)*(v23+q*v33)+v13*(v21**2+v22**2)) + $ (-(v(1,1)*v(2,1)+v(1,2)*v(2,2))*(v(2,3)+q*v(3,3)) + $ +v(1,3)*(v(2,1)**2+v(2,2)**2)) shear_temp%fs(itheta, 2) = (q*dpdt - dpdz) shear_temp%fs(itheta, 3) = dpdp shear_temp%fs(itheta, 4) = - $ twopi**2 * r_val**2 * (v21**2 + v22**2) + $ twopi**2 * r_val**2 * (v(2,1)**2 + v(2,2)**2) shear_temp%fs(itheta, 5) = @@ -1061,7 +1124,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c Contravariant approach: derivative of component 5 shear_deriv = shear_temp%f1(5) - shear_contra = (twopi**2/jac)*(q1 + shear_deriv) + shear_contra = (chi1**2/jac)*(q1 + shear_deriv) shear_fun(itheta) = CMPLX(shear_contra, 0.0_r8, r8) ENDDO @@ -1076,29 +1139,23 @@ END SUBROUTINE gpeq_shear c----------------------------------------------------------------------- c subprogram 12. gpeq_curvature. c compute curvature in spatial domain. -c κ·∇ψ = (|∇ψ|²/B²)[μ₀p' + (1/2)(∂B²/∂ψ) + (1/2)(∂B²/∂θ)(∇ψ·∇ψ)/(∇ψ·∇θ)] +c κ·∇ψ = (|∇ψ|²/B²)[μ₀p' + (1/2)(∂B²/∂ψ) + (1/2)(∂B²/∂θ)(∇ψ·∇θ)/(∇ψ·∇ψ)] c----------------------------------------------------------------------- SUBROUTINE gpeq_curvature(psi, curv_fun) REAL(r8), INTENT(IN) :: psi COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: curv_fun INTEGER :: itheta - REAL(r8) :: q, q1, p1, jac, rfac, eta, r_val, theta_val - REAL(r8) :: chi1, delpsi - REAL(r8) :: w11, w12, w13, w21, w22, w23, w31, w32, w33 - REAL(r8) :: v21, v22, v23, v33 + REAL(r8) :: r_val, theta_val + REAL(r8) :: delpsi REAL(r8) :: bsq_val, bsq_psi, bsq_theta - REAL(r8) :: dpdt , kappa_psi, safe_denom - TYPE(spline_type) :: bsq_temp + REAL(r8) :: dpdt , kappa_psi c----------------------------------------------------------------------- c Setup: get equilibrium at this psi. c----------------------------------------------------------------------- CALL spline_eval(sq, psi, 1) - q = sq%f(4) - q1 = sq%f1(4) - p1 = sq%f1(2) - chi1 = psio * twopi + p1 = sq%f1(2) / mu0 c----------------------------------------------------------------------- c Step 3: Compute curvature at all theta points. @@ -1113,21 +1170,21 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) r_val = ro + rfac*COS(eta) c Compute contravariant metrics - w11 = (1.0_r8 + rzphi%fy(2))*(twopi**2)*rfac*r_val/jac - w12 = -rzphi%fy(1)*pi*r_val/(rfac*jac) + w(1,1) = (1.0+ rzphi%fy(2))*(twopi**2)*rfac*r_val/jac + w(1,2) = -rzphi%fy(1)*pi*r_val/(rfac*jac) - w21 = -(twopi**2)*rfac*r_val*rzphi%fx(2)/jac - w22 = pi*r_val*rzphi%fx(1)/(rfac*jac) + w(2,1) = -(twopi**2)*rfac*r_val*rzphi%fx(2)/jac + w(2,2) = pi*r_val*rzphi%fx(1)/(rfac*jac) - w31 = (twopi*r_val*rfac/jac)* + w(3,1) = (twopi*r_val*rfac/jac)* $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)* $ (1.0_r8 + rzphi%fy(2))) - w32 = (r_val/(2.0_r8*rfac*jac))* + w(3,2) = (r_val/(2.0_r8*rfac*jac))* $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) c Compute |∇ψ|² and ∇ψ·∇θ - delpsi = SQRT(w11**2 + w12**2) - dpdt = w11*w21 + w12*w22 + delpsi = SQRT(w(1,1)**2 + w(1,2)**2) + dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) CALL bicube_eval(eqfun, psi, theta_val, 1) c Retrieve B² and derivatives @@ -1138,7 +1195,7 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) c Compute κ·∇ψ safely kappa_psi = (delpsi**2 / bsq_val) * - $ (p1 + 0.5_r8*bsq_psi + + $ (p1*mu0 + 0.5_r8*bsq_psi + $ 0.5_r8*bsq_theta*dpdt/(delpsi**2)) curv_fun(itheta) = CMPLX(kappa_psi, 0.0_r8, r8) @@ -1150,101 +1207,117 @@ END SUBROUTINE gpeq_curvature c----------------------------------------------------------------------- c subprogram 13. gpeq_K. c compute Bernstein K quantity (stability indicator). +c K = |∇ψ_dcon|^2 * σ * S_dcon + B^2 * σ^2 + 2 P' * κ^psi +c Uses pre-computed metric from idcon_metric c----------------------------------------------------------------------- - SUBROUTINE gpeq_K(psi, K_mn, K_fun) + SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, + $ sigma_fun, jdotb_fun, shear_out, curv_out) REAL(r8), INTENT(IN) :: psi - COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: K_mn - COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) - $ :: K_fun - - INTEGER :: ipert, jpert, dm, m1 - REAL(r8) :: q, f1, p1, chi1, jac, jac1, bpfac, btfac - REAL(r8) :: bth, bze, jth, jze - COMPLEX(r8), DIMENSION(-mband:mband) :: g22, g33, g23 - COMPLEX(r8), DIMENSION(0:mthsurf) :: K_theta_fun - COMPLEX(r8), DIMENSION(mpert) :: bvt_local, bvz_local - COMPLEX(r8), DIMENSION(mpert) :: sigma_mn - + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: K_fun + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ K_term1, K_term2, K_term3, sigma_fun, jdotb_fun + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ shear_out, curv_out + + INTEGER :: itheta + REAL(r8) :: f1raw, delpsi + REAL(r8) :: jwt, jwz, bth, bze, bsq_val, sigma, jdotb_val + REAL(r8) :: g22, g23, g33, r_val + REAL(r8), DIMENSION(0:mthsurf) :: K_t1, K_t2, K_t3 + REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun + chi1 = psio * twopi - - CALL spline_eval(sq, psi, 1) - f1 = sq%f1(1) / twopi - p1 = sq%f1(2) - q = sq%f(4) - CALL bicube_eval(rzphi, psi, 0.0_r8, 0) - jac = rzphi%f(4) - jac1 = rzphi%fx(4) - - CALL cspline_eval(metric%cs, psi, 0) - g22(0:-mband:-1) = metric%cs%f(2*mband+2:3*mband+2) - g23(0:-mband:-1) = metric%cs%f(3*mband+3:4*mband+3) - g33(0:-mband:-1) = metric%cs%f(4*mband+4:5*mband+4) - g22(1:mband) = CONJG(g22(-1:-mband:-1)) - g23(1:mband) = CONJG(g23(-1:-mband:-1)) - g33(1:mband) = CONJG(g33(-1:-mband:-1)) - -c----------------------------------------------------------------------- -c compute K-related quantities in mode space. -c----------------------------------------------------------------------- - ipert = 0 - K_mn = CMPLX(0.0_r8, 0.0_r8, r8) - DO m1 = mlow, mhigh - ipert = ipert + 1 - DO dm = MAX(1-ipert, -mband), MIN(mpert-ipert, mband) - jpert = ipert + dm - jth = -f1*twopi/jac - jze = q*jth - p1/chi1 - bth = chi1 / jac - bze = q * chi1 / jac - - K_mn(ipert) = K_mn(ipert) + - $ (g22(dm)*bth*jth + g33(dm)*bze*jze + - $ g23(dm)*(bth*jze + bze*jth)) * bwp_mn(jpert) - ENDDO - ENDDO - + + IF(debug_flag) PRINT *, "Entering gpeq_K" c----------------------------------------------------------------------- -c convert to spatial functions if requested. +c load equilibrium parameters c----------------------------------------------------------------------- - IF (PRESENT(K_fun)) THEN - CALL iscdftb(mfac, mpert, K_theta_fun, mthsurf, K_mn) - K_fun = K_theta_fun - ENDIF - - RETURN - END SUBROUTINE gpeq_K + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1 = sq%f1(2) / mu0 + q = sq%f(4) c----------------------------------------------------------------------- -c subprogram 13. gpeq_terms. -c compute integration terms for reconstruction diagnostics. +c compute shear and curvature c----------------------------------------------------------------------- - SUBROUTINE gpeq_terms(psi, mode, xspmn, term_mn, term_fun) - REAL(r8), INTENT(IN) :: psi - INTEGER, INTENT(IN) :: mode - COMPLEX(r8), DIMENSION(mpert), INTENT(IN) :: xspmn - COMPLEX(r8), DIMENSION(mpert), INTENT(OUT) :: term_mn - COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) - $ :: term_fun - - INTEGER :: ipert, jpert, dm, m1 - COMPLEX(r8), DIMENSION(0:mthsurf) :: term_theta_fun - + CALL gpeq_shear(psi, shear_fun) + CALL gpeq_curvature(psi, curv_fun) c----------------------------------------------------------------------- -c placeholder: compute integration terms. -c full implementation depends on integration methodology. +c compute K in spatial domain (idcon_metric convention) c----------------------------------------------------------------------- - term_mn = CMPLX(0.0_r8, 0.0_r8, r8) - + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + jac = rzphi%f(4) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + +c |∇ψ| magnitude + w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) + delpsi=SQRT(w(1,1)**2+w(1,2)**2) + +c current and magnetic field components +c note: -f1raw/jac is the mu0*j^theta component (DCON convention), +c so divide by mu0 to get the physical current j for K = mu0*sigma^2*B^2. + jwt = -f1raw/(jac*mu0) + jwz = q*jwt - p1/chi1 + bth = chi1 / jac + bze = q * chi1 / jac + +c contravariant basis vectors (idcon_metric style - NO jac) + v(2,1) = rzphi%fy(1)/(2*rfac) + v(2,2) = (1+rzphi%fy(2))*twopi*rfac + v(2,3) = rzphi%fy(3)*r_val + + v(3,3) = twopi*r_val + +c metric tensor: g_ij = sum(v_i * v_j) (idcon_metric convention) + g22 = (v(2,1)**2 + v(2,2)**2 + v(2,3)**2) + g33 = (v(3,3)**2) + g23 = (v(2,3)*v(3,3)) + +c j·B and σ = (j·B)/B² + CALL bicube_eval(eqfun, psi, theta(itheta), 0) + bsq_val = eqfun%f(1)**2 + + jdotb_val = g22*bth*jwt + g33*bze*jwz + + $ g23*(bth*jwz + bze*jwt) + sigma = jdotb_val / bsq_val + sigma_vals(itheta) = sigma + jdotb_vals(itheta) = jdotb_val + +c Term1: |∇ψ_dcon|^2 * σ * S_dcon + K_t1(itheta) = (delpsi**2) * sigma * REAL(shear_fun(itheta)) + +c Term2: B^2 * σ^2 + K_t2(itheta) = bsq_val * sigma**2 * mu0 + +c Term3: 2 p' * κ^psi + K_t3(itheta) = 2.0_r8 * REAL(curv_fun(itheta)) * p1 + ENDDO + +c total K = T1 + T2 + T3 + DO itheta = 0, mthsurf + K_fun(itheta) = CMPLX(K_t1(itheta) + K_t2(itheta) + + $ K_t3(itheta), 0.0_r8, r8) + ENDDO + +c optionally return individual term values + IF (PRESENT(K_term1)) K_term1 = K_t1 + IF (PRESENT(K_term2)) K_term2 = K_t2 + IF (PRESENT(K_term3)) K_term3 = K_t3 + IF (PRESENT(sigma_fun)) sigma_fun = sigma_vals + IF (PRESENT(jdotb_fun)) jdotb_fun = jdotb_vals + IF (PRESENT(shear_out)) shear_out = shear_fun + IF (PRESENT(curv_out)) curv_out = curv_fun + + IF(debug_flag) PRINT *, "Exiting gpeq_K" c----------------------------------------------------------------------- -c convert to spatial functions if requested. +c terminate. c----------------------------------------------------------------------- - IF (PRESENT(term_fun)) THEN - CALL iscdftb(mfac, mpert, term_theta_fun, mthsurf, term_mn) - term_fun = term_theta_fun - ENDIF - RETURN - END SUBROUTINE gpeq_terms -c----------------------------------------------------------------------- + END SUBROUTINE gpeq_K c subprogram 14. gpeq_fcoords. c transform coordinates to dcon coordinates. c----------------------------------------------------------------------- @@ -1900,13 +1973,9 @@ SUBROUTINE gpeq_alloc $ bno_mn(mpert),bta_mn(mpert),bpa_mn(mpert), $ xrr_mn(mpert),xrz_mn(mpert),xrp_mn(mpert), $ brr_mn(mpert),brz_mn(mpert),brp_mn(mpert), - $ qvp_mn(mpert,0:mpsi),qvt_mn(mpert,0:mpsi), - $ qvz_mn(mpert,0:mpsi),cvp_mn(mpert,0:mpsi), - $ cvt_mn(mpert,0:mpsi),cvz_mn(mpert,0:mpsi), - $ qwp_mn(mpert,0:mpsi),qwt_mn(mpert,0:mpsi), - $ qwz_mn(mpert,0:mpsi),cwp_mn(mpert,0:mpsi), - $ cwt_mn(mpert,0:mpsi),cwz_mn(mpert,0:mpsi), - $ DST_mn(0:mthsurf,0:mpsi),DST_fun(0:mthsurf,0:mpsi)) + $ c2vp_mn(mpert),c2vt_mn(mpert),c2vz_mn(mpert), + $ cvp_mn(mpert),cvt_mn(mpert),cvz_mn(mpert), + $ cwp_mn(mpert),cwt_mn(mpert),cwz_mn(mpert)) IF(debug_flag) PRINT *, "->Leaving gpeq_alloc" c----------------------------------------------------------------------- c terminate. @@ -1925,9 +1994,8 @@ SUBROUTINE gpeq_dealloc $ xvp_mn,xvt_mn,xvz_mn,bvp_mn,bvt_mn,bvz_mn,xmz_mn,bmz_mn, $ xno_mn,xta_mn,xpa_mn,bno_mn,bta_mn,bpa_mn, $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, - $ qvp_mn,qvt_mn,qvz_mn,cvp_mn,cvt_mn,cvz_mn, - $ qwp_mn,qwt_mn,qwz_mn,cwp_mn,cwt_mn,cwz_mn, - $ DST_mn,DST_fun) + $ c2vp_mn,c2vt_mn,c2vz_mn,cvp_mn,cvt_mn,cvz_mn, + $ cwp_mn,cwt_mn,cwz_mn) IF(debug_flag) PRINT *, "->Leaving gpeq_dealloc" c----------------------------------------------------------------------- c terminate. diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 09bfe611f..00ca516fb 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -58,8 +58,8 @@ MODULE gpglobal_mod REAL(r8), DIMENSION(:), ALLOCATABLE :: psifac,rhofac,qfac,singfac, $ r,z,theta,ee,surfee,surfei,rpsifac, - $ surf_indev,vsurf_indev,fsurf_indev,surf_indinvev - $ ,eft,efp + $ surf_indev,vsurf_indev,fsurf_indev,surf_indinvev, + $ eft,efp REAL(r8), DIMENSION(:,:), ALLOCATABLE :: xzpts, $ chperr,chpsqr,grri,grre,griw,grrw,gdr,gdz,gdpsi,gdthe,gdphi REAL(r8), DIMENSION(3,3) :: w,v @@ -73,15 +73,8 @@ MODULE gpglobal_mod $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, $ chi_mn,che_mn,kax_mn,sbno_mn,sbno_fun,edge_mn,edge_fun, $ chy_mn,chx_mn,chw_mn,kaw_mn,mutual_indev,mutual_indinvev - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: cwp_mn,cwt_mn, - $ cwz_mn, cvp_mn,cvt_mn,cvz_mn, - $ qvp_mn,qvt_mn, qvz_mn, qwp_mn,qwt_mn,qwz_mn, - $ c2_cvw_mn,c2_cw_mn, c2_cv_mn - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: cwp_fun,cwt_fun, - $ cwz_fun,cvp_fun,cvt_fun,cvz_fun,cvp_funp,cvt_funp,cvz_funp, - $ qvp_fun,qvt_fun,qvz_fun,qwp_fun,qwt_fun,qwz_fun,c2_cw_fun, - $ c2_cv_fun,c2_cvw_fun - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: DST_mn,DST_fun + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: cwp_mn,cwt_mn, + $ cwz_mn, cvp_mn,cvt_mn,cvz_mn,c2vp_mn,c2vt_mn, c2vz_mn COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: wt,wt0,chp_mn,kap_mn, $ permeabev,chimats,chemats,flxmats,kaxmats,singbno_mn, $ plas_indev,plas_indinvev,reluctev,indrelev,permeabsv, diff --git a/gpec/gpout.f b/gpec/gpout.f index a36d83fbd..d6148e869 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6877,27 +6877,36 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER, INTENT(IN) :: mode COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn - INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, - $ ucov, ucontra_phy, ucontra_met, uQv, uQw, uc2cw, uc2cv, - $ uc2cvw + INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, ucveri, + $ uk_sigma, ucw_fun, ucw_mn, ucv_fun, ucv_mn, ucv2_fun, + $ ucv2_mn, u_int REAL(r8) :: psi, eta, rfac REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals - COMPLEX(r8), DIMENSION(mpert) :: curv_mn, K_mn, + REAL(r8) :: epf_int + COMPLEX(r8) :: dst_int + COMPLEX(r8), DIMENSION(mpert) :: curv_mn, $ term_mn - COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun, + $ cveri_fun + COMPLEX(r8), DIMENSION(0:mthsurf,3) :: cw_fun, cv_fun, cv2_fun + REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 + REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals CHARACTER(8) :: smode - CHARACTER(128) :: shear_fun_file, curv_file - CHARACTER(128) :: k_file, c2cw_file, c2cv_file, c2cvw_file - CHARACTER(128) :: ccov_file, ccontra_phy_file, ccontra_met_file - CHARACTER(128) :: qv_file, qw_file + CHARACTER(128) :: shear_fun_file, curv_file, cveri_file + CHARACTER(128) :: k_file, k_sigma_file + CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file + CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file + REAL(r8), DIMENSION(0:mpsi) :: epf_psi, epf_cumsum + COMPLEX(r8), DIMENSION(0:mpsi) :: dst_psi_k, dst_cumsum_k + REAL(r8) :: epf_int_total + COMPLEX(r8) :: dst_int_total_k, dw_raw_k + REAL(r8) :: psi_min, psi_max, dpsi, dpsi_local c Diagnostic variables for C vector comparison INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 REAL(r8) :: psi_mid, theta_mid, delpsi_sq, jac_mid, q_mid, $ q1_mid, chi1_mid, f1_mid, p1_mid, j_theta_coef, j_zeta_coef - COMPLEX(r8) :: C1_phys, C2_phys, C3_phys, C1_metr, C2_metr, - $ C3_metr, term_exp, j_cross_factor, xi_psi_val - COMPLEX(r8) :: C_psi_val, C_theta_val, C_zeta_val + COMPLEX(r8) :: term_exp, j_cross_factor, xi_psi_val COMPLEX(r8), DIMENSION(-mband:mband) :: g11_diag, g12_diag, $ g22_diag, g23_diag, g31_diag, g33_diag REAL(r8), DIMENSION(10,10) :: w @@ -6908,54 +6917,6 @@ SUBROUTINE gpout_recon(mode, xspmn) $ " diagnostics" IF(verbose) WRITE(*,*)"__________________________________________" -c----------------------------------------------------------------------- -c allocate C vector arrays for EPF calculation. -c Storage: C_*_mn(mpert, 0:mpsi) for all psi levels. -c----------------------------------------------------------------------- -c Allocate C and Q vector spatial function arrays (theta, psi grid) - IF (.NOT. ALLOCATED(cwp_fun)) THEN - ALLOCATE(cwp_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cwt_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cwz_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cvp_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cvt_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cvz_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(cvp_funp(0:mthsurf, 0:mpsi)) - ALLOCATE(cvt_funp(0:mthsurf, 0:mpsi)) - ALLOCATE(cvz_funp(0:mthsurf, 0:mpsi)) - ALLOCATE(c2_cw_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(c2_cv_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(c2_cvw_fun(0:mthsurf, 0:mpsi)) - - cwp_fun = 0.0_r8 - cwt_fun = 0.0_r8 - cwz_fun = 0.0_r8 - cvp_fun = 0.0_r8 - cvt_fun = 0.0_r8 - cvz_fun = 0.0_r8 - cvp_funp = 0.0_r8 - cvt_funp = 0.0_r8 - cvz_funp = 0.0_r8 - c2_cw_fun = 0.0_r8 - c2_cv_fun = 0.0_r8 - c2_cvw_fun = 0.0_r8 - ENDIF - - IF (.NOT. ALLOCATED(qvp_fun)) THEN - ALLOCATE(qvp_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(qvt_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(qvz_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(qwp_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(qwt_fun(0:mthsurf, 0:mpsi)) - ALLOCATE(qwz_fun(0:mthsurf, 0:mpsi)) - qvp_fun = 0.0_r8 - qvt_fun = 0.0_r8 - qvz_fun = 0.0_r8 - qwp_fun = 0.0_r8 - qwt_fun = 0.0_r8 - qwz_fun = 0.0_r8 - ENDIF - c----------------------------------------------------------------------- c allocate bicubes for shear, curvature, K (placeholder allocation). c actual bicube population should be done at each psi in gpeq. @@ -6968,94 +6929,99 @@ SUBROUTINE gpout_recon(mode, xspmn) smode = ADJUSTL(smode) shear_fun_file = "gpec_recon_shear_fun_sol"//TRIM(smode)//".out" curv_file = "gpec_recon_curvature_sol"//TRIM(smode)//".out" + cveri_file = "gpec_recon_cveri_sol"//TRIM(smode)//".out" k_file = "gpec_recon_k_sol"//TRIM(smode)//".out" - c2cw_file = "gpec_recon_C2_cw_sol"//TRIM(smode)//".out" - c2cv_file = "gpec_recon_C2_cv_sol"//TRIM(smode)//".out" - c2cvw_file = "gpec_recon_C2_cvw_sol"//TRIM(smode)//".out" - ccov_file = "gpec_recon_Ccov_sol"//TRIM(smode)//".out" - ccontra_phy_file = "gpec_recon_Ccontra_physics_sol"/ - $ /TRIM(smode)//".out" - ccontra_met_file = "gpec_recon_Ccontra_metric_sol"/ - $ /TRIM(smode)//".out" - qv_file = "gpec_recon_Qv_sol"//TRIM(smode)//".out" - qw_file = "gpec_recon_Qw_sol"/ - $ /TRIM(smode)//".out" + k_sigma_file = "gpec_recon_k_sigma_check_sol"//TRIM(smode)//".out" + cw_fun_file = "gpec_recon_cwfun_sol"//TRIM(smode)//".out" + cw_mn_file = "gpec_recon_cwmn_sol"//TRIM(smode)//".out" + cv_fun_file = "gpec_recon_cvfun_sol"//TRIM(smode)//".out" + cv_mn_file = "gpec_recon_cvmn_sol"//TRIM(smode)//".out" + cv2_fun_file = "gpec_recon_cv2fun_sol"//TRIM(smode)//".out" + cv2_mn_file = "gpec_recon_cv2mn_sol"//TRIM(smode)//".out" + int_file = "gpec_recon_integration_sol"//TRIM(smode)//".out" ushear_fun = 82 ucurv = 72 + ucveri = 74 uk = 73 - uc2cw = 79 - uc2cv = 80 - uc2cvw = 81 - ucov = 74 - ucontra_phy = 75 - ucontra_met = 76 - uQv = 77 - uQw = 78 + uk_sigma = 85 + ucw_fun = 86 + ucw_mn = 87 + ucv_fun = 88 + ucv_mn = 89 + ucv2_fun = 90 + ucv2_mn = 91 + u_int = 92 OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") + OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") - OPEN(UNIT=uc2cw, FILE=c2cw_file, STATUS="UNKNOWN") - OPEN(UNIT=uc2cv, FILE=c2cv_file, STATUS="UNKNOWN") - OPEN(UNIT=uc2cvw, FILE=c2cvw_file, STATUS="UNKNOWN") - OPEN(UNIT=ucov, FILE=ccov_file, STATUS="UNKNOWN") - OPEN(UNIT=ucontra_phy, FILE=ccontra_phy_file, STATUS="UNKNOWN") - OPEN(UNIT=ucontra_met, FILE=ccontra_met_file, STATUS="UNKNOWN") - OPEN(UNIT=uQv, FILE=qv_file, STATUS="UNKNOWN") - OPEN(UNIT=uQw, FILE=qw_file, STATUS="UNKNOWN") + OPEN(UNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") + CALL ascii_open(ucw_fun, cw_fun_file, "UNKNOWN") + CALL ascii_open(ucw_mn, cw_mn_file, "UNKNOWN") + CALL ascii_open(ucv_fun, cv_fun_file, "UNKNOWN") + CALL ascii_open(ucv_mn, cv_mn_file, "UNKNOWN") + CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") + CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") + OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") WRITE(ushear_fun,'(6(1x,a16))') $ "psi","theta","r","z","shear_re","shear_im" WRITE(ucurv,'(6(1x,a16))') $ "psi","theta","r","z","curv_re","curv_im" - WRITE(uk,'(6(1x,a16))') - $ "psi","theta","r","z","K_re","K_im" - WRITE(uc2cw,'(6(1x,a16))') - $ "psi","theta","r","z","C2_cw_re","C2_cw_im" - WRITE(uc2cv,'(6(1x,a16))') - $ "psi","theta","r","z","C2_cv_re","C2_cv_im" - WRITE(uc2cvw,'(6(1x,a16))') - $ "psi","theta","r","z","C2_cvw_re","C2_cvw_im" - WRITE(ucov,'(10(1x,a14))') - $ "psi","theta","r","z", - $ "C_psi_re","C_psi_im","C_theta_re","C_theta_im", - $ "C_zeta_re","C_zeta_im" - WRITE(ucontra_phy,'(10(1x,a14))') - $ "psi","theta","r","z", - $ "C1_phy_re","C1_phy_im","C2_phy_re","C2_phy_im", - $ "C3_phy_re","C3_phy_im" - WRITE(ucontra_met,'(10(1x,a14))') - $ "psi","theta","r","z", - $ "C1_met_re","C1_met_im","C2_met_re","C2_met_im", - $ "C3_met_re","C3_met_im" - WRITE(uQv,'(10(1x,a14))') - $ "psi","theta","r","z", - $ "Qv_psi_re","Qv_psi_im","Qv_theta_re","Qv_theta_im", - $ "Qv_zeta_re","Qv_zeta_im" - WRITE(uQw,'(10(1x,a14))') - $ "psi","theta","r","z", - $ "Qw1_re","Qw1_im","Qw2_re","Qw2_im", - $ "Qw3_re","Qw3_im" - + WRITE(ucveri,'(6(1x,a16))') + $ "psi","theta","r","z","cveri_re","cveri_im" + WRITE(uk,'(9(1x,a16))') + $ "psi","theta","r","z","K_re","K_im", + $ "T1_re","T2_re","T3_re" + WRITE(uk_sigma,'(8(1x,a16))') + $ "psi","theta","r","z","sigma_re","jdotb_re", + $ "bsig2_re","sigjdotb_re" + WRITE(ucw_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cwp_re","cwp_im", + $ "cwt_re","cwt_im","cwz_re","cwz_im" + WRITE(ucw_mn,'(8(1x,a16))') + $ "psi","m","cwp_re","cwp_im","cwt_re","cwt_im", + $ "cwz_re","cwz_im" + WRITE(ucv_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cvp_re","cvp_im", + $ "cvt_re","cvt_im","cvz_re","cvz_im" + WRITE(ucv_mn,'(8(1x,a16))') + $ "psi","m","cvp_re","cvp_im","cvt_re","cvt_im", + $ "cvz_re","cvz_im" + WRITE(ucv2_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cv2p_re","cv2p_im", + $ "cv2t_re","cv2t_im","cv2z_re","cv2z_im" + WRITE(ucv2_mn,'(8(1x,a16))') + $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", + $ "cv2z_re","cv2z_im" + WRITE(u_int,'(7(1x,a16))') + $ "psi","epf_int","dst_k_re","dst_k_im", + $ "epf_cumsum","dst_k_c_re","dst_k_c_im" c----------------------------------------------------------------------- c main loop over all psi levels. -c compute gpeq equilibrium quantities at each psi and call gpeq_epf -c to populate C norm squared for the EPF/DST diagnostics. +c compute gpeq reconstruction diagnostics at each psi. c----------------------------------------------------------------------- CALL gpeq_alloc + epf_int_total = 0.0_r8 + dst_int_total_k = CMPLX(0.0_r8, 0.0_r8, r8) + epf_cumsum = 0.0_r8 + dst_cumsum_k = CMPLX(0.0_r8, 0.0_r8, r8) + DO ipsi = 0, mpsi psi = rzphi%xs(ipsi) -c Compute all necessary gpeq quantities at this psi level. - CALL gpeq_sol(psi) - CALL gpeq_contra(psi) - CALL gpeq_cova(psi) - CALL gpeq_normal(psi) +c Compute surface-integrated EPF and DST terms for this psi. +c Store in arrays for psi integration later. + CALL gpeq_epf(psi, epf_int) + CALL gpeq_dst(psi, dst_int) + epf_psi(ipsi) = epf_int + dst_psi_k(ipsi) = dst_int -c Compute shear/curvature/K in spatial-space. - CALL gpeq_shear(psi, shear_fun) - CALL gpeq_curvature(psi, curv_fun) - CALL gpeq_K(psi, K_mn, K_fun) +c Reconstruct detailed K diagnostics for output. + CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, + $ sigma_vals, jdotb_vals, shear_fun, curv_fun) + CALL gpeq_cveri(psi, cveri_fun) c Store spatial values with coordinates. DO itheta = 0, mthsurf @@ -7071,138 +7037,139 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), REAL(curv_fun(itheta)), $ AIMAG(curv_fun(itheta)) - WRITE(uk,'(6(es17.8e3))') psi, theta(itheta), + WRITE(ucveri,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(cveri_fun(itheta)), + $ AIMAG(cveri_fun(itheta)) + WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), - $ AIMAG(K_fun(itheta)) + $ AIMAG(K_fun(itheta)), K_term1(itheta), K_term2(itheta), + $ K_term3(itheta) + WRITE(uk_sigma,'(8(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), sigma_vals(itheta), + $ jdotb_vals(itheta), K_term2(itheta), + $ sigma_vals(itheta) * jdotb_vals(itheta) ENDDO WRITE(ushear_fun,*) WRITE(ucurv,*) + WRITE(ucveri,*) WRITE(uk,*) + WRITE(uk_sigma,*) -c Compute C and Q vectors (covariant + contravariant) for this psi - CALL gpeq_epf(psi, ipsi) - -c Write C^2 values (contravariant form only - no metric) - DO itheta = 0, mthsurf - WRITE(uc2cw,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(c2_cw_fun(itheta, ipsi)), - $ AIMAG(c2_cw_fun(itheta, ipsi)) - ENDDO - WRITE(uc2cw,*) - -c Write C^2 values (covariant form only - with metric) - DO itheta = 0, mthsurf - WRITE(uc2cv,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(c2_cv_fun(itheta, ipsi)), - $ AIMAG(c2_cv_fun(itheta, ipsi)) - ENDDO - WRITE(uc2cv,*) - -c Write C^2 values (mixed form - Cov x Contra) +c Reconstruct C fields from the already computed EPF state. + CALL iscdftb(mfac, mpert, cw_fun(:,1), mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,2), mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,3), mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,1), mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,2), mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,3), mthsurf, cvz_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,1), mthsurf, c2vp_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,2), mthsurf, c2vt_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,3), mthsurf, c2vz_mn) DO itheta = 0, mthsurf - WRITE(uc2cvw,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(c2_cvw_fun(itheta, ipsi)), - $ AIMAG(c2_cvw_fun(itheta, ipsi)) - ENDDO - WRITE(uc2cvw,*) - -c Write C vectors to files - contravariant components (all 3) - DO itheta = 0, mthsurf - WRITE(ucov,'(10(es15.7e2))') psi, theta(itheta), + WRITE(ucw_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(cw_fun(itheta,1)), + $ AIMAG(cw_fun(itheta,1)), REAL(cw_fun(itheta,2)), + $ AIMAG(cw_fun(itheta,2)), REAL(cw_fun(itheta,3)), + $ AIMAG(cw_fun(itheta,3)) + WRITE(ucv_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(cv_fun(itheta,1)), + $ AIMAG(cv_fun(itheta,1)), REAL(cv_fun(itheta,2)), + $ AIMAG(cv_fun(itheta,2)), REAL(cv_fun(itheta,3)), + $ AIMAG(cv_fun(itheta,3)) + WRITE(ucv2_fun,'(10(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), - $ REAL(cwp_fun(itheta, ipsi)), - $ AIMAG(cwp_fun(itheta, ipsi)), - $ REAL(cwt_fun(itheta, ipsi)), - $ AIMAG(cwt_fun(itheta, ipsi)), - $ REAL(cwz_fun(itheta, ipsi)), - $ AIMAG(cwz_fun(itheta, ipsi)) + $ REAL(cv2_fun(itheta,1)), AIMAG(cv2_fun(itheta,1)), + $ REAL(cv2_fun(itheta,2)), AIMAG(cv2_fun(itheta,2)), + $ REAL(cv2_fun(itheta,3)), AIMAG(cv2_fun(itheta,3)) ENDDO - WRITE(ucov,*) - -c Write C vectors (contravariant) - contrvariant components with p - DO itheta = 0, mthsurf - WRITE(ucontra_phy,'(10(es15.7e2))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), - $ REAL(cvp_funp(itheta, ipsi)), - $ AIMAG(cvp_funp(itheta, ipsi)), - $ REAL(cvt_funp(itheta, ipsi)), - $ AIMAG(cvt_funp(itheta, ipsi)), - $ REAL(cvz_funp(itheta, ipsi)), - $ AIMAG(cvz_funp(itheta, ipsi)) - ENDDO - WRITE(ucontra_phy,*) - - -c Write C vectors (contravariant) - contrvariant components with metric - DO itheta = 0, mthsurf - WRITE(ucontra_met,'(10(es15.7e2))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), - $ REAL(cvp_fun(itheta, ipsi)), - $ AIMAG(cvp_fun(itheta, ipsi)), - $ REAL(cvt_fun(itheta, ipsi)), - $ AIMAG(cvt_fun(itheta, ipsi)), - $ REAL(cvz_fun(itheta, ipsi)), - $ AIMAG(cvz_fun(itheta, ipsi)) + DO ipert = 1, mpert + WRITE(ucw_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cwp_mn(ipert), cwt_mn(ipert), + $ cwz_mn(ipert) + WRITE(ucv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cvp_mn(ipert), cvt_mn(ipert), + $ cvz_mn(ipert) + WRITE(ucv2_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), c2vp_mn(ipert), c2vt_mn(ipert), + $ c2vz_mn(ipert) ENDDO - WRITE(ucontra_met,*) - - -c Write Q covariant vectors - all 3 components - DO itheta = 0, mthsurf - WRITE(uQv,'(10(es15.7e2))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), - $ REAL(qvp_fun(itheta, ipsi)), - $ AIMAG(qvp_fun(itheta, ipsi)), - $ REAL(qvt_fun(itheta, ipsi)), - $ AIMAG(qvt_fun(itheta, ipsi)), - $ REAL(qvz_fun(itheta, ipsi)), - $ AIMAG(qvz_fun(itheta, ipsi)) - ENDDO - WRITE(uQv,*) - - DO itheta = 0, mthsurf - WRITE(uQw,'(10(es15.7e2))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), - $ REAL(qwp_fun(itheta, ipsi)), - $ AIMAG(qwp_fun(itheta, ipsi)), - $ REAL(qwt_fun(itheta, ipsi)), - $ AIMAG(qwt_fun(itheta, ipsi)), - $ REAL(qwz_fun(itheta, ipsi)), - $ AIMAG(qwz_fun(itheta, ipsi)) - ENDDO - WRITE(uQw,*) - - CALL gpeq_dst(psi,ipsi) + WRITE(ucw_fun,*) + WRITE(ucw_mn,*) + WRITE(ucv_fun,*) + WRITE(ucv_mn,*) + WRITE(ucv2_fun,*) + WRITE(ucv2_mn,*) ENDDO - CALL gpeq_dealloc - CLOSE(ushear_fun) - CLOSE(ucurv) - CLOSE(uk) - CLOSE(uc2cw) - CLOSE(uc2cv) - CLOSE(uc2cvw) - CLOSE(ucov) - CLOSE(ucontra_phy) - CLOSE(ucontra_met) - CLOSE(uQv) - CLOSE(uQw) - c----------------------------------------------------------------------- -c C vectors now populated in gpglobal_mod for all psi levels. +c perform psi integration using trapezoidal rule. +c integrate: ∫dpsi epf(psi), ∫dpsi dst_k(psi) c----------------------------------------------------------------------- - IF(ALLOCATED(gpout_shear)) THEN - IF(verbose) WRITE(*,*)" Shear bicube computed" - ENDIF + psi_min = rzphi%xs(0) + psi_max = rzphi%xs(mpsi) + dpsi = (psi_max - psi_min) / REAL(mpsi, r8) - IF(ALLOCATED(gpout_curvature)) THEN - IF(verbose) WRITE(*,*)" Curvature bicube computed" - ENDIF + epf_int_total = 0.0_r8 + dst_int_total_k = CMPLX(0.0_r8, 0.0_r8, r8) + epf_cumsum(0) = 0.0_r8 + dst_cumsum_k(0) = CMPLX(0.0_r8, 0.0_r8, r8) - IF(ALLOCATED(gpout_k)) THEN - IF(verbose) WRITE(*,*)" K quantity bicube computed" - ENDIF + DO ipsi = 0, mpsi-1 + dpsi_local = rzphi%xs(ipsi+1) - rzphi%xs(ipsi) + epf_int_total = epf_int_total + + $ (epf_psi(ipsi) + epf_psi(ipsi+1)) * dpsi_local / 2.0_r8 + dst_int_total_k = dst_int_total_k + + $ (dst_psi_k(ipsi) + dst_psi_k(ipsi+1)) * dpsi_local + $ / 2.0_r8 + epf_cumsum(ipsi+1) = epf_int_total + dst_cumsum_k(ipsi+1) = dst_int_total_k + ENDDO + DO ipsi = 0, mpsi + WRITE(u_int,'(7(es17.8e3))') rzphi%xs(ipsi), epf_psi(ipsi), + $ REAL(dst_psi_k(ipsi)), AIMAG(dst_psi_k(ipsi)), + $ epf_cumsum(ipsi), REAL(dst_cumsum_k(ipsi)), + $ AIMAG(dst_cumsum_k(ipsi)) + ENDDO + WRITE(u_int,*) + +c Write integration results + WRITE(u_int,'(a)') "c Psi integration results (Trapezoidal rule)" + WRITE(u_int,'(a)') "c psi_min, psi_max, nominal_dpsi" + WRITE(u_int,'(3(es17.8e3))') psi_min, psi_max, dpsi + dw_raw_k = 0.5_r8 * + $ (CMPLX(epf_int_total, 0.0_r8, r8) - dst_int_total_k) + + WRITE(u_int,'(a)') "c Integration results:" + WRITE(u_int,'(3(es17.8e3))') epf_int_total, REAL(dst_int_total_k), + $ AIMAG(dst_int_total_k) + WRITE(u_int,'(a)') "c deltaW = 0.5 * (EPF - DST)" + WRITE(u_int,'(a)') "c raw_deltaW(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k), AIMAG(dw_raw_k) + + WRITE(*,'(a)') "GPEC_RECON integration totals:" + WRITE(*,'(a,es17.8e3)') " EPF = ", epf_int_total + WRITE(*,'(a,2es17.8e3)') " DST(K) = ", + $ REAL(dst_int_total_k), AIMAG(dst_int_total_k) + WRITE(*,'(a,2es17.8e3)') " dW_raw(K)= ", + $ REAL(dw_raw_k), AIMAG(dw_raw_k) + WRITE(*,*) "GPEC ep(1):", REAL(ep(1)) + WRITE(*,*) "DCON plasma1-equivalent:", REAL(ep(1))*(mu0*2.0) / + $ psio**2 / (chi1*1e-3)**2 + + CALL gpeq_dealloc + CLOSE(ushear_fun) + CLOSE(ucurv) + CLOSE(ucveri) + CLOSE(uk) + CLOSE(uk_sigma) + CLOSE(u_int) + CALL ascii_close(ucw_fun) + CALL ascii_close(ucw_mn) + CALL ascii_close(ucv_fun) + CALL ascii_close(ucv_mn) + CALL ascii_close(ucv2_fun) + CALL ascii_close(ucv2_mn) c----------------------------------------------------------------------- c completion message. c----------------------------------------------------------------------- From 4e8c2110fc2d19129aa1605f85aba29c1132164c Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 23 Apr 2026 15:42:27 +0900 Subject: [PATCH 116/305] GPEC - FEATURE - Add Python analysis and plotting tools (cherry picked from commit a79f3c183752b2ad68348fdcbf4b854df1649bb5) --- .../DIIID_ideal_example/compare_corrected.py | 37 +++ .../DIIID_ideal_example/compare_correction.py | 42 +++ .../DIIID_ideal_example/compare_with_Q.py | 48 +++ .../DIIID_ideal_example/gpec_netcdf_viewer.py | 83 +++++ docs/examples/DIIID_ideal_example/plot_K.py | 120 +++++++ .../plot_c2_reconstruction.py | 181 ++++++++++ .../DIIID_ideal_example/plot_curvature.py | 134 ++++++++ .../DIIID_ideal_example/plot_cvfun.py | 199 +++++++++++ .../examples/DIIID_ideal_example/plot_cvmn.py | 216 ++++++++++++ .../plot_reconstruction.py | 311 ++++++++++++++++++ .../DIIID_ideal_example/plot_shear.py | 132 ++++++++ .../DIIID_ideal_example/plot_xbrzphi.py | 114 +++++++ 12 files changed, 1617 insertions(+) create mode 100644 docs/examples/DIIID_ideal_example/compare_corrected.py create mode 100644 docs/examples/DIIID_ideal_example/compare_correction.py create mode 100644 docs/examples/DIIID_ideal_example/compare_with_Q.py create mode 100644 docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py create mode 100644 docs/examples/DIIID_ideal_example/plot_K.py create mode 100644 docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py create mode 100644 docs/examples/DIIID_ideal_example/plot_curvature.py create mode 100644 docs/examples/DIIID_ideal_example/plot_cvfun.py create mode 100644 docs/examples/DIIID_ideal_example/plot_cvmn.py create mode 100644 docs/examples/DIIID_ideal_example/plot_reconstruction.py create mode 100644 docs/examples/DIIID_ideal_example/plot_shear.py create mode 100644 docs/examples/DIIID_ideal_example/plot_xbrzphi.py diff --git a/docs/examples/DIIID_ideal_example/compare_corrected.py b/docs/examples/DIIID_ideal_example/compare_corrected.py new file mode 100644 index 000000000..86157ba05 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/compare_corrected.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import numpy as np + +# Load both methods +phys_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") +metric_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") + +print("=" * 80) +print("Sample comparison at different flux surfaces (theta=0)") +print("=" * 80) + +# Get unique psi values +psi_values = phys_data[:, 0] +unique_psi = np.unique(psi_values) + +for i in [0, len(unique_psi)//4, len(unique_psi)//2, 3*len(unique_psi)//4, -1]: + psi_val = unique_psi[i] + idx_phys = np.where((phys_data[:, 0] == psi_val) & (phys_data[:, 1] == 0))[0] + idx_metric = np.where((metric_data[:, 0] == psi_val) & (metric_data[:, 1] == 0))[0] + + if len(idx_phys) > 0 and len(idx_metric) > 0: + phys_row = phys_data[idx_phys[0]] + metric_row = metric_data[idx_metric[0]] + + c1p = phys_row[4] + 1j*phys_row[5] + c2p = phys_row[6] + 1j*phys_row[7] + c3p = phys_row[8] + 1j*phys_row[9] + + c1m = metric_row[4] + 1j*metric_row[5] + c2m = metric_row[6] + 1j*metric_row[7] + c3m = metric_row[8] + 1j*metric_row[9] + + print(f"\nψ = {psi_val:.6e}:") + print(f" Physics: C1={abs(c1p):.3e}, C2={abs(c2p):.3e}, C3={abs(c3p):.3e}") + print(f" Metric: C1={abs(c1m):.3e}, C2={abs(c2m):.3e}, C3={abs(c3m):.3e}") + if abs(c1m) > 1e-15: + print(f" Ratio C2_phy/C1_met = {abs(c2p)/abs(c1m):.4f}") diff --git a/docs/examples/DIIID_ideal_example/compare_correction.py b/docs/examples/DIIID_ideal_example/compare_correction.py new file mode 100644 index 000000000..4db179456 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/compare_correction.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import numpy as np + +# Load physics method +phys_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") +phys_c1 = phys_data[:, 4] + 1j * phys_data[:, 5] +phys_c2 = phys_data[:, 6] + 1j * phys_data[:, 7] +phys_c3 = phys_data[:, 8] + 1j * phys_data[:, 9] + +# Load metric method +metric_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") +metric_c1 = metric_data[:, 4] + 1j * metric_data[:, 5] +metric_c2 = metric_data[:, 6] + 1j * metric_data[:, 7] +metric_c3 = metric_data[:, 8] + 1j * metric_data[:, 9] + +print("=" * 70) +print("CORRECTION: sq.f1(2) and f1(3) already contain μ₀ and 2π respectively") +print("=" * 70) +print("\nUnderneath first flux surface (ψ = psi_low):") +print("\nPhysics Method Results:") +print(f" C1_physics = {phys_c1[0]:.6e}") +print(f" C2_physics = {phys_c2[0]:.6e}") +print(f" C3_physics = {phys_c3[0]:.6e}") + +print("\nMetric Method Results:") +print(f" C1_metric = {metric_c1[0]:.6e}") +print(f" C2_metric = {metric_c2[0]:.6e}") +print(f" C3_metric = {metric_c3[0]:.6e}") + +print("\n" + "=" * 70) +print("COMPARISON (Ratio of Physics / Metric):") +print("=" * 70) +c1_ratio = abs(phys_c1[0]) / abs(metric_c1[0]) +c2_ratio = abs(phys_c2[0]) / abs(metric_c2[0]) +c3_ratio = abs(phys_c3[0]) / abs(metric_c3[0]) + +print(f" C1_physics / C1_metric = {c1_ratio:.6f}") +print(f" C2_physics / C2_metric = {c2_ratio:.6f}") +print(f" C3_physics / C3_metric = {c3_ratio:.6f}") + +print(f"\n 2π ≈ {2 * 3.14159:.6f}") +print(f"\n✓ Physics method should now ≈ Metric method (within numerical error)") diff --git a/docs/examples/DIIID_ideal_example/compare_with_Q.py b/docs/examples/DIIID_ideal_example/compare_with_Q.py new file mode 100644 index 000000000..b1c1f259e --- /dev/null +++ b/docs/examples/DIIID_ideal_example/compare_with_Q.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import numpy as np + +# Load files +Q_data = np.loadtxt("gpec_recon_Qvec_sol1.out") +Ccov_data = np.loadtxt("gpec_recon_Ccov_sol1.out") +Cphy_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") +Cmet_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") + +print("=" * 100) +print("Q VECTOR vs C VECTOR COMPARISON AT FIRST POINT (psi=1e-4, theta=0)") +print("=" * 100) + +# First row (theta=0) +Q_row = Q_data[0] +Ccov_row = Ccov_data[0] +Cphy_row = Cphy_data[0] +Cmet_row = Cmet_data[0] + +print("\nQ VECTOR (from gpeq_epf):") +print(f" Q_psi = {Q_row[4]:.6e} + {Q_row[5]:.6e}i") +print(f" Q_theta = {Q_row[6]:.6e} + {Q_row[7]:.6e}i") + +print("\nC COVARIANT (before adding j×ξ term):") +print(f" C_psi = {Ccov_row[4]:.6e} + {Ccov_row[5]:.6e}i") +print(f" C_theta = {Ccov_row[6]:.6e} + {Ccov_row[7]:.6e}i") + +print("\nC CONTRAVARIANT (Physics Method = Q + j×ξ):") +print(f" C1_phy = {Cphy_row[4]:.6e} + {Cphy_row[5]:.6e}i") +print(f" C2_phy = {Cphy_row[6]:.6e} + {Cphy_row[7]:.6e}i") +print(f" C3_phy = {Cphy_row[8]:.6e} + {Cphy_row[9]:.6e}i") + +print("\nC CONTRAVARIANT (Metric Method = g_ij * C_j):") +print(f" C1_met = {Cmet_row[4]:.6e} + {Cmet_row[5]:.6e}i") +print(f" C2_met = {Cmet_row[6]:.6e} + {Cmet_row[7]:.6e}i") +print(f" C3_met = {Cmet_row[8]:.6e} + {Cmet_row[9]:.6e}i") + +print("\n" + "=" * 100) +print("KEY OBSERVATION:") +print("=" * 100) +Q_theta = Q_row[6] + 1j*Q_row[7] +C2_phy = Cphy_row[6] + 1j*Cphy_row[7] + +print(f"Q_theta = {Q_theta}") +print(f"C2_physics = {C2_phy}") +print(f"C2 == Q_theta? {np.abs(C2_phy - Q_theta) < 1e-10}") +print("\n👉 C2_physics이 Q_theta와 같거나 거의 같은가?") +print(" 만약 그렇다면, j×ξ term이 거의 0이라는 뜻!") diff --git a/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py b/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py new file mode 100644 index 000000000..64cced7ee --- /dev/null +++ b/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py @@ -0,0 +1,83 @@ +import numpy as np +import matplotlib.pyplot as plt +import xarray as xr +from pathlib import Path + +class GPECNetCDFViewer: + """GPEC NetCDF File Viewer and Plotter""" + + def __init__(self, filepath): + self.filepath = Path(filepath) + self.ds = None + self.load_file() + + def load_file(self): + try: + self.ds = xr.open_dataset(self.filepath) + print(f"✓ Loaded: {self.filepath.name}") + except Exception as e: + print(f"✗ Error: {e}") + + def plot_bkappaxi_perp(self): + """Plot Bkappaxi_perp in Fourier space""" + if 'Bkappaxi_perp' not in self.ds: + print("✗ 'Bkappaxi_perp' not found") + return + + data = self.ds['Bkappaxi_perp'].values + real_part = data[:, :, 0] + imag_part = data[:, :, 1] + + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + fig.suptitle('Bkappaxi_perp: Curvature Component (Fourier Space)', + fontsize=14, fontweight='bold') + + im1 = axes[0].contourf(real_part, levels=20, cmap='RdBu_r') + axes[0].set_title('Real Part') + plt.colorbar(im1, ax=axes[0]) + + im2 = axes[1].contourf(imag_part, levels=20, cmap='RdBu_r') + axes[1].set_title('Imaginary Part') + plt.colorbar(im2, ax=axes[1]) + + plt.tight_layout() + return fig + + def plot_bkappaxi_perp_fun(self): + """Plot Bkappaxi_perp_fun in function space""" + if 'Bkappaxi_perp_fun' not in self.ds: + print("✗ 'Bkappaxi_perp_fun' not found") + return + + data = self.ds['Bkappaxi_perp_fun'].values + real_part = data[:, :, 0] + imag_part = data[:, :, 1] + + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + fig.suptitle('Bkappaxi_perp_fun: Curvature Component (Function Space)', + fontsize=14, fontweight='bold') + + im1 = axes[0].contourf(real_part, levels=20, cmap='RdBu_r') + axes[0].set_title('Real Part') + plt.colorbar(im1, ax=axes[0]) + + im2 = axes[1].contourf(imag_part, levels=20, cmap='RdBu_r') + axes[1].set_title('Imaginary Part') + plt.colorbar(im2, ax=axes[1]) + + plt.tight_layout() + return fig + +# Use the viewer +if __name__ == "__main__": + filepath = "gpec_output_n1.nc" # 실제 파일명으로 변경 + viewer = GPECNetCDFViewer(filepath) + + if viewer.ds is not None: + fig1 = viewer.plot_bkappaxi_perp() + plt.savefig('bkappaxi_perp_fourier.png', dpi=150, bbox_inches='tight') + + fig2 = viewer.plot_bkappaxi_perp_fun() + plt.savefig('bkappaxi_perp_function.png', dpi=150, bbox_inches='tight') + + plt.show() diff --git a/docs/examples/DIIID_ideal_example/plot_K.py b/docs/examples/DIIID_ideal_example/plot_K.py new file mode 100644 index 000000000..39e046aed --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_K.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +""" +Plot K diagnostics on the r-z plane. + +Running this script produces two figures: +1. gpec_recon_k_sol1.png +2. gpec_recon_k-term_sol1.png +""" + +import glob +import os +import re +import sys + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.colors import TwoSlopeNorm + + +def find_required_file(pattern): + """Find a reconstruction output file by wildcard pattern.""" + matches = sorted(glob.glob(pattern)) + if not matches: + print(f"Error: Could not find {pattern}") + sys.exit(1) + return matches[0] + + +def extract_suffix(path): + """Extract the solution suffix such as sol1 from the filename.""" + name = os.path.basename(path) + match = re.search(r"(sol\d+)\.out$", name) + if not match: + print(f"Error: Could not extract solution suffix from {name}") + sys.exit(1) + return match.group(1) + + +def make_norm(values): + """Build a diverging normalization centered at zero.""" + vmin_temp, vmax_temp = np.percentile(values, [2, 98]) + vmax = max(abs(vmin_temp), abs(vmax_temp)) + return TwoSlopeNorm(vmin=-vmax, vcenter=0.0, vmax=vmax) + + +def style_axis(ax, title): + """Apply common axis styling.""" + ax.set_xlabel("R (m)", fontsize=11) + ax.set_ylabel("Z (m)", fontsize=11) + ax.set_title(title, fontsize=12, fontweight="bold") + ax.grid(True, alpha=0.3) + ax.set_aspect("equal") + + +def scatter_panel(ax, r, z, values, title, cbar_label): + """Create a single scatter panel with symmetric color scaling.""" + scatter = ax.scatter( + r, + z, + c=values, + cmap="RdBu_r", + s=20, + alpha=0.6, + norm=make_norm(values), + ) + style_axis(ax, title) + cbar = plt.colorbar(scatter, ax=ax) + cbar.set_label(cbar_label, fontsize=10) + + +def main(): + """Main plotting routine.""" + print("=" * 70) + print("GPEC K Diagnostic Plotter") + print("=" * 70) + + k_file = find_required_file("gpec_recon_k_sol*.out") + suffix = extract_suffix(k_file) + + print(f"Reading K data from: {k_file}") + + k_data = np.loadtxt(k_file, skiprows=1) + + r = k_data[:, 2] + z = k_data[:, 3] + + k_total = k_data[:, 4] + t1 = k_data[:, 6] + t2 = k_data[:, 7] + t3 = k_data[:, 8] + + # Figure 1: total K + fig_k, ax_k = plt.subplots(figsize=(7, 8)) + scatter_panel(ax_k, r, z, k_total, "K (Total)", "K") + plt.tight_layout() + k_png = f"gpec_recon_k_{suffix}.png" + fig_k.savefig(k_png, dpi=150, bbox_inches="tight") + + # Figure 2: three terms in one row + fig_terms, axes = plt.subplots(1, 3, figsize=(18, 5.5)) + term_specs = [ + (t1, r"Term1: $|\nabla\psi_{\mathrm{dcon}}|^2 \sigma S_{\mathrm{dcon}}$", "T1"), + (t2, r"Term2: $B^2 \sigma^2$", "T2"), + (t3, r"Term3: $2 P' \kappa^\psi$", "T3"), + ] + + for ax, (values, title, label) in zip(axes, term_specs): + scatter_panel(ax, r, z, values, title, label) + + plt.tight_layout() + terms_png = f"gpec_recon_k-term_{suffix}.png" + fig_terms.savefig(terms_png, dpi=150, bbox_inches="tight") + + print("Saved:") + print(f" {k_png}") + print(f" {terms_png}") + + +if __name__ == "__main__": + main() diff --git a/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py b/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py new file mode 100644 index 000000000..c67c74ae3 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +""" +Plot C² reconstruction diagnostics in r-z plane. + +For each C² output file, creates a PNG with real and imaginary parts +displayed as scatter plots in the r-z grid. +Color scheme: red (positive) - white (0) - blue (negative) + +Usage: + python plot_c2_reconstruction.py +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +from matplotlib.colors import LinearSegmentedColormap +import glob +import os +import sys + + +def create_rwb_colormap(n_bins=256): + """ + Create a red-white-blue colormap. + + Parameters: + ----------- + n_bins : int + Number of bins in the colormap + + Returns: + -------- + cmap : LinearSegmentedColormap + Red-white-blue colormap (white at center for value 0) + """ + colors_list = ['red', 'white', 'blue'] + cmap = LinearSegmentedColormap.from_list('rwb', colors_list, N=n_bins) + return cmap + + +def plot_c2_reconstruction(filepath): + """ + Plot C² reconstruction in r-z plane. + + Creates a PNG with real and imaginary parts as subplots. + Each point is colored according to its C² value using red-white-blue + colormap where white represents zero. + + Parameters: + ----------- + filepath : str + Path to the C² output file + + Returns: + -------- + output_file : str + Path to the generated PNG file + """ + + try: + # Read the file - skip header line + data = np.loadtxt(filepath, skiprows=1) + + if data.size == 0: + print(f"Warning: {filepath} is empty") + return None + + # Handle case where file has only one data point + if data.ndim == 1: + data = data.reshape(1, -1) + + # Extract columns: psi, theta, r, z, C2_re, C2_im + psi = data[:, 0] + theta = data[:, 1] + r = data[:, 2] + z = data[:, 3] + c2_re = data[:, 4] + c2_im = data[:, 5] + + # Create red-white-blue colormap + cmap = create_rwb_colormap(256) + + # Calculate max absolute values for symmetric normalization + vmax_re = np.max(np.abs(c2_re)) + vmax_im = np.max(np.abs(c2_im)) + + # Use CenteredNorm with white at 0 + if vmax_re > 0: + norm_re = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_re) + else: + norm_re = mcolors.Normalize(vmin=-1, vmax=1) + + if vmax_im > 0: + norm_im = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_im) + else: + norm_im = mcolors.Normalize(vmin=-1, vmax=1) + + # Create figure with 2 subplots (real and imaginary) + fig, axes = plt.subplots(1, 2, figsize=(15, 6)) + + # Extract quantity name from filename + # e.g., gpec_recon_C2_cw_sol1.out -> C2_cw + basename = os.path.basename(filepath) + quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] + + # Plot real part + scatter_re = axes[0].scatter(r, z, c=c2_re, cmap=cmap, norm=norm_re, + s=30, alpha=0.8) + axes[0].set_xlabel('R (m)', fontsize=12, fontweight='bold') + axes[0].set_ylabel('Z (m)', fontsize=12, fontweight='bold') + axes[0].set_title(f'{quantity} - Real Part', fontsize=13, fontweight='bold') + axes[0].grid(True, alpha=0.3, linestyle='--') + axes[0].set_aspect('equal', adjustable='box') + + cbar_re = plt.colorbar(scatter_re, ax=axes[0], label='Value') + cbar_re.set_label(f'{quantity} (Re)', fontsize=11, fontweight='bold') + + # Plot imaginary part + scatter_im = axes[1].scatter(r, z, c=c2_im, cmap=cmap, norm=norm_im, + s=30, alpha=0.8) + axes[1].set_xlabel('R (m)', fontsize=12, fontweight='bold') + axes[1].set_ylabel('Z (m)', fontsize=12, fontweight='bold') + axes[1].set_title(f'{quantity} - Imaginary Part', fontsize=13, fontweight='bold') + axes[1].grid(True, alpha=0.3, linestyle='--') + axes[1].set_aspect('equal', adjustable='box') + + cbar_im = plt.colorbar(scatter_im, ax=axes[1], label='Value') + cbar_im.set_label(f'{quantity} (Im)', fontsize=11, fontweight='bold') + + # Add main title with statistics + num_points = len(r) + n_psi = len(np.unique(psi)) + fig.suptitle(f'{quantity} Reconstruction\n(n_psi={n_psi}, n_theta={num_points//n_psi if n_psi > 0 else 0})', + fontsize=14, fontweight='bold', y=1.00) + + plt.tight_layout() + + # Save as PNG + output_file = filepath.replace('.out', '.png') + plt.savefig(output_file, dpi=150, bbox_inches='tight') + print(f"✓ Saved: {output_file}") + plt.close() + + return output_file + + except Exception as e: + print(f"✗ Error processing {filepath}: {e}") + import traceback + traceback.print_exc() + return None + + +def main(): + """ + Main function: find and plot all C² output files. + """ + + # Find all C² output files + c2_files = sorted(glob.glob('gpec_recon_C2_*.out')) + + if not c2_files: + print("No C² output files found (gpec_recon_C2_*.out)") + print(f"Current directory: {os.getcwd()}") + return + + print(f"Found {len(c2_files)} C² output file(s)") + print("-" * 60) + + successful = 0 + for filepath in c2_files: + print(f"Processing: {filepath}") + output_file = plot_c2_reconstruction(filepath) + if output_file: + successful += 1 + + print("-" * 60) + print(f"Completed: {successful}/{len(c2_files)} files processed successfully") + + +if __name__ == '__main__': + main() diff --git a/docs/examples/DIIID_ideal_example/plot_curvature.py b/docs/examples/DIIID_ideal_example/plot_curvature.py new file mode 100644 index 000000000..3ddf2902a --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_curvature.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +""" +Plot curvature diagnostic quantities from GPEC reconstruction output. + +Reads spatial domain data: +- gpec_recon_curvature_sol*.out: Spatial domain (r-z plane) + +Produces single r-z plane plot with curvature values colored. +""" + +import glob +import sys + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.colors import TwoSlopeNorm +from scipy.interpolate import griddata + + +def find_curvature_file(): + """Find curvature output file with wildcard matching.""" + curv_files = glob.glob("gpec_recon_curvature_sol*.out") + + if not curv_files: + print("Error: Could not find gpec_recon_curvature_sol*.out file") + sys.exit(1) + + return curv_files[0] + + +def read_curvature_data(): + """Read spatial curvature data from output file.""" + curv_file = find_curvature_file() + + print(f"Reading spatial data from: {curv_file}") + curv = np.loadtxt(curv_file, skiprows=1) + + psi = curv[:, 0] + theta = curv[:, 1] + r = curv[:, 2] + z = curv[:, 3] + curv_re = curv[:, 4] + curv_im = curv[:, 5] + + return psi, theta, r, z, curv_re, curv_im + + +def main(): + """Main plotting routine.""" + print("=" * 70) + print("GPEC Curvature Diagnostic Plotter (r-z plane only)") + print("=" * 70) + + psi, theta, r, z, curv_re, curv_im = read_curvature_data() + + unique_psi = np.unique(psi) + + print(f"Spatial grid: {len(unique_psi)} psi levels") + print(f"Total grid points: {len(r)}") + print( + f"Curvature range: min={np.min(curv_re):.4e}, max={np.max(curv_re):.4e}, " + f"imag_max={np.max(np.abs(curv_im)):.4e}" + ) + + fig, ax = plt.subplots(figsize=(10, 8)) + + # Use the same compression used by the shear plot so sign structure is visible. + curv_re_asinh = np.arcsinh(curv_re) + + r_min, r_max = np.min(r), np.max(r) + z_min, z_max = np.min(z), np.max(z) + + grid_r = np.linspace(r_min, r_max, 150) + grid_z = np.linspace(z_min, z_max, 150) + grid_r_mesh, grid_z_mesh = np.meshgrid(grid_r, grid_z) + + points = np.c_[r, z] + grid_curv = griddata( + points, curv_re_asinh, (grid_r_mesh, grid_z_mesh), + method="cubic", fill_value=np.nan + ) + + vmin, vmax = np.nanmin(grid_curv), np.nanmax(grid_curv) + norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax) + + ax.contourf( + grid_r_mesh, grid_z_mesh, grid_curv, levels=30, + cmap="RdBu_r", norm=norm + ) + + scatter = ax.scatter( + r, z, c=curv_re_asinh, cmap="RdBu_r", + norm=norm, s=15, alpha=0.6, edgecolors="none" + ) + + ax.contour( + grid_r_mesh, grid_z_mesh, grid_curv, levels=[0], + colors="black", linewidths=2 + ) + + ax.set_xlabel("R", fontsize=22, fontweight="bold") + ax.set_ylabel("Z", fontsize=22, fontweight="bold") + ax.set_title("Curvature (spatial) - r-z plane", fontsize=24, + fontweight="bold") + ax.set_aspect("equal", adjustable="box") + ax.grid(True, alpha=0.3, linestyle="--") + ax.tick_params(labelsize=16) + + r_ticks = np.linspace(r_min, r_max, 6) + ax.set_xticks(r_ticks) + ax.set_xticklabels([f"{rv:.3f}" for rv in r_ticks], fontsize=14) + + z_ticks = np.linspace(z_min, z_max, 6) + ax.set_yticks(z_ticks) + ax.set_yticklabels([f"{zv:.3f}" for zv in z_ticks], fontsize=14) + + cbar = plt.colorbar(scatter, ax=ax) + cbar.set_label("asinh(Curvature)", fontsize=18, fontweight="bold") + cbar.ax.tick_params(labelsize=14) + + cbar_ticks = np.linspace(vmin, vmax, 7) + cbar.set_ticks(cbar_ticks) + cbar.set_ticklabels([f"{val:.1e}" for val in cbar_ticks], fontsize=12) + + plt.tight_layout() + + output_file = "gpec_recon_curvature_rz.png" + print(f"\nSaving plot to: {output_file}") + plt.savefig(output_file, dpi=150, bbox_inches="tight") + print("Plot complete!") + + +if __name__ == "__main__": + main() diff --git a/docs/examples/DIIID_ideal_example/plot_cvfun.py b/docs/examples/DIIID_ideal_example/plot_cvfun.py new file mode 100644 index 000000000..cbd948967 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_cvfun.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +""" +Plot cvfun vs cv2fun on the R-Z plane and report fun-space errors. + +Outputs: + - gpec_recon_cvfun_solX.png + - gpec_recon_cv2fun_solX.png + - gpec_recon_cvdiff_solX.png +""" + +import glob +import os +import re +import sys + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.colors import TwoSlopeNorm + + +def find_required_file(pattern): + matches = sorted(glob.glob(pattern)) + if not matches: + print(f"Error: Could not find {pattern}") + sys.exit(1) + return matches[0] + + +def extract_suffix(path): + name = os.path.basename(path) + match = re.search(r"(sol\d+)\.out$", name) + if not match: + print(f"Error: Could not extract solution suffix from {name}") + sys.exit(1) + return match.group(1) + + +def load_data(path): + data = np.loadtxt(path, skiprows=1) + if data.ndim == 1: + data = data[np.newaxis, :] + return data + + +def make_diverging_norm(values): + vmin_temp, vmax_temp = np.percentile(values, [2, 98]) + vmax = max(abs(vmin_temp), abs(vmax_temp), 1.0e-14) + return TwoSlopeNorm(vmin=-vmax, vcenter=0.0, vmax=vmax) + + +def scatter_component(ax, r, z, values, title, cbar_label): + scatter = ax.scatter( + r, + z, + c=values, + s=10, + cmap="RdBu_r", + alpha=0.75, + edgecolors="none", + norm=make_diverging_norm(values), + ) + ax.set_title(title, fontsize=11, fontweight="bold") + ax.set_xlabel("R (m)", fontsize=10) + ax.set_ylabel("Z (m)", fontsize=10) + ax.set_aspect("equal") + ax.grid(True, alpha=0.25) + cbar = plt.colorbar(scatter, ax=ax, fraction=0.046, pad=0.02) + cbar.set_label(cbar_label, fontsize=9) + return scatter + + +def make_six_panel_figure(r, z, values_by_comp, figure_title, output): + fig, axes = plt.subplots(3, 2, figsize=(13, 15)) + panel_order = [ + ("p_re", r"$C_\psi$ Re", "Re"), + ("p_im", r"$C_\psi$ Im", "Im"), + ("t_re", r"$C_\theta$ Re", "Re"), + ("t_im", r"$C_\theta$ Im", "Im"), + ("z_re", r"$C_\zeta$ Re", "Re"), + ("z_im", r"$C_\zeta$ Im", "Im"), + ] + for ax, (key, title, label) in zip(axes.ravel(), panel_order): + scatter_component( + ax, + r, + z, + values_by_comp[key], + f"{figure_title} {title}", + label, + ) + fig.suptitle(figure_title, fontsize=15, fontweight="bold") + fig.tight_layout(rect=[0, 0, 1, 0.95]) + fig.savefig(output, dpi=160, bbox_inches="tight") + plt.close(fig) + + +def symmetric_relative_l2(lhs, rhs): + denom = np.linalg.norm(lhs) + np.linalg.norm(rhs) + if denom == 0.0: + return 0.0 + return 2.0 * np.linalg.norm(lhs - rhs) / denom + + +def component_arrays(data): + return { + "p_re": data[:, 4], + "p_im": data[:, 5], + "t_re": data[:, 6], + "t_im": data[:, 7], + "z_re": data[:, 8], + "z_im": data[:, 9], + } + + +def main(): + cv_file = find_required_file("gpec_recon_cvfun_sol*.out") + cv2_file = find_required_file("gpec_recon_cv2fun_sol*.out") + suffix = extract_suffix(cv_file) + + print(f"Reading {os.path.basename(cv_file)}") + print(f"Reading {os.path.basename(cv2_file)}") + + cv_data = load_data(cv_file) + cv2_data = load_data(cv2_file) + + if cv_data.shape != cv2_data.shape or not np.allclose( + cv_data[:, :4], cv2_data[:, :4] + ): + print("Error: cvfun and cv2fun grids do not match.") + sys.exit(1) + + r = cv_data[:, 2] + z = cv_data[:, 3] + cv = component_arrays(cv_data) + cv2 = component_arrays(cv2_data) + panel_keys = ["p_re", "p_im", "t_re", "t_im", "z_re", "z_im"] + comp_labels = [("p", r"$C_\psi$"), ("t", r"$C_\theta$"), ("z", r"$C_\zeta$")] + + direct_values = {} + metric_values = {} + diff_values = {} + + print("") + print("Fun-space error summary:") + + for comp, latex_name in comp_labels: + for part in ["re", "im"]: + key = f"{comp}_{part}" + direct = cv[key] + metric = cv2[key] + diff = direct - metric + direct_values[key] = direct + metric_values[key] = metric + diff_values[key] = diff + + rel_l2 = symmetric_relative_l2(direct, metric) + peak = np.max(np.maximum(np.abs(direct), np.abs(metric))) + mask = np.maximum(np.abs(direct), np.abs(metric)) > 0.05 * peak + masked_rel_l2 = symmetric_relative_l2(direct[mask], metric[mask]) + abs_energy_frac = np.sum(diff[mask] ** 2) / max(np.sum(diff**2), 1.0e-30) + idx = np.argmax(np.abs(diff)) + + print( + f" cv{comp}_{part}: rel_L2 = {100*rel_l2:6.3f}%" + f" rel_L2(>5% peak) = {100*masked_rel_l2:6.3f}%" + f" mismatch energy in >5% peak region = {100*abs_energy_frac:6.2f}%" + ) + print( + f" max |Δ| = {abs(diff[idx]):.6e}" + f" at psi={cv_data[idx,0]:.6f}, theta={cv_data[idx,1]:.6f}," + f" R={cv_data[idx,2]:.6f}, Z={cv_data[idx,3]:.6f}" + ) + print( + f" direct = {direct[idx]:.6e}, metric = {metric[idx]:.6e}" + ) + + cv_png = f"gpec_recon_cvfun_{suffix}.png" + cv2_png = f"gpec_recon_cv2fun_{suffix}.png" + diff_png = f"gpec_recon_cvdiff_{suffix}.png" + + make_six_panel_figure( + r, z, direct_values, "Direct cvfun components", cv_png + ) + make_six_panel_figure( + r, z, metric_values, "Metric cv2fun components", cv2_png + ) + make_six_panel_figure( + r, z, diff_values, "Difference components (direct - metric)", diff_png + ) + + print("") + print("Saved:") + print(f" {cv_png}") + print(f" {cv2_png}") + print(f" {diff_png}") + + +if __name__ == "__main__": + main() diff --git a/docs/examples/DIIID_ideal_example/plot_cvmn.py b/docs/examples/DIIID_ideal_example/plot_cvmn.py new file mode 100644 index 000000000..f68d962d1 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_cvmn.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +""" +Plot reconstructed covariant C components in Fourier space. + +This script reads: + - gpec_recon_cvmn_sol*.out + - gpec_recon_cv2mn_sol*.out + +and writes: + - gpec_recon_cvmn.png + - gpec_recon_cv2mn.png + +Each PNG contains 6 subplots: + cvp_re, cvp_im, cvt_re, cvt_im, cvz_re, cvz_im +with psi on the x-axis and one line per m value. +""" + +import glob +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np + + +def find_required_file(pattern): + matches = sorted(glob.glob(pattern)) + if not matches: + print(f"Error: Could not find {pattern}") + sys.exit(1) + return matches[0] + + +def load_mn_data(path): + data = np.loadtxt(path, skiprows=1) + if data.ndim == 1: + data = data[np.newaxis, :] + return data + + +def build_series(data): + psi_vals = np.unique(data[:, 0]) + m_vals = np.unique(data[:, 1].astype(int)) + series = {} + for m in m_vals: + mask = data[:, 1].astype(int) == m + block = data[mask] + order = np.argsort(block[:, 0]) + series[m] = block[order] + return psi_vals, m_vals, series + + +def style_axis(ax, title): + ax.set_title(title, fontsize=11, fontweight="bold") + ax.set_xlabel("psi", fontsize=10) + ax.grid(True, alpha=0.25, color="0.75", linewidth=0.6) + ax.set_facecolor("#fbfbfb") + + +def plot_family(data, titles, png_name, figure_title): + _, m_vals, series = build_series(data) + cmap = plt.colormaps["nipy_spectral"] + linestyles = ["-", "--", "-.", ":"] + + fig, axes = plt.subplots(3, 2, figsize=(14, 12), sharex=True) + axes = axes.ravel() + + for idx, ax in enumerate(axes): + col = idx + 2 + for i, m in enumerate(m_vals): + block = series[m] + color = cmap((i + 0.5) / max(len(m_vals), 1)) + ax.plot( + block[:, 0], + block[:, col], + color=color, + linewidth=1.6 if i % 5 else 2.1, + linestyle=linestyles[i % len(linestyles)], + alpha=0.95, + ) + style_axis(ax, titles[idx]) + + axes[0].set_ylabel("value", fontsize=10) + axes[1].set_ylabel("value", fontsize=10) + axes[2].set_ylabel("value", fontsize=10) + axes[3].set_ylabel("value", fontsize=10) + axes[4].set_ylabel("value", fontsize=10) + axes[5].set_ylabel("value", fontsize=10) + + # Add a compact colorbar-like legend using m-index colors. + sm = plt.cm.ScalarMappable( + cmap=cmap, norm=plt.Normalize(vmin=m_vals.min(), vmax=m_vals.max()) + ) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=axes.tolist(), fraction=0.02, pad=0.02) + cbar.set_label("m", fontsize=10) + + fig.suptitle(figure_title, fontsize=14, fontweight="bold") + fig.tight_layout(rect=[0, 0, 0.96, 0.97]) + fig.savefig(png_name, dpi=160, bbox_inches="tight") + plt.close(fig) + + +def sort_rows(data): + return data[np.lexsort((data[:, 1], data[:, 0]))] + + +def symmetric_relative_l2(lhs, rhs): + denom = np.linalg.norm(lhs) + np.linalg.norm(rhs) + if denom == 0.0: + return 0.0 + return 2.0 * np.linalg.norm(lhs - rhs) / denom + + +def max_pointwise_relative(lhs, rhs): + scale = np.maximum(np.maximum(np.abs(lhs), np.abs(rhs)), 1.0e-14) + return np.max(np.abs(lhs - rhs) / scale) + + +def compare_families(cvmn_data, cv2mn_data): + lhs = sort_rows(cvmn_data) + rhs = sort_rows(cv2mn_data) + + if lhs.shape != rhs.shape: + raise ValueError( + f"Shape mismatch: cvmn {lhs.shape} vs cv2mn {rhs.shape}" + ) + + if not np.allclose(lhs[:, :2], rhs[:, :2], atol=0.0, rtol=0.0): + raise ValueError("psi/m grids do not match between cvmn and cv2mn.") + + labels = ["p", "t", "z"] + results = [] + all_lhs = [] + all_rhs = [] + + for i, label in enumerate(labels): + col = 2 + 2 * i + lhs_comp = lhs[:, col] + 1j * lhs[:, col + 1] + rhs_comp = rhs[:, col] + 1j * rhs[:, col + 1] + all_lhs.append(lhs_comp) + all_rhs.append(rhs_comp) + results.append( + ( + label, + symmetric_relative_l2(lhs_comp, rhs_comp), + max_pointwise_relative(lhs_comp, rhs_comp), + ) + ) + + all_lhs = np.concatenate(all_lhs) + all_rhs = np.concatenate(all_rhs) + overall = ( + symmetric_relative_l2(all_lhs, all_rhs), + max_pointwise_relative(all_lhs, all_rhs), + ) + return results, overall + + +def main(): + cvmn_file = find_required_file("gpec_recon_cvmn_sol*.out") + cv2mn_file = find_required_file("gpec_recon_cv2mn_sol*.out") + + print(f"Reading {os.path.basename(cvmn_file)}") + print(f"Reading {os.path.basename(cv2mn_file)}") + + cvmn_data = load_mn_data(cvmn_file) + cv2mn_data = load_mn_data(cv2mn_file) + component_results, overall = compare_families(cvmn_data, cv2mn_data) + + plot_family( + cvmn_data, + [ + "cvp_re", + "cvp_im", + "cvt_re", + "cvt_im", + "cvz_re", + "cvz_im", + ], + "gpec_recon_cvmn.png", + "Covariant C from direct route (mn)", + ) + + plot_family( + cv2mn_data, + [ + "cv2p_re", + "cv2p_im", + "cv2t_re", + "cv2t_im", + "cv2z_re", + "cv2z_im", + ], + "gpec_recon_cv2mn.png", + "Covariant C from metric route (mn)", + ) + + print("Saved:") + print(" gpec_recon_cvmn.png") + print(" gpec_recon_cv2mn.png") + print("") + print("Symmetric relative error between cvmn and cv2mn:") + for label, rel_l2, rel_max in component_results: + print( + f" cv{label}: rel_L2 = {100.0 * rel_l2:8.3f}%" + f" max_pointwise = {100.0 * rel_max:8.3f}%" + ) + print( + f" overall: rel_L2 = {100.0 * overall[0]:8.3f}%" + f" max_pointwise = {100.0 * overall[1]:8.3f}%" + ) + + +if __name__ == "__main__": + main() diff --git a/docs/examples/DIIID_ideal_example/plot_reconstruction.py b/docs/examples/DIIID_ideal_example/plot_reconstruction.py new file mode 100644 index 000000000..9f5ad08cd --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_reconstruction.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +""" +Plot reconstruction diagnostics in r-z plane. + +For each reconstruction output file, creates a PNG with real and imaginary parts +displayed as scatter plots in the r-z grid. +Color scheme: red (positive) - white (0) - blue (negative) + +Handles different file formats: +- Scalar real-only (5 columns): 1 subplot +- Scalar complex (6 columns): 2 subplots +- 2-component vectors (8 columns): 2 subplots +- 3-component vectors (10 columns): 3 subplots + +Usage: + python plot_reconstruction.py +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +from matplotlib.colors import LinearSegmentedColormap +import glob +import os + + +def create_rwb_colormap(n_bins=256): + """ + Create a red-white-blue colormap where white is centered at 0. + + Parameters: + ----------- + n_bins : int + Number of bins in the colormap + + Returns: + -------- + cmap : LinearSegmentedColormap + Red-white-blue colormap + """ + colors_list = ['red', 'white', 'blue'] + cmap = LinearSegmentedColormap.from_list('rwb', colors_list, N=n_bins) + return cmap + + +def get_component_names(filepath, n_components): + """ + Extract component names from file based on filename and number of components. + + Parameters: + ----------- + filepath : str + Path to the output file + n_components : int + Number of components (1, 2, or 3) + + Returns: + -------- + names : list + List of component names + """ + basename = os.path.basename(filepath) + quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] + + if n_components == 1: + return [quantity] + elif n_components == 2: + # For 2D vectors (like C_psi, C_theta) + if 'Ccov' in basename: + return ['ψ component (C_psi)', 'θ component (C_theta)'] + elif 'Qv' in basename: + return ['ψ component (Qv_psi)', 'θ component (Qv_theta)'] + elif 'Qw' in basename: + return ['ψ component (Qw_psi)', 'θ component (Qw_theta)'] + else: + return ['Component 1', 'Component 2'] + elif n_components == 3: + # For 3D vectors (like C1, C2, C3 or Psi, Theta, Zeta) + if 'Ccov' in basename: + return ['ψ component', 'θ component', 'ζ component'] + elif 'Qv' in basename: + return ['ψ component', 'θ component', 'ζ component'] + elif 'Qw' in basename: + return ['ψ component', 'θ component', 'ζ component'] + elif 'Ccontra' in basename: + return ['C¹ component', 'C² component', 'C³ component'] + else: + return [f'Component {i+1}' for i in range(n_components)] + else: + return [f'Component {i+1}' for i in range(n_components)] + + +def plot_reconstruction(filepath): + """ + Plot reconstruction data in r-z plane. + + Creates PNG files with real and imaginary parts as subplots. + + Parameters: + ----------- + filepath : str + Path to the reconstruction output file + + Returns: + -------- + output_files : list + List of generated PNG file paths + """ + + try: + # Read the file with error handling for inconsistent columns + with open(filepath, 'r') as f: + lines = f.readlines() + + # Skip header and filter out incomplete lines (blank lines, comment lines) + data_lines = [] + for line in lines[1:]: # Skip header + line = line.strip() + if not line or line.startswith('#'): # Skip blank lines + continue + # Count columns: split and filter empty strings + cols = [x for x in line.split() if x] + # Only keep lines with full column count. + # Supported formats: + # 5 cols = psi theta r z scalar_re + # 6 cols = psi theta r z scalar_re scalar_im + # 8 cols = psi theta r z vec2_re/im + # 10 cols = psi theta r z vec3_re/im + if len(cols) in [5, 6, 8, 10]: + data_lines.append(line) + + if not data_lines: + print(f"Warning: {filepath} has no valid data lines") + return [] + + # Convert to array + data_str = '\n'.join(data_lines) + from io import StringIO + data = np.loadtxt(StringIO(data_str)) + + if data.size == 0: + print(f"Warning: {filepath} is empty") + return [] + + # Handle case where file has only one data point + if data.ndim == 1: + data = data.reshape(1, -1) + + # Extract columns + psi = data[:, 0] + theta = data[:, 1] + r = data[:, 2] + z = data[:, 3] + + n_cols = data.shape[1] + scalar_real_only = (n_cols == 5) + + if scalar_real_only: + n_components = 1 + quantities = [{'re': data[:, 4], 'im': None}] + else: + n_data_cols = n_cols - 4 # Subtract psi, theta, r, z + n_components = n_data_cols // 2 # Each component has (re, im) + + # Extract quantities + quantities = [] + for i in range(n_components): + re_col = 4 + i * 2 + im_col = 4 + i * 2 + 1 + quantities.append({ + 're': data[:, re_col], + 'im': data[:, im_col] + }) + + # Get quantity name and component names + basename = os.path.basename(filepath) + quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] + comp_names = get_component_names(filepath, n_components) + + # Create red-white-blue colormap + cmap = create_rwb_colormap(256) + + if scalar_real_only: + fig, ax = plt.subplots(1, 1, figsize=(8, 7)) + axes = np.array([[ax]]) + else: + # Create figure with subplots (n_components rows x 2 columns for re/im) + n_rows = n_components + fig, axes = plt.subplots(n_rows, 2, figsize=(15, 6*n_rows)) + + # Handle single row case (axes not 2D) + if n_rows == 1: + axes = axes.reshape(1, -1) + + output_files = [] + + # Plot each component + for i, (comp_data, comp_name) in enumerate(zip(quantities, comp_names)): + c2_re = comp_data['re'] + c2_im = comp_data['im'] + + # Calculate max absolute values for symmetric normalization + vmax_re = np.max(np.abs(c2_re)) + if c2_im is not None: + vmax_im = np.max(np.abs(c2_im)) + + # Use CenteredNorm with white at 0 + if vmax_re > 0: + norm_re = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_re) + else: + norm_re = mcolors.Normalize(vmin=-1, vmax=1) + + # Plot real part + scatter_re = axes[i, 0].scatter( + r, z, c=c2_re, cmap=cmap, norm=norm_re, s=30, alpha=0.8 + ) + axes[i, 0].set_xlabel('R (m)', fontsize=11, fontweight='bold') + axes[i, 0].set_ylabel('Z (m)', fontsize=11, fontweight='bold') + real_title = f'{quantity} - {comp_name}' if scalar_real_only \ + else f'{quantity} - {comp_name} (Real)' + axes[i, 0].set_title(real_title, fontsize=12, fontweight='bold') + axes[i, 0].grid(True, alpha=0.3, linestyle='--') + axes[i, 0].set_aspect('equal', adjustable='box') + + cbar_re = plt.colorbar(scatter_re, ax=axes[i, 0]) + cbar_re.set_label('Value', fontsize=10, fontweight='bold') + + if c2_im is not None: + if vmax_im > 0: + norm_im = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_im) + else: + norm_im = mcolors.Normalize(vmin=-1, vmax=1) + + scatter_im = axes[i, 1].scatter( + r, z, c=c2_im, cmap=cmap, norm=norm_im, s=30, alpha=0.8 + ) + axes[i, 1].set_xlabel('R (m)', fontsize=11, fontweight='bold') + axes[i, 1].set_ylabel('Z (m)', fontsize=11, fontweight='bold') + axes[i, 1].set_title( + f'{quantity} - {comp_name} (Imaginary)', + fontsize=12, + fontweight='bold' + ) + axes[i, 1].grid(True, alpha=0.3, linestyle='--') + axes[i, 1].set_aspect('equal', adjustable='box') + + cbar_im = plt.colorbar(scatter_im, ax=axes[i, 1]) + cbar_im.set_label('Value', fontsize=10, fontweight='bold') + + # Add main title with statistics + num_points = len(r) + n_psi = len(np.unique(psi)) + n_theta = num_points // n_psi if n_psi > 0 else 0 + + fig.suptitle(f'{quantity} Reconstruction (n_psi={n_psi}, n_theta={n_theta})', + fontsize=14, fontweight='bold', y=0.995) + + plt.tight_layout() + + # Save as PNG + output_file = filepath.replace('.out', '.png') + plt.savefig(output_file, dpi=150, bbox_inches='tight') + print(f"✓ Saved: {output_file}") + plt.close() + + output_files.append(output_file) + return output_files + + except Exception as e: + print(f"✗ Error processing {filepath}: {e}") + import traceback + traceback.print_exc() + return [] + + +def main(): + """ + Main function: find and plot all reconstruction output files. + """ + + # Find all reconstruction output files (excluding C2 which already has its own script) + recon_files = sorted(glob.glob('gpec_recon_*.out')) + + # Filter out C2 files (they have their own dedicated script) + recon_files = [f for f in recon_files if 'C2_' not in f] + + if not recon_files: + print("No reconstruction output files found") + print(f"Current directory: {os.getcwd()}") + return + + print(f"Found {len(recon_files)} reconstruction output file(s)") + print("-" * 70) + + successful = 0 + total_files = 0 + + for filepath in recon_files: + print(f"Processing: {filepath}") + output_files = plot_reconstruction(filepath) + if output_files: + successful += 1 + total_files += len(output_files) + + print("-" * 70) + print(f"Completed: {successful}/{len(recon_files)} files processed successfully") + print(f"Generated {total_files} PNG file(s)") + + +if __name__ == '__main__': + main() diff --git a/docs/examples/DIIID_ideal_example/plot_shear.py b/docs/examples/DIIID_ideal_example/plot_shear.py new file mode 100644 index 000000000..22bc94ca0 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_shear.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +Plot shear diagnostic quantities from GPEC reconstruction output. + +Reads spatial domain data: +- gpec_recon_shear_fun_sol*.out: Spatial domain (r-z plane) + +Produces single r-z plane plot with shear values colored. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.colors import TwoSlopeNorm +from scipy.interpolate import griddata +import sys +import glob + +def find_shear_fun_file(): + """Find shear_fun output file with wildcard matching.""" + fun_files = glob.glob('gpec_recon_shear_fun_sol*.out') + + if not fun_files: + print("Error: Could not find gpec_recon_shear_fun_sol*.out file") + sys.exit(1) + + return fun_files[0] + +def read_shear_fun_data(): + """Read spatial shear data from output file.""" + fun_file = find_shear_fun_file() + + print(f"Reading spatial data from: {fun_file}") + shear_fun = np.loadtxt(fun_file, skiprows=1) + + # Parse spatial data + psi_fun = shear_fun[:, 0] + theta_fun = shear_fun[:, 1] + r_fun = shear_fun[:, 2] + z_fun = shear_fun[:, 3] + shear_re_fun = shear_fun[:, 4] + shear_im_fun = shear_fun[:, 5] + + return psi_fun, theta_fun, r_fun, z_fun, shear_re_fun, shear_im_fun + +def main(): + """Main plotting routine.""" + print("=" * 70) + print("GPEC Shear Diagnostic Plotter (r-z plane only)") + print("=" * 70) + + # Read data + psi_fun, theta_fun, r_fun, z_fun, shear_re_fun, shear_im_fun = read_shear_fun_data() + + # Get unique values + unique_psi_fun = np.unique(psi_fun) + + print(f"Spatial grid: {len(unique_psi_fun)} psi levels") + print(f"Total grid points: {len(r_fun)}") + + # Create figure with single r-z plane plot + fig, ax = plt.subplots(figsize=(10, 8)) + + # Apply asinh transformation to shear + shear_re_fun_asinh = np.arcsinh(shear_re_fun) + + # Create grid for interpolation + r_min, r_max = np.min(r_fun), np.max(r_fun) + z_min, z_max = np.min(z_fun), np.max(z_fun) + + grid_r = np.linspace(r_min, r_max, 150) + grid_z = np.linspace(z_min, z_max, 150) + grid_r_mesh, grid_z_mesh = np.meshgrid(grid_r, grid_z) + + # Interpolate shear values onto grid + points = np.c_[r_fun, z_fun] + grid_shear = griddata(points, shear_re_fun_asinh, (grid_r_mesh, grid_z_mesh), + method='cubic', fill_value=np.nan) + + # Normalize with 0 at white + vmin, vmax = np.nanmin(grid_shear), np.nanmax(grid_shear) + norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax) + + # Plot shear as filled contours with scatter points + contourf = ax.contourf(grid_r_mesh, grid_z_mesh, grid_shear, levels=30, + cmap='RdBu_r', norm=norm) + + # Overlay scatter plot for actual data points + scatter = ax.scatter(r_fun, z_fun, c=shear_re_fun_asinh, cmap='RdBu_r', + norm=norm, s=15, alpha=0.6, edgecolors='none') + + # Draw contour at shear=0 in black + contour_zero = ax.contour(grid_r_mesh, grid_z_mesh, grid_shear, + levels=[0], colors='black', linewidths=2) + + ax.set_xlabel('R', fontsize=22, fontweight='bold') + ax.set_ylabel('Z', fontsize=22, fontweight='bold') + ax.set_title('Shear (spatial) - r-z plane', fontsize=24, fontweight='bold') + ax.set_aspect('equal', adjustable='box') + ax.grid(True, alpha=0.3, linestyle='--') + ax.tick_params(labelsize=16) + + # Set r-axis ticks + r_ticks = np.linspace(r_min, r_max, 6) + ax.set_xticks(r_ticks) + ax.set_xticklabels([f'{r:.3f}' for r in r_ticks], fontsize=14) + + # Set z-axis ticks + z_ticks = np.linspace(z_min, z_max, 6) + ax.set_yticks(z_ticks) + ax.set_yticklabels([f'{z:.3f}' for z in z_ticks], fontsize=14) + + # Colorbar + cbar = plt.colorbar(scatter, ax=ax) + cbar.set_label('asinh(Shear)', fontsize=18, fontweight='bold') + cbar.ax.tick_params(labelsize=14) + + # Set colorbar ticks to span negative and positive values + cbar_ticks = np.linspace(vmin, vmax, 7) + cbar.set_ticks(cbar_ticks) + cbar.set_ticklabels([f'{val:.1e}' for val in cbar_ticks], fontsize=12) + + # Adjust layout and save + plt.tight_layout() + + output_file = 'gpec_recon_shear_rz.png' + print(f"\nSaving plot to: {output_file}") + plt.savefig(output_file, dpi=150, bbox_inches='tight') + print("Plot complete!") + + +if __name__ == '__main__': + main() diff --git a/docs/examples/DIIID_ideal_example/plot_xbrzphi.py b/docs/examples/DIIID_ideal_example/plot_xbrzphi.py new file mode 100644 index 000000000..0d09ba3f0 --- /dev/null +++ b/docs/examples/DIIID_ideal_example/plot_xbrzphi.py @@ -0,0 +1,114 @@ +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd + +def load_gpec_xbrzphi(filepath): + """ + Load GPEC XBRZPHI output file + Returns: pandas DataFrame with all columns + """ + # Skip header lines (first 6 lines contain metadata) + df = pd.read_csv(filepath, sep=r'\s+', skiprows=6) + return df + +def plot_gpec_displacement_rzplane(filepath, figsize=(18, 12)): + """ + Plot 6 R-Z plane subplots for displacement components (xr, xz, xp) + Shows real and imaginary parts - 6 panels total + """ + df = load_gpec_xbrzphi(filepath) + + # Displacement components - real and imaginary + components = [ + ('real(xr) - Radial Displacement (Real)', df['real(xr)']), + ('imag(xr) - Radial Displacement (Imag)', df['imag(xr)']), + ('real(xz) - Vertical Displacement (Real)', df['real(xz)']), + ('imag(xz) - Vertical Displacement (Imag)', df['imag(xz)']), + ('real(xp) - Toroidal Displacement (Real)', df['real(xp)']), + ('imag(xp) - Toroidal Displacement (Imag)', df['imag(xp)']) + ] + + fig, axes = plt.subplots(2, 3, figsize=figsize) + fig.suptitle('GPEC XBRZPHI: Displacement Components in R-Z Plane (n=1)', + fontsize=16, fontweight='bold') + + axes_flat = axes.flatten() + + for idx, (title, values) in enumerate(components): + ax = axes_flat[idx] + + # Create scatter plot colored by value + scatter = ax.scatter(df['r'], df['z'], c=values, s=50, + cmap='RdBu_r', alpha=0.8, edgecolors='none') + + # Add colorbar + cbar = plt.colorbar(scatter, ax=ax) + cbar.set_label('Value', fontsize=10) + + ax.set_xlabel('R (Major Radius)', fontsize=11) + ax.set_ylabel('Z (Vertical Position)', fontsize=11) + ax.set_title(title, fontsize=12, fontweight='bold') + ax.grid(True, alpha=0.3, linestyle='--') + + plt.tight_layout() + return fig, df + +def plot_gpec_magnetic_field_rzplane(filepath, figsize=(18, 12)): + """ + Plot 6 R-Z plane subplots for magnetic field components (br, bz, bp) + Shows real and imaginary parts - 6 panels total + """ + df = load_gpec_xbrzphi(filepath) + + # Magnetic field components - real and imaginary + components = [ + ('real(br) - Radial Magnetic Field (Real)', df['real(br)']), + ('imag(br) - Radial Magnetic Field (Imag)', df['imag(br)']), + ('real(bz) - Vertical Magnetic Field (Real)', df['real(bz)']), + ('imag(bz) - Vertical Magnetic Field (Imag)', df['imag(bz)']), + ('real(bp) - Toroidal Magnetic Field (Real)', df['real(bp)']), + ('imag(bp) - Toroidal Magnetic Field (Imag)', df['imag(bp)']) + ] + + fig, axes = plt.subplots(2, 3, figsize=figsize) + fig.suptitle('GPEC XBRZPHI: Magnetic Field Components in R-Z Plane (n=1)', + fontsize=16, fontweight='bold') + + axes_flat = axes.flatten() + + for idx, (title, values) in enumerate(components): + ax = axes_flat[idx] + + # Create scatter plot colored by value + scatter = ax.scatter(df['r'], df['z'], c=values, s=50, + cmap='RdBu_r', alpha=0.8, edgecolors='none') + + # Add colorbar + cbar = plt.colorbar(scatter, ax=ax) + cbar.set_label('Value', fontsize=10) + + ax.set_xlabel('R (Major Radius)', fontsize=11) + ax.set_ylabel('Z (Vertical Position)', fontsize=11) + ax.set_title(title, fontsize=12, fontweight='bold') + ax.grid(True, alpha=0.3, linestyle='--') + + plt.tight_layout() + return fig, df + +# Usage +if __name__ == "__main__": + filepath = './gpec_xbrzphi_fun_n1.out' + + # Plot 1: Displacement components (x) - 6 panels + fig1, df = plot_gpec_displacement_rzplane(filepath) + output_path_1 = './gpec_xbrzphi_displacement_6panels.png' + fig1.savefig(output_path_1, dpi=150, bbox_inches='tight') + print(f"Saved: {output_path_1}") + + # Plot 2: Magnetic field components (b) - 6 panels + fig2, df = plot_gpec_magnetic_field_rzplane(filepath) + output_path_2 = './gpec_xbrzphi_magnetic_6panels.png' + fig2.savefig(output_path_2, dpi=150, bbox_inches='tight') + print(f"Saved: {output_path_2}") + + # plt.show() \ No newline at end of file From fe92331d943fabea27494366591553a7cb155660 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 23 Apr 2026 15:43:01 +0900 Subject: [PATCH 117/305] GPEC - MINOR - Update example configurations and documentation --- docs/examples/DIIID_ideal_example/gpec.in | 29 +- docs/tex/recon/main.tex | 3065 +++++++++++++++------ 2 files changed, 2248 insertions(+), 846 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/gpec.in b/docs/examples/DIIID_ideal_example/gpec.in index 19c273c7e..f1b422e23 100644 --- a/docs/examples/DIIID_ideal_example/gpec.in +++ b/docs/examples/DIIID_ideal_example/gpec.in @@ -11,7 +11,7 @@ tmag_in =0 ! True(1) if external perturbation has toroidal angle defined by jac_in. False(0) if machine angle. mthsurf =0 ! Number of poloidal angle gridpoints. Minimum of twice the Nyquist condition from DCON m limits is enforced. - coil_flag=t ! Calculate external perturbation from coils in coil.in + coil_flag=f ! Calculate external perturbation from coils in coil.in data_flag=f ! Apply perturbation from thrid party code output data_type="surfmn" ! Name of said code. Choose: surfmn,?? @@ -28,8 +28,8 @@ displacement_flag=f ! Perturbations are displacement (m). Default is field (T). fixed_boundary_flag=f ! Total perturbation = external perturbation - mode_flag = .F. ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) - mode = 0 ! DCON eigen function index (1 is least stable) + mode_flag = .T. ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) + mode = 1 ! DCON eigen function index (1 is least stable) filter_types = 'w' ! Isolate the filter_modes of energy (w), reluctance (r), permiability (p) or singular coupling (s) eigenvectors/singular-vectors filter_modes = 0 ! Number of modes isolated, negative values isolate backwards from the last mode / @@ -49,21 +49,21 @@ tmag_out=1 ! True(1) for magnetic toroidal angle outputs (affects singcoup and singfld, appends gpec_control) mlim_out=64 ! If positive, Fourier decomposed outputs will span -mlim_out<=m<=mlim_out. Default (<=0) outputs on internal m range. - resp_flag=t ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices - filter_flag=t ! Outputs energy, reluctance, pereability, and (optionally) the singular-coupling eigenmodes on the control surface - singcoup_flag=t ! Calculate coupling of each m to resonant surfaces - singfld_flag=t ! Output of resonant surface quantities (flux,current,etc.) + resp_flag=f ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices + filter_flag=f ! Outputs energy, reluctance, pereability, and (optionally) the singular-coupling eigenmodes on the control surface + singcoup_flag=f ! Calculate coupling of each m to resonant surfaces + singfld_flag=f ! Output of resonant surface quantities (flux,current,etc.) vsingfld_flag=f ! Output of vacuum resonant surface quantities - singthresh_flag=t ! Calculate Callen resonant field penetration threshold (requires kinetic profiles from pentrc.in) + singthresh_flag=f ! Calculate Callen resonant field penetration threshold (requires kinetic profiles from pentrc.in) pmodb_flag=f ! Outputs delta-B_Lagrangian - xbnormal_flag=t ! Outputs normal displacement and field in plasma + xbnormal_flag=f ! Outputs normal displacement and field in plasma vbnormal_flag=f ! Outputs vacuum normal displacement and field in plasma - xclebsch_flag=t ! Outputs clebsch coordinate displacement for PENT + xclebsch_flag=f ! Outputs clebsch coordinate displacement for PENT dw_flag=f ! Outputs self-consistent energy and torque profiles (when kin_flag=t from DCON) eqbrzphi_flag=f ! Outputs equilibrium field on regular (r,z) grid - brzphi_flag=t ! Outputs perturbed field on regular (r,z) grid + brzphi_flag=f ! Outputs perturbed field on regular (r,z) grid xrzphi_flag=f ! Outputs displacement on regular (r,z) grid vbrzphi_flag=f ! Outputs field on regular (r,z) grid due to total boundary surface current (not a real field) vvbrzphi_flag=f ! Outputs field on regular (r,z) grid due to exeternal perturbation surface current (not a real field) @@ -76,11 +76,14 @@ nz = 124 ! Number of vertical grid points in rzphi outputs fun_flag=t ! Write outputs to (r,z) in addition to default (psi,m) flux_flag=f ! Write outputs to (psi,theta) in addition to default (psi,m) - bin_flag=f ! Write binary outputs for use with xdraw - bin_2d_flag=f ! Write 2D binary outputs for use with xdraw + bin_flag=t ! Write binary outputs for use with xdraw + bin_2d_flag=t ! Write 2D binary outputs for use with xdraw out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. max_linesout=0 ! Maximum length of ascii data tables enforced by increasing radial step size (iff .gt. 0). verbose=t ! Print run log to terminal + + recon_flag=t + netcdf_flag=t / &GPEC_DIAGNOSE timeit=t ! Print timer splits for major subroutines diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 2866583c6..01f2dd29f 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -1,4 +1,4 @@ -\documentclass[12pt,a4paper]{article} +\documentclass[10pt,a4paper]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} @@ -182,8 +182,7 @@ \section{Coordinate system} % &= \color{blue} \nabla \psi \times (- \nabla \alpha)\\ % -&= \color{blue} g(\psi) \nabla \phi + \nabla \phi \times \nabla \psi \\ -% +&= \color{blue} g(\psi) \nabla \phi + \nabla \phi \times \nabla \psi \end{align} \begin{equation} @@ -193,7 +192,9 @@ \section{Coordinate system} g(\psi) \leftrightarrow (toroidal field functions) \leftrightarrow f_{dcon} \end{equation} -So as follows, \[ +In the following, the blue-side variable $\psi$ is Chance's flux +coordinate. Its relation to the DCON normalized flux is +\[ \color{blue}{\psi} \;\;\longleftrightarrow\;\; \color{black}\frac{\chi_{\mathrm{dcon}}}{2\pi} @@ -204,10 +205,9 @@ \section{Coordinate system} = \color{black}\frac{\chi'_{\mathrm{dcon}}}{2\pi}\, \psi_{\mathrm{dcon}}. \] -will established. -And also +Therefore \[ -{\chi} = \color{black}\chi'_{\mathrm{dcon}} \psi_{\mathrm{dcon}} +{\chi}_{\mathrm{dcon}} = \color{black}\chi'_{\mathrm{dcon}} \psi_{\mathrm{dcon}}. \] \begin{align} @@ -220,7 +220,11 @@ \section{Coordinate system} \end{align} \begin{equation} - R^2 \nabla \cdot \left( \frac{1}{R^2} \nabla \chi_{dcon} \right) = - 2\pi \left(R^2 p'_{chance} + g g' \right) = -(2\pi)\frac{2\pi}{\chi\prime_{dcon}}(ff'+ R^2p') + R^2 \nabla \cdot \left( \frac{1}{R^2} \nabla \chi_{dcon} \right) + = - 2\pi \left(R^2 p'_{chance} + g g' \right), + \qquad + p'_{dcon} \equiv \frac{dp}{d\psi_{dcon}} + = \frac{\chi'_{dcon}}{2\pi}\,p'_{chance}. \end{equation} @@ -299,22 +303,21 @@ \subsubsection{Raising Operator ($B_j \to B^i$)} \subsection{Derivation of Eq. (35) in gpec note} - +In accordance with the actual code implementation, $w_{11}$ and $w_{12}$ include the full +phase derivative $(1 + \partial_\theta\eta)$: \[ -w_{11} = \frac{4 \pi^2 r R}{\mathcal{J}} \frac{\partial \eta}{\partial \theta}, -\quad -v_{22} = \frac{2 \pi r}{\mathcal{J}} \frac{\partial \eta}{\partial \theta}, +w_{11} = \frac{(2\pi)^2 r R}{\mathcal{J}} \left(1 + \frac{\partial \eta}{\partial \theta}\right), \quad +v_{22} = \frac{2 \pi r}{\mathcal{J}} \left(1 + \frac{\partial \eta}{\partial \theta}\right), +\] +\[ w_{12} = \frac{-\pi R}{r \mathcal{J}} \frac{\partial r^2}{\partial \theta}, \quad v_{21} = \frac{1}{2r \mathcal{J}} \frac{\partial r^2}{\partial \theta} \] -\[ -w_{11} = 2 \pi R v_{22}, -\qquad -w_{12} = -2 \pi R v_{21} -\] +Note that $w_{11} = 2 \pi R v_{22}$ remains true with this definition, and +$w_{12} = -2 \pi R v_{21}$ is unchanged. \[ @@ -325,30 +328,6 @@ \subsection{Derivation of Eq. (35) in gpec note} = q\,(w_{11} w_{21} + w_{12} w_{22}) - (w_{11} w_{31} + w_{12} w_{32}) \] - -\[ -= w_{11} (q w_{21} - w_{31}) + w_{12} (q w_{22} - w_{32}) -\] - -\[ -= 2\pi R \Bigl[ - q v_{22} w_{21} - - q v_{21} w_{22} - - v_{22} w_{31} - + v_{21} w_{32} -\Bigr] -\] - -\[ -= 2\pi R \mathcal{J} \Bigl[ - q v_{22} (\cancel{v_{32}v_{13}} - v_{33} v_{12}) - - q v_{21} (v_{33} v_{11} - \cancel{v_{31}v_{13}}) - - v_{22}(v_{23} v_{12} - v_{13} v_{22}) - + v_{21}(v_{13} v_{21} - v_{11} v_{23}) -\Bigr] -\] - - \[ = 2\pi R \mathcal{J} \Bigl[ - q v_{33} (v_{22} v_{12} + v_{21} v_{11}) @@ -356,7 +335,6 @@ \subsection{Derivation of Eq. (35) in gpec note} - v_{23}(v_{22} v_{12} + v_{11} v_{21}) \Bigr] \] - \[ = 2\pi R \mathcal{J} \Bigl[ - (v_{22} v_{12} + v_{21} v_{11})(v_{23}+q v_{33}) @@ -367,26 +345,26 @@ \subsection{Derivation of Eq. (35) in gpec note} \section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} \begin{align} -\delta W_p &= \frac{1}{2} \int d\tau_p +\delta W_p &= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q}|^2 - \mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* -+ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 -+ (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla P \Big\} \\ -&= \frac{1}{2} \int d\tau_p ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 ++ \mu_0 (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \Big\} \\ +&= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q}_\perp|^2 -+ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \boldsymbol{\xi} \cdot \nabla P|^2 ++ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \boldsymbol{\xi} \cdot \nabla p|^2 + \sigma\, \mathbf{B} \times \boldsymbol{\xi}^* \cdot \mathbf{Q} -+ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 -- 2 \boldsymbol{\xi} \cdot \nabla P\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- 2 \boldsymbol{\xi} \cdot \nabla p\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} \Big\} \\ -&= \frac{1}{2} \int d\tau_p -\Big\{ |\mathbf{Q} + \xi_n \mathbf{j} \times \mathbf{n}|^2 -+ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 -- 2\,\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, \xi_n^2 +&= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n (\mu_0\mathbf{j} \times \mathbf{n})|^2 ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- 2\,\mu_0\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, \xi_n^2 \Big\}\\ -&= \frac{1}{2} \int d\tau_p -\Big\{ |\mathbf{Q} + \xi_n j \times \mathbf{n}|^2 -+ \gamma P |\nabla \cdot \boldsymbol{\xi}|^2 -- K\, \xi_n^2 +&= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n (\mu_0 \mathbf{j} \times \mathbf{n})|^2 ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- \mu_0 K\, \xi_n^2 \Big\} \end{align} @@ -399,11 +377,12 @@ \section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} \end{equation} \subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified units)} -In this section, we will see how (34) changes to (36). If we define $\xi$ as follows: +In this section, we will see how (34) changes to (36). It follows the derivation in \cite{Bernstein1983}, but since the unit and normalization are different, we need to adjust the expressions accordingly. +If we define $\xi$ as follows: \begin{equation} \boldsymbol{\xi}=a\,\nabla p + b\,\nabla\times\mathbf{B} + c\,\mathbf{B} \end{equation} -then +then, the following relations hold: \begin{equation} \boldsymbol{\xi}\times\mathbf{B} = -a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p @@ -412,14 +391,16 @@ \subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified unit \boldsymbol{\xi}\times(\nabla\times\mathbf{B}) = -a\,(\nabla\times\mathbf{B})\times\nabla p - \mu_0 c\,\nabla p \end{equation} +\begin{equation} +\mathbf{Q} = \nabla\times(\boldsymbol{\xi}\times\mathbf{B}) = \nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) +\end{equation} -Using the SI form of $\delta W_F$ ($1/4\pi\to 1/\mu_0$), Gauss's theorem, and the boundary condition $\mathbf{n}\cdot\mathbf{B}=0$, one finds \begin{align} 2\mu_0 W_F & = \int d^3r\Bigl\{ \mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ |\mathbf{Q}|^2 \nonumber + (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla P \nonumber ++ |\mathbf{Q}|^2 \nonumber + (\mu_0 \nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \nonumber + \boldsymbol{\xi} \times \mathbf{Q} \cdot \nabla \times \mathbf{B} \Bigr\}\\ & = \int d^3r\Bigl\{\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 @@ -432,48 +413,39 @@ \subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified unit \Bigr\}. \end{align} Since, -\begin{align} -&a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) --\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr] -\nonumber\\ -&= -\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] -\nonumber\\ -&= -b\,\nabla\cdot\Bigl[a(\nabla p)^2(\nabla\times\mathbf{B})\Bigr] - -c\,\nabla\cdot\Bigl[a(\nabla p)^2\mathbf{B}\Bigr] -\nonumber\\ -&= -b\,\nabla\cdot\Bigl[(\nabla p)\times\bigl(a(\nabla\times\mathbf{B})\times\nabla p\bigr)\Bigr] - -c\,\nabla\cdot\Bigl[(\nabla p)\times\bigl(a\mathbf{B}\times\nabla p\bigr)\Bigr] -\nonumber\\ -&= b(\nabla p)\cdot\nabla\times\Bigl[a(\nabla\times\mathbf{B})\times\nabla p\Bigr] - + c(\nabla p)\cdot\nabla\times\Bigl[a\mathbf{B}\times\nabla p\Bigr] -\nonumber\\ -&= \nabla\cdot\Bigl(\bigl[a(\nabla\times\mathbf{B})\times\nabla p\bigr]\times b\nabla p\Bigr) - + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) -\nonumber\\ -&\qquad + c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p). -\end{align} -so, +\begin{equation} +a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)+ \bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] +=\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr] +\end{equation} +So, \begin{align} a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) -&= \nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr] - + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) -\nonumber\\ -&\quad + c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p). +&= -\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] ++\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr]\\ +&= -b\,\nabla\cdot\Bigl[a(\nabla p)^2\Bigr](\nabla\times\mathbf{B})-c\,\nabla\cdot\Bigl[a(\nabla p)^2\Bigr]\mathbf{B}\nonumber\\ +&+\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr]+\nabla\cdot\big[ab(\nabla p)^2(\nabla\times B)\big]\\ +&=-b\,(\nabla\times\mathbf B)\cdot\nabla\big[a(\nabla p)^2\big] ++\nabla\cdot\big[ab(\nabla p)^2(\nabla\times B)\big]\nonumber\\ +&- c\,\mathbf B\cdot\nabla\big[a(\nabla p)^2\big]+\nabla\cdot\big[ac(\nabla p)^2\mathbf B\big]\\ +&=\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr] + + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p)+ c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) \end{align} -Using (44) to eliminate the divergence term involving $b,c$ gives +Using (47) to eliminate the divergence term involving $b,c$ gives \begin{align} 2\mu_0 W_F = \int d^3r\Bigl\{ &\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2 -\nonumber\\ -&+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) -+ 2\mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) -\nonumber\\ -&- a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) -\Bigr\} -\nonumber\\ ++ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2+\mu_0a(\nabla p)^2\nabla\cdot(a\nabla p)\nonumber\\ +& +\cancel{\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr]}+ \mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p)+ \mu_0 c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p)\nonumber\\ +&+ \bigl[a(\nabla\times\mathbf{B})\times\nabla p + \mu_0 c\,\nabla p\bigr]\cdot +\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\Bigr\}\\ += \int d^3r\Bigl\{ +&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 ++ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) ++\cancel{\mu_0 c(\nabla p)\cdot \mu_0 (\nabla \times (b\nabla p))}\nonumber\\ +&+ 2\mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) +- a(\nabla \times \mathbf{B}) \times \nabla p \cdot \nabla\times(a\mathbf{B}\times\nabla p)\Bigr\}\\ = \int d^3r\Bigl\{ &\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 + \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) @@ -482,14 +454,10 @@ \subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified unit &+ a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) - a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 + \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) -\Bigr\}. +\Bigr\} \end{align} -Define -\begin{equation} -\mathbf{n}=\frac{\nabla p}{|\nabla p|}. -\end{equation} -Then, since $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, +Then, $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, \begin{align} &-a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) @@ -575,841 +543,2354 @@ \subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified unit % &=-(\frac{\mathbf{j} \cdot \mathbf{B}}{B^2})\mathbf{B} \times \nabla \psi\, \cdot\, (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'|\nabla \psi|^2 \kappa \cdot \nabla \psi \\ % &=\frac{2}{|\nabla \chi|^2}(\mathbf{j} \times \nabla \psi) \cdot (\mathbf{B} \cdot \nabla) \nabla \chi % \end{align} -\subsection{Derivation of K} -\color{blue} -\begin{align} -K -&= 2(\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \hat{n}\\ -&= \frac{2}{|\nabla \psi|} (\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \nabla \psi + \cancel{2(\mathbf{j} \times \hat{n}) \cdot [\nabla \psi (\mathbf{B} \cdot \nabla) (\frac{1}{|\nabla \psi|})]}\\ -&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j} \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B} + \nabla \psi \times \mathbf{j}\big]\\ -&= -\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} - (\mathbf{j} \times \nabla \psi)^2\big]\\ -&= +2j^2 -\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} \big]\\ -&= 2\sigma^2 B^2 + \underbrace{2 \frac{(\nabla p)^2}{B^2}-\frac{2}{|\nabla \psi|^2} \big[(\mathbf{j} \times \nabla \psi)(\nabla \psi \cdot \nabla)\mathbf{B} \big]}_{\text{$K_1 + K_3$}} -\end{align} -\color{black} -If we decompose current density into perpendicular and parallel components to magnetic field, then the perpendicular and parallel contributions to K are given by - -\color{blue} -\begin{align} -K_3 -&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j}_\perp \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] +2 \frac{(\nabla p)^2}{B^2}\\ -&= -\frac{2}{|\nabla \psi|^2} ((\frac{p^\prime}{B^2} \mathbf{B}\times \nabla \psi) \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] + 2 \frac{(p^\prime \nabla \psi)^2}{B^2}\\ -&= \frac{2}{|\nabla \psi|^2}\frac{p^\prime}{B^2} [\mathbf{B} |\nabla \psi|^2 - \cancel{\nabla \psi (\nabla \psi \cdot \mathbf{B})}] \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] + 2 \frac{(p^\prime \nabla \psi)^2}{B^2}\\ -&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] +(\nabla p \cdot \nabla \psi)\} \\ -&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] +(\mathbf{j} \times \mathbf{B}) \cdot \nabla \psi\} \\ -&= \frac{2p^\prime}{B^2} \{ \mathbf{B} \cdot [(\nabla \psi \cdot \nabla) \mathbf{B}] + \mathbf{B} \cdot (\nabla \psi \times \mathbf{j})\}\\ -&= -\frac{2p^\prime}{B^2} \mathbf{B} \cdot (\mathbf{B} \cdot \nabla) (\nabla \psi) = \frac{2p^\prime}{B^2}(\mathbf{B} \cdot \nabla) \cdot \mathbf{B} \cdot \nabla \psi \\ -&= \frac{2p^\prime}{B^2}((\mathbf{B} \cdot \nabla) \cdot \mathbf{B} \cdot \nabla \psi - \nabla \frac{B^2}{2}\cdot \nabla \psi) = +2 p^\prime [ (\frac{\mathbf{B}}{B} \cdot \nabla) \frac{\mathbf{B}}{B} ]\cdot \nabla \psi \\ -&= +2 p^\prime \boldsymbol{\kappa} \cdot \nabla \psi -\end{align} +\subsection{Derivation of $K$ with explicit $\mu_0$} -% \color{black} +% We define % \begin{equation} -% \mathbf{B}=B \mathbf{b} +% \hat{\mathbf n} \equiv \frac{\nabla\psi}{|\nabla\psi|}, +% \qquad +% \mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}, +% \qquad +% \nabla p = p'(\psi)\nabla\psi, +% \qquad +% \sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, +% \end{equation} +% \begin{equation} +% \mathbf b \equiv \frac{\mathbf B}{B}, +% \qquad +% \boldsymbol{\kappa}\equiv (\mathbf b\cdot\nabla)\mathbf b, +% \qquad +% \kappa_\psi \equiv \boldsymbol{\kappa}\cdot\nabla\psi. % \end{equation} -\color{blue} -\begin{align} -K_1 -&= -\frac{2}{|\nabla \psi|^2} (\mathbf{j}_\parallel \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] +\frac{2}{|\nabla \psi|^2}(\mathbf{j} \times \nabla \psi)^2\\ -&= -\frac{2}{|\nabla \psi|^2} (\frac{\mathbf{j} \cdot \mathbf{B}}{|\mathbf{B}|^2} \mathbf{B}\times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] \\ -&= -\sigma \frac{2}{|\nabla \psi|^2} (\mathbf{B} \times \nabla \psi) \cdot \big[(\nabla \psi \cdot \nabla)\mathbf{B}\big] -\end{align} - -\color{black} -Since we can use the vector identity, +We start from \begin{equation} - \nabla\times(\mathbf a\times\mathbf c) -= \mathbf a(\nabla\cdot\mathbf c)-\mathbf c(\nabla\cdot\mathbf a) -+(\mathbf c\cdot\nabla)\mathbf a-(\mathbf a\cdot\nabla)\mathbf c. -\end{equation} -\begin{equation} -\nabla \times (\mathbf{B} \times \nabla \psi) = \mathbf{B} (\nabla^2 \psi) - \cancel{\nabla \psi (\nabla \cdot \mathbf{B}) }+ (\nabla \psi \cdot \nabla) \mathbf{B} - (\mathbf{B} \cdot \nabla) (\nabla \psi) +\color{blue} K \equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n}. \end{equation} -\begin{equation} - \nabla(\mathbf a\cdot\mathbf b) - =(\mathbf a\cdot\nabla)\mathbf b+(\mathbf b\cdot\nabla)\mathbf a+\mathbf a\times(\nabla\times\mathbf b)+\mathbf b\times(\nabla\times\mathbf a) +Since +\color{blue}\begin{equation} +(\mathbf B\cdot\nabla)\hat{\mathbf n} += +(\mathbf B\cdot\nabla)\left(\frac{\nabla\psi}{|\nabla\psi|}\right) += +\frac{(\mathbf B\cdot\nabla)\nabla\psi}{|\nabla\psi|} ++ +\nabla\psi\,(\mathbf B\cdot\nabla)\left(\frac{1}{|\nabla\psi|}\right), \end{equation} -\begin{equation} -\cancel{\nabla(\mathbf B\cdot\nabla\psi)}=(\mathbf B\cdot\nabla)\nabla\psi+(\nabla\psi\cdot\nabla)\mathbf B+\nabla\psi\times(\nabla\times\mathbf B)+\cancel{\mathbf B\times(\nabla\times\nabla\psi)} +\color{blue}\begin{equation} +(\mathbf j\times \hat{\mathbf n})\cdot \nabla\psi = 0, \end{equation} +\color{black} +we obtain \begin{equation} - (\mathbf{B} \cdot \nabla) (\nabla \psi) = - (\nabla \psi \cdot \nabla) \mathbf{B} - \nabla \psi \times \mathbf{j} +\color{blue}K +\equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n} += +\frac{2}{|\nabla\psi|^2} +(\mathbf j\times\nabla\psi)\cdot(\mathbf B\cdot\nabla)\nabla\psi. \end{equation} +Now use $\mathbf B\cdot\nabla\psi=0$. Then \begin{equation} -\nabla \times (\mathbf{B} \times \nabla \psi) = \mathbf{B} (\nabla^2 \psi) +2 (\nabla \psi \cdot \nabla) \mathbf{B} + \nabla \psi \times \mathbf{j} -\end{equation} -\begin{equation} -(\mathbf{B} \times \nabla \psi) \cdot [\nabla \times (\mathbf{B} \times \nabla \psi)] = \cancel{(\mathbf{B} \times \nabla \psi) \cdot \mathbf{B} (\nabla^2 \psi)} +2 (\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \cdot \nabla) \mathbf{B} + (\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \times \mathbf{j}) +0=\nabla(\mathbf B\cdot\nabla\psi) += +(\mathbf B\cdot\nabla)\nabla\psi ++ +(\nabla\psi\cdot\nabla)\mathbf B ++ +\nabla\psi\times(\nabla\times\mathbf B), \end{equation} -and rearranging the terms, we have \begin{equation} - (\mathbf{B} \times \nabla \psi) \cdot \nabla \times (\frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) - = \cancel{(\mathbf{B} \times \nabla \psi) \cdot \nabla (\frac{1}{|\nabla \psi|^2}) \times (\mathbf{B} \times \nabla \psi) }+ (\mathbf{B} \times \nabla \psi) \cdot \frac{1}{|\nabla \psi|^2} \nabla \times (\mathbf{B} \times \nabla \psi) +(\mathbf B\cdot\nabla)\nabla\psi += +-(\nabla\psi\cdot\nabla)\mathbf B +-\mu_0\,\nabla\psi\times\mathbf j. \end{equation} -\color{blue} -\begin{align} -K_1 -&= -\sigma \frac{1}{|\nabla \psi|^2} (\mathbf{B} \times \nabla \psi) \cdot [\nabla \times (\mathbf{B} \times \nabla \psi)] +\sigma \frac{1}{|\nabla \psi|^2}(\mathbf{B} \times \nabla \psi) \cdot (\nabla \psi \times \mathbf{j})\\ -&= -\sigma \mathbf{B}\times \nabla \psi\cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \sigma \frac{1}{|\nabla \psi|^2} [-|\nabla \psi|^2 (\mathbf{B}\cdot\mathbf{j})+\cancel{(\mathbf{B}\cdot \nabla \psi)} \cdot (\nabla \psi \cdot \mathbf{j})]\\ -&= \sigma S |\nabla \psi|^2 - \sigma (\mathbf{B}\cdot\mathbf{j}) = \sigma S |\nabla \psi|^2 - \sigma^2 B^2 -\end{align} - -\color{blue} -\begin{align} - K - % &= - \mathbf{j} \cdot \mathbf{B} \frac{\mathbf{B}\times \nabla \psi}{|\mathbf{B}|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'(\frac{\mathbf{B}}{B} \cdot \nabla)\frac{\mathbf{B}}{B}\cdot \nabla \psi \\ - % &= - |\nabla \psi|^2 \sigma \frac{\mathbf{B}\times \nabla \psi}{|\nabla\psi|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'\boldsymbol{\kappa} \cdot \nabla \psi \\ - &= |\nabla \psi|^2 \sigma S + \sigma^2 B^2+ 2p'\boldsymbol{\kappa} \cdot \nabla \psi -\end{align} - -\color{black} -\subsection{Normalization of K between Chance and DCON coordinates} -\color{blue} -\begin{align} -K&=|\nabla \psi|^2 \sigma S + \sigma \mathbf{j} \cdot \mathbf{B} + 2p'\boldsymbol{\kappa} \cdot \nabla \psi -\end{align} - -\begin{align} - S&=-\frac{\mathbf{B}\times \nabla \psi}{|\nabla\psi|^2} \cdot (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) -\end{align} -\color{black} - -From this definition, the following relationships hold: -\begin{align} -\color{blue} |\nabla \psi|^2 -&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 |\nabla \psi_{\mathrm{dcon}}|^2 -\end{align} - -Substituting these relations into the original expression of \(K_{\mathrm{Chance}}\), we obtain -\begin{align} +Substituting this into $K$ gives +\color{blue}\begin{align} K -&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 -|\nabla \psi_{\mathrm{dcon}}|^2 \sigma S -+ \sigma \mathbf{j}\!\cdot\!\mathbf{B} -+ 2p'\!\left(\!\boldsymbol{\kappa}\cdot\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\nabla \psi_{\mathrm{dcon}}\right) \\[4pt] -&= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 -|\nabla \psi_{\mathrm{dcon}}|^2 \sigma S -+ \sigma \mathbf{j}\!\cdot\!\mathbf{B} -+ p'\,(\boldsymbol{\kappa}\!\cdot\!\nabla \psi_{\mathrm{dcon}}) \chi^\prime / \pi. +&= +-\frac{2}{|\nabla\psi|^2} +(\mathbf j\times\nabla\psi)\cdot +\Big[(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j\Big] +\\ +&= +-\frac{2}{|\nabla\psi|^2} +(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B ++\frac{2\mu_0}{|\nabla\psi|^2}\,|\mathbf j\times\nabla\psi|^2. \end{align} -Therefore, the final form of \(K\) in the DCON coordinate system is +\color{black} +Because \begin{equation} -\boxed{ -K -= \left(\frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\right)^2 -|\nabla \psi_{\mathrm{dcon}}|^2\,\sigma\,S -+ \sigma \mathbf{j}\!\cdot\!\mathbf{B} -+ p'\,(\boldsymbol{\kappa}\!\cdot\!\nabla \psi_{\mathrm{dcon}}) \chi^\prime / \pi. -} +0=\mathbf j\cdot\nabla p = p'\,\mathbf j\cdot\nabla\psi, \mathbf j\cdot\nabla\psi=0 \end{equation} - - -\subsection{Normalization of S between Chance and DCON coordinates} - -In Chance's formulation, the magnetic shear scalar \(S\) is defined as \begin{equation} -\color{blue} -S -= -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -\cdot -\left( -\nabla\times -\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -\right). +|\mathbf j\times\nabla\psi|^2 = j^2 |\nabla\psi|^2. +\end{equation} +Thus, +\begin{equation} +\color{blue}K += +2\mu_0 j^2 +-\frac{2}{|\nabla\psi|^2} +(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{equation} -Using the DCON normalization of the flux coordinate, +Now decompose the current as \begin{equation} -\color{blue} -\psi -\color{black} -= \frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\, -\psi_{\mathrm{dcon}}, +\mathbf j = \mathbf j_{\parallel}+\mathbf j_{\perp}, \qquad -\color{blue} \nabla \psi -\color{black} -= \frac{\chi^\prime_{\mathrm{dcon}}}{2\pi}\, \nabla \psi_{\mathrm{dcon}}, +\mathbf j_{\parallel}=\sigma \mathbf B, +\qquad +\mathbf j_{\perp}=\frac{\mathbf B\times\nabla p}{B^2} +=\frac{p'}{B^2}\mathbf B\times\nabla\psi. \end{equation} -we can rewrite each factor in terms of DCON variables. - -Hence, the normalized cross term becomes +Then \begin{equation} -\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -= \frac{2\pi}{\chi^\prime_{\mathrm{dcon}}}\, -\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2}. +\color{blue}K = K_{\parallel}+K_{\perp}, \end{equation} - -Substituting this back into the original definition of \(S\), we find -\begin{align} -S -&= -\left(\frac{2\pi}{\chi^\prime_{\mathrm{dcon}}}\right)^2 -\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2} -\cdot -\left[ -\nabla\times -\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}}{|\nabla\psi_{\mathrm{dcon}}|^2} -\right]\\ -&=-(2\pi)^2 -\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}}{|\nabla\psi_{\mathrm{dcon}}*\chi^\prime_{dcon}|^2} -\cdot -\left[ -\nabla\times -\frac{\mathbf{B}\times\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}}{|\nabla\psi_{\mathrm{dcon}}\chi^\prime_{dcon}|^2} -\right]\\ -&= -\,(2\pi)^2 \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} -\cdot -\left( -\nabla\times -\frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} -\right) -\end{align} - -\begin{align} -\boldsymbol{\Lambda} -&\equiv \frac{\mathbf{B} \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} -= \frac{(\nabla \chi_{dcon} \times \nabla \alpha) \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} \\[4pt] -&= \nabla \alpha -- \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon}. +\color{blue}\begin{align} +K_{\parallel} +&= +2\mu_0 j_{\parallel}^2 +-\frac{2}{|\nabla\psi|^2} +(\mathbf j_{\parallel}\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B, +\\ +K_{\perp} +&= +2\mu_0 j_{\perp}^2 +-\frac{2}{|\nabla\psi|^2} +(\mathbf j_{\perp}\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{align} -The curl becomes ($\alpha\, \text{represents}\, \alpha_{dcon}$ below this point) +\color{black} +\paragraph{1. Perpendicular part $K_{\perp}$} +Using +\begin{equation} +\mathbf j_{\perp}=\frac{p'}{B^2}\mathbf B\times\nabla\psi, +\end{equation} +we obtain \begin{align} -\nabla \times \boldsymbol{\Lambda} -&= -\,\nabla \times -\left[ -\frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} -\right] \\[4pt] -&= -\,\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -\times \nabla \chi_{dcon}, +K_{\perp} +&= +\frac{2\mu_0 p'^2 |\nabla\psi|^2}{B^2} +-\frac{2p'}{B^2|\nabla\psi|^2} +\Big[(\mathbf B\times\nabla\psi)\times\nabla\psi\Big] +\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{align} -since $\nabla \times \nabla = 0$. The magnetic shear: - +Now, +\begin{equation} +(\mathbf B\times\nabla\psi)\times\nabla\psi += +\nabla\psi(\mathbf B\cdot\nabla\psi)-\mathbf B|\nabla\psi|^2 += +-\mathbf B|\nabla\psi|^2, +\end{equation} +since $\mathbf B\cdot\nabla\psi=0$. Hence \begin{align} -S -&= - (2\pi)^2\, \boldsymbol{\Lambda} \cdot (\nabla \times \boldsymbol{\Lambda}) \\[4pt] -&= - (2\pi)^2 +K_{\perp} +&= +\frac{2\mu_0 p'^2 |\nabla\psi|^2}{B^2} ++\frac{2p'}{B^2}\,\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B +\\ +&= +\frac{2p'}{B^2} \left[ -\nabla \alpha -- \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} +\mu_0\,\nabla p\cdot\nabla\psi ++\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B \right] -\cdot +\\ +&= +\frac{2p'}{B^2} \left[ -\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -\times \nabla \chi_{dcon} -\right] \\[6pt] -&= (2\pi)^2\, \nabla \alpha \cdot +\mu_0\,(\mathbf j\times\mathbf B)\cdot\nabla\psi ++\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B +\right] +\\ +&= +\frac{2p'}{B^2}\, +\mathbf B\cdot \left[ -\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -\times \nabla \chi_{dcon} -\right] \\[6pt] -&= (2\pi)^2\, (\nabla \chi_{dcon} \times \nabla \alpha) \cdot -\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) \\[6pt] -&= (2\pi)^2\, \mathbf{B} \cdot -\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right)\\ -&= \frac{(2\pi)^2}{\chi\prime_{dcon}}\, \mathbf{B} \cdot \nabla (\frac{\nabla \psi \cdot \nabla \alpha}{\nabla\psi \cdot \nabla\psi}) +(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j +\right]. \end{align} - -The parallel gradient operator is: +Using \begin{equation} -\mathbf{B} \cdot \nabla -= B^{\theta} \frac{\partial}{\partial \theta} -+ B^{\zeta} \frac{\partial}{\partial \zeta} -= \frac{\chi'}{\mathcal{J}} -\left( -\frac{\partial}{\partial \theta} -+ q \frac{\partial}{\partial \zeta} -\right). +(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j += +-(\mathbf B\cdot\nabla)\nabla\psi, \end{equation} - -Also note -\begin{align} -\nabla \psi \cdot \nabla \alpha -&= \nabla \psi \cdot \nabla \big(q(\psi)\theta - \zeta\big) -= q'(\nabla \psi \cdot \nabla \psi)\theta -+ \nabla \psi \cdot \big(q \nabla \theta - \nabla \zeta\big). -\end{align} - -Since $\partial / \partial \zeta = 0$ for a tokamak, one obtains: -\begin{align} -S -&= \frac{(2\pi)^2}{\mathcal{J}} -\frac{\partial}{\partial \theta} -\left[ -q' \theta -+ \frac{q(\nabla \psi \cdot \nabla \theta) -- (\nabla \psi \cdot \nabla \zeta)} -{(\nabla \psi \cdot \nabla \psi)} -\right] \\[6pt] -&= \frac{(2\pi)^2}{\mathcal{J}} -\left[ -q' -+ \frac{\partial}{\partial \theta} -\left( -\frac{q(\nabla \psi \cdot \nabla \theta) -- (\nabla \psi \cdot \nabla \zeta)} -{(\nabla \psi \cdot \nabla \psi)} -\right) -\right]. -\end{align} - -\subsection{$\xi^{\psi}$ in DCON berstein caculation} - -\begin{align} -\boxed{ -\boldsymbol{\xi}_\perp \cdot \hat{n} -= \frac{\boldsymbol{\xi} \cdot \nabla \psi}{|\nabla \psi|} -= \frac{\xi^\psi}{|\nabla \psi|}, -} -\qquad -\boxed{ -(\boldsymbol{\xi}_\perp \cdot \hat{n})^2 -= \frac{(\xi^\psi)^2}{|\nabla \psi|^2}. -} -\end{align} - +we get \begin{equation} -\boxed{ -\boldsymbol{\xi}_\perp \cdot \hat{n} -= \frac{1}{|\nabla \psi|} -\sum_{m,n} v_{m,n}(\psi)\, e^{2\pi i(m\theta - n\zeta)}. -} +K_{\perp} += +-\frac{2p'}{B^2}\,\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi. \end{equation} -\subsection{$\boldsymbol{\kappa}$ in DCON berstein caculation} - - -\section{Reconstruction of energy principle variables in DCON} -\subsection{Reconstruction v1} - -\begin{align} -\xi^\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ -\xi^s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) -\end{align} - -\begin{align} -\vec{j} &= \mathcal{J} (j^{\psi} \nabla \theta \times \nabla \zeta + j^{\theta} \nabla \zeta \times \nabla \psi + j^{\zeta} \nabla \psi \times \nabla \theta) \\ -&= \mathcal{J} ( j^{\psi} \vec{v_1} + j^{\theta} \vec{v_2} + j^{\zeta} \vec{v_3})\\ -&=\mathcal{J} (j^{\theta} \nabla \zeta \times \nabla \psi + j^{\zeta} \nabla \psi \times \nabla \theta) -\end{align} - -\begin{align} -\vec{Q} &= \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) &= \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) \\ -&= Q_1 \nabla \psi + Q_2 \nabla \theta + Q_3 \nabla \zeta -\end{align} - +Also, \begin{equation} -Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi^\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) +0=(\mathbf B\cdot\nabla)(\mathbf B\cdot\nabla\psi) += +((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi ++ +\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi, \end{equation} - -\begin{align} -Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi ^\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \zeta} \\ -&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) -\end{align} - -\begin{align} -Q^{\zeta} -&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi^\psi) - -\frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \theta} \\ -&= - \frac{1}{\mathcal{J}} \sum_{m} \sum_{n} - \left( \chi ^ \prime q \frac{\partial v_{m,n}(\psi)}{\partial \psi} - + \chi^\prime q^\prime v_{m,n}(\psi) - + 2\pi i m u_{m,n}(\psi) \right) - \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). -\end{align} - +so that \begin{equation} -\Xi_{\psi} -= \texttt{u(:,:,1)} \quad \text{(Fortran, DCON)} -= \texttt{u(:,:,1)} \quad \text{(Fortran, MATCH)}. +\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi += +-((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. \end{equation} - +Therefore, \begin{equation} -\Xi_{s} -= \texttt{ud(:,:,2)} \quad \text{(Fortran, DCON)} -= \texttt{u(:,:,4)} \quad \text{(Fortran, MATCH)}. +K_{\perp} += +\frac{2p'}{B^2} +\big((\mathbf B\cdot\nabla)\mathbf B\big)\cdot\nabla\psi. \end{equation} +Finally, with $\mathbf B=B\mathbf b$, \begin{equation} -\Xi_{\psi}^\prime -= \texttt{ud(:,:,1)} -= \texttt{du(:,:,1)} \quad \text{(Fortran, DCON)} -= \texttt{u(:,:,3)} \quad \text{(Fortran, MATCH)}. +(\mathbf B\cdot\nabla)\mathbf B += +B^2(\mathbf b\cdot\nabla)\mathbf b ++ +B(\mathbf b\cdot\nabla B)\mathbf b, \end{equation} - -And if you see the 331 line in ideal.f of MATCH, it constructs all the eigenvectors in variable v. +and since $\mathbf b\cdot\nabla\psi=0$, \begin{equation} -v_{m,n}(\psi) -= v(:,1,mstep) +\big((\mathbf B\cdot\nabla)\mathbf B\big)\cdot\nabla\psi += +B^2\,\boldsymbol{\kappa}\cdot\nabla\psi += +B^2\kappa_\psi. \end{equation} - +Thus, \begin{equation} -v_{m,n}^\prime(\psi) -= v(:,3,mstep) +\boxed{ +K_{\perp}=2p'\kappa_\psi +}. \end{equation} +\paragraph{2. Parallel part $K_{\parallel}$} +Since $\mathbf j_{\parallel}=\sigma\mathbf B$, \begin{equation} -u_{m,n}(\psi) -= v(:,4,mstep) +K_{\parallel} += +2\mu_0\sigma^2B^2 +-\frac{2\sigma}{|\nabla\psi|^2} +(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{equation} +Now use the vector identity \begin{equation} - \mu_0 \vec{j} = \mathcal{J} (\mu_0 j^\theta \vec{v}_2 + \mu_0 j^\zeta \vec{v}_3) +\nabla\times(\mathbf B\times\nabla\psi) += +\mathbf B\nabla^2\psi ++ +(\nabla\psi\cdot\nabla)\mathbf B +- +(\mathbf B\cdot\nabla)\nabla\psi. \end{equation} +Substituting \begin{equation} - \mu_0 \vec{j} \times \nabla \psi = \mathcal{J} \mu_0 j^\theta (\vec{v}_2 \times \nabla \psi) + \mathcal{J} \mu_0 j^\zeta (\vec{v}_3 \times \nabla \psi) +(\mathbf B\cdot\nabla)\nabla\psi += +-(\nabla\psi\cdot\nabla)\mathbf B +-\mu_0\,\nabla\psi\times\mathbf j, \end{equation} - +we obtain \begin{equation} - j^\theta = j \cdot \nabla \theta = - 2 \pi f^\prime / \mathcal{J} +\nabla\times(\mathbf B\times\nabla\psi) += +\mathbf B\nabla^2\psi ++ +2(\nabla\psi\cdot\nabla)\mathbf B ++ +\mu_0\,\nabla\psi\times\mathbf j. \end{equation} +Taking the dot product with $\mathbf B\times\nabla\psi$ gives \begin{equation} - j^\zeta = j \cdot \nabla \zeta = - \mu_0 p^\prime / \chi^\prime - 2 \pi f^\prime q / \mathcal{J} +(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) += +2(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B ++ +\mu_0\,(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j), \end{equation} -Now we can reconstruct the cross term in the energy principle: - +because +\begin{equation} +(\mathbf B\times\nabla\psi)\cdot\mathbf B = 0. +\end{equation} +Hence +\begin{align} +K_{\parallel} +&= +2\mu_0\sigma^2B^2 +- +\frac{\sigma}{|\nabla\psi|^2} +(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) ++ +\frac{\mu_0\sigma}{|\nabla\psi|^2} +(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j). +\end{align} + +Now, +\begin{equation} +(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j) += +(\mathbf B\cdot\nabla\psi)(\nabla\psi\cdot\mathbf j) +- +(\mathbf B\cdot\mathbf j)|\nabla\psi|^2 += +-\sigma B^2|\nabla\psi|^2, +\end{equation} +so +\begin{equation} +K_{\parallel} += +\mu_0\sigma^2B^2 +- +\frac{\sigma}{|\nabla\psi|^2} +(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi). +\end{equation} + +Define the shear quantity +\begin{equation} +S +\equiv +- +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\cdot +\nabla\times\left( +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\right). +\end{equation} +Also, +\begin{equation} +(\mathbf B\times\nabla\psi)\cdot +\left[ +\nabla\left(\frac{1}{|\nabla\psi|^2}\right) +\times +(\mathbf B\times\nabla\psi) +\right] +=0, +\end{equation} +and therefore +\begin{equation} +\frac{1}{|\nabla\psi|^2} +(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) += +(\mathbf B\times\nabla\psi)\cdot +\nabla\times\left( +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\right). +\end{equation} +Thus, +\begin{equation} +\boxed{ +K_{\parallel} += +|\nabla\psi|^2 \sigma S + \mu_0\sigma^2B^2 +}. +\end{equation} + +\paragraph{3. Final expression} +Combining the two pieces, +\begin{equation} +\boxed{ +K += +K_{\parallel}+K_{\perp} += +|\nabla\psi|^2 \sigma S ++\mu_0\sigma^2B^2 ++2p'\kappa_\psi +}. +\end{equation} + +Therefore it is convenient to define +\begin{equation} +K_{\rm shear}\equiv |\nabla\psi|^2 \sigma S, +\qquad +K_\sigma\equiv \mu_0\sigma^2B^2, +\qquad +K_{p\kappa}\equiv 2p'\kappa_\psi, +\end{equation} +so that +\begin{equation} +K = K_{\rm shear}+K_\sigma+K_{p\kappa}. \quad (\hat{\mathbf n} \equiv \frac{\nabla\psi}{|\nabla\psi|}, +\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, +\mathbf b \equiv \frac{\mathbf B}{B}, +\boldsymbol{\kappa}\equiv (\mathbf b\cdot\nabla)\mathbf b, +\kappa_\psi \equiv \boldsymbol{\kappa}\cdot\nabla\psi.) +\end{equation} + +\subsection{Bernstein $K$ in DCON coordinates} + +We use the convention +\begin{equation} +K += +|\nabla\psi|^2 \sigma S ++\mu_0 \sigma^2 B^2 ++2p'\kappa_\psi, +\qquad +\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, +\qquad +\mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}. +\end{equation} + +In DCON straight-field-line coordinates $(\psi,\theta,\zeta)$, we write +\begin{equation} +\mathbf B += +\nabla\chi \times \nabla\alpha += +\chi'(\psi)\,\nabla\psi\times\nabla\alpha, +\qquad +\alpha \equiv q(\psi)\theta-\zeta, +\qquad +\chi' \equiv \frac{d\chi}{d\psi}. +\end{equation} + +The Jacobian is defined by +\begin{equation} +\mathcal J^{-1} +\equiv +\nabla\psi\cdot(\nabla\theta\times\nabla\zeta), +\end{equation} +so that the parallel gradient operator becomes +\begin{equation} +\mathbf B\cdot\nabla += +\frac{\chi'}{\mathcal J} +\left( +\frac{\partial}{\partial\theta} ++ +q\frac{\partial}{\partial\zeta} +\right). +\end{equation} + +We also define the contravariant metric elements +\begin{equation} +g^{\psi\psi}\equiv \nabla\psi\cdot\nabla\psi, +\qquad +g^{\psi\theta}\equiv \nabla\psi\cdot\nabla\theta, +\qquad +g^{\psi\zeta}\equiv \nabla\psi\cdot\nabla\zeta. +\end{equation} + +\subsection{Expression of $S$ in DCON coordinates} + +We define +\begin{equation} +S +\equiv +-\, +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\cdot +\nabla\times +\left( +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\right). +\end{equation} + +Introduce +\begin{equation} +\boldsymbol{\Lambda}_\psi +\equiv +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}. +\end{equation} +Using $\mathbf B=\chi'\nabla\psi\times\nabla\alpha$, we obtain +\begin{align} +\boldsymbol{\Lambda}_\psi +&= +\chi' +\frac{(\nabla\psi\times\nabla\alpha)\times\nabla\psi}{|\nabla\psi|^2} +\\ +&= +\chi' +\left[ +\nabla\alpha +- +\frac{\nabla\psi\cdot\nabla\alpha}{|\nabla\psi|^2}\nabla\psi +\right]. +\end{align} + +Define +\begin{equation} +\Gamma_\alpha +\equiv +\frac{\nabla\psi\cdot\nabla\alpha}{|\nabla\psi|^2}. +\end{equation} +Then +\begin{equation} +\boldsymbol{\Lambda}_\psi += +\chi'(\nabla\alpha-\Gamma_\alpha \nabla\psi). +\end{equation} + +Its curl is +\begin{align} +\nabla\times\boldsymbol{\Lambda}_\psi +&= +\nabla\chi' \times (\nabla\alpha-\Gamma_\alpha\nabla\psi) ++ +\chi'\nabla\times(\nabla\alpha-\Gamma_\alpha\nabla\psi) +\\ +&= +\nabla\chi' \times (\nabla\alpha-\Gamma_\alpha\nabla\psi) +- +\chi'\,\nabla\Gamma_\alpha\times\nabla\psi, +\end{align} +since $\nabla\times\nabla\alpha=0$ and $\nabla\times\nabla\psi=0$. + +Now, because $\nabla\chi' \parallel \nabla\psi$, the first term vanishes when contracted +with $\boldsymbol{\Lambda}_\psi$, so +\begin{align} +S +&= +-\boldsymbol{\Lambda}_\psi\cdot(\nabla\times\boldsymbol{\Lambda}_\psi) +\\ +&= +\chi'^2 +(\nabla\alpha-\Gamma_\alpha\nabla\psi)\cdot +(\nabla\Gamma_\alpha\times\nabla\psi) +\\ +&= +\chi'^2\,\nabla\alpha\cdot(\nabla\Gamma_\alpha\times\nabla\psi) +\\ +&= +\chi'^2\,(\nabla\psi\times\nabla\alpha)\cdot\nabla\Gamma_\alpha +\\ +&= +\chi'\,\mathbf B\cdot\nabla\Gamma_\alpha. +\end{align} +Therefore, +\begin{equation} +\boxed{ +S += +\chi'\,\mathbf B\cdot\nabla +\left( +\frac{\nabla\psi\cdot\nabla\alpha}{\nabla\psi\cdot\nabla\psi} +\right). +} +\end{equation} + +Next, +\begin{align} +\nabla\psi\cdot\nabla\alpha +&= +\nabla\psi\cdot\nabla\big(q(\psi)\theta-\zeta\big) +\\ +&= +q'(\nabla\psi\cdot\nabla\psi)\,\theta ++ +q(\nabla\psi\cdot\nabla\theta) +- +(\nabla\psi\cdot\nabla\zeta), +\end{align} +so that +\begin{equation} +\Gamma_\alpha += +q'\theta ++ +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}}. +\end{equation} + +For an axisymmetric tokamak equilibrium, all equilibrium scalars and metric elements are +independent of $\zeta$, hence $\partial\Gamma_\alpha/\partial\zeta=0$. Using +\begin{equation} +\mathbf B\cdot\nabla += +\frac{\chi'}{\mathcal J} +\left( +\frac{\partial}{\partial\theta} ++ +q\frac{\partial}{\partial\zeta} +\right), +\end{equation} +we obtain +\begin{align} +S +&= +\frac{\chi'^2}{\mathcal J} +\frac{\partial}{\partial\theta} +\left[ +q'\theta ++ +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right] +\\ +&= +\boxed{ +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +} +\end{align} + +\subsection{Normal displacement $\xi_\psi$ in DCON coordinates} + +We define the normal displacement variable by +\begin{equation} +\xi_\psi +\equiv +\boldsymbol{\xi}\cdot\nabla\psi += +\xi^\psi, +\qquad +\hat{\mathbf n} += +\frac{\nabla\psi}{|\nabla\psi|}. +\end{equation} +Therefore, +\begin{equation} +\boxed{ +\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n} += +\frac{\boldsymbol{\xi}\cdot\nabla\psi}{|\nabla\psi|} += +\frac{\xi_\psi}{|\nabla\psi|}. +} +\end{equation} + +Using the DCON Fourier convention, +\begin{equation} +\xi_\psi(\psi,\theta,\zeta) += +\sum_{m,n} +v_{m,n}(\psi)\, +e^{2\pi i(m\theta-n\zeta)}, +\end{equation} +we obtain +\begin{equation} +\boxed{ +\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n} += +\frac{1}{|\nabla\psi|} +\sum_{m,n} +v_{m,n}(\psi)\, +e^{2\pi i(m\theta-n\zeta)}. +} +\end{equation} + +For the quadratic form in the energy principle, it is more precise to write +\begin{equation} +\boxed{ +(\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n}) +(\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n})^* += +\frac{\xi_\psi\xi_\psi^*}{|\nabla\psi|^2}. +} +\end{equation} +If one restricts to a real displacement, the complex conjugation may be dropped at the end. + +\subsection{Expression of $\kappa_\psi$ in DCON coordinates} + +We define +\begin{equation} +\kappa_\psi +\equiv +\boldsymbol{\kappa}\cdot\nabla\psi, +\qquad +\boldsymbol{\kappa}\equiv(\mathbf b\cdot\nabla)\mathbf b, +\qquad +\mathbf b\equiv\frac{\mathbf B}{B}. +\end{equation} + +Using the equilibrium force balance +\begin{equation} +(\nabla\times\mathbf B)\times\mathbf B += +\mu_0\nabla p, +\qquad +\nabla p = p'(\psi)\nabla\psi, +\end{equation} +together with +\begin{equation} +(\nabla\times\mathbf B)\times\mathbf B += +(\mathbf B\cdot\nabla)\mathbf B +- +\nabla\left(\frac{B^2}{2}\right), +\end{equation} +we obtain +\begin{equation} +(\mathbf B\cdot\nabla)\mathbf B += +\mu_0\nabla p ++ +\nabla\left(\frac{B^2}{2}\right). +\end{equation} + +Taking the dot product with $\nabla\psi$ gives +\begin{equation} +B^2\kappa_\psi += +\mu_0 p' |\nabla\psi|^2 ++ +\frac{1}{2}\,\nabla B^2\cdot\nabla\psi. +\end{equation} + +Now expand +\begin{equation} +\nabla B^2 += +\frac{\partial B^2}{\partial\psi}\nabla\psi ++ +\frac{\partial B^2}{\partial\theta}\nabla\theta ++ +\frac{\partial B^2}{\partial\zeta}\nabla\zeta. +\end{equation} +For an axisymmetric tokamak equilibrium, $\partial B^2/\partial\zeta=0$, so +\begin{equation} +\nabla B^2\cdot\nabla\psi += +\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{\partial B^2}{\partial\theta}g^{\psi\theta}. +\end{equation} +Hence +\begin{equation} +\boxed{ +\kappa_\psi += +\frac{1}{B^2} +\left[ +\mu_0 p' g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta}g^{\psi\theta} +\right]. +} +\end{equation} +Equivalently, +\begin{equation} +\boxed{ +\kappa_\psi += +\frac{g^{\psi\psi}}{B^2} +\left[ +\mu_0 p' ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta} +\frac{g^{\psi\theta}}{g^{\psi\psi}} +\right]. +} +\end{equation} + +\subsection{Final DCON expression for $K$} + +Combining the DCON expressions above, we obtain +\begin{equation} +\boxed{ +K += +g^{\psi\psi}\sigma S_\psi ++\mu_0\sigma^2B^2 ++2p'\kappa_\psi. +} +\end{equation} +That is, +\begin{align} +K(\psi,\theta) +&= +g^{\psi\psi}\sigma +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right] ++\mu_0\sigma^2B^2 +\\ +&\quad ++\frac{2p'}{B^2} +\left[ +\mu_0 p' g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta}g^{\psi\theta} +\right]. +\end{align} +% \subsection{Normalization of S between Chance and DCON coordinates} +% \noindent Blue symbols denote Chance variables and black symbols denote DCON variables; the subscripts are omitted in this subsection. + +% In Chance's formulation, the magnetic shear scalar \(S\) is defined as +% \begin{equation} +% \color{blue} +% S +% = -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \cdot +% \left( +% \nabla\times +% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \right). +% \end{equation} + +% Using the DCON normalization of the flux coordinate, +% \begin{equation} +% {\color{blue}\psi} +% = \frac{\chi^\prime}{2\pi}\, +% \psi, +% \qquad +% {\color{blue}\nabla \psi} +% = \frac{\chi^\prime}{2\pi}\, \nabla \psi, +% \end{equation} +% we can rewrite each factor in terms of DCON variables. + +% Hence, the normalized cross term becomes +% \begin{equation} +% \frac{{\color{blue}\mathbf{B}\times\nabla\psi}}{{\color{blue}|\nabla\psi|^2}} +% = \frac{2\pi}{\chi^\prime}\, +% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2}. +% \end{equation} + +% Substituting this back into the original definition of \(S\), we find +% \begin{align} +% {\color{blue}S} +% &= -\left(\frac{2\pi}{\chi^\prime}\right)^2 +% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \cdot +% \left[ +% \nabla\times +% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \right]\\ +% &= \left(\frac{2\pi}{\chi^\prime}\right)^2 +% \underbrace{\left[ +% -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \cdot +% \left( +% \nabla\times +% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} +% \right)\right]}_{S}\\ +% &= -\,(2\pi)^2 \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} +% \cdot +% \left( +% \nabla\times +% \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} +% \right) +% \end{align} + +% Therefore, the final relation between the two shear definitions is +% \begin{equation} +% \boxed{ +% {\color{blue}S} +% = \left(\frac{2\pi}{\chi^\prime}\right)^2 +% S +% = c^{-2} S, +% \qquad +% S +% = \left(\frac{\chi^\prime}{2\pi}\right)^2 +% {\color{blue}S}. +% } +% \end{equation} + +% Below this point in this subsection, \(S\) denotes the DCON quantity. + +% \begin{align} +% \boldsymbol{\Lambda} +% &\equiv \frac{\mathbf{B} \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} +% = \frac{(\nabla \chi_{dcon} \times \nabla \alpha) \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} \\[4pt] +% &= \nabla \alpha +% - \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon}. +% \end{align} + +% The curl becomes ($\alpha\, \text{represents}\, \alpha_{dcon}$ below this point) +% \begin{align} +% \nabla \times \boldsymbol{\Lambda} +% &= -\,\nabla \times +% \left[ +% \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} +% \right] \\[4pt] +% &= -\,\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +% \times \nabla \chi_{dcon}, +% \end{align} + +% since $\nabla \times \nabla = 0$. The magnetic shear: + +% \begin{align} +% {\color{blue}S} +% &= - (2\pi)^2\, \boldsymbol{\Lambda} \cdot (\nabla \times \boldsymbol{\Lambda}) \\[4pt] +% &= - (2\pi)^2 +% \left[ +% \nabla \alpha +% - \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} +% \right] +% \cdot +% \left[ +% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +% \times \nabla \chi_{dcon} +% \right] \\[6pt] +% &= (2\pi)^2\, \nabla \alpha \cdot +% \left[ +% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) +% \times \nabla \chi_{dcon} +% \right] \\[6pt] +% &= (2\pi)^2\, (\nabla \chi_{dcon} \times \nabla \alpha) \cdot +% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) \\[6pt] +% &= (2\pi)^2\, \mathbf{B} \cdot +% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right)\\ +% &= \frac{(2\pi)^2}{\chi\prime_{dcon}}\, \mathbf{B} \cdot \nabla (\frac{\nabla \psi \cdot \nabla \alpha}{\nabla\psi \cdot \nabla\psi}) +% \end{align} + + +% The parallel gradient operator is: +% \begin{equation} +% \mathbf{B} \cdot \nabla +% = B^{\theta} \frac{\partial}{\partial \theta} +% + B^{\zeta} \frac{\partial}{\partial \zeta} +% = \frac{\chi'}{\mathcal{J}} +% \left( +% \frac{\partial}{\partial \theta} +% + q \frac{\partial}{\partial \zeta} +% \right). +% \end{equation} + +% Also note +% \begin{align} +% \nabla \psi \cdot \nabla \alpha +% &= \nabla \psi \cdot \nabla \big(q(\psi)\theta - \zeta\big) +% = q'(\nabla \psi \cdot \nabla \psi)\theta +% + \nabla \psi \cdot \big(q \nabla \theta - \nabla \zeta\big). +% \end{align} + +% Since $\partial / \partial \zeta = 0$ for a tokamak, one obtains: +% \begin{align} +% {\color{blue}S} +% &= \frac{(2\pi)^2}{\mathcal{J}} +% \frac{\partial}{\partial \theta} +% \left[ +% q' \theta +% + \frac{q(\nabla \psi \cdot \nabla \theta) +% - (\nabla \psi \cdot \nabla \zeta)} +% {(\nabla \psi \cdot \nabla \psi)} +% \right] \\[6pt] +% &= \frac{(2\pi)^2}{\mathcal{J}} +% \left[ +% q' +% + \frac{\partial}{\partial \theta} +% \left( +% \frac{q(\nabla \psi \cdot \nabla \theta) +% - (\nabla \psi \cdot \nabla \zeta)} +% {(\nabla \psi \cdot \nabla \psi)} +% \right) +% \right]. +% \end{align} + +% \subsection{$\xi^{\psi}$ in DCON berstein caculation} + +% \begin{align} +% \boxed{ +% \boldsymbol{\xi}_\perp \cdot \hat{n} +% = \frac{\boldsymbol{\xi} \cdot \nabla \psi}{|\nabla \psi|} +% = \frac{\xi^\psi}{|\nabla \psi|}, +% } +% \qquad +% \boxed{ +% (\boldsymbol{\xi}_\perp \cdot \hat{n})^2 +% = \frac{(\xi^\psi)^2}{|\nabla \psi|^2}. +% } +% \end{align} + +% \begin{equation} +% \boxed{ +% \boldsymbol{\xi}_\perp \cdot \hat{n} +% = \frac{1}{|\nabla \psi|} +% \sum_{m,n} v_{m,n}(\psi)\, e^{2\pi i(m\theta - n\zeta)}. +% } +% \end{equation} + +% \subsection{$\boldsymbol{\kappa}$ in DCON berstein caculation} +% \noindent Blue symbols denote Chance variables and black symbols denote DCON variables; the subscripts are omitted in this subsection. +% \begin{align} +% \kappa^\psi &\equiv \boldsymbol{\kappa} \cdot \nabla \psi. +% \end{align} + +% \begin{align} +% B^2 \boldsymbol{\kappa} +% = \mu_0 \nabla p + \nabla_\perp \frac{B^2}{2}, +% \end{align} +% \begin{align} +% \nabla p &= p' \nabla \psi, +% \nabla \frac{B^2}{2} +% = \frac{1}{2}\frac{\partial B^2}{\partial \psi}\nabla \psi +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta}\nabla \theta, +% \end{align} +% \begin{align} +% B^2 \kappa^\psi +% \!= +% \mu_0 p' (\nabla \psi \cdot \nabla \psi) +% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} +% (\nabla \psi \cdot \nabla \psi) +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% (\nabla \psi \cdot \nabla \theta). +% \end{align} +% \begin{align} +% \boxed{ +% \kappa^\psi +% = \frac{1}{B^2} +% \left[ +% \mu_0 p' (\nabla \psi \cdot \nabla \psi) +% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} +% (\nabla \psi \cdot \nabla \psi) +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% (\nabla \psi \cdot \nabla \theta) +% \right] +% } +% \end{align} +% or equivalently +% \begin{align} +% \boxed{ +% \kappa^\psi +% = \frac{|\nabla \psi|^2}{B^2} +% \left[ +% \mu_0 p' +% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% \frac{\nabla \psi \cdot \nabla \theta}{|\nabla \psi|^2} +% \right]. +% } +% \end{align} + +% \begin{align} +% {\color{blue}\psi}= c\,\psi, +% \qquad +% c \equiv \frac{\chi'}{2\pi}. +% \end{align} +% \begin{align} +% {\color{blue}\nabla \psi}&= c\,\nabla \psi, \\ +% {\color{blue}|\nabla \psi|^2}&= c^2 |\nabla \psi|^2, \\ +% \frac{\partial B^2}{\partial {\color{blue}\psi}} +% &= \frac{1}{c}\frac{\partial B^2}{\partial \psi}, \\ +% {\color{blue}p'} +% &= \frac{dp}{d{\color{blue}\psi}} +% = \frac{1}{c} p'. +% \end{align} +% \begin{align} +% {\color{blue}\kappa^\psi} +% &= +% \frac{{\color{blue}|\nabla \psi|^2}}{B^2} +% \left[ +% \mu_0 {\color{blue}p'} +% + \frac{1}{2}\frac{\partial B^2}{\partial {\color{blue}\psi}} +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% \frac{{\color{blue}\nabla \psi \cdot \nabla \theta}} +% {{\color{blue}|\nabla \psi|^2}} +% \right] \\ +% &= +% \frac{c^2 |\nabla \psi|^2}{B^2} +% \left[ +% \frac{1}{c}\mu_0 p' +% + \frac{1}{2c}\frac{\partial B^2}{\partial \psi} +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% \frac{c\,\nabla \psi \cdot \nabla \theta} +% {c^2 |\nabla \psi|^2} +% \right] \\ +% &= +% c\,\frac{|\nabla \psi|^2}{B^2} +% \left[ +% \mu_0 p' +% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% \frac{\nabla \psi \cdot \nabla \theta} +% {|\nabla \psi|^2} +% \right]. +% \end{align} +% \begin{align} +% \boxed{ +% \kappa^\psi +% \equiv +% \boldsymbol{\kappa}\cdot\nabla \psi +% = \frac{|\nabla \psi|^2}{B^2} +% \left[ +% \mu_0 p' +% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} +% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} +% \frac{\nabla \psi \cdot \nabla \theta} +% {|\nabla \psi|^2} +% \right], +% } +% \end{align} +% \begin{align} +% {\color{blue}\kappa^\psi}= c\,\kappa^\psi. +% \end{align} + +\section{Reconstruction of energy principle variables in DCON} + +\begin{equation} +\begin{aligned} +2\mu_0\delta W +&= \int \big[|\vec{C}|^2- \mu_0K(\vec{\xi} \cdot \hat{n})^2 +\gamma p \mu_0|\vec{\nabla} \cdot \vec{\xi}|^2 \big] dx^3\\ +&= \int d\psi d\theta d\zeta \mathcal{J} \Big\{ [ \nabla \xi_s \times \nabla \psi ++ (\nabla \theta \times \nabla \zeta)\,\mathcal{J}\,\mathbf{B}\cdot\nabla \xi_\psi +- \mathbf{B}\,\xi_\psi' \\ +&- +\left( + \chi'' \nabla \zeta \times \nabla \psi + + (q\chi')' \nabla \psi \times \nabla \theta +\right)\xi_\psi \\ +&+\frac{\xi_\psi}{|\nabla \psi|^2} [ +j^\theta +\big( +\nabla\psi(\nabla\zeta\cdot\nabla\psi) +- +\nabla\zeta|\nabla\psi|^2 +\big)+j^\zeta +\big( +\nabla\theta|\nabla\psi|^2 +- +\nabla\psi(\nabla\theta\cdot\nabla\psi) +\big)]]^2 \\ +&-\mu_0 K(\vec{\xi} \cdot \hat{n})^2 +\gamma p \mu_0|\vec{\nabla} \cdot \vec{\xi}|^2\} +\end{aligned} +\end{equation} + +\subsection{Reconstruction v1} + +\begin{align} +\xi^\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ +\xi^s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) +\end{align} +And if you see the 331 line in ideal.f of MATCH, it constructs all the eigenvectors in variable v. +\begin{equation} +v_{m,n}(\psi) += v(:,1,mstep) +\end{equation} +\begin{equation} +v_{m,n}^\prime(\psi) += v(:,3,mstep) +\end{equation} +\begin{equation} +u_{m,n}(\psi) += v(:,4,mstep) +\end{equation} +\medskip +\noindent\rule{\linewidth}{0.4pt} +\begin{align} +\vec{Q} &= \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) &= \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) \\ +&= Q_1 \nabla \psi + Q_2 \nabla \theta + Q_3 \nabla \zeta +\end{align} + +\begin{equation} +Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi^\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) +\end{equation} + +\begin{align} +Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi ^\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \zeta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) +\end{align} + +\begin{align} +Q^{\zeta} +&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi^\psi) + -\frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \theta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m} \sum_{n} + \left( \chi ^ \prime q \frac{\partial v_{m,n}(\psi)}{\partial \psi} + + \chi^\prime q^\prime v_{m,n}(\psi) + + 2\pi i m u_{m,n}(\psi) \right) + \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). +\end{align} + +Then the current vector can be reconstructed by the Grad-Shafranov equation: +\begin{align} +\mu_0 \vec{j} &= \mathcal{J} (\mu_0 j^{\psi} \nabla \theta \times \nabla \zeta + \mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) \\ +&= \mathcal{J} (\mu_0 j^{\psi} \vec{v_1} + \mu_0 j^{\theta} \vec{v_2} + \mu_0 j^{\zeta} \vec{v_3})\\ +&=\mathcal{J} (\mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) +\end{align} +\begin{equation} + \mu_0 j^\theta = \mu_0 j \cdot \nabla \theta = - 2 \pi f^\prime / \mathcal{J}, \mu_0 j^\zeta = \mu_0 j \cdot \nabla \zeta = - \mu_0 p^\prime / \chi^\prime - 2 \pi f^\prime q / \mathcal{J} +\end{equation} +Now we can reconstruct the cross term in the energy principle: +\begin{align} +\vec{C} &\equiv \vec{Q} + (\vec{\xi} \cdot \hat{n} ) (\mu_0 \vec{j} \times \hat{n}) \\ +&= \mathcal{J} ( C^\psi \vec{v}_1 + C^\theta \vec{v}_2 + C^\zeta \vec{v}_3 )\\ +&=C_1 \nabla\psi + C_2 \nabla\theta + C_3 \nabla\zeta +\end{align} +\begin{equation} +\begin{aligned} +C^\psi &= \vec{C}\cdot\nabla\psi = Q^\psi +&\qquad +C_1 &= Q_1 + \frac{\mathcal{J}\xi^\psi}{|\nabla\psi|^2} +\bigl[ \mu_0 j^\theta (\nabla\psi\cdot\nabla\zeta) +- \mu_0 j^\zeta (\nabla\psi\cdot\nabla\theta) \bigr] \\ +C^\theta &= \vec{C}\cdot\nabla\theta = Q^\theta + \xi^\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) +&\qquad +C_2 &= Q_2 + \mathcal{J}\xi^\psi \mu_0 j^\zeta \\ +C^\zeta &= \vec{C}\cdot\nabla\zeta = Q^\zeta - \xi^\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) +&\qquad +C_3 &= Q_3 - \mathcal{J}\xi^\psi \mu_0 j^\theta +\end{aligned} +\end{equation} + +\subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta W$} +In this part, we will reconstruct the first term in $\delta W$ in the matrix form. +\begin{equation} +\boxed{\;\mathcal{J}\,(\vec v_i\cdot\vec v_j)=\frac{g_{ij}}{\mathcal{J}}\;} +\end{equation} + +\begin{equation} +\boxed{ +\left\langle F \right\rangle_{mm'} +\equiv \int_{0}^{1} d\theta\; +\mathcal{J}(\psi,\theta)\, +\exp\!\bigl(2\pi i(m'-m)\theta\bigr)\, +F(\psi,\theta) +} +\end{equation} + +\begin{equation} +\boxed{ +(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}} \right\rangle_{mm'} +} +\end{equation} +where $G_{ij}$ is the matrix representation of the metric tensor in the Fourier space. +First we focus on the first term in $\delta W$. If we change each terms in $\vec{C}$ as follows: +\begin{equation} +\begin{aligned} +&\mathbf X \equiv \nabla \xi_s \times \nabla \psi,\\ +&\mathbf Y \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi,\\ +&\mathbf Z \equiv -\mathbf B\, \xi_\psi',\\ +&\mathbf U \equiv -\left( +\chi'' \nabla \zeta \times \nabla \psi ++ +(q\chi')' +\nabla \psi \times \nabla \theta +\right)\xi_\psi,\\ +&\mathbf V \equiv +\frac{\xi_\psi}{|\nabla \psi|^2} +\, \mu_0 \mathbf j \times \nabla \psi . +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +2\mu_0\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [|\vec{C}|^2]\\ +&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q|^2+ +\left[(\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)\cdot\mathbf V^* + \mathbf V \cdot (\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)^* +\right]+|\mathbf V|^2\} +\end{aligned} +\end{equation} + +\paragraph{Correction of the $\mu_0\vec j\times\nabla\psi$ decomposition} + +Before continuing, one algebraic correction is needed. +From +\begin{equation} +\vec v_2 \times \nabla\psi += +\frac{1}{\mathcal J}(g_{23}\vec v_2-g_{22}\vec v_3), +\end{equation} +and +\begin{align} +\vec v_3 \times \nabla\psi +&= +\vec v_3\times \mathcal J(\vec v_2\times \vec v_3) \\ +&= +\mathcal J\big[\vec v_2(\vec v_3\cdot \vec v_3)-\vec v_3(\vec v_3\cdot \vec v_2)\big] \\ +&= +\mathcal J\left[ +\vec v_2\left(\frac{g_{33}}{\mathcal J^2}\right) +- +\vec v_3\left(\frac{g_{23}}{\mathcal J^2}\right) +\right] \\ +&= +\frac{1}{\mathcal J}(g_{33}\vec v_2-g_{23}\vec v_3), +\end{align} +we obtain +\begin{align} +\mu_0\vec j\times \nabla\psi +&= +\mathcal J\mu_0j^\theta(\vec v_2\times \nabla\psi) ++\mathcal J\mu_0j^\zeta(\vec v_3\times \nabla\psi) \\ +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})\vec v_2 +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})\vec v_3. +\end{align} + +Therefore +\begin{equation} +\boxed{ +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +\left[ +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})\vec v_2 +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})\vec v_3 +\right]. +} +\end{equation} + +For compactness, define +\begin{equation} +\mathcal A_2 \equiv \mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}, +\qquad +\mathcal A_3 \equiv \mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23}, +\end{equation} +so that +\begin{equation} +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +\left( +\mathcal A_2 \vec v_2-\mathcal A_3 \vec v_3 +\right). +\end{equation} + +We will also use the cofactor identities of the covariant metric: +\begin{equation} +\boxed{ +g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2, +} +\end{equation} +\begin{equation} +\boxed{ +g_{12}g_{23}-g_{31}g_{22} += +\mathcal J^2(\nabla\zeta\cdot\nabla\psi), +} +\end{equation} +\begin{equation} +\boxed{ +g_{12}g_{33}-g_{31}g_{23} += +-\mathcal J^2(\nabla\theta\cdot\nabla\psi). +} +\end{equation} + +\paragraph{Matrix representation1} + +Only the final block forms already derived in \cite{10.1063/1.4958328} are listed here: +\begin{equation} +|\mathbf X|^2 \leadsto \Xi_s^\dagger \mathbf A \Xi_s, +\qquad +\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23}\mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33}\mathbf M\right]. +\end{equation} +\begin{equation} +\mathbf X^* \cdot \mathbf Z + \mathbf Z^* \cdot \mathbf X +\leadsto +\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi^{\prime\dagger} \mathbf B^\dagger \Xi_s, +\qquad +\mathbf B=-2\pi i\chi' \left[n(\mathbf G_{22}+q\mathbf G_{23})+\mathbf M(\mathbf G_{23}+q\mathbf G_{33})\right]. +\end{equation} +\begin{equation} +\mathbf X^* \cdot \mathbf Y + \mathbf Y^* \cdot \mathbf X +\leadsto +\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s, +\qquad +\mathbf C_1=-(2\pi)^2\chi'(\mathbf M \mathbf G_{31}+n\mathbf G_{12})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf X^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf X +\leadsto +\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s, +\qquad +\mathbf C_2=-2\pi \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. +\end{equation} +\begin{equation} +|\mathbf Y|^2 \leadsto \Xi_\psi^\dagger \mathbf H_1 \Xi_\psi, +\qquad +\mathbf H_1=(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q. +\end{equation} +\begin{equation} +\mathbf Y^* \cdot \mathbf Z + \mathbf Z^* \cdot \mathbf Y +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_1^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_1=-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf Y^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf Y +\leadsto +\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi, +\qquad +\mathbf H_2=2\pi i\chi'\left[\chi''(\mathbf M \mathbf G_{12}-\mathbf G_{12}\mathbf M)+(q\chi')'(\mathbf M \mathbf G_{31}-\mathbf G_{31}\mathbf M)\right]. +\end{equation} +\begin{equation} +|\mathbf Z|^2 \leadsto \Xi_\psi^{\prime\dagger} \mathbf D \Xi_\psi^\prime, +\qquad +\mathbf D=\chi'^2\left[(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+q(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} +\begin{equation} +\mathbf Z^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf Z +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_2=\chi'\left[\chi''(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+(q\chi')'(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} + +Finally, +\begin{equation} +|\mathbf U|^2 \leadsto \Xi_\psi^\dagger \mathbf H_3 \Xi_\psi, +\qquad +\mathbf{H}_3 += +\chi'' +\left[ +\chi''\,\mathbf{G}_{22} ++ +(q\chi')'\,\mathbf{G}_{23} +\right] ++ +(q\chi')' +\left[ +\chi''\,\mathbf{G}_{23} ++ +(q\chi')'\,\mathbf{G}_{33} +\right] +\end{equation} + +\paragraph{Matrix representation2} +The term $\mathbf X^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf X$ can be written in matrix form as +\[ +\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s, +\] +where $\mathbf C_3$ is the corresponding cross-block matrix: + +\begin{align} +\nabla \xi_s +&=\sum_m +\left[ +u_m' \nabla\psi ++ +2\pi i\,m\,u_m \nabla\theta +- +2\pi i\,n\,u_m \nabla\zeta +\right]e^{2\pi i(m\theta-n\zeta)} +\end{align} +\begin{align} +\mathbf X +&= +\nabla\xi_s\times\nabla\psi \\ +&= +\sum_m +2\pi i\,u_m e^{2\pi i(m\theta-n\zeta)} +\left( +m\,\nabla\theta\times\nabla\psi +- +n\,\nabla\zeta\times\nabla\psi +\right)\\ +\mathbf X^* +&= +\sum_m +(-2\pi i)\,u_m^* e^{-2\pi i(m\theta-n\zeta)} +\left( +m\,\nabla\theta\times\nabla\psi +- +n\,\nabla\zeta\times\nabla\psi +\right) \\ +\mathbf V +&= +\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi += +\sum_{m'} +v_{m'} e^{2\pi i(m'\theta-n\zeta)} +\frac{1}{|\nabla\psi|^2} +\mu_0\mathbf j\times\nabla\psi\\ +\mathbf V^* +&= +\frac{\xi_\psi^*}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi += +\sum_{m'} +v_{m'}^* e^{-2\pi i(m'\theta-n\zeta)} +\frac{1}{|\nabla\psi|^2} +\mu_0\mathbf j\times\nabla\psi +\end{align} + +Therefore +\begin{align} +\mathbf X^*\cdot \mathbf V +&= +\sum_{m,m'} +(-2\pi i)\, +u_m^* v_{m'}\, +e^{2\pi i(m'-m)\theta} +\frac{\mu_0}{|\nabla\psi|^2} +\Big[ +m(\nabla\theta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +- +n(\nabla\zeta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +\Big] +\end{align} + +Now use +$(\mathbf a\times\mathbf c)\cdot(\mathbf b\times\mathbf c)=(\mathbf a\cdot\mathbf b)|\mathbf c|^2-(\mathbf a\cdot\mathbf c)(\mathbf b\cdot\mathbf c)$ +with $\mathbf c=\nabla\psi$, together with $\mathbf j\cdot\nabla\psi=0$. Then +\begin{align} +(\nabla\theta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +&=(\nabla\theta\cdot\mathbf j)|\nabla\psi|^2-(\nabla\theta\cdot\nabla\psi)(\mathbf j\cdot\nabla\psi) &=j^\theta |\nabla\psi|^2\\ +(\nabla\zeta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi)&=j^\zeta |\nabla\psi|^2 +\end{align} + +Hence +\begin{align} +\mathbf X^*\cdot \mathbf V +&= +\sum_{m,m'} +(-2\pi i)\, +u_m^* v_{m'}\, +e^{2\pi i(m'-m)\theta}\, +\mu_0\left(mj^\theta-nj^\zeta\right) +\end{align} + +Now substitute the equilibrium current components. In the normalization used here, +\begin{equation} +\mu_0 j^\theta = -\frac{2\pi f'}{\mathcal J}, +\qquad +\mu_0 j^\zeta = -\frac{\mu_0 p'}{\chi'}-\frac{2\pi f' q}{\mathcal J} +\end{equation} +\begin{align} +\mu_0(mj^\theta-nj^\zeta) +&= +-\frac{2\pi f'}{\mathcal J}m ++ +n\left( +\frac{\mu_0 p'}{\chi'} ++ +\frac{2\pi f'q}{\mathcal J} +\right) \\ +&= +-\frac{2\pi f'}{\mathcal J}(m-nq) ++ +n\frac{\mu_0 p'}{\chi'} +\end{align} +Therefore \begin{align} -\vec{C} &\equiv \vec{Q} + (\vec{\xi} \cdot \hat{n} ) (\mu_0 \vec{j} \times \hat{n}) \\ -&=C^\psi \vec{e}_1 + C^\theta \vec{e}_2 + C^\zeta \vec{e}_3 = \mathcal{J} ( C^\psi \vec{v}_1 + C^\theta \vec{v}_2 + C^\zeta \vec{v}_3 )\\ -&=C_1 \nabla\psi + C_2 \nabla\theta + C_3 \nabla\zeta +(\mathbf C_3)_{mm'} +&= +\int_0^1 d\theta\; +\mathcal J\,e^{2\pi i(m'-m)\theta} +(-2\pi i) +\left[ +-\frac{2\pi f'}{\mathcal J}(m-nq) ++ +n\frac{\mu_0 p'}{\chi'} +\right] \\ +&= +\int_0^1 d\theta\; +e^{2\pi i(m'-m)\theta} +\left[ +(2\pi i)(2\pi f')(m-nq) +- +(2\pi i)n\frac{\mu_0 p'}{\chi'}\mathcal J +\right] \end{align} +\begin{equation} +\boxed{ +\mathbf C_3 += +2\pi i +\left( +2\pi f' \mathbf Q +- +n\frac{\mu_0 p'}{\chi'}\mathbf J +\right) +} +\end{equation} + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +% Thus the missing $\mathbf X$--$\mathbf V$ contribution is exactly the matrix $\mathbf C_3$ appearing in the full coefficient matrix +% \begin{equation} +% \mathbf C=\mathbf C_1+\mathbf C_2+\mathbf C_3 . +% \end{equation} + +The $\mathbf Z$--$\mathbf V$ block has the form +\[ +\Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime. +\] +\begin{align} +\mathbf Z^*\cdot \mathbf V +&= +\sum_{m,m'} +v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +-\chi' +\frac{ +\mathcal A_2(g_{22}+qg_{23})-\mathcal A_3(g_{23}+qg_{33}) +}{ +\mathcal J^2|\nabla\psi|^2 +} +\right] +\end{align} \begin{align} -C^\psi &= \vec{C} \cdot \nabla\psi = Q^\psi \\ -C^\theta &= \vec{C} \cdot \nabla\theta = Q^\theta + \xi^\psi \frac{1}{|\nabla\psi|^2} \mu_0 j^\zeta \\ -C^\zeta &= \vec{C} \cdot \nabla\zeta = Q^\zeta - \xi^\psi \frac{1}{|\nabla\psi|^2} \mu_0 j^\theta +\mathcal A_2(g_{22}+qg_{23})-\mathcal A_3(g_{23}+qg_{33}) +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})(g_{22}+qg_{23}) +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})(g_{23}+qg_{33}) \\ +&= +(\mu_0j^\zeta-q\mu_0j^\theta)(g_{22}g_{33}-g_{23}^2) \\ +&= +(\mu_0j^\zeta-q\mu_0j^\theta)\mathcal J^2|\nabla\psi|^2 . \end{align} - \begin{align} -C_1 &= Q_1 + \frac{\mathcal{J}\xi^\psi}{|\nabla\psi|^2} \left[ \mu_0 j^\theta (\nabla\psi \cdot \nabla\zeta) - \mu_0 j^\zeta (\nabla\psi \cdot \nabla\theta) \right] \\ -C_2 &= Q_2 + \mathcal{J}\xi^\psi \mu_0 j^\zeta \\ -C_3 &= Q_3 - \mathcal{J}\xi^\psi \mu_0 j^\theta +\mathbf Z^*\cdot \mathbf V +&= +-\chi'(\mu_0j^\zeta-q\mu_0j^\theta) +\sum_{m,m'}v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} \\ +&= +\mu_0 p'\sum_{m,m'}v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta}. \end{align} +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf Z^*\cdot \mathbf V+\mathbf V^*\cdot\mathbf Z +\leadsto\; +\Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi ++ +\Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime, \\ +\qquad +\mathbf E_3 +&= +\mu_0p'\mathbf J . +\end{aligned} +} +\end{equation} + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +The term $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is also a pure-$\Xi_\psi$ contribution, so after Fourier projection it takes the form +\[ +\Xi_\psi^\dagger \mathbf H_{YV} \Xi_\psi. +\] \begin{align} -\delta W_{term1} &= \frac{1}{2} \int \bigl(\frac{|\vec{C}|^2}{\mu_0} \bigr) dx^3 \\ -&= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ \frac{|\vec{C}|^2}{\mu_0}\Bigr] +\mathbf Y +&= +(\nabla\theta\times\nabla\zeta)\,\mathcal J\,\mathbf B\cdot\nabla\xi_\psi= +2\pi i\,\chi' +\sum_m (m-nq)\,v_m\,e^{2\pi i(m\theta-n\zeta)}\,\vec v_1 \\ +\mathbf Y^*&= +(-2\pi i)\chi' +\sum_m (m-nq)\,v_m^*\,e^{-2\pi i(m\theta-n\zeta)}\,\vec v_1 \end{align} +Using the corrected form of $\mathbf V$, \begin{align} -g_{11} &\equiv \mathcal{J}^2\,|\nabla\theta\times\nabla\zeta|^2, \\ -g_{22} &\equiv \mathcal{J}^2\,|\nabla\zeta\times\nabla\psi|^2, \\ -g_{33} &\equiv \mathcal{J}^2\,|\nabla\psi\times\nabla\theta|^2, \\ -g_{12} &\equiv \mathcal{J}^2\,(\nabla\theta\times\nabla\zeta)\cdot(\nabla\zeta\times\nabla\psi), \\ -g_{23} &\equiv \mathcal{J}^2\,(\nabla\zeta\times\nabla\psi)\cdot(\nabla\psi\times\nabla\theta), \\ -g_{31} &\equiv \mathcal{J}^2\,(\nabla\psi\times\nabla\theta)\cdot(\nabla\theta\times\nabla\zeta), +\mathbf Y^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{|\nabla\psi|^2}\, +\vec v_1\cdot(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{\mathcal J^2|\nabla\psi|^2} +\big( +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +\big) \end{align} -so that -\begin{equation} -\boxed{\;\mathcal{J}\,(\vec v_i\cdot\vec v_j)=\frac{g_{ij}}{\mathcal{J}}\;} -\end{equation} +Now expand the bracket: +\begin{align} +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{12} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{31} \\ +&= +\mu_0j^\theta(g_{12}g_{23}-g_{31}g_{22}) ++ +\mu_0j^\zeta(g_{12}g_{33}-g_{31}g_{23}) \\ +&= +\mathcal J^2 +\left[ +\mu_0j^\theta(\nabla\zeta\cdot\nabla\psi) +- +\mu_0j^\zeta(\nabla\theta\cdot\nabla\psi) +\right] +\end{align} \begin{equation} \boxed{ -\left\langle F \right\rangle_{mm'} -\equiv \int_{0}^{1} d\theta\; -\mathcal{J}(\psi,\theta)\, -\exp\!\bigl(2\pi i(m'-m)\theta\bigr)\, -F(\psi,\theta) +\begin{aligned} +\mathbf Y^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'}e^{2\pi i(m'-m)\theta} \\ +&\qquad\times +\mu_0 +\frac{ +j^\theta(\nabla\zeta\cdot\nabla\psi) +- +j^\zeta(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. +\end{aligned} } \end{equation} +It is more useful to rewrite this in field form. Since \begin{equation} -\boxed{ -(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}} \right\rangle_{mm'} -} +\mathcal J\,\mathbf B\cdot\nabla += +\chi'\left(\partial_\theta+q\partial_\zeta\right), +\end{equation} +we may write +\begin{align} +\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) +&= +\chi' +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi^* +\right] +\xi_\psi\, +\mathcal F ++ +\chi'\, +\xi_\psi^* +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi +\right]\mathcal F, +\end{align} +where +\begin{equation} +\mathcal F +\equiv +\mathcal J +\frac{ +(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi) +- +(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. \end{equation} +Because the equilibrium is axisymmetric and periodic in $\theta,\zeta$, we may integrate by parts in $\theta$ and drop the boundary term: +\begin{align} +\int d\theta\,d\zeta\;\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) +&= +\chi'\int d\theta\,d\zeta\; +\partial_\theta(|\xi_\psi|^2)\,\mathcal F \\ +&= +-\chi' \int d\theta\,d\zeta\; +|\xi_\psi|^2\,\partial_\theta \mathcal F. +\end{align} + +Substituting $\mu_0 j^\theta$ and $\mu_0 j^\zeta$, \begin{align} -\delta W_{\mathrm{term1}} -&= \frac{1}{2} \int \bigl(\frac{|\vec{C}|^2}{\mu_0} \bigr) dx^3 \\ -&= \frac{1}{2\mu_0}\int d\psi\,d\theta\,d\zeta\; -\mathcal{J}\,|\vec C|^2 -= \frac{1}{2\mu_0}\int d\psi\,d\theta\,d\zeta\;\mathcal{J} \sum_{i,j=1}^{3} C^{i} C^{j*} g_{ij}\\ -&=\frac{1}{2\mu_0} \int d\psi d\theta\,d\zeta\ \left[ \mathcal{J} \sum_{i,j}\ \sum_{m,n}\ \sum_{m'n'}\ C_{m,n}^{i} C_{m',n'}^{j*} g_{ij} e^{2\pi i ((m-m')\theta -(n-n') \zeta)}\right] \\ +\mathcal F +&= +\mathcal J +\left[ +-\frac{2\pi f'}{\mathcal J} +\frac{\nabla\zeta\cdot\nabla\psi}{|\nabla\psi|^2} ++ +\left( +\frac{\mu_0p'}{\chi'} ++ +\frac{2\pi f'q}{\mathcal J} +\right) +\frac{\nabla\theta\cdot\nabla\psi}{|\nabla\psi|^2} +\right] \\ +&= +2\pi f' +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} ++ +\frac{\mu_0p'}{\chi'}\, +\mathcal J\, +\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}. \end{align} -Since $n$ is fixed in DCON so, +Therefore \begin{align} - \delta W_{n=fixed}&=\frac{1}{2\mu_0} \int d\psi\ \left[ \int d\theta \, \sum_{m, m'} e^{2\pi i(m-m')\theta} \mathcal{J}(\psi, \theta) \sum_{i,j} C_{m,n}^{i}(\psi, \theta) C_{m',n}^{j*}(\psi, \theta) g_{ij}(\psi, \theta) \right] \\ - &=\frac{1}{2\mu_0} \int d\psi \int_0^1 d\theta \; \mathcal{J}(\psi, \theta) \sum_{i,j} \left( \sum_m C_{m,n}^i e^{2\pi i m \theta} \right) \left( \sum_{m'} C_{m',n}^{j*} e^{-2\pi i m' \theta} \right) g_{ij}(\psi, \theta) +\partial_\theta \mathcal F +&= +2\pi f'\, +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right] ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. \end{align} -The second term is $\nabla \cdot \vec{\xi} = 0$, it vanishes: +Using the DCON expression for the magnetic shear, \begin{equation} -\boxed{ -\delta W_{\mathrm{term2}} -= \frac{1}{2} \int \gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 dx^3 = \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ \gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 \Bigr] = 0. +S += +\frac{(2\pi)^2}{\mathcal J} +\left[ +q' ++ +\partial_\theta +\left( +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right) +\right], +\end{equation} +we obtain +\begin{equation} +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 } +\right] += +\frac{\mathcal J}{(2\pi)^2}S-q'. \end{equation} -For the last term in $\delta W$, +Hence \begin{align} - \delta W_{\mathrm{term3}} &= \frac{1}{2} \int -K(\vec{\xi} \cdot \hat{n})^2 dx^3 \\ - &= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ -K(\vec{\xi} \cdot \hat{n})^2 \Bigr] \\ - &= \frac{1}{2} \int d\psi d\theta d\zeta \mathcal{J} \Bigl[ -K(\frac{\vec{\xi}^{\psi}}{|\nabla\psi|})^2 \Bigr] +\partial_\theta\mathcal F +&= +\frac{f'}{2\pi}\,\mathcal J\,S +- +2\pi f'q' ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. \end{align} -% --- Term 3 as quadratic form --- +At this point we use the standard coordinate identity \begin{equation} -\delta W_{\mathrm{term3}} -= -\frac{1}{2}\int d\psi\,d\theta\,d\zeta\; -\mathcal{J}\,\frac{K}{|\nabla\psi|^2}\; -\xi^\psi\,\xi^{\psi *}. +\boxed{ +\partial_\theta +\left( +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right) += +-\mathcal J', +} \end{equation} +where the prime on $\mathcal J$ denotes $\partial/\partial\psi$. -\begin{equation} -(K_{mm'}) \equiv \left\langle \frac{K}{|\nabla\psi|^2}\right\rangle_{mm'} -= \int_0^1 d\theta\;\mathcal{J}(\psi,\theta)\, -e^{2\pi i(m'-m)\theta}\, -\frac{K}{|\nabla\psi|^2}. -\end{equation} +Therefore +\begin{align} +\int d\theta\,d\zeta\;\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) +&= +-\int d\theta\,d\zeta\;|\xi_\psi|^2 +\left[ +\frac{f'}{2\pi}\chi'\mathcal J S +- +2\pi f'q'\chi' +- +\mu_0p'\mathcal J' +\right]. +\end{align} \begin{equation} \boxed{ -\delta W_{\mathrm{term3}} -= -\frac{1}{2}\int d\psi \sum_{m,m'} -v^*_{m,n}(\psi)\; -(K_{mm'}(\psi))\; -v_{m',n}(\psi). +\begin{aligned} +\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y +&\leadsto\; +\Xi_\psi^\dagger \mathbf H_{YV}\Xi_\psi, \\ +(\mathbf H_{YV})_{mm'} +&\equiv +\int_0^1 d\theta\; +e^{2\pi i(m'-m)\theta} +\left[ +-\frac{f'}{2\pi}\chi'\mathcal J S ++ +2\pi f'q'\chi' ++ +\mu_0p'\mathcal J' +\right]. +\end{aligned} } \end{equation} -\subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta W$} +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip +\paragraph{The term $\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$.} +Since both $\mathbf U$ and $\mathbf V$ are proportional to $\xi_\psi$, this contribution is a pure-$\Xi_\psi$ block and can be written as +\[ +\Xi_\psi^\dagger \mathbf H_{UV} \Xi_\psi. +\] + +Recall \begin{equation} -\begin{aligned} -2\delta W -&= \int \big[ \frac{|\vec{C}|^2}{\mu_0}- K(\vec{\xi} \cdot \hat{n})^2 +\gamma p |\vec{\nabla} \cdot \vec{\xi}|^2 \big] dx^3\\ -&= \int d\psi d\theta d\zeta \mathcal{J} \Big\{ [ \nabla \xi_s \times \nabla \psi -+ (\nabla \theta \times \nabla \zeta)\,\mathcal{J}\,\mathbf{B}\cdot\nabla \xi_\psi -- \mathbf{B}\,\xi_\psi' \\ -&- +\mathbf U += +-\left( +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\xi_\psi. +\end{equation} +Hence +\begin{align} +\mathbf U^*\cdot\mathbf V +&= +-\frac{|\xi_\psi|^2}{|\nabla\psi|^2} \left( - \chi'' \nabla \zeta \times \nabla \psi - + (q\chi')' \nabla \psi \times \nabla \theta -\right)\xi_\psi \\ -&+\frac{\xi_\psi}{|\nabla \psi|^2} [ -j^\theta -\big( -\nabla\psi(\nabla\zeta\cdot\nabla\psi) +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\cdot +\left( +\mathcal A_2\vec v_2-\mathcal A_3\vec v_3 +\right) \\ +&= +-\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^2} +\left[ +\chi''(\mathcal A_2 g_{22}-\mathcal A_3 g_{23}) ++ +(q\chi')'(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) +\right]. +\end{align} + +Now simplify each bracket: +\begin{align} +\mathcal A_2 g_{22}-\mathcal A_3 g_{23} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{22} - -\nabla\zeta|\nabla\psi|^2 -\big)+j^\zeta -\big( -\nabla\theta|\nabla\psi|^2 +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&= +\mu_0j^\zeta(g_{22}g_{33}-g_{23}^2) \\ +&= +\mu_0j^\zeta \mathcal J^2 |\nabla\psi|^2, +\end{align} +and +\begin{align} +\mathcal A_2 g_{23}-\mathcal A_3 g_{33} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{23} - -\nabla\psi(\nabla\theta\cdot\nabla\psi) -\big)]]^2 \\ -&-K(\vec{\xi} \cdot \hat{n})^2 +\gamma p |\vec{\nabla} \cdot \vec{\xi}|^2\} -\end{aligned} +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{33} \\ +&= +-\mu_0j^\theta(g_{22}g_{33}-g_{23}^2) \\ +&= +-\mu_0j^\theta \mathcal J^2 |\nabla\psi|^2. +\end{align} +Therefore +\begin{align} +\mathbf U^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\mu_0j^\zeta\chi'' +- +\mu_0j^\theta(q\chi')' +\right]. +\end{align} + +Substituting $j^\theta$ and $j^\zeta$, +\begin{align} +\mathbf U^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\left( +-\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J} +\right)\chi'' +- +\left( +-\frac{2\pi f'}{\mathcal J} +\right)(q\chi')' +\right] \\ +&= +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} ++ +\frac{2\pi f'}{\mathcal J} +\left( +q\chi''-(q\chi')' +\right) +\right]. +\end{align} +Using +\begin{equation} +(q\chi')'=q'\chi'+q\chi'', +\end{equation} +we obtain +\begin{equation} +\boxed{ +\mathbf U^*\cdot\mathbf V += +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} +- +\frac{2\pi f' q'\chi'}{\mathcal J} +\right]. +} \end{equation} -\subsubsection{Matrix representation of $\delta W_{\mathrm{term1}}$} -First we focus on the first term in $\delta W$. If we change each terms in $\vec{C}$ as follows: \begin{equation} +\boxed{ \begin{aligned} -&\mathbf X \equiv \nabla \xi_s \times \nabla \psi,\\ -&\mathbf Y \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi,\\ -&\mathbf Z \equiv -\mathbf B\, \xi_\psi',\\ -&\mathbf U \equiv -\left( -\chi'' \nabla \zeta \times \nabla \psi -+ -(q\chi')' -\nabla \psi \times \nabla \theta -\right)\xi_\psi,\\ -&\mathbf V \equiv -\frac{\xi_\psi}{|\nabla \psi|^2} -\, \mu_0 \mathbf j \times \nabla \psi . +\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U +\leadsto\; +\Xi_\psi^\dagger \mathbf H_{UV}\Xi_\psi, \\ +\qquad +\mathbf H_{UV} +&= +2\mu_0p'\frac{\chi''}{\chi'}\,\mathbf J +-4\pi f'q'\chi'\,\mathbf I. \end{aligned} +} \end{equation} -\begin{equation} -\begin{aligned} -2\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [\frac{|\vec{C}|^2}{\mu_0}]\\ -&= \int d\psi d\theta d\zeta \mathcal{J}|\mathbf C|^2\\ -&= \int d\psi d\theta d\zeta \mathcal{J} \{ |\mathbf Q|^2+ -2\,\Re\!\left[ -(\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)\cdot\mathbf V^* -\right]+|\mathbf V|^2\} -\end{aligned} -\end{equation} +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +\paragraph{The term $|\mathbf V|^2$.} + +This is again a pure-$\Xi_\psi$ contribution, so we write +\[ +\Xi_\psi^\dagger \mathbf H_V \Xi_\psi. +\] -The detailed derivation of the term already in \cite{10.1063/1.4958328} willbe skipped here. -The first term $|\mathbf X|^2$ can be written with matrix form as $\Xi_s^\dagger \mathbf A \Xi_s$ where $\Xi_s$ is a vector of the coefficients of the poloidal harmonics in $\xi_s$, and $\mathbf A$ is a matrix given by: +From \begin{equation} -\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23} \mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33} \mathbf M\right]. +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3), \end{equation} +we have +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^4} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3)\cdot +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +\left[ +\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} +\right]. +\end{align} + +Now expand the quadratic form: +\begin{align} +&\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} \\ +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})^2 g_{22} +-2(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}) +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&\qquad ++(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})^2 g_{33} \\ +&= +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} +Therefore +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right] \\ +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} + +This is the completely explicit covariant-form result. -The second term $\mathbf X \cdot \mathbf Z + \mathbf Z \cdot \mathbf X$ can be written in matrix form as $\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi'^\dagger \mathbf B^\dagger \Xi_s$, where $\mathbf B$ is a matrix given by: +It is also useful to rewrite it invariantly. Since \begin{equation} - \mathbf B = -2\pi i\chi' [n(\mathbf G_{22} + q\mathbf G_{23}) + \mathbf M(\mathbf G_{23} + q\mathbf G_{33})] +\mathbf j += +\sigma \mathbf B ++\frac{p'}{B^2}\mathbf B\times \nabla\psi, +\qquad +\sigma\equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, \end{equation} - -The term $\mathbf X \cdot \mathbf Y + \mathbf Y \cdot \mathbf X$ as $\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s$, where $\mathbf C_1$ is a matrix given by: +one finds \begin{equation} - \mathbf C_1 = -(2\pi)^2 \chi^\prime (\mathbf M \mathbf G_{31} + n \mathbf G_{12})\mathbf Q +\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} += +\sigma\,(\mu_0\mathbf j\cdot\mathbf B) ++ +\frac{(\mu_0p')^2}{B^2}|\nabla\psi|^2. \end{equation} - -The term $\mathbf X \cdot \mathbf U + \mathbf U \cdot \mathbf X$ as $\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s$, where $\mathbf C_2$ is a matrix given by: +Hence \begin{equation} - \mathbf C_2 = -2\pi [\chi'' (n \mathbf G_{22} + \mathbf M \mathbf G_{23}) + (q\chi')' (n \mathbf G_{23} + \mathbf M \mathbf G_{33})] +\boxed{ +|\mathbf V|^2 += +|\xi_\psi|^2 +\left[ +\frac{\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++ +\frac{(\mu_0p')^2}{B^2} +\right]. +} \end{equation} -The term $|\mathbf Y|^2$ as $\Xi_\psi^\dagger \mathbf H_1 \Xi_\psi$, where $\mathbf H_1$ is a matrix given by: \begin{equation} - \mathbf H_1 = (2\pi\chi^{\prime} )^2 \mathbf Q\mathbf G_{11} \mathbf Q +\boxed{ +\begin{aligned} +|\mathbf V|^2 +\leadsto\; +\Xi_\psi^\dagger \mathbf H_V \Xi_\psi, \\ +\qquad +(\mathbf H_V)_{mm'} +&\equiv +\left\langle +\frac{\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++ +\frac{(\mu_0p')^2}{B^2} +\right\rangle_{mm'}. +\end{aligned} +} \end{equation} -The term $\mathbf Y \cdot \mathbf Z + \mathbf Z \cdot \mathbf Y$ as $\Xi_\psi^{\prime\dagger }\mathbf E_1 \Xi_\psi + \Xi_\psi^{\dagger} \mathbf E_1^\dagger \Xi_\psi^\prime$, where $\mathbf E_1$ is a matrix given by: +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +\paragraph{Adding the Bernstein scalar term $-K|\xi_\psi|^2/|\nabla\psi|^2$.} + +From the earlier derivation in this note, \begin{equation} - \mathbf E_1 = -2\pi i \chi^{\prime 2} (\mathbf G_{12}+q \mathbf G_{31})\mathbf Q +K += +|\nabla\psi|^2\sigma S ++ +\sigma\,\mathbf j\cdot\mathbf B ++ +2p'\,\boldsymbol{\kappa}\cdot\nabla\psi. \end{equation} - -The term $\mathbf Y \cdot \mathbf U + \mathbf U \cdot \mathbf Y$ as $\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi$, where $\mathbf H_2$ is a matrix given by: +Therefore \begin{equation} - \mathbf H_2 = 2\pi i \chi' +-\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 += +-|\xi_\psi|^2 \left[ -\chi''\left(\mathbf M \mathbf G_{12} -\mathbf G_{12} \mathbf M\right) +\sigma S + -(q\chi')'\left(\mathbf M \mathbf G_{31} -\mathbf G_{31} \mathbf M\right) -\right] +\frac{\sigma\,\mathbf j\cdot\mathbf B}{|\nabla\psi|^2} ++ +\frac{2p'\,\boldsymbol{\kappa}\cdot\nabla\psi}{|\nabla\psi|^2} +\right]. \end{equation} -The term $|\mathbf Z|^2$ as $\Xi_\psi^{\prime \dagger} \mathbf D \Xi_\psi^\prime$, where $\mathbf D$ is a matrix given by: +Now combine the four remaining scalar contributions: \begin{equation} - \mathbf D = \chi'^2 -\left[ -(\mathbf{G}_{22} + q\,\mathbf{G}_{23}) +\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) + -q\,(\mathbf{G}_{23} + q\,\mathbf{G}_{33}) -\right] +\mathcal J(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) ++ +\mathcal J|\mathbf V|^2 +- +\mathcal J\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2. \end{equation} -The term $\mathbf Z \cdot \mathbf U + \mathbf U \cdot \mathbf Z$ as $\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime$, where $\mathbf E_2$ is a matrix given by: +At this stage the cancellations are as follows: + +\begin{enumerate} +\item +The term +\( +\displaystyle +\frac{\sigma\,\mathbf j\cdot\mathbf B}{|\nabla\psi|^2}|\xi_\psi|^2 +\) +coming from $|\mathbf V|^2$ cancels exactly the same term coming from $-K|\xi_\psi|^2/|\nabla\psi|^2$. + +\item +The oscillatory shear term from $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ combines with the $|\nabla\psi|^2\sigma S$ part of $K$ through \begin{equation} -\mathbf E_2 = \chi' +S += +\frac{(2\pi)^2}{\mathcal J} \left[ -\chi'' -\left( -\mathbf{G}_{22} -+ -q\,\mathbf{G}_{23} -\right) +q' + -(q\chi')' +\partial_\theta \left( -\mathbf{G}_{23} -+ -q\,\mathbf{G}_{33} +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} \right) -\right] +\right]. \end{equation} +The non-oscillatory remainder is the $q'$ contribution. + +\item +The pressure-curvature term from $-K|\xi_\psi|^2/|\nabla\psi|^2$ combines with the pressure part of $|\mathbf V|^2$ and the pressure part of $\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$ to give the remaining +\( +\mu_0p'(\mathcal J\chi''/\chi'+\mathcal J') +\) +piece. +\end{enumerate} -Finally, the term $|\mathbf U|^2$ as $\Xi_\psi^\dagger \mathbf H_3 \Xi_\psi$, where $\mathbf H_3$ is a matrix given by: +After these cancellations, the total scalar contribution reduces to \begin{equation} -\mathbf{H}_3 -= -\chi'' +\mathcal J \left[ -\chi''\,\mathbf{G}_{22} -+ -(q\chi')'\,\mathbf{G}_{23} -\right] +(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) + -(q\chi')' -\left[ -\chi''\,\mathbf{G}_{23} +(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) + -(q\chi')'\,\mathbf{G}_{33} +|\mathbf V|^2 +- +\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 \right] += +|\xi_\psi|^2 +\left[ +\mu_0p' +\left( +\mathcal J\frac{\chi''}{\chi'}+\mathcal J' +\right) +- +2\pi f'q'\chi' +\right]. \end{equation} -The new terms are the cross terms between $\mathbf V$ and the other four terms. The term $\mathbf X \cdot \mathbf V + \mathbf V \cdot \mathbf X$ can be written in matrix form as $\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s$, where $\mathbf C_3$ is a matrix given by: \begin{equation} -(\nabla \xi_s \times \nabla \psi) \cdot (\frac{\xi_\psi}{|\nabla \psi|^2} \mu_0 \mathbf j \times \nabla \psi) = (\nabla \xi_s \cdot \mu_0 \mathbf j) - \cancel{(\nabla \xi_s \cdot \nabla \psi)(\mu_0 \mathbf j \cdot \nabla \psi)} -\end{equation} - -\begin{align} - \mathbf{X}^* \cdot \mathbf{V}&= -\left[ --2\pi i\, \xi_s^* -\left( -m\, \nabla\theta \times \nabla\psi +\boxed{ +\begin{aligned} +(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) ++ +(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) ++ +|\mathbf V|^2 - -n\, \nabla\zeta \times \nabla\psi -\right) -\right] -\cdot -\left[ -\frac{\xi_\psi}{|\nabla\psi|^2} -\,\mu_0 +\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 +&\leadsto\; +\Xi_\psi^\dagger \mathbf H_{\mathrm{add}} \Xi_\psi, \\ +\qquad +\mathbf H_{\mathrm{add}} +&= +\mu_0p' \left( -\mathbf{j} \times \nabla\psi +\frac{\chi''}{\chi'}\mathbf J+\mathbf J' \right) -\right]\\ -&= -(-2\pi i)\,\mu_0\, -\xi_s^*\, -\left( -m\, j_\theta - -n\, j_\zeta -\right) -\xi_\psi\, -\end{align} -This term is same as the term $\xi_\psi \mathbf J \cdot \nabla \psi - \xi_s \mathbf J \cdot \nabla \psi$ in original form. -And the matrix form can be written as $\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s$, where $\mathbf C_3$ is a matrix given by: -\begin{equation} -\mathbf C_3 = 2\pi i (2\pi f^\prime \mathbf Q -n \frac{p^\prime}{\chi^\prime} \mathbf J) +2\pi f'q'\chi' \mathbf I. +\end{aligned} +} \end{equation} -The term $Z \cdot V + V \cdot Z$ can be written in matrix form as $\Xi_\psi^\dagger \mathbf E_3 \Xi_\psi^\prime + \Xi_\psi^{\dagger\prime} \mathbf E_3^\dagger \Xi_\psi + \Xi_\psi^\dagger \mathbf H_4 \Xi_\psi$, where $\mathbf E_3$ is a matrix given by: +\paragraph{Final missing pieces.} +Therefore the missing $V$-dependent matrices are \begin{equation} -\mathbf Y^* -= -(\nabla \theta \times \nabla \zeta)\, -(-2\pi i)\chi' -\sum_m -(m - nq)\, -v_m^*\, -e^{-2\pi i (m\theta - n\zeta)} . +\boxed{ +\begin{aligned} +\mathbf C_3 +&= +2\pi i\left( +2\pi f'\mathbf Q +-n\frac{\mu_0p'}{\chi'}\mathbf J +\right). +\end{aligned} +} \end{equation} - \begin{equation} -\mathbf V -= -\sum_{m'} -v_{m'}\, -e^{2\pi i (m'\theta - n\zeta)} -\, -\frac{\mu_0 \mathbf j \times \nabla \psi} -{|\nabla \psi|^2} . +\boxed{ +\mathbf E_3=\mu_0p'\mathbf J. +} \end{equation} - +and \begin{equation} +\boxed{ \begin{aligned} -\mathbf Y^* \cdot \mathbf V -&= -(-2\pi i)\chi' -\sum_{m,m'} -v_m^* -(m - nq) -v_{m'} -e^{2\pi i (m' - m)\theta} -\; -\frac{ -(\nabla \theta \times \nabla \zeta) -\cdot -(\mu_0 \mathbf j \times \nabla \psi) -}{ -|\nabla \psi|^2 -}\\ +\mathbf H_{\mathrm{add}} &= -(-2\pi i)\chi' -\sum_{m,m'} -v_m^* -(m - nq) -v_{m'} -e^{2\pi i (m' - m)\theta} -\; -\mu_0 \, -\frac{ -j^{\theta} \left( \nabla \zeta \cdot \nabla \psi \right) -- -j^{\zeta} \left( \nabla \theta \cdot \nabla \psi \right) -}{ -|\nabla \psi|^2 -}. +\mu_0p' +\left( +\frac{\chi''}{\chi'}\mathbf J+\mathbf J' +\right) \\ +&\qquad +-2\pi f'q'\chi' \mathbf I. \end{aligned} +} \end{equation} - -\section{Plasma Magnetic Potential on the Surface} - -The plasma magnetic potential $\Phi$ on the boundary surface is computed using covariant magnetic field components obtained from the perturbed equilibrium solution. The calculation involves four components that represent different aspects of the magnetic potential response. - -\subsection{Equilibrium Data} - -The equilibrium spline $s_q$ contains the following flux functions indexed by their position in the spline: -\begin{align} -s_q(\psi) &= \begin{cases} -s_q(\psi)_1 &: \psi_{\text{fac}} \; (\text{flux normalization}) \\ -s_q(\psi)_2 &: F(\psi) = R B_\phi \; (\text{poloidal current}) \\ -s_q(\psi)_3 &: \mu_0 p(\psi) \; (\text{pressure term}) \\ -s_q(\psi)_4 &: q(\psi) \; (\text{safety factor}) \\ -s_q(\psi)_5 &: \rho(\psi) \; (\text{auxiliary quantity}) -\end{cases} -\end{align} - -\subsection{Covariant Field Components Transformation} - -From the contravariant field components in mode space $(B_{w,p}^{(m)}, B_{m,t}^{(m)}, B_{m,z}^{(m)})$, the covariant components in physical space are obtained via inverse Fourier transform and metric tensor contraction: -\begin{align} -B_{v,\theta} &= \mathcal{F}^{-1}\{b_{v,t}^{(m)}\} \quad \text{(covariant theta component)}\\ -B_{v,\zeta} &= \mathcal{F}^{-1}\{b_{v,z}^{(m)}\} \quad \text{(covariant zeta component)}\\ -\Xi_\psi &= \mathcal{F}^{-1}\{x_{w,p}^{(m)}\} \quad \text{(poloidal flux perturbation)} -\end{align} - -where $\mathcal{F}^{-1}$ denotes the inverse discrete Fourier transform in the poloidal angle direction. - -\subsection{Magnetic Potential Components} - -The four components of the plasma magnetic potential on the surface are computed as follows: - +Thus the total matrices become \begin{equation} +\boxed{ \begin{aligned} -\Phi_3(\theta) &= -\frac{B_{v,\zeta}(\theta)}{2\pi i n} -\\ -\Phi_1(\theta) &= \Phi_3(\theta) - \frac{\frac{dF}{d\psi} \cdot \Xi_\psi(\theta)}{2\pi i n \cdot \mathcal{J}(\theta)} -\\ -\Phi_4(\theta) &= B_{v,\theta}(\theta) -\\ -\Phi_2(\theta) &= \Phi_4(\theta) - \left( -\frac{dF}{d\psi} q(\psi) + \mathcal{J}(\theta) \frac{d(\mu_0 p)}{d\psi} \chi_1^{-1} -\right) \frac{\Xi_\psi(\theta)}{\mathcal{J}(\theta)} +\mathbf C&=\mathbf C_1+\mathbf C_2+\mathbf C_3, \\ +\mathbf E&=\mathbf E_1+\mathbf E_2+\mathbf E_3, \\ +\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_{\mathrm{add}}. \end{aligned} +} \end{equation} - -Here: -\begin{itemize} -\item $\Phi_3$ represents the potential associated with toroidal field perturbation ($\zeta$ component) -\item $\Phi_1$ includes the coupling between flux perturbation and poloidal current derivative -\item $\Phi_4$ is the direct poloidal field response -\item $\Phi_2$ combines poloidal field response with pressure and current effects -\item $\mathcal{J}(\theta) = \partial(\mathbf{r}, \mathbf{z}, \phi)/\partial(\psi, \theta, \zeta)$ (Jacobian of coordinate transformation) -\item $\chi_1 = \psi_0 \cdot 2\pi$ (flux scaling factor) -\item $n$ is the toroidal mode number -\end{itemize} - -\subsection{Physical Interpretation} - -Each potential component couples different physical mechanisms: -\begin{enumerate} -\item \textbf{$\Phi_3$}: Direct response to toroidal field perturbation, normalized by mode number. - -\item \textbf{$\Phi_1$}: Additional correction accounting for the interaction between the poloidal flux perturbation $\Xi_\psi$ and the poloidal current profile $(dF/d\psi)$. The denominator $\mathcal{J}$ accounts for local metric effects. - -\item \textbf{$\Phi_4$}: The primary poloidal magnetic field response from the perturbed equilibrium. - -\item \textbf{$\Phi_2$}: Complex response combining: -\begin{itemize} -\item Gradient of poloidal current: $(dF/d\psi) q$ -\item Pressure gradient effect: $\mathcal{J} (d\mu_0 p/d\psi)\chi_1^{-1}$ -\item Both weighted by normalized flux perturbation: $\Xi_\psi/\mathcal{J}$ -\end{itemize} -\end{enumerate} - - +which is exactly the Glasser Appendix A6 result. \section{Variable Naming Convention and Mathematical Symbols} This section summarizes the implementation of C and Q vectors with proper tensor transformations between contravariant and covariant representations. @@ -1426,62 +2907,29 @@ \subsection{Fundamental Transformation Laws} \begin{aligned} \text{Contravariant} \quad &Q^i = \frac{b^i}{\mathcal{J}} \\ \text{Covariant} \quad &Q_i = g_{ij} Q^j = g_{ij} \frac{b^j}{\mathcal{J}} \\ -\text{Physics method} \quad &C_i = Q_i + (\vec{j} \times \vec{\xi})_i +\text{Physics method} \quad &C_i = Q_i + ((\mu_0 \mathbf{j} \times \hat{n})\cdot (\xi \cdot \hat{n}))_i \end{aligned} } \end{equation} -where $\mathcal{J}$ is the Jacobian, $g_{ij}$ is the metric tensor at MODE index 0, and $(\vec{j} \times \vec{\xi})_i$ represents current-displacement coupling. - +where $\mathcal{J}$ is the Jacobian, $g_{ij}$ is the metric tensor at MODE index 0. \subsection{Code Variable Mapping Table} -\subsubsection*{Q Vector - Contravariant (w-basis, $Q^i$)} -\footnotesize -\begin{table}[h!] -\centering -\begin{tabular}{|l|l|} -\hline -\textbf{Variable} & \textbf{Symbol/Definition} \\ -\hline -\texttt{qwp\_tmp, qwp\_fun, qwp\_mn} & $Q^\psi$ (contravariant component) \\ -\texttt{qwt\_tmp, qwt\_fun, qwt\_mn} & $Q^\theta$ (contravariant component) \\ -\texttt{qwz\_tmp, qwz\_fun, qwz\_mn} & $Q^\zeta$ (contravariant component) \\ -\hline -\end{tabular} -\end{table} - -\subsubsection*{Q Vector - Covariant (v-basis, $Q_i$)} -\footnotesize -\begin{table}[h!] -\centering -\begin{tabular}{|l|l|} -\hline -\textbf{Variable} & \textbf{Symbol/Definition} \\ -\hline -\texttt{qvp\_tmp, qvp\_fun, qvp\_mn} & $Q_\psi = g_{11}Q^\psi + g_{12}Q^\theta + g_{13}Q^\zeta$ \\ -\texttt{qvt\_tmp, qvt\_fun, qvt\_mn} & $Q_\theta = g_{12}Q^\psi + g_{22}Q^\theta + g_{23}Q^\zeta$ \\ -\texttt{qvz\_tmp, qvz\_fun, qvz\_mn} & $Q_\zeta = g_{13}Q^\psi + g_{23}Q^\theta + g_{33}Q^\zeta$ \\ -\hline -\end{tabular} -\end{table} - \subsubsection*{C Vector - Contravariant (w-basis, $C^i$)} -\footnotesize \begin{table}[h!] \centering \begin{tabular}{|l|l|} \hline \textbf{Variable} & \textbf{Symbol/Definition} \\ \hline -\texttt{cwp\_tmp, cwp\_fun, cwp\_mn} & $C^\psi$ (contravariant component) \\ -\texttt{cwt\_tmp, cwt\_fun, cwt\_mn} & $C^\theta$ (contravariant component) \\ -\texttt{cwz\_tmp, cwz\_fun, cwz\_mn} & $C^\zeta$ (contravariant component) \\ +\texttt{cwp\_tmp, cwp\_fun, cwp\_mn} & $\mathcal{J}C^\psi$ (contravariant component) \\ +\texttt{cwt\_tmp, cwt\_fun, cwt\_mn} & $\mathcal{J}C^\theta$ (contravariant component) \\ +\texttt{cwz\_tmp, cwz\_fun, cwz\_mn} & $\mathcal{J}C^\zeta$ (contravariant component) \\ \hline \end{tabular} \end{table} \subsubsection*{C Vector - Covariant (v-basis, $C_i$)} -\footnotesize \begin{table}[h!] \centering \begin{tabular}{|l|l|} @@ -1492,17 +2940,10 @@ \subsubsection*{C Vector - Covariant (v-basis, $C_i$)} \texttt{cvt\_tmp, cvt\_fun} & $C_\theta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ \texttt{cvz\_tmp, cvz\_fun} & $C_\zeta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ \hline -\multicolumn{2}{|l|}{\textit{Physics form with current-displacement coupling:}} \\ -\hline -\texttt{cvp\_fun\_p, cvp\_mn} & $C_\psi = C_\psi^{\text{metric}} + (\vec{j} \times \vec{\xi})_\psi$ \\ -\texttt{cvt\_fun\_p, cvt\_mn} & $C_\theta = C_\theta^{\text{metric}} + (\vec{j} \times \vec{\xi})_\theta$ \\ -\texttt{cvz\_fun\_p, cvz\_mn} & $C_\zeta = C_\zeta^{\text{metric}} + (\vec{j} \times \vec{\xi})_\zeta$ \\ -\hline \end{tabular} \end{table} \subsubsection*{Input Variables and Parameters} -\footnotesize \begin{table}[h!] \centering \begin{tabular}{|l|l|} @@ -1516,49 +2957,7 @@ \subsubsection*{Input Variables and Parameters} \end{tabular} \end{table} -\paragraph{Variable Storage Types:} -\begin{itemize} -\item \textbf{\texttt{tmp}}: Local 1D array at single flux surface ($\psi$ level), dimension $(0:m_\theta)$ -\item \textbf{\texttt{fun}}: Global 2D spatial array on $(\theta, \psi)$ grid, dimension $(0:m_\theta, 0:m_\psi)$ -\item \textbf{\texttt{mn}}: Global 2D MODE space array, dimension $(m_{pert}, 0:m_\psi)$ -\item \textbf{\texttt{p}}: Physics form (includes j$\times\xi$ coupling term) -\item \textbf{\texttt{w}}-basis: Contravariant (w-basis, using $\nabla$ forms) -\item \textbf{\texttt{v}}-basis: Covariant (v-basis, using cross products) -\end{itemize} - -\subsection{Computational Algorithm} - -The reconstruction algorithm follows these steps for each flux surface ($\psi$ level): - -\begin{enumerate} -\item \textbf{Extract contravariant Q:} -\begin{equation} -Q^i(\theta) = \frac{b^i(\theta)}{\mathcal{J}(\theta)} = \frac{\text{bwp\_fun}(\theta), \text{bwt\_fun}(\theta), \text{bwz\_fun}(\theta)}{\text{jacs}(\theta)} -\end{equation} - -\item \textbf{Apply metric contraction for covariant Q:} -\begin{equation} -Q_i(\theta) = g_{ij}(0) \cdot Q^j(\theta) \quad \text{where } i,j \in \{\psi, \theta, \zeta\} -\end{equation} - -\item \textbf{Compute physics-based C with $j \times \xi$ coupling:} -\begin{equation} -C_i(\theta) = Q_i(\theta) + (\vec{j} \times \vec{\xi})_i(\theta) -\end{equation} - -\item \textbf{Store spatial representations:} -\begin{equation} -\text{qwp\_fun}(\theta, \psi) = Q^\psi(\theta), \quad \text{qvp\_fun}(\theta, \psi) = Q_\psi(\theta), \quad \cdots -\end{equation} - -\item \textbf{Transform to MODE space via Fourier:} -\begin{equation} -\tilde{Q}^\psi_{n,m} = \text{ISCDFTF}(Q^\psi(\theta)) \quad \Rightarrow \quad \text{qwp\_mn}(m_{pert}, \psi) -\end{equation} - -\end{enumerate} - \bibliographystyle{plain} \bibliography{references} -\end{document} \ No newline at end of file +\end{document} From c90ef3aa6844c77cc134707db65880f1a462de26 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 18 May 2026 16:37:21 +0900 Subject: [PATCH 118/305] GPEC - WIP - Update recon coordinate derivation (cherry picked from commit b13d2e393909ab40a0c2322cac95f3fd3333c995) --- docs/tex/recon/main.tex | 1848 +++++++++++++++++---------------------- 1 file changed, 804 insertions(+), 1044 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 01f2dd29f..8e0b33309 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -26,12 +26,19 @@ \section{Definition of covariant and contravariant basis} The dcon context will be in black and \color{blue}the reference\cite{chance1992mhd} would be in blue. -\color{black}\subsection{Covariant basis} +\color{black}\subsection{Jacobian-scaled basis $(\vec{v}_i = \vec{e}_i/\mathcal{J})$} + +The $\vec{v}_i$ are \emph{not} the true covariant tangent basis $\vec{e}_i \equiv \partial\vec{r}/\partial q^i$, but rather its Jacobian-scaled counterpart: +\begin{equation} + \vec{v}_i = \frac{\vec{e}_i}{\mathcal{J}}, \qquad + \mathcal{J}^{-1} \equiv \vec{\nabla}\psi\cdot(\vec{\nabla}\theta\times\vec{\nabla}\zeta). +\end{equation} +Explicitly, \begin{equation} \begin{aligned} \vec{v}_1 &= \vec{\nabla}\theta \times \vec{\nabla}\zeta, & \vec{v}_2 &= \vec{\nabla}\zeta \times \vec{\nabla}\psi, & - \vec{v}_3 &= \vec{\nabla}\psi \times \vec{\nabla}\theta, + \vec{v}_3 &= \vec{\nabla}\psi \times \vec{\nabla}\theta. \end{aligned} \end{equation} @@ -47,7 +54,7 @@ \section{Definition of covariant and contravariant basis} \begin{aligned} v_{11} &= \vec{v}_1 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \psi}, \\[6pt] v_{12} &= \vec{v}_1 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \psi}, \\[6pt] -v_{13} &= \vec{v}_1 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \psi}, , \\[6pt] +v_{13} &= \vec{v}_1 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \psi}, \\[6pt] \end{aligned} \end{equation} @@ -68,7 +75,7 @@ \section{Definition of covariant and contravariant basis} \end{equation} -\subsubsection{Contravariant basis} +\subsection{Contravariant basis} \begin{equation} \begin{aligned} \vec{w}_1 &= \vec{\nabla}\psi, & @@ -169,9 +176,8 @@ \section{Coordinate system} \mathbf{B}_p \cdot d\mathbf{S} \end{equation} -In DCON, $\psi_{dcon}$, $\theta_{dcon}$, $\zeta_{dcon}$ are all normalized in [0,1]. $\chi_{dcon}$ representing the poloidal flux function and $\alpha_{dcon}$ for SFL angle. Also to scale the actual poloidal flux $\chi\prime_{dcon}$ is used.($\chi\prime_{dcon} = 2\pi(ssibry-ssimag)$) - -\begin{align} +\color{red}In DCON, $\psi_{dcon}$, $\theta_{dcon}$, $\zeta_{dcon}$ are all normalized in [0,1]. $\chi_{dcon}$ representing the poloidal flux function and $\alpha_{dcon}$ for SFL angle. Also to scale the actual poloidal flux $\chi\prime_{dcon}$ is used.($\chi\prime_{dcon} = 2\pi(ssibry-ssimag)$) +\color{black}\begin{align} \mathbf{B} &= \nabla \chi_{dcon} \times \nabla \alpha_{dcon}\\ % &= \chi\prime_{dcon} \nabla \psi_{dcon} \times \nabla \alpha_{dcon}\\ @@ -189,7 +195,7 @@ \section{Coordinate system} \alpha_{dcon} = \color{blue} {- \alpha / 2 \pi} \color{black}= q(\psi)\theta - \zeta \end{equation} \begin{equation} -g(\psi) \leftrightarrow (toroidal field functions) \leftrightarrow f_{dcon} +g(\psi) \leftrightarrow \text{(toroidal field functions)} \leftrightarrow f_{dcon} \end{equation} In the following, the blue-side variable $\psi$ is Chance's flux @@ -233,7 +239,7 @@ \subsection{Derivation of Eq.~(29) in GPEC note} \begin{equation} \begin{aligned} - B + \mathbf{B} &= \chi\prime \nabla \psi \times (q\nabla \theta - \nabla \zeta) = \chi\prime q \vec{v}_3 + \chi\prime \vec{v}_2 \\ &= \mathcal{J}(B^\psi \vec{v}_1 + B^\theta \vec{v}_2 + B^\zeta \vec{v}_3) \\ &= (B_\psi \vec{w}_1 + B_\theta \vec{w}_2 + B_\zeta \vec{w}_3) @@ -252,7 +258,7 @@ \subsection{Derivation of Eq.~(29) in GPEC note} B^\theta &\equiv \vec{B}\cdot \vec{w}_2 = \frac{\chi'}{\mathcal{J}}, \qquad B^\zeta \equiv \vec{B}\cdot \vec{w}_3 = \frac{q\,\chi'}{\mathcal{J}}, \\[4pt] -\vec{B} \times \vec{w}_\psi +\vec{B} \times \vec{w}_1 &= \frac{1}{\mathcal{J}}\Big( B^\zeta\,\vec{v}_2 - B^\theta\,\vec{v}_3 \Big) = \frac{1}{\mathcal{J}}\left(\frac{q\,\chi'}{\mathcal{J}}\,\vec{v}_2 - \frac{\chi'}{\mathcal{J}}\,\vec{v}_3\right) \\ &= \frac{\chi'}{\mathcal{J}^{2}}\Big( q\,\vec{v}_2 - \vec{v}_3 \Big), @@ -267,12 +273,23 @@ \subsection{Derivation of Eq.~(29) in GPEC note} \subsection{Metric Tensor and Index Operations} -Given the definitions of the basis vectors where $\vec{v}_i = \vec{\nabla}\theta \times \vec{\nabla}\zeta$ (requiring the Jacobian $\mathcal{J}$ for physical dimensions) and $\vec{w}_i = \vec{\nabla}\psi$ (representing the dual basis directly), the metric tensors for lowering and raising indices are derived as follows. +Throughout this section we adopt the following conventions for expanding $\vec{B}$: +\begin{equation} + \vec{B} = \mathcal{J}\,B^j\,\vec{v}_j = B^j\,\vec{e}_j + \qquad\text{(contravariant expansion)}, +\end{equation} +\begin{equation} + \vec{B} = B_j\,\vec{w}_j = B_j\,\vec{e}^j + \qquad\text{(covariant expansion)}, +\end{equation} +where the contravariant components satisfy $B^i = \vec{B}\cdot\vec{w}_i$ and the covariant components satisfy $B_i = \vec{B}\cdot\vec{e}_i$. +These are consistent because $\vec{B}\cdot\vec{w}_i = \mathcal{J}B^j\vec{v}_j\cdot\vec{w}_i = \mathcal{J}B^j\,\delta_{ji}/\mathcal{J} = B^i$. \subsubsection{Lowering Operator ($B^j \to B_i$)} -To transform contravariant components $B^j$ into covariant components $B_i$, we utilize the relation between the physical basis $\vec{e}_i$ and the defined basis $\vec{v}_i$, where $\vec{e}_i = \mathcal{J}\vec{v}_i$. +To transform contravariant components $B^j$ into covariant components $B_i$, we use the true covariant tangent basis $\vec{e}_i = \mathcal{J}\vec{v}_i$. +Since $B_i \equiv \vec{B}\cdot\vec{e}_i$ and $\vec{B} = \mathcal{J}B^j\vec{v}_j$: -Using the definition $\vec{B} = \mathcal{J} B^j \vec{v}_j$ and taking the dot product with $\vec{e}_i$: +Using the definition $\vec{B} = \mathcal{J} B^j \vec{v}_j$ and taking the dot product with $\vec{e}_i = \mathcal{J}\vec{v}_i$: \begin{equation} \begin{aligned} B_i &= \vec{B} \cdot \vec{e}_i \\ @@ -286,7 +303,7 @@ \subsubsection{Lowering Operator ($B^j \to B_i$)} \end{equation} \subsubsection{Raising Operator ($B_j \to B^i$)} -To transform covariant components $B_j$ into contravariant components $B^i$, we use the gradient basis $\vec{w}_i$, which corresponds to the dual basis $\vec{e}^i$ (i.e., $\vec{e}^i = \vec{w}_i$). +To transform covariant components $B_j$ into contravariant components $B^i$, we use the gradient basis $\vec{w}_i$, which corresponds to the dual basis $\vec{e}^i$ (i.e., $\vec{e}^1 = \vec{w}_1 = \vec{\nabla}\psi$, etc.). Using the definition $\vec{B} = B_j \vec{w}_j$ and taking the dot product with $\vec{w}_i$: \begin{equation} @@ -342,8 +359,7 @@ \subsection{Derivation of Eq. (35) in gpec note} \Bigr] \] -\section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} - +\section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} \begin{align} \delta W_p &= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q}|^2 - \mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* @@ -369,220 +385,182 @@ \section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} \end{align} The derivation of (35) could be found in \cite{RevModPhys.76.1071} and the original derivation of (36) is in \cite{Bernstein1983}. -The K term in (37) is defined in \cite{chance1992mhd}. +The K term in (37) is defined in \cite{chance1992mhd}. +\subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/DCON units)} +We follow the derivation of \cite{Bernstein1983}, while keeping the present SI/DCON conventions. +Throughout this subsection, we employ the following definitions: \begin{equation} -\sigma = \frac{\mathbf{j} \cdot \mathbf{B}}{B^2}, \hat{\mathbf n}=\frac{\nabla\psi}{|\nabla\psi|}, \mathbf Q \equiv \nabla\times(\boldsymbol{\xi}\times\mathbf B) (\delta\mathbf B = \mathbf Q) +\sigma \equiv \frac{\mathbf{j}\cdot\mathbf{B}}{B^2}, \qquad +\mathbf Q \equiv \nabla\times(\boldsymbol{\xi}\times\mathbf B), \qquad +\mu_0\mathbf j = \nabla\times\mathbf B, \end{equation} +\begin{equation} +\hat{\mathbf n}\equiv \frac{\nabla\psi}{|\nabla\psi|}, \qquad +\xi_n \equiv \hat{\mathbf n}\cdot\boldsymbol{\xi}, \qquad +\delta\mathbf B = \mathbf Q, +\end{equation} +with \(p=p(\psi)\). -\subsection{Transformation of $\delta W_F$ in \cite{Bernstein1983}(Modified units)} -In this section, we will see how (34) changes to (36). It follows the derivation in \cite{Bernstein1983}, but since the unit and normalization are different, we need to adjust the expressions accordingly. -If we define $\xi$ as follows: +We begin from \begin{equation} -\boldsymbol{\xi}=a\,\nabla p + b\,\nabla\times\mathbf{B} + c\,\mathbf{B} +2\mu_0 \delta W_F += +\int d^3r\left\{ +|\mathbf Q|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 ++\mu_0(\nabla\cdot\boldsymbol{\xi})^*\,\boldsymbol{\xi}\cdot\nabla p ++(\boldsymbol{\xi}^*\times\mathbf Q)\cdot(\nabla\times\mathbf B) +\right\}. \end{equation} -then, the following relations hold: + +Since $\mathbf{B}$, $\nabla p$, and $\nabla\times\mathbf{B}$ are mutually orthogonal (satisfying $\mathbf{B}\cdot\nabla p = (\nabla\times\mathbf{B})\cdot\nabla p = \mathbf{B}\cdot(\nabla\times\mathbf{B})=0$), they form an orthogonal basis. We thus introduce \begin{equation} -\boldsymbol{\xi}\times\mathbf{B} -= -a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p +\boldsymbol{\xi} += +a\,\nabla p + b\,\nabla\times\mathbf B + c\,\mathbf B. \end{equation} +Since \(p=p(\psi)\), \begin{equation} -\boldsymbol{\xi}\times(\nabla\times\mathbf{B}) -= -a\,(\nabla\times\mathbf{B})\times\nabla p - \mu_0 c\,\nabla p +\nabla p = p'(\psi)\nabla\psi = p'(\psi)|\nabla\psi|\,\hat{\mathbf n}, +\qquad +\xi_n = \hat{\mathbf n}\cdot\boldsymbol{\xi} = a\,p'(\psi)|\nabla\psi|, \end{equation} +so that \begin{equation} -\mathbf{Q} = \nabla\times(\boldsymbol{\xi}\times\mathbf{B}) = \nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) +a\,\nabla p = \xi_n\,\hat{\mathbf n}. \end{equation} - -Gauss's theorem, and the boundary condition $\mathbf{n}\cdot\mathbf{B}=0$, -one finds +Also, because \(\mathbf B\cdot\nabla p=0\) and \((\nabla\times\mathbf B)\cdot\nabla p=0\), \begin{align} -2\mu_0 W_F -& = \int d^3r\Bigl\{ \mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ |\mathbf{Q}|^2 \nonumber + (\mu_0 \nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \nonumber -+ \boldsymbol{\xi} \times \mathbf{Q} \cdot \nabla \times \mathbf{B} -\Bigr\}\\ -& = \int d^3r\Bigl\{\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2 -\nonumber\\ -&+ \mu_0 a(\nabla p)^2\nabla\cdot\bigl(a\nabla p + b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) -\nonumber\\ -&+ \bigl[a(\nabla\times\mathbf{B})\times\nabla p + \mu_0 c\,\nabla p\bigr]\cdot -\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) -\Bigr\}. +\boldsymbol{\xi}\times\mathbf B +&= +-a\,\mathbf B\times\nabla p ++b\,(\nabla\times\mathbf B)\times\mathbf B += +-a\,\mathbf B\times\nabla p+\mu_0 b\,\nabla p, \\ +\boldsymbol{\xi}\times(\nabla\times\mathbf B) +&= +-a\,(\nabla\times\mathbf B)\times\nabla p ++c\,\mathbf B\times(\nabla\times\mathbf B) += +-a\,(\nabla\times\mathbf B)\times\nabla p-\mu_0 c\,\nabla p. \end{align} -Since, +Hence \begin{equation} -a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)+ \bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] -=\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr] +\mathbf Q += +\nabla\times\bigl(-a\,\mathbf B\times\nabla p+\mu_0 b\,\nabla p\bigr). \end{equation} -So, + +Substituting $\boldsymbol{\xi} = a\,\nabla p + b\,\nabla\times\mathbf B + c\,\mathbf B$: \begin{align} -a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr) -&= -\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\cdot\nabla\!\Bigl[a(\nabla p)^2\Bigr] -+\nabla\cdot\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf{B}+c\mathbf{B}\bigr)\Bigr]\\ -&= -b\,\nabla\cdot\Bigl[a(\nabla p)^2\Bigr](\nabla\times\mathbf{B})-c\,\nabla\cdot\Bigl[a(\nabla p)^2\Bigr]\mathbf{B}\nonumber\\ -&+\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr]+\nabla\cdot\big[ab(\nabla p)^2(\nabla\times B)\big]\\ -&=-b\,(\nabla\times\mathbf B)\cdot\nabla\big[a(\nabla p)^2\big] -+\nabla\cdot\big[ab(\nabla p)^2(\nabla\times B)\big]\nonumber\\ -&- c\,\mathbf B\cdot\nabla\big[a(\nabla p)^2\big]+\nabla\cdot\big[ac(\nabla p)^2\mathbf B\big]\\ -&=\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr] - + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p)+ c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) +\nabla\cdot\boldsymbol{\xi} &= a\nabla^2 p, \quad \boldsymbol{\xi}\cdot\nabla p = a(\nabla p)^2. \end{align} - -Using (47) to eliminate the divergence term involving $b,c$ gives +By the product rule: +\begin{equation} +a(\nabla p)^2\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B) = \nabla\cdot\Bigl[a(\nabla p)^2(b\nabla\times\mathbf B+c\mathbf B)\Bigr] - (b\nabla\times\mathbf B+c\mathbf B)\cdot\nabla\bigl[a(\nabla p)^2\bigr]. +\end{equation} +On flux surfaces the divergence vanishes, giving \begin{align} -2\mu_0 W_F -= \int d^3r\Bigl\{ -&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2+\mu_0a(\nabla p)^2\nabla\cdot(a\nabla p)\nonumber\\ -& +\cancel{\nabla\cdot\Bigl[ac(\nabla p)^2\mathbf{B}\Bigr]}+ \mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p)+ \mu_0 c(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p)\nonumber\\ -&+ \bigl[a(\nabla\times\mathbf{B})\times\nabla p + \mu_0 c\,\nabla p\bigr]\cdot -\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\Bigr\}\\ -= \int d^3r\Bigl\{ -&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr)\bigr]^2+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) -+\cancel{\mu_0 c(\nabla p)\cdot \mu_0 (\nabla \times (b\nabla p))}\nonumber\\ -&+ 2\mu_0 a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(b\nabla p) -- a(\nabla \times \mathbf{B}) \times \nabla p \cdot \nabla\times(a\mathbf{B}\times\nabla p)\Bigr\}\\ -= \int d^3r\Bigl\{ -&\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+ \bigl[\nabla\times\bigl(-a\,\mathbf{B}\times\nabla p + \mu_0 b\,\nabla p\bigr) - + a(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 +a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr) +&= +-\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr)\cdot\nabla\!\bigl[a(\nabla p)^2\bigr] \nonumber\\ -&+ a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) -- a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 -+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) -\Bigr\} +&\quad ++\nabla\cdot\!\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr)\Bigr]. \end{align} - -Then, $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, +For a volume bounded by flux surfaces, \(\hat{\mathbf n}\cdot\mathbf B=0\) and \(\hat{\mathbf n}\cdot(\nabla\times\mathbf B)=0\), so the surface integral of the divergence term vanishes. Using also \(\nabla\cdot\mathbf B=0\) and \(\nabla\cdot(\nabla\times\mathbf B)=0\), one obtains \begin{align} -&-a^2\bigl[(\nabla\times\mathbf{B})\times\nabla p\bigr]^2 -+ a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(a\mathbf{B}\times\nabla p) -+ \mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) -\nonumber\\ -&= -a^2(\nabla\times\mathbf{B})^2(\nabla p)^2 -+ a^2(\nabla\times\mathbf{B})\times(\nabla p)\cdot\nabla\times(\mathbf{B}\times\nabla p) -\nonumber\\ -&\quad + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot(\nabla a)\times(\mathbf{B}\times\nabla p) -+ \mu_0 a^2(\nabla p)^2\nabla^2 p -+ \mu_0 a(\nabla p)^2(\nabla a)\cdot\nabla p -\nonumber\\ -&= -a^2(\nabla\times\mathbf{B})^2(\nabla p)^2 -+ a^2(\nabla\times\mathbf{B})\times(\nabla p)\cdot\Bigl[(\nabla p)\cdot\nabla\mathbf{B} -- \mathbf{B}\cdot\nabla\nabla p + \mathbf{B}\nabla^2 p\Bigr] +2\mu_0 \delta W_F += +\int d^3r\Bigl\{ +&\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 ++\Bigl|\mathbf Q+a(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 \nonumber\\ -&\quad + a(\nabla\times\mathbf{B})\times(\nabla p)\cdot\Bigl[\mathbf{B}(\nabla a)\cdot\nabla p -- (\nabla p)\,\mathbf{B}\cdot\nabla a\Bigr] -+ \mu_0 a^2(\nabla p)^2\nabla^2 p -+ \mu_0 a(\nabla p)^2(\nabla a)\cdot\nabla p +&\quad +-a^2\Bigl|(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 ++a(\nabla\times\mathbf B)\times\nabla p\cdot\nabla\times(a\,\mathbf B\times\nabla p) \nonumber\\ -&= a^2(\nabla p)^2\Bigl[-(\nabla\times\mathbf{B})^2 -+ (\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{n}\cdot\nabla\mathbf{B} -- \mathbf{B}\cdot\nabla\mathbf{n})\Bigr]. +&\quad ++\mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) +\Bigr\}. \end{align} -Since $\nabla\cdot\mathbf{B}=0$, it follows that -\begin{equation} -0=\nabla(\mathbf{n}\cdot\mathbf{B}) -=\mathbf{n}\cdot\nabla\mathbf{B}+\mathbf{B}\cdot\nabla\mathbf{n} -+\mathbf{n}\times(\nabla\times\mathbf{B})+\mathbf{B}\times(\nabla\times\mathbf{n}), -\end{equation} -whence +Using $\mu_0\nabla p = (\nabla\times\mathbf B)\times\mathbf B$ and $\hat{\mathbf n}\cdot\mathbf B=0$, the three terms (L485--489) combine to: \begin{align} -&-(\nabla\times\mathbf{B})^2 -+(\nabla\times\mathbf{B})\times\mathbf{n}\cdot\bigl[\mathbf{n}\cdot\nabla\mathbf{B} --\mathbf{B}\cdot\nabla\mathbf{n}\bigr] -\nonumber\\ -&=-(\nabla\times\mathbf{B})^2 -+(\nabla\times\mathbf{B})\times\mathbf{n}\cdot\bigl[-2\mathbf{B}\cdot\nabla\mathbf{n} --\mathbf{n}\times(\nabla\times\mathbf{B})-\mathbf{B}\times(\nabla\times\mathbf{n})\bigr] -\nonumber\\ -&=-(\nabla\times\mathbf{B})^2+\bigl[\mathbf{n}\times(\nabla\times\mathbf{B})\bigr]^2 --2(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}) -\nonumber\\ -&\quad -(\nabla\times\mathbf{B})\cdot\mathbf{B}\,\mathbf{n}\cdot\nabla\times\mathbf{n} --(\nabla\times\mathbf{B})\cdot(\nabla\times\mathbf{n})\,\mathbf{n}\cdot\mathbf{B} -\nonumber\\ -&=-2(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}). +&-a^2\Bigl|(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 +a(\nabla\times\mathbf B)\times\nabla p\cdot\nabla\times(a\,\mathbf B\times\nabla p) +\mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) \nonumber\\ +&= a^2(\nabla p)^2 \Bigl[-|\nabla\times\mathbf B|^2 +(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot\Bigl((\hat{\mathbf n}\cdot\nabla)\mathbf B-(\mathbf B\cdot\nabla)\hat{\mathbf n}\Bigr)\Bigr]. \end{align} -Since $\mu_0\nabla p=(\nabla\times\mathbf{B})\times\mathbf{B}$, it follows that -$\mathbf{n}\cdot\nabla\times\mathbf{B}=0$, whence +Now, since \(\hat{\mathbf n}\cdot\mathbf B=0\), \begin{equation} -\bigl[\mathbf{n}\times(\nabla\times\mathbf{B})\bigr]^2 -=\mathbf{n}^2(\nabla\times\mathbf{B})^2-(\mathbf{n}\cdot\nabla\times\mathbf{B})^2 -=(\nabla\times\mathbf{B})^2. +0=\nabla(\hat{\mathbf n}\cdot\mathbf B) += +(\hat{\mathbf n}\cdot\nabla)\mathbf B ++(\mathbf B\cdot\nabla)\hat{\mathbf n} ++\hat{\mathbf n}\times(\nabla\times\mathbf B) ++\mathbf B\times(\nabla\times\hat{\mathbf n}). \end{equation} - -Also, since $\mathbf{n}$ is a unit normal to the surfaces $p=\text{const}$, for any area -contained in such a surface, Stokes' theorem gives +Moreover, \begin{equation} -\int d^2r\,\mathbf{n}\cdot\nabla\times\mathbf{n} -=\oint d\mathbf{r}\cdot\mathbf{n}=0, +(\nabla\times\mathbf B)\cdot\hat{\mathbf n}=0, +\end{equation} +and, since \(\hat{\mathbf n}=\lambda\nabla\psi\) with \(\lambda=|\nabla\psi|^{-1}\), +\begin{equation} +\nabla\times\hat{\mathbf n} += +\nabla\lambda\times\nabla\psi, +\qquad +\hat{\mathbf n}\cdot(\nabla\times\hat{\mathbf n})=0. +\end{equation} +Therefore, +\begin{equation} +-|\nabla\times\mathbf B|^2 ++(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot +\Bigl((\hat{\mathbf n}\cdot\nabla)\mathbf B-(\mathbf B\cdot\nabla)\hat{\mathbf n}\Bigr) += +-2\,(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}). \end{equation} -and since the surface is arbitrary, $\mathbf{n}\cdot\nabla\times\mathbf{n}=0$. -Thus one can write, +Using \(a\nabla p=\xi_n\hat{\mathbf n}\) and \(\mu_0\mathbf j=\nabla\times\mathbf B\), we finally obtain \begin{equation} -2\mu_0 W_F -=\int_I d^3r\Bigl\{ -\mu_0\gamma p(\nabla\cdot\boldsymbol{\xi})^2 -+\bigl[\mathbf{Q}+(\nabla\times\mathbf{B})\times\mathbf{n}\,(\mathbf{n}\cdot\boldsymbol{\xi})\bigr]^2 --2(\mathbf{n}\cdot\boldsymbol{\xi})^2\,(\nabla\times\mathbf{B})\times\mathbf{n}\cdot(\mathbf{B}\cdot\nabla\mathbf{n}) -\Bigr\}. +2\mu_0 \delta W_F += +\int d^3r\left\{ +\Bigl|\mathbf Q+\xi_n(\mu_0\mathbf j\times\hat{\mathbf n})\Bigr|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 +-2\,|\xi_n|^2\,(\mu_0\mathbf j\times\hat{\mathbf n})\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}) +\right\}. +\end{equation} +Defining +\begin{equation} +K \equiv 2\,(\mathbf j\times\hat{\mathbf n})\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}), +\end{equation} +the Bernstein form becomes +\begin{equation} +\delta W_F += +\frac{1}{2\mu_0}\int d^3r\left\{ +\Bigl|\mathbf Q+\xi_n(\mu_0\mathbf j\times\hat{\mathbf n})\Bigr|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 +-\mu_0 K\,|\xi_n|^2 +\right\}. \end{equation} -% If desired, replace curl B by current density j: -% \nabla\times\mathbf{B} = \mu_0\mathbf{j}, so the equilibrium is \nabla p=\mathbf{j}\times\mathbf{B}. -% \begin{align} -% K &= 2(\mathbf{j} \times \hat{n}) \cdot (\mathbf{B} \cdot \nabla) \hat{n} \\ -% &=\frac{2}{|\nabla \psi|^2}(\mathbf{j} \times \nabla \psi) \cdot (\mathbf{B} \cdot \nabla) \nabla \psi \\ -% &=-(\frac{\mathbf{j} \cdot \mathbf{B}}{B^2})\mathbf{B} \times \nabla \psi\, \cdot\, (\nabla \times \frac{\mathbf{B} \times \nabla \psi}{|\nabla \psi|^2}) + \mathbf{j} \cdot \mathbf{B} + 2p'|\nabla \psi|^2 \kappa \cdot \nabla \psi \\ -% &=\frac{2}{|\nabla \chi|^2}(\mathbf{j} \times \nabla \psi) \cdot (\mathbf{B} \cdot \nabla) \nabla \chi -% \end{align} \subsection{Derivation of $K$ with explicit $\mu_0$} -% We define -% \begin{equation} -% \hat{\mathbf n} \equiv \frac{\nabla\psi}{|\nabla\psi|}, -% \qquad -% \mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}, -% \qquad -% \nabla p = p'(\psi)\nabla\psi, -% \qquad -% \sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, -% \end{equation} -% \begin{equation} -% \mathbf b \equiv \frac{\mathbf B}{B}, -% \qquad -% \boldsymbol{\kappa}\equiv (\mathbf b\cdot\nabla)\mathbf b, -% \qquad -% \kappa_\psi \equiv \boldsymbol{\kappa}\cdot\nabla\psi. -% \end{equation} - We start from \begin{equation} \color{blue} K \equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n}. \end{equation} - Since -\color{blue}\begin{equation} -(\mathbf B\cdot\nabla)\hat{\mathbf n} -= -(\mathbf B\cdot\nabla)\left(\frac{\nabla\psi}{|\nabla\psi|}\right) -= -\frac{(\mathbf B\cdot\nabla)\nabla\psi}{|\nabla\psi|} -+ -\nabla\psi\,(\mathbf B\cdot\nabla)\left(\frac{1}{|\nabla\psi|}\right), -\end{equation} -\color{blue}\begin{equation} +\color{blue}\begin{align} +(\mathbf B\cdot\nabla)\hat{\mathbf n} &= (\mathbf B\cdot\nabla)\left(\frac{\nabla\psi}{|\nabla\psi|}\right), (\mathbf j\times \hat{\mathbf n})\cdot \nabla\psi = 0, -\end{equation} +\end{align} \color{black} -we obtain +we have \begin{equation} \color{blue}K \equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n} @@ -591,64 +569,17 @@ \subsection{Derivation of $K$ with explicit $\mu_0$} (\mathbf j\times\nabla\psi)\cdot(\mathbf B\cdot\nabla)\nabla\psi. \end{equation} -Now use $\mathbf B\cdot\nabla\psi=0$. Then -\begin{equation} -0=\nabla(\mathbf B\cdot\nabla\psi) -= -(\mathbf B\cdot\nabla)\nabla\psi -+ -(\nabla\psi\cdot\nabla)\mathbf B -+ -\nabla\psi\times(\nabla\times\mathbf B), -\end{equation} +Now use $\mathbf B\cdot\nabla\psi=0$: \begin{equation} -(\mathbf B\cdot\nabla)\nabla\psi -= --(\nabla\psi\cdot\nabla)\mathbf B --\mu_0\,\nabla\psi\times\mathbf j. +0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B), \end{equation} - -Substituting this into $K$ gives +thus \color{blue}\begin{align} -K -&= --\frac{2}{|\nabla\psi|^2} -(\mathbf j\times\nabla\psi)\cdot -\Big[(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j\Big] -\\ -&= --\frac{2}{|\nabla\psi|^2} -(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B -+\frac{2\mu_0}{|\nabla\psi|^2}\,|\mathbf j\times\nabla\psi|^2. +K &= 2\mu_0 j^2 - \frac{2}{|\nabla\psi|^2}(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{align} - \color{black} -Because -\begin{equation} -0=\mathbf j\cdot\nabla p = p'\,\mathbf j\cdot\nabla\psi, \mathbf j\cdot\nabla\psi=0 -\end{equation} -\begin{equation} -|\mathbf j\times\nabla\psi|^2 = j^2 |\nabla\psi|^2. -\end{equation} -Thus, -\begin{equation} -\color{blue}K -= -2\mu_0 j^2 --\frac{2}{|\nabla\psi|^2} -(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. -\end{equation} -Now decompose the current as -\begin{equation} -\mathbf j = \mathbf j_{\parallel}+\mathbf j_{\perp}, -\qquad -\mathbf j_{\parallel}=\sigma \mathbf B, -\qquad -\mathbf j_{\perp}=\frac{\mathbf B\times\nabla p}{B^2} -=\frac{p'}{B^2}\mathbf B\times\nabla\psi. -\end{equation} -Then +Decompose $\mathbf j = \sigma\mathbf B + \frac{p'}{B^2}\mathbf B\times\nabla\psi \equiv \mathbf j_{\parallel}+\mathbf j_{\perp}$: \begin{equation} \color{blue}K = K_{\parallel}+K_{\perp}, \end{equation} @@ -668,304 +599,154 @@ \subsection{Derivation of $K$ with explicit $\mu_0$} \color{black} \paragraph{1. Perpendicular part $K_{\perp}$} -Using -\begin{equation} -\mathbf j_{\perp}=\frac{p'}{B^2}\mathbf B\times\nabla\psi, -\end{equation} -we obtain -\begin{align} -K_{\perp} -&= -\frac{2\mu_0 p'^2 |\nabla\psi|^2}{B^2} --\frac{2p'}{B^2|\nabla\psi|^2} -\Big[(\mathbf B\times\nabla\psi)\times\nabla\psi\Big] -\cdot(\nabla\psi\cdot\nabla)\mathbf B. -\end{align} - -Now, +With $\mathbf j_{\perp}=\frac{p'}{B^2}\mathbf B\times\nabla\psi$: \begin{equation} -(\mathbf B\times\nabla\psi)\times\nabla\psi -= -\nabla\psi(\mathbf B\cdot\nabla\psi)-\mathbf B|\nabla\psi|^2 -= --\mathbf B|\nabla\psi|^2, +j_{\perp}^{2} = \frac{p'^{\,2}}{B^{2}}|\nabla\psi|^{2}, \end{equation} -since $\mathbf B\cdot\nabla\psi=0$. Hence -\begin{align} -K_{\perp} -&= -\frac{2\mu_0 p'^2 |\nabla\psi|^2}{B^2} -+\frac{2p'}{B^2}\,\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B -\\ -&= -\frac{2p'}{B^2} -\left[ -\mu_0\,\nabla p\cdot\nabla\psi -+\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B -\right] -\\ -&= -\frac{2p'}{B^2} -\left[ -\mu_0\,(\mathbf j\times\mathbf B)\cdot\nabla\psi -+\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B -\right] -\\ -&= -\frac{2p'}{B^2}\, -\mathbf B\cdot -\left[ -(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j -\right]. -\end{align} - -Using +where $\mathbf B\cdot\nabla\psi=0$. Also, \begin{equation} -(\nabla\psi\cdot\nabla)\mathbf B+\mu_0\,\nabla\psi\times\mathbf j -= --(\mathbf B\cdot\nabla)\nabla\psi, +\mathbf j_{\perp}\times\nabla\psi = -\frac{p'}{B^{2}}\mathbf B|\nabla\psi|^{2}. \end{equation} -we get +Hence \begin{equation} -K_{\perp} -= --\frac{2p'}{B^2}\,\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi. +K_{\perp} = \frac{2\mu_{0}p'^{\,2}}{B^{2}}|\nabla\psi|^{2} + \frac{2p'}{B^{2}}\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{equation} -Also, +To simplify, use $0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B)$. +Dotting with $\mathbf B$ and using $\nabla\times\mathbf B=\mu_{0}\mathbf j$: \begin{equation} -0=(\mathbf B\cdot\nabla)(\mathbf B\cdot\nabla\psi) -= -((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi -+ -\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi, +\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B = -\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi - \mu_{0}\mathbf B\cdot(\nabla\psi\times\mathbf j). \end{equation} -so that +From $(\mathbf B\cdot\nabla)(\mathbf B\cdot\nabla\psi)=0$: \begin{equation} -\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi -= --((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. +\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi = -((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. \end{equation} -Therefore, -\begin{equation} -K_{\perp} -= -\frac{2p'}{B^2} -\big((\mathbf B\cdot\nabla)\mathbf B\big)\cdot\nabla\psi. -\end{equation} - -Finally, with $\mathbf B=B\mathbf b$, +From equilibrium $\mathbf j\times\mathbf B=p'\nabla\psi$: \begin{equation} -(\mathbf B\cdot\nabla)\mathbf B -= -B^2(\mathbf b\cdot\nabla)\mathbf b -+ -B(\mathbf b\cdot\nabla B)\mathbf b, +\mathbf B\cdot(\nabla\psi\times\mathbf j) = p'|\nabla\psi|^{2}. \end{equation} -and since $\mathbf b\cdot\nabla\psi=0$, +Therefore \begin{equation} -\big((\mathbf B\cdot\nabla)\mathbf B\big)\cdot\nabla\psi -= -B^2\,\boldsymbol{\kappa}\cdot\nabla\psi -= -B^2\kappa_\psi. +\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B = ((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi - \mu_{0}p'|\nabla\psi|^{2}. \end{equation} -Thus, + +Substituting: +\begin{align} +K_{\perp} &= \frac{2\mu_{0}p'^{\,2}}{B^{2}}|\nabla\psi|^{2} + \frac{2p'}{B^{2}}\left[((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi - \mu_{0}p'|\nabla\psi|^{2}\right] \\ +&= \frac{2p'}{B^{2}}((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. +\end{align} + +With $\mathbf B=B\mathbf b$ and $\mathbf b\cdot\nabla\psi=0$: \begin{equation} -\boxed{ -K_{\perp}=2p'\kappa_\psi -}. +((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi = B^2\,\boldsymbol{\kappa}\cdot\nabla\psi = B^2\kappa_\psi. \end{equation} +Thus, $\boxed{K_{\perp}=2p'\kappa_\psi}$. \paragraph{2. Parallel part $K_{\parallel}$} -Since $\mathbf j_{\parallel}=\sigma\mathbf B$, +With $\mathbf j_{\parallel}=\sigma\mathbf B$: \begin{equation} -K_{\parallel} -= -2\mu_0\sigma^2B^2 --\frac{2\sigma}{|\nabla\psi|^2} -(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. +K_{\parallel} = 2\mu_0\sigma^2B^2 - \frac{2\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. \end{equation} -Now use the vector identity +Define $\mathbf A \equiv \mathbf B\times\nabla\psi$. Using \begin{equation} -\nabla\times(\mathbf B\times\nabla\psi) -= -\mathbf B\nabla^2\psi -+ -(\nabla\psi\cdot\nabla)\mathbf B -- -(\mathbf B\cdot\nabla)\nabla\psi. +\nabla\times(\mathbf B\times\nabla\psi) = \mathbf B\nabla^2\psi + (\nabla\psi\cdot\nabla)\mathbf B - (\mathbf B\cdot\nabla)\nabla\psi, \end{equation} -Substituting +and from $0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B)$ with $\nabla\times\mathbf B=\mu_0\mathbf j$: \begin{equation} -(\mathbf B\cdot\nabla)\nabla\psi -= --(\nabla\psi\cdot\nabla)\mathbf B --\mu_0\,\nabla\psi\times\mathbf j, +(\mathbf B\cdot\nabla)\nabla\psi = -(\nabla\psi\cdot\nabla)\mathbf B - \mu_0\nabla\psi\times\mathbf j. \end{equation} -we obtain +Therefore \begin{equation} -\nabla\times(\mathbf B\times\nabla\psi) -= -\mathbf B\nabla^2\psi -+ -2(\nabla\psi\cdot\nabla)\mathbf B -+ -\mu_0\,\nabla\psi\times\mathbf j. +\nabla\times(\mathbf B\times\nabla\psi) = \mathbf B\nabla^2\psi + 2(\nabla\psi\cdot\nabla)\mathbf B + \mu_0\nabla\psi\times\mathbf j. \end{equation} -Taking the dot product with $\mathbf B\times\nabla\psi$ gives +Dotting with $\mathbf A=\mathbf B\times\nabla\psi$ (note: $\mathbf A\cdot\mathbf B=0$): \begin{equation} -(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) -= -2(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B -+ -\mu_0\,(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j), +\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) = 2\mathbf A\cdot(\nabla\psi\cdot\nabla)\mathbf B + \mu_0\mathbf A\cdot(\nabla\psi\times\mathbf j), \end{equation} -because +giving \begin{equation} -(\mathbf B\times\nabla\psi)\cdot\mathbf B = 0. +\mathbf A\cdot(\nabla\psi\cdot\nabla)\mathbf B = \frac{1}{2}\left[\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) - \mu_0\mathbf A\cdot(\nabla\psi\times\mathbf j)\right]. \end{equation} -Hence -\begin{align} -K_{\parallel} -&= -2\mu_0\sigma^2B^2 -- -\frac{\sigma}{|\nabla\psi|^2} -(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) -+ -\frac{\mu_0\sigma}{|\nabla\psi|^2} -(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j). -\end{align} -Now, +Substituting into $K_{\parallel}$: \begin{equation} -(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j) -= -(\mathbf B\cdot\nabla\psi)(\nabla\psi\cdot\mathbf j) -- -(\mathbf B\cdot\mathbf j)|\nabla\psi|^2 -= --\sigma B^2|\nabla\psi|^2, -\end{equation} -so -\begin{equation} -K_{\parallel} -= -\mu_0\sigma^2B^2 -- -\frac{\sigma}{|\nabla\psi|^2} -(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi). +K_{\parallel} = 2\mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) + \frac{\mu_0\sigma}{|\nabla\psi|^2}\mathbf A\cdot(\nabla\psi\times\mathbf j). \end{equation} -Define the shear quantity -\begin{equation} -S -\equiv -- -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} -\cdot -\nabla\times\left( -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} -\right). -\end{equation} -Also, +For the last term, using the vector identity $(\mathbf a\times\mathbf b)\cdot(\mathbf c\times\mathbf d) = (\mathbf a\cdot\mathbf c)(\mathbf b\cdot\mathbf d) - (\mathbf a\cdot\mathbf d)(\mathbf b\cdot\mathbf c)$: \begin{equation} -(\mathbf B\times\nabla\psi)\cdot -\left[ -\nabla\left(\frac{1}{|\nabla\psi|^2}\right) -\times -(\mathbf B\times\nabla\psi) -\right] -=0, +(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j) = (\mathbf B\cdot\nabla\psi)(\nabla\psi\cdot\mathbf j) - (\mathbf B\cdot\mathbf j)|\nabla\psi|^2 = -\sigma B^2|\nabla\psi|^2, \end{equation} -and therefore +where $\mathbf B\cdot\nabla\psi=0$ and $\mathbf B\cdot\mathbf j=\sigma B^2$ were used. Therefore +\begin{align} +K_{\parallel} &= 2\mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) - \mu_0\sigma^2B^2 \\ +&= \mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi). +\end{align} + +Define \begin{equation} -\frac{1}{|\nabla\psi|^2} -(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) -= -(\mathbf B\times\nabla\psi)\cdot -\nabla\times\left( -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} -\right). +S \equiv -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\cdot\nabla\times\left(\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\right), \end{equation} -Thus, +which is equivalent to $S = -\frac{1}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi)$ by the product rule for curl. +Thus \begin{equation} -\boxed{ -K_{\parallel} -= -|\nabla\psi|^2 \sigma S + \mu_0\sigma^2B^2 -}. +\boxed{K_{\parallel} = \mu_0\sigma^2B^2 + \sigma|\nabla\psi|^2 S}. \end{equation} \paragraph{3. Final expression} -Combining the two pieces, \begin{equation} \boxed{ -K -= -K_{\parallel}+K_{\perp} -= -|\nabla\psi|^2 \sigma S -+\mu_0\sigma^2B^2 -+2p'\kappa_\psi -}. +K = |\nabla\psi|^2 \sigma S + \mu_0\sigma^2B^2 + 2p'\kappa_\psi +} \end{equation} -Therefore it is convenient to define +\subsection{Expression of $K$ in DCON coordinates} + +Following the Bernstein formulation from §3.1-3.2, the energy principle yields +\color{blue} \begin{equation} -K_{\rm shear}\equiv |\nabla\psi|^2 \sigma S, -\qquad -K_\sigma\equiv \mu_0\sigma^2B^2, -\qquad -K_{p\kappa}\equiv 2p'\kappa_\psi, +K = |\nabla\psi|^2 \sigma S + \mu_0 \sigma^2 B^2 + 2p'\kappa_\psi, \quad +\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, \quad +\mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}. \end{equation} -so that +\color{black} +In §3.2, this expression was obtained in a general flux-coordinate form. We now rewrite +it so that each ingredient can be evaluated directly from the DCON equilibrium data. From +this point on, $\psi$ denotes the DCON normalized flux coordinate, $\psi\in[0,1]$, and +$p' \equiv dp/d\psi$ is the derivative with respect to this normalized flux label. + +In DCON coordinates $(\psi,\theta,\zeta)$, \begin{equation} -K = K_{\rm shear}+K_\sigma+K_{p\kappa}. \quad (\hat{\mathbf n} \equiv \frac{\nabla\psi}{|\nabla\psi|}, -\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, -\mathbf b \equiv \frac{\mathbf B}{B}, -\boldsymbol{\kappa}\equiv (\mathbf b\cdot\nabla)\mathbf b, -\kappa_\psi \equiv \boldsymbol{\kappa}\cdot\nabla\psi.) +\mathbf B = \chi'(\psi)\,\nabla\psi\times\nabla\alpha, \quad +\alpha \equiv q(\psi)\theta-\zeta, \quad +\chi' \equiv \frac{d\chi}{d\psi}, \quad +\mathcal J^{-1} \equiv \nabla\psi\cdot(\nabla\theta\times\nabla\zeta). \end{equation} -\subsection{Bernstein $K$ in DCON coordinates} - -We use the convention +We use the contravariant metric elements \begin{equation} -K -= -|\nabla\psi|^2 \sigma S -+\mu_0 \sigma^2 B^2 -+2p'\kappa_\psi, -\qquad -\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, +g^{ij}\equiv \nabla q^i\cdot\nabla q^j, \qquad -\mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}. +(q^1,q^2,q^3) \equiv (\psi,\theta,\zeta), \end{equation} - -In DCON straight-field-line coordinates $(\psi,\theta,\zeta)$, we write +with in particular \begin{equation} -\mathbf B -= -\nabla\chi \times \nabla\alpha -= -\chi'(\psi)\,\nabla\psi\times\nabla\alpha, -\qquad -\alpha \equiv q(\psi)\theta-\zeta, -\qquad -\chi' \equiv \frac{d\chi}{d\psi}. +g^{\psi\psi}\equiv \nabla\psi\cdot\nabla\psi, \quad +g^{\psi\theta}\equiv \nabla\psi\cdot\nabla\theta, \quad +g^{\psi\zeta}\equiv \nabla\psi\cdot\nabla\zeta. \end{equation} - -The Jacobian is defined by +The combinations involving $\alpha=q\theta-\zeta$ are \begin{equation} -\mathcal J^{-1} -\equiv -\nabla\psi\cdot(\nabla\theta\times\nabla\zeta), +g^{\psi\alpha}\equiv \nabla\psi\cdot\nabla\alpha += q\,g^{\psi\theta}-g^{\psi\zeta}, +\end{equation} +\begin{equation} +g^{\alpha\alpha}\equiv \nabla\alpha\cdot\nabla\alpha += q^2 g^{\theta\theta}-2q\,g^{\theta\zeta}+g^{\zeta\zeta}. \end{equation} -so that the parallel gradient operator becomes +Accordingly, the parallel derivative operator becomes \begin{equation} \mathbf B\cdot\nabla = @@ -974,32 +755,40 @@ \subsection{Bernstein $K$ in DCON coordinates} \frac{\partial}{\partial\theta} + q\frac{\partial}{\partial\zeta} -\right). +\right), \end{equation} - -We also define the contravariant metric elements +and the field strength is written as \begin{equation} -g^{\psi\psi}\equiv \nabla\psi\cdot\nabla\psi, -\qquad -g^{\psi\theta}\equiv \nabla\psi\cdot\nabla\theta, -\qquad -g^{\psi\zeta}\equiv \nabla\psi\cdot\nabla\zeta. +\boxed{ +B^2 += +\chi'^2|\nabla\psi\times\nabla\alpha|^2 += +\chi'^2\left(g^{\psi\psi}g^{\alpha\alpha}-(g^{\psi\alpha})^2\right). +} \end{equation} +These relations provide the DCON input form used below for the explicit evaluation of +$S$, $\xi_\psi$, $\kappa_\psi$, and finally $K$. -\subsection{Expression of $S$ in DCON coordinates} +\subsubsection{Shear parameter $S$ in DCON} -We define +\color{blue} +For any flux label $\lambda$, the shear parameter associated with that label is \begin{equation} -S +S_{\lambda} \equiv -\, -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} +\frac{\mathbf B\times\nabla\lambda}{|\nabla\lambda|^2} \cdot \nabla\times \left( -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2} -\right). +\frac{\mathbf B\times\nabla\lambda}{|\nabla\lambda|^2} +\right), \end{equation} +and the shear contribution to $K$ is $|\nabla\lambda|^2\sigma S_{\lambda}$. + +\color{black} +In this subsection we take $\lambda=\psi$, where $\psi$ is the DCON normalized flux label. Introduce \begin{equation} @@ -1054,7 +843,7 @@ \subsection{Expression of $S$ in DCON coordinates} Now, because $\nabla\chi' \parallel \nabla\psi$, the first term vanishes when contracted with $\boldsymbol{\Lambda}_\psi$, so \begin{align} -S +S_{\psi} &= -\boldsymbol{\Lambda}_\psi\cdot(\nabla\times\boldsymbol{\Lambda}_\psi) \\ @@ -1075,7 +864,7 @@ \subsection{Expression of $S$ in DCON coordinates} Therefore, \begin{equation} \boxed{ -S +S_{\psi} = \chi'\,\mathbf B\cdot\nabla \left( @@ -1083,6 +872,8 @@ \subsection{Expression of $S$ in DCON coordinates} \right). } \end{equation} +Here $S_{\psi}$ denotes the shear parameter associated with the DCON normalized flux +label $\psi$, not the $\chi/(2\pi)$-based shear used in the GPEC note. Next, \begin{align} @@ -1120,7 +911,7 @@ \subsection{Expression of $S$ in DCON coordinates} \end{equation} we obtain \begin{align} -S +S_{\psi} &= \frac{\chi'^2}{\mathcal J} \frac{\partial}{\partial\theta} @@ -1143,10 +934,58 @@ \subsection{Expression of $S$ in DCON coordinates} \right]. } \end{align} +Therefore, in DCON coordinates the shear contribution to $K$ is written as +\begin{equation} +\boxed{ +K_{\mathrm{shear}}^{DCON} += +g^{\psi\psi}\sigma S_{\psi} += +g^{\psi\psi}\sigma +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +} +\end{equation} -\subsection{Normal displacement $\xi_\psi$ in DCON coordinates} +\noindent\textbf{Relation to the GPEC note.} +If one instead uses the flux label +\begin{equation} +\lambda = \frac{\chi}{2\pi} = \frac{\chi'}{2\pi}\psi, +\end{equation} +then +\begin{equation} +S_{\lambda} += +\left(\frac{2\pi}{\chi'}\right)^2 S_{\psi} += +\frac{(2\pi)^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +\end{equation} +This is the shear normalization used in the GPEC note. The first term of $K$ is the +same only when the corresponding gradient factor is used consistently: +\begin{equation} +\left|\nabla\left(\frac{\chi}{2\pi}\right)\right|^2\sigma S_{\lambda} += +g^{\psi\psi}\sigma S_{\psi}. +\end{equation} + +\subsubsection{Normal displacement $\xi_\psi$} -We define the normal displacement variable by +In DCON coordinates, the flux-normal displacement variable is \begin{equation} \xi_\psi \equiv @@ -1168,6 +1007,8 @@ \subsection{Normal displacement $\xi_\psi$ in DCON coordinates} \frac{\xi_\psi}{|\nabla\psi|}. } \end{equation} +The variable $\xi_\psi$ itself depends on the chosen flux label, but the ratio +$\xi_\psi/|\nabla\psi|$ does not. Using the DCON Fourier convention, \begin{equation} @@ -1189,29 +1030,53 @@ \subsection{Normal displacement $\xi_\psi$ in DCON coordinates} } \end{equation} -For the quadratic form in the energy principle, it is more precise to write +For the quadratic form in the energy principle, the magnitude squared is: \begin{equation} \boxed{ (\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n}) (\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n})^* = -\frac{\xi_\psi\xi_\psi^*}{|\nabla\psi|^2}. +\frac{\xi_\psi(\xi_\psi)^*}{g^{\psi\psi}}, +\qquad +g^{\psi\psi}=|\nabla\psi|^2. +} +\end{equation} +Since $\xi_\psi$ is represented as a complex Fourier expansion, the product with its +complex conjugate gives the magnitude squared. Therefore the Bernstein scalar term is +evaluated in DCON as +\begin{equation} +\boxed{ +K\left|\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n}\right|^2 += +\frac{K}{g^{\psi\psi}}|\xi_\psi|^2. } \end{equation} -If one restricts to a real displacement, the complex conjugation may be dropped at the end. -\subsection{Expression of $\kappa_\psi$ in DCON coordinates} +\subsubsection{Curvature $\kappa_\psi$} -We define +\color{blue} +For any flux label $\lambda$, define \begin{equation} -\kappa_\psi +\kappa_\lambda \equiv -\boldsymbol{\kappa}\cdot\nabla\psi, +\boldsymbol{\kappa}\cdot\nabla\lambda, \qquad \boldsymbol{\kappa}\equiv(\mathbf b\cdot\nabla)\mathbf b, \qquad \mathbf b\equiv\frac{\mathbf B}{B}. \end{equation} +This is the magnetic-field-line curvature projected along the chosen flux-gradient +direction. + +\color{black} +In DCON coordinates we take $\lambda=\psi$, so +\begin{equation} +\kappa_\psi\equiv\boldsymbol{\kappa}\cdot\nabla\psi, +\qquad +p'\equiv\frac{dp}{d\psi}. +\end{equation} +Both $p'$ and $\kappa_\psi$ depend on this flux-label normalization, while their product +$p'\kappa_\psi$ is the invariant pressure-curvature combination entering $K$. Using the equilibrium force balance \begin{equation} @@ -1296,22 +1161,26 @@ \subsection{Expression of $\kappa_\psi$ in DCON coordinates} \right]. } \end{equation} +Here $B^2$, $\partial B^2/\partial\psi$, and $\partial B^2/\partial\theta$ are evaluated +from the DCON field-strength expression in §3.3.1. -\subsection{Final DCON expression for $K$} +\subsubsection{Final expression} +\color{black} Combining the DCON expressions above, we obtain \begin{equation} \boxed{ -K +K_{DCON} = -g^{\psi\psi}\sigma S_\psi +g^{\psi\psi}\sigma S_{\psi} +\mu_0\sigma^2B^2 +2p'\kappa_\psi. } \end{equation} -That is, +Here each flux-label dependent quantity is evaluated with the DCON normalized coordinate +$\psi$. Explicitly: \begin{align} -K(\psi,\theta) +K_{DCON}(\psi,\theta) &= g^{\psi\psi}\sigma \frac{\chi'^2}{\mathcal J} @@ -1335,308 +1204,6 @@ \subsection{Final DCON expression for $K$} \frac{1}{2}\frac{\partial B^2}{\partial\theta}g^{\psi\theta} \right]. \end{align} -% \subsection{Normalization of S between Chance and DCON coordinates} -% \noindent Blue symbols denote Chance variables and black symbols denote DCON variables; the subscripts are omitted in this subsection. - -% In Chance's formulation, the magnetic shear scalar \(S\) is defined as -% \begin{equation} -% \color{blue} -% S -% = -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \cdot -% \left( -% \nabla\times -% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \right). -% \end{equation} - -% Using the DCON normalization of the flux coordinate, -% \begin{equation} -% {\color{blue}\psi} -% = \frac{\chi^\prime}{2\pi}\, -% \psi, -% \qquad -% {\color{blue}\nabla \psi} -% = \frac{\chi^\prime}{2\pi}\, \nabla \psi, -% \end{equation} -% we can rewrite each factor in terms of DCON variables. - -% Hence, the normalized cross term becomes -% \begin{equation} -% \frac{{\color{blue}\mathbf{B}\times\nabla\psi}}{{\color{blue}|\nabla\psi|^2}} -% = \frac{2\pi}{\chi^\prime}\, -% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2}. -% \end{equation} - -% Substituting this back into the original definition of \(S\), we find -% \begin{align} -% {\color{blue}S} -% &= -\left(\frac{2\pi}{\chi^\prime}\right)^2 -% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \cdot -% \left[ -% \nabla\times -% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \right]\\ -% &= \left(\frac{2\pi}{\chi^\prime}\right)^2 -% \underbrace{\left[ -% -\,\frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \cdot -% \left( -% \nabla\times -% \frac{\mathbf{B}\times\nabla\psi}{|\nabla\psi|^2} -% \right)\right]}_{S}\\ -% &= -\,(2\pi)^2 \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} -% \cdot -% \left( -% \nabla\times -% \frac{\mathbf{B}\times\nabla\chi_{dcon}}{|\nabla\chi_{dcon}|^2} -% \right) -% \end{align} - -% Therefore, the final relation between the two shear definitions is -% \begin{equation} -% \boxed{ -% {\color{blue}S} -% = \left(\frac{2\pi}{\chi^\prime}\right)^2 -% S -% = c^{-2} S, -% \qquad -% S -% = \left(\frac{\chi^\prime}{2\pi}\right)^2 -% {\color{blue}S}. -% } -% \end{equation} - -% Below this point in this subsection, \(S\) denotes the DCON quantity. - -% \begin{align} -% \boldsymbol{\Lambda} -% &\equiv \frac{\mathbf{B} \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} -% = \frac{(\nabla \chi_{dcon} \times \nabla \alpha) \times \nabla \chi_{dcon}}{|\nabla \chi_{dcon}|^2} \\[4pt] -% &= \nabla \alpha -% - \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon}. -% \end{align} - -% The curl becomes ($\alpha\, \text{represents}\, \alpha_{dcon}$ below this point) -% \begin{align} -% \nabla \times \boldsymbol{\Lambda} -% &= -\,\nabla \times -% \left[ -% \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} -% \right] \\[4pt] -% &= -\,\nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -% \times \nabla \chi_{dcon}, -% \end{align} - -% since $\nabla \times \nabla = 0$. The magnetic shear: - -% \begin{align} -% {\color{blue}S} -% &= - (2\pi)^2\, \boldsymbol{\Lambda} \cdot (\nabla \times \boldsymbol{\Lambda}) \\[4pt] -% &= - (2\pi)^2 -% \left[ -% \nabla \alpha -% - \frac{(\nabla \chi_{dcon} \cdot \nabla \alpha)}{|\nabla \chi_{dcon}|^2}\, \nabla \chi_{dcon} -% \right] -% \cdot -% \left[ -% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -% \times \nabla \chi_{dcon} -% \right] \\[6pt] -% &= (2\pi)^2\, \nabla \alpha \cdot -% \left[ -% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) -% \times \nabla \chi_{dcon} -% \right] \\[6pt] -% &= (2\pi)^2\, (\nabla \chi_{dcon} \times \nabla \alpha) \cdot -% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right) \\[6pt] -% &= (2\pi)^2\, \mathbf{B} \cdot -% \nabla \left(\frac{\nabla \chi_{dcon} \cdot \nabla \alpha}{|\nabla \chi_{dcon}|^2}\right)\\ -% &= \frac{(2\pi)^2}{\chi\prime_{dcon}}\, \mathbf{B} \cdot \nabla (\frac{\nabla \psi \cdot \nabla \alpha}{\nabla\psi \cdot \nabla\psi}) -% \end{align} - - -% The parallel gradient operator is: -% \begin{equation} -% \mathbf{B} \cdot \nabla -% = B^{\theta} \frac{\partial}{\partial \theta} -% + B^{\zeta} \frac{\partial}{\partial \zeta} -% = \frac{\chi'}{\mathcal{J}} -% \left( -% \frac{\partial}{\partial \theta} -% + q \frac{\partial}{\partial \zeta} -% \right). -% \end{equation} - -% Also note -% \begin{align} -% \nabla \psi \cdot \nabla \alpha -% &= \nabla \psi \cdot \nabla \big(q(\psi)\theta - \zeta\big) -% = q'(\nabla \psi \cdot \nabla \psi)\theta -% + \nabla \psi \cdot \big(q \nabla \theta - \nabla \zeta\big). -% \end{align} - -% Since $\partial / \partial \zeta = 0$ for a tokamak, one obtains: -% \begin{align} -% {\color{blue}S} -% &= \frac{(2\pi)^2}{\mathcal{J}} -% \frac{\partial}{\partial \theta} -% \left[ -% q' \theta -% + \frac{q(\nabla \psi \cdot \nabla \theta) -% - (\nabla \psi \cdot \nabla \zeta)} -% {(\nabla \psi \cdot \nabla \psi)} -% \right] \\[6pt] -% &= \frac{(2\pi)^2}{\mathcal{J}} -% \left[ -% q' -% + \frac{\partial}{\partial \theta} -% \left( -% \frac{q(\nabla \psi \cdot \nabla \theta) -% - (\nabla \psi \cdot \nabla \zeta)} -% {(\nabla \psi \cdot \nabla \psi)} -% \right) -% \right]. -% \end{align} - -% \subsection{$\xi^{\psi}$ in DCON berstein caculation} - -% \begin{align} -% \boxed{ -% \boldsymbol{\xi}_\perp \cdot \hat{n} -% = \frac{\boldsymbol{\xi} \cdot \nabla \psi}{|\nabla \psi|} -% = \frac{\xi^\psi}{|\nabla \psi|}, -% } -% \qquad -% \boxed{ -% (\boldsymbol{\xi}_\perp \cdot \hat{n})^2 -% = \frac{(\xi^\psi)^2}{|\nabla \psi|^2}. -% } -% \end{align} - -% \begin{equation} -% \boxed{ -% \boldsymbol{\xi}_\perp \cdot \hat{n} -% = \frac{1}{|\nabla \psi|} -% \sum_{m,n} v_{m,n}(\psi)\, e^{2\pi i(m\theta - n\zeta)}. -% } -% \end{equation} - -% \subsection{$\boldsymbol{\kappa}$ in DCON berstein caculation} -% \noindent Blue symbols denote Chance variables and black symbols denote DCON variables; the subscripts are omitted in this subsection. -% \begin{align} -% \kappa^\psi &\equiv \boldsymbol{\kappa} \cdot \nabla \psi. -% \end{align} - -% \begin{align} -% B^2 \boldsymbol{\kappa} -% = \mu_0 \nabla p + \nabla_\perp \frac{B^2}{2}, -% \end{align} -% \begin{align} -% \nabla p &= p' \nabla \psi, -% \nabla \frac{B^2}{2} -% = \frac{1}{2}\frac{\partial B^2}{\partial \psi}\nabla \psi -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta}\nabla \theta, -% \end{align} -% \begin{align} -% B^2 \kappa^\psi -% \!= -% \mu_0 p' (\nabla \psi \cdot \nabla \psi) -% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} -% (\nabla \psi \cdot \nabla \psi) -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% (\nabla \psi \cdot \nabla \theta). -% \end{align} -% \begin{align} -% \boxed{ -% \kappa^\psi -% = \frac{1}{B^2} -% \left[ -% \mu_0 p' (\nabla \psi \cdot \nabla \psi) -% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} -% (\nabla \psi \cdot \nabla \psi) -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% (\nabla \psi \cdot \nabla \theta) -% \right] -% } -% \end{align} -% or equivalently -% \begin{align} -% \boxed{ -% \kappa^\psi -% = \frac{|\nabla \psi|^2}{B^2} -% \left[ -% \mu_0 p' -% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% \frac{\nabla \psi \cdot \nabla \theta}{|\nabla \psi|^2} -% \right]. -% } -% \end{align} - -% \begin{align} -% {\color{blue}\psi}= c\,\psi, -% \qquad -% c \equiv \frac{\chi'}{2\pi}. -% \end{align} -% \begin{align} -% {\color{blue}\nabla \psi}&= c\,\nabla \psi, \\ -% {\color{blue}|\nabla \psi|^2}&= c^2 |\nabla \psi|^2, \\ -% \frac{\partial B^2}{\partial {\color{blue}\psi}} -% &= \frac{1}{c}\frac{\partial B^2}{\partial \psi}, \\ -% {\color{blue}p'} -% &= \frac{dp}{d{\color{blue}\psi}} -% = \frac{1}{c} p'. -% \end{align} -% \begin{align} -% {\color{blue}\kappa^\psi} -% &= -% \frac{{\color{blue}|\nabla \psi|^2}}{B^2} -% \left[ -% \mu_0 {\color{blue}p'} -% + \frac{1}{2}\frac{\partial B^2}{\partial {\color{blue}\psi}} -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% \frac{{\color{blue}\nabla \psi \cdot \nabla \theta}} -% {{\color{blue}|\nabla \psi|^2}} -% \right] \\ -% &= -% \frac{c^2 |\nabla \psi|^2}{B^2} -% \left[ -% \frac{1}{c}\mu_0 p' -% + \frac{1}{2c}\frac{\partial B^2}{\partial \psi} -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% \frac{c\,\nabla \psi \cdot \nabla \theta} -% {c^2 |\nabla \psi|^2} -% \right] \\ -% &= -% c\,\frac{|\nabla \psi|^2}{B^2} -% \left[ -% \mu_0 p' -% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% \frac{\nabla \psi \cdot \nabla \theta} -% {|\nabla \psi|^2} -% \right]. -% \end{align} -% \begin{align} -% \boxed{ -% \kappa^\psi -% \equiv -% \boldsymbol{\kappa}\cdot\nabla \psi -% = \frac{|\nabla \psi|^2}{B^2} -% \left[ -% \mu_0 p' -% + \frac{1}{2}\frac{\partial B^2}{\partial \psi} -% + \frac{1}{2}\frac{\partial B^2}{\partial \theta} -% \frac{\nabla \psi \cdot \nabla \theta} -% {|\nabla \psi|^2} -% \right], -% } -% \end{align} -% \begin{align} -% {\color{blue}\kappa^\psi}= c\,\kappa^\psi. -% \end{align} \section{Reconstruction of energy principle variables in DCON} @@ -1652,13 +1219,13 @@ \section{Reconstruction of energy principle variables in DCON} \chi'' \nabla \zeta \times \nabla \psi + (q\chi')' \nabla \psi \times \nabla \theta \right)\xi_\psi \\ -&+\frac{\xi_\psi}{|\nabla \psi|^2} [ -j^\theta +&+\frac{\mathcal J\xi_\psi}{|\nabla \psi|^2} [ +\mu_0j^\theta \big( \nabla\psi(\nabla\zeta\cdot\nabla\psi) - \nabla\zeta|\nabla\psi|^2 -\big)+j^\zeta +\big)+\mu_0j^\zeta \big( \nabla\theta|\nabla\psi|^2 - @@ -1669,51 +1236,55 @@ \section{Reconstruction of energy principle variables in DCON} \end{equation} \subsection{Reconstruction v1} - \begin{align} -\xi^\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ -\xi^s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) +\xi_\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ +\xi_s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \end{align} -And if you see the 331 line in ideal.f of MATCH, it constructs all the eigenvectors in variable v. -\begin{equation} -v_{m,n}(\psi) -= v(:,1,mstep) -\end{equation} -\begin{equation} -v_{m,n}^\prime(\psi) -= v(:,3,mstep) -\end{equation} -\begin{equation} -u_{m,n}(\psi) -= v(:,4,mstep) -\end{equation} -\medskip -\noindent\rule{\linewidth}{0.4pt} +Here $\xi_\psi\equiv\vec\xi\cdot\nabla\psi$ is the normal displacement amplitude used by DCON, and $\xi_s$ is the surface displacement variable. \begin{align} \vec{Q} &= \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) &= \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) \\ &= Q_1 \nabla \psi + Q_2 \nabla \theta + Q_3 \nabla \zeta \end{align} \begin{equation} -Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi^\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) +Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi_\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) \end{equation} \begin{align} -Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi ^\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \zeta} \\ -&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) +Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi_\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi_s}{\partial \zeta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + \chi^{\prime\prime}v_{m,n}(\psi) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) \end{align} \begin{align} Q^{\zeta} -&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi^\psi) - -\frac{1}{\mathcal{J}} \frac{\partial \xi^s}{\partial \theta} \\ +&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi_\psi) + -\frac{1}{\mathcal{J}} \frac{\partial \xi_s}{\partial \theta} \\ &= - \frac{1}{\mathcal{J}} \sum_{m} \sum_{n} \left( \chi ^ \prime q \frac{\partial v_{m,n}(\psi)}{\partial \psi} - + \chi^\prime q^\prime v_{m,n}(\psi) + + (\chi^{\prime\prime}q+\chi^\prime q^\prime) v_{m,n}(\psi) + 2\pi i m u_{m,n}(\psi) \right) \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). \end{align} +\paragraph{Implementation note on \texttt{reg\_flag}.} +The formulas above are the unregularized Bernstein reconstruction. In +\texttt{gpeq\_sol} and \texttt{gpeq\_c}, the code may instead use modified +tangential quantities when \texttt{reg\_flag=.true.}. The modification replaces +the radial derivative and surface variable entering the tangential upper +components by +\begin{equation} +x_{\psi}' \longrightarrow x_{\psi}'\, +\frac{(m-nq)^2}{(m-nq)^2+\texttt{reg\_spot}^2}, +\qquad +x_s \longrightarrow x_s^{\mathrm{mod}}, +\end{equation} +and then forms \texttt{bmt\_mn} and \texttt{bmz\_mn} from these modified +quantities. Therefore, if \texttt{reg\_flag=.false.}, \texttt{bmt\_mn=bwt\_mn} +and \texttt{bmz\_mn=bwz\_mn}, so the code follows the expressions above. If +\texttt{reg\_flag=.true.}, the v1 diagnostic is a regularized reconstruction +near rational surfaces \(m-nq=0\), not the strictly unregularized Bernstein +formula. + Then the current vector can be reconstructed by the Grad-Shafranov equation: \begin{align} \mu_0 \vec{j} &= \mathcal{J} (\mu_0 j^{\psi} \nabla \theta \times \nabla \zeta + \mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) \\ @@ -1733,23 +1304,54 @@ \subsection{Reconstruction v1} \begin{aligned} C^\psi &= \vec{C}\cdot\nabla\psi = Q^\psi &\qquad -C_1 &= Q_1 + \frac{\mathcal{J}\xi^\psi}{|\nabla\psi|^2} +C_1 &= Q_1 + \frac{\mathcal{J}\xi_\psi}{|\nabla\psi|^2} \bigl[ \mu_0 j^\theta (\nabla\psi\cdot\nabla\zeta) - \mu_0 j^\zeta (\nabla\psi\cdot\nabla\theta) \bigr] \\ -C^\theta &= \vec{C}\cdot\nabla\theta = Q^\theta + \xi^\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) +C^\theta &= \vec{C}\cdot\nabla\theta = Q^\theta + \xi_\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) &\qquad -C_2 &= Q_2 + \mathcal{J}\xi^\psi \mu_0 j^\zeta \\ -C^\zeta &= \vec{C}\cdot\nabla\zeta = Q^\zeta - \xi^\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) +C_2 &= Q_2 + \mathcal{J}\xi_\psi \mu_0 j^\zeta \\ +C^\zeta &= \vec{C}\cdot\nabla\zeta = Q^\zeta - \xi_\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) &\qquad -C_3 &= Q_3 - \mathcal{J}\xi^\psi \mu_0 j^\theta +C_3 &= Q_3 - \mathcal{J}\xi_\psi \mu_0 j^\theta +\end{aligned} +\end{equation} + +For direct reconstruction on the $(\psi,\theta)$ grid, the energy density is closed by the metric contraction +\begin{equation} +|\vec C|^2 += +g_{ij}C^{i*}C^j += +C_i^*C^i, +\qquad +C_i=g_{ij}C^j, +\qquad +i,j\in\{\psi,\theta,\zeta\}. +\end{equation} +Equivalently, +\begin{equation} +\begin{aligned} +|\vec C|^2 +&= +g_{\psi\psi}|C^\psi|^2 ++g_{\theta\theta}|C^\theta|^2 ++g_{\zeta\zeta}|C^\zeta|^2 \\ +&\quad ++2\operatorname{Re}\!\left[ +g_{\psi\theta}C^{\psi*}C^\theta ++g_{\psi\zeta}C^{\psi*}C^\zeta ++g_{\theta\zeta}C^{\theta*}C^\zeta +\right]. \end{aligned} \end{equation} \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta W$} -In this part, we will reconstruct the first term in $\delta W$ in the matrix form. +In this part, we show that the reconstructed Bernstein form can be written in the same radial matrix structure as the DCON/Newcomb formulation. \begin{equation} -\boxed{\;\mathcal{J}\,(\vec v_i\cdot\vec v_j)=\frac{g_{ij}}{\mathcal{J}}\;} +\boxed{\;\vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal{J}^2}\;} \end{equation} +Here $g_{ij}$ denotes the true covariant metric used in the previous sections. +The DCON matrix coefficients use the Fourier projection of $\vec v_i\cdot\vec v_j$, not of $g_{ij}$ itself. \begin{equation} \boxed{ @@ -1763,11 +1365,121 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \begin{equation} \boxed{ -(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}} \right\rangle_{mm'} +(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}^2} \right\rangle_{mm'} +} +\end{equation} +where $G_{ij}$ is the DCON matrix representation of $\vec v_i\cdot\vec v_j$ in Fourier space. + +The reconstructed Bernstein form is written in the DCON/Newcomb radial matrix +form as +\begin{equation} +\boxed{ +\begin{aligned} +2\mu_0\delta W +&= +\int d\psi\, +\Big[ +\Xi_s^\dagger \mathbf A \Xi_s ++\Xi_s^\dagger(\mathbf B\Xi_\psi' + \mathbf C\Xi_\psi) ++(\Xi_\psi^{\prime\dagger}\mathbf B^\dagger+\Xi_\psi^\dagger\mathbf C^\dagger)\Xi_s \\ +&\qquad ++\Xi_\psi^{\prime\dagger}\mathbf D\Xi_\psi' ++\Xi_\psi^{\prime\dagger}\mathbf E\Xi_\psi ++\Xi_\psi^\dagger\mathbf E^\dagger\Xi_\psi' ++\Xi_\psi^\dagger\mathbf H\Xi_\psi +\Big]. +\end{aligned} +} +\end{equation} +Here +\begin{equation} +\Xi_s\equiv\{u_m\}, +\qquad +\Xi_\psi\equiv\{v_m\}, +\qquad +\mathbf M_{mm'}=m\delta_{mm'}, +\qquad +\mathbf Q_{mm'}=(m-nq)\delta_{mm'}. +\end{equation} +We also use +\begin{equation} +\mathbf J\equiv\langle 1\rangle, +\qquad +\mathbf J'\equiv\left\langle \frac{\mathcal J'}{\mathcal J}\right\rangle, +\qquad +\mathbf I_{mm'}=\delta_{mm'}. +\end{equation} +With the DCON-normalized flux scale $v'\leftrightarrow\chi'$ and $P'\leftrightarrow\mu_0p'$, the coefficient matrices are +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf A +&=(2\pi)^2 +\left[ +n(n\mathbf G_{22}+\mathbf G_{23}\mathbf M) ++\mathbf M(n\mathbf G_{23}+\mathbf G_{33}\mathbf M) +\right],\\ +\mathbf B +&=-2\pi i\chi' +\left[ +n(\mathbf G_{22}+q\mathbf G_{23}) ++\mathbf M(\mathbf G_{23}+q\mathbf G_{33}) +\right],\\ +\mathbf C +&=-2\pi i +\left[ +\chi''(n\mathbf G_{22}+\mathbf M\mathbf G_{23}) ++(q\chi')'(n\mathbf G_{23}+\mathbf M\mathbf G_{33}) +\right] \\ +&\quad +-(2\pi)^2\chi'(n\mathbf G_{12}+\mathbf M\mathbf G_{31})\mathbf Q ++2\pi i +\left( +2\pi f'\mathbf Q +-n\frac{\mu_0p'}{\chi'}\mathbf J +\right),\\ +\mathbf D +&=\chi'^2 +\left[ +(\mathbf G_{22}+q\mathbf G_{23}) ++q(\mathbf G_{23}+q\mathbf G_{33}) +\right],\\ +\mathbf E +&=\chi' +\left[ +\chi''(\mathbf G_{22}+q\mathbf G_{23}) ++(q\chi')'(\mathbf G_{23}+q\mathbf G_{33}) +\right] \\ +&\quad +-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q ++\mu_0p'\mathbf J,\\ +\mathbf H +&= +\chi'' +\left[ +\chi''\mathbf G_{22}+(q\chi')'\mathbf G_{23} +\right] ++(q\chi')' +\left[ +\chi''\mathbf G_{23}+(q\chi')'\mathbf G_{33} +\right] \\ +&\quad ++2\pi i\chi' +\left[ +\chi''(\mathbf M\mathbf G_{12}-\mathbf G_{12}\mathbf M) ++(q\chi')'(\mathbf M\mathbf G_{31}-\mathbf G_{31}\mathbf M) +\right] \\ +&\quad ++(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q ++\left[ +\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) +-2\pi f'q'\chi'\mathbf I +\right]. +\end{aligned} } \end{equation} -where $G_{ij}$ is the matrix representation of the metric tensor in the Fourier space. -First we focus on the first term in $\delta W$. If we change each terms in $\vec{C}$ as follows: + +Decompose the $\vec C$ term as \begin{equation} \begin{aligned} &\mathbf X \equiv \nabla \xi_s \times \nabla \psi,\\ @@ -1785,12 +1497,123 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \end{aligned} \end{equation} +\noindent Define +\begin{equation} +\mathbf Q_0\equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, +\qquad +\vec C=\mathbf Q_0+\mathbf V . +\end{equation} +Then the Bernstein part of the energy is +\begin{equation} +\begin{aligned} +\int d^3x\left(|\vec C|^2-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2}\right) +&= +\int d\psi d\theta d\zeta\,\mathcal J +\Big[ +|\mathbf Q_0|^2 ++\mathbf Q_0^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Q_0 \\ +&\qquad ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\Big]. +\end{aligned} +\end{equation} +Using \(e_m\equiv e^{2\pi i(m\theta-n\zeta)}\), the four $\mathbf Q_0$ pieces are +\begin{equation} +\begin{aligned} +\mathbf X +&= +-2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3),\\ +\mathbf Y +&= +2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1,\\ +\mathbf Z +&= +-\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3),\\ +\mathbf U +&= +-\sum_m v_m e_m\, +\left[\chi''\vec v_2+(q\chi')'\vec v_3\right]. +\end{aligned} +\end{equation} +Therefore each bilinear product has a fixed matrix block: +\begin{equation} +\begin{array}{c|c|c} +\text{product} & \text{radial amplitudes} & \text{matrix block} \\ \hline +\mathbf X^*\!\cdot\mathbf X & \Xi_s^\dagger\Xi_s & \mathbf A\\ +\mathbf X^*\!\cdot\mathbf Z+\mathbf Z^*\!\cdot\mathbf X +& \Xi_s^\dagger\Xi_\psi' + \Xi_\psi^{\prime\dagger}\Xi_s & \mathbf B\\ +\mathbf X^*\!\cdot(\mathbf Y+\mathbf U+\mathbf V) ++(\mathbf Y+\mathbf U+\mathbf V)^*\!\cdot\mathbf X +& \Xi_s^\dagger\Xi_\psi + \Xi_\psi^\dagger\Xi_s & \mathbf C\\ +\mathbf Z^*\!\cdot\mathbf Z & \Xi_\psi^{\prime\dagger}\Xi_\psi' & \mathbf D\\ +\mathbf Z^*\!\cdot(\mathbf Y+\mathbf U+\mathbf V) ++(\mathbf Y+\mathbf U+\mathbf V)^*\!\cdot\mathbf Z +& \Xi_\psi^{\prime\dagger}\Xi_\psi + \Xi_\psi^\dagger\Xi_\psi' & \mathbf E\\ +\mathbf Y,\mathbf U,\mathbf V,K\ \text{pure terms} +& \Xi_\psi^\dagger\Xi_\psi & \mathbf H . +\end{array} +\end{equation} +Concretely, every entry is obtained by expanding the product in Fourier modes and using +\begin{equation} +\int_0^1 d\theta\, +\mathcal J\,e^{2\pi i(m'-m)\theta} +(\vec v_i\cdot\vec v_j) += +(G_{ij})_{mm'}. +\end{equation} +Equivalently, if +\begin{equation} +\mathbf P=\sum_m p_m e_m P_m^i\vec v_i, +\qquad +\mathbf R=\sum_{m'} r_{m'} e_{m'} R_{m'}^j\vec v_j, +\end{equation} +then +\begin{equation} +\boxed{ +\int_0^1 d\theta\,\mathcal J\,\mathbf P^*\cdot\mathbf R += +\sum_{m,m'}p_m^* +\left[ +\sum_{i,j} (P_m^i)^*(G_{ij})_{mm'}R_{m'}^j +\right]r_{m'} . +} +\end{equation} +This is the actual route from the real-space Bernstein expression to the radial matrix form. The factors \(m\) and \(m-nq\) are then written as the diagonal matrices \(\mathbf M\) and \(\mathbf Q\). +For instance, +\begin{equation} +\begin{aligned} +\int d\theta\,\mathcal J\,\mathbf X^*\cdot\mathbf X +&= +\Xi_s^\dagger +(2\pi)^2 +\left[ +n^2\mathbf G_{22} +{}+n\mathbf G_{23}\mathbf M +{}+n\mathbf M\mathbf G_{23} +{}+\mathbf M\mathbf G_{33}\mathbf M +\right]\Xi_s,\\ +\int d\theta\,\mathcal J\,\mathbf X^*\cdot\mathbf Z +&= +\Xi_s^\dagger +\left\{ +-2\pi i\chi' +\left[ +n(\mathbf G_{22}+q\mathbf G_{23}) +{}+\mathbf M(\mathbf G_{23}+q\mathbf G_{33}) +\right] +\right\} +\Xi_\psi'. +\end{aligned} +\end{equation} +The same projection rule gives \(\mathbf C_1,\mathbf C_2,\mathbf D,\mathbf E_1,\mathbf E_2,\mathbf H_1,\mathbf H_2,\mathbf H_3\) below. The only nontrivial part is the current term \(\mathbf V\), because its pure-\(\Xi_\psi\) pieces must be recombined with the scalar Bernstein term involving \(K\). + \begin{equation} \begin{aligned} 2\mu_0\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [|\vec{C}|^2]\\ -&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q|^2+ -\left[(\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)\cdot\mathbf V^* + \mathbf V \cdot (\mathbf X+\mathbf Y+\mathbf Z+\mathbf U)^* -\right]+|\mathbf V|^2\} +&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q_0|^2+ +\left[\mathbf Q_0\cdot\mathbf V^* + \mathbf V \cdot \mathbf Q_0^* +\right]+|\mathbf V|^2\} . \end{aligned} \end{equation} @@ -1882,9 +1705,9 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta } \end{equation} -\paragraph{Matrix representation1} +\paragraph{Blocks from \(\mathbf Q_0^*\cdot\mathbf Q_0\)} -Only the final block forms already derived in \cite{10.1063/1.4958328} are listed here: +Applying the projection rule above to the products that do not contain \(\mathbf V\) gives the following blocks: \begin{equation} |\mathbf X|^2 \leadsto \Xi_s^\dagger \mathbf A \Xi_s, \qquad @@ -1909,7 +1732,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \leadsto \Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s, \qquad -\mathbf C_2=-2\pi \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. +\mathbf C_2=-2\pi i \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. \end{equation} \begin{equation} |\mathbf Y|^2 \leadsto \Xi_\psi^\dagger \mathbf H_1 \Xi_\psi, @@ -1964,12 +1787,21 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \right] \end{equation} -\paragraph{Matrix representation2} -The term $\mathbf X^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf X$ can be written in matrix form as +\paragraph{Bernstein-current correction terms} + +The vector $\mathbf V$ is not an additional physical term. It appears only because the Bernstein form rewrites the original current--pressure contribution into +\[ +\vec C=\vec Q+\frac{\xi_\psi}{|\nabla\psi|^2}\mu_0\vec j\times\nabla\psi +=\mathbf X+\mathbf Y+\mathbf Z+\mathbf U+\mathbf V. +\] +Therefore the $\mathbf V$-dependent pieces from $|\vec C|^2$ must be combined with the scalar term +$-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$. After this recombination, the same $\mathbf C$, $\mathbf E$, and $\mathbf H$ blocks in the DCON matrix form are obtained. + +The cross term $\mathbf X^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf X$ produces the current part of the $\mathbf C$ block: \[ \Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s, \] -where $\mathbf C_3$ is the corresponding cross-block matrix: +where $\mathbf C_3$ is obtained as follows. \begin{align} \nabla \xi_s @@ -2117,12 +1949,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \noindent\rule{\linewidth}{0.4pt} \medskip -% Thus the missing $\mathbf X$--$\mathbf V$ contribution is exactly the matrix $\mathbf C_3$ appearing in the full coefficient matrix -% \begin{equation} -% \mathbf C=\mathbf C_1+\mathbf C_2+\mathbf C_3 . -% \end{equation} - -The $\mathbf Z$--$\mathbf V$ block has the form +Next, the $\mathbf Z$--$\mathbf V$ block produces the pressure part of the $\mathbf E$ block: \[ \Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime. \] @@ -2179,10 +2006,10 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \noindent\rule{\linewidth}{0.4pt} \medskip -The term $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is also a pure-$\Xi_\psi$ contribution, so after Fourier projection it takes the form -\[ -\Xi_\psi^\dagger \mathbf H_{YV} \Xi_\psi. -\] +The term $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is also a +pure-$\Xi_\psi$ contribution, but it is not closed as an independent DCON +block. It must be combined with the other pure scalar current--pressure terms +before the final $\mathbf H_4$ block is obtained. \begin{align} \mathbf Y @@ -2356,11 +2183,11 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \right]. \end{align} -Using the DCON expression for the magnetic shear, +Using the DCON-normalized shear parameter, \begin{equation} -S +S_\psi = -\frac{(2\pi)^2}{\mathcal J} +\frac{\chi'^2}{\mathcal J} \left[ q' + @@ -2385,14 +2212,14 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta } \right] = -\frac{\mathcal J}{(2\pi)^2}S-q'. +\frac{\mathcal J}{\chi'^2}S_\psi-q'. \end{equation} Hence \begin{align} \partial_\theta\mathcal F &= -\frac{f'}{2\pi}\,\mathcal J\,S +\frac{2\pi f'}{\chi'^2}\,\mathcal J\,S_\psi - 2\pi f'q' + @@ -2403,53 +2230,20 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \right]. \end{align} -At this point we use the standard coordinate identity +The last derivative, \begin{equation} -\boxed{ \partial_\theta -\left( -\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} -\right) -= --\mathcal J', -} -\end{equation} -where the prime on $\mathcal J$ denotes $\partial/\partial\psi$. - -Therefore -\begin{align} -\int d\theta\,d\zeta\;\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -&= --\int d\theta\,d\zeta\;|\xi_\psi|^2 -\left[ -\frac{f'}{2\pi}\chi'\mathcal J S -- -2\pi f'q'\chi' -- -\mu_0p'\mathcal J' -\right]. -\end{align} - -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y -&\leadsto\; -\Xi_\psi^\dagger \mathbf H_{YV}\Xi_\psi, \\ -(\mathbf H_{YV})_{mm'} -&\equiv -\int_0^1 d\theta\; -e^{2\pi i(m'-m)\theta} \left[ --\frac{f'}{2\pi}\chi'\mathcal J S -+ -2\pi f'q'\chi' -+ -\mu_0p'\mathcal J' -\right]. -\end{aligned} -} +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right], \end{equation} +is not a general coordinate identity and is not reduced here as a standalone +matrix block. Therefore +$\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is kept as an +intermediate pure-$\Xi_\psi$ Bernstein scalar term. The closed DCON block is +obtained only after this term is combined below with +$\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$, $|\mathbf V|^2$, and +the scalar $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$ contribution. \medskip \noindent\rule{\linewidth}{0.4pt} @@ -2678,7 +2472,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \begin{equation} \frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} = -\sigma\,(\mu_0\mathbf j\cdot\mathbf B) +\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B) + \frac{(\mu_0p')^2}{B^2}|\nabla\psi|^2. \end{equation} @@ -2689,7 +2483,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta = |\xi_\psi|^2 \left[ -\frac{\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} +\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} + \frac{(\mu_0p')^2}{B^2} \right]. @@ -2706,7 +2500,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta (\mathbf H_V)_{mm'} &\equiv \left\langle -\frac{\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} +\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} + \frac{(\mu_0p')^2}{B^2} \right\rangle_{mm'}. @@ -2716,109 +2510,75 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \medskip \noindent\rule{\linewidth}{0.4pt} -\medskip - -\paragraph{Adding the Bernstein scalar term $-K|\xi_\psi|^2/|\nabla\psi|^2$.} - -From the earlier derivation in this note, -\begin{equation} -K -= -|\nabla\psi|^2\sigma S -+ -\sigma\,\mathbf j\cdot\mathbf B -+ -2p'\,\boldsymbol{\kappa}\cdot\nabla\psi. -\end{equation} -Therefore +\paragraph{The scalar current--pressure block.} +The reduction to a radial matrix is automatic once the integrand is a +bilinear form in the Fourier amplitudes +\((\Xi_s,\Xi_\psi,\Xi_\psi')\). What is not automatic is the equality between +the expanded Bernstein \(\mathbf V\)-sector and the compact DCON +current--pressure block. Using the energy-principle transformation proved in +the previous section, the required sesquilinear identity is \begin{equation} --\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 -= --|\xi_\psi|^2 +\mathcal J \left[ -\sigma S -+ -\frac{\sigma\,\mathbf j\cdot\mathbf B}{|\nabla\psi|^2} -+ -\frac{2p'\,\boldsymbol{\kappa}\cdot\nabla\psi}{|\nabla\psi|^2} -\right]. +\mathbf Q_0^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Q_0 ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\right] +\equiv +\mathcal I_{\mathrm{cp}}, \end{equation} +up to the periodic surface-divergence terms that integrate to zero. Therefore +the compact DCON matrices follow from the equivalent DCON current--pressure +density below. A term-by-term reduction of the separate Bernstein pieces is +not sufficient. -Now combine the four remaining scalar contributions: +The separate pure-\(\Xi_\psi\) terms +\(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y\), +\(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U\), +\(|\mathbf V|^2\), and +\(-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2\) +are intermediate Bernstein pieces. For a closed derivation of their sum, use the DCON current--pressure density \begin{equation} -\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -+ -\mathcal J(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) -+ -\mathcal J|\mathbf V|^2 -- -\mathcal J\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2. +\begin{aligned} +\mathcal I_{\mathrm{cp}} +&= +\xi_\psi\,\mathbf J\cdot\nabla\xi_s +-\xi_s\,\mathbf J\cdot\nabla\xi_\psi ++\left[J_\theta(q\chi')'-J_\zeta\chi''\right]|\xi_\psi|^2 ++\frac{P'}{\mathcal J}\left(\mathcal J|\xi_\psi|^2\right)' , +\end{aligned} \end{equation} - -At this stage the cancellations are as follows: - -\begin{enumerate} -\item -The term -\( -\displaystyle -\frac{\sigma\,\mathbf j\cdot\mathbf B}{|\nabla\psi|^2}|\xi_\psi|^2 -\) -coming from $|\mathbf V|^2$ cancels exactly the same term coming from $-K|\xi_\psi|^2/|\nabla\psi|^2$. - -\item -The oscillatory shear term from $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ combines with the $|\nabla\psi|^2\sigma S$ part of $K$ through +where \(\mathbf J=\nabla\times\mathbf B=\mu_0\mathbf j\) and \(P'=\mu_0p'\). The first two terms give the already derived \(\mathbf C_3\). The remaining two terms determine \(\mathbf E_3\) and \(\mathbf H_4\): \begin{equation} -S -= -\frac{(2\pi)^2}{\mathcal J} +\begin{aligned} +\mathcal J\left[J_\theta(q\chi')'-J_\zeta\chi''\right]|\xi_\psi|^2 +&= +|\xi_\psi|^2 \left[ -q' -+ -\partial_\theta -\left( -\frac{ -q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) -}{ -|\nabla\psi|^2 -} -\right) -\right]. +-2\pi f'q'\chi' ++\mu_0p'\mathcal J\frac{\chi''}{\chi'} +\right],\\ +\mu_0p'\left(\mathcal J|\xi_\psi|^2\right)' +&= +\mu_0p'\mathcal J' +|\xi_\psi|^2 ++\mu_0p'\mathcal J +\left(\xi_\psi^{\prime *}\xi_\psi+\xi_\psi^*\xi_\psi'\right). +\end{aligned} \end{equation} -The non-oscillatory remainder is the $q'$ contribution. - -\item -The pressure-curvature term from $-K|\xi_\psi|^2/|\nabla\psi|^2$ combines with the pressure part of $|\mathbf V|^2$ and the pressure part of $\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$ to give the remaining -\( -\mu_0p'(\mathcal J\chi''/\chi'+\mathcal J') -\) -piece. -\end{enumerate} - -After these cancellations, the total scalar contribution reduces to +Therefore \begin{equation} -\mathcal J -\left[ -(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -+ -(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) -+ -|\mathbf V|^2 -- -\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 -\right] -= -|\xi_\psi|^2 -\left[ -\mu_0p' -\left( -\mathcal J\frac{\chi''}{\chi'}+\mathcal J' -\right) -- -2\pi f'q'\chi' -\right]. +\boxed{ +\begin{aligned} +\mathbf Z^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Z +&\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_3\Xi_\psi ++\Xi_\psi^\dagger\mathbf E_3^\dagger\Xi_\psi',\\ +\mathbf E_3&=\mu_0p'\mathbf J, +\end{aligned} +} \end{equation} - +and \begin{equation} \boxed{ \begin{aligned} @@ -2828,12 +2588,12 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta + |\mathbf V|^2 - -\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 +\mu_0\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 &\leadsto\; -\Xi_\psi^\dagger \mathbf H_{\mathrm{add}} \Xi_\psi, \\ +\Xi_\psi^\dagger \mathbf H_4 \Xi_\psi, \\ \qquad -\mathbf H_{\mathrm{add}} -&= +\mathbf H_4 +&= \mu_0p' \left( \frac{\chi''}{\chi'}\mathbf J+\mathbf J' @@ -2844,53 +2604,50 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta } \end{equation} -\paragraph{Final missing pieces.} - -Therefore the missing $V$-dependent matrices are -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf C_3 -&= -2\pi i\left( -2\pi f'\mathbf Q --n\frac{\mu_0p'}{\chi'}\mathbf J -\right). -\end{aligned} -} -\end{equation} -\begin{equation} -\boxed{ -\mathbf E_3=\mu_0p'\mathbf J. -} -\end{equation} -and +The complete term-to-matrix assignment is therefore \begin{equation} \boxed{ \begin{aligned} -\mathbf H_{\mathrm{add}} -&= -\mu_0p' -\left( -\frac{\chi''}{\chi'}\mathbf J+\mathbf J' -\right) \\ -&\qquad --2\pi f'q'\chi' \mathbf I. +\mathbf A &: \quad \mathbf X^*\!\cdot\mathbf X,\\ +\mathbf B &: \quad \mathbf X^*\!\cdot\mathbf Z,\\ +\mathbf C &: \quad +\mathbf X^*\!\cdot\mathbf Y\to\mathbf C_1,\quad +\mathbf X^*\!\cdot\mathbf U\to\mathbf C_2,\quad +\mathbf X^*\!\cdot\mathbf V\to\mathbf C_3,\\ +\mathbf D &: \quad \mathbf Z^*\!\cdot\mathbf Z,\\ +\mathbf E &: \quad +\mathbf Z^*\!\cdot\mathbf Y\to\mathbf E_1,\quad +\mathbf Z^*\!\cdot\mathbf U\to\mathbf E_2,\quad +\mathbf Z^*\!\cdot\mathbf V\to\mathbf E_3,\\ +\mathbf H &: \quad +\mathbf Y^*\!\cdot\mathbf Y\to\mathbf H_1,\quad +\mathbf Y^*\!\cdot\mathbf U+\mathbf U^*\!\cdot\mathbf Y\to\mathbf H_2,\quad +\mathbf U^*\!\cdot\mathbf U\to\mathbf H_3,\\ +&\quad +\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y ++\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\to\mathbf H_4 . \end{aligned} } \end{equation} - -Thus the total matrices become +Thus the final coefficient matrices are \begin{equation} \boxed{ \begin{aligned} \mathbf C&=\mathbf C_1+\mathbf C_2+\mathbf C_3, \\ \mathbf E&=\mathbf E_1+\mathbf E_2+\mathbf E_3, \\ -\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_{\mathrm{add}}. +\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_4. \end{aligned} } \end{equation} -which is exactly the Glasser Appendix A6 result. +Subject to the same boundary and reduced-compressional assumptions stated +above, this is the DCON-normalized version of the matrix structure in +Eq.~(35) of \texttt{dcon.tex}. Thus the Bernstein form and the original DCON +current--pressure form lead to the same radial matrix functional; the +$\mathbf V$-dependent pieces are intermediate cancellation/recombination +terms, not extra physics. \section{Variable Naming Convention and Mathematical Symbols} This section summarizes the implementation of C and Q vectors with proper tensor transformations between contravariant and covariant representations. @@ -2936,9 +2693,12 @@ \subsubsection*{C Vector - Covariant (v-basis, $C_i$)} \hline \textbf{Variable} & \textbf{Symbol/Definition} \\ \hline -\texttt{cvp\_tmp, cvp\_fun} & $C_\psi^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ -\texttt{cvt\_tmp, cvt\_fun} & $C_\theta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ -\texttt{cvz\_tmp, cvz\_fun} & $C_\zeta^{\text{metric}} = g_{ij}C^j$ (metric contraction) \\ +\texttt{cvp\_tmp, cvp\_fun} & $C_\psi$ reconstructed directly from $Q_\psi+(\xi\cdot\hat n)(\mu_0\mathbf j\times\hat n)_\psi$ \\ +\texttt{cvt\_tmp, cvt\_fun} & $C_\theta$ reconstructed directly from $Q_\theta+\mathcal J\xi_\psi\mu_0j^\zeta$ \\ +\texttt{cvz\_tmp, cvz\_fun} & $C_\zeta$ reconstructed directly from $Q_\zeta-\mathcal J\xi_\psi\mu_0j^\theta$ \\ +\texttt{c2vp\_mn, cv2p\_fun} & $g_{\psi j}C^j$ from metric lowering of the reconstructed upper components \\ +\texttt{c2vt\_mn, cv2t\_fun} & $g_{\theta j}C^j$ from metric lowering of the reconstructed upper components \\ +\texttt{c2vz\_mn, cv2z\_fun} & $g_{\zeta j}C^j$ from metric lowering of the reconstructed upper components \\ \hline \end{tabular} \end{table} From 47482ea0a6c4823c06b83f0a5ed5de06cdb8bae1 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 18 May 2026 16:38:14 +0900 Subject: [PATCH 119/305] GPEC - FEATURE - Add recon energy diagnostics (cherry picked from commit e9cc5d5590bd734baef22514e399bfcd24855666) --- gpec/gpeq.f | 76 +++++++++++----- gpec/gpout.f | 245 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 226 insertions(+), 95 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index ad1263812..b11f65105 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -935,16 +935,18 @@ END SUBROUTINE gpeq_cveri c c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - SUBROUTINE gpeq_epf(psi, epf_int) + SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- REAL(r8), INTENT(IN) :: psi REAL(r8), INTENT(OUT) :: epf_int + REAL(r8), OPTIONAL, INTENT(OUT) :: epf_p, epf_t, epf_z INTEGER :: itheta COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun - COMPLEX(r8), DIMENSION(0:mthsurf) :: cv2p_fun, cv2t_fun, cv2z_fun - COMPLEX(r8) :: epf_theta + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + REAL(r8) :: epf_psi_int, epf_theta_int, epf_zeta_int + REAL(r8) :: epf_fac IF(debug_flag) PRINT *, "Entering gpeq_epf" c----------------------------------------------------------------------- @@ -959,20 +961,29 @@ SUBROUTINE gpeq_epf(psi, epf_int) CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) - CALL iscdftb(mfac, mpert, cv2p_fun, mthsurf, c2vp_mn) - CALL iscdftb(mfac, mpert, cv2t_fun, mthsurf, c2vt_mn) - CALL iscdftb(mfac, mpert, cv2z_fun, mthsurf, c2vz_mn) + CALL iscdftb(mfac, mpert, cvp_fun, mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cvt_fun, mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cvz_fun, mthsurf, cvz_mn) epf_int = 0.0_r8 + epf_psi_int = 0.0_r8 + epf_theta_int = 0.0_r8 + epf_zeta_int = 0.0_r8 DO itheta = 0, mthsurf-1 CALL bicube_eval(rzphi, psi, theta(itheta), 0) jac = rzphi%f(4) - epf_theta = CONJG(cwp_fun(itheta)/jac) * cv2p_fun(itheta) + - $ CONJG(cwt_fun(itheta)/jac) * cv2t_fun(itheta) + - $ CONJG(cwz_fun(itheta)/jac) * cv2z_fun(itheta) - epf_int = epf_int + REAL(epf_theta, r8) / - $ (mu0 * REAL(mthsurf, r8)) * jac + epf_fac = jac / (mu0 * REAL(mthsurf, r8)) + epf_psi_int = epf_psi_int + epf_fac * + $ REAL(CONJG(cwp_fun(itheta)/jac) * cvp_fun(itheta), r8) + epf_theta_int = epf_theta_int + epf_fac * + $ REAL(CONJG(cwt_fun(itheta)/jac) * cvt_fun(itheta), r8) + epf_zeta_int = epf_zeta_int + epf_fac * + $ REAL(CONJG(cwz_fun(itheta)/jac) * cvz_fun(itheta), r8) ENDDO + epf_int = epf_psi_int + epf_theta_int + epf_zeta_int + IF (PRESENT(epf_p)) epf_p = epf_psi_int + IF (PRESENT(epf_t)) epf_t = epf_theta_int + IF (PRESENT(epf_z)) epf_z = epf_zeta_int IF(debug_flag) PRINT *, "->Leaving gpeq_epf" c----------------------------------------------------------------------- @@ -988,11 +999,15 @@ END SUBROUTINE gpeq_epf c c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - SUBROUTINE gpeq_dst(psi, dst_int) + SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3) REAL(r8), INTENT(IN) :: psi COMPLEX(r8), INTENT(OUT) :: dst_int + COMPLEX(r8), OPTIONAL, INTENT(OUT) :: dst_t1, dst_t2, dst_t3 INTEGER :: itheta COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xno_fun + REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 + REAL(r8) :: xin2_fac + COMPLEX(r8) :: dst1_int, dst2_int, dst3_int c----------------------------------------------------------------------- c DST(psi) = \int dtheta dzeta J * K * xi_n^2 @@ -1001,16 +1016,28 @@ SUBROUTINE gpeq_dst(psi, dst_int) CALL gpeq_contra(psi) CALL gpeq_cova(psi) CALL gpeq_normal(psi) - CALL gpeq_K(psi, K_fun) + CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3) CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) dst_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst1_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst2_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst3_int = CMPLX(0.0_r8, 0.0_r8, r8) DO itheta = 0, mthsurf-1 CALL bicube_eval(rzphi, psi, theta(itheta), 0) jac = rzphi%f(4) - dst_int = dst_int + - $ jac* K_fun(itheta) * - $ ABS(xno_fun(itheta))**2 / REAL(mthsurf, r8) + xin2_fac = jac * ABS(xno_fun(itheta))**2 / + $ REAL(mthsurf, r8) + dst1_int = dst1_int + CMPLX(K_term1(itheta) * xin2_fac, + $ 0.0_r8, r8) + dst2_int = dst2_int + CMPLX(K_term2(itheta) * xin2_fac, + $ 0.0_r8, r8) + dst3_int = dst3_int + CMPLX(K_term3(itheta) * xin2_fac, + $ 0.0_r8, r8) ENDDO + dst_int = dst1_int + dst2_int + dst3_int + IF (PRESENT(dst_t1)) dst_t1 = dst1_int + IF (PRESENT(dst_t2)) dst_t2 = dst2_int + IF (PRESENT(dst_t3)) dst_t3 = dst3_int c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -1040,11 +1067,11 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c----------------------------------------------------------------------- c Allocate spline for shear_temp with 5 components: -c 1: covariant numerator term +c 1: legacy covariant numerator term c 2: (q*dpdt - dpdz) numerator c 3: g_psi_g_psi denominator -c 4: contravariant denominator (v21^2 + v22^2)*r^2 -c 5: covariant shear (component 1 / component 4) +c 4: legacy covariant denominator +c 5: DCON shear geometry term (component 2 / component 3) c----------------------------------------------------------------------- CALL spline_alloc(shear_temp, mthsurf, 5) shear_temp%xs = theta(0:mthsurf) @@ -1104,7 +1131,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) shear_temp%fs(itheta, 5) = - $ shear_temp%fs(itheta, 1) / shear_temp%fs(itheta, 4) + $ shear_temp%fs(itheta, 2) / shear_temp%fs(itheta, 3) ENDDO c----------------------------------------------------------------------- @@ -1112,8 +1139,8 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c----------------------------------------------------------------------- CALL spline_fit(shear_temp, "periodic") c----------------------------------------------------------------------- -c Step 3: Compute shear_fun = (2π²/J)*(q' + ∂shear_temp/∂θ) -c Compare covariant and contravariant approaches. +c Step 3: Compute shear_fun = (chi1^2/J)*(q' + d shear_temp/d theta) +c with shear_temp = (q*g^psi_theta - g^psi_zeta)/g^psi_psi. c----------------------------------------------------------------------- DO itheta = 0, mthsurf theta_val = theta(itheta) @@ -1207,7 +1234,8 @@ END SUBROUTINE gpeq_curvature c----------------------------------------------------------------------- c subprogram 13. gpeq_K. c compute Bernstein K quantity (stability indicator). -c K = |∇ψ_dcon|^2 * σ * S_dcon + B^2 * σ^2 + 2 P' * κ^psi +c K = |∇ψ_dcon|^2 * σ * S_dcon +c + mu0 * B^2 * σ^2 + 2 p' * κ^psi c Uses pre-computed metric from idcon_metric c----------------------------------------------------------------------- SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, @@ -1290,7 +1318,7 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, c Term1: |∇ψ_dcon|^2 * σ * S_dcon K_t1(itheta) = (delpsi**2) * sigma * REAL(shear_fun(itheta)) -c Term2: B^2 * σ^2 +c Term2: mu0 * B^2 * σ^2 K_t2(itheta) = bsq_val * sigma**2 * mu0 c Term3: 2 p' * κ^psi diff --git a/gpec/gpout.f b/gpec/gpout.f index d6148e869..ebaf26619 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -79,7 +79,7 @@ MODULE gpout_mod $ fldflxmat ! convert power normalized field to area normalized flux (i.e. increase by sqrt(A_m)/sqrt(A) weighting) CONTAINS - + !----------------------------------------------------------------- function str(k,fmt) !----------------------------------------------------------------- @@ -6879,10 +6879,10 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, ucveri, $ uk_sigma, ucw_fun, ucw_mn, ucv_fun, ucv_mn, ucv2_fun, - $ ucv2_mn, u_int + $ ucv2_mn, u_int, u_log REAL(r8) :: psi, eta, rfac REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals - REAL(r8) :: epf_int + REAL(r8) :: epf_int, epf_p_int, epf_t_int, epf_z_int COMPLEX(r8) :: dst_int COMPLEX(r8), DIMENSION(mpert) :: curv_mn, $ term_mn @@ -6896,11 +6896,17 @@ SUBROUTINE gpout_recon(mode, xspmn) CHARACTER(128) :: k_file, k_sigma_file CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file - REAL(r8), DIMENSION(0:mpsi) :: epf_psi, epf_cumsum - COMPLEX(r8), DIMENSION(0:mpsi) :: dst_psi_k, dst_cumsum_k - REAL(r8) :: epf_int_total - COMPLEX(r8) :: dst_int_total_k, dw_raw_k - REAL(r8) :: psi_min, psi_max, dpsi, dpsi_local + REAL(r8) :: dpsi_local + INTEGER :: istep + REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr + REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev + REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr + REAL(r8) :: epf_int_total_hr, epf_p_total_hr, + $ epf_t_total_hr, epf_z_total_hr, dcon_fac + COMPLEX(r8) :: dst_prev, dst_curr, dst1_prev, dst2_prev, + $ dst3_prev, dst1_curr, dst2_curr, dst3_curr, + $ dst_int_total_k_hr, dst1_total_hr, dst2_total_hr, + $ dst3_total_hr, dw_raw_k_hr, dw_dcon_k_hr, dw_cum c Diagnostic variables for C vector comparison INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 @@ -6952,6 +6958,7 @@ SUBROUTINE gpout_recon(mode, xspmn) ucv2_fun = 90 ucv2_mn = 91 u_int = 92 + u_log = 93 OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") @@ -6976,7 +6983,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "T1_re","T2_re","T3_re" WRITE(uk_sigma,'(8(1x,a16))') $ "psi","theta","r","z","sigma_re","jdotb_re", - $ "bsig2_re","sigjdotb_re" + $ "mu0_bsig2_re","mu0_sigjdotb" WRITE(ucw_fun,'(10(1x,a16))') $ "psi","theta","r","z","cwp_re","cwp_im", $ "cwt_re","cwt_im","cwz_re","cwz_im" @@ -6995,28 +7002,25 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucv2_mn,'(8(1x,a16))') $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", $ "cv2z_re","cv2z_im" - WRITE(u_int,'(7(1x,a16))') - $ "psi","epf_int","dst_k_re","dst_k_im", - $ "epf_cumsum","dst_k_c_re","dst_k_c_im" + WRITE(u_int,'(15(1x,a16))') + $ "psi","c2_mu0","k_xin2_re","k_xin2_im", + $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", + $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", + $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" c----------------------------------------------------------------------- c main loop over all psi levels. c compute gpeq reconstruction diagnostics at each psi. c----------------------------------------------------------------------- + CALL idcon_build(mode, xspmn) CALL gpeq_alloc - epf_int_total = 0.0_r8 - dst_int_total_k = CMPLX(0.0_r8, 0.0_r8, r8) - epf_cumsum = 0.0_r8 - dst_cumsum_k = CMPLX(0.0_r8, 0.0_r8, r8) DO ipsi = 0, mpsi psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT -c Compute surface-integrated EPF and DST terms for this psi. -c Store in arrays for psi integration later. +c Compute the J|C|^2/mu0 state once so C fields below use +c the current psi. CALL gpeq_epf(psi, epf_int) - CALL gpeq_dst(psi, dst_int) - epf_psi(ipsi) = epf_int - dst_psi_k(ipsi) = dst_int c Reconstruct detailed K diagnostics for output. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, @@ -7047,7 +7051,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(uk_sigma,'(8(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), sigma_vals(itheta), $ jdotb_vals(itheta), K_term2(itheta), - $ sigma_vals(itheta) * jdotb_vals(itheta) + $ mu0 * sigma_vals(itheta) * jdotb_vals(itheta) ENDDO WRITE(ushear_fun,*) WRITE(ucurv,*) @@ -7055,7 +7059,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(uk,*) WRITE(uk_sigma,*) -c Reconstruct C fields from the already computed EPF state. +c Reconstruct C fields from the already computed C state. CALL iscdftb(mfac, mpert, cw_fun(:,1), mthsurf, cwp_mn) CALL iscdftb(mfac, mpert, cw_fun(:,2), mthsurf, cwt_mn) CALL iscdftb(mfac, mpert, cw_fun(:,3), mthsurf, cwz_mn) @@ -7102,60 +7106,159 @@ SUBROUTINE gpout_recon(mode, xspmn) ENDDO c----------------------------------------------------------------------- -c perform psi integration using trapezoidal rule. -c integrate: ∫dpsi epf(psi), ∫dpsi dst_k(psi) -c----------------------------------------------------------------------- - psi_min = rzphi%xs(0) - psi_max = rzphi%xs(mpsi) - dpsi = (psi_max - psi_min) / REAL(mpsi, r8) - - epf_int_total = 0.0_r8 - dst_int_total_k = CMPLX(0.0_r8, 0.0_r8, r8) - epf_cumsum(0) = 0.0_r8 - dst_cumsum_k(0) = CMPLX(0.0_r8, 0.0_r8, r8) - - DO ipsi = 0, mpsi-1 - dpsi_local = rzphi%xs(ipsi+1) - rzphi%xs(ipsi) - epf_int_total = epf_int_total + - $ (epf_psi(ipsi) + epf_psi(ipsi+1)) * dpsi_local / 2.0_r8 - dst_int_total_k = dst_int_total_k + - $ (dst_psi_k(ipsi) + dst_psi_k(ipsi+1)) * dpsi_local - $ / 2.0_r8 - epf_cumsum(ipsi+1) = epf_int_total - dst_cumsum_k(ipsi+1) = dst_int_total_k +c Perform the energy check on the DCON solution grid. Do not use +c rzphi%xs for this comparison; it is a coarse geometry grid. +c----------------------------------------------------------------------- + dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + epf_int_total_hr = 0.0_r8 + epf_p_total_hr = 0.0_r8 + epf_t_total_hr = 0.0_r8 + epf_z_total_hr = 0.0_r8 + dst_int_total_k_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst1_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst2_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst3_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + psi_prev = MAX(psifac(0), rzphi%xs(0)) + CALL gpeq_epf(psi_prev, epf_prev, epf_p_prev, epf_t_prev, + $ epf_z_prev) + CALL gpeq_dst(psi_prev, dst_prev, dst1_prev, dst2_prev, + $ dst3_prev) + WRITE(u_int,'(a)') "c psifac-grid integration results" + WRITE(u_int,'(a)') "c C2/mu0 = int J |C|^2 / mu0" + WRITE(u_int,'(a)') "c Kxin2 = int J K |xi_n|^2" + WRITE(u_int,'(a)') "c dW_gpec(K) = 0.5 * (C2/mu0 - Kxin2)" + dw_cum = CMPLX(0.0_r8, 0.0_r8, r8) + WRITE(u_int,'(15(es17.8e3))') psi_prev, epf_prev, + $ REAL(dst_prev), AIMAG(dst_prev), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_prev, epf_t_prev, + $ epf_z_prev, REAL(dst1_prev), REAL(dst2_prev), + $ REAL(dst3_prev) + DO istep = 0, mstep-1 + psi_curr = psifac(istep+1) + IF (psi_curr < rzphi%xs(0)) CYCLE + IF (psi_curr > rzphi%xs(mpsi)) EXIT + CALL gpeq_epf(psi_curr, epf_curr, epf_p_curr, + $ epf_t_curr, epf_z_curr) + CALL gpeq_dst(psi_curr, dst_curr, dst1_curr, dst2_curr, + $ dst3_curr) + dpsi_local = psi_curr - psi_prev + epf_int_total_hr = epf_int_total_hr + + $ (epf_prev + epf_curr) * dpsi_local / 2.0_r8 + epf_p_total_hr = epf_p_total_hr + + $ (epf_p_prev + epf_p_curr) * dpsi_local / 2.0_r8 + epf_t_total_hr = epf_t_total_hr + + $ (epf_t_prev + epf_t_curr) * dpsi_local / 2.0_r8 + epf_z_total_hr = epf_z_total_hr + + $ (epf_z_prev + epf_z_curr) * dpsi_local / 2.0_r8 + dst_int_total_k_hr = dst_int_total_k_hr + + $ (dst_prev + dst_curr) * dpsi_local / 2.0_r8 + dst1_total_hr = dst1_total_hr + + $ (dst1_prev + dst1_curr) * dpsi_local / 2.0_r8 + dst2_total_hr = dst2_total_hr + + $ (dst2_prev + dst2_curr) * dpsi_local / 2.0_r8 + dst3_total_hr = dst3_total_hr + + $ (dst3_prev + dst3_curr) * dpsi_local / 2.0_r8 + psi_prev = psi_curr + epf_prev = epf_curr + epf_p_prev = epf_p_curr + epf_t_prev = epf_t_curr + epf_z_prev = epf_z_curr + dst_prev = dst_curr + dst1_prev = dst1_curr + dst2_prev = dst2_curr + dst3_prev = dst3_curr + dw_cum = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - + $ dst_int_total_k_hr) + WRITE(u_int,'(15(es17.8e3))') psi_curr, epf_curr, + $ REAL(dst_curr), AIMAG(dst_curr), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_curr, epf_t_curr, + $ epf_z_curr, REAL(dst1_curr), REAL(dst2_curr), + $ REAL(dst3_curr) ENDDO + dw_raw_k_hr = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - dst_int_total_k_hr) + dw_dcon_k_hr = dw_raw_k_hr * dcon_fac - DO ipsi = 0, mpsi - WRITE(u_int,'(7(es17.8e3))') rzphi%xs(ipsi), epf_psi(ipsi), - $ REAL(dst_psi_k(ipsi)), AIMAG(dst_psi_k(ipsi)), - $ epf_cumsum(ipsi), REAL(dst_cumsum_k(ipsi)), - $ AIMAG(dst_cumsum_k(ipsi)) - ENDDO WRITE(u_int,*) - -c Write integration results - WRITE(u_int,'(a)') "c Psi integration results (Trapezoidal rule)" - WRITE(u_int,'(a)') "c psi_min, psi_max, nominal_dpsi" - WRITE(u_int,'(3(es17.8e3))') psi_min, psi_max, dpsi - dw_raw_k = 0.5_r8 * - $ (CMPLX(epf_int_total, 0.0_r8, r8) - dst_int_total_k) - - WRITE(u_int,'(a)') "c Integration results:" - WRITE(u_int,'(3(es17.8e3))') epf_int_total, REAL(dst_int_total_k), - $ AIMAG(dst_int_total_k) - WRITE(u_int,'(a)') "c deltaW = 0.5 * (EPF - DST)" - WRITE(u_int,'(a)') "c raw_deltaW(K)" - WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k), AIMAG(dw_raw_k) - - WRITE(*,'(a)') "GPEC_RECON integration totals:" - WRITE(*,'(a,es17.8e3)') " EPF = ", epf_int_total - WRITE(*,'(a,2es17.8e3)') " DST(K) = ", - $ REAL(dst_int_total_k), AIMAG(dst_int_total_k) - WRITE(*,'(a,2es17.8e3)') " dW_raw(K)= ", - $ REAL(dw_raw_k), AIMAG(dw_raw_k) + WRITE(u_int,'(a)') "c Final psifac-grid integration results:" + WRITE(u_int,'(3(es17.8e3))') epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) + WRITE(u_int,'(a)') "c Final C2 component totals:" + WRITE(u_int,'(3(es17.8e3))') epf_p_total_hr, + $ epf_t_total_hr, epf_z_total_hr + WRITE(u_int,'(a)') "c Final K component totals:" + WRITE(u_int,'(3(es17.8e3))') REAL(dst1_total_hr), + $ REAL(dst2_total_hr), REAL(dst3_total_hr) + WRITE(u_int,'(a)') "c dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k_hr), + $ AIMAG(dw_raw_k_hr) + WRITE(u_int,'(a)') "c dW_dcon(K), normalized from dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_k_hr), + $ AIMAG(dw_dcon_k_hr) + + WRITE(*,'(a)') "GPEC_RECON final psifac-grid dW terms:" + WRITE(*,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(*,'(a,2es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) + WRITE(*,'(a,2es17.8e3)') " dW_gpec(K) = ", + $ REAL(dw_raw_k_hr), AIMAG(dw_raw_k_hr) + WRITE(*,'(a,2es17.8e3)') " dW_dcon(K) = ", + $ REAL(dw_dcon_k_hr), AIMAG(dw_dcon_k_hr) + WRITE(*,'(a)') "GPEC_RECON C2 component totals:" + WRITE(*,'(a,es17.8e3)') " C2_psi/mu0 = ", + $ epf_p_total_hr + WRITE(*,'(a,es17.8e3)') " C2_theta/mu0 = ", + $ epf_t_total_hr + WRITE(*,'(a,es17.8e3)') " C2_zeta/mu0 = ", + $ epf_z_total_hr + WRITE(*,'(a)') "GPEC_RECON K component totals:" + WRITE(*,'(a,2es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr), AIMAG(dst1_total_hr) + WRITE(*,'(a,2es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) + WRITE(*,'(a,2es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) WRITE(*,*) "GPEC ep(1):", REAL(ep(1)) WRITE(*,*) "DCON plasma1-equivalent:", REAL(ep(1))*(mu0*2.0) / $ psio**2 / (chi1*1e-3)**2 + + OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a)') " psifac-grid dW terms:" + WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(u_log,'(a,2es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) + WRITE(u_log,'(a,2es17.8e3)') " dW_gpec(K) = ", + $ REAL(dw_raw_k_hr), AIMAG(dw_raw_k_hr) + WRITE(u_log,'(a,2es17.8e3)') " dW_dcon(K) = ", + $ REAL(dw_dcon_k_hr), AIMAG(dw_dcon_k_hr) + WRITE(u_log,'(a)') " C2 component totals:" + WRITE(u_log,'(a,es17.8e3)') " C2_psi/mu0 = ", + $ epf_p_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_theta/mu0 = ", + $ epf_t_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_zeta/mu0 = ", + $ epf_z_total_hr + WRITE(u_log,'(a)') " K component totals:" + WRITE(u_log,'(a,2es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr), AIMAG(dst1_total_hr) + WRITE(u_log,'(a,2es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) + WRITE(u_log,'(a,2es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) + WRITE(u_log,'(a,es17.8e3)') " GPEC ep(1) = ", REAL(ep(1)) + WRITE(u_log,'(a,es17.8e3)') " DCON plasma1-equivalent = ", + $ REAL(ep(1))*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + WRITE(u_log,*) + CLOSE(u_log) CALL gpeq_dealloc CLOSE(ushear_fun) From 6b278321e525f1afc729769797e12d2c2f02b550 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 18 May 2026 16:41:51 +0900 Subject: [PATCH 120/305] GPEC - MINOR - Document recon input settings (cherry picked from commit 86eae46c88eed6990d6d8bdaf0232a3b7192fc64) --- input/gpec.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/input/gpec.in b/input/gpec.in index 44d90230e..d49015413 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -85,7 +85,8 @@ out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. verbose=t ! Print run log to terminal - recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983] + recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / &GPEC_DIAGNOSE timeit=f ! Print timer splits for major subroutines From f4ccfb8935dad08eab11c3da549ab2aca5a8b292 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:04:16 +0900 Subject: [PATCH 121/305] GPEC - MINOR - Update external perturbation flags in gpec.in (cherry picked from commit d8a5dff64d903bb39af9677f6e5c6ae1a0ec2b7c) --- docs/examples/DIIID_ideal_example/gpec.in | 31 ++++++++++------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/gpec.in b/docs/examples/DIIID_ideal_example/gpec.in index f1b422e23..99f9b9e12 100644 --- a/docs/examples/DIIID_ideal_example/gpec.in +++ b/docs/examples/DIIID_ideal_example/gpec.in @@ -11,7 +11,7 @@ tmag_in =0 ! True(1) if external perturbation has toroidal angle defined by jac_in. False(0) if machine angle. mthsurf =0 ! Number of poloidal angle gridpoints. Minimum of twice the Nyquist condition from DCON m limits is enforced. - coil_flag=f ! Calculate external perturbation from coils in coil.in + coil_flag=t ! Calculate external perturbation from coils in coil.in data_flag=f ! Apply perturbation from thrid party code output data_type="surfmn" ! Name of said code. Choose: surfmn,?? @@ -28,8 +28,8 @@ displacement_flag=f ! Perturbations are displacement (m). Default is field (T). fixed_boundary_flag=f ! Total perturbation = external perturbation - mode_flag = .T. ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) - mode = 1 ! DCON eigen function index (1 is least stable) + mode_flag = .F. ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) + mode = 0 ! DCON eigen function index (1 is least stable) filter_types = 'w' ! Isolate the filter_modes of energy (w), reluctance (r), permiability (p) or singular coupling (s) eigenvectors/singular-vectors filter_modes = 0 ! Number of modes isolated, negative values isolate backwards from the last mode / @@ -49,21 +49,21 @@ tmag_out=1 ! True(1) for magnetic toroidal angle outputs (affects singcoup and singfld, appends gpec_control) mlim_out=64 ! If positive, Fourier decomposed outputs will span -mlim_out<=m<=mlim_out. Default (<=0) outputs on internal m range. - resp_flag=f ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices - filter_flag=f ! Outputs energy, reluctance, pereability, and (optionally) the singular-coupling eigenmodes on the control surface - singcoup_flag=f ! Calculate coupling of each m to resonant surfaces - singfld_flag=f ! Output of resonant surface quantities (flux,current,etc.) + resp_flag=t ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices + filter_flag=t ! Outputs energy, reluctance, pereability, and (optionally) the singular-coupling eigenmodes on the control surface + singcoup_flag=t ! Calculate coupling of each m to resonant surfaces + singfld_flag=t ! Output of resonant surface quantities (flux,current,etc.) vsingfld_flag=f ! Output of vacuum resonant surface quantities - singthresh_flag=f ! Calculate Callen resonant field penetration threshold (requires kinetic profiles from pentrc.in) + singthresh_flag=t ! Calculate Callen resonant field penetration threshold (requires kinetic profiles from pentrc.in) pmodb_flag=f ! Outputs delta-B_Lagrangian - xbnormal_flag=f ! Outputs normal displacement and field in plasma + xbnormal_flag=t ! Outputs normal displacement and field in plasma vbnormal_flag=f ! Outputs vacuum normal displacement and field in plasma - xclebsch_flag=f ! Outputs clebsch coordinate displacement for PENT + xclebsch_flag=t ! Outputs clebsch coordinate displacement for PENT dw_flag=f ! Outputs self-consistent energy and torque profiles (when kin_flag=t from DCON) eqbrzphi_flag=f ! Outputs equilibrium field on regular (r,z) grid - brzphi_flag=f ! Outputs perturbed field on regular (r,z) grid + brzphi_flag=t ! Outputs perturbed field on regular (r,z) grid xrzphi_flag=f ! Outputs displacement on regular (r,z) grid vbrzphi_flag=f ! Outputs field on regular (r,z) grid due to total boundary surface current (not a real field) vvbrzphi_flag=f ! Outputs field on regular (r,z) grid due to exeternal perturbation surface current (not a real field) @@ -76,16 +76,13 @@ nz = 124 ! Number of vertical grid points in rzphi outputs fun_flag=t ! Write outputs to (r,z) in addition to default (psi,m) flux_flag=f ! Write outputs to (psi,theta) in addition to default (psi,m) - bin_flag=t ! Write binary outputs for use with xdraw - bin_2d_flag=t ! Write 2D binary outputs for use with xdraw + bin_flag=f ! Write binary outputs for use with xdraw + bin_2d_flag=f ! Write 2D binary outputs for use with xdraw out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. max_linesout=0 ! Maximum length of ascii data tables enforced by increasing radial step size (iff .gt. 0). verbose=t ! Print run log to terminal - - recon_flag=t - netcdf_flag=t / &GPEC_DIAGNOSE timeit=t ! Print timer splits for major subroutines radvar_flag=f ! Map various radial variables (rho,psi_tor) on psi_n grid -/ +/ \ No newline at end of file From 08a326b9c7b6a10cbc076443f35452f0384325cb Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:06:36 +0900 Subject: [PATCH 122/305] GPEC - FEATURE - Add configurable recon integration method (cherry picked from commit 7b8d4a254297fd849c651b0c211f3c2e759749fd) --- gpec/gpec.f | 10 ++- gpec/gpglobal.F | 2 +- gpec/gpout.f | 169 ++++++++++++++++++++++++++++++++++++------------ 3 files changed, 135 insertions(+), 46 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index 5ff5ab9eb..a64a0e8e4 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -70,7 +70,7 @@ PROGRAM gpec_main $ xclebsch_flag,pbrzphi_flag,verbose,max_linesout,filter_flag, $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, - $ singthresh_slayer_inpr,out_ahg2msc,recon_flag + $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -195,6 +195,7 @@ PROGRAM gpec_main eigm_flag=.FALSE. mutual_test_flag=.FALSE. recon_flag=.FALSE. + recon_int="spline" majr=10.0 minr=1.0 @@ -225,6 +226,12 @@ PROGRAM gpec_main READ(in_unit,NML=gpec_output) READ(in_unit,NML=gpec_diagnose) CALL ascii_close(in_unit) + SELECT CASE(TRIM(recon_int)) + CASE("spline","trapezoid") + CASE DEFAULT + CALL gpec_stop("gpec_output recon_int must be spline or "// + $ "trapezoid") + END SELECT galsol%gal_flag=gal_flag IF(timeit) CALL gpec_timer(0) @@ -837,4 +844,3 @@ PROGRAM gpec_main CALL gpec_dealloc CALL gpec_stop("Normal termination.") END PROGRAM gpec_main - diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 00ca516fb..b690bc51e 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -38,7 +38,7 @@ MODULE gpglobal_mod CHARACTER(2) :: sn,ss CHARACTER(10) :: date,time,zone - CHARACTER(16) :: jac_type,jac_in,jac_out,data_type + CHARACTER(16) :: jac_type,jac_in,jac_out,data_type,recon_int CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile,dcon_dir INTEGER, PARAMETER :: hmnum=128 diff --git a/gpec/gpout.f b/gpec/gpout.f index ebaf26619..04ea8e10b 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6897,7 +6897,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file REAL(r8) :: dpsi_local - INTEGER :: istep + INTEGER :: istep, nint_pts, iint REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr @@ -6907,6 +6907,11 @@ SUBROUTINE gpout_recon(mode, xspmn) $ dst3_prev, dst1_curr, dst2_curr, dst3_curr, $ dst_int_total_k_hr, dst1_total_hr, dst2_total_hr, $ dst3_total_hr, dw_raw_k_hr, dw_dcon_k_hr, dw_cum + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, epf_vals, + $ epf_p_vals, epf_t_vals, epf_z_vals + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dst_vals, dst1_vals, + $ dst2_vals, dst3_vals + TYPE(cspline_type) :: recon_epf_spl, recon_dst_spl c Diagnostic variables for C vector comparison INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 @@ -7127,13 +7132,29 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_int,'(a)') "c C2/mu0 = int J |C|^2 / mu0" WRITE(u_int,'(a)') "c Kxin2 = int J K |xi_n|^2" WRITE(u_int,'(a)') "c dW_gpec(K) = 0.5 * (C2/mu0 - Kxin2)" - dw_cum = CMPLX(0.0_r8, 0.0_r8, r8) - WRITE(u_int,'(15(es17.8e3))') psi_prev, epf_prev, - $ REAL(dst_prev), AIMAG(dst_prev), epf_int_total_hr, - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), - $ REAL(dw_cum), AIMAG(dw_cum), epf_p_prev, epf_t_prev, - $ epf_z_prev, REAL(dst1_prev), REAL(dst2_prev), - $ REAL(dst3_prev) + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + nint_pts = 1 + DO istep = 0, mstep-1 + psi_curr = psifac(istep+1) + IF (psi_curr < rzphi%xs(0)) CYCLE + IF (psi_curr > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + ENDDO + ALLOCATE(psi_int_pts(nint_pts), epf_vals(nint_pts), + $ epf_p_vals(nint_pts), epf_t_vals(nint_pts), + $ epf_z_vals(nint_pts), dst_vals(nint_pts), + $ dst1_vals(nint_pts), dst2_vals(nint_pts), + $ dst3_vals(nint_pts)) + psi_int_pts(1) = psi_prev + epf_vals(1) = epf_prev + epf_p_vals(1) = epf_p_prev + epf_t_vals(1) = epf_t_prev + epf_z_vals(1) = epf_z_prev + dst_vals(1) = dst_prev + dst1_vals(1) = dst1_prev + dst2_vals(1) = dst2_prev + dst3_vals(1) = dst3_prev + iint = 1 DO istep = 0, mstep-1 psi_curr = psifac(istep+1) IF (psi_curr < rzphi%xs(0)) CYCLE @@ -7142,42 +7163,104 @@ SUBROUTINE gpout_recon(mode, xspmn) $ epf_t_curr, epf_z_curr) CALL gpeq_dst(psi_curr, dst_curr, dst1_curr, dst2_curr, $ dst3_curr) - dpsi_local = psi_curr - psi_prev - epf_int_total_hr = epf_int_total_hr + - $ (epf_prev + epf_curr) * dpsi_local / 2.0_r8 - epf_p_total_hr = epf_p_total_hr + - $ (epf_p_prev + epf_p_curr) * dpsi_local / 2.0_r8 - epf_t_total_hr = epf_t_total_hr + - $ (epf_t_prev + epf_t_curr) * dpsi_local / 2.0_r8 - epf_z_total_hr = epf_z_total_hr + - $ (epf_z_prev + epf_z_curr) * dpsi_local / 2.0_r8 - dst_int_total_k_hr = dst_int_total_k_hr + - $ (dst_prev + dst_curr) * dpsi_local / 2.0_r8 - dst1_total_hr = dst1_total_hr + - $ (dst1_prev + dst1_curr) * dpsi_local / 2.0_r8 - dst2_total_hr = dst2_total_hr + - $ (dst2_prev + dst2_curr) * dpsi_local / 2.0_r8 - dst3_total_hr = dst3_total_hr + - $ (dst3_prev + dst3_curr) * dpsi_local / 2.0_r8 - psi_prev = psi_curr - epf_prev = epf_curr - epf_p_prev = epf_p_curr - epf_t_prev = epf_t_curr - epf_z_prev = epf_z_curr - dst_prev = dst_curr - dst1_prev = dst1_curr - dst2_prev = dst2_curr - dst3_prev = dst3_curr - dw_cum = 0.5_r8 * - $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - - $ dst_int_total_k_hr) - WRITE(u_int,'(15(es17.8e3))') psi_curr, epf_curr, - $ REAL(dst_curr), AIMAG(dst_curr), epf_int_total_hr, - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), - $ REAL(dw_cum), AIMAG(dw_cum), epf_p_curr, epf_t_curr, - $ epf_z_curr, REAL(dst1_curr), REAL(dst2_curr), - $ REAL(dst3_curr) + iint = iint + 1 + psi_int_pts(iint) = psi_curr + epf_vals(iint) = epf_curr + epf_p_vals(iint) = epf_p_curr + epf_t_vals(iint) = epf_t_curr + epf_z_vals(iint) = epf_z_curr + dst_vals(iint) = dst_curr + dst1_vals(iint) = dst1_curr + dst2_vals(iint) = dst2_curr + dst3_vals(iint) = dst3_curr ENDDO + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon_epf_spl, nint_pts-1, 4) + CALL cspline_alloc(recon_dst_spl, nint_pts-1, 4) + recon_epf_spl%xs = psi_int_pts + recon_dst_spl%xs = psi_int_pts + recon_epf_spl%fs(:,1) = CMPLX(epf_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,2) = CMPLX(epf_p_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,3) = CMPLX(epf_t_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,4) = CMPLX(epf_z_vals, 0.0_r8, r8) + recon_dst_spl%fs(:,1) = dst_vals + recon_dst_spl%fs(:,2) = dst1_vals + recon_dst_spl%fs(:,3) = dst2_vals + recon_dst_spl%fs(:,4) = dst3_vals + CALL cspline_fit(recon_epf_spl, "extrap") + CALL cspline_fit(recon_dst_spl, "extrap") + CALL cspline_int(recon_epf_spl) + CALL cspline_int(recon_dst_spl) + DO iint = 1, nint_pts + epf_int_total_hr = REAL(recon_epf_spl%fsi(iint-1,1)) + epf_p_total_hr = REAL(recon_epf_spl%fsi(iint-1,2)) + epf_t_total_hr = REAL(recon_epf_spl%fsi(iint-1,3)) + epf_z_total_hr = REAL(recon_epf_spl%fsi(iint-1,4)) + dst_int_total_k_hr = recon_dst_spl%fsi(iint-1,1) + dst1_total_hr = recon_dst_spl%fsi(iint-1,2) + dst2_total_hr = recon_dst_spl%fsi(iint-1,3) + dst3_total_hr = recon_dst_spl%fsi(iint-1,4) + dw_cum = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - + $ dst_int_total_k_hr) + WRITE(u_int,'(15(es17.8e3))') psi_int_pts(iint), + $ epf_vals(iint), REAL(dst_vals(iint)), + $ AIMAG(dst_vals(iint)), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(iint), + $ epf_t_vals(iint), epf_z_vals(iint), + $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), + $ REAL(dst3_vals(iint)) + ENDDO + CALL cspline_dealloc(recon_epf_spl) + CALL cspline_dealloc(recon_dst_spl) + ELSE + dw_cum = CMPLX(0.0_r8, 0.0_r8, r8) + WRITE(u_int,'(15(es17.8e3))') psi_int_pts(1), epf_vals(1), + $ REAL(dst_vals(1)), AIMAG(dst_vals(1)), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(1), epf_t_vals(1), + $ epf_z_vals(1), REAL(dst1_vals(1)), REAL(dst2_vals(1)), + $ REAL(dst3_vals(1)) + DO iint = 2, nint_pts + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + epf_int_total_hr = epf_int_total_hr + + $ (epf_vals(iint-1) + epf_vals(iint)) * dpsi_local / 2.0_r8 + epf_p_total_hr = epf_p_total_hr + + $ (epf_p_vals(iint-1) + epf_p_vals(iint)) * dpsi_local + $ / 2.0_r8 + epf_t_total_hr = epf_t_total_hr + + $ (epf_t_vals(iint-1) + epf_t_vals(iint)) * dpsi_local + $ / 2.0_r8 + epf_z_total_hr = epf_z_total_hr + + $ (epf_z_vals(iint-1) + epf_z_vals(iint)) * dpsi_local + $ / 2.0_r8 + dst_int_total_k_hr = dst_int_total_k_hr + + $ (dst_vals(iint-1) + dst_vals(iint)) * dpsi_local / 2.0_r8 + dst1_total_hr = dst1_total_hr + + $ (dst1_vals(iint-1) + dst1_vals(iint)) * + $ dpsi_local / 2.0_r8 + dst2_total_hr = dst2_total_hr + + $ (dst2_vals(iint-1) + dst2_vals(iint)) * + $ dpsi_local / 2.0_r8 + dst3_total_hr = dst3_total_hr + + $ (dst3_vals(iint-1) + dst3_vals(iint)) * + $ dpsi_local / 2.0_r8 + dw_cum = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - + $ dst_int_total_k_hr) + WRITE(u_int,'(15(es17.8e3))') psi_int_pts(iint), + $ epf_vals(iint), REAL(dst_vals(iint)), + $ AIMAG(dst_vals(iint)), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(iint), + $ epf_t_vals(iint), epf_z_vals(iint), + $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), + $ REAL(dst3_vals(iint)) + ENDDO + ENDIF + DEALLOCATE(psi_int_pts, epf_vals, epf_p_vals, epf_t_vals, + $ epf_z_vals, dst_vals, dst1_vals, dst2_vals, dst3_vals) dw_raw_k_hr = 0.5_r8 * $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - dst_int_total_k_hr) dw_dcon_k_hr = dw_raw_k_hr * dcon_fac From 0101e98bbb7ce2b49f6d63aff3bd70ef1d4e7fc7 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:06:43 +0900 Subject: [PATCH 123/305] GPEC - MINOR - Document recon integration option in input (cherry picked from commit a978489ae17e29317e064141f5f5c7455921f900) --- input/gpec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/input/gpec.in b/input/gpec.in index d49015413..8de3a6ce5 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -86,6 +86,7 @@ verbose=t ! Print run log to terminal recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / &GPEC_DIAGNOSE From a62982c9a7ec549875af53a2e7307b41fa2d4a75 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:15:15 +0900 Subject: [PATCH 124/305] GPEC - MINOR - Fix recon ep summary for selected mode (cherry picked from commit 7f5e53da975d2a3f358a34ddf3a20bce5e473564) --- gpec/gpout.f | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index 04ea8e10b..c5e69a240 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6897,8 +6897,8 @@ SUBROUTINE gpout_recon(mode, xspmn) CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file REAL(r8) :: dpsi_local - INTEGER :: istep, nint_pts, iint - REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr + INTEGER :: istep, nint_pts, iint, ep_index + REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr, ep_selected REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr REAL(r8) :: epf_int_total_hr, epf_p_total_hr, @@ -7012,6 +7012,12 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" + ep_index = 1 + IF (mode_flag) ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon selected mode is out of range") + ENDIF + ep_selected = REAL(ep(ep_index)) c----------------------------------------------------------------------- c main loop over all psi levels. c compute gpeq reconstruction diagnostics at each psi. @@ -7305,8 +7311,9 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(*,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) - WRITE(*,*) "GPEC ep(1):", REAL(ep(1)) - WRITE(*,*) "DCON plasma1-equivalent:", REAL(ep(1))*(mu0*2.0) / + WRITE(*,'(a,I8,a,es17.8e3)') "GPEC ep(", ep_index, "):", + $ ep_selected + WRITE(*,*) "DCON plasma1-equivalent:", ep_selected*(mu0*2.0) / $ psio**2 / (chi1*1e-3)**2 OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", @@ -7337,9 +7344,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(u_log,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) - WRITE(u_log,'(a,es17.8e3)') " GPEC ep(1) = ", REAL(ep(1)) + WRITE(u_log,'(a,I8,a,es17.8e3)') " GPEC ep(", ep_index, + $ ") = ", ep_selected WRITE(u_log,'(a,es17.8e3)') " DCON plasma1-equivalent = ", - $ REAL(ep(1))*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 WRITE(u_log,*) CLOSE(u_log) From 63ee5639be967bc4c69feda27003cc35d7827b51 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:18:29 +0900 Subject: [PATCH 125/305] GPEC - MINOR - Remove recon completion banner (cherry picked from commit b08b3d46ae13fccd1c80063a543b371b35e432fe) --- gpec/gpout.f | 8 -------- 1 file changed, 8 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index c5e69a240..c673e1175 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7365,14 +7365,6 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL ascii_close(ucv2_fun) CALL ascii_close(ucv2_mn) c----------------------------------------------------------------------- -c completion message. -c----------------------------------------------------------------------- - IF(verbose) WRITE(*,*) - $ "GPDIAG_RECON: Reconstruction diagnostics complete" - IF(verbose) WRITE(*,*)" C vectors computed and stored in"// - $ " cw*_mn(mpert, 0:mpsi)" - IF(verbose) WRITE(*,*)"__________________________________________" -c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- RETURN From cb85c360c9458d4ecf70bef8919a6b91556a45a3 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 12:38:03 +0900 Subject: [PATCH 126/305] GPEC - MINOR - Align recon ep summary labels (cherry picked from commit 31e6c4709235832decd83fa2f2d03857a89900d5) --- gpec/gpout.f | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index c673e1175..5fd540962 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7311,10 +7311,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(*,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) - WRITE(*,'(a,I8,a,es17.8e3)') "GPEC ep(", ep_index, "):", + WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", $ ep_selected - WRITE(*,*) "DCON plasma1-equivalent:", ep_selected*(mu0*2.0) / - $ psio**2 / (chi1*1e-3)**2 + WRITE(*,'(a,a,a,es17.8e3)') "DCON ep(", TRIM(smode), "):", + $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", $ POSITION="APPEND") @@ -7344,10 +7344,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(u_log,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) - WRITE(u_log,'(a,I8,a,es17.8e3)') " GPEC ep(", ep_index, + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), $ ") = ", ep_selected - WRITE(u_log,'(a,es17.8e3)') " DCON plasma1-equivalent = ", - $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 WRITE(u_log,*) CLOSE(u_log) From a22f2afbc340adb55ff7fe7305652b998eb75762 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 13:16:10 +0900 Subject: [PATCH 127/305] GPEC - MINOR - Document C verify identity in reconstruction note (cherry picked from commit 3137ed96abd5b7e8de3585a8dc49b4e81ab43953) --- docs/tex/recon/main.tex | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 8e0b33309..fba9ab731 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -1316,6 +1316,35 @@ \subsection{Reconstruction v1} \end{aligned} \end{equation} +With this definition, the reconstructed $\vec C$ should satisfy the identity +\begin{equation} +(\nabla\times \vec C)\cdot \nabla \psi += +\nabla\cdot\left(\vec C\times \nabla \psi\right) +=0, +\end{equation} +which is the relation referred to in the code as the \texttt{C verify} +check; see also the discussion of the Bernstein energy-principle form in +\cite{RevModPhys.76.1071}. The verification target is therefore the vanishing of +$ (\nabla\times \vec C)\cdot \nabla \psi $, or equivalently +$ \nabla\cdot(\vec C\times \nabla \psi) $. In the code, this same quantity +is evaluated through its flux-coordinate form +\begin{equation} +(\nabla\times \vec C)\cdot \nabla \psi += +\frac{1}{\mathcal{J}} +\left( +\frac{\partial C_\zeta}{\partial \theta} +- +\frac{\partial C_\theta}{\partial \zeta} +\right). +\end{equation} +Here $C_\theta$ and $C_\zeta$ are the covariant components in +$\vec C = C_\psi \nabla\psi + C_\theta \nabla\theta + C_\zeta \nabla\zeta$. +Therefore the present verification tests whether the reconstructed covariant +components satisfy this cancellation to numerical roundoff on each +reconstructed flux surface. + For direct reconstruction on the $(\psi,\theta)$ grid, the energy density is closed by the metric contraction \begin{equation} |\vec C|^2 From 2107a5493929face5c7acd0808fd9a1417ca7072 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 13:16:15 +0900 Subject: [PATCH 128/305] GPEC - FEATURE - Add optional C verify diagnostics for recon (cherry picked from commit 35edba064a672da0e570d5efa21d17360418d678) --- gpec/gpec.f | 7 +++++-- gpec/gpglobal.F | 3 ++- gpec/gpout.f | 50 +++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index a64a0e8e4..8b9149384 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -38,7 +38,8 @@ PROGRAM gpec_main $ singcurs_flag,m3d_flag,cas3d_flag,test_flag,nrzeq_flag, $ arzphifun_flag,xbrzphifun_flag,pmodbmn_flag,xclebsch_flag, $ filter_flag,gal_flag,delpsi_flag,recon_flag, - $ singthresh_callen_flag,singthresh_slayer_flag,singthresh_flag + $ singthresh_callen_flag,singthresh_slayer_flag, + $ singthresh_flag LOGICAL, DIMENSION(100) :: ss_flag COMPLEX(r8), DIMENSION(:), POINTER :: finmn,foutmn,xspmn, $ fxmn,fxfun @@ -70,7 +71,8 @@ PROGRAM gpec_main $ xclebsch_flag,pbrzphi_flag,verbose,max_linesout,filter_flag, $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, - $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int + $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, + $ cveri_flag NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -196,6 +198,7 @@ PROGRAM gpec_main mutual_test_flag=.FALSE. recon_flag=.FALSE. recon_int="spline" + cveri_flag=.FALSE. majr=10.0 minr=1.0 diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index b690bc51e..e3de10209 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -18,7 +18,8 @@ MODULE gpglobal_mod $ fun_flag,flux_flag,vsbrzphi_flag,displacement_flag, $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, - $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag + $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, + $ cveri_flag INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, diff --git a/gpec/gpout.f b/gpec/gpout.f index 5fd540962..ff8d18edc 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6903,6 +6903,8 @@ SUBROUTINE gpout_recon(mode, xspmn) REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr REAL(r8) :: epf_int_total_hr, epf_p_total_hr, $ epf_t_total_hr, epf_z_total_hr, dcon_fac + REAL(r8) :: cveri_abs, cveri_max, cveri_rms, cveri_sumsq, + $ cveri_tol COMPLEX(r8) :: dst_prev, dst_curr, dst1_prev, dst2_prev, $ dst3_prev, dst1_curr, dst2_curr, dst3_curr, $ dst_int_total_k_hr, dst1_total_hr, dst2_total_hr, @@ -6912,6 +6914,8 @@ SUBROUTINE gpout_recon(mode, xspmn) COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dst_vals, dst1_vals, $ dst2_vals, dst3_vals TYPE(cspline_type) :: recon_epf_spl, recon_dst_spl + INTEGER :: cveri_count + CHARACTER(4) :: cveri_stat c Diagnostic variables for C vector comparison INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 @@ -6967,7 +6971,9 @@ SUBROUTINE gpout_recon(mode, xspmn) OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") - OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + IF (cveri_flag) THEN + OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + ENDIF OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") OPEN(UNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") CALL ascii_open(ucw_fun, cw_fun_file, "UNKNOWN") @@ -6981,7 +6987,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "psi","theta","r","z","shear_re","shear_im" WRITE(ucurv,'(6(1x,a16))') $ "psi","theta","r","z","curv_re","curv_im" - WRITE(ucveri,'(6(1x,a16))') + IF (cveri_flag) WRITE(ucveri,'(6(1x,a16))') $ "psi","theta","r","z","cveri_re","cveri_im" WRITE(uk,'(9(1x,a16))') $ "psi","theta","r","z","K_re","K_im", @@ -7018,6 +7024,11 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL gpec_stop("gpout_recon selected mode is out of range") ENDIF ep_selected = REAL(ep(ep_index)) + cveri_max = 0.0_r8 + cveri_rms = 0.0_r8 + cveri_sumsq = 0.0_r8 + cveri_tol = 1.0e-10_r8 + cveri_count = 0 c----------------------------------------------------------------------- c main loop over all psi levels. c compute gpeq reconstruction diagnostics at each psi. @@ -7036,7 +7047,7 @@ SUBROUTINE gpout_recon(mode, xspmn) c Reconstruct detailed K diagnostics for output. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ sigma_vals, jdotb_vals, shear_fun, curv_fun) - CALL gpeq_cveri(psi, cveri_fun) + IF (cveri_flag) CALL gpeq_cveri(psi, cveri_fun) c Store spatial values with coordinates. DO itheta = 0, mthsurf @@ -7052,9 +7063,15 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), REAL(curv_fun(itheta)), $ AIMAG(curv_fun(itheta)) - WRITE(ucveri,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(cveri_fun(itheta)), - $ AIMAG(cveri_fun(itheta)) + IF (cveri_flag) WRITE(ucveri,'(6(es17.8e3))') psi, + $ theta(itheta), rvals(itheta), zvals(itheta), + $ REAL(cveri_fun(itheta)), AIMAG(cveri_fun(itheta)) + IF (cveri_flag) THEN + cveri_abs = ABS(cveri_fun(itheta)) + cveri_max = MAX(cveri_max, cveri_abs) + cveri_sumsq = cveri_sumsq + cveri_abs**2 + cveri_count = cveri_count + 1 + ENDIF WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), $ AIMAG(K_fun(itheta)), K_term1(itheta), K_term2(itheta), @@ -7066,7 +7083,7 @@ SUBROUTINE gpout_recon(mode, xspmn) ENDDO WRITE(ushear_fun,*) WRITE(ucurv,*) - WRITE(ucveri,*) + IF (cveri_flag) WRITE(ucveri,*) WRITE(uk,*) WRITE(uk_sigma,*) @@ -7311,6 +7328,18 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(*,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) + IF (cveri_flag) THEN + cveri_stat = "PASS" + IF (cveri_count > 0) THEN + cveri_rms = SQRT(cveri_sumsq / REAL(cveri_count, r8)) + ELSE + cveri_stat = "SKIP" + ENDIF + IF (cveri_max > cveri_tol) cveri_stat = "WARN" + WRITE(*,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') + $ "C verify Eq(99): ", TRIM(cveri_stat), " max=", + $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol + ENDIF WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') "DCON ep(", TRIM(smode), "):", @@ -7344,6 +7373,11 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) WRITE(u_log,'(a,2es17.8e3)') " K3_xin2 = ", $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) + IF (cveri_flag) THEN + WRITE(u_log,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') + $ " C verify Eq(99): ", TRIM(cveri_stat), " max=", + $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol + ENDIF WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), @@ -7354,7 +7388,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL gpeq_dealloc CLOSE(ushear_fun) CLOSE(ucurv) - CLOSE(ucveri) + IF (cveri_flag) CLOSE(ucveri) CLOSE(uk) CLOSE(uk_sigma) CLOSE(u_int) From bcbdd0a2b3800881847e1b5c03edccd7389f1098 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 13:16:21 +0900 Subject: [PATCH 129/305] GPEC - MINOR - Document cveri_flag in input (cherry picked from commit d16bd071c15db714293b8c301da5b48d36971c58) --- input/gpec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/input/gpec.in b/input/gpec.in index 8de3a6ce5..e13272fd1 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -86,6 +86,7 @@ verbose=t ! Print run log to terminal recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + cveri_flag=f ! Under recon_flag, verify Eq. (99) and write gpec_recon_cveri_sol*.out. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / From c9937729cfae80699ada58b1ac9bdc4fe8dcb28c Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 13:29:01 +0900 Subject: [PATCH 130/305] GPEC - MINOR - Refine C verify output wording (cherry picked from commit 3ccce21a4dd283f26a1ee3eec6787e9b8efa8a0c) --- gpec/gpout.f | 8 ++++++-- input/gpec.in | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index ff8d18edc..bd19dac8e 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7047,6 +7047,8 @@ SUBROUTINE gpout_recon(mode, xspmn) c Reconstruct detailed K diagnostics for output. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ sigma_vals, jdotb_vals, shear_fun, curv_fun) +c Verify (curl C).grad(psi)=0 using the flux-coordinate form +c (d_theta C_zeta - d_zeta C_theta) / J IF (cveri_flag) CALL gpeq_cveri(psi, cveri_fun) c Store spatial values with coordinates. @@ -7337,7 +7339,8 @@ SUBROUTINE gpout_recon(mode, xspmn) ENDIF IF (cveri_max > cveri_tol) cveri_stat = "WARN" WRITE(*,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') - $ "C verify Eq(99): ", TRIM(cveri_stat), " max=", + $ "C verify (curl C).grad(psi)=0: ", + $ TRIM(cveri_stat), " max=", $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol ENDIF WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", @@ -7375,7 +7378,8 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) IF (cveri_flag) THEN WRITE(u_log,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') - $ " C verify Eq(99): ", TRIM(cveri_stat), " max=", + $ " C verify (curl C).grad(psi)=0: ", + $ TRIM(cveri_stat), " max=", $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol ENDIF WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), diff --git a/input/gpec.in b/input/gpec.in index e13272fd1..95dc1e8eb 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -86,7 +86,7 @@ verbose=t ! Print run log to terminal recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. - cveri_flag=f ! Under recon_flag, verify Eq. (99) and write gpec_recon_cveri_sol*.out. + cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / From 219a7522cd5e0f9e035311b93efc5a0e9a9eca2f Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 13:39:50 +0900 Subject: [PATCH 131/305] GPEC - MINOR - Refine recon summary formatting (cherry picked from commit cc1882ad3f11b2cd73a705c8186fb562fe0f6edd) --- gpec/gpout.f | 88 ++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index bd19dac8e..48e6fecf5 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7307,15 +7307,6 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_k_hr), $ AIMAG(dw_dcon_k_hr) - WRITE(*,'(a)') "GPEC_RECON final psifac-grid dW terms:" - WRITE(*,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", - $ epf_int_total_hr - WRITE(*,'(a,2es17.8e3)') " int_J_K_xin2 = ", - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) - WRITE(*,'(a,2es17.8e3)') " dW_gpec(K) = ", - $ REAL(dw_raw_k_hr), AIMAG(dw_raw_k_hr) - WRITE(*,'(a,2es17.8e3)') " dW_dcon(K) = ", - $ REAL(dw_dcon_k_hr), AIMAG(dw_dcon_k_hr) WRITE(*,'(a)') "GPEC_RECON C2 component totals:" WRITE(*,'(a,es17.8e3)') " C2_psi/mu0 = ", $ epf_p_total_hr @@ -7324,12 +7315,25 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(*,'(a,es17.8e3)') " C2_zeta/mu0 = ", $ epf_z_total_hr WRITE(*,'(a)') "GPEC_RECON K component totals:" - WRITE(*,'(a,2es17.8e3)') " K1_xin2 = ", - $ REAL(dst1_total_hr), AIMAG(dst1_total_hr) - WRITE(*,'(a,2es17.8e3)') " K2_xin2 = ", - $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) - WRITE(*,'(a,2es17.8e3)') " K3_xin2 = ", - $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) + WRITE(*,'(a,es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr) + WRITE(*,'(a,es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr) + WRITE(*,'(a,es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr) + WRITE(*,'(a)') "GPEC_RECON final psifac-grid dW terms:" + WRITE(*,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(*,'(a,es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon) = ", + $ REAL(dw_raw_k_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon-normalize)= ", + $ REAL(dw_dcon_k_hr) + WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') "DCON ep(", TRIM(smode), "):", + $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 IF (cveri_flag) THEN cveri_stat = "PASS" IF (cveri_count > 0) THEN @@ -7338,30 +7342,17 @@ SUBROUTINE gpout_recon(mode, xspmn) cveri_stat = "SKIP" ENDIF IF (cveri_max > cveri_tol) cveri_stat = "WARN" - WRITE(*,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') - $ "C verify (curl C).grad(psi)=0: ", - $ TRIM(cveri_stat), " max=", - $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol + WRITE(*,'(a,a)') "GPEC_RECON C verify: ", TRIM(cveri_stat) + WRITE(*,'(a,es17.8e3)') " max = ", cveri_max + WRITE(*,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(*,'(a,es17.8e3)') " tol = ", cveri_tol ENDIF - WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", - $ ep_selected - WRITE(*,'(a,a,a,es17.8e3)') "DCON ep(", TRIM(smode), "):", - $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", $ POSITION="APPEND") WRITE(u_log,'(a)') "GPEC_RECON final results:" WRITE(u_log,'(a,I8)') " mode = ", mode WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag - WRITE(u_log,'(a)') " psifac-grid dW terms:" - WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", - $ epf_int_total_hr - WRITE(u_log,'(a,2es17.8e3)') " int_J_K_xin2 = ", - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) - WRITE(u_log,'(a,2es17.8e3)') " dW_gpec(K) = ", - $ REAL(dw_raw_k_hr), AIMAG(dw_raw_k_hr) - WRITE(u_log,'(a,2es17.8e3)') " dW_dcon(K) = ", - $ REAL(dw_dcon_k_hr), AIMAG(dw_dcon_k_hr) WRITE(u_log,'(a)') " C2 component totals:" WRITE(u_log,'(a,es17.8e3)') " C2_psi/mu0 = ", $ epf_p_total_hr @@ -7370,22 +7361,31 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_log,'(a,es17.8e3)') " C2_zeta/mu0 = ", $ epf_z_total_hr WRITE(u_log,'(a)') " K component totals:" - WRITE(u_log,'(a,2es17.8e3)') " K1_xin2 = ", - $ REAL(dst1_total_hr), AIMAG(dst1_total_hr) - WRITE(u_log,'(a,2es17.8e3)') " K2_xin2 = ", - $ REAL(dst2_total_hr), AIMAG(dst2_total_hr) - WRITE(u_log,'(a,2es17.8e3)') " K3_xin2 = ", - $ REAL(dst3_total_hr), AIMAG(dst3_total_hr) - IF (cveri_flag) THEN - WRITE(u_log,'(a,a,a,es17.8e3,a,es17.8e3,a,es17.8e3)') - $ " C verify (curl C).grad(psi)=0: ", - $ TRIM(cveri_stat), " max=", - $ cveri_max, " rms=", cveri_rms, " tol=", cveri_tol - ENDIF + WRITE(u_log,'(a,es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr) + WRITE(u_log,'(a)') " psifac-grid dW terms:" + WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(u_log,'(a,es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon) = ", + $ REAL(dw_raw_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon-normalize)= ", + $ REAL(dw_dcon_k_hr) WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), $ ") = ", ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + IF (cveri_flag) THEN + WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) + WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max + WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol + ENDIF WRITE(u_log,*) CLOSE(u_log) From 4afa45bb0fea4513b56f6baf54c19ccd3ab8534b Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 15:28:08 +0900 Subject: [PATCH 132/305] GPEC - MINOR - Add recon output control and reuse C verify state (cherry picked from commit 69c7f28c67e286e1a17dcac402120c2064ef71a1) --- gpec/gpec.f | 3 +- gpec/gpglobal.F | 2 +- gpec/gpout.f | 477 ++++++++++++++++++++++++++---------------------- 3 files changed, 258 insertions(+), 224 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index 8b9149384..a62dd4a9c 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -72,7 +72,7 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, - $ cveri_flag + $ cveri_flag,recon_out NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -199,6 +199,7 @@ PROGRAM gpec_main recon_flag=.FALSE. recon_int="spline" cveri_flag=.FALSE. + recon_out=.TRUE. majr=10.0 minr=1.0 diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index e3de10209..0654dff1d 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,7 +19,7 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag + $ cveri_flag,recon_out INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, diff --git a/gpec/gpout.f b/gpec/gpout.f index 48e6fecf5..ec4763c17 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -36,6 +36,7 @@ c----------------------------------------------------------------------- MODULE gpout_mod USE netcdf + USE gpglobal_mod, ONLY : recon_out USE gpresp_mod USE gpvacuum_mod USE gpdiag_mod @@ -6933,12 +6934,14 @@ SUBROUTINE gpout_recon(mode, xspmn) IF(verbose) WRITE(*,*)"__________________________________________" c----------------------------------------------------------------------- -c allocate bicubes for shear, curvature, K (placeholder allocation). -c actual bicube population should be done at each psi in gpeq. +c allocate bicubes for shear, curvature, K only when the recon +c output tables are requested. c----------------------------------------------------------------------- - ALLOCATE(gpout_shear) - ALLOCATE(gpout_curvature) - ALLOCATE(gpout_k) + IF (recon_out) THEN + ALLOCATE(gpout_shear) + ALLOCATE(gpout_curvature) + ALLOCATE(gpout_k) + ENDIF WRITE(smode,'(I8)') mode smode = ADJUSTL(smode) @@ -6969,55 +6972,57 @@ SUBROUTINE gpout_recon(mode, xspmn) u_int = 92 u_log = 93 - OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") - OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") - IF (cveri_flag) THEN - OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + IF (recon_out) THEN + OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") + OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") + IF (cveri_flag) THEN + OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + ENDIF + OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") + OPEN(UNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") + CALL ascii_open(ucw_fun, cw_fun_file, "UNKNOWN") + CALL ascii_open(ucw_mn, cw_mn_file, "UNKNOWN") + CALL ascii_open(ucv_fun, cv_fun_file, "UNKNOWN") + CALL ascii_open(ucv_mn, cv_mn_file, "UNKNOWN") + CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") + CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") + OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + WRITE(ushear_fun,'(6(1x,a16))') + $ "psi","theta","r","z","shear_re","shear_im" + WRITE(ucurv,'(6(1x,a16))') + $ "psi","theta","r","z","curv_re","curv_im" + IF (cveri_flag) WRITE(ucveri,'(6(1x,a16))') + $ "psi","theta","r","z","cveri_re","cveri_im" + WRITE(uk,'(9(1x,a16))') + $ "psi","theta","r","z","K_re","K_im", + $ "T1_re","T2_re","T3_re" + WRITE(uk_sigma,'(8(1x,a16))') + $ "psi","theta","r","z","sigma_re","jdotb_re", + $ "mu0_bsig2_re","mu0_sigjdotb" + WRITE(ucw_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cwp_re","cwp_im", + $ "cwt_re","cwt_im","cwz_re","cwz_im" + WRITE(ucw_mn,'(8(1x,a16))') + $ "psi","m","cwp_re","cwp_im","cwt_re","cwt_im", + $ "cwz_re","cwz_im" + WRITE(ucv_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cvp_re","cvp_im", + $ "cvt_re","cvt_im","cvz_re","cvz_im" + WRITE(ucv_mn,'(8(1x,a16))') + $ "psi","m","cvp_re","cvp_im","cvt_re","cvt_im", + $ "cvz_re","cvz_im" + WRITE(ucv2_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cv2p_re","cv2p_im", + $ "cv2t_re","cv2t_im","cv2z_re","cv2z_im" + WRITE(ucv2_mn,'(8(1x,a16))') + $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", + $ "cv2z_re","cv2z_im" + WRITE(u_int,'(15(1x,a16))') + $ "psi","c2_mu0","k_xin2_re","k_xin2_im", + $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", + $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", + $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" ENDIF - OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") - OPEN(UNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") - CALL ascii_open(ucw_fun, cw_fun_file, "UNKNOWN") - CALL ascii_open(ucw_mn, cw_mn_file, "UNKNOWN") - CALL ascii_open(ucv_fun, cv_fun_file, "UNKNOWN") - CALL ascii_open(ucv_mn, cv_mn_file, "UNKNOWN") - CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") - CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") - OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") - WRITE(ushear_fun,'(6(1x,a16))') - $ "psi","theta","r","z","shear_re","shear_im" - WRITE(ucurv,'(6(1x,a16))') - $ "psi","theta","r","z","curv_re","curv_im" - IF (cveri_flag) WRITE(ucveri,'(6(1x,a16))') - $ "psi","theta","r","z","cveri_re","cveri_im" - WRITE(uk,'(9(1x,a16))') - $ "psi","theta","r","z","K_re","K_im", - $ "T1_re","T2_re","T3_re" - WRITE(uk_sigma,'(8(1x,a16))') - $ "psi","theta","r","z","sigma_re","jdotb_re", - $ "mu0_bsig2_re","mu0_sigjdotb" - WRITE(ucw_fun,'(10(1x,a16))') - $ "psi","theta","r","z","cwp_re","cwp_im", - $ "cwt_re","cwt_im","cwz_re","cwz_im" - WRITE(ucw_mn,'(8(1x,a16))') - $ "psi","m","cwp_re","cwp_im","cwt_re","cwt_im", - $ "cwz_re","cwz_im" - WRITE(ucv_fun,'(10(1x,a16))') - $ "psi","theta","r","z","cvp_re","cvp_im", - $ "cvt_re","cvt_im","cvz_re","cvz_im" - WRITE(ucv_mn,'(8(1x,a16))') - $ "psi","m","cvp_re","cvp_im","cvt_re","cvt_im", - $ "cvz_re","cvz_im" - WRITE(ucv2_fun,'(10(1x,a16))') - $ "psi","theta","r","z","cv2p_re","cv2p_im", - $ "cv2t_re","cv2t_im","cv2z_re","cv2z_im" - WRITE(ucv2_mn,'(8(1x,a16))') - $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", - $ "cv2z_re","cv2z_im" - WRITE(u_int,'(15(1x,a16))') - $ "psi","c2_mu0","k_xin2_re","k_xin2_im", - $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", - $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", - $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" ep_index = 1 IF (mode_flag) ep_index = mode IF (ep_index < 1 .OR. ep_index > mpert) THEN @@ -7041,98 +7046,117 @@ SUBROUTINE gpout_recon(mode, xspmn) IF (psi > psilim) EXIT c Compute the J|C|^2/mu0 state once so C fields below use -c the current psi. +c the current psi. This keeps the psi-local reconstruction path +c identical to the original logic. CALL gpeq_epf(psi, epf_int) -c Reconstruct detailed K diagnostics for output. +c Reconstruct detailed K diagnostics for output. Keep this on +c the original path so recon terminal totals remain unchanged. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ sigma_vals, jdotb_vals, shear_fun, curv_fun) -c Verify (curl C).grad(psi)=0 using the flux-coordinate form -c (d_theta C_zeta - d_zeta C_theta) / J - IF (cveri_flag) CALL gpeq_cveri(psi, cveri_fun) - -c Store spatial values with coordinates. - DO itheta = 0, mthsurf - CALL bicube_eval(rzphi, psi, theta(itheta), 1) - rfac = SQRT(rzphi%f(1)) - eta = twopi*(theta(itheta) + rzphi%f(2)) - rvals(itheta) = ro + rfac*COS(eta) - zvals(itheta) = zo + rfac*SIN(eta) - - WRITE(ushear_fun,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(shear_fun(itheta)), - $ AIMAG(shear_fun(itheta)) - WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(curv_fun(itheta)), - $ AIMAG(curv_fun(itheta)) - IF (cveri_flag) WRITE(ucveri,'(6(es17.8e3))') psi, - $ theta(itheta), rvals(itheta), zvals(itheta), - $ REAL(cveri_fun(itheta)), AIMAG(cveri_fun(itheta)) - IF (cveri_flag) THEN +c Verify (curl C).grad(psi)=0 using the same psi-local C state +c already prepared by gpeq_epf. This avoids rebuilding the +c full equilibrium/C reconstruction a second time at each psi. + IF (cveri_flag) THEN + DO ipert = 1, mpert + curv_mn(ipert) = twopi * ifac * mfac(ipert) + $ * cvz_mn(ipert) + term_mn(ipert) = curv_mn(ipert) + + $ twopi * ifac * nn * cvt_mn(ipert) + ENDDO + CALL iscdftb(mfac, mpert, cveri_fun, mthsurf, term_mn) + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + cveri_fun(itheta) = cveri_fun(itheta) / jac cveri_abs = ABS(cveri_fun(itheta)) cveri_max = MAX(cveri_max, cveri_abs) cveri_sumsq = cveri_sumsq + cveri_abs**2 cveri_count = cveri_count + 1 - ENDIF - WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), - $ AIMAG(K_fun(itheta)), K_term1(itheta), K_term2(itheta), - $ K_term3(itheta) - WRITE(uk_sigma,'(8(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), sigma_vals(itheta), - $ jdotb_vals(itheta), K_term2(itheta), - $ mu0 * sigma_vals(itheta) * jdotb_vals(itheta) - ENDDO - WRITE(ushear_fun,*) - WRITE(ucurv,*) - IF (cveri_flag) WRITE(ucveri,*) - WRITE(uk,*) - WRITE(uk_sigma,*) + ENDDO + ENDIF + + IF (recon_out) THEN +c Store spatial values with coordinates only when recon file +c output is requested. + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + + WRITE(ushear_fun,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(shear_fun(itheta)), AIMAG(shear_fun(itheta)) + WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(curv_fun(itheta)), + $ AIMAG(curv_fun(itheta)) + IF (cveri_flag) WRITE(ucveri,'(6(es17.8e3))') psi, + $ theta(itheta), rvals(itheta), zvals(itheta), + $ REAL(cveri_fun(itheta)), AIMAG(cveri_fun(itheta)) + WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), + $ AIMAG(K_fun(itheta)), K_term1(itheta), + $ K_term2(itheta), K_term3(itheta) + WRITE(uk_sigma,'(8(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), sigma_vals(itheta), + $ jdotb_vals(itheta), K_term2(itheta), + $ mu0 * sigma_vals(itheta) * jdotb_vals(itheta) + ENDDO + WRITE(ushear_fun,*) + WRITE(ucurv,*) + IF (cveri_flag) WRITE(ucveri,*) + WRITE(uk,*) + WRITE(uk_sigma,*) -c Reconstruct C fields from the already computed C state. - CALL iscdftb(mfac, mpert, cw_fun(:,1), mthsurf, cwp_mn) - CALL iscdftb(mfac, mpert, cw_fun(:,2), mthsurf, cwt_mn) - CALL iscdftb(mfac, mpert, cw_fun(:,3), mthsurf, cwz_mn) - CALL iscdftb(mfac, mpert, cv_fun(:,1), mthsurf, cvp_mn) - CALL iscdftb(mfac, mpert, cv_fun(:,2), mthsurf, cvt_mn) - CALL iscdftb(mfac, mpert, cv_fun(:,3), mthsurf, cvz_mn) - CALL iscdftb(mfac, mpert, cv2_fun(:,1), mthsurf, c2vp_mn) - CALL iscdftb(mfac, mpert, cv2_fun(:,2), mthsurf, c2vt_mn) - CALL iscdftb(mfac, mpert, cv2_fun(:,3), mthsurf, c2vz_mn) - DO itheta = 0, mthsurf - WRITE(ucw_fun,'(10(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(cw_fun(itheta,1)), - $ AIMAG(cw_fun(itheta,1)), REAL(cw_fun(itheta,2)), - $ AIMAG(cw_fun(itheta,2)), REAL(cw_fun(itheta,3)), - $ AIMAG(cw_fun(itheta,3)) - WRITE(ucv_fun,'(10(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), REAL(cv_fun(itheta,1)), - $ AIMAG(cv_fun(itheta,1)), REAL(cv_fun(itheta,2)), - $ AIMAG(cv_fun(itheta,2)), REAL(cv_fun(itheta,3)), - $ AIMAG(cv_fun(itheta,3)) - WRITE(ucv2_fun,'(10(es17.8e3))') psi, theta(itheta), - $ rvals(itheta), zvals(itheta), - $ REAL(cv2_fun(itheta,1)), AIMAG(cv2_fun(itheta,1)), - $ REAL(cv2_fun(itheta,2)), AIMAG(cv2_fun(itheta,2)), - $ REAL(cv2_fun(itheta,3)), AIMAG(cv2_fun(itheta,3)) - ENDDO - DO ipert = 1, mpert - WRITE(ucw_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, - $ mfac(ipert), cwp_mn(ipert), cwt_mn(ipert), - $ cwz_mn(ipert) - WRITE(ucv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, - $ mfac(ipert), cvp_mn(ipert), cvt_mn(ipert), - $ cvz_mn(ipert) - WRITE(ucv2_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, - $ mfac(ipert), c2vp_mn(ipert), c2vt_mn(ipert), - $ c2vz_mn(ipert) - ENDDO - WRITE(ucw_fun,*) - WRITE(ucw_mn,*) - WRITE(ucv_fun,*) - WRITE(ucv_mn,*) - WRITE(ucv2_fun,*) - WRITE(ucv2_mn,*) +c Reconstruct C fields from the already computed C state only +c for recon output tables. + CALL iscdftb(mfac, mpert, cw_fun(:,1), mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,2), mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,3), mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,1), mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,2), mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,3), mthsurf, cvz_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,1), mthsurf, c2vp_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,2), mthsurf, c2vt_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,3), mthsurf, c2vz_mn) + DO itheta = 0, mthsurf + WRITE(ucw_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cw_fun(itheta,1)), AIMAG(cw_fun(itheta,1)), + $ REAL(cw_fun(itheta,2)), AIMAG(cw_fun(itheta,2)), + $ REAL(cw_fun(itheta,3)), AIMAG(cw_fun(itheta,3)) + WRITE(ucv_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cv_fun(itheta,1)), AIMAG(cv_fun(itheta,1)), + $ REAL(cv_fun(itheta,2)), AIMAG(cv_fun(itheta,2)), + $ REAL(cv_fun(itheta,3)), AIMAG(cv_fun(itheta,3)) + WRITE(ucv2_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cv2_fun(itheta,1)), AIMAG(cv2_fun(itheta,1)), + $ REAL(cv2_fun(itheta,2)), AIMAG(cv2_fun(itheta,2)), + $ REAL(cv2_fun(itheta,3)), AIMAG(cv2_fun(itheta,3)) + ENDDO + DO ipert = 1, mpert + WRITE(ucw_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cwp_mn(ipert), cwt_mn(ipert), + $ cwz_mn(ipert) + WRITE(ucv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cvp_mn(ipert), cvt_mn(ipert), + $ cvz_mn(ipert) + WRITE(ucv2_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), c2vp_mn(ipert), c2vt_mn(ipert), + $ c2vz_mn(ipert) + ENDDO + WRITE(ucw_fun,*) + WRITE(ucw_mn,*) + WRITE(ucv_fun,*) + WRITE(ucv_mn,*) + WRITE(ucv2_fun,*) + WRITE(ucv2_mn,*) + ENDIF ENDDO c----------------------------------------------------------------------- @@ -7153,11 +7177,13 @@ SUBROUTINE gpout_recon(mode, xspmn) $ epf_z_prev) CALL gpeq_dst(psi_prev, dst_prev, dst1_prev, dst2_prev, $ dst3_prev) - WRITE(u_int,'(a)') "c psifac-grid integration results" - WRITE(u_int,'(a)') "c C2/mu0 = int J |C|^2 / mu0" - WRITE(u_int,'(a)') "c Kxin2 = int J K |xi_n|^2" - WRITE(u_int,'(a)') "c dW_gpec(K) = 0.5 * (C2/mu0 - Kxin2)" - WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + IF (recon_out) THEN + WRITE(u_int,'(a)') "c psifac-grid integration results" + WRITE(u_int,'(a)') "c C2/mu0 = int J |C|^2 / mu0" + WRITE(u_int,'(a)') "c Kxin2 = int J K |xi_n|^2" + WRITE(u_int,'(a)') "c dW_gpec(K) = 0.5 * (C2/mu0 - Kxin2)" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF nint_pts = 1 DO istep = 0, mstep-1 psi_curr = psifac(istep+1) @@ -7228,12 +7254,12 @@ SUBROUTINE gpout_recon(mode, xspmn) dw_cum = 0.5_r8 * $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - $ dst_int_total_k_hr) - WRITE(u_int,'(15(es17.8e3))') psi_int_pts(iint), - $ epf_vals(iint), REAL(dst_vals(iint)), - $ AIMAG(dst_vals(iint)), epf_int_total_hr, - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), - $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(iint), - $ epf_t_vals(iint), epf_z_vals(iint), + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(iint), epf_vals(iint), + $ REAL(dst_vals(iint)), AIMAG(dst_vals(iint)), + $ epf_int_total_hr, REAL(dst_int_total_k_hr), + $ AIMAG(dst_int_total_k_hr), REAL(dw_cum), AIMAG(dw_cum), + $ epf_p_vals(iint), epf_t_vals(iint), epf_z_vals(iint), $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), $ REAL(dst3_vals(iint)) ENDDO @@ -7241,8 +7267,9 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL cspline_dealloc(recon_dst_spl) ELSE dw_cum = CMPLX(0.0_r8, 0.0_r8, r8) - WRITE(u_int,'(15(es17.8e3))') psi_int_pts(1), epf_vals(1), - $ REAL(dst_vals(1)), AIMAG(dst_vals(1)), epf_int_total_hr, + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(1), epf_vals(1), REAL(dst_vals(1)), + $ AIMAG(dst_vals(1)), epf_int_total_hr, $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(1), epf_t_vals(1), $ epf_z_vals(1), REAL(dst1_vals(1)), REAL(dst2_vals(1)), @@ -7274,12 +7301,12 @@ SUBROUTINE gpout_recon(mode, xspmn) dw_cum = 0.5_r8 * $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - $ dst_int_total_k_hr) - WRITE(u_int,'(15(es17.8e3))') psi_int_pts(iint), - $ epf_vals(iint), REAL(dst_vals(iint)), - $ AIMAG(dst_vals(iint)), epf_int_total_hr, - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), - $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(iint), - $ epf_t_vals(iint), epf_z_vals(iint), + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(iint), epf_vals(iint), + $ REAL(dst_vals(iint)), AIMAG(dst_vals(iint)), + $ epf_int_total_hr, REAL(dst_int_total_k_hr), + $ AIMAG(dst_int_total_k_hr), REAL(dw_cum), AIMAG(dw_cum), + $ epf_p_vals(iint), epf_t_vals(iint), epf_z_vals(iint), $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), $ REAL(dst3_vals(iint)) ENDDO @@ -7290,22 +7317,24 @@ SUBROUTINE gpout_recon(mode, xspmn) $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - dst_int_total_k_hr) dw_dcon_k_hr = dw_raw_k_hr * dcon_fac - WRITE(u_int,*) - WRITE(u_int,'(a)') "c Final psifac-grid integration results:" - WRITE(u_int,'(3(es17.8e3))') epf_int_total_hr, - $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) - WRITE(u_int,'(a)') "c Final C2 component totals:" - WRITE(u_int,'(3(es17.8e3))') epf_p_total_hr, - $ epf_t_total_hr, epf_z_total_hr - WRITE(u_int,'(a)') "c Final K component totals:" - WRITE(u_int,'(3(es17.8e3))') REAL(dst1_total_hr), - $ REAL(dst2_total_hr), REAL(dst3_total_hr) - WRITE(u_int,'(a)') "c dW_gpec(K)" - WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k_hr), - $ AIMAG(dw_raw_k_hr) - WRITE(u_int,'(a)') "c dW_dcon(K), normalized from dW_gpec(K)" - WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_k_hr), - $ AIMAG(dw_dcon_k_hr) + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final psifac-grid integration results:" + WRITE(u_int,'(3(es17.8e3))') epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) + WRITE(u_int,'(a)') "c Final C2 component totals:" + WRITE(u_int,'(3(es17.8e3))') epf_p_total_hr, + $ epf_t_total_hr, epf_z_total_hr + WRITE(u_int,'(a)') "c Final K component totals:" + WRITE(u_int,'(3(es17.8e3))') REAL(dst1_total_hr), + $ REAL(dst2_total_hr), REAL(dst3_total_hr) + WRITE(u_int,'(a)') "c dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k_hr), + $ AIMAG(dw_raw_k_hr) + WRITE(u_int,'(a)') "c dW_dcon(K), normalized from dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_k_hr), + $ AIMAG(dw_dcon_k_hr) + ENDIF WRITE(*,'(a)') "GPEC_RECON C2 component totals:" WRITE(*,'(a,es17.8e3)') " C2_psi/mu0 = ", @@ -7348,60 +7377,64 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(*,'(a,es17.8e3)') " tol = ", cveri_tol ENDIF - OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", - $ POSITION="APPEND") - WRITE(u_log,'(a)') "GPEC_RECON final results:" - WRITE(u_log,'(a,I8)') " mode = ", mode - WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag - WRITE(u_log,'(a)') " C2 component totals:" - WRITE(u_log,'(a,es17.8e3)') " C2_psi/mu0 = ", - $ epf_p_total_hr - WRITE(u_log,'(a,es17.8e3)') " C2_theta/mu0 = ", - $ epf_t_total_hr - WRITE(u_log,'(a,es17.8e3)') " C2_zeta/mu0 = ", - $ epf_z_total_hr - WRITE(u_log,'(a)') " K component totals:" - WRITE(u_log,'(a,es17.8e3)') " K1_xin2 = ", - $ REAL(dst1_total_hr) - WRITE(u_log,'(a,es17.8e3)') " K2_xin2 = ", - $ REAL(dst2_total_hr) - WRITE(u_log,'(a,es17.8e3)') " K3_xin2 = ", - $ REAL(dst3_total_hr) - WRITE(u_log,'(a)') " psifac-grid dW terms:" - WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", - $ epf_int_total_hr - WRITE(u_log,'(a,es17.8e3)') " int_J_K_xin2 = ", - $ REAL(dst_int_total_k_hr) - WRITE(u_log,'(a,es17.8e3)') " dW_p(recon) = ", - $ REAL(dw_raw_k_hr) - WRITE(u_log,'(a,es17.8e3)') " dW_p(recon-normalize)= ", - $ REAL(dw_dcon_k_hr) - WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), - $ ") = ", ep_selected - WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), - $ ") = ", ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 - IF (cveri_flag) THEN - WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) - WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max - WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms - WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol + IF (recon_out) THEN + OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a)') " C2 component totals:" + WRITE(u_log,'(a,es17.8e3)') " C2_psi/mu0 = ", + $ epf_p_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_theta/mu0 = ", + $ epf_t_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_zeta/mu0 = ", + $ epf_z_total_hr + WRITE(u_log,'(a)') " K component totals:" + WRITE(u_log,'(a,es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr) + WRITE(u_log,'(a)') " psifac-grid dW terms:" + WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(u_log,'(a,es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon) = ", + $ REAL(dw_raw_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon-normalize)= ", + $ REAL(dw_dcon_k_hr) + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + IF (cveri_flag) THEN + WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) + WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max + WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol + ENDIF + WRITE(u_log,*) + CLOSE(u_log) ENDIF - WRITE(u_log,*) - CLOSE(u_log) CALL gpeq_dealloc - CLOSE(ushear_fun) - CLOSE(ucurv) - IF (cveri_flag) CLOSE(ucveri) - CLOSE(uk) - CLOSE(uk_sigma) - CLOSE(u_int) - CALL ascii_close(ucw_fun) - CALL ascii_close(ucw_mn) - CALL ascii_close(ucv_fun) - CALL ascii_close(ucv_mn) - CALL ascii_close(ucv2_fun) - CALL ascii_close(ucv2_mn) + IF (recon_out) THEN + CLOSE(ushear_fun) + CLOSE(ucurv) + IF (cveri_flag) CLOSE(ucveri) + CLOSE(uk) + CLOSE(uk_sigma) + CLOSE(u_int) + CALL ascii_close(ucw_fun) + CALL ascii_close(ucw_mn) + CALL ascii_close(ucv_fun) + CALL ascii_close(ucv_mn) + CALL ascii_close(ucv2_fun) + CALL ascii_close(ucv2_mn) + ENDIF c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- From 648e6c3bef911e44f182c4986b61b425b32d7273 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 19 May 2026 15:28:23 +0900 Subject: [PATCH 133/305] GPEC - MINOR - Document recon_out input control (cherry picked from commit 6d2a766c38e6b3d1e1cb23cd2819cac0d42fd930) --- input/gpec.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/input/gpec.in b/input/gpec.in index 95dc1e8eb..cbae4a0a3 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -86,7 +86,8 @@ verbose=t ! Print run log to terminal recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. - cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. + cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. + recon_out=t ! Under recon_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / From d87ff4bc08fd320d5d32108bff96fa6225e8e9c6 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 26 May 2026 12:55:26 +0900 Subject: [PATCH 134/305] GPEC - RECON2 - 2026-05-22 add recon_flag2 entrypoint (cherry picked from commit a031559a51896a6db0fb7464d3b24bdd2996be81) --- gpec/gpec.f | 6 +++++- gpec/gpglobal.F | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index a62dd4a9c..ac7a8670a 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -72,7 +72,7 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, - $ cveri_flag,recon_out + $ cveri_flag,recon_out,recon_flag2 NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -197,6 +197,7 @@ PROGRAM gpec_main eigm_flag=.FALSE. mutual_test_flag=.FALSE. recon_flag=.FALSE. + recon_flag2=.FALSE. recon_int="spline" cveri_flag=.FALSE. recon_out=.TRUE. @@ -692,6 +693,9 @@ PROGRAM gpec_main IF (recon_flag) THEN CALL gpout_recon(mode,xspmn) ENDIF + IF (recon_flag2) THEN + CALL gpout_recon2(mode,xspmn) + ENDIF c----------------------------------------------------------------------- c diagnose. c----------------------------------------------------------------------- diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 0654dff1d..8a22772f4 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,7 +19,7 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag,recon_out + $ cveri_flag,recon_out,recon_flag2 INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, From 72bce24d8cad3c3045cc27c034820a3fd2da3685 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 26 May 2026 12:55:32 +0900 Subject: [PATCH 135/305] GPEC - RECON2 - 2026-05-25 add first-form energy kernel (cherry picked from commit bc56df6f208282225b2c8407b5435744f91c81a6) --- gpec/gpeq.f | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index b11f65105..775daeb9f 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -928,6 +928,140 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) RETURN END SUBROUTINE gpeq_cveri c----------------------------------------------------------------------- +c subprogram 9d. gpeq_firstform. +c evaluate the first-form plasma energy density components on one +c flux surface with gamma term omitted: +c +c |Q|^2 / mu0 +c j . (Q x xi*) / mu0 +c (div xi)* (xi . grad p) +c +c Here xi.grad p is evaluated directly as p'(psi) xi^psi, and +c div xi is evaluated directly from the Jacobian-weighted +c contravariant displacement: +c +c div xi = 1/J [ d(J xi^psi)/dpsi +c + d(J xi^theta)/dtheta +c + d(J xi^zeta)/dzeta ]. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_firstform(psi, q2_int, jqx_int, pdiv_int, + $ total_int, q2_fun, jqx_fun, pdiv_fun, total_fun) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: q2_int + COMPLEX(r8), INTENT(OUT) :: jqx_int, pdiv_int, total_int + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ q2_fun, jqx_fun, pdiv_fun, total_fun + + INTEGER :: itheta + REAL(r8) :: f1raw, p1 + REAL(r8) :: q2_density + REAL(r8) :: mu0jtheta, mu0jzeta + COMPLEX(r8) :: jqx_density, total_density, pdiv_density + COMPLEX(r8) :: det_term, divxi_density, xigradp_density + COMPLEX(r8), DIMENSION(0:mthsurf) :: xsp_fun, xmp1_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun, xmt_fun, xmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + TYPE(cspline_type) :: divspl + + IF(debug_flag) PRINT *, "Entering gpeq_firstform" +c----------------------------------------------------------------------- +c prepare psi-local perturbed equilibrium quantities. +c----------------------------------------------------------------------- + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1 = sq%f1(2) / mu0 + + CALL iscdftb(mfac, mpert, xsp_fun, mthsurf, xsp_mn) + CALL iscdftb(mfac, mpert, xmp1_fun, mthsurf, xmp1_mn) + CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) + CALL iscdftb(mfac, mpert, xmt_fun, mthsurf, xmt_mn) + CALL iscdftb(mfac, mpert, xmz_fun, mthsurf, xmz_mn) + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) + + CALL cspline_alloc(divspl, mthsurf, 2) + divspl%xs = theta + DO itheta = 0, mthsurf + divspl%fs(itheta,1) = xmt_fun(itheta) + divspl%fs(itheta,2) = xmz_fun(itheta) + ENDDO + CALL cspline_fit(divspl, "periodic") + + q2_int = 0.0_r8 + jqx_int = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_int = CMPLX(0.0_r8, 0.0_r8, r8) + total_int = CMPLX(0.0_r8, 0.0_r8, r8) + + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + CALL cspline_eval(divspl, theta(itheta), 1) + jac = rzphi%f(4) + jac1 = rzphi%fx(4) + +c pointwise contravariant current components: mu0 j^theta, +c mu0 j^zeta. These match the definitions already used in gpeq_c. + mu0jtheta = -f1raw / jac + mu0jzeta = -mu0 * p1 / chi1 - sq%f(4) * f1raw / jac + + q2_density = REAL(CONJG(bwp_fun(itheta)) * bvp_fun(itheta) + + $ CONJG(bmt_fun(itheta)) * bvt_fun(itheta) + + $ CONJG(bmz_fun(itheta)) * bvz_fun(itheta), r8) + $ / (mu0 * jac) + + det_term = -mu0jtheta * + $ (bwp_fun(itheta) * CONJG(xmz_fun(itheta)) - + $ bmz_fun(itheta) * CONJG(xwp_fun(itheta))) + + $ mu0jzeta * + $ (bwp_fun(itheta) * CONJG(xmt_fun(itheta)) - + $ bmt_fun(itheta) * CONJG(xwp_fun(itheta))) + jqx_density = det_term / (mu0 * jac) + + divxi_density = xmp1_fun(itheta) + + $ (jac1 / jac) * xsp_fun(itheta) + + $ divspl%f1(1) / jac - + $ (twopi * ifac * nn) * divspl%f(2) / jac + xigradp_density = p1 * xsp_fun(itheta) + pdiv_density = CONJG(divxi_density) * xigradp_density + total_density = CMPLX(q2_density, 0.0_r8, r8) - + $ jqx_density + pdiv_density + + IF (PRESENT(q2_fun)) q2_fun(itheta) = + $ CMPLX(q2_density, 0.0_r8, r8) + IF (PRESENT(jqx_fun)) jqx_fun(itheta) = jqx_density + IF (PRESENT(pdiv_fun)) pdiv_fun(itheta) = pdiv_density + IF (PRESENT(total_fun)) total_fun(itheta) = total_density + + IF (itheta < mthsurf) THEN + q2_int = q2_int + q2_density * jac / REAL(mthsurf, r8) + jqx_int = jqx_int + jqx_density * jac / REAL(mthsurf, r8) + pdiv_int = pdiv_int + pdiv_density * jac / + $ REAL(mthsurf, r8) + total_int = total_int + total_density * jac / + $ REAL(mthsurf, r8) + ENDIF + ENDDO + + CALL cspline_dealloc(divspl) + + IF(debug_flag) PRINT *, "->Leaving gpeq_firstform" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_firstform +c----------------------------------------------------------------------- c subprogram 9b. gpeq_epf. c compute the flux-surface integral of the first EPF kernel, c From a33f708c8de34b87580edaf9144049b75403d952 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 26 May 2026 12:55:40 +0900 Subject: [PATCH 136/305] GPEC - RECON2 - 2026-05-25 add first-form recon output (cherry picked from commit 98155120d31efb480585f50004b9ab74306d89e9) --- gpec/gpout.f | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/gpec/gpout.f b/gpec/gpout.f index ec4763c17..4881f5849 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -27,6 +27,7 @@ c 19. gpout_init_netcdf c 20. gpout_close_netcdf c 21. gpout_recon +c 22. gpout_recon2 c----------------------------------------------------------------------- c subprogram 0. gpout_mod. c module declarations. @@ -7440,5 +7441,241 @@ SUBROUTINE gpout_recon(mode, xspmn) c----------------------------------------------------------------------- RETURN END SUBROUTINE gpout_recon +c----------------------------------------------------------------------- +c subprogram 22. gpout_recon2 +c first-form plasma energy diagnostic based on +c +c |Q|^2 - j.(Q x xi*) + mu0 (div xi)* (xi.grad p) +c +c with the gamma p |div xi|^2 term intentionally omitted. +c The pressure-divergence contribution is evaluated directly from +c div xi and xi.grad p rather than closed as a Bernstein residual. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon2(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi, itheta, usurf, u_int, u_log + INTEGER :: istep, nint_pts, iint + REAL(r8) :: psi, eta, rfac, dpsi_local, q2_hr + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + COMPLEX(r8) :: jqx_hr, pdiv_hr, total_hr + COMPLEX(r8) :: jqx_cum, pdiv_cum, total_cum, dw_raw_hr, + $ dw_dcon_hr + COMPLEX(r8), DIMENSION(0:mthsurf) :: q2_fun, jqx_fun, pdiv_fun, + $ total_fun + CHARACTER(8) :: smode + CHARACTER(128) :: surf_file, int_file + REAL(r8) :: dcon_fac + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, q2_vals_r + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: q2_vals, jqx_vals, + $ pdiv_vals, total_vals + TYPE(cspline_type) :: recon2_spl + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON2: Starting first-form"// + $ " diagnostics" + IF(verbose) WRITE(*,*)"__________________________________________" + + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + surf_file = "gpec_recon2_terms_sol"//TRIM(smode)//".out" + int_file = "gpec_recon2_integration_sol"//TRIM(smode)//".out" + usurf = 94 + u_int = 95 + u_log = 96 + + IF (recon_out) THEN + OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + WRITE(usurf,'(12(1x,a16))') + $ "psi","theta","r","z","q2_re","q2_im","jqx_re", + $ "jqx_im","pdiv_re","pdiv_im","total_re","total_im" + WRITE(u_int,'(10(1x,a16))') + $ "psi","q2_re","q2_im","jqx_re","jqx_im","pdiv_re", + $ "pdiv_im","total_re","total_im","dw_re" + ENDIF + + CALL idcon_build(mode, xspmn) + CALL gpeq_alloc + + DO ipsi = 0, mpsi + psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT + CALL gpeq_firstform(psi, q2_hr, jqx_hr, pdiv_hr, total_hr, + $ q2_fun, jqx_fun, pdiv_fun, total_fun) + + IF (recon_out) THEN + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + WRITE(usurf,'(12(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(q2_fun(itheta)), + $ AIMAG(q2_fun(itheta)), REAL(jqx_fun(itheta)), + $ AIMAG(jqx_fun(itheta)), REAL(pdiv_fun(itheta)), + $ AIMAG(pdiv_fun(itheta)), REAL(total_fun(itheta)), + $ AIMAG(total_fun(itheta)) + ENDDO + WRITE(usurf,*) + ENDIF + ENDDO + + dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + q2_hr = 0.0_r8 + jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) + total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + + nint_pts = 0 + DO istep = 0, mstep + IF (psifac(istep) < rzphi%xs(0)) CYCLE + IF (psifac(istep) > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + ENDDO + ALLOCATE(psi_int_pts(nint_pts), q2_vals_r(nint_pts), + $ q2_vals(nint_pts), jqx_vals(nint_pts), pdiv_vals(nint_pts), + $ total_vals(nint_pts)) + iint = 0 + DO istep = 0, mstep + psi = psifac(istep) + IF (psi < rzphi%xs(0)) CYCLE + IF (psi > rzphi%xs(mpsi)) EXIT + CALL gpeq_firstform(psi, q2_hr, jqx_hr, pdiv_hr, total_hr, + $ q2_fun, jqx_fun, pdiv_fun, total_fun) + iint = iint + 1 + psi_int_pts(iint) = psi + q2_vals_r(iint) = q2_hr + q2_vals(iint) = CMPLX(q2_hr, 0.0_r8, r8) + jqx_vals(iint) = jqx_hr + pdiv_vals(iint) = pdiv_hr + total_vals(iint) = total_hr + ENDDO + + IF (recon_out) THEN + WRITE(u_int,'(a)') "c first-form plasma-energy terms" + WRITE(u_int,'(a)') "c total = q2 - jqx + pdiv" + WRITE(u_int,'(a)') "c pdiv term is computed directly" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF + + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon2_spl, nint_pts-1, 4) + recon2_spl%xs = psi_int_pts + recon2_spl%fs(:,1) = q2_vals + recon2_spl%fs(:,2) = jqx_vals + recon2_spl%fs(:,3) = pdiv_vals + recon2_spl%fs(:,4) = total_vals + CALL cspline_fit(recon2_spl, "extrap") + CALL cspline_int(recon2_spl) + DO iint = 1, nint_pts + q2_hr = REAL(recon2_spl%fsi(iint-1,1), r8) + jqx_hr = recon2_spl%fsi(iint-1,2) + pdiv_hr = recon2_spl%fsi(iint-1,3) + total_hr = recon2_spl%fsi(iint-1,4) + dw_raw_hr = 0.5_r8 * total_hr + IF (recon_out) WRITE(u_int,'(10(es17.8e3))') + $ psi_int_pts(iint), q2_vals_r(iint), 0.0_r8, + $ REAL(jqx_vals(iint)), AIMAG(jqx_vals(iint)), + $ REAL(pdiv_vals(iint)), AIMAG(pdiv_vals(iint)), + $ REAL(total_vals(iint)), AIMAG(total_vals(iint)), + $ REAL(dw_raw_hr) + ENDDO + CALL cspline_dealloc(recon2_spl) + ELSE + q2_hr = 0.0_r8 + jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) + total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + DO iint = 1, nint_pts + IF (iint > 1) THEN + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + q2_hr = q2_hr + (q2_vals_r(iint-1) + q2_vals_r(iint)) * + $ dpsi_local / 2.0_r8 + jqx_hr = jqx_hr + (jqx_vals(iint-1) + jqx_vals(iint)) * + $ dpsi_local / 2.0_r8 + pdiv_hr = pdiv_hr + (pdiv_vals(iint-1) + + $ pdiv_vals(iint)) * dpsi_local / 2.0_r8 + total_hr = total_hr + (total_vals(iint-1) + + $ total_vals(iint)) * dpsi_local / 2.0_r8 + ENDIF + dw_raw_hr = 0.5_r8 * total_hr + IF (recon_out) WRITE(u_int,'(10(es17.8e3))') + $ psi_int_pts(iint), q2_vals_r(iint), 0.0_r8, + $ REAL(jqx_vals(iint)), AIMAG(jqx_vals(iint)), + $ REAL(pdiv_vals(iint)), AIMAG(pdiv_vals(iint)), + $ REAL(total_vals(iint)), AIMAG(total_vals(iint)), + $ REAL(dw_raw_hr) + ENDDO + ENDIF + + dw_raw_hr = 0.5_r8 * total_hr + dw_dcon_hr = dw_raw_hr * dcon_fac + + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final first-form totals:" + WRITE(u_int,'(2(es17.8e3))') q2_hr, 0.0_r8 + WRITE(u_int,'(2(es17.8e3))') REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(u_int,'(2(es17.8e3))') REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(u_int,'(2(es17.8e3))') REAL(total_hr), AIMAG(total_hr) + WRITE(u_int,'(a)') "c dW_p(recon2)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_hr), AIMAG(dw_raw_hr) + WRITE(u_int,'(a)') "c dW_p(recon2-normalize)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_hr), + $ AIMAG(dw_dcon_hr) + ENDIF + + WRITE(*,'(a)') "GPEC_RECON2 final first-form totals:" + WRITE(*,'(a,es17.8e3)') " int_J_Q2_over_mu0 = ", q2_hr + WRITE(*,'(a,2es17.8e3)') " int_J_jQxixi_over_mu0= ", + $ REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(*,'(a,2es17.8e3)') " int_J_pdiv_term = ", + $ REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(*,'(a,2es17.8e3)') " int_J_total = ", + $ REAL(total_hr), AIMAG(total_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon2) = ", + $ REAL(dw_raw_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", + $ REAL(dw_dcon_hr) + + IF (recon_out) THEN + OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON2 final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a,es17.8e3)') " int_J_Q2_over_mu0 = ", q2_hr + WRITE(u_log,'(a,2es17.8e3)') " int_J_jQxixi_over_mu0= ", + $ REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(u_log,'(a,2es17.8e3)') " int_J_pdiv_term = ", + $ REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(u_log,'(a,2es17.8e3)') " int_J_total = ", + $ REAL(total_hr), AIMAG(total_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon2) = ", + $ REAL(dw_raw_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", + $ REAL(dw_dcon_hr) + WRITE(u_log,*) + CLOSE(u_log) + ENDIF + + DEALLOCATE(psi_int_pts, q2_vals_r, q2_vals, jqx_vals, pdiv_vals, + $ total_vals) + CALL gpeq_dealloc + IF (recon_out) THEN + CLOSE(usurf) + CLOSE(u_int) + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon2 END MODULE gpout_mod From bebefe5217a2dec5ea10f809c6ed5331a177596b Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 27 May 2026 11:37:11 +0900 Subject: [PATCH 137/305] GPEC - RECON2 - add template input flag (cherry picked from commit 8d4348d9e2d84204a02aeecc72225566d4fc459a) --- input/gpec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/input/gpec.in b/input/gpec.in index cbae4a0a3..0e4019a50 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -86,6 +86,7 @@ verbose=t ! Print run log to terminal recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under recon_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". From f9e6acfcb0a69cb06c97d4da763f5ffc47683f2b Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 27 May 2026 14:23:41 +0900 Subject: [PATCH 138/305] GPEC - WIP add recon xi input flags (cherry picked from commit 5450935ac6ddab8c840bc13d41cfd2ff684dece7) --- gpec/gpec.f | 5 ++++- gpec/gpglobal.F | 6 ++++-- input/gpec.in | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index ac7a8670a..4d0f904d3 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -72,7 +72,7 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, - $ cveri_flag,recon_out,recon_flag2 + $ cveri_flag,recon_out,recon_flag2,xi_flag,xifile NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -91,6 +91,7 @@ PROGRAM gpec_main dcon_dir="" ieqfile="psi_in.bin" idconfile="euler.bin" + xifile="" ivacuumfile="DEPRECATED" rdconfile="globalsol.bin" power_flag=.TRUE. @@ -198,6 +199,7 @@ PROGRAM gpec_main mutual_test_flag=.FALSE. recon_flag=.FALSE. recon_flag2=.FALSE. + xi_flag=.FALSE. recon_int="spline" cveri_flag=.FALSE. recon_out=.TRUE. @@ -309,6 +311,7 @@ PROGRAM gpec_main CALL setahgdir(dcon_dir) idconfile = TRIM(dcon_dir)//"/"//TRIM(idconfile) ieqfile = TRIM(dcon_dir)//"/"//TRIM(ieqfile) + IF (xifile /= "") xifile = TRIM(dcon_dir)//"/"//TRIM(xifile) ivacuumfile = TRIM(dcon_dir)//"/"//TRIM(ivacuumfile) rdconfile = TRIM(dcon_dir)//"/"//TRIM(rdconfile) ENDIF diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 8a22772f4..0d7366aa2 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,7 +19,8 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag,recon_out,recon_flag2 + $ cveri_flag,recon_out,recon_flag2, + $ xi_flag INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, @@ -40,7 +41,8 @@ MODULE gpglobal_mod CHARACTER(2) :: sn,ss CHARACTER(10) :: date,time,zone CHARACTER(16) :: jac_type,jac_in,jac_out,data_type,recon_int - CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile,dcon_dir + CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile + CHARACTER(128) :: dcon_dir,xifile INTEGER, PARAMETER :: hmnum=128 REAL(r8), PARAMETER :: gauss=0.0001 diff --git a/input/gpec.in b/input/gpec.in index 0e4019a50..e0c82d28f 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -85,8 +85,11 @@ out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. verbose=t ! Print run log to terminal + recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. + xi_flag=f ! Under recon workflows, reserve an external xi source file instead of using the default perturbation source. + xifile="" ! Optional external xi file path used when xi_flag is true.(file format should be same with euler.bin) cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under recon_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". From fdf3afdf4851c425976ff5c4b65186df038a7422 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 27 May 2026 17:19:26 +0900 Subject: [PATCH 139/305] DOCS - Remove DIIID ideal example Python scripts from repo (cherry picked from commit 99834407b9992070b76d01db17fe8950f31e8333) --- .../DIIID_ideal_example/compare_corrected.py | 37 --- .../DIIID_ideal_example/compare_correction.py | 42 --- .../DIIID_ideal_example/compare_with_Q.py | 48 --- .../DIIID_ideal_example/gpec_netcdf_viewer.py | 83 ----- docs/examples/DIIID_ideal_example/plot_K.py | 120 ------- .../plot_c2_reconstruction.py | 181 ---------- .../DIIID_ideal_example/plot_curvature.py | 134 -------- .../DIIID_ideal_example/plot_cvfun.py | 199 ----------- .../examples/DIIID_ideal_example/plot_cvmn.py | 216 ------------ .../plot_reconstruction.py | 311 ------------------ .../DIIID_ideal_example/plot_shear.py | 132 -------- .../DIIID_ideal_example/plot_xbrzphi.py | 114 ------- 12 files changed, 1617 deletions(-) delete mode 100644 docs/examples/DIIID_ideal_example/compare_corrected.py delete mode 100644 docs/examples/DIIID_ideal_example/compare_correction.py delete mode 100644 docs/examples/DIIID_ideal_example/compare_with_Q.py delete mode 100644 docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_K.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_curvature.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_cvfun.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_cvmn.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_reconstruction.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_shear.py delete mode 100644 docs/examples/DIIID_ideal_example/plot_xbrzphi.py diff --git a/docs/examples/DIIID_ideal_example/compare_corrected.py b/docs/examples/DIIID_ideal_example/compare_corrected.py deleted file mode 100644 index 86157ba05..000000000 --- a/docs/examples/DIIID_ideal_example/compare_corrected.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -import numpy as np - -# Load both methods -phys_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") -metric_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") - -print("=" * 80) -print("Sample comparison at different flux surfaces (theta=0)") -print("=" * 80) - -# Get unique psi values -psi_values = phys_data[:, 0] -unique_psi = np.unique(psi_values) - -for i in [0, len(unique_psi)//4, len(unique_psi)//2, 3*len(unique_psi)//4, -1]: - psi_val = unique_psi[i] - idx_phys = np.where((phys_data[:, 0] == psi_val) & (phys_data[:, 1] == 0))[0] - idx_metric = np.where((metric_data[:, 0] == psi_val) & (metric_data[:, 1] == 0))[0] - - if len(idx_phys) > 0 and len(idx_metric) > 0: - phys_row = phys_data[idx_phys[0]] - metric_row = metric_data[idx_metric[0]] - - c1p = phys_row[4] + 1j*phys_row[5] - c2p = phys_row[6] + 1j*phys_row[7] - c3p = phys_row[8] + 1j*phys_row[9] - - c1m = metric_row[4] + 1j*metric_row[5] - c2m = metric_row[6] + 1j*metric_row[7] - c3m = metric_row[8] + 1j*metric_row[9] - - print(f"\nψ = {psi_val:.6e}:") - print(f" Physics: C1={abs(c1p):.3e}, C2={abs(c2p):.3e}, C3={abs(c3p):.3e}") - print(f" Metric: C1={abs(c1m):.3e}, C2={abs(c2m):.3e}, C3={abs(c3m):.3e}") - if abs(c1m) > 1e-15: - print(f" Ratio C2_phy/C1_met = {abs(c2p)/abs(c1m):.4f}") diff --git a/docs/examples/DIIID_ideal_example/compare_correction.py b/docs/examples/DIIID_ideal_example/compare_correction.py deleted file mode 100644 index 4db179456..000000000 --- a/docs/examples/DIIID_ideal_example/compare_correction.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -import numpy as np - -# Load physics method -phys_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") -phys_c1 = phys_data[:, 4] + 1j * phys_data[:, 5] -phys_c2 = phys_data[:, 6] + 1j * phys_data[:, 7] -phys_c3 = phys_data[:, 8] + 1j * phys_data[:, 9] - -# Load metric method -metric_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") -metric_c1 = metric_data[:, 4] + 1j * metric_data[:, 5] -metric_c2 = metric_data[:, 6] + 1j * metric_data[:, 7] -metric_c3 = metric_data[:, 8] + 1j * metric_data[:, 9] - -print("=" * 70) -print("CORRECTION: sq.f1(2) and f1(3) already contain μ₀ and 2π respectively") -print("=" * 70) -print("\nUnderneath first flux surface (ψ = psi_low):") -print("\nPhysics Method Results:") -print(f" C1_physics = {phys_c1[0]:.6e}") -print(f" C2_physics = {phys_c2[0]:.6e}") -print(f" C3_physics = {phys_c3[0]:.6e}") - -print("\nMetric Method Results:") -print(f" C1_metric = {metric_c1[0]:.6e}") -print(f" C2_metric = {metric_c2[0]:.6e}") -print(f" C3_metric = {metric_c3[0]:.6e}") - -print("\n" + "=" * 70) -print("COMPARISON (Ratio of Physics / Metric):") -print("=" * 70) -c1_ratio = abs(phys_c1[0]) / abs(metric_c1[0]) -c2_ratio = abs(phys_c2[0]) / abs(metric_c2[0]) -c3_ratio = abs(phys_c3[0]) / abs(metric_c3[0]) - -print(f" C1_physics / C1_metric = {c1_ratio:.6f}") -print(f" C2_physics / C2_metric = {c2_ratio:.6f}") -print(f" C3_physics / C3_metric = {c3_ratio:.6f}") - -print(f"\n 2π ≈ {2 * 3.14159:.6f}") -print(f"\n✓ Physics method should now ≈ Metric method (within numerical error)") diff --git a/docs/examples/DIIID_ideal_example/compare_with_Q.py b/docs/examples/DIIID_ideal_example/compare_with_Q.py deleted file mode 100644 index b1c1f259e..000000000 --- a/docs/examples/DIIID_ideal_example/compare_with_Q.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import numpy as np - -# Load files -Q_data = np.loadtxt("gpec_recon_Qvec_sol1.out") -Ccov_data = np.loadtxt("gpec_recon_Ccov_sol1.out") -Cphy_data = np.loadtxt("gpec_recon_Ccontra_physics_sol1.out") -Cmet_data = np.loadtxt("gpec_recon_Ccontra_metric_sol1.out") - -print("=" * 100) -print("Q VECTOR vs C VECTOR COMPARISON AT FIRST POINT (psi=1e-4, theta=0)") -print("=" * 100) - -# First row (theta=0) -Q_row = Q_data[0] -Ccov_row = Ccov_data[0] -Cphy_row = Cphy_data[0] -Cmet_row = Cmet_data[0] - -print("\nQ VECTOR (from gpeq_epf):") -print(f" Q_psi = {Q_row[4]:.6e} + {Q_row[5]:.6e}i") -print(f" Q_theta = {Q_row[6]:.6e} + {Q_row[7]:.6e}i") - -print("\nC COVARIANT (before adding j×ξ term):") -print(f" C_psi = {Ccov_row[4]:.6e} + {Ccov_row[5]:.6e}i") -print(f" C_theta = {Ccov_row[6]:.6e} + {Ccov_row[7]:.6e}i") - -print("\nC CONTRAVARIANT (Physics Method = Q + j×ξ):") -print(f" C1_phy = {Cphy_row[4]:.6e} + {Cphy_row[5]:.6e}i") -print(f" C2_phy = {Cphy_row[6]:.6e} + {Cphy_row[7]:.6e}i") -print(f" C3_phy = {Cphy_row[8]:.6e} + {Cphy_row[9]:.6e}i") - -print("\nC CONTRAVARIANT (Metric Method = g_ij * C_j):") -print(f" C1_met = {Cmet_row[4]:.6e} + {Cmet_row[5]:.6e}i") -print(f" C2_met = {Cmet_row[6]:.6e} + {Cmet_row[7]:.6e}i") -print(f" C3_met = {Cmet_row[8]:.6e} + {Cmet_row[9]:.6e}i") - -print("\n" + "=" * 100) -print("KEY OBSERVATION:") -print("=" * 100) -Q_theta = Q_row[6] + 1j*Q_row[7] -C2_phy = Cphy_row[6] + 1j*Cphy_row[7] - -print(f"Q_theta = {Q_theta}") -print(f"C2_physics = {C2_phy}") -print(f"C2 == Q_theta? {np.abs(C2_phy - Q_theta) < 1e-10}") -print("\n👉 C2_physics이 Q_theta와 같거나 거의 같은가?") -print(" 만약 그렇다면, j×ξ term이 거의 0이라는 뜻!") diff --git a/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py b/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py deleted file mode 100644 index 64cced7ee..000000000 --- a/docs/examples/DIIID_ideal_example/gpec_netcdf_viewer.py +++ /dev/null @@ -1,83 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import xarray as xr -from pathlib import Path - -class GPECNetCDFViewer: - """GPEC NetCDF File Viewer and Plotter""" - - def __init__(self, filepath): - self.filepath = Path(filepath) - self.ds = None - self.load_file() - - def load_file(self): - try: - self.ds = xr.open_dataset(self.filepath) - print(f"✓ Loaded: {self.filepath.name}") - except Exception as e: - print(f"✗ Error: {e}") - - def plot_bkappaxi_perp(self): - """Plot Bkappaxi_perp in Fourier space""" - if 'Bkappaxi_perp' not in self.ds: - print("✗ 'Bkappaxi_perp' not found") - return - - data = self.ds['Bkappaxi_perp'].values - real_part = data[:, :, 0] - imag_part = data[:, :, 1] - - fig, axes = plt.subplots(1, 2, figsize=(14, 5)) - fig.suptitle('Bkappaxi_perp: Curvature Component (Fourier Space)', - fontsize=14, fontweight='bold') - - im1 = axes[0].contourf(real_part, levels=20, cmap='RdBu_r') - axes[0].set_title('Real Part') - plt.colorbar(im1, ax=axes[0]) - - im2 = axes[1].contourf(imag_part, levels=20, cmap='RdBu_r') - axes[1].set_title('Imaginary Part') - plt.colorbar(im2, ax=axes[1]) - - plt.tight_layout() - return fig - - def plot_bkappaxi_perp_fun(self): - """Plot Bkappaxi_perp_fun in function space""" - if 'Bkappaxi_perp_fun' not in self.ds: - print("✗ 'Bkappaxi_perp_fun' not found") - return - - data = self.ds['Bkappaxi_perp_fun'].values - real_part = data[:, :, 0] - imag_part = data[:, :, 1] - - fig, axes = plt.subplots(1, 2, figsize=(14, 5)) - fig.suptitle('Bkappaxi_perp_fun: Curvature Component (Function Space)', - fontsize=14, fontweight='bold') - - im1 = axes[0].contourf(real_part, levels=20, cmap='RdBu_r') - axes[0].set_title('Real Part') - plt.colorbar(im1, ax=axes[0]) - - im2 = axes[1].contourf(imag_part, levels=20, cmap='RdBu_r') - axes[1].set_title('Imaginary Part') - plt.colorbar(im2, ax=axes[1]) - - plt.tight_layout() - return fig - -# Use the viewer -if __name__ == "__main__": - filepath = "gpec_output_n1.nc" # 실제 파일명으로 변경 - viewer = GPECNetCDFViewer(filepath) - - if viewer.ds is not None: - fig1 = viewer.plot_bkappaxi_perp() - plt.savefig('bkappaxi_perp_fourier.png', dpi=150, bbox_inches='tight') - - fig2 = viewer.plot_bkappaxi_perp_fun() - plt.savefig('bkappaxi_perp_function.png', dpi=150, bbox_inches='tight') - - plt.show() diff --git a/docs/examples/DIIID_ideal_example/plot_K.py b/docs/examples/DIIID_ideal_example/plot_K.py deleted file mode 100644 index 39e046aed..000000000 --- a/docs/examples/DIIID_ideal_example/plot_K.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -""" -Plot K diagnostics on the r-z plane. - -Running this script produces two figures: -1. gpec_recon_k_sol1.png -2. gpec_recon_k-term_sol1.png -""" - -import glob -import os -import re -import sys - -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.colors import TwoSlopeNorm - - -def find_required_file(pattern): - """Find a reconstruction output file by wildcard pattern.""" - matches = sorted(glob.glob(pattern)) - if not matches: - print(f"Error: Could not find {pattern}") - sys.exit(1) - return matches[0] - - -def extract_suffix(path): - """Extract the solution suffix such as sol1 from the filename.""" - name = os.path.basename(path) - match = re.search(r"(sol\d+)\.out$", name) - if not match: - print(f"Error: Could not extract solution suffix from {name}") - sys.exit(1) - return match.group(1) - - -def make_norm(values): - """Build a diverging normalization centered at zero.""" - vmin_temp, vmax_temp = np.percentile(values, [2, 98]) - vmax = max(abs(vmin_temp), abs(vmax_temp)) - return TwoSlopeNorm(vmin=-vmax, vcenter=0.0, vmax=vmax) - - -def style_axis(ax, title): - """Apply common axis styling.""" - ax.set_xlabel("R (m)", fontsize=11) - ax.set_ylabel("Z (m)", fontsize=11) - ax.set_title(title, fontsize=12, fontweight="bold") - ax.grid(True, alpha=0.3) - ax.set_aspect("equal") - - -def scatter_panel(ax, r, z, values, title, cbar_label): - """Create a single scatter panel with symmetric color scaling.""" - scatter = ax.scatter( - r, - z, - c=values, - cmap="RdBu_r", - s=20, - alpha=0.6, - norm=make_norm(values), - ) - style_axis(ax, title) - cbar = plt.colorbar(scatter, ax=ax) - cbar.set_label(cbar_label, fontsize=10) - - -def main(): - """Main plotting routine.""" - print("=" * 70) - print("GPEC K Diagnostic Plotter") - print("=" * 70) - - k_file = find_required_file("gpec_recon_k_sol*.out") - suffix = extract_suffix(k_file) - - print(f"Reading K data from: {k_file}") - - k_data = np.loadtxt(k_file, skiprows=1) - - r = k_data[:, 2] - z = k_data[:, 3] - - k_total = k_data[:, 4] - t1 = k_data[:, 6] - t2 = k_data[:, 7] - t3 = k_data[:, 8] - - # Figure 1: total K - fig_k, ax_k = plt.subplots(figsize=(7, 8)) - scatter_panel(ax_k, r, z, k_total, "K (Total)", "K") - plt.tight_layout() - k_png = f"gpec_recon_k_{suffix}.png" - fig_k.savefig(k_png, dpi=150, bbox_inches="tight") - - # Figure 2: three terms in one row - fig_terms, axes = plt.subplots(1, 3, figsize=(18, 5.5)) - term_specs = [ - (t1, r"Term1: $|\nabla\psi_{\mathrm{dcon}}|^2 \sigma S_{\mathrm{dcon}}$", "T1"), - (t2, r"Term2: $B^2 \sigma^2$", "T2"), - (t3, r"Term3: $2 P' \kappa^\psi$", "T3"), - ] - - for ax, (values, title, label) in zip(axes, term_specs): - scatter_panel(ax, r, z, values, title, label) - - plt.tight_layout() - terms_png = f"gpec_recon_k-term_{suffix}.png" - fig_terms.savefig(terms_png, dpi=150, bbox_inches="tight") - - print("Saved:") - print(f" {k_png}") - print(f" {terms_png}") - - -if __name__ == "__main__": - main() diff --git a/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py b/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py deleted file mode 100644 index c67c74ae3..000000000 --- a/docs/examples/DIIID_ideal_example/plot_c2_reconstruction.py +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env python3 -""" -Plot C² reconstruction diagnostics in r-z plane. - -For each C² output file, creates a PNG with real and imaginary parts -displayed as scatter plots in the r-z grid. -Color scheme: red (positive) - white (0) - blue (negative) - -Usage: - python plot_c2_reconstruction.py -""" - -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.colors as mcolors -from matplotlib.colors import LinearSegmentedColormap -import glob -import os -import sys - - -def create_rwb_colormap(n_bins=256): - """ - Create a red-white-blue colormap. - - Parameters: - ----------- - n_bins : int - Number of bins in the colormap - - Returns: - -------- - cmap : LinearSegmentedColormap - Red-white-blue colormap (white at center for value 0) - """ - colors_list = ['red', 'white', 'blue'] - cmap = LinearSegmentedColormap.from_list('rwb', colors_list, N=n_bins) - return cmap - - -def plot_c2_reconstruction(filepath): - """ - Plot C² reconstruction in r-z plane. - - Creates a PNG with real and imaginary parts as subplots. - Each point is colored according to its C² value using red-white-blue - colormap where white represents zero. - - Parameters: - ----------- - filepath : str - Path to the C² output file - - Returns: - -------- - output_file : str - Path to the generated PNG file - """ - - try: - # Read the file - skip header line - data = np.loadtxt(filepath, skiprows=1) - - if data.size == 0: - print(f"Warning: {filepath} is empty") - return None - - # Handle case where file has only one data point - if data.ndim == 1: - data = data.reshape(1, -1) - - # Extract columns: psi, theta, r, z, C2_re, C2_im - psi = data[:, 0] - theta = data[:, 1] - r = data[:, 2] - z = data[:, 3] - c2_re = data[:, 4] - c2_im = data[:, 5] - - # Create red-white-blue colormap - cmap = create_rwb_colormap(256) - - # Calculate max absolute values for symmetric normalization - vmax_re = np.max(np.abs(c2_re)) - vmax_im = np.max(np.abs(c2_im)) - - # Use CenteredNorm with white at 0 - if vmax_re > 0: - norm_re = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_re) - else: - norm_re = mcolors.Normalize(vmin=-1, vmax=1) - - if vmax_im > 0: - norm_im = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_im) - else: - norm_im = mcolors.Normalize(vmin=-1, vmax=1) - - # Create figure with 2 subplots (real and imaginary) - fig, axes = plt.subplots(1, 2, figsize=(15, 6)) - - # Extract quantity name from filename - # e.g., gpec_recon_C2_cw_sol1.out -> C2_cw - basename = os.path.basename(filepath) - quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] - - # Plot real part - scatter_re = axes[0].scatter(r, z, c=c2_re, cmap=cmap, norm=norm_re, - s=30, alpha=0.8) - axes[0].set_xlabel('R (m)', fontsize=12, fontweight='bold') - axes[0].set_ylabel('Z (m)', fontsize=12, fontweight='bold') - axes[0].set_title(f'{quantity} - Real Part', fontsize=13, fontweight='bold') - axes[0].grid(True, alpha=0.3, linestyle='--') - axes[0].set_aspect('equal', adjustable='box') - - cbar_re = plt.colorbar(scatter_re, ax=axes[0], label='Value') - cbar_re.set_label(f'{quantity} (Re)', fontsize=11, fontweight='bold') - - # Plot imaginary part - scatter_im = axes[1].scatter(r, z, c=c2_im, cmap=cmap, norm=norm_im, - s=30, alpha=0.8) - axes[1].set_xlabel('R (m)', fontsize=12, fontweight='bold') - axes[1].set_ylabel('Z (m)', fontsize=12, fontweight='bold') - axes[1].set_title(f'{quantity} - Imaginary Part', fontsize=13, fontweight='bold') - axes[1].grid(True, alpha=0.3, linestyle='--') - axes[1].set_aspect('equal', adjustable='box') - - cbar_im = plt.colorbar(scatter_im, ax=axes[1], label='Value') - cbar_im.set_label(f'{quantity} (Im)', fontsize=11, fontweight='bold') - - # Add main title with statistics - num_points = len(r) - n_psi = len(np.unique(psi)) - fig.suptitle(f'{quantity} Reconstruction\n(n_psi={n_psi}, n_theta={num_points//n_psi if n_psi > 0 else 0})', - fontsize=14, fontweight='bold', y=1.00) - - plt.tight_layout() - - # Save as PNG - output_file = filepath.replace('.out', '.png') - plt.savefig(output_file, dpi=150, bbox_inches='tight') - print(f"✓ Saved: {output_file}") - plt.close() - - return output_file - - except Exception as e: - print(f"✗ Error processing {filepath}: {e}") - import traceback - traceback.print_exc() - return None - - -def main(): - """ - Main function: find and plot all C² output files. - """ - - # Find all C² output files - c2_files = sorted(glob.glob('gpec_recon_C2_*.out')) - - if not c2_files: - print("No C² output files found (gpec_recon_C2_*.out)") - print(f"Current directory: {os.getcwd()}") - return - - print(f"Found {len(c2_files)} C² output file(s)") - print("-" * 60) - - successful = 0 - for filepath in c2_files: - print(f"Processing: {filepath}") - output_file = plot_c2_reconstruction(filepath) - if output_file: - successful += 1 - - print("-" * 60) - print(f"Completed: {successful}/{len(c2_files)} files processed successfully") - - -if __name__ == '__main__': - main() diff --git a/docs/examples/DIIID_ideal_example/plot_curvature.py b/docs/examples/DIIID_ideal_example/plot_curvature.py deleted file mode 100644 index 3ddf2902a..000000000 --- a/docs/examples/DIIID_ideal_example/plot_curvature.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -""" -Plot curvature diagnostic quantities from GPEC reconstruction output. - -Reads spatial domain data: -- gpec_recon_curvature_sol*.out: Spatial domain (r-z plane) - -Produces single r-z plane plot with curvature values colored. -""" - -import glob -import sys - -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.colors import TwoSlopeNorm -from scipy.interpolate import griddata - - -def find_curvature_file(): - """Find curvature output file with wildcard matching.""" - curv_files = glob.glob("gpec_recon_curvature_sol*.out") - - if not curv_files: - print("Error: Could not find gpec_recon_curvature_sol*.out file") - sys.exit(1) - - return curv_files[0] - - -def read_curvature_data(): - """Read spatial curvature data from output file.""" - curv_file = find_curvature_file() - - print(f"Reading spatial data from: {curv_file}") - curv = np.loadtxt(curv_file, skiprows=1) - - psi = curv[:, 0] - theta = curv[:, 1] - r = curv[:, 2] - z = curv[:, 3] - curv_re = curv[:, 4] - curv_im = curv[:, 5] - - return psi, theta, r, z, curv_re, curv_im - - -def main(): - """Main plotting routine.""" - print("=" * 70) - print("GPEC Curvature Diagnostic Plotter (r-z plane only)") - print("=" * 70) - - psi, theta, r, z, curv_re, curv_im = read_curvature_data() - - unique_psi = np.unique(psi) - - print(f"Spatial grid: {len(unique_psi)} psi levels") - print(f"Total grid points: {len(r)}") - print( - f"Curvature range: min={np.min(curv_re):.4e}, max={np.max(curv_re):.4e}, " - f"imag_max={np.max(np.abs(curv_im)):.4e}" - ) - - fig, ax = plt.subplots(figsize=(10, 8)) - - # Use the same compression used by the shear plot so sign structure is visible. - curv_re_asinh = np.arcsinh(curv_re) - - r_min, r_max = np.min(r), np.max(r) - z_min, z_max = np.min(z), np.max(z) - - grid_r = np.linspace(r_min, r_max, 150) - grid_z = np.linspace(z_min, z_max, 150) - grid_r_mesh, grid_z_mesh = np.meshgrid(grid_r, grid_z) - - points = np.c_[r, z] - grid_curv = griddata( - points, curv_re_asinh, (grid_r_mesh, grid_z_mesh), - method="cubic", fill_value=np.nan - ) - - vmin, vmax = np.nanmin(grid_curv), np.nanmax(grid_curv) - norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax) - - ax.contourf( - grid_r_mesh, grid_z_mesh, grid_curv, levels=30, - cmap="RdBu_r", norm=norm - ) - - scatter = ax.scatter( - r, z, c=curv_re_asinh, cmap="RdBu_r", - norm=norm, s=15, alpha=0.6, edgecolors="none" - ) - - ax.contour( - grid_r_mesh, grid_z_mesh, grid_curv, levels=[0], - colors="black", linewidths=2 - ) - - ax.set_xlabel("R", fontsize=22, fontweight="bold") - ax.set_ylabel("Z", fontsize=22, fontweight="bold") - ax.set_title("Curvature (spatial) - r-z plane", fontsize=24, - fontweight="bold") - ax.set_aspect("equal", adjustable="box") - ax.grid(True, alpha=0.3, linestyle="--") - ax.tick_params(labelsize=16) - - r_ticks = np.linspace(r_min, r_max, 6) - ax.set_xticks(r_ticks) - ax.set_xticklabels([f"{rv:.3f}" for rv in r_ticks], fontsize=14) - - z_ticks = np.linspace(z_min, z_max, 6) - ax.set_yticks(z_ticks) - ax.set_yticklabels([f"{zv:.3f}" for zv in z_ticks], fontsize=14) - - cbar = plt.colorbar(scatter, ax=ax) - cbar.set_label("asinh(Curvature)", fontsize=18, fontweight="bold") - cbar.ax.tick_params(labelsize=14) - - cbar_ticks = np.linspace(vmin, vmax, 7) - cbar.set_ticks(cbar_ticks) - cbar.set_ticklabels([f"{val:.1e}" for val in cbar_ticks], fontsize=12) - - plt.tight_layout() - - output_file = "gpec_recon_curvature_rz.png" - print(f"\nSaving plot to: {output_file}") - plt.savefig(output_file, dpi=150, bbox_inches="tight") - print("Plot complete!") - - -if __name__ == "__main__": - main() diff --git a/docs/examples/DIIID_ideal_example/plot_cvfun.py b/docs/examples/DIIID_ideal_example/plot_cvfun.py deleted file mode 100644 index cbd948967..000000000 --- a/docs/examples/DIIID_ideal_example/plot_cvfun.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python -""" -Plot cvfun vs cv2fun on the R-Z plane and report fun-space errors. - -Outputs: - - gpec_recon_cvfun_solX.png - - gpec_recon_cv2fun_solX.png - - gpec_recon_cvdiff_solX.png -""" - -import glob -import os -import re -import sys - -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.colors import TwoSlopeNorm - - -def find_required_file(pattern): - matches = sorted(glob.glob(pattern)) - if not matches: - print(f"Error: Could not find {pattern}") - sys.exit(1) - return matches[0] - - -def extract_suffix(path): - name = os.path.basename(path) - match = re.search(r"(sol\d+)\.out$", name) - if not match: - print(f"Error: Could not extract solution suffix from {name}") - sys.exit(1) - return match.group(1) - - -def load_data(path): - data = np.loadtxt(path, skiprows=1) - if data.ndim == 1: - data = data[np.newaxis, :] - return data - - -def make_diverging_norm(values): - vmin_temp, vmax_temp = np.percentile(values, [2, 98]) - vmax = max(abs(vmin_temp), abs(vmax_temp), 1.0e-14) - return TwoSlopeNorm(vmin=-vmax, vcenter=0.0, vmax=vmax) - - -def scatter_component(ax, r, z, values, title, cbar_label): - scatter = ax.scatter( - r, - z, - c=values, - s=10, - cmap="RdBu_r", - alpha=0.75, - edgecolors="none", - norm=make_diverging_norm(values), - ) - ax.set_title(title, fontsize=11, fontweight="bold") - ax.set_xlabel("R (m)", fontsize=10) - ax.set_ylabel("Z (m)", fontsize=10) - ax.set_aspect("equal") - ax.grid(True, alpha=0.25) - cbar = plt.colorbar(scatter, ax=ax, fraction=0.046, pad=0.02) - cbar.set_label(cbar_label, fontsize=9) - return scatter - - -def make_six_panel_figure(r, z, values_by_comp, figure_title, output): - fig, axes = plt.subplots(3, 2, figsize=(13, 15)) - panel_order = [ - ("p_re", r"$C_\psi$ Re", "Re"), - ("p_im", r"$C_\psi$ Im", "Im"), - ("t_re", r"$C_\theta$ Re", "Re"), - ("t_im", r"$C_\theta$ Im", "Im"), - ("z_re", r"$C_\zeta$ Re", "Re"), - ("z_im", r"$C_\zeta$ Im", "Im"), - ] - for ax, (key, title, label) in zip(axes.ravel(), panel_order): - scatter_component( - ax, - r, - z, - values_by_comp[key], - f"{figure_title} {title}", - label, - ) - fig.suptitle(figure_title, fontsize=15, fontweight="bold") - fig.tight_layout(rect=[0, 0, 1, 0.95]) - fig.savefig(output, dpi=160, bbox_inches="tight") - plt.close(fig) - - -def symmetric_relative_l2(lhs, rhs): - denom = np.linalg.norm(lhs) + np.linalg.norm(rhs) - if denom == 0.0: - return 0.0 - return 2.0 * np.linalg.norm(lhs - rhs) / denom - - -def component_arrays(data): - return { - "p_re": data[:, 4], - "p_im": data[:, 5], - "t_re": data[:, 6], - "t_im": data[:, 7], - "z_re": data[:, 8], - "z_im": data[:, 9], - } - - -def main(): - cv_file = find_required_file("gpec_recon_cvfun_sol*.out") - cv2_file = find_required_file("gpec_recon_cv2fun_sol*.out") - suffix = extract_suffix(cv_file) - - print(f"Reading {os.path.basename(cv_file)}") - print(f"Reading {os.path.basename(cv2_file)}") - - cv_data = load_data(cv_file) - cv2_data = load_data(cv2_file) - - if cv_data.shape != cv2_data.shape or not np.allclose( - cv_data[:, :4], cv2_data[:, :4] - ): - print("Error: cvfun and cv2fun grids do not match.") - sys.exit(1) - - r = cv_data[:, 2] - z = cv_data[:, 3] - cv = component_arrays(cv_data) - cv2 = component_arrays(cv2_data) - panel_keys = ["p_re", "p_im", "t_re", "t_im", "z_re", "z_im"] - comp_labels = [("p", r"$C_\psi$"), ("t", r"$C_\theta$"), ("z", r"$C_\zeta$")] - - direct_values = {} - metric_values = {} - diff_values = {} - - print("") - print("Fun-space error summary:") - - for comp, latex_name in comp_labels: - for part in ["re", "im"]: - key = f"{comp}_{part}" - direct = cv[key] - metric = cv2[key] - diff = direct - metric - direct_values[key] = direct - metric_values[key] = metric - diff_values[key] = diff - - rel_l2 = symmetric_relative_l2(direct, metric) - peak = np.max(np.maximum(np.abs(direct), np.abs(metric))) - mask = np.maximum(np.abs(direct), np.abs(metric)) > 0.05 * peak - masked_rel_l2 = symmetric_relative_l2(direct[mask], metric[mask]) - abs_energy_frac = np.sum(diff[mask] ** 2) / max(np.sum(diff**2), 1.0e-30) - idx = np.argmax(np.abs(diff)) - - print( - f" cv{comp}_{part}: rel_L2 = {100*rel_l2:6.3f}%" - f" rel_L2(>5% peak) = {100*masked_rel_l2:6.3f}%" - f" mismatch energy in >5% peak region = {100*abs_energy_frac:6.2f}%" - ) - print( - f" max |Δ| = {abs(diff[idx]):.6e}" - f" at psi={cv_data[idx,0]:.6f}, theta={cv_data[idx,1]:.6f}," - f" R={cv_data[idx,2]:.6f}, Z={cv_data[idx,3]:.6f}" - ) - print( - f" direct = {direct[idx]:.6e}, metric = {metric[idx]:.6e}" - ) - - cv_png = f"gpec_recon_cvfun_{suffix}.png" - cv2_png = f"gpec_recon_cv2fun_{suffix}.png" - diff_png = f"gpec_recon_cvdiff_{suffix}.png" - - make_six_panel_figure( - r, z, direct_values, "Direct cvfun components", cv_png - ) - make_six_panel_figure( - r, z, metric_values, "Metric cv2fun components", cv2_png - ) - make_six_panel_figure( - r, z, diff_values, "Difference components (direct - metric)", diff_png - ) - - print("") - print("Saved:") - print(f" {cv_png}") - print(f" {cv2_png}") - print(f" {diff_png}") - - -if __name__ == "__main__": - main() diff --git a/docs/examples/DIIID_ideal_example/plot_cvmn.py b/docs/examples/DIIID_ideal_example/plot_cvmn.py deleted file mode 100644 index f68d962d1..000000000 --- a/docs/examples/DIIID_ideal_example/plot_cvmn.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python -""" -Plot reconstructed covariant C components in Fourier space. - -This script reads: - - gpec_recon_cvmn_sol*.out - - gpec_recon_cv2mn_sol*.out - -and writes: - - gpec_recon_cvmn.png - - gpec_recon_cv2mn.png - -Each PNG contains 6 subplots: - cvp_re, cvp_im, cvt_re, cvt_im, cvz_re, cvz_im -with psi on the x-axis and one line per m value. -""" - -import glob -import os -import sys - -import matplotlib.pyplot as plt -import numpy as np - - -def find_required_file(pattern): - matches = sorted(glob.glob(pattern)) - if not matches: - print(f"Error: Could not find {pattern}") - sys.exit(1) - return matches[0] - - -def load_mn_data(path): - data = np.loadtxt(path, skiprows=1) - if data.ndim == 1: - data = data[np.newaxis, :] - return data - - -def build_series(data): - psi_vals = np.unique(data[:, 0]) - m_vals = np.unique(data[:, 1].astype(int)) - series = {} - for m in m_vals: - mask = data[:, 1].astype(int) == m - block = data[mask] - order = np.argsort(block[:, 0]) - series[m] = block[order] - return psi_vals, m_vals, series - - -def style_axis(ax, title): - ax.set_title(title, fontsize=11, fontweight="bold") - ax.set_xlabel("psi", fontsize=10) - ax.grid(True, alpha=0.25, color="0.75", linewidth=0.6) - ax.set_facecolor("#fbfbfb") - - -def plot_family(data, titles, png_name, figure_title): - _, m_vals, series = build_series(data) - cmap = plt.colormaps["nipy_spectral"] - linestyles = ["-", "--", "-.", ":"] - - fig, axes = plt.subplots(3, 2, figsize=(14, 12), sharex=True) - axes = axes.ravel() - - for idx, ax in enumerate(axes): - col = idx + 2 - for i, m in enumerate(m_vals): - block = series[m] - color = cmap((i + 0.5) / max(len(m_vals), 1)) - ax.plot( - block[:, 0], - block[:, col], - color=color, - linewidth=1.6 if i % 5 else 2.1, - linestyle=linestyles[i % len(linestyles)], - alpha=0.95, - ) - style_axis(ax, titles[idx]) - - axes[0].set_ylabel("value", fontsize=10) - axes[1].set_ylabel("value", fontsize=10) - axes[2].set_ylabel("value", fontsize=10) - axes[3].set_ylabel("value", fontsize=10) - axes[4].set_ylabel("value", fontsize=10) - axes[5].set_ylabel("value", fontsize=10) - - # Add a compact colorbar-like legend using m-index colors. - sm = plt.cm.ScalarMappable( - cmap=cmap, norm=plt.Normalize(vmin=m_vals.min(), vmax=m_vals.max()) - ) - sm.set_array([]) - cbar = fig.colorbar(sm, ax=axes.tolist(), fraction=0.02, pad=0.02) - cbar.set_label("m", fontsize=10) - - fig.suptitle(figure_title, fontsize=14, fontweight="bold") - fig.tight_layout(rect=[0, 0, 0.96, 0.97]) - fig.savefig(png_name, dpi=160, bbox_inches="tight") - plt.close(fig) - - -def sort_rows(data): - return data[np.lexsort((data[:, 1], data[:, 0]))] - - -def symmetric_relative_l2(lhs, rhs): - denom = np.linalg.norm(lhs) + np.linalg.norm(rhs) - if denom == 0.0: - return 0.0 - return 2.0 * np.linalg.norm(lhs - rhs) / denom - - -def max_pointwise_relative(lhs, rhs): - scale = np.maximum(np.maximum(np.abs(lhs), np.abs(rhs)), 1.0e-14) - return np.max(np.abs(lhs - rhs) / scale) - - -def compare_families(cvmn_data, cv2mn_data): - lhs = sort_rows(cvmn_data) - rhs = sort_rows(cv2mn_data) - - if lhs.shape != rhs.shape: - raise ValueError( - f"Shape mismatch: cvmn {lhs.shape} vs cv2mn {rhs.shape}" - ) - - if not np.allclose(lhs[:, :2], rhs[:, :2], atol=0.0, rtol=0.0): - raise ValueError("psi/m grids do not match between cvmn and cv2mn.") - - labels = ["p", "t", "z"] - results = [] - all_lhs = [] - all_rhs = [] - - for i, label in enumerate(labels): - col = 2 + 2 * i - lhs_comp = lhs[:, col] + 1j * lhs[:, col + 1] - rhs_comp = rhs[:, col] + 1j * rhs[:, col + 1] - all_lhs.append(lhs_comp) - all_rhs.append(rhs_comp) - results.append( - ( - label, - symmetric_relative_l2(lhs_comp, rhs_comp), - max_pointwise_relative(lhs_comp, rhs_comp), - ) - ) - - all_lhs = np.concatenate(all_lhs) - all_rhs = np.concatenate(all_rhs) - overall = ( - symmetric_relative_l2(all_lhs, all_rhs), - max_pointwise_relative(all_lhs, all_rhs), - ) - return results, overall - - -def main(): - cvmn_file = find_required_file("gpec_recon_cvmn_sol*.out") - cv2mn_file = find_required_file("gpec_recon_cv2mn_sol*.out") - - print(f"Reading {os.path.basename(cvmn_file)}") - print(f"Reading {os.path.basename(cv2mn_file)}") - - cvmn_data = load_mn_data(cvmn_file) - cv2mn_data = load_mn_data(cv2mn_file) - component_results, overall = compare_families(cvmn_data, cv2mn_data) - - plot_family( - cvmn_data, - [ - "cvp_re", - "cvp_im", - "cvt_re", - "cvt_im", - "cvz_re", - "cvz_im", - ], - "gpec_recon_cvmn.png", - "Covariant C from direct route (mn)", - ) - - plot_family( - cv2mn_data, - [ - "cv2p_re", - "cv2p_im", - "cv2t_re", - "cv2t_im", - "cv2z_re", - "cv2z_im", - ], - "gpec_recon_cv2mn.png", - "Covariant C from metric route (mn)", - ) - - print("Saved:") - print(" gpec_recon_cvmn.png") - print(" gpec_recon_cv2mn.png") - print("") - print("Symmetric relative error between cvmn and cv2mn:") - for label, rel_l2, rel_max in component_results: - print( - f" cv{label}: rel_L2 = {100.0 * rel_l2:8.3f}%" - f" max_pointwise = {100.0 * rel_max:8.3f}%" - ) - print( - f" overall: rel_L2 = {100.0 * overall[0]:8.3f}%" - f" max_pointwise = {100.0 * overall[1]:8.3f}%" - ) - - -if __name__ == "__main__": - main() diff --git a/docs/examples/DIIID_ideal_example/plot_reconstruction.py b/docs/examples/DIIID_ideal_example/plot_reconstruction.py deleted file mode 100644 index 9f5ad08cd..000000000 --- a/docs/examples/DIIID_ideal_example/plot_reconstruction.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env python3 -""" -Plot reconstruction diagnostics in r-z plane. - -For each reconstruction output file, creates a PNG with real and imaginary parts -displayed as scatter plots in the r-z grid. -Color scheme: red (positive) - white (0) - blue (negative) - -Handles different file formats: -- Scalar real-only (5 columns): 1 subplot -- Scalar complex (6 columns): 2 subplots -- 2-component vectors (8 columns): 2 subplots -- 3-component vectors (10 columns): 3 subplots - -Usage: - python plot_reconstruction.py -""" - -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.colors as mcolors -from matplotlib.colors import LinearSegmentedColormap -import glob -import os - - -def create_rwb_colormap(n_bins=256): - """ - Create a red-white-blue colormap where white is centered at 0. - - Parameters: - ----------- - n_bins : int - Number of bins in the colormap - - Returns: - -------- - cmap : LinearSegmentedColormap - Red-white-blue colormap - """ - colors_list = ['red', 'white', 'blue'] - cmap = LinearSegmentedColormap.from_list('rwb', colors_list, N=n_bins) - return cmap - - -def get_component_names(filepath, n_components): - """ - Extract component names from file based on filename and number of components. - - Parameters: - ----------- - filepath : str - Path to the output file - n_components : int - Number of components (1, 2, or 3) - - Returns: - -------- - names : list - List of component names - """ - basename = os.path.basename(filepath) - quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] - - if n_components == 1: - return [quantity] - elif n_components == 2: - # For 2D vectors (like C_psi, C_theta) - if 'Ccov' in basename: - return ['ψ component (C_psi)', 'θ component (C_theta)'] - elif 'Qv' in basename: - return ['ψ component (Qv_psi)', 'θ component (Qv_theta)'] - elif 'Qw' in basename: - return ['ψ component (Qw_psi)', 'θ component (Qw_theta)'] - else: - return ['Component 1', 'Component 2'] - elif n_components == 3: - # For 3D vectors (like C1, C2, C3 or Psi, Theta, Zeta) - if 'Ccov' in basename: - return ['ψ component', 'θ component', 'ζ component'] - elif 'Qv' in basename: - return ['ψ component', 'θ component', 'ζ component'] - elif 'Qw' in basename: - return ['ψ component', 'θ component', 'ζ component'] - elif 'Ccontra' in basename: - return ['C¹ component', 'C² component', 'C³ component'] - else: - return [f'Component {i+1}' for i in range(n_components)] - else: - return [f'Component {i+1}' for i in range(n_components)] - - -def plot_reconstruction(filepath): - """ - Plot reconstruction data in r-z plane. - - Creates PNG files with real and imaginary parts as subplots. - - Parameters: - ----------- - filepath : str - Path to the reconstruction output file - - Returns: - -------- - output_files : list - List of generated PNG file paths - """ - - try: - # Read the file with error handling for inconsistent columns - with open(filepath, 'r') as f: - lines = f.readlines() - - # Skip header and filter out incomplete lines (blank lines, comment lines) - data_lines = [] - for line in lines[1:]: # Skip header - line = line.strip() - if not line or line.startswith('#'): # Skip blank lines - continue - # Count columns: split and filter empty strings - cols = [x for x in line.split() if x] - # Only keep lines with full column count. - # Supported formats: - # 5 cols = psi theta r z scalar_re - # 6 cols = psi theta r z scalar_re scalar_im - # 8 cols = psi theta r z vec2_re/im - # 10 cols = psi theta r z vec3_re/im - if len(cols) in [5, 6, 8, 10]: - data_lines.append(line) - - if not data_lines: - print(f"Warning: {filepath} has no valid data lines") - return [] - - # Convert to array - data_str = '\n'.join(data_lines) - from io import StringIO - data = np.loadtxt(StringIO(data_str)) - - if data.size == 0: - print(f"Warning: {filepath} is empty") - return [] - - # Handle case where file has only one data point - if data.ndim == 1: - data = data.reshape(1, -1) - - # Extract columns - psi = data[:, 0] - theta = data[:, 1] - r = data[:, 2] - z = data[:, 3] - - n_cols = data.shape[1] - scalar_real_only = (n_cols == 5) - - if scalar_real_only: - n_components = 1 - quantities = [{'re': data[:, 4], 'im': None}] - else: - n_data_cols = n_cols - 4 # Subtract psi, theta, r, z - n_components = n_data_cols // 2 # Each component has (re, im) - - # Extract quantities - quantities = [] - for i in range(n_components): - re_col = 4 + i * 2 - im_col = 4 + i * 2 + 1 - quantities.append({ - 're': data[:, re_col], - 'im': data[:, im_col] - }) - - # Get quantity name and component names - basename = os.path.basename(filepath) - quantity = basename.replace('gpec_recon_', '').replace('.out', '').rsplit('_sol', 1)[0] - comp_names = get_component_names(filepath, n_components) - - # Create red-white-blue colormap - cmap = create_rwb_colormap(256) - - if scalar_real_only: - fig, ax = plt.subplots(1, 1, figsize=(8, 7)) - axes = np.array([[ax]]) - else: - # Create figure with subplots (n_components rows x 2 columns for re/im) - n_rows = n_components - fig, axes = plt.subplots(n_rows, 2, figsize=(15, 6*n_rows)) - - # Handle single row case (axes not 2D) - if n_rows == 1: - axes = axes.reshape(1, -1) - - output_files = [] - - # Plot each component - for i, (comp_data, comp_name) in enumerate(zip(quantities, comp_names)): - c2_re = comp_data['re'] - c2_im = comp_data['im'] - - # Calculate max absolute values for symmetric normalization - vmax_re = np.max(np.abs(c2_re)) - if c2_im is not None: - vmax_im = np.max(np.abs(c2_im)) - - # Use CenteredNorm with white at 0 - if vmax_re > 0: - norm_re = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_re) - else: - norm_re = mcolors.Normalize(vmin=-1, vmax=1) - - # Plot real part - scatter_re = axes[i, 0].scatter( - r, z, c=c2_re, cmap=cmap, norm=norm_re, s=30, alpha=0.8 - ) - axes[i, 0].set_xlabel('R (m)', fontsize=11, fontweight='bold') - axes[i, 0].set_ylabel('Z (m)', fontsize=11, fontweight='bold') - real_title = f'{quantity} - {comp_name}' if scalar_real_only \ - else f'{quantity} - {comp_name} (Real)' - axes[i, 0].set_title(real_title, fontsize=12, fontweight='bold') - axes[i, 0].grid(True, alpha=0.3, linestyle='--') - axes[i, 0].set_aspect('equal', adjustable='box') - - cbar_re = plt.colorbar(scatter_re, ax=axes[i, 0]) - cbar_re.set_label('Value', fontsize=10, fontweight='bold') - - if c2_im is not None: - if vmax_im > 0: - norm_im = mcolors.CenteredNorm(vcenter=0, halfrange=vmax_im) - else: - norm_im = mcolors.Normalize(vmin=-1, vmax=1) - - scatter_im = axes[i, 1].scatter( - r, z, c=c2_im, cmap=cmap, norm=norm_im, s=30, alpha=0.8 - ) - axes[i, 1].set_xlabel('R (m)', fontsize=11, fontweight='bold') - axes[i, 1].set_ylabel('Z (m)', fontsize=11, fontweight='bold') - axes[i, 1].set_title( - f'{quantity} - {comp_name} (Imaginary)', - fontsize=12, - fontweight='bold' - ) - axes[i, 1].grid(True, alpha=0.3, linestyle='--') - axes[i, 1].set_aspect('equal', adjustable='box') - - cbar_im = plt.colorbar(scatter_im, ax=axes[i, 1]) - cbar_im.set_label('Value', fontsize=10, fontweight='bold') - - # Add main title with statistics - num_points = len(r) - n_psi = len(np.unique(psi)) - n_theta = num_points // n_psi if n_psi > 0 else 0 - - fig.suptitle(f'{quantity} Reconstruction (n_psi={n_psi}, n_theta={n_theta})', - fontsize=14, fontweight='bold', y=0.995) - - plt.tight_layout() - - # Save as PNG - output_file = filepath.replace('.out', '.png') - plt.savefig(output_file, dpi=150, bbox_inches='tight') - print(f"✓ Saved: {output_file}") - plt.close() - - output_files.append(output_file) - return output_files - - except Exception as e: - print(f"✗ Error processing {filepath}: {e}") - import traceback - traceback.print_exc() - return [] - - -def main(): - """ - Main function: find and plot all reconstruction output files. - """ - - # Find all reconstruction output files (excluding C2 which already has its own script) - recon_files = sorted(glob.glob('gpec_recon_*.out')) - - # Filter out C2 files (they have their own dedicated script) - recon_files = [f for f in recon_files if 'C2_' not in f] - - if not recon_files: - print("No reconstruction output files found") - print(f"Current directory: {os.getcwd()}") - return - - print(f"Found {len(recon_files)} reconstruction output file(s)") - print("-" * 70) - - successful = 0 - total_files = 0 - - for filepath in recon_files: - print(f"Processing: {filepath}") - output_files = plot_reconstruction(filepath) - if output_files: - successful += 1 - total_files += len(output_files) - - print("-" * 70) - print(f"Completed: {successful}/{len(recon_files)} files processed successfully") - print(f"Generated {total_files} PNG file(s)") - - -if __name__ == '__main__': - main() diff --git a/docs/examples/DIIID_ideal_example/plot_shear.py b/docs/examples/DIIID_ideal_example/plot_shear.py deleted file mode 100644 index 22bc94ca0..000000000 --- a/docs/examples/DIIID_ideal_example/plot_shear.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -""" -Plot shear diagnostic quantities from GPEC reconstruction output. - -Reads spatial domain data: -- gpec_recon_shear_fun_sol*.out: Spatial domain (r-z plane) - -Produces single r-z plane plot with shear values colored. -""" - -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.colors import TwoSlopeNorm -from scipy.interpolate import griddata -import sys -import glob - -def find_shear_fun_file(): - """Find shear_fun output file with wildcard matching.""" - fun_files = glob.glob('gpec_recon_shear_fun_sol*.out') - - if not fun_files: - print("Error: Could not find gpec_recon_shear_fun_sol*.out file") - sys.exit(1) - - return fun_files[0] - -def read_shear_fun_data(): - """Read spatial shear data from output file.""" - fun_file = find_shear_fun_file() - - print(f"Reading spatial data from: {fun_file}") - shear_fun = np.loadtxt(fun_file, skiprows=1) - - # Parse spatial data - psi_fun = shear_fun[:, 0] - theta_fun = shear_fun[:, 1] - r_fun = shear_fun[:, 2] - z_fun = shear_fun[:, 3] - shear_re_fun = shear_fun[:, 4] - shear_im_fun = shear_fun[:, 5] - - return psi_fun, theta_fun, r_fun, z_fun, shear_re_fun, shear_im_fun - -def main(): - """Main plotting routine.""" - print("=" * 70) - print("GPEC Shear Diagnostic Plotter (r-z plane only)") - print("=" * 70) - - # Read data - psi_fun, theta_fun, r_fun, z_fun, shear_re_fun, shear_im_fun = read_shear_fun_data() - - # Get unique values - unique_psi_fun = np.unique(psi_fun) - - print(f"Spatial grid: {len(unique_psi_fun)} psi levels") - print(f"Total grid points: {len(r_fun)}") - - # Create figure with single r-z plane plot - fig, ax = plt.subplots(figsize=(10, 8)) - - # Apply asinh transformation to shear - shear_re_fun_asinh = np.arcsinh(shear_re_fun) - - # Create grid for interpolation - r_min, r_max = np.min(r_fun), np.max(r_fun) - z_min, z_max = np.min(z_fun), np.max(z_fun) - - grid_r = np.linspace(r_min, r_max, 150) - grid_z = np.linspace(z_min, z_max, 150) - grid_r_mesh, grid_z_mesh = np.meshgrid(grid_r, grid_z) - - # Interpolate shear values onto grid - points = np.c_[r_fun, z_fun] - grid_shear = griddata(points, shear_re_fun_asinh, (grid_r_mesh, grid_z_mesh), - method='cubic', fill_value=np.nan) - - # Normalize with 0 at white - vmin, vmax = np.nanmin(grid_shear), np.nanmax(grid_shear) - norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax) - - # Plot shear as filled contours with scatter points - contourf = ax.contourf(grid_r_mesh, grid_z_mesh, grid_shear, levels=30, - cmap='RdBu_r', norm=norm) - - # Overlay scatter plot for actual data points - scatter = ax.scatter(r_fun, z_fun, c=shear_re_fun_asinh, cmap='RdBu_r', - norm=norm, s=15, alpha=0.6, edgecolors='none') - - # Draw contour at shear=0 in black - contour_zero = ax.contour(grid_r_mesh, grid_z_mesh, grid_shear, - levels=[0], colors='black', linewidths=2) - - ax.set_xlabel('R', fontsize=22, fontweight='bold') - ax.set_ylabel('Z', fontsize=22, fontweight='bold') - ax.set_title('Shear (spatial) - r-z plane', fontsize=24, fontweight='bold') - ax.set_aspect('equal', adjustable='box') - ax.grid(True, alpha=0.3, linestyle='--') - ax.tick_params(labelsize=16) - - # Set r-axis ticks - r_ticks = np.linspace(r_min, r_max, 6) - ax.set_xticks(r_ticks) - ax.set_xticklabels([f'{r:.3f}' for r in r_ticks], fontsize=14) - - # Set z-axis ticks - z_ticks = np.linspace(z_min, z_max, 6) - ax.set_yticks(z_ticks) - ax.set_yticklabels([f'{z:.3f}' for z in z_ticks], fontsize=14) - - # Colorbar - cbar = plt.colorbar(scatter, ax=ax) - cbar.set_label('asinh(Shear)', fontsize=18, fontweight='bold') - cbar.ax.tick_params(labelsize=14) - - # Set colorbar ticks to span negative and positive values - cbar_ticks = np.linspace(vmin, vmax, 7) - cbar.set_ticks(cbar_ticks) - cbar.set_ticklabels([f'{val:.1e}' for val in cbar_ticks], fontsize=12) - - # Adjust layout and save - plt.tight_layout() - - output_file = 'gpec_recon_shear_rz.png' - print(f"\nSaving plot to: {output_file}") - plt.savefig(output_file, dpi=150, bbox_inches='tight') - print("Plot complete!") - - -if __name__ == '__main__': - main() diff --git a/docs/examples/DIIID_ideal_example/plot_xbrzphi.py b/docs/examples/DIIID_ideal_example/plot_xbrzphi.py deleted file mode 100644 index 0d09ba3f0..000000000 --- a/docs/examples/DIIID_ideal_example/plot_xbrzphi.py +++ /dev/null @@ -1,114 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd - -def load_gpec_xbrzphi(filepath): - """ - Load GPEC XBRZPHI output file - Returns: pandas DataFrame with all columns - """ - # Skip header lines (first 6 lines contain metadata) - df = pd.read_csv(filepath, sep=r'\s+', skiprows=6) - return df - -def plot_gpec_displacement_rzplane(filepath, figsize=(18, 12)): - """ - Plot 6 R-Z plane subplots for displacement components (xr, xz, xp) - Shows real and imaginary parts - 6 panels total - """ - df = load_gpec_xbrzphi(filepath) - - # Displacement components - real and imaginary - components = [ - ('real(xr) - Radial Displacement (Real)', df['real(xr)']), - ('imag(xr) - Radial Displacement (Imag)', df['imag(xr)']), - ('real(xz) - Vertical Displacement (Real)', df['real(xz)']), - ('imag(xz) - Vertical Displacement (Imag)', df['imag(xz)']), - ('real(xp) - Toroidal Displacement (Real)', df['real(xp)']), - ('imag(xp) - Toroidal Displacement (Imag)', df['imag(xp)']) - ] - - fig, axes = plt.subplots(2, 3, figsize=figsize) - fig.suptitle('GPEC XBRZPHI: Displacement Components in R-Z Plane (n=1)', - fontsize=16, fontweight='bold') - - axes_flat = axes.flatten() - - for idx, (title, values) in enumerate(components): - ax = axes_flat[idx] - - # Create scatter plot colored by value - scatter = ax.scatter(df['r'], df['z'], c=values, s=50, - cmap='RdBu_r', alpha=0.8, edgecolors='none') - - # Add colorbar - cbar = plt.colorbar(scatter, ax=ax) - cbar.set_label('Value', fontsize=10) - - ax.set_xlabel('R (Major Radius)', fontsize=11) - ax.set_ylabel('Z (Vertical Position)', fontsize=11) - ax.set_title(title, fontsize=12, fontweight='bold') - ax.grid(True, alpha=0.3, linestyle='--') - - plt.tight_layout() - return fig, df - -def plot_gpec_magnetic_field_rzplane(filepath, figsize=(18, 12)): - """ - Plot 6 R-Z plane subplots for magnetic field components (br, bz, bp) - Shows real and imaginary parts - 6 panels total - """ - df = load_gpec_xbrzphi(filepath) - - # Magnetic field components - real and imaginary - components = [ - ('real(br) - Radial Magnetic Field (Real)', df['real(br)']), - ('imag(br) - Radial Magnetic Field (Imag)', df['imag(br)']), - ('real(bz) - Vertical Magnetic Field (Real)', df['real(bz)']), - ('imag(bz) - Vertical Magnetic Field (Imag)', df['imag(bz)']), - ('real(bp) - Toroidal Magnetic Field (Real)', df['real(bp)']), - ('imag(bp) - Toroidal Magnetic Field (Imag)', df['imag(bp)']) - ] - - fig, axes = plt.subplots(2, 3, figsize=figsize) - fig.suptitle('GPEC XBRZPHI: Magnetic Field Components in R-Z Plane (n=1)', - fontsize=16, fontweight='bold') - - axes_flat = axes.flatten() - - for idx, (title, values) in enumerate(components): - ax = axes_flat[idx] - - # Create scatter plot colored by value - scatter = ax.scatter(df['r'], df['z'], c=values, s=50, - cmap='RdBu_r', alpha=0.8, edgecolors='none') - - # Add colorbar - cbar = plt.colorbar(scatter, ax=ax) - cbar.set_label('Value', fontsize=10) - - ax.set_xlabel('R (Major Radius)', fontsize=11) - ax.set_ylabel('Z (Vertical Position)', fontsize=11) - ax.set_title(title, fontsize=12, fontweight='bold') - ax.grid(True, alpha=0.3, linestyle='--') - - plt.tight_layout() - return fig, df - -# Usage -if __name__ == "__main__": - filepath = './gpec_xbrzphi_fun_n1.out' - - # Plot 1: Displacement components (x) - 6 panels - fig1, df = plot_gpec_displacement_rzplane(filepath) - output_path_1 = './gpec_xbrzphi_displacement_6panels.png' - fig1.savefig(output_path_1, dpi=150, bbox_inches='tight') - print(f"Saved: {output_path_1}") - - # Plot 2: Magnetic field components (b) - 6 panels - fig2, df = plot_gpec_magnetic_field_rzplane(filepath) - output_path_2 = './gpec_xbrzphi_magnetic_6panels.png' - fig2.savefig(output_path_2, dpi=150, bbox_inches='tight') - print(f"Saved: {output_path_2}") - - # plt.show() \ No newline at end of file From ce7c7d4504dc057d8a188210b91af627ebdd1afd Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 2 Jun 2026 15:04:07 +0900 Subject: [PATCH 140/305] GPEC - RECON3 - add |C|^2 Bernstein decomposition diagnostic Add recon_flag3 path computing the |C|^2 decomposition of delta-W_p: - gpeq_recon3: A=|Q|^2/mu0, B=2Re(V*.Q)/mu0 (V computed directly from the equilibrium current, not via C-Q), Cpar=K2 xi_n^2, Iperp term, plus optional per-theta surf_extra(0:mthsurf,22) field pack. - gpout_recon3: 30-column surface output (gated by recon_out) and a C-free dW_p = 1/2(A+B+Iperp-dst1-dst3) via gpeq_dst (dst2 cancels). - Unify recon1/2/3 terminal+log output (dW_p, dW_p-normalize, GPEC ep, DCON ep); fix recon1 DCON ep normalization to use dcon_fac. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 3f1f64b652777ffd23437a8da130b64fcf86ecda) --- gpec/gpec.f | 6 +- gpec/gpeq.f | 249 ++++++++++++++++++++++++++++++++++++- gpec/gpglobal.F | 2 +- gpec/gpout.f | 318 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 568 insertions(+), 7 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index 4d0f904d3..b8b015363 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -72,7 +72,7 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, - $ cveri_flag,recon_out,recon_flag2,xi_flag,xifile + $ cveri_flag,recon_out,recon_flag2,recon_flag3,xi_flag,xifile NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -199,6 +199,7 @@ PROGRAM gpec_main mutual_test_flag=.FALSE. recon_flag=.FALSE. recon_flag2=.FALSE. + recon_flag3=.FALSE. xi_flag=.FALSE. recon_int="spline" cveri_flag=.FALSE. @@ -699,6 +700,9 @@ PROGRAM gpec_main IF (recon_flag2) THEN CALL gpout_recon2(mode,xspmn) ENDIF + IF (recon_flag3) THEN + CALL gpout_recon3(mode,xspmn) + ENDIF c----------------------------------------------------------------------- c diagnose. c----------------------------------------------------------------------- diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 775daeb9f..414bd30a6 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -19,6 +19,7 @@ c 11. gpeq_shear (reconstruction: magnetic shear) c 12. gpeq_curvature (reconstruction: curvature) c 13. gpeq_K (reconstruction: Bernstein K quantity) +c 13b. gpeq_recon3 (reconstruction: |C|^2 decomposition) c 14. gpeq_fcoords c 15. gpeq_fcoordsout c 16. gpeq_bcoords @@ -1480,8 +1481,254 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, c----------------------------------------------------------------------- RETURN END SUBROUTINE gpeq_K +c----------------------------------------------------------------------- +c subprogram 13b. gpeq_recon3. +c Bernstein-form |C|^2 decomposition for recon3 diagnostic. +c Vector identity: C = Q + V with V = xi_n * mu0 * j x n_hat. +c |C|^2 = |Q|^2 + 2 Re(V*.Q) + |V|^2 +c j is purely tangent to flux surface (j.n=0), so +c |V|^2 = mu0^2 xi_n^2 |j|^2 = mu0^2 xi_n^2 (sigma^2 B^2 +c + p'^2 |grad psi|^2 / B^2). +c Returns the surface integrals (theta only, jac measure): +c A_int = int |Q|^2 / mu0 * J dtheta / mthsurf +c B_int = int 2 Re(V*.Q) / mu0 * J dtheta / mthsurf +c directly via V = C - Q (covariant pairing) +c Cpar_int = int mu0 sigma^2 B^2 xi_n^2 * J dtheta / mthsurf +c (= K_2 xi_n^2 integral; matches gpeq_dst's dst2) +c Iperp_int= int mu0 p'^2 |grad psi|^2 xi_n^2 / B^2 * +c J dtheta / mthsurf +c epf_int = int |C|^2 / mu0 * J dtheta / mthsurf +c Identity check: epf_int = A_int + B_int + Cpar_int + Iperp_int +c should hold within numerical roundoff. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_recon3(psi, A_int, B_int, Iperp_int, Cpar_int, + $ epf_int, A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: A_int, B_int, Iperp_int, Cpar_int, + $ epf_int + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ A_fun, B_fun, Iperp_fun, Cpar_fun +c surf_extra: packed per-theta fields for R-Z heatmaps (recon_out). +c columns: 1 VdotQ_re, 2 VdotQ_im, 3 Bcur_den, 4 Bpre_den, +c 5/6 Qp_re/im, 7/8 Qt_re/im, 9/10 Qz_re/im, +c 11/12 Vt_re/im, 13/14 Vz_re/im, 15 jpar, 16 jperp, +c 17/18 xin_re/im, 19 delpsi, 20 Bmod, +c 21 dst1_den (K1 xin2), 22 dst3_den (K3 xin2). + REAL(r8), DIMENSION(0:mthsurf,22), OPTIONAL, INTENT(OUT) :: + $ surf_extra + + INTEGER :: itheta + REAL(r8) :: f1raw, p1_local + REAL(r8) :: A_density, B_density, Iperp_density, Cpar_density, + $ epf_density + REAL(r8) :: delpsi_val, bsq_val, sigma_local, jdotb_val + REAL(r8) :: jwt, jwz, bth, bze, g22, g33, g23, r_val + REAL(r8) :: xin2, mu0jwt, mu0jwz + REAL(r8) :: mu0jpar_t, mu0jpar_z, mu0jper_t, mu0jper_z + REAL(r8) :: jsq_val, jpar_val, bmod_val + COMPLEX(r8) :: jvt_loc, jvz_loc, vdotq_loc + COMPLEX(r8) :: jvt_cur, jvz_cur, jvt_pre, jvz_pre + COMPLEX(r8), DIMENSION(0:mthsurf) :: kfun_loc + REAL(r8), DIMENSION(0:mthsurf) :: kt1_loc, kt2_loc, kt3_loc + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xno_fun, xwp_fun + + IF(debug_flag) PRINT *, "Entering gpeq_recon3" +c----------------------------------------------------------------------- +c prepare psi-local perturbed equilibrium and C, Q. +c----------------------------------------------------------------------- + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_c(psi, 0) + + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1_local = sq%f1(2) / mu0 + chi1 = psio * twopi + + CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) + CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cvp_fun, mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cvt_fun, mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cvz_fun, mthsurf, cvz_mn) + CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) + +c K term breakdown (K1 shear, K3 curvature) for dst1/dst3 densities; +c only needed for the R-Z heatmap output. + IF (PRESENT(surf_extra)) THEN + CALL gpeq_K(psi, kfun_loc, kt1_loc, kt2_loc, kt3_loc) + ENDIF + + A_int = 0.0_r8 + B_int = 0.0_r8 + Iperp_int = 0.0_r8 + Cpar_int = 0.0_r8 + epf_int = 0.0_r8 +c----------------------------------------------------------------------- +c accumulate densities in theta with jac measure. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + CALL bicube_eval(eqfun, psi, theta(itheta), 0) + jac = rzphi%f(4) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + bsq_val = eqfun%f(1)**2 + +c |grad psi| + w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) + delpsi_val=SQRT(w(1,1)**2+w(1,2)**2) + +c currents and B (idcon_metric convention, same as gpeq_K). + jwt = -f1raw/(jac*mu0) + jwz = sq%f(4)*jwt - p1_local/chi1 + bth = chi1 / jac + bze = sq%f(4) * chi1 / jac + + v(2,1) = rzphi%fy(1)/(2*rfac) + v(2,2) = (1+rzphi%fy(2))*twopi*rfac + v(2,3) = rzphi%fy(3)*r_val + v(3,3) = twopi*r_val + g22 = (v(2,1)**2 + v(2,2)**2 + v(2,3)**2) + g33 = (v(3,3)**2) + g23 = (v(2,3)*v(3,3)) + + jdotb_val = g22*bth*jwt + g33*bze*jwz + + $ g23*(bth*jwz + bze*jwt) + sigma_local = jdotb_val / bsq_val + + xin2 = REAL(CONJG(xno_fun(itheta))*xno_fun(itheta), r8) + +c |Q|^2 / mu0 density (matches gpeq_firstform q2_density) + A_density = REAL(CONJG(bwp_fun(itheta)) * bvp_fun(itheta) + + $ CONJG(bmt_fun(itheta)) * bvt_fun(itheta) + + $ CONJG(bmz_fun(itheta)) * bvz_fun(itheta), r8) + $ / (mu0 * jac) + +c |C|^2 / mu0 density (matches gpeq_epf integrand) + epf_density = REAL( + $ CONJG(cwp_fun(itheta)) * cvp_fun(itheta) + + $ CONJG(cwt_fun(itheta)) * cvt_fun(itheta) + + $ CONJG(cwz_fun(itheta)) * cvz_fun(itheta), r8) + $ / (mu0 * jac) + +c B density = 2 Re(V*.Q) / mu0, with V = xi_n (mu0 j x n_hat) +c computed DIRECTLY from the equilibrium current, NOT from C - Q. +c These are the same C^i - Q^i terms derived in main.tex (Sec. 5, +c the C component box): J V^psi = 0, and with xwp_fun = J xi^psi, +c J V^theta = xwp/(|grad psi|^2 J) (mu0 j^theta g23 + mu0 j^zeta g33) +c J V^zeta = -xwp/(|grad psi|^2 J) (mu0 j^theta g22 + mu0 j^zeta g23). +c mu0 j^i = mu0 * (physical j^i); jwt,jwz already hold physical j^i. + mu0jwt = mu0 * jwt + mu0jwz = mu0 * jwz + jvt_loc = xwp_fun(itheta) / (delpsi_val**2 * jac) * + $ (mu0jwt * g23 + mu0jwz * g33) + jvz_loc = -xwp_fun(itheta) / (delpsi_val**2 * jac) * + $ (mu0jwt * g22 + mu0jwz * g23) +c V is contravariant (J V^i); pair with covariant Q_i (bvt,bvz). + B_density = 2.0_r8 * REAL( + $ CONJG(jvt_loc) * bvt_fun(itheta) + + $ CONJG(jvz_loc) * bvz_fun(itheta), r8) / (mu0 * jac) + +c K_2 xi_n^2 density (parallel current; should match dst2) + Cpar_density = mu0 * sigma_local**2 * bsq_val * xin2 + +c I_perp density (perpendicular Pfirsch-Schlueter current^2) + Iperp_density = mu0 * p1_local**2 * delpsi_val**2 * + $ xin2 / bsq_val + +c Extra per-theta fields for R-Z heatmaps (only if requested). + IF (PRESENT(surf_extra)) THEN + bmod_val = SQRT(bsq_val) +c V*.Q (complex); B_density = 2 Re(vdotq_loc)/mu0. + vdotq_loc = (CONJG(jvt_loc) * bvt_fun(itheta) + + $ CONJG(jvz_loc) * bvz_fun(itheta)) / jac +c Split V = V_par + V_perp via j = j_par + j_perp. +c Parallel current: mu0 j_par^i = mu0 sigma B^i (B^t=bth,B^z=bze). + mu0jpar_t = mu0 * sigma_local * bth + mu0jpar_z = mu0 * sigma_local * bze + mu0jper_t = mu0jwt - mu0jpar_t + mu0jper_z = mu0jwz - mu0jpar_z + jvt_cur = xwp_fun(itheta) / (delpsi_val**2 * jac) * + $ (mu0jpar_t * g23 + mu0jpar_z * g33) + jvz_cur = -xwp_fun(itheta) / (delpsi_val**2 * jac) * + $ (mu0jpar_t * g22 + mu0jpar_z * g23) + jvt_pre = jvt_loc - jvt_cur + jvz_pre = jvz_loc - jvz_cur +c j_par = sigma B; |j_perp|^2 = |j|^2 - j_par^2 (j physical). + jpar_val = sigma_local * bmod_val + jsq_val = g22 * jwt**2 + g33 * jwz**2 + + $ 2.0_r8 * g23 * jwt * jwz + surf_extra(itheta,1) = REAL(vdotq_loc, r8) + surf_extra(itheta,2) = AIMAG(vdotq_loc) + surf_extra(itheta,3) = 2.0_r8 * REAL( + $ CONJG(jvt_cur) * bvt_fun(itheta) + + $ CONJG(jvz_cur) * bvz_fun(itheta), r8) / (mu0 * jac) + surf_extra(itheta,4) = 2.0_r8 * REAL( + $ CONJG(jvt_pre) * bvt_fun(itheta) + + $ CONJG(jvz_pre) * bvz_fun(itheta), r8) / (mu0 * jac) + surf_extra(itheta,5) = REAL(bvp_fun(itheta), r8) + surf_extra(itheta,6) = AIMAG(bvp_fun(itheta)) + surf_extra(itheta,7) = REAL(bvt_fun(itheta), r8) + surf_extra(itheta,8) = AIMAG(bvt_fun(itheta)) + surf_extra(itheta,9) = REAL(bvz_fun(itheta), r8) + surf_extra(itheta,10) = AIMAG(bvz_fun(itheta)) + surf_extra(itheta,11) = REAL(jvt_loc, r8) + surf_extra(itheta,12) = AIMAG(jvt_loc) + surf_extra(itheta,13) = REAL(jvz_loc, r8) + surf_extra(itheta,14) = AIMAG(jvz_loc) + surf_extra(itheta,15) = jpar_val + surf_extra(itheta,16) = SQRT(MAX(0.0_r8, + $ jsq_val - jpar_val**2)) + surf_extra(itheta,17) = REAL(xno_fun(itheta), r8) + surf_extra(itheta,18) = AIMAG(xno_fun(itheta)) + surf_extra(itheta,19) = delpsi_val + surf_extra(itheta,20) = bmod_val +c dst1 = K1 xin2 (shear), dst3 = K3 xin2 (curvature). + surf_extra(itheta,21) = kt1_loc(itheta) * xin2 + surf_extra(itheta,22) = kt3_loc(itheta) * xin2 + ENDIF + + IF (PRESENT(A_fun)) A_fun(itheta) = A_density + IF (PRESENT(B_fun)) B_fun(itheta) = B_density + IF (PRESENT(Iperp_fun)) Iperp_fun(itheta) = Iperp_density + IF (PRESENT(Cpar_fun)) Cpar_fun(itheta) = Cpar_density + + IF (itheta < mthsurf) THEN + A_int = A_int + A_density * jac / REAL(mthsurf, r8) + B_int = B_int + B_density * jac / REAL(mthsurf, r8) + epf_int = epf_int + epf_density * jac / REAL(mthsurf, r8) + Cpar_int = Cpar_int + Cpar_density * jac / + $ REAL(mthsurf, r8) + Iperp_int = Iperp_int + Iperp_density * jac / + $ REAL(mthsurf, r8) + ENDIF + ENDDO + + IF(debug_flag) PRINT *, "->Leaving gpeq_recon3" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_recon3 +c----------------------------------------------------------------------- c subprogram 14. gpeq_fcoords. -c transform coordinates to dcon coordinates. +c transform coordinates to dcon coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) c----------------------------------------------------------------------- diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 0d7366aa2..9c21a82a3 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,7 +19,7 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag,recon_out,recon_flag2, + $ cveri_flag,recon_out,recon_flag2,recon_flag3, $ xi_flag INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, diff --git a/gpec/gpout.f b/gpec/gpout.f index 4881f5849..eb61d5c58 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -28,6 +28,7 @@ c 20. gpout_close_netcdf c 21. gpout_recon c 22. gpout_recon2 +c 23. gpout_recon3 c----------------------------------------------------------------------- c subprogram 0. gpout_mod. c module declarations. @@ -7360,10 +7361,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dw_raw_k_hr) WRITE(*,'(a,es17.8e3)') " dW_p(recon-normalize)= ", $ REAL(dw_dcon_k_hr) - WRITE(*,'(a,a,a,es17.8e3)') "GPEC ep(", TRIM(smode), "):", + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", $ ep_selected - WRITE(*,'(a,a,a,es17.8e3)') "DCON ep(", TRIM(smode), "):", - $ ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_selected*dcon_fac IF (cveri_flag) THEN cveri_stat = "PASS" IF (cveri_count > 0) THEN @@ -7410,7 +7411,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), - $ ") = ", ep_selected*(mu0*2.0) / psio**2 / (chi1*1e-3)**2 + $ ") = ", ep_selected*dcon_fac IF (cveri_flag) THEN WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max @@ -7470,6 +7471,8 @@ SUBROUTINE gpout_recon2(mode, xspmn) CHARACTER(8) :: smode CHARACTER(128) :: surf_file, int_file REAL(r8) :: dcon_fac + INTEGER :: ep_index + REAL(r8) :: ep_selected, ep_dcon REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, q2_vals_r COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: q2_vals, jqx_vals, $ pdiv_vals, total_vals @@ -7527,6 +7530,13 @@ SUBROUTINE gpout_recon2(mode, xspmn) ENDDO dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + ep_index = 1 + IF (mode_flag) ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon2 selected mode is out of range") + ENDIF + ep_selected = REAL(ep(ep_index)) + ep_dcon = ep_selected * dcon_fac q2_hr = 0.0_r8 jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) @@ -7643,6 +7653,10 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ REAL(dw_raw_hr) WRITE(*,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", $ REAL(dw_dcon_hr) + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_dcon IF (recon_out) THEN OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", @@ -7661,6 +7675,10 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ REAL(dw_raw_hr) WRITE(u_log,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", $ REAL(dw_dcon_hr) + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_dcon WRITE(u_log,*) CLOSE(u_log) ENDIF @@ -7677,5 +7695,297 @@ SUBROUTINE gpout_recon2(mode, xspmn) c----------------------------------------------------------------------- RETURN END SUBROUTINE gpout_recon2 +c----------------------------------------------------------------------- +c subprogram 23. gpout_recon3 +c Bernstein |C|^2 decomposition diagnostic. +c |C|^2 = |Q|^2 + 2 Re(V*.Q) + |V|^2 with V = xi_n mu0 j x n_hat. +c j is tangent to the flux surface (j.n=0) so +c |V|^2 = mu0^2 xi_n^2 |j|^2 = mu0^2 xi_n^2 (sigma^2 B^2 +c + p'^2 |grad psi|^2 / B^2). +c This routine writes ABS decomposition pieces: +c A = int J |Q|^2 / mu0 (perturbed B energy) +c Cpar = int J mu0 sigma^2 B^2 xi_n^2 (parallel current^2) +c Iperp = int J mu0 p'^2 |grad psi|^2 xi_n^2 / B^2 (perp current^2) +c epf = int J |C|^2 / mu0 (already in recon1) +c and derives B = epf - A - Cpar - Iperp. +c Diagnostic: Cpar should equal dst2 (K_2 xi_n^2) from recon1 +c pointwise and integrated, since both express mu0 sigma^2 B^2 xi_n^2. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon3(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi, itheta, usurf, u_int, u_log + INTEGER :: istep, nint_pts, iint + REAL(r8) :: psi, dpsi_local + REAL(r8) :: A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr + REAL(r8) :: A_total, B_total, Iperp_total, Cpar_total, epf_total + REAL(r8) :: dst1_total, dst3_total + REAL(r8) :: identity_residual + REAL(r8) :: dw_raw3, dw_dcon3, ep_selected, ep_dcon + INTEGER :: ep_index + COMPLEX(r8) :: dst_int_c, dst1_c, dst2_c, dst3_c + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + REAL(r8), DIMENSION(0:mthsurf) :: A_fun, B_fun, Iperp_fun + REAL(r8), DIMENSION(0:mthsurf) :: Cpar_fun + REAL(r8), DIMENSION(0:mthsurf,22) :: surf_extra + INTEGER :: kcol + CHARACTER(8) :: smode + CHARACTER(128) :: surf_file, int_file + REAL(r8) :: dcon_fac + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts + REAL(r8), DIMENSION(:), ALLOCATABLE :: A_vals, B_vals, + $ Iperp_vals, Cpar_vals, epf_vals, dst1_vals, dst3_vals + TYPE(cspline_type) :: recon3_spl + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON3: Bernstein |C|^2 decomp" + IF(verbose) WRITE(*,*)"__________________________________________" + + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + surf_file = "gpec_recon3_terms_sol"//TRIM(smode)//".out" + int_file = "gpec_recon3_integration_sol"//TRIM(smode)//".out" + usurf = 97 + u_int = 98 + u_log = 99 + + IF (recon_out) THEN + OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + WRITE(usurf,'(30(1x,a16))') + $ "psi","theta","r","z","A_den","B_den","Iperp_den", + $ "Cpar_den","VdotQ_re","VdotQ_im","Bcur_den","Bpre_den", + $ "Qp_re","Qp_im","Qt_re","Qt_im","Qz_re","Qz_im", + $ "Vt_re","Vt_im","Vz_re","Vz_im","jpar","jperp", + $ "xin_re","xin_im","delpsi","Bmod","dst1_den","dst3_den" + WRITE(u_int,'(12(1x,a16))') + $ "psi","A","B","Iperp","Cpar","epf","A_cum","B_cum", + $ "Iperp_cum","Cpar_cum","epf_cum","identity_res" + ENDIF + + CALL idcon_build(mode, xspmn) + CALL gpeq_alloc + + DO ipsi = 0, mpsi + psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT + CALL gpeq_recon3(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr, + $ A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) + IF (recon_out) THEN + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(rzphi%f(1)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + WRITE(usurf,'(30(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), A_fun(itheta), + $ B_fun(itheta), Iperp_fun(itheta), Cpar_fun(itheta), + $ (surf_extra(itheta,kcol), kcol=1,22) + ENDDO + WRITE(usurf,*) + ENDIF + ENDDO + + dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + + nint_pts = 0 + DO istep = 0, mstep + IF (psifac(istep) < rzphi%xs(0)) CYCLE + IF (psifac(istep) > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + ENDDO + ALLOCATE(psi_int_pts(nint_pts), A_vals(nint_pts), + $ B_vals(nint_pts), Iperp_vals(nint_pts), Cpar_vals(nint_pts), + $ epf_vals(nint_pts), dst1_vals(nint_pts), dst3_vals(nint_pts)) + iint = 0 + DO istep = 0, mstep + psi = psifac(istep) + IF (psi < rzphi%xs(0)) CYCLE + IF (psi > rzphi%xs(mpsi)) EXIT + CALL gpeq_recon3(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr) +c dst1 (=K1 xi_n^2) and dst3 (=K3 xi_n^2) for the C-free dW form +c 2 dW_p = A + B + Iperp - dst1 - dst3 (dst2 = Cpar cancels). + CALL gpeq_dst(psi, dst_int_c, dst1_c, dst2_c, dst3_c) + iint = iint + 1 + psi_int_pts(iint) = psi + A_vals(iint) = A_hr + B_vals(iint) = B_hr + Iperp_vals(iint) = Iperp_hr + Cpar_vals(iint) = Cpar_hr + epf_vals(iint) = epf_hr + dst1_vals(iint) = REAL(dst1_c, r8) + dst3_vals(iint) = REAL(dst3_c, r8) + ENDDO + + IF (recon_out) THEN + WRITE(u_int,'(a)') "c Bernstein |C|^2 decomposition (direct)" + WRITE(u_int,'(a)') "c identity: epf = A + B + Cpar + Iperp" + WRITE(u_int,'(a)') "c identity_res = epf - A - B - Cpar"// + $ " - Iperp (should be near zero)" + WRITE(u_int,'(a)') "c Cpar should equal dst2 from recon1" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF + + A_total = 0.0_r8 + B_total = 0.0_r8 + Iperp_total = 0.0_r8 + Cpar_total = 0.0_r8 + epf_total = 0.0_r8 + dst1_total = 0.0_r8 + dst3_total = 0.0_r8 + + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon3_spl, nint_pts-1, 7) + recon3_spl%xs = psi_int_pts + recon3_spl%fs(:,1) = CMPLX(A_vals, 0.0_r8, r8) + recon3_spl%fs(:,2) = CMPLX(B_vals, 0.0_r8, r8) + recon3_spl%fs(:,3) = CMPLX(Iperp_vals, 0.0_r8, r8) + recon3_spl%fs(:,4) = CMPLX(Cpar_vals, 0.0_r8, r8) + recon3_spl%fs(:,5) = CMPLX(epf_vals, 0.0_r8, r8) + recon3_spl%fs(:,6) = CMPLX(dst1_vals, 0.0_r8, r8) + recon3_spl%fs(:,7) = CMPLX(dst3_vals, 0.0_r8, r8) + CALL cspline_fit(recon3_spl, "extrap") + CALL cspline_int(recon3_spl) + DO iint = 1, nint_pts + A_total = REAL(recon3_spl%fsi(iint-1,1), r8) + B_total = REAL(recon3_spl%fsi(iint-1,2), r8) + Iperp_total = REAL(recon3_spl%fsi(iint-1,3), r8) + Cpar_total = REAL(recon3_spl%fsi(iint-1,4), r8) + epf_total = REAL(recon3_spl%fsi(iint-1,5), r8) + dst1_total = REAL(recon3_spl%fsi(iint-1,6), r8) + dst3_total = REAL(recon3_spl%fsi(iint-1,7), r8) + identity_residual = epf_total - A_total - B_total - + $ Cpar_total - Iperp_total + IF (recon_out) WRITE(u_int,'(12(es17.8e3))') + $ psi_int_pts(iint), A_vals(iint), B_vals(iint), + $ Iperp_vals(iint), Cpar_vals(iint), epf_vals(iint), + $ A_total, B_total, Iperp_total, Cpar_total, epf_total, + $ identity_residual + ENDDO + CALL cspline_dealloc(recon3_spl) + ELSE + DO iint = 1, nint_pts + IF (iint > 1) THEN + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + A_total = A_total + (A_vals(iint-1)+A_vals(iint)) * + $ dpsi_local / 2.0_r8 + B_total = B_total + (B_vals(iint-1)+B_vals(iint)) * + $ dpsi_local / 2.0_r8 + Iperp_total = Iperp_total + (Iperp_vals(iint-1) + + $ Iperp_vals(iint)) * dpsi_local / 2.0_r8 + Cpar_total = Cpar_total + (Cpar_vals(iint-1) + + $ Cpar_vals(iint)) * dpsi_local / 2.0_r8 + epf_total = epf_total + (epf_vals(iint-1) + + $ epf_vals(iint)) * dpsi_local / 2.0_r8 + dst1_total = dst1_total + (dst1_vals(iint-1) + + $ dst1_vals(iint)) * dpsi_local / 2.0_r8 + dst3_total = dst3_total + (dst3_vals(iint-1) + + $ dst3_vals(iint)) * dpsi_local / 2.0_r8 + ENDIF + identity_residual = epf_total - A_total - B_total - + $ Cpar_total - Iperp_total + IF (recon_out) WRITE(u_int,'(12(es17.8e3))') + $ psi_int_pts(iint), A_vals(iint), B_vals(iint), + $ Iperp_vals(iint), Cpar_vals(iint), epf_vals(iint), + $ A_total, B_total, Iperp_total, Cpar_total, epf_total, + $ identity_residual + ENDDO + ENDIF + + identity_residual = epf_total - A_total - B_total - Cpar_total + $ - Iperp_total + +c C-free reconstructed dW_p: 2 dW = A + B + Iperp - dst1 - dst3 +c (the parallel-current piece dst2 = Cpar cancels identically). + dw_raw3 = 0.5_r8 * (A_total + B_total + Iperp_total + $ - dst1_total - dst3_total) + dw_dcon3 = dw_raw3 * dcon_fac + ep_index = 1 + IF (mode_flag) ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon3 selected mode is out of range") + ENDIF + ep_selected = REAL(ep(ep_index)) + ep_dcon = ep_selected * dcon_fac + + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final |C|^2 decomposition totals:" + WRITE(u_int,'(a)') "c A_total = int J |Q|^2 / mu0" + WRITE(u_int,'(a)') "c B_total = int J 2 Re(V*.Q) / mu0" + WRITE(u_int,'(a)') "c Cpar_total = int J mu0 sigma^2 B^2"// + $ " xi_n^2" + WRITE(u_int,'(a)') "c Iperp_total= int J mu0 p'^2 |gp|^2"// + $ " xi_n^2 / B^2" + WRITE(u_int,'(a)') "c epf_total = int J |C|^2 / mu0" + WRITE(u_int,'(a)') "c identity_res = epf - (A+B+Cpar+Iperp)" + WRITE(u_int,'(es17.8e3)') A_total + WRITE(u_int,'(es17.8e3)') B_total + WRITE(u_int,'(es17.8e3)') Cpar_total + WRITE(u_int,'(es17.8e3)') Iperp_total + WRITE(u_int,'(es17.8e3)') epf_total + WRITE(u_int,'(es17.8e3)') identity_residual + ENDIF + + WRITE(*,'(a)') "GPEC_RECON3 |C|^2 decomposition totals:" + WRITE(*,'(a,es17.8e3)') " A_total = ", A_total + WRITE(*,'(a,es17.8e3)') " B_total = ", B_total + WRITE(*,'(a,es17.8e3)') " Cpar_total = ", Cpar_total + WRITE(*,'(a,es17.8e3)') " Iperp_total = ", Iperp_total + WRITE(*,'(a,es17.8e3)') " epf_total = ", epf_total + WRITE(*,'(a,es17.8e3)') " identity_res = ", identity_residual + WRITE(*,'(a)') " (identity_res should be ~ 0; Cpar = dst2.)" + WRITE(*,'(a,es17.8e3)') " dW_p(recon3) = ", dw_raw3 + WRITE(*,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", dw_dcon3 + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_dcon + + IF (recon_out) THEN + OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON3 final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a)') " Bernstein |C|^2 decomposition:" + WRITE(u_log,'(a,es17.8e3)') " A_total = ", A_total + WRITE(u_log,'(a,es17.8e3)') " B_total = ", B_total + WRITE(u_log,'(a,es17.8e3)') " Cpar_total = ", Cpar_total + WRITE(u_log,'(a,es17.8e3)') " Iperp_total = ", Iperp_total + WRITE(u_log,'(a,es17.8e3)') " epf_total = ", epf_total + WRITE(u_log,'(a,es17.8e3)') " identity_res = ", + $ identity_residual + WRITE(u_log,'(a)') " (identity_res should be ~0;"// + $ " Cpar should match dst2 from recon1.)" + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3) = ", + $ dw_raw3 + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", + $ dw_dcon3 + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_dcon + WRITE(u_log,*) + CLOSE(u_log) + ENDIF + + DEALLOCATE(psi_int_pts, A_vals, B_vals, Iperp_vals, Cpar_vals, + $ epf_vals, dst1_vals, dst3_vals) + CALL gpeq_dealloc + IF (recon_out) THEN + CLOSE(usurf) + CLOSE(u_int) + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon3 END MODULE gpout_mod From daeabf6d4943a3966e7533219c2a1b478807f66a Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 5 Jun 2026 14:24:16 +0900 Subject: [PATCH 141/305] GPEC - RECON1 - add per-(psi,theta) terms R-Z output Add gpec_recon_terms_solX.out (per-point C-form energy densities: c2_den, c2_psi/theta/zeta_den, dst1/2/3_den) from gpout_recon, the recon1 analog of recon3_terms for 2 dW_p = C2/mu0 - dst1 - dst2 - dst3. gpeq_epf / gpeq_dst gain optional per-theta density out-args; their theta loops now run 0..mthsurf with an itheta (cherry picked from commit f80b993fa3724ced30d1714f087f51a7dbe6cd1c) --- gpec/gpeq.f | 67 ++++++++++++++++++++++++++++++++++++---------------- gpec/gpout.f | 28 +++++++++++++++++++--- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 414bd30a6..5d3481147 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -1070,18 +1070,23 @@ END SUBROUTINE gpeq_firstform c c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z) + SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, + $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- REAL(r8), INTENT(IN) :: psi REAL(r8), INTENT(OUT) :: epf_int REAL(r8), OPTIONAL, INTENT(OUT) :: epf_p, epf_t, epf_z +c optional per-theta C^2/mu0 densities for R-Z heatmaps. Same +c convention as gpeq_recon3 epf_density: integral = sum den*jac/mth. + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun INTEGER :: itheta COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun REAL(r8) :: epf_psi_int, epf_theta_int, epf_zeta_int - REAL(r8) :: epf_fac + REAL(r8) :: dpsi_d, dthe_d, dzet_d IF(debug_flag) PRINT *, "Entering gpeq_epf" c----------------------------------------------------------------------- @@ -1104,16 +1109,26 @@ SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z) epf_psi_int = 0.0_r8 epf_theta_int = 0.0_r8 epf_zeta_int = 0.0_r8 - DO itheta = 0, mthsurf-1 + DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 0) jac = rzphi%f(4) - epf_fac = jac / (mu0 * REAL(mthsurf, r8)) - epf_psi_int = epf_psi_int + epf_fac * - $ REAL(CONJG(cwp_fun(itheta)/jac) * cvp_fun(itheta), r8) - epf_theta_int = epf_theta_int + epf_fac * - $ REAL(CONJG(cwt_fun(itheta)/jac) * cvt_fun(itheta), r8) - epf_zeta_int = epf_zeta_int + epf_fac * - $ REAL(CONJG(cwz_fun(itheta)/jac) * cvz_fun(itheta), r8) +c per-theta densities (Re(conjg(cw_i) cv_i)/(mu0 jac)). + dpsi_d = REAL(CONJG(cwp_fun(itheta)) * cvp_fun(itheta), r8) + $ / (mu0 * jac) + dthe_d = REAL(CONJG(cwt_fun(itheta)) * cvt_fun(itheta), r8) + $ / (mu0 * jac) + dzet_d = REAL(CONJG(cwz_fun(itheta)) * cvz_fun(itheta), r8) + $ / (mu0 * jac) + IF (PRESENT(epf_p_fun)) epf_p_fun(itheta) = dpsi_d + IF (PRESENT(epf_t_fun)) epf_t_fun(itheta) = dthe_d + IF (PRESENT(epf_z_fun)) epf_z_fun(itheta) = dzet_d + IF (PRESENT(epf_den_fun)) epf_den_fun(itheta) = + $ dpsi_d + dthe_d + dzet_d + IF (itheta < mthsurf) THEN + epf_psi_int = epf_psi_int + dpsi_d*jac/REAL(mthsurf, r8) + epf_theta_int = epf_theta_int + dthe_d*jac/REAL(mthsurf, r8) + epf_zeta_int = epf_zeta_int + dzet_d*jac/REAL(mthsurf, r8) + ENDIF ENDDO epf_int = epf_psi_int + epf_theta_int + epf_zeta_int IF (PRESENT(epf_p)) epf_p = epf_psi_int @@ -1134,14 +1149,19 @@ END SUBROUTINE gpeq_epf c c and return the theta/zeta integrated value for one psi. c----------------------------------------------------------------------- - SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3) + SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, + $ dst1_den_fun, dst2_den_fun, dst3_den_fun) REAL(r8), INTENT(IN) :: psi COMPLEX(r8), INTENT(OUT) :: dst_int COMPLEX(r8), OPTIONAL, INTENT(OUT) :: dst_t1, dst_t2, dst_t3 +c optional per-theta K_i xi_n^2 densities for R-Z heatmaps. Same +c convention as gpeq_recon3: integral = sum den*jac/mthsurf. + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ dst1_den_fun, dst2_den_fun, dst3_den_fun INTEGER :: itheta COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xno_fun REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 - REAL(r8) :: xin2_fac + REAL(r8) :: xin2_fac, xin2_loc, d1, d2, d3 COMPLEX(r8) :: dst1_int, dst2_int, dst3_int c----------------------------------------------------------------------- @@ -1157,17 +1177,22 @@ SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3) dst1_int = CMPLX(0.0_r8, 0.0_r8, r8) dst2_int = CMPLX(0.0_r8, 0.0_r8, r8) dst3_int = CMPLX(0.0_r8, 0.0_r8, r8) - DO itheta = 0, mthsurf-1 + DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 0) jac = rzphi%f(4) - xin2_fac = jac * ABS(xno_fun(itheta))**2 / - $ REAL(mthsurf, r8) - dst1_int = dst1_int + CMPLX(K_term1(itheta) * xin2_fac, - $ 0.0_r8, r8) - dst2_int = dst2_int + CMPLX(K_term2(itheta) * xin2_fac, - $ 0.0_r8, r8) - dst3_int = dst3_int + CMPLX(K_term3(itheta) * xin2_fac, - $ 0.0_r8, r8) + xin2_loc = ABS(xno_fun(itheta))**2 + d1 = K_term1(itheta) * xin2_loc + d2 = K_term2(itheta) * xin2_loc + d3 = K_term3(itheta) * xin2_loc + IF (PRESENT(dst1_den_fun)) dst1_den_fun(itheta) = d1 + IF (PRESENT(dst2_den_fun)) dst2_den_fun(itheta) = d2 + IF (PRESENT(dst3_den_fun)) dst3_den_fun(itheta) = d3 + IF (itheta < mthsurf) THEN + xin2_fac = jac / REAL(mthsurf, r8) + dst1_int = dst1_int + CMPLX(d1 * xin2_fac, 0.0_r8, r8) + dst2_int = dst2_int + CMPLX(d2 * xin2_fac, 0.0_r8, r8) + dst3_int = dst3_int + CMPLX(d3 * xin2_fac, 0.0_r8, r8) + ENDIF ENDDO dst_int = dst1_int + dst2_int + dst3_int IF (PRESENT(dst_t1)) dst_t1 = dst1_int diff --git a/gpec/gpout.f b/gpec/gpout.f index eb61d5c58..272c1570c 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6882,7 +6882,10 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, ucveri, $ uk_sigma, ucw_fun, ucw_mn, ucv_fun, ucv_mn, ucv2_fun, - $ ucv2_mn, u_int, u_log + $ ucv2_mn, u_int, u_log, u_terms + REAL(r8), DIMENSION(0:mthsurf) :: epf_den_fun, epf_p_fun, + $ epf_t_fun, epf_z_fun, dst1_den_fun, dst2_den_fun, + $ dst3_den_fun REAL(r8) :: psi, eta, rfac REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals REAL(r8) :: epf_int, epf_p_int, epf_t_int, epf_z_int @@ -6898,7 +6901,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CHARACTER(128) :: shear_fun_file, curv_file, cveri_file CHARACTER(128) :: k_file, k_sigma_file CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file - CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file + CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file, terms_file REAL(r8) :: dpsi_local INTEGER :: istep, nint_pts, iint, ep_index REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr, ep_selected @@ -6959,6 +6962,7 @@ SUBROUTINE gpout_recon(mode, xspmn) cv2_fun_file = "gpec_recon_cv2fun_sol"//TRIM(smode)//".out" cv2_mn_file = "gpec_recon_cv2mn_sol"//TRIM(smode)//".out" int_file = "gpec_recon_integration_sol"//TRIM(smode)//".out" + terms_file = "gpec_recon_terms_sol"//TRIM(smode)//".out" ushear_fun = 82 ucurv = 72 @@ -6973,6 +6977,7 @@ SUBROUTINE gpout_recon(mode, xspmn) ucv2_mn = 91 u_int = 92 u_log = 93 + u_terms = 94 IF (recon_out) THEN OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") @@ -6989,6 +6994,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + OPEN(UNIT=u_terms, FILE=terms_file, STATUS="UNKNOWN") WRITE(ushear_fun,'(6(1x,a16))') $ "psi","theta","r","z","shear_re","shear_im" WRITE(ucurv,'(6(1x,a16))') @@ -7024,6 +7030,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" + WRITE(u_terms,'(11(1x,a16))') + $ "psi","theta","r","z","c2_den","c2_psi_den", + $ "c2_theta_den","c2_zeta_den","dst1_den","dst2_den", + $ "dst3_den" ENDIF ep_index = 1 IF (mode_flag) ep_index = mode @@ -7050,7 +7060,9 @@ SUBROUTINE gpout_recon(mode, xspmn) c Compute the J|C|^2/mu0 state once so C fields below use c the current psi. This keeps the psi-local reconstruction path c identical to the original logic. - CALL gpeq_epf(psi, epf_int) + CALL gpeq_epf(psi, epf_int, epf_den_fun=epf_den_fun, + $ epf_p_fun=epf_p_fun, epf_t_fun=epf_t_fun, + $ epf_z_fun=epf_z_fun) c Reconstruct detailed K diagnostics for output. Keep this on c the original path so recon terminal totals remain unchanged. @@ -7081,6 +7093,9 @@ SUBROUTINE gpout_recon(mode, xspmn) IF (recon_out) THEN c Store spatial values with coordinates only when recon file c output is requested. +c K_i xi_n^2 per-theta densities for the recon terms file. + CALL gpeq_dst(psi, dst_int, dst1_den_fun=dst1_den_fun, + $ dst2_den_fun=dst2_den_fun, dst3_den_fun=dst3_den_fun) DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) rfac = SQRT(rzphi%f(1)) @@ -7088,6 +7103,11 @@ SUBROUTINE gpout_recon(mode, xspmn) rvals(itheta) = ro + rfac*COS(eta) zvals(itheta) = zo + rfac*SIN(eta) + WRITE(u_terms,'(11(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), epf_den_fun(itheta), + $ epf_p_fun(itheta), epf_t_fun(itheta), + $ epf_z_fun(itheta), dst1_den_fun(itheta), + $ dst2_den_fun(itheta), dst3_den_fun(itheta) WRITE(ushear_fun,'(6(es17.8e3))') psi, theta(itheta), $ rvals(itheta), zvals(itheta), $ REAL(shear_fun(itheta)), AIMAG(shear_fun(itheta)) @@ -7107,6 +7127,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ jdotb_vals(itheta), K_term2(itheta), $ mu0 * sigma_vals(itheta) * jdotb_vals(itheta) ENDDO + WRITE(u_terms,*) WRITE(ushear_fun,*) WRITE(ucurv,*) IF (cveri_flag) WRITE(ucveri,*) @@ -7430,6 +7451,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CLOSE(uk) CLOSE(uk_sigma) CLOSE(u_int) + CLOSE(u_terms) CALL ascii_close(ucw_fun) CALL ascii_close(ucw_mn) CALL ascii_close(ucv_fun) From 8a40f7eb9cf52f4f6f4b92375d65e25a92649171 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 16 Jun 2026 12:21:01 +0900 Subject: [PATCH 142/305] DOCS - recon: C effective-field figure + Eulerian/Lagrangian derivation main.tex section 4.1: add "Verification: C as an effective field" passage (proof that (curl C).grad psi = 0; Eulerian vs Lagrangian frames; effective current j_eff = curl C / mu0 by analogy with Ampere's law) and Figure 1 (C_eulerian_lagrangian_n1.png, force-added since *.png is gitignored). Also reframe the V-component block as the correction term, drop section 6.1 (dcon_fac moved to the section-6 intro), and rename sections 4.1/4.2. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 73aaf92eaefe12f2462d8f80b206e32f3ee469ea) --- docs/tex/recon/C_eulerian_lagrangian_n1.png | Bin 0 -> 337616 bytes docs/tex/recon/main.tex | 1599 ++++++++++++++++--- 2 files changed, 1355 insertions(+), 244 deletions(-) create mode 100644 docs/tex/recon/C_eulerian_lagrangian_n1.png diff --git a/docs/tex/recon/C_eulerian_lagrangian_n1.png b/docs/tex/recon/C_eulerian_lagrangian_n1.png new file mode 100644 index 0000000000000000000000000000000000000000..df7d728e6ddab40a9a5d79f897430a010cd340ff GIT binary patch literal 337616 zcmeFZWmuGJ_cx4+ff6aq;!LHcS$2ENJtE&q`(Z_-6#l%2*^;v5K@xE zfONm-1>XDKd%yp0&+&YDj{h8cD_qW;*E-j^;%D~j zi@2&5H)aj5#mhZ1$?EbyU;EUo7E8xJR?5}6d-%}g9bL4^7E9{(5_gfAS$7TA`d1RC zL!oo>e&xGNOK7uye~HO_m3I02g_EC~r5>^W>tbZ{y%W9s{jxI2`A>iU9sDeIIfI(y zU)Q7!Z{nW&*EOcFfM@@@MEzRnD*nH&A=obm09F0{<$qK1e}((ILU3^YS9AYsbN{r6 z|Mk}YvrvL)$Wh6%8m)Fr5OB3h7ylE2l+W^WpXk}YA09goetJ_;S;8CvZ`l zMyi~KI%$@$|1%N(d7%GKhgy;s?bE$89noqU8u9Z_y@l>XdNWy&oejm8itcyT&Bh^0^6PtOEX_*jAcoqj9+&--&F8#TR;fgwAwGF%--0R(7K~Um3fB-_T?{sA` z6m1p+CU9^fwM(y4;^5F}KY8+6TU#55I9RB6zlEI+=2jL; za^UO#@go}!zdq34FRlE&wRUas;^~rq{=6y-%aS$-azi);NXYBoZQ^sw-$MLc)m_L(8-IgwOKHnE+}U4 zCFj)25piJ>;9kKd722i=nR0zGXpFj#PXo}KX@&L;$^=Du`M2PN=yK}oh2}ToSag2v z*QKZ5wcPt>o|Yg6*dTHyj?G|YUoa*>CvSkcZj2T7`?-*lKr*bC=pVbGt_9%2pYE57 zfGJ5Jwx-MMilt9b%jA9?aIeY3f4mhZJtE*4jrf zEr+;pd*JrXo3BPCeSLkK=Bx+vVVa`<5YPX|2Vjx#nD*&~ZJ{_?-1dxr8JW#f5Efux zF>X=~U2$=7uQvaQjNT9cz(Wlqc#zxG(-ToG-@<-<@L#&(InmXvy!4S5n6|wCY59Lh zgo7x~hYs{oJ`zc9hS4q zRI&vflEH=<^Z!Tb@~f+VY*!hQ&=iR}B%@HMS6lzG3=zmOf}$DVUlf?Kj42YimDDYc_h@fo15Fp&Ti}0er0+2mjxF>tpgB}P0EJ7dkWO^a`W~N(c0?xM&5IU=W})SSPET1jThJJA#ud z5+T6uGV~47)s&@!jgF_rK}f$g+?S(t;98(K zFb4!R*w4s_c7a}<`kjb&_tFX83f*#e>)ImYpz`e0>-On2dFvlPe$3x*R-j*4CA~@! z-$@VhR7CqUxoHCu*}ARMi$bL@E-uFGCgTK5I@OfBEGSRBl^_ff2T%ZBPn^7;={CC! zIk1?3^y?(;>>4QB8?321J3BWC1salonEr_1=<5BMK^z=$8eTjQf}TOKSK2GbVeK*e z@|7zfx2x*w7lMx!RQTg@p{Gzxa7davt zzyQbtQ;6j9<6~oU($YR{k7C>yG1wEg!lk|CWR*742l69WU&@9v>ij3=#hF1m?7die zdiul0qJx8j4}-RrmJ8cScP(&8$ZB9<2e|j|pM=t!^z_GfBBa30k)bc>bIQwAL2PLZ zCGOCk8R1P}X`Vi{!3s3%%%3=5LEV#u-iyKxVETWoI3a3Fhc8T9q_MJ&qG#dwMHwaOTNl|Yf;qs1)+kuGpHjg9(m-@di7 zvVvPCl%g>u_?$L5kvh@!yA>}yN`WC8HVu9KDl@Sy${yb~|50o?GDA*&h5;;bl^J3Z zq>CE`>p)oSV_2}Q&{osaV}U`(J#tNh&OTo~PmL#09w;ccsEDX~wA8c{=+(P32vs;{ zw=J)?;8quubi^Jj^gza%|Bpb3f7VX663Is zJh`75m%kgP`n7<-qzWDMtq6cS2uE68DS6={8P35Sl?eDIv22J~p?(4OqvO4Wm~B$#(f;C12esz;Bu7 z9ZMOrBdSgnq#y(!dfyKB9vhp6hlhud_<10TzY=Z2Zn;UfDZ53wmgLQS7<)q{wqrmel`m9lvLQ}2TW1xE3= zsxhpz0a;~gdFq>9`ntM8Go`?LfF-C|TU&qc8-Q#M=kX1Uw>CgZT+JcxT{;V*1kYao z|J`y&5ffvVac&zn8znb|Y-}9grmPuT`F!E!yGvZR%uG$``;0UF!NL0WgaRYE3EUW{ zjEr)Z%MUoV#1;Op{o;1l!L>;UclqP4rY?3&Iv{4}KlP$&xz_klgj^*^#+jj z(LZFR>dGiD=Xw$KIi<^NU0(1PaAX&O8x%^vOSB70hi(2-K(fE;jrngDzzqdi0B0({ zuuvh;P}$Q^lP0<|q2Tx+xw)a4;8eSL! z|1Dx-*>s?H==E{JqAP2cw(2yG2RwpEI>(vR{3@n z+XF#(W2nG?sKI)3E|5pwwvC?Rzf1D!Cjp4d(#B@vP4*HB?PY>-~K3Rbv}> z=&j-qvhe2vMdj*Y!}GPNcUzz&9Qg6$URECyNJK5{AlAjErm_XKq=7OFcy1fl1wXgC zF$ki8)N+_9AScz;)kVW%pYSUZ%b%<;u{}6C`k;lqABCDiSF*6MXjj?{zX>)tNo<@p zg21VB3kwOEwlslMAi@ldR9Nq0I)O%8TU#MCyb$mV1oYG5kj--CSokI}@pj#hdmf>6 zd3kw0?a>2PYL1R2z!=%da$UDgT!PB3m44^qIttLf|CS#!U!G(FmcY4mBEUfXHp z1^cK%3(r62!57a{FHi=t*FH76z7^|J@?>u}T1#lFEDAAl)a$N0H#e6DYPk6yAmMs9 zT?2Ln$JQ;OdyF0iXqNjWLi)nJ-Qzvt@GxJu2IYQaL=tGjlW4=EBiADcotd@}ojBpZ ztdr&J*Vvav04IXnkei)NQeFUpJCvfFY9R2mF9kA(dZBJkW+uZ#>C8PiXV9%B3@cJG zoMWKUmg8qNBQFd%h`-V|Tpa2yps@GwZ}s){uiAS1`%Pqi3y<2lf7>fTV>0Rb%*x(_ ze>bzWEzmMBuvZ5$Z#>#WyFk4RfENZwTq6K`ecH<=p6=RbNl$^p;Lg}=(gBI}neI^p z*yJ_z+QgB#Fi?9-h~qPKl-)Ey0lxck07BUt6z1o1Q4&fu`P4Kv`hMmC!T(L_#H0g$ zNCx(KF8DVP;r4w7#Zt?Fw}1A%yX!mUk#w;POK>2ZS6H~UiJ{T~5T$8r>X4i(DN1Bo znka8z(fYpr{_s=D&ZFKRKP*5E_6Vb@p+OXK|J_9Uv(~99D6C0Kc0{U#x{4H3RJeZU zm$SCc!}=T_AF~1B@87@QwYTR9HaGZyjScH=_tSyTC=R<6&0NFX7pCvaq7J7C8tL!v z$5NECF+SDPvyF?7-xjReoM_tntpMV=nTkquk?^s%>O{>w{*akY!&c+Ei=Ot`Qp_{= zR7}y5cSn;D$8+;7j*51uW6nsBm!54v4idQqpPYJa*%)s!@AWyr50zO`;`n8D@A|Ey z4yxl&%95$nH}_ar-g@{=hNv5rvn|{pfKCNyalwIa&$jMqw2TiPP2HG?iC{46CEz`W zBg`x;lupMsR#s#&bW{q^CfA8#A8goXV5>Ke?JnNwgkNaY(9u~!>-Q>Dw(s+?v9WFY zn17;E16f;2E{^a(KmGkx0u6)TgU1h5orJ%)n3e60gbexK{$epKx@T8?37PERhu-`A z`yV>B%?|k4xL(`n96Z?_zqqtC?G~O0=}vAX;HSx#w5`_f+a$TqtbaQB%pqS)ZHW?L z&_o?dbQ*;B_0eNZ$u0mGHCMj9S<}%ku+kxa7RTQNYC=$B=v!K^^ey}GHjddvc<%cI z$Nm2O`$L?)tt}?r0R-`fY?rr=2@h_lb}*0yD;;06F{1fCPdG`(0uJWFqiRb4#+6wKLf7wlRbq&~mdw!Z=FDiR+~lXZK^Tl+)6 zvnsUhhz(C40DXeQq~U1H9WHHy2M8IvF0SY*YCh8LiOUc#w0JZ825_}s*rSqT8WpP}Y@sIpvPm)zB+-Q6cK6F)rh#ROJ3q?{>`|>jPXt z0_?|7^GWYUi9;R;CXYMK<(D&f=#c#ccCM$$=$d`*%%B`6|CNB~$x)AI zI-hQL?^0c9!K8jlVvEjr<41U(w9+dT95X{->30iCnpDQPxF$C(BQy84k)$&m-uo# zFFSi4;-t2g1Y>+3M?Gk-8L%RF{y;-bEvK&%$GSc78u8~plP7^GLJE0aEV20c6I40yO|jb+=xc zh<1Yn2{=?hZRH##UJbTbG0!kPcBs*P6!C#mSMZZBTgMg}*dI<9Psz6|0`s5QZ_>z~ zk6-Qf`1VwC-NOX|pBx2Qs&QgnTFTYd!s1fSaav>si56YEeyQEI1Ks)-y(lSJ_FCo7zU>)I*GMA{AB=4gS*||3 z6ZYcCIRK9y6~x<|7~n^X=r`WlT)NI(+=nTG$=%uVJ;`5dDok$mIL}pB4-ulrrGaal zP^x&Aho^hs&o>!c>5X}Sjz2J2g*@Qj;MnU)}m6ospxX<9AzpT~U4F z8z`0KgqnHH+Vw?akhsq1IO~Xd4jGFH}C! z6pN|&-MW1{aBTeJXTxfh&?nc$^Xb+dQf(gi)IJs!1M zRb;A_dfx*$6Hu8}DvvYs#9sGB(G6ZGBws0s55t==5$TRz+m|`tdoV89a&)cd@PXC| zN~f9lTt?_;2Nm9-@JSQ+XKwUXWZ-l?bEOyPhPn+J-_xfMl)jofZ&S_ zw_!%sKTB?{h{W09&1h21oj-s6p|xN#eW!06^S+djy*_O4#I>Js!G3vMI73@ z!&mht)gnodNT>+kmHJFXdmnZepnTepb}IgmAFyATJN6}`jWdkm?T%Dr5UD|}#{L-8 zBpLplvnX&|2$MAp*MlB*dm5q>ug?orp?Y=Lgf37Z!2IPuM>)B4rI(lXeiv=vZpz^e^P zmEaVFL7*`N@{MWPa7=1yQE6%EyD;VJZ;n+Zax`5?LmXCw4C>qpK!{P*)1!R(2Cn!4 z_qnmLasLR>cHyonsp~zh2q5(!B=yc8sY$w~q7*~p#OA!xZl57E<(eG~sBpuK6!(-& zd#sEO#T_^PbP`S4Ox;6dd)yGVQM8}B4y+}hc2M&04+dM&!&s5tkRV$60 zrdVq~y5@6m3jT;Yy|wcJee|g4NydkW(UE6>D}*CJk0EN_-RyYG`=`&hIQrzUZ{zT8NxP>F2m5 z(Kh}NHF|jpS=NkV6+?qw@Uuf0ku>c~0l}cVg3zdY6i~3w6Q`rK5scfw;2`K4c5=zT z&&(MdREOjT8a6e%A)5&ue#@W3|3Uq?!=FlB4pncy`(1Akqt8FN3Y5E z@U1#C>L_zYfinZvjQH6U|AM%!ITpxi%Y*)Kh3DLjH#=I|J9&06yli^x_`+xnTWXcnK<*o~3{S@fWM@DA*qPvoomHRMnF5$)@b+Wu zv%n!BAb6=yzS7+O@oxXqorzCEBUxM|~c~J<}99L5Ic`fBYo&jrYjMfbpmJNr7mq3PQ3;w(B#4^$^+HOE9fOL0RBD z*QlA7n+sInaIzFG=n~y*sCfx2bGhepU^GgeVrlO(2*us7Rlh`XG6NCx} z>$t!8#*PpC?$tmMXk~#J1JI<3-kZ`ao={|_n41h!NDhIo1$0@Ah+bbijA%Pm^83$s zQ+ci*N12Or7eCDcMNzu$h#1((Yh&5C2MgOH(yfM&6}XF7kOYFL)c^f^WXx`$16|*? zP>jTEG04JMvf)l#GmO!uscZ4e`>!SJN{LPi8={kVM0*@c~^>7s{@# zn;&qstBD&f6cSsdgPu(3ggLO)%lt2ZSm1O4(^z5D8uDQfZ^Ogb@x{YYEF)>e`ioS0 z?R~jYc>-ScI{=>2-*boJ@UisTBQodO&_^kQkJ@{E#ws z@*(oRUF`TED`)5D+dLHnE!~#FL=aDoGB`Yi?tlnu`Fp0LRP%wj2!mouJ{X??Q5xhF zIk1l5t+Cy0K~Nd}sEo!(`(*!6;g)*;0L>HkMSd&Zs@$2(A|=M6wr?!rMF#gi!~&EU}$TAw)8uSyKl zm2Y{Rq8A)Tqd^&p6a4s;ZR!MUlQ-L^-t9Mmp!fR-FFxuzzYpxq3%py$kJZ55z{Lar zn=;4jc!72#90e%CeLKP>tbx)C)R&4|Yg*{_UV6NW{!S2d>~3A59W=IOSA{;UyVvQH z&fdb^#z+wcwLAbMU3nJ;q12bu68qH4Z69$#FKM@!IBYMDiYW)QORc2gj5)uKI?X< zw>H&rz_a7^jK(R_#4es?{sc5;C0--=7eU)OdL2MX8Alsjsi0)4Hfl5JOe0jIyS-wZ z?Gpeg>Sl}2pr-w{v}otk>lHpKhG@x#!?US&{@dcELZ^(mMXaMh;IM_Yww3)cY zP64`mc2zpSx}iC+_UR}PWdK$IHUn@AXx4(kqrrTw43NUWAQkAeZ6#`dx2muLqhs2o zCc*eEtMV<39$999xD2_caLg12Hw@CvZ8j&CEZRI@!3|?-1 zy)NKl)Id@OL^WWE8Nu8X=t2YpoUIwN^8g*XM!@w$UF#21V1|IEb-YR+xYN_lz`et; zjWN34d{`aUep_LO+E1ET&Zx_bB9`+c(- zVjM*-P$h6(sCQ6;wyE@=D><5o@=IZj3<YOk!Ex5lhrfy*ey)&q$gVC~5;b-;bfUXU!mx;CQJCF+u(}7xV zR@MkMPFFT&r}5-T>xWQDz|w{VsN)Rd1-NR?^YEUr?{y0ZMkQ9?F$%CDJkjI&|U1oxCM=xJ&4MX#$vj+ zo=`6nubtG+_^$z51Gv@9P+~Z*hXxR@Lk9tx(4axM49y^1j|HwyXAjUA1juwbX8R?L zMY^bJD*ERP`d2{b`n6pYz_Kd9kg)vcr#oI_pi+c-96}X{29w(7y;rcP@qOq1EO{H5F=WlAppiK53j>@i%(Y)tCkc$)z}ai zxWamXR<+z76D#YTQpL|yXfUpcBbI*J7>|jI+mk$>4Lsa|s{~NbpiBVJ^ajQ0U=EJ* zu*H|t&0V-1;3<^yZex041&|TOsEYyZZqTq%uzlKN$(c*a=#DyRa$+A-?{`|T@oDj>Bs>S5}yh*51%&_xFAunl20X66uW zM+?x~11tm`n4Rh34dA&R@%L5nHt3guAMVMo$A*9!ANyEtqV;5g2pD7nis^JcKsRo^ zC9x(61mPG=F%Y|XfngXhHUlO!qGl-h>p&C*lrq`mU71spnR*Kx1PAU9wDfjM2~19b zVM+3*xL?11rGx6a#szZ72*CJ*g-q6owJhY7mMVkl8w`%X5g<4jys#+VF9OqApfe2a z>GOKk2cs;MlJsZ!4x@z3_w&>snIIjf+j0sD+P0Da%W!WG`yiY~M0mdU5kjP7R*&@l#mV-VW_ z4Z3$(+Lk8~C?3oubRrAw}96IgJfo5Z9Lf}PMBLx zjEd~Q$xbsgfY*b3MtjnI&Hy>a;7m5?AN%??0@DQ)3kV=|K`o!Mi!J?6?{WLjJ)p3F zy$fO^5;Abg?l%y#q^{m}{>sT+{JEm6_$yR9|J&(Lr#Ts3@`nsZ4a{5vkpR63vY1dW z4FNPxmY@FsDfR61D8#H}ktYJ4EJm$OLId>}m2J6h_<^&A!#$bV0RiWR(dMTJmk2r? zXYjuV8vs*gGk+V@JB35Y#nw}yaHy63HNAYQk^lTj?fsuAga2H?!TG=X01|Zv<-)xB zQdFTt5VUV<*gu|Z61$GBUe9jy#4-)T@Z;lC2#){kR(5ANVVn4e);d3hDB~< zLtBYN5Y!0x_gZj%ht+q!M>{+P6OR4cBl;PfFRnrNls5avo+Bmne^|`(`c0%|PzoRF zPoTeE`UKtU>^~mY@~8Xmm@jo)=UGK&U`@DlBqDk8%ojT@R1N+0l4kE|z}qJoPXhmRdSEuKPZK7Q3+)ZpRc*L zIXpa&$Fe^%JC0@uZB3UA&6McB13u{g`JYnZvWL4ZXeipmcRKUUyKa%C0i1g4xUr#1 zexAU-RTa)(wgm(Q zDomFY8XEc{y^bH&B(jWnn1p$nk~$^(v^}J3Jnl<~>}56mC9TD;tX{Z{O>$ITK^#Rm z_95{4kZXO&c8v6T289J$Y%UAGdl-!f=A>Wpw)_qLJByuA zQHcCaEc1Ri?F64tKR7i7X~JYFx@{HZP$_|O9f9(tv39$Kc|rK_jj^MzEAa{q9JME9 z4NzOlkAcy&RN-3r6d#OX)!KnIdSgO^$U1Ab-#ygt5MTJ}YVjy3<9t{Ocsx&#NaiA; z%tanTUp87D=6f-jDfi?BpCot$Za*~4+&1%tPC*G85eDZv^WE{OwyWO3UT5;nraxhe zJ@rI2q+ww=LBwuqt)atimMdE0 zphYzXIT3jIhP${dRUQAxnd2o1CNz~^3A-^hG~9cLLcbjkqSN9)EMTL5rQT;bh%Emp zWcLfo;my!UI<$+(D4JO`;?TY=tEpu0%iu(fNyVFg|$w2tYKWf#l3tFmqXn@AnOTlx=0QGJS1;OV4ke*RM&J`*ik zP@srj9Ny|hla|%DZ%%e{0T51bMerH>R&eEo_iv9WIF3M51p7zoyHlW6A zZY1RT`KGE1H)%AO85zH~Y0PLIcfaD-Rpp|KWth$5FL^}Jz8veu?LpOZ^ewSnu$v~? zp~R(LoJ^3v_Zk$VVZi~ZJO0gkw zH`$H->7!LK3=3*cs(}cW;FRvt{A9d+v<&fW`HK;BUx3q4!}czVa|HGlEib-RZznso zHhr3BNAQx>M}$4FoD4#!h%h9(yyvo$NzZ`x4G(PP>cX9fxP&8S^Y5C25C@SN{t~s< zUjMov#ZR_DT+Xq2gUQlOY3I=8P8>%&hzOHa*N7-DA_eE33`!QN2`prwIy9tTQ$>}w zNzija^1QhKo}{=McsnA+Z;|xSY#q<4ulk*Py#1YdjRPHU^t;29_c&K8o@EV>Fu->qlshnFjJ@K|&eZu5qo>@a1Si?B@8@ z)O&IGmY+OOmZswrXie)WBPc?{aX|d@FK-8l@sf&S)r_yq9#$Nc$=E=QBk6Qhg@||% z>SGh^HsilBsyD6_;NHaG`ebJ!{^5;=j0;UgsseT}&oBy`1o4QG6L(sOQaR^O?dmQ= zen+GIzF&WcaA<|VdWPfDo_o7+J}GCG?CxvjM)?G;cES7VLe3m|l&DXX%1eXWcd#~b zgf=l@82FTGcqG51z?#uQc^-0_P44InHvV%lNuFYH=ZMtAPgdTx;YAUVivwVKrqH-FZJ!;jr3eZvd6lnxmzP?xXd=dZ8kG zV-nV&W2FX9ThUsMF&8u_dTvHIRVfbkls4v#kzVV*cPpJuS4fJOCE@yzh`3X}yyqN? zLUYH9o8j6_Z!7^40l$pfmD2tbA50r{z*n!NKDW-{kV=Ib3GmTp3}>&4dv(J&wO& zIXQgEcVSk0;d|lt!LF~d;cQU~2iY!3;$))S3^bYb<996Qv=<8eq!W*4DdqWY8Z*Eq ze$bglyp{Lv?ML7D*{(x)*EceT)N%DTFzcULRZ=6X?>3spP=%R~>=WTz zR&hKgNtv6ZS=KLSxiDYfQLPsCQj^!!jb280V_klKKL2LZl{ui zTMqtem607p`H_+(di}RP2U*(`5+&tF7XSUOQ38s{@802B1h!!-87nXzff8D5#3U}Xg5Ut<59OYTCIxx<#!Wy};; zmsoZ_xU?}Mql+S)7~5DIdw+fLwVb|zBKM1}6v^^${dB8GhEp-Z%Yx>i{6e=N(hG|@ z-pdkY@j2ed!-EPzF%tl2Nd`P?5kp5aT^Xe4^zR>@P&$hus?2Ix~Fao*Z zh2w8@j+Uj&7IMq7p3t<>4R~lmX1K$PmETIfm@%}qY_u@S24gf}H8qhe(gRCVKRQAB z!mRo@{s^BXP!^?XUOB zt(*yS?hMXE4AIj}6wT}cIkkJ-R*8rFyx7LKE|t0cz9V{RaU zMl&UbCQ^MFSP&hUuJqSJ8 z8R(0Nj=iQenv+?l;)z~J#VxQ-ksCHXDz%_oy~ zZGI@Xww=Mu-g+*DuV1hFwIj4%7_3XJ@HZ1MmO>h&Fj0<89B(?RAj_B$Q{>3{gIBKt zNt{JlSojpbehas&13_TSvHeRYH3pZqspzSL5JE~BGIiPCnHY-Ij1mW-knOiex%ae$lLJx)3J~A zBKuuy>rt+Smj=-xB5bB}lJr85lyQGa`LRyFF#7079y@Wv+)JuP(}gla7g6S8FVl4B z&*jnRa#U5{r)Fj%Ilhv5Lvb&on^WF$ zj%;Ce0%m`dYO;mfut?bHt<`l~{(If)={|)h)`KXQBwC&lXph&RG5phfN)qeV#5~}` zx9a6Q&Zf5^iW=Px8vI2YA+4r1eue21GgH(`4P*HUE~h&=LgZNF(WdRfz(sI-n@0D_ zf>vG`V+ILsuFp{=EjlEl8y6RTL|r33c9#tHjXJaiPXKVYkLG$b=x`5>=GK&5=BhHu zVdxg+-g*exI?aDGd(Py?G~)z;hm5_3hzQYGSmH7F0S8D|{Eck#`s_|#e*1|V6-BUy zhO}10GT8Bfxy2G#07se+I zezcThqBBOB2&~z$Zjt$qbB*?0R(+!j>RrhXm7KF?tj1$>yOQB~!5&%<@gNvYg7;T8 zm$u8aTCiQ&{HQl7zUy05cdgdm&mm;V;K$B`^;DE? z6-@Y(V$OVYEPa6vqp5jNBP&?c{}oNv1?pK!e(FPvHHcb|4R|@F1o9pWV6Bz)eSx%2 z^H%Y?L^3_cj|Ix_|GX>sV1097`B<%rerI$LQU9RJr;6{#K1qg$O1std*UezXHxK_x zE2scV8MJ=nZExz{D2ahjm3Cu?RT%C4EgBe#s?j?)fBGK9+XUeyL>4x3d~g3tXLwh< zNWC2MQGLup6+w!pOzoz6PTu_kzN0mwoMQHa%@e<+1b)k8+cJ%su7cTir4^BX4U>^3 z8f!-!A8GCNC?a19UT8Rc_DlJYeu?v%JEx;f<}Zb=fzbfnuEah4Ppd%Y3xKxA5mWsO zY()Mh#6;bxQ}aYK`D{4?ynUTgN%g?J)9`RA*w#uIh4Cub+naWsT6V+>W0uG0-alLt zL~RjE>brf#LrG!bw|$>_ZfWJD-z_zI2CJRBlY^iu z3!Jr4CBfaTzvXer{7Q^2(eo=WBbGNCMsptSDR&=^*uN)@wqpe7qu!VHve%VBrkj9^ zIDN(n0p&bt%OYNBzx~>N!{@1r`uBV$&oeQyr5Qd+&&$&AJL=ItoRHn;UNxD?i|^^% z#Tt{fy2y6WJ2P z4WY_10DkfuGkDnIyWZWkQelpMb}U{4`|!s<}3dem)t3 z892dOQ?p1&upM{q`~2)g#T!I-?*+s}AzK^R7V}fgr^zHWB62!IvXt>b9pL}rl)j=% zOqN&1Ttp5noohOtDngDGwee8?K~r!35ib;A>>2~< zZT)){c9fnqTA_#}ytNQ!BPl^om!S;wMqZPxhSbn1vDsFoq`Ab%ma(v>bNe;nt*^ee zV^mFaJ~E_&TD9|j2pxkcg~sN6ZRfa+7`4Ul-#O`MmiI5(UY(L!m!kJAvGSYf6e3?r z*-V%5Aw*(oJaACA5}JmaH)T=}qBDJ0i1ucx{aTTofYO?OnI#u4YXFWxno8?r8VumB zE%k+{L}b;`!wn8nE!6G=yn8=4<~`Z*{U*mbrLoN)JQz_D-88HS?MA>)Am{I8>v%84 z#8EFapvp4!x{?>~i#DvZa8^7U`4NexF+ME%N4iN~i8LaIJ?`zrGcmqOaoT;h1{I z*R9A?yqdMo2_c)T*FY9c`TW($RySabwlAZ4WJB$8%4`L1EK(sE8yPn7Re~*#^Ig`Qn9Ow#rb0fx5O3cIZLeJDqLgaU_Dk)} z0a13*LaJ)8p~1l3&NkK@%uZ5rxfXNE!s)@GG?hPc_x6pF>)R0a^zx#I4G#~0?dlqM z_t2Qj>>J6%L`_W%^!w-F8{oP6b0>on3w>GEZDFKjqQ3PH;=WJGkm*!Pryl0_kC35# zN9`7t%Z$;dM<8v`Ww+2XB;A$x(!d-gv@l(PI&d~7CYDTA`4v6{?_00%)RR?3S4i)T z+*;>$RaQ_%g9ivd&Yz6&;Y40)5Qbt9v7hh;2dLCE4wMBpb9Y*Ar4jRz?sY{Ax*#$b zmw6hWuJdl1g$Qz>AgJSmRE6}>n5n_N=dmxDd;B0&(Ht$+qbYs{T;J=5#Laq z_LO1why=b5%*hYkMSsq&0DAH74Fbp*9OSqFA|BI?y>-0H;U=olM`_7j^NynTpRfB^ zFvT>Wq9t5=JSm(RIDomR6Wd=x-AA>d+vL%RUX?3ziz-i_UXutRO_NIl06IN7J zZSPlQWQdQAiTlNaJ@2978*e({gYPMTp17l9x_`fump1-5U)Q)TOi5oqrR^;_G?Vg{ zN_2eNoy*yHjh%^2cF4~pZe@2rJiFYzZD*`@?~qXf$pRD;rk}IvHvoPR%e5Z9E4%?) z_}G>f&$L(XvX%^71hVlsAI#oDhE7FZ4QFo}euuzP%)8sT7cym77>&--xsT9lIE0 zgI?({0v1h|$GvLYjJxnF5oz3hcmM1u-3`dT`GvTn-D8A?)k}k{pU)Q(E+Q=%+gS_g zzK@R=f*UDIg;VrSwge3flQz^RStb}34E3##OV^BzjV-RM41)IlOMOvZUg#<11U|=f z28E<-STutr3IT(D1zoDPa7uD@-I5tTT7$eHZtK3n$~Wi{Up}d#qpeQnqy8bpLY4%L z0Hh#FP}`*XX@3EAmOlf0W#9e67|SVI6DeOB+& zsj4P&>6`p?d39Gy+ma7!lsKEA@2g5@tSKCUp!q`X0ugGWQDmb(aN1ltb+3%8?p~qc z`3zvZU<`n*aVK5hbAi5LXDU?SS-E8|qwfb&MxUpKNTj^VpqqGK#PU#i%f<0S_ohtB z;UY^Z!ug^s1Ex7V*#Il6XKO_z)(zp5wgim1h`zD00@tNM%duM5+T){rpCaPhx2<~9 zpLibcPZT+K^Gktes9#FLYJQe`7w8YAZwg+B_>4+^U$DYTzQmU!z>sQo|661mK@jIJ8(jPu{D1YnX?0uIdNkl#Vmg0c1HK7EuVSto) zp(1O0Y4GjDV7hf9>+Gx)5+X~&2v1F`#3TW=NF~!V#=XJ8*%KqO8Oi0SKH5yC;O<@p zzQ`#LJXmE7Muj{lUlZ@i3-WDNmu*HZ`5{K}R32Mx$22T$1|N&01C3dXL>-d#^>NRX zauTiJ?Re@e-65?T^19+b!E#n1ern=r^_U%)^5N#Iwno!6l^+wwaBLBV}t0Re&j;o%%(!|uo2z;%lU z!9&*q4nO@t)XB8$10_~}&ex6(yu5-*@5Ps#8wZl&T|q(J?t$sU9P*++bTY4C8x>QJ zKl77$jn*hxLam$y=Fw$OoNi}GV3Lrj?|0~0px>aa zD7+by;_-`=QpEfDcc-{-z*{HdT}Ce!5#^F1K6zx>G4tY|JHawh} zng|QS*Lo1=PXvQD;pruw9Vf30Vv=^5*;{A;%;0cQ0}cpU^Tqo0?!M77(Y%7>vKFiD zopbj0n}m0;qaI@%EPUm%ubXz#9wbw~Bwl^q90EBPFqX4|Lo$BMu>oCAZc?!6)hRRI z3$xXw0bh~Hw!UN#JyUDK|_7mGVLwzj~`nmqVoF@haFkb4Lz1rQlh`~M$VZvsta8?}#DDwQJ1 zl&MgXnMw#1LS~YgBxF|R@l+BSLlPM?W;*8Km@6dnoZ*<~c{+x}Iez=;{l0Jg*ZQBe zTJKt|-gBO3-}k+TYhU}i+g+(`@8=CI_iZl^?N>cfRZWE9hJT^`meprqEPIvzun%U6 z_|CvosJZ#O&wr49&ZtUZYaz1=-}T)|lu*EsqWDJ)o}2$JF%&_g{e5%Ep@MnDkU-}i zO~Dt@hJN(>@%!cFW%q^e>07&#p^G0l;yZE;euiQMz5jSp8#wr77pk1DXeUs;uem(N zcNoXxj-S{V5aBB_3IrVy><(<(tw_E&OL}p>`8QV zG~0GB*xxQCyc#t$IOw=Gb{|M%YcJsdFJmjTJa$%kiG=-#+&tIzcySnA(3&|E&n2tI zYfbBzgIbr!kRiGI0G7&puHP;+lDjc$?c3_&cDnbs1GZKI?f;4s9N#F>&~=OJ>7^Oo z@THYY-H|uR5je+12iZ$;vF1>zT|XGH*rbu%$?i3xqk(+Ycd~I8IN8{w zCL2Thch@KV405xw6#U4Ux{{>)kVkEep~kmZdif5J+!7YfDlgYT*AOx6tEc`5nYV3G zpK6a45z1D3*Dmrhe5vI0E4PyrMdyFVj@S&^L?;_IukAIHe5S{*;OQ}ri>pppCoKzW z;?_qK*iQeO_njv@-0@8cd;>VR7U2n`d}Z&8%-fm8*MCvDLy`l;HoBfqZ^Lh*=S)PG zH*T0t2L)#w5+;54PQ_z;(|r7fK1F?HQcI}&{pXK59An5mXq_3v#l@?Sjf;D1zW|dC z=9~C|Qo)4B(r~#(n#DtbjmsvB3muE0{d3ui-pqpmY2OP=sxE9z@U?@lD8mY+s(N@I ze%x5i{$%K}T;+>D^Hxdditie70-ym=HF8<}RbkH%_3(x{mfK)g@-!1qaaEOpUa4(w zUyfd}*Z#J#>=9=9vJ-Xl++t!w!k6!rk+VoJJ~noimG$F8akvG3PFG3Db=#b=SQ##l zfxTQ%DBGr{%T2?>6P?U==$(vlf8Ty|#>{+MurZuFHJjgTj z7TN7I+-F1)&xw{#(dc~RFCNR!7O-RzlaCmp2q{}i36XNgc7N+umJg=n6BH7mDAu)Atb%#LsPSM)&T}CKHlBkAnb4ih9Ua!tK4J_JMnqTLW(;_OzFcbZ| z|1EwH8ZnB4y%|;?o8{~bb2AP>s(y+(e*6{ z0yo56FwE}GyRo_hq-j}WE!~<=#gruXn`yT?+Nf|ZW^M`c{G5HdV}YqAv>O_fjI-cd zdaquJ5ZYKmh=X(n+-9urGmag(9D-!KKnH{aKR?%*49Z)zFCa~uuM1TWj~kh#nw9Gp zXH^ovl7?LwtucZL2lw4^KisfGyNv#em`E|hz0Ee&y0 z%7A}0Zy{=Ne>eF$Z2HTDf3uE9&U2BqMLb%Kd}6>s4Iybq%R=4$G**>K)KLzx^<0wQ zR*#0t_tH{fOB)v##!g~sd_Vk&VmEzmX7%T-&{nkrZ*S3$XSkraRFkT@;D`CBgQ7&T zw>vfEGF|*9>e4vH_fobxq`F8bj`x}sAiUjZ^LUXLj^jS06br@uhTU1{>IIK%qZ&hS zI#+f&ZI?RrR1OW(^|w+JKTCQHaosN^Y#h`J^3VfLtY=j2N%R2ds#JUbJp=n!B@E8Y zVo2E{BqjGY5}3pc0rSrP6p1;3{qHzPAMSnF>IwDHyWJ*adqNP77;$Hm1- z(Kwl#n`TTv$~E$H<@M_5+!unOKpvRo5expkvf=I(!Z#wB``X-9cUG$@hB+4(9o!-F zU>cY5XqFY9sE&wESo<2_MX!7QQ_?=Kz+rUFfRK3eP@j`eG83Nd`)s_2^3I3{`bmy% zDhxk?FE7(oZ}jYTe+A66_Kh7q{qQk3$jnL=O)(z0gjM zjxQi7cb`v<+L{RDTkxed?11<7v0PoAW$qZ3Vd;HmQDPkR)!8+{nn%wkWD^{e){``u zNG+b8UzJysg&Yi{9&Ic5oxB)Yqt3PLrgUT92NL0qe{Gf{uP;(R60nao8@?p}@ZozJ zZtX;%R*<{f!JJ8quw6XjVi4mm{T(wie=rfmB5Byjf~pLWB)ZD0L)N~%I*Nu$&9X6s zX|UWO9fLVGD}2=gkcpoqs0Qc|jx{|Iuc({b!N57KZ&W&435^Y_7TPC^X&lwx3j0wy zF6JnOeeR#Lf7~OJ4)yv|i}hk0hZD%P`moKStvG-fRN;s>0{Fw`9B>7U^+mLFWT(DH!k0aFLHd%d&)J?jz^- z=MfsEca0bxGla&wN2iVKiN|?_b;9|^fw}Q# z-oXsI;!*UdEC++A)8C`oG%t;+Q>Ti2^)YEhw6LEYwPLX|D%`5WGRmD80UEgpq> z$-Cp~k{B)tYu>0*(cP^}#3QxidA+HgBfowLNruIf7tp!>`7rWqOuo>BLE|G5c#!HF zQpgw~^AM2fL53R;<zZlD+a<9ln{GCYeE49qlE2X26BEaQOs?a3sre*@_!PG?qLkL~R29uJjjbBiu!7dpcJ0|DBMvNG*~f&14& zD=Jb)3@yIQQ-l_|SWV_83led5BHqN{#2Bxa#C_bT-SGFXeDPexC68+Mg16Eo-S!t9 zhF^9?s63KV>=f1o1ZEM+`<`wU$(-~AZ(Y8cgI6_yG}~P)7P*9iEBy&CU!dEDHr}?4 z;0r>mw$Bdrcl)w*33E_e9#o^|H4^ZjVAf1iRh8fMe)p%Cm5BK4Pwswn##h-CFe$pa zo*JqNRPc77C8t4h-l@GuPUTR=zPXL}vxET)jvkuY?vA@_<9PwDVxpo&Hbb|0wDax( z)bqhoU(OcHJ6Pd@F0mO(BOxW9Ghx~P!hL^=EkWDkUX9)GRe8+9l7;SFYzGn_+%~4d z#IV!hPYlq9%rR+3=2{D{O&jt8Q5X;3Kguf~CAO_Nzp7;d;{A~8{~AQ<1CWSr`AHt! zuOZ^l$r6{=AWLZ0^LpS+5g67H^l~!q5kr zA|`%czIC*JVk33zE4d|VKGZDgHhR*O{EqKIosRH=EKc%qd%m+gl5z54=2#6pmWweH z0bDn|4|a*?R`j`YFovQ7SbXuOSBhh8S2g=^_a&Z&K}UA;+0Xi zS*9u?esM6**yoz!m&C=~3SLVa5~=z5Te#N!QT4@f-d<0$c;+%YUv>*fyhQh=A`tY? z63QwmVf!K-{GyVjLruzlfvuW}*%-q?=pr^_A(!ulhB$Box;PHGm13y_LxETDnjLY~ zmzQ6zAG>$MeKDtWYtiUXcL(c|0D@|E^^PuI#g18Z50c!Gf#IMv>0KXpma2w`AsJ#-yO}&jf=B zJ|hX>LX<`4+2ibgoW3M>yY_c=m3UXp>L;QGThufGue2@PYwc$D)X%z5!^MfGSKndC zV=*XUoaaou#>M}!>VHB>h*dNBl{B)n(0%RR+d_m%mP3Et3XL>HH6Y(3(^7FRBW{1r z-h5?uGh3h2Yk%m?=vN6WFrEg?6?%KajipCM>CGW&2`d$P1dK&|AIaBZHx)hY#Zd9S zG+&;fBJYN#a7fTew+7iHyQ-DiPwIO^?8gt!N~E`YtvKlBJ$HyVGZkdn0;T1Hm}B{)*t zLD5ru#jZ$lvqh-*c~iJQ{^*0wj*di|p^|fZ-v1N#B9ukt8<|{U3kpeFVvFZly4a>g z0s1>^#b~OS8?wsdt#|C4G~0jrFtjkTc`XTd&6{=}AcSSPR|wwJ#Z1e0guP;9#84bR zm#kauSq{hqRpf2Zo|Pc3hc3og<#wK+)lY=c!Hgm??(R>vW%u1z%Kz?MbqfrY+#lH8 zicEuE(N?QuW$MWFcxqca_s8hN<@ALWI``*`WV~hf62Ld()he2Kn88*U|5C!P2u!(Gw>&`@XHD5e@<$Tigqr@X*7y_nemx?;NuM` zggoU%dk-Gt$ghjgiw-NB9jC>ic}(`JZg<|?YRz3;yt&y+B$HJ=YaNqTVmF$rW#}dY zfC9_QJpq%3xs(XyuKkf638e%HLe&F>%MAdV!TeNS{;A1IT^n^MSQ9i{kWJTdXFd zs!IDF#Lq`p1YvN*W3^Ysdn3X){R7z(m@ly{8$W4)BH$nuQ2wZ64@o@y0P_HtVp|{GP*5aa&Osl@*h#oWVM4)l=mdv$-E6*Uv{I^2iIrmC`Nk)m zvndLwYo>zk2y5@W6(k|=7LBruI>pgb$36LeF7?fNtaMI`qt>hKe_ZYArI6Ek6U7o> z5#zqUT#Ea{g?_O*QpLtAX)|~erj`(=@~5qh{5#)eXr~Moxow)kn%;-T$r(!~m= zA)t3ASd-D-*xai~Nk8P+Ri2H%>a}sjLjV33HKfj=MmPwZ-_p_^!tA*gDD!=jk{3ZN z6t*z&0AhC;z8Z9)ONBXW0A^ z@6v?O(CFU6xF+Z(f;<^aVuA7p3?pG}`)~E@CwJYUy#nK(plW;{RBZU!@{qRb8{zFJ2(xNSgZk*|2gMjnuqAtvID;7usX*Q$#%U@+F$|RYKR~in+wy4|tYf!QjG*bRH3Pj9C@z7o=-6IQWovqZm@${@eG>J-n{~*HxdxWHVJooV!(wtKca()sLT` zefIh&Xl_8%i&ZwSnLsW^0Ld`W{ss+Ezy}b)?Q#2GS93v+vs^znqeKU!Bl7G^{~In2 zXNQ0OPm(m;Tjn=Na>F3$#eQ_?^;in8IVq%rnF8BS9VS>Q zi>Zaro$D$v?X=tZL6$mta1Fm4@~R?6%yH<@R^6q&Bi(;CK~%@6>RI*bo?ZoD5-QgH zZ8L3&_&bA@Qn@vrDm;2d=Qv9yH-)w$3mvuwgbHr)&SHOzeQ>CxB~MSdpst5F*SAv2 z=k8s{kTZ3-p!PrA-ip67CFtN@`aNyYY`RrK1_ONGmR-sr0Hb@Mx3KktjBhvh{#g*W zvRt327oGm3q0WIidAy^$I~n?B3&Fx8AZVjzXSe#XIo@NXTy(bR!Th+2f|$|iT%Q(! zuExa_4RnxlE&Q-Bsvu%yr^)LRuTvlnG)6aPEb{9Fy=z1 z0*(?43!V{s7aPmyh3`)2i>f&=jdPocHZG0ePvhhbz_f`5wDq%|2Y-`^S| zWE#7*$B|l@Bulxy0|!$}Io;u}X_|6eW?`GC0#c(xkbQ4JBX*I@hW6aKUz-QlaA8+JCX3FgXsX2Y#@>I&GlP+z+d){lX%&-b)_E*LwVkNziuyEFjd-KmL<22 z%o_Kw#OCu(-6<4IQK{zBGZGh*-5xMW;ad6JR(X5M8D8$lSzwp*AZ-03u2hVd93dC2VYSe;=9B?xBbJFt^yp3YLscq$7X%3S50oK*8|Kr zi~4=z-^fip;)Jd4>e=H?N_}XxwfMyG_1+>Y z##QLYn2l2PCvE>FTi?%#SJEE|Vnq}k9t4h=Rc?5S=a zQu3Xr+WzrDbSFjL-FWi;sFkp&S*SL!1WgKr95tF4XPr>s#Iju1NI;AfKW3@{##x z5F{aUV0M4g-CW7`QqnW0=cWDJG`_O)GK@!RNnQ_@|9JpoQnlBAz!2)?RsSGFa2oVc zKv244kp?cIfO+!(?DaatZGuQ?#@Jteq3n(q()PLzcn85#~ho#{j7iPJ~AV!P+Js7Vyge5cb^uQ@gb z3E2zZ-q7ahfU=G<5cCNz0&$@i?~WhgG(D^{Szua`C^NC?l1f-YxTPv)`nRkF@&<;I zqYiglKL)M-mIoxdoe}bIxt{OV+@HCaE>dm;{gJoOl0dFQ#o<0c4C5)XlHPv?f z8GfXj{G_q9a++y&6-=9=i_LAB;#QDPBb7;NI zvsi5~i9WFS1s#H$#y^e+ym_OlB(8?BD8AXuJiJj6lj6a5rusFK>B=LtvgCLaB^t{- zbvi%c$B&(~GGs43QX~NhmB>$@-c@;n+#4jp5R3pGO{S$glHxCGIEY5ZUJbBSKBG&^ zk;GLqi~d1xz8VV=gB>%hsCC8S;T{*0eGQ`{Bk-0$;XN8!r{9L`Y7784E|~AnEqf3! zbuy}%FVv8mu2VKJeIYQarFZG=?=yYv+IQ~PRL-+U#)e7GRq63YXRVuza%~t^y5_G` ztV%3H_tjJceLj;7Xn+XlueQY$KlRW$v;!cx!74tURc9vwy$=J z!vJgih)dwx(CTD!qH8I0slJG{htW#4#wKr>p=i#{KVipCHSJn-&BlLJK6qi@ENu6g zxWj63`2KGL;K;AP09n5_W7bjB3q?yy>sLvvbvhUdqzP@LdXoQY6axVj#Qj2DKN%m9 z(}Z9B2E(%W@f)Foq8|8~y@Uk0MV95AF0q)*m?f5sM!~HYMHPJn4LiyGd#VI;GqZ)y zdbVLeP+)QyPJ*l4{0`{5-qy1c*8J4(2&cD1v&FfY6|w!`7Zl>gjBVA)RF3~6t)M>u`Vhvy zp8m7VF}lpl+Tk#~{sd=Kno*dn5ijYbP$ROvXFPJ5lARPnY>*|$6TCOxjrS9BU-@@I z*z@}*IzX4?Yb#A31}Ir@T?66agOjg2f87~OWm zrJhj*nsEdvrlPY3r`+t@_1tlMqZFBkKs3-^?91s!qX<^eP-e?+|M(MY(A2PXc; z^egApksL`tncTMr%y9rBFV?;xQ440dcV1Ai*K0g(uw-p8k*3s<@$o8&)^E@K?{uU{ zpX&uQ*=sXy=ed?bLNIlEV{@N2C2y_{aCjuR1OUP$3-&eKSDHQAiMpxU zUhV9)Sl2pOxOZ+IO-&dPy4q{zCG%BCnZBUPWZ7@)PeOHWd~S&GzW97f#1`;57gkY+ z70M!}bK4J7n|UK1J$j%wyU}byMK)o3@Ru7q_a*0LdGvj>Nxt^hI0Z^OR05xI+jcmE z&vTsYPb(9CV$vRIGJ%yBVZAW>D;2$+0tflXDj&Ir8R~Xd%#p>&>f1^U-CQnhVca0h z&)9l-b=@#EB#?3n=`k@eSz257NKuHVK(v#EvGk!VkgASOqt978dfYKSqjy`V`1Q3vI<*C5N&)FZj zY&`P!wyR?V=nr^hCCS*i?64$l8PP{IN4pJtw27{&vUGQi9@KRvFINN|kCp0uTc~xE zqx4_N@4iVF3Y5W*Q=~RU_~;RTQKeET``akW(R0Mwzg;PxWk#?j2>9K=T>fck4j>GE zBdR8QGDJ+dhOlf0qH)yIa!8eMMD840FgaroW1-aOA7c46{T)(Hmv6W2MzXaibd1#q++Ca|RUZYM0uIk)4F~7Dz1e*~7Z06}>hYGBk zT_M~(w_x)=8N!isg#jd;l%*iJ1&T0_K~)K~=a5!;{9PU31;R$T^jQ1ck*4PvWS2;9 zsg1S~Qw<9*DumzGRqhSQ9(r$MB{gYRgxVxKI$EgBjoF(49C*_V3yA%nHfE7%(M+Xi zRzQvFc$(*GPd&wN&12v-k-iLJw^6e16Q>4brk;?37{S+vyB#kf2H7hTIeh zR0t6VF5n|;g>{^YJmv~c)Cn$zn<1h+wergzG?Z#~Gy zfNm^o6;SIjE>`u@v*Zv71)|Wzyr_GusG< zOIR1j$GtR->#Hp(E~hlz=Po~@ntyx2o%+1$Jt&g@$|O*W{Z-|D9dP|})uRYjnfo6@ zZgSptS2}f7TxgT%f9y(A3w!ie{C@VdphH{rthH%4d)!vA zV_3w}hW+5Gz$zUHpWrKe*(GEV@TS6X{s(aGGgOlvllEE>Zo(d~!K>RW zO*?RJiz`rh%7Op8H}1!RY%>w}o?<`xem#sMXU)L|8DW2CcDZ!4548x-$Qo`pA0Oj? zXCh?ZoHoKs`_i}+r^C4)+@yJ5vbAOdXm}n$ zBYqXT4pQk%GPOaUeV_&7@#4(Gs9}HqQ)aC( zGIDaKaEtxzeY#h!T(KS~s82PL1-g8$GgXMc|5%tTvoT!$J1{6IZsdidW_uW9zmH*6 zw_k3XAUSvVj_z3{;pkt%NzG@hXlkm@*VW!aVIEHl@orVlQTKA`IsSr>!(o_=KG*ravfi071%hA-7 z=OLWoP~h+HFIS!9VaYn-FLvhbp~K*l_b>D)Gd-5B{r3C-2eGTjLL)BwYBfDp5sZd{w7}xEcNGa8MO3Ix*wAh;X&TvGI)58$P`68jU zU^t&)Sw^8OlJPlu-IA9SrP?UsA@ zs=f9LJa%j$0<9F)*P;j~P{jF=9*DIk0630p!t=gBNB_06Uxeiz;%JBrPjY3JL>1H)!L3D?Es0CKdx zn=e#;<>)O;N4A>+HA&zU$I<%nHwj6KQc*t^ISBG!Q%rAK4F4VbB@Lfnnm10-P;y@W zmYf`Y%Zld6`1tsMAzvX=hzD*Va$$3SwI*SiQChmdWGBBxyya9##qX@?&J(pvqP2m1Kf>5l zlONieU;XbbZS6%x!+tl*+Zy$Ue7GuR;^x+MR9V_=#n+3n$+TML>Db+jzdusf^r&u6 z)ZlOB4Ft6+Ag`(a3>@F~u<`?G_EI>mmo+(tfU%s$Y3)=WY`{vvBr2_~tzEL&90cglZeMQ_iS5S^OjR7)|mnBQP|zvqoksmEb)|P zx7#-xcu8X;vZ}ts!{NNKH7IZ~I8gakb&>lW6Nas+X^%_?kL3b-zBR(m+K5L-It4W3 zZWqwRdtajAIc8_KJ4tcsY|C;`%4Lx}#a>nYahCRVr$px zs99IFVkdERdg;0Y85@c`wfzKz`z@Xi^`1X>K1FaCZ_Rx|Q!qP9&G-&8*wr=!+Yi~? zI7zdUb%}hZ^=-n+m5y^`k!_7y|b%4?s4&`R+Un^T}hP#iO(I)vtRH-7KZVT zuOrk>_g`CfcHLC+4_4aq9J}}D%X5P;*0nypj&cWEbJi1@sWcs^+pc&L)f=@fT5+BO zo9`s6EKP}V1Ml7)hlZqhrWU#_h+gQ`a>{$_LS18`=iFb)c$5U;+0)k>({-bg<_^e; z!KbuvXqmT4H9@n^oB1qe@mhm~ZlLnHz)9myg_Ep%A@x-5-ejdCi@FEfP5g(a^K^p$ zI}L&m3&*dR$godPaxxSeCY z?M(KVC-7O!5mCra_V=e-P;_LS<-1z$g}rA)wo_XD;bmxVidFIjSC564MRL_x~OK34>h~z zEqF`PY~fNZqgkU3o#6(|p6?od{e~XFWq0~(+NbVhQF{|bQqS*}&&JXoJ4}Cg&Sdk# zzm*MR%wNp#s30NT)t*DiS~N&L31kjd7@LgTi?Wen4;9o)Y^KXV)mW{gTHlHI2yvew z0;YDybC(|oBF5m2e>I9pEUWx;ed*~-tm+w9?MeoE`sn#vwo7mIYP<>7)1P#NX;#dN zFN=tZ#+*ENi67{;NSyaMd9J{q!^5QUDP>q}o-J0ChET%wb5EMWPQn6r@rlL^@y4)K zBcrW^lMVK(d2plW3P^}k9}k>jnZ8u_+HH&}*7EOj!Om0+M+LLu%%q9rucp3|yj|*)D>;;tDm6?Enf~nneTLLJ;=-*2aU<=aihopzEzz zp^RN8%bDW0MSZYT{5~qIVf8hCvn8gTGsZFcev-LG=}y!Z2?cXY4~8|o`OodwZ#HW= zY&y|V^}h;DOYFz8yzdG=KQ*K1oGSr~0qIu~v?mxvUj0%!Z64NoGLYu}Y};Ckko}x$ zU*C5_Y?T}Gpzx?6aFUlKUUBMK?Yc*or|ZZgD=yKhFun?oF6>yl^?T)nd&yUF>sO;G z3Yir(FIGaN1o$^I5A^myAokDJe76XSSScn%1N(>5@xL4nf?6hTi4Nzf>h+i*+X|iv z3)4TrOmDHAg(s?nX=Jr>^#|hSA!htGD_kG6LJK^tJ#x$rLmxpV z+8{yPH3^UCmb}r)f182ZFt&?wcRsyvr|WHvD7-rUD+usCUv zW*%`N$RnF(r-WPSHQMiMAywGj*5_}@lgG9j^#e!ug<8DnvYY-cJ6rZ-ys4={VUjqM zW5=N1_8dmZ*soo~2w1?&hy9R(ouoc@%lhxyLcg$c8N~Sht%Y8$uk@yE!Es)JG(0h> zPPsL_?CkHt*kse@6PH?o>pORb9aKKQf)5LLR}TB;4^7$n`ENxWW>mk2PbyCd zgLLR5@MF_qR1;8sw*5U(lft_2tDH=465RiYW1F)y73Pn#{kQS=26K&O&G47_D5$V@ ze061wRsxB=O2_xU!`|-@j7}u%cRbIqTKAZ#tCSTOu<6Oxi6CY1{OFEbFcysNdIxu2 z0J!>NXB5?b^wub3uar$U#ha+Y9;`xoUXFKqB6{3zuxNgvU&P=nBNJ0x+T}fbNMrQ6 z(fTU*|1$gWV=&S9EG;(__G6?10go$;4fOZVg24zSsB)r#ozt7&k@ucFS8n;O5v7FP z_=J*zmO=Tkbd?GhCCVoH)P-6SvO2UjeiIeaShS?+?zoCn7;cln!fV^xZ9Oz!h3(Re zf4;ATBKTKhW5tzuh6Alr!>MFv8D>Uur+c$1?0Eh;KguR1j*^1KRTEx64_!sa@|-)9 z%vtp$E+f);XWqyhR}XkQR87(v|B8o*jz;3z0DS#So7id!>X7(}*FY4z#-){;2gA)X z?FsFmfIa-B3^`+UX-L@H0&_)X+B<=hjK&c0yAodbGEaji0ikw|CN5)33%yyiY_cIO zUBwU}9^s&oXyh#Wh?SKdzlg3WgPzC$*s51RW%Tk}1-6iauGx`xAypCHRZxtSW4gd;_n(%7qv2$>2LX3Y2t-u1a)=R?dN{_Cr zb)|L`Tfu*cgcY3~_gj`_D5d`~gvbX0{(WQT=!HAyaC!F{PuA1zX!9^zYP@GX8ZYuO z(SM5T8dVYfQJ#1Q9`DA-+PjbccyT96f4H!<*dGcc+KI`@h?T3P6wJ>jetm#>C8`SL z86b3({+__ z%aCZE;$|iPqG&&B)zMWSAzMm36`0_M3iMFAM&-27GhRo^lA;{9W%NW&PVOnh}tyI zcpHTtMIA1sMLmF3*fUyR;I}`zOQVGHUcRm^bVkn2zO&F=1%g^58DHgVKiQcO6(&jV zyD$$k1&Od(qofwyR=sVtfp;ub*gCz@^TIm5ntP?T4M+x|J0qG0hZfoH%mwC0tqEZU zGnjehuggvE^k_Fs3&rocyBGwJx=mYqvxcLh!A4xUv?uI{~+r znYig#Ha50G&m1;rq#sR`3uFBr;AM9cf?rk*#eXQwRlOQ!%CH~^i6icdJj`BOYLF9P z1mY#$9FNLf>2fdCJUtOXnzi1Ajm*&gLCeZ!+;rhDhWakn{_I4ZkA>~?067UwJPq9) zib|jE1f%t92URzFRiOc?Q*22M9XgIXcYGEF`Q3&|*$vCLT`HLl^=AkJer{Wf;w`$U z_ksCM9f?PSE}h`3I?ki}I#lHvN=-)2q8~s4$@fRtZ&P1GEaVLp6>%>D0<$F4>dymFnX=~HLH^bJ?19T&c>7CzM`X4~@FFaV}kEyAV%+JsN4+)4En)jEx2Bb8A zG8(juyr%hc9|wz-d5yjB6TtmEHsAm4rY0d`tX&eK;3w4fs*6d(p>EYB!7?3A*1eAP z)-@9sxyo4ggU=Ej%UpS`DINpR1*8{sybdxUPHtJEaYpRf7B!Buce$^{^qxvG@p<;R zqKP7+>Gc{U8$Mih&Ebw;T2!_;?;EPm$tOKbm@;lx{j1TLO!vXZQy>=!`w;LsqaaGqdD=Mj1fu7sc9OjD-wukH> z!AfJ$KpTp$RBDXYfH9=+Awh(G5bO@_!36SY_`CB=OiEzH(`009ckjktk$OMn9B}0B z#&0&n%R+#u-kV{7>Uy;g2T2c|uq`9pHBg4%KTkvRd12=Yng;3s3O?x#G?c<8&(hTb z${N?ux3b2qse4w=C0C78h~${E>PiPW049Gc`gh5zM?Fb$bm=Sn{HL;$8J>1wKW?p# zax857Nv1*`vCRr(Bf1rv<~iC(n}53#L3ae9+p*{IrcZ=vwdqML`uW-STD`Sa&L##zgN zna-SrlF480O}ZZ~>z;UGAfaMOxN4dDrJ_unwXIeDk!c^Y;tb@w>6 ziv{)w3z-X6{nIusPz~e1Q~cq#++5$ew%C^TSK`g> ze4bPC5{q~8rds)Twbs_0897+Pb#5~p9I#t|UDEZe*hy@nqF07n1ED?G{wI>_Ltr3D zB4FTvpf)oN1RxIY6yos0j;SE4neg|oHpXQYxnj#8z)l;@YwCe&N1^g_bovrJ^ge%6 z_N}$LAI10c-D!ZwK2nX6xo^EzOAZ>9m3}o+UX!GKi$A)+aSzfAdMK`7)ciO9%;#J_ zweK$z6mzVOE%=`3%D*qLpn0}daMyU3u){t5B_Nc-dX$`IHtr*0?Ub<&@mpaBaGh2_ z_vm)&lck4^Zk-I7&ZyJSgC; zU5&4N#AS&QuMeXc%1mE`8q07p>_&l58ANIs0xH&-vSZ@*BK71=mIIs5{WYk=W9|nM zoMN1Y4$VCL9wi=q2qqBbDYerhy^+2-+g|u`W5_Wee(rABq-=*AhyDluI_oE2gy@{h zU!j_gi`V#yRxv&VzIgT%E@o!tl_oXB1h4H0)*8qe_sR?jKPA-S>(k#HMB+=xQI6EAc9T);vvw)7tw2^h`{pt~&z+u|ev`(9`6PmZA)m0jtKeo@Vnrg%Ivq~u8NFm&GCGFw zJ{nyfb&WNgiAH5^kLmI1+}%D~-{_s6Ntz9_2pFwii=Yeed1dhU@kJmX8iNz$tq*-M z-8!2P+@PQNWLvmq{+jD7)9y6wTwiMy#RGCi`%zt1^`|F}A76vIqQGS*1@=cVgggM8O%z81IrpwCqMS+)2-S&3eps8ayo)#~FLW|r)$&ffQT&{q=6)<1mk z*Khwx=C1PY#(0}a&IkTw!#kL|wIbxH&Rjqo45Wd%0U;sRz!d{|vFxH`X0R}tV!{7= z=hK~Q>Pg2uwKL(5 zr2t>fs_`l3QXABK*HsV;3hlLY8j3-pliP>?S?qo&HhiItSm3nyBx*vobaTeH;?X@} zlF*yky3`FpZ`?K$HM5p-oYoS>1TTa=V83(c%WCz02+TaFe{&U3UVgd!;@2hf?M_s4 zkK*c%OvIJDS|0rJH0@g?k0RfG{HTcL`WFmY8^m5t9Cw&$xgv1oP070Cq(bE+*@LWm zUxgjmg(<_%y5WCRbtfv4%;uktda6!wr-E;22>RGat-^vjr~?C)+1KG{*O3jnLH55k z_74n91_I*&Odasnqp(eW>6bjD8bhzlUGpg}jx}5c7W5f( z5JlM z#Q*}44`7sa4ks8Qy#If9ul|Dv$B^LDdt)|^PpQ_$z{|BLiMD6qq?0>+!WaN6J}lm^ zMbJ}yimr|(1m}dfE(T^q*Sf}?& z)_sm_Sr~?=CHqP3ZCH?4_523N?BC~EC0t7?2r1VEDyq{Z28o(K1ZQb)p=kcxz9}I> zNvX9*?F$LXi|Lyh46Uu&mMf1Kt2s%cRO&(v#wR{-+c@F3EMN;ISaNf6et^oV0InYf zqZAb7BHP_C`M*=$q}J5bgjB)6Qoj>?>+4t}v&7lV*%QR8iQVZ>1*h+Qo_{e+m!_gJ ze@Q>H=YR*y~^0olG7czNlfBRr<@&c z%@9kdpIU4V6u;4hma%A&kXUPAu<+6R-{N%k_{o#%&^%IfW>}t_o%D@(P@?WM7BVgp z5$i8!cKj27hF8k;Gsibdeg@$@L;Nbu)`Lyy8kMH6;IAM2ZJmx-duA*Qd)cKI_;lGlDu9Y8ciY48llV=%()fG~M9r@wUFG)I<+ zRId^o`r6u`=qYJwL9P`2M{4%>nzE!)MdZKONam*

f-Kp3%>4xO4F39PJ}xLO#bY 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

XJUA7DOzjyv&P_u^-PIJqbZlcY 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 z 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 z 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

T3b(nQtc<5$^H7-ycH+D@wadN_tJm^3*W9 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 literal 0 HcmV?d00001 diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index fba9ab731..3a8a79b9b 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -6,13 +6,15 @@ \usepackage{geometry} \geometry{margin=1in} \usepackage{graphicx} +\usepackage{float} \usepackage{amsmath, amssymb} \usepackage{hyperref} \usepackage{cancel} \usepackage{color} \usepackage{pdflscape} +\numberwithin{equation}{section} -\title{DCON $recon flag$ document} +\title{DCON \texttt{recon\_flag} document} \author{Sunjae Lee} \date{\today} @@ -43,10 +45,7 @@ \section{Definition of covariant and contravariant basis} \end{equation} \begin{equation} - \begin{aligned} - \vec{v}_i &= v_{ij}\hat{e}_j, \qquad & - % g_{ij} &\equiv \color{red}\mathcal{J}\,\vec{v}_i \cdot \vec{v}_j = \mathcal{J}\,v_{ik}v_{kj} - \end{aligned} + \vec{v}_i = v_{ij}\hat{e}_j. \end{equation} @@ -61,7 +60,7 @@ \section{Definition of covariant and contravariant basis} \begin{equation} \begin{aligned} v_{21} &= \vec{v}_2 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \theta}, \\[6pt] -v_{22} &= \vec{v}_2 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \theta}, \\[6pt] +v_{22} &= \vec{v}_2 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\left(1+\frac{\partial \eta}{\partial \theta}\right), \\[6pt] v_{23} &= \vec{v}_2 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \theta}, \\[6pt] \end{aligned} \end{equation} @@ -75,7 +74,7 @@ \section{Definition of covariant and contravariant basis} \end{equation} -\subsection{Contravariant basis} +\subsection{Gradient basis $(\vec w_i=\nabla q^i)$} \begin{equation} \begin{aligned} \vec{w}_1 &= \vec{\nabla}\psi, & @@ -105,7 +104,7 @@ \subsection{Contravariant basis} &= \vec\nabla\psi \cdot \hat e_1 = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_1 = \mathcal{J}\,(v_{22}v_{33}-v_{23}v_{32}) - = \frac{4\pi^{2} r R}{\mathcal{J}}\frac{\partial \eta}{\partial \theta}, \\[6pt] + = \frac{4\pi^{2} r R}{\mathcal{J}}\left(1+\frac{\partial \eta}{\partial \theta}\right), \\[6pt] w_{12} &= \vec\nabla\psi \cdot \hat e_2 = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_2 @@ -147,7 +146,7 @@ \subsection{Contravariant basis} = \mathcal{J}\,(v_{12}v_{23}-v_{13}v_{22}) = \frac{2\pi r R}{\mathcal{J}}\!\left( \frac{\partial \eta}{\partial \psi}\frac{\partial \phi}{\partial \theta} - - \frac{\partial \phi}{\partial \psi}\frac{\partial \eta}{\partial \theta} + - \frac{\partial \phi}{\partial \psi}\left(1+\frac{\partial \eta}{\partial \theta}\right) \right), \\[6pt] w_{32} &= \vec\nabla\zeta \cdot \hat e_2 @@ -216,24 +215,6 @@ \section{Coordinate system} {\chi}_{\mathrm{dcon}} = \color{black}\chi'_{\mathrm{dcon}} \psi_{\mathrm{dcon}}. \] -\begin{align} -\nabla \cdot \left( \frac{1}{R^2} \nabla \psi \right) -&= \nabla \cdot \left(\frac{1}{R^2}\, \frac{\chi\prime_{dcon}}{2\pi} \nabla\, \psi_{dcon} \right)\\ -&= \frac{1}{{2\pi}} \nabla \cdot \left(\frac{1}{R^2}\, \chi\prime_{dcon} \nabla\, \psi_{dcon} \right) \\ -&= \frac{1}{{2\pi}} \nabla \cdot \left(\frac{1}{R^2}\, \nabla \chi_{dcon} \right) \\ -&= \mathbf{J} \cdot \nabla \phi\\ -&= - \left( p'_{chance} + \frac{1}{R^2} g g' \right) -\end{align} - -\begin{equation} - R^2 \nabla \cdot \left( \frac{1}{R^2} \nabla \chi_{dcon} \right) - = - 2\pi \left(R^2 p'_{chance} + g g' \right), - \qquad - p'_{dcon} \equiv \frac{dp}{d\psi_{dcon}} - = \frac{\chi'_{dcon}}{2\pi}\,p'_{chance}. -\end{equation} - - \subsection{Derivation of Eq.~(29) in GPEC note} @@ -259,21 +240,31 @@ \subsection{Derivation of Eq.~(29) in GPEC note} \qquad B^\zeta \equiv \vec{B}\cdot \vec{w}_3 = \frac{q\,\chi'}{\mathcal{J}}, \\[4pt] \vec{B} \times \vec{w}_1 -&= \frac{1}{\mathcal{J}}\Big( B^\zeta\,\vec{v}_2 - B^\theta\,\vec{v}_3 \Big) -= \frac{1}{\mathcal{J}}\left(\frac{q\,\chi'}{\mathcal{J}}\,\vec{v}_2 - \frac{\chi'}{\mathcal{J}}\,\vec{v}_3\right) \\ -&= \frac{\chi'}{\mathcal{J}^{2}}\Big( q\,\vec{v}_2 - \vec{v}_3 \Big), +&= \vec{B}\times\nabla\psi += \chi'\big(g^{\psi\zeta}-q\,g^{\psi\theta}\big)\,\nabla\psi ++ q\,\chi'\,g^{\psi\psi}\,\nabla\theta +- \chi'\,g^{\psi\psi}\,\nabla\zeta \\ +&= \chi'\big(g^{\psi\zeta}-q\,g^{\psi\theta}\big)\,\vec{w}_1 ++ q\,\chi'\,g^{\psi\psi}\,\vec{w}_2 +- \chi'\,g^{\psi\psi}\,\vec{w}_3, \end{aligned} \end{equation} \begin{equation} (\vec{B} \times \vec{w}_1)^\psi = 0, \qquad -(\vec{B} \times \vec{w}_1)^\theta = \frac{q\,\chi'}{\mathcal{J}^{2}}, \qquad -(\vec{B} \times \vec{w}_1)^\zeta = -\,\frac{\chi'}{\mathcal{J}^{2}}. +(\vec{B} \times \vec{w}_1)^\theta = \frac{\chi'\,(g_{23}+q\,g_{33})}{\mathcal{J}^{2}}, \qquad +(\vec{B} \times \vec{w}_1)^\zeta = -\,\frac{\chi'\,(g_{22}+q\,g_{23})}{\mathcal{J}^{2}}, \end{equation} +where $g_{ij}=\mathcal J^2(\vec v_i\cdot\vec v_j)$ is the true covariant +metric. The important distinction is that $\mathbf B\times\nabla\psi$ is most +naturally written first in the gradient basis $\{\vec w_i\}$ and only then, if +needed, converted to contravariant components in the $\{\vec v_i\}$ basis. \subsection{Metric Tensor and Index Operations} -Throughout this section we adopt the following conventions for expanding $\vec{B}$: +Throughout this section we distinguish carefully between the true tangent basis +$\vec e_i=\partial\vec r/\partial q^i=\mathcal J\vec v_i$ and the dual gradient +basis $\vec e^{\,i}=\vec w_i=\nabla q^i$. The vector $\vec B$ is expanded as \begin{equation} \vec{B} = \mathcal{J}\,B^j\,\vec{v}_j = B^j\,\vec{e}_j \qquad\text{(contravariant expansion)}, @@ -282,8 +273,15 @@ \subsection{Metric Tensor and Index Operations} \vec{B} = B_j\,\vec{w}_j = B_j\,\vec{e}^j \qquad\text{(covariant expansion)}, \end{equation} -where the contravariant components satisfy $B^i = \vec{B}\cdot\vec{w}_i$ and the covariant components satisfy $B_i = \vec{B}\cdot\vec{e}_i$. -These are consistent because $\vec{B}\cdot\vec{w}_i = \mathcal{J}B^j\vec{v}_j\cdot\vec{w}_i = \mathcal{J}B^j\,\delta_{ji}/\mathcal{J} = B^i$. +where the contravariant components satisfy +$B^i = \vec{B}\cdot\vec{w}_i$ and the covariant components satisfy +$B_i = \vec{B}\cdot\vec{e}_i$. These definitions are consistent because +\begin{equation} +\vec{B}\cdot\vec{w}_i += \mathcal{J} B^j \vec{v}_j\cdot\vec{w}_i += \mathcal{J} B^j \frac{\delta_{ji}}{\mathcal J} += B^i. +\end{equation} \subsubsection{Lowering Operator ($B^j \to B_i$)} To transform contravariant components $B^j$ into covariant components $B_i$, we use the true covariant tangent basis $\vec{e}_i = \mathcal{J}\vec{v}_i$. @@ -362,32 +360,33 @@ \subsection{Derivation of Eq. (35) in gpec note} \section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} \begin{align} \delta W_p &= \frac{1}{2\mu_0} \int d\tau_p -\Big\{ |\mathbf{Q}|^2 - \mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* -+ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 -+ \mu_0 (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \Big\} \\ +\Big\{ |\mathbf{Q}|^2 - \mu_0\,\mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 ++ \mu_0 (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \Big\} \label{eq:ep_first}\\ &= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q}_\perp|^2 -+ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \boldsymbol{\xi} \cdot \nabla p|^2 -+ \sigma\, \mathbf{B} \times \boldsymbol{\xi}^* \cdot \mathbf{Q} -+ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 -- 2 \boldsymbol{\xi} \cdot \nabla p\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} -\Big\} \\ ++ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \mu_0\,\boldsymbol{\xi} \cdot \nabla p|^2 ++ \mu_0\,\sigma\, \mathbf{B} \times \boldsymbol{\xi}^* \cdot \mathbf{Q} ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- 2 \mu_0\,\boldsymbol{\xi} \cdot \nabla p\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} +\Big\} \label{eq:ep_intuitive}\\ &= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q} + \xi_n (\mu_0\mathbf{j} \times \mathbf{n})|^2 + \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 -- 2\,\mu_0\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, \xi_n^2 -\Big\}\\ +- 2\,\mu_0\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, |\xi_n|^2 +\Big\}\label{eq:ep_bernstein}\\ &= \frac{1}{2\mu_0} \int d\tau_p \Big\{ |\mathbf{Q} + \xi_n (\mu_0 \mathbf{j} \times \mathbf{n})|^2 + \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 -- \mu_0 K\, \xi_n^2 -\Big\} +- \mu_0 K\, |\xi_n|^2 +\Big\}\label{eq:ep_bernsteinK} \end{align} -The derivation of (35) could be found in \cite{RevModPhys.76.1071} and the original derivation of (36) is in \cite{Bernstein1983}. -The K term in (37) is defined in \cite{chance1992mhd}. +The derivation of \eqref{eq:ep_intuitive} can be found in \cite{RevModPhys.76.1071} and the original derivation of \eqref{eq:ep_bernstein} is in \cite{Bernstein1983}. +The $K$ term in \eqref{eq:ep_bernsteinK} is defined in \cite{chance1992mhd}. \subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/DCON units)} +\label{sec:bernstein_transform} We follow the derivation of \cite{Bernstein1983}, while keeping the present SI/DCON conventions. Throughout this subsection, we employ the following definitions: @@ -415,7 +414,15 @@ \subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/ \right\}. \end{equation} -Since $\mathbf{B}$, $\nabla p$, and $\nabla\times\mathbf{B}$ are mutually orthogonal (satisfying $\mathbf{B}\cdot\nabla p = (\nabla\times\mathbf{B})\cdot\nabla p = \mathbf{B}\cdot(\nabla\times\mathbf{B})=0$), they form an orthogonal basis. We thus introduce +The vectors $\mathbf{B}$, $\nabla p$, and $\nabla\times\mathbf{B}$ form a local +basis away from degenerate points where \(p'=0\) or where \(\mathbf B\) and +\(\nabla\times\mathbf B\) become linearly dependent. Here $\nabla p$ is +orthogonal to both $\mathbf B$ and $\nabla\times\mathbf B$ (since +$\mathbf{B}\cdot\nabla p = (\nabla\times\mathbf{B})\cdot\nabla p = 0$, i.e. +$\mathbf B$ and $\mathbf j$ lie in the flux surface), while $\mathbf B$ and +$\nabla\times\mathbf B$ are independent but not orthogonal +($\mathbf{B}\cdot(\nabla\times\mathbf{B})=\mu_0 j_\parallel B\neq0$). We thus +introduce \begin{equation} \boldsymbol{\xi} = @@ -455,13 +462,16 @@ \subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/ Substituting $\boldsymbol{\xi} = a\,\nabla p + b\,\nabla\times\mathbf B + c\,\mathbf B$: \begin{align} -\nabla\cdot\boldsymbol{\xi} &= a\nabla^2 p, \quad \boldsymbol{\xi}\cdot\nabla p = a(\nabla p)^2. +\nabla\cdot\boldsymbol{\xi} &= \nabla\cdot(a\nabla p)+\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B), \quad \boldsymbol{\xi}\cdot\nabla p = a(\nabla p)^2. \end{align} By the product rule: \begin{equation} -a(\nabla p)^2\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B) = \nabla\cdot\Bigl[a(\nabla p)^2(b\nabla\times\mathbf B+c\mathbf B)\Bigr] - (b\nabla\times\mathbf B+c\mathbf B)\cdot\nabla\bigl[a(\nabla p)^2\bigr]. +a(\nabla p)^2\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B) = +\nabla\cdot\Bigl[a(\nabla p)^2(b\nabla\times\mathbf B+c\mathbf B)\Bigr] +- (b\nabla\times\mathbf B+c\mathbf B)\cdot\nabla\bigl[a(\nabla p)^2\bigr]. \end{equation} -On flux surfaces the divergence vanishes, giving +After integration over a volume bounded by flux surfaces, the divergence term +has no surface contribution, giving \begin{align} a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr) &= @@ -487,7 +497,7 @@ \subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/ \Bigr\}. \end{align} -Using $\mu_0\nabla p = (\nabla\times\mathbf B)\times\mathbf B$ and $\hat{\mathbf n}\cdot\mathbf B=0$, the three terms (L485--489) combine to: +Using $\mu_0\nabla p = (\nabla\times\mathbf B)\times\mathbf B$ and $\hat{\mathbf n}\cdot\mathbf B=0$, the last three scalar terms combine to: \begin{align} &-a^2\Bigl|(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 +a(\nabla\times\mathbf B)\times\nabla p\cdot\nabla\times(a\,\mathbf B\times\nabla p) +\mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) \nonumber\\ &= a^2(\nabla p)^2 \Bigl[-|\nabla\times\mathbf B|^2 +(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot\Bigl((\hat{\mathbf n}\cdot\nabla)\mathbf B-(\mathbf B\cdot\nabla)\hat{\mathbf n}\Bigr)\Bigr]. @@ -689,7 +699,7 @@ \subsection{Derivation of $K$ with explicit $\mu_0$} \begin{equation} S \equiv -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\cdot\nabla\times\left(\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\right), \end{equation} -which is equivalent to $S = -\frac{1}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi)$ by the product rule for curl. +which is equivalent to $S = -\frac{1}{|\nabla\psi|^4}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi)$ by the product rule for curl. Thus \begin{equation} \boxed{K_{\parallel} = \mu_0\sigma^2B^2 + \sigma|\nabla\psi|^2 S}. @@ -896,6 +906,11 @@ \subsubsection{Shear parameter $S$ in DCON} + \frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}}. \end{equation} +The \(q'\theta\) piece is a gauge term coming from +\(\alpha=q(\psi)\theta-\zeta\); it is not itself periodic in \(\theta\). In the +following expressions only its derivative, \(\partial_\theta(q'\theta)=q'\), +enters the physical scalar \(S_\psi\), while integrations by parts must be +applied only to the resulting periodic combinations. For an axisymmetric tokamak equilibrium, all equilibrium scalars and metric elements are independent of $\zeta$, hence $\partial\Gamma_\alpha/\partial\zeta=0$. Using @@ -957,9 +972,9 @@ \subsubsection{Shear parameter $S$ in DCON} \noindent\textbf{Relation to the GPEC note.} If one instead uses the flux label \begin{equation} -\lambda = \frac{\chi}{2\pi} = \frac{\chi'}{2\pi}\psi, +\lambda = \frac{\chi(\psi)}{2\pi}, \end{equation} -then +then \(\nabla\lambda=(\chi'/2\pi)\nabla\psi\) on each flux surface and \begin{equation} S_{\lambda} = @@ -991,12 +1006,15 @@ \subsubsection{Normal displacement $\xi_\psi$} \equiv \boldsymbol{\xi}\cdot\nabla\psi = -\xi^\psi, +\xi^\psi_{\rm tang}, \qquad \hat{\mathbf n} = \frac{\nabla\psi}{|\nabla\psi|}. \end{equation} +Here \(\xi^\psi_{\rm tang}\) denotes the contravariant component in the true +tangent basis \(\vec e_i=\partial\vec r/\partial q^i\). It should not be +confused with a covariant component in the gradient basis. Therefore, \begin{equation} \boxed{ @@ -1235,10 +1253,12 @@ \section{Reconstruction of energy principle variables in DCON} \end{aligned} \end{equation} -\subsection{Reconstruction v1} +\subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ grid} \begin{align} -\xi_\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) \\ -\xi_s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot \exp 2\pi i(m\theta -n\zeta) +\xi_\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot +\exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] \\ +\xi_s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot +\exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] \end{align} Here $\xi_\psi\equiv\vec\xi\cdot\nabla\psi$ is the normal displacement amplitude used by DCON, and $\xi_s$ is the surface displacement variable. \begin{align} @@ -1247,12 +1267,12 @@ \subsection{Reconstruction v1} \end{align} \begin{equation} -Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi_\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp 2 \pi i(m\theta -n\zeta)\cdot 2 \pi i (m-nq) +Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi_\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp\!\bigl[2 \pi i(m\theta -n\zeta)\bigr]\cdot 2 \pi i (m-nq) \end{equation} \begin{align} Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi_\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi_s}{\partial \zeta} \\ -&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + \chi^{\prime\prime}v_{m,n}(\psi) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp 2\pi i(m\theta -n\zeta) +&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + \chi^{\prime\prime}v_{m,n}(\psi) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] \end{align} \begin{align} @@ -1266,25 +1286,6 @@ \subsection{Reconstruction v1} \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). \end{align} -\paragraph{Implementation note on \texttt{reg\_flag}.} -The formulas above are the unregularized Bernstein reconstruction. In -\texttt{gpeq\_sol} and \texttt{gpeq\_c}, the code may instead use modified -tangential quantities when \texttt{reg\_flag=.true.}. The modification replaces -the radial derivative and surface variable entering the tangential upper -components by -\begin{equation} -x_{\psi}' \longrightarrow x_{\psi}'\, -\frac{(m-nq)^2}{(m-nq)^2+\texttt{reg\_spot}^2}, -\qquad -x_s \longrightarrow x_s^{\mathrm{mod}}, -\end{equation} -and then forms \texttt{bmt\_mn} and \texttt{bmz\_mn} from these modified -quantities. Therefore, if \texttt{reg\_flag=.false.}, \texttt{bmt\_mn=bwt\_mn} -and \texttt{bmz\_mn=bwz\_mn}, so the code follows the expressions above. If -\texttt{reg\_flag=.true.}, the v1 diagnostic is a regularized reconstruction -near rational surfaces \(m-nq=0\), not the strictly unregularized Bernstein -formula. - Then the current vector can be reconstructed by the Grad-Shafranov equation: \begin{align} \mu_0 \vec{j} &= \mathcal{J} (\mu_0 j^{\psi} \nabla \theta \times \nabla \zeta + \mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) \\ @@ -1316,34 +1317,27 @@ \subsection{Reconstruction v1} \end{aligned} \end{equation} -With this definition, the reconstructed $\vec C$ should satisfy the identity -\begin{equation} -(\nabla\times \vec C)\cdot \nabla \psi -= -\nabla\cdot\left(\vec C\times \nabla \psi\right) -=0, -\end{equation} -which is the relation referred to in the code as the \texttt{C verify} -check; see also the discussion of the Bernstein energy-principle form in -\cite{RevModPhys.76.1071}. The verification target is therefore the vanishing of -$ (\nabla\times \vec C)\cdot \nabla \psi $, or equivalently -$ \nabla\cdot(\vec C\times \nabla \psi) $. In the code, this same quantity -is evaluated through its flux-coordinate form +The correction term that turns the bare perturbed field $\vec Q$ into the effective field +$\vec C$, namely $\vec V\equiv\vec C-\vec Q=(\vec\xi\cdot\hat n)(\mu_0\vec j\times\hat n)$, has +contravariant and covariant components ($\vec V=V_i\nabla x^i$): \begin{equation} -(\nabla\times \vec C)\cdot \nabla \psi -= -\frac{1}{\mathcal{J}} -\left( -\frac{\partial C_\zeta}{\partial \theta} -- -\frac{\partial C_\theta}{\partial \zeta} -\right). +\begin{aligned} +V^\psi &= 0 +&\qquad +V_\psi &= \frac{\mathcal{J}\xi_\psi}{|\nabla\psi|^2} +\bigl[ \mu_0 j^\theta (\nabla\psi\cdot\nabla\zeta) +- \mu_0 j^\zeta (\nabla\psi\cdot\nabla\theta) \bigr] \\ +V^\theta &= \frac{\xi_\psi}{|\nabla\psi|^2 \mathcal{J}} +(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) +&\qquad +V_\theta &= \mathcal{J}\xi_\psi\,\mu_0 j^\zeta \\ +V^\zeta &= -\frac{\xi_\psi}{|\nabla\psi|^2 \mathcal{J}} +(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) +&\qquad +V_\zeta &= -\mathcal{J}\xi_\psi\,\mu_0 j^\theta +\end{aligned} +\label{eq:Vcomp} \end{equation} -Here $C_\theta$ and $C_\zeta$ are the covariant components in -$\vec C = C_\psi \nabla\psi + C_\theta \nabla\theta + C_\zeta \nabla\zeta$. -Therefore the present verification tests whether the reconstructed covariant -components satisfy this cancellation to numerical roundoff on each -reconstructed flux surface. For direct reconstruction on the $(\psi,\theta)$ grid, the energy density is closed by the metric contraction \begin{equation} @@ -1374,7 +1368,87 @@ \subsection{Reconstruction v1} \end{aligned} \end{equation} -\subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta W$} +\begin{figure}[t] + \centering + \includegraphics[width=0.62\linewidth]{C_eulerian_lagrangian_n1.png} + \caption{Effective field $\vec C$ on two flux surfaces ($\psi=0.45,\,0.82$) in the + (a)~Eulerian and (b)~Lagrangian frames: equilibrium current $\vec j$ (blue) and its + apparent normal component (red). DIII-D \#147131 at $2300$~ms; $n=1$, displacement + exaggerated.} + \label{fig:C_frame} +\end{figure} + +\paragraph{Verification: $\vec C$ as an effective field and the $\nabla\times\vec C$ check.} +The reconstruction is closed by a self-consistency test of the condition +$(\nabla\times\vec C)\cdot\nabla\psi=0$, which holds on every flux surface and admits both a +variational and a physical reading. \emph{Variationally}, it is the Euler--Lagrange +equation obtained when the surface displacement $\xi_s$ is eliminated by minimizing +$\delta W$ at fixed $\xi_\psi$: because $\xi_s$ enters the energy only through +$\nabla\xi_s\times\nabla\psi$ in $|\vec C|^2$---the compressional term +$\gamma p\,|\nabla\cdot\vec\xi|^2$ being carried by a separate (parallel) displacement +component---the stationarity condition is $\nabla\cdot(\vec C\times\nabla\psi)=0$, i.e.\ +$(\nabla\times\vec C)\cdot\nabla\psi=0$. + +\emph{Physically}, in analogy with Amp\`ere's law the curl of $\vec C$ defines an effective +perturbed current, +\begin{equation} +\nabla\times\vec B=\mu_0\vec j +\quad\Longrightarrow\quad +\vec j_{\mathrm{eff}}\equiv\frac{\nabla\times\vec C}{\mu_0}, +\end{equation} +so the reconstruction condition states exactly that this effective current is tangent to the +flux surface, $(\nabla\times\vec C)\cdot\nabla\psi=0\Leftrightarrow\vec j_{\mathrm{eff}}\cdot\nabla\psi=0$. In the equilibrium the current +lies in flux surfaces, $\vec j\cdot\nabla\psi=0$. Under an ideal-MHD displacement the flux +is frozen into the fluid, so each surface is convected to the perturbed label +$\Psi=\psi-\vec\xi\cdot\nabla\psi$; followed along this displaced surface (the Lagrangian +description) the perturbed current keeps no component through it. Referred instead to the +unperturbed normal $\hat n=\nabla\psi/|\nabla\psi|$ at a fixed point---the Eulerian +description natural to DCON's fixed control surface---the convected equilibrium current +acquires an \emph{apparent} normal component; with $\vec Q=\delta\vec B$, +\begin{equation} +\frac{1}{\mu_0}(\nabla\times\vec Q)\cdot\nabla\psi +=\nabla\cdot\!\big[(\vec\xi\cdot\nabla\psi)\,\vec j\big] +=-\,\nabla\psi\cdot\nabla\times\!\big[\xi_n(\vec j\times\hat n)\big]\neq0, +\end{equation} +where the middle equality uses $\nabla\cdot(a\vec j)=\vec j\cdot\nabla a$ (as +$\nabla\cdot\vec j=0$), and the last uses $\nabla\psi\cdot\nabla\times\vec G=\nabla\cdot(\vec G\times\nabla\psi)$ +(valid since $\nabla\times\nabla\psi=0$) together with $\nabla\psi=|\nabla\psi|\hat n$ and the +triple product $(\vec j\times\hat n)\times\hat n=(\vec j\cdot\hat n)\hat n-\vec j=-\vec j$ +(because $\vec j\cdot\hat n=0$), giving +$\nabla\cdot[(\vec\xi\cdot\nabla\psi)\vec j]=-\nabla\psi\cdot\nabla\times[\xi_n(\vec j\times\hat n)]$. +This apparent normal component is an artifact of measuring a displaced surface against its +original normal. The cross term +$\xi_n(\mu_0\vec j\times\hat n)$ in the definition of $\vec C$ is exactly this convective +(Lagrangian$\leftrightarrow$Eulerian) contribution and cancels it, +\begin{equation} +(\nabla\times\vec C)\cdot\nabla\psi +=(\nabla\times\vec Q)\cdot\nabla\psi ++\mu_0\,\nabla\psi\cdot\nabla\times\!\big[\xi_n(\vec j\times\hat n)\big]=0. +\end{equation} +Hence $\vec C$ is an \emph{effective perturbed magnetic field}---the Eulerian field +$\vec Q$ corrected onto the displaced surface, not the perturbed flux $\Psi$ itself---whose +curl is the genuine surface current (Fig.~\ref{fig:C_frame}); where $\vec j=0$ (e.g.\ outside the plasma) the +correction drops and $\vec C=\vec Q$. (See Boozer~\cite{RevModPhys.76.1071}; +Park~\cite{Park2009Ideal}, \S2.2.) + +The code's \texttt{C verify} check tests this condition on the reconstructed components +through its flux-coordinate form +\begin{equation} +(\nabla\times \vec C)\cdot \nabla \psi += +\frac{1}{\mathcal{J}} +\left( +\frac{\partial C_\zeta}{\partial \theta} +- +\frac{\partial C_\theta}{\partial \zeta} +\right), +\end{equation} +with $C_\theta,C_\zeta$ the covariant components of +$\vec C=C_\psi\nabla\psi+C_\theta\nabla\theta+C_\zeta\nabla\zeta$; it thus verifies that the +reconstructed components satisfy this cancellation to numerical roundoff on each flux +surface. + +\subsection{Reconstruction v2: radial matrix representation of $\delta W$} In this part, we show that the reconstructed Bernstein form can be written in the same radial matrix structure as the DCON/Newcomb formulation. \begin{equation} \boxed{\;\vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal{J}^2}\;} @@ -1528,9 +1602,10 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \noindent Define \begin{equation} -\mathbf Q_0\equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, +\mathbf Q \equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, \qquad -\vec C=\mathbf Q_0+\mathbf V . +\vec C=\mathbf Q+\mathbf V . +\label{eq:CeqQplusV} \end{equation} Then the Bernstein part of the energy is \begin{equation} @@ -1539,15 +1614,15 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta &= \int d\psi d\theta d\zeta\,\mathcal J \Big[ -|\mathbf Q_0|^2 -+\mathbf Q_0^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Q_0 \\ +|\mathbf Q|^2 ++\mathbf Q^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Q \\ &\qquad +|\mathbf V|^2 -\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} \Big]. \end{aligned} \end{equation} -Using \(e_m\equiv e^{2\pi i(m\theta-n\zeta)}\), the four $\mathbf Q_0$ pieces are +Using \(e_m\equiv e^{2\pi i(m\theta-n\zeta)}\), the four $\mathbf Q$ pieces are \begin{equation} \begin{aligned} \mathbf X @@ -1583,31 +1658,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta & \Xi_\psi^\dagger\Xi_\psi & \mathbf H . \end{array} \end{equation} -Concretely, every entry is obtained by expanding the product in Fourier modes and using -\begin{equation} -\int_0^1 d\theta\, -\mathcal J\,e^{2\pi i(m'-m)\theta} -(\vec v_i\cdot\vec v_j) -= -(G_{ij})_{mm'}. -\end{equation} -Equivalently, if -\begin{equation} -\mathbf P=\sum_m p_m e_m P_m^i\vec v_i, -\qquad -\mathbf R=\sum_{m'} r_{m'} e_{m'} R_{m'}^j\vec v_j, -\end{equation} -then -\begin{equation} -\boxed{ -\int_0^1 d\theta\,\mathcal J\,\mathbf P^*\cdot\mathbf R -= -\sum_{m,m'}p_m^* -\left[ -\sum_{i,j} (P_m^i)^*(G_{ij})_{mm'}R_{m'}^j -\right]r_{m'} . -} -\end{equation} + This is the actual route from the real-space Bernstein expression to the radial matrix form. The factors \(m\) and \(m-nq\) are then written as the diagonal matrices \(\mathbf M\) and \(\mathbf Q\). For instance, \begin{equation} @@ -1640,8 +1691,8 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \begin{equation} \begin{aligned} 2\mu_0\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [|\vec{C}|^2]\\ -&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q_0|^2+ -\left[\mathbf Q_0\cdot\mathbf V^* + \mathbf V \cdot \mathbf Q_0^* +&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q|^2+ +\left[\mathbf Q\cdot\mathbf V^* + \mathbf V \cdot \mathbf Q^* \right]+|\mathbf V|^2\} . \end{aligned} \end{equation} @@ -1734,7 +1785,7 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta } \end{equation} -\paragraph{Blocks from \(\mathbf Q_0^*\cdot\mathbf Q_0\)} +\paragraph{Blocks from \(\mathbf Q^*\cdot\mathbf Q\)} Applying the projection rule above to the products that do not contain \(\mathbf V\) gives the following blocks: \begin{equation} @@ -2539,99 +2590,21 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \medskip \noindent\rule{\linewidth}{0.4pt} -\paragraph{The scalar current--pressure block.} -The reduction to a radial matrix is automatic once the integrand is a -bilinear form in the Fourier amplitudes -\((\Xi_s,\Xi_\psi,\Xi_\psi')\). What is not automatic is the equality between -the expanded Bernstein \(\mathbf V\)-sector and the compact DCON -current--pressure block. Using the energy-principle transformation proved in -the previous section, the required sesquilinear identity is -\begin{equation} -\mathcal J -\left[ -\mathbf Q_0^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Q_0 -+|\mathbf V|^2 --\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\right] -\equiv -\mathcal I_{\mathrm{cp}}, -\end{equation} -up to the periodic surface-divergence terms that integrate to zero. Therefore -the compact DCON matrices follow from the equivalent DCON current--pressure -density below. A term-by-term reduction of the separate Bernstein pieces is -not sufficient. +\paragraph{Closed form of the $\mathbf V$-sector ($\mathbf H_4$).} -The separate pure-\(\Xi_\psi\) terms -\(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y\), -\(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U\), -\(|\mathbf V|^2\), and -\(-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2\) -are intermediate Bernstein pieces. For a closed derivation of their sum, use the DCON current--pressure density +The four pure-$\Xi_\psi$ contributions assemble into \begin{equation} -\begin{aligned} -\mathcal I_{\mathrm{cp}} -&= -\xi_\psi\,\mathbf J\cdot\nabla\xi_s --\xi_s\,\mathbf J\cdot\nabla\xi_\psi -+\left[J_\theta(q\chi')'-J_\zeta\chi''\right]|\xi_\psi|^2 -+\frac{P'}{\mathcal J}\left(\mathcal J|\xi_\psi|^2\right)' , -\end{aligned} +\boxed{\;\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K\;,} \end{equation} -where \(\mathbf J=\nabla\times\mathbf B=\mu_0\mathbf j\) and \(P'=\mu_0p'\). The first two terms give the already derived \(\mathbf C_3\). The remaining two terms determine \(\mathbf E_3\) and \(\mathbf H_4\): +equal, term by term, to the $\mathbf V$-sector of the Bernstein form +\eqref{eq:ep_bernsteinK} without invoking any equilibrium identity. The +Grad--Shafranov equation reduces it to the compact block \begin{equation} -\begin{aligned} -\mathcal J\left[J_\theta(q\chi')'-J_\zeta\chi''\right]|\xi_\psi|^2 -&= -|\xi_\psi|^2 -\left[ --2\pi f'q'\chi' -+\mu_0p'\mathcal J\frac{\chi''}{\chi'} -\right],\\ -\mu_0p'\left(\mathcal J|\xi_\psi|^2\right)' -&= -\mu_0p'\mathcal J' -|\xi_\psi|^2 -+\mu_0p'\mathcal J -\left(\xi_\psi^{\prime *}\xi_\psi+\xi_\psi^*\xi_\psi'\right). -\end{aligned} -\end{equation} -Therefore -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf Z^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Z -&\leadsto -\Xi_\psi^{\prime\dagger}\mathbf E_3\Xi_\psi -+\Xi_\psi^\dagger\mathbf E_3^\dagger\Xi_\psi',\\ -\mathbf E_3&=\mu_0p'\mathbf J, -\end{aligned} -} -\end{equation} -and -\begin{equation} -\boxed{ -\begin{aligned} -(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -+ -(\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U) -+ -|\mathbf V|^2 -- -\mu_0\frac{K}{|\nabla\psi|^2}|\xi_\psi|^2 -&\leadsto\; -\Xi_\psi^\dagger \mathbf H_4 \Xi_\psi, \\ -\qquad -\mathbf H_4 -&= -\mu_0p' -\left( -\frac{\chi''}{\chi'}\mathbf J+\mathbf J' -\right) -- -2\pi f'q'\chi' \mathbf I. -\end{aligned} -} +\boxed{\; +\mathbf H_4=\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) +-2\pi f'q'\chi'\,\mathbf I\;,} \end{equation} +as proved in Appendix~\ref{app:h4}. The complete term-to-matrix assignment is therefore \begin{equation} @@ -2671,37 +2644,43 @@ \subsection{Reconstruction $\boldsymbol{v2}$ : Matrix representation of $\delta \end{aligned} } \end{equation} -Subject to the same boundary and reduced-compressional assumptions stated -above, this is the DCON-normalized version of the matrix structure in -Eq.~(35) of \texttt{dcon.tex}. Thus the Bernstein form and the original DCON -current--pressure form lead to the same radial matrix functional; the -$\mathbf V$-dependent pieces are intermediate cancellation/recombination -terms, not extra physics. +Subject to the boundary and reduced-compressional assumptions stated above, +this is the DCON-normalized version of the matrix structure in Eq.~(35) of +\texttt{dcon.tex}: the Bernstein form and the original DCON current--pressure +form yield the same radial matrix functional, the $\mathbf V$-dependent pieces +being purely intermediate recombination terms. \section{Variable Naming Convention and Mathematical Symbols} +\label{sec:naming} -This section summarizes the implementation of C and Q vectors with proper tensor transformations between contravariant and covariant representations. +This section summarizes the code implementation of the $\vec C$ and $\vec Q$ vectors and the tensor transformations between their contravariant and covariant representations. \subsection{Fundamental Transformation Laws} -The C and Q vectors are defined in two complementary basis systems: +The C and Q vectors are defined using a dual gradient basis and a tangent basis: -\textbf{Contravariant basis:} $\vec{w}_i \equiv \nabla \psi, \nabla \theta, \nabla \zeta$ \\ -\textbf{Covariant basis:} $\vec{v}_i \equiv \nabla \theta \times \nabla \zeta, \nabla \zeta \times \nabla \psi, \nabla \psi \times \nabla \theta$ +\textbf{Gradient basis (covariant one-forms):} +$\vec{w}_i \equiv \nabla \psi, \nabla \theta, \nabla \zeta$ \\ +\textbf{Jacobian-scaled tangent basis:} +$\vec{v}_i \equiv \nabla \theta \times \nabla \zeta, \nabla \zeta \times \nabla \psi, \nabla \psi \times \nabla \theta$, +so that the true tangent basis is \(\vec e_i=\mathcal J\vec v_i\). \begin{equation} \boxed{ \begin{aligned} \text{Contravariant} \quad &Q^i = \frac{b^i}{\mathcal{J}} \\ \text{Covariant} \quad &Q_i = g_{ij} Q^j = g_{ij} \frac{b^j}{\mathcal{J}} \\ -\text{Physics method} \quad &C_i = Q_i + ((\mu_0 \mathbf{j} \times \hat{n})\cdot (\xi \cdot \hat{n}))_i +\text{Physics method} \quad &C_i = Q_i + \left[\xi_n(\mu_0 \mathbf{j} \times \hat{n})\right]_i \end{aligned} } \end{equation} -where $\mathcal{J}$ is the Jacobian, $g_{ij}$ is the metric tensor at MODE index 0. +where $\mathcal{J}$ is the Jacobian, \(g_{ij}\) is the true covariant tangent +metric, \(\xi_n=\boldsymbol\xi\cdot\hat n\), and \(b^i\equiv\mathcal J Q^i\) +denotes the Jacobian-weighted contravariant arrays stored by the code +(\texttt{bwp}, \texttt{bwt}, \texttt{bwz}). \subsection{Code Variable Mapping Table} -\subsubsection*{C Vector - Contravariant (w-basis, $C^i$)} +\subsubsection*{C Vector - Contravariant components in the tangent basis ($C^i$)} \begin{table}[h!] \centering \begin{tabular}{|l|l|} @@ -2715,7 +2694,7 @@ \subsubsection*{C Vector - Contravariant (w-basis, $C^i$)} \end{tabular} \end{table} -\subsubsection*{C Vector - Covariant (v-basis, $C_i$)} +\subsubsection*{C Vector - Covariant components in the gradient basis ($C_i$)} \begin{table}[h!] \centering \begin{tabular}{|l|l|} @@ -2746,6 +2725,1138 @@ \subsubsection*{Input Variables and Parameters} \end{tabular} \end{table} +\section{Log-output quantities and their exact integrands} +\label{sec:logmap} + +This section gives the precise mathematical definition of every scalar +printed to the terminal and to \texttt{gpec.log} by the three reconstruction +diagnostics. The three blocks correspond to the input flags +\texttt{recon\_flag} (\texttt{GPEC\_RECON}, driver \texttt{gpout\_recon}), +\texttt{recon\_flag2} (\texttt{GPEC\_RECON2}, driver \texttt{gpout\_recon2} +$\to$ \texttt{gpeq\_firstform}), and \texttt{recon\_flag3} +(\texttt{GPEC\_RECON3}, driver \texttt{gpout\_recon3} $\to$ +\texttt{gpeq\_recon3}). + +The DCON normalization factor that converts the GPEC energy to the +DCON-normalized $\delta W$, used in the normalized outputs below, is +\begin{equation} +\texttt{dcon\_fac} +=\frac{2\mu_0}{\psi_o^{2}\,(10^{-3}\chi')^{2}}, +\qquad +\chi'=2\pi\psi_o . +\end{equation} + +\subsection{\texttt{GPEC\_RECON} (\texttt{recon\_flag})} + +This route reconstructs the closed Bernstein form \eqref{eq:ep_bernsteinK}, +\begin{equation} +\delta W_p=\frac{1}{2\mu_0}\int d\tau_p +\Big\{\,|\vec C|^2 ++\mu_0\gamma p\,|\nabla\!\cdot\!\vec\xi|^2 +-\mu_0 K\,|\xi_n|^2\,\Big\}, +\qquad +\vec C\equiv\vec Q+\xi_n\,(\mu_0\vec j\times\hat n), +\label{eq:dWp_recon1} +\end{equation} +For the reduced diagnostic used here the positive compressional norm +\(\gamma p\,|\nabla\!\cdot\!\vec\xi|^2\) is omitted; this is a reconstruction +assumption, not a general ideal-MHD identity. The reconstructed energy is then +\(\delta W_p=\tfrac{1}{2\mu_0}\int(|\vec C|^2-\mu_0K|\xi_n|^2)\,d\tau_p\). +Here the symbols are +\begin{itemize} +\item $\vec Q=\nabla\times(\vec\xi\times\vec B)$ is the perturbed field; +\item $\xi_n\equiv\vec\xi\cdot\hat n$ is the normal displacement, $\hat + n=\nabla\psi/|\nabla\psi|$ (code \texttt{xno\_mn}); +\item $\vec C=\vec Q+\xi_n(\mu_0\vec j\times\hat n)$ is the cross vector, + whose squared norm is closed by the metric contraction + $|\vec C|^2=g_{ij}C^{i*}C^{j}=C_i^*C^i$ (routine \texttt{gpeq\_epf}); +\item $K=K_1+K_2+K_3$ is the destabilizing curvature kernel (routine + \texttt{gpeq\_K}) integrated against $|\xi_n|^2$ (routine + \texttt{gpeq\_dst}). +\end{itemize} +The printed scalars are the radial/poloidal integrals of these pieces: + +\begin{align} +\texttt{C2\_psi/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\psi)^*\,C_\psi\big]\,\mathcal J\,d\theta,\\ +\texttt{C2\_theta/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\theta)^*\,C_\theta\big]\,\mathcal J\,d\theta,\\ +\texttt{C2\_zeta/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\zeta)^*\,C_\zeta\big]\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_C2\_over\_mu0} +&=\texttt{C2\_psi/mu0}+\texttt{C2\_theta/mu0}+\texttt{C2\_zeta/mu0} + =\int\! d\psi\,\frac{1}{\mu_0}\oint |\vec C|^2\,\mathcal J\,d\theta. +\end{align} +The curvature ($K$) pieces, with +$K_1=|\nabla\psi|^2\,\sigma\,S$ (shear $S$), +$K_2=\mu_0 B^2\sigma^2$ (parallel current), and +$K_3=2p'\kappa_\psi$ (curvature projection +\(\kappa_\psi=\boldsymbol\kappa\cdot\nabla\psi\)), give +\begin{align} +\texttt{Ki\_xin2} +&=\int\! d\psi\oint K_i\,|\xi_n|^2\,\mathcal J\,d\theta, +\qquad i=1,2,3,\\ +\texttt{int\_J\_K\_xin2} +&=\sum_{i=1}^{3}\texttt{Ki\_xin2} + =\int\! d\psi\oint K\,|\xi_n|^2\,\mathcal J\,d\theta. +\end{align} +The reconstructed potential energy and its DCON-normalized value are +\begin{align} +\texttt{dW\_p(recon)} +&=\tfrac12\bigl(\texttt{int\_J\_C2\_over\_mu0}-\texttt{int\_J\_K\_xin2}\bigr) + =\tfrac12\!\int\!\Big[\tfrac{|\vec C|^2}{\mu_0}-K|\xi_n|^2\Big] + \mathcal J\,d\psi\,d\theta,\\ +\texttt{dW\_p(recon-normalize)} +&=\texttt{dcon\_fac}\times\texttt{dW\_p(recon)} . +\end{align} +The \texttt{C verify} block reports $\max$/$\mathrm{rms}$ of +$(\nabla\times\vec C)\cdot\nabla\psi=\tfrac1{\mathcal J} +(\partial_\theta C_\zeta-\partial_\zeta C_\theta)$, which must vanish to +\texttt{tol}. + +\subsection{\texttt{GPEC\_RECON2} (\texttt{recon\_flag2})} + +This route reconstructs the original ``first form'' of $\delta W_p$, Eq.~\eqref{eq:ep_first}, +\begin{equation} +\delta W_p=\frac{1}{2\mu_0}\int d\tau_p +\Big\{\,|\vec Q|^2 +-\mu_0\,\vec j\cdot\vec Q\times\vec\xi^{*} ++\mu_0\gamma p\,|\nabla\!\cdot\!\vec\xi|^2 ++\mu_0(\nabla\!\cdot\!\vec\xi)^{*}\,\vec\xi\cdot\nabla p\,\Big\}, +\label{eq:dWp_recon2} +\end{equation} +The reduced diagnostic omits only the positive +\(\gamma p|\nabla\!\cdot\!\vec\xi|^2\) norm; the pressure--compression coupling +term is retained. The three retained groups map one-to-one onto the printed +scalars, with +\begin{itemize} +\item $|\vec Q|^2$ $\to$ \texttt{int\_J\_Q2\_over\_mu0}; +\item the current cross term $-\vec j\cdot\vec Q\times\vec\xi^{*}$ $\to$ + \texttt{int\_J\_jQxixi\_over\_mu0} (evaluated as a determinant of the + Jacobian-weighted contravariant $Q$ and $\xi$ components against + $\mu_0 j^\theta,\mu_0 j^\zeta$); +\item the pressure--compression term + $(\nabla\!\cdot\!\vec\xi)^{*}\,\vec\xi\cdot\nabla p$ $\to$ + \texttt{int\_J\_pdiv\_term}. +\end{itemize} +The route (routine \texttt{gpeq\_firstform}) evaluates these groups pointwise +in $\theta$ and integrates in $\psi$. Writing +$\tilde Q^i\equiv\mathcal JQ^i$ and $\tilde\xi^i\equiv\mathcal J\xi^i$ for the +Jacobian-weighted contravariant components, +\begin{align} +\texttt{int\_J\_Q2\_over\_mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[Q_\psi^*Q^\psi+Q_\theta^*Q^\theta+Q_\zeta^*Q^\zeta\big] + \mathcal J\,d\theta + =\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec Q|^2\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_jQxixi\_over\_mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint\frac{1}{\mathcal J} + \Big[-\mu_0 j^\theta\bigl(\tilde Q^\psi\,\tilde\xi^{\zeta*}-\tilde Q^\zeta\,\tilde\xi^{\psi*}\bigr) + +\mu_0 j^\zeta\bigl(\tilde Q^\psi\,\tilde\xi^{\theta*}-\tilde Q^\theta\,\tilde\xi^{\psi*}\bigr) + \Big]\mathcal J\,d\theta + =\int\! d\psi\oint \vec j\cdot(\vec Q\times\vec\xi^{\,*})\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_pdiv\_term} +&=\int\! d\psi\oint + (\nabla\!\cdot\!\vec\xi)^{*}\,p'\,\xi^{\psi}\, + \mathcal J\,d\theta,\\ +\texttt{int\_J\_total} +&=\texttt{int\_J\_Q2\_over\_mu0} + -\texttt{int\_J\_jQxixi\_over\_mu0} + +\texttt{int\_J\_pdiv\_term}. +\end{align} +Here $\tilde Q^i=\mathcal JQ^i$ and $\tilde\xi^i=\mathcal J\xi^i$ are the +Jacobian-weighted \emph{contravariant} components (code variables +\texttt{bwp\_fun}, \texttt{bmt\_fun}, \texttt{bmz\_fun} and +\texttt{xwp\_fun}, \texttt{xmt\_fun}, \texttt{xmz\_fun}), and +$\nabla\!\cdot\!\vec\xi$ is the compressional density assembled in +\texttt{divxi\_density}. The reconstructed energy is +\begin{equation} +\texttt{dW\_p(recon2)}=\tfrac12\,\texttt{int\_J\_total}, +\qquad +\texttt{dW\_p(recon2-normalize)}=\texttt{dcon\_fac}\times\texttt{dW\_p(recon2)} . +\end{equation} +Each line is printed as a complex pair (real, imaginary); the imaginary part +is a roundoff diagnostic and should vanish. + +\subsection{\texttt{GPEC\_RECON3} (\texttt{recon\_flag3})} + +This route keeps the same closed form as recon1, Eq.~\eqref{eq:dWp_recon1}, +but expands the cross-energy density $|\vec C|^2$ term by term using +$\vec C=\vec Q+\vec V$, Eq.~\eqref{eq:CeqQplusV}, +\begin{equation} +|\vec C|^2=|\vec Q|^2+2\,\mathrm{Re}\bigl(\vec V^{*}\!\cdot\vec Q\bigr) ++|\vec V|^2 . +\label{eq:Cdecomp} +\end{equation} +Crucially, $\vec V$ is evaluated \emph{directly} from its explicit components +in Eq.~\eqref{eq:Vcomp} (equilibrium current, metric, and $\xi_\psi$), not as +$\vec C-\vec Q$, so the $B$ term below does not depend on the reconstructed +$\vec C$. Using that $\vec j$ is tangent to the flux surface ($\vec +j\cdot\hat n=0$), the last piece separates into a parallel-current and a +perpendicular (Pfirsch--Schl\"uter) contribution, +\begin{equation} +|\vec V|^2=\mu_0^2\,|\xi_n|^2\Bigl(\sigma^2B^2 ++\frac{p'^2|\nabla\psi|^2}{B^2}\Bigr). +\end{equation} +The five printed scalars are the $\psi,\theta$ integrals of the four density +pieces of Eq.~\eqref{eq:Cdecomp} plus the full $|\vec C|^2$: +\begin{align} +\texttt{A\_total} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec Q|^2\,\mathcal J\,d\theta + \;\;(=\texttt{int\_J\_Q2\_over\_mu0}\ \text{of recon2}),\\ +\texttt{B\_total} +&=\int\! d\psi\,\frac{2}{\mu_0}\oint + \mathrm{Re}\!\big[V_i^{*}\,Q^{i}\big]\,\mathcal J\,d\theta + \;\;\bigl(=\tfrac{2}{\mu_0}\!\int\!\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)\bigr),\\ +\texttt{Cpar\_total} +&=\int\! d\psi\oint \mu_0\,\sigma^2 B^2\,|\xi_n|^2\,\mathcal J\,d\theta + \;\;(=\texttt{K2\_xin2}\ \text{of recon1}=dst_2),\\ +\texttt{Iperp\_total} +&=\int\! d\psi\oint \frac{\mu_0\,(p')^2\,|\nabla\psi|^2}{B^2}\,|\xi_n|^2\, + \mathcal J\,d\theta,\\ +\texttt{epf\_total} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec C|^2\,\mathcal J\,d\theta + \;\;(=\texttt{int\_J\_C2\_over\_mu0}\ \text{of recon1}). +\end{align} +The closure of the decomposition is reported as +\begin{equation} +\texttt{identity\_res} +=\texttt{epf\_total} +-\bigl(\texttt{A\_total}+\texttt{B\_total} + +\texttt{Cpar\_total}+\texttt{Iperp\_total}\bigr), +\end{equation} +which must vanish to roundoff since +$|\vec C|^2=|\vec Q|^2+2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)+|\vec V|^2$. + +\subsubsection{Combined $\delta W_p$ and the $K_2$ cancellation} +\label{sec:recon3_combined} + +The recon3 outputs $(A,B,I_\perp)$ can be combined with the recon1 +destabilizing pieces $\mathrm{dst}_1,\mathrm{dst}_3$ to write $\delta W_p$ in a +form in which the parallel-current contribution cancels identically. Start +from the recon1 form, Eq.~\eqref{eq:dWp_recon1}, +\begin{equation} +\delta W_p=\frac12\Bigl[\int\mathcal J\,\frac{|\vec C|^2}{\mu_0}\, +-\int\mathcal J\,K\,|\xi_n|^2\Bigr]. +\end{equation} +The recon3 decomposition of the stabilizing density reads +\begin{equation} +\int\mathcal J\,\frac{|\vec C|^2}{\mu_0} +=A+B+C_\parallel+C_\perp, +\qquad +C_\parallel=\mathrm{dst}_2\ (=K_2|\xi_n|^2\ \text{integral}),\quad +C_\perp=I_\perp, +\end{equation} +while the curvature kernel splits as +$\int\mathcal J\,K\,|\xi_n|^2=\mathrm{dst}_1+\mathrm{dst}_2+\mathrm{dst}_3$. +Substituting, +\begin{equation} +2\,\delta W_p +=(A+B+\mathrm{dst}_2+I_\perp) +-(\mathrm{dst}_1+\mathrm{dst}_2+\mathrm{dst}_3). +\end{equation} +The term $\mathrm{dst}_2$ ($=K_2|\xi_n|^2$, the parallel-current$^2$ piece) +enters $|\vec C|^2$ and $K$ with \emph{opposite} sign and cancels exactly, +leaving +\begin{equation} +\boxed{\;2\,\delta W_p=A+B+I_\perp-\mathrm{dst}_1-\mathrm{dst}_3\;.} +\label{eq:dWp_combined} +\end{equation} +Written out term by term, +\begin{equation} +\delta W_p +=\frac12\!\int\!\mathcal J\,d\psi\,d\theta\, +\Bigl[ +\underbrace{\tfrac{|\vec Q|^2}{\mu_0}}_{A_{\text{density}}} +\;+\; +\underbrace{\tfrac{2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)}{\mu_0}}_{B_{\text{density}}} +\;+\; +\underbrace{\tfrac{\mu_0\,p'^2\,|\nabla\psi|^2}{B^2}\,|\xi_n|^2}_{I_{\perp,\text{density}}} +\;-\; +\underbrace{|\nabla\psi|^2\,\sigma S\,|\xi_n|^2}_{K_1|\xi_n|^2\,(\mathrm{dst}_1)} +\;-\; +\underbrace{2p'\kappa_\psi\,|\xi_n|^2}_{K_3|\xi_n|^2\,(\mathrm{dst}_3)} +\Bigr], +\end{equation} +with $\vec V=\xi_n\,\mu_0\,\vec j\times\hat n$ and +$\sigma=\vec j\cdot\vec B/B^2$. In the DIII-D example +($n=1$, \texttt{gpec.log}) this gives +$2\,\delta W_p=0.81768+(-0.99951)+0.04056-(-0.03096)-0.12767=-0.23798$, +i.e. $\delta W_p=-0.11899$, matching \texttt{dW\_p(recon)} to roundoff. + +\paragraph{Physical role of each term.} +\begin{itemize} +\item $+A$ (sign $+$, stabilizing): magnetic energy of the perturbed field + $\vec Q$ itself (field-line bending). +\item $+B$ (sign-indefinite): cross-coupling of $\vec Q$ with the equilibrium + current, $2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)$ --- acts as a kink + driver or suppressor depending on phase. +\item $+I_\perp$ (sign $+$): Pfirsch--Schl\"uter perpendicular-current$^2$ + contribution $\propto p'^2|\nabla\psi|^2|\xi_n|^2/B^2$, positive definite. +\item $-\mathrm{dst}_1$ (sign-indefinite): shear--current coupling + $|\nabla\psi|^2\sigma S\,|\xi_n|^2$; stabilizing or destabilizing with the + sign of $K_1$. +\item $-\mathrm{dst}_3$ (sign-indefinite): pressure--curvature term + $2p'\kappa_\psi\,|\xi_n|^2$; a ballooning drive when $p'\kappa_\psi<0$. +\end{itemize} + +\paragraph{Meaning of the vanishing $K_2$.} +The cancellation does \emph{not} mean the parallel current $j_\parallel^2$ is +absent from $\delta W$. The same quantity $C_\parallel=K_2|\xi_n|^2$ enters the +stabilizing $|\vec C|^2$ and the destabilizing $K$ with opposite sign, so the +explicit $j_\parallel^2$ piece cancels in the energy balance. The residual +effect of $j_\parallel$ survives only indirectly --- through the shear term +$K_1$ (its dependence on $\sigma S$) and through the cross term $B$. + +\subsection{Cross-checks between the three routes} + +The identities below are exact and are confirmed at roundoff in the DIII-D +example ($n=1$, \texttt{gpec.log}): +\begin{align} +\texttt{recon3:A\_total} &= \texttt{recon2:int\_J\_Q2\_over\_mu0} + &&(0.817676),\\ +\texttt{recon3:Cpar\_total} &= \texttt{recon1:K2\_xin2} + = \texttt{gpeq\_dst}:dst_2 &&(1.082617),\\ +\texttt{recon3:epf\_total} &= \texttt{recon1:int\_J\_C2\_over\_mu0} + &&(0.941346),\\ +\texttt{recon1:dW\_p} &\approx \texttt{recon2:dW\_p} + &&(-0.1190\ \text{vs}\ -0.1198). +\end{align} + + +\clearpage +\appendix + +\section{Full reduction of the $\mathbf H_4$ ($\mathbf V$-sector) block}\label{app:h4} + +This appendix proves the $\mathbf H_4$ ($\mathbf V$-sector) identity quoted in +the main text at two levels: (i)~the equilibrium-free Bernstein form +$\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K$, equal term by +term to \eqref{eq:ep_bernsteinK}; and (ii)~its collapse to the compact block +$\mathbf H_4=\mu_0p'\!\left(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J'\right)-2\pi +f'q'\chi'\,\mathbf I$, whose only equilibrium input is the Grad--Shafranov +equation. The steps that invoke an equilibrium relation (force balance, the +magnetic-surface condition $\mathbf B\cdot\nabla\psi=0$, Grad--Shafranov) are +noted where they occur. + +\subsection{Setup: the $\mathbf V$-sector pieces and the single geometric rule} + +We work in DCON straight-field-line flux coordinates $(\psi,\theta,\zeta)$, all +normalized to $[0,1]$, with $e_m\equiv e^{2\pi i(m\theta-n\zeta)}$ and the +Jacobian-scaled basis $\vec v_i=\vec e_i/\mathcal J$, so that +$\vec v_i\cdot\vec v_j=g_{ij}/\mathcal J^2$. We start from +\begin{equation} +\mathbf Q \equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, +\qquad +\vec C = \mathbf Q+\mathbf V, +\end{equation} +so that $|\vec C|^2$ contains $5$ diagonal and $10$ cross products, plus the +scalar Bernstein term $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$. +The field pieces are +\begin{equation} +\begin{aligned} +\mathbf X &= -2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3), +& +\mathbf Y &= 2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1,\\ +\mathbf Z &= -\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3), +& +\mathbf U &= -\sum_m v_m e_m\left[\chi''\vec v_2+(q\chi')'\vec v_3\right],\\ +\mathbf V &= \frac{\xi_\psi}{|\nabla\psi|^2}\left(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3\right), +\end{aligned} +\end{equation} +with +\begin{equation} +\mathcal A_2 \equiv \mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}, +\qquad +\mathcal A_3 \equiv \mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23}. +\end{equation} +All dot products are reduced with the single geometric rule +\begin{equation} +\vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal J^2}, +\qquad +e_m^*e_{m'}=e^{2\pi i(m'-m)\theta}. +\end{equation} + +Throughout, the only current data are the DCON contravariant current densities +\begin{equation} +\mu_0 j^\theta=-\frac{2\pi f'}{\mathcal J}, +\qquad +\mu_0 j^\zeta=-\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J}, +\label{eq:app_dconcurrents} +\end{equation} +and the equilibrium is a magnetic surface, $\mathbf B\cdot\nabla\psi=0$, so that +$\mathbf B=\chi'\,(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)$ +lies in the surface and $\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$ with +$\sigma\equiv\mathbf j\cdot\mathbf B/B^2$. Because the perturbation carries a +single toroidal mode number $n$, +$|\xi_\psi|^2=\sum_{m,m'}v_m^*v_{m'}\,e^{2\pi i(m'-m)\theta}$ is independent of +$\zeta$; this is used in Sec.~\ref{app:assembly}. + +\subsection{The five diagonal and ten cross products} + +Every $|\mathbf C|^2$ product is reduced with +$\vec v_i\cdot\vec v_j=g_{ij}/\mathcal J^2$ and the cofactor identity +$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$. Only the +$\mathbf V$-touching products +($\mathbf X^*\!\cdot\mathbf V,\ \mathbf Y^*\!\cdot\mathbf V,\ +\mathbf Z^*\!\cdot\mathbf V,\ \mathbf U^*\!\cdot\mathbf V,\ |\mathbf V|^2$) and the +scalar $-\mu_0K$ term enter $\mathbf H_4$; the remaining diagonals and cross +products are listed for completeness and feed the blocks +$\mathbf A,\mathbf B,\mathbf C,\mathbf D,\mathbf E,\mathbf H_{1\text{--}3}$ of the +main text. + +\medskip\noindent\emph{Diagonal products.} + +\begin{equation} +|\mathbf X|^2 += +\frac{(2\pi)^2}{\mathcal J^2} +\sum_{m,m'} u_m^* u_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +n^2 g_{22}+n(m+m')g_{23}+m m' g_{33} +\right], +\end{equation} + +\begin{equation} +|\mathbf Y|^2 += +\frac{(2\pi\chi')^2}{\mathcal J^2} +\sum_{m,m'} (m-nq)(m'-nq)\,v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta}\, +g_{11}, +\end{equation} + +\begin{equation} +|\mathbf Z|^2 += +\frac{\chi'^2}{\mathcal J^2} +\sum_{m,m'} v_m^{\prime *} v_{m'}'\,e^{2\pi i(m'-m)\theta} +\left[ +g_{22}+2q\,g_{23}+q^2 g_{33} +\right], +\end{equation} + +\begin{equation} +|\mathbf U|^2 += +\frac{1}{\mathcal J^2} +\sum_{m,m'} v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +\chi''^2 g_{22}+2\chi''(q\chi')' g_{23}+(q\chi')'^2 g_{33} +\right], +\end{equation} + +\begin{equation} +\begin{aligned} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left[ +\mu_0^2 g_{22}(j^\theta)^2 ++2\mu_0^2 g_{23}\,j^\theta j^\zeta ++\mu_0^2 g_{33}(j^\zeta)^2 +\right] \\ +&= +|\xi_\psi|^2 +\left[ +\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\frac{(\mu_0p')^2}{B^2} +\right]. +\end{aligned} +\end{equation} + +The second equality for $|\mathbf V|^2$ is \emph{not} a generic metric identity: +it uses the magnetic-surface condition and force balance. Writing the bracket +as +$\mu_0^2[g_{22}(j^\theta)^2+2g_{23}j^\theta j^\zeta+g_{33}(j^\zeta)^2] +=|\mu_0\mathbf j\times\nabla\psi|^2/|\nabla\psi|^2$ and using +$\mathbf B\cdot\nabla\psi=0$ (so $|\mathbf B\times\nabla\psi|^2=B^2|\nabla\psi|^2$ +and $\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$), +\begin{equation} +\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} +-\left[\frac{\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\frac{(\mu_0p')^2}{B^2}\right] +\;\propto\; +\chi'^2\,(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2-(\mu_0p')^2 . +\end{equation} +Inserting the DCON currents~\eqref{eq:app_dconcurrents}, the +$2\pi f'q/\mathcal J$ pieces cancel and +$\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, so the residual vanishes +identically: the parallel ($f'$) current supplies the +$\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)$ term and the in-surface pressure +current supplies $|\nabla\psi|^2(\mu_0p')^2/B^2$. + +\medskip\noindent\emph{Cross products.} + +\begin{equation} +\mathbf X^*\!\cdot\mathbf Z += +-\frac{2\pi i\chi'}{\mathcal J^2} +\sum_{m,m'} u_m^* v_{m'}'\,e^{2\pi i(m'-m)\theta} +\left[ +n(g_{22}+q g_{23})+m(g_{23}+q g_{33}) +\right], +\end{equation} + +\begin{equation} +\mathbf X^*\!\cdot\mathbf Y += +-\frac{(2\pi)^2\chi'}{\mathcal J^2} +\sum_{m,m'} (m'-nq)\,u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +n\,g_{12}+m\,g_{31} +\right], +\end{equation} + +\begin{equation} +\begin{aligned} +\mathbf X^*\!\cdot\mathbf U +&= +-\frac{2\pi i}{\mathcal J^2} +\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} \\ +&\qquad\times +\left[ +\chi''(n g_{22}+m g_{23})+(q\chi')'(n g_{23}+m g_{33}) +\right], +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +\mathbf X^*\!\cdot\mathbf V +&= +-2\pi i +\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta}\, +\mu_0\!\left(m j^\theta-n j^\zeta\right) \\ +&= +-2\pi i +\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +-\frac{2\pi f'}{\mathcal J}(m-nq)+n\frac{\mu_0p'}{\chi'} +\right], +\end{aligned} +\end{equation} + +The collapse of $\mathbf X^*\!\cdot\mathbf V$ to a two-component current form uses +the cofactor identity: with $\mathcal A_2,\mathcal A_3$ as above, +\begin{equation} +n(\mathcal A_2 g_{22}-\mathcal A_3 g_{23})+m(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) +=(g_{22}g_{33}-g_{23}^2)\,(n\,\mu_0 j^\zeta-m\,\mu_0 j^\theta), +\end{equation} +since the $\mu_0 j^\theta$ terms drop from the $n$-bracket and the +$\mu_0 j^\zeta$ terms from the $m$-bracket; dividing by +$|\nabla\psi|^2\mathcal J^2$ and using +$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$ gives +$-(m\,\mu_0 j^\theta-n\,\mu_0 j^\zeta)$. + +\begin{equation} +\mathbf Y^*\!\cdot\mathbf Z += +\frac{2\pi i\chi'^2}{\mathcal J^2} +\sum_{m,m'} (m-nq)\,v_m^* v_{m'}'\,e^{2\pi i(m'-m)\theta} +\left[ +g_{12}+q\,g_{31} +\right], +\end{equation} + +\begin{equation} +\mathbf Y^*\!\cdot\mathbf U += +\frac{2\pi i\chi'}{\mathcal J^2} +\sum_{m,m'} (m-nq)\,v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +\chi''\,g_{12}+(q\chi')'\,g_{31} +\right], +\end{equation} + +\begin{equation} +\mathbf Z^*\!\cdot\mathbf U += +\frac{\chi'}{\mathcal J^2} +\sum_{m,m'} v_m^{\prime *} v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +\chi''(g_{22}+q g_{23})+(q\chi')'(g_{23}+q g_{33}) +\right], +\end{equation} + +\begin{equation} +\mathbf Z^*\!\cdot\mathbf V += +\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi +\qquad(\text{pointwise, after } g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2), +\end{equation} + +For $\mathbf Z^*\!\cdot\mathbf V$ the same cofactor cancellation gives, before +inserting currents, +$\mathbf Z^*\!\cdot\mathbf V=\chi'\,\xi_\psi^{\prime *}\xi_\psi\, +(q\,\mu_0 j^\theta-\mu_0 j^\zeta)$; inserting~\eqref{eq:app_dconcurrents}, +$q\,\mu_0 j^\theta-\mu_0 j^\zeta +=-\tfrac{2\pi f'q}{\mathcal J}+\tfrac{\mu_0p'}{\chi'}+\tfrac{2\pi f'q}{\mathcal J} +=\tfrac{\mu_0p'}{\chi'}$ (the $2\pi f'q/\mathcal J$ terms cancel), so +$\mathbf Z^*\!\cdot\mathbf V=\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi$. + +\begin{equation} +\mathbf U^*\!\cdot\mathbf V += +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'}-\frac{2\pi f'q'\chi'}{\mathcal J} +\right] +\qquad(\text{pointwise}), +\end{equation} + +For $\mathbf U^*\!\cdot\mathbf V$ the cofactor cancellation leaves the pointwise +coefficient $|\xi_\psi|^2[-\chi''\mu_0 j^\zeta+(q\chi')'\mu_0 j^\theta]$; +inserting~\eqref{eq:app_dconcurrents} and expanding $(q\chi')'=q'\chi'+q\chi''$, +the $\pm2\pi f'q\chi''/\mathcal J$ cross terms cancel, leaving +$|\xi_\psi|^2[\mu_0p'\tfrac{\chi''}{\chi'}-\tfrac{2\pi f'q'\chi'}{\mathcal J}]$. + +\begin{equation} +\begin{aligned} +\mathcal J\,(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) +&= +\chi'\,\mathcal F +\left\{ +\left[(\partial_\theta+q\partial_\zeta)\xi_\psi^*\right]\xi_\psi ++\xi_\psi^*\left[(\partial_\theta+q\partial_\zeta)\xi_\psi\right] +\right\}, \\ +\mathcal F +&\equiv +\mathcal J\, +\frac{(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi)-(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi)}{|\nabla\psi|^2}. +\end{aligned} +\end{equation} + +\medskip\noindent\emph{Scalar Bernstein term.} + +\begin{equation} +-\mu_0\,\mathcal J\,K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} += +|\xi_\psi|^2 +\left[ +-\mathcal J\mu_0\sigma S_\psi +-\mathcal J\frac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2} +-2\mu_0p'\mathcal J\frac{\kappa_\psi}{|\nabla\psi|^2} +\right]. +\end{equation} + +\subsection{Assembly of the $\mathbf V$-sector and the $\theta$-integration by parts} +\label{app:assembly} + +The four $\mathbf V$-touching contributions combine, under a common +$\mathcal J$ weight and before any equilibrium input, as + +\begin{equation} +\begin{aligned} +&\int d\theta\,d\zeta\;\mathcal J +\left[ +(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) ++(\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U) ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\right]\\ +&=\int d\theta\,d\zeta\, +\bigg[ +\underbrace{\chi'\,\mathcal F\,\partial_\theta|\xi_\psi|^2}_{\mathbf Y^*\mathbf V+\text{c.c.}} ++|\xi_\psi|^2\Big( +\underbrace{2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-4\pi f'q'\chi'}_{\mathbf U^*\mathbf V+\text{c.c.}} ++\underbrace{\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\mathcal J\tfrac{(\mu_0p')^2}{B^2}}_{|\mathbf V|^2}\\ +&\hspace{5em} +\underbrace{ +-\,\mathcal J\mu_0\sigma S_\psi +-\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2} +-2\mu_0p'\mathcal J\tfrac{\kappa_\psi}{|\nabla\psi|^2}}_{-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2} +\Big) +\bigg], +\quad +\mathcal F\equiv +\mathcal J\frac{\mu_0 j^\theta(\nabla\zeta\cdot\nabla\psi)-\mu_0 j^\zeta(\nabla\theta\cdot\nabla\psi)}{|\nabla\psi|^2}. +\end{aligned} +\end{equation} + +Only $\partial_\theta|\xi_\psi|^2$ appears (not the full +$(\partial_\theta+q\partial_\zeta)|\xi_\psi|^2$ that +$\mathbf Y^*\!\cdot\mathbf V+\text{c.c.}$ produces) because $|\xi_\psi|^2$ is +$\zeta$-independent for a single-$n$ perturbation, so +$\partial_\zeta|\xi_\psi|^2=0$ pointwise and the $q\,\partial_\zeta$ piece drops +identically. + +Only $\mathbf Y^*\!\cdot\mathbf V$ carries a $\theta$ derivative. Integrating +it by parts (the boundary term vanishes because $\chi'$ is a flux function and +$\mathcal F,|\xi_\psi|^2$ are $\theta$-periodic), its $|\xi_\psi|^2$ +coefficient becomes $-\chi'\,\partial_\theta\mathcal F$: +\begin{equation} +\int d\theta\,\chi'\mathcal F\,\partial_\theta|\xi_\psi|^2 +=-\int d\theta\;|\xi_\psi|^2\,\chi'\,\partial_\theta\mathcal F, +\qquad +-\chi'\,\partial_\theta\mathcal F +=-\frac{2\pi f'}{\chi'}\mathcal J S_\psi ++2\pi f'q'\chi' +-\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right), +\end{equation} + +A $\theta$ derivative cannot generate $q'=dq/d\psi$; the explicit +$+2\pi f'q'\chi'$ above only compensates the $q'$ introduced by rewriting part +of $-\chi'\partial_\theta\mathcal F$ through +$S_\psi=\tfrac{\chi'^2}{\mathcal J}[q'+\partial_\theta(\cdots)]$, so the net +$q'$ content of $-\chi'\partial_\theta\mathcal F$ is zero. As written, the +$+2\pi f'q'\chi'$ pairs below with the $-4\pi f'q'\chi'$ of +$\mathbf U^*\!\cdot\mathbf V$ to give $-2\pi f'q'\chi'\,\mathbf I$. + +Three immediate cancellations follow: + +\begin{equation} +-\frac{2\pi f'}{\chi'}\mathcal J S_\psi-\mathcal J\mu_0\sigma S_\psi +=-\mathcal J S_\psi\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right), +\quad +\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)-\mu_0^2\sigma^2B^2}{|\nabla\psi|^2}=0, +\quad +2\pi f'q'\chi'-4\pi f'q'\chi'=-2\pi f'q'\chi', +\end{equation} +and substituting the DCON shear and curvature +\begin{equation} +S_\psi=\frac{\chi'^2}{\mathcal J}\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right], +\qquad +\kappa_\psi=\frac{|\nabla\psi|^2}{B^2}\left[\mu_0 p'+\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right] +\end{equation} +(so that $\mathcal J$ cancels in $-\mathcal J S_\psi$ and the two $(\mu_0p')^2/B^2$ pieces combine), the whole sector becomes the single fully expanded identity +\begin{equation} +\begin{aligned} +&\int d\theta\,d\zeta\;\mathcal J +\left[ +(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) ++(\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U) ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\right]\\ +&=\int d\theta\,d\zeta\;|\xi_\psi|^2 +\bigg[ +-2\pi f'q'\chi' ++2\mu_0p'\mathcal J\frac{\chi''}{\chi'} +-\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right) +-\mathcal J\frac{(\mu_0p')^2}{B^2}\\ +&\hspace{4em} +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +-\chi'^2\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right)\! +\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right] +\bigg]. +\end{aligned} +\label{eq:H4_expanded} +\end{equation} + +Up to \eqref{eq:H4_expanded} no equilibrium input beyond the DCON currents has +been used. The first two terms give $-2\pi f'q'\chi'\mathbf I$ and +$\mu_0p'(\chi''/\chi')\mathbf J$; it remains to show, for arbitrary Fourier +amplitudes $\{v_m\}$, that the rest closes, +\begin{equation} +\begin{aligned} +&\int d\theta\,d\zeta\;|\xi_\psi|^2 +\bigg[ +-\chi'^2\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right)\! +\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right] +-\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right)\\ +&\hspace{4em} +-\mathcal J\frac{(\mu_0p')^2}{B^2} +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\bigg]\\ +&=\int d\theta\,d\zeta\;|\xi_\psi|^2 +\bigg[ +-2\pi f'q'\chi' +-\mu_0\sigma\chi'^2 q' +-2\pi f'\chi'\,\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\\ +&\hspace{4em} +-\mu_0\sigma\chi'^2\,\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right) +-\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right) +-\mathcal J\frac{(\mu_0p')^2}{B^2}\\ +&\hspace{4em} +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 +-\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\bigg]. +\end{aligned} +\end{equation} + +\subsection{The $\sigma$ identity: collapse of the parallel-current coefficient} +\label{app:sigma} + +The remaining bracket closes because of a single physical identity: the +shear/parallel-current coefficient $\tfrac{2\pi f'}{\chi'}+\mu_0\sigma$ is not +zero but a pure pressure--geometry quantity. Write +$\sigma=\mathbf j\cdot\mathbf B/B^2$ in current components, using +$\mathbf B=\chi'\,(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)$ and +$\mathbf j=\mathcal J\,(j^\theta\,\nabla\zeta\times\nabla\psi+j^\zeta\,\nabla\psi\times\nabla\theta)$: +\begin{equation} +\begin{aligned} +\mu_0\mathbf j\cdot\mathbf B +&=\mathcal J\chi'\big[\mu_0 j^\theta\,(\nabla\zeta\times\nabla\psi)+\mu_0 j^\zeta\,(\nabla\psi\times\nabla\theta)\big] +\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta),\\ +B^2&=\chi'^2\,|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2. +\end{aligned} +\end{equation} +Inserting the DCON contravariant currents +$\mu_0 j^\theta=-\dfrac{2\pi f'}{\mathcal J}$, +$\mu_0 j^\zeta=-\dfrac{\mu_0p'}{\chi'}-\dfrac{2\pi f'q}{\mathcal J}$ +and using $\nabla\zeta\times\nabla\psi=-\nabla\psi\times\nabla\zeta$ to group the $f'$ terms, +\begin{equation} +\begin{aligned} +\mu_0\mathbf j\cdot\mathbf B +&=\mathcal J\chi'\left[-\frac{2\pi f'}{\mathcal J}(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)-\frac{\mu_0p'}{\chi'}(\nabla\psi\times\nabla\theta)\right]\\ +&\qquad\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)\\ +&=-2\pi f'\chi'\,|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2 +-\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta). +\end{aligned} +\end{equation} +Dividing by $B^2$, +\begin{equation} +\mu_0\sigma=-\frac{2\pi f'}{\chi'}-\frac{\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)}{B^2}, +\end{equation} +so the parallel-current coefficient collapses to a pure-$p'$ form, +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma +=-\frac{\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)}{B^2}. +\end{equation} +Applying the cross-product identity +$(\mathbf A\times\mathbf B)\cdot(\mathbf C\times\mathbf D)=(\mathbf A\cdot\mathbf C)(\mathbf B\cdot\mathbf D)-(\mathbf A\cdot\mathbf D)(\mathbf B\cdot\mathbf C)$ +to the two cross-product factors, +\begin{equation} +\begin{aligned} +|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2 +&=(\nabla\psi\cdot\nabla\psi)(q^2\nabla\theta\cdot\nabla\theta-2q\,\nabla\theta\cdot\nabla\zeta+\nabla\zeta\cdot\nabla\zeta) +-(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)^2,\\ +(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta) +&=(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta). +\end{aligned} +\end{equation} +The first is $B^2/\chi'^2$; the second turns the collapse into the pure-$p'$, dot-product form +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma +=-\frac{\mu_0p'\mathcal J}{B^2} +\big[(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)\big]. +\end{equation} +Write the local shorthands +$\mathcal D\equiv(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)$ +and +$G_s\equiv\dfrac{q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}$. +Inserting $\mu_0\sigma=-\tfrac{2\pi f'}{\chi'}-\tfrac{\mu_0 p'\mathcal J\,\mathcal D}{B^2}$ into the two $-\mu_0\sigma\chi'^2(\cdots)$ pieces of the distributed form, each $f'$ part \emph{exactly} cancels the existing $-2\pi f'\chi'(\cdots)$ partner: +\begin{equation} +\begin{aligned} +\cancel{-\,2\pi f'q'\chi'}\;-\,\mu_0\sigma\chi'^2 q' +&=\cancel{-\,2\pi f'q'\chi'}\;\cancel{+\,2\pi f'q'\chi'}\;+\;\frac{\mu_0p'\chi'^2\mathcal J\,q'\,\mathcal D}{B^2},\\ +\cancel{-\,2\pi f'\chi'\,\partial_\theta G_s}\;-\,\mu_0\sigma\chi'^2\,\partial_\theta G_s +&=\cancel{-\,2\pi f'\chi'\,\partial_\theta G_s}\;\cancel{+\,2\pi f'\chi'\,\partial_\theta G_s}\;+\;\frac{\mu_0p'\chi'^2\mathcal J\,\mathcal D}{B^2}\,\partial_\theta G_s. +\end{aligned} +\end{equation} +The crossed-out $f'$ pairs vanish identically, so the entire $\mathbf V$-sector +residual reduces to pure $\mu_0p'$ pieces; the surviving geometric +$\theta$-derivatives are closed by the Grad--Shafranov identity of +Sec.~\ref{app:master}. + +\subsection{Master identity, cofactor form, and the Grad--Shafranov closure} +\label{app:master} + +The closure is most transparent pointwise in $\theta$. + +\paragraph{Four weights and target.} +Write the sector as $\int d\theta\,\mathcal W\,|\xi_\psi|^2$ with +$\mathcal W=W_{UV}+W_V+W_K+W_{YV}$ ($\mathbf Y^*\!\cdot\mathbf V$ already +integrated by parts), abbreviating $g^{\psi\psi}=|\nabla\psi|^2$: +\begin{align} +W_{UV}&=2\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J-4\pi f'q'\chi', +\label{eq:WUV}\\ +W_{V}&=\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{g^{\psi\psi}} + +\mathcal J\tfrac{(\mu_0p')^2}{B^2}, +\label{eq:WV}\\ +W_{K}&=-\mathcal J\mu_0\sigma S_\psi + -\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}} + -\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}}, +\label{eq:WK}\\ +W_{YV}&=-\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi+2\pi f'q'\chi' + -\mu_0p'\,\partial_\theta\!\big(\mathcal J\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big). +\label{eq:WYV} +\end{align} +Target +$\;W_\star=\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J+\mu_0p'\mathcal J'-2\pi f'q'\chi'$, +i.e.\ $\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. + +\paragraph{Step 1 — parallel current ($\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$).} +The first term of \eqref{eq:WV} and the second of \eqref{eq:WK} cancel pointwise: +\begin{equation} +\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{g^{\psi\psi}} +-\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}}=0 +\;\Longrightarrow\; +W_V+W_K=\mathcal J\tfrac{(\mu_0p')^2}{B^2} +-\mathcal J\mu_0\sigma S_\psi +-\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}}. +\label{eq:step1} +\end{equation} + +\paragraph{Step 2 — $\sigma$ identity +$\;\mu_0\sigma=-\tfrac{2\pi f'}{\chi'}-\tfrac{\mu_0p'\mathcal J\,\mathcal D}{B^2}$.} +The shear term of \eqref{eq:step1} splits: +\begin{equation} +-\mathcal J\mu_0\sigma S_\psi +=\underbrace{\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi}_{\text{cancels \eqref{eq:WYV}}} ++\tfrac{\mu_0p'\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\label{eq:step2} +\end{equation} + +\paragraph{Step 3 — sum $\mathcal W=\eqref{eq:WUV}+\eqref{eq:step1}+\eqref{eq:WYV}$.} +The $\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi$ pair cancels and +$-4\pi f'q'\chi'+2\pi f'q'\chi'=-2\pi f'q'\chi'$, leaving +\begin{equation} +\mathcal W=-2\pi f'q'\chi' ++2\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J ++\mathcal J\tfrac{(\mu_0p')^2}{B^2} +-\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}} +-\mu_0p'\,\partial_\theta\!\big(\mathcal J\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big) ++\tfrac{\mu_0p'\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\label{eq:step3} +\end{equation} + +\paragraph{Step 4 — $\mathcal W=W_\star$, divide by $\mu_0p'$ (master identity).} +Subtracting $W_\star$ (the $-2\pi f'q'\chi'$ and one $\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J$ match): +\begin{equation} +\boxed{\; +\mathcal J\frac{\chi''}{\chi'} ++\frac{\mathcal J\,\mu_0p'}{B^2} +-\frac{2\mathcal J\,\kappa_\psi}{g^{\psi\psi}} +-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) ++\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi +=\mathcal J' \;} +\label{eq:master} +\end{equation} +with the inputs +\begin{equation} +\mathcal D +\equiv g^{\psi\psi}(q\,g^{\theta\theta}-g^{\theta\zeta}) +-g^{\psi\theta}(q\,g^{\psi\theta}-g^{\psi\zeta}), +\qquad +S_\psi=\frac{\chi'^2}{\mathcal J}\Big[q'+\partial_\theta G_s\Big], +\qquad +G_s\equiv\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} . +\label{eq:DSdef} +\end{equation} + +\paragraph{Cofactor form of $\mathcal D$ ($g^{ij}=(g_{ij})^{-1}$, $\det g_{ij}=\mathcal J^2$).} +The minors are +$g^{\psi\psi}g^{\theta\theta}-(g^{\psi\theta})^2=g_{\zeta\zeta}/\mathcal J^2$, +$\;g^{\psi\psi}g^{\theta\zeta}-g^{\psi\theta}g^{\psi\zeta}=-g_{\theta\zeta}/\mathcal J^2$, hence +\begin{equation} +\boxed{\; +\mathcal D=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2} +=\frac{\partial_q G_\alpha}{2\mathcal J^2}, +\qquad +\frac{\chi'^2\mathcal D}{B^2} +=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{G_\alpha}, +\;} +\qquad +G_\alpha\equiv g_{\theta\theta}+2q\,g_{\theta\zeta}+q^2g_{\zeta\zeta}=\frac{\mathcal J^2B^2}{\chi'^2}. +\label{eq:cofactor} +\end{equation} + +\paragraph{Step 5 — insert $\kappa_\psi$ and $S_\psi$ into \eqref{eq:master}.} +With +$\kappa_\psi=\tfrac{g^{\psi\psi}}{B^2}\big[\mu_0p' ++\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\,\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big]$, +\begin{equation} +-\frac{2\mathcal J\kappa_\psi}{g^{\psi\psi}} +=-\frac{2\mathcal J\mu_0p'}{B^2} + -\frac{\mathcal J\,\partial_\psi B^2}{B^2} + -\frac{\mathcal J\,g^{\psi\theta}\,\partial_\theta B^2}{g^{\psi\psi}B^2}, +\qquad +\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi +=\mathcal J\,\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{G_\alpha}\,\big(q'+\partial_\theta G_s\big). +\label{eq:step5a} +\end{equation} +Using $\beta^2\equiv B^2/\chi'^2=G_\alpha/\mathcal J^2$, +\begin{equation} +\frac{\partial_\psi B^2}{B^2}=2\frac{\chi''}{\chi'}+\frac{\partial_\psi\beta^2}{\beta^2}, +\qquad +\frac{\partial_\theta B^2}{B^2}=\frac{\partial_\theta G_\alpha}{G_\alpha}-2\frac{\partial_\theta\mathcal J}{\mathcal J}, +\qquad +\frac{\mathcal J\mu_0p'}{B^2}-\frac{2\mathcal J\mu_0p'}{B^2}=-\frac{\mathcal J\mu_0p'}{B^2}, +\label{eq:step5b} +\end{equation} +so $\mathcal J\tfrac{\chi''}{\chi'}-2\mathcal J\tfrac{\chi''}{\chi'}=-\mathcal J\tfrac{\chi''}{\chi'}$ and \eqref{eq:master} becomes +\begin{equation} +\boxed{\; +\begin{aligned} +&-\mathcal J\frac{\chi''}{\chi'} +-\frac{\mathcal J\mu_0p'}{B^2} +-\mathcal J\frac{\partial_\psi\beta^2}{\beta^2} +-\frac{\mathcal J\,g^{\psi\theta}}{g^{\psi\psi}}\Big(\frac{\partial_\theta G_\alpha}{G_\alpha}-2\frac{\partial_\theta\mathcal J}{\mathcal J}\Big)\\ +&\qquad +-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) ++\frac{\mathcal J\,(q\,g_{\zeta\zeta}+g_{\theta\zeta})}{G_\alpha}\big(q'+\partial_\theta G_s\big) +=\mathcal J' . +\end{aligned}\;} +\label{eq:gsstep} +\end{equation} + +Equation~\eqref{eq:gsstep} is the remaining Grad--Shafranov input. It can be +derived directly from the covariant-component form of +\(\nabla\times\mathbf B=\mu_0\mathbf j\), as follows. Define +\begin{equation} +A\equiv\frac{g^{\psi\theta}}{g^{\psi\psi}}, +\qquad +H\equiv qg_{\zeta\zeta}+g_{\theta\zeta}, +\qquad +\mathcal C_B\equiv B_\theta+qB_\zeta . +\end{equation} +Since \(B^\theta=\chi'/\mathcal J\) and \(B^\zeta=q\chi'/\mathcal J\), +\begin{equation} +B_i=g_{i\theta}B^\theta+g_{i\zeta}B^\zeta +=\frac{\chi'}{\mathcal J}(g_{i\theta}+qg_{i\zeta}), +\qquad +B_\zeta=\frac{\chi'}{\mathcal J}H, +\qquad +\mathcal C_B=\frac{\chi'}{\mathcal J}G_\alpha +=\frac{\mathcal J B^2}{\chi'} . +\end{equation} +The inverse-metric cofactor identities give +\begin{equation} +g_{\psi\theta}+qg_{\psi\zeta} +=-A\,G_\alpha+G_s\,H, +\end{equation} +and therefore +\begin{equation} +B_\psi=-A\,\mathcal C_B+G_s B_\zeta . +\label{eq:Bpsi_GS_closure} +\end{equation} + +For an axisymmetric equilibrium the contravariant curl components are +\begin{equation} +\mu_0j^\theta=-\frac{1}{\mathcal J}\partial_\psi B_\zeta, +\qquad +\mu_0j^\zeta= +\frac{1}{\mathcal J}\left(\partial_\psi B_\theta-\partial_\theta B_\psi\right). +\label{eq:covcurl_GS_closure} +\end{equation} +Using the physical-\(f'\) current convention of +\eqref{eq:app_dconcurrents}, the first of \eqref{eq:covcurl_GS_closure} gives +\begin{equation} +\partial_\psi B_\zeta=2\pi f', +\qquad +B_\zeta=2\pi f(\psi), +\qquad +\partial_\theta B_\zeta=0 . +\end{equation} +Here the last two statements are the usual Grad--Shafranov convention that the +covariant toroidal-field function \(f\) is a flux function; this is the same +convention encoded in \eqref{eq:app_dconcurrents}. The second of +\eqref{eq:covcurl_GS_closure} gives +\begin{equation} +\partial_\psi B_\theta-\partial_\theta B_\psi +=-\frac{\mathcal J\mu_0p'}{\chi'}-2\pi qf' . +\end{equation} +Substituting \(\mathcal C_B=B_\theta+qB_\zeta\) and +\(\partial_\psi B_\zeta=2\pi f'\) cancels the explicit \(2\pi qf'\) terms and +leaves +\begin{equation} +\partial_\psi\mathcal C_B-\partial_\theta B_\psi +=q'B_\zeta-\frac{\mathcal J\mu_0p'}{\chi'} . +\end{equation} +Using \eqref{eq:Bpsi_GS_closure} and \(\partial_\theta B_\zeta=0\), this becomes +\begin{equation} +\partial_\psi\mathcal C_B+\partial_\theta(A\mathcal C_B) +-B_\zeta(q'+\partial_\theta G_s) ++\frac{\mathcal J\mu_0p'}{\chi'}=0 . +\label{eq:CB_GS_closure} +\end{equation} + +Multiply \eqref{eq:CB_GS_closure} by +\(\chi'/B^2=\mathcal J/\mathcal C_B\). The three geometric factors reduce as +\begin{align} +\frac{\chi'}{B^2}\partial_\psi\mathcal C_B +&= +\mathcal J' ++\mathcal J\frac{\partial_\psi B^2}{B^2} +-\mathcal J\frac{\chi''}{\chi'},\\ +\frac{\chi'}{B^2}\partial_\theta(A\mathcal C_B) +&= +\partial_\theta(\mathcal J A) ++\mathcal J A\frac{\partial_\theta B^2}{B^2},\\ +\frac{\chi'}{B^2}B_\zeta +&= +\mathcal J\frac{H}{G_\alpha}. +\end{align} +Thus Grad--Shafranov gives +\begin{equation} +\begin{aligned} +\mathcal J' +&= +\mathcal J\frac{\chi''}{\chi'} +-\mathcal J\frac{\partial_\psi B^2}{B^2} +-\partial_\theta(\mathcal J A) +-\mathcal J A\frac{\partial_\theta B^2}{B^2}\\ +&\quad ++\mathcal J\frac{H}{G_\alpha}(q'+\partial_\theta G_s) +-\frac{\mathcal J\mu_0p'}{B^2}. +\end{aligned} +\label{eq:GS_before_beta} +\end{equation} +Finally use +\[ +\frac{\partial_\psi B^2}{B^2} +=2\frac{\chi''}{\chi'}+\frac{\partial_\psi\beta^2}{\beta^2}, +\qquad +\frac{\partial_\theta B^2}{B^2} +=\frac{\partial_\theta G_\alpha}{G_\alpha} +-2\frac{\partial_\theta\mathcal J}{\mathcal J}, +\qquad +H=qg_{\zeta\zeta}+g_{\theta\zeta}. +\] +Equation~\eqref{eq:GS_before_beta} is then exactly \eqref{eq:gsstep}. Hence the +master identity \eqref{eq:master} is an analytic consequence of the +Grad--Shafranov equilibrium, not a numerical closure. + +This closes the residual of Sec.~\ref{app:assembly}: multiplying~\eqref{eq:gsstep} by $\mu_0p'$ +converts the geometric $\theta$-derivative +$-\mu_0p'\partial_\theta(\mathcal J\,g^{\psi\theta}/g^{\psi\psi})$, the +$(\mu_0p')^2/B^2$ term of $|\mathbf V|^2$, and the pressure--curvature part of +$K$ into $\mu_0p'\mathcal J'$, so the bracket of~\eqref{eq:H4_expanded} becomes +$\mu_0p'(\mathcal J'-\mathcal J\chi''/\chi')$. Together with the clean +\(\mu_0p'\mathcal J\chi''/\chi'\) piece already isolated in +\eqref{eq:step3}, this gives +\[ +\mathcal W +=-2\pi f'q'\chi' ++\mu_0p'\left(\mathcal J\frac{\chi''}{\chi'}+\mathcal J'\right), +\] +and the compact $\mathbf H_4$ follows by Fourier projection. + +\subsection{The two equivalent final forms of $\mathbf H_4$} + +\paragraph{Equilibrium-free form.} +Projecting \eqref{eq:WUV}--\eqref{eq:WYV} directly, +\begin{equation} +\boxed{\;\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K\;,} +\end{equation} +\begin{equation} +\begin{aligned} +\mathbf H_{UV}&=2\mu_0p'\tfrac{\chi''}{\chi'}\mathbf J-4\pi f'q'\chi'\,\mathbf I, +&\; +(\mathbf H_V)_{mm'}&=\Big\langle\tfrac{(\mu_0p')^2}{B^2}+\tfrac{\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)}{g^{\psi\psi}}\Big\rangle_{mm'},\\[4pt] +(\mathbf H_K)_{mm'}&=-\Big\langle\mu_0\sigma S_\psi+\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}}+\tfrac{2\mu_0p'\kappa_\psi}{g^{\psi\psi}}\Big\rangle_{mm'}, +&\; +(\mathbf H_{YV})_{mm'}&=-\chi'\!\int_0^1\! e^{2\pi i(m'-m)\theta}\,\partial_\theta\mathcal F\,d\theta, +\end{aligned} +\end{equation} +exact term by term ($=$ Eq.~\eqref{eq:ep_bernsteinK}), with no equilibrium +identity invoked. The compact form +$\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$ +is the same matrix, obtained from \eqref{eq:WUV}--\eqref{eq:WYV} through the +Grad--Shafranov closure \eqref{eq:gsstep} derived above. The global +term-to-matrix assignment and the resulting $\mathbf C,\mathbf E,\mathbf H$ +matrices are collected in the main text. + \bibliographystyle{plain} \bibliography{references} From 3bf3334e7dbfdad3c4011ece9e42648dc8e0118d Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 16 Jun 2026 14:21:45 +0900 Subject: [PATCH 143/305] DOCS - recon: fix H4 appendix A.2 (|V|^2 residual =, block labeling) - A.11: replace the proportionality with an exact equality; expose the 1/(B^2|grad psi|^2) prefactor and that the numerator vanishes by force balance mu0 j x B = mu0 p' grad psi. - A.2 intro: correct which V-products enter H4. Only Y*.V, U*.V, and |V|^2 (with the -mu0 K scalar) form H4; X*.V feeds the C block and Z*.V = mu0 p' xi_psi'^* xi_psi feeds the E block. - Remove redundant "Input Variables and Parameters" table. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 1ce5e3cffaf3079213dcd63e7343ea553bedae1b) --- docs/tex/recon/main.tex | 82 +++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 3a8a79b9b..81f59cb02 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -2711,20 +2711,6 @@ \subsubsection*{C Vector - Covariant components in the gradient basis ($C_i$)} \end{tabular} \end{table} -\subsubsection*{Input Variables and Parameters} -\begin{table}[h!] -\centering -\begin{tabular}{|l|l|} -\hline -\textbf{Variable} & \textbf{Symbol/Definition} \\ -\hline -\texttt{bwp\_fun, bwt\_fun, bwz\_fun} & $\mathcal{J} Q^\psi, \mathcal{J} Q^\theta, \mathcal{J} Q^\zeta$ \\ -\texttt{jacs}$(itheta)$ & $\mathcal{J}(\theta)$ = Jacobian at each $\theta$ \\ -\texttt{metric\%cs\%f}$(0, i, j)$ & $g_{ij}(0)$ = Metric tensor at MODE index 0 \\ -\hline -\end{tabular} -\end{table} - \section{Log-output quantities and their exact integrands} \label{sec:logmap} @@ -3053,8 +3039,17 @@ \subsection{Setup: the $\mathbf V$-sector pieces and the single geometric rule} \qquad \vec C = \mathbf Q+\mathbf V, \end{equation} -so that $|\vec C|^2$ contains $5$ diagonal and $10$ cross products, plus the -scalar Bernstein term $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$. +so that $|\vec C|^2$ expands into $5$ diagonal and $10$ cross products. The scalar +Bernstein term $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$ is a \emph{separate} piece of the energy +$2\mu_0\delta W=\int[\,|\vec C|^2+\mu_0\gamma p|\nabla\cdot\vec\xi|^2-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2\,]$; +the purpose of this appendix is to show that the $\mathbf H_4$ part of the +$\mathbf V$-sector --- the products +$2\,\mathrm{Re}(\mathbf V^{*}\!\cdot\mathbf Y) + +2\,\mathrm{Re}(\mathbf V^{*}\!\cdot\mathbf U)+|\mathbf V|^2$, +together with that scalar --- collapses to the compact block +$\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. +(The other two $\mathbf V$-cross products, $\mathbf V^{*}\!\cdot\mathbf X$ and +$\mathbf V^{*}\!\cdot\mathbf Z$, are \emph{not} part of $\mathbf H_4$; see below.) The field pieces are \begin{equation} \begin{aligned} @@ -3099,14 +3094,17 @@ \subsection{The five diagonal and ten cross products} Every $|\mathbf C|^2$ product is reduced with $\vec v_i\cdot\vec v_j=g_{ij}/\mathcal J^2$ and the cofactor identity -$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$. Only the -$\mathbf V$-touching products -($\mathbf X^*\!\cdot\mathbf V,\ \mathbf Y^*\!\cdot\mathbf V,\ -\mathbf Z^*\!\cdot\mathbf V,\ \mathbf U^*\!\cdot\mathbf V,\ |\mathbf V|^2$) and the -scalar $-\mu_0K$ term enter $\mathbf H_4$; the remaining diagonals and cross -products are listed for completeness and feed the blocks -$\mathbf A,\mathbf B,\mathbf C,\mathbf D,\mathbf E,\mathbf H_{1\text{--}3}$ of the -main text. +$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$. Of the $\mathbf V$-touching +products, only $\mathbf Y^*\!\cdot\mathbf V$, $\mathbf U^*\!\cdot\mathbf V$, and +$|\mathbf V|^2$ --- together with the scalar $-\mu_0K$ term --- enter +$\mathbf H_4$. The other two route elsewhere: +$\mathbf X^*\!\cdot\mathbf V$ (a $\xi_s$--$\xi_\psi$ coupling) feeds the +$\mathbf C$ block and +$\mathbf Z^*\!\cdot\mathbf V=\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi$ (a +$\xi_\psi'$--$\xi_\psi$ coupling) the $\mathbf E$ block; the remaining +$\mathbf Q$-sector diagonals and cross products are listed for completeness and +feed the blocks +$\mathbf A,\mathbf B,\mathbf D,\mathbf H_{1\text{--}3}$ of the main text. \medskip\noindent\emph{Diagonal products.} @@ -3168,25 +3166,29 @@ \subsection{The five diagonal and ten cross products} \end{equation} The second equality for $|\mathbf V|^2$ is \emph{not} a generic metric identity: -it uses the magnetic-surface condition and force balance. Writing the bracket -as -$\mu_0^2[g_{22}(j^\theta)^2+2g_{23}j^\theta j^\zeta+g_{33}(j^\zeta)^2] -=|\mu_0\mathbf j\times\nabla\psi|^2/|\nabla\psi|^2$ and using -$\mathbf B\cdot\nabla\psi=0$ (so $|\mathbf B\times\nabla\psi|^2=B^2|\nabla\psi|^2$ -and $\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$), -\begin{equation} -\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} +it uses force balance. With the Lagrange identity +$|\mathbf j|^2B^2=(\mathbf j\cdot\mathbf B)^2+|\mathbf j\times\mathbf B|^2$ and +$\sigma\equiv\mathbf j\cdot\mathbf B/B^2$ (so the bracket +$\mu_0^2[g_{22}(j^\theta)^2+2g_{23}j^\theta j^\zeta+g_{33}(j^\zeta)^2]=\mu_0^2|\mathbf j|^2$), +the difference between the two forms of $|\mathbf V|^2/|\xi_\psi|^2$ is an exact +equality, not a proportionality: +\begin{equation} +\frac{\mu_0^2|\mathbf j|^2}{|\nabla\psi|^2} -\left[\frac{\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} +\frac{(\mu_0p')^2}{B^2}\right] -\;\propto\; -\chi'^2\,(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2-(\mu_0p')^2 . -\end{equation} -Inserting the DCON currents~\eqref{eq:app_dconcurrents}, the -$2\pi f'q/\mathcal J$ pieces cancel and -$\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, so the residual vanishes -identically: the parallel ($f'$) current supplies the +=\frac{\mu_0^2|\mathbf j\times\mathbf B|^2-(\mu_0p')^2|\nabla\psi|^2} + {B^2|\nabla\psi|^2} +=0 , +\end{equation} +the numerator vanishing identically by force balance +$\mu_0\mathbf j\times\mathbf B=\mu_0\nabla p=\mu_0 p'\nabla\psi$. Equivalently, in +the DCON currents~\eqref{eq:app_dconcurrents} the $2\pi f'q/\mathcal J$ pieces +cancel and $\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, whence +$\mu_0^2|\mathbf j\times\mathbf B|^2 +=\chi'^2(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2|\nabla\psi|^2=(\mu_0p')^2|\nabla\psi|^2$: +the parallel ($f'$) current supplies the $\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)$ term and the in-surface pressure -current supplies $|\nabla\psi|^2(\mu_0p')^2/B^2$. +current supplies the $(\mu_0p')^2/B^2$ term. \medskip\noindent\emph{Cross products.} From 029dad9349feab653873fe276e8a3ea98f1d088d Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 16 Jun 2026 14:38:42 +0900 Subject: [PATCH 144/305] DOCS - recon: frame V as the rotated edge current + triple-product rule Appendix A.1 presented V only through its components A_2,A_3, which read backwards (the components are the *result* of expanding the physical field). Fix the framing: - State up front that V is the edge current rotated into the surface, V = (xi_psi/|grad psi|^2) mu0 j x grad psi -- a concrete (psi,theta)-grid field -- and that A_2,A_3 are just its Jacobian-weighted contravariant components. - Add the scalar-triple-product rule (A.4): W . V = xi_psi (a mu0 j^zeta - b mu0 j^theta) for W = a v_2 + b v_3, i.e. the cofactor identity in coordinate-free form. - Replace the repeated per-product cofactor derivations for X*.V, Z*.V, U*.V with one-line references to (A.4); drop the now-redundant cofactor intermediate equation. No change to results; presentation only. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 69c6edc11caba637f6e88f38489c3a2bdc2d71da) --- docs/tex/recon/main.tex | 47 ++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 81f59cb02..7d623764f 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -3068,6 +3068,30 @@ \subsection{Setup: the $\mathbf V$-sector pieces and the single geometric rule} \qquad \mathcal A_3 \equiv \mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23}. \end{equation} + +Physically $\mathbf V$ is simply the edge current rotated into the flux surface, +$\mathbf V=\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi$ --- a +concrete field on the $(\psi,\theta)$ grid --- and the components +$\mathcal A_2=\mathcal J(\mu_0\mathbf j\times\nabla\psi)^{\theta}$, +$\mathcal A_3=-\mathcal J(\mu_0\mathbf j\times\nabla\psi)^{\zeta}$ are just its +Jacobian-weighted contravariant entries (the expansion is in the main text). +Because $\mathbf V$ is a cross product, every \emph{in-surface} product with it +collapses in one line by a scalar triple product: for any surface vector +$\mathbf W=a\vec v_2+b\vec v_3$, +\begin{equation} +\mathbf W\cdot\mathbf V +=\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\,\nabla\psi\cdot(\mathbf W\times\mathbf j) +=\xi_\psi\,(a\,\mu_0 j^\zeta-b\,\mu_0 j^\theta), +\label{eq:app_Vrule} +\end{equation} +since $\mathbf W\times\mathbf j=(a\,j^\zeta-b\,j^\theta)\nabla\psi$ for two +in-surface vectors. This is the cofactor identity +$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$ in coordinate-free form, and is +the only tool needed for $\mathbf X^*\!\cdot\mathbf V$, $\mathbf Z^*\!\cdot\mathbf V$, +and $\mathbf U^*\!\cdot\mathbf V$ below; likewise +$|\mathbf V|^2=|\xi_\psi|^2\mu_0^2|\mathbf j|^2/|\nabla\psi|^2$ follows from +$|\mu_0\mathbf j\times\nabla\psi|^2=\mu_0^2|\mathbf j|^2|\nabla\psi|^2$ +($\mathbf j\cdot\nabla\psi=0$). All dot products are reduced with the single geometric rule \begin{equation} \vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal J^2}, @@ -3241,17 +3265,9 @@ \subsection{The five diagonal and ten cross products} \end{aligned} \end{equation} -The collapse of $\mathbf X^*\!\cdot\mathbf V$ to a two-component current form uses -the cofactor identity: with $\mathcal A_2,\mathcal A_3$ as above, -\begin{equation} -n(\mathcal A_2 g_{22}-\mathcal A_3 g_{23})+m(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) -=(g_{22}g_{33}-g_{23}^2)\,(n\,\mu_0 j^\zeta-m\,\mu_0 j^\theta), -\end{equation} -since the $\mu_0 j^\theta$ terms drop from the $n$-bracket and the -$\mu_0 j^\zeta$ terms from the $m$-bracket; dividing by -$|\nabla\psi|^2\mathcal J^2$ and using -$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$ gives -$-(m\,\mu_0 j^\theta-n\,\mu_0 j^\zeta)$. +Here $\mathbf W=n\vec v_2+m\vec v_3$, so the triple-product +rule~\eqref{eq:app_Vrule} gives the coefficient +$n\,\mu_0 j^\zeta-m\,\mu_0 j^\theta=-(m\,\mu_0 j^\theta-n\,\mu_0 j^\zeta)$ directly. \begin{equation} \mathbf Y^*\!\cdot\mathbf Z @@ -3287,11 +3303,11 @@ \subsection{The five diagonal and ten cross products} \mathbf Z^*\!\cdot\mathbf V = \mu_0p'\,\xi_\psi^{\prime *}\xi_\psi -\qquad(\text{pointwise, after } g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2), +\qquad(\text{pointwise}), \end{equation} -For $\mathbf Z^*\!\cdot\mathbf V$ the same cofactor cancellation gives, before -inserting currents, +For $\mathbf Z^*\!\cdot\mathbf V$ the rule~\eqref{eq:app_Vrule} with +$\mathbf W=\vec v_2+q\vec v_3$ gives, before inserting currents, $\mathbf Z^*\!\cdot\mathbf V=\chi'\,\xi_\psi^{\prime *}\xi_\psi\, (q\,\mu_0 j^\theta-\mu_0 j^\zeta)$; inserting~\eqref{eq:app_dconcurrents}, $q\,\mu_0 j^\theta-\mu_0 j^\zeta @@ -3309,7 +3325,8 @@ \subsection{The five diagonal and ten cross products} \qquad(\text{pointwise}), \end{equation} -For $\mathbf U^*\!\cdot\mathbf V$ the cofactor cancellation leaves the pointwise +For $\mathbf U^*\!\cdot\mathbf V$ the rule~\eqref{eq:app_Vrule} with +$\mathbf W=\chi''\vec v_2+(q\chi')'\vec v_3$ leaves the pointwise coefficient $|\xi_\psi|^2[-\chi''\mu_0 j^\zeta+(q\chi')'\mu_0 j^\theta]$; inserting~\eqref{eq:app_dconcurrents} and expanding $(q\chi')'=q'\chi'+q\chi''$, the $\pm2\pi f'q\chi''/\mathcal J$ cross terms cancel, leaving From 42f2ddbb86e39d1575c87f1b35bceee3e1876c21 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 17 Jun 2026 12:32:46 +0900 Subject: [PATCH 145/305] GPEC - recon: covariant bare-Q (qvmn) output + fix QvsC figure gpout_recon: dump gpec_recon_qvmn_sol*.out = covariant Q_i (bare delta-B = bv*_mn, captured right after gpeq_epf before gpeq_K/gpeq_dst). Effective field is cvmn = C_i = Q_i + V_i; cwmn is the contravariant J*C^i. The quantitative QvsC figure built its (curl Q).grad(psi) panel from cwmn (contravariant) fed into the covariant curl operator -> spurious RMS 3.8e-2. Rebuilt from qvmn (covariant Q_i): RMS 2.1e-3 (Q pierces) vs 2.1e-12 (C tangent), matching the C-verify diagnostic; both panels now use covariant components as the caption states. Q-vs-C table + caption updated; figure swapped C_eulerian_lagrangian_n1.png -> QvsC_quant_n1.png (+ gitignore exception). Co-Authored-By: Claude Opus 4.8 (cherry picked from commit fc716a45261db669adb8234fbdc6754c0d485829) --- docs/tex/recon/C_eulerian_lagrangian_n1.png | Bin 337616 -> 0 bytes docs/tex/recon/QvsC_quant_n1.png | Bin 0 -> 1105847 bytes docs/tex/recon/main.tex | 70 +++++++++++++------- gpec/gpout.f | 27 +++++++- 4 files changed, 73 insertions(+), 24 deletions(-) delete mode 100644 docs/tex/recon/C_eulerian_lagrangian_n1.png create mode 100644 docs/tex/recon/QvsC_quant_n1.png diff --git a/docs/tex/recon/C_eulerian_lagrangian_n1.png b/docs/tex/recon/C_eulerian_lagrangian_n1.png deleted file mode 100644 index df7d728e6ddab40a9a5d79f897430a010cd340ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 337616 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

f-Kp3%>4xO4F39PJ}xLO#bY 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

XJUA7DOzjyv&P_u^-PIJqbZlcY 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 z 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 z z6pDQm;k^O!YxFTkzu{@O+bwgrXJG1Ge7aLJv8a-pEmAmretw9r)cT!DSN+6j`Paef zyV8L0i25`jKQ9CW76Ccv)|#eGKXLx(^YPnY#!0Oh40r!T-wO}jrj!3B>b0;u}wyMa`2<{WYX z-ugno!ro(lxD`@pS5!H3ww(jq8m*68An}O{f`vK!J9(0nPQl0+3C8Qn{FE6)y1NqD z>wV13UTgw0Bl>0wbr9Yx%C-N3(~9F=5#+3ktgicQv)N zLIVS_YfIo2UI$5#>~z1IoJ!_aj?nqX0U}VT{CBCR0qLsUy=VUx@=W0@wET|mIu{qs zN+JHVGsr@-PtPs}`sL!I2ir?guHWWYr$d<)<>dUgt_Pw3;5hW9A(JjK=CvTR-;Qwo z)-UY?V`Jktz)R*%hQxsKPgo7|pKZFyQ=YD8TraVKL|xXWghfn3V#H81^3Z#=AJ*1K z{=tL9#)vV<^ARX(Awh@1-)JnHiVgTZE9cdMVtzdAlWo#}aCfZSmKi&+EIplSASRWJ89h(lv+Gb(4_a$|HK8_?*6l z^&-Ec+m5P+hAih{@<|5`u?o)W{;S-e_H8TFS~l`_fX2E3kRLZ6^2CWTuuSXY^D)SnG*GMp=iF#sRIf|3U*gd$cZ>n9DZ5wC)+En%PD z-}R^go`qm4--c}~tg4&*Le2&`+^j-E_mC#^whUr zH7MrFIaIL&Mltja)VlqB@bVnMFSlJB{L={#v#v+lg{a>$#6+1B5z7|9J9?X+AD8X~ zF+}WNtQ&2|W`*1<83gT%40zGF&x7*j()aV1Mk_1DYGYO3$OBA*J_l=As_WwNX?r|C z?Fg0UXW_f?UK8uWnc8pNq!OY@91p2H`-|{E6F??9qZ+mvR`9Hzb_#8$ZhsTeOLP+i&}N18Tut8exWBLn-P) zR@~I}2qoaYZWv^5Yg_nV82YDqniz&UX!_SL)5E7Pq^ACccJfQj=omKH)T;gXe%iaq zRgAhKBko74?yTICP0R7+7}S$qZd2_SIPSB?oisByZJ=n#qg>0SD!!ABco6l zK%vnBh-ze3_7n8rB>LlvDszl1S>?_r&;hlXK?#-ZRR&F&1)3$dTHf01P*H&uEJ*}a ztNrgTy##kD;e)w!O7G`!M$?2dubnA354ZZ%maUOYhaP_%~rmM3`) zf+x9ndKF2d%U9sR9+0FmCO*5p5qx-=ck}RE_oYnv5N~>gYawQhq}8UI{s{(dDr!nf z0@~MK-4z!yhCqU1s+NGQw+x%|v!wkXr{UxY?8SScHCnH*)^JDiqwlx#+sTcg08ElNI$WnNGQbMft|KXHPxh%?+X|qPh!$pmc5l z44yHC1%4=8_TlE49*U09_4TsgH!R3ysvylI>&pnXF^TC;Ad{yjc}EDOD}5Z-#h%}uSUSq7VxyH!Q}*(|{B;T>Lo5H~Rz8a4kbyH~z>7V=BgvLQa#L}XBs7l^qU(qw zzF96n)OJXM$_ApVMXax&DhMLrS(s8;Auq^;8zP8g8=ze!RaKF6NHYv335AE0_!sF= zdEO{@+Kb$r?=!@mS`0QX30;HMzr`q1I3x=J<8B{0VD4 ziP*l&ki+%sfsKr=XCPjiUm0X_hkh)S1|bO*$7Q*rwc!PskLa(?f&sE3!X+q=c3<6IKa|Q@K$~F_{%o|2J6~rLdGhCC-k-rSD41RM*1Ub4WhxR%UUXHhTHW z`B9Fv(CS+gPYqlylbwqJIeU;ePvY6!n=!8TNe~p}e0^o0G}&qat%U#>@-(-nPP#15 znyCKCXh@;_%dSLrgB)xFZkm~K-shommS6(g%c-?BJ28(|9=U#Hez6D@3g{HjYtA5< zs9yUoMK{W|Y(`w{e39kTSEz$yvnDr8Ba1))Z{Hm_-Bsl8uYb6JfJ*NH!~tJ4_BkL_&otsQ z_cL2tVSlvokLh)2*GP7o^wGK^6YqMmuRba8WRd;p@<@Q?Q`#>&I0nnHd|w7qCsAeRZ$h31!9znsqvt9Ylx8a{ zD=m^j5Osb&;|^D>MjN>PG!0P+4Dgh?g-Br(98=2L*l*Jf6V2;Vgfa_{CBJN~+LCtZvj=m5Zoe)jAc(vTf2 zGVsgq3j$|Ha2($LgK(&fj98QS4JO_*N)CN;jY3>D0oyzH>M=VAZ-gBK3QX1ZAl<&4 zS%HL-^{4HS05n%j`dZNv5#vty(5Um+)VtrKuwP{?_K{p~=W1~3#v+wM>AEq+rW4)C zB;wU4lG-9$Zz5~}N~feNN?cs-eOz=9w7u>gZ)Tlj14Y)cYw~zUbnoCmpONf_VRLSS zq*FlYoj$I3z30!LPXQ$GJl*)Cz_1eWp?z%MN{CWe!2Kp)Y_tM{1_)?DfK?P_-MLNK|7|In^-)_T1F^ko81w8D`(r*Nfw#m;@V0Ya^Gs$;V3G% zT1BKM;BSi)z99{|BYZ3_rk3=yERogz7zf32@nXB7s;g_c{3gVd-PZ$_CD6bM+F0m& zBhR^{nZzF6uuF=SX#Snw^ZRYNFZPmV%G8p6*kaqYTj?MSDoMzIJ_h+iAdM*~pa3u; zV`h#49~uPpfJe|Sx26MDbX;t#KQ1A)A^1-Wmzv(znl2WKFc`4B%Bh+jHt=4{7<>aE z=D_EbR8($QTJrSv^&!riu*nMdGxVY#@Fad8TIP*h{TbyBCqR!WL6R`I0-P4DM+(8 zkuz^Tbv@=1hSuO8(W|3!r9&DPDpST)>3O z&(HVVSoIURsjQ?Vr>aT<8+@wRDs~A_hV8)*8ZVh{JWzB$;32>2iG_3^`v`TNh}XYR zkbLWU+Ik-C=^6zoNV5I<)HQ@{9`Do=%@hGHp4&5W^0>vTBy#3?VPfVC*-uKGlAb3e zne8~ug2mPr{yRtiTmxFwEn@WRF}wO%l6HhLIsWG18;uQ?ZtuEj$cgpFGN_j zRkJ6QLPdg^?J;k_9rSUerzu1hPUA+wTtUY}Q>3Ng3mhN+l7d8YnX~Q(KgIv;sVhSl zD$CoTX2}wM{9E^F#9UO%2oArsT`CHC%_t(eD>#s6q+GS4Mux$RxeV!ntEHn+IfRYN z;o;#wM&6xBae+xt5FYVoG4+g52mbHF(l1uMulqB|;7Qdm#$J5*bR5i+ba}msrz^Jg@t&zyT&+|dAni_Un={vMt_vKb_(~2kUu3qAe1Y#3% zuA#wR-9mBh9B@dGA`?78weO2v19@?y38~wOS$ey+#TZW8A^r>Rix_JMHvK(TLc1Zw z+y{mB_rsZz_|WHlhf5Gk=+OOzH(ABTtt9Q@Fue2&U=0!^3gp9k?6LK&d$W^uB;YOV zlmu2;Au!c^Uq6}8#0Ep)CZ<1kteLxBs0kI6ciM!{FU@b0EFK-`1q2SZou)BhugRkc;N!h z^Xdqic))L6F^D!8BSLa$_BQ|LyI7_l&}AITH$nrP>Xo|b9GJ+&2gUb69I3*-%0ouJ z?bj{qw?Qmwb=E8K76%gh;^IJ+5ezZ${aIJOfrrCZDkYBQzLzvVG%OC#RNQ+ALrT6b zfvGABIE=otI_L7g#M71Skp-HYm-ne+Za^r$Y}PMl2B7!vX*_WaEwB+{PEIXLj zpLS|t>Fxc~e4Hx*jTI+n^KzrIkjdf$3L8&Mlz&l(n*HWgiH~vKdxzOYu}W6gRXmscRyXEzOAMn`}v1~s6rvq?~25vB)m^LkU)SiU|@fo&>x&Wjf*~ReRxzs0@c5so@YT% zS;Iq!k!;P*yC_z#_>Nx(kFd70do0xN8kt1cKiO5ukk3Q11F0tIHL_xemhZbnHKz^-v=qxbI2>aEd24sq*Utp` z!jS7pe5!>FjE4$ijeqfOWAbtGONZayk=`@j3DKsfoYu2 z%roOIPi_DC)8q&FV|369DZ}v$e^p~KL4aaj0*7=u1*hi`^7Qu}XTgV27)_oX(a$ApP{(XwN|skgbX zYk3}dcY)S)oLgD(=6zyH#|bbRX2qA-zgoO>(%DVOgw5;A5L@EC5I=HkJyb#8w(;x` zaNiA+3ecytD&*q8Te!DJkr+0n2~zfg=_k74WT?)^XtOH+5oDf0PCtADh5W=zlF?BngkE|o>Mdi3#; z-9$PB69=FVOn@tuvkDd8$@0a?sHtZ* z92ewFMG&i!^tPf_*}Art@R+XYsjAO~RTG&t6~I#=x$B6tw|Z@JvnJ3#tsbW)h%t*I zHL0w?*jyO(DhxbS?53<@M#5+E|SSCpW{G!MN@X33bRQP)oZSIc@i~?dN$Kx z)aV31wx}675tI{S}m!gtv-KMLhmw0Zdp_BnVE`!fLx|n4lFqs*jcx~^N9WXXLOn0 z3Hf0&9zzDr@??Re(}-*ozsTP+4b2_Lr%2Gxd+HyYb@k?S;duK}WIsvDk&qx1<*LFL$*of-{o+OBU@|tmGnR>r&_fh@MZDz+%n0{nC8H*23cL zfE6GZ^GuH(eXxoyDf|ld1?84enM~c*7ri-4AAoO@i4^e2!D6B#zYY6}OvvhdvY6zW zF+p5kHebHKZjVNTlV^kd{exiE4<+BKJot18YW7&C8e@KdzVr^|4XJ~9#5*;9Gt+11 zA*dIz(lYT%eXqzo>B|}6O_d5d!2q6^DgZwut zMr5xc+s5l2NqqGCO(2hL>z&t65$z?-GB1~B<0fi)RZ4q!VH|C`&@1`TqYQC+TrPM; zvbT(vJzcY3r3DQzovFR_F>O~@5f~yA4t*fw&J@8GD_m9IsE|#bnqM2F%W>U(gt!qj zuD2Q7ga|7jr6BO@V`Hu8FS}<8E;k>*Kpz}*AxlR!AP%fXU(dcDoZp{Ye)#3itdg>F zZ#^xv%(uc=?PrDNCu+}`Pt-81F;P0y@9;?_{to@#y7W0b085V2WSB~JylVe@D!S&+ z*=zytHb6=Xf58;#hQR{Gvx?PpqPC1kDl|1E*>Efx`Tr61)lpS#?biqC2I&?MkPfA! z1w~p=xfO(ay5?WkuUf$Y+7oymF#DswfGYsR-d36~7sNrk0&pC5`9D=O z_5+D7W2Zv?26~VWq2pj*;kVaFh&mqV!OELSaO?9xY1vq+uB~l($^B7D2}ZmftrrUO zh0|N^2v8}>QYk728nwCk`Cu|D2L1zD(4dfqm9fCjwGk~L#EnZA$72Xgl64F4-lnly zb!6x=m)Bl%esX=6BxIu6A>PpA?bT@JgOSrhQE3aB z>yV0GPKe(kT#oNzGvcqSjB47dn%$%J2?LAQN{HBpqG3adH?9yc6Umdr^oQ?(7$(f{ z0*Vd|jU;Mld?dIh5WOYh->NOX^XI3QI({&*(_dB+<*ULPPAS**08RrcAUFuUo9jJ5 z6z*GQX=^2H2~Ut+aCc-!!lT*>eWF$vGhOeFj+*YLuhb=k`WR~%SUl_jHCqsF+z1b6 za_pfRB?srE7jX&3SLl;|hzZt>6`=Ko+0p>gAQ&L=1Kn|iJS9|6R{|IQ+WNZGkUCJ1Wuqc|(&SAC7)A_N*a%kF zXh$c`vN2X{wZ$ato|!?}KUGd+8x`H)udWw;kOG|>2_Is+XF>j2d))*NIEvrvdt92{ zVpfekSrC(#3%d!NaH1Ho-znP+Ck-MyJRH~1$+!!LW&4UpxuqlbjZH)eI9@Nb7flqI zv8jM=lGHv0)RurebN^V0%e4SOg|y-G%mGCADoCPz&?~Te1v9{~lZj+nP%at=7B^Vx zvl>ES4ywg!`nFa;hWo9C2C34=kC>ss0*&yCE~^nqBHqHFd5i#MDe#}H9=@9{JOneW zj&^_K^BzIZ_#<=*ga8FdMP((>MT(P&qtG^^w1cBHxGP(>2{>m!f8s>w(+0k*iun2g2_TXO*w*1-vlL?~7hv!Gv5u%#@%LoDNHUo*GAHw}B zVBn-d=d!d%RWkx<$(Qr%c0@9t6hR>MfMShIGb8ZixYAc*wlN=+^0?Yh`@d^k#zEj9 z5Ge_UnJwjw_o67b2) zROSa0p2g>(G?ZRf`c*a3&gq_e%RxA;hf?36Q|~`P5}ME@! z?-hw4@RGV67=3?biQr}xG{d43pMh2&94vf_fo!;3x$1Y{HhKAk_tT+L_|9paqDPS;Ha!Z1bsAg1RvxRp2P5mLI@_GJG(cmx`}~FQgHc^N zB}DJ9ecO>a`L}d;fi&$PAo(L|8QZhLZS(}k=2EpSa?>}Q%14EyOdM5^3olr{Mi&$i z5^SMB_FBPzSkEGQn46TDzyMMm&`1?6^Ye#!CmvPW{!--!eSpz#=_69kqv zH4P1Ey&kSjK#3v?M3R6sweOoONdK_IUPYcMXX`KJBHt`kK4Enj!`z5V+C=xU!FD37v5EQis%u$qCuR(%U_#>*}#!30WYFz%@& zmFL%yVuz(sI?=G1yCvBBf&y(utlJ1|4I=7t;&`Un*24?ZTFxR5z$3}2H3>d z!Tf-5&uM{JaH+u2|0`X}YC3JDr9&5vPlrXU$FIj)K?j?a)qe&b8uOE$!C2f=dqq@N z(0vx?t4noqrbqk1*X^kU2mXipt_u`vj}Da4(MqzL+VN7QM;aUds<+(f2p77%&X?ZA^xXVt9{tRu$b9cTyVBq1D7y& zxN>=m1qV{zIw`nc5<~dpxTw~DuRJRwvD!y6t^3X+f{$yr*ImtjWp#r^X7Ukj|JA*tF5AH88*D5W5VfaLd$5GYFtcR1*A1N$I-lM4^+$BG37IW&Z9{ z@O^wF?COT2@#A_$`?L_$5rU{`t7#*iZ zep>b~>Habr?4-#%rC@vfy;@o1svTL>fW~Gu!l{uG098fHLfn(I2-rQMoOrGsIR0u8 zIH4OUTvu9tO~2}2>t;Om67m1A`civ)WA_CJ_kIlwM7p}Yo%Qqt_rVg}Ca@hpP8J(R zI!ChzaaB&ZMx^P5lUYDCigd3JID_qN0pkvW85AS)AaVGB>sZ+jH>X}Nz|XF|=+sHv+KZ(Kkt>=6jT)T_cn$F#-5m%^@zk73r^QGSsJ?*@s zfuV1d2$IFsC17UXumMINAc%?OL@!_+f*DX-SvBQzVE{DUm0 zl>g(smXH7DPVsANHFd%!ZdN`*?g^tr<7TXO;J*O3l=-;la&5BnsR#3U#NRt*x!;0SHO{cc_-w)|uwfUUa}!!3y14f4In9MO7Kca2mW`NA^}f6WAKDGk+#wgzDo^CTp-W$t{qObd>8us73oq|5HQa3ZEyCT z1}7c-Yq{wO4r3`737e@ZO;#S^5tf~Sl0zW|#zn_zEyFUE?%MYs{lCy{cw*2sAQS>c zvMH}Z*x`WE8WE^M?r$%30Yb-OG4J#B0&hd%0u#~d772x;75(}2aB|1%Chrfv^aA+s z@SYFtyFH;4-d;OQeFSmJDmT*SB7vK!5)108q(( z4I8*iJc{_httKI?(JF+7&3ZnZb6*m3!;E^8>cf?j0}_^uokunTfyl=! zDU;S=RJY4na8!9P&K$WxyB{JTwfVjXO!UxFk(C`2Pyuo9k;FY?wDQ*! z3Bbh4Y?i@_EHGt)-ObcVb%6GQFd7QRDLR{<2NoI5b;V<1{(Ml@d$qAn6#jDBbx1B) zt9R{oIST2n@*XvOoTA2+ggqb{t6g88qPuPH>3fMWc0fn-eq(7_a{T?7xY`Q7)@6b7 zv35I7)0HJ9OAX8^X%y~eZ9MxrTk6SK&n`u3kvT8WS-qy|;N9?8J z{m1Sa)9WgNJT{Qi&W zSaV05I4}PuZ#Pu`yy3Z94I}rsGiP+j_MDt(Tm&3Vwt!LxoM_D*^(IZ_&u+KeAKjeV z6M|jg5^Y=MkMtG|&ztw+8sm z)lN|r>Q zGJ|&SrOnf?+9?L9*Sm8vJF}Bkwn7r-;k8`zwz1mNoeq&Q+_A217g>iuG!EWU3Pw9c zl#b|?4j}$cKU@en2}_6jzTOLQ!J8ACD-vGfr4upE%qYV48yFkEli>sWTFJm|tputD zcVLNGyBBW;OYhw*R+YG~!}O=Un89A^X@@G*g(G~;qq+@UB87+jM;~hR^vtT%8E=&c zUN0sI&#;L*BQhXt%>h&z41m9ios3YJvpeCXMs`Dk#^F>lkVXr00XP^i3pB6?qY`pq ziz&yKtgM4O9u1B7^D%{o(tzF|p0^!I_cmTm{&%2O=p1~Kod~22&r+@Mn#|a=o4`xDBjf;t==yj_s@V7>iHh*5Wolwj^sGeKFjD-ZGOGpZF|VVVk_aAjiJ5*+ML2; z)20ms^|EbqoYXG&56dol4ei0G=M`H`Z%uf_vnq>YOBa)4+YF=N>^Gmdx^5_3t>G^+bt zJzD~sk4yj=*dxb`PB4#p77W9>*}@+E@S-)P-@gZd;~tYyb>6eOO@7!EP|}QWn*~W; zL7}$pW(M)y*u;Oa0l7o6iw^I3SCp9Gm6)Z>9aCtY#BgzICR@7Ecl>a+Wu|Y8Fp7u zLdlu$@2M~T!5`-ty#!ort6YB2sNBM;k=GY5W?bn_BHr{1dQZc@s z^L``?jzZ2?sC{@1D<0*aeWiaU&EhA`aVM+EjF3}af#8++jD3d;Xc4NpdJ5{?b5b?R z7g=hEBi++vg9kNO`xWg36Y9`X1lG9nX?!7WoH+ZWh!Y{sZ}v@Z-rg`*i8y(@+!xc^ z$dT_KX%2yjaV0J#(ncX;nw3X z;xyOxum4$w8+=@T9kHx(l<&g4MuZK6Q^wc}$!3rHiQH|KcnMUCuSjT1J*HsOK^Af% zG!k8DJEr7kdO#0Dr{0-ZMTaV=4#nD^|E2vUO|JU>lth)r(sGcmmeT}aN@NQ&J*SK| zi0-p7b*#jT&1(52&bFigB&|AtK^X%XuFwbX%Q&!lGB%df5(SWqdsQbX3Z2ITe5_jC zK*N+izJTKo==ov3$h&qNp9B$Q>nPODSlVvQ<-w3AK^xb9(@Lh}>IxF1-8(6h-+Bu~ zr0elv)sI!ttx89Y1Bx&JC?_w6n2>ZpHUSyC7${Ks0ACWa{kz%vA(#Q+$B+I5%qnWt zQ0cr8=16&Y`9P(@Dl^QTMFcTX*`(6GI`64xX@Cg`R1OD{Z;&BG*7T^5gNc?9GEW|L zhlZL6>|daI%;_Oyt)VRK0Kh`~e5uLGq@eCs3`!HQqesHwU2KUxRCsCR=L39YZTF8< zf?v;f`@C40`JI^-D`Cl6=Hc4=_z&?i_cOo^Fo<1_apV7PKgaWTjdYC*4yK^EOCOY) zITr!JUF{)uf(KfKUz?R^!OMlp&WE164;=k!G&$;$WLkSAnT432x#?K@p%Jd**CO0x zOTWRAjGj@k$b;)gng`EOy|4ZIi>yiiaBVUeu5Fo@o4Z8-&CknIcl5fv=3Q{f>E4`H z^1yJw=a|*Nm{YGZ-)RDrpJI*b{`@8YiIqCvZHhcj=R5xJCQHEytD&#{o20w-3}Ao! zywrBA`ciFWMQ4Es5ZM_(&bO zJ}!=5qHBKg?HR)D>BdFg#l$cZN<~<0!MnJ0F@t!##*2*&CkHeuYv%=E0|p3(%gZ6} ztw-7U6KS6Q;GP#tnhSb{Qs?f4Za#JHnzv)B`AaBcN1ixpTWk#mtv+@q^Deh@*}%e5s=0vfDY@iMp=at&UZ@ zx1_u<8PC?9ZSC&8m9S4^1*ib+GLc+?x+)S5UJ|A*Il2h}&Sw_y!rWXq*u>i?a-8MQEypj&sfV2CVwyujlSFgj6wJHDn9h)oq4*#9K<*gjw!mUl;% zNJMj^EtiI^x;!g8zh&GIZB_L&CXx^GeTAvB@BzrDf+79I$|rsJi94mQ?m8kN-X$OP zZ~I7J)h2tvb&)QMp@oN?B1ckI54af#MW}QMv=X;)I0?G3Q0m-qWn|o=Tm)b{>b%MH zMDn;PJcR-^YX`aj-n~ZtTad-4Wgy zpMlxvtj%1N^5NDugS{{V!103kfYs_n3q^C#9mC-2?>XP6Vj`P?e<=@fW$qCVD=Q`# zYa{+q{A6L;Ucsq^2$ZN`-n4mn`T$IMub^o>qR(^Gp3y`AX|wWjt|qX zZjNu%eu0{P>8&53?~=TFCKzl@(MTZ%VY{V08uUcUT0~0gq8f-?`Ln5DFfv&5?C?UR zp{5xT#}Vd8%n4+#OpL*#UgasU)>$8%a+HpkFEIO{X!3CCTS6F#9?pzAW5f@SugcS3^gDbS}8+B*0@jdKP8{ z2-dWLTXBNX&D(<5ELw| z3Bc9%4eSbL_}B(jbbcO4|a##ut>vWEMNq1P71R-&o!!%6XZ@%Kuo+D$AZ zusI68Y>e;X~%t8CsNAhc^yx!Lgm@;QJS4Mk zhvWWzDI;o%;u2pYSLT2f?{fwQL%=BmueS$;A5dz3S1N2k^Ce=_3I?z%z!EjIFhIr| zv;rVck-rs~UQVCBR=YxSZ(Jnu-)}mSUS{QTZ+|{DHkasj3)~93w(P^ z-4@jPwSl!BnPXMI(^jml;u^Rs^nHl-gxoGX!p~^#z(Hov5!+c=S+AD-vD1>lh)-SL zB2B;drDSp(e()d2a3t77bXh0(KHi>gA~j6ZD4pfIEl$8J&*P(~P!0Ut025El(lXT;Sts(m5=#7UPmfy~hIq;^?Eg;DQ2kPo@Ze zP^#|hNRj5m0A3zKgVVoEYYE)q?#oVdT@rW#FF&;MSQ_y?yxw@#)Mf0S#LE^QpV@)p z3J3h%&Q=QK)8Du^uIh{dyySMuQ#ztRWU!|vTzZV#oWhMYK&vh7FEtoMpZnENtcwVT z9j@~NX(}2OwW?1Xux+$|86%i&izQG_L7^9ucg_cZ8G0b#y_c2kf;9`^!vb*(%&A~> zu$^5m3zNjqPRC{ClLcR4XDNgE2QABnXC_KdsdVa^ zk89z)h-z|9j*#+M;392@Yv9da z;oT(-kK&9Vp7wz+YV>BumwyzFGc})be%NNLj~f|23%di=%H?qZH?AT!sCCpTjR*7O zS7X3nx9lf6SAFSxf%!Mp1-}`3%pErm?y4_;&tDi!4K}wBHYVfXFSM;Q``jq$?NIqx z%?3U`T(oHESW9~thwPYS6{oDX`(E|PQl1|C1@8xG1RRd{}WB6#aoOcrglqtCoS zAWwitpeo1Up$FXSiYIH$SU5c<|l~M z?ptns88;aXN@+6>LQVs!sc99Wmv}2)rzkiSKB|Xgk;6`LE4|_b92c#`bRY%c*1!7a z1Cy_0mc+@Thtt3oi z1CZ$*DJBz(zO@{%yV>h_o-3OefhwBkS)&-^08p z6!|xgA!10Ij}6~08A85&{G$;H&3TbT&LmWTC21!|sINEk!~m>{B5{B}dU%ZaqgQ;Eu9zdPRHD_3Vj z>m@nMD2@=_JH{4irF4Q#W~s!=rph&gbi_D(zK7Ss6gnYbGW{G1WVf}v@6Y8G;sKrV zB_kuw@LU>-h;?rh0zSn`Ky-PJZd-wj!sUSOafVA&UtQ^xkLEib$lpE)ggpS`k|nt3 zv=&=T_J_XY@-rCFRsZVWg0o_&tTb9jakO-=E(0gdlg7$ugnF&WAWFCQ*0Qq;+0IR# zw@zt%wjIeZVUBowN-n{N*><&8?H-f3*p>p6^T(w4ujMAyTnq^ws*iQ+c9(jO{?s@} z3?S$|)P0NehiyUKuHk*p&7HiCwcXdCSXeZ>IjqfVd1_MIiY}t7%YrV$%Owthx{_QS zxG(iFz@__#`3eX<3YHlL*+T(?OTr3lqTfCAtGO_fKPFizJq1RA8M?$A$MP6YPmi=_ zsc@+iI%LLLO#<)clQNe=G_C3pYv=6HLD>&ipO&5n!f#(NvA3YR`BT69ecj*nHXbiB z|DG%Vq?qz*fa9-NrZ1R#CKF)ySK9x0nbXVVIzzwCO9R1(=;0K}WgzQYWnB0}X7epp zRJP76-^2X;*_)>tD1Zaxj*OW1;)jp+2e3V)SZ-5{9EIuGj>hDa$EeKcH{yQCdZzPq zWcHxRyEvP3dj9L4)y#c88{4nnFI$`Wkr}}ff(S0-@3Y1_5a-sWgLMc{G0*le4dh-% zrvarCJ^)n8n*+kjS{@*J*p;vt>L&(XN$tRX|1tXVk67kH5*vmj6U;AeQ=30b+nIhZVu%-EpnyiEMrxDBD5VFs? z(M?NlTh3kE^rhnVpYxu5!D^qz);&7qv{$V?X>wJyWJ*MRCfGIk5x9GF$&BH^2u!Yo zglnCiLQF0eo+4Cu5k-M+7M_f+!KzmM?LlMu1W+~S)oO6)lL-Onq(-tVz188&2VtnvD;Psiez+hv_Xv>Z-54Xl<%y4)ZsV{>jzlb=Ke-B(O0u0N z-!UIpT8LQuvokKad}caTk2f+}$FkNr;OAWC;#_%U-QY5@8 zx6B@wyY%HZ8CLW3{9RQ_c98D_v=emCNI^XuTo=%1w2~zp?_$v}tTbW?W&ec;K8d*> zs-=_wkTV97hgjx-XY(soL0C%E$9>@&5iag{>146D9sNmiEai74~~?xWa1rA0BtchPt(c8wh+%G&SL5HvCSj_4`Vp zBENc(-@Msd^z%>mKK$9EJ8K497=kZ9vElvte(^cErvEq+ZIb5KqHJ?0wUzcqwEAGC zKW9!;C-JK(tL96;?0zhao0o&{VX&AacbH@l*?s4mQQz#d%pWKsq$(4O3~0G;hfm{e zdAB$A4h@qrnJ*{!{R`i`gx#9xyZk^s@Rn^;EA+kQ7U)8Uy#>}M9JvQ2>~_n0DeY%) zfI$^MY;gy~w&m_m;H{CpoJd=3TsNs8Kg4NHfd;AS%w``SmvJFawwx3GPD@_hCW#U1 zr+SI!^Fd7ydyB74C<)6IVjpM-2-bbf22_;}VrZ zgWro~GibtoRK-&>F5l_;S#61Gp#vm(Ic0>t9_*x2nN33Xt2V$!Y`d}j;VYnB*(U&= z$?7`UBGng8BYkS`8b`pnqm(~Mnpxgb5Wn{#2Ni3WO6MH2ul(XMGjn$TnrJ9PGnQ<#Z(HXb7fUntOg z%a)txdhs%p1H2Khw-<8r5Jp=fzB&&k$b6Cd7wu);QD1*)@k=5h5=D?pb43d^Ig?sg zr5iu9;{9oQw@`zUNK-ueQSZW0r}9j>O#RJT0Q%Fvh3&#zRZeG>P}rDM2Bqb)YKWzbs|0zWzkS;|Q*+*WGyE;8|N-e;TTD z<;rSf2!HwM&hb>NJ72k@Fr=hEbz5g2oGWv60KIrKiSoa5MLDbGIJuE_)sbYM3X@6i z_37?zo?Yw}fA`b*1fbW|{4J3Knb^zSapmNklXe;!^G-gG6S@PZousRqtH6@MsE#V) zXJFmS!nGKEV$}lpM^_)6LiDR?!LMqutH%4cKP6W+v!aQf!amo$e_`qvMVIPj&W#E^ z`#%5R&&0Ogl`JGGMnV`V5xAMAO*9?%km*PR)MQ$!cE$x5DnCpStX8qHGA4{?3cde* zyg&9s(O>G3*LTypXmEwi`Q#3>)Rz9$$o;Z`0f77FTCF%aT~Ca_X$13^`x9i&4Q_0+ zfAd%;7a(fF41{a5r?}_5ugsb?@+DjNix1PoV64#?dm)yHS%dIH z=1}sWh)j0|kYLspGWfNw+z{BObT+@%r;LuguF-Cqv7n+uYLVIx%@TAn{9JI}oD$cu zS4eb3CkZ5tdjOCK^_qX-iQY2tYD3M+(M9g#D31n4-7MKL_@E%8qrj#E_c*D6z<+x* z`;1$6Q3;#8T@03HS)lWs_tXXQ0jHbutyZ{+F)@J3g7*OS z+S2AO&}A0tqX&9x;69XACBr#-a_LTa;m3{s_c9g^cGZ3Vh=%QUSV$n;erVSEo{A?G zJ$5X0gl(@=c|!3%1b#0T4VzN%7r3a+I6(c0oC`zk6wV}HWcYN$na>cqRn?Sb$;|HD z^5*^GV_T+N3&rkRj$-?Ii(OXZt-p1-vxYJUclNJs%2+do3R@LpWH2hd#J)!-`@P3(hkY@M&Q2lO4ycP-x+YIjim26~{X-Ob{$8rvDs^onrLWx-bE^f^2 zrrU}z7$AXh51wl4K|;efh|)1#dTNQtk?qX>S;bJV(G+w-i>e`BdjDoqQVU#Tz5j|} zN`ftG{WZsUJ5TMB6S1#|*L7_8*R{Ht0fnARU2c${Z{UK;<_11i1???L7k zRr(pPn*X-){)^veL)QR9$8O3UgggD4;itT z*a5nrA(Dh-qjfV48y#H$K+QT$<>EQ;#K|6Zmq#KFeKW}(z5jh+U9WkAzq_8#wQfyR z~l$V z9;N#3Ye`1^PEZ^PEU3b1NJxb8{+B>|tBckBhR!QnKJUq*%ONM2E9Z$Phz~oTIJ<@D z6SI*ef#*caI=VomJ0SAVY9WYV=5T4l1-n8Wo!~h@ z+(o$BxKqrCnwA*-X*I_x9xB|bw{=oH`m~ZY0=n&O^tuL^!~*ewm3FW<7b7)Ck~9+p z9~Aj==u&7!c3FO4R?~a4h0F|Z?nl(%{G5wQ1Bzgf9E1D|0r;e>P?)piRLoC0;0clI5E~K53p3qE+d3 z|J|TSHi$H#nxhPQ%GeGJS|}tV$&Zb=K&Qvf!FQ zax&3$G#{6e^5BT{qXi2#`@KjH>orw-Inf-+tX1FBsfzCVT|!Q352Pgtv`knj`IzQjFOR7fQJ<%z@$Na*ur1$2jx$bHIY%a7qn|7Xw2z~l@QxNq zG5${BN-cfhr>GO(Is8g$E59qJD91RtPKjLr!fc1ce-Vd3UrZR|P@P(`TKFB>I4!(T z+WSzJSXAi1>-|eaqK|$hDncz8E8N^kz31NMX}~ijO-)jOtU*LFk7d@0Qxn18JN0S!xL98 zbk04)@MBL~@f<4Ia=qr?lR9o^7DW|m(SIvIR>_lGjd4X`c0C{7Zd06yeI?_3d=k<3 zJcJ8;mwaZmv{=&yMnw7B5s--)69hU}h zA2VstX$EP9%WN8WG>oRmNY++toJQv|JT{;24e~Dj2D+0JEN-~(BK#z&0%Q{Bk|uly zQ}wr%hP~yf<6~l0PS#U^dhY?%HHU*Y+&k#ki>NoNvrTayU?s^>d6JyqNbOT$ewXaY zH<;~qB|GQ-Ocdz}ag`alRz1)nUCj~|9;>;nZ6g|*?$|-3)>gQe2lTIV1Ru{S6jB)5 zw&~_ATX85!-qm{5YLC9GF&>xsz%+KKffq)0$4xP1p|%~*MV0u-UgM6Co#G-0_KEP> z{`UcJCd-C5wLEPh+>AYsw9;J3lTgY%_aO1$T72gIw>xeLY;eHlfY^agaT!~7KA~(} zw~rrH8+kL|b}XM4r{8dp6r!p|jYG9l!Amms4Av*+Fu^+8h7Tij<#JL1V^$K9h5O%c zXe38{dn4&Mj)v2p^-uUOl+G61!V3Ff@%RA*t~$@$(UcdnQcMWX`ATqYe;N7h_WFlS zJPlObuX~P8lL4EnXWdAw0bVH z5TIJ2Kyep@74NyFF@7Dl1oXsG0AlRIl^p!Od_33e1@<{pz+j52($Tahv-;)us@mvB zC?z@?n!!Y|dfbcSQbv=_p>}xewj@p*vfY5O$EOsSmBy}*_ULtKvdCjp8~scoP;B6hBcv=GR6QRH&XJq>`R~91u6y|Jz$N!kuU1}9 z30&?_zeLL6TFCczRIf>NSmP~*YdLFqaFb4C+b_voG4_^2W;1Pm-&jQeW#76KzCv{6 zi32As{lAmmoy;d^B*Hd!KUj$#G|Hs|nqye(1u-;*onKJ;=6oJ9hlUm-Yo{%mfYduB z1|5x(2UzJjTI6)DG@Usm=3#~uIN$dK9%ghUM5h!w^~W*3vY6u7rTIM2VP4u^yExNI zB~0Gn9JDju4h)k-Fq8OjV`7?4)dM7;1@N$=eh++FJMg8hC$ zFW(WSlg0>>B`a=?jB4vby*+~}RO{FQ1l{bzE4QQ(%%OwdcKv?w3|u+|@xYLNb^i)J zHZvq&E>)>iN64CvfLFlt<`r;K?KGOIWBZIHtd_3(#|33<>mc2RhDK@Y2elgb%$kM_ zcoa|i%`8Z^)rZn&Tc7&u&(^vNB;db&puRvRdHhq7f<3P#i~0ZrHeuS$46z}>_?9($ zsrVUyTLw_wo@DLT^3NOId@Y|TQ@zN;^ap=^;stKu0HR2^pP|Q#V@9@|Y5PXa;l$!` z!^olmgpk{;TiZ=C+r!v9D4a!uus&q@-M@C`_oi&Q>G#|vumf5f{3%#>ZSdGGjKJR{ z`2F8cwbD{I8s0;8f^F}!eap2m+hd%w*1xaf$;+^)ZD*V?6C>$)*L%%3IKqk;B?rq} z%9aM)r|f(z$L;;2B7Gu3b&F}kPQR% zkASMMJ4ambdt5^IC&If}LEgg-SHP2E1IQq^-kkSYww5_=cthpU9%NE^EM0)=f9Zau zr^k0`f~^t6gBCI!H`JE9rw>=~P6uCgy?n7nzH%BaC1`&CEK@g8g3q83IFMMnKST3w z)E73+Zpw9_I4G0P3b*AMU8b)Yyo$jhx>*0n!KgG7RpPo=u5IbLxQ5yU?pCU_jSUB&#C96k3M0HlgMx91{n?fx zjRn#6x0}XYbSN2i`=pD=NASNu{8ux*Z}~Eki;Dbs(Hcy|O}tc|W&(>5&9`4#nO%J5Gq)hjCuYU;>I znfE?X!gk(6Z|J$X;{o6;Q1aISHu}QlW&9W<8bgxINjmJx%p6k6o zuBtYlGyn?EiGoDBTxmqAhe&dPE(~Dr8DHaH1c~q2`V0)f7PG@eYDGYS%YXqt;Q`ToFf{LsV?MgKOcSh2 zg@Z=|w9g#}R9#(NjF zT=>vf)7*Y_d$@dMd$`FeO0SSnyxj&GCYpt(DySa*U-UHcUC7$GI| z57ULvo(>3xm|*>BRYitba@r+F3Eu<*sUPvnK~SWA}f`ST+{eg6L!gDW-5!4eW^;vtGko3K*@AGbEL$W4U3_K}ZKNtS-=lu#}0 zaAAbTk|M7J^y|%lG|DheL*u94))2KkK(@zzvL;IE^&6`x0`q~Ql*gaPbmS{AJ9XzO zbZG}s5T#Z-+i-_#vb$N=|ENC$8x}?W_a+u!nT{a%G>3^iJD8(pqnuCMz5@?%N}GVM zEJl|#S(J>`JTvHsH(5C>9JW8qI6*^m!cK(M7DriT;*{`_r*jE9-De@>)VJ zyj71xMX|xtc(?|K|2}+vZ45d?HU=M|+r7#ZEExE)BL8^@^1+W8V>n3yeRk&eCi~t% z&*YB23(5xGFH8e5M?Rh3&Wd1W{DK?K7&9oqustCQ=f5Lgd}TRLPC~-n7p0FN2nG*rN@yNI~|O=$4K9QH;*$m zeL=>n=H2h{@aDo>gs7fAJ;Q-Nf}`Il#dwO<7&~GzPCcWOR{tKSK1y~z#}6c zUlJH_Db-l0JJaK{^SNKJJYIS~ZdT=k-WagO?{%cX4WUsAzssTK(**C(xo8x5XJM7i{UJ_yPDq!M;I3}02@!eU!ZwE3|Lp}K$Y`r=!x{k>5O=#)$T zjtxwqidt9{J9*eAeP(Eam`0s%wpZ>!=J6aD*8s{yPmp9_G6T zB#&|Im0=@Y!;59DU83z%Z_VJ45Zn4S;9=J10|!4x{;6ph$dkhs=uGAAxs0t8uJCK_ zm+}Dg10r>BH?s7&osPXC_^m3?bT~3acBu5$mrNm*<=1?y zsLun)BD&|Cc1z9<=4S=!!2}_+>Uq*F@$V)grByqZXw-&ghr35d()k~ ziyo_0`O2;@;dgIDAF23_BZrV+ga+`4gs|MhAIY+G0WC{%WrYx}N-#(gpL_uUr0>#hSbpF|q zC}G5x9Nyhs&oA^*cPngbT=a0O5(*=8_6lZ7N<42RL9%#2qj&a4uTT0zHI6{31y}9n z#+HiDQoz%B!mB-b?tv(+a_RxrZW$~7n?hXw1hy+zS66(3-fO>b*|Zzhx+RqM#;72e z$Z&ec*vt2-TGob-V;>d3j@Qbz(Lr8e4^0eSV$O2dLfL_MFSl5p-W0~xJ?W$SErsyS z7H#4{0+Vv6uGe({*fehBEOr?MDI6vvmp0*0WR@j)n(}9tn?3Jedda8JeuV&Hyo*v$ zZPq?r7)k3g#`zP|vQQwu)c zow6ljqM+yjSClcR_H2ZHcPd8bufLCztL_K}q^}U~#GAeWc^ZjaV)Rn9miEYDhK!)D zFr6^k?(l9RB(EpNx|@!YsvUs}&9PDvU9*u;zbJt=s5*aZ4?8}RC)PbfbYuWnGyd{S z-u8HQz-IgVDQWA6x@|*_q6o05a?*3m2X;2W*w*xK1;K31p^b~z>BivM&iFuBNbVFW z6Kbue{rN!cf?F7XGXO7f=O_luiTwo*>ihwG;7+Tr(${Bm#X-&oLB^hGrQm~@3>lTA zbHJs^t`J5Wu<;Xmxsp1`J8Kzoi6@r-am7et{hc9{3dBtRtd9U>&${(K0aR6u?4>UA zKnd^)m;|RyoRzMM&t?sa=1u(XeugIxeyD+Z?wU#t?Fa5?Hzi7iCfZ?U&7PK>5o|ij z==hnXQ&;#p+l2(XXuM-$ai^G&ePUP6tb1+DafSXuog#Ix=kEG6Br@{S6tiTw@SmJ77n zDg17VhTHRmHTLf3Ln&~exZJjMvGB>Gj|j`PVSqu#&tPEB_R%AcWTPZyepD1Aj~B*` z<)zcCyeT)5@sPJn#59ne-CmK)JSY0>A zm&(mFU;5n^I~6-V_Fw_l8(_t0)t z_6uGoM5@y5*RA8H}ebL@mCd&fc35o7lyAI zynd2xuW)|y485O2gcFwo@BD(Z>nUglW2{VhM8*4o`OoNwiH#i>A20p%DG|6uo>0q{ zLd}x2u)v|9coD(}F<05@`KtPgchMy-j_=%&5mTu1l3=VhdCa3qqQz%=RYQ5ZPFpQU z{wCZYQV=2DOI9&9yKgDz-Z#AijEvvEqhDjy>E;{InjwZG7>OO&=g_?;32tzXHt{1R zi#0xMwrThg3W0o{TEum6W2}rLk@A5B(LkVA;a_suUDA5`;N!8O$&QBwVTho>Jw$(79YgTjda(&Z72R=)p!XBDi^62 z-as4ipD)FhHonqcpXxU1jq2 zet;;J8xtZiyY8kZU@bh@*t_Qeb!xP4=H9RHeI-}I&$9F*^ ze0AD0`t*3=6&6`c+BFu8a|wT1+t>XSbIYAi8^?wQm{l^eAyh!<71q{x1?!A2vM?ne z+#k9Tge5Om^Bu8*XD|B%{~JJJ3Urll#G4Qos->>iZ>U)DwKgs&?$cgLV~*AEo>`8s z!M(EJbJ?Y^ikKBRdiJ@Gs`dWzOHJY!KuNu%Rs!?r4&-}_d6TpDcv06lFam=ig(8VT~hw{)->TR;fa^=!Gv8?4^%~7&Js(KNl(^EFV1(gguG_Whnv$@ShoFF zVXW&l50m@-bj6eW3{$j3RE>8BWYeqg7lFN@Q;jtAK#-bBfQE*ap?F{49_T|TJR^_K z2#rbq%)K%NPBzqjDSJyK*+67Av*gCJ;_orW-PXaW;z1u&sra6of1L@@0G5qwYAScj z%Hyvr4qWTLC1#8N9z>`F_P~~Zrp#gyh2U47Nio`5vBZ3e{W&-I*ZUhkR0e zojq16MyvbfhRycL71~R~D_>u*!tmVus^UU#@XO7!zj%{=5{n)2-M>HaQEN&&7SGY` z;K4pZQ4t@-6a)SY2WR24PgqSCr>uA7<9AFXcVnRw4FZKk9v|mBDw+JXh=zFJ0ZU7l zhJJ;%r$!9go*UYcQ`8a*9y4LO{O>y*J=1gW;(TOYQW;47m`{Wl_iG@s_!;tnfkBxZ z%Q_Q_wj#i1&&kauE^DXn{^xO6T7ls{D&z>RZf8-Ltzd>DAtil7%55dlOnP3ocY7d{ zbKp4SzrNysGbwu3|228_`6i*9ZC)?zwMq0FISRLv{Kw|;nqecHBw`!Yq6 z*o64E5gqM{Kmuyrk+4ufVp>|2OmUx5<%b9v;0cG{;$-6C>BsAAAAqCN0XZ1y!|E&~ z-Jcfn{#(a}niN=LK;sQfW&KK(0-qdDkg#aePac2Egt}d=OSC7cd1daq#@3Y^20v~Q z@77lH?z9mdpPGb_S>!n?Tm2fe zP}zmA6S6hyD^dv+rhm|2fKo%dP(ie}mG5`Y)UTg%-PM@{~7gtw41b+Pm3m}xW?`m~% zF(-@xyr#cQ(sMb2bYG!&Q$=8ERqZ-T2u8O1Bw&}K0cV{`z(9lJ=Vv| zv~LX)#Ozz@k6S7Q$uw9vFAQ8^ynC11aq%bHgddtAe_M%Bi9P+K{cGm=@d`V_LLDz# zA7Yljs;a8wg7$QNXS*R?)N&58ER49c=fA-!JXMK$#4CUQ0{M zB853PbjYUtAULx%){tCBmQCes!|VZ`m?)!q#f}~`Uf8~Wa$Nf5fy040T%=X9se{_l1e6}2h9>E^ zd}0mIuf?E$stC`|eT9>7X2_lD{?Xv(?dR7Vv^9!H?xN zXWoMsXx3ZSpV!83rtmqWbp1A)7z07#_A6x_o0#M_HOf1V?BP9z>~GeA7$< zrb~n8L2WJ~Hqi2Pe-+duEtWmkeUN>HIs*UQc*J?Ikx*w3$x|Y?_l>%R5%$o%gp+e> zD4t89v@OLsPGZ5QgTz{i?rc`kb$fc#ajJot#NEg9L6_Ti3jm6B>-LbdU5#9R53;|n zoC%i^I{RYDoMD;1GS_iD`D<#{o%9G!Y3>5F^Wr|IPa)Gv6q-ENW%6#y&Dx?Db{4$- z3l8vB{+kUkWdK=7%P?s2ZLmF*Z`?WH<~>|~r5=v&tUY2fix*!o^1ixS5VWuvo5dsG zfZ_h~^bwYAMyDUy<*l-w<;D-NELE*H$!^`YYwp;bYtQ8DNt;3|m_ZzhV=HDBh*#2KlT$HPk=|yBt&81T@ToT=! zLnySZ-3VRCon=(RDL!pfIv=#5(mg|cD+E@AY08U3mT*B$2vW!W3JYf;uzD{Yu#OGZq`^*;q?j>wmgHx0CCx;-6C?o?m8{kcHb^e)r8 z1X7V=rg%E|HM6aF9;`ivRqfQdT5Mo*3R;=W!!HXGZC7wO@G!)Gxqd;dG3!ni-B1w& zF-;FF`ut~SXRYm${Cx^?AQ!yhG5ZOFU~{VwBAsPV20t=*w@eYi*FI z`34I1-t23?nTL~JJOPAxw(~(b+XKh%);pPN`s)N9Sj0;Ntd*}oqzLTEog}_MVls(Aw{(B9CwOx-jS@5SlR;VLsCAP}VK>0mbY%qf&tU|ji z`|);LQF0_A6L_GDQENBvxNrUFuNRg`z%RA?5YGL6o^PhggT)rV0ED@xMubME8fAlX zWl!pP*i#nEn+(1fxXt3e?-KNLFpz#5f?x2++E|$T8bv!}E=io>r33t1B1wOkQa2qF ze?Sr=D}^q0g6F6?odc!VPatk`|yD-BqCSU-g$jE0-E}^ zxs&Ziq_UD+Y;iADgxLmRM~!54Tgv{T`wQhpLY-C$*R9DX6|aT_?eE8A&ZPDJoM9$T zhz&3N>DX!#c(Rw}dZ_b0OpZ9#{aV7i*ETak#RbC@Tdq^HsVOPHU}NwL@=%=icefjk zXg={p)I$)!Uq+X;!Oqg0B?%ZkoFJ3|fY16QRLAx=ZY%%hjgZ6^0RbUVn|Qm(Rj_p> zmt!n@uHd&abznr!)Im8iT)z3T{J_QdR5|xq*P%uftypGv^)>-Q39=|}3>^lu->U?GXxh?8>J zjF&r%6QNAD`K~H7zWt(|?jRPmHy_KWs`zMZ_!uj=|6)2ybGLJ9(g`8veK=%I5a|(N zUbSLi3hlUk>7x7TGe!p#wt*80Q@0D1gTdDFU)lJNw!b5N6mM>`Qa#x-a z_5BL^-UL)unvoWs6>{902%ie8#GY~U^Ja9r-~j7 zstF<3f>1)?lP!@Il03*D_z6iVoB!*JSX*-F%j??)n5^#W3jg{3kU?vTYv0wDp{2=vdC67hr6&x?DoN*R~9bbyB^$P;2)KV zTG)i1{jWv*Q2h#L8WMcqdIR?Gyk=ZuoBPLk+&nw~qcD+9+Kbh}dh*R0It*;dgS8AD z1L?mi+BeR`X~b=@Wz!`DbAA_cy&?gEnIcnO4p6IL9u9#vbNSv6aCGokPBwQ;(Z(f) zJrE?aZIy1phsPi&IEweLXf3A1TP>cQjitjJFdsVBT7ullPDJ45nXDxmW9GDDO&u>pw;rtrx96GppU;Ur;K}W~*!%4ux zv@5Mw!#=A%wLV2Z>~+x_IkLI;ybU!aOJ6+xuQUD-&uNeWTOeW4%!Gs=(787x$+e@! zg+;c>aEme`DJ6~yFJto(E@>aFc4lR~(C8%9P{~P`^4ayR zGYTxFfM_0$#K?$<9JlT1uez%rTaHzV8xC(H#Ex{8;}q!Oi?c;@j@3p*SBjHdq& z=3y9VH9@?jl-)}EEHZ(Bb2vc&d_eu;U6*r3ulSRJx zf#^8t+S=NGnw%5wVS-jpi)q%ySNJSn^)Bp$9cJs2SLXOf42Fvi1dugjNz^WRW8k6gNym8R%(cZBPJJ&QF|_q?JSr` z!|tyl()GH$mdZTUl|QtFCeqhCW{iB%uDD+a?lE5XSgKX$4S$G1GZt>?Zd$!7mRMH} z=h*%2KU6Ne8E!t(`}C$B`vH&CMVsgb<|>B9cde%MM=H&xYIg?J06_AYi;*p|f%e{A zb-`#?@VJ^<`f{tjQdNh%2G{c)#!NF6yWiQdgRsk5d!@~Uqx$6!O1qs*h>w!3P!((d zetnvl*RW*|s1XZnI8fvgTnr78K*UDLwlst4mE)1sUc7X*&(`6+d)n)V9m`_Tp`?NW zPzC9)LAQhN-S7NMAP@$$q-PWKYKQELqi((Mw>r1uc4}f zCNHryMWhu*>+FxCxoLwe>jo;nb{O1NN59@@h%j^ZR8tRvH9i z0!P65sfPS^TCh>@laRgvZXWl`AJGX3%jISO%fz#4^#0!u?=~Az3q)+~E5o9TD;a0| z=zfG$?~-LQern5dn}aRxT?Wi2C$SfkzrXWha0k2Im)-oEk^YeF@z3PVk{zK$Q<(~m zzRvHxQE>a|fA}ZZV6g{-bj#6_tbr7;^YH-fjgu^bnRmGh!&>A<=H^Kp@%yCyL-9PT z4R)OnW~|1Z+AlVmZghv+B%9-=el43-Qe&kfNicrvO2S?r*%(#|QVMm@Pf^w?*-S zNxlM_YUv3S^f5_TK3HZ->0}_F%lq$#Mvo6V`X-u#zXhtOjQ$ei7DXOB>isU9S$eFq zx%Ktyuy~gPB~z|IP4cbN#@3){o-1D`{Fb%@H*N>M4xGI}?#1g!{~dehWr#fTpQ~p2 z9N%!wy)h<-7s~v!<}!hN0C?10*i50ui@Dg0jNZ@BKIslOfKU~j zWWDH747kv0CH0qic>_^4`>I65E4mH3nL0WI9dRkPPF>mzs{ZZiKlqW)zGP-BRo;Dl z)+746374#_)5c1k*o>TOn>$o!zP{I_QUD{wA{3U0r1<|kyZJs7!~ej+zi~rmJ4>Yl z^&%^py)=SJir$lRXuTH;!F@~3_WF&!R5MlNZ#QY}A~I{!Py@>$r*`=j+RSdNDIbCO z`%96T!J&(Zwg?e%a~`R6?AhNvePAPu5$EmgI2m7<+%IP@RA*_nL!GAqTf zmrk9scRVi7kH+fUiU(3M#Ju{O{QUkmXI6zdidzn4Uu#jlvbeu=Iw|+0>f8Beuaxk| z*;nKg+BFZc)|n!V2OGuNb{uBJ3POsCrN_7=^7w#@2UWVvW6!M0r-)QFRXk*>*KG_P z$gWQd%lB;W>2z0RKoCys}mvy+y#WpeSf_#~;UQ2ZqTPlueXQZd2?gbu`n3hn-fK=FYEl}l(Nw$gCxWE!a zduC9dqdozXv0u$as)RDH*Ewd?*CyM`o7yA4F2qwqNpB3}j~=pjB=~f#SW1l=T}0_5 zZjW;XDj(cQ8-`569Uv(>&Nic6IXgc`eb+ipa`X;x&w4|PaZuE^$`aLqy6aF%C_u6T z)IaHy{0cs`7Z<-Xu?M_;$N+fHZ!_*LluT+4UGDMB*H39pjy7tF1PO)TM=1?f>{r)d zl<09=C(xZsAn+6aU^Q{L9XyNNdd(#^NyKu)vjGb`(cdVjEigZUu>ErjEKy&=@ zvblEgIax+QH=%fOoFU8HVH8*DY?$wJoWE|9Qi?&wXPCmfn9d@ci~Z zJDDtX@BwweDrUF>^t)640@MHS1oMI2x6m>ZgwdJsx#N zllv|MU?32wvQD}Os}>ZW3V<1AD+zxwC*2VjAA&C~hbYEEjMayx8q^;dFqGv*o411U zyfbn?lqvn$^d5iL)FSVmxL3CLC*<>7vzmud-o3RL-wtVyky6{!_ABW$CsZ&;gVaRS zT`$M`WaF8Ddag;RU88rU`=eG&xTv;^low}BS4eUp2@9d;Hno%kG9Y-T+)Js#Vw--J zQ9gguLlf26DSv(vP+#7AcD?$O0%?w<*pXe|0rKUy)sZT>7S~-m#imr=I6L#R-HiH0 zfq;eOGyx2^ZydRn(F^oEmoM@piK<-ht?Zn^M)Y{MgSN~Dt4OPi2_kElxcYdHE^|=x zc|!eSZIP$OpZ)lb1^BtTtZ`W!0CcjFvUY_CJJuv-iKa0Fm6Mfq+xg=CITbLrn z9e~nTgP-Rbo-$k-lVf2kGjsbbU=D9YeP63zx!=*vlys^o)f(tcC$0}1XWZMV*Cz$3 z7Yj3^No!gwKhTr5{h(Voe#Z0@(e3tASH(#{e|{f zE(^fQplCjf>CZS6osm`~I+L9Z-A+~xkqcMG3J7)a3LYO*vU9Svpa zzVWMEaoZSeK}Ecdl}`8B0WEr}&JA{t^Nd^g-s2QE5F*8RVQ*pz=(4~5n^1Kz&aZ=K zu{18*WnXN$G{GC6a zqTv68KQ8-o$?-$=mn(dERi4b@a{S(GOv1zAvk@XzyS-u+tHbxuX~R2h){9fm`55(F zCp;NqWfP;CH$uMo^rhs~8GDQ#QTqO^5resv_{UwEgXC_VbDw+>M_lz+j}Ly*jSmty zd2B+lG@HP;CdY#9fMI9s-7$U)w?BOWG-95gDXIm(<+i=^g7rlzsx+W*Ou2x(rtkm0 zmf@^#d#g>b*AC29|4duu7H0hG&Vk29@GteK?g>|8m^x~KY`vJy2dt;-wB`OMpXd+C z+584fA_ct8XTU~^1U^j$(LTXssCAf{%!Q#WMSFT&dpfW z!mB4<=C3}7nzW^$iR8Att)g4DSazy2d$hv+v@%0S{EJkB{fo_T)2Rzqp~fF3qg;wY zc3TaftVoBtFk2dZo>pyDw8sZ-05$f6K6cWRcPczlKb;q@-ZQ_q+4zF5s#k&iv@YSQ<;qcyN#8?5H;NYnC;YGc zodDE9e`q!1^{mDFOQr!`?P6z^S<=h3}4{B$>5u-(W3n9EHseUcD%D7aiHwi~n>g^IY=JOf5c; zl!}`&5_o6-yCxX!S{z2YmsqU~gkEE3N=c(WjsbbOD&!)>+1hheAPti zQv8Itxaepi(o4fxON0K9)UL-X!z{0o*LX_(cE;FJx%h+T)<3@QxlRYxlglb`adA|j=r}rRt(krAz8;=;dUvrxEsnz$Qs7-E^B@Op zH_3Sc*GbIjJ54sD#G}a*GDuKigENRQWhqW;144@LUZw_a9 z{5EUfL{nl+ywjxjj{R!%y44!tn~)b$nwV8GtLhSI!xi#^qBCdhFQ!wDsBX@0s402l zvm)41ADT=Gg^ZvSk~xy+hCR168>v*)j1V+c%L(4)Mz|Yk0X`YHj>*+Z$khG6B`$?-Ga zO|xn2g6RPhYhpLy8VX->T-vG-J>HfQA-0B*Q+l#H>-P9%F^^`VBfK9xc-MGG<|T{X zoe}N}p}W;D)Su=SUvER@oTIQbm^jYQV9N&3S``r~Y5Y3ky<{)fhDfWP@SjrWC(RMr zZ-zcV^0_|bwld7q{O^z!OF6AvX9dR4k?IZNBM@ea3cMnxzN1pMnC?rZEB?Mbn^kMg z`|LG$$jBlHK30I-bP}ze9jXvkCPD7qM0|GopgrB$N2$S=%aL#L^k#hF%CI1~X-rly zYc?UBhvR}<{L`I096Bkl<}sTKKWE^}SV?Sr(%ck@vHwanw$i1w#q71f%o(zMxX`wT z<7BZNOLJfJih;F~`I5uRR5Jubg9G$Qlt3&TgitwOmYMF86SdU(IH`6eZ=Y^w8_n1% z@McPN8~Ui$V9&hA(ErOFl|w0 zy<4#(<(PK4!552A)KlW_7rvThIlfPU7FuOj(HhM%HViOn!+n^uAwt;3AG!8Rb$;DHOh^rVzu8&szl^kS!|>7ABs z0)*XUbqts_gK3d<$UW7E4d;gwRHE+DCwt7Leq$#0`B$Y54y9P06%7xie8!p@I~-u$ z-<(KBN3UZhigSl5ILujqFM$u-3kuTWkG!@phi z4Oww%DU>V!JU*`HZwCQnY{QXAlT_b4)vxZJ<0mM6(| z)ShTQXGf`3E|4y$=AK*YvfR6Wse!?u(-;_zJv&@XpZ)bnGMv;E24s zSLZ1bY$L>fUM@5e(3ijR<7JBLWZw;TX6yJS+$sHjADfPwI|e0cCn1T;83uuSqYqNG z%uK1uyOLD$x8JD24Trh%8g#$oqg;Su2fsY;GHrk(@yM75e-pJwd#nRuI~XY;nKLFS zDQU)G@iuv0m0G?nukv@13#+c!y9$5C%JP8QR;}XgEecdU6QE&fM7=MXj$PXfRxQ%k z=#MQf`4gcJ!c*uq&07ZDuH93+iQcxyUM81y(`j%Tr+$f24ly@__sYP|?y4)F>H2NG z>ewG6O_^p()1%f1Sw_9(nwja`!|zf;+IF6CkGyA$amaYjwtL41@6%poVd<+ANE6y~ zGcsZPs(a1W^U584_MT) zfP@bX2s7*AZ$=k9bPD8UBOmz0yp0&2U0)be`f9^Z2{#*xZkrq#5){M@#5mB{F8oF$ zqO&dEQ{2MpOiWL>n;dw|^aoA#T)_bVil-l?uWc{F{p&z-u3!LX1Xone1fy1cZv{ym znKxai`3=P6sun@P!BCT$`Wk(_B5A8;bQ{PU!oq*2;=H}5GQ?k{%23Lr(Ys*|R<@Il zKC-^GO=OL3XE>&7i%y;~lJkRRNX9rZr{oE$ z^VL3{95S0VAjqw=#v;JQo&OXiuF8wTKjj>Vo?Q==f7G9|fyiTS32Jy+6yxR;=I(EB z;JWYq;Q_V8HXAJ4SXz= z-M(y7_-pgS#NN|F-l54Gy?h{g1QOUW*rQZCes5&e%#nXz4FJJ4T-?8^xGeVlH|9D- zbontRxVR+@iLNc*aNg{D)B_?z)q5U?2dt=O$F1bJujYwM-raw-nw%K%`Dqq9m;?*Arlq%zy>AVx)U`EYPDieZ6})J1n^|(hW$Cu&A8jJaJq%)* zGCD#Or3NUGS@4k3Xc1QVEX0@@wsC1zFbB&rZ7+2^H>c*uz|h6#cpPi6Mf*auaqT5m zux^Q;!wHY~-zSGbIz=`GvYqBSjlmIWSLEb)-Q5Eg5_2#9j$0K!t9bRC5M{FmC9uUL z5SKlDByDmrosD7oX)PIA)YlJ^7QzUV`tCeSse7G|I}V1`llHjS+;RK$F1_TlH3h4% zmqpRH1p?o1FBei&Yu~E2;+RCTs;_TUh6>#zCzk{6AfQHBP$__5BiGaXJ=v%^?U}u8 zURVE+@08%2TEx$vaw&X!-L?+!Y%>0HZDS#=b>3RsKRZ2GfAZvaj}FTuOots{RfB_r zW3MIRmuT4cy5CVGXnP;qV!B=k=;}!*{Sm6Hss8qm+F4bC zsATY6l>9{`yhhlYU@b+#(br$ud%#C2@fzXPlGB!fU{)m>`&!~A4tO)btU67YR^t`L z8g+#Dm)Ey4@P_I}4W4!~!*+iOvh1>eeXGfq;N3-!*}Qi7dP<^(O=M9mGH|k!xbQC7(;t{jPTo@|udPnz3OYP0UzvZ+)x99O*w7*ur}8~+afo2LdW*xs z99l#T+Fl!Ro2B!EUfRLr93OLOru*SwRB>0k_<|Silr8=`8Tx-VQoJ z{A%1@c<+|KN?mhrat0ogY9C`q^I90!Hx|V@>d3FvhZTxuupX5u3O16E%z0rOo~!}k zZYR`_{!X;a%vL4pCFt=aE|2&J81nr7_!y7j&K)rt2wQ7c`wxB#3d~n!71nULAUlE; z7Azdv%Yc3NvxC+z?bA4+KX)wanNZ=!(uS$0Y|Ekw{8cZn!c>P?z}9)sA`T)S1_ z!?5NEJ4{-?D4%j8a%*v_j$2LX%g&0gB8wGNp3F9HtA*D5m)XWpn7TD+_Ih*Tkw7gTf15w1N=s*i2BDH}=8xer{=CHJ`Km!EdECJ2zh;OI z7e7R`Vz$4-PXXH%Ss&0>Q>U@KVyg{=lrCEZQnZZWRZ?FHTv8Bi_amzRdk->Wp0|Y0j_B4i# zjHKrG`eNq!Yx%q2z9m;PU*3I%?CP}iyP5}A#JmpMN2;PlHQ}Xre!q3FV&n1ny(qGH*p9LPxicKnwkiD z&s^mEG2=SpU1GXqHHAdsKXqaKZCgx{`G1ykH}&?EhJ;N9ZdCJVn%jjWmx z@xN^b$|c+C#*yos)KkBDPwbqQ_VuqwN=l+YS*c+BPIZkMMY=-Dgt?w_?t0!MtzV4# zjw-W-I#Xql?#k)nPa9aJFJT5H7Z*s*c3w%ur{H-54YTK6uz3Y2>nR9H4r!?}4x35_ z!i}WsIqB&9DedYKS0zpdj=LYELgSN^pqWN!on#-IjSWl)n43ay3gJ*PUQ4B24_b!!~;FTGjbylBKgF4wV%%&J8l_)?=GGjHkD zxgOY~?wM^air`PrP>{xBssF!;-&>|)@sIqvnAaSLlu(3SfM9wLQ(oSR^?=Mj z^*ZM_0%Y&*(6t@JYbHuc84ctH9?koknDzM2=+7^Nk+>fxZqSln+!Yl~^FbaGQ&6nt zfD;UZDk=2u#P*)7id$nPV;k|V9s^Bc4m^X~E`d2h%a-cWMK&x=OoTt=x1Cei4As`IHTT+^ekyVkB> z;&u3Q-MIJ*^R~@z&vzWW zM!7JpYtpXFDNj_k_@nKdtO!|QVf9IM&Ud5`Ajz7Ks#@C3qUSts$Uj<$a}17;i_9a? z8c-Xp+9ye9bW_VUn+08AG(1~jYb>nKs!Y*`E^R`pUv86#h|Om-@9x?kmv{}|Kzuv4 z|GI@Cx)-wEUp3>agX)GE7|>&`w=g%)ts35mDZ}RX;EtBNz`1s9Zdk`|$+0HVo8;_@ zdH0f&?>D}i2@SS0Ve7}9h@g1mJg0LUNenjq%gL*1*LqWbadW-Y9yT+ap|cR8qR}UG zyTIqGT~gq+hhQ7%s7)@;PO@{yDUV+o8y|zoOhd+F+!&|2xjdv^!d;T)v}T=Rehi3l zj{y%JPvLa<+D85pHRBRq64kyt`C-`!wz_f(tlm8@8Hzx1E{@#bMKPs6onm71R$ojo z7G2&Po!WX|dewp&Xvh}ZBM@&pYTF&xf(Wrt!GjiGIDdAQ>0K(UW$|JV|rfR%9z9uQ1&l%X~T(bFWUzSy!@3#KVu7e#Di=b)|sB%jVL1bW|p{z=mYu0-3HNbPXRLwSE}+$li4Q)7|c&I83y5U#T@!dSC;6h>VUD(y%@ZNSMar8ErVgh4`UAuh81m((E zJn#nP1jM2a{~MHV7wt0ZF`n%=#GTk?Wyz@H2H!T9?12tq+ybe^faKJ^6u0Q|AKGq{ zwK3ZE-f0&J%+KdCLI(jrg#ZRN))$KEd!=U0Su=*i=R3+aNtbUH%nbeSu+Gju+Ft6q z3$DqqbcBlaEEoQ>6l)Xxa=UWn#s2*+iP}4Ayd~g9o@}0B1V084`Gp0eX;JqLZi@jd z8gC-AUS~Vt7!MZ~SuxH4!yq;-IqNQ$^);m-Zj+RNAo>((|=aErzyJ7D6#Py zanGpzuj8o6m#PMW+XU#BL{Yx_%4632U|skV74JJ%YUdC8S8h10-$zD9x>mEFYVuwq zYx+|QYOrd%-$T?SYO1Po3JRa3!*0+Jpq{wTzb6jdl{Ty|5)ghC38F;K93b<;5f39_ z=CPfurW!I(=Kph|bYsBPbU@SHM990p#dnX5v%lYPUYcA~aJ#4ZN=u7}fz)4(?q`}8 z{N8=Ixz4X$XqP@xj9wk8Yn|l;))WnXvKPEN;Mygj6y>!P?vN90?G{5z5iT0R5s-5_AxL)Bm`=#vA7kezah*T)S_UYt>KXmi`! z2+(+1&NPZ8DfS(|aCc`P{F8zF?y4N}qz7oMaXfRZ~sO>ILReZfY_y>?et7`gT_| zE(<#40{BRb_7eLpo>?G?jzgA(<6tZw5Nq?TN0vT)J!VDgc8n|i~}Ee z=togV&70+(xuwKCkqGm4n#;Ot$Ah_(2HPY!2S}+hkq`2+T(|0btRedp70l;>GWU7% zq;seO{Mwdb8=S3O@u~*d*ZdIdHiGd3X8c0()r`(hCktaJb*emVZ94gY%l9WMBs3zz zMSBFAD5?fh)PraEu9-7jvLG9hr@$-=VZBnofoLF?&-Mq8vQC?)WqCf7r<*e(Y+{CA z5ZK~~zUf13YHPozV}iK(4m(Icqw~BzhX>`=0vaS*D*EzEj*`n>*5D<(b5@1z;URaO zq3yhQmzn8=gZ&x-$B#-celNldvzp&Ae9LuDxV+5dErI>0Pp$phkr;Dj1f-nisL3Xg z!*s@)7A!~LRqXed2`0KB8@Uhl-dx0W&%XJ}2X3y3y$;ae2lI$mm@@K0et4_O8@qA048b z4CqFKGNGSi>j*{do5gRsj}%$SrD9Cgj-^K6)zvu7<>xie&?az(sOJ0?iScUB@Sl=s z%NVnNwO3L2tcLnQhit{hh9iPEK484X>6{Tq~sY zryEf^+7r;5ExVgnDyG5Ho~RK#{6Td!kazRBu3SA1_N}%H10l~pL|r!NnwG0AkJYke zCHU{RMrni$Wh;gSIz%zEvqwN$TZu>6f>-EJfkqxM!`_hQ=qk zNFowNX`aAW@c}q#&^>y1x<7jWSLi!9W5S|#JjvDebLcs5}&>UfwbVbZO6+qyN8&&yg;0*1zhGENF;H!IIVk0Y2Vg$qfQgGygZGO1svEr zVv(P9CMc(P`J%SJB5Sv>?++PN=tIpd=ebr)l&dHBXB6lfOw@ZgHJ&U}u8kCL2beS` z^X_a`?JPtQ-CAjMG4J-zRhp{G(2WCM>E9(rQi%frpptaacb~)jXE73EEPFKgmMHc5 z^jG6d-L+;~mrI2a`cJ6GPShzE){TxGt;9~%uW80PBqH6958JK$T>0(-`7xGM zmsi$i8_|oFIoXIzik{%tk(WBxub1y+>R3H3@P3-%D?d*kjCSj=<-%aDS+X}%@CU8} z;fo|M$4T8OR^s~!e^-W#_*f|B*WV+YesJLfF4_*J3Mjc*B@=8JpxCHY+3G1W#U+x+ zXBndy(xZRjEz}BtcLnLs^gr-1h$^9$E2@jDbB}+66Ofm+Yw45gMgS{jZ-0OV z=f!433!+02x}f6|(*7R}?;aq_`!Vp0E{og0|0ww}`F<%ue}hlp@KyX5Qw(<~Ypt*M zF7*S9$h*@;5}}|7ea+Xa;}`SXr@JS2eJd8jO5FdukUO_{De+_#(HWl&%DA05^NfHF z>aAm;=l-9$a(egG{v6m*8|Q=H%U5WGe9uo&zc=v!G`#Pqgkql;yx0ZODN&kU*_R<) zwY*lZ?XkRRPbnk26eT%-LWyqgjbAsD#2YLg=q7xDb#cpB`dB6*o26g#&@9vEVa#r_ z?d3Wd=ogoMLn=pYP}Ut72p2$wkf-0^Nh7!X(4K43<<_LH2;}A&c%zL(pMU8|4gXP$ z2;|s$;4N`=9`P{h@?i&ak@!_GsEN|!$3Pw*bGfXk|F6aGeQvv6PG4;;|6m1?$;{JV zHZdAb`YLN_R1Hb&Djh;(9{z^PHqE6~H-ffj=r{(s(bIzT%WIpCQNOQ)u03Nn^SF~U z^mSewKyw)2c;W7%{op0miyWoR648t7za!tPof~ysQ?5wxvcHP|pts3|3b^>-ht?ED z>O@MwS>_k6uM9gGzza2PX*ypt4Qnb*+qL~aqP_wut1a4EL=cr$x)cF_R6~*Qd!V6_cDCyI22l`tsAS{W~)6 zVx|6YyB*t_SFZ8+EkG{N2&f4l?A}3}`KAx0j=!bE$iJxb=aY`+88?C850GlFsqETFb9)CX=K0 z6)}}_*)|p9Zxr`li5b4yb*}fJ-}Do$XEW9$rk$2rI-ZEaTtuwWjWmq}4%I$vW>TCS zMwQ85ZMAV8dLi0P4fcvRqST!8$4A3%DUN51Msar8v?}oo4D@B<|14Acev|OUrSsBb zKcIr7%O%7vZ4d;Lb0-7VaOKMES}hIZGdVNQOt(9YM`xTY?v~w`zx56y(s<(6?<359 z$*>GRlBWZZLf=>GKkCc}v_lc*?_d;z(GlEwVC+vdhr9)X*3oW{d^s>f5=iLjhvU2l zI=r5SzfSD+`+F_Fx3$cpd&tJ=1}+$%`8X&nH3j`nxqA1W0qT6MRA=&0?z6VpHb%Y* zLh^~I(&p|UzsaB#z`b!W#Uev|vaXS?;?aXy-B0iheeUY&nv=WnC*^85gFHf`pq7l5 zV32R%ov15%w|}8&vwSe|RrhK498;I6@}X6X8^s?~tH1H%7IwndwK^$1_n@SLF}{_} zogI)Yh4h}_5yZUs-YbV%1{~SA(!{+9bvRFaT^>Y8PKAB^Sm!X|CINgyGG8C>erNTP z$cFiJ94F?HWGNmD>-t-fD<%@VS#`mz?Pd4;N}K4@ZMuJs;daN#{{CW(wYL2JPr5y6 znppL&#Z>PVzI@z}9N(1hlfCmSwWxFLa=&*XPPGni+W6!dX7B`5B){m+;Et9Tv1O9+ zTTH!u!5Bkgk*TSj!L%_^n*^#miWRo+ocC9yK=XH7@Y5~&{FbT0#E+B5PK}xU7xlfi z_u)P`5Cbqe%uD+JE~kD_A4U`vH%}LmP+e{ZV+&2cP`z1CQ5n00% zIrPZmAW_4UtM;^M!QJ|D@$eGnT4HO$O2*v1oUy|#%XU^LEe6r5 zSsmG`D=o?+wNEe5NY3LE&wrEqn|>@UHdE#x>9~s9Me19RT#17AEPwv|DX*ykT|1c= z4o{s%Umd2!9$UaWnz0X;nfcs??anGU#855ajd$_f-H7{9pVl%5QYap*$Mwmv9~>MQ zfszqitSuMjNX=-CEZG;3AO5_CE6!k-}UlZD_O#FU%p+adX4&+9k%INy8h<%Yex;@;7lOemtkxY9! zMncO?Etg%&dqJ&%kx}Z^>6D(5zWiZ7&RLd*#Pny+_-m_Vj>A_!4pqn0Q5mtE_E1A(d^1 z#1CO(yai{L{ij9__4%(>ISw#x^KpBkjS!bH*jfSxy;Y*uGU-PXKLCu>_UI=AIf<~*1*Y+d&dZGbIT|jq1EOG|jQA_T!<6xV_IvN! z;C4{2;ZQ%_1Yr5(Y`0vA`Ku>+4A~dGI$WE?Ny=5*jn>i9FjuMhAIfdL##C%Kj6cfL z<~xrSpbrwR#z=byAUdnLHlj-`^b~2bB=nxr<@?(#PcS!?;b?e@p>|3aPR9o`k3MGt4;X4BprYyo zf8|F|z6n!;(^~4}zjWOHmgY5*MGcX#YjV$_jp5a2+!t3zJIG*+@a8I8+}i7fNBD|u zu0e|?UK7zzM<#B|tY#did`LE7d|f?Hhx3wP2P;~c-H6QdRQfUoOG$rZ`3}atKTyhW ze-;`N#;NC_P#4lp(zUimj~~P$IapX7ZfyT@_N~h3{@vu16r`&JEuUjG&xLH$Np<6meu%u#p!5CaigQLHjyhUMkU$n*;rD{)9s?O@nYte zvTsy$_!fB0+im%Hc$5XHkg_IS*@K}|u?`Iw2@V5+wGUZD2)N0*@0W{m! zWDC2@@}Q}0?50DQPVoiT?fIW8z zWL~IWzln0ctQ8lxFLAmh0ip!LCQj|^V;q*+T`L*#eukaa2V?;xQ@cET#jU`V?xjE3 ze>c#;a4At3vj%Oz-t7*Osc5>aaRWDGx?qV@5EQ5;LBhxoP~E&NP=C+W?q`vF zUO>YYw_3&1yy~s3x+FdQuJp&5 z#X`U=+C?^d;y@l{hIG>vfs!ogR&v?316Av(F!=-PWozS-1%bXB3tR55HuD`zmrj6x z0&=dv(CY`KR`X&*LKgQwb{YYJ@hca?6P+5da|hFKYlR?vtNUMI8SDRXqZqe$HB# zKbq26AAC7v%Qd|5in96Tm$G-tB3eY4b3-&F=eN48%SK+YMRMrAGgEu2HgY|7uw+uY zV&tDR-RcA0ukqam6-shxHY1oy-xT=eP2A5%7-72PBWZxC_q6bGlpWFvrYu;4(0FzZF1Nj-Cy*>DRy*t$wk}ijT>Sh06 zjGvw4^_5BP?J@U;YFY7MGT|!t%+EEwpv4$i;YuE|z29Y!@O^#~)Xg}dE{f$bYm3KO z7&=_j)x6k?9QDbJy)2O+lkWEI`op*A;HBU*4@}7G+p<-C-`8;J%XK*G8+=|=A5XOC zhSab6O*M!}RxI}W$GCkJyYa36nz5~4;Ey_mpY-FGfs!W^&ld)FYaC$zX3o}dZzHbv zh_H-J#bq{D9QQM#%afQY}R5qi$`@S$nomfru3BR{ zY{EXhV>bq?+_}E9M;QJnzrhpUv^Hcsw9mnI_U1YM)yHXHO@;3(lC<^veB!>H%=G?M z$AQZeGG*^O8|!5iDW5a0kOveM{YXnN!{{`nz?vP_q^`|U*ZcM-LHM5Y)XhE*)yRgi zeT#!1f2=?aNuVbm)bXb4SD1A7|DJh*02xXRHUdbqJ(SG6u(p!C-cP`{@W)eR0{3=k8?OQX^nwZ>XncesT@B~ae5 zP_~k1D`ZzPz4l5_EE4xcCifvmrO}$YWP2V#bj0J=d3ZI%-DWCeBqS}VQsD@k9?h-= z2Nn)0nW($a!Ke-1yt?_qee1~1>!P^c=r;;QQDw_Ju2UtkPhe9YHkw$82VQRE05=@o_9#|5Vd0Z&MWE}Of*^aozJ>zs z$BQqi&+qi>4M7d)#P5cEuROxdv(?n%I&SXeuXDHWZf{tCYR+xS%MItJ%9mYFZD<+{ zeR}yM%XMovBCY4TE%HB7qF-BZ(sfV?NsVf4?0)EQ`avmnnCJIUNd#`mMC;2Sv$waD zHVjd%HO?wnTk7LQ*c1*mwL!hM6qk5QSUNWoiK?~>5Vin>d4rke;M8h~hJ(E1iLmf|{N&xQB3ryQoQdk~i)NXl z7dJCz3N5UQRDZC5ba|2O*bu5~N2%>ApRJM)@q&<9-|+B}@WCwq4x zz(Fb2{^WFNG?H#VdCvnRLtGH8a`{`Pb|*!Cw(W(3^a6E`r`GjKe$o+l zjWo*F!wk0>I%nnWW;|v>Szp$le>&4*ACAsmFCJ?@b?tbVMcI(vXf--o!s1{byzPi? z?(8f8Q^H2D=pYNppIzyC#0hAA1k0PhDNd=RQWe+}xNUE^EGu8$9&%B8`D3C&!3&rDT*fiY_efk!%IXa0+AtsO`w6lt9)V=3Qe$?h}G`CijcvtP zyW|ST4Fq5efHtzbIu!mXtu4>CrFhLUf?`4TrO?I!8hcvk*DQjBmOkvf^x@jGR^Ncv zv}JDttA-a(RAx3Xk|fz;Xr(xa;-j&jlP!=XZlSH2wsY~KM`!EQ)+(oYS6|~x&Ke;1 zDD0_vcFp)1hLu{2WGD}MMp~MN*%diCIs4Z}`LEv&7coL^OMn0V#Z`Psg0Mv034@it zj|YnF%S{zKR)02kltdr*kPHk-$+@3KhPHJ?5S1;@6Q8Tpp zzOM@;+8ody_UW##2t60E;C!+)jNjKQ!pNU>Ey6_U8$FLkp!|$y=)QKRTeo=#%)Vp> zJV>QiFF{Ecp^~C3Kli?uPNmnRKWcND?xHSpmIcu;C+P0I8!pN;5|i30w4v`HPdqEP z4wkIdqo#6E?}q#`o()mch9vQE0HM!9tre^^qqD9253G};JA>!dGdomC?-SXbpkGa3 zyEXlkm{m1mtw=ks_6wzE%1xUCM|-hNQ4ET$57MZpisp5p+`Z2Rw^};5%e0x=pSFF{ z*|vEi*3C&lwp-Yx@4qK~*-du;B$!C;aT?G5hPU*b!%vyNT&9P|j_XYqMMV!w%*Wcm zY9WE=@&z|qC5*sT$lqBvD6H>r(djlFD1IqBHLf*SadN3W%D_#|a>T6kWxnpyQVF=9 z7(YDxPbalDi)!hO8xQ^Y`n}UqkOM`;$zk#4I`wHW>R@B1qenfq}b2c)-~sl52cj0^i%uNe3iqXjV9dh(OI*lwc2 zdgXmTll7=!r$OUDxTJ8oAB^E{wlU}a&eakP-KFj8^?b!~!2UGUr{Bkl%W&d;$2QcbIB^)x*1(Uxsmd;2Rug!%@2NjT`#C&2>Z-ruhgKZ zGuhE01FYPiu*R)jUG}OYz|ya9*pLI5R_Je#l1+P(9XmE<|0VOp`k`JHxT7+}8X;p; zB!&6?Sz4l#3~=>@oLbx=L-}J^$leE2KF!m=;gEbN-hMp`K0e+K>3K&UE>n}X7cUa2 zPiY;__Zff4Z`i*<-KLEt>O3YMES-_W?3SBovC-4zm~i7gJr@MLJuG-KaicEwMuvlj zmSkqF+$Bof?kipn7-IJP8u1saTrnJIr)OV}ik)a)b$kpd79z&7Z#HZ-9E(OV{M``R z=#FavtmF&POqy%L__c}A%l6V!55N<`fS8$gZLUv6%Z|Sf#ExWvA1C}f6(Rlpq9H%9 z6me%kv*xOo4}DLg9+5mx4nB6 zBPg~#p|Fsi)r{$@UXAG54cvQ6BkRwxeF>r~6WW=;b{L0(2T}jgD^^YDhnF4kJ*OfN z49M8+82Y+m541Z`8Usf@4V_$hS9Q`vQQdZcdl~>dU2JL=X9@-Nxr3)XbD4K8QcvoOWnN!k6!&!BQaiC4xHcG^ zE_QNL-e9XY`oz)7lqD>;>hj6K!Vi4x$(gHr!_j-~-lMjbw@{6bGW996CHQOjosS=O zB;lnxrFs$Z!Fa1HnNE{GiIq3J z#x8VgGqez-DEHd<(_&lYd~%;XUjCEZ;#VD%B4x2o#{WsLC4@hL>EXi&@Va<@k&BG` z_%D!QvqPcPS>hp^C6W$^2K z3+;Tde<;=?;5jOocc*jt)yAu?!ootJf#5R8-Llzfc?!VjV*ozCz4{(SA`ou8wy7H9 zhBtY9%?I0sME4jWXY*K$tG_KnU7W{s&Tp@S|8DAEh%Lx6`8EreW!398oZjKiSp2lQ zYU&Q47bYI{>e#qAQ~t+GG&%27yiJBRu&(9~TZME6yyL^hc=DZ?Ms&V}q6@F{*wUr} z`*`DxXX{j*F~`=Mg)O#jJD$DHJBL zCptb2-higsxs3|GN@?I^4^2pT)Z9>u?lHM(m30eIk$LY}%0ifNN9>J>5I@C{AnMxr zH)Yl=G%|6`@%%RGatE~+XQ2R1Seq*#CnA;fk8nJumyJV9A^)tZfW!KSdhJbJJ=#}l zj~-p};}UEAC1<5dGHa1)_>G2JjP3gIIl<|Vy_O^pCM0fmW2 z#$u~etTbj*HEGSl!o!FoZJMnW-(hVK-p$&3nZGJ_JPpqWW;BUQD?$YO-I}NTK^q}= zp`X9YNY%;ySR|2}=m~`lKabYs2~~)N$G317j~qbHxK-=oSS7k=45XhCLG8I=Fd2t` z6lI)@D3ee57K&*Do`t6(RoEdKcI zjg%|2p+*fxE}IYLe4I^uV*Ri0q$!t)zMMkqqJzR(YJwNL#3SW`m0{7G z@BEaw$fSa@a(xDYub-byC-;Ekx0g@{w8?uS#NteH<5HhGEKJ@`J8~?|D#r)7eovU= z{39M4h6HQbrI_z{$EX%)^5bGORAdNSik6v@qWwT>3 zba`dqNiw~`EBvlylMj%< z`NVf}V`JkhIKYTAi9*za(`U><{?$qzGvV_>(JD|sS4YdkVf(sdu!k`%W=w>)mJE(D z^N{9|JwG>*$my&9UU<_<|Lw!-VeyDV$57{);HtjI!jR505n*+8b{cGbN^=!CV8Ata zub5${`iH3?GSTon2ZcBgtgm5Y#Hg9i$>`ChNm;VLb5c=wG zg*Ipj%Va~ARfl=g(@jA{Sj3F?APp^$HLA8|i=#R(oV*R4Wb8GC7Ul)pcWbJLM@HRA z3mHk;2jZyzYkqOS+bUeR*?V(i|LM{CK?c4h> z{P_ilCkW75-ndOLyCL*jowp%0V1Vb#6PZ27HaXxWyX>#(z`Kp-v+_dpGSjXLzm&=1 z^Fxmk#uzao8cYzEHMY#rHq#D5kCB zyvZ2(Kb?a>>Vqv$BIYle)qnR^?tv=+f+UvTs5NRQU?%8vR!j*yP^NzOq9=)@rAdKH zNk_Z<183J`OIg@xOXJmo;P_yz3Tk_Z>vbad!L1AcY9It~CoORIEK2*$L8Z%AAzJxF`HBmSwvRdA*=(_`0Z#}{=zZrh)l7fe`5AZR7l8q zn$c_Ff1SrtAZ76+5zL8z_i6;=f|}+pQmv)C3$|CfZsjKSd`fs`zf4xpmDDWC=QXC* zb2u__-VCx3&q1w$l-HaRO!Vl$HDKG4j`*BBS1)zjLd6IvMtHXyVP|PfTq# z&I5<;b9zKKH|gT;?=3q$y?!m{TAf~~@H{7ISHN&R(?4Z=S|Ug}AR+{tUVZJhdE35H zd5B6G5oxQDMztQeBk(Nd)Y_8o51||*E>?u?S@Ub{dVc%FGI*Le}H{sy_E0qrVx%_Rrg4-_f(_K zruN~CnEuSe83g5Nh(;!%tmWMTiQ^mRz)Y%NH}v$b-T0eZtndJ#(jobP!B``( z8vP0As($}d4)&*r-SrAiA{HF|ZT~Sln?){BNUHk!`4x(=EH>c6Lg9vZEkp2q3M?p? zfM6^U0ZVm+v255$z6?Zu*cgJsAY%@_l?3qFB8HPJ4IW$TX4eyx~EL?^6wmW3H6M6~n0V7ecc* zZ@G6lZISG?cqu^LVEh6;xL_DZ0y7~7 zu}K{=F~StRP^d4FY5MKzNYanwTeI}37vS|Ib8v*`vELc{Pc%d}$8Xyjfq zx|W}TDDuL+#)SA><&;fd2L@3iv|)k1tPKZ&kFsgv7ixP}3Kp`pEs_~@d+)%#kp^NT z?|pon@@*EpPGw_0aAZs0N^$9>ugl#GtdD$UZq9}{i$YXHWSw>vx}z5l0R;UE*D4~r z@D=xI8$EMNbDI`V;clr6?oo|~zO2iMvzTP}$l}gj+lrnbDo8VxXfXW>C$N#K5UgXg zIk~uiWC@+M8S7HI@l9nH1-ZKlCeO)v7(~J1e8j?bY5Me~IPIr}gT^JHvpx|obeUf4 z)*E}8SjU=!IYi46Z@;vzMGC^49-{UKRtmZxxIH&siVW9-xKW)48kqRTg-{7X()pr5 z%G!EcdE3qky}q#LfS_`d*<9-)O|7Q})i=gs#{Dryz*D_XDBhg4<6*YlON*hQ%`pe? z!K%6tx#DbiPZJWSKPeJNU<>-X(w-IZbOQjR?s$QziKKi!xA0nUAmd)SHJ2h^i5Y)} zduWD=x}JFwhv)CboT~gu{}G!abJC8&6kRNq=?9-^qo3H@^B|81=)c?w+>26@lDFy8 zcPE}ejG-M*H8Jk{!D zjpXs{=WDz(3OUGdFmgj*DZs=Pcx5j5d4$r4hK!lB|DlBUk^*TB_@q=d>aIF%PPVGl5mFt zS=I8iJs}2~8&eYf;ze1gtuxyk9v^dp{07Webi$T!gysj>o?w3d{CSQO1^Wh&B4m#5 zk>;Z^cyil~@dU_Ne?xtW%moP5t!`}em&b}@Oq(!MCbO3CDvrv#kQSL702<5 z_(I{lp_TQ5JmpA51kc`?ljW}DGxvfBe}Ak?e+z$*;z;CGT75UJY`!C8rx?yuEcQXk z9nq#~=isP=QNwHwL^R1?Nd_uSoGZKq)Y=P6J*jDF1mxt)faLwu0K<3Nwc$uu0GO;I z0CR+9(EiIG3ak~ueNky|#%{N$I&;5!6nn!A)Fcf-1?n}+x~SBuJlt1lz;Z)YLhQ#l zi}iz`zOLo5g*UO5Q3(=bmG3%Q!laE(a(^#_p<+ct9jpX3+$_kW2+KM@ZZN`rH$U?b zUkG*Xs_A}Y09A$pq+gCA9zzg>k(p`4UJ;D2>iuZke*ufNZI80Nyd16*Ux>(?;NEZv z;a>~bE`GfD0~rjEB?4>X9#4vLhg_e{W*t~_4r(p^U6Zn49a9W0X39yloE^OEl%urq zQG4EuODIoJhX7};PMCl=g1K?{ST}$bgqVKbi(g7UFJZA`#y2f}!(X_;8)EiGEZkpx zosvI3ZantoY@Ey;G?5}x{EiU1E_P~%4>&Uc6cjTXE3RpV1QxOUHuNx=Z62ogj7IbW zy;e3qnb7HG;|J0Q4eew91vtiZ-qZbBUB48wfJsXPaT+eQ*r=`%PyGpnIYL){neq%N zv@9k>;4*6Z`oynnx4s@m!=T{RN__S`FE5Xyw6PAp@hq4eF=y9>s}C1<-_n6L&4KBj z-v)0Iox3#Q0+)1&$5WRROtIwL_XIRvFN1{P9Ru|%u5 zKglHsJb*Ft_TOKT1K}T+TPA(Z- z-`IYQkHMsCYiEafs|v0?0M&c}O~-g0gq#moH@6@h48Qm9@u{d}!7&r68MZzYI)O$0 z5S8h383U;Xe!~5X>EwbRnqYGvtB8lV&HzzI7aZvj(_z@ap@{>KM#2u--O#1Qc0`Sz^{3mAsbH~n9mz0HSctS7#C7Ymciw(?L5@q$GS{1^mH(z-EHOif za;&QC(QUr94z#XS^~de1#OoBJevfl&f`&}l{={45e|0_T37~DsF*lgc9gvvO`>Qwm zv$2cD+j0QS-s8gu>}MZH9~&DRkK=lW39vfmpN8TIBSIH_}tU9Qb(Z2 zpWiPxe0^-hS5Pwesk!9LR6yM?KA!T-m095$1_s6-(4T{&Aa7_$55BdKIc#;04i~pfdRaPBQE}c4j(xbb;;!)p3%Ylg?4{k5Gn^vM@~R8ps5 zqyFFLNdF`83#`wtdxSS{eipD}0?yQ3dU_ZN6oW+u_zcd3F9;VnS7$IPrgOl|f1>Ml zD`5f(COVKQS=;5_fK0`VsKY9vtzvwu5smixx4MH}42TzRAZwf`ss_$sq(`fMGE7b( z)=C%W2em9(HSs`yF9g?9*sODV?&q%FlL|LZLhM{ZK3(x*H{8BKU+@Y=tG3q?8`rcX z{GGqTj*+3HCEB>=lR(LuOYDBU$J{P)PPWmc?^lhob<*nIhuUB)dYP{tgewW!er45!UrV}+(7awEA9mwTuqvNnR2UuS? zxQ-mSo^G``6M7=&MhQ26fDh-2gxbxax3z&Vw)+=acnC-|#|o0?%sFMry)96Y30&;J z*1)CYtMf#^dc7B#8E^?dhFS;i8%WRQP}H|>iEC@$ewN~pc*UStwY8(;E*Dog{2izh zAvd6dnaT@b6jlGygEyox{fOhyO8*adrMNic520#yK0J3Agmp(Mf00QS<)mKGRMZ3KSQ zVsGY67#RoUPkQ;p4)7fTbPy>dz+%VcM=4}wFd^{~bq;^z0zL$gn#Exoz+V6w8Ego&oxb zFvS5R+=PMcLa0AZEjJ;@6LK~Wx47gvUvVXN1%G!TY0B=}hi}r_j#OSHo%Sv{qY9W_ z?DI1u{-Ij8Ga2?HoY+)A9!~;ZdD;H32^q)a^W{DYC371JY|LrhhS&MrjHn5ZeeUv& z3QW>s-9X3T8pjwef>q)e8$A6aKemHgYUdLdHSGo5l}iuzCwk3naGGgrqSusf3}}sg zAfS8ZSYU&|U18fdU2w8p(*~z7OP<6~DnrQ0g+h_r!RmGyA9Snq4GgSc$Vk`Z^5x4g z?tGVn<0AlC5FMAsDtTee?=3SxH3i2pJT5NoA}l=oiJTlRq*k-@6&K>U(&anF#0VIb zCX(S_YA`?ex?K2kT3na)q0EtJaE?Uf4V_8Y8pLesl28c)jKK=d#Pn=za^DTXkRyT) zLBBj;^dBSxCBRBvUTEtI49&1{P{RN+kTF5~2}z>Cr~bPeV!O#LKfu>19D zo@PA&%%EV!Oitg(C_EuSW0FxF)X7-3gaGI=9j_`^cdk%!a{EC`^ryb(4T~&admCWU zUBxc5lr{9|32{E?PAC+RpYEfcnnM8r)fTXS`==G*M9oP;Gaz#xfQ?1)qPhg&kS)3C z+C;4=g5tnShuRL~O9Q43DD*rh!psTG^-;2b+y&B_Z5T(qrN#vrAS_>&Kcw8&EmRJc z@TFZkGE`zl0SWgkC@IXr7i$qyE722bx@`NNsyYitGntxm1A&XgU+JV&Jg#QMr>4ZN z?8lphbJHfJ(vCm2F;WSRpgBh0yAIRaJBbtB!Rz6Ku{2}ib&F%qftpn zMYY!VXgyL^jDvjAm+hs@qMC}1_EuH+h|cl-yjxptMcc35@?SA7rQg!0m*z>B$yk$` zRXxKBwm#e0OK7Z|-q0$kl}Bzj@v64BUNIthdhYr6{`8CE&i%Ar9ehB}Z9H2&E}q)J zL5dCTX;DZ$gMt(SGpK3$SeqB3*eRy7{OA{L4pIm?fB;eA`sFA}MHb9GBA+<5{*)lW zCFGt{Hpt<1x>F+oF{BA@o$Gx+o12}h@ZeZ3|C|J5AiDAzL0CH@4^I?Cunt2T&&rPp z0KY1@W7&hXFmD~jymKcI6nO5M*tV$}@eBmK;0z)vykS#ETIOlfh*E!} zA0E5C5Xce%Ld8@@r_cl;7N&kXKw_i~F3~}-tv^qozEfmh*gh6J(nTes-F~VfC0Lx> zYXjOtJvkbJIaNUWgR5L!of0(p?zy|C-MAl}(O0QZmi4r3T%Vv@u#`rm2A4C?eb;Dx zPw}X4Lp6#%-r!*C6UIg`=GCN&V%9ro6<5{Zr84nqD1P_lalbwxtgyOCrgtmp>V*%| zfWy%dYQR=i&gQy^qMk z_%uU)+Vx6BV{S_X9xdx;wctJ4zF}a>O3KJwL9S18ngh_rnD^6$+EEiil#;nsI(E`WJp*}Z`}I7BqGZA9Em>>N)W29VPV}C6s-KUwYzKUemHd%Q5%`- z68{+o#Q}_#0R_zlEC!>E2czvj3)-+U2nS3=SG9)8`mo0mGr&g!5brmPd7p{` z{ZSt7sb?vFyvL$652669@>05**)kP0_0-o>{za&P?1XkxmT#&76|9%0@!hnzvHja5qKEN{D@UCWLWQ0dX zK2=sGhRt+@nxCK7EmqyyVm#XDL1`{*Z*A3gce7tKLT(KLSD@U31-7vFftR-fYXE=T zJ%~|~Mw)$mY*FvpLke7_KecqtEa-i)>EY-~5Nv^uay^(AGB~&*omNjF=JZ95eVLI=-wUM}(HDTD;8rGhuNda3z|aC@ih7pGp#UGpy~GHplE$E0_nK3A2F{e||hrYy4Czj1UTB)tAH!g_9=rrHaJinjR3Xvo^PF~rP$3M(nwpxP zFMt&hQ*ePt)o)AEvT)&J)$CDnO$)WZ{&`6OrZJm*??&pu{6>GB-<_BbU;lYyS zdHRjKyfb_yVQaqoEDvxNc=hB>eYdMw2uW@}x2&VF5L0EDTsfb30J{>fCb1s(Jagl% z#^#iVD+748iiqfWhrrh4<^IyJ>UYqMZPkSO9T3PY|)5u zvZT(xPfJ8YlXT$(9oFZrr)e#aRjLd}TJ5Py+93reU?I&Ag#&+l7IrCtWVWv(iTTP2 zDNZl;&OIg}_C*RJAOMIq)*zpEr*UGSnpe>KROZpr<@*0m0=P~P7(o#i`Qh`y*34H` zmc6qNDb3B)g0DiO=5J6~RH4ZWef{zt9>-sa?T>KX;*I@eR4%uEqR&j+m&X$9yx>Vd zo2XE0cqyFW;Y_fT-=OX7A00czIScKvS}M1OCy)!EY&wv$n;m|z2_es9rNOlXj^^8? z5RwBH%64O*yn9aeZ-2G|La&bLg=l7AV|tTR62d)}ZhxCbv=`GR53kw!cpPt+-RUs* z<&R72RD&Ko!D4M`SAmLeE8>@%(LJ>=8!&t5y#L_+e|gG+S!zb(Lei5`^WPbv65Y_% z15iEZ^vRc(ynJO@hq2Y|x3GZUovAB!+0}7gRcrikl2>`8GU^@M$!9a4#KAWia#Y9r zYij9E&{h4(&8kR8+ou3>M4oH#&Z8Vd;5xrGs9Edo_Sv~YeHWI*x<{6mpGwqr5g%G| z+X}q23IvD8q?rWMYUVoHhc-Ui2UiZs%g?8ug`r#=}MJ)#P#WSe&Xfob;;PP z4pgTdH9T{59ZxbDXxLv%6M&&YATk>Cn10#Wnu)u;7UAHGJb8D!%=99|O{WJt1e`bq zYGt$ymYCu7iM3`Nq@<9c%H?_HSO{b=&g1z-a#PJs9 z7Jxzl7qfu<>My9!uKTzqWUl7=NJ^Ef-gAV;nGdzzU0Mag$L?LI&MbXMong2W#;kgm6T=ne0yLRi?3x)4evY3?MCW~3| z@N7mFCkR71r;6j-D}<*Zo>~xKiZ=hO%VeMRKn~f+({F|r_|KLSgp%wk+6GYFEwWXX07Ok1mo`u=cshNCygW zEyl(^;XY8sdHhqa-|bR+5_3_dkKWbXBOxgnR$g`=KFU*ry{kn+g(M!TpQWlQ7T-8; zoRq(K*D{!QW8wr@Hpa_7_4lMF2lEM0WXRc|jSjBA)R|+aS!ne6f0nORieWBD(DBX@ zTNhb!n@|D)1vj?QPAR0!vhYalLdde0Aq=3*C?TTPd$#!YkU_!o2Ou8|oRC5NMG|ge zxQ})>CX!mi84BT>VD71yF7p*|b2gW(Y*@trgCsw#@zl9kiFZ^>rxIU)9_ffA=AwvBXf7Oe;<`dywJ0&<=_H~C+Oct4cj(q zrFA=|sR5bYbF4Uqn{XmdnJSHiEDrmnupN~<6R zyA;p>1lU-ds72-5K_&-S!A+z99kXNHX7DC(cO=6-Ym@!q8Xv_3x3mGlqQlfcHrza? zdg6MCWPR@|2+pJPnZt~=M_68OzWAe8Q*YVM^3CNQu*#?@h&wy0mv0I75iyk5ELcFt zQpEMdL8$Wdm6y%mt(^zIGnZ(-y9-kKpx0jlwN=YLgp5TH7MLR?zj=+tU5FP4H+M@@ z6dqW?J-b{VWkbSFQ|sy7Y$zvfQR3F1)x0~oMera-{q6_a;EYQkay9KsVO}>dP+BDb zp^<=>U%nAS|4YcDspAGrd0h0(3y0!sk8_#fU1UIlfxsLwF~4AE@jO|4Bl;)yf)%NM626Zz=>!nVHPsn^?9HrCbJ>-#4m)>$oW*RVY#$@V!@(yzV&bb zt6j=wrPr=U?!nYqt=JCl7Zk*ctLHrnNsViz-bqPnHb_n%L4v#l6P`M!+m0L7q=)Pq zdvvfv4D?EM;#@>8h6WNl)7j*tPezEXTJl0jRdL%U1`6%2yeYRISF=ui;U+utMg~*x z>=@?@3~$bjS0F*4rOE3YHrdMv+u|{(N{I*ELA$&?pCase)==@I@hlJCV(asmWQYsS zDRcyxUa`HnN~EuUs;%1lqXE~lyeU}oU8y_JC|UnK!?ER2bTo}T_< z2PXf*K7F!2iY3UzGUoB|r@u7XI}EQ2h;OSSrQH-7(7%yf)9Qg{9)JBu z4!f;?pP^nukPX&17X!~TGr9FIE%n7|8*?D{a$vr|P2+aOdA9^!m%kG6{4@Naws!$; z1B3`L3V=sdFZA*JBVrmo^;$&_^-ZvtWuC-G+2>s}Phh^+UADBz=WeWg_8?ti?R4#u zC>`%!ImRA~uazj?lP( zC|p}t0|n+N>f#J8psb%QcI%^`v$M0UHzb(uc;B%w2%i|wuo0#UZor`w(Q>`NFk(?V zmzj~ct?P~ae{}D>v^)_zw7x<)5mCC~Tl!UFDv5#$_v%3nCyh%m^Y!2}%~Bl2W0AJT z@8kFCVrM>UI&AoYfOn#>Fjp;nJeV7jm2FEMy2MvlI5?#z>RK1?Ap>w4uwL$PaQqJH zMEYWC%2hv0DY)KD+C=vhDDVG}SvF`YU8!cvpd2a0CFAEIHs_O$PM@+33}VYgq)}3_ zkwh*uhTB9rlZ`Vv18_W&>$T3qX=tPEYkyzl@M_(ZuOfc=@+FuEL>}^a*S}b=mb|ww zaeENrNmentMKu&!?xm~Daa2pTG8jp|WE(h3H}bi9R`hPo zIe8^{$)0#LxF3H>Vu#}ZS0I)r;;d!}vAb&(cZ zz1#i$g5c`C+1T31x6Uc`b!Xn>gd`NIQEbS%P$K+m>ObIA_$fTx3ZSVugxpwGR;GIr z6dcT&m+bSq-`v16k3#h8rE@~J=LMgF8DFG2_ zq#FYhkt-k|T_W9GhY(N^5KuZq3F+}RiM zt-0o$>!C~ZSGYZ90dZG{0O7O3Jv@B;o3W2P#v2oSb2if{xh-o*5)*qHlZoKQx zQTCTB>(^tLxhHdlcB6bTmw9+lU!3m&T7~*1K`{HFQp+4<20VtRbl)UyycFfHY!{7b8_Z&nm+>`}gt4>Iu}RsKA1gDiEI*m4OO&|eP-~Td0fnB_{lKK_p?C_M#CatQ%*@GE zwhoyeOM&He{{ZGzNb&lJul=A*`{FI!!K8v_PVe7~&R(l+os+Ek*E2r8sp z$P{iD8dIft`*{5^Q2G>5&a^*#NGE7db|1TEKMHi;bg`g6DH~@DI@31Y7uIt=7NzdG zfoY!G%P#Vj%^X~`+i%m0SQ+boV;m`AJ@{WJj<+6f9yOLGC{9CDkwwGqUng!wx&0^W zP_r)T>c2?e;z9LSl(ziSK7+=YAKwvr$}6wqSLA_)%(reORv8v2oU2xMd=8w_mcbAy zM&d#B>wo-UyIQt4@4};cdY6LtFYeva!AEEOIU2MI$zLm{T0E{ew5<>*MNkUM)q_B5 z_2_(B&n|gmY10`81(YQ%wemwAC}xNrroH=}XL>=0=K8N-hFiHw3f=2QtC40m*wYVZ zko<&f?Q*5&;RA+`YFg%BvuUN%ov;KQH*W)|IlA})9?fx{$<@|U(nhECH~0;X>RFhwz>Kn$0QfJg=KSk1{=LacI)0LA_Ty}xO1k0I`rybm3&8T}6*Oy} zG(C?wu_Py93Xsg3(aqWJ#!tSY`*wM_c=zCdTxBTnCBcsW$}dGV(>iT7*Y=B`S`6oD zd5eLA$VeA@xa0e?HJ-Z|7>^L5R3(OT-v$&8BBUG%lUN+9W$ee*xaP_t1#3CeneRk` zuvaaVb{|hsDUa>Ta9weUW)%^M1h$D5r`{c84r_7|ME*d-1<0}5PRe*wCo(iD#ZRnc z&T5_zoj~98D|E(Vz%}#T9VSsU zsHb1JU4;Su4m_8bMWkWg9FEkJCVo^xId|{! zi;GuCHfX3y0Kd2}fb}Wb_H?2~v82n90kpGl3u0l~SJG@TRi;H>|9ni+E^~_|VgW^xenQgC+|6&KsmbB&Jhv^e4b#*U8u) ziJ~N64{-z@6T{3ux^r3v2kscm@TJ%-24IzPKVBqTiH&2B^at6m2$wkf52?p z!8*Eua7KGon0b5uc)k?MN7M9=BFOuLhXVyNG(1YK5V=DC0-CLC7I(?f81PpVvg|hj zqt5J-ET2Ni70%K0@cPO*DUz#{=*e8?kKFJDQYAduf!T3iGIM9S52VsfS*x4mrBB-K z2&ND59^k%MR&9cNZi8yhm!r9@O{KgLD8jos9T^h`U61zy6p)gLEVqmphP7Q@*AAUj8<_&kRa9(T}vDfQ`L?mn{)r;<)HHp)`a@dAWh))oJN06s|Kr zk-sTh*SHz`ld0#*T>%ddyL)j@X69xdimq1SApdv~QjsVX5aE$}qmBrN55tR|+b+80 z8;PNSR}Swcw_#T?(@XzL5B=X>5g+Uk-$P|p~g%1Iz2 zCLmxfK!zkB<4wJ`1x$y-fLaxrAP1kcMh~@8{GbXbwU1{(ni+D$^a0aV zr2)5WI7Ehtcahf@5?C(iu@~MiL40Z=c$ra7(2#}9r^trGtn4cNm1nwAKLdA;y3!Iq z>@!Q~w6(G1@bc!Ii)~Y$Xch~SHmD)+ew!^NrAl2*H(QzT6!_9%4zh97_ z@NBnFuUKj>5oLg%Be2ZcYH(Ews@eo1u6nwauv(nRjm+q)X!dzJu*0ETT@Vrd2dJ`Nrc*7}(&Xj$7XP{D+UW(}7e z8K}1HN*nwrHxwR20eAbtcKILZG_4=-M7F_vf^5mzwR%8{;wXqZ=e-ZxRFsDVi>=;d zPY=mZG&Zvcb0NDk;Y%boq_Yf_SB7JQ&ua`5H29KIVF^-^ESGJ(a8>1x)kqw9xZX8J z*t4jzh3n*c_!%(&J5a|%Pn#q9th2M`+<)NzeVvbGLGcy0#L-!&ZTp=l_+a?x&Qyp0 zvUMmwDRxyka-p2ykFT(o>iG>NZp=X61#FI$^1>Z6EA z)Z5muuF*wA3Oe81_U`YGadgV}21?sDr>HoTezu(G(PnqqdNb?Zkoa-6HTB7@d~IQD zbNyOT{tkygU?k*%;UI96(CTKh?bS1Fz;frW?oCHGQF@w|oI5@;>u5vpMcMl$iMJjK zXMcSxKH5U3V;opZs-ixwsVNua#|K@lahM3$mEr-vI6aN#T%CTBRbt`ClEU|Vm&C*9 zib_ySOgj(>o=QovW{q`p1OQF?;cx@Iz{P#72yV> z64ZN-{}0t^@2KwAugi0pdH2v#aBkM9Id0sKvJ^zO*_t17VR=@xS93XDbGGX-nP1t_SrYj6l)29NGOnyTzt|Rfm6~^67i68k2?z*; z!r&%zAkCdQ@O#7nBL!{<3zwu(!z@Y!|Rc!Coq;RfcOM{N(Wt_Y=C55x4_h?QD=5yS z83-nl&5!#2#0w@}eK9j?g5yiYBb@fUV6?*neFsa`qV5V2m6CNjoFYYkSIt(Z2^yvC z)Kqe>4ObA{HzHTjE*uIMFrxvJEkHX)Z2G!j(Q~Yk?nh`~Ocw(5{GO>ll<_ItoL<~c zs0-;e=?=Iy*3M!(tY?>Hy5bOX;wVA-e8_9L46?G~cYOF2)EdqdMK4OrzPd-hbBA!= zIM?;Vmly{}a8Y5ApAcTMjpq~;-yRVc&0M$Ne7SpQ)n7zbaX_N(c+Rwp45%LbW1D~9 z5MD;0r}y^vmPl~)wZ_z^!bZ@wG|HDuWzHZyb)|F#vjQgA`uS8NSQ?{K>J&wf*q-mU z8&LF*n`dd)eWb6g0&=+`V*U^xbgIIRcYRD?gmw3Qtox|@P@^y^rBSx>22XAX)8)a~ z2!l86gK~${N7QB93s@>7iDYP_oByREJ#AIZHg_|os?il+n#_@_Q02Rm)xAKdqDX(e zm&K+-;zZPL*y6E4DZ5bcIpuU-ZtM?8XwF28@DF=lC8!pxf!t>80$IWnuW_qlO2lodc##NC-vuYlh15;OiGO@8 zy+wa;qkz6e?&vzeuMql(4rKIl>?dC;-BFeGBT_zh^_fcCu`B2CKG zpM2fBBor`$EueN~x@{SGO?(&@(7t5LUhZvvnl<+cGFS49K_+;bCqRQ8;nNY}JuoOm z0Fd+dS2&QR#ZndG=XCpHNP$DI-KalCd0Y5`YT+x|(L+N<_bvrn+7>q3XI5F6J`sqP zOgBx4OGk83O<~hsx225B+`LUYNf31`+NWlx78t-&hR`Ztn7wkH^QBr{kE`tK;`N`J zhC+kRs1&Lb5M-(ompiKU^24P{N=Dt$5PEMLDUdVJnb5d4;>}8soAlX`*9wFvvcI`s z=L)RU?=!s3-B>u>ddCjeTLT;|4S%;`>%RPP;*FZB8SfV@2pM;0jNH{w5zc?0E{WX= zJ{CiIj^{D0M>1@{2}kiAByJFwCo+h1P~E(lV-Y7ydvb&168?*tDy*3cM?V?q={*=J zE2v4G2rI|2?zZWf51k?Sz-IdqTnAu0Sv#oyRAVxL84M&gLJ5h=-?ob`dRFRKN_3cB zKr-9BU3}=6k8#0OYJ%f|{@CpLfvAcJSCZL3;}r%6jm`PwDczR};D$LY?Ae_if}OO` zS^He$BJbbZDuWZy9jHd$*S=hR8VX{kVf#rxsPaM+bYLeW-~EJuj&SQA^$*hvEv=32 z1n}C)y;A@%E^xj*h?vLJf(9>UmHJ&Thwb^uwtAQX_`29Kp{2VgQzI6%OqXbA)SJCA zi~Wlo4`)RA@%Ta`v*qsRJ0ZB+k-InP-d(9oBEN=*rzQbuCNsM8`c7?CoXti?YLEUz z-XhGj2akO6)E;F`6!;YY+w3MUZyp;G&Y$2S2nB@ohaihc8~s{Sl56w6+|W9F=J$Cd z$fO@#!y6^n*tAcd0XUN&Rb}xqiH|4HgTes#Ef$R)FxCSLeb9)0%R@d6KJeVxeCqp5 z2NBv0cFH}HZASQ-IEns`S+SZnJ;`6=1 zh_Az`yQZnR3Jd(!xuK^J)6$K5_AF?4#^4Z8{F0~wiw_IqH7rbRfG(skPKbTb*8xw` z&9kyb@kdqAcnN==`l$3a2W%zC8{(75i70>z>Pqo)D4vb6auEK})d>9O zBH%$vbt9h#5Ox(H81i#?Q zHM3F*WYrITHY@gUGs(`FHzUyS?QEd+sIt8vicwSMLnr2`jTD{7d0fy+h~pzE6s-q< z!!T(3OrtG93x=Gtj7u1s7s=tzUyXDQ;;i`@69YeoH8*`3wv3jbmqb=3{<=uk`NRM` zslRo?#-K2cv$QkY6)_oFC*Qi1J8G3z@|8vD<6_o}BCwNOfic7k)TcRnv)v zpCeI3#ONu(!_Rm1sBUJkIf~|7d9H-!CMwgeI}c9#z9Xh~4$Y{e1&v`4+9l{CfEomo zfh^9ixC=75;5eiZIh*nA?|wL}y$a)b909kXO3&+e7{&d-2E}P-8CuA4nd;@Tye#nT zUq7r_3SdS%2yL2Ps>bo8bljwJoC4W`JY{kC>Q>l9ZPm;45#?+4z;q#^4Om%WVK~|aX zb^`KudImqg2psM$G%fB(?SWBQ@yf{~c=a7PCs`f`KL)3~+&zti#GIjbsOq$o4>dxO z=qoT`-96msxO3g+=>ue`e+9QzS0MbcK|P6p82F?3!TuA5=d=rY6h(m;@sX^y;||jC z0Z;c4Lmp_TSVr^QPmP3Iw(0)H@F~`b?y~B^`NEniAdSUAlQRmcuxaLriHR^Uk2yY$ zQAQ%jF>El)IH6k(~L^jb_2M~C3OM{;ZNva`C?vuGVJ>8 zwA$5-cG@@U7sr)HknjJ27me-}x-f!cs)AL`7abA{auS$fV-7B5p>hI{86rXQ>eZQ? z(@oSYW0EUzQdVqGJceaP@_UO1UYA?8?A~Ukcq;x2${Vp-X#I{I%y;@D)OLD$w`xtz z`(utoAFV1r#+71p*77REBDG7ts^0D$u@W7S1`L!t6)rGS^6LUS6gXZLtoxXV_ugr~ zXLYvDBKsX)M6*fzDtbKI^w-Kqj&?`6#p_~V!<)39G5ZIBwuYdU(Mz*`o8Cys$ zZ7V<%j-Wx|CXu*2D_EsHHt9mwO*Nv(&)Wi|eJMUEDf9uR9o2)tD4^CSgU$oI8<>HTcOuZw8K z?LrEx-@(ms3~({$4L7WT5ie+rNzl@Xj7{fqHld(GqB~MQz?{*X9Ry4IC!D4a1&!k2 z*1moTFCps|)y$1_vv3j+<^}hCg#L1BqRhIezi?t%p5|Wr9Q@o z=sePT1`mC-4)^NrYQ6Q(Xt|V02F!zgf(??%waEq^yJgkTd{7v)MhXM~5aSdF`v!iP zlUc8Otxicxuip78etGdR2Le+yWWcAfxY^KF*^%OqK-nGvdxfsdz2!H^V|k2v*$^qz zPC`-=4DA|5-|EvqGCfXfggplEK@Z)nPYpf-^-9gpvoh&I$51EKk!^mDlVKmq;~9;5)&&$R zQuYX&y+LP{a7f9?$%TWm4o2B{)x#tVuguJ=$;g)@>_Avq!Esu21CQczf>*h2oTk-h z%!}}u9Ipb5JN6(>jHH~v$eC+*wBPxZLV^r`alJF&K@e3+7x7!yG^x5LD>k|pN83SM`2Z*i{(7Nu%sce~*Z(Y4~>-p^sgk=WN z%gM38A6-pHEae;(OGoZzGGt8+P%2AtY~tNR!sVXc1*}4LH*^Gt6ueT%I)LyA)&VAD zz}}MT1vg?SVoz71NVb%Z&PK)Fn=Fci^r`1#>CIuMx$PdbY;P z%1oLI+JmfTZv}O-pl}2VGZ`8n9LvU@lG!<8U6%~G*9P$zh3iAh?ygpQ;`^#@As9%F zl_1(Dbi4sotT^_KaG0Og!?1|0t+`nnP`aCpS1QEqdR{4ZB}jz^*&+pKkK1WjS}t1G zs}bz*4SAgWR$?NLS7eDz!*!1lGAMOJ=yJqIMGa!dlDKaMoA2+G{b#N)e&xyV+G1|U zI%qwMULC;1m5o-OjxY0~@R_jicZimie1|fVXBAAc7`|fVtQUfH%zQGE(~8!PyG?gg zcVxl^{uKEXi-SGRA%)n0@flrx+Y!Ug+nXRoo>4NN^6vuQ{kuSYk@KG_{@nOL&W>WJ zas1x@J8JVLcueg9qFLULc(7lK?hhizYZtWA8f!{lts`jU7%)^jIh>W)>#H1Axi+2M zKr__#d_LCwFxS^FkC z^O2h#DsSCUKU;o-AP&~C7Wg!Tem0B!fiMW>IyA3;BS21k#DAEO`-{4oFK+KH7QIj{ zYr*5}x0fp@a6Siz*fnee*~F)Id{x;OmMFp2n8DdrO!tcGS-OTxnzaq4GJJ2EF9VJw zRA>m(N$-K_{&#^p$Oe>5`P?OD$n#z0OZRo2r+K+Ir*Kmmo!IuFja}XaL&~2F^e<)2 zz1&?wE%!8Rdq!5`R%(VOKq?0w6qXZ`;|~oG#=qmva{7WkeDlPYe=9m~ZgiYvBhlx){8LvO+l|;y_>P zz+uP9j)cI<1$d6dR3yg;bSjkmFu*RH;KA709}6w`h!_)wfflJGMT4_8%x@NdBrpTT zDBM&t@Wssgq}ArklwM914FuXfa1oKZe_R36gvj^o!4f|3e6^mJ!%l4?=SZrV(B(%c z>;Ardc?5-+!`?FELq`Pv;gwKcHz$F(=IL=adTCS)Ac%AN`x#Dvc{;E z8LmQ6B;>FjuQ&uYs1{Na&j9?Mx|(a)_lmUU8#B5-k{GCW;~$t}%op;2fHw`EDY6Kp z0&Dh>XMfj1BZy|rs%?_lB6G;1`T#u+qphj zGkVycgKf3EG~s}JOzSeW!~y#`YN(VdF#uaiuWM3K;(TAlq5_5e`cxB!3)$^YamNEt z8LHqW`5;qFn2ZnNuJfBETS&FeJyO-I90(+Gqb<(MGwv9xgt-skx>Pp!1Gb7>h;i5* z8rN8u2wIxWHG#vmR}wR*tuk;-)!~!W>9(9<{0OH?!9-?1n~-i&EUS4VkleBdj7OJ|DJc3PSF8Fat0L4k?PHnZ#;i<1o^`Z> z+6X`$)~0uZF69fTWUgyy^qFC{E$rKuKh7nFsUME)U$iL5-z#lXwXac7f&DNGuMen; z?eibPj?kXgtLoFy?6|lyFEV_-cC5u|BAyUgWLj}mePAbfL(^J=B?q0F$^HVE2`8(81XkV|Vy>AVSItWi9sL9V%+blJF$ct_rgSF!! z@u^)f0f6fAXBV0Z3hWQ8BdIIaUl$u1(j>2bBWz>=19rLaEDWH(Mu7>bvD|rtQUzcf&9*AtX6Oz-%0J&IN$`syh0dv z)uT8N@yo?`g*0r*Igh;@scPOedF*kWZOhi^)dmTzFikfH4qj-miKE& z4X#r4bNJcV5v<3(B^kJLjgungbdOHe|Q+G_;V^g_-r=QZ`Q!Qkyo7@lp^C z_ciTJcA!9a1KRhQzwO8x7R;zA?J9l?dsTo}NisrfZIl9!KLa4y z=)K*02a(tWH+|o-2NN(Q+h(pZI$GspZ4_sSlB#PYk%Oh27qrx`?vWe^YGEa|8-jSf@1w zJNPJvc3LkPyz0Ny%RM_WXK~b7EGTwmpr-tRkUZ<_5yInbjJ465Q>pqBgFo-xZC3phJ*um=`)G+S0OO9?pT@f?gbdMBg$(narlM}Mv9(e086aIEs zcndVyzyoO z9G)W8sqkQZR20=bZVtBk0UL0dm)gujI>Y0%ZR`c*CK3U@`4@^%`{%_bRxELW2O|-c z;Ab$+Z5DBXD~cNZ3MPiA{__3m%m)FFo0m4dxxGIkfA9Fz(Bg?fKGm7Ht#tvFhY>~m zy?fNd-~hpK)wix2_%2Uu^a`sz@U=$rzBjmdqGzskP6+qO4J?U5xsJ@8GL2Sd#fYw8 zQjhWoR`tslQl>wxf*^;t0=Q=``!5`))Jz3cNuG2^@e9hHWZoyFCBu_@707izyLXy{ z|K{Gis?4}cMxJ_>7ABbj>Ruw&j(FCrQ1|6Or*~SNInHG*6jKpH{03BR_HT?#G?S zdZz`a?6cFH^UK4AbDEoNV`Ul1q-}&E1eI1tq{Ny<%8o<#hqok&9Q@#=57wg8?AaVqe zX)Di8$eHgV+qDd%|2@2a_x|YLy}t_m;DGWLcz4Wce(-pRfRND2u@QB=LCeXDU|-L= zUAZ<-&aaRfVcBtb4W_HT2#k(ylNK2H1!wk%@H8}4AfHeC-`!V8WF}l;nj~6OLPx8^ zw%iBe2=r2W!TJ1Hi_4Yc*S3w(JX9f8(DdZyhxzC9vX1||4$l|Rf)dTa%{Zovv$<{s z!Btoc3GrZa)XBO88<9wlGNc|(oyGQAPrl}-4Ao|X_Z&6y?5!lDe~Pj2T3 zL19yv2;t3k33_fqw)UBzhvvp8?LDlYE$0n6-Rhe!EmmQMmu)^N%G3N zqM?KlP!z**iFI!babU%Xq%0PlI^5ep-bBc*M$_dGK7@?;-r3-$sS-@`A~~gvhhtsb z;Lz4(V{ZM!ZXeFkd9B(8@Har4Ij)fU6_c|5`oPVP zfC#ddr^{!L$W5RzYNfnUjZgbIDrzyoAJ$*_?-s@I2F|-)i`R}YsS1QaFd6%BqW2G& zNpfM5z8@USaEGu{nDeP|vB%quddns;k8WP&NJD8?pbFsFw0i)@=Bt$QZGn9O;wS{b z+1`Prl@%zsY~5Xu)gAq2w}QOTqEk>qSP91j23AAXQU1pdmW34$2PV6)iC9h%fMN`x zmzzq982rA8pl(U2=u_aPXo{SOx?Us`HcSwyV*>D`lD|`t?2c=!9(6DGJmz&53BB8< zRM-p?u$y7-kCcLfLR>b8D=$$k?(at-H{fsz=lptr*s((`71dj|)vE$VH1^2|Kti~^ zJko+tZU9XfNkPFlH+wjuuOLVe{NMGiJyR}0ht%I%KKUz7nY8fr%8Ry2+~-_lCe&SY zrHXXUg;5s2ih*uEY<$3Ipa=s|X$f@3Pm{Z5uhT=1Mw^QQsY;`JqhRtAR6$OdZx>Z5 zFdm;z&76Vs*tAC`Ml>ASSL*-XTV@Hqh`TLu?MD1A*sqz@4UnjDA@a)180o^W^D`YF zW*pEHq%J7zf!rJ6S1F|CclJYi{odD?^(kg|PlNdjav!8H+=L=KyPcMO3#Q&ffn4?n ztmEYEQWY~3L8%`l?lPFZUpVUM%M@c@Q;YDyVd3)s9MR2A7XQs!lLOm$e0)+Gcsm(l zRscC;;WQ#?jlxo?u+6NvM-EFYua1{dCpcudpb3*~7} zBHF0Sy#^6dIDH^n2VW|LZB6zuY}aU)lcOD^VGlu|G>z}NADE6XAqGS0bEoiRN`klA zx`)yWL06|(;2^Emk!x_lIp+#vnnqNCX7I4(sn6dZT7CC-<|%jHL5IF`QDF}xg`iG$ zh&vJ&7f0}9=q`nPdyRRnNthV>-e<)1VChfV>u!TKU}K!_OGVI=>CJV<>FVi~q-||% z=s>U;hF^;TjRx|QQn*YiOwAu3jYfYzF^AU&3+X|E_gHxs0)yXr-LzU~4)&(%F1beI zA>nA+iJU*s_65^AFnW2V-wG@Sn5^>woU4Pj5FlX!``^2&1pb_)qN{G*5wWP_Z~zhN zq^KdjC1e@hQL<`zlY$KRgs;)bR#NT7!^O=uAHNS4H7_}4F?*HFB4zr&_xg)@@p8+` zMf$JMz;<&eVul?n|NBLC(u46i$DR4iqI2~x@W<|kCP0hm3ai59Ju?|-H^DTCM9|+d zE+GNv7sF|WyXJvUWA6qTY7!q9o~MMC!258%ZQLzZ!^f7< z6udU$kNoFIf!JMOHqi$P>CPqBB*vH9!IDE4HwsBnPmh>jpjg~#kO9=w45V++;6 zC^myQR&ZvEEcDjE3m1&5q~fwaf4%@+2q{S2qy~}X`+dV{f;52(1b7Zwm`$~&4IX2Q zU*R~9_plVy+_PG(mDngi#p3wVuxDKiDTVR2q+**FI>ZmsRJK5mI+GOZI+2o^!Lo*o z$AzEf_#^fU7nbwljZCYPj10cQ`!e22v%4$&E%67MMJxM*!yY0cg;L_%-k%aT{KR7a;2FR(m0JeURj3hod0M2?zyIB6jVV zr0b!LHlZ^&fh>TCV=E36s~0JVFyPr9nDGf&&;1vd4J9_>)LK&;J=TbPo~6*gC1bqf z_v4zt&(iJ)M9&5uot!qn@xJ2CN3J(Ud@lBiSj95V6LEB}JRn!#D-#C4Rq|>K9)s)A zmLjAcGp45pCjE3~izL^c5PPxpS2XyZIORYXP5w!e?k?kVf9CbhJbopK7_Cj}CY*9W zLKA?7LrSL-Sd3f~bL54#E8}0J0r1P{-zMWV;<)Kda5&;l{q#y)HLY}7vBs-|w z?jVXG@$be=>xFj9!%qzjQ!rW6M_n^fXA6d@qP3mM>azkbFh#}|-03caMR5M$;6;Y* z0&)MLrl#h*F;XY==}(zjB)U;1KMrQ&C_1&d;1pwl`~$2QAPG& zHA_b_5Rqm|@YrlkpH6kebV)Cg=D%ZY_%e4HiS8BP;l*X#&jt1~QoNza3sLwFr+s>h z_WhEIW}lqmfr!{I!~Au)Aty|(Ou*e<{kE?*PQ#amG#%fL&C78~?M1#p_bnK&UJX^# zi0~FL&XmCB92BgK7$hDZPpu)ZdjUgq2ygE4ckiX=$J^j**#zkLKmR@IJemNvwVITw=!Hm% z3zLU6UHG)lRlv}Sg5`T3IqFCV)A^|UTSl62!O0=`p1HPF*Cd}dx;^2-!0@y@0BmB= z=$(N40anr-p9T$>8u*&+5hJ}t_zA7+34pka{wB5TMai13z)C4#x`|}#mO|;roNV?N zsBHW4vGu~Tu(TaR=jFj}NfVAJ3L-qjnwjlFatDzYWZ6d8%5 zFJOZ7IVNVgH~D0*1GU~RvZDVG(9r{HHxv}{;$@*y5tqeo7 z=qGR$kWatJ2If9668*sHz}9|Ot7utxcUz^gY0Z_Bsc<%(D9W;EZVfMbeB0}S^3Y#i zI1b1)oC9HwG)`*I{CXaED1+bV8X(jE{}t~#9*_LZA`=<0L^X{SD6uIsDoMttWDjYM zkD+J#hEoLunw=&TmU^cx9did%IS}3dd;8OoEA*rPrC!Us7E@^eSx{nVZ1)4Od)|Wq z4ol@VGFc|$i*?E0emn%M&#j)(E8HEI?hoN9}JtJ74l$10HzvoZm!umJ^`O8Al7apZ{(}UYAi+7mxuPs*UL^^)wy=}X2(~@ z*Tqgk%+L!Knr$48Wg|)yM?$-Jg3&Y3bVOWN!E?CSW)M=O^^FdRZpZ|KFr-y@UG^T@ z`OKwaw&oAayAKwg`OmUINEQO%6Dg1vHwL&>HML)NNJ6H#QV@MHz|w? zYiqoKc46M9wmM{nHvh8dOoD&7%FTB4c#RKgw*__F9~&BNsQtf8NZ8a8P~?DmK>+;y z3BlQt$R`4y%`J-Oh+nVge7a>{J1b{dJVtvHrxz(^#M>Daq(gEEK@C=SCEV&HT%8OWUu%Aac zA8d13&~Glxa##6|;Q1btP)aZ-R$)C*_i48)6rxNzk!PK@#cM(?(_ukm`t;#Z z^q4W`#wI>yBtP}C&=qt3GiK1F9($FIfGW5WoVwpa({dVt6XQS0@}#GYW%=b-nri0$ zpuYNG+(2#mE{04I~Y@kPTz)@2qruRaAeytbvSac=GW3 z6-@uPj6FvvuXGj9t2Fg&<>qgxnC0g{Q2zMQL)hjS>Tssb{N#vB|3YgFadGj(zSmZo zz0q|(?tT?81tL@nj?i|V4p9&Yk1g9$*#C}y>by4{dg}fI`orH@JiL^1dGX;k0xt_D z_lBT_PtVHQf=R8Iqoi8itkv^~in!1gR+jtOx$DT zfH4TgXMNnU71ETD1GHsn0pSuRzdxlUI4HMCvc$(r+9zEST<6d(&V82Eq^k`Z8ysba zRRF`4XD1cK8bfJ_TlFO4*o~h1@%Vwc`KY)N2~bHsNL+RKWOm`x-o=P*D`6XO9s=?v zK963jdFmA)CqekH1y@&%pv=2SuJel-w9n_KclE-0hYXsq<=Ct|Oyj^zNY!Pl2N(&B zBIQuYNihXkDwWQP8rdaf9oQ!hHS7sl?sD1P)QYc0_kC*-;|PF(Wiw!*Gl^-@fF({b zpuaHFoc-kq^6@5*~<-scNO`kqc8#?WeCG z!O@X0D5z=*Kal-n1}Rsq2_cX(z~~TMtyCv%DFnej@BiS+Fq@}*yVLS#chIeZqfbk+ zKHzA{48W{qIO5C|(>}5AiphSeR&7|ohAH;QzD!a}(@lMQ`rS32@kG**^Ej~*s2H<~ zy~IW?bFel73iN*%pgDyXLt1zhqrek*o^_g_>=J%KSwp)B6^Wy3P*dh(;}@hu;PnOO zusipGdGQKb;4pD7CS2qO)+0X;k!3d<&gud)&QCB)4hqSXuieZ*9jpeg5g~K5n@UpJ ztJJHYor;Ir_RU|6|7ZLYptxUYPaYdbiD5CBne-aqUqV^#-G4aGC! z;Xx`hLMI)r64H+<&$jKCrir;83PK-`ft5A26A@LwIl;unmewkU6yWl@NLl#gYmxwG z{C$-}!L-Pb%DoLb)^Z6OSD!Uu7Iu5zKOHU~v#1 z*}_p|Te2X7uq5M~yCcbE;}T-s3q|glDj#Ru(|D6*9lP-gF^sGO#AWI4GMxP}?l^RJ zxvkt-K2 zq#a#4W6R{8yZUGq?YK(3st#0#o+tiw?IE59X(KK%TTUIpAxnoxpME<|T&FFuE3Yx| z14~e-M-Zm&L;pgUIReJ^+JBnYV6)3g&m|$L6?paByqMt{UzLGUo7`{v2wjo$BYlkY zvgcuj$%<-atoH}fYSangb`7=$z`oFVY=-dpz+9dOtgYSDcU;^9Q2%AijoJ>GI0Lfr zzW}r)&vR9HSmbD5`Kz)=$^ zv{2T`9Z-T{P=Q*Sp4E4OEB3c9GxoI0mp;b?cobY}`Vh_qWdRgVM#hJi=`K+;IS^dI zDs2jh%PBNwXFZJmxREiSqwO?DgzZ=F6;1j1Oif)jEt;kaS>fkKuhnfjmXDjiL<%Y8;rSB4&e7;PtXW?`B6at{9xaYBf3x>4^7~nM) zTt3lFUFR;6qkjJALG~mVrXkD{$bQN@!!k2D0fS@!Nn0>G0-VY}!Se|T0Ti3%MS#GU zXLRFM;1zN_vz*JXTp8*<(j;S;uAHw8aMJFsAStOYFkku2ykGukMvaEBk4G4NGaD=G zv>;;*P%^eHpnm>#V88aTWRgnUeca1X;c$%LcL&w&tPXHxLo#p>jD(=ZFsE2uCm&yO zr>iTmtNnmO8#IxtP!377z+*)}H{|G$8S7Y^q6&y<=xC_&A`#HBGj_7;J95#S; z?TkSqOv~0DegFPl6w{D>@-P8GkuW+77Tm}yqm|d&7hA_{`VQH|M~y8U`=L|`7qs}E z1X9{$VN1(z5Ad@DPY!y(<5PnGyDwcu_o;1$-@9MDu0vKaFyTHpT<-8ZOVUn;RvXg} zc|ZBJYfVr?7tg1pHjkc4yU)!-SWpSJ`A3OkkLJxHi9t zGrZkvo7gJWC{xKK0xF7|8OJ46w&3Or9{lP5(0}^SbwX%(iDYI^*W|n{>ka_O{w2e& z&`>jjXV$uI4efX0b#&ugP7?zjuw4gr1PCLcm!W_GLX)>o%dzC!5o&~fPBUiw=LOcy zW4|j#9d-khWDmA#NYw&6PIw!{YOW|E2$IJVDLj&{5&EZpd=sXZ*16)jfS zp=l1bSSCo?K8&aw0ZkSR|CE0INg)Tro+4wGJ&2^s`MMD?;;T7!er1yZ0z#Ol%ie4o zaGWnGEn5{DTz(!rh&!7P6SCDxz5q^F+^ADx{kH}ZdF064f!6{8;6JdF<-8|0UfGu& zhr5DA5jApS#y};l`Ktw;bm4{o+S9{Fveb5(C!puqwhQU{e5p~54cEE%N~D>R9p9S|^46$KRTK-nSvgIL0A|(mu82lmZZT?X|#h zke4H5X3L}5W>iWMza5TuP=daJ6vWF=`qm||HckY(5CxI91_XRVBO7VN8Yr)GKhHD`iB{w&B+3%S5PhMTUh_3b3(J|<` zQKUMPdSylm+|zNl_xAS2XJ-|E@9!@g~N|T$TNKy$9>j zb-zczHdjVJ{RrqIGVSgU@a0-(F=db8zdedRJUrY*3fsJT)549y$|Grnc5dO*bQb27 zE_~@*Qh#wG#`?@AZF0&CnV_)=K6$E6r@JrtDqsSAPSGJ0?0@rwghWtI5CZ=P{mGl~ z6AoC>Oi7%}f6DW&G{df@qv7retI<`Vs~0X*LF%@)vy%j@6{H2&+1be*jnLiVQ~o{L zixiLqz?o;IoD0lusOM&*yfcPnC!t2rma(Tu7czY-n2?tOlbv zV(1P(hjBYM2~wu>;?v*o*cdlpLCJ9lC2mQvx@3Hwyi*B>DU41!OzN)bPdY#J2!HjQ~_R#Zmv)@&rM@5?^4% zI=DLm2qef92BbL@_^kEmjsD6bAsXJJNP!H8Fru-li_$8rlT-mmyC{k!{?L4zR#$5S z_4TjsPhZJ+f+aIAN9w4MKiRS0%HOygZ^IGvuE%f-vq2xP(~+ReMNXbn1pKIPS_R4dn;e`0oYL&4IU z-*k8mlNERe5A|*^4j|iqlg@AFsk>Z(<)xJ36TZ-NmcAHXoH^a9C zF#h7r9?QSwyM3f_7BxGblh`}Lo4Y(1F)Cl}yJ; zHugPM#RQ0=sed#)Fz@GkK{91ZyfSpoVnh_7|E@X?THoQ2 z@{6Po0AUKz?t}`nFbN5Tmg0~lH@7eO1mIO=`#B7e<9lQ_!aM>rfQnAiaSs;wKcdTR zmm2x^u3R5dPvS#WYH>R;L>BkdfAxD{pwzNw_z$|3#j_}UP@C~W>OQr>X5-!+l!&-E zP3g!of)28rE2TaNO4`+}BNN}*^KDtDT;j2}SF&=>h)~k3j135UD!@l4`qMnD$U>hb z&OOo6=r!^P|Hb1kK#el1JmSam+<0st225Kj3D;pMwc@66H-PiD3_;*&zbsU=r#JT- z7g)lUc1-lIdb)@mw9r#Hc0U7VRm*PdYrHQT~ZBa!n}(DsGCmM?h(Mi{rCN|TD5zo2 z45NjR)j~M5A`JnPKRS}gRf0o8>X8~mV9a^4uKgHo4zPNbnMQTM(E%M_zPU@P0+;!> zorBAPDQ11zVKz)kx#t6V@~Xb(YfXGFYiL%d7Z;C#+MDdZN**;cr@)?D8Z%6^GbL(c z8_PYY@!9?uM#BXEfdGH$+Ght}PdD8AQo};;Wzt&C6Xt?nSgN67v78>awE1?SsH*Ht zd(8uG7iE1(fa(N=OK$gF3Uju+9r?UnQCUDJO;p!0K)g`wj7=t6&J09eHr1jFQjhTN zBpPoIDPvtQeNw*|nqFy!EMs$c^|-kphutN(`IyW9kF;H~*=L00T&$(za&F#khlOm; zhAri%TNxCGc8j)@m7J~&mCmr-h`1)^Z=Ig!kH>^~t|y1Ln|6lrY4660$NYT>EF74= zI?+)5vQuS6F^S$5roY84msaNI>7lhuk(8i(FkP;=vFRP4{6zx@It^3zWY4#ziRBv9lB zt_dq#39L| zZQ|ASrovr_*~3sDrzvH7yr`v#UwRe1A!AbTg_&q^)?26&6SY$+?2@rO-qdQZGkF67+`|QplZo)RG0ZNfmDl zPEwG``V6Q%(FH@MwJlN_2_K&jXPw~hPVl>C`A>%_Hff<9w=aoy>t1U!btH5?Lpb6( z|A)B(+-ZOwqd2v%sNNMS#Zi1VcVIE-H}bCCln54^JtTq!@A>k=>~>EOALS?|QA1%? z*Kp)ONKz9o`RHCqjHS26u^pko({msT%`zF{2Rr=Ow|9lplyUJ%DB{L&@IP^7^(b5H zOs}_$dy=x4I?>CPCoomlsG!lD564WoYkM;9W{Rw{7Yr>S^Cm>M`*r%I)i)z{Ij`HW z6Lkj?z{gM(D%8XOFEBAoXCUq`alR5z&+x zQvHy`#MF2CMELMtc^9W!!YcM95PYLyIT4fkP7_cVAs}j=@xbq%9SqjG-l+wxL`W+G z>j)oJJ_uoS+hHTU3VPmE!jzVT!V2%Yp#XsAmrE8njQLe!uwDNv1?wT=G?@Rb zct1n{DqvL=Bwh))El#vZjde+a^D>^e-7=L<{ikuT2*ba%g2hofxg=X#)thFG$17G{ zh8k@+Vof1dBIJ;`MAIFWuj5>$x4y}2$6IrO~ z0c%DE!NKbRm^UTAaU+l&Q!tF%C%C6AsQ47aPV5zI9(y*YTdv!Gw0*qz|K9D$?!bnY zN#zYS}fOM9#(69rO{H8&PH;Gq<8mnz2y{yZg*L{7Zu0q^Do=_kt=){a8W>~?TOZ4)eV{$XFjZoQ9YW16zjUr4BjdaO;f{l+dx4FNg8EfgH%)ut0#<++o>a))+4kkM zUHag2$J>P;2SVQeA-c**aUjn7p|P^;h2Zd>7_V=unr`8c1JEM?&!hDrp{D=o%9>F$ z`@n;;fWv_@ys0(jPas*ST~lF^>wD4we}fML^7daJ#feK6bcNC&`w-4U-Q4PXV=;B3 zF+|YF`!Luz1R0}>u!~?^siD9^%@w&ZrW<({ALQ`EWWx{sGZQXIQ<+*)Q~9I4 z{w>ifB;Zq;&w#w7>?u7G9I-MB!{IjK3S#s7?<76<3!6R?UCObud=njv_%)Z<_#Xd% zOuctJm;3)eUJ*q|Lb8e?38Cy!Q7Nl~vUezZ?@?rACS|>hl##Occ-bpk*2~`8%ijC< zxaj+Pf3Dl@ob$(Va<1z+9_v0tzd`Ff#PX5|`s${O=3Lvw&)Z?G*+O^U-U`jbSsM)f z`n3k!g?7Nx8L_R+F@*+m!Oj$FwkPvP*_MY_lVI+*&s{HID9eI=3@aP z#-Fs%pr(cPkHcRNGv#2tti+nqAA#Sd=b1 z*rY~~Ai%{xb?*%(sMM~x241?F1%=yhKV2dD_TrrCSiyfKbDZBK7Y62ChxGAmI>P=9 z7Qa3mR1pypDa9E|#Ol;xx$JZUF;(JB&QDT}ZSX-fZfsq|-tKNSXt`|f(w-}IqDo{M zTw@afCmi^H__|8PMT>^J4 zWvv*;ITiqvol4ysd^E95$*KDM(0%MzORmFi?dJ9lAKl!>2G3g0M(Vlq%Q~-Lm$WM$ zpCUlbb%}nb(iM39g>MR{(Z!hG&{I>x)_%zNcQkLA;3Gq?T=Y#y6KeA~v<_`P*|ub^ zM5lYJhPk^fJmQUzV)D8`&1aya&lxMF7XfBC8z%3o`;cU5_q{Y z%s)e8Yuu9m%h$slOm}ey()HTTbS!1jAhe@qBXw_-Zt@)M+A;;S#+s`~=MS6-c)nN( z_5N+ddkV5CaF=&&Q18y#;ww@+kVu}_uzTx5SCq)FK_Q>4OZba^tSY6y@6xN*)>)gA zGN<~$v~W?*(euV-r{d<w(}>Bgo!PNVvVu06J)v=%IBcV3EDJ3uJ9 zH=iyu9TAw?v30s&RCsxn%~k@?QL~UnE^Yv}#S$(>`SciKU*r@fAFJWQKV<~e@k#SL zT1x%)vX0cp+9<;OukUA37Xl*zEFA3@Em4Cl%zpWQcOvi$T*jI0d)0gX-nfg@+Jnwu z)&L{(5i_?vYK)o{kwMGEQlzVK|5lVJmFn1TKFOQPiHFjhs3;Pg={E|++Z-oEQSs{R zJNjg9We2w-m3>LhYToS{i`C%xlr`fj!T%ClkaK~xmCawT>hSfcJX(RJRmaETG@;Sw z8y7ue4`)XRn68KrdX=7iRNK;+jk6dL-Uzz7{NmC)7*Gn>sEMZgNYFw5 z4)NLBhlV5D>SjvLjqjr2PgHb9Ib52V?3d@J{AWAL2lbG|9v<(rxVvi;J>9syek99u z-1#G2{wzwA!)Nm<@mrXa^w&BK_SS694mnEDx<9TKYj3Gw##D7z8sOde97gXSOX9L< zVUJkJ0Rk0Cxuqug!ZnVAF(R1{Wy)MF^F>p8%BG&KVu=$R-NMK|YiwFZT72 z>QaW@>JNWcGp6garZYO1t`dYW7lR{2a2dSfkmEcX1hj1cqDa!9VyxY5=!c%_8_F$m z(Q}Z=`kU00n+lj8C+FhviEn6zLZ5O7d-TSrW5b)LhMGj%NnIUThF7_#K?Jnp04poe zn7{vL*19F8)l#wq7GJ>W;t=f_Z&w>*w3|u60_%)*Y6nX0A`(sFE&s)K0mF72Jkfx& z)UC=I<-C(xnPM*c9#o=J7I>w5=fhI=8eHy6M-${47Mi(|CTke;@GbwSssg-rJ|+biXa5LT-s~~l*=Yg4JvAo?X2XR zWNbN|8f(>G-mcr@w=9}D@-P*i@WNSqEbdFTC5RS0*MhY&c8)eL;3kONvM^IeqP|?* zzg?5YUQFK?bmJ2t+SuWYk)zxwr>&rvUHa65=I}S^hG$z{?cYuhSo-ZvQ3lW5vzn z^Z=)Mq^TWDa00ZJu+ihHn90AMbGEs;u8@1@t#S8$k_gu3HFwAN=`ka=!$!iea2}%l z{efa45QuMyeMge+H8^|~Q&vPm{FIXa9vfQ#q_nZX8jcKMrWO4e2fkOvJsIyoLQ;z> zVup%NN=ew|$uV%b? z;{~vcui);uQg(FEnk)_Gah{@w+aBOfT<~27J`_C3Ptd5RcS}S&^Xn)Dnd%k12{+%# zO$&s5ZvM+n(EHH`rIbA~k3ZsVK&v_3@O0G6uA)B(a0nUJvvQ?JhfW}sI$kPp9P=Li zYmg4!UnFMK#2cB+NS7EcoES4a+gG28{?U=;AUoPe$$btTZ;O8AhzB5bxXHu0syE_{of}@ z#>q3!3>I&{PgSh~{kdF2(qtAMG-2$(DXRL%!$aT5)I`G$@QI-uQC#iO4GRAHb#fUI z#xa0Y$X`C+^5l!N1k{T+X3DPQm+wr*7|vgZg|4oVS@5*OuZYv6rCy>ulYi%Z3~xw8 z#50(jf*~+WMA?Qjx_&E$M5#5E@*l4n*T3_48e1O37BjXaLT}!gq#M_te}ADiD=h)v zPTu)U#Ql+_l!8(1KeHLia@V#;y+L4Cw+pHhhi5m~FA&Sc_?vscG z_sq;2@1(8GO?_Bbka{Nt<&^K=PeN{d-`e?at<6UxhI^RFNd2d31ii0{{d_M4UtKB> zT3*!Rqn_{CZUc~`;khy-by)#WI!xfY8N2~ehw=10jt5Eh4n?Ps7qCe0yuiAYBGkYk z*m}COCUEbL&KdP_ySB~s_Y7u1Z+n6-F_ey1frWR?>>dt&sltj}V8{@nhl;W;vwNYc@cx-e` z@qi&$W=0(!gdPTI34O1}#DqjddeuIp;NM$)KlgY+ z>i!*CB4rcX&=k@7>9T5FtFe?1=PLptta{b=7cji1`KXkF6!%1{7rkg#1G0MqF5`7AlI+Nf?W3o=3c+k|(} z#k0)9V>}>kZGDeN^{+`wG1zv2bD$>>q$Qst$eiGYf8Cd(9iqw_8_)T z?OCg?n@*;xL-CRV`!*g0zv+BM*_nzLd>2y|%R!S~n!a-NX?dvn`a*@Yvf8{%Vt{Kn z2z&D1-++q30mcq*Iwdr-{$(>(kQY%LhL(@bknvd1 zjfJ9mm*C>zRa>KP$wfz!6P8^AvFShBU@ryUYG0O@%_%94XH#GQv9!8?#xh+joz!bH z@u-)XGs1q{_w8#`$@2>vroz^TV_%0#I5bM0q$sip&4 zMz{RJ4yuxqJ>1<95*dP{yF2>;7dd5c&e}g#U(h(1yXzE~RHWznn@A@xzU{ZPk17Oc z!&{pM+hciStr;&rxzS?DM6?@~-CUzZW3kxpB(=yn^1vgi93CFFIo>N03ExjBqW5*U zA_{Wd$h@{v>OOBUE0=6s=zTT47x`KhElF#&Z;Y4pnJbxo3`bkLt$?$-uBwV8EWtW> zj)j{HU1DTghh4v+ZuL~<-! z(m#CYRr6gw?cZKmGqx3c8}SN`yF~LJ>-oGq+UFMY`wVhKO4(YI;~Cm9Rlj@sFX&6o z+zWb8xao>r=eQQ8C2(6zTKZp;q3xc;$chvQrpSt7C8a+=BQ9*Vpe86Q07K`LHYk_$Os0~=CM zpQNh0m0<97Zm)xrE=AB$_~YkZR^dr6sh>gObSxxgMBN;%g&t@Y4k52 zt1vk8WdXdk=^3k{^QQ(J*7j%u9MwA%V9Ndsh;J$X6cKzB&Slq&4-?0sZJ#>DT_w#w4#cm&7DV}M|c`MJr$U*lS&|QRJ zrzZ~d*b{A*zx5sQve9jUQJj&q zTR-~+hR49I>2HR%7&J#JiFgMdn44$#`5lL!U$t<&7Gl^RL4_S{?I=cCr>EcV)8`m^ zb6(b5fWI-$CX2h{r;8Txn&|F>Xnb*k-38uop~4Bytsz1ZVO$Eyn$8IsDwgG;i+qJ4 zGmO0h8#V&PGL&S#>|QlGh}w)6qA#+&U6B0Zd7>cx3UjTeKjR@OB({rTgFc9DI$NKN{r>f(|dW@UB7peDWF0purGzdZ`RomfCz z;ZSnG132-hWIS4TXAeH!eYQnq?eWzu`DkJsJK$3wr3UB@$^5?oLb8Gs6B)Eu$97PC z%k8NqGrhk;JFJYkpSEKwd0JWuDYCc+UnKY_s0fyM+2IS);pJ^t+L%@NDAxcS{zO~i z20c<15%>fI=t27%+{NXQqclkY&IVyYS!|?soHzF z_$2?5_N!MLzCZT6B4lsw*+~v?P5@r13rqvP2gDa;;wAytuLXvWr#^UjaY1_#_VOd( zp`9ZkLClV;tHpJ6b&)@8w9*qQfJwYBBs6>v?ClFAjcm?F550(wYGGambvVw^y3m}W zRd#pkT()}?#sfUAF4MMXG+HUwkQ~rV>kr}>zP6EwmJ4t3fdV^WqOA9`kZ%MwTRqtZ zB+$R-FzQaRa2!NPmCy}u2TljC!U>E|FTur-A<$1axZ!&)vr;~Da%bjIL(~&|Cx45C zLGvr3^l0ZnwKyMp@i}EBuCtrzw$9NRLw65#e|ELG*dV32L@ol5J(mIN4sz7uIywyC z5^ezB=5e5(udfcU?@rwew7O{*HD>@V2&jn@+E&r;wG2`-cbG$M&KoB!I5lLPue`IkbNJdI{^F%em7upyzDRajspM*+w3Ta#cGXBi z*tgKNB>Ged(6~bbiv*d|GH`bk%0~lrdZk^G!vYdYolK196k)_UE;2YoW7%K;(c{g)U1r)mz?J_$(WgZD%EBmk5#rzSQ=HCJm z$9H=}6L$6tAU{lC)D9=&Gi(sR;YeQCAqGBe*YAKI_JF%=28G!H6;+vOb;YZ&F@m*6 zvIs~>rhzW`l?S*F>~h67d5@-ZzJ_-7YwE?8&an-6&|=0%gcBc6Iy3!s#etoFK5~1XhtHARt+f zMQ`9FbUj(yJ={MiqCj&dFyc@%Y2_JEUd`^JiEyhZUD6(H{uH6|@TIShYVShEteCyo z&nj&#R{d9dfv=_8-kco&c(u;!O5%oSs3^&(sU_dMxdlcER&S!w(bj{#RJ>+Sb>4xa zHaLUIClIvx%6?A0JG|klIb&+`nYx{MN$oXep}i zNbtQ4YMQzjx+cK>uK;HD&zHYLnH$Jg&{?+UT7qMUJCdprrI$P9N9GHOuOr(ZJRH#5 zV}9`o313Seod)CFF`!+stW`)Ke;25srK?6sxSHK~#=8gp$fcu{OiCl~gm7?9nXYFY z#JHIc2yDN(JdlO!e$_rviUZupleoE-fgbR2SAg4C~d2r@7S@P?K{_A|{;a9E*Uv=HCu(6`JIeEib=xS+f zJ&?_)z!&1jlK@KrK*>xa{+oZ`1pZSiAq|dPXn3mKcJ!Zjwp3neGw%G;njm4PQ$y5v z%yx=uE(~%!-Ns4>_wE$bkKWB7=N8>1)=e0;DbyZ;kt~

T3b(nQtc<5$^H7-ycH+D@wadN_tJm^3*W9 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 diff --git a/docs/tex/recon/QvsC_quant_n1.png b/docs/tex/recon/QvsC_quant_n1.png new file mode 100644 index 0000000000000000000000000000000000000000..bb697a4be1f1edcb5c20931376e2efbf364b8dda GIT binary patch literal 1105847 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

XL2xXwnk7j37GI5<>u* zXgkdHW_7>N9WJhudrIo1kxjw30!yEc4HYz0XLg0X%m3|7t}X*ZzVuSV=@;z z=pl@55@JWyn5Rq?JBSol6Z$mdKOSq5Ksn*T#)`Eeeuvl$Wh3($fO|g;Ebn*{VS8o5 zf{qZYvsqEGv9pB+^Rb%luA9pj$rY8WS5pm|5PdAi$beFpj(xva;akfm4h$gjBS

u0}@ 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

$3*dSle0r=;YC#W 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

^ZcZTsA_B^f& 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

V`6(BYy3 zL}C-IQ~SyEapHi5@9_eBT{GOMJmP*@UmVs#Uk-gfC_FcUg>1Tp1)TnSs7gJ_C$SEj zB1UBRwfcQw0W;0&%7UGWCs&fD4U~uE3{4rh+mDPQ3E~hAycE$Ubm-;lC>A1Vlmu}l zcSR?&!OwuH`n}-EMt>8XWDBko%LvXz`aexU=7VWnmGGRO}s3r?k88 z^H2P5l5xFYoHDm3&Nd#Oz_hmm-{3w+A<{KjYq&Re4hn|NWmjA{AeY0p>Gv<%fsbXU z{2Yh+zlXoA;rwA?r%81*BMbg`; zM;y0TDT0f4)Sq!5n4=r&k_V+sHr`dVsQ0p&etYm{j1wr|\M`_-*hTU?;nMthX< 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%0DHOP92CSvqkC5AN*K}e^@3_ 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�c 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 y zw>@8n!5s*H_+YBGn@$K5BXRtYJo~TAZ6f@9;w2c3sULTsmg1kpFjy-{fz^4Vi8P2@ zK%NC}t-l@7&yAT@W`KQ1OEJ@nqL$o;Lk^4=Hbkp5mVjGA@`r{{YS0NE4k@g#RAomo z)g8F~!BZr+z#%ROKm;nM45mn33a7fD>5^xLP7>D4YekJqLO+8(=~D4KogdoH18Z5l2(jfkVu`0g z;>=_yY@BRYy~&16**uNCM3DJ4=mn!b&8cG7Sj>jo4-rD@b+0SX6C>9HufPP&PDrn% zK2Yr>LvL){3@5}jW2&sUbX3Q-8(ju0tGBbK1_tY@UKOPEtovphzHPnbc0MM=ZR`;D zg$#Y$sZ(BFAMvISbq%1O>RR2N2*npZcUKpa>}u)c4zWrd;e-OrJ$c?G^Irk}W&|u@ zBA|SQ>#0!h{-4iy>+P>f=b2ogAJ8_6e0K!p;PX)Pu_*fQo6@l>9fLl~=}W~=tcMdf z2nZ<<45nBvFS&?bF8Tl`646i*dUsp;Yb3bfFA`-%MT@S(Bn@h{@gW0s?nwhi)Gn~` z&2$jJ?|1d+@HZ9W-b8IONAE9yI>b85&thD@C|7FyhM^8E9mywgc#(8lr*V>A5(*Zy zQFnWLWNpLm*kC4RW{_81cR$@lr;sAd&O%wuNtfhcr(xtj;oon`V#HT-ga3#uCYfAj zgeHVGmR;Y~mfnAm1(I)F(kL! z)*e}}n24_6Ir%=_O)m^TsBUykMq$U+GZ?<_foY!IQUh-?NiU1nbSlG{%Y(fVKhkNk zr~z$uf0Rom`{&G&h)J&$aj0vGe}y{#doInR@bMs#oW&r6b7DOXoBqUcyIc%OyQW{dHe8t3_wTe zvCu_j?i8C}CM}4ijcfgj&&hdE_a8_v5D*~b!w-Zj;PI9AT>VDTMO%~2xCx7>MflHO z9t0;q0(WYd{jiw~SXE^m8Mq%-o0m+^d$xmn%ekHOoa*yR{ z{^ZVZ#Xl(6Y0PsO0aU2#RQ-r_S=Tp3ZF* zwYvMlViO(knspYD*Ale`v!04s&M|e(Xg3bVhfPtFi(heNzzE+lTsF5t3$n#Gc_$SS z_$eTkB5xffn9kbphDyn*dGs&Ba_zG&LYA>^Nye_+F;$>)*Yi7uP#?f=Y;jSxZjj(a z7#cDzg=>vobRi2kFyxj6tay#>3z+2}*IIRMyDCgj5-750Nu5ck|3JAcF&PT{U4|cE zX)hr;Mn7Im z-_rk|&ZwTm(LYnS|5yiLtE}SYPMC%~v8b}eS<-#Wi@g5PORQi7@BV6?MNlGWG zx;SL-`uZmqxqTEqO@jbtAFtu?F5DB{?dt5-D+3*C>+83&>%Y2eWaj|@O`X^f2w9LZ zu=Jx^d^pb!JzbI{;PwfA=rYB75rVKPF3Ag8n_ z8fx%$K!BEf&7Ko^VUt8+X9N**RX{@{njGb6jwMfL#REx$M&n z>HGHE<6F4MD>|^Midu6xDLX+~)C@iTnJ1hmZfx6E7sY~I25cs2OgIUGRI9^74G)ck zw|Iz~uV|;8W?m_HHReUv&`b-H`wYg)D&%Q$k}YeYoNzBR9u+>hPp?F@xDy>Dh0sVY zs5M?fz?!a?c**`6QfdR>xy!xkb0f|{F^T%~6u+%wMv&Apr`Xd1l=j0RMitR;3G%vp zxr6%6d>_;pDwj(yflzs2ah+*v%Zg+XlTY&e(?rxukSNS~%KrgY*yLX?MyQ(QA+sLs z;h9XH5m-2!89lq`uCJj-M9Lb1GhyoW(o*LNjz%tl+iXxy`dYO&e zCrQ8?jJ@m33Q&n>UOz?%NeJUIp#j4#%;!=FWP{rAvssXE8ua#=S*=9F=L)wuvW8_R zk$*oT#OxzTP=y9aStp}lugfl^DrNiVyacR@Dia`!-v`#76_<(x`PB_-#MD%}9HzWZ zg^E6k;~=gU2b8)r3ytnm*6KwL)&%oW#lpt+7FOSzsLgV)#LdRStwauS*?Gm+omN>{d%plCVV#aL=m8Zk;5Pu~t zjF5%bu;7fqqF{0P{NC|qsQG(WA5WQ+h$=CakH~=C-!?*9=70T~=`#psHJRNT=l^a7 z){`sbeH~a?^W#sf_Lk9|0^VZ=%bk^`A|FCC%-iEde!X>T9AFvqSC7KI1mn+&07LE1EF(d|CpVv=2IYB z`>gI~sns{jKk%Ne1e)qKFDlDsJH$8IJ~-d$5>kE`I2Axh9~tR<~c zozRR`jwgj0W4o{XjszaxKn+IS@n_p~2vHE;VNcakGQAK9vglDVe}oBa6Ep$sKIOeI z-IRrqp!0g^n*TLmUgk)0*)AXq?kBn+fSV&=8EGvETG6)u1>DO${)$~Ehp_YQtS#Rd& zDu4-&5ZJfz{m>J8mkhDOTwX**{~kOSK0?An+pcOpu>w`+1Z^HMaJ?%)f`>)y!4yHF z$m7boOxQOXd7(qtLh&ue!jyN{+Knij)5lEQGv^3`gE{slmzyc%Q9~!A8N>DGH_nNu zMx)4nj3*=S^Du=h818i$`*d@iNkO!<91^hNd(tdL{(gy%ntI;=!ivg6ub^1c1P&4+ z-i?If6mG%BYIHmNj1=yC3tVFGv(BVxzeGj@l-C_gdc()&o~AYfGw^h$r6in`={f4t zZnF*Fy;vUL&?czkBU&~*{~~6~_i1P9^Lz>f`vRvcv4h(jBJoBV`bkW9PJRV?2E^?A6Ny>W zarPy;#x>MWtXD(lN?8rG--wsi*Fl~-;5FG2ZMG(m%-Wr=7MQNNokC=8?J?R~UdQ5W zf4M|7hNnwbY-{;IfGN~R*GJp?RarAwqns-_#|c8Q`Tw*Pe(t@nvyt0de0?*LGGxlQf5bbI!Qs zd1&+YQAGNivb|e9OP*l-)vPBkT%B?KzT=pJ5MflF2+MkSAwSfChSr~fyzFRJigWm8 zJf1wpk+)m|j|U2~dKn)H!|ixCv+=1n)-xCsK}MLFrM-5g0UM&4n*W$SYB(9N%>QzVXFUqz6YIgXQWQmN`vy6tl3iKJRxilFdH3kg$ zrwv~Xe&9OJeDEA8bk_oo;V>0jFU|;J{97%K$3^O8+=a}pFh`fOXb{QE5{UV#=hOtE z_<)YjEMW#UJmTJ4Cin0Yqc!-BlZmjWN8W_x72z&^ucXw;&f{*q{Y(`q*|m|8H@CVy zAyY$IHQ7Rjv$*#Fd%X8{ARvI^V>j(nBll;DtQfruZfQsbr3H=AmWH}+D4f{9<`$Al z=2ER;TEodWK2VQ@y&Lw9Lh8mmk4@S=w>OjX+TB+ZMFl18%uzIiE#aY0hNAlp4fkh4 zsOb{k#^NToTz#QebD5^MjV0q+5&NA;+=W4Rv$qjO0XTvmjdRpK=Vqjk-R9fvF9Ge;6-zO9FS~Nv3yYPN z)GcK${e!6+IRkI^_4~JLlN(0T-`Y?8UUyuAT3|ByQOcsRl!@F~C`op&2P%-hbSysz z>Rk~3avg@;slQi)p40MvdTzVy~Tp6c9??#&yq z;vP2&Nsj4@b?yzlL~}4Rqs-87U@i?P8$^$*o6x@|Mv8LxJ*pfS0P`?{9M_MBhBQHa zNEX2!k&`95wny}^UgndAzS9+qLxCaH8=eZh%Rt7oJ5y(r{@t30P||!WjvDfqT{L9I zV;#m<3TW?OJ}{-JeA1mpcHGD_DW^Zs=!eoi2DW?D2|ERu6`TZ9(w%mSLGJ*s%@LI8GHU0aEA};{Hi^j%UoWyeA@%u_@aRh8Nu&Yu5Zob5oHn7ntf1KxEWEY~#Py@T6E)4rX@N&b!UY+O*R4MR$jjs7Dj+Zk3=Jw&7rWHQQzwS_i2^ho?}BhS-fPiLl& zejW)ZTOgU;FV9`XG>!7CbWL#acW*{tu-;qSVz=#pnJkrn4a^$VM7n+wkM!SvR?A+LsWZOaW-row-odQ)I z=x$2awx}IMG@b{TvoPAn5zo0GE^H_iR8J&g> zV#xufh1Yw3^WNI5l~m^c`u=PEO2t7>5){qgIGRcC5ekLSSKcJ}Q2lW|Zah{oQj1S@!%Emg(6!>9ZOa zx=GI*o>*c?2lSiqqOj$1kR-qKsbsXH7i9LA+Wp~0%3&`)gev{ml`Fg6a_mG+EQQT? z2$(1x1d?;cc6EVPbK|ylZN z3a0Q4!kri&;H_eX#U9Yu>2Hdr#4Oy(GbxxNnCzP<>X;Ef501XV@g0S3VxNyeew=yF zTzBevz(~}JB)TXNqMHH9MyT_q9C#!u>i=Rkd9~XHmMJ_()+p4nf0^I?4m1#|MIL$< zCMNl#!pg4}hi!z(O{<2CM?=|jS7^~0Tasx6pM|bVTZ(p40rcipYn}i+ShLJst;S&3 zPpPp>z3RgB3L<&`awR#7u}SeviUY1S!kdOip~#K1{1^_b0RvFa%5049Q-jRE%|ea} z>5WHh!W|XUxtZN;^hKQuVZa!UkCFPshr@OsR8U*r0RGZ}A-rp$g!Q&l{k zr^AB;iq-e|E7XR8@@^zl+w58UAcu;Gng5pOk*V$cqRKO34{A;$Qgo=O5vE;+KKO|A z*J`H`18@0+wVbT)!}K(U%`wS6U)bs$y^ZR{xZmrUrGQ|V|8w~4O)le1kBiIXr(dq zc_?wc;8hWmdQJDd%+eT|td9nl5T#6$jaiQ8vkjgZm>G7NcTiS9XFmJrU;>To5%fj{ z6mp@U>chmxI4P2Aqa0Is@S;=Yo{yzM*784ye%)gQ!jwo~KCbH_AL?Q!N{L(Ar4ljEZ379A)4UrBJy#)iEL8di#7bo5~de3-~8n zxSTx!MGD_Vqx1U}IM8X02c5@NjMqybcIBBh>RSelY$w=O9IVKlabb2@;9=whAV|{f8i3!ACx9!z zXAak2K&>Nl^XbO0ClF?A@KPAXNPbDcBpSSFVhkasCf=RI<_ze&iT(6ySEMkag@7->oTPP~q zHu*baxUsG-5Cn~Y26LK`!rjTVt+{ES!d_)l@w2x5)MzHvwjGEm`tL5cRn^K33JRj` zFE5`!G30Xfu1#I_kR76WnHK4RwkLtpJe$h3Ca2&LRhV|oc89A%L(p@s0#%n64@)fx zNv=rB3YYZuyU(ZkPzNdkw+2PGocPfkmJeM(lXEdO#bgbzj&@S#84*+qx^TPEMkQeoA-sR@#;)4){NStz(hio2sXFPivt)LXf>I~+)x-Du* z1A)>;eg*tP2RJ?ple~=;%$cnhPs3HAVa}8x2*O3G4kXMK7Q;i+^A5{q?mK1hkroUN zq*K2Zz#`brveS`>CF*2`GmE_rJMdn)4Xc#>&HZ2u57md9@S;x@W1mXemKtLFP5dfJ zSXz6tlbM-PC5Y;iy<$Z&^vO&jKe?WTid!i-@AJOwM@^O>lQEioeJ>hFV0y5rd!M=( z*S<@NW|QIw`9(1+3;Ya!qvSZlEul#0L<&$>C%cr??iuuJn21@o3*zNYC#bY4&$6x# zC#@!Cr{0gflT@Qw2D4Eus1(J@&W64+i{@)EAI~w|WCSJXhv#tiA0ZvOE|f!1V7U<| zQrnHBb{aIX&?9(A5cR07{M%=rLP9VML8R4q6p%WPVI;8cxXDnZ1yDKkJR$2qqA-16 zWRyh68jm$>51tVC3lO2!G~>aQhD7;(pqf7WAwU<>bkDaIVoJ5N*|YkLooVx{M|Om8 zWAgRD?e^f6-=j-9=O1+npPQZj@HTxLel@u@*snPk>qo1618L9#N6*w`XIRZu5CU0q zDrgh5I9N@2NGZ8jJlQIt+CpvBxc%skeygDVb`=*Ml|&G3AQvLIM6{+7KUWf;JKGy@ zzN!bJK>q*z{vS@YDBlHv8wR~RH#n0Z;B%7C^T~`yWvY^>Qt(AO-PNEMV|)1b0q52ya;Ty`O`DL7THTRA0&S zIcZ6vzw5eXe9ZceB%M)V)Tp4>C5{yXDjq>qwVv`jBF8Kn!V0rbZj{zI$I#Y%bWAB*vE4vhzR>BK?~#yn zq-K;}Png;&>)KyOfa_rKcOJ(Mo=MNFD}JIG*f%U2Hn68+Q9+9aRLJdA0})xTQRigf z441snk&Rl+lpVrp*$9%tcMy|Yk*U`zL|D||a5NVmfTM)e7eoxp$jYgu;^uhAEiHfT zs7`A`r7Edv2gTd&OV5RA9Ly1#!(7zip7j6=Vd{3G(AB8wjElo)!C7q$cFbF0>?nwA z4GDH`e7qSb$!w(@(Z~j1^05~j6y}uJYNL1vFi%Ky?HHRJPjX^h2){<|B@jN7Ax7f! zua~}`bpj^O)SEf-pM?87rYqXA6Hd23Ey7cMny(vs<{40X=PMZJ$n_2Jg}ppT&2eP4 z4iLOZp~#lJ+a^~&PSs4jjLKS5Rj%jRa3{8}4a1B0iN8G`zilVGQHccjWX;*ib9IdO zIxUt(LQkiiCEDi5M-;4hUFxmx&R2Do#N}U%q&RfO?6W3vU)KC9(KY{9qK8S)o7hhvw05CY-j8sBX<&=SPH#plMFf=b z99JZ-=BPfalqd?f{JF)JMvsBryB%Y0fHe~NI8jkudf;|->k`eDl&=ZV-v}QmFRSX4 zZYpPgmGHu`6(G$gQ#qcnAt%whe9#Jt@^aop@*=Yuq^~V4#KSTDZ=ri zShnREBeF^a9iE-&O8FY0rOQs%ekBkMHX=7^W>Z0)qf%)a%hhHBp;vh|HbiEEHgoJ7Dlw^s5vjGo5Wv zCuJNFJxLKWla)87?v^?0R`wvzH0rj%6D%2C=3HSaN>fC>QHiYBMb=D;XjTUX>`+S| z+A#zP``x9KediAHvTa4Tz-0uJj8i^rI#fo8>YNz@4OF?e14>zx#DpuzLks(yovG&vxF%tdS2BT|bx=KVrgg54P;#EXjc zSJ5S6D$!z9gk7CpTBS(2clsXijz7X&E*muPOB)Ut(nnOVGJ9QjE&hqI;#1*WZJBp$ zM-vBS6Hc-1^@7TpprD*U5a14^Iz)sJ@T(EMAYd*Q;U>KaHhw83O`)I;)=pAyU^e8^ z4#L36lz$)48Z3KiTPB*YQ5G09EuC>SfN07Hnp?n%Jd1(#_y`Yyu;h34hsOreycuW| z+Qu(Bhze#EHA?~HoG(Rz(+TEDi3>j&io`B#--!!VaJ;$atu{4xrmmM6rerE-zk%(u zbGEU&NwI3iwWxn$ge9cXhderErQ%@w(6$OI?YgVc(xH@}o5G^u|6SQKU13nhyrI2a zwFF#xf2&@huLKqPKUc2=*nON)pN|{^XLUWkq!Z9P;;*#*WZ&9(^(Wi~xv|~>uG5Mz zHw8Z5T_4{HvK;DcPNn@P3HyLFr>nmj;256{u?A0O z?Q$7R0e&^L;gepNfxwrI`EHiUho;!wh))9{k_%@BWXWP3ERC%~-$~5J!oNx%on%}e zt^N|5AoopOMHFvV2i5F_w3fg$b|WOqyH2h0wyCUl^xZOIj{B$4M-gVj-b<^i_&5E|z7S?U?nbi=+P7?VtwS7yEG{nSUB=!f z+r+G@C*Bt_sxv#{345ajxhBpxIV65Hi1s6mt<-{)_thjoBk4G?-1|`V7620FJHR#F z@PnFG?wUGXOv^i%tSsb1jy@l)GuA;LG9#P4UPD`KtblK{Na;aAn&Cv-zpkP-g`dsg z_OgWwk{WZ~MhKvK2zrW@;qQDB6xlI4m(JF8se^(a>d?gN9Cgy3`SW>geS!?odOoME z?vf2W_&t>|q948%_=*A|x;?GuBB>kVTF~00PcfDuP5X|`6W9gayspmRRqIk)z|So@ z;YPzor`1*KpT#uXL{t=eM;P{(+3_-&wEHVunNeT8LAzh*c6gp;qG$`yxn>E8i zM&D`>%|GEONZ#OsMA%`3%abo@n-5o0=zj27@dv>3bZlewerU9c&Rw- zM0ivZln}%}8#TwD=DbKXx}JKQee{uoQsUAn9jl8+CSZ{@>N!H+rdXd_|8)O->t4G8 z$>7A<8ao-|E$ZvPdGs*D?{QSupfil3Uze!nPKxK|1U3dJm7SX;^nY0ygGG5}#@9R@ zEA)mtjek#WYg+JRioNQ-dtCb`vWo!L>b6zVdism8h`sY_r2yy{GBq@^6a{VyVmXWT za_0kuLB|@g=iDTS)wZjVnTNDvZuedpn#p3n>kU@xHBZR9`EL3<5QCy2_6e@KC)MZ3 zY?QbB2)r7;0|v{EE2PkjupOw0vC0^oJJ>@%jg{T$9#)L_VJ;38hr~8d>uKJ)@mIs% z!;~h^x5%TGee5Hr65^2^D^)$b4@FFEP^XTXhMfFiijZ5f&{X`2qM{jbOsQlg(q(eS z(SOaa`>y);xdLsKwJj8iGf5aftikZkPwDvrXG1;w>hyAa(r^0oSMlJAu0&iDwo{zN z?Q{I3Onye#@0UY(OAX(0So+$ND~maq1V4ii`P{Y#vn2V&B|>yJw$D32!(Plug1!~q z*jUf1PK-%HPFHcE8t@V!lxo7MVA~WBH|)^}(1D7B_3Tt6LNch|0vJOZwBngut=k$~ z4q7)z5Gql$#0o!PSIUEKhwboen(Lp%0bZ&NVTL{s_e2AFhl%4)>mEyAZc&Fnybt*! z8xC|V4rtS0Pb54k?i6z^AXaX7CA<%%2vkujL%0ots?a@WJ4Lm3f}L$>8;`M+-VICnbd`h4Dey`DWqhNL!Rlcdl%bnLCFhU`@Ka905RjyQ zHP28BRGYV+A4YQrLvpa4n{5gM7!GQD7&c2)+WfF(>5LsDIH2M#at$XI!;82`iP>|$ zr=Wq&i!cdsLClO=$~)eyn)-{oxsdUDwzux93k}j7!WXlmG+r0AedzYVDb{H}n%bXR zD}|tXl)Nsn;$F>T52hk+b~1jiWyB5j7OIW zFCp3)n4wkWC$w%aOxdB&OIlQBY!Z8;4*i zSXHCKv#?Ummc#Wm9X6V8w?K;Qqk2++D&mp#(x2B!oD}4;c%Z<9v<=PG$*X?3oxBu6 z3$o-{LAm&=D50a5UjJ9pc?!7&^B^rEI@j?*YP7bUL56hgF*IYkibDT$$3=3++_D_9 zIhn}lxpp!!%k4Ao&fsERJ#Naj)dFtx6XsPR|FDzq+@uX??d|n$+xyIu4D{}i*|%6{ z$u)5dqjIg`h$q#(tjhF}lg=~bZ1#uJt+*4kR!T@^;x@Gmp<8ofcx2Ku0#z5buk0jz zF^QFy1)*5CZ3E7hifPW6wwUsK5kO)g8awA(Jh!fm&QK@Q##p`7U1(f)DK7^~z z`c^Y*Q6s6`$4d-zy@^vpf-&6XiaU7Jj-P_ow;RcoI{uY=#v7??ay#}?vC*cMYYIz0 zX?vgY0flU`o;`-sPwKh9%9*1c6jA$IhAh3n_7w2QNBx&}Bh#OhPHwnA9d>c#;RF<7 zj;_?~=Lp^XL}Wk}Nc?j|4=0^#-loEeqxmP*w0Pt3sIqR|$opp2c4tQr4kkyBi-#JO zc)+S=Y2<)|b1_$nkr72p=rUOD^D?n*3O-w2-D?F}Nhl?MVXt02_e4XQ?H1{tgmcjD zfmqV~pWkMZbQrhYpM3W#vyK4Wu-o2YG3aY=HGm2cNMkl%kseM{)20%;KQucF7dlFN zOU$TN4f%axV)B+;D~I+l7#nGK+^-|ca=p2L^dplI+v-Jl=y}UeVCKg2B#vPDuOrIF ztd>!C2wQsdX~;|SMgY=-5REOXOb{^sFQAw~Fow;?mbQ z`d!^vRQKU#kA^{qWZzX7c?nKbmbCDifFAxWiG^96?+`+kQZKqhLz?Xv2SZ7 z-{Zve*)&%sFV{Ð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%0AfA+YyP_&A39UK3Pz(erGc+Wzi11r!jQ<(lC^ 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

    OX_7nVCGaWxgLLFb0huCHAqKFKP*I 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�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 literal 0 HcmV?d00001 diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 7d623764f..fd717c461 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -8,6 +8,7 @@ \usepackage{graphicx} \usepackage{float} \usepackage{amsmath, amssymb} +\usepackage{array} \usepackage{hyperref} \usepackage{cancel} \usepackage{color} @@ -1368,16 +1369,6 @@ \subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ \end{aligned} \end{equation} -\begin{figure}[t] - \centering - \includegraphics[width=0.62\linewidth]{C_eulerian_lagrangian_n1.png} - \caption{Effective field $\vec C$ on two flux surfaces ($\psi=0.45,\,0.82$) in the - (a)~Eulerian and (b)~Lagrangian frames: equilibrium current $\vec j$ (blue) and its - apparent normal component (red). DIII-D \#147131 at $2300$~ms; $n=1$, displacement - exaggerated.} - \label{fig:C_frame} -\end{figure} - \paragraph{Verification: $\vec C$ as an effective field and the $\nabla\times\vec C$ check.} The reconstruction is closed by a self-consistency test of the condition $(\nabla\times\vec C)\cdot\nabla\psi=0$, which holds on every flux surface and admits both a @@ -1399,12 +1390,10 @@ \subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ so the reconstruction condition states exactly that this effective current is tangent to the flux surface, $(\nabla\times\vec C)\cdot\nabla\psi=0\Leftrightarrow\vec j_{\mathrm{eff}}\cdot\nabla\psi=0$. In the equilibrium the current lies in flux surfaces, $\vec j\cdot\nabla\psi=0$. Under an ideal-MHD displacement the flux -is frozen into the fluid, so each surface is convected to the perturbed label -$\Psi=\psi-\vec\xi\cdot\nabla\psi$; followed along this displaced surface (the Lagrangian -description) the perturbed current keeps no component through it. Referred instead to the -unperturbed normal $\hat n=\nabla\psi/|\nabla\psi|$ at a fixed point---the Eulerian -description natural to DCON's fixed control surface---the convected equilibrium current -acquires an \emph{apparent} normal component; with $\vec Q=\delta\vec B$, +is frozen into the fluid, so the equilibrium current is convected with the displaced surface +$\Psi=\psi-\vec\xi\cdot\nabla\psi$ (the Lagrangian picture) and keeps no component through it. +Referred instead to the \emph{fixed} (Eulerian) surface $\psi$ that DCON uses, this convected +current acquires an \emph{apparent} normal component; with $\vec Q=\delta\vec B$, \begin{equation} \frac{1}{\mu_0}(\nabla\times\vec Q)\cdot\nabla\psi =\nabla\cdot\!\big[(\vec\xi\cdot\nabla\psi)\,\vec j\big] @@ -1416,21 +1405,56 @@ \subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ triple product $(\vec j\times\hat n)\times\hat n=(\vec j\cdot\hat n)\hat n-\vec j=-\vec j$ (because $\vec j\cdot\hat n=0$), giving $\nabla\cdot[(\vec\xi\cdot\nabla\psi)\vec j]=-\nabla\psi\cdot\nabla\times[\xi_n(\vec j\times\hat n)]$. -This apparent normal component is an artifact of measuring a displaced surface against its -original normal. The cross term -$\xi_n(\mu_0\vec j\times\hat n)$ in the definition of $\vec C$ is exactly this convective -(Lagrangian$\leftrightarrow$Eulerian) contribution and cancels it, +This apparent normal is a frame effect---the frozen current viewed against the unperturbed +surface. The cross term $\xi_n(\mu_0\vec j\times\hat n)$ in the definition of $\vec C$ is +exactly this convective (Lagrangian$\leftrightarrow$Eulerian) contribution and cancels it, \begin{equation} (\nabla\times\vec C)\cdot\nabla\psi =(\nabla\times\vec Q)\cdot\nabla\psi +\mu_0\,\nabla\psi\cdot\nabla\times\!\big[\xi_n(\vec j\times\hat n)\big]=0. \end{equation} -Hence $\vec C$ is an \emph{effective perturbed magnetic field}---the Eulerian field -$\vec Q$ corrected onto the displaced surface, not the perturbed flux $\Psi$ itself---whose -curl is the genuine surface current (Fig.~\ref{fig:C_frame}); where $\vec j=0$ (e.g.\ outside the plasma) the +Hence $\vec C$ is an \emph{effective perturbed magnetic field}---$\vec Q$ with the +frozen-current artifact removed, so that its curl is a genuine surface current on the +\emph{fixed} (Eulerian) flux surface (Fig.~\ref{fig:C_frame}); where $\vec j=0$ (e.g.\ outside the plasma) the correction drops and $\vec C=\vec Q$. (See Boozer~\cite{RevModPhys.76.1071}; Park~\cite{Park2009Ideal}, \S2.2.) +Table~\ref{tab:QvsC} contrasts the bare perturbed field with the effective field. + +\begin{figure} + \centering + \includegraphics[width=0.8\linewidth]{QvsC_quant_n1.png} + \caption{Quantitative check of $(\nabla\times\vec C)\cdot\nabla\psi=0$ on the actual run + (DIII-D \#147131, $n=1$). The real normal effective-current density + $(\nabla\times\,\cdot\,)\cdot\nabla\psi$ (at $\phi=0$) on the equilibrium flux grid for + (a)~the bare perturbed field $\vec Q=\delta\vec B$ and (b)~the effective field + $\vec C=\vec Q+\vec V$, \emph{both in the fixed (Eulerian) frame}. $\vec Q$'s effective + current pierces the flux surface (RMS $=2.1\times10^{-3}$) while $\vec C$'s is tangent to + numerical roundoff (RMS $=2.1\times10^{-12}$). Computed spectrally from the code's + covariant field components ($Q_i,C_i$) and validated against the \texttt{C verify} diagnostic; the + near-axis speckle is a $1/|\nabla\psi|$ reconstruction artifact.} + \label{fig:C_frame} +\end{figure} + +\begin{table}[ht] +\centering +\renewcommand{\arraystretch}{1.3} +\begin{tabular}{l >{\raggedright\arraybackslash}p{0.32\linewidth} >{\raggedright\arraybackslash}p{0.38\linewidth}} +\hline\hline + & $\vec Q=\delta\vec B$ & $\vec C=\vec Q+\vec V$ \\ +\hline +Name & perturbed field (Eulerian $\delta\vec B$) & effective perturbed field ($\delta\vec B+\text{correction}$) \\ +Normal field (flux) & $b_n$ & $b_n$\quad\emph{(identical)} \\ +$\nabla\times$ on fixed $\psi$ & not tangent --- apparent normal current & tangent: $(\nabla\times\vec C)\cdot\nabla\psi=0$ \\ +\hline\hline +\end{tabular} +\caption{The bare perturbed field $\vec Q=\delta\vec B$ versus the effective perturbed +field $\vec C=\vec Q+\vec V$. Both carry the \emph{same} normal field (flux) $b_n$ and differ +only in their tangential (surface-current) part, so that only $\nabla\times\vec C$ is tangent +to the fixed flux surface ($\vec j_{\mathrm{eff}}\cdot\nabla\psi=0$).} +\label{tab:QvsC} +\end{table} + The code's \texttt{C verify} check tests this condition on the reconstructed components through its flux-coordinate form \begin{equation} diff --git a/gpec/gpout.f b/gpec/gpout.f index 272c1570c..13445bb50 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6882,7 +6882,7 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, ucveri, $ uk_sigma, ucw_fun, ucw_mn, ucv_fun, ucv_mn, ucv2_fun, - $ ucv2_mn, u_int, u_log, u_terms + $ ucv2_mn, uqv_mn, u_int, u_log, u_terms REAL(r8), DIMENSION(0:mthsurf) :: epf_den_fun, epf_p_fun, $ epf_t_fun, epf_z_fun, dst1_den_fun, dst2_den_fun, $ dst3_den_fun @@ -6895,6 +6895,7 @@ SUBROUTINE gpout_recon(mode, xspmn) COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun, $ cveri_fun COMPLEX(r8), DIMENSION(0:mthsurf,3) :: cw_fun, cv_fun, cv2_fun + COMPLEX(r8), DIMENSION(mpert) :: qvp_mn, qvt_mn, qvz_mn REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals CHARACTER(8) :: smode @@ -6902,6 +6903,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CHARACTER(128) :: k_file, k_sigma_file CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file, terms_file + CHARACTER(128) :: qv_mn_file REAL(r8) :: dpsi_local INTEGER :: istep, nint_pts, iint, ep_index REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr, ep_selected @@ -6961,6 +6963,7 @@ SUBROUTINE gpout_recon(mode, xspmn) cv_mn_file = "gpec_recon_cvmn_sol"//TRIM(smode)//".out" cv2_fun_file = "gpec_recon_cv2fun_sol"//TRIM(smode)//".out" cv2_mn_file = "gpec_recon_cv2mn_sol"//TRIM(smode)//".out" + qv_mn_file = "gpec_recon_qvmn_sol"//TRIM(smode)//".out" int_file = "gpec_recon_integration_sol"//TRIM(smode)//".out" terms_file = "gpec_recon_terms_sol"//TRIM(smode)//".out" @@ -6978,6 +6981,7 @@ SUBROUTINE gpout_recon(mode, xspmn) u_int = 92 u_log = 93 u_terms = 94 + uqv_mn = 95 IF (recon_out) THEN OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") @@ -6993,6 +6997,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL ascii_open(ucv_mn, cv_mn_file, "UNKNOWN") CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") + CALL ascii_open(uqv_mn, qv_mn_file, "UNKNOWN") OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") OPEN(UNIT=u_terms, FILE=terms_file, STATUS="UNKNOWN") WRITE(ushear_fun,'(6(1x,a16))') @@ -7025,6 +7030,9 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucv2_mn,'(8(1x,a16))') $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", $ "cv2z_re","cv2z_im" + WRITE(uqv_mn,'(8(1x,a16))') + $ "psi","m","qvp_re","qvp_im","qvt_re","qvt_im", + $ "qvz_re","qvz_im" WRITE(u_int,'(15(1x,a16))') $ "psi","c2_mu0","k_xin2_re","k_xin2_im", $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", @@ -7064,6 +7072,18 @@ SUBROUTINE gpout_recon(mode, xspmn) $ epf_p_fun=epf_p_fun, epf_t_fun=epf_t_fun, $ epf_z_fun=epf_z_fun) +c Capture covariant bare-Q components (Q_i) set by gpeq_cova +c inside gpeq_epf, before gpeq_K/gpeq_dst run. These are the +c genuine perturbed-field covariant components delta-B_i; the +c effective field is cvmn = C_i = Q_i + V_i. Writing qvmn lets +c (curl Q).grad(psi) be formed from covariant components, the +c same way cveri forms (curl C).grad(psi) from cvmn. + IF (recon_out) THEN + qvp_mn = bvp_mn + qvt_mn = bvt_mn + qvz_mn = bvz_mn + ENDIF + c Reconstruct detailed K diagnostics for output. Keep this on c the original path so recon terminal totals remain unchanged. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, @@ -7172,6 +7192,9 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucv2_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, $ mfac(ipert), c2vp_mn(ipert), c2vt_mn(ipert), $ c2vz_mn(ipert) + WRITE(uqv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), qvp_mn(ipert), qvt_mn(ipert), + $ qvz_mn(ipert) ENDDO WRITE(ucw_fun,*) WRITE(ucw_mn,*) @@ -7179,6 +7202,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(ucv_mn,*) WRITE(ucv2_fun,*) WRITE(ucv2_mn,*) + WRITE(uqv_mn,*) ENDIF ENDDO @@ -7458,6 +7482,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL ascii_close(ucv_mn) CALL ascii_close(ucv2_fun) CALL ascii_close(ucv2_mn) + CALL ascii_close(uqv_mn) ENDIF c----------------------------------------------------------------------- c terminate. From 182660899773abd6fa0ab9d4d5f3263ac8d6dd84 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 19 Jun 2026 16:48:59 +0900 Subject: [PATCH 146/305] GPEC - recon: remove unused xi injection input flags (xi_flag, xifile) The xi_flag/xifile namelist entries + declarations were WIP and never wired up (idcon_inject_wt was never called). Removed from gpec.f, gpglobal.F, and input/gpec.in to avoid future conflicts. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 602eba2c1223a1445f41018bc020d24e5d869dbf) --- gpec/gpec.f | 5 +---- gpec/gpglobal.F | 5 ++--- input/gpec.in | 2 -- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index b8b015363..59e81505c 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -72,7 +72,7 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,out_ahg2msc,recon_flag,recon_int, - $ cveri_flag,recon_out,recon_flag2,recon_flag3,xi_flag,xifile + $ cveri_flag,recon_out,recon_flag2,recon_flag3 NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -91,7 +91,6 @@ PROGRAM gpec_main dcon_dir="" ieqfile="psi_in.bin" idconfile="euler.bin" - xifile="" ivacuumfile="DEPRECATED" rdconfile="globalsol.bin" power_flag=.TRUE. @@ -200,7 +199,6 @@ PROGRAM gpec_main recon_flag=.FALSE. recon_flag2=.FALSE. recon_flag3=.FALSE. - xi_flag=.FALSE. recon_int="spline" cveri_flag=.FALSE. recon_out=.TRUE. @@ -312,7 +310,6 @@ PROGRAM gpec_main CALL setahgdir(dcon_dir) idconfile = TRIM(dcon_dir)//"/"//TRIM(idconfile) ieqfile = TRIM(dcon_dir)//"/"//TRIM(ieqfile) - IF (xifile /= "") xifile = TRIM(dcon_dir)//"/"//TRIM(xifile) ivacuumfile = TRIM(dcon_dir)//"/"//TRIM(ivacuumfile) rdconfile = TRIM(dcon_dir)//"/"//TRIM(rdconfile) ENDIF diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 9c21a82a3..2a8f4bb7e 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,8 +19,7 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag,recon_out,recon_flag2,recon_flag3, - $ xi_flag + $ cveri_flag,recon_out,recon_flag2,recon_flag3 INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, @@ -42,7 +41,7 @@ MODULE gpglobal_mod CHARACTER(10) :: date,time,zone CHARACTER(16) :: jac_type,jac_in,jac_out,data_type,recon_int CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile - CHARACTER(128) :: dcon_dir,xifile + CHARACTER(128) :: dcon_dir INTEGER, PARAMETER :: hmnum=128 REAL(r8), PARAMETER :: gauss=0.0001 diff --git a/input/gpec.in b/input/gpec.in index e0c82d28f..6e5b0edff 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -88,8 +88,6 @@ recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. - xi_flag=f ! Under recon workflows, reserve an external xi source file instead of using the default perturbation source. - xifile="" ! Optional external xi file path used when xi_flag is true.(file format should be same with euler.bin) cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under recon_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". From 192f7589677cdc24e968e092641df8d47f519f8a Mon Sep 17 00:00:00 2001 From: gpec-deps-bot Date: Thu, 25 Jun 2026 03:06:36 +0000 Subject: [PATCH 147/305] INSTALL - MINOR - regenerate install/DEPENDENCIES.inc (cherry picked from commit ae5b8692030229a291bad4bf79663a9d5f656364) --- install/DEPENDENCIES.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index a7c0d3f46..d842459fa 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -197,7 +197,7 @@ ../gpec/gpec.o: ../coil/coil.o ../gpec/gpdiag.o ../gpec/gpout.o ../gpec/rdcon.o ../gpec/gpeq.o: ../gpec/idcon.o ../gpec/gpglobal.o: ../equil/bicube.o ../equil/fspline.o ../equil/local.o -../gpec/gpout.o: ../coil/field.o ../gpec/gpdiag.o ../gpec/gpresp.o ../gpec/gpvacuum.o ../gpec/idcon.o ../gpec/version.inc ../pentrc/inputs.o ../pentrc/pentrc_interface.o ../pentrc/utilities.o ../slayer/gslayer.o +../gpec/gpout.o: ../coil/field.o ../gpec/gpdiag.o ../gpec/gpglobal.o ../gpec/gpresp.o ../gpec/gpvacuum.o ../gpec/idcon.o ../gpec/version.inc ../pentrc/inputs.o ../pentrc/pentrc_interface.o ../pentrc/utilities.o ../slayer/gslayer.o ../gpec/gpresp.o: ../gpec/gpeq.o ../gpec/gpvacuum.o: ../gpec/gpeq.o ../vacuum/vacuum_ma.o ../gpec/idcon.o: ../equil/equil.o ../gpec/gpglobal.o ../gpec/ismath.o ../vacuum/vacuum_ma.o From 3b14c8a8644ea6512812ff0e4b4bfc8978977377 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 20 Jul 2026 14:18:46 +0900 Subject: [PATCH 148/305] GPEC - recon: review fixes for gpeq/gpout recon routines - remove unused gpout_shear/curvature/k bicube globals (allocated but never bicube_alloc'd, filled, read, or deallocated) and the banner text claiming bicube storage - gpout_recon3 banner: B is computed independently from the equilibrium current, not derived as epf - A - Cpar - Iperp; identity_res is a genuine check (verified ~ -3e-10 on solovev) - state that the Cpar = dst2 cross-check is not performed in-code - move ep_index range checks to the top of recon2/3 (fail fast, matching gpout_recon); totals verified bit-identical - document the psifac lower-bound handling difference between gpout_recon (clamp) and recon2/3 (skip) - delete two dead declaration blocks (C-comparison diagnostics, unused *_cum accumulators) - gpeq_c: comments said contravariant components are extracted by dividing by the Jacobian; no such division exists - the routine carries J Q^i / J C^i throughout (Jacobian-weighted convention) - gpeq_cova header: metric%cs stores g_ij/J, so outputs are plain covariant, not Jacobian-weighted - gpeq_cveri <-> gpout_recon inline copy cross-referenced - fix wrong debug trace name in gpeq_c and gpeq_K exit message Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 3255c2f402f6afb8866e3d73aa9310099f8c0e56) (cherry picked from commit 143bdaa1fd93b24dbdaf769eeea52711c92a73a4) --- docs/examples/DIIID_ideal_example/gpec.in | 2 +- gpec/gpeq.f | 64 ++++++++----------- gpec/gpglobal.F | 3 - gpec/gpout.f | 76 ++++++++++------------- 4 files changed, 60 insertions(+), 85 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/gpec.in b/docs/examples/DIIID_ideal_example/gpec.in index abeb532bb..ece94a5c4 100644 --- a/docs/examples/DIIID_ideal_example/gpec.in +++ b/docs/examples/DIIID_ideal_example/gpec.in @@ -87,4 +87,4 @@ &GPEC_DIAGNOSE timeit=t ! Print timer splits for major subroutines radvar_flag=f ! Map various radial variables (rho,psi_tor) on psi_n grid -/ \ No newline at end of file +/ diff --git a/gpec/gpeq.f b/gpec/gpeq.f index d846b9a8d..068ca5704 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -19,7 +19,7 @@ c 11. gpeq_shear (reconstruction: magnetic shear) c 12. gpeq_curvature (reconstruction: curvature) c 13. gpeq_K (reconstruction: Bernstein K quantity) -c 13b. gpeq_recon3 (reconstruction: |C|^2 decomposition) +c 13b. gpeq_epfterms (reconstruction: |C|^2 decomposition) c 14. gpeq_fcoords c 15. gpeq_fcoordsout c 16. gpeq_bcoords @@ -231,8 +231,8 @@ SUBROUTINE gpeq_contra(psi) END SUBROUTINE gpeq_contra c----------------------------------------------------------------------- c subprogram 3. gpeq_cova. -c compute Jacobian-weighted covariant components from -c Jacobian-weighted contravariant components. +c compute covariant components from Jacobian-weighted ones. +c metric%cs holds g_ij/J, so the contraction cancels J. c----------------------------------------------------------------------- SUBROUTINE gpeq_cova(psi) c----------------------------------------------------------------------- @@ -685,7 +685,7 @@ SUBROUTINE gpeq_c(psi, ipsi) REAL(r8), DIMENSION(0:mthsurf) :: g_22, g_23, g_33 COMPLEX(r8), DIMENSION(-mband:mband) :: g11,g22,g33,g23,g31,g12 - IF(debug_flag) PRINT *, "Entering gpeq_epf at ipsi=", ipsi + IF(debug_flag) PRINT *, "Entering gpeq_c at ipsi=", ipsi c----------------------------------------------------------------------- c 1) Setup: equilibrium and metric at this psi. c----------------------------------------------------------------------- @@ -709,14 +709,9 @@ SUBROUTINE gpeq_c(psi, ipsi) CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) c----------------------------------------------------------------------- -c 4) Extract contravariant components: divide by jacobian. -c -c bwp_mn, bwt_mn, bwz_mn are complex Fourier mode coefficients -c (derived from complex eigenfunctions xsp_mn, xss_mn). -c After IFFT via iscdftb(), they remain complex-valued functions -c of theta. Dividing by real jacobian preserves complex nature. -c This is essential because perturbations are inherently complex -c functions in MHD stability analysis. +c 4) Evaluate equilibrium geometry and metric on the theta grid. +c No Jacobian division: upper family stays J-weighted (J Q^i, +c J C^i), lower family is plain covariant. c----------------------------------------------------------------------- DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) @@ -885,6 +880,9 @@ END SUBROUTINE gpeq_c c c (curl C) · grad(psi) c = (d_theta C_zeta + 2*pi*i*n*C_theta)/J. +c +c Self-contained; gpout_recon inlines the same identity to reuse +c gpeq_epf's state. Keep the two in sync. c----------------------------------------------------------------------- SUBROUTINE gpeq_cveri(psi, cveri_fun) c----------------------------------------------------------------------- @@ -1085,7 +1083,7 @@ SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, REAL(r8), INTENT(OUT) :: epf_int REAL(r8), OPTIONAL, INTENT(OUT) :: epf_p, epf_t, epf_z c optional per-theta C^2/mu0 densities for R-Z heatmaps. Same -c convention as gpeq_recon3 epf_density: integral = sum den*jac/mth. +c convention as gpeq_epfterms epf_density: integral = sum den*jac/mth. REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun INTEGER :: itheta @@ -1161,7 +1159,7 @@ SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, COMPLEX(r8), INTENT(OUT) :: dst_int COMPLEX(r8), OPTIONAL, INTENT(OUT) :: dst_t1, dst_t2, dst_t3 c optional per-theta K_i xi_n^2 densities for R-Z heatmaps. Same -c convention as gpeq_recon3: integral = sum den*jac/mthsurf. +c convention as gpeq_epfterms: integral = sum den*jac/mthsurf. REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: $ dst1_den_fun, dst2_den_fun, dst3_den_fun INTEGER :: itheta @@ -1506,33 +1504,23 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, IF (PRESENT(shear_out)) shear_out = shear_fun IF (PRESENT(curv_out)) curv_out = curv_fun - IF(debug_flag) PRINT *, "Exiting gpeq_K" + IF(debug_flag) PRINT *, "->Leaving gpeq_K" c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- RETURN END SUBROUTINE gpeq_K c----------------------------------------------------------------------- -c subprogram 13b. gpeq_recon3. -c Bernstein-form |C|^2 decomposition for recon3 diagnostic. -c Vector identity: C = Q + V with V = xi_n * mu0 * j x n_hat. -c |C|^2 = |Q|^2 + 2 Re(V*.Q) + |V|^2 -c j is purely tangent to flux surface (j.n=0), so -c |V|^2 = mu0^2 xi_n^2 |j|^2 = mu0^2 xi_n^2 (sigma^2 B^2 -c + p'^2 |grad psi|^2 / B^2). -c Returns the surface integrals (theta only, jac measure): -c A_int = int |Q|^2 / mu0 * J dtheta / mthsurf -c B_int = int 2 Re(V*.Q) / mu0 * J dtheta / mthsurf -c directly via V = C - Q (covariant pairing) -c Cpar_int = int mu0 sigma^2 B^2 xi_n^2 * J dtheta / mthsurf -c (= K_2 xi_n^2 integral; matches gpeq_dst's dst2) -c Iperp_int= int mu0 p'^2 |grad psi|^2 xi_n^2 / B^2 * -c J dtheta / mthsurf -c epf_int = int |C|^2 / mu0 * J dtheta / mthsurf -c Identity check: epf_int = A_int + B_int + Cpar_int + Iperp_int -c should hold within numerical roundoff. -c----------------------------------------------------------------------- - SUBROUTINE gpeq_recon3(psi, A_int, B_int, Iperp_int, Cpar_int, +c subprogram 13b. gpeq_epfterms. +c split the epf surface integral of gpeq_epf into terms of +c comparable magnitude, using C = Q + V, V = xi_n mu0 j x n_hat: +c A = int |Q|^2 / mu0 B = int 2 Re(V*.Q) / mu0 +c Cpar = int mu0 sigma^2 B^2 xin^2 (= gpeq_dst's dst2) +c Iperp = int mu0 p'^2 |grad psi|^2 xin^2 / B^2 +c each on the jac measure, as int (..) J dtheta / mthsurf. The +c terms satisfy epf = A + B + Cpar + Iperp to roundoff. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, $ epf_int, A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) REAL(r8), INTENT(IN) :: psi REAL(r8), INTENT(OUT) :: A_int, B_int, Iperp_int, Cpar_int, @@ -1567,7 +1555,7 @@ SUBROUTINE gpeq_recon3(psi, A_int, B_int, Iperp_int, Cpar_int, COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: xno_fun, xwp_fun - IF(debug_flag) PRINT *, "Entering gpeq_recon3" + IF(debug_flag) PRINT *, "Entering gpeq_epfterms" c----------------------------------------------------------------------- c prepare psi-local perturbed equilibrium and C, Q. c----------------------------------------------------------------------- @@ -1751,12 +1739,12 @@ SUBROUTINE gpeq_recon3(psi, A_int, B_int, Iperp_int, Cpar_int, ENDIF ENDDO - IF(debug_flag) PRINT *, "->Leaving gpeq_recon3" + IF(debug_flag) PRINT *, "->Leaving gpeq_epfterms" c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- RETURN - END SUBROUTINE gpeq_recon3 + END SUBROUTINE gpeq_epfterms c----------------------------------------------------------------------- c subprogram 14. gpeq_fcoords. c transform coordinates to dcon coordinates. diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 0c3c6cdf7..064a102ba 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -93,9 +93,6 @@ MODULE gpglobal_mod TYPE(spline_type) :: sq TYPE(bicube_type) :: psi_in,eqfun,rzphi - TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_shear - TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_curvature - TYPE(bicube_type), ALLOCATABLE, TARGET :: gpout_k TYPE(cspline_type) :: u1,u2,u3,u4,u5,bpsi TYPE(cspline_type) :: smats,tmats,xmats,ymats,zmats TYPE(fspline_type) :: metric diff --git a/gpec/gpout.f b/gpec/gpout.f index 710f26749..a812fbdc5 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6898,9 +6898,8 @@ END SUBROUTINE gpout_close_netcdf c----------------------------------------------------------------------- c subprogram 20. gpout_recon -c main entry point for reconstruction diagnostics. -c calls gpeq functions to compute shear, curvature, K quantities -c and stores results as bicubes for visualization/analysis. +c reconstruction diagnostics: shear, curvature, K, field C and +c the integrals epf, dst giving delta-W_p. c----------------------------------------------------------------------- SUBROUTINE gpout_recon(mode, xspmn) c----------------------------------------------------------------------- @@ -6953,32 +6952,12 @@ SUBROUTINE gpout_recon(mode, xspmn) TYPE(cspline_type) :: recon_epf_spl, recon_dst_spl INTEGER :: cveri_count CHARACTER(4) :: cveri_stat - -c Diagnostic variables for C vector comparison - INTEGER :: ipsi_mid, itheta_mid, ipert_diag, m1 - REAL(r8) :: psi_mid, theta_mid, delpsi_sq, jac_mid, q_mid, - $ q1_mid, chi1_mid, f1_mid, p1_mid, j_theta_coef, j_zeta_coef - COMPLEX(r8) :: term_exp, j_cross_factor, xi_psi_val - COMPLEX(r8), DIMENSION(-mband:mband) :: g11_diag, g12_diag, - $ g22_diag, g23_diag, g31_diag, g33_diag - REAL(r8), DIMENSION(10,10) :: w - REAL(r8) :: g11_r, g22_r, g33_r, g23_r, g31_r, g12_r - + IF(verbose) WRITE(*,*)"" IF(verbose) WRITE(*,*)"GPOUT_RECON: Starting reconstruction"// $ " diagnostics" IF(verbose) WRITE(*,*)"__________________________________________" -c----------------------------------------------------------------------- -c allocate bicubes for shear, curvature, K only when the recon -c output tables are requested. -c----------------------------------------------------------------------- - IF (recon_out) THEN - ALLOCATE(gpout_shear) - ALLOCATE(gpout_curvature) - ALLOCATE(gpout_k) - ENDIF - WRITE(smode,'(I8)') mode smode = ADJUSTL(smode) shear_fun_file = "gpec_recon_shear_fun_sol"//TRIM(smode)//".out" @@ -7120,6 +7099,7 @@ SUBROUTINE gpout_recon(mode, xspmn) c Verify (curl C).grad(psi)=0 using the same psi-local C state c already prepared by gpeq_epf. This avoids rebuilding the c full equilibrium/C reconstruction a second time at each psi. +c Same identity as gpeq_cveri, inlined; keep the two in sync. IF (cveri_flag) THEN DO ipert = 1, mpert curv_mn(ipert) = twopi * ifac * mfac(ipert) @@ -7540,8 +7520,7 @@ SUBROUTINE gpout_recon2(mode, xspmn) REAL(r8) :: psi, eta, rfac, dpsi_local, q2_hr REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals COMPLEX(r8) :: jqx_hr, pdiv_hr, total_hr - COMPLEX(r8) :: jqx_cum, pdiv_cum, total_cum, dw_raw_hr, - $ dw_dcon_hr + COMPLEX(r8) :: dw_raw_hr, dw_dcon_hr COMPLEX(r8), DIMENSION(0:mthsurf) :: q2_fun, jqx_fun, pdiv_fun, $ total_fun CHARACTER(8) :: smode @@ -7578,6 +7557,13 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ "pdiv_im","total_re","total_im","dw_re" ENDIF +c fail fast on an out-of-range mode selection. + ep_index = 1 + IF (mode_flag) ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon2 selected mode is out of range") + ENDIF + CALL idcon_build(mode, xspmn) CALL gpeq_alloc @@ -7606,11 +7592,6 @@ SUBROUTINE gpout_recon2(mode, xspmn) ENDDO dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 - ep_index = 1 - IF (mode_flag) ep_index = mode - IF (ep_index < 1 .OR. ep_index > mpert) THEN - CALL gpec_stop("gpout_recon2 selected mode is out of range") - ENDIF ep_selected = REAL(ep(ep_index)) ep_dcon = ep_selected * dcon_fac q2_hr = 0.0_r8 @@ -7618,6 +7599,8 @@ SUBROUTINE gpout_recon2(mode, xspmn) pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) total_hr = CMPLX(0.0_r8, 0.0_r8, r8) +c integrate on psifac points inside the spline domain; points +c below rzphi%xs(0) are skipped (gpout_recon clamps instead). nint_pts = 0 DO istep = 0, mstep IF (psifac(istep) < rzphi%xs(0)) CYCLE @@ -7782,10 +7765,11 @@ END SUBROUTINE gpout_recon2 c A = int J |Q|^2 / mu0 (perturbed B energy) c Cpar = int J mu0 sigma^2 B^2 xi_n^2 (parallel current^2) c Iperp = int J mu0 p'^2 |grad psi|^2 xi_n^2 / B^2 (perp current^2) +c B = int J 2 Re(V*.Q) / mu0 (cross term) c epf = int J |C|^2 / mu0 (already in recon1) -c and derives B = epf - A - Cpar - Iperp. -c Diagnostic: Cpar should equal dst2 (K_2 xi_n^2) from recon1 -c pointwise and integrated, since both express mu0 sigma^2 B^2 xi_n^2. +c All five are computed independently (B from the equilibrium +c current, not as a remainder), so identity_res is a real check. +c Cpar should equal recon1's dst2; not compared here. c----------------------------------------------------------------------- SUBROUTINE gpout_recon3(mode, xspmn) c----------------------------------------------------------------------- @@ -7843,13 +7827,20 @@ SUBROUTINE gpout_recon3(mode, xspmn) $ "Iperp_cum","Cpar_cum","epf_cum","identity_res" ENDIF +c fail fast on an out-of-range mode selection. + ep_index = 1 + IF (mode_flag) ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon3 selected mode is out of range") + ENDIF + CALL idcon_build(mode, xspmn) CALL gpeq_alloc DO ipsi = 0, mpsi psi = rzphi%xs(ipsi) IF (psi > psilim) EXIT - CALL gpeq_recon3(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr, + CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr, $ A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) IF (recon_out) THEN DO itheta = 0, mthsurf @@ -7869,6 +7860,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 +c integrate on psifac points inside the spline domain; points +c below rzphi%xs(0) are skipped (gpout_recon clamps instead). nint_pts = 0 DO istep = 0, mstep IF (psifac(istep) < rzphi%xs(0)) CYCLE @@ -7883,7 +7876,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) psi = psifac(istep) IF (psi < rzphi%xs(0)) CYCLE IF (psi > rzphi%xs(mpsi)) EXIT - CALL gpeq_recon3(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr) + CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr) c dst1 (=K1 xi_n^2) and dst3 (=K3 xi_n^2) for the C-free dW form c 2 dW_p = A + B + Iperp - dst1 - dst3 (dst2 = Cpar cancels). CALL gpeq_dst(psi, dst_int_c, dst1_c, dst2_c, dst3_c) @@ -7903,7 +7896,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) WRITE(u_int,'(a)') "c identity: epf = A + B + Cpar + Iperp" WRITE(u_int,'(a)') "c identity_res = epf - A - B - Cpar"// $ " - Iperp (should be near zero)" - WRITE(u_int,'(a)') "c Cpar should equal dst2 from recon1" + WRITE(u_int,'(a)') "c Cpar should equal dst2 from recon1"// + $ " (compare against gpec_recon_integration_sol*.out)" WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) ENDIF @@ -7981,11 +7975,6 @@ SUBROUTINE gpout_recon3(mode, xspmn) dw_raw3 = 0.5_r8 * (A_total + B_total + Iperp_total $ - dst1_total - dst3_total) dw_dcon3 = dw_raw3 * dcon_fac - ep_index = 1 - IF (mode_flag) ep_index = mode - IF (ep_index < 1 .OR. ep_index > mpert) THEN - CALL gpec_stop("gpout_recon3 selected mode is out of range") - ENDIF ep_selected = REAL(ep(ep_index)) ep_dcon = ep_selected * dcon_fac @@ -8015,7 +8004,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) WRITE(*,'(a,es17.8e3)') " Iperp_total = ", Iperp_total WRITE(*,'(a,es17.8e3)') " epf_total = ", epf_total WRITE(*,'(a,es17.8e3)') " identity_res = ", identity_residual - WRITE(*,'(a)') " (identity_res should be ~ 0; Cpar = dst2.)" + WRITE(*,'(a)') " (identity_res should be ~ 0. Cpar = dst2"// + $ " from recon1, not checked here.)" WRITE(*,'(a,es17.8e3)') " dW_p(recon3) = ", dw_raw3 WRITE(*,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", dw_dcon3 WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", From 6e2c2c249e8481b215607a33f81f7a369829ebb0 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 20 Jul 2026 14:19:02 +0900 Subject: [PATCH 149/305] GPEC - recon: document recon_flag3 in the reference input recon_flag3 drives the Bernstein |C|^2 decomposition (the recon3 diagnostic) but was missing from input/gpec.in; also note that recon_out gates recon_flag2/recon_flag3 file output as well. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit cca31be7679547930599540020e542b47057bbcd) (cherry picked from commit 47e86e5c7d5a2c8cab65973a8115fcd2cef9bee1) --- input/gpec.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/input/gpec.in b/input/gpec.in index a5c3a5348..f06c19867 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -92,8 +92,9 @@ recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. + recon_flag3=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. - recon_out=t ! Under recon_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. + recon_out=t ! Under recon_flag/recon_flag2/recon_flag3, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / From 0e1f8750b8c3bdfb93085fb8be031f24963e1921 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 20 Jul 2026 14:19:02 +0900 Subject: [PATCH 150/305] DOCS - recon: document recon_out/recon_int/cveri_flag in main.tex Name the three GPEC_OUTPUT switches in the log-output section: recon_out (file output gate), recon_int (spline/trapezoid psi quadrature), cveri_flag (curl-C verify with PASS/WARN/SKIP against 1e-10), and label the cveri paragraph for the cross-reference. Includes the current working revision of the derivation text. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 8ad60e848273cd9bbc870be8d721cad7cdd1c4ae) (cherry picked from commit f89d399ed82155e708d54430f1f0670879a7295b) --- docs/tex/recon/main.tex | 2159 +++++++++++++++------------------------ 1 file changed, 818 insertions(+), 1341 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index fd717c461..80d812db5 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -1370,6 +1370,7 @@ \subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ \end{equation} \paragraph{Verification: $\vec C$ as an effective field and the $\nabla\times\vec C$ check.} +\label{par:cveri} The reconstruction is closed by a self-consistency test of the condition $(\nabla\times\vec C)\cdot\nabla\psi=0$, which holds on every flux surface and admits both a variational and a physical reading. \emph{Variationally}, it is the Euler--Lagrange @@ -1609,24 +1610,26 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} Decompose the $\vec C$ term as \begin{equation} \begin{aligned} -&\mathbf X \equiv \nabla \xi_s \times \nabla \psi,\\ -&\mathbf Y \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi,\\ -&\mathbf Z \equiv -\mathbf B\, \xi_\psi',\\ -&\mathbf U \equiv -\left( +&\mathbf L \equiv \nabla \xi_s \times \nabla \psi=-2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3),\\ +&\mathbf M \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi=2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1\\ +&\mathbf N \equiv -\mathbf B\, \xi_\psi'=-\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3)\\ +&\mathbf O \equiv -\left( \chi'' \nabla \zeta \times \nabla \psi + (q\chi')' \nabla \psi \times \nabla \theta -\right)\xi_\psi,\\ +\right)\xi_\psi=-\sum_m v_m e_m\, +\left[\chi''\vec v_2+(q\chi')'\vec v_3\right]\\ &\mathbf V \equiv \frac{\xi_\psi}{|\nabla \psi|^2} -\, \mu_0 \mathbf j \times \nabla \psi . +\, \mu_0 \mathbf j \times \nabla \psi. \end{aligned} +\label{eq:fieldpieces} \end{equation} \noindent Define \begin{equation} -\mathbf Q \equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, +\mathbf Q \equiv \mathbf L+\mathbf M+\mathbf N+\mathbf O, \qquad \vec C=\mathbf Q+\mathbf V . \label{eq:CeqQplusV} @@ -1646,48 +1649,12 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \Big]. \end{aligned} \end{equation} -Using \(e_m\equiv e^{2\pi i(m\theta-n\zeta)}\), the four $\mathbf Q$ pieces are -\begin{equation} -\begin{aligned} -\mathbf X -&= --2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3),\\ -\mathbf Y -&= -2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1,\\ -\mathbf Z -&= --\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3),\\ -\mathbf U -&= --\sum_m v_m e_m\, -\left[\chi''\vec v_2+(q\chi')'\vec v_3\right]. -\end{aligned} -\end{equation} -Therefore each bilinear product has a fixed matrix block: -\begin{equation} -\begin{array}{c|c|c} -\text{product} & \text{radial amplitudes} & \text{matrix block} \\ \hline -\mathbf X^*\!\cdot\mathbf X & \Xi_s^\dagger\Xi_s & \mathbf A\\ -\mathbf X^*\!\cdot\mathbf Z+\mathbf Z^*\!\cdot\mathbf X -& \Xi_s^\dagger\Xi_\psi' + \Xi_\psi^{\prime\dagger}\Xi_s & \mathbf B\\ -\mathbf X^*\!\cdot(\mathbf Y+\mathbf U+\mathbf V) -+(\mathbf Y+\mathbf U+\mathbf V)^*\!\cdot\mathbf X -& \Xi_s^\dagger\Xi_\psi + \Xi_\psi^\dagger\Xi_s & \mathbf C\\ -\mathbf Z^*\!\cdot\mathbf Z & \Xi_\psi^{\prime\dagger}\Xi_\psi' & \mathbf D\\ -\mathbf Z^*\!\cdot(\mathbf Y+\mathbf U+\mathbf V) -+(\mathbf Y+\mathbf U+\mathbf V)^*\!\cdot\mathbf Z -& \Xi_\psi^{\prime\dagger}\Xi_\psi + \Xi_\psi^\dagger\Xi_\psi' & \mathbf E\\ -\mathbf Y,\mathbf U,\mathbf V,K\ \text{pure terms} -& \Xi_\psi^\dagger\Xi_\psi & \mathbf H . -\end{array} -\end{equation} This is the actual route from the real-space Bernstein expression to the radial matrix form. The factors \(m\) and \(m-nq\) are then written as the diagonal matrices \(\mathbf M\) and \(\mathbf Q\). For instance, \begin{equation} \begin{aligned} -\int d\theta\,\mathcal J\,\mathbf X^*\cdot\mathbf X +\int d\theta\,\mathcal J\,\mathbf L^*\cdot\mathbf L &= \Xi_s^\dagger (2\pi)^2 @@ -1697,7 +1664,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} {}+n\mathbf M\mathbf G_{23} {}+\mathbf M\mathbf G_{33}\mathbf M \right]\Xi_s,\\ -\int d\theta\,\mathcal J\,\mathbf X^*\cdot\mathbf Z +\int d\theta\,\mathcal J\,\mathbf L^*\cdot\mathbf N &= \Xi_s^\dagger \left\{ @@ -1721,6 +1688,88 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \end{aligned} \end{equation} +\paragraph{Blocks from \(\mathbf Q^*\cdot\mathbf Q\)} + +Applying the projection rule above to the products that do not contain \(\mathbf V\) gives the following blocks: +\begin{equation} +|\mathbf L|^2 \leadsto \Xi_s^\dagger \mathbf A \Xi_s, +\qquad +\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23}\mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33}\mathbf M\right]. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf N + \mathbf N^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi^{\prime\dagger} \mathbf B^\dagger \Xi_s, +\qquad +\mathbf B=-2\pi i\chi' \left[n(\mathbf G_{22}+q\mathbf G_{23})+\mathbf M(\mathbf G_{23}+q\mathbf G_{33})\right]. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf M + \mathbf M^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s, +\qquad +\mathbf C_1=-(2\pi)^2\chi'(\mathbf M \mathbf G_{31}+n\mathbf G_{12})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s, +\qquad +\mathbf C_2=-2\pi i \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. +\end{equation} +\begin{equation} +|\mathbf M|^2 \leadsto \Xi_\psi^\dagger \mathbf H_1 \Xi_\psi, +\qquad +\mathbf H_1=(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q. +\end{equation} +\begin{equation} +\mathbf M^* \cdot \mathbf N + \mathbf N^* \cdot \mathbf M +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_1^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_1=-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf M^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf M +\leadsto +\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi, +\qquad +\mathbf H_2=2\pi i\chi'\left[\chi''(\mathbf M \mathbf G_{12}-\mathbf G_{12}\mathbf M)+(q\chi')'(\mathbf M \mathbf G_{31}-\mathbf G_{31}\mathbf M)\right]. +\end{equation} +\begin{equation} +|\mathbf N|^2 \leadsto \Xi_\psi^{\prime\dagger} \mathbf D \Xi_\psi^\prime, +\qquad +\mathbf D=\chi'^2\left[(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+q(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} +\begin{equation} +\mathbf N^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf N +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_2=\chi'\left[\chi''(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+(q\chi')'(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} + +Finally, +\begin{equation} +|\mathbf O|^2 \leadsto \Xi_\psi^\dagger \mathbf H_3 \Xi_\psi, +\qquad +\mathbf{H}_3 += +\chi'' +\left[ +\chi''\,\mathbf{G}_{22} ++ +(q\chi')'\,\mathbf{G}_{23} +\right] ++ +(q\chi')' +\left[ +\chi''\,\mathbf{G}_{23} ++ +(q\chi')'\,\mathbf{G}_{33} +\right] +\end{equation} + \paragraph{Correction of the $\mu_0\vec j\times\nabla\psi$ decomposition} Before continuing, one algebraic correction is needed. @@ -1809,99 +1858,17 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} } \end{equation} -\paragraph{Blocks from \(\mathbf Q^*\cdot\mathbf Q\)} - -Applying the projection rule above to the products that do not contain \(\mathbf V\) gives the following blocks: -\begin{equation} -|\mathbf X|^2 \leadsto \Xi_s^\dagger \mathbf A \Xi_s, -\qquad -\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23}\mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33}\mathbf M\right]. -\end{equation} -\begin{equation} -\mathbf X^* \cdot \mathbf Z + \mathbf Z^* \cdot \mathbf X -\leadsto -\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi^{\prime\dagger} \mathbf B^\dagger \Xi_s, -\qquad -\mathbf B=-2\pi i\chi' \left[n(\mathbf G_{22}+q\mathbf G_{23})+\mathbf M(\mathbf G_{23}+q\mathbf G_{33})\right]. -\end{equation} -\begin{equation} -\mathbf X^* \cdot \mathbf Y + \mathbf Y^* \cdot \mathbf X -\leadsto -\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s, -\qquad -\mathbf C_1=-(2\pi)^2\chi'(\mathbf M \mathbf G_{31}+n\mathbf G_{12})\mathbf Q. -\end{equation} -\begin{equation} -\mathbf X^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf X -\leadsto -\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s, -\qquad -\mathbf C_2=-2\pi i \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. -\end{equation} -\begin{equation} -|\mathbf Y|^2 \leadsto \Xi_\psi^\dagger \mathbf H_1 \Xi_\psi, -\qquad -\mathbf H_1=(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q. -\end{equation} -\begin{equation} -\mathbf Y^* \cdot \mathbf Z + \mathbf Z^* \cdot \mathbf Y -\leadsto -\Xi_\psi^{\prime\dagger}\mathbf E_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_1^\dagger \Xi_\psi^\prime, -\qquad -\mathbf E_1=-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q. -\end{equation} -\begin{equation} -\mathbf Y^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf Y -\leadsto -\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi, -\qquad -\mathbf H_2=2\pi i\chi'\left[\chi''(\mathbf M \mathbf G_{12}-\mathbf G_{12}\mathbf M)+(q\chi')'(\mathbf M \mathbf G_{31}-\mathbf G_{31}\mathbf M)\right]. -\end{equation} -\begin{equation} -|\mathbf Z|^2 \leadsto \Xi_\psi^{\prime\dagger} \mathbf D \Xi_\psi^\prime, -\qquad -\mathbf D=\chi'^2\left[(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+q(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. -\end{equation} -\begin{equation} -\mathbf Z^* \cdot \mathbf U + \mathbf U^* \cdot \mathbf Z -\leadsto -\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime, -\qquad -\mathbf E_2=\chi'\left[\chi''(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+(q\chi')'(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. -\end{equation} - -Finally, -\begin{equation} -|\mathbf U|^2 \leadsto \Xi_\psi^\dagger \mathbf H_3 \Xi_\psi, -\qquad -\mathbf{H}_3 -= -\chi'' -\left[ -\chi''\,\mathbf{G}_{22} -+ -(q\chi')'\,\mathbf{G}_{23} -\right] -+ -(q\chi')' -\left[ -\chi''\,\mathbf{G}_{23} -+ -(q\chi')'\,\mathbf{G}_{33} -\right] -\end{equation} - \paragraph{Bernstein-current correction terms} The vector $\mathbf V$ is not an additional physical term. It appears only because the Bernstein form rewrites the original current--pressure contribution into \[ \vec C=\vec Q+\frac{\xi_\psi}{|\nabla\psi|^2}\mu_0\vec j\times\nabla\psi -=\mathbf X+\mathbf Y+\mathbf Z+\mathbf U+\mathbf V. +=\mathbf L+\mathbf M+\mathbf N+\mathbf O+\mathbf V. \] Therefore the $\mathbf V$-dependent pieces from $|\vec C|^2$ must be combined with the scalar term $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$. After this recombination, the same $\mathbf C$, $\mathbf E$, and $\mathbf H$ blocks in the DCON matrix form are obtained. -The cross term $\mathbf X^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf X$ produces the current part of the $\mathbf C$ block: +The cross term $\mathbf L^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf L$ produces the current part of the $\mathbf C$ block: \[ \Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s, \] @@ -1919,7 +1886,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \right]e^{2\pi i(m\theta-n\zeta)} \end{align} \begin{align} -\mathbf X +\mathbf L &= \nabla\xi_s\times\nabla\psi \\ &= @@ -1930,7 +1897,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} - n\,\nabla\zeta\times\nabla\psi \right)\\ -\mathbf X^* +\mathbf L^* &= \sum_m (-2\pi i)\,u_m^* e^{-2\pi i(m\theta-n\zeta)} @@ -1959,7 +1926,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} Therefore \begin{align} -\mathbf X^*\cdot \mathbf V +\mathbf L^*\cdot \mathbf V &= \sum_{m,m'} (-2\pi i)\, @@ -1980,11 +1947,12 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} (\nabla\theta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) &=(\nabla\theta\cdot\mathbf j)|\nabla\psi|^2-(\nabla\theta\cdot\nabla\psi)(\mathbf j\cdot\nabla\psi) &=j^\theta |\nabla\psi|^2\\ (\nabla\zeta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi)&=j^\zeta |\nabla\psi|^2 +\label{eq:Vtriple} \end{align} Hence \begin{align} -\mathbf X^*\cdot \mathbf V +\mathbf L^*\cdot \mathbf V &= \sum_{m,m'} (-2\pi i)\, @@ -1998,6 +1966,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \mu_0 j^\theta = -\frac{2\pi f'}{\mathcal J}, \qquad \mu_0 j^\zeta = -\frac{\mu_0 p'}{\chi'}-\frac{2\pi f' q}{\mathcal J} +\label{eq:dconcurrents} \end{equation} \begin{align} \mu_0(mj^\theta-nj^\zeta) @@ -2053,12 +2022,12 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \noindent\rule{\linewidth}{0.4pt} \medskip -Next, the $\mathbf Z$--$\mathbf V$ block produces the pressure part of the $\mathbf E$ block: +Next, the $\mathbf N$--$\mathbf V$ block produces the pressure part of the $\mathbf E$ block: \[ \Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime. \] \begin{align} -\mathbf Z^*\cdot \mathbf V +\mathbf N^*\cdot \mathbf V &= \sum_{m,m'} v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} @@ -2083,7 +2052,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} (\mu_0j^\zeta-q\mu_0j^\theta)\mathcal J^2|\nabla\psi|^2 . \end{align} \begin{align} -\mathbf Z^*\cdot \mathbf V +\mathbf N^*\cdot \mathbf V &= -\chi'(\mu_0j^\zeta-q\mu_0j^\theta) \sum_{m,m'}v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} \\ @@ -2093,7 +2062,7 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} \begin{equation} \boxed{ \begin{aligned} -\mathbf Z^*\cdot \mathbf V+\mathbf V^*\cdot\mathbf Z +\mathbf N^*\cdot \mathbf V+\mathbf V^*\cdot\mathbf N \leadsto\; \Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi + @@ -2106,567 +2075,62 @@ \subsection{Reconstruction v2: radial matrix representation of $\delta W$} } \end{equation} + \medskip \noindent\rule{\linewidth}{0.4pt} -\medskip - -The term $\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is also a -pure-$\Xi_\psi$ contribution, but it is not closed as an independent DCON -block. It must be combined with the other pure scalar current--pressure terms -before the final $\mathbf H_4$ block is obtained. - -\begin{align} -\mathbf Y -&= -(\nabla\theta\times\nabla\zeta)\,\mathcal J\,\mathbf B\cdot\nabla\xi_\psi= -2\pi i\,\chi' -\sum_m (m-nq)\,v_m\,e^{2\pi i(m\theta-n\zeta)}\,\vec v_1 \\ -\mathbf Y^*&= -(-2\pi i)\chi' -\sum_m (m-nq)\,v_m^*\,e^{-2\pi i(m\theta-n\zeta)}\,\vec v_1 -\end{align} +\paragraph{Closed form of the $\mathbf V$-sector ($\mathbf H_4$).} -Using the corrected form of $\mathbf V$, -\begin{align} -\mathbf Y^*\cdot\mathbf V -&= -(-2\pi i)\chi' -\sum_{m,m'} -v_m^*(m-nq)v_{m'} -e^{2\pi i(m'-m)\theta} -\; -\frac{1}{|\nabla\psi|^2}\, -\vec v_1\cdot(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ -&= -(-2\pi i)\chi' -\sum_{m,m'} -v_m^*(m-nq)v_{m'} -e^{2\pi i(m'-m)\theta} -\; -\frac{1}{\mathcal J^2|\nabla\psi|^2} -\big( -\mathcal A_2 g_{12}-\mathcal A_3 g_{31} -\big) -\end{align} +The four pure-$\Xi_\psi$ contributions assemble into +\begin{equation} +\boxed{\;\mathbf H_4=\mathbf H_{MV}+\mathbf H_{OV}+\mathbf H_V+\mathbf H_K\;,} +\end{equation} +equal, term by term, to the $\mathbf V$-sector of the Bernstein form +\eqref{eq:ep_bernsteinK} without invoking any equilibrium identity. The +Grad--Shafranov equation reduces it to the compact block +\begin{equation} +\boxed{\; +\mathbf H_4=\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) +-2\pi f'q'\chi'\,\mathbf I\;,} +\end{equation} +as proved in Appendix~\ref{app:h4}. -Now expand the bracket: -\begin{align} -\mathcal A_2 g_{12}-\mathcal A_3 g_{31} -&= -(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{12} -- -(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{31} \\ -&= -\mu_0j^\theta(g_{12}g_{23}-g_{31}g_{22}) -+ -\mu_0j^\zeta(g_{12}g_{33}-g_{31}g_{23}) \\ -&= -\mathcal J^2 -\left[ -\mu_0j^\theta(\nabla\zeta\cdot\nabla\psi) -- -\mu_0j^\zeta(\nabla\theta\cdot\nabla\psi) -\right] -\end{align} +The complete term-to-matrix assignment is therefore \begin{equation} \boxed{ \begin{aligned} -\mathbf Y^*\cdot\mathbf V -&= -(-2\pi i)\chi' -\sum_{m,m'} -v_m^*(m-nq)v_{m'}e^{2\pi i(m'-m)\theta} \\ -&\qquad\times -\mu_0 -\frac{ -j^\theta(\nabla\zeta\cdot\nabla\psi) -- -j^\zeta(\nabla\theta\cdot\nabla\psi) -}{ -|\nabla\psi|^2 -}. +\mathbf A &: \quad \mathbf L\!\cdot\mathbf L,\\ +\mathbf B &: \quad \mathbf L\!\cdot\mathbf N,\\ +\mathbf C &: \quad +\mathbf L\!\cdot\mathbf M\to\mathbf C_1,\quad +\mathbf L\!\cdot\mathbf O\to\mathbf C_2,\quad +\mathbf L\!\cdot\mathbf V\to\mathbf C_3,\\ +\mathbf D &: \quad \mathbf N\!\cdot\mathbf N,\\ +\mathbf E &: \quad +\mathbf N\!\cdot\mathbf M\to\mathbf E_1,\quad +\mathbf N\!\cdot\mathbf O\to\mathbf E_2,\quad +\mathbf N\!\cdot\mathbf V\to\mathbf E_3,\\ +\mathbf H &: \quad +\mathbf M\!\cdot\mathbf M\to\mathbf H_1,\quad +\mathbf M\!\cdot\mathbf O+\to\mathbf H_2,\quad +\mathbf O\!\cdot\mathbf O\to\mathbf H_3,\\ +&\quad +\mathbf M\!\cdot\mathbf V ++\mathbf O\!\cdot\mathbf V ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\to\mathbf H_4 . \end{aligned} } \end{equation} - -It is more useful to rewrite this in field form. Since +Thus the final coefficient matrices are \begin{equation} -\mathcal J\,\mathbf B\cdot\nabla -= -\chi'\left(\partial_\theta+q\partial_\zeta\right), -\end{equation} -we may write -\begin{align} -\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -&= -\chi' -\left[ -(\partial_\theta+q\partial_\zeta)\xi_\psi^* -\right] -\xi_\psi\, -\mathcal F -+ -\chi'\, -\xi_\psi^* -\left[ -(\partial_\theta+q\partial_\zeta)\xi_\psi -\right]\mathcal F, -\end{align} -where -\begin{equation} -\mathcal F -\equiv -\mathcal J -\frac{ -(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi) -- -(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi) -}{ -|\nabla\psi|^2 -}. -\end{equation} - -Because the equilibrium is axisymmetric and periodic in $\theta,\zeta$, we may integrate by parts in $\theta$ and drop the boundary term: -\begin{align} -\int d\theta\,d\zeta\;\mathcal J(\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y) -&= -\chi'\int d\theta\,d\zeta\; -\partial_\theta(|\xi_\psi|^2)\,\mathcal F \\ -&= --\chi' \int d\theta\,d\zeta\; -|\xi_\psi|^2\,\partial_\theta \mathcal F. -\end{align} - -Substituting $\mu_0 j^\theta$ and $\mu_0 j^\zeta$, -\begin{align} -\mathcal F -&= -\mathcal J -\left[ --\frac{2\pi f'}{\mathcal J} -\frac{\nabla\zeta\cdot\nabla\psi}{|\nabla\psi|^2} -+ -\left( -\frac{\mu_0p'}{\chi'} -+ -\frac{2\pi f'q}{\mathcal J} -\right) -\frac{\nabla\theta\cdot\nabla\psi}{|\nabla\psi|^2} -\right] \\ -&= -2\pi f' -\frac{ -q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) -}{ -|\nabla\psi|^2 -} -+ -\frac{\mu_0p'}{\chi'}\, -\mathcal J\, -\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}. -\end{align} - -Therefore -\begin{align} -\partial_\theta \mathcal F -&= -2\pi f'\, -\partial_\theta -\left[ -\frac{ -q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) -}{ -|\nabla\psi|^2 -} -\right] -+ -\frac{\mu_0p'}{\chi'}\, -\partial_\theta -\left[ -\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} -\right]. -\end{align} - -Using the DCON-normalized shear parameter, -\begin{equation} -S_\psi -= -\frac{\chi'^2}{\mathcal J} -\left[ -q' -+ -\partial_\theta -\left( -\frac{ -q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) -}{ -|\nabla\psi|^2 -} -\right) -\right], -\end{equation} -we obtain -\begin{equation} -\partial_\theta -\left[ -\frac{ -q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) -}{ -|\nabla\psi|^2 -} -\right] -= -\frac{\mathcal J}{\chi'^2}S_\psi-q'. -\end{equation} - -Hence -\begin{align} -\partial_\theta\mathcal F -&= -\frac{2\pi f'}{\chi'^2}\,\mathcal J\,S_\psi -- -2\pi f'q' -+ -\frac{\mu_0p'}{\chi'}\, -\partial_\theta -\left[ -\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} -\right]. -\end{align} - -The last derivative, -\begin{equation} -\partial_\theta -\left[ -\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} -\right], -\end{equation} -is not a general coordinate identity and is not reduced here as a standalone -matrix block. Therefore -$\mathbf Y^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Y$ is kept as an -intermediate pure-$\Xi_\psi$ Bernstein scalar term. The closed DCON block is -obtained only after this term is combined below with -$\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$, $|\mathbf V|^2$, and -the scalar $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$ contribution. - -\medskip -\noindent\rule{\linewidth}{0.4pt} -\medskip - -\paragraph{The term $\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U$.} - -Since both $\mathbf U$ and $\mathbf V$ are proportional to $\xi_\psi$, this contribution is a pure-$\Xi_\psi$ block and can be written as -\[ -\Xi_\psi^\dagger \mathbf H_{UV} \Xi_\psi. -\] - -Recall -\begin{equation} -\mathbf U -= --\left( -\chi''\vec v_2+(q\chi')'\vec v_3 -\right)\xi_\psi. -\end{equation} -Hence -\begin{align} -\mathbf U^*\cdot\mathbf V -&= --\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\left( -\chi''\vec v_2+(q\chi')'\vec v_3 -\right)\cdot -\left( -\mathcal A_2\vec v_2-\mathcal A_3\vec v_3 -\right) \\ -&= --\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^2} -\left[ -\chi''(\mathcal A_2 g_{22}-\mathcal A_3 g_{23}) -+ -(q\chi')'(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) -\right]. -\end{align} - -Now simplify each bracket: -\begin{align} -\mathcal A_2 g_{22}-\mathcal A_3 g_{23} -&= -(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{22} -- -(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ -&= -\mu_0j^\zeta(g_{22}g_{33}-g_{23}^2) \\ -&= -\mu_0j^\zeta \mathcal J^2 |\nabla\psi|^2, -\end{align} -and -\begin{align} -\mathcal A_2 g_{23}-\mathcal A_3 g_{33} -&= -(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{23} -- -(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{33} \\ -&= --\mu_0j^\theta(g_{22}g_{33}-g_{23}^2) \\ -&= --\mu_0j^\theta \mathcal J^2 |\nabla\psi|^2. -\end{align} -Therefore -\begin{align} -\mathbf U^*\cdot\mathbf V -&= --|\xi_\psi|^2 -\left[ -\mu_0j^\zeta\chi'' -- -\mu_0j^\theta(q\chi')' -\right]. -\end{align} - -Substituting $j^\theta$ and $j^\zeta$, -\begin{align} -\mathbf U^*\cdot\mathbf V -&= --|\xi_\psi|^2 -\left[ -\left( --\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J} -\right)\chi'' -- -\left( --\frac{2\pi f'}{\mathcal J} -\right)(q\chi')' -\right] \\ -&= -|\xi_\psi|^2 -\left[ -\mu_0p'\frac{\chi''}{\chi'} -+ -\frac{2\pi f'}{\mathcal J} -\left( -q\chi''-(q\chi')' -\right) -\right]. -\end{align} -Using -\begin{equation} -(q\chi')'=q'\chi'+q\chi'', -\end{equation} -we obtain -\begin{equation} -\boxed{ -\mathbf U^*\cdot\mathbf V -= -|\xi_\psi|^2 -\left[ -\mu_0p'\frac{\chi''}{\chi'} -- -\frac{2\pi f' q'\chi'}{\mathcal J} -\right]. -} -\end{equation} - -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf U^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf U -\leadsto\; -\Xi_\psi^\dagger \mathbf H_{UV}\Xi_\psi, \\ -\qquad -\mathbf H_{UV} -&= -2\mu_0p'\frac{\chi''}{\chi'}\,\mathbf J --4\pi f'q'\chi'\,\mathbf I. -\end{aligned} -} -\end{equation} - -\medskip -\noindent\rule{\linewidth}{0.4pt} -\medskip - -\paragraph{The term $|\mathbf V|^2$.} - -This is again a pure-$\Xi_\psi$ contribution, so we write -\[ -\Xi_\psi^\dagger \mathbf H_V \Xi_\psi. -\] - -From -\begin{equation} -\mathbf V -= -\frac{\xi_\psi}{|\nabla\psi|^2} -(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3), -\end{equation} -we have -\begin{align} -|\mathbf V|^2 -&= -\frac{|\xi_\psi|^2}{|\nabla\psi|^4} -(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3)\cdot -(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ -&= -\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} -\left[ -\mathcal A_2^2 g_{22} --2\mathcal A_2\mathcal A_3 g_{23} -+\mathcal A_3^2 g_{33} -\right]. -\end{align} - -Now expand the quadratic form: -\begin{align} -&\mathcal A_2^2 g_{22} --2\mathcal A_2\mathcal A_3 g_{23} -+\mathcal A_3^2 g_{33} \\ -&= -(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})^2 g_{22} --2(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}) -(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ -&\qquad -+(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})^2 g_{33} \\ -&= -(g_{22}g_{33}-g_{23}^2) -\left[ -\mu_0^2g_{22}(j^\theta)^2 -+ -2\mu_0^2g_{23}j^\theta j^\zeta -+ -\mu_0^2g_{33}(j^\zeta)^2 -\right]. -\end{align} -Therefore -\begin{align} -|\mathbf V|^2 -&= -\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} -(g_{22}g_{33}-g_{23}^2) -\left[ -\mu_0^2g_{22}(j^\theta)^2 -+ -2\mu_0^2g_{23}j^\theta j^\zeta -+ -\mu_0^2g_{33}(j^\zeta)^2 -\right] \\ -&= -\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\left[ -\mu_0^2g_{22}(j^\theta)^2 -+ -2\mu_0^2g_{23}j^\theta j^\zeta -+ -\mu_0^2g_{33}(j^\zeta)^2 -\right]. -\end{align} - -This is the completely explicit covariant-form result. - -It is also useful to rewrite it invariantly. Since -\begin{equation} -\mathbf j -= -\sigma \mathbf B -+\frac{p'}{B^2}\mathbf B\times \nabla\psi, -\qquad -\sigma\equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, -\end{equation} -one finds -\begin{equation} -\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} -= -\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B) -+ -\frac{(\mu_0p')^2}{B^2}|\nabla\psi|^2. -\end{equation} -Hence -\begin{equation} -\boxed{ -|\mathbf V|^2 -= -|\xi_\psi|^2 -\left[ -\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} -+ -\frac{(\mu_0p')^2}{B^2} -\right]. -} -\end{equation} - -\begin{equation} -\boxed{ -\begin{aligned} -|\mathbf V|^2 -\leadsto\; -\Xi_\psi^\dagger \mathbf H_V \Xi_\psi, \\ -\qquad -(\mathbf H_V)_{mm'} -&\equiv -\left\langle -\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} -+ -\frac{(\mu_0p')^2}{B^2} -\right\rangle_{mm'}. -\end{aligned} -} -\end{equation} - -\medskip -\noindent\rule{\linewidth}{0.4pt} -\paragraph{Closed form of the $\mathbf V$-sector ($\mathbf H_4$).} - -The four pure-$\Xi_\psi$ contributions assemble into -\begin{equation} -\boxed{\;\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K\;,} -\end{equation} -equal, term by term, to the $\mathbf V$-sector of the Bernstein form -\eqref{eq:ep_bernsteinK} without invoking any equilibrium identity. The -Grad--Shafranov equation reduces it to the compact block -\begin{equation} -\boxed{\; -\mathbf H_4=\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) --2\pi f'q'\chi'\,\mathbf I\;,} -\end{equation} -as proved in Appendix~\ref{app:h4}. - -The complete term-to-matrix assignment is therefore -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf A &: \quad \mathbf X^*\!\cdot\mathbf X,\\ -\mathbf B &: \quad \mathbf X^*\!\cdot\mathbf Z,\\ -\mathbf C &: \quad -\mathbf X^*\!\cdot\mathbf Y\to\mathbf C_1,\quad -\mathbf X^*\!\cdot\mathbf U\to\mathbf C_2,\quad -\mathbf X^*\!\cdot\mathbf V\to\mathbf C_3,\\ -\mathbf D &: \quad \mathbf Z^*\!\cdot\mathbf Z,\\ -\mathbf E &: \quad -\mathbf Z^*\!\cdot\mathbf Y\to\mathbf E_1,\quad -\mathbf Z^*\!\cdot\mathbf U\to\mathbf E_2,\quad -\mathbf Z^*\!\cdot\mathbf V\to\mathbf E_3,\\ -\mathbf H &: \quad -\mathbf Y^*\!\cdot\mathbf Y\to\mathbf H_1,\quad -\mathbf Y^*\!\cdot\mathbf U+\mathbf U^*\!\cdot\mathbf Y\to\mathbf H_2,\quad -\mathbf U^*\!\cdot\mathbf U\to\mathbf H_3,\\ -&\quad -\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y -+\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U -+|\mathbf V|^2 --\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\to\mathbf H_4 . -\end{aligned} -} -\end{equation} -Thus the final coefficient matrices are -\begin{equation} -\boxed{ -\begin{aligned} -\mathbf C&=\mathbf C_1+\mathbf C_2+\mathbf C_3, \\ -\mathbf E&=\mathbf E_1+\mathbf E_2+\mathbf E_3, \\ -\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_4. -\end{aligned} -} +\boxed{ +\begin{aligned} +\mathbf C&=\mathbf C_1+\mathbf C_2+\mathbf C_3, \\ +\mathbf E&=\mathbf E_1+\mathbf E_2+\mathbf E_3, \\ +\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_4. +\end{aligned} +} \end{equation} Subject to the boundary and reduced-compressional assumptions stated above, this is the DCON-normalized version of the matrix structure in Eq.~(35) of @@ -2747,6 +2211,28 @@ \section{Log-output quantities and their exact integrands} (\texttt{GPEC\_RECON3}, driver \texttt{gpout\_recon3} $\to$ \texttt{gpeq\_recon3}). +Three further switches in \texttt{\&GPEC\_OUTPUT} control how these +diagnostics run: +\begin{itemize} +\item \texttt{recon\_out} (default \texttt{.TRUE.}) gates the + \emph{file} output. When true, each route writes its per-surface + ASCII tables and appends its summary block to \texttt{gpec.log}; + when false the terminal summary is still printed, but no files are + written. +\item \texttt{recon\_int} (default \texttt{"spline"}, alternative + \texttt{"trapezoid"}) selects the quadrature used for the radial + $\psi$ integration on the \texttt{psifac} grid. Every + $\delta W_p$ total reported below is built with this scheme, so the + choice affects the reported numbers at the level of the quadrature + error. +\item \texttt{cveri\_flag} (default \texttt{.FALSE.}) enables the + $(\nabla\times\vec C)\cdot\nabla\psi=0$ verification of + \S\ref{par:cveri} alongside \texttt{recon\_flag}. The pointwise + residual is accumulated over all surfaces and reported as a status + of \texttt{PASS}, \texttt{WARN} or \texttt{SKIP} against a fixed + tolerance of $10^{-10}$, together with its maximum and rms. +\end{itemize} + The DCON normalization factor that converts the GPEC energy to the DCON-normalized $\delta W$, used in the normalized outputs below, is \begin{equation} @@ -2865,9 +2351,12 @@ \subsection{\texttt{GPEC\_RECON2} (\texttt{recon\_flag2})} \texttt{int\_J\_jQxixi\_over\_mu0} &=\int\! d\psi\,\frac{1}{\mu_0}\oint\frac{1}{\mathcal J} \Big[-\mu_0 j^\theta\bigl(\tilde Q^\psi\,\tilde\xi^{\zeta*}-\tilde Q^\zeta\,\tilde\xi^{\psi*}\bigr) + \notag\\ +&\qquad\qquad +\mu_0 j^\zeta\bigl(\tilde Q^\psi\,\tilde\xi^{\theta*}-\tilde Q^\theta\,\tilde\xi^{\psi*}\bigr) \Big]\mathcal J\,d\theta - =\int\! d\psi\oint \vec j\cdot(\vec Q\times\vec\xi^{\,*})\,\mathcal J\,d\theta,\\ + \notag\\ +&=\int\! d\psi\oint \vec j\cdot(\vec Q\times\vec\xi^{\,*})\,\mathcal J\,d\theta,\\ \texttt{int\_J\_pdiv\_term} &=\int\! d\psi\oint (\nabla\!\cdot\!\vec\xi)^{*}\,p'\,\xi^{\psi}\, @@ -3021,200 +2510,514 @@ \subsubsection{Combined $\delta W_p$ and the $K_2$ cancellation} effect of $j_\parallel$ survives only indirectly --- through the shear term $K_1$ (its dependence on $\sigma S$) and through the cross term $B$. -\subsection{Cross-checks between the three routes} +\subsection{Cross-checks between the three routes} + +The identities below are exact and are confirmed at roundoff in the DIII-D +example ($n=1$, \texttt{gpec.log}): +\begin{align} +\texttt{recon3:A\_total} &= \texttt{recon2:int\_J\_Q2\_over\_mu0} + &&(0.817676),\\ +\texttt{recon3:Cpar\_total} &= \texttt{recon1:K2\_xin2} + = \texttt{gpeq\_dst}:dst_2 &&(1.082617),\\ +\texttt{recon3:epf\_total} &= \texttt{recon1:int\_J\_C2\_over\_mu0} + &&(0.941346),\\ +\texttt{recon1:dW\_p} &\approx \texttt{recon2:dW\_p} + &&(-0.1190\ \text{vs}\ -0.1198). +\end{align} + + +\clearpage +\appendix + +\section{Full reduction of the $\mathbf H_4$ ($\mathbf V$-sector) block}\label{app:h4} + +This appendix proves the $\mathbf H_4$ ($\mathbf V$-sector) identity quoted in +the main text at two levels: (i)~the equilibrium-free Bernstein form +$\mathbf H_4=\mathbf H_{MV}+\mathbf H_{OV}+\mathbf H_V+\mathbf H_K$, equal term by +term to \eqref{eq:ep_bernsteinK}; and (ii)~its collapse to the compact block +$\mathbf H_4=\mu_0p'\!\left(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J'\right)-2\pi +f'q'\chi'\,\mathbf I$, whose only equilibrium input is the Grad--Shafranov +equation. The steps that invoke an equilibrium relation (force balance, the +magnetic-surface condition $\mathbf B\cdot\nabla\psi=0$, Grad--Shafranov) are +noted where they occur. + +\subsection{Explicit pure-$\Xi_\psi$ term reductions}\label{app:reductions} + +The following term-by-term reductions of the pure-$\Xi_\psi$ contributions +were relocated here from the main text; they build the four pieces +$\mathbf H_{MV},\mathbf H_{OV},\mathbf H_V,\mathbf H_K$ assembled in the +closed form of Sec.~\ref{app:h4}. + +The term $\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M$ is also a +pure-$\Xi_\psi$ contribution, but it is not closed as an independent DCON +block. It must be combined with the other pure scalar current--pressure terms +before the final $\mathbf H_4$ block is obtained. + +\begin{align} +\mathbf M +&= +(\nabla\theta\times\nabla\zeta)\,\mathcal J\,\mathbf B\cdot\nabla\xi_\psi= +2\pi i\,\chi' +\sum_m (m-nq)\,v_m\,e^{2\pi i(m\theta-n\zeta)}\,\vec v_1 \\ +\mathbf M^*&= +(-2\pi i)\chi' +\sum_m (m-nq)\,v_m^*\,e^{-2\pi i(m\theta-n\zeta)}\,\vec v_1 +\end{align} + +Using the corrected form of $\mathbf V$, +\begin{align} +\mathbf M^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{|\nabla\psi|^2}\, +\vec v_1\cdot(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{\mathcal J^2|\nabla\psi|^2} +\big( +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +\big) +\end{align} + +Now expand the bracket: +\begin{align} +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{12} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{31} \\ +&= +\mu_0j^\theta(g_{12}g_{23}-g_{31}g_{22}) ++ +\mu_0j^\zeta(g_{12}g_{33}-g_{31}g_{23}) \\ +&= +\mathcal J^2 +\left[ +\mu_0j^\theta(\nabla\zeta\cdot\nabla\psi) +- +\mu_0j^\zeta(\nabla\theta\cdot\nabla\psi) +\right] +\end{align} +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf M^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'}e^{2\pi i(m'-m)\theta} \\ +&\qquad\times +\mu_0 +\frac{ +j^\theta(\nabla\zeta\cdot\nabla\psi) +- +j^\zeta(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. +\end{aligned} +} +\end{equation} + +It is more useful to rewrite this in field form. Since +\begin{equation} +\mathcal J\,\mathbf B\cdot\nabla += +\chi'\left(\partial_\theta+q\partial_\zeta\right), +\end{equation} +we may write +\begin{align} +\mathcal J(\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M) +&= +\chi' +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi^* +\right] +\xi_\psi\, +\mathcal F ++ +\chi'\, +\xi_\psi^* +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi +\right]\mathcal F, +\end{align} +where +\begin{equation} +\mathcal F +\equiv +\mathcal J +\frac{ +(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi) +- +(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. +\end{equation} + +Because the equilibrium is axisymmetric and periodic in $\theta,\zeta$, we may integrate by parts in $\theta$ and drop the boundary term: +\begin{align} +\int d\theta\,d\zeta\;\mathcal J(\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M) +&= +\chi'\int d\theta\,d\zeta\; +\partial_\theta(|\xi_\psi|^2)\,\mathcal F \\ +&= +-\chi' \int d\theta\,d\zeta\; +|\xi_\psi|^2\,\partial_\theta \mathcal F. +\end{align} + +Substituting $\mu_0 j^\theta$ and $\mu_0 j^\zeta$, +\begin{align} +\mathcal F +&= +\mathcal J +\left[ +-\frac{2\pi f'}{\mathcal J} +\frac{\nabla\zeta\cdot\nabla\psi}{|\nabla\psi|^2} ++ +\left( +\frac{\mu_0p'}{\chi'} ++ +\frac{2\pi f'q}{\mathcal J} +\right) +\frac{\nabla\theta\cdot\nabla\psi}{|\nabla\psi|^2} +\right] \\ +&= +2\pi f' +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} ++ +\frac{\mu_0p'}{\chi'}\, +\mathcal J\, +\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}. +\end{align} + +Therefore +\begin{align} +\partial_\theta \mathcal F +&= +2\pi f'\, +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right] ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. +\end{align} + +Using the DCON-normalized shear parameter, +\begin{equation} +S_\psi += +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\partial_\theta +\left( +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right) +\right], +\end{equation} +we obtain +\begin{equation} +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right] += +\frac{\mathcal J}{\chi'^2}S_\psi-q'. +\end{equation} -The identities below are exact and are confirmed at roundoff in the DIII-D -example ($n=1$, \texttt{gpec.log}): +Hence \begin{align} -\texttt{recon3:A\_total} &= \texttt{recon2:int\_J\_Q2\_over\_mu0} - &&(0.817676),\\ -\texttt{recon3:Cpar\_total} &= \texttt{recon1:K2\_xin2} - = \texttt{gpeq\_dst}:dst_2 &&(1.082617),\\ -\texttt{recon3:epf\_total} &= \texttt{recon1:int\_J\_C2\_over\_mu0} - &&(0.941346),\\ -\texttt{recon1:dW\_p} &\approx \texttt{recon2:dW\_p} - &&(-0.1190\ \text{vs}\ -0.1198). +\partial_\theta\mathcal F +&= +\frac{2\pi f'}{\chi'^2}\,\mathcal J\,S_\psi +- +2\pi f'q' ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. \end{align} +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip -\clearpage -\appendix +\paragraph{The term $\mathbf O^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf O$.} -\section{Full reduction of the $\mathbf H_4$ ($\mathbf V$-sector) block}\label{app:h4} +Since both $\mathbf O$ and $\mathbf V$ are proportional to $\xi_\psi$, this contribution is a pure-$\Xi_\psi$ block and can be written as +\[ +\Xi_\psi^\dagger \mathbf H_{UV} \Xi_\psi. +\] -This appendix proves the $\mathbf H_4$ ($\mathbf V$-sector) identity quoted in -the main text at two levels: (i)~the equilibrium-free Bernstein form -$\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K$, equal term by -term to \eqref{eq:ep_bernsteinK}; and (ii)~its collapse to the compact block -$\mathbf H_4=\mu_0p'\!\left(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J'\right)-2\pi -f'q'\chi'\,\mathbf I$, whose only equilibrium input is the Grad--Shafranov -equation. The steps that invoke an equilibrium relation (force balance, the -magnetic-surface condition $\mathbf B\cdot\nabla\psi=0$, Grad--Shafranov) are -noted where they occur. +Recall +\begin{equation} +\mathbf O += +-\left( +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\xi_\psi. +\end{equation} +Hence +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left( +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\cdot +\left( +\mathcal A_2\vec v_2-\mathcal A_3\vec v_3 +\right) \\ +&= +-\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^2} +\left[ +\chi''(\mathcal A_2 g_{22}-\mathcal A_3 g_{23}) ++ +(q\chi')'(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) +\right]. +\end{align} -\subsection{Setup: the $\mathbf V$-sector pieces and the single geometric rule} +Now simplify each bracket: +\begin{align} +\mathcal A_2 g_{22}-\mathcal A_3 g_{23} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{22} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&= +\mu_0j^\zeta(g_{22}g_{33}-g_{23}^2) \\ +&= +\mu_0j^\zeta \mathcal J^2 |\nabla\psi|^2, +\end{align} +and +\begin{align} +\mathcal A_2 g_{23}-\mathcal A_3 g_{33} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{23} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{33} \\ +&= +-\mu_0j^\theta(g_{22}g_{33}-g_{23}^2) \\ +&= +-\mu_0j^\theta \mathcal J^2 |\nabla\psi|^2. +\end{align} +Therefore +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\mu_0j^\zeta\chi'' +- +\mu_0j^\theta(q\chi')' +\right]. +\end{align} -We work in DCON straight-field-line flux coordinates $(\psi,\theta,\zeta)$, all -normalized to $[0,1]$, with $e_m\equiv e^{2\pi i(m\theta-n\zeta)}$ and the -Jacobian-scaled basis $\vec v_i=\vec e_i/\mathcal J$, so that -$\vec v_i\cdot\vec v_j=g_{ij}/\mathcal J^2$. We start from +Substituting $j^\theta$ and $j^\zeta$, +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\left( +-\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J} +\right)\chi'' +- +\left( +-\frac{2\pi f'}{\mathcal J} +\right)(q\chi')' +\right] \\ +&= +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} ++ +\frac{2\pi f'}{\mathcal J} +\left( +q\chi''-(q\chi')' +\right) +\right]. +\end{align} +Using \begin{equation} -\mathbf Q \equiv \mathbf X+\mathbf Y+\mathbf Z+\mathbf U, -\qquad -\vec C = \mathbf Q+\mathbf V, -\end{equation} -so that $|\vec C|^2$ expands into $5$ diagonal and $10$ cross products. The scalar -Bernstein term $-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$ is a \emph{separate} piece of the energy -$2\mu_0\delta W=\int[\,|\vec C|^2+\mu_0\gamma p|\nabla\cdot\vec\xi|^2-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2\,]$; -the purpose of this appendix is to show that the $\mathbf H_4$ part of the -$\mathbf V$-sector --- the products -$2\,\mathrm{Re}(\mathbf V^{*}\!\cdot\mathbf Y) - +2\,\mathrm{Re}(\mathbf V^{*}\!\cdot\mathbf U)+|\mathbf V|^2$, -together with that scalar --- collapses to the compact block -$\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. -(The other two $\mathbf V$-cross products, $\mathbf V^{*}\!\cdot\mathbf X$ and -$\mathbf V^{*}\!\cdot\mathbf Z$, are \emph{not} part of $\mathbf H_4$; see below.) -The field pieces are +(q\chi')'=q'\chi'+q\chi'', +\end{equation} +we obtain \begin{equation} -\begin{aligned} -\mathbf X &= -2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3), -& -\mathbf Y &= 2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1,\\ -\mathbf Z &= -\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3), -& -\mathbf U &= -\sum_m v_m e_m\left[\chi''\vec v_2+(q\chi')'\vec v_3\right],\\ -\mathbf V &= \frac{\xi_\psi}{|\nabla\psi|^2}\left(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3\right), -\end{aligned} +\boxed{ +\mathbf O^*\cdot\mathbf V += +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} +- +\frac{2\pi f' q'\chi'}{\mathcal J} +\right]. +} \end{equation} -with + \begin{equation} -\mathcal A_2 \equiv \mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}, -\qquad -\mathcal A_3 \equiv \mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23}. -\end{equation} - -Physically $\mathbf V$ is simply the edge current rotated into the flux surface, -$\mathbf V=\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi$ --- a -concrete field on the $(\psi,\theta)$ grid --- and the components -$\mathcal A_2=\mathcal J(\mu_0\mathbf j\times\nabla\psi)^{\theta}$, -$\mathcal A_3=-\mathcal J(\mu_0\mathbf j\times\nabla\psi)^{\zeta}$ are just its -Jacobian-weighted contravariant entries (the expansion is in the main text). -Because $\mathbf V$ is a cross product, every \emph{in-surface} product with it -collapses in one line by a scalar triple product: for any surface vector -$\mathbf W=a\vec v_2+b\vec v_3$, -\begin{equation} -\mathbf W\cdot\mathbf V -=\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\,\nabla\psi\cdot(\mathbf W\times\mathbf j) -=\xi_\psi\,(a\,\mu_0 j^\zeta-b\,\mu_0 j^\theta), -\label{eq:app_Vrule} -\end{equation} -since $\mathbf W\times\mathbf j=(a\,j^\zeta-b\,j^\theta)\nabla\psi$ for two -in-surface vectors. This is the cofactor identity -$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$ in coordinate-free form, and is -the only tool needed for $\mathbf X^*\!\cdot\mathbf V$, $\mathbf Z^*\!\cdot\mathbf V$, -and $\mathbf U^*\!\cdot\mathbf V$ below; likewise -$|\mathbf V|^2=|\xi_\psi|^2\mu_0^2|\mathbf j|^2/|\nabla\psi|^2$ follows from -$|\mu_0\mathbf j\times\nabla\psi|^2=\mu_0^2|\mathbf j|^2|\nabla\psi|^2$ -($\mathbf j\cdot\nabla\psi=0$). -All dot products are reduced with the single geometric rule -\begin{equation} -\vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal J^2}, +\boxed{ +\begin{aligned} +\mathbf O^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf O +\leadsto\; +\Xi_\psi^\dagger \mathbf H_{UV}\Xi_\psi, \\ \qquad -e_m^*e_{m'}=e^{2\pi i(m'-m)\theta}. +\mathbf H_{UV} +&= +2\mu_0p'\frac{\chi''}{\chi'}\,\mathbf J +-4\pi f'q'\chi'\,\mathbf I. +\end{aligned} +} \end{equation} -Throughout, the only current data are the DCON contravariant current densities +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +\paragraph{The term $|\mathbf V|^2$.} + +This is again a pure-$\Xi_\psi$ contribution, so we write +\[ +\Xi_\psi^\dagger \mathbf H_V \Xi_\psi. +\] + +From \begin{equation} -\mu_0 j^\theta=-\frac{2\pi f'}{\mathcal J}, -\qquad -\mu_0 j^\zeta=-\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J}, -\label{eq:app_dconcurrents} -\end{equation} -and the equilibrium is a magnetic surface, $\mathbf B\cdot\nabla\psi=0$, so that -$\mathbf B=\chi'\,(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)$ -lies in the surface and $\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$ with -$\sigma\equiv\mathbf j\cdot\mathbf B/B^2$. Because the perturbation carries a -single toroidal mode number $n$, -$|\xi_\psi|^2=\sum_{m,m'}v_m^*v_{m'}\,e^{2\pi i(m'-m)\theta}$ is independent of -$\zeta$; this is used in Sec.~\ref{app:assembly}. - -\subsection{The five diagonal and ten cross products} - -Every $|\mathbf C|^2$ product is reduced with -$\vec v_i\cdot\vec v_j=g_{ij}/\mathcal J^2$ and the cofactor identity -$g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2$. Of the $\mathbf V$-touching -products, only $\mathbf Y^*\!\cdot\mathbf V$, $\mathbf U^*\!\cdot\mathbf V$, and -$|\mathbf V|^2$ --- together with the scalar $-\mu_0K$ term --- enter -$\mathbf H_4$. The other two route elsewhere: -$\mathbf X^*\!\cdot\mathbf V$ (a $\xi_s$--$\xi_\psi$ coupling) feeds the -$\mathbf C$ block and -$\mathbf Z^*\!\cdot\mathbf V=\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi$ (a -$\xi_\psi'$--$\xi_\psi$ coupling) the $\mathbf E$ block; the remaining -$\mathbf Q$-sector diagonals and cross products are listed for completeness and -feed the blocks -$\mathbf A,\mathbf B,\mathbf D,\mathbf H_{1\text{--}3}$ of the main text. - -\medskip\noindent\emph{Diagonal products.} - -\begin{equation} -|\mathbf X|^2 +\mathbf V = -\frac{(2\pi)^2}{\mathcal J^2} -\sum_{m,m'} u_m^* u_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ -n^2 g_{22}+n(m+m')g_{23}+m m' g_{33} -\right], +\frac{\xi_\psi}{|\nabla\psi|^2} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3), \end{equation} +we have +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^4} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3)\cdot +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +\left[ +\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} +\right]. +\end{align} + +Now expand the quadratic form: +\begin{align} +&\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} \\ +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})^2 g_{22} +-2(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}) +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&\qquad ++(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})^2 g_{33} \\ +&= +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} +Therefore +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right] \\ +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} -\begin{equation} -|\mathbf Y|^2 -= -\frac{(2\pi\chi')^2}{\mathcal J^2} -\sum_{m,m'} (m-nq)(m'-nq)\,v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta}\, -g_{11}, -\end{equation} +This is the completely explicit covariant-form result. +It is also useful to rewrite it invariantly. Since \begin{equation} -|\mathbf Z|^2 +\mathbf j = -\frac{\chi'^2}{\mathcal J^2} -\sum_{m,m'} v_m^{\prime *} v_{m'}'\,e^{2\pi i(m'-m)\theta} -\left[ -g_{22}+2q\,g_{23}+q^2 g_{33} -\right], +\sigma \mathbf B ++\frac{p'}{B^2}\mathbf B\times \nabla\psi, +\qquad +\sigma\equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, \end{equation} - +one finds \begin{equation} -|\mathbf U|^2 +\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} = -\frac{1}{\mathcal J^2} -\sum_{m,m'} v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ -\chi''^2 g_{22}+2\chi''(q\chi')' g_{23}+(q\chi')'^2 g_{33} -\right], +\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B) ++ +\frac{(\mu_0p')^2}{B^2}|\nabla\psi|^2. \end{equation} - +Hence \begin{equation} -\begin{aligned} +\boxed{ |\mathbf V|^2 -&= -\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\left[ -\mu_0^2 g_{22}(j^\theta)^2 -+2\mu_0^2 g_{23}\,j^\theta j^\zeta -+\mu_0^2 g_{33}(j^\zeta)^2 -\right] \\ -&= += |\xi_\psi|^2 \left[ -\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} -+\frac{(\mu_0p')^2}{B^2} +\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++ +\frac{(\mu_0p')^2}{B^2} \right]. -\end{aligned} +} \end{equation} -The second equality for $|\mathbf V|^2$ is \emph{not} a generic metric identity: -it uses force balance. With the Lagrange identity +With the Lagrange identity $|\mathbf j|^2B^2=(\mathbf j\cdot\mathbf B)^2+|\mathbf j\times\mathbf B|^2$ and $\sigma\equiv\mathbf j\cdot\mathbf B/B^2$ (so the bracket $\mu_0^2[g_{22}(j^\theta)^2+2g_{23}j^\theta j^\zeta+g_{33}(j^\zeta)^2]=\mu_0^2|\mathbf j|^2$), @@ -3222,171 +3025,44 @@ \subsection{The five diagonal and ten cross products} equality, not a proportionality: \begin{equation} \frac{\mu_0^2|\mathbf j|^2}{|\nabla\psi|^2} --\left[\frac{\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} +-\left[\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} +\frac{(\mu_0p')^2}{B^2}\right] =\frac{\mu_0^2|\mathbf j\times\mathbf B|^2-(\mu_0p')^2|\nabla\psi|^2} {B^2|\nabla\psi|^2} =0 , \end{equation} the numerator vanishing identically by force balance -$\mu_0\mathbf j\times\mathbf B=\mu_0\nabla p=\mu_0 p'\nabla\psi$. Equivalently, in -the DCON currents~\eqref{eq:app_dconcurrents} the $2\pi f'q/\mathcal J$ pieces -cancel and $\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, whence -$\mu_0^2|\mathbf j\times\mathbf B|^2 -=\chi'^2(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2|\nabla\psi|^2=(\mu_0p')^2|\nabla\psi|^2$: -the parallel ($f'$) current supplies the -$\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)$ term and the in-surface pressure -current supplies the $(\mu_0p')^2/B^2$ term. - -\medskip\noindent\emph{Cross products.} - -\begin{equation} -\mathbf X^*\!\cdot\mathbf Z -= --\frac{2\pi i\chi'}{\mathcal J^2} -\sum_{m,m'} u_m^* v_{m'}'\,e^{2\pi i(m'-m)\theta} -\left[ -n(g_{22}+q g_{23})+m(g_{23}+q g_{33}) -\right], -\end{equation} - -\begin{equation} -\mathbf X^*\!\cdot\mathbf Y -= --\frac{(2\pi)^2\chi'}{\mathcal J^2} -\sum_{m,m'} (m'-nq)\,u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ -n\,g_{12}+m\,g_{31} -\right], -\end{equation} - -\begin{equation} -\begin{aligned} -\mathbf X^*\!\cdot\mathbf U -&= --\frac{2\pi i}{\mathcal J^2} -\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} \\ -&\qquad\times -\left[ -\chi''(n g_{22}+m g_{23})+(q\chi')'(n g_{23}+m g_{33}) -\right], -\end{aligned} -\end{equation} - +$\mu_0\mathbf j\times\mathbf B=\mu_0\nabla p=\mu_0 p'\nabla\psi$. \begin{equation} \begin{aligned} -\mathbf X^*\!\cdot\mathbf V -&= --2\pi i -\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta}\, -\mu_0\!\left(m j^\theta-n j^\zeta\right) \\ +|\mathbf V|^2 &= --2\pi i -\sum_{m,m'} u_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ --\frac{2\pi f'}{\mathcal J}(m-nq)+n\frac{\mu_0p'}{\chi'} -\right], -\end{aligned} -\end{equation} - -Here $\mathbf W=n\vec v_2+m\vec v_3$, so the triple-product -rule~\eqref{eq:app_Vrule} gives the coefficient -$n\,\mu_0 j^\zeta-m\,\mu_0 j^\theta=-(m\,\mu_0 j^\theta-n\,\mu_0 j^\zeta)$ directly. - -\begin{equation} -\mathbf Y^*\!\cdot\mathbf Z -= -\frac{2\pi i\chi'^2}{\mathcal J^2} -\sum_{m,m'} (m-nq)\,v_m^* v_{m'}'\,e^{2\pi i(m'-m)\theta} -\left[ -g_{12}+q\,g_{31} -\right], -\end{equation} - -\begin{equation} -\mathbf Y^*\!\cdot\mathbf U -= -\frac{2\pi i\chi'}{\mathcal J^2} -\sum_{m,m'} (m-nq)\,v_m^* v_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ -\chi''\,g_{12}+(q\chi')'\,g_{31} -\right], -\end{equation} - -\begin{equation} -\mathbf Z^*\!\cdot\mathbf U -= -\frac{\chi'}{\mathcal J^2} -\sum_{m,m'} v_m^{\prime *} v_{m'}\,e^{2\pi i(m'-m)\theta} -\left[ -\chi''(g_{22}+q g_{23})+(q\chi')'(g_{23}+q g_{33}) -\right], -\end{equation} - -\begin{equation} -\mathbf Z^*\!\cdot\mathbf V -= -\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi -\qquad(\text{pointwise}), -\end{equation} - -For $\mathbf Z^*\!\cdot\mathbf V$ the rule~\eqref{eq:app_Vrule} with -$\mathbf W=\vec v_2+q\vec v_3$ gives, before inserting currents, -$\mathbf Z^*\!\cdot\mathbf V=\chi'\,\xi_\psi^{\prime *}\xi_\psi\, -(q\,\mu_0 j^\theta-\mu_0 j^\zeta)$; inserting~\eqref{eq:app_dconcurrents}, -$q\,\mu_0 j^\theta-\mu_0 j^\zeta -=-\tfrac{2\pi f'q}{\mathcal J}+\tfrac{\mu_0p'}{\chi'}+\tfrac{2\pi f'q}{\mathcal J} -=\tfrac{\mu_0p'}{\chi'}$ (the $2\pi f'q/\mathcal J$ terms cancel), so -$\mathbf Z^*\!\cdot\mathbf V=\mu_0p'\,\xi_\psi^{\prime *}\xi_\psi$. - -\begin{equation} -\mathbf U^*\!\cdot\mathbf V -= -|\xi_\psi|^2 +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} \left[ -\mu_0p'\frac{\chi''}{\chi'}-\frac{2\pi f'q'\chi'}{\mathcal J} -\right] -\qquad(\text{pointwise}), -\end{equation} - -For $\mathbf U^*\!\cdot\mathbf V$ the rule~\eqref{eq:app_Vrule} with -$\mathbf W=\chi''\vec v_2+(q\chi')'\vec v_3$ leaves the pointwise -coefficient $|\xi_\psi|^2[-\chi''\mu_0 j^\zeta+(q\chi')'\mu_0 j^\theta]$; -inserting~\eqref{eq:app_dconcurrents} and expanding $(q\chi')'=q'\chi'+q\chi''$, -the $\pm2\pi f'q\chi''/\mathcal J$ cross terms cancel, leaving -$|\xi_\psi|^2[\mu_0p'\tfrac{\chi''}{\chi'}-\tfrac{2\pi f'q'\chi'}{\mathcal J}]$. - -\begin{equation} -\begin{aligned} -\mathcal J\,(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) +\mu_0^2 g_{22}(j^\theta)^2 ++2\mu_0^2 g_{23}\,j^\theta j^\zeta ++\mu_0^2 g_{33}(j^\zeta)^2 +\right] \\ &= -\chi'\,\mathcal F -\left\{ -\left[(\partial_\theta+q\partial_\zeta)\xi_\psi^*\right]\xi_\psi -+\xi_\psi^*\left[(\partial_\theta+q\partial_\zeta)\xi_\psi\right] -\right\}, \\ -\mathcal F -&\equiv -\mathcal J\, -\frac{(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi)-(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi)}{|\nabla\psi|^2}. -\end{aligned} -\end{equation} - -\medskip\noindent\emph{Scalar Bernstein term.} - -\begin{equation} --\mu_0\,\mathcal J\,K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -= |\xi_\psi|^2 \left[ --\mathcal J\mu_0\sigma S_\psi --\mathcal J\frac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2} --2\mu_0p'\mathcal J\frac{\kappa_\psi}{|\nabla\psi|^2} +\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\frac{(\mu_0p')^2}{B^2} \right]. +\end{aligned} \end{equation} +Equivalently, in +the DCON currents~\eqref{eq:dconcurrents} the $2\pi f'q/\mathcal J$ pieces +cancel and $\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, whence +$\mu_0^2|\mathbf j\times\mathbf B|^2 +=\chi'^2(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2|\nabla\psi|^2=(\mu_0p')^2|\nabla\psi|^2$: +the parallel ($f'$) current supplies the +$\mu_0^2\sigma(\mathbf j\cdot\mathbf B)$ term and the in-surface pressure +current supplies the $(\mu_0p')^2/B^2$ term. -\subsection{Assembly of the $\mathbf V$-sector and the $\theta$-integration by parts} -\label{app:assembly} +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip The four $\mathbf V$-touching contributions combine, under a common $\mathcal J$ weight and before any equilibrium input, as @@ -3395,131 +3071,31 @@ \subsection{Assembly of the $\mathbf V$-sector and the $\theta$-integration by p \begin{aligned} &\int d\theta\,d\zeta\;\mathcal J \left[ -(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) -+(\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U) +(\mathbf M^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf M) ++(\mathbf O^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf O) +|\mathbf V|^2 -\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} \right]\\ &=\int d\theta\,d\zeta\, -\bigg[ -\underbrace{\chi'\,\mathcal F\,\partial_\theta|\xi_\psi|^2}_{\mathbf Y^*\mathbf V+\text{c.c.}} -+|\xi_\psi|^2\Big( -\underbrace{2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-4\pi f'q'\chi'}_{\mathbf U^*\mathbf V+\text{c.c.}} -+\underbrace{\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} -+\mathcal J\tfrac{(\mu_0p')^2}{B^2}}_{|\mathbf V|^2}\\ -&\hspace{5em} -\underbrace{ --\,\mathcal J\mu_0\sigma S_\psi --\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2} +\bigg[|\xi_\psi|^2\Big( +\underbrace{-\frac{2\pi f'}{\chi'}\,\mathcal J\,S_\psi+{\color{red}\cancel{2\pi \chi' f'q'}} +-\mu_0p'\partial_\theta\left[\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right]}_{\mathbf M^*\mathbf V+\mathbf V^*\mathbf M} ++\underbrace{2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-2\pi f'q'\chi'\,{\color{red}\cancel{-\,2\pi f'q'\chi'}}}_{\mathbf O^*\mathbf V+\mathbf V^*\mathbf O}\\ +&+\underbrace{{\color{blue}\cancel{\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2}}} ++\mathcal J\tfrac{(\mu_0p')^2}{B^2}}_{|\mathbf V|^2} +\underbrace{-\,\mathcal J\mu_0\sigma S_\psi +\,{\color{blue}\cancel{-\,\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2}}} -2\mu_0p'\mathcal J\tfrac{\kappa_\psi}{|\nabla\psi|^2}}_{-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2} \Big) -\bigg], -\quad -\mathcal F\equiv -\mathcal J\frac{\mu_0 j^\theta(\nabla\zeta\cdot\nabla\psi)-\mu_0 j^\zeta(\nabla\theta\cdot\nabla\psi)}{|\nabla\psi|^2}. -\end{aligned} -\end{equation} - -Only $\partial_\theta|\xi_\psi|^2$ appears (not the full -$(\partial_\theta+q\partial_\zeta)|\xi_\psi|^2$ that -$\mathbf Y^*\!\cdot\mathbf V+\text{c.c.}$ produces) because $|\xi_\psi|^2$ is -$\zeta$-independent for a single-$n$ perturbation, so -$\partial_\zeta|\xi_\psi|^2=0$ pointwise and the $q\,\partial_\zeta$ piece drops -identically. - -Only $\mathbf Y^*\!\cdot\mathbf V$ carries a $\theta$ derivative. Integrating -it by parts (the boundary term vanishes because $\chi'$ is a flux function and -$\mathcal F,|\xi_\psi|^2$ are $\theta$-periodic), its $|\xi_\psi|^2$ -coefficient becomes $-\chi'\,\partial_\theta\mathcal F$: -\begin{equation} -\int d\theta\,\chi'\mathcal F\,\partial_\theta|\xi_\psi|^2 -=-\int d\theta\;|\xi_\psi|^2\,\chi'\,\partial_\theta\mathcal F, -\qquad --\chi'\,\partial_\theta\mathcal F -=-\frac{2\pi f'}{\chi'}\mathcal J S_\psi -+2\pi f'q'\chi' --\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right), -\end{equation} - -A $\theta$ derivative cannot generate $q'=dq/d\psi$; the explicit -$+2\pi f'q'\chi'$ above only compensates the $q'$ introduced by rewriting part -of $-\chi'\partial_\theta\mathcal F$ through -$S_\psi=\tfrac{\chi'^2}{\mathcal J}[q'+\partial_\theta(\cdots)]$, so the net -$q'$ content of $-\chi'\partial_\theta\mathcal F$ is zero. As written, the -$+2\pi f'q'\chi'$ pairs below with the $-4\pi f'q'\chi'$ of -$\mathbf U^*\!\cdot\mathbf V$ to give $-2\pi f'q'\chi'\,\mathbf I$. - -Three immediate cancellations follow: - -\begin{equation} --\frac{2\pi f'}{\chi'}\mathcal J S_\psi-\mathcal J\mu_0\sigma S_\psi -=-\mathcal J S_\psi\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right), -\quad -\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)-\mu_0^2\sigma^2B^2}{|\nabla\psi|^2}=0, -\quad -2\pi f'q'\chi'-4\pi f'q'\chi'=-2\pi f'q'\chi', -\end{equation} -and substituting the DCON shear and curvature -\begin{equation} -S_\psi=\frac{\chi'^2}{\mathcal J}\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right], -\qquad -\kappa_\psi=\frac{|\nabla\psi|^2}{B^2}\left[\mu_0 p'+\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right] -\end{equation} -(so that $\mathcal J$ cancels in $-\mathcal J S_\psi$ and the two $(\mu_0p')^2/B^2$ pieces combine), the whole sector becomes the single fully expanded identity -\begin{equation} -\begin{aligned} -&\int d\theta\,d\zeta\;\mathcal J -\left[ -(\mathbf Y^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf Y) -+(\mathbf U^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf U) -+|\mathbf V|^2 --\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} -\right]\\ -&=\int d\theta\,d\zeta\;|\xi_\psi|^2 -\bigg[ --2\pi f'q'\chi' -+2\mu_0p'\mathcal J\frac{\chi''}{\chi'} --\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right) --\mathcal J\frac{(\mu_0p')^2}{B^2}\\ -&\hspace{4em} --\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 --\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} --\chi'^2\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right)\! -\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right] -\bigg]. -\end{aligned} -\label{eq:H4_expanded} -\end{equation} - -Up to \eqref{eq:H4_expanded} no equilibrium input beyond the DCON currents has -been used. The first two terms give $-2\pi f'q'\chi'\mathbf I$ and -$\mu_0p'(\chi''/\chi')\mathbf J$; it remains to show, for arbitrary Fourier -amplitudes $\{v_m\}$, that the rest closes, -\begin{equation} -\begin{aligned} -&\int d\theta\,d\zeta\;|\xi_\psi|^2 -\bigg[ --\chi'^2\!\left(\frac{2\pi f'}{\chi'}+\mu_0\sigma\right)\! -\left[q'+\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\right] --\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right)\\ -&\hspace{4em} --\mathcal J\frac{(\mu_0p')^2}{B^2} --\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 --\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} \bigg]\\ -&=\int d\theta\,d\zeta\;|\xi_\psi|^2 -\bigg[ --2\pi f'q'\chi' --\mu_0\sigma\chi'^2 q' --2\pi f'\chi'\,\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right)\\ -&\hspace{4em} --\mu_0\sigma\chi'^2\,\partial_\theta\!\left(\frac{q \nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}\right) --\mu_0p'\,\partial_\theta\!\left(\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right) --\mathcal J\frac{(\mu_0p')^2}{B^2}\\ -&\hspace{4em} --\frac{\mu_0p'\mathcal J}{B^2}\partial_\psi B^2 --\frac{\mu_0p'\mathcal J}{B^2}\partial_\theta B^2\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} -\bigg]. +&=\int d\theta\,d\zeta\, +\bigg[|\xi_\psi|^2\Big( +-\mu_0p'\partial_\theta\left[\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right] ++2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-2\pi f'q'\chi'\, ++\mathcal J\tfrac{(\mu_0p')^2}{B^2} +-\,\big(\mu_0\sigma +\frac{2\pi f'}{\chi'}\big)\mathcal JS_\psi +-2\mu_0p'\mathcal J\tfrac{\kappa_\psi}{|\nabla\psi|^2} +\Big)\bigg]. \end{aligned} \end{equation} @@ -3565,16 +3141,12 @@ \subsection{The $\sigma$ identity: collapse of the parallel-current coefficient} Applying the cross-product identity $(\mathbf A\times\mathbf B)\cdot(\mathbf C\times\mathbf D)=(\mathbf A\cdot\mathbf C)(\mathbf B\cdot\mathbf D)-(\mathbf A\cdot\mathbf D)(\mathbf B\cdot\mathbf C)$ to the two cross-product factors, -\begin{equation} -\begin{aligned} -|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2 -&=(\nabla\psi\cdot\nabla\psi)(q^2\nabla\theta\cdot\nabla\theta-2q\,\nabla\theta\cdot\nabla\zeta+\nabla\zeta\cdot\nabla\zeta) --(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)^2,\\ -(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta) -&=(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) --(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta). -\end{aligned} -\end{equation} + +$|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2=(\nabla\psi\cdot\nabla\psi)(q^2\nabla\theta\cdot\nabla\theta-2q\,\nabla\theta\cdot\nabla\zeta+\nabla\zeta\cdot\nabla\zeta) +-(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)^2,$ +$(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)=(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta).$ + The first is $B^2/\chi'^2$; the second turns the collapse into the pure-$p'$, dot-product form \begin{equation} \frac{2\pi f'}{\chi'}+\mu_0\sigma @@ -3582,77 +3154,38 @@ \subsection{The $\sigma$ identity: collapse of the parallel-current coefficient} \big[(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) -(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)\big]. \end{equation} -Write the local shorthands -$\mathcal D\equiv(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) --(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)$ -and -$G_s\equiv\dfrac{q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta}{|\nabla\psi|^2}$. -Inserting $\mu_0\sigma=-\tfrac{2\pi f'}{\chi'}-\tfrac{\mu_0 p'\mathcal J\,\mathcal D}{B^2}$ into the two $-\mu_0\sigma\chi'^2(\cdots)$ pieces of the distributed form, each $f'$ part \emph{exactly} cancels the existing $-2\pi f'\chi'(\cdots)$ partner: + +and define $\mathcal D\equiv\big[(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)\big]$ to obtain the final form + +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma=-\frac{\mu_0p'\mathcal J\,\mathcal D}{B^2}. +\end{equation} +This $\mathcal D$ can be expressed in terms of the metric coefficients as \begin{equation} \begin{aligned} -\cancel{-\,2\pi f'q'\chi'}\;-\,\mu_0\sigma\chi'^2 q' -&=\cancel{-\,2\pi f'q'\chi'}\;\cancel{+\,2\pi f'q'\chi'}\;+\;\frac{\mu_0p'\chi'^2\mathcal J\,q'\,\mathcal D}{B^2},\\ -\cancel{-\,2\pi f'\chi'\,\partial_\theta G_s}\;-\,\mu_0\sigma\chi'^2\,\partial_\theta G_s -&=\cancel{-\,2\pi f'\chi'\,\partial_\theta G_s}\;\cancel{+\,2\pi f'\chi'\,\partial_\theta G_s}\;+\;\frac{\mu_0p'\chi'^2\mathcal J\,\mathcal D}{B^2}\,\partial_\theta G_s. +\mathcal D +&\equiv g^{\psi\psi}(q\,g^{\theta\theta}-g^{\theta\zeta}) +-g^{\psi\theta}(q\,g^{\psi\theta}-g^{\psi\zeta})=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2}. +%=\frac{\partial_q G_\alpha}{2\mathcal J^2}, \end{aligned} \end{equation} -The crossed-out $f'$ pairs vanish identically, so the entire $\mathbf V$-sector -residual reduces to pure $\mu_0p'$ pieces; the surviving geometric -$\theta$-derivatives are closed by the Grad--Shafranov identity of -Sec.~\ref{app:master}. - -\subsection{Master identity, cofactor form, and the Grad--Shafranov closure} -\label{app:master} - -The closure is most transparent pointwise in $\theta$. - -\paragraph{Four weights and target.} -Write the sector as $\int d\theta\,\mathcal W\,|\xi_\psi|^2$ with -$\mathcal W=W_{UV}+W_V+W_K+W_{YV}$ ($\mathbf Y^*\!\cdot\mathbf V$ already -integrated by parts), abbreviating $g^{\psi\psi}=|\nabla\psi|^2$: -\begin{align} -W_{UV}&=2\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J-4\pi f'q'\chi', -\label{eq:WUV}\\ -W_{V}&=\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{g^{\psi\psi}} - +\mathcal J\tfrac{(\mu_0p')^2}{B^2}, -\label{eq:WV}\\ -W_{K}&=-\mathcal J\mu_0\sigma S_\psi - -\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}} - -\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}}, -\label{eq:WK}\\ -W_{YV}&=-\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi+2\pi f'q'\chi' - -\mu_0p'\,\partial_\theta\!\big(\mathcal J\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big). -\label{eq:WYV} -\end{align} -Target -$\;W_\star=\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J+\mu_0p'\mathcal J'-2\pi f'q'\chi'$, -i.e.\ $\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. -\paragraph{Step 1 — parallel current ($\mu_0\mathbf j\cdot\mathbf B=\mu_0\sigma B^2$).} -The first term of \eqref{eq:WV} and the second of \eqref{eq:WK} cancel pointwise: \begin{equation} -\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{g^{\psi\psi}} --\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}}=0 -\;\Longrightarrow\; -W_V+W_K=\mathcal J\tfrac{(\mu_0p')^2}{B^2} --\mathcal J\mu_0\sigma S_\psi --\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}}. -\label{eq:step1} +G_\alpha\equiv g_{\theta\theta}+2q\,g_{\theta\zeta}+q^2g_{\zeta\zeta}=\frac{\mathcal J^2B^2}{\chi'^2}. \end{equation} -\paragraph{Step 2 — $\sigma$ identity -$\;\mu_0\sigma=-\tfrac{2\pi f'}{\chi'}-\tfrac{\mu_0p'\mathcal J\,\mathcal D}{B^2}$.} -The shear term of \eqref{eq:step1} splits: -\begin{equation} --\mathcal J\mu_0\sigma S_\psi -=\underbrace{\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi}_{\text{cancels \eqref{eq:WYV}}} -+\tfrac{\mu_0p'\mathcal J^2\mathcal D}{B^2}\,S_\psi . -\label{eq:step2} -\end{equation} +\subsection{Master identity and the Grad--Shafranov closure} +\label{app:master} -\paragraph{Step 3 — sum $\mathcal W=\eqref{eq:WUV}+\eqref{eq:step1}+\eqref{eq:WYV}$.} -The $\tfrac{2\pi f'}{\chi'}\mathcal J S_\psi$ pair cancels and -$-4\pi f'q'\chi'+2\pi f'q'\chi'=-2\pi f'q'\chi'$, leaving +The closure is most transparent pointwise in $\theta$. Write the sector as +$\int d\theta\,\mathcal W\,|\xi_\psi|^2$, abbreviating $g^{\psi\psi}=|\nabla\psi|^2$. The +assembled pointwise weight $\mathcal W$ was obtained at the end of +Sec.~\ref{app:reductions}; inserting the $\sigma$ identity of +Sec.~\ref{app:sigma}, +$\tfrac{2\pi f'}{\chi'}+\mu_0\sigma=-\tfrac{\mu_0p'\mathcal J\,\mathcal D}{B^2}$, +into its shear term $-\big(\mu_0\sigma+\tfrac{2\pi f'}{\chi'}\big)\mathcal J S_\psi$ +gives \begin{equation} \mathcal W=-2\pi f'q'\chi' +2\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J @@ -3662,9 +3195,12 @@ \subsection{Master identity, cofactor form, and the Grad--Shafranov closure} +\tfrac{\mu_0p'\mathcal J^2\mathcal D}{B^2}\,S_\psi . \label{eq:step3} \end{equation} +The target is +$\;W_\star=\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J+\mu_0p'\mathcal J'-2\pi f'q'\chi'$, +i.e.\ $\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. -\paragraph{Step 4 — $\mathcal W=W_\star$, divide by $\mu_0p'$ (master identity).} -Subtracting $W_\star$ (the $-2\pi f'q'\chi'$ and one $\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J$ match): +\paragraph{Master identity ($\mathcal W=W_\star$, divided by $\mu_0p'$).} +Subtracting $W_\star$ from \eqref{eq:step3} (the $-2\pi f'q'\chi'$ and one $\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J$ match): \begin{equation} \boxed{\; \mathcal J\frac{\chi''}{\chi'} @@ -3677,194 +3213,135 @@ \subsection{Master identity, cofactor form, and the Grad--Shafranov closure} \end{equation} with the inputs \begin{equation} -\mathcal D -\equiv g^{\psi\psi}(q\,g^{\theta\theta}-g^{\theta\zeta}) --g^{\psi\theta}(q\,g^{\psi\theta}-g^{\psi\zeta}), -\qquad S_\psi=\frac{\chi'^2}{\mathcal J}\Big[q'+\partial_\theta G_s\Big], \qquad -G_s\equiv\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} . +G_s\equiv\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}}, \label{eq:DSdef} \end{equation} +and $\mathcal D$ as defined in Sec.~\ref{app:sigma}. -\paragraph{Cofactor form of $\mathcal D$ ($g^{ij}=(g_{ij})^{-1}$, $\det g_{ij}=\mathcal J^2$).} -The minors are -$g^{\psi\psi}g^{\theta\theta}-(g^{\psi\theta})^2=g_{\zeta\zeta}/\mathcal J^2$, -$\;g^{\psi\psi}g^{\theta\zeta}-g^{\psi\theta}g^{\psi\zeta}=-g_{\theta\zeta}/\mathcal J^2$, hence -\begin{equation} -\boxed{\; -\mathcal D=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2} -=\frac{\partial_q G_\alpha}{2\mathcal J^2}, -\qquad -\frac{\chi'^2\mathcal D}{B^2} -=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{G_\alpha}, -\;} -\qquad -G_\alpha\equiv g_{\theta\theta}+2q\,g_{\theta\zeta}+q^2g_{\zeta\zeta}=\frac{\mathcal J^2B^2}{\chi'^2}. -\label{eq:cofactor} -\end{equation} - -\paragraph{Step 5 — insert $\kappa_\psi$ and $S_\psi$ into \eqref{eq:master}.} -With -$\kappa_\psi=\tfrac{g^{\psi\psi}}{B^2}\big[\mu_0p' -+\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\,\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big]$, +\paragraph{Direct proof from Amp\`ere's law.} +The master identity \eqref{eq:master} is proved directly from +\(\nabla\times\mathbf B=\mu_0\mathbf j\); nothing beyond the covariant field +components $B_i$ is needed. Since $B^\theta=\chi'/\mathcal J$, +$B^\zeta=q\chi'/\mathcal J$, and $B^\psi=0$, \begin{equation} --\frac{2\mathcal J\kappa_\psi}{g^{\psi\psi}} -=-\frac{2\mathcal J\mu_0p'}{B^2} - -\frac{\mathcal J\,\partial_\psi B^2}{B^2} - -\frac{\mathcal J\,g^{\psi\theta}\,\partial_\theta B^2}{g^{\psi\psi}B^2}, +B_i=g_{i\theta}B^\theta+g_{i\zeta}B^\zeta +=\frac{\chi'}{\mathcal J}\,(g_{i\theta}+qg_{i\zeta}), \qquad -\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi -=\mathcal J\,\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{G_\alpha}\,\big(q'+\partial_\theta G_s\big). -\label{eq:step5a} +B^2=B_\theta B^\theta+B_\zeta B^\zeta +=\frac{\chi'}{\mathcal J}\,\big(B_\theta+qB_\zeta\big), +\label{eq:Bcov_GS} \end{equation} -Using $\beta^2\equiv B^2/\chi'^2=G_\alpha/\mathcal J^2$, +so that \begin{equation} -\frac{\partial_\psi B^2}{B^2}=2\frac{\chi''}{\chi'}+\frac{\partial_\psi\beta^2}{\beta^2}, -\qquad -\frac{\partial_\theta B^2}{B^2}=\frac{\partial_\theta G_\alpha}{G_\alpha}-2\frac{\partial_\theta\mathcal J}{\mathcal J}, +B_\theta+qB_\zeta=\frac{\mathcal J B^2}{\chi'}, \qquad -\frac{\mathcal J\mu_0p'}{B^2}-\frac{2\mathcal J\mu_0p'}{B^2}=-\frac{\mathcal J\mu_0p'}{B^2}, -\label{eq:step5b} +\mathcal D +=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2} +=\frac{B_\zeta}{\mathcal J\chi'}, +\label{eq:Dzeta_GS} \end{equation} -so $\mathcal J\tfrac{\chi''}{\chi'}-2\mathcal J\tfrac{\chi''}{\chi'}=-\mathcal J\tfrac{\chi''}{\chi'}$ and \eqref{eq:master} becomes +the second using the cofactor form of $\mathcal D$ from +Sec.~\ref{app:sigma} together with +$B_\zeta=\tfrac{\chi'}{\mathcal J}(g_{\theta\zeta}+qg_{\zeta\zeta})$ +from \eqref{eq:Bcov_GS}. Moreover, raising the index with $B^\psi=0$, \begin{equation} -\boxed{\; -\begin{aligned} -&-\mathcal J\frac{\chi''}{\chi'} --\frac{\mathcal J\mu_0p'}{B^2} --\mathcal J\frac{\partial_\psi\beta^2}{\beta^2} --\frac{\mathcal J\,g^{\psi\theta}}{g^{\psi\psi}}\Big(\frac{\partial_\theta G_\alpha}{G_\alpha}-2\frac{\partial_\theta\mathcal J}{\mathcal J}\Big)\\ -&\qquad --\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) -+\frac{\mathcal J\,(q\,g_{\zeta\zeta}+g_{\theta\zeta})}{G_\alpha}\big(q'+\partial_\theta G_s\big) -=\mathcal J' . -\end{aligned}\;} -\label{eq:gsstep} +0=B^\psi=g^{\psi\psi}B_\psi+g^{\psi\theta}B_\theta+g^{\psi\zeta}B_\zeta +\;\Longrightarrow\; +B_\psi +=-\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\frac{\mathcal J B^2}{\chi'} ++G_s\,B_\zeta , +\label{eq:Bpsi_GS} \end{equation} +where the definition \eqref{eq:DSdef} of $G_s$ supplies +$g^{\psi\zeta}/g^{\psi\psi}=q\,g^{\psi\theta}/g^{\psi\psi}-G_s$ and +\eqref{eq:Dzeta_GS} recombines $B_\theta+qB_\zeta$. -Equation~\eqref{eq:gsstep} is the remaining Grad--Shafranov input. It can be -derived directly from the covariant-component form of -\(\nabla\times\mathbf B=\mu_0\mathbf j\), as follows. Define +For an axisymmetric equilibrium ($\partial_\zeta=0$) the contravariant +components of $\mu_0\mathbf j=\nabla\times\mathbf B$ are \begin{equation} -A\equiv\frac{g^{\psi\theta}}{g^{\psi\psi}}, +\mu_0\mathcal J\,j^\psi=\partial_\theta B_\zeta, \qquad -H\equiv qg_{\zeta\zeta}+g_{\theta\zeta}, +\mu_0\mathcal J\,j^\theta=-\partial_\psi B_\zeta, \qquad -\mathcal C_B\equiv B_\theta+qB_\zeta . +\mu_0\mathcal J\,j^\zeta=\partial_\psi B_\theta-\partial_\theta B_\psi . \end{equation} -Since \(B^\theta=\chi'/\mathcal J\) and \(B^\zeta=q\chi'/\mathcal J\), +With the currents \eqref{eq:dconcurrents} and the magnetic-surface condition +$\mathbf j\cdot\nabla\psi=0$, \begin{equation} -B_i=g_{i\theta}B^\theta+g_{i\zeta}B^\zeta -=\frac{\chi'}{\mathcal J}(g_{i\theta}+qg_{i\zeta}), +\partial_\theta B_\zeta=0, \qquad -B_\zeta=\frac{\chi'}{\mathcal J}H, +\partial_\psi B_\zeta=2\pi f', \qquad -\mathcal C_B=\frac{\chi'}{\mathcal J}G_\alpha -=\frac{\mathcal J B^2}{\chi'} . -\end{equation} -The inverse-metric cofactor identities give -\begin{equation} -g_{\psi\theta}+qg_{\psi\zeta} -=-A\,G_\alpha+G_s\,H, -\end{equation} -and therefore -\begin{equation} -B_\psi=-A\,\mathcal C_B+G_s B_\zeta . -\label{eq:Bpsi_GS_closure} +\partial_\psi B_\theta-\partial_\theta B_\psi +=-\frac{\mathcal J\mu_0p'}{\chi'}-2\pi qf' , +\label{eq:ampere_GS} \end{equation} +i.e.\ $B_\zeta=2\pi f(\psi)$ is a flux function and the $j^\zeta$ component +carries the Grad--Shafranov force balance. -For an axisymmetric equilibrium the contravariant curl components are +Now differentiate $\mathcal JB^2/\chi'=B_\theta+qB_\zeta$ radially and insert +\eqref{eq:ampere_GS}; the explicit $2\pi qf'$ terms cancel: \begin{equation} -\mu_0j^\theta=-\frac{1}{\mathcal J}\partial_\psi B_\zeta, -\qquad -\mu_0j^\zeta= -\frac{1}{\mathcal J}\left(\partial_\psi B_\theta-\partial_\theta B_\psi\right). -\label{eq:covcurl_GS_closure} +\partial_\psi\!\Big(\frac{\mathcal JB^2}{\chi'}\Big) +=\partial_\psi B_\theta+q'B_\zeta+2\pi qf' +=\partial_\theta B_\psi+q'B_\zeta-\frac{\mathcal J\mu_0p'}{\chi'} . \end{equation} -Using the physical-\(f'\) current convention of -\eqref{eq:app_dconcurrents}, the first of \eqref{eq:covcurl_GS_closure} gives +Expanding the left side by the product rule and multiplying through by +$\chi'/B^2$, \begin{equation} -\partial_\psi B_\zeta=2\pi f', -\qquad -B_\zeta=2\pi f(\psi), -\qquad -\partial_\theta B_\zeta=0 . +\mathcal J' +=\mathcal J\frac{\chi''}{\chi'} +-\mathcal J\frac{\partial_\psi B^2}{B^2} +-\frac{\mathcal J\mu_0p'}{B^2} ++\frac{\chi'}{B^2}\big(\partial_\theta B_\psi+q'B_\zeta\big) . +\label{eq:Jprime_GS} \end{equation} -Here the last two statements are the usual Grad--Shafranov convention that the -covariant toroidal-field function \(f\) is a flux function; this is the same -convention encoded in \eqref{eq:app_dconcurrents}. The second of -\eqref{eq:covcurl_GS_closure} gives +The $\theta$-derivative of \eqref{eq:Bpsi_GS} (with $\partial_\theta B_\zeta=0$) +gives \begin{equation} -\partial_\psi B_\theta-\partial_\theta B_\psi -=-\frac{\mathcal J\mu_0p'}{\chi'}-2\pi qf' . +\frac{\chi'}{B^2}\,\partial_\theta B_\psi +=-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) +-\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\frac{\partial_\theta B^2}{B^2} ++\frac{\chi' B_\zeta}{B^2}\,\partial_\theta G_s , \end{equation} -Substituting \(\mathcal C_B=B_\theta+qB_\zeta\) and -\(\partial_\psi B_\zeta=2\pi f'\) cancels the explicit \(2\pi qf'\) terms and -leaves +while \eqref{eq:Dzeta_GS} and the definition \eqref{eq:DSdef} of $S_\psi$ +combine the $q'$ and $\partial_\theta G_s$ pieces into \begin{equation} -\partial_\psi\mathcal C_B-\partial_\theta B_\psi -=q'B_\zeta-\frac{\mathcal J\mu_0p'}{\chi'} . +\frac{\chi' B_\zeta}{B^2}\,\big(q'+\partial_\theta G_s\big) +=\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi . \end{equation} -Using \eqref{eq:Bpsi_GS_closure} and \(\partial_\theta B_\zeta=0\), this becomes +Hence \eqref{eq:Jprime_GS} becomes \begin{equation} -\partial_\psi\mathcal C_B+\partial_\theta(A\mathcal C_B) --B_\zeta(q'+\partial_\theta G_s) -+\frac{\mathcal J\mu_0p'}{\chi'}=0 . -\label{eq:CB_GS_closure} -\end{equation} - -Multiply \eqref{eq:CB_GS_closure} by -\(\chi'/B^2=\mathcal J/\mathcal C_B\). The three geometric factors reduce as -\begin{align} -\frac{\chi'}{B^2}\partial_\psi\mathcal C_B -&= \mathcal J' -+\mathcal J\frac{\partial_\psi B^2}{B^2} --\mathcal J\frac{\chi''}{\chi'},\\ -\frac{\chi'}{B^2}\partial_\theta(A\mathcal C_B) -&= -\partial_\theta(\mathcal J A) -+\mathcal J A\frac{\partial_\theta B^2}{B^2},\\ -\frac{\chi'}{B^2}B_\zeta -&= -\mathcal J\frac{H}{G_\alpha}. -\end{align} -Thus Grad--Shafranov gives +=\mathcal J\frac{\chi''}{\chi'} +-\frac{\mathcal J\mu_0p'}{B^2} +-\frac{\mathcal J}{B^2}\Big(\partial_\psi B^2 ++\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\partial_\theta B^2\Big) +-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) ++\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\label{eq:Jprime_GS2} +\end{equation} +Finally, the curvature +$\kappa_\psi=\tfrac{g^{\psi\psi}}{B^2}\big[\mu_0p' ++\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\,\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big]$ +identifies \begin{equation} -\begin{aligned} -\mathcal J' -&= -\mathcal J\frac{\chi''}{\chi'} --\mathcal J\frac{\partial_\psi B^2}{B^2} --\partial_\theta(\mathcal J A) --\mathcal J A\frac{\partial_\theta B^2}{B^2}\\ -&\quad -+\mathcal J\frac{H}{G_\alpha}(q'+\partial_\theta G_s) --\frac{\mathcal J\mu_0p'}{B^2}. -\end{aligned} -\label{eq:GS_before_beta} +-\frac{\mathcal J}{B^2}\Big(\partial_\psi B^2 ++\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\partial_\theta B^2\Big) +=-\frac{2\mathcal J\kappa_\psi}{g^{\psi\psi}} ++\frac{2\mathcal J\mu_0p'}{B^2}, \end{equation} -Finally use -\[ -\frac{\partial_\psi B^2}{B^2} -=2\frac{\chi''}{\chi'}+\frac{\partial_\psi\beta^2}{\beta^2}, -\qquad -\frac{\partial_\theta B^2}{B^2} -=\frac{\partial_\theta G_\alpha}{G_\alpha} --2\frac{\partial_\theta\mathcal J}{\mathcal J}, -\qquad -H=qg_{\zeta\zeta}+g_{\theta\zeta}. -\] -Equation~\eqref{eq:GS_before_beta} is then exactly \eqref{eq:gsstep}. Hence the -master identity \eqref{eq:master} is an analytic consequence of the -Grad--Shafranov equilibrium, not a numerical closure. +which turns \eqref{eq:Jprime_GS2} into exactly \eqref{eq:master}. Hence the +master identity is an analytic consequence of the Grad--Shafranov +equilibrium, not a numerical closure. -This closes the residual of Sec.~\ref{app:assembly}: multiplying~\eqref{eq:gsstep} by $\mu_0p'$ +This closes the reduction: multiplying~\eqref{eq:master} by $\mu_0p'$ converts the geometric $\theta$-derivative $-\mu_0p'\partial_\theta(\mathcal J\,g^{\psi\theta}/g^{\psi\psi})$, the $(\mu_0p')^2/B^2$ term of $|\mathbf V|^2$, and the pressure--curvature part of -$K$ into $\mu_0p'\mathcal J'$, so the bracket of~\eqref{eq:H4_expanded} becomes +$K$ into $\mu_0p'\mathcal J'$, so the corresponding bracket of~\eqref{eq:step3} becomes $\mu_0p'(\mathcal J'-\mathcal J\chi''/\chi')$. Together with the clean \(\mu_0p'\mathcal J\chi''/\chi'\) piece already isolated in \eqref{eq:step3}, this gives @@ -3878,7 +3355,7 @@ \subsection{Master identity, cofactor form, and the Grad--Shafranov closure} \subsection{The two equivalent final forms of $\mathbf H_4$} \paragraph{Equilibrium-free form.} -Projecting \eqref{eq:WUV}--\eqref{eq:WYV} directly, +Projecting the reduced pointwise weights of Sec.~\ref{app:reductions} directly, \begin{equation} \boxed{\;\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K\;,} \end{equation} @@ -3895,8 +3372,8 @@ \subsection{The two equivalent final forms of $\mathbf H_4$} exact term by term ($=$ Eq.~\eqref{eq:ep_bernsteinK}), with no equilibrium identity invoked. The compact form $\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$ -is the same matrix, obtained from \eqref{eq:WUV}--\eqref{eq:WYV} through the -Grad--Shafranov closure \eqref{eq:gsstep} derived above. The global +is the same matrix, obtained through the +Grad--Shafranov closure \eqref{eq:master} derived above. The global term-to-matrix assignment and the resulting $\mathbf C,\mathbf E,\mathbf H$ matrices are collected in the main text. From d814320b9358d619af94228524ed22bee6764695 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 24 Jun 2025 16:25:38 +0900 Subject: [PATCH 151/305] FEATURE - Add sol_num parameter to match routine. Adding sol_num parameter and set the wanted number of solution component then this will make Solution{sol_num}.bin file containing the {sol_num}th solution on Wt. If there aren't any sol_num then this will work just as same as before. (cherry picked from commit 89e45169b93b7be56ce95da73affb99d26dd0304) (cherry picked from commit 92948563d81f696b0318f0bee7e0994b1ce8ce7e) --- match/ideal.f | 21 +++++++++++++++++---- match/match.f | 6 +++--- match/match_mod.f | 1 + 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/match/ideal.f b/match/ideal.f index deaaa3db3..8b28c8202 100644 --- a/match/ideal.f +++ b/match/ideal.f @@ -305,7 +305,9 @@ END SUBROUTINE ideal_transform c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE ideal_build + SUBROUTINE ideal_build(sol_num) + + INTEGER, INTENT(IN) :: sol_num INTEGER :: istep,ifix,jfix,kfix,ieq,info INTEGER, DIMENSION(mpert) :: ipiv @@ -319,7 +321,8 @@ SUBROUTINE ideal_build uedge=0 uedge(mripple-mlow+1)=1 ELSE - uedge=wt(:,1) + WRITE(*,*)"sol_num = ",sol_num + uedge=wt(:,sol_num) ENDIF temp2=soltype(mstep)%u(:,1:mpert,1) CALL zgetrf(mpert,mpert,temp2,mpert,ipiv,info) @@ -357,12 +360,15 @@ END SUBROUTINE ideal_build c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE ideal_write + SUBROUTINE ideal_write(sol_num) + + INTEGER, INTENT(IN) :: sol_num LOGICAL, PARAMETER :: diagnose=.FALSE. INTEGER :: ipert,istep,m REAL(r8), DIMENSION(mpert) :: singfac COMPLEX(r8), DIMENSION(mpert,0:mstep) :: b + CHARACTER(128) :: outputname c----------------------------------------------------------------------- c compute normal perturbed magnetic field. c----------------------------------------------------------------------- @@ -375,7 +381,14 @@ SUBROUTINE ideal_write c write binary output for graphs. c----------------------------------------------------------------------- WRITE(*,*)"Write binary output for graphs." - CALL bin_open(bin_unit,"solutions.bin","UNKNOWN","REWIND","none") + + IF (sol_num == 1) THEN + outputname = "solutions.bin" + ELSE + WRITE(outputname, '(A,I0,A)') "solutions", sol_num, ".bin" + END IF + + CALL bin_open(bin_unit, outputname, "UNKNOWN", "REWIND", "none") DO ipert=1,mpert DO istep=0,mstep WRITE(bin_unit)REAL(psifac(istep),4),REAL(rho(istep),4), diff --git a/match/match.f b/match/match.f index e73e03f4c..4b5b1b200 100644 --- a/match/match.f +++ b/match/match.f @@ -29,7 +29,7 @@ PROGRAM match_main $ big,small,ns,root,scan_flag,root_flag,contour_flag,full, $ z_level_type,phi_level,transform_flag,poly_form_type, $ poly_test_type,scan0,scan1,nscan,delta0,delta1,condense, - $ ripple_flag,mripple + $ ripple_flag,mripple,sol_num c----------------------------------------------------------------------- c format statements. c----------------------------------------------------------------------- @@ -55,8 +55,8 @@ PROGRAM match_main c----------------------------------------------------------------------- IF(ideal_flag)THEN CALL ideal_transform - CALL ideal_build - CALL ideal_write + CALL ideal_build(sol_num) + CALL ideal_write(sol_num) CALL ideal_chord IF(contour_flag)CALL ideal_contour ENDIF diff --git a/match/match_mod.f b/match/match_mod.f index 501e91bcc..5ff8d20c9 100644 --- a/match/match_mod.f +++ b/match/match_mod.f @@ -42,6 +42,7 @@ MODULE match_mod $ kin_flag=.FALSE.,con_flag=.FALSE. LOGICAL, DIMENSION(:), POINTER :: sing_flag INTEGER :: mfix,mhigh,mlow,mpert,mstep,nn,msing,mmatch,mbit,mterm + INTEGER :: sol_num = 1 INTEGER, DIMENSION(:), POINTER :: fixstep REAL(r8) :: sfac0=1 REAL(r8), DIMENSION(:), POINTER :: psifac,rho,q From 12f50f68b4b8710f94c6a45bc284c967e38bcc2f Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 24 Jun 2025 17:28:45 +0900 Subject: [PATCH 152/305] FEATURE - Add sol_num parameter to match input file. This change initializes sol_num to 1, allowing for better control of solution components. (cherry picked from commit d9756973653d15ed5f0e27c264a0e7f39f45d803) (cherry picked from commit 3cbd047eb75ce7122f1115466cf9aa2783c23866) --- input/match.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/input/match.in b/input/match.in index e4b9e15ea..6f2dda0b8 100644 --- a/input/match.in +++ b/input/match.in @@ -5,6 +5,8 @@ ideal_flag=t contour_flag=t + sol_num=1 + res_flag=f ff0=1e6 ff1=1e6 From ccf446cdc62f7665dde016d5617ecf3bda323730 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Wed, 11 Mar 2026 14:35:26 +0900 Subject: [PATCH 153/305] Clarify comment for sol_num in match.in regarding DCON eigen function index (cherry picked from commit 25ddd6ef05a181148bc33a466e2486280fbe1ee4) (cherry picked from commit b5b3203931408cbf82a3b2f0d0202e7f48f5931b) --- input/match.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input/match.in b/input/match.in index 6f2dda0b8..2998fac2a 100644 --- a/input/match.in +++ b/input/match.in @@ -5,7 +5,7 @@ ideal_flag=t contour_flag=t - sol_num=1 + sol_num=1 !DCON eigen function index (1 is least stable), which will be calculates by MATCH res_flag=f ff0=1e6 From a49c79ab74d4b57f744a94eaf2571f6c5af11555 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 25 Jun 2026 12:18:54 +0900 Subject: [PATCH 154/305] MATCH - recon: unify sol_num index wording (1 = most unstable) Match input/match.in comment to match/match.f; sol_num=1 selects the most unstable DCON ideal eigenfunction. Addresses snu_offshoot review Q2. Co-Authored-By: Claude Opus 4.8 (1M context) (cherry picked from commit 311922c01191e14cc5079886b2869f6874a384c9) (cherry picked from commit f998b3b5cf4d9569afcff24864c26f0fa7f73c88) --- input/match.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input/match.in b/input/match.in index 2998fac2a..31acfa658 100644 --- a/input/match.in +++ b/input/match.in @@ -5,7 +5,7 @@ ideal_flag=t contour_flag=t - sol_num=1 !DCON eigen function index (1 is least stable), which will be calculates by MATCH + sol_num=1 !DCON eigen function index (1 is most unstable), which will be calculated by MATCH res_flag=f ff0=1e6 From 2c03f043184827599eca81258850af29684ce313 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 20 Jul 2026 14:19:56 +0900 Subject: [PATCH 155/305] MATCH - recon: validate sol_num/mripple, drop debug print - range-check sol_num against mpert (read from euler.bin at runtime) before indexing wt(:,sol_num); out-of-range values previously read past the array and wrote a garbage eigenfunction silently - same check for mripple against mlow..mhigh on the ripple path - remove the unconditional 'sol_num = ' debug write - ripple_flag ignores sol_num, so write solutions.bin rather than a sol_num-labeled filename in that case Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 0bf08bfd350da0a005fc7f562dec77d7b73b8de4) --- match/ideal.f | 12 +++++++++++- match/match.f | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/match/ideal.f b/match/ideal.f index 8b28c8202..033d3ad5b 100644 --- a/match/ideal.f +++ b/match/ideal.f @@ -313,15 +313,25 @@ SUBROUTINE ideal_build(sol_num) INTEGER, DIMENSION(mpert) :: ipiv COMPLEX(r8), DIMENSION(mpert) :: uedge,temp1 COMPLEX(r8), DIMENSION(mpert,mpert) :: temp2 + CHARACTER(128) :: message c----------------------------------------------------------------------- c construct uedge. c----------------------------------------------------------------------- WRITE(*,*)"Construct ideal eigenfunctions." IF(ripple_flag)THEN + IF(mripple < mlow .OR. mripple > mhigh)THEN + WRITE(message,'(a,i6,a,i6,a,i6)')"mripple = ",mripple, + $ " out of range ",mlow," to ",mhigh + CALL program_stop(message) + ENDIF uedge=0 uedge(mripple-mlow+1)=1 ELSE - WRITE(*,*)"sol_num = ",sol_num + IF(sol_num < 1 .OR. sol_num > mpert)THEN + WRITE(message,'(a,i6,a,i6)')"sol_num = ",sol_num, + $ " out of range 1 to ",mpert + CALL program_stop(message) + ENDIF uedge=wt(:,sol_num) ENDIF temp2=soltype(mstep)%u(:,1:mpert,1) diff --git a/match/match.f b/match/match.f index 4b5b1b200..939f56a50 100644 --- a/match/match.f +++ b/match/match.f @@ -56,7 +56,13 @@ PROGRAM match_main IF(ideal_flag)THEN CALL ideal_transform CALL ideal_build(sol_num) - CALL ideal_write(sol_num) +c ripple_flag builds a unit-vector uedge and ignores sol_num, so +c keep the default output name rather than a sol_num-labeled one. + IF(ripple_flag)THEN + CALL ideal_write(1) + ELSE + CALL ideal_write(sol_num) + ENDIF CALL ideal_chord IF(contour_flag)CALL ideal_contour ENDIF From 94547e95a1243a963b34e4ac42ba7d59d239afd3 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Tue, 21 Jul 2026 13:08:07 +0900 Subject: [PATCH 156/305] GPEC - recon: factor psi-local C-state setup into gpeq_prep_c gpeq_sol/contra/cova/normal + gpeq_c were repeated inline at every recon call site; bundle them as gpeq_prep_c(psi) and make the recon kernels (gpeq_cveri, gpeq_epf, gpeq_dst, gpeq_epfterms) assume the state is prepared. gpout_recon now calls gpeq_cveri directly instead of carrying an inlined copy of the identity. Also fix a stray minus in the cveri_flag comment of input/gpec.in. Co-Authored-By: Claude Opus 4.8 --- gpec/gpec.f | 2 +- gpec/gpeq.f | 158 +++++++++++++++++++++++------------------------- gpec/gpglobal.F | 2 +- gpec/gpout.f | 101 ++++++++++++------------------- input/gpec.in | 2 +- 5 files changed, 118 insertions(+), 147 deletions(-) diff --git a/gpec/gpec.f b/gpec/gpec.f index 84b467ae0..28a8ec89f 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -635,7 +635,7 @@ PROGRAM gpec_main ENDIF IF (recon_flag) THEN CALL gpout_recon(mode,xspmn) - ENDIF + ENDIF IF (recon_flag2) THEN CALL gpout_recon2(mode,xspmn) ENDIF diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 068ca5704..e95e57bbd 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -14,23 +14,27 @@ c 6. gpeq_parallel c 7. gpeq_rzphi c 8. gpeq_surface -c 9. gpeq_epf (reconstruction: C vector for EPF) -c 10. gpeq_dst (reconstruction: C vector for DST) -c 11. gpeq_shear (reconstruction: magnetic shear) -c 12. gpeq_curvature (reconstruction: curvature) -c 13. gpeq_K (reconstruction: Bernstein K quantity) -c 13b. gpeq_epfterms (reconstruction: |C|^2 decomposition) -c 14. gpeq_fcoords -c 15. gpeq_fcoordsout -c 16. gpeq_bcoords -c 17. gpeq_bcoordsout -c 18. gpeq_weight -c 19. gpeq_rzpgrid -c 20. gpeq_rzpdiv -c 21. gpeq_alloc -c 22. gpeq_dealloc -c 23. gpeq_interp_singsurf -c 24. gpeq_interp_sol +c 9. gpeq_prep_c (reconstruction: build psi-local C state) +c 10. gpeq_c (reconstruction: C vector components) +c 11. gpeq_cveri (reconstruction: (curl C).grad(psi) check) +c 12. gpeq_firstform (reconstruction: first-form energy terms) +c 13. gpeq_epf (reconstruction: |C|^2 surface integral) +c 14. gpeq_dst (reconstruction: K xi_n^2 surface integral) +c 15. gpeq_shear (reconstruction: magnetic shear) +c 16. gpeq_curvature (reconstruction: curvature) +c 17. gpeq_K (reconstruction: Bernstein K quantity) +c 18. gpeq_epfterms (reconstruction: |C|^2 decomposition) +c 19. gpeq_fcoords +c 20. gpeq_fcoordsout +c 21. gpeq_bcoords +c 22. gpeq_bcoordsout +c 23. gpeq_weight +c 24. gpeq_rzpgrid +c 25. gpeq_rzpdiv +c 26. gpeq_alloc +c 27. gpeq_dealloc +c 28. gpeq_interp_singsurf +c 29. gpeq_interp_sol c----------------------------------------------------------------------- c subprogram 0. gpeq_mod. c module declarations. @@ -652,7 +656,30 @@ SUBROUTINE gpeq_surface(psi) RETURN END SUBROUTINE gpeq_surface c----------------------------------------------------------------------- -c subprogram 9. gpeq_c. +c subprogram 9. gpeq_prep_c. +c build the psi-local perturbed equilibrium state and the C vector +c in one pass: gpeq_sol/contra/cova/normal followed by gpeq_c. +c The recon kernels gpeq_cveri, gpeq_epf, gpeq_dst and +c gpeq_epfterms assume this has been called for the current psi. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_prep_c(psi) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_c(psi, 0) +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_prep_c +c----------------------------------------------------------------------- +c subprogram 10. gpeq_c. c compute C vector (covariant + contravariant) for EPF calculation. c----------------------------------------------------------------------- SUBROUTINE gpeq_c(psi, ipsi) @@ -664,18 +691,17 @@ SUBROUTINE gpeq_c(psi, ipsi) INTEGER :: itheta, ipert, jpert, m1, dm - REAL(r8) :: q, q1, f1raw, p1, chi1, jac + REAL(r8) :: q, f1raw, p1, chi1, jac REAL(r8) :: eta, rfac, v21, v22, v23, v33 - REAL(r8), DIMENSION(0:mthsurf) :: jacs, dphi, r_vec, z_vec + REAL(r8), DIMENSION(0:mthsurf) :: jacs, r_vec - COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun, xno_fun, bno_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun c Local temporal arrays for computation COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun - COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_funp, cvt_funp, cvz_funp c Supporting variables for Fourier reconstruction REAL(r8) :: jwt, jwz @@ -692,7 +718,6 @@ SUBROUTINE gpeq_c(psi, ipsi) CALL spline_eval(sq, psi, 1) CALL cspline_eval(metric%cs, psi, 0) q = sq%f(4) - q1 = sq%f1(4) f1raw = sq%f1(1) p1 = sq%f1(2) / mu0 chi1 = psio * twopi @@ -719,9 +744,7 @@ SUBROUTINE gpeq_c(psi, ipsi) rfac = SQRT(rzphi%f(1)) eta = twopi*(theta(itheta) + rzphi%f(2)) r_vec(itheta) = ro + rfac*COS(eta) - z_vec(itheta) = zo + rfac*SIN(eta) jacs(itheta) = jac - dphi(itheta) = rzphi%f(3) w(1,1) = (1.0+ rzphi%fy(2))*twopi**2*rfac*r_vec(itheta)/jac w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) @@ -749,9 +772,6 @@ SUBROUTINE gpeq_c(psi, ipsi) g_33(itheta) = (v33**2) g_23(itheta) = (v23*v33) ENDDO - - xno_fun=xwp_fun/(jacs*delpsi) - bno_fun=bwp_fun/(jacs*delpsi) c----------------------------------------------------------------------- c 6) Compute C components. c bwp stores J Q^psi, while bmt/bmz store the modified @@ -850,10 +870,6 @@ SUBROUTINE gpeq_c(psi, ipsi) ENDDO ENDDO - CALL iscdftb(mfac, mpert, cvp_funp, mthsurf, c2vp_mn) - CALL iscdftb(mfac, mpert, cvt_funp, mthsurf, c2vt_mn) - CALL iscdftb(mfac, mpert, cvz_funp, mthsurf, c2vz_mn) - IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi c----------------------------------------------------------------------- c terminate. @@ -861,7 +877,7 @@ SUBROUTINE gpeq_c(psi, ipsi) RETURN END SUBROUTINE gpeq_c c----------------------------------------------------------------------- -c subprogram 9c. gpeq_cveri. +c subprogram 11. gpeq_cveri. c verify the C-vector identity c c (curl C) · grad(psi) @@ -881,8 +897,8 @@ END SUBROUTINE gpeq_c c (curl C) · grad(psi) c = (d_theta C_zeta + 2*pi*i*n*C_theta)/J. c -c Self-contained; gpout_recon inlines the same identity to reuse -c gpeq_epf's state. Keep the two in sync. +c Assumes gpeq_prep_c(psi) has been called for this psi, so the +c module cvt_mn/cvz_mn hold the covariant C state. c----------------------------------------------------------------------- SUBROUTINE gpeq_cveri(psi, cveri_fun) c----------------------------------------------------------------------- @@ -897,15 +913,6 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) IF(debug_flag) PRINT *, "Entering gpeq_cveri" c----------------------------------------------------------------------- -c prepare psi-local equilibrium and reconstruct C. -c----------------------------------------------------------------------- - CALL gpeq_sol(psi) - CALL gpeq_contra(psi) - CALL gpeq_cova(psi) - CALL gpeq_normal(psi) - CALL gpeq_c(psi, 0) - -c----------------------------------------------------------------------- c exact theta/zeta derivatives in mode space. c----------------------------------------------------------------------- DO ipert = 1, mpert @@ -933,7 +940,7 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) RETURN END SUBROUTINE gpeq_cveri c----------------------------------------------------------------------- -c subprogram 9d. gpeq_firstform. +c subprogram 12. gpeq_firstform. c evaluate the first-form plasma energy density components on one c flux surface with gamma term omitted: c @@ -1067,12 +1074,13 @@ SUBROUTINE gpeq_firstform(psi, q2_int, jqx_int, pdiv_int, RETURN END SUBROUTINE gpeq_firstform c----------------------------------------------------------------------- -c subprogram 9b. gpeq_epf. +c subprogram 13. gpeq_epf. c compute the flux-surface integral of the first EPF kernel, c c \int dtheta dzeta J |C|^2 / mu0 c c and return the theta/zeta integrated value for one psi. +c Assumes gpeq_prep_c(psi) has been called for this psi. c----------------------------------------------------------------------- SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun) @@ -1094,14 +1102,8 @@ SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, IF(debug_flag) PRINT *, "Entering gpeq_epf" c----------------------------------------------------------------------- -c Prepare psi-local perturbed equilibrium and compute C components. +c reconstruct the C components prepared by gpeq_prep_c. c----------------------------------------------------------------------- - CALL gpeq_sol(psi) - CALL gpeq_contra(psi) - CALL gpeq_cova(psi) - CALL gpeq_normal(psi) - CALL gpeq_c(psi, 0) - CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) @@ -1146,12 +1148,13 @@ SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, RETURN END SUBROUTINE gpeq_epf c----------------------------------------------------------------------- -c subprogram 10. gpeq_dst. +c subprogram 14. gpeq_dst. c compute the flux-surface integral of the destabilizing kernel, c c \int dtheta dzeta J * K * xi_n^2 c c and return the theta/zeta integrated value for one psi. +c Assumes gpeq_prep_c(psi) has been called for this psi. c----------------------------------------------------------------------- SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, $ dst1_den_fun, dst2_den_fun, dst3_den_fun) @@ -1171,10 +1174,6 @@ SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, c----------------------------------------------------------------------- c DST(psi) = \int dtheta dzeta J * K * xi_n^2 c----------------------------------------------------------------------- - CALL gpeq_sol(psi) - CALL gpeq_contra(psi) - CALL gpeq_cova(psi) - CALL gpeq_normal(psi) CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3) CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) dst_int = CMPLX(0.0_r8, 0.0_r8, r8) @@ -1208,7 +1207,7 @@ SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, RETURN END SUBROUTINE gpeq_dst c----------------------------------------------------------------------- -c subprogram 11. gpeq_shear. +c subprogram 15. gpeq_shear. c compute magnetic shear at a single psi level. c approach: compute spatial domain first (theta), then FFT to mode space. c----------------------------------------------------------------------- @@ -1328,7 +1327,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) RETURN END SUBROUTINE gpeq_shear c----------------------------------------------------------------------- -c subprogram 12. gpeq_curvature. +c subprogram 16. gpeq_curvature. c compute curvature in spatial domain. c κ·∇ψ = (|∇ψ|²/B²)[μ₀p' + (1/2)(∂B²/∂ψ) + (1/2)(∂B²/∂θ)(∇ψ·∇θ)/(∇ψ·∇ψ)] c----------------------------------------------------------------------- @@ -1396,7 +1395,7 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) RETURN END SUBROUTINE gpeq_curvature c----------------------------------------------------------------------- -c subprogram 13. gpeq_K. +c subprogram 17. gpeq_K. c compute Bernstein K quantity (stability indicator). c K = |∇ψ_dcon|^2 * σ * S_dcon c + mu0 * B^2 * σ^2 + 2 p' * κ^psi @@ -1412,7 +1411,7 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ shear_out, curv_out INTEGER :: itheta - REAL(r8) :: f1raw, delpsi + REAL(r8) :: f1raw, delpsi, chi1 REAL(r8) :: jwt, jwz, bth, bze, bsq_val, sigma, jdotb_val REAL(r8) :: g22, g23, g33, r_val REAL(r8), DIMENSION(0:mthsurf) :: K_t1, K_t2, K_t3 @@ -1511,7 +1510,7 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, RETURN END SUBROUTINE gpeq_K c----------------------------------------------------------------------- -c subprogram 13b. gpeq_epfterms. +c subprogram 18. gpeq_epfterms. c split the epf surface integral of gpeq_epf into terms of c comparable magnitude, using C = Q + V, V = xi_n mu0 j x n_hat: c A = int |Q|^2 / mu0 B = int 2 Re(V*.Q) / mu0 @@ -1519,6 +1518,7 @@ END SUBROUTINE gpeq_K c Iperp = int mu0 p'^2 |grad psi|^2 xin^2 / B^2 c each on the jac measure, as int (..) J dtheta / mthsurf. The c terms satisfy epf = A + B + Cpar + Iperp to roundoff. +c Assumes gpeq_prep_c(psi) has been called for this psi. c----------------------------------------------------------------------- SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, $ epf_int, A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) @@ -1537,7 +1537,7 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, $ surf_extra INTEGER :: itheta - REAL(r8) :: f1raw, p1_local + REAL(r8) :: f1raw, p1_local, chi1 REAL(r8) :: A_density, B_density, Iperp_density, Cpar_density, $ epf_density REAL(r8) :: delpsi_val, bsq_val, sigma_local, jdotb_val @@ -1557,14 +1557,8 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, IF(debug_flag) PRINT *, "Entering gpeq_epfterms" c----------------------------------------------------------------------- -c prepare psi-local perturbed equilibrium and C, Q. +c reconstruct the C, Q state prepared by gpeq_prep_c. c----------------------------------------------------------------------- - CALL gpeq_sol(psi) - CALL gpeq_contra(psi) - CALL gpeq_cova(psi) - CALL gpeq_normal(psi) - CALL gpeq_c(psi, 0) - CALL spline_eval(sq, psi, 1) f1raw = sq%f1(1) p1_local = sq%f1(2) / mu0 @@ -1746,7 +1740,7 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, RETURN END SUBROUTINE gpeq_epfterms c----------------------------------------------------------------------- -c subprogram 14. gpeq_fcoords. +c subprogram 19. gpeq_fcoords. c transform coordinates to dcon coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -1880,7 +1874,7 @@ SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_fcoords c----------------------------------------------------------------------- -c subprogram 14. gpeq_fcoordsout. +c subprogram 20. gpeq_fcoordsout. c transform to dcon coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) @@ -1943,7 +1937,7 @@ SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_fcoordsout c----------------------------------------------------------------------- -c subprogram 15. gpeq_bcoords. +c subprogram 21. gpeq_bcoords. c transform dcon coordinates to other coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -2082,7 +2076,7 @@ SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_bcoords c----------------------------------------------------------------------- -c subprogram 16. gpeq_bcoordsout. +c subprogram 22. gpeq_bcoordsout. c transform dcon to other coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) @@ -2145,7 +2139,7 @@ SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_bcoordsout c----------------------------------------------------------------------- -c subprogram 17. gpeq_weight. +c subprogram 23. gpeq_weight. c switch between a function and a weighted function. c----------------------------------------------------------------------- SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) @@ -2216,7 +2210,7 @@ SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) RETURN END SUBROUTINE gpeq_weight c----------------------------------------------------------------------- -c subprogram 18. gpeq_rzpgrid. +c subprogram 24. gpeq_rzpgrid. c find magnetic coordinates for given rz coords. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) @@ -2319,7 +2313,7 @@ SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) RETURN END SUBROUTINE gpeq_rzpgrid c----------------------------------------------------------------------- -c subprogram 19. gpeq_rzpdiv. +c subprogram 25. gpeq_rzpdiv. c make zero divergence of rzphi functions. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) @@ -2385,7 +2379,7 @@ SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) RETURN END SUBROUTINE gpeq_rzpdiv c----------------------------------------------------------------------- -c subprogram 20. gpeq_alloc. +c subprogram 26. gpeq_alloc. c allocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_alloc @@ -2411,7 +2405,7 @@ SUBROUTINE gpeq_alloc RETURN END SUBROUTINE gpeq_alloc c----------------------------------------------------------------------- -c subprogram 21. gpeq_dealloc. +c subprogram 27. gpeq_dealloc. c deallocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_dealloc @@ -2431,7 +2425,7 @@ SUBROUTINE gpeq_dealloc RETURN END SUBROUTINE gpeq_dealloc c----------------------------------------------------------------------- -c subprogram 22. gpeq_interp_singsurf. +c subprogram 28. gpeq_interp_singsurf. c create spline for interpretation of solution near singular surface. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_singsurf(fsp_sol,spots,npsi) @@ -2515,7 +2509,7 @@ SUBROUTINE gpeq_interp_singsurf(fsp_sol,spots,npsi) RETURN END SUBROUTINE gpeq_interp_singsurf c----------------------------------------------------------------------- -c subprogram 23. gpeq_interp_sol. +c subprogram 29. gpeq_interp_sol. c get bwn at psi after calling gpeq_interp_singsurf. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_sol(fsp_sol,psi,interpbwn) diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 064a102ba..50c9c57c1 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -157,7 +157,7 @@ SUBROUTINE gpec_dealloc $ plas_indevmats,permeabevmats,chxmats,chwmats, $ mutual_indmats,mutual_indevmats,mutual_indinvmats, $ mutual_indinvevmats) - + CALL spline_dealloc(sq) CALL bicube_dealloc(eqfun) CALL bicube_dealloc(rzphi) diff --git a/gpec/gpout.f b/gpec/gpout.f index a812fbdc5..6c213a881 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6897,7 +6897,7 @@ SUBROUTINE gpout_close_netcdf END SUBROUTINE gpout_close_netcdf c----------------------------------------------------------------------- -c subprogram 20. gpout_recon +c subprogram 21. gpout_recon c reconstruction diagnostics: shear, curvature, K, field C and c the integrals epf, dst giving delta-W_p. c----------------------------------------------------------------------- @@ -6908,9 +6908,14 @@ SUBROUTINE gpout_recon(mode, xspmn) INTEGER, INTENT(IN) :: mode COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn - INTEGER :: ipsi, itheta, ipert, ushear_fun, ucurv, uk, ucveri, - $ uk_sigma, ucw_fun, ucw_mn, ucv_fun, ucv_mn, ucv2_fun, - $ ucv2_mn, uqv_mn, u_int, u_log, u_terms + INTEGER :: ipsi, itheta, ipert +c recon output units: 72-99 are reserved for the recon diagnostics +c (gpout_recon/2/3) and are not opened elsewhere in the GPEC +c executable. + INTEGER, PARAMETER :: ushear_fun=82, ucurv=72, ucveri=74, uk=73, + $ uk_sigma=85, ucw_fun=86, ucw_mn=87, ucv_fun=88, ucv_mn=89, + $ ucv2_fun=90, ucv2_mn=91, u_int=92, u_log=93, u_terms=94, + $ uqv_mn=95 REAL(r8), DIMENSION(0:mthsurf) :: epf_den_fun, epf_p_fun, $ epf_t_fun, epf_z_fun, dst1_den_fun, dst2_den_fun, $ dst3_den_fun @@ -6918,8 +6923,6 @@ SUBROUTINE gpout_recon(mode, xspmn) REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals REAL(r8) :: epf_int, epf_p_int, epf_t_int, epf_z_int COMPLEX(r8) :: dst_int - COMPLEX(r8), DIMENSION(mpert) :: curv_mn, - $ term_mn COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun, $ cveri_fun COMPLEX(r8), DIMENSION(0:mthsurf,3) :: cw_fun, cv_fun, cv2_fun @@ -6938,7 +6941,7 @@ SUBROUTINE gpout_recon(mode, xspmn) REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr REAL(r8) :: epf_int_total_hr, epf_p_total_hr, - $ epf_t_total_hr, epf_z_total_hr, dcon_fac + $ epf_t_total_hr, epf_z_total_hr REAL(r8) :: cveri_abs, cveri_max, cveri_rms, cveri_sumsq, $ cveri_tol COMPLEX(r8) :: dst_prev, dst_curr, dst1_prev, dst2_prev, @@ -6975,22 +6978,6 @@ SUBROUTINE gpout_recon(mode, xspmn) int_file = "gpec_recon_integration_sol"//TRIM(smode)//".out" terms_file = "gpec_recon_terms_sol"//TRIM(smode)//".out" - ushear_fun = 82 - ucurv = 72 - ucveri = 74 - uk = 73 - uk_sigma = 85 - ucw_fun = 86 - ucw_mn = 87 - ucv_fun = 88 - ucv_mn = 89 - ucv2_fun = 90 - ucv2_mn = 91 - u_int = 92 - u_log = 93 - u_terms = 94 - uqv_mn = 95 - IF (recon_out) THEN OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") @@ -7073,15 +7060,15 @@ SUBROUTINE gpout_recon(mode, xspmn) psi = rzphi%xs(ipsi) IF (psi > psilim) EXIT -c Compute the J|C|^2/mu0 state once so C fields below use -c the current psi. This keeps the psi-local reconstruction path -c identical to the original logic. +c Build the psi-local perturbed equilibrium and C state once; +c gpeq_epf, gpeq_cveri and gpeq_dst below all reuse it. + CALL gpeq_prep_c(psi) CALL gpeq_epf(psi, epf_int, epf_den_fun=epf_den_fun, $ epf_p_fun=epf_p_fun, epf_t_fun=epf_t_fun, $ epf_z_fun=epf_z_fun) c Capture covariant bare-Q components (Q_i) set by gpeq_cova -c inside gpeq_epf, before gpeq_K/gpeq_dst run. These are the +c inside gpeq_prep_c, before gpeq_K/gpeq_dst run. These are the c genuine perturbed-field covariant components delta-B_i; the c effective field is cvmn = C_i = Q_i + V_i. Writing qvmn lets c (curl Q).grad(psi) be formed from covariant components, the @@ -7096,22 +7083,10 @@ SUBROUTINE gpout_recon(mode, xspmn) c the original path so recon terminal totals remain unchanged. CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ sigma_vals, jdotb_vals, shear_fun, curv_fun) -c Verify (curl C).grad(psi)=0 using the same psi-local C state -c already prepared by gpeq_epf. This avoids rebuilding the -c full equilibrium/C reconstruction a second time at each psi. -c Same identity as gpeq_cveri, inlined; keep the two in sync. +c Verify (curl C).grad(psi)=0 using the C state prepared above. IF (cveri_flag) THEN - DO ipert = 1, mpert - curv_mn(ipert) = twopi * ifac * mfac(ipert) - $ * cvz_mn(ipert) - term_mn(ipert) = curv_mn(ipert) + - $ twopi * ifac * nn * cvt_mn(ipert) - ENDDO - CALL iscdftb(mfac, mpert, cveri_fun, mthsurf, term_mn) + CALL gpeq_cveri(psi, cveri_fun) DO itheta = 0, mthsurf - CALL bicube_eval(rzphi, psi, theta(itheta), 0) - jac = rzphi%f(4) - cveri_fun(itheta) = cveri_fun(itheta) / jac cveri_abs = ABS(cveri_fun(itheta)) cveri_max = MAX(cveri_max, cveri_abs) cveri_sumsq = cveri_sumsq + cveri_abs**2 @@ -7219,7 +7194,6 @@ SUBROUTINE gpout_recon(mode, xspmn) c Perform the energy check on the DCON solution grid. Do not use c rzphi%xs for this comparison; it is a coarse geometry grid. c----------------------------------------------------------------------- - dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 epf_int_total_hr = 0.0_r8 epf_p_total_hr = 0.0_r8 epf_t_total_hr = 0.0_r8 @@ -7229,6 +7203,7 @@ SUBROUTINE gpout_recon(mode, xspmn) dst2_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) dst3_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) psi_prev = MAX(psifac(0), rzphi%xs(0)) + CALL gpeq_prep_c(psi_prev) CALL gpeq_epf(psi_prev, epf_prev, epf_p_prev, epf_t_prev, $ epf_z_prev) CALL gpeq_dst(psi_prev, dst_prev, dst1_prev, dst2_prev, @@ -7266,6 +7241,7 @@ SUBROUTINE gpout_recon(mode, xspmn) psi_curr = psifac(istep+1) IF (psi_curr < rzphi%xs(0)) CYCLE IF (psi_curr > rzphi%xs(mpsi)) EXIT + CALL gpeq_prep_c(psi_curr) CALL gpeq_epf(psi_curr, epf_curr, epf_p_curr, $ epf_t_curr, epf_z_curr) CALL gpeq_dst(psi_curr, dst_curr, dst1_curr, dst2_curr, @@ -7371,7 +7347,8 @@ SUBROUTINE gpout_recon(mode, xspmn) $ epf_z_vals, dst_vals, dst1_vals, dst2_vals, dst3_vals) dw_raw_k_hr = 0.5_r8 * $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - dst_int_total_k_hr) - dw_dcon_k_hr = dw_raw_k_hr * dcon_fac + dw_dcon_k_hr = dw_raw_k_hr * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (recon_out) THEN WRITE(u_int,*) @@ -7418,7 +7395,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", - $ ep_selected*dcon_fac + $ ep_selected * (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (cveri_flag) THEN cveri_stat = "PASS" IF (cveri_count > 0) THEN @@ -7465,7 +7442,8 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), - $ ") = ", ep_selected*dcon_fac + $ ") = ", ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (cveri_flag) THEN WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max @@ -7515,7 +7493,9 @@ SUBROUTINE gpout_recon2(mode, xspmn) INTEGER, INTENT(IN) :: mode COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn - INTEGER :: ipsi, itheta, usurf, u_int, u_log + INTEGER :: ipsi, itheta +c recon2 output units, within the reserved recon range 72-99. + INTEGER, PARAMETER :: usurf=94, u_int=95, u_log=96 INTEGER :: istep, nint_pts, iint REAL(r8) :: psi, eta, rfac, dpsi_local, q2_hr REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals @@ -7525,7 +7505,6 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ total_fun CHARACTER(8) :: smode CHARACTER(128) :: surf_file, int_file - REAL(r8) :: dcon_fac INTEGER :: ep_index REAL(r8) :: ep_selected, ep_dcon REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, q2_vals_r @@ -7542,9 +7521,6 @@ SUBROUTINE gpout_recon2(mode, xspmn) smode = ADJUSTL(smode) surf_file = "gpec_recon2_terms_sol"//TRIM(smode)//".out" int_file = "gpec_recon2_integration_sol"//TRIM(smode)//".out" - usurf = 94 - u_int = 95 - u_log = 96 IF (recon_out) THEN OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") @@ -7591,9 +7567,9 @@ SUBROUTINE gpout_recon2(mode, xspmn) ENDIF ENDDO - dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 ep_selected = REAL(ep(ep_index)) - ep_dcon = ep_selected * dcon_fac + ep_dcon = ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 q2_hr = 0.0_r8 jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) @@ -7684,7 +7660,8 @@ SUBROUTINE gpout_recon2(mode, xspmn) ENDIF dw_raw_hr = 0.5_r8 * total_hr - dw_dcon_hr = dw_raw_hr * dcon_fac + dw_dcon_hr = dw_raw_hr * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (recon_out) THEN WRITE(u_int,*) @@ -7778,7 +7755,9 @@ SUBROUTINE gpout_recon3(mode, xspmn) INTEGER, INTENT(IN) :: mode COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn - INTEGER :: ipsi, itheta, usurf, u_int, u_log + INTEGER :: ipsi, itheta +c recon3 output units, within the reserved recon range 72-99. + INTEGER, PARAMETER :: usurf=97, u_int=98, u_log=99 INTEGER :: istep, nint_pts, iint REAL(r8) :: psi, dpsi_local REAL(r8) :: A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr @@ -7795,7 +7774,6 @@ SUBROUTINE gpout_recon3(mode, xspmn) INTEGER :: kcol CHARACTER(8) :: smode CHARACTER(128) :: surf_file, int_file - REAL(r8) :: dcon_fac REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts REAL(r8), DIMENSION(:), ALLOCATABLE :: A_vals, B_vals, $ Iperp_vals, Cpar_vals, epf_vals, dst1_vals, dst3_vals @@ -7809,9 +7787,6 @@ SUBROUTINE gpout_recon3(mode, xspmn) smode = ADJUSTL(smode) surf_file = "gpec_recon3_terms_sol"//TRIM(smode)//".out" int_file = "gpec_recon3_integration_sol"//TRIM(smode)//".out" - usurf = 97 - u_int = 98 - u_log = 99 IF (recon_out) THEN OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") @@ -7840,6 +7815,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) DO ipsi = 0, mpsi psi = rzphi%xs(ipsi) IF (psi > psilim) EXIT + CALL gpeq_prep_c(psi) CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr, $ A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) IF (recon_out) THEN @@ -7858,8 +7834,6 @@ SUBROUTINE gpout_recon3(mode, xspmn) ENDIF ENDDO - dcon_fac = (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 - c integrate on psifac points inside the spline domain; points c below rzphi%xs(0) are skipped (gpout_recon clamps instead). nint_pts = 0 @@ -7876,6 +7850,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) psi = psifac(istep) IF (psi < rzphi%xs(0)) CYCLE IF (psi > rzphi%xs(mpsi)) EXIT + CALL gpeq_prep_c(psi) CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr) c dst1 (=K1 xi_n^2) and dst3 (=K3 xi_n^2) for the C-free dW form c 2 dW_p = A + B + Iperp - dst1 - dst3 (dst2 = Cpar cancels). @@ -7974,9 +7949,11 @@ SUBROUTINE gpout_recon3(mode, xspmn) c (the parallel-current piece dst2 = Cpar cancels identically). dw_raw3 = 0.5_r8 * (A_total + B_total + Iperp_total $ - dst1_total - dst3_total) - dw_dcon3 = dw_raw3 * dcon_fac + dw_dcon3 = dw_raw3 * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 ep_selected = REAL(ep(ep_index)) - ep_dcon = ep_selected * dcon_fac + ep_dcon = ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (recon_out) THEN WRITE(u_int,*) diff --git a/input/gpec.in b/input/gpec.in index f06c19867..9adf1c7d5 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -93,7 +93,7 @@ recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. recon_flag3=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. - cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi-)=0 and write gpec_recon_cveri_sol*.out. + cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under recon_flag/recon_flag2/recon_flag3, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. From 58173ef1135c4fc9fbee1927480c5c144925d0f7 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 27 Jul 2026 14:06:00 +0900 Subject: [PATCH 157/305] GPEC - recon: keep p1/q scratch local to recon kernels gpeq_shear, gpeq_curvature and gpeq_K assigned the gpglobal module variables p1/q/q1 through host association. p1 in particular was stored as p' while the rest of GPEC keeps mu0*p' there (idcon.f, gpdiag.f). No reader consumes the stale values today, but shadow them with routine-local declarations so the recon diagnostics leave module state untouched. Results are bit-identical. Also note in gpeq_c that the removed xno/bno and theta-space c2v* fields were development-time verification aids. Co-Authored-By: Claude Opus 5 --- gpec/gpeq.f | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index e95e57bbd..c6bd1395a 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -869,6 +869,7 @@ SUBROUTINE gpeq_c(psi, ipsi) $ g23(dm)*cwt_mn(jpert)+g33(dm)*cwz_mn(jpert) ENDDO ENDDO +c (xno/bno and theta-space c2v* were dev-time checks; removed) IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi c----------------------------------------------------------------------- @@ -1216,7 +1217,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: shear_fun INTEGER :: itheta - REAL(r8) :: r_val, theta_val + REAL(r8) :: r_val, theta_val, q, q1 REAL(r8) :: dpdp, dpdt, dpdz, shear_deriv REAL(r8) :: shear_contra TYPE(spline_type) :: shear_temp @@ -1336,7 +1337,7 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: curv_fun INTEGER :: itheta - REAL(r8) :: r_val, theta_val + REAL(r8) :: r_val, theta_val, p1 REAL(r8) :: delpsi REAL(r8) :: bsq_val, bsq_psi, bsq_theta REAL(r8) :: dpdt , kappa_psi @@ -1411,7 +1412,7 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ shear_out, curv_out INTEGER :: itheta - REAL(r8) :: f1raw, delpsi, chi1 + REAL(r8) :: f1raw, delpsi, chi1, p1, q REAL(r8) :: jwt, jwz, bth, bze, bsq_val, sigma, jdotb_val REAL(r8) :: g22, g23, g33, r_val REAL(r8), DIMENSION(0:mthsurf) :: K_t1, K_t2, K_t3 From 3f2d03fc44f633a77aac78c5b4e3b9ff463ce1aa Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Mon, 27 Jul 2026 14:06:00 +0900 Subject: [PATCH 158/305] MATCH - MINOR - Header comment reflects sol_num selection Co-Authored-By: Claude Opus 5 --- match/match.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/match/match.f b/match/match.f index 939f56a50..5654ee673 100644 --- a/match/match.f +++ b/match/match.f @@ -51,7 +51,7 @@ PROGRAM match_main CALL timer(0,out_unit) CALL ideal_read(filename) c----------------------------------------------------------------------- -c reconstruct most unstable ideal solution. +c reconstruct the ideal solution selected by sol_num. c----------------------------------------------------------------------- IF(ideal_flag)THEN CALL ideal_transform From ddadfa62f62b6afd6dd3f0eba73204232f1491ff Mon Sep 17 00:00:00 2001 From: Jake Halpern Date: Mon, 5 Oct 2026 15:48:26 -0400 Subject: [PATCH 159/305] DCON - BUGFIX - Put the vacuum matrix and norm in the plasma's Fourier frame VACUUM runs on a theta-reversed boundary, so wv comes back complex-conjugated relative to wp; conjugate it after mscvac (free_run and free_wvmats). The eigenvector norm used jmat(jpert-ipert), the transpose of the plasma-matrix index order; use jmat(ipert-jpert). Up-down symmetric cases are unchanged; asymmetric ones no longer depend on the theta origin. --- dcon/free.f | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dcon/free.f b/dcon/free.f index 2baf6a2c7..a6cb330c3 100644 --- a/dcon/free.f +++ b/dcon/free.f @@ -168,6 +168,8 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,nzero,op_netcdf_out) IF(wv_farwall_flag)THEN wv=temp ENDIF +c vacuum used the reversed theta grid: conjugate to dcon frame. + wv=CONJG(wv) singfac=mlow-nn*qlim+(/(ipert,ipert=0,mpert-1)/) DO ipert=1,mpert @@ -197,7 +199,7 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,nzero,op_netcdf_out) norm=0 DO ipert=1,mpert DO jpert=1,mpert - norm=norm+jmat(jpert-ipert) + norm=norm+jmat(ipert-jpert) $ *wt(ipert,isol)*CONJG(wt(jpert,isol)) ENDDO ENDDO @@ -729,7 +731,7 @@ SUBROUTINE free_test(plasma1,vacuum1,total1,psifac) norm=0 DO ipert=1,mpert DO jpert=1,mpert - norm=norm+jmat(jpert-ipert) + norm=norm+jmat(ipert-jpert) $ *wt(ipert,isol)*CONJG(wt(jpert,isol)) ENDDO ENDDO @@ -827,6 +829,7 @@ SUBROUTINE free_wvmats ALLOCATE(grri(2*(mthvac+5),mpert*2),xzpts(mthvac+5,4)) CALL mscvac(wv,mpert,mtheta,mthvac,complex_flag,kernelsignin, $ wall_flag,farwal_flag,grri,xzpts,ahgstr) + wv=CONJG(wv) singfac=mlow-nn*sq%f(4)+(/(ipert,ipert=0,mpert-1)/) DO ipert=1,mpert wv(ipert,:)=wv(ipert,:)*singfac From af8f08314e3549169e69d69f1c9b217be10ec6fa Mon Sep 17 00:00:00 2001 From: Jake Halpern Date: Mon, 5 Oct 2026 15:58:41 -0400 Subject: [PATCH 160/305] RDCON - BUGFIX - Put the vacuum matrix and norm in the plasma's Fourier frame Same change as DCON: conjugate wv after mscvac (free_run and free_get_wvac, used by the Galerkin solve) and transpose the norm. --- rdcon/free.f | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rdcon/free.f b/rdcon/free.f index 2a20399d0..3c84387d8 100644 --- a/rdcon/free.f +++ b/rdcon/free.f @@ -162,6 +162,8 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,nzero) !CALL system("rm -f ahg2msc.out") DEALLOCATE(grri,xzpts) IF(vac_memory)CALL unset_dcon_params() +c vacuum used the reversed theta grid: conjugate to dcon frame. + wv=CONJG(wv) singfac=mlow-nn*qlim+(/(ipert,ipert=0,mpert-1)/) DO ipert=1,mpert wv(ipert,:)=wv(ipert,:)*singfac @@ -190,7 +192,7 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,nzero) norm=0 DO ipert=1,mpert DO jpert=1,mpert - norm=norm+jmat(jpert-ipert) + norm=norm+jmat(ipert-jpert) $ *wt(ipert,isol)*CONJG(wt(jpert,isol)) ENDDO ENDDO @@ -692,6 +694,7 @@ SUBROUTINE free_get_wvac() ALLOCATE(grri(2*(mthvac+5),mpert*2),xzpts(mthvac+5,4)) CALL mscvac(wvac,mpert,mtheta,mthvac,complex_flag,kernelsignin, $ wall_flag,farwal_flag,grri,xzpts,ahgstr) + wvac=CONJG(wvac) DEALLOCATE(grri,xzpts) IF(vac_memory)CALL unset_dcon_params() From 34a73e5c4794a73b264a9a9cc7c389a73bcbb1a4 Mon Sep 17 00:00:00 2001 From: Jake Halpern Date: Mon, 5 Oct 2026 15:58:41 -0400 Subject: [PATCH 161/305] STRIDE - BUGFIX - Keep the complex vacuum matrix in the plasma's Fourier frame STRIDE called mscvac with complex_flag false, keeping only Re(wv), which is itself origin-dependent. Keep the full matrix, conjugate it as in DCON, and transpose the norm. --- stride/free.f | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stride/free.f b/stride/free.f index 8a205e9bd..6e4ddcab0 100755 --- a/stride/free.f +++ b/stride/free.f @@ -105,7 +105,7 @@ SUBROUTINE free_calc_wv() REAL(r8), DIMENSION(mpert) :: singfac INTEGER :: ipert, sTime, fTime, cr - LOGICAL, PARAMETER :: complex_flag=.FALSE. + LOGICAL, PARAMETER :: complex_flag=.TRUE. REAL(r8), DIMENSION(:,:), POINTER :: grri,xzpts CHARACTER(128) :: ahg_file @@ -130,6 +130,8 @@ SUBROUTINE free_calc_wv() ALLOCATE(grri(2*(mthvac+5),mpert*2),xzpts(mthvac+5,4)) CALL mscvac(wv,mpert,mtheta,mthvac,complex_flag,1.0_r8, $ .FALSE.,.FALSE.,grri,xzpts,ahg_file) +c vacuum used the reversed theta grid: conjugate to dcon frame. + wv=CONJG(wv) DEALLOCATE(grri,xzpts) IF(vac_memory)CALL unset_dcon_params() singfac=mlow-nn*qlim+(/(ipert,ipert=0,mpert-1)/) @@ -206,7 +208,7 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) norm=0 DO ipert=1,mpert DO jpert=1,mpert - norm=norm+jmat(jpert-ipert) + norm=norm+jmat(ipert-jpert) $ *wt(ipert,isol)*CONJG(wt(jpert,isol)) ENDDO ENDDO From 225d2d1fc1bf79ad913304f542e950a7a56ae50c Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:32:58 -0700 Subject: [PATCH 162/305] EQUIL - FEATURE - spline_fit_hermite: cubic spline fit that keeps caller-supplied knot derivatives Co-Authored-By: Claude Opus 5.5 --- equil/spline.f | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/equil/spline.f b/equil/spline.f index f7aca7896..4256cfc25 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -32,6 +32,7 @@ c 21. spline_thomas. c 22. spline_roots. c 23. spline_refine_root. +c 24. spline_fit_hermite. c----------------------------------------------------------------------- c subprogram 0. spline_type definition. c defines spline_type. @@ -1849,5 +1850,39 @@ SUBROUTINE spline_refine_root(spl, iqty, x, op_tol) c----------------------------------------------------------------------- RETURN END SUBROUTINE spline_refine_root +c----------------------------------------------------------------------- +c subprogram 24. spline_fit_hermite. +c fits cubic splines, keeping the caller's knot derivatives fs1 +c for the quantities flagged in keep (cubic Hermite interpolation). +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE spline_fit_hermite(spl,endmode,keep) + + TYPE(spline_type), INTENT(INOUT) :: spl + CHARACTER(*), INTENT(IN) :: endmode + LOGICAL, DIMENSION(spl%nqty), INTENT(IN) :: keep + + INTEGER :: iqty + REAL(r8), DIMENSION(0:spl%mx,spl%nqty) :: fs1 +c----------------------------------------------------------------------- +c fit all quantities, then restore the supplied derivatives. +c----------------------------------------------------------------------- + DO iqty=1,spl%nqty + IF(keep(iqty) .AND. ANY(spl%xpower(:,iqty) /= 0)) + $ CALL program_stop("spline_fit_hermite: "// + $ "supplied derivatives incompatible with xpower") + ENDDO + fs1=spl%fs1 + CALL spline_fit(spl,endmode) + DO iqty=1,spl%nqty + IF(keep(iqty))spl%fs1(:,iqty)=fs1(:,iqty) + ENDDO +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE spline_fit_hermite END MODULE spline_mod From a3e12fbd2b8edf180dc452e84bab5009ba0c5c93 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:38:39 -0700 Subject: [PATCH 163/305] EQUIL - FEATURE - profile_source: build f and p from the tabulated FF' and p' (efit, ldp_i) New equil.in variable profile_source: values - tabulated f, p; spline-fitted derivatives (default, unchanged) hermite - tabulated f, p with the tabulated derivatives as knot slopes integrate - f**2/2 and p integrated inward from the boundary values (as in Julia GPEC PR #506), tabulated derivatives as slopes read_eq_efit no longer discards FFPRIM and PPRIME, and fixes an out-of-bounds index when the g-file has no q profile. read_eq_ldp_i reads optional trailing FF' and p' records. Falls back to values with a warning when the derivatives are non-finite or disagree in sign. Co-Authored-By: Claude Opus 5.5 --- equil/equil.f | 3 +- equil/global.f | 2 + equil/read_eq.f | 141 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 137 insertions(+), 9 deletions(-) diff --git a/equil/equil.f b/equil/equil.f index be7456d46..fbc12fe8f 100644 --- a/equil/equil.f +++ b/equil/equil.f @@ -39,7 +39,7 @@ SUBROUTINE equil_loadnamelists(op_psihigh, op_psilow) $ newq0,psihigh,psilow,input_only,jac_type,power_bp,power_r, $ power_b,jac_method,convert_type,power_flag,ns1, $ sp_pfac,sp_nx,sp_dx1,sp_dx2,use_galgrid,etol,enstep, - $ use_classic_splines + $ use_classic_splines,profile_source NAMELIST/equil_output/bin_2d,bin_eq_1d,bin_eq_2d,out_2d,out_eq_1d, $ out_eq_2d,bin_fl,out_fl,interp,gse_flag,dump_flag,verbose, $ out_ahg2msc @@ -150,6 +150,7 @@ SUBROUTINE equil_read(unit, op_psihigh, op_psilow) c----------------------------------------------------------------------- c read equilibrium data and diagnose. c----------------------------------------------------------------------- + sq_in_slopes=.FALSE. SELECT CASE(TRIM(eq_type)) CASE("fluxgrid") CALL read_eq_fluxgrid diff --git a/equil/global.f b/equil/global.f index b57137f02..53594f553 100644 --- a/equil/global.f +++ b/equil/global.f @@ -16,6 +16,8 @@ MODULE global_mod CHARACTER(16) :: eq_type="fluxgrid" CHARACTER(128) :: eq_filename="fluxgrid.dat" + CHARACTER(16) :: profile_source="values" + LOGICAL, DIMENSION(4) :: sq_in_slopes=.FALSE. LOGICAL :: direct_flag=.FALSE. LOGICAL :: out_eq_1d=.FALSE.,bin_eq_1d=.FALSE. diff --git a/equil/read_eq.f b/equil/read_eq.f index 3ad187791..ad14747bc 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -37,6 +37,8 @@ c 29. read_eq_chease4. c 30. read_eq_marklin_direct. c 31. read_eq_marklin_inverse. +c 32. read_eq_profiles_from_derivatives. +c 33. read_eq_profiles_warn. c----------------------------------------------------------------------- c subprogram 0. read_eq_mod. c module declarations. @@ -625,6 +627,7 @@ SUBROUTINE read_eq_efit REAL(r8) :: bcentr,cpasma,rgrid,rmaxis,rzero,ssibry1, $ ssibry2,ssimag1,ssimag2,xdim,xdum,zdim,zmaxis,zmid REAL(r8) :: tmp + REAL(r8), DIMENSION(:), ALLOCATABLE :: ffprim,pprime CHARACTER(LEN=7) :: shotstr, timestr c----------------------------------------------------------------------- c read equilibrium data. @@ -636,14 +639,15 @@ SUBROUTINE read_eq_efit READ(in_unit,'(5e16.9)')cpasma,ssimag2,xdum,rmaxis,xdum READ(in_unit,'(5e16.9)')zmaxis,xdum,ssibry2,xdum,xdum CALL spline_alloc(sq_in,nw-1,4) + ALLOCATE(ffprim(0:nw-1),pprime(0:nw-1)) READ(in_unit,'(5e16.9)')(sq_in%fs(i,1),i=0,nw-1) READ(in_unit,'(5e16.9)')(sq_in%fs(i,2),i=0,nw-1) - READ(in_unit,'(5e16.9)')(sq_in%fs(i,3),i=0,nw-1) - READ(in_unit,'(5e16.9)')(sq_in%fs(i,3),i=0,nw-1) + READ(in_unit,'(5e16.9)')(ffprim(i),i=0,nw-1) + READ(in_unit,'(5e16.9)')(pprime(i),i=0,nw-1) CALL bicube_alloc(psi_in,nw-1,nh-1,1) READ(in_unit,'(5e16.9)')((psi_in%fs(i,j,1),i=0,nw-1),j=0,nh-1) READ(in_unit,'(5e16.9)',iostat=ios)(sq_in%fs(i,3),i=0,nw-1) - IF(ios /= 0)sq_in%fs(i,3)=0 + IF(ios /= 0)sq_in%fs(:,3)=0 CALL ascii_close(in_unit) c----------------------------------------------------------------------- c translate to internal quantities. @@ -667,6 +671,9 @@ SUBROUTINE read_eq_efit sq_in%xs=(/(ia,ia=0,ma)/)/dfloat(ma) sq_in%fs(:,1)=ABS(sq_in%fs(:,1)) sq_in%fs(:,2)=MAX(sq_in%fs(:,2)*mu0,zero) + CALL read_eq_profiles_from_derivatives(ffprim*psio, + $ pprime*mu0*psio) + DEALLOCATE(ffprim,pprime) c----------------------------------------------------------------------- c copy and convert 2D quantities. c----------------------------------------------------------------------- @@ -808,8 +815,8 @@ END SUBROUTINE read_eq_ldp_d c----------------------------------------------------------------------- SUBROUTINE read_eq_ldp_i - INTEGER :: mx,my - REAL(r8), DIMENSION(:), POINTER :: psi,f,p,q + INTEGER :: mx,my,ios1,ios2 + REAL(r8), DIMENSION(:), POINTER :: psi,f,p,q,ffp,pp REAL(r8), DIMENSION(:,:), POINTER :: r,z c----------------------------------------------------------------------- c open data file, read scalar data. @@ -823,9 +830,9 @@ SUBROUTINE read_eq_ldp_i my=my-1 mx=mx-1 ALLOCATE(psi(0:mx),f(0:mx),p(0:mx),q(0:mx), - $ r(0:my,0:mx),z(0:my,0:mx)) + $ r(0:my,0:mx),z(0:my,0:mx),ffp(0:mx),pp(0:mx)) c----------------------------------------------------------------------- -c read binary data. +c read binary data; f*df/dpsi and dp/dpsi records are optional. c----------------------------------------------------------------------- READ(in_unit)psi READ(in_unit)f @@ -833,6 +840,8 @@ SUBROUTINE read_eq_ldp_i READ(in_unit)q READ(in_unit)r READ(in_unit)z + READ(in_unit,IOSTAT=ios1)ffp + READ(in_unit,IOSTAT=ios2)pp CALL bin_close(in_unit) c----------------------------------------------------------------------- c copy and revise 1D arrays. @@ -843,6 +852,8 @@ SUBROUTINE read_eq_ldp_i sq_in%fs(:,1)=f sq_in%fs(:,2)=p*mu0 sq_in%fs(:,3)=q + IF(ios1 == 0 .AND. ios2 == 0) + $ CALL read_eq_profiles_from_derivatives(ffp*psio,pp*mu0*psio) if(psio.lt.0)psio=-psio c----------------------------------------------------------------------- c copy and revise 2D arrays. @@ -855,7 +866,7 @@ SUBROUTINE read_eq_ldp_i c----------------------------------------------------------------------- c process inverse equilibrium and deallocate local arrays. c----------------------------------------------------------------------- - DEALLOCATE(psi,f,p,q,r,z) + DEALLOCATE(psi,f,p,q,r,z,ffp,pp) CALL inverse_run c----------------------------------------------------------------------- c terminate. @@ -2378,4 +2389,118 @@ SUBROUTINE read_eq_marklin_inverse c----------------------------------------------------------------------- RETURN END SUBROUTINE read_eq_marklin_inverse +c----------------------------------------------------------------------- +c subprogram 32. read_eq_profiles_from_derivatives. +c uses tabulated derivatives ffp = f*df/dpsifac and +c pp = d(mu0 p)/dpsifac for the f and mu0 p columns of sq_in, +c according to profile_source: +c "values": tabulated values, spline-fitted derivatives. +c "hermite": tabulated values and tabulated derivatives. +c "integrate": values integrated inward from the boundary +c values of f**2/2 and mu0 p, and tabulated derivatives. +c falls back to "values" if the derivatives are unusable. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) + + REAL(r8), DIMENSION(0:), INTENT(IN) :: ffp,pp + + INTEGER :: mx + REAL(r8) :: sgn_f,sgn_p,dfmax,dpmax + REAL(r8), DIMENSION(0:SIZE(ffp)-1) :: f,p,f2,fint,pint + TYPE(spline_type) :: spl +c----------------------------------------------------------------------- +c select method. +c----------------------------------------------------------------------- + SELECT CASE(TRIM(profile_source)) + CASE("values") + RETURN + CASE("hermite","integrate") + CASE DEFAULT + CALL program_stop("Cannot recognize profile_source = " + $ //TRIM(profile_source)) + END SELECT + mx=sq_in%mx + f=sq_in%fs(:,1) + p=sq_in%fs(:,2) +c----------------------------------------------------------------------- +c check derivatives are finite and agree in sign with the values. +c----------------------------------------------------------------------- + IF(.NOT.(ALL(ABS(ffp) <= HUGE(one)) .AND. + $ ALL(ABS(pp) <= HUGE(one)) .AND. ALL(f /= 0)))THEN + CALL read_eq_profiles_warn("non-finite derivatives or f = 0") + RETURN + ENDIF + sgn_f=SIGN(one,SUM((f(1:mx)-f(0:mx-1)) + $ *(ffp(1:mx)/f(1:mx)+ffp(0:mx-1)/f(0:mx-1)))) + sgn_p=SIGN(one,SUM((p(1:mx)-p(0:mx-1))*(pp(1:mx)+pp(0:mx-1)))) + IF(sgn_f /= sgn_p)THEN + CALL read_eq_profiles_warn("derivative signs disagree with" + $ //" the values for only one of f and p") + RETURN + ENDIF +c----------------------------------------------------------------------- +c integrate derivatives inward from the boundary. +c----------------------------------------------------------------------- + CALL spline_alloc(spl,mx,2) + spl%xs=sq_in%xs + spl%fs(:,1)=sgn_f*ffp + spl%fs(:,2)=sgn_f*pp + CALL spline_fit(spl,"extrap") + CALL spline_int(spl) + f2=f(mx)**2-2*(spl%fsi(mx,1)-spl%fsi(:,1)) + pint=p(mx)-(spl%fsi(mx,2)-spl%fsi(:,2)) + CALL spline_dealloc(spl) + IF(ANY(f2 <= 0))THEN + CALL read_eq_profiles_warn("integrated f**2 <= 0") + RETURN + ENDIF + fint=SIGN(SQRT(f2),f(mx)) +c----------------------------------------------------------------------- +c report consistency of tabulated values and derivatives. +c----------------------------------------------------------------------- + dfmax=MAXVAL(ABS(fint-f))/MAXVAL(ABS(f)) + dpmax=MAXVAL(ABS(pint-p))/MAX(MAXVAL(ABS(p)),TINY(one)) + IF(verbose)WRITE(*,'(1x,2a,1p,2(a,es9.2))')"profile_source = ", + $ TRIM(profile_source),", max |df|/|f| =",dfmax, + $ ", max |dp|/|p| =",dpmax + IF(dfmax > 1e-4 .OR. dpmax > 5e-2)WRITE(*,'(1x,a)') + $ "WARNING: tabulated values and derivatives of f, p disagree" +c----------------------------------------------------------------------- +c store values and derivatives. +c----------------------------------------------------------------------- + IF(profile_source == "integrate")THEN + sq_in%fs(:,1)=fint + sq_in%fs(:,2)=pint + ENDIF + sq_in%fs1(:,1)=sgn_f*ffp/sq_in%fs(:,1) + sq_in%fs1(:,2)=sgn_f*pp + sq_in_slopes(1:2)=.TRUE. +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE read_eq_profiles_from_derivatives +c----------------------------------------------------------------------- +c subprogram 33. read_eq_profiles_warn. +c warns that profile derivatives are not used. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE read_eq_profiles_warn(reason) + + CHARACTER(*), INTENT(IN) :: reason +c----------------------------------------------------------------------- +c write warning. +c----------------------------------------------------------------------- + WRITE(*,'(1x,3a)')"WARNING: profile_source = values used: ", + $ reason,"." +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE read_eq_profiles_warn END MODULE read_eq_mod From 49a09a9e04c859e82ddb7f6912ddda3e79317858 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:38:40 -0700 Subject: [PATCH 164/305] EQUIL - FEATURE - keep supplied f, p derivatives through direct and inverse sq construction sq_in and sq are fit with spline_fit_hermite so the profile slopes from profile_source are not re-derived by spline differentiation. newq0 rescaling updates the f slope consistently (f' -> f'/ffac). With profile_source = values, output is bit-for-bit unchanged. Co-Authored-By: Claude Opus 5.5 --- equil/direct.f | 9 ++++++--- equil/inverse.f | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/equil/direct.f b/equil/direct.f index b58e3de29..2a0377a6c 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -74,7 +74,7 @@ SUBROUTINE direct_run sq_in%fs(:,4)=SQRT(sq_in%xs) sq_in%name=" sq " sq_in%title=(/"psifac"," f ","mu0 p "," q "," rho "/) - CALL spline_fit(sq_in,"extrap") + CALL spline_fit_hermite(sq_in,"extrap",sq_in_slopes) psi_in%xs=rmin+(/(ir,ir=0,psi_in%mx)/)*(rmax-rmin)/psi_in%mx psi_in%ys=zmin+(/(iz,iz=0,psi_in%my)/)*(zmax-zmin)/psi_in%my CALL bicube_fit(psi_in,"extrap","extrap") @@ -179,6 +179,8 @@ SUBROUTINE direct_run c----------------------------------------------------------------------- sq%fs(ipsi,1)=bf%f*twopi sq%fs(ipsi,2)=bf%p + sq%fs1(ipsi,1)=bf%f1*twopi + sq%fs1(ipsi,2)=bf%p1 sq%fs(ipsi,3)=y_out(istep,1)*twopi*psio sq%fs(ipsi,4)=y_out(istep,3)*bf%f/twopi CALL spline_dealloc(ff) @@ -191,7 +193,7 @@ SUBROUTINE direct_run c----------------------------------------------------------------------- c fit surface quantities to cubic splines. c----------------------------------------------------------------------- - CALL spline_fit(sq,"extrap") + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) sq%name=" sq " sq%title=(/" psi "," f "," p "," q "/) q0=sq%fs(0,4)-sq%fs1(0,4)*sq%xs(0) @@ -205,10 +207,11 @@ SUBROUTINE direct_run DO ipsi=0,mpsi ffac=SQRT(1+f0fac/sq%fs(ipsi,1)**2) sq%fs(ipsi,1)=sq%fs(ipsi,1)*ffac + sq%fs1(ipsi,1)=sq%fs1(ipsi,1)/ffac sq%fs(ipsi,4)=sq%fs(ipsi,4)*ffac rzphi%fs(ipsi,:,3)=rzphi%fs(ipsi,:,3)*ffac ENDDO - CALL spline_fit(sq,"extrap") + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) ENDIF qa=sq%fs(mpsi,4)+sq%fs1(mpsi,4)*(1-sq%xs(mpsi)) c----------------------------------------------------------------------- diff --git a/equil/inverse.f b/equil/inverse.f index 922b027e6..06eceab6f 100644 --- a/equil/inverse.f +++ b/equil/inverse.f @@ -54,7 +54,7 @@ SUBROUTINE inverse_run sq_in%fs(:,4)=SQRT(sq_in%xs) sq_in%name=" sq " sq_in%title=(/"psifac"," f ","mu0 p "," q "," rho "/) - CALL spline_fit(sq_in,"extrap") + CALL spline_fit_hermite(sq_in,"extrap",sq_in_slopes) rz_in%xs=sq_in%xs rz_in%ys=(/(itheta,itheta=0,rz_in%my)/)/REAL(rz_in%my,r8) rz_in%name=" rz " @@ -230,12 +230,15 @@ SUBROUTINE inverse_run c----------------------------------------------------------------------- c compute surface quantities. c----------------------------------------------------------------------- + CALL spline_eval(sq_in,psifac,1) sq%fs(ipsi,1)=sq_in%f(1)*twopi sq%fs(ipsi,2)=sq_in%f(2) + sq%fs1(ipsi,1)=sq_in%f1(1)*twopi + sq%fs1(ipsi,2)=sq_in%f1(2) sq%fs(ipsi,3)=spl%fsi(mtheta,3)*twopi*pi sq%fs(ipsi,4)=spl%fsi(mtheta,4)*sq%fs(ipsi,1)/(2*twopi*psio) ENDDO - CALL spline_fit(sq,"extrap") + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) q0=sq%fs(0,4)-sq%fs1(0,4)*sq%xs(0) c----------------------------------------------------------------------- c revise q profile. @@ -247,10 +250,11 @@ SUBROUTINE inverse_run DO ipsi=0,mpsi ffac=SQRT(1+f0fac/sq%fs(ipsi,1)**2) sq%fs(ipsi,1)=sq%fs(ipsi,1)*ffac + sq%fs1(ipsi,1)=sq%fs1(ipsi,1)/ffac sq%fs(ipsi,4)=sq%fs(ipsi,4)*ffac rzphi%fs(ipsi,:,3)=rzphi%fs(ipsi,:,3)*ffac ENDDO - CALL spline_fit(sq,"extrap") + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) ENDIF qa=sq%fs(mpsi,4)+sq%fs1(mpsi,4)*(one-sq%xs(mpsi)) c----------------------------------------------------------------------- From 5a8f0b9cda74b18fe6c7040e8cc155729d50a548 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:52:10 -0700 Subject: [PATCH 165/305] EQUIL - FIX - sq_out diagnostic keeps the supplied f, p derivatives Co-Authored-By: Claude Opus 5.5 --- equil/equil_out.f | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/equil/equil_out.f b/equil/equil_out.f index 1069179be..43c0e8f6c 100644 --- a/equil/equil_out.f +++ b/equil/equil_out.f @@ -97,7 +97,10 @@ SUBROUTINE equil_out_diagnose(sq_flag,unit) sq_out%fs(:,2)=sq%fs(:,2) sq_out%fs(:,3)=sq%fs(:,4) sq_out%fs(:,4)=SQRT(sq_out%xs) - CALL spline_fit(sq_out,"extrap") + sq_out%fs1(:,1)=sq%fs1(:,1)/twopi + sq_out%fs1(:,2)=sq%fs1(:,2) + CALL spline_fit_hermite(sq_out,"extrap", + $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) sq_out%name=" sq " sq_out%title=(/"psifac"," f ","mu0 p "," q "," rho "/) IF(bin_eq_1d)CALL bin_open(bin_2d_unit,"sq_out.bin","UNKNOWN", From 9846ddb6acbdc65059dbeb9a57a5ac521d5c9d5c Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:52:10 -0700 Subject: [PATCH 166/305] EQUIL - FEATURE - profile_source defaults to integrate Against a TokaMaker equilibrium with known profiles, integrate gives F'' errors of 0.1-0.8% (0.1 < psi_n < 0.85) that fall with resolution, where values and hermite give 2-47% that grow with mpsi (single-precision f amplified by differentiation). STRIDE Delta' is correspondingly more consistent across g-file resolutions. Set profile_source="values" to recover the previous behaviour. Co-Authored-By: Claude Opus 5.5 --- equil/global.f | 2 +- input/equil.in | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/equil/global.f b/equil/global.f index 53594f553..45ee91050 100644 --- a/equil/global.f +++ b/equil/global.f @@ -16,7 +16,7 @@ MODULE global_mod CHARACTER(16) :: eq_type="fluxgrid" CHARACTER(128) :: eq_filename="fluxgrid.dat" - CHARACTER(16) :: profile_source="values" + CHARACTER(16) :: profile_source="integrate" LOGICAL, DIMENSION(4) :: sq_in_slopes=.FALSE. LOGICAL :: direct_flag=.FALSE. diff --git a/input/equil.in b/input/equil.in index 98cbb3a91..01685bf07 100644 --- a/input/equil.in +++ b/input/equil.in @@ -15,6 +15,7 @@ newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions + profile_source="integrate" ! How f and p are built when the file has FF' and p' (efit, ldp_i). integrate: integrate FF', p' inward from the boundary values and use them as spline derivatives (recommended). hermite: tabulated f, p with FF', p' as derivatives. values: tabulated f, p only (previous behaviour) input_only=f ! Generate information about the input and then quit with no further calculation use_galgrid=t ! Use the same grid as galerkin method when regrid=.True. From 414877b28a63967b25375e11cf32275866809e87 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 12:53:40 -0700 Subject: [PATCH 167/305] TESTS - NEW - spline unit tests: Hermite with supplied slopes, extrap end-slope order Co-Authored-By: Claude Opus 5.5 --- regression/spline_tests/makefile | 13 +++ .../spline_tests/test_spline_hermite.f90 | 86 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 regression/spline_tests/makefile create mode 100644 regression/spline_tests/test_spline_hermite.f90 diff --git a/regression/spline_tests/makefile b/regression/spline_tests/makefile new file mode 100644 index 000000000..2582f4ace --- /dev/null +++ b/regression/spline_tests/makefile @@ -0,0 +1,13 @@ +# Unit tests for equil/spline.f. Build GPEC first (install/make), then: make run +FC ?= gfortran +GPEC = ../.. +LIBS ?= -L$(LAPACKHOME)/lib -llapack -lblas + +test_spline_hermite: test_spline_hermite.f90 $(GPEC)/deps/lib/libequil.a + $(FC) $(FFLAGS) -I$(GPEC)/equil -o $@ $< $(GPEC)/deps/lib/libequil.a $(LIBS) + +run: test_spline_hermite + ./test_spline_hermite + +clean: + rm -f test_spline_hermite *.o *.mod diff --git a/regression/spline_tests/test_spline_hermite.f90 b/regression/spline_tests/test_spline_hermite.f90 new file mode 100644 index 000000000..3f2f23ffc --- /dev/null +++ b/regression/spline_tests/test_spline_hermite.f90 @@ -0,0 +1,86 @@ +!----------------------------------------------------------------------- +! Unit tests for spline_fit_hermite and the "extrap" end slopes. +! 1. cubic Hermite with exact knot slopes converges at 4th order. +! 2. "extrap" end slopes converge at 3rd order or better. +! 3. columns not kept are bit-for-bit those of spline_fit. +! Exits with status 1 on failure. +!----------------------------------------------------------------------- +PROGRAM test_spline_hermite + USE spline_mod + IMPLICIT NONE + + INTEGER, PARAMETER :: nres=4, mfine=2001 + INTEGER, DIMENSION(nres), PARAMETER :: mxs=(/16,32,64,128/) + INTEGER :: ires,ix,mx + REAL(r8), DIMENSION(nres) :: err_val,err_end + REAL(r8) :: x,order_val,order_end + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: fs1_ref + TYPE(spline_type) :: spl + LOGICAL :: ok=.TRUE. + + DO ires=1,nres + mx=mxs(ires) + CALL spline_alloc(spl,mx,2) + spl%xs=(/(ix,ix=0,mx)/)/REAL(mx,r8) + spl%fs(:,1)=f(spl%xs) + spl%fs(:,2)=f(spl%xs) + spl%fs1(:,1)=df(spl%xs) + CALL spline_fit_hermite(spl,"extrap",(/.TRUE.,.FALSE./)) + err_val(ires)=0 + DO ix=0,mfine + x=ix/REAL(mfine,r8) + CALL spline_eval(spl,x,0) + err_val(ires)=MAX(err_val(ires),ABS(spl%f(1)-f1(x))) + ENDDO + err_end(ires)=MAX(ABS(spl%fs1(0,2)-df1(0._r8)),ABS(spl%fs1(mx,2)-df1(1._r8))) + ALLOCATE(fs1_ref(0:mx,2)) + fs1_ref=spl%fs1 + spl%fs(:,2)=f(spl%xs) + CALL spline_fit(spl,"extrap") + IF(ANY(spl%fs1(:,2) /= fs1_ref(:,2)))THEN + WRITE(*,*)"FAIL: column not kept differs from spline_fit, mx = ",mx + ok=.FALSE. + ENDIF + DEALLOCATE(fs1_ref) + CALL spline_dealloc(spl) + ENDDO + order_val=LOG(err_val(nres-1)/err_val(nres))/LOG(2._r8) + order_end=LOG(err_end(nres-1)/err_end(nres))/LOG(2._r8) + WRITE(*,'(a,4es10.2,a,f5.2)')" hermite max error:",err_val,", order",order_val + WRITE(*,'(a,4es10.2,a,f5.2)')" extrap end slope error:",err_end,", order",order_end + IF(order_val < 3.8_r8)THEN + WRITE(*,*)"FAIL: Hermite with exact slopes is not 4th order" + ok=.FALSE. + ENDIF + IF(order_end < 2.8_r8)THEN + WRITE(*,*)"FAIL: extrap end slopes are not 3rd order" + ok=.FALSE. + ENDIF + IF(.NOT.ok)STOP 1 + WRITE(*,*)"PASS" + +CONTAINS + + ELEMENTAL REAL(r8) FUNCTION f1(x) + REAL(r8), INTENT(IN) :: x + f1=SIN(3*x)+EXP(-x)*x**2 + END FUNCTION f1 + + ELEMENTAL REAL(r8) FUNCTION df1(x) + REAL(r8), INTENT(IN) :: x + df1=3*COS(3*x)+EXP(-x)*(2*x-x**2) + END FUNCTION df1 + + FUNCTION f(x) + REAL(r8), DIMENSION(:), INTENT(IN) :: x + REAL(r8), DIMENSION(SIZE(x)) :: f + f=f1(x) + END FUNCTION f + + FUNCTION df(x) + REAL(r8), DIMENSION(:), INTENT(IN) :: x + REAL(r8), DIMENSION(SIZE(x)) :: df + df=df1(x) + END FUNCTION df + +END PROGRAM test_spline_hermite From 822e8b598f658ac93b2e60d0e9bb89b4d54a4504 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 13:40:47 -0700 Subject: [PATCH 168/305] EQUIL - BUGFIX - Keep the 5-entry sq title in direct_run A second assignment of 4 titles reallocated sq%title, so the dump record was 6 bytes short and read_eq_dump failed (I/O past end of record). Co-Authored-By: Claude Opus 5.5 --- equil/direct.f | 1 - 1 file changed, 1 deletion(-) diff --git a/equil/direct.f b/equil/direct.f index 2a0377a6c..675b8f534 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -195,7 +195,6 @@ SUBROUTINE direct_run c----------------------------------------------------------------------- CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) sq%name=" sq " - sq%title=(/" psi "," f "," p "," q "/) q0=sq%fs(0,4)-sq%fs1(0,4)*sq%xs(0) c----------------------------------------------------------------------- c revise q profile. From 8717d83240805cf4b6609680c8d359975744d6d6 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 13:40:48 -0700 Subject: [PATCH 169/305] EQUIL - FIX - eq_type dump keeps the f, p slopes and eqfun equil_out_dump appends the supplied-derivative flags with sq%fs1(:,1:2), then eqfun; read_eq_dump reads them when present (older dumps read as before) and no longer fails when the equilibrium is re-read (STRIDE psilim reform). DCON efit -> dump -> dcon reproduces f, mu0p, q, D_I, D_R exactly. Co-Authored-By: Claude Opus 5.5 --- equil/equil_out.f | 5 +++++ equil/read_eq.f | 28 +++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/equil/equil_out.f b/equil/equil_out.f index 43c0e8f6c..18e6c9d22 100644 --- a/equil/equil_out.f +++ b/equil/equil_out.f @@ -848,6 +848,11 @@ SUBROUTINE equil_out_dump WRITE(dump_unit)rzphi%name WRITE(dump_unit)rzphi%periodic c----------------------------------------------------------------------- +c write supplied f, p derivatives and eqfun (read only if present). +c----------------------------------------------------------------------- + WRITE(dump_unit)sq_in_slopes(1:2),sq%fs1(:,1:2) + WRITE(dump_unit)eqfun%fs +c----------------------------------------------------------------------- c close dump file. c----------------------------------------------------------------------- CALL bin_close(dump_unit) diff --git a/equil/read_eq.f b/equil/read_eq.f index ad14747bc..3b2f1887a 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -1436,7 +1436,8 @@ END SUBROUTINE read_eq_rtaylor c----------------------------------------------------------------------- SUBROUTINE read_eq_dump - INTEGER :: nqty + INTEGER :: nqty,ios + LOGICAL, DIMENSION(2) :: slopes LOGICAL, PARAMETER :: diagnose=.TRUE. INTEGER :: ix,iy,iqty,iside,ipsi @@ -1468,14 +1469,15 @@ SUBROUTINE read_eq_dump c write 1D splines. c----------------------------------------------------------------------- READ(dump_unit)mpsi,nqty + IF(sq%allocated)CALL spline_dealloc(sq) CALL spline_alloc(sq,mpsi,nqty) READ(dump_unit)sq%xs,sq%fs, $ sq%xpower,sq%x0,sq%title,sq%name,sq%periodic - CALL spline_fit(sq,"extrap") c----------------------------------------------------------------------- c read 2D splines. c----------------------------------------------------------------------- READ(dump_unit)mpsi,mtheta,nqty + IF(rzphi%allocated)CALL bicube_dealloc(rzphi) CALL bicube_alloc(rzphi,mpsi,mtheta,nqty) READ(dump_unit)rzphi%xs READ(dump_unit)rzphi%ys @@ -1490,6 +1492,24 @@ SUBROUTINE read_eq_dump READ(dump_unit)rzphi%name READ(dump_unit)rzphi%periodic c----------------------------------------------------------------------- +c read supplied f, p derivatives and eqfun if present; fit sq. +c----------------------------------------------------------------------- + READ(dump_unit,IOSTAT=ios)slopes,sq%fs1(:,1:2) + IF(ios == 0)sq_in_slopes(1:2)=slopes + CALL spline_fit_hermite(sq,"extrap", + $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) + IF(eqfun%allocated)CALL bicube_dealloc(eqfun) + CALL bicube_alloc(eqfun,mpsi,mtheta,3) + READ(dump_unit,IOSTAT=ios)eqfun%fs + IF(ios == 0)THEN + eqfun%xs=sq%xs + eqfun%ys=rzphi%ys + eqfun%title=(/" b0 "," "," " /) + CALL bicube_fit(eqfun,"extrap","periodic") + ELSE + CALL bicube_dealloc(eqfun) + ENDIF +c----------------------------------------------------------------------- c restore x powers and fit. c----------------------------------------------------------------------- ALLOCATE(xfac(0:rzphi%mx)) @@ -1516,10 +1536,12 @@ SUBROUTINE read_eq_dump DO ipsi=0,mpsi ffac=SQRT(1+f0fac/sq%fs(ipsi,1)**2) sq%fs(ipsi,1)=sq%fs(ipsi,1)*ffac + sq%fs1(ipsi,1)=sq%fs1(ipsi,1)/ffac sq%fs(ipsi,4)=sq%fs(ipsi,4)*ffac rzphi%fs(ipsi,:,3)=rzphi%fs(ipsi,:,3)*ffac ENDDO - CALL spline_fit(sq,"extrap") + CALL spline_fit_hermite(sq,"extrap", + $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) ENDIF qa=sq%fs(mpsi,4)+sq%fs1(mpsi,4)*(one-sq%xs(mpsi)) c----------------------------------------------------------------------- From 8ec2df3b174c0d8a9fbb2051a8e0854761959b14 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:18:49 -0400 Subject: [PATCH 170/305] SLAYER - BUGFIX - Pass a dummy Jacobian subroutine to LSODE in the non-stiff Riccati calls riccati() and directred() passed a REAL placeholder in LSODE's JAC position while riccati_del_s() and riccati_f() pass a subroutine. gfortran 15 rejects the mixed use within one file ("Invalid procedure argument"), so delta.f did not compile. All calls now pass a subroutine; mf=10 never calls it, so results are unchanged. Co-Authored-By: Claude Opus 5.5 --- slayer/delta.f | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index a05ec4f6e..67a2947cf 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -69,7 +69,6 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, REAL(r8) :: x,xout ! current and target x REAL(r8) :: xmin ! inner integration bound REAL(r8) :: rtol ! relative tolerance - REAL(r8) :: jac ! dummy Jacobian (unused for mf=10) c --- work arrays COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy ! W and dW INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work @@ -135,7 +134,7 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, DO WHILE (x>xout) istep=istep+1 CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) WRITE(out2_unit,'(1x,3(es17.8e3))') x,REAL(y),AIMAG(y) ENDDO @@ -146,7 +145,7 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, istep = 1 itask = 1 CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) ENDIF c --- extract Delta from final W derivative at x_min @@ -192,7 +191,6 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) REAL(r8) :: xout ! target integration endpoint REAL(r8) :: xmin ! inner integration bound REAL(r8) :: rtol ! relative tolerance - REAL(r8) :: jac ! dummy (overridden by jac_del_s) REAL(r8) :: my_q ! integration variable (large -> small) REAL(r8) :: P_hat ! normalized P_perp REAL(r8) :: alpha ! boundary condition coefficient @@ -274,6 +272,18 @@ FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) END FUNCTION riccati_del_s c----------------------------------------------------------------------- +c jac_dummy: placeholder Jacobian for lsode calls whose method flag +c does not use a user-supplied Jacobian (mf=10, mf=22). lsode never +c calls it; it exists so that every lsode call in this module passes +c a subroutine in the JAC position. +c----------------------------------------------------------------------- + SUBROUTINE jac_dummy(neq, x, y, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: x + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + END SUBROUTINE jac_dummy +c----------------------------------------------------------------------- c jacobian for riccati_del_s(): pd = dF/dW for stiff lsode. c----------------------------------------------------------------------- SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) @@ -335,7 +345,6 @@ FUNCTION riccati_f() REAL(r8) :: xout ! target integration endpoint REAL(r8) :: xmin ! inner integration bound REAL(r8) :: rtol ! relative tolerance - REAL(r8) :: jac ! dummy (overridden by jac_f) REAL(r8) :: my_p ! integration variable p (large -> small) REAL(r8) :: bk ! asymptotic coefficient b_k c --- boundary-condition intermediates @@ -1002,7 +1011,7 @@ FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - REAL(r8) :: xintv,x,xout,rtol,jac,xmax + REAL(r8) :: xintv,x,xout,rtol,xmax COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy INTEGER, DIMENSION(:), ALLOCATABLE :: iwork @@ -1042,7 +1051,7 @@ FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) DO istep=istep+1 CALL lsode(phi_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) xout=xout+xintv IF (xmax-xout Date: Fri, 9 Oct 2026 19:24:36 -0400 Subject: [PATCH 171/305] SLAYER - NEW FEATURE - Extract dispersion roots from the AMR scan The matched growth-rate path wrote only the AMR scan cloud; gamma_sol_arr was a placeholder zero left for an external root finder. extract_roots now locates the zeros and poles of the dispersion function from the final AMR cells: the phase of D winds by +2*pi around a cell holding a zero and by -2*pi around a pole. Each zero is refined by a secant iteration confined to the neighbourhood of its cell. The most unstable root is reported per surface as gamma_sol (gamma in 1/s, omega in rad/s), and all roots and poles of each scan are written to the NetCDF file (Q_roots, Q_poles, gamma_roots, omega_roots, n_roots, n_poles, root_polished). Surfaces with no root get NaN rather than zero. No physical filtering of roots is applied. Co-Authored-By: Claude Opus 5.5 --- slayer/growthrates.f | 217 +++++++++++++++++++++++++++++++++++++++-- slayer/sglobal.f | 18 ++++ slayer/slayer.f | 30 +++++- slayer/slayer_netcdf.f | 99 +++++++++++++++++++ 4 files changed, 351 insertions(+), 13 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 502fffae7..0704b1bd6 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -20,6 +20,8 @@ MODULE growthrates_mod c 9. dispersion_AMR_v2 - AMR scan (cell-based storage) c 10. check_cell_crossing_sub - zero-crossing test c 11. subdivide_cell_sub - cell refinement +c 12. eval_dispersion - dispersion function at one Q +c 13. extract_roots - zeros and poles from the AMR cells c----------------------------------------------------------------------- USE omp_lib @@ -35,6 +37,7 @@ MODULE growthrates_mod $ MAX_PTS,HASH_SZ,HASH_SCALE, ! v1 constants $ hash_head,hash_next, ! v1 hash $ MAX_CELLS, ! v2 limit + $ MAX_ROOTS,roots_outputs_type, ! root output $ slayer_inputs_type,slayer_outputs_type, $ deltas_outputs_type, $ tau_r,dc_tmp,dc_type, @@ -44,6 +47,7 @@ MODULE growthrates_mod USE params_mod USE layerinputs_mod USE slayer_netcdf_mod + USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan IMPLICIT NONE @@ -345,16 +349,8 @@ SUBROUTINE get_or_compute_v2(q_in, idx_out, n_k, idx_out = n_pts Q_store(idx_out) = q_in - IF (coupling_flag) THEN -c dispersion_det sets g_tmp per-surface internally; -c pass q_in*ifac directly as g_in argument. - delta_val = dispersion_det(q_in * ifac, n_k, sl_in, - $ msing_max) - ELSE - g_tmp = q_in * ifac - delta_val = riccati_f() - delta_val = delta_val - delta_eff - END IF + delta_val = eval_dispersion(q_in, n_k, sl_in, msing_max, + $ coupling_flag) D_store(idx_out) = delta_val c --- 4. insert into hash chain (prepend) @@ -713,4 +709,205 @@ SUBROUTINE subdivide_cell_sub(parent, RETURN END SUBROUTINE subdivide_cell_sub +c----------------------------------------------------------------------- +c subprogram 12. eval_dispersion. +c Dispersion function at one scan-plane point q_in, with the ifac +c rotation g = i*q_in applied. Uncoupled: riccati_f() - delta_eff +c using the module-level layer parameters of the current surface. +c Coupled: the determinant from dispersion_det. +c----------------------------------------------------------------------- + FUNCTION eval_dispersion(q_in, n_k, sl_in, msing_max, + $ coupling_flag) + + IMPLICIT NONE + + COMPLEX(r8), INTENT(IN) :: q_in + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + COMPLEX(r8) :: eval_dispersion + + IF (coupling_flag) THEN +c dispersion_det sets g_tmp per-surface internally; +c pass q_in*ifac directly as g_in argument. + eval_dispersion = dispersion_det(q_in * ifac, n_k, sl_in, + $ msing_max) + ELSE + g_tmp = q_in * ifac + eval_dispersion = riccati_f() - delta_eff + END IF + + END FUNCTION eval_dispersion + +c----------------------------------------------------------------------- +c subprogram 13. extract_roots. +c Locate the zeros and poles of the dispersion function from the +c leaf cells left in amr_cells by dispersion_AMR_v2. +c +c The dispersion function is analytic in the scan variable, so the +c phase of D winds by +2*pi counter-clockwise around a cell that +c holds a simple zero and by -2*pi around one that holds a simple +c pole. The winding number is taken from the four corner values. +c Each zero is then refined by a secant iteration confined to the +c neighbourhood of its cell; if that leaves the neighbourhood or +c does not converge, the best point evaluated is kept and the root +c is marked unpolished. +c +c Roots are returned in the scan plane (q = omega_hat - i*gamma_hat) +c sorted by descending growth rate. No physical filtering of the +c roots is attempted here. +c----------------------------------------------------------------------- + SUBROUTINE extract_roots(n_k, sl_in, msing_max, coupling_flag, + $ tauk_ref, roots) + + IMPLICIT NONE + +c --- arguments + INTEGER, INTENT(IN) :: n_k, msing_max + TYPE(slayer_inputs_type), INTENT(IN) :: sl_in + LOGICAL, INTENT(IN) :: coupling_flag + REAL(r8), INTENT(IN) :: tauk_ref ! tau_k of the scanned Q + TYPE(roots_outputs_type), INTENT(OUT) :: roots +c --- parameters + INTEGER, PARAMETER :: ccw(5) = (/1,2,4,3,1/) ! BL,BR,TR,TL,BL + INTEGER, PARAMETER :: MAXIT_POLISH = 30 + REAL(r8), PARAMETER :: POLISH_TOL = 1.0e-9_r8 +c --- locals + INTEGER :: c, j, it, nw, i, ins + REAL(r8) :: wsum, h, nan + COMPLEX(r8) :: d(4), z, q_c, d_c, slope + COMPLEX(r8) :: qa, qb, qn, da, db, dn, q_best, d_best + LOGICAL :: conv, dup + + nan = ieee_value(1.0_r8, ieee_quiet_nan) + roots%n_root = 0 + roots%n_pole = 0 + roots%q_root = CMPLX(nan, nan, KIND=r8) + roots%q_pole = CMPLX(nan, nan, KIND=r8) + roots%polished = .FALSE. + roots%tauk = tauk_ref + + IF (.NOT. ALLOCATED(amr_cells)) RETURN + + DO c = 1, n_amr_cells + d = amr_cells(c)%D +c skip cells with a failed (non-finite) or exactly zero corner + IF (ANY(d /= d) .OR. ANY(ABS(d) > HUGE(1.0_r8)) + $ .OR. ANY(ABS(d) == 0.0_r8)) CYCLE + +c winding number of D around the cell, counter-clockwise + wsum = 0.0_r8 + DO j = 1, 4 + z = (d(ccw(j+1))/ABS(d(ccw(j+1)))) + $ * CONJG(d(ccw(j))/ABS(d(ccw(j)))) + wsum = wsum + ATAN2(AIMAG(z), REAL(z)) + END DO + nw = NINT(wsum / (2.0_r8*pi)) + IF (nw == 0) CYCLE + + q_c = 0.25_r8 * SUM(amr_cells(c)%Q) + h = REAL(amr_cells(c)%Q(2) - amr_cells(c)%Q(1)) + +c pole: record the cell centre + IF (nw < 0) THEN + IF (roots%n_pole < MAX_ROOTS) THEN + roots%n_pole = roots%n_pole + 1 + roots%q_pole(roots%n_pole) = q_c + END IF + CYCLE + END IF + +c zero: linear estimate from the corner values, then secant + d_c = 0.25_r8 * SUM(d) + slope = ((d(2) - d(1)) + (d(4) - d(3))) / (2.0_r8*h) + qa = q_c + da = eval_dispersion(qa, n_k, sl_in, msing_max, + $ coupling_flag) + qb = q_c + 0.25_r8*h + IF (ABS(slope) > 0.0_r8) THEN + qn = q_c - d_c/slope + IF (ABS(REAL(qn - q_c)) <= 0.5_r8*h .AND. + $ ABS(AIMAG(qn - q_c)) <= 0.5_r8*h .AND. + $ ABS(qn - q_c) > 0.0_r8) qb = qn + END IF + db = eval_dispersion(qb, n_k, sl_in, msing_max, + $ coupling_flag) + IF (ABS(db) < ABS(da) .OR. da /= da) THEN + q_best = qb + d_best = db + ELSE + q_best = qa + d_best = da + END IF + conv = .FALSE. + DO it = 1, MAXIT_POLISH + IF (da /= da .OR. db /= db) EXIT + IF (ABS(db - da) == 0.0_r8) EXIT + qn = qb - db*(qb - qa)/(db - da) + IF (qn /= qn) EXIT +c stay within 1.5 cell widths of the cell centre + IF (ABS(REAL(qn - q_c)) > 1.5_r8*h .OR. + $ ABS(AIMAG(qn - q_c)) > 1.5_r8*h) EXIT + dn = eval_dispersion(qn, n_k, sl_in, msing_max, + $ coupling_flag) + IF (dn /= dn) EXIT + qa = qb + da = db + qb = qn + db = dn + IF (ABS(db) < ABS(d_best)) THEN + q_best = qb + d_best = db + END IF + IF (ABS(qb - qa) <= + $ POLISH_TOL*MAX(1.0_r8, ABS(qb))) THEN + conv = .TRUE. + q_best = qb + d_best = db + EXIT + END IF + END DO + +c drop a root already found from a neighbouring cell + dup = .FALSE. + DO i = 1, roots%n_root + IF (ABS(roots%q_root(i) - q_best) < 0.5_r8*ABS(h)) + $ dup = .TRUE. + END DO + IF (dup .OR. roots%n_root >= MAX_ROOTS) CYCLE + +c insert, keeping descending growth rate (ascending AIMAG(q)) + ins = roots%n_root + 1 + DO i = 1, roots%n_root + IF (AIMAG(q_best) < AIMAG(roots%q_root(i))) THEN + ins = i + EXIT + END IF + END DO + DO i = roots%n_root, ins, -1 + roots%q_root(i+1) = roots%q_root(i) + roots%polished(i+1) = roots%polished(i) + END DO + roots%q_root(ins) = q_best + roots%polished(ins) = conv + roots%n_root = roots%n_root + 1 + END DO + +c --- report + WRITE(*,'(A,I0,A,I0)') ' Dispersion roots found: ', + $ roots%n_root, ', poles: ', roots%n_pole + DO i = 1, roots%n_root + WRITE(*,'(A,I2,A,2ES14.6,A,ES14.6,A,ES14.6,A,L1)') + $ ' root', i, ': Q=', REAL(roots%q_root(i)), + $ -AIMAG(roots%q_root(i)), + $ ' omega[rad/s]=', REAL(roots%q_root(i))/tauk_ref, + $ ' gamma[1/s]=', -AIMAG(roots%q_root(i))/tauk_ref, + $ ' polished=', roots%polished(i) + END DO + IF (roots%n_root == 0) WRITE(*,'(A)') + $ ' WARNING: no dispersion root in the scan window.' + + RETURN + END SUBROUTINE extract_roots + END MODULE growthrates_mod diff --git a/slayer/sglobal.f b/slayer/sglobal.f index bd4d5556a..63bc85b63 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -190,4 +190,22 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: imag_deltas(:) ! Im(Delta) END TYPE deltas_outputs_type +c roots_outputs_type -- zeros and poles of the dispersion function +c located in one AMR scan. Q values are stored in the scan plane, +c q = omega_hat - i*gamma_hat (so gamma = -AIMAG(q)/tauk and +c omega = REAL(q)/tauk), sorted by descending growth rate. + INTEGER, PARAMETER :: MAX_ROOTS = 32 ! max stored per scan + TYPE roots_outputs_type + INTEGER :: n_root = 0 ! zeros found + INTEGER :: n_pole = 0 ! poles found + COMPLEX(r8) :: q_root(MAX_ROOTS) ! zeros (scan plane) + COMPLEX(r8) :: q_pole(MAX_ROOTS) ! poles (scan plane) + LOGICAL :: polished(MAX_ROOTS) ! secant converged? + REAL(r8) :: tauk = 0.0_r8 ! Q -> [1/s] factor + END TYPE roots_outputs_type + +c per-scan roots, filled by extract_roots (growthrates_mod) and +c written by slayer_netcdf_out + TYPE(roots_outputs_type), ALLOCATABLE :: roots_out(:) + END MODULE sglobal_mod diff --git a/slayer/slayer.f b/slayer/slayer.f index 15b4e6674..f61b90779 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -17,6 +17,7 @@ PROGRAM slayer USE gslayer_mod USE growthrates_mod USE layerinputs_mod + USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan IMPLICIT NONE c----------------------------------------------------------------------- @@ -708,10 +709,17 @@ PROGRAM slayer c----------------------------------------------------------------------- IF (AMR_flag .AND. .NOT. coupling_flag) THEN ALLOCATE(all_deltas_out(n_k)) + ALLOCATE(roots_out(n_k)) ELSEIF (AMR_flag .AND. coupling_flag) THEN ALLOCATE(all_deltas_out(1)) + ALLOCATE(roots_out(1)) END IF +c growth rates stay NaN on surfaces where no root is extracted + sl_out%gamma_sol_arr = CMPLX( + $ ieee_value(1.0_r8, ieee_quiet_nan), + $ ieee_value(1.0_r8, ieee_quiet_nan), KIND=r8) + IF (AMR_flag) THEN IF (coupling_flag) THEN m_AMR = 1 @@ -756,9 +764,6 @@ PROGRAM slayer $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) pe = 0.0 - ! Placeholder: gamma_sol_arr filled for external root-finder. - sl_out%gamma_sol_arr(k) = 0.0 - c----------------------------------------------------------------------- c uncoupled AMR scan (one surface at a time). c dispersion_AMR_v2 (growthrates_mod) populates Q_store, D_store. @@ -770,6 +775,18 @@ PROGRAM slayer CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) + ! Zeros of the dispersion function in this surface's + ! own ExB frame; the most unstable one is reported as + ! gamma_sol = gamma [1/s] + i*omega [rad/s]. + CALL extract_roots(n_k,sl_in,msing_max,coupling_flag, + $ sl_in%Qconv_arr(k),roots_out(k)) + IF (roots_out(k)%n_root > 0) THEN + sl_out%gamma_sol_arr(k) = CMPLX( + $ -AIMAG(roots_out(k)%q_root(1)), + $ REAL(roots_out(k)%q_root(1)), KIND=r8) + $ / sl_in%Qconv_arr(k) + END IF + ! Re-allocate output arrays for this surface. IF (ALLOCATED(all_deltas_out(k)%inQs)) $ DEALLOCATE(all_deltas_out(k)%inQs) @@ -886,6 +903,13 @@ PROGRAM slayer CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag) + ! Zeros of the coupled determinant. Q is normalised by + ! the first surface's tau_k. The coupled eigenvalue is + ! global, so it is reported through roots_out only and + ! the per-surface gamma_sol_arr is left as NaN. + CALL extract_roots(n_k,sl_in,msing_max,coupling_flag, + $ sl_in%Qconv_arr(1),roots_out(1)) + ! Re-allocate output arrays. IF (ALLOCATED(all_deltas_out(1)%inQs)) $ DEALLOCATE(all_deltas_out(1)%inQs) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 82e9bd91f..015ca78bb 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -127,6 +127,22 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: var_d_id ! "Deltas_AMR" — scan Delta values INTEGER :: var_npts_id ! "n_amr_pts" — points per surface +c root variable IDs (zeros and poles of the dispersion function) + INTEGER :: root_dim ! dim: stored roots per scan + INTEGER :: nroot_id ! "n_roots" + INTEGER :: npole_id ! "n_poles" + INTEGER :: qroot_id ! "Q_roots" + INTEGER :: qpole_id ! "Q_poles" + INTEGER :: groot_id ! "gamma_roots" + INTEGER :: oroot_id ! "omega_roots" + INTEGER :: proot_id ! "root_polished" + LOGICAL :: have_roots ! roots_out holds this run's roots + REAL(r8), ALLOCATABLE :: buffer_r(:,:,:) ! (root, surf, Re/Im) + REAL(r8), ALLOCATABLE :: buffer_p(:,:,:) ! (root, surf, Re/Im) + REAL(r8), ALLOCATABLE :: buffer_g(:,:) ! gamma (root, surf) + REAL(r8), ALLOCATABLE :: buffer_o(:,:) ! omega (root, surf) + INTEGER, ALLOCATABLE :: buffer_f(:,:) ! polished (root, surf) + c----------------------------------------------------------------------- c declarations -- AMR rectangular-buffer workspace. c The ragged per-surface scan data are padded into fixed-size @@ -217,6 +233,33 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, END DO END IF +c step 4: roots and poles, in the same (omega_hat, gamma_hat) +c convention as Q_AMR. + have_roots = .FALSE. + IF (ALLOCATED(roots_out)) have_roots = SIZE(roots_out) >= m_AMR + $ .AND. m_AMR > 0 + IF (have_roots) THEN + ALLOCATE(buffer_r(MAX_ROOTS, m_AMR, 2)) + ALLOCATE(buffer_p(MAX_ROOTS, m_AMR, 2)) + ALLOCATE(buffer_g(MAX_ROOTS, m_AMR)) + ALLOCATE(buffer_o(MAX_ROOTS, m_AMR)) + ALLOCATE(buffer_f(MAX_ROOTS, m_AMR)) + buffer_f = 0 + DO s = 1, m_AMR + buffer_r(:, s, 1) = REAL(roots_out(s)%q_root) + buffer_r(:, s, 2) = -AIMAG(roots_out(s)%q_root) + buffer_p(:, s, 1) = REAL(roots_out(s)%q_pole) + buffer_p(:, s, 2) = -AIMAG(roots_out(s)%q_pole) + buffer_g(:, s) = fill_val + buffer_o(:, s) = fill_val + IF (roots_out(s)%tauk > 0.0_r8) THEN + buffer_g(:, s) = buffer_r(:, s, 2)/roots_out(s)%tauk + buffer_o(:, s) = buffer_r(:, s, 1)/roots_out(s)%tauk + END IF + WHERE (roots_out(s)%polished) buffer_f(:, s) = 1 + END DO + END IF + c----------------------------------------------------------------------- c define global file attributes. c----------------------------------------------------------------------- @@ -287,6 +330,10 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_def_var(ncid, "growth rate", $ nf90_double, (/qsing_dim, i_dim/), gs_id) ) + CALL sl_check( nf90_put_att(ncid, gs_id, "long_name", + $ "Most unstable matched root on each surface: gamma [1/s] "// + $ "(i=1) and omega [rad/s] (i=2), in that surface's ExB "// + $ "frame; NaN where no root was extracted") ) IF (ALLOCATED(sl_out%br_th_arr)) THEN CALL sl_check( nf90_def_var(ncid, "br_th", @@ -305,6 +352,45 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_def_var(ncid, "Deltas_AMR", NF90_DOUBLE, $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id) ) +c roots and poles of the dispersion function from each AMR scan. + IF (have_roots) THEN + CALL sl_check( nf90_def_dim(ncid, "root", MAX_ROOTS, + $ root_dim) ) + CALL sl_check( nf90_def_var(ncid, "n_roots", NF90_INT, + $ (/nAMR_dim/), nroot_id) ) + CALL sl_check( nf90_def_var(ncid, "n_poles", NF90_INT, + $ (/nAMR_dim/), npole_id) ) + CALL sl_check( nf90_def_var(ncid, "Q_roots", NF90_DOUBLE, + $ (/root_dim, nAMR_dim, i_dim/), qroot_id) ) + CALL sl_check( nf90_put_att(ncid, qroot_id, "long_name", + $ "Zeros of the dispersion function, (omega, gamma)*"// + $ "tau_k, sorted by descending gamma") ) + CALL sl_check( nf90_def_var(ncid, "Q_poles", NF90_DOUBLE, + $ (/root_dim, nAMR_dim, i_dim/), qpole_id) ) + CALL sl_check( nf90_put_att(ncid, qpole_id, "long_name", + $ "Poles of the dispersion function, (omega, gamma)*"// + $ "tau_k, AMR cell centres") ) + CALL sl_check( nf90_def_var(ncid, "gamma_roots", + $ NF90_DOUBLE, (/root_dim, nAMR_dim/), groot_id) ) + CALL sl_check( nf90_put_att(ncid, groot_id, "units", "1/s") ) + CALL sl_check( nf90_def_var(ncid, "omega_roots", + $ NF90_DOUBLE, (/root_dim, nAMR_dim/), oroot_id) ) + CALL sl_check( nf90_put_att(ncid, oroot_id, "units", + $ "rad/s") ) + CALL sl_check( nf90_def_var(ncid, "root_polished", NF90_INT, + $ (/root_dim, nAMR_dim/), proot_id) ) + CALL sl_check( nf90_put_att(ncid, proot_id, "long_name", + $ "1 if the secant refinement of the root converged") ) + CALL sl_check( nf90_put_att(ncid, qroot_id, + $ "_FillValue", fill_val) ) + CALL sl_check( nf90_put_att(ncid, qpole_id, + $ "_FillValue", fill_val) ) + CALL sl_check( nf90_put_att(ncid, groot_id, + $ "_FillValue", fill_val) ) + CALL sl_check( nf90_put_att(ncid, oroot_id, + $ "_FillValue", fill_val) ) + END IF + c set _FillValue attribute on AMR arrays for proper padding. CALL sl_check( nf90_put_att(ncid, var_q_id, $ "_FillValue", fill_val) ) @@ -389,6 +475,19 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_put_var(ncid, var_q_id, buffer_q) ) CALL sl_check( nf90_put_var(ncid, var_d_id, buffer_d) ) + IF (have_roots) THEN + CALL sl_check( nf90_put_var(ncid, nroot_id, + $ roots_out(1:m_AMR)%n_root) ) + CALL sl_check( nf90_put_var(ncid, npole_id, + $ roots_out(1:m_AMR)%n_pole) ) + CALL sl_check( nf90_put_var(ncid, qroot_id, buffer_r) ) + CALL sl_check( nf90_put_var(ncid, qpole_id, buffer_p) ) + CALL sl_check( nf90_put_var(ncid, groot_id, buffer_g) ) + CALL sl_check( nf90_put_var(ncid, oroot_id, buffer_o) ) + CALL sl_check( nf90_put_var(ncid, proot_id, buffer_f) ) + DEALLOCATE(buffer_r, buffer_p, buffer_g, buffer_o, buffer_f) + END IF + c----------------------------------------------------------------------- c deallocate AMR rectangular buffers. c----------------------------------------------------------------------- From c4d129abf73cb0724d4ebb34aa91b145b3b3fe6f Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:26:30 -0400 Subject: [PATCH 172/305] SLAYER - BUGFIX - Correct the inter-surface Q rescaling in the coupled determinant dispersion_det evaluated surface k's layer at g*tau_1/tau_k. With g = tau_1*(gamma + i*omega) the same physical eigenvalue reaches surface k as g*tau_k/tau_1, which is the normalisation its Q_e and Q_i carry. The old direction got only the reference surface right, so every other surface's branch of the coupled determinant sat at the wrong frequency and growth rate. Check on the DIII-D-like example with a real, diagonal Delta' (where the coupled roots must equal the uncoupled ones): the 3/1 and 4/1 roots now land on Q_k*tau_1/tau_k to 7 digits. Before, the 3/1 branch sat at Re(Q) = -0.514 instead of -1.062. Same defect and fix as OpenFUSIONToolkit/GPEC#463. Co-Authored-By: Claude Opus 5.5 --- slayer/growthrates.f | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 0704b1bd6..ef69b1237 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -251,9 +251,12 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c atol = 1e-7*pr**0.4 does not inherit a stale/zero module value. pr = P_perp -c evaluate riccati_f at rescaled growth rate, de-normalise -c rescale g_in to this surface's normalisation - g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk +c evaluate riccati_f at rescaled growth rate, de-normalise. +c g_in is normalised by the first surface's tau_k, i.e. +c g_in = tau_1*(gamma + i*omega). The same physical +c eigenvalue reaches surface k as tau_k*(gamma + i*omega) +c = g_in*tau_k/tau_1, the normalisation its Q_e and Q_i use. + g_tmp = g_in*(tauk/sl_in%Qconv_arr(1)) delta_Q(k,k)=riccati_f() delta_Q(k,k)=delta_Q(k,k)* $ sl_in%lu_arr(k)**(1.0_r8/3.0_r8) From 6ebfdb400fecd8496d8da8daecb20e4e9afc53e9 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:28:44 -0400 Subject: [PATCH 173/305] SLAYER, STRIDE - BUGFIX - Convert Delta' to the r_s reference length before layer matching The dispersion relation Delta' - Delta_crit = S^(1/3)*Delta(Q) compared STRIDE's Delta', which is referenced to unit normalised poloidal flux, with layer quantities that are all referenced to x = (r - r_s)/r_s. build_inputs now converts the matrix with the Mercier-exponent transform Dp_rs(i,j) = K_i^(1/2+alpha_i) * Dp(i,j) * K_j^(alpha_j-1/2) K = r_s*dpsi_N/dr, alpha = sqrt(-D_I) whose diagonal factor K^(2*alpha) reduces to r_s*Delta' at the slab point D_I = -1/4. The growth-rate estimate uses the converted diagonal too. Mercier-unstable surfaces are left unconverted. STRIDE writes the Mercier index at the rational surfaces (di_rational) for this. The SLAYER NetCDF keeps the STRIDE value in Delta_prime_rational and adds Delta_prime_rs_rational, k_ref and alpha_mercier. Namelist-driven runs (read_eq=f) are unchanged: the input delta_prime is taken as already r_s-referenced. DIII-D-like example, uncoupled, dc_type none: K^(2*alpha) = 0.80 at the 2/1 and its growth rate moves from 107.1 to 86.0 1/s. Same defect and fix as OpenFUSIONToolkit/GPEC#403. Co-Authored-By: Claude Opus 5.5 --- slayer/growthrates.f | 8 +++- slayer/layerinputs.f | 86 ++++++++++++++++++++++++++++++++++++++---- slayer/sglobal.f | 14 +++++-- slayer/slayer.f | 6 +++ slayer/slayer_netcdf.f | 27 +++++++++++++ stride/stride_netcdf.f | 15 ++++++-- 6 files changed, 141 insertions(+), 15 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index ef69b1237..5f06f8b64 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -93,7 +93,13 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%P_perp_arr(n_k),sl_in%tau_arr(n_k), $ sl_in%D_norm_arr(n_k), $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), - $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k)) + $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k), + $ sl_in%Re_dp_psi_arr(n_k),sl_in%Im_dp_psi_arr(n_k), + $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k)) +c namelist inputs are taken as already r_s-referenced: identity +c reference-length conversion (K = 1). + sl_in%k_ref_arr = 1.0_r8 + sl_in%alpha_arr = 0.5_r8 RETURN END SUBROUTINE allocate_inputs c----------------------------------------------------------------------- diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index ffedd8191..0e4914a99 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -38,7 +38,7 @@ MODULE layerinputs_mod c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, - $ shear,r_o,my_bt0,my_psio,dr_vals,mpsi,nn,resm) + $ shear,r_o,my_bt0,my_psio,dr_vals,di_vals,mpsi,nn,resm) c --- arguments (all INTENT(OUT) except ncfile) CHARACTER(512), INTENT(IN) :: ncfile ! path to STRIDE NetCDF @@ -58,7 +58,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ my_bt0, ! toroidal field B_t0 $ my_psio, ! poloidal flux psi_0 $ mpsi, ! poloidal mode number array - $ dr_vals ! radial width dr at each surface + $ dr_vals, ! resistive interchange index D_R + $ di_vals ! Mercier index D_I INTEGER, DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ nn, ! toroidal mode number(s) $ resm ! resonant poloidal mode numbers @@ -70,6 +71,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, INTEGER(kind=nf90_int) :: dp_id, qr_id, pr_id ! variable ids INTEGER(kind=nf90_int) :: dgeo_id, shear_id ! variable ids INTEGER(kind=nf90_int) :: resm_id, drr_id ! variable ids + INTEGER(kind=nf90_int) :: dir_id ! variable id INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! NetCDF hyperslab INTEGER :: i ! loop index INTEGER :: bt0_len, ro_len, psio_len ! attribute lengths @@ -94,7 +96,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, c --- allocate output arrays sized by msing ALLOCATE(Re_dp_diagonal(msing), q_rational(msing), $ psi_n_rational(msing), shear(msing), dgeo(msing), - $ resm(msing), Im_dp_diagonal(msing), dr_vals(msing)) + $ resm(msing), Im_dp_diagonal(msing), dr_vals(msing), + $ di_vals(msing)) ALLOCATE(dp_mat(msing, msing, 2)) c --- read lengths of scalar / small-array global attributes @@ -133,6 +136,14 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) stat = nf90_inq_varid(ncid, 'dr_rational', drr_id) CALL sl_check(stat) + stat = nf90_inq_varid(ncid, 'di_rational', dir_id) + IF (stat /= nf90_noerr) THEN + WRITE(*,*) 'ERROR: di_rational is missing from ',TRIM(ncfile) + WRITE(*,*) ' Rerun STRIDE from this GPEC version; SLAYER' + WRITE(*,*) ' needs D_I to convert Delta_prime to the r_s' + WRITE(*,*) ' reference length.' + END IF + CALL sl_check(stat) c --- read global attributes (equilibrium scalars) stat = nf90_get_att(ncid, NF90_GLOBAL, 'ro', r_o) @@ -161,6 +172,8 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) stat = nf90_get_var(ncid, drr_id, dr_vals) CALL sl_check(stat) + stat = nf90_get_var(ncid, dir_id, di_vals) + CALL sl_check(stat) c --- extract diagonal of the complex Deltaprime matrix DO i = 1, msing @@ -339,7 +352,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, $ Im_dp_diagonal, q_rational, psi_n_rational, $ shear, dgeo, r_o, my_bt0, my_psio, mpsi_arr, - $ dr_vals, dr_arr + $ dr_vals, di_vals, dr_arr c --- local per-surface kinetic arrays (for future NetCDF diagnostic output) REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr, te_arr, $ ni_arr, ti_arr, zeff_arr, bt_arr, rs_arr, R0_arr, @@ -357,13 +370,17 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8) :: da_dpsiN ! da_surf/dpsiN (Jacobian) REAL(r8) :: dpsi_h ! finite-diff step for Jacobian REAL(r8) :: s_fitz ! Fitzpatrick shear r*dq/dr/q +c --- Deltaprime reference-length conversion (psi_N -> r_s) + REAL(r8) :: k_ref ! K = r_s*dpsi_N/dr + REAL(r8) :: alpha_m ! Mercier exponent sqrt(-D_I) + INTEGER :: jsing ! second surface index c----------------------------------------------------------------------- c read STRIDE NetCDF: Deltaprime matrix, geometry, equilibrium scalars. c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational, $ psi_n_rational,dgeo,shear,r_o,my_bt0,my_psio,dr_vals, - $ mpsi_arr,nn,resm) + $ di_vals,mpsi_arr,nn,resm) mpsi = INT(mpsi_arr(1)) mthsurf = 512 ! poloidal segments for surface integral @@ -380,7 +397,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%D_norm_arr(msing), $ sl_in%d_beta_arr(msing), sl_in%gammafac_arr(msing), $ sl_in%c_beta_arr(msing), sl_in%lu_arr(msing), - $ sl_in%Qconv_arr(msing)) + $ sl_in%Qconv_arr(msing), + $ sl_in%Re_dp_psi_arr(msing), sl_in%Im_dp_psi_arr(msing), + $ sl_in%k_ref_arr(msing), sl_in%alpha_arr(msing)) c --- allocate local kinetic arrays (for future NetCDF diagnostic output) ALLOCATE(ne_arr(msing), te_arr(msing), ni_arr(msing), @@ -412,8 +431,10 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) CALL equil_read(out_unit) -c store full complex Deltaprime matrix in sl_in - sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1), dp_mat(:,:,2)) +c store full complex Deltaprime matrix in sl_in. It is converted +c to the r_s reference length after the surface loop below. + sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1), dp_mat(:,:,2), + $ KIND=r8) dpsi_h = 0.002 ! finite-difference step for Jacobian @@ -539,6 +560,43 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ l_n,l_t,my_qval,s_fitz,my_bt,my_rs,R_0,mu_i, $ zeff,.false.) +c----------------------------------------------------------------------- +c reference-length conversion of the outer Deltaprime. +c STRIDE's Deltaprime is referenced to unit normalised poloidal +c flux, while the layer response S^(1/3)*Delta(Q), S and the +c critical Delta are all referenced to x = (r - r_s)/r_s. +c Near the surface the outer solution scales as +c |x|^(1/2 -+ alpha) with the Mercier exponent +c alpha = sqrt(-D_I) (Glasser, Greene & Johnson 1975), so +c rescaling x_psi = K*x with K = r_s*dpsi_N/dr gives +c Dp_rs(i,j) = K_i^(1/2+alpha_i) * Dp(i,j) * K_j^(alpha_j-1/2) +c whose diagonal factor is K^(2*alpha), reducing to the slab +c r_s*Deltaprime at D_I = -1/4. Mercier-unstable surfaces +c (D_I >= 0) are left unconverted through alpha = 0, and K = 1 +c is used if da/dpsi_N is not a usable positive number. +c----------------------------------------------------------------------- + IF (da_dpsiN > 0.0_r8 .AND. da_dpsiN < HUGE(da_dpsiN)) THEN + k_ref = my_rs / da_dpsiN + ELSE + k_ref = 1.0_r8 + WRITE(*,*) 'WARNING: da/dpsi_N =',da_dpsiN,' at psi_N =', + $ respsi,' is not usable;' + WRITE(*,*) ' Delta_prime is left unconverted there.' + END IF + IF (di_vals(ising) < 0.0_r8) THEN + alpha_m = SQRT(-di_vals(ising)) + ELSE + alpha_m = 0.0_r8 + END IF + sl_in%k_ref_arr(ising) = k_ref + sl_in%alpha_arr(ising) = alpha_m + sl_in%Re_dp_psi_arr(ising) = Re_dp_diagonal(ising) + sl_in%Im_dp_psi_arr(ising) = Im_dp_diagonal(ising) + Re_dp_diagonal(ising) = Re_dp_diagonal(ising) + $ * k_ref**(2.0_r8*alpha_m) + Im_dp_diagonal(ising) = Im_dp_diagonal(ising) + $ * k_ref**(2.0_r8*alpha_m) + c growth-rate conversion factor: Deltaprime -> gamma gammafac = (my_rs * Re_dp_diagonal(ising)) / tau_r @@ -571,6 +629,18 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) sl_in%P_tor_arr(ising) = P_tor sl_in%Qconv_arr(ising) = tauk ENDDO + +c convert the full Deltaprime matrix to the r_s reference length + DO jsing = 1, msing + DO ising = 1, msing + sl_in%dp_matrix(ising,jsing) = + $ sl_in%k_ref_arr(ising) + $ **(0.5_r8 + sl_in%alpha_arr(ising)) + $ * sl_in%dp_matrix(ising,jsing) + $ * sl_in%k_ref_arr(jsing) + $ **(sl_in%alpha_arr(jsing) - 0.5_r8) + END DO + END DO c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 63bc85b63..88de29ba9 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -168,10 +168,18 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: omegas_e_arr(:) ! omega_e [rad/s] REAL(r8), ALLOCATABLE :: omegas_i_arr(:) ! omega_i [rad/s] REAL(r8), ALLOCATABLE :: gammafac_arr(:) ! gamma conversion - REAL(r8), ALLOCATABLE :: Re_dp_arr(:) ! Re(Deltaprime) - REAL(r8), ALLOCATABLE :: Im_dp_arr(:) ! Im(Deltaprime) +c Deltaprime used in the layer matching is referenced to the +c minor radius r_s (unit x = (r - r_s)/r_s), like every layer +c quantity. The *_psi arrays keep the outer-region value as +c STRIDE writes it, referenced to unit normalised poloidal flux. + REAL(r8), ALLOCATABLE :: Re_dp_arr(:) ! Re(Deltaprime), r_s ref. + REAL(r8), ALLOCATABLE :: Im_dp_arr(:) ! Im(Deltaprime), r_s ref. + REAL(r8), ALLOCATABLE :: Re_dp_psi_arr(:) ! Re(Deltaprime), psi_N ref. + REAL(r8), ALLOCATABLE :: Im_dp_psi_arr(:) ! Im(Deltaprime), psi_N ref. + REAL(r8), ALLOCATABLE :: k_ref_arr(:) ! K = r_s*dpsi_N/dr + REAL(r8), ALLOCATABLE :: alpha_arr(:) ! Mercier exponent sqrt(-D_I) REAL(r8), ALLOCATABLE :: d_crit_arr(:) ! Delta_crit - COMPLEX(r8), ALLOCATABLE :: dp_matrix(:,:) ! full Deltaprime matrix + COMPLEX(r8), ALLOCATABLE :: dp_matrix(:,:) ! full Deltaprime matrix, r_s ref. END TYPE slayer_inputs_type c slayer_outputs_type -- per-surface solver results diff --git a/slayer/slayer.f b/slayer/slayer.f index f61b90779..b4f0347ee 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -563,6 +563,8 @@ PROGRAM slayer sl_in%psi_n_arr = (/ 0.0 /) sl_in%Re_dp_arr = (/ REAL(delta_prime) /) sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) + sl_in%Re_dp_psi_arr = (/ REAL(delta_prime) /) + sl_in%Im_dp_psi_arr = (/ AIMAG(delta_prime) /) sl_in%d_crit_arr = (/ dc_tmp /) sl_in%P_perp_arr = (/ P_perp /) sl_in%P_tor_arr = (/ P_tor /) @@ -690,6 +692,8 @@ PROGRAM slayer sl_in%psi_n_arr = (/ 0.0 /) sl_in%Re_dp_arr = (/ REAL(delta_prime) /) sl_in%Im_dp_arr = (/ AIMAG(delta_prime) /) + sl_in%Re_dp_psi_arr = (/ REAL(delta_prime) /) + sl_in%Im_dp_psi_arr = (/ AIMAG(delta_prime) /) sl_in%d_crit_arr = (/ dc_tmp /) sl_in%P_perp_arr = (/ P_perp /) sl_in%P_tor_arr = (/ P_tor /) @@ -1037,6 +1041,8 @@ PROGRAM slayer sl_in%psi_n_arr = (/ 0.0 /) sl_in%Re_dp_arr = (/ 0.0 /) sl_in%Im_dp_arr = (/ 0.0 /) + sl_in%Re_dp_psi_arr = (/ 0.0 /) + sl_in%Im_dp_psi_arr = (/ 0.0 /) sl_in%d_crit_arr = (/ 0.0 /) sl_in%P_perp_arr = (/ P_perp /) sl_in%P_tor_arr = (/ P_tor /) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 015ca78bb..85fd44231 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -115,6 +115,9 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: p_tor_id ! "P_tor" INTEGER :: Dnorm_id ! "D" — normalised D INTEGER :: dpp_id ! "Delta_prime_rational" (complex) + INTEGER :: dprs_id ! "Delta_prime_rs_rational" (complex) + INTEGER :: kref_id ! "k_ref" + INTEGER :: alpha_id ! "alpha_mercier" INTEGER :: dc_id ! "Delta_crit_rational" INTEGER :: dels_db_id ! "delta_s_d_b" (complex) INTEGER :: d_b_id ! "d_beta" @@ -316,6 +319,23 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ qsing_dim, Dnorm_id) ) CALL sl_check( nf90_def_var(ncid, "Delta_prime_rational", $ nf90_double, (/qsing_dim, i_dim/), dpp_id) ) + CALL sl_check( nf90_put_att(ncid, dpp_id, "long_name", + $ "Outer-region Delta_prime diagonal, referenced to unit "// + $ "normalised poloidal flux (as written by STRIDE)") ) + CALL sl_check( nf90_def_var(ncid, "Delta_prime_rs_rational", + $ nf90_double, (/qsing_dim, i_dim/), dprs_id) ) + CALL sl_check( nf90_put_att(ncid, dprs_id, "long_name", + $ "Delta_prime diagonal referenced to the minor radius "// + $ "r_s, K^(2*alpha)*Delta_prime; the value used in the "// + $ "layer matching") ) + CALL sl_check( nf90_def_var(ncid, "k_ref", nf90_double, + $ qsing_dim, kref_id) ) + CALL sl_check( nf90_put_att(ncid, kref_id, "long_name", + $ "Reference-length factor K = r_s*dpsi_N/dr") ) + CALL sl_check( nf90_def_var(ncid, "alpha_mercier", nf90_double, + $ qsing_dim, alpha_id) ) + CALL sl_check( nf90_put_att(ncid, alpha_id, "long_name", + $ "Mercier exponent sqrt(-D_I)") ) CALL sl_check( nf90_def_var(ncid, "Delta_crit_rational", $ nf90_double, qsing_dim, dc_id) ) @@ -432,8 +452,15 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c write complex Delta' as a RESHAPE'd (msing, 2) real array. c----------------------------------------------------------------------- CALL sl_check( nf90_put_var(ncid, dpp_id, + $ RESHAPE( (/sl_in%Re_dp_psi_arr, sl_in%Im_dp_psi_arr/), + $ (/msing, 2/) )) ) + CALL sl_check( nf90_put_var(ncid, dprs_id, $ RESHAPE( (/sl_in%Re_dp_arr, sl_in%Im_dp_arr/), $ (/msing, 2/) )) ) + CALL sl_check( nf90_put_var(ncid, kref_id, + $ sl_in%k_ref_arr) ) + CALL sl_check( nf90_put_var(ncid, alpha_id, + $ sl_in%alpha_arr) ) CALL sl_check( nf90_put_var(ncid, dc_id, $ sl_in%d_crit_arr) ) diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index 304fb5e41..d30087b6f 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -63,13 +63,13 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id,drr_id,dgeo_id + $ shear_id,resm_id,drr_id,dgeo_id,dir_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile REAL(r8) :: respsi - REAL(r8), DIMENSION(msing) :: dr_rationals + REAL(r8), DIMENSION(msing) :: dr_rationals,di_rationals INTEGER, DIMENSION(msing) :: resm INTEGER :: ising,jsing @@ -93,10 +93,12 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, ev = CMPLX(evi, 0.0) et = CMPLX(eti, 0.0) - ! evaluate resistive interchange parameter on rational surfaces + ! evaluate the Mercier (D_I) and resistive interchange (D_R) + ! indices on rational surfaces DO i=1,msing respsi = sing(i)%psifac CALL spline_eval(locstab,respsi,0) + di_rationals(i)=locstab%f(1)/respsi dr_rationals(i)=locstab%f(2)/respsi END DO c----------------------------------------------------------------------- @@ -209,6 +211,12 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, $ resm_id) ) CALL check( nf90_def_var(ncid, "dr_rational", nf90_double, $ r_dim, drr_id) ) + CALL check( nf90_put_att(ncid,drr_id,"long_name", + $ "Resistive Interchange Index D_R at Rational Surfaces") ) + CALL check( nf90_def_var(ncid, "di_rational", nf90_double, + $ r_dim, dir_id) ) + CALL check( nf90_put_att(ncid,dir_id,"long_name", + $ "Mercier Index D_I at Rational Surfaces") ) ENDIF ! define variables IF(debug_flag) PRINT *," - Defining variables in netcdf" @@ -276,6 +284,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, CALL check( nf90_put_var(ncid,resm_id, resm) ) CALL check( nf90_put_var(ncid,dgeo_id, dgeo) ) CALL check( nf90_put_var(ncid,drr_id, dr_rationals)) + CALL check( nf90_put_var(ncid,dir_id, di_rationals)) ENDIF IF(debug_flag) PRINT *," - Putting profile variables in netcdf" From af715b72698076b0f2ad0766a62221acce40cd0b Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:30:15 -0400 Subject: [PATCH 174/305] SLAYER - BUGFIX - Normalize D by the electron diamagnetic share, not the temperature ratio D_norm used sqrt(tau/(1+tau)) with tau = T_i/T_e. Fitzpatrick's definition is D = S^(1/3) * iota_e^(1/2) * d_beta/r_s with iota_e = Q_e/(Q_e - Q_i), which reduces to 1/(1+tau) = T_e/(T_e+T_i) for equal electron and ion gradient ratios: the old form had the two temperatures transposed, and the two agree only at T_i = T_e. D_norm is now built from the iota_e the code already computes, through slayer_d_norm, and is recomputed after any namelist override of Q_e or Q_i. A non-positive iota_e (same-sign diamagnetic frequencies) stops with a message instead of producing a NaN. DIII-D-like example, 2/1 surface (T_i/T_e = 1.20, iota_e = 0.39): D_norm 3.24 -> 2.75, uncoupled growth rate 86.0 -> 79.5 1/s. Same defect and fix as OpenFUSIONToolkit/GPEC#434. Co-Authored-By: Claude Opus 5.5 --- slayer/growthrates.f | 3 ++- slayer/layerinputs.f | 7 ++++-- slayer/params.f | 52 +++++++++++++++++++++++++++++++++++++++++--- slayer/sglobal.f | 2 ++ slayer/slayer.f | 21 ++++++++++++++++++ 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 5f06f8b64..5324c1b1b 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -95,7 +95,8 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%d_beta_arr(n_k),sl_in%gammafac_arr(n_k), $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k), $ sl_in%Re_dp_psi_arr(n_k),sl_in%Im_dp_psi_arr(n_k), - $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k)) + $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k), + $ sl_in%D_fac_arr(n_k)) c namelist inputs are taken as already r_s-referenced: identity c reference-length conversion (K = 1). sl_in%k_ref_arr = 1.0_r8 diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 0e4914a99..7b4a2da11 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -394,7 +394,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%Re_dp_arr(msing), sl_in%Im_dp_arr(msing), $ sl_in%d_crit_arr(msing), sl_in%P_tor_arr(msing), $ sl_in%P_perp_arr(msing), sl_in%tau_arr(msing), - $ sl_in%D_norm_arr(msing), + $ sl_in%D_norm_arr(msing), sl_in%D_fac_arr(msing), $ sl_in%d_beta_arr(msing), sl_in%gammafac_arr(msing), $ sl_in%c_beta_arr(msing), sl_in%lu_arr(msing), $ sl_in%Qconv_arr(msing), @@ -617,7 +617,10 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) sl_in%Q_i_arr(ising) = -tauk * omega_i sl_in%c_beta_arr(ising) = c_beta sl_in%d_beta_arr(ising) = d_beta - sl_in%D_norm_arr(ising) = D_norm +c D_norm uses iota_e from the Q_e, Q_i just set above + sl_in%D_fac_arr(ising) = D_fac + sl_in%D_norm_arr(ising) = slayer_d_norm(D_fac, + $ sl_in%Q_e_arr(ising),sl_in%Q_i_arr(ising)) sl_in%tau_arr(ising) = tau sl_in%omegas_arr(ising) = omega sl_in%psi_n_arr(ising) = respsi diff --git a/slayer/params.f b/slayer/params.f index 0f366e68f..8e7254e3f 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -9,6 +9,8 @@ c 1. params -- derive all normalised quantities (Q, ds, c_beta, c D_norm, P_perp, P_tor, lu, delta_crit, ...) for a c single rational surface and store them in sglobal_mod. +c 2. slayer_d_norm -- D_norm from D_fac and the diamagnetic +c frequencies. c======================================================================= MODULE params_mod @@ -184,10 +186,20 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- c Normalised beta-related width and Delta norm factor. c----------------------------------------------------------------------- -c d_beta uses Fitzpatrick's definition (tau' form) +c d_beta uses Fitzpatrick's definition (tau' form). +c D = S^(1/3) * iota_e^(1/2) * d_beta/r_s with +c iota_e = Q_e/(Q_e - Q_i), the electron share of the diamagnetic +c frequency (see slayer_d_norm). When this call does not set Q_e +c and Q_i, the equal-gradient-ratio value iota_e = T_e/(T_e + T_i) +c = 1/(1 + tau) is used provisionally; callers that set Q_e and Q_i +c afterwards must reset D_norm = slayer_d_norm(D_fac,Q_e,Q_i). d_beta = c_beta * d_i - D_norm = (d_beta/rs) * lu**(1.0/3.0) - $ * (tau/(1+tau))**(0.5) + D_fac = (d_beta/rs) * lu**(1.0/3.0) + IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN + D_norm = slayer_d_norm(D_fac,Q_e,Q_i) + ELSE + D_norm = D_fac * SQRT(1.0_r8/(1.0_r8+tau)) + END IF delta_n = lu**(1.0/3.0) / rs ! normalisation for Delta values @@ -262,5 +274,39 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- RETURN END SUBROUTINE params +c----------------------------------------------------------------------- +c subprogram 2. slayer_d_norm. +c Normalised ion-sound-radius parameter of the Fitzpatrick layer +c equations, D = S^(1/3) * iota_e^(1/2) * d_beta/r_s, from +c D_fac = S^(1/3)*d_beta/r_s and iota_e = Q_e/(Q_e - Q_i). +c iota_e must be positive, which holds when the electron and ion +c diamagnetic frequencies have opposite signs. +c----------------------------------------------------------------------- + FUNCTION slayer_d_norm(D_fac_in,Q_e_in,Q_i_in) + + REAL(r8), INTENT(IN) :: D_fac_in ! S^(1/3)*d_beta/r_s + REAL(r8), INTENT(IN) :: Q_e_in ! normalised electron diamag. + REAL(r8), INTENT(IN) :: Q_i_in ! normalised ion diamag. + REAL(r8) :: slayer_d_norm + REAL(r8) :: iota ! Q_e/(Q_e - Q_i) + + IF (Q_e_in == Q_i_in) THEN + WRITE(*,*) 'ERROR: Q_e = Q_i =',Q_e_in, + $ ', so iota_e = Q_e/(Q_e - Q_i) is undefined.' + STOP 'slayer_d_norm: Q_e equals Q_i' + END IF + iota = Q_e_in / (Q_e_in - Q_i_in) + IF (.NOT. (iota > 0.0_r8)) THEN + WRITE(*,*) 'ERROR: iota_e = Q_e/(Q_e - Q_i) =',iota, + $ ' is not positive (Q_e =',Q_e_in,', Q_i =',Q_i_in,').' + WRITE(*,*) ' D_norm = S^(1/3)*sqrt(iota_e)*d_beta/r_s is' + WRITE(*,*) ' undefined. The electron and ion diamagnetic' + WRITE(*,*) ' frequencies are expected to have opposite signs.' + STOP 'slayer_d_norm: iota_e is not positive' + END IF + slayer_d_norm = D_fac_in * SQRT(iota) + + RETURN + END FUNCTION slayer_d_norm END MODULE params_mod \ No newline at end of file diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 88de29ba9..ed8f03566 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -73,6 +73,7 @@ MODULE sglobal_mod REAL(r8) :: ds ! normalised ion Larmor radius REAL(r8) :: c_beta ! compressional beta parameter REAL(r8) :: D_norm ! normalised beta-weighted ion scale + REAL(r8) :: D_fac ! D_norm / sqrt(iota_e) REAL(r8) :: delta_n ! Delta normalisation factor REAL(r8) :: Qconv ! frequency normalisation (Cole) c --- diamagnetic and rotation frequencies @@ -161,6 +162,7 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: c_beta_arr(:) ! compressional beta REAL(r8), ALLOCATABLE :: d_beta_arr(:) ! beta-related width REAL(r8), ALLOCATABLE :: D_norm_arr(:) ! normalised D + REAL(r8), ALLOCATABLE :: D_fac_arr(:) ! D / sqrt(iota_e) REAL(r8), ALLOCATABLE :: tau_arr(:) ! T_i / T_e REAL(r8), ALLOCATABLE :: P_perp_arr(:) ! perp Prandtl number REAL(r8), ALLOCATABLE :: P_tor_arr(:) ! toroidal Prandtl diff --git a/slayer/slayer.f b/slayer/slayer.f index b4f0347ee..2a90cc4f6 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -518,6 +518,13 @@ PROGRAM slayer sl_in%Q_i_arr(k) = inQ_i_ovr(k) END IF END DO +c D_norm carries sqrt(iota_e): keep it consistent with the +c overridden Q_e, Q_i. + DO k = 1, n_k + sl_in%D_norm_arr(k) = slayer_d_norm( + $ sl_in%D_fac_arr(k),sl_in%Q_e_arr(k), + $ sl_in%Q_i_arr(k)) + END DO ELSE ! Single-surface mode: build inputs from namelist. @@ -550,6 +557,8 @@ PROGRAM slayer END IF IF (inds > 0.0) THEN D_norm = inds ! NAMELIST + ELSE + D_norm = slayer_d_norm(D_fac,Q_e,Q_i) END IF CALL allocate_inputs(n_k,sl_in) ! growthrates_mod @@ -570,6 +579,7 @@ PROGRAM slayer sl_in%P_tor_arr = (/ P_tor /) sl_in%tau_arr = (/ tau /) sl_in%D_norm_arr = (/ D_norm /) + sl_in%D_fac_arr = (/ D_fac /) sl_in%d_beta_arr = (/ d_beta /) sl_in%gammafac_arr = (/ gamma_fac /) sl_in%c_beta_arr = (/ c_beta /) @@ -642,6 +652,13 @@ PROGRAM slayer sl_in%Q_i_arr(k) = inQ_i_ovr(k) END IF END DO +c D_norm carries sqrt(iota_e): keep it consistent with the +c overridden Q_e, Q_i. + DO k = 1, n_k + sl_in%D_norm_arr(k) = slayer_d_norm( + $ sl_in%D_fac_arr(k),sl_in%Q_e_arr(k), + $ sl_in%Q_i_arr(k)) + END DO ELSE n_k = 1 @@ -677,6 +694,8 @@ PROGRAM slayer END IF IF (inds > 0.0) THEN D_norm = inds ! NAMELIST + ELSE + D_norm = slayer_d_norm(D_fac,Q_e,Q_i) END IF IF (.NOT. est_gamma_flag) THEN @@ -699,6 +718,7 @@ PROGRAM slayer sl_in%P_tor_arr = (/ P_tor /) sl_in%tau_arr = (/ tau /) sl_in%D_norm_arr = (/ D_norm /) + sl_in%D_fac_arr = (/ D_fac /) sl_in%d_beta_arr = (/ d_beta /) sl_in%gammafac_arr = (/ gamma_fac /) sl_in%c_beta_arr = (/ c_beta /) @@ -1048,6 +1068,7 @@ PROGRAM slayer sl_in%P_tor_arr = (/ P_tor /) sl_in%tau_arr = (/ tau /) sl_in%D_norm_arr = (/ D_norm /) + sl_in%D_fac_arr = (/ D_fac /) sl_in%d_beta_arr = (/ d_beta /) sl_in%gammafac_arr = (/ gamma_fac /) sl_in%c_beta_arr = (/ c_beta /) From ffa95f13ad492bb74097f4af3298617a8c8b982a Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:32:32 -0400 Subject: [PATCH 175/305] SLAYER - BUGFIX - Use the shear magnitude in the layer timescales and widths params() used the signed shear in the Alfven time, the long-mean-free- path parallel diffusivity, the island-width proxy W_d and the 'lar' critical Delta. On a reverse-shear surface that made the Alfven time and Lundquist number negative, so S^(1/3) and every quantity built on it was NaN. These are magnitudes and now use ABS(sval). No change for positive shear. Namelist-driven check at sval = -1.5: NaN before; now the same root as sval = +1.5. Same defect and fix as OpenFUSIONToolkit/GPEC#431. Co-Authored-By: Claude Opus 5.5 --- slayer/params.f | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/slayer/params.f b/slayer/params.f index 8e7254e3f..b8b93083a 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -124,7 +124,10 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, d_i = ( (mu_i*m_p) / (n_e*(chag**2)*mu0) )**0.5 ! ion skin depth [m] - tau_h = R0*(mu0*rho)**0.5 / (nn*sval*bt) ! Alfvén time [s] +c The layer timescales and widths below depend on the magnitude of +c the shear, not its sign: ABS(sval) keeps them positive on a +c reverse-shear surface. + tau_h = R0*(mu0*rho)**0.5 / (nn*ABS(sval)*bt) ! Alfvén time [s] tau_r = mu0*(rs**2.0)*sigma_par ! resistive time [s] (Fitzpatrick) IF (pr > 0.0_r8) THEN tau_v = tau_r / pr ! viscous time [s] @@ -222,11 +225,11 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, Wd = 0.1 ! initial guess DO wit = 1, 100 chi_par_lmfp = (2.0*R0*vte) - $ / (SQRT(pi)*nr*sval*Wd) + $ / (SQRT(pi)*nr*ABS(sval)*Wd) chi_par = (chi_par_smfp*chi_par_lmfp) $ / (chi_par_smfp + chi_par_lmfp) Wd_new = SQRT(8.0)*((chis(1)/chi_par)**0.25) - $ * (1.0/SQRT((rs/R0)*sval*nr)) + $ * (1.0/SQRT((rs/R0)*ABS(sval)*nr)) IF (ABS(Wd_new - Wd) / MAX(ABS(Wd), 1.0e-30_r8) $ < 1.0e-10_r8) THEN Wd = Wd_new @@ -243,7 +246,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, CASE('lar') dc_tmp = 0.5*(-dr_val)*(pi**1.5) $ *((chi_par/chis(1))**0.25) - $ *( (nr*sval)/(R0*rs) )**0.5 + $ *( (nr*ABS(sval))/(R0*rs) )**0.5 CASE('rfitzp') dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val) / Wd CASE('toroidal') From d43cfb53b1681d157757f9493dbaab11f2f9f839 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:32:32 -0400 Subject: [PATCH 176/305] SLAYER - BUGFIX - Carry the toroidal mode number into the diamagnetic frequencies and layer parameters Three places lost the toroidal mode number n: - build_inputs computed omega_*e and omega_*i as (dp/dpsi)/(e n_e), the n = 1 value. omega_* = k_theta v_* = n (dp/dpsi)/(e n_e), so Q_e and Q_i were low by a factor n. Same defect and fix as OpenFUSIONToolkit/GPEC#433. - build_inputs declared a local array named nn, hiding the module-level nn that params() uses for the Alfven time. The layer parameters therefore used the slayer.in value of nn (zero if unset) while W_d used STRIDE's n. The module-level nn, nr, mr and the output file-name suffix are now set from the STRIDE run, with a warning if slayer.in disagrees. - The real copies mr, nr were only set in the est_gamma and params_flag branches, so a namelist-driven match_gamma run without est_gamma hit nr = 0 and stopped in the W_d iteration. They are now set once after the namelist read. No change at n = 1. Check at n = 2 on the DIII-D-like example: Q_e/tau_k at the q = 2 surface is 21074 rad/s against 10537 rad/s at n = 1. Co-Authored-By: Claude Opus 5.5 --- slayer/layerinputs.f | 35 +++++++++++++++++++++++++++++------ slayer/slayer.f | 6 ++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 7b4a2da11..70b20b256 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -357,7 +357,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr, te_arr, $ ni_arr, ti_arr, zeff_arr, bt_arr, rs_arr, R0_arr, $ mu_i_arr, omegas_e_arr, omegas_i_arr - INTEGER, DIMENSION(:), ALLOCATABLE :: nn, resm, nns_arr +c nn_arr is STRIDE's toroidal mode number. It must not be named nn: +c that would hide the module-level nn that params() reads. + INTEGER, DIMENSION(:), ALLOCATABLE :: nn_arr, resm, nns_arr c --- surface-integral workspace INTEGER :: msing, i, mthsurf ! surface count, loop idx, theta pts REAL(r8), DIMENSION(0:512) :: unitfun ! unit function for area integral @@ -380,7 +382,22 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) CALL read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational, $ psi_n_rational,dgeo,shear,r_o,my_bt0,my_psio,dr_vals, - $ di_vals,mpsi_arr,nn,resm) + $ di_vals,mpsi_arr,nn_arr,resm) + +c The toroidal mode number of the STRIDE run defines the layer +c problem: it sets the module-level nn used by params() (Alfven +c time) and by the output file names, whatever slayer.in holds. + IF (nn /= nn_arr(1) .AND. nn /= 0) THEN + WRITE(*,'(1x,a,i0,a,i0)') 'WARNING: slayer.in nn = ',nn, + $ ' replaced by the STRIDE toroidal mode number ',nn_arr(1) + END IF + nn = nn_arr(1) + IF (nn < 10) THEN + WRITE(UNIT=sn_str,FMT='(I1)') nn + sn_str = ADJUSTL(sn_str) + ELSE + WRITE(UNIT=sn_str,FMT='(I2)') nn + ENDIF mpsi = INT(mpsi_arr(1)) mthsurf = 512 ! poloidal segments for surface integral @@ -473,11 +490,16 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) CALL spline_eval(kin,respsi,1) c diamagnetic frequencies (rad/s) from GPEC kinetic splines. -c These compute the ELECTRON diamagnetic frequency directly. +c omega_* = k_theta*v_* = (m/r)*(dp/dr)/(e*n*B). In flux +c coordinates dpsi/dr = r*B_phi/q absorbs the q of m = n*q and +c leaves omega_* = n*(dp/dpsi)/(e*n_e): the toroidal mode +c number multiplies the n = 1 expression. omega_e = twopi*kin%f(4)*kin%f1(2)/(e*chi1*kin%f(2)) $ +twopi*kin%f1(4)/(e*chi1) omega_i = -twopi*kin%f(3)*kin%f1(1)/(e*zi*chi1*kin%f(1)) $ -twopi*kin%f1(3)/(e*zi*chi1) + omega_e = REAL(nn,r8)*omega_e + omega_i = REAL(nn,r8)*omega_i sl_in%omegas_e_arr(ising) = omega_e sl_in%omegas_i_arr(ising) = omega_i @@ -542,9 +564,10 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) mu_i_arr(ising) = mu_i mms = resm(ising) - nns = nn(1) - nns_arr(ising) = nn(1) - nr = nn(1) ! module-level toroidal mode number + nns = nn + nns_arr(ising) = nn + nr = REAL(nn,r8) ! module-level mode numbers (real) + mr = REAL(mms,r8) l_n = 0.0 l_t = 0.0 diff --git a/slayer/slayer.f b/slayer/slayer.f index 2a90cc4f6..38326c932 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -339,6 +339,12 @@ PROGRAM slayer READ(in_unit,NML=slayer_diagnose) CLOSE(UNIT=in_unit) + ! Real copies of the mode numbers, read by params() in every + ! namelist-driven branch below. build_inputs resets them from + ! the STRIDE run when read_eq is set. + mr = mm + nr = nn + ! Build toroidal-mode-number string for output filenames. IF (nn<10) THEN WRITE(UNIT=sn_str,FMT='(I1)') nn From 8e59462715e249b9e2fa7a286830b66e8482004f Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 9 Oct 2026 19:35:38 -0400 Subject: [PATCH 177/305] SLAYER - NEW FEATURE - Doppler-shift the coupled determinant by the ExB rotation The coupled determinant evaluated every surface's layer at the same frame frequency, as if the surfaces rotated together. The layer's Q is the mode frequency in the local ExB frame, so with one lab-frame eigenvalue surface k is evaluated at Q_k = Q*tau_k/tau_1 - tau_k*n*Omega_E,k using the ExB rotation already read from the kinetic file. Applied in coupled mode only (rotation_flag, default true): an uncoupled layer is solved in its own frame, where the shift only relabels the frequency. The coupled root is therefore reported in the lab frame. The scan window gets a Re(Q) centre (scan_center). Left unset, a coupled scan with rotation is centred on the Doppler-shifted branches, and a warning is printed when they do not all fit in the window. The uncoupled scan warns when -Q_e lies outside the window. Q_shift is written to the NetCDF file. Check with a real, diagonal Delta' on the DIII-D-like example: the coupled roots equal the uncoupled roots shifted by tau_1*n*Omega_E,k to 6 digits. With the full matrix the 2/1 root moves from -10.52 to +31.00 krad/s, with the growth rate unchanged at 79.5 1/s. Follows OpenFUSIONToolkit/GPEC#463. Co-Authored-By: Claude Opus 5.5 --- slayer/growthrates.f | 14 ++++++-- slayer/layerinputs.f | 4 ++- slayer/sglobal.f | 3 +- slayer/slayer.f | 75 ++++++++++++++++++++++++++++++++++++++++-- slayer/slayer_netcdf.f | 10 ++++++ 5 files changed, 98 insertions(+), 8 deletions(-) diff --git a/slayer/growthrates.f b/slayer/growthrates.f index 5324c1b1b..ddcb19731 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -96,7 +96,8 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k), $ sl_in%Re_dp_psi_arr(n_k),sl_in%Im_dp_psi_arr(n_k), $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k), - $ sl_in%D_fac_arr(n_k)) + $ sl_in%D_fac_arr(n_k),sl_in%q_shift_arr(n_k)) + sl_in%q_shift_arr = 0.0_r8 c namelist inputs are taken as already r_s-referenced: identity c reference-length conversion (K = 1). sl_in%k_ref_arr = 1.0_r8 @@ -264,6 +265,12 @@ FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c eigenvalue reaches surface k as tau_k*(gamma + i*omega) c = g_in*tau_k/tau_1, the normalisation its Q_e and Q_i use. g_tmp = g_in*(tauk/sl_in%Qconv_arr(1)) +c The layer's Q is the mode frequency in the local ExB +c frame, while the coupled scan runs in the lab frame: +c Q_layer = tau_k*omega_lab - tau_k*n*Omega_E. q_shift_arr +c holds that real offset (zero unless the driver sets it), +c and g = i*Q. + g_tmp = g_tmp + ifac*sl_in%q_shift_arr(k) delta_Q(k,k)=riccati_f() delta_Q(k,k)=delta_Q(k,k)* $ sl_in%lu_arr(k)**(1.0_r8/3.0_r8) @@ -386,7 +393,7 @@ END SUBROUTINE get_or_compute_v2 c----------------------------------------------------------------------- SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, $ scan_width, Q_num, AMR_passes, - $ coupling_flag) + $ coupling_flag, scan_center) IMPLICIT NONE @@ -396,6 +403,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, INTEGER, INTENT(IN) :: Q_num ! grid points per axis INTEGER, INTENT(IN) :: AMR_passes ! refinement passes REAL(r8), INTENT(IN) :: scan_width ! half-width of Re/Im scan window + REAL(r8), INTENT(IN) :: scan_center ! Re(Q) centre of the window TYPE(slayer_inputs_type), INTENT(IN) :: sl_in LOGICAL, INTENT(IN) :: coupling_flag ! coupled dispersion_det? c --- locals @@ -438,7 +446,7 @@ SUBROUTINE dispersion_AMR_v2(n_k, sl_in, msing_max, DO i = 1, Q_num - 1 DO j = 1, Q_num - 1 - x = -scan_width + DBLE(i-1) * step + x = scan_center - scan_width + DBLE(i-1) * step y = -scan_width + DBLE(j-1) * step n_amr_cells = n_amr_cells + 1 diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 70b20b256..91081f19b 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -416,7 +416,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%c_beta_arr(msing), sl_in%lu_arr(msing), $ sl_in%Qconv_arr(msing), $ sl_in%Re_dp_psi_arr(msing), sl_in%Im_dp_psi_arr(msing), - $ sl_in%k_ref_arr(msing), sl_in%alpha_arr(msing)) + $ sl_in%k_ref_arr(msing), sl_in%alpha_arr(msing), + $ sl_in%q_shift_arr(msing)) + sl_in%q_shift_arr = 0.0_r8 c --- allocate local kinetic arrays (for future NetCDF diagnostic output) ALLOCATE(ne_arr(msing), te_arr(msing), ni_arr(msing), diff --git a/slayer/sglobal.f b/slayer/sglobal.f index ed8f03566..7454aa930 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -166,7 +166,8 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: tau_arr(:) ! T_i / T_e REAL(r8), ALLOCATABLE :: P_perp_arr(:) ! perp Prandtl number REAL(r8), ALLOCATABLE :: P_tor_arr(:) ! toroidal Prandtl - REAL(r8), ALLOCATABLE :: omegas_arr(:) ! rotation [rad/s] + REAL(r8), ALLOCATABLE :: omegas_arr(:) ! ExB rotation per unit n [rad/s] + REAL(r8), ALLOCATABLE :: q_shift_arr(:) ! ExB Doppler shift of Q REAL(r8), ALLOCATABLE :: omegas_e_arr(:) ! omega_e [rad/s] REAL(r8), ALLOCATABLE :: omegas_i_arr(:) ! omega_i [rad/s] REAL(r8), ALLOCATABLE :: gammafac_arr(:) ! gamma conversion diff --git a/slayer/slayer.f b/slayer/slayer.f index 38326c932..03f1d075a 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -54,6 +54,7 @@ PROGRAM slayer LOGICAL :: match_gamma_flag ! asymptotically matched gamma LOGICAL :: Pperp_Ptor_flag ! use Fitzpatrick layer model LOGICAL :: coupling_flag ! coupled rational surfaces + LOGICAL :: rotation_flag ! ExB Doppler shift when coupled LOGICAL :: br_th_flag ! Br threshold test scan LOGICAL :: bal_flag ! torque balance scan LOGICAL :: stability_flag ! complex-Q delta scan @@ -101,6 +102,8 @@ PROGRAM slayer REAL(r8) :: dr_val ! radial derivative parameter REAL(r8) :: dgeo_val ! geometric factor parameter REAL(r8) :: scan_width ! half-width of complex-Q scan + REAL(r8) :: scan_center ! Re(Q) centre of complex-Q scan + REAL(r8) :: q_lo,q_hi,q_br ! expected coupled-branch Re(Q) c----------------------------------------------------------------------- c local scalars — normalized layer parameters (namelist overrides). c----------------------------------------------------------------------- @@ -193,6 +196,7 @@ PROGRAM slayer $ inds,intau,Q0,delta_prime,delta_n_p,ingamma, $ inQ_e_ovr,inQ_i_ovr NAMELIST/slayer_control/inum,jnum,knum,Q_num,scan_width, + $ scan_center,rotation_flag, $ AMR_passes,msing_max,dc_type,read_eq,Pperp_Ptor_flag, $ coupling_flag,QPscan_flag,Qscan_flag,QPescan_flag, $ Qbscan_flag,onscan_flag,otscan_flag,ntscan_flag, @@ -266,6 +270,10 @@ PROGRAM slayer knum = 100 ! 2-D scan axis-2 resolution Q_num = 100 ! stability scan Re(Q) resolution scan_width = 2.0 ! half-width for complex-Q scans + ! Re(Q) centre of the scans. Left at HUGE, it is chosen by the + ! code: zero, or the middle of the Doppler-shifted branches in + ! a coupled scan with rotation. + scan_center = HUGE(scan_center) AMR_passes = 4 ! AMR refinement levels msing_max = 2 ! max singular surfaces to process @@ -284,6 +292,7 @@ PROGRAM slayer match_gamma_flag = .FALSE. Pperp_Ptor_flag = .FALSE. coupling_flag = .FALSE. + rotation_flag = .TRUE. params_flag = .TRUE. input_flag = .FALSE. @@ -734,6 +743,53 @@ PROGRAM slayer END IF END IF +c----------------------------------------------------------------------- +c ExB Doppler shift and scan-window centre. +c Each uncoupled layer is solved in its own ExB frame, where a +c rotation only relabels the frequency, so no shift is applied. +c In the coupled determinant the surfaces share one lab-frame +c eigenvalue and surface k sees it Doppler-shifted by +c n*Omega_E,k: q_shift = -tau_k*n*Omega_E,k. In the scanned Q +c (normalised by tau_1) branch k then sits near +c tau_1*(n*Omega_E,k - Q_e,k/tau_k), taking the electron-drift +c frequency as the estimate of its own-frame root. +c----------------------------------------------------------------------- + sl_in%q_shift_arr = 0.0_r8 + IF (coupling_flag .AND. rotation_flag .AND. read_eq) THEN + q_lo = HUGE(q_lo) + q_hi = -HUGE(q_hi) + DO k = 1, MIN(n_k,msing_max) + sl_in%q_shift_arr(k) = -sl_in%Qconv_arr(k) + $ * REAL(nn,r8) * sl_in%omegas_arr(k) + q_br = sl_in%Qconv_arr(1)*( REAL(nn,r8) + $ * sl_in%omegas_arr(k) + $ - sl_in%Q_e_arr(k)/sl_in%Qconv_arr(k) ) + q_lo = MIN(q_lo,q_br) + q_hi = MAX(q_hi,q_br) + WRITE(*,'(A,I0,A,ES11.4,A,ES11.4)') + $ ' ExB rotation, surface ',k,': n*Omega_E [rad/s] =', + $ REAL(nn,r8)*sl_in%omegas_arr(k), + $ ', branch expected near Re(Q) =',q_br + END DO + IF (scan_center >= HUGE(scan_center)) + $ scan_center = 0.5_r8*(q_lo + q_hi) + IF (q_lo < scan_center - scan_width .OR. + $ q_hi > scan_center + scan_width) THEN + WRITE(*,'(A)') ' WARNING: the Doppler-shifted branches' + $ //' do not all fit in the scan window' + WRITE(*,'(A,2ES11.3,A,2ES11.3,A)') + $ ' Re(Q) = [',scan_center-scan_width, + $ scan_center+scan_width,'], branches span [', + $ q_lo,q_hi,'].' + WRITE(*,'(A)') ' Increase scan_width or set ' + $ //'scan_center, or a coupled root may be missed.' + END IF + END IF + IF (scan_center >= HUGE(scan_center)) scan_center = 0.0_r8 + WRITE(*,'(A,ES11.4,A,ES11.4)') + $ ' Scan window: Re(Q) centre =',scan_center, + $ ', half-width =',scan_width + c----------------------------------------------------------------------- c allocate output arrays for AMR delta storage. c----------------------------------------------------------------------- @@ -802,8 +858,17 @@ PROGRAM slayer WRITE(*,'(A,I0,A)') 'Calling uncoupled AMR scan on q = ', $ sl_in%qval_arr(k),' rational surface:' + ! The tearing root usually sits near the electron + ! drift frequency, Re(Q) = -Q_e. + IF (ABS(-Q_e - scan_center) > scan_width) THEN + WRITE(*,'(A,ES10.3,A)') + $ ' WARNING: Re(Q) = -Q_e =',-Q_e, + $ ' is outside the scan window.' + WRITE(*,'(A)') ' Increase scan_width or set ' + $ //'scan_center, or the root may be missed.' + END IF CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, - $ Q_num,AMR_passes,coupling_flag) + $ Q_num,AMR_passes,coupling_flag,scan_center) ! Zeros of the dispersion function in this surface's ! own ExB frame; the most unstable one is reported as @@ -931,7 +996,7 @@ PROGRAM slayer IF (AMR_flag .AND. coupling_flag) THEN CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, - $ Q_num,AMR_passes,coupling_flag) + $ Q_num,AMR_passes,coupling_flag,scan_center) ! Zeros of the coupled determinant. Q is normalised by ! the first surface's tau_k. The coupled eigenvalue is @@ -993,8 +1058,12 @@ PROGRAM slayer DO i = 1, (Q_num+1) DO j = 1, Q_num + ! This scan passes g = gamma_hat + i*omega_hat + ! directly, so its imaginary axis is the frequency + ! and carries the scan-window centre. ing_coarse = -scan_width + (i - 1) * ing_step - iing_coarse = -scan_width + (j - 1) * ing_step + iing_coarse = scan_center - scan_width + $ + (j - 1) * ing_step inQs(i) = ing_coarse iinQs(j) = iing_coarse diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 85fd44231..d45e20af7 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -117,6 +117,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: dpp_id ! "Delta_prime_rational" (complex) INTEGER :: dprs_id ! "Delta_prime_rs_rational" (complex) INTEGER :: kref_id ! "k_ref" + INTEGER :: qsh_id ! "Q_shift" INTEGER :: alpha_id ! "alpha_mercier" INTEGER :: dc_id ! "Delta_crit_rational" INTEGER :: dels_db_id ! "delta_s_d_b" (complex) @@ -297,6 +298,13 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ nf90_double, qsing_dim, qr_id) ) CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, $ qsing_dim, omegas_id) ) + CALL sl_check( nf90_put_att(ncid, omegas_id, "long_name", + $ "ExB rotation frequency per unit n [rad/s]") ) + CALL sl_check( nf90_def_var(ncid, "Q_shift", nf90_double, + $ qsing_dim, qsh_id) ) + CALL sl_check( nf90_put_att(ncid, qsh_id, "long_name", + $ "ExB Doppler shift -tau_k*n*Omega_E applied to Q in "// + $ "the coupled determinant (zero when not applied)") ) CALL sl_check( nf90_def_var(ncid, "tau_k", nf90_double, $ qsing_dim, qc_id) ) CALL sl_check( nf90_def_var(ncid, "Q_e", nf90_double, @@ -435,6 +443,8 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ sl_in%omegas_arr)) CALL sl_check( nf90_put_var(ncid, S_id, $ sl_in%lu_arr) ) + CALL sl_check( nf90_put_var(ncid, qsh_id, + $ sl_in%q_shift_arr) ) CALL sl_check( nf90_put_var(ncid, qc_id, $ sl_in%Qconv_arr) ) CALL sl_check( nf90_put_var(ncid, Q_e_id, From de9fa96013eb61183b2df7d74ecebdd155196e4c Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 9 Oct 2026 21:47:58 -0700 Subject: [PATCH 178/305] RDCON - BUGFIX - Galerkin F built from the factored spline used by the asymptotics gal_get_fkg took F from fmats_gal (cubic spline of Fbar) while the singular-surface series (sing_mmat/sing1_mmat) and the resonant lsode integrals use fmats (cubic spline of its Cholesky factor L). Between knots spline(L L^H) /= spline(L) spline(L)^H, so the big solution is not a solution of the Galerkin operator; the residual is singular at the surface and Delta' depended on nx, dx, cutoff, sing_order and gal_tol. Build F = (Q L)(Q L)^H from fmats instead. Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index 308dfa0ce..69cbf90de 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1016,6 +1016,7 @@ SUBROUTINE gal_get_fkg(psifac,f,k,g) COMPLEX(r8), DIMENSION(mpert,mpert), INTENT(OUT) :: f,k,g INTEGER :: ipert,jpert,iqty + COMPLEX(r8), DIMENSION(mpert,mpert) :: lmat REAL(r8) :: q REAL(r8), DIMENSION(mpert) :: singfac c----------------------------------------------------------------------- @@ -1025,20 +1026,19 @@ SUBROUTINE gal_get_fkg(psifac,f,k,g) q=sq%f(4) singfac=mlow-nn*q+(/(ipert,ipert=0,mpert-1)/) c----------------------------------------------------------------------- -c compute f. +c compute f = (Q L)(Q L)^H from the factored spline used by the +c asymptotic series, so the big solution solves this operator. c----------------------------------------------------------------------- - CALL cspline_eval(fmats_gal,psifac,0) + CALL cspline_eval(fmats,psifac,0) + lmat=0 iqty=1 DO jpert=1,mpert DO ipert=jpert,MIN(mpert,jpert+mband) - f(ipert,jpert)=fmats_gal%f(iqty) - $ *singfac(ipert)*singfac(jpert) - IF(ipert /= jpert)THEN - f(jpert,ipert)=CONJG(f(ipert,jpert)) - ENDIF + lmat(ipert,jpert)=fmats%f(iqty)*singfac(ipert) iqty=iqty+1 ENDDO ENDDO + f=MATMUL(lmat,CONJG(TRANSPOSE(lmat))) c----------------------------------------------------------------------- c compute k. c----------------------------------------------------------------------- From 8a57848295431a5265320299631a7b3a787c1e26 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 15:45:21 -0700 Subject: [PATCH 179/305] EQUIL - BUGFIX - ldp_i reads |f| (as efit does); document the i-file format With f < 0 (e.g. a TokaMaker equilibrium with B_t < 0) inverse_run built a negative q and stopped ('Invalid extrapolation near axis'). Flipping the sign of f in an i-file now gives identical results. Co-Authored-By: Claude Opus 5.5 --- equil/read_eq.f | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/equil/read_eq.f b/equil/read_eq.f index 3ad187791..e042ed87f 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -801,7 +801,13 @@ SUBROUTINE read_eq_ldp_d END SUBROUTINE read_eq_ldp_d c----------------------------------------------------------------------- c subprogram 12. read_eq_ldp_i. -c reads data from L. Don Pearlstein's inverse equilibrium code. +c reads data from L. Don Pearlstein's inverse equilibrium code, also +c written by OpenFUSIONToolkit TokaMaker (save_ifile). Sequential +c unformatted records, real*8, axis first, theta index fastest: +c mx,my (integer*4: number of surfaces, number of angles) +c psi(mx) [Wb/rad], f(mx) = R*B_t [T m], p(mx) [Pa], q(mx) +c r(my,mx), z(my,mx) [m], periodic point duplicated +c optional: f*df/dpsi(mx), dp/dpsi(mx) (not read here) c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. @@ -840,7 +846,7 @@ SUBROUTINE read_eq_ldp_i psio=psi(mx)-psi(0) CALL spline_alloc(sq_in,mx,4) sq_in%xs=(psi-psi(0))/psio - sq_in%fs(:,1)=f + sq_in%fs(:,1)=ABS(f) sq_in%fs(:,2)=p*mu0 sq_in%fs(:,3)=q if(psio.lt.0)psio=-psio From 1622a1e89f8e07b86f197e4617a4543682bb5377 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Thu, 8 Oct 2026 06:42:18 -0700 Subject: [PATCH 180/305] RDCON - BUGFIX - Delta-matrix output formats sized with i0 The delta_gw.out and related Delta-matrix headers used i2.2 for the number of singular surfaces. At 25 or more surfaces the repeat count overflowed to '**' and rdcon crashed writing delta_gw.out. Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index 308dfa0ce..ecf5de727 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1480,9 +1480,9 @@ SUBROUTINE gal_write_delta(delta,name) c----------------------------------------------------------------------- c format statements. c----------------------------------------------------------------------- - 10 FORMAT('(/1x,"i\j",',i2.2,'(4x,"re ",i1,a1,6x,"im ",i1,a1,2x)/)') - 20 FORMAT('(i3,a1,1p,',i2.2,'e11.3)') - 30 FORMAT('(i3,"c",1p,',i2.2,'e11.3)') + 10 FORMAT('(/1x,"i\j",',i0,'(4x,"re ",i1,a1,6x,"im ",i1,a1,2x)/)') + 20 FORMAT('(i3,a1,1p,',i0,'e11.3)') + 30 FORMAT('(i3,"c",1p,',i0,'e11.3)') c----------------------------------------------------------------------- c create formats. c----------------------------------------------------------------------- From ff47f012a1971fe45fd93d206560a3cfd3701176 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Wed, 7 Oct 2026 09:37:54 -0700 Subject: [PATCH 181/305] RDCON - BUGFIX - Stop on a failed Galerkin factorization (zgbtrf/zpbtrf info) gal_solve ignored info from zgbtrf/zpbtrf, so an indefinite matrix with solver='cholesky' gave a garbage Delta' with no warning (g147131 n=1, TPS settings: 2/1 = 9.7e6). Now program_stop with the info value. LU results unchanged (bit-identical Delta' on g147131). Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rdcon/gal.f b/rdcon/gal.f index 308dfa0ce..a6d0d24ff 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1399,6 +1399,7 @@ END SUBROUTINE gal_make_arrays SUBROUTINE gal_solve INTEGER :: info,isol,jsol,imap,ising,ix,msol_old + CHARACTER(128) :: message TYPE(gal_type) :: gal c----------------------------------------------------------------------- c allocate and compute arrays. @@ -1417,12 +1418,23 @@ SUBROUTINE gal_solve WRITE(*,*)"Performing Galerkin matrix LU factorization" CALL zgbtrf(gal%ndim,gal%ndim,gal%kl,gal%ku,gal%mat,gal%ldab, $ gal%ipiv,info) + IF (info /= 0) THEN + WRITE(message,'(a,i0,a)')"zgbtrf info = ",info, + $ ": Galerkin matrix is singular." + CALL program_stop(TRIM(message)) + ENDIF WRITE(*,*)"Calculating Galerkin matrix solution" CALL zgbtrs("N",gal%ndim,gal%kl,gal%ku,gal%nsol,gal%mat, $ gal%ldab,gal%ipiv,gal%sol,gal%ndim,info ) ELSEIF (solver == "cholesky") THEN WRITE(*,*)"Performing Galerkin matrix Cholesky factorization" CALL zpbtrf('L',gal%ndim,gal%kl,gal%mat,gal%ldab,info) + IF (info /= 0) THEN + WRITE(message,'(a,i0,a)')"zpbtrf info = ",info, + $ ": Galerkin matrix is not positive definite;" + $ //" use solver='LU'." + CALL program_stop(TRIM(message)) + ENDIF WRITE(*,*)"Calculating Galerkin matrix solution" CALL zpbtrs('L',gal%ndim,gal%kl,gal%nsol,gal%mat,gal%ldab, $ gal%sol,gal%ndim,info) From 0ac647af5f76ae3b1ed37c818fee63716a19713e Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Wed, 7 Oct 2026 09:54:06 -0700 Subject: [PATCH 182/305] RDCON - BUGFIX - Cholesky falls back to LU (with a warning) when the Galerkin matrix is not positive definite zpbtrf info was ignored, so solver='cholesky' on an indefinite matrix gave a garbage Delta' (g147131 n=1, TPS settings: 2/1 = 9.7e6). Now, when zpbtrf fails, gal_solve warns, sets solver='LU', reassembles the full band from the element matrices (gal_assemble_mat) and solves it by LU (2/1 = 2.1359; solver='LU' gives 2.1296, it does not zero the boundary columns). zgbtrf failure stops the run. solver='LU' runs and Cholesky runs that succeed are bit-identical. Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index a6d0d24ff..b9fb65f1f 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1414,6 +1414,33 @@ SUBROUTINE gal_solve c----------------------------------------------------------------------- gal%sol=gal%rhs WRITE(*,*)"Grid generated with ",gal%ndim," DOF" +c----------------------------------------------------------------------- +c Cholesky; if the matrix is not positive definite, warn and +c reassemble the full band from the element matrices for LU. +c The boundary rows and columns are already set in cell%mat. +c----------------------------------------------------------------------- + IF (solver == "cholesky") THEN + WRITE(*,*)"Performing Galerkin matrix Cholesky factorization" + CALL zpbtrf('L',gal%ndim,gal%kl,gal%mat,gal%ldab,info) + IF (info /= 0) THEN + WRITE(*,'(a,i0,a)')" WARNING: zpbtrf info = ",info, + $ ": Galerkin matrix is not positive definite;" + $ //" switching to solver='LU'." + solver="LU" + gal%ldab=2*gal%kl+gal%ku+1 + DEALLOCATE(gal%mat) + ALLOCATE(gal%mat(gal%ldab,gal%ndim)) + gal%mat=0 + CALL gal_assemble_mat(gal) + ELSE + WRITE(*,*)"Calculating Galerkin matrix solution" + CALL zpbtrs('L',gal%ndim,gal%kl,gal%nsol,gal%mat,gal%ldab, + $ gal%sol,gal%ndim,info) + ENDIF + ENDIF +c----------------------------------------------------------------------- +c LU. +c----------------------------------------------------------------------- IF (solver == "LU") THEN WRITE(*,*)"Performing Galerkin matrix LU factorization" CALL zgbtrf(gal%ndim,gal%ndim,gal%kl,gal%ku,gal%mat,gal%ldab, @@ -1426,18 +1453,6 @@ SUBROUTINE gal_solve WRITE(*,*)"Calculating Galerkin matrix solution" CALL zgbtrs("N",gal%ndim,gal%kl,gal%ku,gal%nsol,gal%mat, $ gal%ldab,gal%ipiv,gal%sol,gal%ndim,info ) - ELSEIF (solver == "cholesky") THEN - WRITE(*,*)"Performing Galerkin matrix Cholesky factorization" - CALL zpbtrf('L',gal%ndim,gal%kl,gal%mat,gal%ldab,info) - IF (info /= 0) THEN - WRITE(message,'(a,i0,a)')"zpbtrf info = ",info, - $ ": Galerkin matrix is not positive definite;" - $ //" use solver='LU'." - CALL program_stop(TRIM(message)) - ENDIF - WRITE(*,*)"Calculating Galerkin matrix solution" - CALL zpbtrs('L',gal%ndim,gal%kl,gal%nsol,gal%mat,gal%ldab, - $ gal%sol,gal%ndim,info) ENDIF c----------------------------------------------------------------------- c compute and write delta from small resonant coefficients. From d10b6be9a225f12b55cc58c745afd92787c78a3f Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 9 Oct 2026 21:47:59 -0700 Subject: [PATCH 183/305] RDCON, DCON - BUGFIX - Taylor-slot typos in sing_mmat (F 6th order in slot 6, DCON G 3rd order from G''') sing_mmat (rdcon/sing.f, dcon/sing.f) and sing1_mmat (rdcon/sing1.f) wrote the 6th derivative term of Q*Lbar (20*Q'''*Lbar''') into f(:,:,5), overwriting the 5th-order term and leaving the 6th zero, whenever the series order is >= 6 (in RDCON with sing_order_ceiling, the default, that is sing_order + ceil(2 Re alpha)). The K term and stride/sing.F already use slot 6. dcon/sing.f also set the 3rd-order G term from gmats%f2/6 instead of gmats%f3/6 whenever sing_order >= 3. RDCON (sing_mmat, sing1_mmat) and STRIDE use f3. Co-Authored-By: Claude Opus 5.5 --- dcon/sing.f | 4 ++-- rdcon/sing.f | 2 +- rdcon/sing1.f | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dcon/sing.f b/dcon/sing.f index 5ffb77e86..9272fcc63 100644 --- a/dcon/sing.f +++ b/dcon/sing.f @@ -449,7 +449,7 @@ SUBROUTINE sing_mmat(ising) $ =10*singfac(ipert,2)*fmats%f3(iqty) $ +10*singfac(ipert,3)*fmats%f2(iqty) IF(sing_order < 6)CYCLE - f(1+ipert-jpert,jpert,5) + f(1+ipert-jpert,jpert,6) $ =20*singfac(ipert,3)*fmats%f3(iqty) ENDDO ENDDO @@ -530,7 +530,7 @@ SUBROUTINE sing_mmat(ising) IF(sing_order < 2)CYCLE g(1+ipert-jpert,jpert,2)=gmats%f2(iqty)/2 IF(sing_order < 3)CYCLE - g(1+ipert-jpert,jpert,3)=gmats%f2(iqty)/6 + g(1+ipert-jpert,jpert,3)=gmats%f3(iqty)/6 ENDDO ENDDO c----------------------------------------------------------------------- diff --git a/rdcon/sing.f b/rdcon/sing.f index 8be2e7470..76c3cf672 100644 --- a/rdcon/sing.f +++ b/rdcon/sing.f @@ -507,7 +507,7 @@ SUBROUTINE sing_mmat(ising) $ =10*singfac(ipert,2)*fmats%f3(iqty)*sig $ +10*singfac(ipert,3)*fmats%f2(iqty) IF(singp%order < 6)CYCLE - f(1+ipert-jpert,jpert,5) + f(1+ipert-jpert,jpert,6) $ =20*singfac(ipert,3)*fmats%f3(iqty)*sig ENDDO ENDDO diff --git a/rdcon/sing1.f b/rdcon/sing1.f index f879e7b83..50af956ed 100644 --- a/rdcon/sing1.f +++ b/rdcon/sing1.f @@ -171,7 +171,7 @@ SUBROUTINE sing1_mmat(ising) $ =10*singfac(ipert,2)*fmats%f3(iqty) $ +10*singfac(ipert,3)*fmats%f2(iqty) IF(singp%order < 6)CYCLE - f(1+ipert-jpert,jpert,5) + f(1+ipert-jpert,jpert,6) $ =20*singfac(ipert,3)*fmats%f3(iqty) ENDDO ENDDO From 49091c8ca245d4d362811e6de4fd7a47f78f2303 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 20:28:32 -0700 Subject: [PATCH 184/305] DCON, RDCON, STRIDE - BUGFIX - Trace-aware Frobenius exponents at resonant surfaces The Frobenius powers at a resonant surface are the eigenvalues tr/2 -+ SQRT(tr**2/4-det) of the 2x2 resonant block of M0. The codes used -+ SQRT(-det), which is only right for a traceless block. On up-down asymmetric equilibria the block has a small imaginary trace, and the wrong powers and zeroth-order eigenvectors give a spurious imaginary Delta'. sing_vmat/sing1_vmat now store the eigenvalues in singp%power and build the eigenvectors from them. sing_get_ua, sing_get_dua, sing_get_ua_cut and the sing1 versions scale each column by its own power. The RDCON sing_order ceiling uses the exponent separation 2*Re SQRT(tr**2/4-det). singp%alpha keeps the Mercier value SQRT(-di). RDCON appends the two resonant powers to the end of each surface's header record in vmat.bin, and RMATCH uses them, so it stays consistent with the RDCON series. Older readers skip the appended values; for files without them RMATCH uses -alpha, +alpha, exactly as before. Port of OpenFUSIONToolkit/GPEC#517. Co-Authored-By: Claude Opus 5.5 --- dcon/sing.f | 22 +++++++++++++--------- rdcon/sing.f | 40 ++++++++++++++++++++-------------------- rdcon/sing1.f | 50 +++++++++++++++++++++++++------------------------- rmatch/msing.f | 24 +++++++++++++++++------- stride/sing.F | 20 ++++++++++++-------- 5 files changed, 87 insertions(+), 69 deletions(-) diff --git a/dcon/sing.f b/dcon/sing.f index 5ffb77e86..323217ab8 100644 --- a/dcon/sing.f +++ b/dcon/sing.f @@ -259,7 +259,7 @@ SUBROUTINE sing_vmat(ising) INTEGER :: ipert0,ipert,k REAL(r8) :: psifac,di,di0,q,q1,rho,dpsi REAL(r8), PARAMETER :: half=.5_r8 - COMPLEX(r8) :: det + COMPLEX(r8) :: det,tr,sqrtd TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- c allocate and initialize solution array. @@ -305,10 +305,15 @@ SUBROUTINE sing_vmat(ising) di=m0mat(1,1)*m0mat(2,2)-m0mat(2,1)*m0mat(1,2) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di)) +c----------------------------------------------------------------------- +c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c----------------------------------------------------------------------- + tr=m0mat(1,1)+m0mat(2,2) + sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) ALLOCATE(singp%power(2*mpert)) singp%power=0 - singp%power(ipert0)=-singp%alpha - singp%power(ipert0+mpert)=singp%alpha + singp%power(ipert0)=tr/2-sqrtd + singp%power(ipert0+mpert)=tr/2+sqrtd WRITE(out_unit,'(i3,1p,7e11.3)')ising,psifac,rho,q,q1,di0, $ singp%di,singp%di/di0-1 c----------------------------------------------------------------------- @@ -325,9 +330,9 @@ SUBROUTINE sing_vmat(ising) singp%vmat(ipert0,ipert0,1,0)=1 singp%vmat(ipert0,ipert0+mpert,1,0)=1 singp%vmat(ipert0,ipert0,2,0) - $ =-(m0mat(1,1)+singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-singp%power(ipert0))/m0mat(1,2) singp%vmat(ipert0,ipert0+mpert,2,0) - $ =-(m0mat(1,1)-singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-singp%power(ipert0+mpert))/m0mat(1,2) det=CONJG(singp%vmat(ipert0,ipert0,1,0)) $ *singp%vmat(ipert0,ipert0+mpert,2,0) $ -CONJG(singp%vmat(ipert0,ipert0+mpert,1,0)) @@ -764,7 +769,7 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) INTEGER :: iorder INTEGER, DIMENSION(:), POINTER :: r1,r2 - COMPLEX(r8) :: dpsi,pfac,sqrtfac + COMPLEX(r8) :: dpsi,sqrtfac COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -779,7 +784,6 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- dpsi=psifac-singp%psifac sqrtfac=SQRT(dpsi) - pfac=ABS(dpsi)**singp%alpha c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -792,8 +796,8 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)*ABS(dpsi)**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*ABS(dpsi)**singp%power(r2(2)) c----------------------------------------------------------------------- c renormalize. c----------------------------------------------------------------------- diff --git a/rdcon/sing.f b/rdcon/sing.f index 8be2e7470..8151fb277 100644 --- a/rdcon/sing.f +++ b/rdcon/sing.f @@ -76,7 +76,7 @@ SUBROUTINE sing_scan DO ising=1,msing singp => sing(ising) WRITE(debug_unit)singp%order,singp%alpha,singp%psifac, - $ singp%q,singp%r1,singp%r2 + $ singp%q,singp%r1,singp%r2,singp%power(singp%r2) WRITE(debug_unit)singp%vmatl,singp%vmatr ENDDO CLOSE(UNIT=debug_unit) @@ -252,6 +252,7 @@ SUBROUTINE sing_vmat(ising) INTEGER :: ipert0,ipert,k,iside REAL(r8) :: psifac,di,di0,q,q1,rho,sig REAL(r8), PARAMETER :: half=.5_r8 + COMPLEX(r8) :: tr,sqrtd COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat,mmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -300,11 +301,16 @@ SUBROUTINE sing_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di)) c----------------------------------------------------------------------- +c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c----------------------------------------------------------------------- + tr=m0mat(1,1)+m0mat(2,2) + sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) +c----------------------------------------------------------------------- c reset order and compute higher-order terms. c----------------------------------------------------------------------- singp%order=sing_order IF(sing_order_ceiling) - $ singp%order=singp%order+CEILING(2*REAL(singp%alpha)) + $ singp%order=singp%order+CEILING(2*REAL(sqrtd)) DEALLOCATE(singp%mmatl,singp%mmatr) CALL sing_mmat(ising) c----------------------------------------------------------------------- @@ -312,8 +318,8 @@ SUBROUTINE sing_vmat(ising) c----------------------------------------------------------------------- ALLOCATE(singp%power(2*mpert)) singp%power=0 - singp%power(ipert0)=-singp%alpha - singp%power(ipert0+mpert)=singp%alpha + singp%power(ipert0)=tr/2-sqrtd + singp%power(ipert0+mpert)=tr/2+sqrtd c----------------------------------------------------------------------- c write to table of singular surface quantities. c----------------------------------------------------------------------- @@ -354,9 +360,9 @@ SUBROUTINE sing_vmat(ising) vmat(ipert0,ipert0,1,0)=1 vmat(ipert0,ipert0+mpert,1,0)=1 vmat(ipert0,ipert0,2,0) - $ =-(m0mat(1,1)+sig*singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-sig*singp%power(ipert0))/m0mat(1,2) vmat(ipert0,ipert0+mpert,2,0) - $ =-(m0mat(1,1)-sig*singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-sig*singp%power(ipert0+mpert))/m0mat(1,2) c----------------------------------------------------------------------- c compute higher-order solutions. c----------------------------------------------------------------------- @@ -784,7 +790,6 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) INTEGER :: iorder INTEGER, DIMENSION(:), POINTER :: r1,r2 COMPLEX(r8) :: dpsi,sqrtfac - COMPLEX(r8) :: pfac COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -809,7 +814,6 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c compute powers. c----------------------------------------------------------------------- sqrtfac=SQRT(dpsi) - pfac=dpsi**singp%alpha c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -822,8 +826,8 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)*dpsi**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*dpsi**singp%power(r2(2)) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -845,7 +849,6 @@ SUBROUTINE sing_get_dua(ising,psifac,dua) INTEGER :: iorder,ipert0 INTEGER, DIMENSION(:), POINTER :: r1,r2 COMPLEX(r8) :: dpsi,sqrtfac,sig - COMPLEX(r8) :: pfac COMPLEX(r8), DIMENSION(mpert,2*mpert,2) :: power COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp @@ -873,7 +876,6 @@ SUBROUTINE sing_get_dua(ising,psifac,dua) c compute distance from singular surface and its powers. c----------------------------------------------------------------------- sqrtfac=SQRT(dpsi) - pfac=dpsi**singp%alpha c----------------------------------------------------------------------- c compute powers. c----------------------------------------------------------------------- @@ -881,8 +883,8 @@ SUBROUTINE sing_get_dua(ising,psifac,dua) power=2*singp%order power(r1,:,1)=power(r1,:,1)-1 power(r1,:,2)=power(r1,:,2)+1 - power(:,r2(1),:)=power(:,r2(1),:)-2*singp%alpha - power(:,r2(2),:)=power(:,r2(2),:)+2*singp%alpha + power(:,r2(1),:)=power(:,r2(1),:)+2*singp%power(r2(1)) + power(:,r2(2),:)=power(:,r2(2),:)+2*singp%power(r2(2)) c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -896,8 +898,8 @@ SUBROUTINE sing_get_dua(ising,psifac,dua) c----------------------------------------------------------------------- dua(r1,:,1)=dua(r1,:,1)/sqrtfac dua(r1,:,2)=dua(r1,:,2)*sqrtfac - dua(:,r2(1),:)=dua(:,r2(1),:)/pfac - dua(:,r2(2),:)=dua(:,r2(2),:)*pfac + dua(:,r2(1),:)=dua(:,r2(1),:)*dpsi**singp%power(r2(1)) + dua(:,r2(2),:)=dua(:,r2(2),:)*dpsi**singp%power(r2(2)) dua=dua*sig/(2*dpsi) c----------------------------------------------------------------------- c terminate. @@ -1244,7 +1246,6 @@ SUBROUTINE sing_get_ua_cut(ising,psifac,ua) INTEGER :: iorder INTEGER, DIMENSION(:), POINTER :: r1,r2 COMPLEX(r8) :: dpsi,sqrtfac - COMPLEX(r8) :: pfac COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -1269,7 +1270,6 @@ SUBROUTINE sing_get_ua_cut(ising,psifac,ua) c compute powers. c----------------------------------------------------------------------- sqrtfac=SQRT(dpsi) - pfac=dpsi**singp%alpha c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -1282,8 +1282,8 @@ SUBROUTINE sing_get_ua_cut(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)*dpsi**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*dpsi**singp%power(r2(2)) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/rdcon/sing1.f b/rdcon/sing1.f index f879e7b83..ac403b7ee 100644 --- a/rdcon/sing1.f +++ b/rdcon/sing1.f @@ -445,6 +445,7 @@ SUBROUTINE sing1_vmat(ising) INTEGER :: ipert0,ipert,k REAL(r8) :: psifac,di,di0,q,q1,rho REAL(r8), PARAMETER :: half=.5_r8 + COMPLEX(r8) :: tr,sqrtd COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat,mmat COMPLEX(r8), DIMENSION(:,:,:,:), ALLOCATABLE :: amat,bmat TYPE(sing_type), POINTER :: singp @@ -491,6 +492,11 @@ SUBROUTINE sing1_vmat(ising) di=m0mat(1,1)*m0mat(2,2)-m0mat(2,1)*m0mat(1,2) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di)) +c----------------------------------------------------------------------- +c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c----------------------------------------------------------------------- + tr=m0mat(1,1)+m0mat(2,2) + sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) c singp%degen=.TRUE. singp%degen=ABS(di+0.25_r8) < degen_tol c$$$ WRITE(*,*)'SINGP%DEGEN=',singp%degen,'di+0.25=',di+0.25_r8 @@ -500,7 +506,7 @@ SUBROUTINE sing1_vmat(ising) c----------------------------------------------------------------------- singp%order=sing_order IF(sing_order_ceiling) - $ singp%order=singp%order+CEILING(2*REAL(singp%alpha)) + $ singp%order=singp%order+CEILING(2*REAL(sqrtd)) DEALLOCATE(singp%mmat) CALL sing1_mmat(ising) c----------------------------------------------------------------------- @@ -508,8 +514,8 @@ SUBROUTINE sing1_vmat(ising) c----------------------------------------------------------------------- ALLOCATE(singp%power(2*mpert)) singp%power=0 - singp%power(ipert0)=-singp%alpha - singp%power(ipert0+mpert)=singp%alpha + singp%power(ipert0)=tr/2-sqrtd + singp%power(ipert0+mpert)=tr/2+sqrtd c----------------------------------------------------------------------- c write to table of singular surface quantities. c----------------------------------------------------------------------- @@ -536,9 +542,9 @@ SUBROUTINE sing1_vmat(ising) vmat(ipert0,ipert0,1,0)=1 vmat(ipert0,ipert0+mpert,1,0)=1 vmat(ipert0,ipert0,2,0) - $ =-(m0mat(1,1)+singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-singp%power(ipert0))/m0mat(1,2) vmat(ipert0,ipert0+mpert,2,0) - $ =-(m0mat(1,1)-singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-singp%power(ipert0+mpert))/m0mat(1,2) c----------------------------------------------------------------------- c compute higher-order solutions. c----------------------------------------------------------------------- @@ -587,7 +593,7 @@ SUBROUTINE sing1_get_ua(ising,psifac,ua) INTEGER :: iorder,isol INTEGER, DIMENSION(:), POINTER :: r1,r2 - COMPLEX(r8) :: pfac,z,sqrtfac,phase + COMPLEX(r8) :: z,sqrtfac,phase COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -609,11 +615,10 @@ SUBROUTINE sing1_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- c restore powers. c----------------------------------------------------------------------- - pfac=z**singp%alpha ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)*z**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*z**singp%power(r2(2)) c----------------------------------------------------------------------- c compute degenerate solutions. c----------------------------------------------------------------------- @@ -622,10 +627,9 @@ SUBROUTINE sing1_get_ua(ising,psifac,ua) c adjust phase. c----------------------------------------------------------------------- IF(psifac < singp%psifac .AND. uad%phase)THEN - phase=EXP(ifac*pi*(.5+singp%alpha)) DO isol=1,2 + phase=EXP(ifac*pi*(.5-singp%power(r2(isol)))) ua(:,r2(isol),:)=ua(:,r2(isol),:)*phase - phase=EXP(ifac*pi*(.5-singp%alpha)) ENDDO ENDIF c----------------------------------------------------------------------- @@ -648,7 +652,7 @@ SUBROUTINE sing1_get_dua(ising,psifac,dua) INTEGER :: iorder,ipert0,isol INTEGER, DIMENSION(:), POINTER :: r1,r2 - COMPLEX(r8) :: pfac,z,sqrtfac,phase + COMPLEX(r8) :: z,sqrtfac,phase COMPLEX(r8), DIMENSION(mpert,2*mpert,2) :: power COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp @@ -666,8 +670,8 @@ SUBROUTINE sing1_get_dua(ising,psifac,dua) power=2*singp%order power(r1,:,1)=power(r1,:,1)-1 power(r1,:,2)=power(r1,:,2)+1 - power(:,r2(1),:)=power(:,r2(1),:)-2*singp%alpha - power(:,r2(2),:)=power(:,r2(2),:)+2*singp%alpha + power(:,r2(1),:)=power(:,r2(1),:)+2*singp%power(r2(1)) + power(:,r2(2),:)=power(:,r2(2),:)+2*singp%power(r2(2)) c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -681,11 +685,10 @@ SUBROUTINE sing1_get_dua(ising,psifac,dua) c----------------------------------------------------------------------- c restore powers. c----------------------------------------------------------------------- - pfac=z**singp%alpha dua(r1,:,1)=dua(r1,:,1)/sqrtfac dua(r1,:,2)=dua(r1,:,2)*sqrtfac - dua(:,r2(1),:)=dua(:,r2(1),:)/pfac - dua(:,r2(2),:)=dua(:,r2(2),:)*pfac + dua(:,r2(1),:)=dua(:,r2(1),:)*z**singp%power(r2(1)) + dua(:,r2(2),:)=dua(:,r2(2),:)*z**singp%power(r2(2)) dua=dua/(2*z) c----------------------------------------------------------------------- c compute degenerate solutions. @@ -695,10 +698,9 @@ SUBROUTINE sing1_get_dua(ising,psifac,dua) c adjust phase. c----------------------------------------------------------------------- IF(psifac < singp%psifac .AND. uad%phase)THEN - phase=EXP(ifac*pi*(.5+singp%alpha)) DO isol=1,2 + phase=EXP(ifac*pi*(.5-singp%power(r2(isol)))) dua(:,r2(isol),:)=dua(:,r2(isol),:)*phase - phase=EXP(ifac*pi*(.5-singp%alpha)) ENDDO ENDIF c----------------------------------------------------------------------- @@ -1526,7 +1528,7 @@ SUBROUTINE sing1_get_ua_cut(ising,psifac,ua) INTEGER :: isol INTEGER, DIMENSION(:), POINTER :: r1,r2 - COMPLEX(r8) :: pfac,z,sqrtfac,phase + COMPLEX(r8) :: z,sqrtfac,phase COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -1548,11 +1550,10 @@ SUBROUTINE sing1_get_ua_cut(ising,psifac,ua) c----------------------------------------------------------------------- c restore powers. c----------------------------------------------------------------------- - pfac=z**singp%alpha ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)*z**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*z**singp%power(r2(2)) c----------------------------------------------------------------------- c compute degenerate solutions. c----------------------------------------------------------------------- @@ -1561,10 +1562,9 @@ SUBROUTINE sing1_get_ua_cut(ising,psifac,ua) c adjust phase. c----------------------------------------------------------------------- IF(psifac < singp%psifac .AND. uad%phase)THEN - phase=EXP(ifac*pi*(.5+singp%alpha)) DO isol=1,2 + phase=EXP(ifac*pi*(.5-singp%power(r2(isol)))) ua(:,r2(isol),:)=ua(:,r2(isol),:)*phase - phase=EXP(ifac*pi*(.5-singp%alpha)) ENDDO ENDIF c----------------------------------------------------------------------- diff --git a/rmatch/msing.f b/rmatch/msing.f index 42d183401..0fd96d060 100644 --- a/rmatch/msing.f +++ b/rmatch/msing.f @@ -29,6 +29,7 @@ MODULE msing_mod INTEGER, DIMENSION(2) :: r2 REAL(r8) :: psifac,q COMPLEX(r8) :: alpha + COMPLEX(r8), DIMENSION(2) :: power COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmatl,vmatr END TYPE sing_type @@ -53,7 +54,7 @@ MODULE msing_mod c----------------------------------------------------------------------- SUBROUTINE msing_read(vmat_filename) CHARACTER(*) :: vmat_filename - INTEGER :: ising + INTEGER :: ising,ios c----------------------------------------------------------------------- c read power series data to binary file. c----------------------------------------------------------------------- @@ -64,8 +65,18 @@ SUBROUTINE msing_read(vmat_filename) ALLOCATE(sing(msing)) DO ising=1,msing singp => sing(ising) - READ(debug_unit)singp%order,singp%alpha,singp%psifac, - $ singp%q,singp%r1,singp%r2 + READ(debug_unit,IOSTAT=ios)singp%order,singp%alpha, + $ singp%psifac,singp%q,singp%r1,singp%r2,singp%power +c----------------------------------------------------------------------- +c files without the powers end this record after r2; for them the +c powers are -alpha, +alpha. +c----------------------------------------------------------------------- + IF(ios /= 0)THEN + BACKSPACE(debug_unit) + READ(debug_unit)singp%order,singp%alpha,singp%psifac, + $ singp%q,singp%r1,singp%r2 + singp%power=(/-singp%alpha,singp%alpha/) + ENDIF ALLOCATE(singp%vmatl(mpert,2*mpert,2,0:2*singp%order)) ALLOCATE(singp%vmatr(mpert,2*mpert,2,0:2*singp%order)) READ(debug_unit)singp%vmatl,singp%vmatr @@ -91,7 +102,7 @@ SUBROUTINE msing_get_ua(ising,psifac,ua) INTEGER :: iorder INTEGER, DIMENSION(:), POINTER :: r1,r2 - COMPLEX(r8) :: dpsi,sqrtfac,pfac + COMPLEX(r8) :: dpsi,sqrtfac COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat c----------------------------------------------------------------------- c set pointers and compute distance from singular surface. @@ -110,7 +121,6 @@ SUBROUTINE msing_get_ua(ising,psifac,ua) c compute powers. c----------------------------------------------------------------------- sqrtfac=SQRT(dpsi) - pfac=dpsi**singp%alpha c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -123,8 +133,8 @@ SUBROUTINE msing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac + ua(:,r2(1),:)=ua(:,r2(1),:)/dpsi**(-singp%power(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*dpsi**singp%power(2) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- diff --git a/stride/sing.F b/stride/sing.F index b7c4b9c06..6667cdd9a 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -347,6 +347,7 @@ SUBROUTINE sing_vmat(ising) INTEGER :: ipert0,ipert,k,iside REAL(r8) :: psifac,di,di0,q,q1,rho,sig REAL(r8), PARAMETER :: half=.5_r8 + COMPLEX(r8) :: tr,sqrtd COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat,mmat COMPLEX(r8), DIMENSION(2,2) :: m0mat INTEGER, DIMENSION(:), POINTER :: r1,r2,n1,n2 @@ -397,6 +398,11 @@ SUBROUTINE sing_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di)) c----------------------------------------------------------------------- +c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c----------------------------------------------------------------------- + tr=m0mat(1,1)+m0mat(2,2) + sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) +c----------------------------------------------------------------------- c reset order and compute higher-order terms. c----------------------------------------------------------------------- singp%order=sing_order @@ -407,8 +413,8 @@ SUBROUTINE sing_vmat(ising) c----------------------------------------------------------------------- ALLOCATE(singp%power(2*mpert)) singp%power=0 - singp%power(ipert0)=-singp%alpha - singp%power(ipert0+mpert)=singp%alpha + singp%power(ipert0)=tr/2-sqrtd + singp%power(ipert0+mpert)=tr/2+sqrtd c----------------------------------------------------------------------- c allocate vmatl and vmatr at singular surface. c----------------------------------------------------------------------- @@ -444,9 +450,9 @@ SUBROUTINE sing_vmat(ising) vmat(ipert0,ipert0,1,0)=1 vmat(ipert0,ipert0+mpert,1,0)=1 vmat(ipert0,ipert0,2,0) - $ =-(m0mat(1,1)+sig*singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-sig*singp%power(ipert0))/m0mat(1,2) vmat(ipert0,ipert0+mpert,2,0) - $ =-(m0mat(1,1)-sig*singp%alpha)/m0mat(1,2) + $ =-(m0mat(1,1)-sig*singp%power(ipert0+mpert))/m0mat(1,2) c----------------------------------------------------------------------- c compute higher-order solutions. c----------------------------------------------------------------------- @@ -857,7 +863,6 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) INTEGER :: iorder INTEGER, DIMENSION(:), POINTER :: r1,r2 COMPLEX(r8) :: dpsi,sqrtfac - COMPLEX(r8) :: pfac COMPLEX(r8), DIMENSION(:,:,:,:), POINTER :: vmat TYPE(sing_type), POINTER :: singp c----------------------------------------------------------------------- @@ -877,7 +882,6 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c compute powers of distance from singular surface. c----------------------------------------------------------------------- sqrtfac=SQRT(dpsi) - pfac=dpsi**singp%alpha c----------------------------------------------------------------------- c compute power series by Horner's method. c----------------------------------------------------------------------- @@ -890,8 +894,8 @@ SUBROUTINE sing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac !<---mult row by shear z^(-1/2) ua(r1,:,2)=ua(r1,:,2)*sqrtfac !<---mult row by shear z^(+1/2) - ua(:,r2(1),:)=ua(:,r2(1),:)/pfac !<---mult column by z^alpha- - ua(:,r2(2),:)=ua(:,r2(2),:)*pfac !<---mult column by z^alpha+ + ua(:,r2(1),:)=ua(:,r2(1),:)*dpsi**singp%power(r2(1)) + ua(:,r2(2),:)=ua(:,r2(2),:)*dpsi**singp%power(r2(2)) c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- From 608e0942022bd95fb1bae11ef493ec970d8bbfdc Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Fri, 9 Oct 2026 21:49:00 -0700 Subject: [PATCH 185/305] EQUIL - FEATURE - ldp_i reads an optional psi bounds record [psi_axis, psi_lcfs] TokaMaker's save_ifile pads the last i-file surface inward from the LCFS. An optional final record of length 2 holding the unpadded plasma bounds now normalizes psi: xs = (psi - psi_axis)/(psi_lcfs - psi_axis), psio = psi_lcfs - psi_axis, so psihigh is the true psi_N; psihigh above the last file surface is clamped to it with a note. The record may follow the optional FF', P' records (skipped here) and is told apart by length: a length-mx read of it fails, and the reader then rewinds and skips the records already read (the position after a failed read is processor dependent, so no BACKSPACE). Without the record results are bit-identical; older builds ignore it. Co-Authored-By: Claude Opus 5.5 --- equil/read_eq.f | 50 +++++++++++++++++++++++++++++++++++++++++-------- input/equil.in | 2 +- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/equil/read_eq.f b/equil/read_eq.f index e042ed87f..10ba97093 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -807,15 +807,20 @@ END SUBROUTINE read_eq_ldp_d c mx,my (integer*4: number of surfaces, number of angles) c psi(mx) [Wb/rad], f(mx) = R*B_t [T m], p(mx) [Pa], q(mx) c r(my,mx), z(my,mx) [m], periodic point duplicated -c optional: f*df/dpsi(mx), dp/dpsi(mx) (not read here) +c optional, each only if present, in this order: +c f*df/dpsi(mx), dp/dpsi(mx) (skipped here); +c psi_axis, psi_lcfs (2 reals): unpadded plasma bounds, which +c normalize psi; the last surface may lie inside the lcfs and +c caps psihigh. Optional records are told apart by length (mx, 2). c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- SUBROUTINE read_eq_ldp_i - INTEGER :: mx,my - REAL(r8), DIMENSION(:), POINTER :: psi,f,p,q + INTEGER :: mx,my,irec,ios1,ios2,ios3 + REAL(r8), DIMENSION(2) :: psib + REAL(r8), DIMENSION(:), POINTER :: psi,f,p,q,buf REAL(r8), DIMENSION(:,:), POINTER :: r,z c----------------------------------------------------------------------- c open data file, read scalar data. @@ -829,9 +834,12 @@ SUBROUTINE read_eq_ldp_i my=my-1 mx=mx-1 ALLOCATE(psi(0:mx),f(0:mx),p(0:mx),q(0:mx), - $ r(0:my,0:mx),z(0:my,0:mx)) + $ r(0:my,0:mx),z(0:my,0:mx),buf(0:mx)) c----------------------------------------------------------------------- -c read binary data. +c read binary data. The psi bounds record follows z, ffp or pp. A +c length-mx read of it fails (short record); the file position is +c then processor dependent, so rewind and skip the 7 fixed records +c and any ffp, pp records before reading it. c----------------------------------------------------------------------- READ(in_unit)psi READ(in_unit)f @@ -839,13 +847,39 @@ SUBROUTINE read_eq_ldp_i READ(in_unit)q READ(in_unit)r READ(in_unit)z + READ(in_unit,IOSTAT=ios1)buf + ios2=ios1 + IF(ios1 == 0)READ(in_unit,IOSTAT=ios2)buf + ios3=ios2 + IF(ios2 == 0)READ(in_unit,IOSTAT=ios3)psib + IF(ios2 > 0)THEN + REWIND(in_unit) + DO irec=1,7+COUNT((/ios1,ios2/) == 0) + READ(in_unit) + ENDDO + READ(in_unit,IOSTAT=ios3)psib + ENDIF CALL bin_close(in_unit) c----------------------------------------------------------------------- c copy and revise 1D arrays. c----------------------------------------------------------------------- - psio=psi(mx)-psi(0) CALL spline_alloc(sq_in,mx,4) - sq_in%xs=(psi-psi(0))/psio + IF(ios3 == 0)THEN + psio=psib(2)-psib(1) + sq_in%xs=(psi-psib(1))/psio + IF(.NOT.(sq_in%xs(mx) > 0 .AND. sq_in%xs(mx) <= 1)) + $ CALL program_stop("ldp_i psi bounds disagree with psi") + IF(verbose)WRITE(*,'(1x,a,1p,2es14.6)') + $ "ldp_i psi bounds: psi_axis, psi_lcfs =",psib + IF(psihigh > sq_in%xs(mx))THEN + WRITE(*,'(1x,2(a,es12.5))')"Note: psihigh =",psihigh, + $ " clamped to the last i-file surface",sq_in%xs(mx) + psihigh=sq_in%xs(mx) + ENDIF + ELSE + psio=psi(mx)-psi(0) + sq_in%xs=(psi-psi(0))/psio + ENDIF sq_in%fs(:,1)=ABS(f) sq_in%fs(:,2)=p*mu0 sq_in%fs(:,3)=q @@ -861,7 +895,7 @@ SUBROUTINE read_eq_ldp_i c----------------------------------------------------------------------- c process inverse equilibrium and deallocate local arrays. c----------------------------------------------------------------------- - DEALLOCATE(psi,f,p,q,r,z) + DEALLOCATE(psi,f,p,q,r,z,buf) CALL inverse_run c----------------------------------------------------------------------- c terminate. diff --git a/input/equil.in b/input/equil.in index 98cbb3a91..7cbce739c 100644 --- a/input/equil.in +++ b/input/equil.in @@ -9,7 +9,7 @@ grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. psilow=1e-2 ! Minimum value of psi, normalized from 0 to 1 - psihigh=0.994 ! Maximum value of psi, normalized from 0 to 1 + psihigh=0.994 ! Maximum value of psi, normalized from 0 to 1 (ldp_i with psi bounds: capped at the last file surface) mpsi=128 ! Number of radial grid intervals for equilibrium quantities mtheta=256 ! Number of equally spaced poloidal grid intervals for all splines newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. From 24e8d32155268e8caebda6cf8cba8d985ce0fc5b Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 20:35:32 -0700 Subject: [PATCH 186/305] DCON, RDCON, STRIDE - BUGFIX - Compute the Mercier alpha in double precision singp%alpha = SQRT(-CMPLX(singp%di)): CMPLX without KIND returns default (single precision) complex, so alpha carried ~1e-7 relative rounding. Use KIND=r8. alpha no longer sets the powers or the order ceiling, so on the DIII-D and Solov'ev examples only the alpha stored in vmat.bin changes. Co-Authored-By: Claude Opus 5.5 --- dcon/sing.f | 2 +- rdcon/sing.f | 2 +- rdcon/sing1.f | 2 +- stride/sing.F | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dcon/sing.f b/dcon/sing.f index 323217ab8..83dbe29b2 100644 --- a/dcon/sing.f +++ b/dcon/sing.f @@ -304,7 +304,7 @@ SUBROUTINE sing_vmat(ising) m0mat=TRANSPOSE(singp%mmat(r1(1),r2,:,0)) di=m0mat(1,1)*m0mat(2,2)-m0mat(2,1)*m0mat(1,2) singp%di=di - singp%alpha=SQRT(-CMPLX(singp%di)) + singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). c----------------------------------------------------------------------- diff --git a/rdcon/sing.f b/rdcon/sing.f index 8151fb277..4448b9817 100644 --- a/rdcon/sing.f +++ b/rdcon/sing.f @@ -299,7 +299,7 @@ SUBROUTINE sing_vmat(ising) WRITE(*,'(3x,a4,es10.3E2,a9,I4)') "di =",di, $ ", ising =",ising singp%di=di - singp%alpha=SQRT(-CMPLX(singp%di)) + singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). c----------------------------------------------------------------------- diff --git a/rdcon/sing1.f b/rdcon/sing1.f index ac403b7ee..a60fb4761 100644 --- a/rdcon/sing1.f +++ b/rdcon/sing1.f @@ -491,7 +491,7 @@ SUBROUTINE sing1_vmat(ising) m0mat=TRANSPOSE(singp%mmat(r1(1),r2,:,0)) di=m0mat(1,1)*m0mat(2,2)-m0mat(2,1)*m0mat(1,2) singp%di=di - singp%alpha=SQRT(-CMPLX(singp%di)) + singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). c----------------------------------------------------------------------- diff --git a/stride/sing.F b/stride/sing.F index 6667cdd9a..40a57d30b 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -396,7 +396,7 @@ SUBROUTINE sing_vmat(ising) di=m0mat(1,1)*m0mat(2,2)-m0mat(2,1)*m0mat(1,2) WRITE(*, '(1x,i5,1x,es11.3)') ising, di singp%di=di - singp%alpha=SQRT(-CMPLX(singp%di)) + singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). c----------------------------------------------------------------------- From 6eda483e6747fd7758584846f470d36e7fc74b4d Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 22:47:22 -0700 Subject: [PATCH 187/305] RDCON - BUGFIX - MRE runs without a Zeff table use a flat Zeff, as before With MRE_flag, a run whose rdcon.in has no psi_N_Zeff stopped ("Provide Zeff profile values"), where develop used the scalar Zeff (default 1.52). One Zeff value, or none, without psi_N_Zeff now gives a flat profile at that value, matching develop (mufrac to 1e-16 with Zeff = 2.0; 1e-9 with the default, which develop held in single precision). The profile spline's "extrap" end condition needs 4 nodes, so shorter tables, which gave NaN, now stop with a message. Co-Authored-By: Claude Opus 5.5 --- rdcon/dcon.f | 17 +++++++++++++++++ rdcon/mercier.f | 12 ++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index ee948fbca..77d635e26 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -222,6 +222,7 @@ SUBROUTINE dcon_run LOGICAL :: cyl_flag=.FALSE.,regrid_flag=.FALSE.,verbose=.TRUE. INTEGER :: mmin,ipsi + REAL(r8) :: zeff0 COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: mx0 COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: vx0 @@ -346,6 +347,22 @@ SUBROUTINE dcon_run IF(MRE_flag)THEN CALL read_var_len(Zeff,Zeff_) CALL read_var_len(psi_N_Zeff,psi_N_Zeff_) +c one Zeff value (default 1.52) without psi_N_Zeff: flat profile. + IF(.NOT.ALLOCATED(psi_N_Zeff_))THEN + zeff0=1.52_r8 + IF(ALLOCATED(Zeff_))THEN + IF(SIZE(Zeff_) > 1) + $ CALL program_stop("A Zeff profile needs psi_N_Zeff") + zeff0=Zeff_(1) + DEALLOCATE(Zeff_) + ENDIF + ALLOCATE(Zeff_(4),psi_N_Zeff_(4)) + Zeff_=zeff0 + psi_N_Zeff_=(/0.0_r8,1.0_r8/3,2.0_r8/3,1.0_r8/) + ENDIF + IF(.NOT.ALLOCATED(Zeff_) .OR. SIZE(Zeff_) /= SIZE(psi_N_Zeff_) + $ .OR. SIZE(Zeff_) < 4) + $ CALL program_stop("Zeff, psi_N_Zeff: equal sizes, >= 4") CALL spline_alloc(mreterms,mpsi,30) mreterms%xs=sq%xs mreterms%fs=0 diff --git a/rdcon/mercier.f b/rdcon/mercier.f index b3e03ed23..277894db6 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -43,14 +43,10 @@ SUBROUTINE mercier_scan c----------------------------------------------------------------------- IF(MRE_flag)THEN CALL spline_alloc(ff,mtheta,22) - IF(ALLOCATED(Zeff_) .AND. ALLOCATED(psi_N_Zeff_))THEN - CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_)-1,1) - Zeff_spline%xs=psi_N_Zeff_ - Zeff_spline%fs(:,1)=Zeff_ - CALL spline_fit(Zeff_spline,"extrap") - ELSE - CALL program_stop("Provide Zeff profile values") - ENDIF + CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_)-1,1) + Zeff_spline%xs=psi_N_Zeff_ + Zeff_spline%fs(:,1)=Zeff_ + CALL spline_fit(Zeff_spline,"extrap") ELSE CALL spline_alloc(ff,mtheta,5) ENDIF From 4cbcd199b28c7dbc7eb5b0a1a314651866d57530 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 13:44:10 -0700 Subject: [PATCH 188/305] EQUIL - FEATURE - spline_fit_pchip: monotone cubic Hermite fit for tabulated data without derivatives Fritsch-Carlson interior slopes with scipy PchipInterpolator's end slopes, so Python front ends (bouquet, TPS) can reproduce GPEC's interpolant exactly. Unit test checks values and derivatives against scipy. Co-Authored-By: Claude Opus 5.5 --- equil/spline.f | 63 +++++++++++++++++++ regression/spline_tests/.gitignore | 1 + regression/spline_tests/makefile | 13 ++++ regression/spline_tests/test_spline_pchip.f90 | 56 +++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 regression/spline_tests/.gitignore create mode 100644 regression/spline_tests/makefile create mode 100644 regression/spline_tests/test_spline_pchip.f90 diff --git a/equil/spline.f b/equil/spline.f index f7aca7896..02a0799ba 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -32,6 +32,7 @@ c 21. spline_thomas. c 22. spline_roots. c 23. spline_refine_root. +c 24. spline_fit_pchip. c----------------------------------------------------------------------- c subprogram 0. spline_type definition. c defines spline_type. @@ -1849,5 +1850,67 @@ SUBROUTINE spline_refine_root(spl, iqty, x, op_tol) c----------------------------------------------------------------------- RETURN END SUBROUTINE spline_refine_root +c----------------------------------------------------------------------- +c subprogram 24. spline_fit_pchip. +c fits monotone piecewise cubic Hermite interpolants (Fritsch and +c Carlson, SIAM J. Numer. Anal. 17, 238, 1980), with the end slopes +c of scipy's PchipInterpolator. For tabulated data without +c derivatives: no overshoot or ringing at steep gradients. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE spline_fit_pchip(spl) + + TYPE(spline_type), INTENT(INOUT) :: spl + + INTEGER :: iqty,ix,mx + REAL(r8) :: w1,w2 + REAL(r8), DIMENSION(spl%mx) :: h,del +c----------------------------------------------------------------------- +c interior slopes: weighted harmonic mean, zero at extrema. +c----------------------------------------------------------------------- + IF(ANY(spl%xpower /= 0))CALL program_stop("spline_fit_pchip: "// + $ "xpower not supported") + mx=spl%mx + h=spl%xs(1:mx)-spl%xs(0:mx-1) + DO iqty=1,spl%nqty + del=(spl%fs(1:mx,iqty)-spl%fs(0:mx-1,iqty))/h + DO ix=1,mx-1 + IF(del(ix)*del(ix+1) <= 0)THEN + spl%fs1(ix,iqty)=0 + ELSE + w1=2*h(ix+1)+h(ix) + w2=h(ix+1)+2*h(ix) + spl%fs1(ix,iqty)=(w1+w2)/(w1/del(ix)+w2/del(ix+1)) + ENDIF + ENDDO + IF(mx == 1)THEN + spl%fs1(:,iqty)=del(1) + ELSE + spl%fs1(0,iqty)=pchip_end(h(1),h(2),del(1),del(2)) + spl%fs1(mx,iqty)=pchip_end(h(mx),h(mx-1),del(mx),del(mx-1)) + ENDIF + ENDDO +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + + CONTAINS +c----------------------------------------------------------------------- +c shape-preserving three-point end slope. +c----------------------------------------------------------------------- + REAL(r8) FUNCTION pchip_end(h0,h1,d0,d1) + REAL(r8), INTENT(IN) :: h0,h1,d0,d1 + pchip_end=((2*h0+h1)*d0-h0*d1)/(h0+h1) + IF(SIGN(one,pchip_end) /= SIGN(one,d0) .OR. d0 == 0)THEN + pchip_end=0 + ELSEIF(SIGN(one,d0) /= SIGN(one,d1) + $ .AND. ABS(pchip_end) > ABS(3*d0))THEN + pchip_end=3*d0 + ENDIF + END FUNCTION pchip_end + END SUBROUTINE spline_fit_pchip END MODULE spline_mod diff --git a/regression/spline_tests/.gitignore b/regression/spline_tests/.gitignore new file mode 100644 index 000000000..a911dc3cc --- /dev/null +++ b/regression/spline_tests/.gitignore @@ -0,0 +1 @@ +test_spline_pchip diff --git a/regression/spline_tests/makefile b/regression/spline_tests/makefile new file mode 100644 index 000000000..65f493d71 --- /dev/null +++ b/regression/spline_tests/makefile @@ -0,0 +1,13 @@ +# Unit tests for equil/spline.f. Build GPEC first (install/make), then: make run +FC ?= gfortran +GPEC = ../.. +LIBS ?= -L$(LAPACKHOME)/lib -llapack -lblas + +test_spline_pchip: test_spline_pchip.f90 $(GPEC)/deps/lib/libequil.a + $(FC) $(FFLAGS) -I$(GPEC)/equil -o $@ $< $(GPEC)/deps/lib/libequil.a $(LIBS) + +run: test_spline_pchip + ./test_spline_pchip + +clean: + rm -f test_spline_pchip *.o *.mod diff --git a/regression/spline_tests/test_spline_pchip.f90 b/regression/spline_tests/test_spline_pchip.f90 new file mode 100644 index 000000000..1f9dd1a1e --- /dev/null +++ b/regression/spline_tests/test_spline_pchip.f90 @@ -0,0 +1,56 @@ +!----------------------------------------------------------------------- +! Unit tests for spline_fit_pchip. +! 1. matches scipy.interpolate.PchipInterpolator on a pedestal-like table. +! 2. a 2-point table gives the straight line (flat for equal values). +! Exits with status 1 on failure. +!----------------------------------------------------------------------- +PROGRAM test_spline_pchip + USE spline_mod + IMPLICIT NONE + + INTEGER :: ix + REAL(r8) :: x + REAL(r8), DIMENSION(10), PARAMETER :: px=(/0._r8,.1_r8,.25_r8,.3_r8,.5_r8, & + .7_r8,.85_r8,.9_r8,.95_r8,1._r8/), py=(/1._r8,1.05_r8,1.1_r8,1.1_r8, & + 1.3_r8,1.6_r8,2.4_r8,3.5_r8,3.6_r8,3.62_r8/) + REAL(r8), DIMENSION(5), PARAMETER :: pxe=(/.05_r8,.2_r8,.4_r8,.875_r8,.97_r8/), & + ! scipy 1.x PchipInterpolator(px,py)(pxe) and its derivative + pye=(/1.02701577_r8,1.09154154_r8,1.17_r8,2.98681184_r8,3.61184_r8/), & + pde=(/0.50698198_r8,0.30930931_r8,1.2_r8,29.69419306_r8,0.496_r8/) + TYPE(spline_type) :: spl + LOGICAL :: ok=.TRUE. + + CALL spline_alloc(spl,9,1) + spl%xs=px + spl%fs(:,1)=py + CALL spline_fit_pchip(spl) + DO ix=1,5 + CALL spline_eval(spl,pxe(ix),1) + IF(ABS(spl%f(1)-pye(ix)) > 1e-7_r8 .OR. ABS(spl%f1(1)-pde(ix)) > 1e-6_r8)THEN + WRITE(*,*)"FAIL: pchip differs from scipy at x = ",pxe(ix),spl%f(1),spl%f1(1) + ok=.FALSE. + ENDIF + ENDDO + CALL spline_dealloc(spl) + WRITE(*,*)"pchip vs scipy checked at 5 points" + + CALL spline_alloc(spl,1,2) + spl%xs=(/0._r8,1._r8/) + spl%fs(:,1)=1.52_r8 + spl%fs(:,2)=(/1._r8,3._r8/) + CALL spline_fit_pchip(spl) + DO ix=0,4 + x=ix/4._r8 + CALL spline_eval(spl,x,1) + IF(ABS(spl%f(1)-1.52_r8) > 1e-14_r8 .OR. ABS(spl%f1(1)) > 1e-14_r8 & + .OR. ABS(spl%f(2)-1-2*x) > 1e-14_r8 .OR. ABS(spl%f1(2)-2) > 1e-14_r8)THEN + WRITE(*,*)"FAIL: 2-point pchip is not the straight line at x = ",x,spl%f,spl%f1 + ok=.FALSE. + ENDIF + ENDDO + CALL spline_dealloc(spl) + WRITE(*,*)"2-point tables checked at 5 points" + IF(.NOT.ok)STOP 1 + WRITE(*,*)"PASS" + +END PROGRAM test_spline_pchip From bdd6e48bdb93aa11c990cecb60d436a9949423bc Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 13:44:11 -0700 Subject: [PATCH 189/305] RDCON, PENTRC - FEATURE - pchip for the Zeff profile and the kinetic profile table The tabulated Zeff (RDCON MRE terms) and the PENTRC kinetic input table carry pedestal-scale gradients; a cubic spline rings there, pchip does not. Co-Authored-By: Claude Opus 5.5 --- pentrc/inputs.f90 | 4 ++-- rdcon/mercier.f | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index a21b80877..1b0c54c46 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -31,7 +31,7 @@ module inputs use params, only : r8,xj,mp,me,e,mu0,twopi use utilities, only : get_free_file_unit,readtable,nunique,progressbar,iscdftf,iscdftb - use spline_mod, only : spline_type,spline_alloc,spline_fit,spline_eval,spline_write1 + use spline_mod, only : spline_type,spline_alloc,spline_fit,spline_fit_pchip,spline_eval,spline_write1 use cspline_mod, only : cspline_type,cspline_alloc,cspline_dealloc,& cspline_fit,cspline_write,cspline_eval use fspline_mod, only : fspline_eval @@ -215,7 +215,7 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) do i=1,5 tmp%fs(0:,i) = table(1:,i+1) enddo - call spline_fit(tmp,"extrap") + call spline_fit_pchip(tmp) ! monotone: no ringing at the pedestal if(write_log) print *,"Formed temporary spline" ! extrapolate to regular spline (helps smooth core??) diff --git a/rdcon/mercier.f b/rdcon/mercier.f index 277894db6..a2f942a1a 100644 --- a/rdcon/mercier.f +++ b/rdcon/mercier.f @@ -46,7 +46,7 @@ SUBROUTINE mercier_scan CALL spline_alloc(Zeff_spline,SIZE(psi_N_Zeff_)-1,1) Zeff_spline%xs=psi_N_Zeff_ Zeff_spline%fs(:,1)=Zeff_ - CALL spline_fit(Zeff_spline,"extrap") + CALL spline_fit_pchip(Zeff_spline) ELSE CALL spline_alloc(ff,mtheta,5) ENDIF From 224c7f4bc112cdb9975ca5bd3f7d3bcec60d72e7 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 22:51:42 -0700 Subject: [PATCH 190/305] RDCON - FEATURE - Zeff tables of 2 or more points pchip fits any table of 2 or more points, so the 4-point minimum of the "extrap" spline goes. A scalar Zeff now becomes a flat profile on 2 nodes, the same table that tearing-physics-suite writes for a scalar Zeff (psi_N_Zeff = 0, 1). Co-Authored-By: Claude Opus 5.5 --- rdcon/dcon.f | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index 77d635e26..370e303a4 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -356,13 +356,13 @@ SUBROUTINE dcon_run zeff0=Zeff_(1) DEALLOCATE(Zeff_) ENDIF - ALLOCATE(Zeff_(4),psi_N_Zeff_(4)) + ALLOCATE(Zeff_(2),psi_N_Zeff_(2)) Zeff_=zeff0 - psi_N_Zeff_=(/0.0_r8,1.0_r8/3,2.0_r8/3,1.0_r8/) + psi_N_Zeff_=(/0.0_r8,1.0_r8/) ENDIF IF(.NOT.ALLOCATED(Zeff_) .OR. SIZE(Zeff_) /= SIZE(psi_N_Zeff_) - $ .OR. SIZE(Zeff_) < 4) - $ CALL program_stop("Zeff, psi_N_Zeff: equal sizes, >= 4") + $ .OR. SIZE(Zeff_) < 2) + $ CALL program_stop("Zeff, psi_N_Zeff: equal sizes, >= 2") CALL spline_alloc(mreterms,mpsi,30) mreterms%xs=sq%xs mreterms%fs=0 From 9154d006a1352df300084d41d3d1532bf63a1038 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 14:24:11 -0700 Subject: [PATCH 191/305] EQUIL - CLEANUP - review fixes for profile_source and the dump slopes Derivative sign taken from f and p together, so a flat p (or f) no longer forces the values fallback; the dump writes eqfun only when it exists; shorter equil.in entry; unit test drops redundant wrappers. Co-Authored-By: Claude Opus 5.5 --- equil/equil_out.f | 2 +- equil/read_eq.f | 19 ++++++++++--------- input/equil.in | 2 +- regression/spline_tests/.gitignore | 1 + .../spline_tests/test_spline_hermite.f90 | 19 +++---------------- 5 files changed, 16 insertions(+), 27 deletions(-) create mode 100644 regression/spline_tests/.gitignore diff --git a/equil/equil_out.f b/equil/equil_out.f index 18e6c9d22..88d67fcf9 100644 --- a/equil/equil_out.f +++ b/equil/equil_out.f @@ -851,7 +851,7 @@ SUBROUTINE equil_out_dump c write supplied f, p derivatives and eqfun (read only if present). c----------------------------------------------------------------------- WRITE(dump_unit)sq_in_slopes(1:2),sq%fs1(:,1:2) - WRITE(dump_unit)eqfun%fs + IF(eqfun%allocated)WRITE(dump_unit)eqfun%fs c----------------------------------------------------------------------- c close dump file. c----------------------------------------------------------------------- diff --git a/equil/read_eq.f b/equil/read_eq.f index 3b2f1887a..8401b812e 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -2430,7 +2430,7 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) REAL(r8), DIMENSION(0:), INTENT(IN) :: ffp,pp INTEGER :: mx - REAL(r8) :: sgn_f,sgn_p,dfmax,dpmax + REAL(r8) :: sf,sp,sgn,dfmax,dpmax REAL(r8), DIMENSION(0:SIZE(ffp)-1) :: f,p,f2,fint,pint TYPE(spline_type) :: spl c----------------------------------------------------------------------- @@ -2455,10 +2455,11 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) CALL read_eq_profiles_warn("non-finite derivatives or f = 0") RETURN ENDIF - sgn_f=SIGN(one,SUM((f(1:mx)-f(0:mx-1)) - $ *(ffp(1:mx)/f(1:mx)+ffp(0:mx-1)/f(0:mx-1)))) - sgn_p=SIGN(one,SUM((p(1:mx)-p(0:mx-1))*(pp(1:mx)+pp(0:mx-1)))) - IF(sgn_f /= sgn_p)THEN + sf=SUM((f(1:mx)-f(0:mx-1)) + $ *(ffp(1:mx)/f(1:mx)+ffp(0:mx-1)/f(0:mx-1))) + sp=SUM((p(1:mx)-p(0:mx-1))*(pp(1:mx)+pp(0:mx-1))) + sgn=SIGN(one,sf+sp) + IF(sf*sp < 0)THEN CALL read_eq_profiles_warn("derivative signs disagree with" $ //" the values for only one of f and p") RETURN @@ -2468,8 +2469,8 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) c----------------------------------------------------------------------- CALL spline_alloc(spl,mx,2) spl%xs=sq_in%xs - spl%fs(:,1)=sgn_f*ffp - spl%fs(:,2)=sgn_f*pp + spl%fs(:,1)=sgn*ffp + spl%fs(:,2)=sgn*pp CALL spline_fit(spl,"extrap") CALL spline_int(spl) f2=f(mx)**2-2*(spl%fsi(mx,1)-spl%fsi(:,1)) @@ -2497,8 +2498,8 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) sq_in%fs(:,1)=fint sq_in%fs(:,2)=pint ENDIF - sq_in%fs1(:,1)=sgn_f*ffp/sq_in%fs(:,1) - sq_in%fs1(:,2)=sgn_f*pp + sq_in%fs1(:,1)=sgn*ffp/sq_in%fs(:,1) + sq_in%fs1(:,2)=sgn*pp sq_in_slopes(1:2)=.TRUE. c----------------------------------------------------------------------- c terminate. diff --git a/input/equil.in b/input/equil.in index 01685bf07..9ba3d176f 100644 --- a/input/equil.in +++ b/input/equil.in @@ -15,7 +15,7 @@ newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions - profile_source="integrate" ! How f and p are built when the file has FF' and p' (efit, ldp_i). integrate: integrate FF', p' inward from the boundary values and use them as spline derivatives (recommended). hermite: tabulated f, p with FF', p' as derivatives. values: tabulated f, p only (previous behaviour) + profile_source="integrate" ! f, p from the file's FF', p' (efit, ldp_i). integrate: integrated from the boundary, FF', p' as spline derivatives. hermite: file f, p with FF', p' as derivatives. values: file f, p only (previous behaviour) input_only=f ! Generate information about the input and then quit with no further calculation use_galgrid=t ! Use the same grid as galerkin method when regrid=.True. diff --git a/regression/spline_tests/.gitignore b/regression/spline_tests/.gitignore new file mode 100644 index 000000000..ca5a5db51 --- /dev/null +++ b/regression/spline_tests/.gitignore @@ -0,0 +1 @@ +test_spline_hermite diff --git a/regression/spline_tests/test_spline_hermite.f90 b/regression/spline_tests/test_spline_hermite.f90 index 3f2f23ffc..d13694e40 100644 --- a/regression/spline_tests/test_spline_hermite.f90 +++ b/regression/spline_tests/test_spline_hermite.f90 @@ -22,9 +22,9 @@ PROGRAM test_spline_hermite mx=mxs(ires) CALL spline_alloc(spl,mx,2) spl%xs=(/(ix,ix=0,mx)/)/REAL(mx,r8) - spl%fs(:,1)=f(spl%xs) - spl%fs(:,2)=f(spl%xs) - spl%fs1(:,1)=df(spl%xs) + spl%fs(:,1)=f1(spl%xs) + spl%fs(:,2)=f1(spl%xs) + spl%fs1(:,1)=df1(spl%xs) CALL spline_fit_hermite(spl,"extrap",(/.TRUE.,.FALSE./)) err_val(ires)=0 DO ix=0,mfine @@ -35,7 +35,6 @@ PROGRAM test_spline_hermite err_end(ires)=MAX(ABS(spl%fs1(0,2)-df1(0._r8)),ABS(spl%fs1(mx,2)-df1(1._r8))) ALLOCATE(fs1_ref(0:mx,2)) fs1_ref=spl%fs1 - spl%fs(:,2)=f(spl%xs) CALL spline_fit(spl,"extrap") IF(ANY(spl%fs1(:,2) /= fs1_ref(:,2)))THEN WRITE(*,*)"FAIL: column not kept differs from spline_fit, mx = ",mx @@ -71,16 +70,4 @@ ELEMENTAL REAL(r8) FUNCTION df1(x) df1=3*COS(3*x)+EXP(-x)*(2*x-x**2) END FUNCTION df1 - FUNCTION f(x) - REAL(r8), DIMENSION(:), INTENT(IN) :: x - REAL(r8), DIMENSION(SIZE(x)) :: f - f=f1(x) - END FUNCTION f - - FUNCTION df(x) - REAL(r8), DIMENSION(:), INTENT(IN) :: x - REAL(r8), DIMENSION(SIZE(x)) :: df - df=df1(x) - END FUNCTION df - END PROGRAM test_spline_hermite From ef882527aee107bede190896486cc15fe0fc4131 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 6 Oct 2026 15:00:46 -0700 Subject: [PATCH 192/305] EQUIL - CLEANUP - drop profile_source = hermite Tabulated values paired with tabulated derivatives put the values' rounding noise into f'' (the worst option in the comparison). spline_fit_hermite stays, used by integrate to keep the derivatives. Co-Authored-By: Claude Opus 5.5 --- equil/read_eq.f | 26 ++++++++++++-------------- input/equil.in | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/equil/read_eq.f b/equil/read_eq.f index 8401b812e..acdf5fff6 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -2413,14 +2413,14 @@ SUBROUTINE read_eq_marklin_inverse END SUBROUTINE read_eq_marklin_inverse c----------------------------------------------------------------------- c subprogram 32. read_eq_profiles_from_derivatives. -c uses tabulated derivatives ffp = f*df/dpsifac and -c pp = d(mu0 p)/dpsifac for the f and mu0 p columns of sq_in, -c according to profile_source: -c "values": tabulated values, spline-fitted derivatives. -c "hermite": tabulated values and tabulated derivatives. -c "integrate": values integrated inward from the boundary -c values of f**2/2 and mu0 p, and tabulated derivatives. -c falls back to "values" if the derivatives are unusable. +c profile_source = "integrate": builds the f and mu0 p columns of +c sq_in from ffp = f*df/dpsifac and pp = d(mu0 p)/dpsifac. Values +c are integrated inward from the boundary values of f**2/2 and +c mu0 p; knot derivatives ffp/f and pp are kept by +c spline_fit_hermite. Tabulated values are not paired with the +c tabulated derivatives: their rounding noise would set f''. +c "values": tabulated values only. Falls back to "values" if the +c derivatives are unusable. c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. @@ -2439,7 +2439,7 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) SELECT CASE(TRIM(profile_source)) CASE("values") RETURN - CASE("hermite","integrate") + CASE("integrate") CASE DEFAULT CALL program_stop("Cannot recognize profile_source = " $ //TRIM(profile_source)) @@ -2494,11 +2494,9 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) c----------------------------------------------------------------------- c store values and derivatives. c----------------------------------------------------------------------- - IF(profile_source == "integrate")THEN - sq_in%fs(:,1)=fint - sq_in%fs(:,2)=pint - ENDIF - sq_in%fs1(:,1)=sgn*ffp/sq_in%fs(:,1) + sq_in%fs(:,1)=fint + sq_in%fs(:,2)=pint + sq_in%fs1(:,1)=sgn*ffp/fint sq_in%fs1(:,2)=sgn*pp sq_in_slopes(1:2)=.TRUE. c----------------------------------------------------------------------- diff --git a/input/equil.in b/input/equil.in index 9ba3d176f..0a80b7d4f 100644 --- a/input/equil.in +++ b/input/equil.in @@ -15,7 +15,7 @@ newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions - profile_source="integrate" ! f, p from the file's FF', p' (efit, ldp_i). integrate: integrated from the boundary, FF', p' as spline derivatives. hermite: file f, p with FF', p' as derivatives. values: file f, p only (previous behaviour) + profile_source="integrate" ! f, p from the file's FF', p' (efit, ldp_i). integrate: integrated from the boundary, FF', p' as spline derivatives. values: file f, p only (previous behaviour) input_only=f ! Generate information about the input and then quit with no further calculation use_galgrid=t ! Use the same grid as galerkin method when regrid=.True. From a47fc0205ac53f7b71fded73eec634764808d4de Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 15:40:26 -0700 Subject: [PATCH 193/305] EQUIL - FEATURE - profile_source defaults to values; integrate is opt-in Review (d-burg): with double-precision g-files the tabulated f, p are clean, and integrate can move outer surfaces because the quadrature assumes a profile shape between knots near pedestal kinks. Keep the previous behaviour (values) as the default and offer integrate for single-precision g-files, where differentiating rounded f, p sets f''. With the default, the DIIID_ideal (DCON) and DIIID_resistive (RDCON) examples are bit-identical to the base branch. Co-Authored-By: Claude Opus 5.5 --- equil/global.f | 2 +- equil/read_eq.f | 18 ++++++++++-------- input/equil.in | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/equil/global.f b/equil/global.f index 45ee91050..53594f553 100644 --- a/equil/global.f +++ b/equil/global.f @@ -16,7 +16,7 @@ MODULE global_mod CHARACTER(16) :: eq_type="fluxgrid" CHARACTER(128) :: eq_filename="fluxgrid.dat" - CHARACTER(16) :: profile_source="integrate" + CHARACTER(16) :: profile_source="values" LOGICAL, DIMENSION(4) :: sq_in_slopes=.FALSE. LOGICAL :: direct_flag=.FALSE. diff --git a/equil/read_eq.f b/equil/read_eq.f index acdf5fff6..fbea12931 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -2413,14 +2413,16 @@ SUBROUTINE read_eq_marklin_inverse END SUBROUTINE read_eq_marklin_inverse c----------------------------------------------------------------------- c subprogram 32. read_eq_profiles_from_derivatives. -c profile_source = "integrate": builds the f and mu0 p columns of -c sq_in from ffp = f*df/dpsifac and pp = d(mu0 p)/dpsifac. Values -c are integrated inward from the boundary values of f**2/2 and -c mu0 p; knot derivatives ffp/f and pp are kept by -c spline_fit_hermite. Tabulated values are not paired with the -c tabulated derivatives: their rounding noise would set f''. -c "values": tabulated values only. Falls back to "values" if the -c derivatives are unusable. +c profile_source = "values" (default): tabulated values only. +c "integrate" (opt-in, for single-precision g-files): builds the f +c and mu0 p columns of sq_in from ffp = f*df/dpsifac and +c pp = d(mu0 p)/dpsifac. Values are integrated inward from the +c boundary values of f**2/2 and mu0 p; knot derivatives ffp/f and +c pp are kept by spline_fit_hermite. Tabulated values are not +c paired with the tabulated derivatives: their rounding noise +c would set f''. The quadrature between knots can move outer +c surfaces near sharp profile features. Falls back to "values" if +c the derivatives are unusable. c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. diff --git a/input/equil.in b/input/equil.in index 0a80b7d4f..761ec70a5 100644 --- a/input/equil.in +++ b/input/equil.in @@ -15,7 +15,7 @@ newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions - profile_source="integrate" ! f, p from the file's FF', p' (efit, ldp_i). integrate: integrated from the boundary, FF', p' as spline derivatives. values: file f, p only (previous behaviour) + profile_source="values" ! How f, p are built for files with FF', p' (efit, ldp_i). values: tabulated f, p (default). integrate: integrated from the boundary with FF', p' as spline derivatives; opt-in for single-precision g-files, can move outer surfaces input_only=f ! Generate information about the input and then quit with no further calculation use_galgrid=t ! Use the same grid as galerkin method when regrid=.True. From 78977ab94141f9a1dfa26fd9cdcb4383e9aa95c7 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Mon, 12 Oct 2026 20:21:59 -0700 Subject: [PATCH 194/305] EQUIL - CLEANUP - one sq_in evaluation per surface, uniform sq_in_slopes, ldp_i profile_source warning - inverse_run evaluates sq_in once per surface with mode 1 instead of a third evaluation at the same psifac. - Every spline_fit_hermite call on sq, sq_in and sq_out passes sq_in_slopes; entries 3:4 are always false. - ldp_i warns when profile_source is not "values" and the file has no ffp, pp records, as the other fallbacks do. Default (values) DCON DIIID_ideal, RDCON DIIID_resistive and ldp_i outputs are bit-identical to a4b144d5, as are sq_out.bin, the dump and dump re-reads (newq0 = 0 and 1.1), and ldp_i with integrate. Co-Authored-By: Claude Opus 5.5 --- equil/equil_out.f | 3 +-- equil/inverse.f | 3 +-- equil/read_eq.f | 13 +++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/equil/equil_out.f b/equil/equil_out.f index 88d67fcf9..3428b754f 100644 --- a/equil/equil_out.f +++ b/equil/equil_out.f @@ -99,8 +99,7 @@ SUBROUTINE equil_out_diagnose(sq_flag,unit) sq_out%fs(:,4)=SQRT(sq_out%xs) sq_out%fs1(:,1)=sq%fs1(:,1)/twopi sq_out%fs1(:,2)=sq%fs1(:,2) - CALL spline_fit_hermite(sq_out,"extrap", - $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) + CALL spline_fit_hermite(sq_out,"extrap",sq_in_slopes) sq_out%name=" sq " sq_out%title=(/"psifac"," f ","mu0 p "," q "," rho "/) IF(bin_eq_1d)CALL bin_open(bin_2d_unit,"sq_out.bin","UNKNOWN", diff --git a/equil/inverse.f b/equil/inverse.f index 06eceab6f..ac19224bb 100644 --- a/equil/inverse.f +++ b/equil/inverse.f @@ -180,7 +180,7 @@ SUBROUTINE inverse_run CALL spline_alloc(spl,mtheta,5) DO ipsi=0,mpsi psifac=rzphi%xs(ipsi) - CALL spline_eval(sq_in,psifac,0) + CALL spline_eval(sq_in,psifac,1) spl%xs=rzphi%ys c----------------------------------------------------------------------- c interpolate to new surface. @@ -230,7 +230,6 @@ SUBROUTINE inverse_run c----------------------------------------------------------------------- c compute surface quantities. c----------------------------------------------------------------------- - CALL spline_eval(sq_in,psifac,1) sq%fs(ipsi,1)=sq_in%f(1)*twopi sq%fs(ipsi,2)=sq_in%f(2) sq%fs1(ipsi,1)=sq_in%f1(1)*twopi diff --git a/equil/read_eq.f b/equil/read_eq.f index fbea12931..0c13bfdba 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -852,8 +852,11 @@ SUBROUTINE read_eq_ldp_i sq_in%fs(:,1)=f sq_in%fs(:,2)=p*mu0 sq_in%fs(:,3)=q - IF(ios1 == 0 .AND. ios2 == 0) - $ CALL read_eq_profiles_from_derivatives(ffp*psio,pp*mu0*psio) + IF(ios1 == 0 .AND. ios2 == 0)THEN + CALL read_eq_profiles_from_derivatives(ffp*psio,pp*mu0*psio) + ELSEIF(TRIM(profile_source) /= "values")THEN + CALL read_eq_profiles_warn("no ffp, pp records in the file") + ENDIF if(psio.lt.0)psio=-psio c----------------------------------------------------------------------- c copy and revise 2D arrays. @@ -1496,8 +1499,7 @@ SUBROUTINE read_eq_dump c----------------------------------------------------------------------- READ(dump_unit,IOSTAT=ios)slopes,sq%fs1(:,1:2) IF(ios == 0)sq_in_slopes(1:2)=slopes - CALL spline_fit_hermite(sq,"extrap", - $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) IF(eqfun%allocated)CALL bicube_dealloc(eqfun) CALL bicube_alloc(eqfun,mpsi,mtheta,3) READ(dump_unit,IOSTAT=ios)eqfun%fs @@ -1540,8 +1542,7 @@ SUBROUTINE read_eq_dump sq%fs(ipsi,4)=sq%fs(ipsi,4)*ffac rzphi%fs(ipsi,:,3)=rzphi%fs(ipsi,:,3)*ffac ENDDO - CALL spline_fit_hermite(sq,"extrap", - $ (/sq_in_slopes(1:2),.FALSE.,.FALSE./)) + CALL spline_fit_hermite(sq,"extrap",sq_in_slopes) ENDIF qa=sq%fs(mpsi,4)+sq%fs1(mpsi,4)*(one-sq%xs(mpsi)) c----------------------------------------------------------------------- From 82eca30ebb2a14732ab2ecf3a0a9397f376c232a Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:36:42 -0700 Subject: [PATCH 195/305] EQUIL - MINOR - Plainer read_eq_ldp_i comments Co-Authored-By: Claude Opus 5.5 --- equil/read_eq.f | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/equil/read_eq.f b/equil/read_eq.f index 10ba97093..aeefd72fd 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -809,9 +809,9 @@ END SUBROUTINE read_eq_ldp_d c r(my,mx), z(my,mx) [m], periodic point duplicated c optional, each only if present, in this order: c f*df/dpsi(mx), dp/dpsi(mx) (skipped here); -c psi_axis, psi_lcfs (2 reals): unpadded plasma bounds, which -c normalize psi; the last surface may lie inside the lcfs and -c caps psihigh. Optional records are told apart by length (mx, 2). +c psi_axis, psi_lcfs (2 reals): psi is normalized to these; the +c last surface may lie inside the lcfs and then caps psihigh. +c Optional records are told apart by length (mx, 2). c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. @@ -836,10 +836,10 @@ SUBROUTINE read_eq_ldp_i ALLOCATE(psi(0:mx),f(0:mx),p(0:mx),q(0:mx), $ r(0:my,0:mx),z(0:my,0:mx),buf(0:mx)) c----------------------------------------------------------------------- -c read binary data. The psi bounds record follows z, ffp or pp. A -c length-mx read of it fails (short record); the file position is -c then processor dependent, so rewind and skip the 7 fixed records -c and any ffp, pp records before reading it. +c read binary data. The psi bounds record follows z, ffp or pp. +c Reading it into a profile array fails (short record); the file +c position is then processor dependent, so rewind and skip the 7 +c fixed records and any ffp, pp records before reading it. c----------------------------------------------------------------------- READ(in_unit)psi READ(in_unit)f From 3c990d195e6d381e68553e6633921fce75cd3e51 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:36:46 -0700 Subject: [PATCH 196/305] RDCON - MINOR - Say what zpbtrf reads in the gal_solve comment Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index b9fb65f1f..1d94d74cf 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1415,9 +1415,9 @@ SUBROUTINE gal_solve gal%sol=gal%rhs WRITE(*,*)"Grid generated with ",gal%ndim," DOF" c----------------------------------------------------------------------- -c Cholesky; if the matrix is not positive definite, warn and -c reassemble the full band from the element matrices for LU. -c The boundary rows and columns are already set in cell%mat. +c Cholesky (zpbtrf reads the lower band and the real part of the +c diagonal). If it fails, reassemble the full band from the element +c matrices, which already hold the boundary conditions, and use LU. c----------------------------------------------------------------------- IF (solver == "cholesky") THEN WRITE(*,*)"Performing Galerkin matrix Cholesky factorization" From 36c708f8fa4bf729d419ba79b234e619710d6c7a Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:36:48 -0700 Subject: [PATCH 197/305] RDCON - MINOR - Name the spline in the gal_get_fkg comment Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index 69cbf90de..c6c155388 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1026,8 +1026,8 @@ SUBROUTINE gal_get_fkg(psifac,f,k,g) q=sq%f(4) singfac=mlow-nn*q+(/(ipert,ipert=0,mpert-1)/) c----------------------------------------------------------------------- -c compute f = (Q L)(Q L)^H from the factored spline used by the -c asymptotic series, so the big solution solves this operator. +c compute f = (Q L)(Q L)^H from fmats, the spline of L that +c sing_mmat uses, so both use the same F. c----------------------------------------------------------------------- CALL cspline_eval(fmats,psifac,0) lmat=0 From ee37bf4db41ea26a95eb42076d647e375f15f3fb Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:36:56 -0700 Subject: [PATCH 198/305] RMATCH - BUGFIX - Reread old vmat.bin headers by rewind and skip, not BACKSPACE; clearer comments Co-Authored-By: Claude Opus 5.5 --- dcon/sing.f | 2 +- rdcon/sing.f | 2 +- rdcon/sing1.f | 2 +- rmatch/msing.f | 13 +++++++++---- stride/sing.F | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/dcon/sing.f b/dcon/sing.f index 83dbe29b2..d7005a11b 100644 --- a/dcon/sing.f +++ b/dcon/sing.f @@ -306,7 +306,7 @@ SUBROUTINE sing_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- -c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c eigenvalues of m0mat: tr/2 -+ SQRT(tr**2/4-det(m0mat)). c----------------------------------------------------------------------- tr=m0mat(1,1)+m0mat(2,2) sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) diff --git a/rdcon/sing.f b/rdcon/sing.f index 4448b9817..bc7a5b64b 100644 --- a/rdcon/sing.f +++ b/rdcon/sing.f @@ -301,7 +301,7 @@ SUBROUTINE sing_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- -c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c eigenvalues of m0mat: tr/2 -+ SQRT(tr**2/4-det(m0mat)). c----------------------------------------------------------------------- tr=m0mat(1,1)+m0mat(2,2) sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) diff --git a/rdcon/sing1.f b/rdcon/sing1.f index a60fb4761..72121196f 100644 --- a/rdcon/sing1.f +++ b/rdcon/sing1.f @@ -493,7 +493,7 @@ SUBROUTINE sing1_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- -c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c eigenvalues of m0mat: tr/2 -+ SQRT(tr**2/4-det(m0mat)). c----------------------------------------------------------------------- tr=m0mat(1,1)+m0mat(2,2) sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) diff --git a/rmatch/msing.f b/rmatch/msing.f index 0fd96d060..133d57a12 100644 --- a/rmatch/msing.f +++ b/rmatch/msing.f @@ -54,7 +54,7 @@ MODULE msing_mod c----------------------------------------------------------------------- SUBROUTINE msing_read(vmat_filename) CHARACTER(*) :: vmat_filename - INTEGER :: ising,ios + INTEGER :: ising,ios,irec c----------------------------------------------------------------------- c read power series data to binary file. c----------------------------------------------------------------------- @@ -68,11 +68,15 @@ SUBROUTINE msing_read(vmat_filename) READ(debug_unit,IOSTAT=ios)singp%order,singp%alpha, $ singp%psifac,singp%q,singp%r1,singp%r2,singp%power c----------------------------------------------------------------------- -c files without the powers end this record after r2; for them the -c powers are -alpha, +alpha. +c files without the powers end this record after r2: reread it (the +c position after a failed read is processor dependent, so rewind +c and skip) and use -alpha, +alpha. c----------------------------------------------------------------------- IF(ios /= 0)THEN - BACKSPACE(debug_unit) + REWIND(debug_unit) + DO irec=1,2*ising-1 + READ(debug_unit) + ENDDO READ(debug_unit)singp%order,singp%alpha,singp%psifac, $ singp%q,singp%r1,singp%r2 singp%power=(/-singp%alpha,singp%alpha/) @@ -133,6 +137,7 @@ SUBROUTINE msing_get_ua(ising,psifac,ua) c----------------------------------------------------------------------- ua(r1,:,1)=ua(r1,:,1)/sqrtfac ua(r1,:,2)=ua(r1,:,2)*sqrtfac +c power(1) as a division keeps the old ua bitwise for old files. ua(:,r2(1),:)=ua(:,r2(1),:)/dpsi**(-singp%power(1)) ua(:,r2(2),:)=ua(:,r2(2),:)*dpsi**singp%power(2) c----------------------------------------------------------------------- diff --git a/stride/sing.F b/stride/sing.F index 40a57d30b..cf897e1e5 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -398,7 +398,7 @@ SUBROUTINE sing_vmat(ising) singp%di=di singp%alpha=SQRT(-CMPLX(singp%di,KIND=r8)) c----------------------------------------------------------------------- -c eigenvalues of m0mat: tr/2 -+ sqrtd, sqrtd = SQRT(tr**2/4-det). +c eigenvalues of m0mat: tr/2 -+ SQRT(tr**2/4-det(m0mat)). c----------------------------------------------------------------------- tr=m0mat(1,1)+m0mat(2,2) sqrtd=SQRT(tr*tr/4-m0mat(1,1)*m0mat(2,2)+m0mat(2,1)*m0mat(1,2)) From 0960ae5723e298e298948c7dcfd1316a42d96870 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:37:36 -0700 Subject: [PATCH 199/305] EQUIL - MINOR - Unit test checks the kept slopes exactly; shorter profile_source comments Co-Authored-By: Claude Opus 5.5 --- equil/equil_out.f | 2 +- equil/read_eq.f | 19 ++--- input/equil.in | 2 +- .../spline_tests/test_spline_hermite.f90 | 75 +++++-------------- 4 files changed, 30 insertions(+), 68 deletions(-) diff --git a/equil/equil_out.f b/equil/equil_out.f index 3428b754f..bb4fb90d8 100644 --- a/equil/equil_out.f +++ b/equil/equil_out.f @@ -847,7 +847,7 @@ SUBROUTINE equil_out_dump WRITE(dump_unit)rzphi%name WRITE(dump_unit)rzphi%periodic c----------------------------------------------------------------------- -c write supplied f, p derivatives and eqfun (read only if present). +c write f, p knot derivatives and eqfun; optional for read_eq_dump. c----------------------------------------------------------------------- WRITE(dump_unit)sq_in_slopes(1:2),sq%fs1(:,1:2) IF(eqfun%allocated)WRITE(dump_unit)eqfun%fs diff --git a/equil/read_eq.f b/equil/read_eq.f index 0c13bfdba..943973687 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -2414,16 +2414,12 @@ SUBROUTINE read_eq_marklin_inverse END SUBROUTINE read_eq_marklin_inverse c----------------------------------------------------------------------- c subprogram 32. read_eq_profiles_from_derivatives. -c profile_source = "values" (default): tabulated values only. -c "integrate" (opt-in, for single-precision g-files): builds the f -c and mu0 p columns of sq_in from ffp = f*df/dpsifac and -c pp = d(mu0 p)/dpsifac. Values are integrated inward from the -c boundary values of f**2/2 and mu0 p; knot derivatives ffp/f and -c pp are kept by spline_fit_hermite. Tabulated values are not -c paired with the tabulated derivatives: their rounding noise -c would set f''. The quadrature between knots can move outer -c surfaces near sharp profile features. Falls back to "values" if -c the derivatives are unusable. +c profile_source = "integrate": replaces the f and mu0 p columns +c of sq_in by integrals of ffp = f*df/dpsifac and +c pp = d(mu0 p)/dpsifac inward from the boundary values, and sets +c ffp/f and pp as knot derivatives for spline_fit_hermite. Leaves +c sq_in unchanged for "values", or, with a warning, if the +c derivatives are unusable. c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. @@ -2451,7 +2447,8 @@ SUBROUTINE read_eq_profiles_from_derivatives(ffp,pp) f=sq_in%fs(:,1) p=sq_in%fs(:,2) c----------------------------------------------------------------------- -c check derivatives are finite and agree in sign with the values. +c check derivatives are finite and that f and p agree on their sign +c relative to the values (that common sign is applied below). c----------------------------------------------------------------------- IF(.NOT.(ALL(ABS(ffp) <= HUGE(one)) .AND. $ ALL(ABS(pp) <= HUGE(one)) .AND. ALL(f /= 0)))THEN diff --git a/input/equil.in b/input/equil.in index 761ec70a5..d922f0ed6 100644 --- a/input/equil.in +++ b/input/equil.in @@ -15,7 +15,7 @@ newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions - profile_source="values" ! How f, p are built for files with FF', p' (efit, ldp_i). values: tabulated f, p (default). integrate: integrated from the boundary with FF', p' as spline derivatives; opt-in for single-precision g-files, can move outer surfaces + profile_source="values" ! efit, and ldp_i files with FF', p' records. "values" (default): spline the tabulated f, p. "integrate": integrate FF', p' inward from the boundary and keep them as knot derivatives; for g-files with single-precision values; can shift pedestal surfaces input_only=f ! Generate information about the input and then quit with no further calculation use_galgrid=t ! Use the same grid as galerkin method when regrid=.True. diff --git a/regression/spline_tests/test_spline_hermite.f90 b/regression/spline_tests/test_spline_hermite.f90 index d13694e40..d57f1d415 100644 --- a/regression/spline_tests/test_spline_hermite.f90 +++ b/regression/spline_tests/test_spline_hermite.f90 @@ -1,73 +1,38 @@ !----------------------------------------------------------------------- -! Unit tests for spline_fit_hermite and the "extrap" end slopes. -! 1. cubic Hermite with exact knot slopes converges at 4th order. -! 2. "extrap" end slopes converge at 3rd order or better. -! 3. columns not kept are bit-for-bit those of spline_fit. +! Unit test for spline_fit_hermite: flagged columns keep the supplied +! knot derivatives; other columns are those of spline_fit. ! Exits with status 1 on failure. !----------------------------------------------------------------------- PROGRAM test_spline_hermite USE spline_mod IMPLICIT NONE - INTEGER, PARAMETER :: nres=4, mfine=2001 - INTEGER, DIMENSION(nres), PARAMETER :: mxs=(/16,32,64,128/) - INTEGER :: ires,ix,mx - REAL(r8), DIMENSION(nres) :: err_val,err_end - REAL(r8) :: x,order_val,order_end - REAL(r8), DIMENSION(:,:), ALLOCATABLE :: fs1_ref + INTEGER, PARAMETER :: mx=32 + INTEGER :: ix + REAL(r8), DIMENSION(0:mx) :: slopes + REAL(r8), DIMENSION(0:mx,2) :: fs1_ref TYPE(spline_type) :: spl LOGICAL :: ok=.TRUE. - DO ires=1,nres - mx=mxs(ires) - CALL spline_alloc(spl,mx,2) - spl%xs=(/(ix,ix=0,mx)/)/REAL(mx,r8) - spl%fs(:,1)=f1(spl%xs) - spl%fs(:,2)=f1(spl%xs) - spl%fs1(:,1)=df1(spl%xs) - CALL spline_fit_hermite(spl,"extrap",(/.TRUE.,.FALSE./)) - err_val(ires)=0 - DO ix=0,mfine - x=ix/REAL(mfine,r8) - CALL spline_eval(spl,x,0) - err_val(ires)=MAX(err_val(ires),ABS(spl%f(1)-f1(x))) - ENDDO - err_end(ires)=MAX(ABS(spl%fs1(0,2)-df1(0._r8)),ABS(spl%fs1(mx,2)-df1(1._r8))) - ALLOCATE(fs1_ref(0:mx,2)) - fs1_ref=spl%fs1 - CALL spline_fit(spl,"extrap") - IF(ANY(spl%fs1(:,2) /= fs1_ref(:,2)))THEN - WRITE(*,*)"FAIL: column not kept differs from spline_fit, mx = ",mx - ok=.FALSE. - ENDIF - DEALLOCATE(fs1_ref) - CALL spline_dealloc(spl) - ENDDO - order_val=LOG(err_val(nres-1)/err_val(nres))/LOG(2._r8) - order_end=LOG(err_end(nres-1)/err_end(nres))/LOG(2._r8) - WRITE(*,'(a,4es10.2,a,f5.2)')" hermite max error:",err_val,", order",order_val - WRITE(*,'(a,4es10.2,a,f5.2)')" extrap end slope error:",err_end,", order",order_end - IF(order_val < 3.8_r8)THEN - WRITE(*,*)"FAIL: Hermite with exact slopes is not 4th order" + CALL spline_alloc(spl,mx,2) + spl%xs=(/(ix,ix=0,mx)/)/REAL(mx,r8) + spl%fs(:,1)=SIN(3*spl%xs) + spl%fs(:,2)=spl%fs(:,1) + CALL spline_fit(spl,"extrap") + fs1_ref=spl%fs1 + slopes=3*COS(3*spl%xs) + spl%fs1(:,1)=slopes + CALL spline_fit_hermite(spl,"extrap",(/.TRUE.,.FALSE./)) + IF(ANY(spl%fs1(:,1) /= slopes))THEN + WRITE(*,*)"FAIL: flagged column lost the supplied derivatives" ok=.FALSE. ENDIF - IF(order_end < 2.8_r8)THEN - WRITE(*,*)"FAIL: extrap end slopes are not 3rd order" + IF(ANY(spl%fs1(:,2) /= fs1_ref(:,2)))THEN + WRITE(*,*)"FAIL: column not flagged differs from spline_fit" ok=.FALSE. ENDIF + CALL spline_dealloc(spl) IF(.NOT.ok)STOP 1 WRITE(*,*)"PASS" -CONTAINS - - ELEMENTAL REAL(r8) FUNCTION f1(x) - REAL(r8), INTENT(IN) :: x - f1=SIN(3*x)+EXP(-x)*x**2 - END FUNCTION f1 - - ELEMENTAL REAL(r8) FUNCTION df1(x) - REAL(r8), INTENT(IN) :: x - df1=3*COS(3*x)+EXP(-x)*(2*x-x**2) - END FUNCTION df1 - END PROGRAM test_spline_hermite From 2a889791081fcf283467cce357b1d5653b7e42a2 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 01:37:54 -0700 Subject: [PATCH 200/305] EQUIL, PENTRC - MINOR - Correct the pchip reference and comments Co-Authored-By: Claude Opus 5.5 --- equil/spline.f | 8 ++++---- pentrc/inputs.f90 | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/equil/spline.f b/equil/spline.f index 02a0799ba..53eb194d8 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -1852,10 +1852,10 @@ SUBROUTINE spline_refine_root(spl, iqty, x, op_tol) END SUBROUTINE spline_refine_root c----------------------------------------------------------------------- c subprogram 24. spline_fit_pchip. -c fits monotone piecewise cubic Hermite interpolants (Fritsch and -c Carlson, SIAM J. Numer. Anal. 17, 238, 1980), with the end slopes -c of scipy's PchipInterpolator. For tabulated data without -c derivatives: no overshoot or ringing at steep gradients. +c monotone piecewise cubic Hermite fit (pchip) for tabulated data +c without derivatives: no overshoot between points. Slopes as in +c scipy's PchipInterpolator (Fritsch and Butland, SIAM J. Sci. +c Stat. Comput. 5, 300, 1984). c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index 1b0c54c46..12397fbe2 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -215,7 +215,7 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) do i=1,5 tmp%fs(0:,i) = table(1:,i+1) enddo - call spline_fit_pchip(tmp) ! monotone: no ringing at the pedestal + call spline_fit_pchip(tmp) ! monotone between table points if(write_log) print *,"Formed temporary spline" ! extrapolate to regular spline (helps smooth core??) From 8fc560616a85d5e5321b875009e25ff666e0e3ac Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Tue, 13 Oct 2026 05:50:52 -0700 Subject: [PATCH 201/305] RDCON - BUGFIX - Galerkin vacuum matrix keeps its imaginary part free_get_wvac called mscvac with complex_flag=.FALSE., so the Galerkin Delta' saw only Re(wv) and, on up-down asymmetric equilibria, missed the frame fix of the previous commits. Co-Authored-By: Claude Opus 5.5 --- rdcon/free.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdcon/free.f b/rdcon/free.f index 3c84387d8..f7b236e69 100644 --- a/rdcon/free.f +++ b/rdcon/free.f @@ -670,7 +670,7 @@ END SUBROUTINE free_ahb_write c declarations. c----------------------------------------------------------------------- SUBROUTINE free_get_wvac() - LOGICAL, PARAMETER :: complex_flag=.FALSE. + LOGICAL, PARAMETER :: complex_flag=.TRUE. INTEGER :: ipert REAL(r8) :: kernelsignin REAL(r8), DIMENSION(mpert) :: singfac From 7ff5cc770c836be747560cdc111ae4f63b12deeb Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 11 Oct 2026 11:48:37 +0200 Subject: [PATCH 202/305] Preserve constant offsets in spline and cspline endpoint derivatives The 'extrap' end derivatives were evaluated as SUM(c*f) with c(1) = -sum(c(2:4)). For a constant profile, or any profile with a large offset, on a grid clustered at an endpoint the four terms cancel catastrophically (error ~ eps*|f|/h^3). Evaluate the same four-point derivative from differences to the endpoint value. --- equil/cspline.f | 8 ++- equil/spline.f | 8 ++- .../test/spline_endpoint_polynomials.f90 | 71 +++++++++++++++++++ .../test/test_spline_endpoint_polynomials.py | 46 ++++++++++++ 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 regression/test/spline_endpoint_polynomials.f90 create mode 100644 regression/test/test_spline_endpoint_polynomials.py diff --git a/equil/cspline.f b/equil/cspline.f index 7338e6255..b03885647 100644 --- a/equil/cspline.f +++ b/equil/cspline.f @@ -295,9 +295,11 @@ SUBROUTINE cspline_fit_ahg(spl,endmode) SELECT CASE(endmode) CASE("extrap") DO iqty=1,spl%nqty - spl%fs1(0,iqty)=SUM(cl(1:4)*spl%fs(0:3,iqty)) - spl%fs1(spl%mx,iqty)=SUM(cr(1:4) - $ *spl%fs(spl%mx:spl%mx-3:-1,iqty)) + spl%fs1(0,iqty)=SUM(cl(2:4) + $ *(spl%fs(1:3,iqty)-spl%fs(0,iqty))) + spl%fs1(spl%mx,iqty)=SUM(cr(2:4) + $ *(spl%fs(spl%mx-1:spl%mx-3:-1,iqty) + $ -spl%fs(spl%mx,iqty))) spl%fs1(1,iqty)=spl%fs1(1,iqty)-spl%fs1(0,iqty) $ /(spl%xs(1)-spl%xs(0)) spl%fs1(spl%mx-1,iqty)= diff --git a/equil/spline.f b/equil/spline.f index ec3829196..1a57b170f 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -197,9 +197,11 @@ SUBROUTINE spline_fit_ahg(spl,endmode) SELECT CASE(endmode) CASE("extrap") DO iqty=1,spl%nqty - spl%fs1(0,iqty)=SUM(cl(1:4)*spl%fs(0:3,iqty)) - spl%fs1(spl%mx,iqty)=SUM(cr(1:4) - $ *spl%fs(spl%mx:spl%mx-3:-1,iqty)) + spl%fs1(0,iqty)=SUM(cl(2:4) + $ *(spl%fs(1:3,iqty)-spl%fs(0,iqty))) + spl%fs1(spl%mx,iqty)=SUM(cr(2:4) + $ *(spl%fs(spl%mx-1:spl%mx-3:-1,iqty) + $ -spl%fs(spl%mx,iqty))) spl%fs1(1,iqty)=spl%fs1(1,iqty)-spl%fs1(0,iqty) $ /(spl%xs(1)-spl%xs(0)) spl%fs1(spl%mx-1,iqty)= diff --git a/regression/test/spline_endpoint_polynomials.f90 b/regression/test/spline_endpoint_polynomials.f90 new file mode 100644 index 000000000..f3b674932 --- /dev/null +++ b/regression/test/spline_endpoint_polynomials.f90 @@ -0,0 +1,71 @@ +program spline_endpoint_polynomials + use spline_mod, only: r8, spline_type, spline_alloc, spline_fit, & + spline_eval, spline_int, spline_dealloc + use cspline_mod, only: cspline_type, cspline_alloc, cspline_fit, & + cspline_dealloc + implicit none + integer :: resolution, degree, m, i + real(r8) :: value_error, derivative_error, integral_error, s, pi + real(r8) :: q0, q1, expected_integral + type(spline_type) :: profile, extended + type(cspline_type) :: cprofile + + pi = acos(-1.0_r8) + do resolution = 1, 2 + m = 256 * 2**resolution + do degree = 0, 3, 3 + call spline_alloc(profile, m, 1) + profile%xs = 1e-4_r8 + (1.0_r8 - 2e-4_r8) * & + sin([(real(i, r8) / m, i = 0, m)] * pi / 2)**2 + profile%fs(:, 1) = polynomial(profile%xs, degree) + call spline_fit(profile, 'extrap') + derivative_error = maxval(abs(profile%fs1(:, 1) - & + derivative(profile%xs, degree))) + call spline_eval(profile, 0.0_r8, 0) + q0 = profile%f(1) + call spline_eval(profile, 1.0_r8, 0) + q1 = profile%f(1) + value_error = max(abs(q0 - polynomial(0.0_r8, degree)), & + abs(q1 - polynomial(1.0_r8, degree))) + call spline_alloc(extended, m + 2, 1) + extended%xs = [0.0_r8, profile%xs, 1.0_r8] + extended%fs(:, 1) = [q0, profile%fs(:, 1), q1] + call spline_fit(extended, 'extrap') + call spline_int(extended) + expected_integral = 1.0_r8 + if (degree == 3) expected_integral = 2.0_r8 + 1.5_r8 + & + 4.0_r8 / 3 + 1.25_r8 + integral_error = abs(extended%fsi(m + 2, 1) - expected_integral) + write(*, '(2i6,3es25.16)') m, degree, derivative_error, & + value_error, integral_error + call spline_dealloc(extended) + call spline_dealloc(profile) + end do + end do + ! complex profile with a large constant offset, as the F, G, K matrices have + do resolution = 1, 2 + m = 256 * 2**resolution + call cspline_alloc(cprofile, m, 1) + cprofile%xs = 1e-4_r8 + (1.0_r8 - 2e-4_r8) * & + sin([(real(i, r8) / m, i = 0, m)] * pi / 2)**2 + cprofile%fs(:, 1) = cmplx(300.0_r8, -2.0_r8, r8) + call cspline_fit(cprofile, 'extrap') + derivative_error = maxval(abs(cprofile%fs1(:, 1))) + write(*, '(a,i6,es25.16)') 'complex', m, derivative_error + call cspline_dealloc(cprofile) + end do +contains + elemental real(r8) function polynomial(x, degree) result(q) + real(r8), intent(in) :: x + integer, intent(in) :: degree + q = 1.0_r8 + if (degree == 3) q = 2.0_r8 + x * (3.0_r8 + x * (4.0_r8 + 5.0_r8*x)) + end function polynomial + + elemental real(r8) function derivative(x, degree) result(dq) + real(r8), intent(in) :: x + integer, intent(in) :: degree + dq = 0.0_r8 + if (degree == 3) dq = 3.0_r8 + x * (8.0_r8 + 15.0_r8*x) + end function derivative +end program spline_endpoint_polynomials diff --git a/regression/test/test_spline_endpoint_polynomials.py b/regression/test/test_spline_endpoint_polynomials.py new file mode 100644 index 000000000..9349a2ad9 --- /dev/null +++ b/regression/test/test_spline_endpoint_polynomials.py @@ -0,0 +1,46 @@ +"""Endpoint extrapolation must preserve exact low-degree profiles.""" +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +class SplineEndpointPolynomials(unittest.TestCase): + @unittest.skipUnless(shutil.which('gfortran'), 'gfortran is required') + def test_clustered_grid_preserves_polynomial_derivatives_and_flux(self): + root = Path(__file__).resolve().parents[2] + scratch = Path(os.environ.get('GPEC_TEST_SCRATCH', root / 'build-test')) + scratch.mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(dir=scratch) as directory: + work = Path(directory) + for name in ('io', 'local', 'utils', 'spline', 'cspline'): + source = root / 'equil' / (name + '.f') + if name == 'spline' and os.environ.get('GPEC_SPLINE_SOURCE'): + source = Path(os.environ['GPEC_SPLINE_SOURCE']) + subprocess.run(['gfortran', '-O2', '-ffixed-line-length-none', + '-c', str(source)], cwd=work, check=True, + capture_output=True) + driver = root / 'regression/test/spline_endpoint_polynomials.f90' + subprocess.run(['gfortran', '-O2', str(driver), + *map(str, sorted(work.glob('*.o'))), + '-llapack', '-lblas', '-o', str(work / 'probe')], + cwd=work, check=True, capture_output=True) + result = subprocess.run([str(work / 'probe')], cwd=work, check=True, + capture_output=True, text=True) + for line in result.stdout.splitlines(): + if line.startswith('complex'): + _, resolution, derivative = line.split() + with self.subTest(resolution=resolution, profile='complex'): + self.assertLess(float(derivative), 1e-7) + continue + resolution, degree, derivative, endpoint, integral = line.split() + with self.subTest(resolution=resolution, degree=degree): + self.assertLess(float(derivative), 1e-7) + self.assertLess(float(endpoint), 1e-8) + self.assertLess(float(integral), 1e-10) + + +if __name__ == '__main__': + unittest.main() From c2ab98fb355ae7286fb460de5cfad40fd76d80cf Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Tue, 13 Oct 2026 15:40:28 +0200 Subject: [PATCH 203/305] RDCON - BUG - Scale the Galerkin matrix before the LU solve The unscaled band matrix has a condition number near 1e20 (zgbcon rcond 3e-22 for the a5 tearing example), so the pivot order and the result depend on roundoff, e.g. on the thread count of a threaded BLAS: Delta_ij 13.18-13.30 for 1-8 OpenBLAS threads. Symmetric diagonal scaling lowers the condition number to about 2e12 and gives 13.23 for every thread count. The cholesky path is unchanged. --- rdcon/gal.f | 29 +++++++++++++++- regression/test/test_rdcon_lu_threads.py | 44 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 regression/test/test_rdcon_lu_threads.py diff --git a/rdcon/gal.f b/rdcon/gal.f index 47bfb9d01..46e7a16cf 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1371,7 +1371,8 @@ END SUBROUTINE gal_make_arrays c----------------------------------------------------------------------- SUBROUTINE gal_solve - INTEGER :: info,isol,jsol,imap,ising,ix,msol_old + INTEGER :: info,isol,jsol,imap,ising,ix,msol_old,i,j,irow + REAL(r8), DIMENSION(:), ALLOCATABLE :: dscale COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: delta TYPE(gal_type) :: gal c----------------------------------------------------------------------- @@ -1387,12 +1388,38 @@ SUBROUTINE gal_solve c----------------------------------------------------------------------- gal%sol=gal%rhs IF (solver == "LU") THEN +c----------------------------------------------------------------------- +c symmetric diagonal scaling, D^-1/2 A D^-1/2. the unscaled Galerkin +c matrix has a condition number near 1e20, so the pivot order of the +c LU factorization, and with it the result, depends on roundoff +c (e.g. on the BLAS threading). cholesky does not need this. +c----------------------------------------------------------------------- + ALLOCATE(dscale(gal%ndim)) + DO j=1,gal%ndim + dscale(j)=ABS(gal%mat(gal%kl+gal%ku+1,j)) + IF(dscale(j) > 0)THEN + dscale(j)=1/SQRT(dscale(j)) + ELSE + dscale(j)=1 + ENDIF + ENDDO + DO j=1,gal%ndim + DO i=MAX(1,j-gal%ku),MIN(gal%ndim,j+gal%kl) + irow=gal%kl+gal%ku+1+i-j + gal%mat(irow,j)=gal%mat(irow,j)*dscale(i)*dscale(j) + ENDDO + gal%sol(j,:)=gal%sol(j,:)*dscale(j) + ENDDO WRITE(*,*)"Performing Galerkin matrix LU factorization" CALL zgbtrf(gal%ndim,gal%ndim,gal%kl,gal%ku,gal%mat,gal%ldab, $ gal%ipiv,info) WRITE(*,*)"Calculating Galerkin matrix solution" CALL zgbtrs("N",gal%ndim,gal%kl,gal%ku,gal%nsol,gal%mat, $ gal%ldab,gal%ipiv,gal%sol,gal%ndim,info ) + DO j=1,gal%ndim + gal%sol(j,:)=gal%sol(j,:)*dscale(j) + ENDDO + DEALLOCATE(dscale) ELSEIF (solver == "cholesky") THEN WRITE(*,*)"Performing Galerkin matrix Cholesky factorization" CALL zpbtrf('L',gal%ndim,gal%kl,gal%mat,gal%ldab,info) diff --git a/regression/test/test_rdcon_lu_threads.py b/regression/test/test_rdcon_lu_threads.py new file mode 100644 index 000000000..3257c0325 --- /dev/null +++ b/regression/test/test_rdcon_lu_threads.py @@ -0,0 +1,44 @@ +"""The LU Galerkin solve in rdcon must not depend on the BLAS thread count. + +Needs a built bin/rdcon (GPEC_RDCON may point at another binary). The check +only discriminates when LAPACK/BLAS is a threaded library such as OpenBLAS; +with reference BLAS both runs agree trivially. +""" +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +def delta_prime(root, exe, threads, scratch): + """Run the a5 tearing example (solver="LU") and return the Delta_ij entry.""" + example = root / 'docs/examples/a5_tearing_example' + with tempfile.TemporaryDirectory(dir=scratch) as directory: + work = Path(directory) + for name in ('equil.in', 'gfile_a5_cir', 'rdcon.in', 'vac.in'): + shutil.copy(example / name, work) + env = dict(os.environ, GPECHOME=str(root), OMP_NUM_THREADS=str(threads), + OPENBLAS_NUM_THREADS=str(threads)) + # the end of the run may fail after the matching data are written + subprocess.run([str(exe)], cwd=work, env=env, capture_output=True) + lines = (work / 'pest3_data_re.out').read_text().splitlines() + return float(lines[2].split()[-1]) + + +class RdconLuThreads(unittest.TestCase): + def test_delta_prime_independent_of_thread_count(self): + root = Path(__file__).resolve().parents[2] + exe = Path(os.environ.get('GPEC_RDCON', root / 'bin/rdcon')) + if not exe.exists(): + self.skipTest('rdcon is not built') + scratch = Path(os.environ.get('GPEC_TEST_SCRATCH', root / 'build-test')) + scratch.mkdir(exist_ok=True) + values = [delta_prime(root, exe, n, scratch) for n in (1, 2, 4, 8)] + for value in values: + self.assertAlmostEqual(value, values[0], delta=2e-3) + + +if __name__ == '__main__': + unittest.main() From 1afb2349a2f160ee9887b5faa87991751cd0a766 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 14 Oct 2026 09:13:00 +0200 Subject: [PATCH 204/305] PENTRC: export radial surface responses without stale dense derivatives --- install/TARGETS.inc | 9 ++++++ pentrc/torque.F90 | 8 +++-- regression/test/pentrc_radial_profile.f90 | 39 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 regression/test/pentrc_radial_profile.f90 diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..3f7ab585a 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -431,3 +431,12 @@ mkbin: pentrc dcon rdcon stride match rmatch slayer multi sum gpec cp ../sum/sum $(BINDIR) cp ../gpec/gpec $(BINDIR) $(call print_success,All binaries copied to $(BINDIR)) + +# Focused PENTRC regression; CASE contains a normal readable PENTRC input deck. +.PHONY: check-pentrc-radial +check-pentrc-radial: pentrc + $(FCPENTRC) -I../pentrc ../regression/test/pentrc_radial_profile.f90 \ + $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) \ + -o $(BINDIR)/test_pentrc_radial_profile + @test -n "$(CASE)" || { echo 'Set CASE to a PENTRC case directory'; exit 2; } + cd "$(CASE)" && $(abspath $(BINDIR))/test_pentrc_radial_profile diff --git a/pentrc/torque.F90 b/pentrc/torque.F90 index b4e41df77..a930b6194 100644 --- a/pentrc/torque.F90 +++ b/pentrc/torque.F90 @@ -1230,7 +1230,7 @@ function tintgrl_lsode(psilim,n,nl,zi,mi,wdfac,divxfac,electron,method) complex(r8), dimension(maxsteps,mpert**2,6) :: kel_flat_mats character(8) :: methcom ! declare lsode input variables - integer iopt, istate, itask, itol, mf, iflag,neqarray(6),& + integer iopt, istate, itask, itol, mf,neqarray(6),& neq,liw,lrw integer, dimension(:), allocatable :: iwork real(r8) :: x,xout @@ -1287,7 +1287,11 @@ function tintgrl_lsode(psilim,n,nl,zi,mi,wdfac,divxfac,electron,method) xlast = x call lsode(tintgrnd, neqarray, y, x, xout, itol, rtol,& atol,itask,istate, iopt, rwork, lrw, iwork, liw, noj, mf) - call dintdy(x, 1, rwork(21), neq, dky, iflag) + ! Export the surface response, rather than the derivative of + ! LSODE's integral polynomial. At TCRIT, returned x can exceed + ! internal TN by a few ulps: DINTDY then leaves dky stale. Direct + ! evaluation also keeps flux functions and matrices at this x. + call tintgrnd(neqarray, x, y, dky) ! flux/diffusivity profiles call spline_eval(sq,x,0) diff --git a/regression/test/pentrc_radial_profile.f90 b/regression/test/pentrc_radial_profile.f90 new file mode 100644 index 000000000..05d97b3b3 --- /dev/null +++ b/regression/test/pentrc_radial_profile.f90 @@ -0,0 +1,39 @@ +! Run in a PENTRC case directory (pentrc.in, euler.bin, profiles and peq). +! The oracle evaluates the surface response directly, independently of the +! adaptive integral's dense-output polynomial. +program pentrc_radial_profile + use pentrc_interface, only: initialize_pentrc, r8, nn, zi, mi, & + wdfac, divxfac, electron, nl + use torque, only: tintgrl_lsode, trans, tpsi, atol_psi, rtol_psi, & + output_ascii, output_netcdf, torque_record + implicit none + integer :: i + real(r8) :: bounds(2), relative_error, max_error + complex(r8) :: integral, expected, actual + + call initialize_pentrc + output_ascii = .false. + output_netcdf = .false. + atol_psi = 1.0e-4_r8 + rtol_psi = 1.0e-4_r8 + bounds = [0.02_r8, 0.6_r8] + integral = tintgrl_lsode(bounds, nn, nl, zi, mi, wdfac, divxfac, & + electron, 'fgar') + max_error = 0.0_r8 + do i = 0, trans%mx + call tpsi(expected, trans%xs(i), nn, 0, zi, mi, wdfac, & + divxfac, electron, 'fgar') + actual = cmplx(torque_record(1, 1)%ell_record(7, nl + 1, i + 1), & + torque_record(1, 1)%ell_record(8, nl + 1, i + 1), r8) + relative_error = abs(actual - expected)/ & + max(abs(expected), tiny(1.0_r8)) + max_error = max(max_error, relative_error) + end do + print '(a,i0,a,es12.4)', 'Surface evaluations: ', trans%mx + 1, & + '; max relative error: ', max_error + if (abs(trans%xs(trans%mx) - bounds(2)) > 1.0e-14_r8) & + error stop 'Adaptive profile does not reach the requested endpoint' + if (max_error > 1.0e-10_r8) & + error stop 'Adaptive profile differs from direct surface response' + print '(a)', 'PASS: adaptive profile equals direct surface response' +end program pentrc_radial_profile From e1e98f27df29e64090cc7f1db8f8217d3d5b7749 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 14 Oct 2026 11:54:16 +0200 Subject: [PATCH 205/305] PENTRC: stop when the LSODE energy integration fails with ISTATE other than -1 --- install/TARGETS.inc | 10 ++++++++++ pentrc/energy.f90 | 21 +++++++++++++++++++++ regression/test/pentrc_energy_istate.f90 | 23 +++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 regression/test/pentrc_energy_istate.f90 diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..350d7222b 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -431,3 +431,13 @@ mkbin: pentrc dcon rdcon stride match rmatch slayer multi sum gpec cp ../sum/sum $(BINDIR) cp ../gpec/gpec $(BINDIR) $(call print_success,All binaries copied to $(BINDIR)) + +# Focused PENTRC regression: LSODE failures in the energy integral must stop. +.PHONY: check-pentrc-energy-istate +check-pentrc-energy-istate: pentrc + $(FCPENTRC) -I../pentrc ../regression/test/pentrc_energy_istate.f90 \ + $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) \ + -o $(BINDIR)/test_pentrc_energy_istate + $(BINDIR)/test_pentrc_energy_istate default + ! $(BINDIR)/test_pentrc_energy_istate tight > $(BINDIR)/test_pentrc_energy_istate.log 2>&1 + grep -q "ERROR: xintgrl_lsode - LSODE failed" $(BINDIR)/test_pentrc_energy_istate.log diff --git a/pentrc/energy.f90 b/pentrc/energy.f90 index e0a70b14e..345d11c39 100644 --- a/pentrc/energy.f90 +++ b/pentrc/energy.f90 @@ -209,6 +209,7 @@ function xintgrl_lsode(wn,wt,we,wd,wb,nuk,ell,leff,n,psi,lambda,method,op_record do while (x0)." endif + if(istate<0) call xintgrl_lsode_fail(istate, x, psi, lambda) ! save integration profile to memory if(record_this) then @@ -298,6 +302,23 @@ function xintgrl_lsode(wn,wt,we,wd,wb,nuk,ell,leff,n,psi,lambda,method,op_record return end function xintgrl_lsode + !======================================================================= + subroutine xintgrl_lsode_fail(istate, x, psi, lambda) + !----------------------------------------------------------------------- + !*DESCRIPTION: + ! Stop on an LSODE failure (ISTATE < 0) other than too many steps, + ! which xintgrl_lsode reports itself. LSODE returns the integral only + ! up to the failure point x on that contour leg, so the result + ! must not be used. + !----------------------------------------------------------------------- + integer, intent(in) :: istate + real(r8), intent(in) :: x, psi, lambda + + print *, "psi =", psi, ", lambda =", lambda, ", leff =", energy_leff + print *, "LSODE istate =", istate, " at x =", x + error stop "ERROR: xintgrl_lsode - LSODE failed before the end of the energy contour." + end subroutine xintgrl_lsode_fail + diff --git a/regression/test/pentrc_energy_istate.f90 b/regression/test/pentrc_energy_istate.f90 new file mode 100644 index 000000000..974c7b794 --- /dev/null +++ b/regression/test/pentrc_energy_istate.f90 @@ -0,0 +1,23 @@ +! Energy integral with an LSODE failure other than ISTATE = -1. +! "default": default tolerances, must return a finite integral. +! "tight": rtol = 1e-17, atol = 1e-30 makes LSODE return ISTATE = -2 (too much +! accuracy requested) near x = 8e-4; xintgrl_lsode must not return. +program pentrc_energy_istate + use params, only: r8 + use energy_integration, only: xintgrl_lsode, xatol, xrtol + implicit none + character(16) :: mode + complex(r8) :: integral + + call get_command_argument(1, mode) + if (trim(mode) == "tight") then + xatol = 1.0e-30_r8 + xrtol = 1.0e-17_r8 + end if + integral = xintgrl_lsode(0.1_r8, 1.0_r8, 0.5_r8, 0.1_r8, 0.0_r8, 1.0e-2_r8, & + 0, 0.0_r8, 1, 0.5_r8, 0.5_r8, "fgar") + print '(a,2es24.15)', "integral ", integral + if (trim(mode) == "tight") error stop "xintgrl_lsode returned after an LSODE failure" + if (.not. (abs(integral) > 0.0_r8 .and. abs(integral) < huge(1.0_r8))) & + error stop "default integral is not finite and nonzero" +end program pentrc_energy_istate From 6445606f0f926905c3867993e5e220a9dccbaaac Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 14 Oct 2026 15:38:43 -0400 Subject: [PATCH 206/305] SLAYER - MINOR - Give every real literal in params.f the r8 kind Exponents such as lu**(1.0/3.0), constants such as SQRT(2.0) and LOG(10.0), and the empirical coefficients were evaluated in single precision before promotion. Results move at the 1e-7 relative level. Co-Authored-By: Claude Opus 5.5 --- slayer/params.f | 80 ++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/slayer/params.f b/slayer/params.f index b8b93083a..b21bc564e 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -90,26 +90,26 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c Coulomb logarithm, basic plasma quantities, and Spitzer c resistivity. c----------------------------------------------------------------------- - lnLamb = 24 + 3.0*LOG(10.0) - 0.5*LOG(n_e) + LOG(t_e) + lnLamb = 24 + 3.0_r8*LOG(10.0_r8) - 0.5_r8*LOG(n_e) + LOG(t_e) tau = t_i / t_e ! T_i / T_e - tau_i = 6.6e17*mu_i**0.5*(t_i/1e3)**1.5 + tau_i = 6.6e17_r8*mu_i**0.5_r8*(t_i/1e3_r8)**1.5_r8 $ / (n_e*lnLamb) ! ion collision time [s] - eta = 1.65e-9*lnLamb / (t_e/1e3)**1.5 ! Spitzer resistivity (Wesson) + eta = 1.65e-9_r8*lnLamb / (t_e/1e3_r8)**1.5_r8 ! Spitzer resistivity (Wesson) rho = (mu_i*m_p)*n_e ! mass density [kg/m^3] c----------------------------------------------------------------------- c Electron-electron collision time (Braginskii) and Spitzer-Härm c parallel conductivity. c----------------------------------------------------------------------- - tau_ee_num = 6.0*SQRT(2.0)*(pi**1.5) - $ *(eps0**2.0)*(m_e**0.5)*(t_e**1.5) - tau_ee_denom = lnLamb*(chag**2.5)*n_e + tau_ee_num = 6.0_r8*SQRT(2.0_r8)*(pi**1.5_r8) + $ *(eps0**2.0_r8)*(m_e**0.5_r8)*(t_e**1.5_r8) + tau_ee_denom = lnLamb*(chag**2.5_r8)*n_e tau_ee = tau_ee_num / tau_ee_denom - sigma_par_1 = ( SQRT(2.0) + 13.0*(Zeff/4.0) ) - $ / (Zeff*(SQRT(2.0) + Zeff)) - sigma_par_2 = (n_e * (chag**2.0) * tau_ee) / m_e + sigma_par_1 = ( SQRT(2.0_r8) + 13.0_r8*(Zeff/4.0_r8) ) + $ / (Zeff*(SQRT(2.0_r8) + Zeff)) + sigma_par_2 = (n_e * (chag**2.0_r8) * tau_ee) / m_e sigma_par = sigma_par_1 * sigma_par_2 c----------------------------------------------------------------------- @@ -119,16 +119,16 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c module-level variables set before calling params(). c----------------------------------------------------------------------- b_l = (nr/mr)*rs*sval*bt/R0 ! characteristic B [T] - v_a = b_l / (mu0*rho)**0.5 ! Alfvén velocity [m/s] - rho_s = 1.02e-4*(mu_i*t_e)**0.5 / bt ! ion Larmor at T_e [m] - d_i = ( (mu_i*m_p) / (n_e*(chag**2)*mu0) )**0.5 + v_a = b_l / (mu0*rho)**0.5_r8 ! Alfvén velocity [m/s] + rho_s = 1.02e-4_r8*(mu_i*t_e)**0.5_r8 / bt ! ion Larmor at T_e [m] + d_i = ( (mu_i*m_p) / (n_e*(chag**2)*mu0) )**0.5_r8 ! ion skin depth [m] c The layer timescales and widths below depend on the magnitude of c the shear, not its sign: ABS(sval) keeps them positive on a c reverse-shear surface. - tau_h = R0*(mu0*rho)**0.5 / (nn*ABS(sval)*bt) ! Alfvén time [s] - tau_r = mu0*(rs**2.0)*sigma_par ! resistive time [s] (Fitzpatrick) + tau_h = R0*(mu0*rho)**0.5_r8 / (nn*ABS(sval)*bt) ! Alfvén time [s] + tau_r = mu0*(rs**2.0_r8)*sigma_par ! resistive time [s] (Fitzpatrick) IF (pr > 0.0_r8) THEN tau_v = tau_r / pr ! viscous time [s] visc = rho*rs**2.0_r8 / tau_v @@ -145,7 +145,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c Qconv converts dimensional frequencies to the normalised Q c used in the SLAYER dispersion relation (Cole scaling). c----------------------------------------------------------------------- - Qconv = lu**(1.0/3.0) * tau_h ! frequency normalisation (Cole) + Qconv = lu**(1.0_r8/3.0_r8) * tau_h ! frequency normalisation (Cole) tauk = Qconv ! stored in sglobal_mod Q = Qconv * omega ! normalised rotation frequency @@ -154,20 +154,20 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c compute omega_e/omega_i by other means must set Q_e and Q_i c themselves afterwards. IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN - omega_e = -t_e/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval - omega_i = t_i/(bt*R0)*(1.0/l_n + 1.0/l_t)*qval + omega_e = -t_e/(bt*R0)*(1.0_r8/l_n + 1.0_r8/l_t)*qval + omega_i = t_i/(bt*R0)*(1.0_r8/l_n + 1.0_r8/l_t)*qval Q_e = -Qconv * omega_e Q_i = -Qconv * omega_i END IF c normalised ion Larmor radius (critical stability parameter) - ds = lu**(1.0/3.0) * rho_s / rs + ds = lu**(1.0_r8/3.0_r8) * rho_s / rs c----------------------------------------------------------------------- c Plasma beta and Prandtl-number-like transport ratios. c----------------------------------------------------------------------- - lbeta = (5.0/3.0)*mu0*n_e*chag*(t_e+t_i) / bt**2.0 - c_beta = (lbeta / (1.0+lbeta))**0.5 + lbeta = (5.0_r8/3.0_r8)*mu0*n_e*chag*(t_e+t_i) / bt**2.0_r8 + c_beta = (lbeta / (1.0_r8+lbeta))**0.5_r8 c kappa-based P_perp (JKP's definition, to implement later if desired) c K_val = chis(3) / eta @@ -180,10 +180,10 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c END IF c effective perpendicular and toroidal Prandtl numbers - tau_perp = (rs**2.0) / chis(1) + tau_perp = (rs**2.0_r8) / chis(1) P_perp = tau_r / tau_perp ! perp magnetic Prandtl number - tau_tor = (rs**2.0) / chis(2) + tau_tor = (rs**2.0_r8) / chis(2) P_tor = tau_r / tau_tor ! toroidal magnetic Prandtl number c----------------------------------------------------------------------- @@ -197,14 +197,14 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c = 1/(1 + tau) is used provisionally; callers that set Q_e and Q_i c afterwards must reset D_norm = slayer_d_norm(D_fac,Q_e,Q_i). d_beta = c_beta * d_i - D_fac = (d_beta/rs) * lu**(1.0/3.0) + D_fac = (d_beta/rs) * lu**(1.0_r8/3.0_r8) IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN D_norm = slayer_d_norm(D_fac,Q_e,Q_i) ELSE D_norm = D_fac * SQRT(1.0_r8/(1.0_r8+tau)) END IF - delta_n = lu**(1.0/3.0) / rs ! normalisation for Delta values + delta_n = lu**(1.0_r8/3.0_r8) / rs ! normalisation for Delta values c----------------------------------------------------------------------- c Critical Deltaprime (dc_tmp) via iterative chi_parallel calculation. @@ -216,20 +216,20 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c 'toroidal' -- toroidal geometry using dgeo_val c default -- dc_tmp = 0 c----------------------------------------------------------------------- - IF (ABS(dr_val) > 0.0) THEN + IF (ABS(dr_val) > 0.0_r8) THEN - vte = SQRT((2.0*(t_e*chag)) / m_e) - chi_par_smfp = (1.581*tau_ee*(vte**2.0)) - $ / (1.0 + 0.2535*Zeff) + vte = SQRT((2.0_r8*(t_e*chag)) / m_e) + chi_par_smfp = (1.581_r8*tau_ee*(vte**2.0_r8)) + $ / (1.0_r8 + 0.2535_r8*Zeff) - Wd = 0.1 ! initial guess + Wd = 0.1_r8 ! initial guess DO wit = 1, 100 - chi_par_lmfp = (2.0*R0*vte) + chi_par_lmfp = (2.0_r8*R0*vte) $ / (SQRT(pi)*nr*ABS(sval)*Wd) chi_par = (chi_par_smfp*chi_par_lmfp) $ / (chi_par_smfp + chi_par_lmfp) - Wd_new = SQRT(8.0)*((chis(1)/chi_par)**0.25) - $ * (1.0/SQRT((rs/R0)*ABS(sval)*nr)) + Wd_new = SQRT(8.0_r8)*((chis(1)/chi_par)**0.25_r8) + $ * (1.0_r8/SQRT((rs/R0)*ABS(sval)*nr)) IF (ABS(Wd_new - Wd) / MAX(ABS(Wd), 1.0e-30_r8) $ < 1.0e-10_r8) THEN Wd = Wd_new @@ -244,20 +244,20 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, SELECT CASE(dc_type) CASE('lar') - dc_tmp = 0.5*(-dr_val)*(pi**1.5) - $ *((chi_par/chis(1))**0.25) - $ *( (nr*ABS(sval))/(R0*rs) )**0.5 + dc_tmp = 0.5_r8*(-dr_val)*(pi**1.5_r8) + $ *((chi_par/chis(1))**0.25_r8) + $ *( (nr*ABS(sval))/(R0*rs) )**0.5_r8 CASE('rfitzp') - dc_tmp = -(SQRT(2.0)*(pi**(1.5))*dr_val) / Wd + dc_tmp = -(SQRT(2.0_r8)*(pi**(1.5_r8))*dr_val) / Wd CASE('toroidal') - dc_tmp = 0.5*(-dr_val)*(pi**1.5) - $ *((chi_par/chis(1))**0.25)*dgeo_val + dc_tmp = 0.5_r8*(-dr_val)*(pi**1.5_r8) + $ *((chi_par/chis(1))**0.25_r8)*dgeo_val CASE default - dc_tmp = 0.0 + dc_tmp = 0.0_r8 END SELECT ELSE - dc_tmp = 0.0 + dc_tmp = 0.0_r8 END IF c----------------------------------------------------------------------- From 70fec274356343d870f51311b05c94f06356269d Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 14 Oct 2026 15:38:52 -0400 Subject: [PATCH 207/305] SLAYER - BUGFIX - Keep the imaginary part of the Delta' diagonal in the uncoupled matching The uncoupled dispersion used Re(Delta'_kk) only, while the coupled determinant uses the complex matrix, so the uncoupled result was not the diagonal limit of the coupled one. delta_eff is now complex, as in the Julia GPEC surface coupling. DIII-D-like example, dc_type none: 2/1 growth rate 79.55 -> 79.64 1/s; the 4/1, whose STRIDE Delta' has a large imaginary part, moves -306.6 -> -336.1 1/s. Co-Authored-By: Claude Opus 5.5 --- slayer/sglobal.f | 4 ++-- slayer/slayer.f | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 7454aa930..5df0b5026 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -84,8 +84,8 @@ MODULE sglobal_mod REAL(r8) :: Q_i ! normalised ion diamagnetic Q c --- stability / Delta_crit REAL(r8) :: dc_tmp ! computed Delta_crit - REAL(r8) :: delta_eff ! effective Deltaprime shift - CHARACTER(20) :: dc_type ! dc formula selector ('lar','rfitzp','toroidal') + COMPLEX(r8) :: delta_eff ! (Deltaprime - Delta_crit)/S^(1/3) + CHARACTER(20) :: dc_type ! dc formula: 'rfitzp','lar','toroidal','none' c --- solver workspace / results COMPLEX(r8) :: g_tmp ! temporary complex growth rate REAL(r8) :: gamma_fac ! growth-rate conversion factor diff --git a/slayer/slayer.f b/slayer/slayer.f index 03f1d075a..be50cbb89 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -842,12 +842,16 @@ PROGRAM slayer WRITE(*,*)"D_norm: ",D_norm WRITE(*,*)"tauk: ",tauk WRITE(*,*)"iota_e: ",iota_e - WRITE(*,*)"Delta_prime: ",sl_in%Re_dp_arr(k) + WRITE(*,*)"Delta_prime (r_s ref.): ",sl_in%Re_dp_arr(k), + $ sl_in%Im_dp_arr(k) WRITE(*,*)"Delta_crit: ",sl_in%d_crit_arr(k) - ! Calculate (Deltaprime - D_crit)/S^1/3 - delta_eff = (sl_in%Re_dp_arr(k) - - $ sl_in%d_crit_arr(k))/(sl_in%lu_arr(k)**(1.0/3.0)) + ! (Deltaprime - Delta_crit)/S^(1/3), with the complex + ! Deltaprime diagonal so that the uncoupled matching is + ! the diagonal limit of the coupled determinant. + delta_eff = (CMPLX(sl_in%Re_dp_arr(k),sl_in%Im_dp_arr(k), + $ KIND=r8) - sl_in%d_crit_arr(k)) + $ /(sl_in%lu_arr(k)**(1.0_r8/3.0_r8)) pe = 0.0 c----------------------------------------------------------------------- From 6b6b73a9f0c1976f725bf7f9e4e50a028e2f9ea0 Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 14 Oct 2026 15:38:52 -0400 Subject: [PATCH 208/305] SLAYER - BUGFIX - Harden the matched growth-rate path and default to the rfitzp critical Delta - dc_type defaults to 'rfitzp' (here and in input/slayer.in). A misspelt dc_type stops the run instead of silently dropping the critical Delta, and 'toroidal' warns that it is experimental. - The coupled determinant takes any number of surfaces (LU via LAPACK above rank 3) instead of stopping above 3, and msing_max = 1 goes through the same matrix path. msing_max is clipped to the number of surfaces, and coupling_flag without read_eq stops with a message instead of reading an unallocated Delta' matrix. - riccati_f returns NaN when LSODE fails, so the scan and the root search skip the point instead of using an unconverged Delta. - zeff and mu_i come from slayer.in in read_eq mode; they were hard-coded to 2. - Surfaces are labelled by their resonant m; INT(q) mislabelled n > 1 surfaces. The NetCDF q_rational holds the real q. - An estimate-only run (est_gamma_flag without match_gamma_flag) crashed in the unused baseline Riccati call and resized per-surface outputs to length 1. Both fixed, and the NetCDF writer no longer touches AMR data that a run did not produce. - input/slayer.in: uncoupled by default, new options documented (rotation_flag, scan_center), comments corrected (omega in rad/s, delta_prime in the r_s reference). Co-Authored-By: Claude Opus 5.5 --- input/slayer.in | 36 +++---- slayer/delta.f | 7 ++ slayer/growthrates.f | 218 +++++++++++++++++++---------------------- slayer/layerinputs.f | 44 +++++---- slayer/sglobal.f | 3 +- slayer/slayer.f | 81 +++++++++++---- slayer/slayer_netcdf.f | 36 +++++-- 7 files changed, 246 insertions(+), 179 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index 24077b95f..d2a37879b 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -1,6 +1,6 @@ &SLAYER_INPUT input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table - infile='' ! Path to ascii table of profile quantities read when using input_flag + infile='' ! Path to the kinetic profile file (read_eq=t), or to an ascii table of surface quantities (input_flag=t) ncfile='' ! Path to netCDF file of STRIDE outputs params_flag=f ! calculates normalized parameters from profile quantities ! >>> @@ -15,14 +15,14 @@ bt=2.0 ! toroidal field [T] rs=0.5 ! minor radius of resonant surface [m] R0=1.67 ! major radius of magnetic axis [m] - omega=5.00E+03 ! ExB frequency [Hz] + omega=5.00E+03 ! ExB rotation frequency [rad/s] l_t=0.11 ! temperature gradient scale length l_n=0.25 ! density gradient scale length qval=2.0 ! q of resonant surface - mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma - zeff=2.0 ! plasma Z_effective - dr_val=-0.1 ! GGJ resistive interchange criterion D_R - dgeo_val=10.0 ! J.W. Connor geometric prefactor for Delta_crit + mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma. Also used when read_eq=t + zeff=2.0 ! plasma Z_effective. Also used when read_eq=t (the kinetic file's Z_eff is not used) + dr_val=-0.1 ! GGJ resistive interchange index D_R (taken from STRIDE when read_eq=t) + dgeo_val=10.0 ! J.W. Connor geometric prefactor for the experimental dc_type="toroidal" (taken from STRIDE when read_eq=t) chi_p_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular energy diffusivity profile chi_t_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular ion momentum diffusivity profile kappa_prof=0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 ! Rational surface thermal conductivity profile (NOTE: set > 0 to override chi_p_prof) @@ -38,7 +38,7 @@ inds=0.0 ! normalized ion sound radius, or normalized ion skin depth. Set /= 0.0 to override internal calculation intau=1.0 ! ratio of ion to electron temperature Q0=1.0 ! unnecessary placeholder variable for inQ? - delta_prime=(0.0,0.0) ! input Delta' or (Delta' - Delta_crit) for growth rate calculation, set /= 0.0 to override internal calculation + delta_prime=(0.0,0.0) ! input Delta' for the growth rate calculation when read_eq=f, referenced to the minor radius (r_s*Delta') delta_n_p=(0.01,0.01) ! delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). ingamma=(0.0,0.0) ! input (Re(Q),Im(Q)) to calculate inner layer Delta. Set /= 0.0 to override internal root finding / @@ -47,14 +47,16 @@ inum=400 ! resolution to find error field thresholds. jnum=100 ! resolution for 2d scan along with Q,omega. knum=100 ! resolution for 2d scan along with the other. - Q_num=100 ! resolution for stability scan along Re(Q),Im(Q) axes - scan_width=1.5 ! stability scan width for Re(Q),Im(Q) scan - AMR_passes=4 - msing_max=2 ! number of surfaces to include in coupled tearing growth rate calculation - dc_type="toroidal" ! Delta_crit type, options are "toroidal", "lar", and "rfitzp" - read_eq=f ! read in equilibrium quantities from STRIDE and kinetic file. Set all normalized params to 0.0 + Q_num=100 ! number of points per axis of the initial Re(Q),Im(Q) scan grid + scan_width=1.5 ! half-width of the Re(Q),Im(Q) scan window, in normalized Q + AMR_passes=4 ! number of adaptive refinement passes of the scan grid + !scan_center=0.0 ! Re(Q) centre of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan + msing_max=2 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t + dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", "none", or "toroidal" (experimental) + read_eq=f ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation - coupling_flag=t ! Solve determinant problem and output 2D scan + coupling_flag=f ! solve the coupled multi-surface determinant instead of each surface separately. Requires read_eq=t + rotation_flag=t ! Doppler-shift each surface by its ExB rotation in the coupled determinant (root reported in the lab frame) QPscan_flag=f ! scan (Q,P) space for delta and torque. Qscan_flag=f ! scan Q space QPescan_flag=f ! scan (Q,Pe) space for delta and torque. @@ -75,11 +77,11 @@ bin_flag=f ! writes results to binary files netcdf_flag=f ! writes results to netcdf files est_gamma_flag=t ! Calculate and output growth rate estimate - match_gamma_flag=t ! Find Delta_hat = Delta_prime match to solve for growth rate + match_gamma_flag=t ! match the layer Delta to Delta' and solve for the growth rate stability_flag=f ! calculate delta dependence on complex Q stabscan_flag=f ! Generate Re(Q),Im(Q) scan and output as ASCII - coupled_stabscan_flag=f ! Generate Re(Q),Im(Q) scan and output as ASCII - amr_flag=t ! Generate Re(Q),Im(Q) scan and output as ASCII + coupled_stabscan_flag=f ! uniform-grid scan of the coupled determinant, output as ASCII + amr_flag=t ! adaptive Re(Q),Im(Q) scan and root extraction for match_gamma_flag (results in the netcdf file) br_th_flag=f ! Calculate br_th bal_flag=f ! calculate the resonant field penetration threshold from torque balance / diff --git a/slayer/delta.f b/slayer/delta.f index 67a2947cf..38da8630b 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -21,6 +21,7 @@ MODULE delta_mod c----------------------------------------------------------------------- USE sglobal_mod + USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan IMPLICIT NONE @@ -443,6 +444,12 @@ FUNCTION riccati_f() c --- extract Delta from final W derivative at p_min CALL w_der_f(neq,my_p,W,dWdp) riccati_f = pi / dWdp(1) +c A failed integration (istate < 0) leaves W short of p_min. Return +c NaN so that scans and the root search skip the point instead of +c using a Delta built from an unconverged W. + IF (istate < 0) riccati_f = CMPLX( + $ ieee_value(1.0_r8, ieee_quiet_nan), + $ ieee_value(1.0_r8, ieee_quiet_nan), KIND=r8) END FUNCTION riccati_f c----------------------------------------------------------------------- diff --git a/slayer/growthrates.f b/slayer/growthrates.f index ddcb19731..e76de684c 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -14,7 +14,7 @@ MODULE growthrates_mod c 3. allocate_outputs - allocate slayer_outputs_type c 4. shrink_array - trim over-allocated scan arrays c 5. grow_array - expand scan arrays dynamically -c 6. calc_determinant - 2x2 / 3x3 complex determinant +c 6. calc_determinant - complex determinant c 7. dispersion_det - coupled dispersion determinant c 8. get_or_compute_v2 - hash-cached dispersion eval c 9. dispersion_AMR_v2 - AMR scan (cell-based storage) @@ -96,7 +96,8 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%c_beta_arr(n_k),sl_in%lu_arr(n_k),sl_in%Qconv_arr(n_k), $ sl_in%Re_dp_psi_arr(n_k),sl_in%Im_dp_psi_arr(n_k), $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k), - $ sl_in%D_fac_arr(n_k),sl_in%q_shift_arr(n_k)) + $ sl_in%D_fac_arr(n_k),sl_in%q_shift_arr(n_k), + $ sl_in%q_arr(n_k)) sl_in%q_shift_arr = 0.0_r8 c namelist inputs are taken as already r_s-referenced: identity c reference-length conversion (K = 1). @@ -150,167 +151,148 @@ SUBROUTINE grow_array(arr, old_size, new_size) END SUBROUTINE grow_array c----------------------------------------------------------------------- c subprogram 6. calc_determinant. -c Compute the determinant of a 2x2 or 3x3 complex matrix. -c Returns (0,0) and sets status=-1 for unsupported sizes. -c status=0 on success, -1 when nk is neither 2 nor 3. +c Determinant of an nk x nk complex matrix: closed forms for +c nk <= 3, LU factorisation (LAPACK zgetrf) above that. +c status=0 on success, -1 for nk < 1 or a failed factorisation. c----------------------------------------------------------------------- SUBROUTINE calc_determinant(matk, nk, detk, status) IMPLICIT NONE c --- arguments - INTEGER, INTENT(IN) :: nk ! matrix rank (2 or 3) + INTEGER, INTENT(IN) :: nk ! matrix rank COMPLEX(r8), DIMENSION(nk,nk), INTENT(IN) :: matk ! input matrix COMPLEX(r8), INTENT(OUT) :: detk ! determinant result - INTEGER, INTENT(OUT) :: status ! 0=success, -1=unsupported rank - + INTEGER, INTENT(OUT) :: status ! 0=success, -1=failure +c --- locals (LU path) + COMPLEX(r8), DIMENSION(nk,nk) :: lu_mat + INTEGER, DIMENSION(nk) :: ipiv + INTEGER :: info, i + status = 0 ! Initialize status as success - + SELECT CASE (nk) + CASE (1) + detk = matk(1,1) + CASE (2) ! 2x2 determinant: ad - bc detk = matk(1,1) * matk(2,2) - matk(1,2) * matk(2,1) - + CASE (3) ! 3x3 determinant using cofactor expansion along first row detk = matk(1,1)*(matk(2,2)*matk(3,3)-matk(2,3) $ *matk(3,2))-matk(1,2)*(matk(2,1)*matk(3,3) $ -matk(2,3)*matk(3,1))+matk(1,3)*(matk(2,1) $ *matk(3,2)-matk(2,2)*matk(3,1)) - + CASE default - ! Unsupported matrix size + IF (nk < 1) THEN detk = CMPLX(0.0_r8, 0.0_r8, KIND=r8) status = -1 - + ELSE + ! product of the LU diagonal, sign from the row exchanges. + ! info > 0 flags an exactly singular matrix, whose zero + ! pivot already makes the product zero. + lu_mat = matk + CALL zgetrf(nk, nk, lu_mat, nk, ipiv, info) + IF (info < 0) THEN + detk = CMPLX(0.0_r8, 0.0_r8, KIND=r8) + status = -1 + ELSE + detk = CMPLX(1.0_r8, 0.0_r8, KIND=r8) + DO i = 1, nk + detk = detk * lu_mat(i,i) + IF (ipiv(i) /= i) detk = -detk + END DO + END IF + END IF + END SELECT RETURN END SUBROUTINE calc_determinant c----------------------------------------------------------------------- c subprogram 7. dispersion_det. -c Compute the coupled dispersion determinant for n_k surfaces. +c Coupled dispersion determinant for the first msing_max rational +c surfaces: c -c For n_k = 1 (single surface): -c Evaluate riccati_f() (uses module g_tmp), de-normalise by lu^(1/3), and -c return Deltaprime - delta(Q). -c -c For n_k = 2 or 3 (coupled surfaces): -c Build the diagonal delta(Q) matrix, subtract from dp_matrix, -c and return det(dp_matrix - delta_Q). +c det( Dp - diag(Delta_crit_k) - diag(S_k^(1/3)*Delta_k(g_k)) ) c +c Dp is the r_s-referenced Deltaprime matrix (complex), of which +c the leading msing_max x msing_max block is used, so a subset of +c the surfaces STRIDE found can be coupled. Delta_crit is a local +c layer offset and enters the diagonal only. Each layer response +c is evaluated at that surface's own normalised eigenvalue g_k. +c For msing_max = 1 this is the single-surface matching condition. c----------------------------------------------------------------------- FUNCTION dispersion_det(g_in,n_k,sl_in,msing_max) c --- arguments COMPLEX(r8), INTENT(IN) :: g_in ! complex growth rate INTEGER, INTENT(IN) :: n_k ! number of surfaces - INTEGER, INTENT(IN) :: msing_max ! max surfaces to include + INTEGER, INTENT(IN) :: msing_max ! surfaces to include TYPE(slayer_inputs_type), INTENT(IN) :: sl_in c --- function result and locals COMPLEX(r8) :: dispersion_det ! returned determinant COMPLEX(r8) :: det_val ! intermediate determinant - COMPLEX(r8) :: tmp_delta ! single-surface delta - COMPLEX(r8), ALLOCATABLE :: delta_Q(:,:) ! diagonal delta matrix - COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! dp - delta_Q + COMPLEX(r8), ALLOCATABLE :: result_matrix(:,:) ! Dp - layer terms INTEGER :: k ! surface loop index INTEGER :: det_status ! calc_determinant error status -c --- single-surface branch - IF (msing_max < 2) THEN -c set module-level variables for riccati_f - Q_e = sl_in%Q_e_arr(1) - Q_i = sl_in%Q_i_arr(1) - P_perp = sl_in%P_perp_arr(1) - P_tor = sl_in%P_tor_arr(1) - tau = sl_in%tau_arr(1) - D_norm = sl_in%D_norm_arr(1) - c_beta = sl_in%c_beta_arr(1) - tauk = sl_in%Qconv_arr(1) + IF (msing_max < 1 .OR. msing_max > n_k) THEN + WRITE(*,*) 'ERROR: dispersion_det needs 1 <= msing_max <= ', + $ 'number of surfaces; got msing_max=',msing_max, + $ ' with',n_k,' surfaces' + STOP 'dispersion_det: msing_max out of range' + END IF + IF (.NOT. ALLOCATED(sl_in%dp_matrix)) THEN + WRITE(*,*) 'ERROR: the coupled determinant needs the STRIDE' + WRITE(*,*) ' Deltaprime matrix; run with read_eq=t.' + STOP 'dispersion_det: no Deltaprime matrix' + END IF + + ALLOCATE(result_matrix(msing_max,msing_max)) + result_matrix = sl_in%dp_matrix(1:msing_max,1:msing_max) + + DO k=1,msing_max +c set module-level variables for this surface + Q_e = sl_in%Q_e_arr(k) + Q_i = sl_in%Q_i_arr(k) + P_perp = sl_in%P_perp_arr(k) + P_tor = sl_in%P_tor_arr(k) + tau = sl_in%tau_arr(k) + D_norm = sl_in%D_norm_arr(k) + c_beta = sl_in%c_beta_arr(k) + tauk = sl_in%Qconv_arr(k) iota_e = Q_e / (Q_e - Q_i) -c BENCHMARK PATCH: ensure pr is set so riccati_f's -c atol = 1e-7*pr**0.4 does not inherit a stale/zero module value. - pr = P_perp - - g_tmp = g_in - tmp_delta=riccati_f() -c de-normalise delta by lu^(1/3) - det_val=tmp_delta* - $ (sl_in%lu_arr(1)**(1.0_r8/3.0_r8)) - -c return Deltaprime - delta(Q) - dispersion_det = sl_in%Re_dp_arr(1) - det_val - -c --- coupled-surface branch (2 or 3 surfaces) - ELSEIF ((msing_max == 2) .OR. (msing_max == 3)) THEN - ALLOCATE(delta_Q(msing_max,msing_max)) - delta_Q=CMPLX(0.0_r8, 0.0_r8, KIND=r8) - DO k=1,msing_max -c set module-level variables for this surface - Q_e = sl_in%Q_e_arr(k) - Q_i = sl_in%Q_i_arr(k) - P_perp = sl_in%P_perp_arr(k) - P_tor = sl_in%P_tor_arr(k) - tau = sl_in%tau_arr(k) - D_norm = sl_in%D_norm_arr(k) - c_beta = sl_in%c_beta_arr(k) - tauk = sl_in%Qconv_arr(k) - iota_e = Q_e / (Q_e - Q_i) -c BENCHMARK PATCH: ensure pr is set so riccati_f's -c atol = 1e-7*pr**0.4 does not inherit a stale/zero module value. - pr = P_perp - -c evaluate riccati_f at rescaled growth rate, de-normalise. -c g_in is normalised by the first surface's tau_k, i.e. -c g_in = tau_1*(gamma + i*omega). The same physical -c eigenvalue reaches surface k as tau_k*(gamma + i*omega) -c = g_in*tau_k/tau_1, the normalisation its Q_e and Q_i use. - g_tmp = g_in*(tauk/sl_in%Qconv_arr(1)) -c The layer's Q is the mode frequency in the local ExB -c frame, while the coupled scan runs in the lab frame: -c Q_layer = tau_k*omega_lab - tau_k*n*Omega_E. q_shift_arr -c holds that real offset (zero unless the driver sets it), -c and g = i*Q. - g_tmp = g_tmp + ifac*sl_in%q_shift_arr(k) - delta_Q(k,k)=riccati_f() - delta_Q(k,k)=delta_Q(k,k)* - $ sl_in%lu_arr(k)**(1.0_r8/3.0_r8) - END DO -c Coupled dispersion: det(Delta' - diag(Delta_c) - delta_Q). -c Delta_crit is a *local* layer-intrinsic offset at each -c rational surface, so it modifies only the diagonal of -c Delta'; outer-region off-diagonal couplings are untouched. -c This reduces to the single-surface matching -c (Delta'_kk - Delta_crit_k) = S^(1/3) * Delta_s -c on the diagonal. When dc_type='none', d_crit_arr is zero -c and the subtraction is a no-op, so this is backward -c compatible with the original coupled behaviour. -c -c BENCHMARK PATCH: take only the (1:msing_max, 1:msing_max) -c sub-block of dp_matrix so callers can compute the coupled -c dispersion on a subset of the rational surfaces that STRIDE -c actually found. This is needed when STRIDE finds 4+ surfaces -c (e.g. DIII-D 147131 with sas_flag=t, qhigh=1e3) but the -c coupled-dispersion code only supports msing_max ≤ 3. - result_matrix = sl_in%dp_matrix(1:msing_max,1:msing_max) - $ - delta_Q - DO k = 1, msing_max - result_matrix(k,k) = result_matrix(k,k) - $ - CMPLX(sl_in%d_crit_arr(k), 0.0_r8, KIND=r8) - END DO - CALL calc_determinant(result_matrix, msing_max, det_val, - $ det_status) - IF (det_status /= 0) THEN - WRITE(*,*) 'ERROR: calc_determinant unsupported rank=', - $ msing_max - STOP 'dispersion_det: calc_determinant failed' - END IF - DEALLOCATE(delta_Q, result_matrix) - dispersion_det = det_val - ELSE - WRITE(*,*) "Error: no support for msing > 3" - STOP "dispersion_det: unsupported n_k" +c g_in is normalised by the first surface's tau_k, i.e. +c g_in = tau_1*(gamma + i*omega). The same physical +c eigenvalue reaches surface k as tau_k*(gamma + i*omega) +c = g_in*tau_k/tau_1, the normalisation its Q_e and Q_i use. + g_tmp = g_in*(tauk/sl_in%Qconv_arr(1)) +c The layer's Q is the mode frequency in the local ExB +c frame, while the coupled scan runs in the lab frame: +c Q_layer = tau_k*omega_lab - tau_k*n*Omega_E. q_shift_arr +c holds that real offset (zero unless the driver sets it), +c and g = i*Q. + g_tmp = g_tmp + ifac*sl_in%q_shift_arr(k) + +c layer response, de-normalised by S^(1/3), and Delta_crit + result_matrix(k,k) = result_matrix(k,k) + $ - riccati_f()*sl_in%lu_arr(k)**(1.0_r8/3.0_r8) + $ - CMPLX(sl_in%d_crit_arr(k), 0.0_r8, KIND=r8) + END DO + + CALL calc_determinant(result_matrix, msing_max, det_val, + $ det_status) + IF (det_status /= 0) THEN + WRITE(*,*) 'ERROR: calc_determinant failed for rank=', + $ msing_max + STOP 'dispersion_det: calc_determinant failed' END IF + DEALLOCATE(result_matrix) + dispersion_det = det_val END FUNCTION dispersion_det c----------------------------------------------------------------------- diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index 91081f19b..d32f16d4b 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -316,7 +316,7 @@ END FUNCTION issurfint c c----------------------------------------------------------------------- - SUBROUTINE build_inputs(infile,ncfile,sl_in) + SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- @@ -324,6 +324,8 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) CHARACTER(512), INTENT(IN) :: infile ! kinetic input file path CHARACTER(512), INTENT(IN) :: ncfile ! STRIDE NetCDF file path TYPE(slayer_inputs_type), INTENT(INOUT) :: sl_in + REAL(r8), INTENT(IN) :: zeff_in ! Z_eff from slayer.in + REAL(r8), INTENT(IN) :: mu_i_in ! ion mass / proton mass c --- surface-loop control LOGICAL :: firstsurf ! first-call flag for issurfint REAL(r8) :: respsi ! normalised psi at current surface @@ -417,7 +419,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) $ sl_in%Qconv_arr(msing), $ sl_in%Re_dp_psi_arr(msing), sl_in%Im_dp_psi_arr(msing), $ sl_in%k_ref_arr(msing), sl_in%alpha_arr(msing), - $ sl_in%q_shift_arr(msing)) + $ sl_in%q_shift_arr(msing), sl_in%q_arr(msing)) sl_in%q_shift_arr = 0.0_r8 c --- allocate local kinetic arrays (for future NetCDF diagnostic output) @@ -442,7 +444,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) tfac = 1.0 wefac = 1.0 wpfac = 1.0 - e = 1.6021917e-19 ! elementary charge [C] + e = chag ! elementary charge [C] chi1 = twopi * my_psio(1) ! total poloidal flux (module-level) CALL read_kin(infile,zi,zimp,mi,mimp,nfac, @@ -455,7 +457,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) sl_in%dp_matrix(:,:) = CMPLX(dp_mat(:,:,1), dp_mat(:,:,2), $ KIND=r8) - dpsi_h = 0.002 ! finite-difference step for Jacobian + dpsi_h = 0.002_r8 ! finite-difference step for Jacobian c----------------------------------------------------------------------- c loop over singular surfaces: evaluate kinetic/equilibrium @@ -474,14 +476,14 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c compute Jacobian da_surf/dpsiN by central difference a_surf_p = issurfint(unitfun,mthsurf, - $ MIN(respsi+dpsi_h, REAL(1.0,r8)),3,1, + $ MIN(respsi+dpsi_h, 1.0_r8),3,1, $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) a_surf_m = issurfint(unitfun,mthsurf, - $ MAX(respsi-dpsi_h, REAL(0.001,r8)),3,1, + $ MAX(respsi-dpsi_h, 0.001_r8),3,1, $ fsave,psave,jacs,delpsi,rsurf,asurf,firstsurf) da_dpsiN = (a_surf_p - a_surf_m) - $ / (MIN(respsi+dpsi_h, REAL(1.0,r8)) - $ - MAX(respsi-dpsi_h, REAL(0.001,r8))) + $ / (MIN(respsi+dpsi_h, 1.0_r8) + $ - MAX(respsi-dpsi_h, 0.001_r8)) c----------------------------------------------------------------------- c evaluate kinetic splines at this surface. @@ -511,12 +513,15 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) n_i = kin%f(1) t_i = kin%f(3) / e -c Z_eff: kinetic spline value may be incorrect if ni=ne in gpeckf -c (quasi-neutrality assumption makes Zeff=1). Override to 2.0 -c for deuterium plasma with carbon impurities (matching TJ). -c TODO: fix gpeckf generation to include proper ni for Zeff, -c or read Zeff from a namelist parameter. - zeff = 2.0 +c Z_eff and the ion mass ratio come from slayer.in (zeff, mu_i), +c not from the kinetic file: its n_i/n_e ratio is often not a +c usable Z_eff. Unset (non-positive) values fall back to 2, a +c deuterium plasma with carbon impurities. + IF (zeff_in > 0.0_r8) THEN + zeff = zeff_in + ELSE + zeff = 2.0_r8 + END IF omega = kin%f(5) my_qval = q_rational(ising) @@ -525,7 +530,11 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) my_bt = my_bt0(1) my_rs = a_surf R_0 = r_o(1) - mu_i = 2.0 ! deuterium + IF (mu_i_in > 0.0_r8) THEN + mu_i = mu_i_in + ELSE + mu_i = 2.0_r8 ! deuterium + END IF dr_val = dr_vals(ising) c convert STRIDE shear (psiN-based) to Fitzpatrick shear (r-based). @@ -537,7 +546,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c because (r/psi_N) > (dr/dpsi_N) inside the mid-radius region. s_fitz = my_sval * my_rs / (respsi * da_dpsiN) WRITE(*,'(A,I2,A,F7.4,A,F6.3,A,F8.4,A,F8.4,A,F7.3,A,F7.3)') - $ ' [layerinputs] q=', resm(ising), + $ ' [layerinputs] m=', resm(ising), $ ' psiN=', respsi, $ ' r_s=', my_rs, $ ' da/dpsiN=', da_dpsiN, @@ -630,7 +639,8 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in) c Q_e/Q_i use spline-derived omega_e/omega_i normalised by c tauk (= Fitzpatrick S^(1/3) * tau_H, from params()). c----------------------------------------------------------------------- - sl_in%qval_arr(ising) = INT(my_qval) + sl_in%qval_arr(ising) = resm(ising) + sl_in%q_arr(ising) = my_qval sl_in%lu_arr(ising) = lu c Use the same -tauk prefactor for both so opposite-signed c spline omega_e, omega_i (electron vs ion diamagnetic diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 5df0b5026..2203f83d5 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -150,7 +150,8 @@ MODULE sglobal_mod c slayer_inputs_type -- per-surface input arrays for SLAYER TYPE slayer_inputs_type - INTEGER, ALLOCATABLE :: qval_arr(:) ! safety-factor integers + INTEGER, ALLOCATABLE :: qval_arr(:) ! resonant poloidal m (labels) + REAL(r8), ALLOCATABLE :: q_arr(:) ! safety factor m/n REAL(r8), ALLOCATABLE :: chi_p_arr(:) ! chi_perp [m^2/s] REAL(r8), ALLOCATABLE :: chi_t_arr(:) ! chi_tor [m^2/s] REAL(r8), ALLOCATABLE :: kappa_arr(:) ! kappa (thermal cond.) diff --git a/slayer/slayer.f b/slayer/slayer.f index be50cbb89..353c46712 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -262,7 +262,7 @@ PROGRAM slayer ! global module scalars (sglobal_mod) gamma_fac = 0.0 - dc_type = "" + dc_type = "rfitzp" ! scan resolution defaults inum = 400 ! 1-D resolution (error-field threshold scans) @@ -275,6 +275,7 @@ PROGRAM slayer ! a coupled scan with rotation. scan_center = HUGE(scan_center) AMR_passes = 4 ! AMR refinement levels + m_AMR = 0 ! no AMR scan stored yet msing_max = 2 ! max singular surfaces to process ! I/O unit numbers (sglobal_mod) @@ -348,6 +349,21 @@ PROGRAM slayer READ(in_unit,NML=slayer_diagnose) CLOSE(UNIT=in_unit) + ! Critical-Delta model: fail on a misspelt name rather than + ! silently running without the stabilising offset. + SELECT CASE (TRIM(dc_type)) + CASE ('rfitzp','lar','none','') + CASE ('toroidal') + WRITE(*,*) "WARNING: dc_type='toroidal' is experimental." + WRITE(*,*) " Its geometric factor (STRIDE Delta_geo) has" + WRITE(*,*) " not been verified in the r_s reference" + WRITE(*,*) " length; prefer dc_type='rfitzp'." + CASE DEFAULT + WRITE(*,*) "ERROR: unknown dc_type='",TRIM(dc_type),"'." + WRITE(*,*) " Use 'rfitzp', 'lar', 'toroidal' or 'none'." + STOP 'slayer: unknown dc_type' + END SELECT + ! Real copies of the mode numbers, read by params() in every ! namelist-driven branch below. build_inputs resets them from ! the STRIDE run when read_eq is set. @@ -391,7 +407,9 @@ PROGRAM slayer c skipped when the matched-gamma path is active (it computes c its own delta internally). c----------------------------------------------------------------------- - IF (.NOT. (match_gamma_flag)) THEN +c Also skipped for the growth-rate estimate, which does not use +c it and is usually run with these namelist inputs unset. + IF (.NOT. (match_gamma_flag .OR. est_gamma_flag)) THEN delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] @@ -505,7 +523,7 @@ PROGRAM slayer sl_in%chi_t_arr = chi_t_prof sl_in%kappa_arr = kappa_prof - CALL build_inputs(infile,ncfile,sl_in) + CALL build_inputs(infile,ncfile,sl_in,zeff,mu_i) n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) @@ -579,7 +597,8 @@ PROGRAM slayer CALL allocate_inputs(n_k,sl_in) ! growthrates_mod CALL allocate_outputs(n_k,sl_out) ! growthrates_mod - sl_in%qval_arr = (/ qval /) + sl_in%qval_arr = (/ mm /) + sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) !sl_in%Q_arr = (/ Q /) sl_in%Q_e_arr = (/ Q_e /) @@ -606,7 +625,7 @@ PROGRAM slayer DO k=1,n_k WRITE(*,*) WRITE(*,'(A,I0,A)') 'Calculating growth rate '// - $ 'estimate on q = ', + $ 'estimate on m = ', $ sl_in%qval_arr(k),' rational surface' D_norm = sl_in%D_norm_arr(k) @@ -627,7 +646,10 @@ PROGRAM slayer ENDDO IF (.NOT. (match_gamma_flag)) THEN - sl_out%gamma_sol_arr = (/0./) + ! no matched solve in this run + sl_out%gamma_sol_arr = CMPLX( + $ ieee_value(1.0_r8, ieee_quiet_nan), + $ ieee_value(1.0_r8, ieee_quiet_nan), KIND=r8) CALL output_gamma(est_gamma_flag,m_AMR,sl_in,sl_out, $ all_deltas_out) END IF @@ -651,7 +673,7 @@ PROGRAM slayer sl_in%chi_t_arr = chi_t_prof sl_in%kappa_arr = kappa_prof - CALL build_inputs(infile,ncfile,sl_in) + CALL build_inputs(infile,ncfile,sl_in,zeff,mu_i) n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) @@ -718,7 +740,8 @@ PROGRAM slayer CALL allocate_outputs(n_k,sl_out) END IF - sl_in%qval_arr = (/ qval /) + sl_in%qval_arr = (/ mm /) + sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) !sl_in%Q_arr = (/ Q /) sl_in%Q_e_arr = (/ Q_e /) @@ -743,6 +766,26 @@ PROGRAM slayer END IF END IF +c----------------------------------------------------------------------- +c sanity checks on the surface count and the coupling request. +c----------------------------------------------------------------------- + IF (msing_max < 1) THEN + WRITE(*,*) 'WARNING: msing_max < 1; using msing_max = 1' + msing_max = 1 + END IF + IF (msing_max > n_k) THEN + WRITE(*,'(A,I0,A,I0,A)') ' NOTE: msing_max = ',msing_max, + $ ' exceeds the ',n_k,' rational surfaces available;' + $ //' using all of them.' + msing_max = n_k + END IF + IF (coupling_flag .AND. .NOT. read_eq) THEN + WRITE(*,*) 'ERROR: coupling_flag=t needs the STRIDE' + WRITE(*,*) ' Deltaprime matrix; set read_eq=t, infile' + WRITE(*,*) ' and ncfile, or set coupling_flag=f.' + STOP 'slayer: coupling_flag requires read_eq' + END IF + c----------------------------------------------------------------------- c ExB Doppler shift and scan-window centre. c Each uncoupled layer is solved in its own ExB frame, where a @@ -814,13 +857,13 @@ PROGRAM slayer END IF END IF - WRITE(*,*),"Rational q domain: ",sl_in%qval_arr + WRITE(*,*) "Resonant m of the surfaces: ",sl_in%qval_arr c----------------------------------------------------------------------- c loop over rational surfaces to find matched growth rates. c----------------------------------------------------------------------- DO k=1,MIN(n_k,msing_max) WRITE(*,*) - WRITE(*,'(A,I0,A)') 'Calculating growth rate on q = ', + WRITE(*,'(A,I0,A)') 'Calculating growth rate on m = ', $ sl_in%qval_arr(k),' rational surface:' ! Load per-surface parameters into module-level scalars. @@ -860,7 +903,7 @@ PROGRAM slayer c----------------------------------------------------------------------- IF (AMR_flag .AND. .NOT. coupling_flag) THEN - WRITE(*,'(A,I0,A)') 'Calling uncoupled AMR scan on q = ', + WRITE(*,'(A,I0,A)') 'Calling uncoupled AMR scan on m = ', $ sl_in%qval_arr(k),' rational surface:' ! The tearing root usually sits near the electron ! drift frequency, Re(Q) = -Q_e. @@ -989,8 +1032,8 @@ PROGRAM slayer ENDDO IF (.NOT. (est_gamma_flag)) THEN - sl_in%d_beta_arr = (/ 0. /) - sl_out%dels_db_arr = (/ 0. /) + sl_in%d_beta_arr = 0.0_r8 + sl_out%dels_db_arr = CMPLX(0.0_r8, 0.0_r8, KIND=r8) END IF c----------------------------------------------------------------------- @@ -1028,7 +1071,10 @@ PROGRAM slayer DO i = 1, n_pts all_deltas_out(1)%inQs(i) = REAL(Q_store(i)) - all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) ! verify this sign convention + ! scan plane q = omega_hat - i*gamma_hat: store the + ! growth-rate axis as +gamma_hat, as in the uncoupled + ! output above. + all_deltas_out(1)%iinQs(i) = -AIMAG(Q_store(i)) all_deltas_out(1)%real_deltas(i) = REAL(D_store(i)) all_deltas_out(1)%imag_deltas(i) = AIMAG(D_store(i)) @@ -1110,7 +1156,7 @@ PROGRAM slayer sl_in%chi_p_arr = chi_p_prof sl_in%chi_t_arr = chi_t_prof sl_in%kappa_arr = kappa_prof - CALL build_inputs(infile,ncfile,sl_in) + CALL build_inputs(infile,ncfile,sl_in,zeff,mu_i) n_k = SIZE(sl_in%qval_arr) CALL allocate_outputs(n_k,sl_out) ELSE @@ -1133,7 +1179,8 @@ PROGRAM slayer CALL allocate_inputs(n_k,sl_in) CALL allocate_outputs(n_k,sl_out) - sl_in%qval_arr = (/ qval /) + sl_in%qval_arr = (/ mm /) + sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) sl_in%Q_e_arr = (/ Q_e /) sl_in%Q_i_arr = (/ Q_i /) @@ -1164,7 +1211,7 @@ PROGRAM slayer DO k=1,n_k WRITE(*,*) - WRITE(*,'(A,I0,A)') 'Computing Br threshold on q = ', + WRITE(*,'(A,I0,A)') 'Computing Br threshold on m = ', $ sl_in%qval_arr(k),' rational surface' Q_e = sl_in%Q_e_arr(k) diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index d45e20af7..99942fb50 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -141,6 +141,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, INTEGER :: oroot_id ! "omega_roots" INTEGER :: proot_id ! "root_polished" LOGICAL :: have_roots ! roots_out holds this run's roots + LOGICAL :: have_amr ! AMR scan data are present REAL(r8), ALLOCATABLE :: buffer_r(:,:,:) ! (root, surf, Re/Im) REAL(r8), ALLOCATABLE :: buffer_p(:,:,:) ! (root, surf, Re/Im) REAL(r8), ALLOCATABLE :: buffer_g(:,:) ! gamma (root, surf) @@ -200,7 +201,11 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c step 1: find the maximum AMR grid size across all surfaces. max_pts_all = 0 - IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN + have_amr = .FALSE. + IF (m_AMR > 0) THEN + have_amr = ALLOCATED(all_deltas_out(1)%inQs) + END IF + IF (have_amr) THEN DO s = 1, m_AMR max_pts_all = MAX(max_pts_all, $ SIZE(all_deltas_out(s)%inQs)) @@ -218,7 +223,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, n_pts_arr = 0 c step 3: flatten the ragged data into the buffers. - IF (ALLOCATED(all_deltas_out(1)%inQs)) THEN + IF (have_amr) THEN DO s = 1, m_AMR n_curr = SIZE(all_deltas_out(s)%inQs) n_pts_arr(s) = n_curr @@ -240,8 +245,8 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c step 4: roots and poles, in the same (omega_hat, gamma_hat) c convention as Q_AMR. have_roots = .FALSE. - IF (ALLOCATED(roots_out)) have_roots = SIZE(roots_out) >= m_AMR - $ .AND. m_AMR > 0 + IF (ALLOCATED(roots_out) .AND. have_amr) + $ have_roots = SIZE(roots_out) >= m_AMR IF (have_roots) THEN ALLOCATE(buffer_r(MAX_ROOTS, m_AMR, 2)) ALLOCATE(buffer_p(MAX_ROOTS, m_AMR, 2)) @@ -288,12 +293,17 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c -- core dimensions -- CALL sl_check( nf90_def_dim(ncid, "r", msing, qsing_dim) ) - CALL sl_check( nf90_def_dim(ncid, "r_AMR", m_AMR, nAMR_dim) ) + IF (have_amr) THEN + CALL sl_check( nf90_def_dim(ncid, "r_AMR", m_AMR, + $ nAMR_dim) ) + END IF CALL sl_check( nf90_def_dim(ncid, "i", 2, i_dim) ) c -- scalar per-surface variables -- CALL sl_check( nf90_def_var(ncid, "r", nf90_int, $ qsing_dim, qsing_id) ) + CALL sl_check( nf90_put_att(ncid, qsing_id, "long_name", + $ "Resonant poloidal mode number m of each surface") ) CALL sl_check( nf90_def_var(ncid, "q_rational", $ nf90_double, qsing_dim, qr_id) ) CALL sl_check( nf90_def_var(ncid, "omegas", nf90_double, @@ -371,14 +381,19 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c----------------------------------------------------------------------- c define AMR scan dimensions and variables. c----------------------------------------------------------------------- + IF (have_amr) THEN CALL sl_check( nf90_def_dim(ncid, "amr_pts", $ max_pts_all, dim_pts_id) ) CALL sl_check( nf90_def_var(ncid, "n_amr_pts", NF90_INT, $ (/nAMR_dim/), var_npts_id) ) CALL sl_check( nf90_def_var(ncid, "Q_AMR", NF90_DOUBLE, $ (/dim_pts_id, nAMR_dim, i_dim/), var_q_id) ) + CALL sl_check( nf90_put_att(ncid, var_q_id, "long_name", + $ "AMR scan points, (omega, gamma)*tau_k") ) CALL sl_check( nf90_def_var(ncid, "Deltas_AMR", NF90_DOUBLE, $ (/dim_pts_id, nAMR_dim, i_dim/), var_d_id) ) + CALL sl_check( nf90_put_att(ncid, var_d_id, "long_name", + $ "Dispersion function at the AMR scan points") ) c roots and poles of the dispersion function from each AMR scan. IF (have_roots) THEN @@ -424,6 +439,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ "_FillValue", fill_val) ) CALL sl_check( nf90_put_att(ncid, var_d_id, $ "_FillValue", fill_val) ) + END IF c----------------------------------------------------------------------- c end NetCDF define mode. @@ -436,7 +452,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, CALL sl_check( nf90_put_var(ncid, qsing_id, $ sl_in%qval_arr) ) CALL sl_check( nf90_put_var(ncid, qr_id, - $ sl_in%qval_arr) ) + $ sl_in%q_arr) ) CALL sl_check( nf90_put_var(ncid, pr_id, $ sl_in%psi_n_arr) ) CALL sl_check( nf90_put_var(ncid, omegas_id, @@ -508,9 +524,11 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, c----------------------------------------------------------------------- c write AMR scan arrays. c----------------------------------------------------------------------- - CALL sl_check( nf90_put_var(ncid, var_npts_id, n_pts_arr) ) - CALL sl_check( nf90_put_var(ncid, var_q_id, buffer_q) ) - CALL sl_check( nf90_put_var(ncid, var_d_id, buffer_d) ) + IF (have_amr) THEN + CALL sl_check( nf90_put_var(ncid, var_npts_id, n_pts_arr) ) + CALL sl_check( nf90_put_var(ncid, var_q_id, buffer_q) ) + CALL sl_check( nf90_put_var(ncid, var_d_id, buffer_d) ) + END IF IF (have_roots) THEN CALL sl_check( nf90_put_var(ncid, nroot_id, From 445d07c8d03f54dea31440260a02ae945cd92c5a Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 14 Oct 2026 15:40:40 -0400 Subject: [PATCH 209/305] SLAYER, STRIDE - BUGFIX - Remove the toroidal critical-Delta option dc_type='toroidal' used a geometric factor (STRIDE Delta_geo) that was not correct in the r_s reference length the layer matching now uses. On the DIII-D-like example it came out within 1% of 'rfitzp', while the validated derivation (Connor et al. 2015 Eq. 59 in the r_s reference, with a toroidal parallel-conduction closure; OpenFUSIONToolkit/GPEC#441) puts it 22-60% above. The option now stops with a message pointing to 'rfitzp'. STRIDE no longer computes or writes Delta_geo and keeps only the shear output SLAYER reads. dgeo_val stays in the namelist, unused, so older inputs still read. A port of the Julia derivation can follow once it is merged there. Co-Authored-By: Claude Opus 5.5 --- input/slayer.in | 4 +- slayer/layerinputs.f | 23 +++---- slayer/params.f | 6 +- slayer/sglobal.f | 2 +- slayer/slayer.f | 13 ++-- stride/free.f | 144 ++--------------------------------------- stride/stride.F | 2 +- stride/stride_netcdf.f | 9 +-- 8 files changed, 31 insertions(+), 172 deletions(-) diff --git a/input/slayer.in b/input/slayer.in index d2a37879b..ec62d0815 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -22,7 +22,7 @@ mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma. Also used when read_eq=t zeff=2.0 ! plasma Z_effective. Also used when read_eq=t (the kinetic file's Z_eff is not used) dr_val=-0.1 ! GGJ resistive interchange index D_R (taken from STRIDE when read_eq=t) - dgeo_val=10.0 ! J.W. Connor geometric prefactor for the experimental dc_type="toroidal" (taken from STRIDE when read_eq=t) + dgeo_val=0.0 ! unused (the toroidal critical Delta was removed); accepted so older inputs still read chi_p_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular energy diffusivity profile chi_t_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular ion momentum diffusivity profile kappa_prof=0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 ! Rational surface thermal conductivity profile (NOTE: set > 0 to override chi_p_prof) @@ -52,7 +52,7 @@ AMR_passes=4 ! number of adaptive refinement passes of the scan grid !scan_center=0.0 ! Re(Q) centre of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan msing_max=2 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t - dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", "none", or "toroidal" (experimental) + dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" read_eq=f ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation coupling_flag=f ! solve the coupled multi-surface determinant instead of each surface separately. Requires read_eq=t diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index d32f16d4b..a5532ea55 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -31,13 +31,13 @@ MODULE layerinputs_mod c----------------------------------------------------------------------- c subprogram 1. read_stride_netcdf_diagonal. c Read the STRIDE NetCDF file and extract the Deltaprime matrix, -c rational-surface geometry (q, psi_n, shear, dgeo, dr), and +c rational-surface geometry (q, psi_n, shear, D_R, D_I), and c equilibrium scalars (R0, B_t0, psi0, m_psi, n, resm). c All global-attribute reads use NF90_GLOBAL explicitly; c variable IDs are obtained via nf90_inq_varid before use. c----------------------------------------------------------------------- SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, - $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational,dgeo, + $ Re_dp_diagonal,Im_dp_diagonal,q_rational,psi_n_rational, $ shear,r_o,my_bt0,my_psio,dr_vals,di_vals,mpsi,nn,resm) c --- arguments (all INTENT(OUT) except ncfile) @@ -51,8 +51,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ q_rational, ! rational-surface q values $ psi_n_rational, ! normalised psi at each surface - $ shear, ! magnetic shear - $ dgeo ! geometric delta (Shafranov shift) + $ shear ! magnetic shear REAL(r8), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: $ r_o, ! major radius R0 $ my_bt0, ! toroidal field B_t0 @@ -69,7 +68,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, INTEGER(kind=nf90_int) :: ncid, stat ! NetCDF file id / return status INTEGER(kind=nf90_int) :: r_dim_id, r_dim ! dimension id / length (unused) INTEGER(kind=nf90_int) :: dp_id, qr_id, pr_id ! variable ids - INTEGER(kind=nf90_int) :: dgeo_id, shear_id ! variable ids + INTEGER(kind=nf90_int) :: shear_id ! variable id INTEGER(kind=nf90_int) :: resm_id, drr_id ! variable ids INTEGER(kind=nf90_int) :: dir_id ! variable id INTEGER(kind=nf90_int), DIMENSION(1) :: start, count ! NetCDF hyperslab @@ -95,7 +94,7 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, c --- allocate output arrays sized by msing ALLOCATE(Re_dp_diagonal(msing), q_rational(msing), - $ psi_n_rational(msing), shear(msing), dgeo(msing), + $ psi_n_rational(msing), shear(msing), $ resm(msing), Im_dp_diagonal(msing), dr_vals(msing), $ di_vals(msing)) ALLOCATE(dp_mat(msing, msing, 2)) @@ -128,8 +127,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) stat = nf90_inq_varid(ncid, 'psi_n_rational', pr_id) CALL sl_check(stat) - stat = nf90_inq_varid(ncid, 'Delta_geo', dgeo_id) - CALL sl_check(stat) stat = nf90_inq_varid(ncid, 'shear', shear_id) CALL sl_check(stat) stat = nf90_inq_varid(ncid, 'resm', resm_id) @@ -164,8 +161,6 @@ SUBROUTINE read_stride_netcdf_diagonal(ncfile,msing,dp_mat, CALL sl_check(stat) stat = nf90_get_var(ncid, pr_id, psi_n_rational) CALL sl_check(stat) - stat = nf90_get_var(ncid, dgeo_id, dgeo) - CALL sl_check(stat) stat = nf90_get_var(ncid, shear_id, shear) CALL sl_check(stat) stat = nf90_get_var(ncid, resm_id, resm) @@ -343,7 +338,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) REAL(r8) :: my_qval, my_sval ! safety factor, magnetic shear REAL(r8) :: my_bt, my_rs, R_0 ! toroidal field, minor radius, major radius REAL(r8) :: zeff ! effective charge from kin%f(9) - REAL(r8) :: dgeo_val ! geometric delta (Shafranov) + REAL(r8) :: dgeo_val ! unused params() argument REAL(r8) :: mu_i ! ion mass ratio to proton REAL(r8) :: dr_val ! radial width dr at surface REAL(r8) :: l_n, l_t ! density / temperature gradient lengths @@ -353,7 +348,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) REAL(r8), DIMENSION(:,:,:), ALLOCATABLE :: dp_mat REAL(r8), DIMENSION(:), ALLOCATABLE :: Re_dp_diagonal, $ Im_dp_diagonal, q_rational, psi_n_rational, - $ shear, dgeo, r_o, my_bt0, my_psio, mpsi_arr, + $ shear, r_o, my_bt0, my_psio, mpsi_arr, $ dr_vals, di_vals, dr_arr c --- local per-surface kinetic arrays (for future NetCDF diagnostic output) REAL(r8), DIMENSION(:), ALLOCATABLE :: ne_arr, te_arr, @@ -383,7 +378,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) c----------------------------------------------------------------------- CALL read_stride_netcdf_diagonal(ncfile,msing,dp_mat, $ Re_dp_diagonal,Im_dp_diagonal,q_rational, - $ psi_n_rational,dgeo,shear,r_o,my_bt0,my_psio,dr_vals, + $ psi_n_rational,shear,r_o,my_bt0,my_psio,dr_vals, $ di_vals,mpsi_arr,nn_arr,resm) c The toroidal mode number of the STRIDE run defines the layer @@ -526,7 +521,7 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) omega = kin%f(5) my_qval = q_rational(ising) my_sval = shear(ising) - dgeo_val = dgeo(ising) + dgeo_val = 0.0_r8 my_bt = my_bt0(1) my_rs = a_surf R_0 = r_o(1) diff --git a/slayer/params.f b/slayer/params.f index b21bc564e..459d9b3a5 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -40,7 +40,7 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, REAL(r8), INTENT(IN) :: t_i ! ion temperature [eV] REAL(r8), INTENT(IN) :: omega ! toroidal rotation [rad/s] REAL(r8), INTENT(IN) :: dr_val ! radial width dr at surface - REAL(r8), INTENT(IN) :: dgeo_val ! geometric delta (Shafranov shift factor) + REAL(r8), INTENT(IN) :: dgeo_val ! unused; kept for the interface REAL(r8), INTENT(IN) :: l_n ! density gradient length REAL(r8), INTENT(IN) :: l_t ! temperature gradient length REAL(r8), INTENT(IN) :: qval ! safety factor @@ -213,7 +213,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c dc_type (from sglobal_mod) selects the formula: c 'lar' -- cylindrical (Lutjens) c 'rfitzp' -- R. Fitzpatrick -c 'toroidal' -- toroidal geometry using dgeo_val c default -- dc_tmp = 0 c----------------------------------------------------------------------- IF (ABS(dr_val) > 0.0_r8) THEN @@ -249,9 +248,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, $ *( (nr*ABS(sval))/(R0*rs) )**0.5_r8 CASE('rfitzp') dc_tmp = -(SQRT(2.0_r8)*(pi**(1.5_r8))*dr_val) / Wd - CASE('toroidal') - dc_tmp = 0.5_r8*(-dr_val)*(pi**1.5_r8) - $ *((chi_par/chis(1))**0.25_r8)*dgeo_val CASE default dc_tmp = 0.0_r8 END SELECT diff --git a/slayer/sglobal.f b/slayer/sglobal.f index 2203f83d5..cc93fa52c 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -85,7 +85,7 @@ MODULE sglobal_mod c --- stability / Delta_crit REAL(r8) :: dc_tmp ! computed Delta_crit COMPLEX(r8) :: delta_eff ! (Deltaprime - Delta_crit)/S^(1/3) - CHARACTER(20) :: dc_type ! dc formula: 'rfitzp','lar','toroidal','none' + CHARACTER(20) :: dc_type ! dc formula: 'rfitzp','lar','none' c --- solver workspace / results COMPLEX(r8) :: g_tmp ! temporary complex growth rate REAL(r8) :: gamma_fac ! growth-rate conversion factor diff --git a/slayer/slayer.f b/slayer/slayer.f index 353c46712..f1abce011 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -100,7 +100,7 @@ PROGRAM slayer REAL(r8) :: mu_i ! ion mass number REAL(r8) :: zeff ! effective charge REAL(r8) :: dr_val ! radial derivative parameter - REAL(r8) :: dgeo_val ! geometric factor parameter + REAL(r8) :: dgeo_val ! unused; still read so old inputs parse REAL(r8) :: scan_width ! half-width of complex-Q scan REAL(r8) :: scan_center ! Re(Q) centre of complex-Q scan REAL(r8) :: q_lo,q_hi,q_br ! expected coupled-branch Re(Q) @@ -354,13 +354,14 @@ PROGRAM slayer SELECT CASE (TRIM(dc_type)) CASE ('rfitzp','lar','none','') CASE ('toroidal') - WRITE(*,*) "WARNING: dc_type='toroidal' is experimental." - WRITE(*,*) " Its geometric factor (STRIDE Delta_geo) has" - WRITE(*,*) " not been verified in the r_s reference" - WRITE(*,*) " length; prefer dc_type='rfitzp'." + WRITE(*,*) "ERROR: dc_type='toroidal' is not available." + WRITE(*,*) " Its geometric factor was not correct in the" + WRITE(*,*) " r_s reference length and has been removed." + WRITE(*,*) " Use dc_type='rfitzp'." + STOP 'slayer: dc_type toroidal removed' CASE DEFAULT WRITE(*,*) "ERROR: unknown dc_type='",TRIM(dc_type),"'." - WRITE(*,*) " Use 'rfitzp', 'lar', 'toroidal' or 'none'." + WRITE(*,*) " Use 'rfitzp', 'lar' or 'none'." STOP 'slayer: unknown dc_type' END SELECT diff --git a/stride/free.f b/stride/free.f index 034e943cd..f9d0ee2df 100755 --- a/stride/free.f +++ b/stride/free.f @@ -173,15 +173,10 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) COMPLEX(r8), DIMENSION(mpert,mpert) :: wt,wpt,wvt COMPLEX(r8), DIMENSION(mpert,mpert) :: nmat,smat CHARACTER(24), DIMENSION(mpert) :: message - INTEGER :: ipsi,itheta REAL(r8), DIMENSION(sq%mx+1) :: ln_q - REAL(r8), DIMENSION(msing) :: dgeo,shr - TYPE(spline_type) :: psi_t,avg_dpsi_spl,avg_bsq_spl,v_spl, - $ vol_spl,shr_spl - REAL(r8) :: bsq,chi1,dpsisq,myeta,jac,psifac,q,q1,respsi, - $ rfac,v1,v21,v22,v23,v33,al,Lam,mytheta,myr,V_s - REAL(r8), DIMENSION(:), POINTER :: avg - TYPE(spline_type), TARGET :: fspl + REAL(r8), DIMENSION(msing) :: shr + TYPE(spline_type) :: shr_spl + REAL(r8) :: v1,respsi c----------------------------------------------------------------------- c write formats. c----------------------------------------------------------------------- @@ -301,155 +296,30 @@ SUBROUTINE free_run(plasma1,vacuum1,total1,op_netcdf_out) WRITE(out_unit,90)(isol,ep(isol),ev(isol),isol=1,mpert) WRITE(out_unit,80) c----------------------------------------------------------------------- -c compute toroidal Delta_crit +c magnetic shear s = psi_N*d(ln q)/d(psi_N) at the rational surfaces +c (written to the netcdf file for SLAYER). c----------------------------------------------------------------------- - ! Prepare toroidal flux spline - CALL spline_alloc(psi_t,SIZE(sq%fsi(:, 4))-1,1) - psi_t%xs=sq%xs(:) - psi_t%fs(:,1)=sq%fsi(:,4)*twopi*psio ! Un-normalize toroidal flux - CALL spline_fit(psi_t,"extrap") - - ! Prepare geometric splines - CALL spline_alloc(avg_dpsi_spl,SIZE(sq%xs(:))-1,1) - avg_dpsi_spl%xs=sq%xs(:) - CALL spline_alloc(avg_bsq_spl,SIZE(sq%xs(:))-1,1) - avg_bsq_spl%xs=sq%xs(:) - CALL spline_alloc(v_spl,SIZE(sq%xs(:))-1,1) - v_spl%xs=sq%xs(:) - ! Cumulative volume V(psi_N) [m^3] = integral of dV/dpsi from axis. - CALL spline_alloc(vol_spl,SIZE(sq%xs(:))-1,1) - vol_spl%xs=sq%xs(:) - vol_spl%fs(:,1)=sq%fsi(:,3) - CALL spline_fit(vol_spl,"extrap") - - ! Prepare shear spline CALL spline_alloc(shr_spl,SIZE(sq%xs(:))-1,1) shr_spl%xs=sq%xs(:) ln_q=LOG(sq%fs(:,4)) shr_spl%fs(:,1)=ln_q - CALL spline_fit(shr_spl,"extrap") - - CALL spline_alloc(fspl,mtheta,3) - fspl%xs=rzphi%ys - - DO ipsi=0,mpsi - psifac=sq%xs(ipsi) - v1=sq%fs(ipsi,3) - q=sq%fs(ipsi,4) - q1=sq%fs1(ipsi,4) - chi1=twopi*psio -c----------------------------------------------------------------------- -c evaluate coordinates and jacobian. -c----------------------------------------------------------------------- - DO itheta=0,mtheta - CALL bicube_eval(rzphi,rzphi%xs(ipsi),rzphi%ys(itheta),1) - mytheta=rzphi%ys(itheta) - rfac=SQRT(rzphi%f(1)) - myeta=twopi*(mytheta+rzphi%f(2)) - myr=ro+rfac*COS(myeta) - jac=rzphi%f(4) -c----------------------------------------------------------------------- -c evaluate other local quantities. -c----------------------------------------------------------------------- - v21=rzphi%fy(1)/(2*rfac*jac) - v22=(1+rzphi%fy(2))*twopi*rfac/jac - v23=rzphi%fy(3)*myr/jac - v33=twopi*myr/jac - bsq=chi1**2*(v21**2+v22**2+(v23+q*v33)**2) - dpsisq=(twopi*myr)**2*(v21**2+v22**2) -c----------------------------------------------------------------------- -c evaluate integrands. -c----------------------------------------------------------------------- - fspl%fs(itheta,1)=dpsisq*(v1**2) ! Converting to \nabla V - fspl%fs(itheta,2)=bsq - fspl%fs(itheta,3)=v1 - fspl%fs(itheta,:)=fspl%fs(itheta,:)*(jac/v1) - ENDDO -c----------------------------------------------------------------------- -c integrate quantities with respect to theta. -c----------------------------------------------------------------------- - CALL spline_fit(fspl,"periodic") - CALL spline_int(fspl) - avg => fspl%fsi(mtheta,:) - avg_dpsi_spl%fs(ipsi,1)=avg(1) - avg_bsq_spl%fs(ipsi,1)=avg(2) - v_spl%fs(ipsi,1)=avg(3) - ENDDO - CALL spline_dealloc(fspl) - - CALL spline_fit(avg_dpsi_spl,"extrap") - CALL spline_fit(avg_bsq_spl,"extrap") - CALL spline_fit(v_spl,"extrap") - DO ising=1,msing respsi=sing(ising)%psifac - - ! Evaluate splines on rational surface - CALL spline_eval(sq,respsi,1) - CALL spline_eval(psi_t,respsi,1) - CALL spline_eval(avg_dpsi_spl,respsi,1) - CALL spline_eval(avg_bsq_spl,respsi,1) - CALL spline_eval(v_spl,respsi,1) - CALL spline_eval(vol_spl,respsi,1) CALL spline_eval(shr_spl,respsi,1) - -c Connor et al. 2015 (PPCF 57 065001) eq. (59): -c Delta_crit = (pi^{3/2}/2) (chi_par/chi_perp)^{1/4} V_s -c * [alpha^2 Lambda^2/(<|grad V|^2>)]^{1/4} -c * (-D_R) -c Identify dgeo = V_s * [alpha^2 Lambda^2 -c /(<|grad V|^2>)]^{1/4}. -c -c Connor's alpha has dimension m^3/Wb: the phase 2 pi i n u / q -c must be dimensionless with u = psi_pol'(V) * theta (Hamada), -c which has units Wb/m^3, so alpha = (2 pi n / q) * v1_SI with -c v1_SI = dV/dpsi_pol [m^3/Wb]. -c Lambda = -q'(V) * (psi_pol'(V))^2 (SI: Wb^2/m^9) -c = -psio^2 * (dq/dpsi_N) / (dV/dpsi_N)^3 -c where psio is total poloidal flux at edge (Wb). -c V_s = V(psi_N_res) (m^3) -c STRIDE's sq uses psi_N (psifac); convert via psi_pol = psi_N * psio. -c sq%f(3) = dV / d(psi_N) [m^3] -c sq%f1(4) = dq / d(psi_N) [dimensionless] -c psio = chi1/(2 pi) [Wb] (total poloidal flux) -c v1_SI = sq%f(3)/psio [m^3/Wb] - V_s = vol_spl%f(1) - q = sq%f(4) - - ! alpha = (2 pi n / q) * v1_SI [m^3/Wb] - al = twopi * nn / q * sq%f(3) / psio - - ! Lambda = -psio^2 * (dq/dpsi_N) / (dV/dpsi_N)^3 [Wb^2/m^9] - Lam = -(psio**2.0) * sq%f1(4) / (sq%f(3)**3.0) - shr(ising) = respsi * shr_spl%f1(1) -c Final 2 sqrt(2 pi q) factor bridges Connor's Hamada-0-to-1 -c convention (eq. 59) to the Fitzpatrick/PEST3 r_s-normalized -c convention used by STRIDE's Delta_prime (PEST3 matrix) and -c SLAYER's rfitzp dc_type. In the LAR limit this reduces -c dgeo -> sqrt(ns/(R r_s)), matching Connor eq. (61) times r_s. - dgeo(ising) = 2.0d0*SQRT(twopi*q) * V_s - $ * ( (((al**2.0)*(Lam**2.0))/ - $ (avg_bsq_spl%f(1)*avg_dpsi_spl%f(1)))**0.25 ) ENDDO - ! Deallocate the new cumulative volume spline - CALL spline_dealloc(vol_spl) + CALL spline_dealloc(shr_spl) c----------------------------------------------------------------------- c optionally write netcdf file. c----------------------------------------------------------------------- IF(present(op_netcdf_out))THEN IF(op_netcdf_out) CALL stride_netcdf_out(wp,wv,wt,ep,ev,et, - $ delta_prime_mat,plasma1,vacuum1,total1,shr,dgeo) + $ delta_prime_mat,plasma1,vacuum1,total1,shr) ENDIF c----------------------------------------------------------------------- c deallocate c----------------------------------------------------------------------- - CALL spline_dealloc(psi_t) - CALL spline_dealloc(avg_dpsi_spl) - CALL spline_dealloc(avg_bsq_spl) - CALL spline_dealloc(v_spl) - CALL spline_dealloc(shr_spl) CALL stride_dealloc c----------------------------------------------------------------------- c terminate. diff --git a/stride/stride.F b/stride/stride.F index 6ae14ece8..10cd23f45 100644 --- a/stride/stride.F +++ b/stride/stride.F @@ -350,7 +350,7 @@ PROGRAM stride ALLOCATE(mx0(mpert,mpert),vx0(mpert)) CALL stride_netcdf_out(mx0,mx0,mx0,vx0,vx0,vx0, $ delta_prime_mat,plasma1,vacuum1,total1, - $ SPREAD(0._r8,1,msing),SPREAD(0._r8,1,msing)) + $ SPREAD(0._r8,1,msing)) CALL stride_dealloc ENDIF IF(mat_flag .OR. ode_flag)DEALLOCATE(amat,bmat,cmat,ipiva,jmat) diff --git a/stride/stride_netcdf.f b/stride/stride_netcdf.f index d30087b6f..939ebf075 100644 --- a/stride/stride_netcdf.f +++ b/stride/stride_netcdf.f @@ -49,13 +49,13 @@ END SUBROUTINE check c declarations. c----------------------------------------------------------------------- SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, - $ pl1,va1,to1,shr,dgeo) + $ pl1,va1,to1,shr) REAL(r8), DIMENSION(mpert), INTENT(IN) :: epi,evi,eti COMPLEX(r8), DIMENSION(mpert,mpert), INTENT(IN) :: wp,wv,wt COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE, INTENT(IN) :: dp REAL(r8), INTENT(IN) :: pl1,va1,to1 - REAL(r8), DIMENSION(msing), INTENT(IN) :: shr,dgeo + REAL(r8), DIMENSION(msing), INTENT(IN) :: shr INTEGER :: i, ncid, $ i_dim, m_dim, mo_dim, p_dim, i_id, m_id, mo_id, p_id, @@ -63,7 +63,7 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, $ wp_id, wpv_id, wv_id, wvv_id, wt_id, wtv_id, $ r_dim, rp_dim, l_dim, lp_dim, r_id, rp_id, l_id, lp_id, $ pr_id, qr_id, dp_id, ap_id, bp_id, gp_id, dpp_id, - $ shear_id,resm_id,drr_id,dgeo_id,dir_id + $ shear_id,resm_id,drr_id,dir_id COMPLEX(r8), DIMENSION(mpert) :: ep,ev,et CHARACTER(2) :: sn CHARACTER(64) :: ncfile @@ -205,8 +205,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, $ "Safety Factor at Rational Surfaces") ) CALL check( nf90_def_var(ncid, "shear", nf90_double, r_dim, $ shear_id) ) - CALL check( nf90_def_var(ncid, "Delta_geo", nf90_double, r_dim, - $ dgeo_id) ) CALL check( nf90_def_var(ncid, "resm", nf90_int, r_dim, $ resm_id) ) CALL check( nf90_def_var(ncid, "dr_rational", nf90_double, @@ -282,7 +280,6 @@ SUBROUTINE stride_netcdf_out(wp,wv,wt,epi,evi,eti,dp, $ i=1,msing)/)) ) CALL check( nf90_put_var(ncid,shear_id, shr) ) CALL check( nf90_put_var(ncid,resm_id, resm) ) - CALL check( nf90_put_var(ncid,dgeo_id, dgeo) ) CALL check( nf90_put_var(ncid,drr_id, dr_rationals)) CALL check( nf90_put_var(ncid,dir_id, di_rationals)) ENDIF From c540e7b04d7d271c93d6f572e2b1133eeb20276e Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 14 Oct 2026 15:45:47 -0400 Subject: [PATCH 210/305] DOCS - EXAMPLES - Add a SLAYER growth-rate run to the DIII-D example and fix the a10 SLAYER input - DIIID_ideal_example gains a slayer.in that reads STRIDE's output and the kinetic file (read_eq=t), and a README section describing the run and its result: with the rfitzp critical Delta the 2/1, 3/1 and 4/1 tearing modes are damped; without it the 2/1 grows at about 80 /s. - a10_ideal_example/slayer.in is restored to develop's version, with two fixes that let gfortran read it: delta_n_p is a complex value, and the file ends in a newline. Co-Authored-By: Claude Opus 5.5 --- docs/examples/DIIID_ideal_example/README | 14 ++++ docs/examples/DIIID_ideal_example/slayer.in | 93 +++++++++++++++++++++ docs/examples/a10_ideal_example/slayer.in | 17 ++-- 3 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 docs/examples/DIIID_ideal_example/slayer.in diff --git a/docs/examples/DIIID_ideal_example/README b/docs/examples/DIIID_ideal_example/README index bc4e65dcb..f699a00f0 100644 --- a/docs/examples/DIIID_ideal_example/README +++ b/docs/examples/DIIID_ideal_example/README @@ -23,6 +23,20 @@ A ``stride.in`` is also included, allowing STRIDE to be run as an alternative st .. note:: STRIDE does not write ``euler.bin`` (its ``bin_euler`` flag is a dummy variable), so DCON must be run when GPEC will be executed in subsequent steps. +STRIDE also writes ``stride_output_n1.nc``, which holds the outer-region tearing stability matrix Delta' and the rational-surface data that SLAYER needs to compute resistive-layer tearing growth rates. With ``read_eq=t``, the included ``slayer.in`` reads that file together with the kinetic profiles in ``TkMkr_D3Dlike_Hmode.gpeckf``. It then matches each layer response to Delta' on the first three rational surfaces:: + + slayer + + Calculating growth rate on m = 2 rational surface: + Delta_prime (r_s ref.): 5.93 ... + Delta_crit: 33.3 + Dispersion roots found: 1, poles: 1 + root 1: Q= -1.162346E+00 -4.163883E-02 omega[rad/s]= -1.059332E+04 gamma[1/s]= -3.794858E+02 polished=T + +Delta' is converted from STRIDE's normalized-flux reference length to the minor-radius reference that the layer quantities use before matching. With the default ``dc_type="rfitzp"`` critical Delta, the 2/1, 3/1 and 4/1 tearing modes are all damped here, because Delta_crit exceeds Delta' on each surface. Setting ``dc_type="none"`` drops this stabilization and leaves the 2/1 weakly unstable (gamma of about 80 /s). Each root is reported in its surface's ExB frame. The results, including every root and pole found in the scan, are written to ``slayer_output_n1.nc``. + +Setting ``coupling_flag=t`` instead solves the coupled multi-surface dispersion relation for the first ``msing_max`` surfaces. Each surface is then Doppler-shifted by its ExB rotation from the kinetic file, so the root is reported in the lab frame. + Satisfied with the DCON result, we run GPEC, which computes the ideal plasma response to the external 3D field from the coils defined in ``coil.in``:: gpec diff --git a/docs/examples/DIIID_ideal_example/slayer.in b/docs/examples/DIIID_ideal_example/slayer.in new file mode 100644 index 000000000..74d6a001f --- /dev/null +++ b/docs/examples/DIIID_ideal_example/slayer.in @@ -0,0 +1,93 @@ +&SLAYER_INPUT + input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table + infile='TkMkr_D3Dlike_Hmode.gpeckf' ! Path to the kinetic profile file (read_eq=t), or to an ascii table of surface quantities (input_flag=t) + ncfile='stride_output_n1.nc' ! Path to netCDF file of STRIDE outputs + params_flag=f ! calculates normalized parameters from profile quantities + ! >>> + ! >>> Input rational surface quantities + ! >>> + mm=2 ! poloidal mode number + nn=1 ! toroidal mode number + n_e=5.50E+19 ! electron density [m^-3] + t_e=1000.0 ! electron temperature [eV] + t_i=1000.0 ! ion temperature [eV] + sval=1.5 ! magnetic shear at the layer + bt=2.0 ! toroidal field [T] + rs=0.5 ! minor radius of resonant surface [m] + R0=1.67 ! major radius of magnetic axis [m] + omega=5.00E+03 ! ExB rotation frequency [rad/s] + l_t=0.11 ! temperature gradient scale length + l_n=0.25 ! density gradient scale length + qval=2.0 ! q of resonant surface + mu_i=2.0 ! ion mass ratio to proton mass, 2 for deuterium plasma. Also used when read_eq=t + zeff=2.0 ! plasma Z_effective. Also used when read_eq=t (the kinetic file's Z_eff is not used) + dr_val=-0.1 ! GGJ resistive interchange index D_R (taken from STRIDE when read_eq=t) + dgeo_val=0.0 ! unused (the toroidal critical Delta was removed); accepted so older inputs still read + chi_p_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular energy diffusivity profile + chi_t_prof=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 ! Rational surface anomalous perpendicular ion momentum diffusivity profile + kappa_prof=0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 ! Rational surface thermal conductivity profile (NOTE: set > 0 to override chi_p_prof) + ! >>> + ! >>> Input normalized layer parameters. If read_eq=f, each will individually override internal calcuation using above inputs + ! >>> + inpr=0.0 ! Prantdl number. Set /= 0.0 to override internal calculation + inpe=0.0 ! electron viscosity + inQ=1.0 ! normalized ExB frequency. TO DO: update for mode rotation frequency calculation! + inQ_e=0.0 ! normalized electron diamagnetic frequency. Set /= 0.0 to override internal calculation + inQ_i=0.0 ! normalized ion diamagnetic frequency. Set /= 0.0 to override internal calculation + inc_beta=0.0 ! dimensionless measure of the plasma pressure. Set /= 0.0 to override internal calculation + inds=0.0 ! normalized ion sound radius, or normalized ion skin depth. Set /= 0.0 to override internal calculation + intau=1.0 ! ratio of ion to electron temperature + Q0=1.0 ! unnecessary placeholder variable for inQ? + delta_prime=(0.0,0.0) ! input Delta' for the growth rate calculation when read_eq=f, referenced to the minor radius (r_s*Delta') + delta_n_p=(0.01,0.01) ! delta offset used for jxb calculations. Default for latter is (1e-2,1e-2). + ingamma=(0.0,0.0) ! input (Re(Q),Im(Q)) to calculate inner layer Delta. Set /= 0.0 to override internal root finding +/ + +&SLAYER_CONTROL + inum=400 ! resolution to find error field thresholds. + jnum=100 ! resolution for 2d scan along with Q,omega. + knum=100 ! resolution for 2d scan along with the other. + Q_num=100 ! number of points per axis of the initial Re(Q),Im(Q) scan grid + scan_width=1.5 ! half-width of the Re(Q),Im(Q) scan window, in normalized Q + AMR_passes=4 ! number of adaptive refinement passes of the scan grid + !scan_center=0.0 ! Re(Q) centre of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan + msing_max=3 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t + dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" + read_eq=t ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) + Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation + coupling_flag=f ! solve the coupled multi-surface determinant instead of each surface separately. Requires read_eq=t + rotation_flag=t ! Doppler-shift each surface by its ExB rotation in the coupled determinant (root reported in the lab frame) + QPscan_flag=f ! scan (Q,P) space for delta and torque. + Qscan_flag=f ! scan Q space + QPescan_flag=f ! scan (Q,Pe) space for delta and torque. + Qbscan_flag=f ! scan (Q,beta) space for delta and torque. + onscan_flag=f ! scan (omega,n) space for error fields. + otscan_flag=f ! scan (omega,t) space for error fields. + ntscan_flag=f ! scan (n,te) space for error fields. + nbtscan_flag=f ! scan (n,bt) space for error fields. + parflow_flag=f ! set parallel flow on + PeOhmOnly_flag=t ! only include Pe from Ohm's law + Pe_flag=f ! if true, calculate classical inpe value from inpr and include in SLAYER run + layfac=0.02 ! layfac*EXP(ifac*ATAN2(AIMAG(Q-Q_e),REAL(Q-Q_e)) is added to Q_e if ABS(Q-Q_e) Date: Wed, 14 Oct 2026 15:46:37 -0400 Subject: [PATCH 211/305] SLAYER - MINOR - Restore CRLF line endings in delta.f develop stores delta.f with CRLF line endings. The merge converted it to LF, which made every line of the file show as changed. Content is unchanged. Co-Authored-By: Claude Opus 5.5 --- slayer/delta.f | 2178 ++++++++++++++++++++++++------------------------ 1 file changed, 1089 insertions(+), 1089 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 38da8630b..739dc74d1 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -1,1090 +1,1090 @@ - MODULE delta_mod -c----------------------------------------------------------------------- -c delta_mod: Riccati-based tearing-mode layer Delta solvers. -c -c Contains three Riccati formulations: -c riccati - standard formulation (non-stiff, lsode mf=10) -c riccati_del_s - del_s formulation (stiff, lsode mf=21) -c riccati_f - Fitzpatrick P_perp/P_tor formulation (mf=21) -c -c Each solver integrates a Riccati ODE for W(x) from a large-|x| -c asymptotic boundary condition inward to x ~ 0, then extracts -c Delta = pi / W'(0). -c -c Associated ODE subroutines (w_der, w_der_del_s, w_der_f) and -c Jacobian subroutines (jac_del_s, jac_f) follow below. -c -c Module-level flags: -c riccati_out - write W(x) profile to binary + text file -c parflow_flag - include parallel electron flow terms in w_der -c PeOhmOnly_flag - retain only Pe-Ohm coupling in w_der -c----------------------------------------------------------------------- - - USE sglobal_mod - USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan - - IMPLICIT NONE - -c --- module-level control flags - LOGICAL :: riccati_out ! write W(x) profile to binary + text - LOGICAL :: parflow_flag ! enable parallel-flow terms in w_der - LOGICAL :: PeOhmOnly_flag ! Pe-Ohm-only coupling in w_der - - CONTAINS -c----------------------------------------------------------------------- -c riccati: compute Delta via Riccati integration of w_der. -c -c Integrates W(x) from x_start inward to x_min using lsode -c (non-stiff, mf=10). Optionally applies the layfac singularity -c guard when Q is near Q_e. Returns Delta = pi / W'(x_min). -c -c If riccati_out = .TRUE., writes the W(x) profile to -c slayer_riccati_profile_n.{bin,out}. -c -c----------------------------------------------------------------------- - FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, - $ iinQ,inx,iny) - -c --- input arguments: physical parameters for this surface - REAL(r8),INTENT(IN) :: inQ ! real part of Q - REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq - REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq - REAL(r8),INTENT(IN) :: inpr ! Prandtl number - REAL(r8),INTENT(IN) :: inpe ! electron Prandtl number - REAL(r8),INTENT(IN) :: inc_beta ! c_beta parameter - REAL(r8),INTENT(IN) :: inds ! magnetic shear ds - REAL(r8),INTENT(IN) :: intau ! ion-to-electron temp ratio -c --- optional arguments - REAL(r8),INTENT(IN),OPTIONAL :: iinQ ! imaginary part of Q - REAL(r8),INTENT(IN),OPTIONAL :: inx ! override starting x - COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W -c --- function result - COMPLEX(r8) :: riccati - -c --- lsode solver control - INTEGER :: istep ! integration step counter - INTEGER :: neq ! number of equations (=2) - INTEGER :: itol,itask ! lsode tolerance/task flags - INTEGER :: istate,iopt,mf ! lsode state/option/method flags - INTEGER :: liw,lrw ! lsode work array sizes - REAL(r8) :: x,xout ! current and target x - REAL(r8) :: xmin ! inner integration bound - REAL(r8) :: rtol ! relative tolerance -c --- work arrays - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy ! W and dW - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work - REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work - -c --- copy input arguments to module-level globals for w_der - Q=inQ - IF(present(iinQ)) Q=inQ+ifac*iinQ - Q_e=inQ_e - Q_i=inQ_i - pr=inpr - pe=inpe - c_beta=inc_beta - ds=inds - tau=intau - -c --- singularity guard: displace Q away from Q_e when too close - IF ((layfac>0).AND.(ABS(Q-Q_e)xout) - istep=istep+1 - CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) - WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) - WRITE(out2_unit,'(1x,3(es17.8e3))') x,REAL(y),AIMAG(y) - ENDDO - CLOSE(bin_unit) - CLOSE(out2_unit) - ELSE -c single-shot integration to x_min - istep = 1 - itask = 1 - CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) - ENDIF - -c --- extract Delta from final W derivative at x_min -c NOTE: W -> 0 when Q -> Q_e (see layfac guard above). - CALL w_der(neq,x,y,dy) - riccati=pi/dy(1) - DEALLOCATE(atol,y,dy,iwork,rwork) - - END FUNCTION riccati -c----------------------------------------------------------------------- -c riccati_del_s: compute Delta via the del_s Riccati formulation. -c -c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian -c (jac_del_s). Integrates W(q) from large q inward to q_min. -c Returns Delta = -(pi / sqrt(1+1/tau)) * W'(q_min). -c -c Module-level variables tau, D_norm, and P_tor must be valid -c before entry; callers are responsible for setting them. -c Q_e, Q_i, and P_perp are set from arguments inside this -c function. Q, c_beta, and d_beta are not referenced here. -c Note: slayer.f passes Q_e_arr (not Q_arr) as the first -c argument -- this is intentional. -c----------------------------------------------------------------------- - FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) - -c --- input arguments - REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq - REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq - REAL(r8),INTENT(IN) :: inpr ! mapped to P_perp (see below) -c --- optional arguments - REAL(r8),INTENT(IN) :: inx ! starting q for integration - COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W -c --- function result - COMPLEX(r8) :: riccati_del_s - -c --- lsode solver control - INTEGER :: istep ! integration step counter - INTEGER :: neq ! number of equations (=2) - INTEGER :: itol,itask ! lsode tolerance/task flags - INTEGER :: istate,iopt,mf ! lsode state/option/method flags - INTEGER :: liw,lrw ! lsode work array sizes - REAL(r8) :: x ! secondary x variable (set from inx) - REAL(r8) :: xout ! target integration endpoint - REAL(r8) :: xmin ! inner integration bound - REAL(r8) :: rtol ! relative tolerance - REAL(r8) :: my_q ! integration variable (large -> small) - REAL(r8) :: P_hat ! normalized P_perp - REAL(r8) :: alpha ! boundary condition coefficient -c --- work arrays - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq ! W and dW/dq - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work - REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work - -c --- configure lsode: stiff BDF method with user Jacobian (mf=21) - neq = 2 - itol = 2 - rtol = 1e-10 - ALLOCATE(atol(neq),W(1),dW_dq(1)) - atol(:) = 1e-10 - itask = 2 - istate = 1 - iopt = 1 ! enable optional inputs (iwork(6)) - mf = 21 ! stiff, user-supplied Jacobian (jac_del_s) - liw = 20*2 - lrw = 22+9*neq+neq**2 ! stiff work array size - ALLOCATE(iwork(liw+neq),rwork(lrw)) - -c --- set maximum internal steps - iwork=0 - iwork(6)=50000 ! MXSTEP: max internal steps per call - rwork=0 - -c --- set starting integration point - my_q=inx ! start backwards integration at large q - xmin=1e-5 - x=inx - xout=xmin - -c --- copy input arguments to module-level globals for w_der_del_s - Q_e = inQ_e - Q_i = inQ_i - P_perp = inpr - - P_hat = P_perp / D_norm**6.0 - -c --- asymptotic boundary condition at large q - alpha = (P_hat/(1+1/tau))**0.5 - W(1) = -alpha*my_q**2 - 0.5 - IF(present(iny)) W(1)=iny - -c --- integrate W(q) from q_start inward to q_min via lsode - IF (riccati_out) THEN -c profile output: step-by-step integration with file writes - istep = 1 - itask = 2 - OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', - $ POSITION='REWIND',FORM='UNFORMATTED') - - OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') - WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" - DO WHILE (my_q>xout) - istep=istep+1 - CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) - WRITE(bin_unit)REAL(my_q,4),REAL(REAL(W),4),REAL(AIMAG(W),4) - WRITE(out2_unit,'(1x,3(es17.8e3))')my_q,REAL(W),AIMAG(W) - ENDDO - CLOSE(bin_unit) - CLOSE(out2_unit) - ELSE -c single-shot integration to q_min - istep = 1 - itask = 1 - CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) - ENDIF - -c --- extract Delta from final W derivative at q_min - CALL w_der_del_s(neq,my_q,W,dW_dq) - riccati_del_s=-( pi/((1+1/tau)**0.5) )*dW_dq(1) - DEALLOCATE(atol,W,dW_dq,iwork,rwork) - - END FUNCTION riccati_del_s -c----------------------------------------------------------------------- -c jac_dummy: placeholder Jacobian for lsode calls whose method flag -c does not use a user-supplied Jacobian (mf=10, mf=22). lsode never -c calls it; it exists so that every lsode call in this module passes -c a subroutine in the JAC position. -c----------------------------------------------------------------------- - SUBROUTINE jac_dummy(neq, x, y, ml, mu, pd, nrpd) - INTEGER, INTENT(IN) :: neq, ml, mu, nrpd - REAL(r8), INTENT(IN) :: x - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y - COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd - END SUBROUTINE jac_dummy -c----------------------------------------------------------------------- -c jacobian for riccati_del_s(): pd = dF/dW for stiff lsode. -c----------------------------------------------------------------------- - SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) - INTEGER, INTENT(IN) :: neq, ml, mu, nrpd - REAL(r8), INTENT(IN) :: my_q - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W - COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd - pd(1,1) = 1.0/my_q - 2.0d0*W(1)/my_q - END SUBROUTINE jac_del_s -c----------------------------------------------------------------------- -c w_der_del_s: ODE right-hand side dW/dq for riccati_del_s. -c Implements the del_s dispersion relation using normalised -c quantities Q_hat, P_perp_hat, P_tor_hat. -c----------------------------------------------------------------------- - SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) - - INTEGER, INTENT(IN) :: neq - REAL(r8), INTENT(IN) :: my_q - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W - COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dW_dq - REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat - COMPLEX(r8) :: E,F - -c --- normalise physical quantities - Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 - P_perp_hat = P_perp / D_norm**6.0 - P_tor_hat = P_tor / D_norm**6.0 -c --- build the E and F dispersion coefficients - E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ - $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) - F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 - -c --- Riccati ODE: dW/dq = W/q - W^2/q + q*E/F - dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F - RETURN - END SUBROUTINE w_der_del_s -c----------------------------------------------------------------------- -c riccati_f: compute Delta via Fitzpatrick P_perp / P_tor -c Riccati formulation. -c -c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian -c (jac_f). Boundary conditions are set analytically from the -c large-p asymptotic behaviour; the branch depends on whether -c D_norm^2 exceeds iota_e * P_perp / P_tor^(2/3). -c -c----------------------------------------------------------------------- - FUNCTION riccati_f() - -c --- function result - COMPLEX(r8) :: riccati_f - -c --- lsode solver control - INTEGER :: istep ! integration step counter - INTEGER, PARAMETER :: neq = 2 ! number of equations - INTEGER :: itol,itask ! lsode tolerance/task flags - INTEGER :: istate,iopt,mf ! lsode state/option/method flags - INTEGER, PARAMETER :: liw = 42 ! 20*2 + neq - INTEGER, PARAMETER :: lrw = 44 ! 22 + 9*neq + neq**2 - REAL(r8) :: xout ! target integration endpoint - REAL(r8) :: xmin ! inner integration bound - REAL(r8) :: rtol ! relative tolerance - REAL(r8) :: my_p ! integration variable p (large -> small) - REAL(r8) :: bk ! asymptotic coefficient b_k -c --- boundary-condition intermediates - COMPLEX(r8) :: ak ! asymptotic coefficient a_k - COMPLEX(r8) :: ck ! asymptotic coefficient c_k - COMPLEX(r8) :: xk ! asymptotic coefficient x_k - COMPLEX(r8) :: W_bound ! boundary value for W(p_start) -c --- work arrays (fixed-size, stack-allocated to avoid heap overhead) - COMPLEX(r8) :: W(1), dWdp(1) ! W and dW/dp - REAL(r8) :: atol(neq) ! absolute tolerance - INTEGER :: iwork(liw) ! lsode int work - REAL(r8) :: rwork(lrw) ! lsode real work - -c --- configure lsode: stiff BDF method with user Jacobian (mf=21) - itol = 2 - rtol = 1.0d-10 - atol(:) = 1.0d-10 - itask = 2 - istate = 1 - iopt = 1 ! enable optional inputs (iwork(6)) - mf = 21 ! stiff, user-supplied Jacobian (jac_f) - -c --- set maximum internal steps (only iwork(5:6) matter for lsode) - iwork(:) = 0 - iwork(6) = 50000 ! MXSTEP: max internal steps per call - rwork(:) = 0.0d0 - - xmin=1e-6 - xout=xmin - -c --- compute starting p and W boundary condition -c Branch on asymptotic regime: D_norm^2 vs iota_e*P_perp/P_tor^(2/3) -c --- branch 1: D_norm^2 > iota_e * P_perp / P_tor^(2/3) -c large-D_norm regime: p scales with (P_tor*D_norm^2/(iota_e*...)) - IF (D_norm**2 > iota_e*P_perp / P_tor**(2.0d0/3.0d0)) THEN - my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25d0 - my_p = MAX(my_p, 6.0d0) - - ak = -(g_tmp + ifac*Q_e) - bk = (iota_e*P_perp*P_tor)/(P_tor*D_norm**2) - - ck = bk*(1 + (g_tmp+ifac*Q_i) - $ *((P_tor+P_perp)/(P_tor*P_perp)) - $ - (P_perp + (g_tmp+ifac*Q_i)*D_norm**2) - $ *(iota_e/(P_tor*D_norm**2))) - - xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0d0*SQRT(bk)) - - W_bound = xk - SQRT(bk)*my_p - ELSE -c --- branch 2: D_norm^2 <= iota_e * P_perp / P_tor^(2/3) -c small-D_norm regime: p scales with 1/P_tor^(1/6) - my_p = 1.0d0 / P_tor**(1.0d0/6.0d0) - my_p = MAX(my_p, 6.0d0) - - ak = -(g_tmp + ifac*Q_e) - bk = P_tor - ck = -ifac*(Q_e - Q_i)*(P_tor/P_perp) + (g_tmp + ifac*Q_i) - xk = (ak*bk - ck)/(2.0d0*SQRT(bk)) - - W_bound = -1.0d0 + xk*my_p - SQRT(bk)*my_p**3 - END IF - - W(1) = W_bound - -c --- integrate W(p) from p_start inward to p_min via lsode - IF (riccati_out) THEN -c profile output: step-by-step integration with file writes - istep = 1 - itask = 2 - OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', - $ POSITION='REWIND',FORM='UNFORMATTED') - - OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// - $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') - WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" - DO WHILE (my_p>xout) - istep=istep+1 - CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) - WRITE(bin_unit)REAL(my_p,4),REAL(REAL(W),4), - $ REAL(AIMAG(W),4) - WRITE(out2_unit,'(1x,3(es17.8e3))')my_p,REAL(W),AIMAG(W) - ENDDO - CLOSE(bin_unit) - CLOSE(out2_unit) - ELSE -c single-shot integration to p_min - istep = 1 - itask = 1 - CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) - ENDIF - -c --- extract Delta from final W derivative at p_min - CALL w_der_f(neq,my_p,W,dWdp) - riccati_f = pi / dWdp(1) -c A failed integration (istate < 0) leaves W short of p_min. Return -c NaN so that scans and the root search skip the point instead of -c using a Delta built from an unconverged W. - IF (istate < 0) riccati_f = CMPLX( - $ ieee_value(1.0_r8, ieee_quiet_nan), - $ ieee_value(1.0_r8, ieee_quiet_nan), KIND=r8) - - END FUNCTION riccati_f -c----------------------------------------------------------------------- -c jacobian for riccati_f(): pd = dF/dW for stiff lsode. -c----------------------------------------------------------------------- - SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) - INTEGER, INTENT(IN) :: neq, ml, mu, nrpd - REAL(r8), INTENT(IN) :: my_p - COMPLEX(r8) :: fA_p, denom - REAL(r8) :: p2 - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W - COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd - - p2 = my_p * my_p - denom = g_tmp + ifac*Q_e + p2 - fA_p = (denom - 2.0d0*p2) / denom - - pd(1,1) = (-fA_p/my_p) - (2.0d0*W(1))/my_p - END SUBROUTINE jac_f -c----------------------------------------------------------------------- -c w_der_f: ODE right-hand side dW/dp for riccati_f. -c Implements the Fitzpatrick P_perp / P_tor dispersion relation. -c Coefficients fA, fB, fC are evaluated at the current p. -c----------------------------------------------------------------------- - SUBROUTINE w_der_f(neq,my_p,W,dWdp) - - INTEGER, INTENT(IN) :: neq - REAL(r8), INTENT(IN) :: my_p - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W - COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dWdp - COMPLEX(r8) :: fA, fA_prime, fB, fC - COMPLEX(r8) :: denom ! cached g_tmp + i*Q_e + p^2 - REAL(r8) :: p2, p4 ! cached p^2, p^4 - REAL(r8) :: D2 ! cached D_norm^2 - -c cache powers and shared denominator - p2 = my_p * my_p - p4 = p2 * p2 - D2 = D_norm * D_norm - denom = g_tmp + ifac*Q_e + p2 - -c evaluate coefficients at the current p - fA = p2 / denom - fA_prime = (denom - 2.0d0*p2) / denom ! (g+iQe - p^2)/(g+iQe + p^2) - - fB = g_tmp*(g_tmp + ifac*Q_i) - $ + (g_tmp + ifac*Q_i)*(P_perp + P_tor)*p2 - $ + (P_perp*P_tor)*p4 - - fC = g_tmp + ifac*Q_e - $ + (P_perp + (g_tmp + ifac*Q_i)*D2)*p2 - $ + (1.0d0/iota_e)*P_tor*D2*p4 - - dWdp(1) = -(fA_prime/my_p)*W(1) - W(1)*W(1)/my_p - $ + (fB/(fA*fC))*(p2*my_p) - - RETURN - END SUBROUTINE w_der_f -c----------------------------------------------------------------------- -c W derivative for riccati() -c----------------------------------------------------------------------- - SUBROUTINE w_der(neq,x,y,dy) - - INTEGER, INTENT(IN) :: neq - REAL(r8), INTENT(IN) :: x - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y - COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy - COMPLEX(r8) :: G - COMPLEX(r8) :: C1 - COMPLEX(r8) :: C1p - COMPLEX(r8) :: C2 - COMPLEX(r8) :: C3 - COMPLEX(r8) :: C3p - COMPLEX(r8) :: C2p - COMPLEX(r8) :: A1 - COMPLEX(r8) :: A2 - !COMPLEX(r8), PARAMETER :: ifac=(0,1) - - IF (parflow_flag) THEN - C1=((1 + tau)*x**2*pe* - $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i)))) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - - C1p=((1 + tau)*x**2*pe* - $ ((2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ **2)/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** - $ 3 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (12*ds**2*pr*(1 + tau)*x**2 + - $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** - $ 2 - (2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** - $ 2 - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** - $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((12*ds**2*pr*(1 + tau)*x**2 + - $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) - $ + (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2 - $ - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)**2* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**3 - $ + ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ - ((1 + tau)*x**2*pe* - $ (2*pr*x + 2*x*pe + - $ (ds**2*(1 + tau)*x**6*pe**2* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2))/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2) - - $ (6*ds**2*(1 + tau)*x**5*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e) - $ )) - (ifac*(1 + tau)*x**2*pe* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2)*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ **2 + (2*ifac*(1 + tau)*x*pe* - $ Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ *(-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ ) + (ifac*(1 + tau)*x**2*pe* - $ Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ **2 + (2*(1 + tau)*x*pe* - $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - - C2=((1 + tau)*x**2*pe* - $ ((ds**2*x**4*pe)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ ) - (ifac*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - - C2p=((1 + tau)*x**2*pe* - $ (-((ds**2*x**4*pe* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2))/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2)) + - $ (4*ds**2*x**3*pe)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e) - $ )) + (ifac* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2)*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ **2 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* - $ (4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** - $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (-((4*ds**2*pr*(1 + tau)*x**3 + - $ 2*x* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2* - $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) - $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) - $ /(ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + - $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ - ((1 + tau)*x**2*pe* - $ (2*pr*x + 2*x*pe + - $ (ds**2*(1 + tau)*x**6*pe**2* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2))/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + - $ ifac*(Q - Q_e))**2) - - $ (6*ds**2*(1 + tau)*x**5*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e) - $ )) - (ifac*(1 + tau)*x**2*pe* - $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2)*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ **2 + (2*ifac*(1 + tau)*x*pe* - $ Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ *((ds**2*x**4*pe)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e) - $ )) - (ifac*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ ) + (ifac*(1 + tau)*x**2*pe* - $ Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ **2 + (2*(1 + tau)*x*pe* - $ ((ds**2*x**4*pe)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e) - $ )) - (ifac*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* - $ (1 - (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + - $ ifac*ds**2*(Q - Q_i)))/ - $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ - $ (ds**2*pr*(1 + tau)*x**4 + - $ ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ - $ (ifac*Q + pr*x**2 + x**2*pe - - $ (ds**2*(1 + tau)*x**6*pe**2)/ - $ (c_beta**2* - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e))) - ELSE - C1=0 - C1p=0 - C2=0 - C2p=0 - ENDIF - - IF (PeOhmOnly_flag) THEN - G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + - $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) - ELSE - G=(x**2*pe + - $ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + - $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ - $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + - $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) - ENDIF - - C3=x**2/(x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + ifac*(Q - Q_e)) - - C3p=-((x**2*(2*x + (4*ds**2*(1 + tau)*x**3*pe)/ - $ c_beta**2))/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + (0,1)*(Q - Q_e))**2 - $ ) + (2*x)/ - $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ - $ c_beta**2 + (0,1)*(Q - Q_e)) - - A1=(C1 + (C3p/C3)*(C2 + 1) + C2p)/(C2 + 1) - - A2=(C1p + C1*(C3p/C3) - G/C3)/(C2 + 1) - - dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x - - RETURN - END SUBROUTINE w_der -c----------------------------------------------------------------------- -c riccati integration with simplified version for test. -c----------------------------------------------------------------------- - SUBROUTINE w_der_temp(neq,x,y,dy) - - INTEGER, INTENT(IN) :: neq - REAL(r8), INTENT(IN) :: x - COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y - COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy - !COMPLEX(r8), PARAMETER :: ifac=(0,1) - - dy(1)=(2.0*x/(ifac*(Q-Q_e)+x**2.0)-1.0/x)*y(1)-y(1)*y(1)/x - $ +x*(ifac*(Q-Q_e)+x**2.0) - $ *(-Q*(Q-Q_i)+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 - $ +pr*c_beta**2.0*x**4.0)/(ifac*(Q-Q_e) - $ +(c_beta**2.0+ifac*(Q-Q_i)*ds**2.0)*x**2.0 - $ +(1+tau)*pr*ds**2.0*x**4.0) - -c dy(1)=-y(1)/x-y(1)*y(1)/x+x*(ifac*(Q-Q_e)) -c $ *(-Q*(Q-Q_i)+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 -c $ +pr*c_beta**2.0*x**4.0)/(ifac*(Q-Q_e) -c $ +(c_beta**2.0+ifac*(Q-Q_i)*ds**2.0)*x**2.0 -c $ +(1+tau)*pr*ds**2.0*x**4.0) - - RETURN - END SUBROUTINE w_der_temp -c----------------------------------------------------------------------- -c calculate delta based on direct phi_der (obsolete). -c----------------------------------------------------------------------- - FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) - - REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds - REAL(r8),INTENT(IN) :: intau - COMPLEX(r8) :: directred - - INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf - - REAL(r8) :: xintv,x,xout,rtol,xmax - COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy - - INTEGER, DIMENSION(:), ALLOCATABLE :: iwork - REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork - - Q=inQ - Q_e=inQ_e - Q_i=inQ_i - pr=inpr - pe=inpe - c_beta=inc_beta - ds=inds - tau=intau - - neq = 4 - itol = 2 - rtol = 1e-3 - ALLOCATE(atol(neq),y(2),dy(2)) - atol(:) = 1e-4 - itask = 1 - istate = 1 - iopt = 0 - mf = 10 - liw = 20 - lrw = 22+16*neq - ALLOCATE(iwork(liw),rwork(lrw)) - - xintv = 0.1 - x=0.1 - istep=1 - xout=x+xintv - xmax=1e3 - - y(1)=1e-10 - y(2)=y(1)/(c_beta/(sqrt(1+tau)*ds)*(1+ifac*(Q-Q_e)*x**2.0)*x) - - DO - istep=istep+1 - CALL lsode(phi_der,neq,y,x,xout,itol,rtol,atol, - $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) - xout=xout+xintv - IF (xmax-xout.{bin,out}. +c +c----------------------------------------------------------------------- + FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, + $ iinQ,inx,iny) + +c --- input arguments: physical parameters for this surface + REAL(r8),INTENT(IN) :: inQ ! real part of Q + REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq + REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq + REAL(r8),INTENT(IN) :: inpr ! Prandtl number + REAL(r8),INTENT(IN) :: inpe ! electron Prandtl number + REAL(r8),INTENT(IN) :: inc_beta ! c_beta parameter + REAL(r8),INTENT(IN) :: inds ! magnetic shear ds + REAL(r8),INTENT(IN) :: intau ! ion-to-electron temp ratio +c --- optional arguments + REAL(r8),INTENT(IN),OPTIONAL :: iinQ ! imaginary part of Q + REAL(r8),INTENT(IN),OPTIONAL :: inx ! override starting x + COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W +c --- function result + COMPLEX(r8) :: riccati + +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER :: neq ! number of equations (=2) + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER :: liw,lrw ! lsode work array sizes + REAL(r8) :: x,xout ! current and target x + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance +c --- work arrays + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy ! W and dW + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work + REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work + +c --- copy input arguments to module-level globals for w_der + Q=inQ + IF(present(iinQ)) Q=inQ+ifac*iinQ + Q_e=inQ_e + Q_i=inQ_i + pr=inpr + pe=inpe + c_beta=inc_beta + ds=inds + tau=intau + +c --- singularity guard: displace Q away from Q_e when too close + IF ((layfac>0).AND.(ABS(Q-Q_e)xout) + istep=istep+1 + CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) + WRITE(bin_unit)REAL(x,4),REAL(REAL(y),4),REAL(AIMAG(y),4) + WRITE(out2_unit,'(1x,3(es17.8e3))') x,REAL(y),AIMAG(y) + ENDDO + CLOSE(bin_unit) + CLOSE(out2_unit) + ELSE +c single-shot integration to x_min + istep = 1 + itask = 1 + CALL lsode(w_der,neq,y,x,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) + ENDIF + +c --- extract Delta from final W derivative at x_min +c NOTE: W -> 0 when Q -> Q_e (see layfac guard above). + CALL w_der(neq,x,y,dy) + riccati=pi/dy(1) + DEALLOCATE(atol,y,dy,iwork,rwork) + + END FUNCTION riccati +c----------------------------------------------------------------------- +c riccati_del_s: compute Delta via the del_s Riccati formulation. +c +c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian +c (jac_del_s). Integrates W(q) from large q inward to q_min. +c Returns Delta = -(pi / sqrt(1+1/tau)) * W'(q_min). +c +c Module-level variables tau, D_norm, and P_tor must be valid +c before entry; callers are responsible for setting them. +c Q_e, Q_i, and P_perp are set from arguments inside this +c function. Q, c_beta, and d_beta are not referenced here. +c Note: slayer.f passes Q_e_arr (not Q_arr) as the first +c argument -- this is intentional. +c----------------------------------------------------------------------- + FUNCTION riccati_del_s(inQ_e,inQ_i,inpr,inx,iny) + +c --- input arguments + REAL(r8),INTENT(IN) :: inQ_e ! electron diamagnetic freq + REAL(r8),INTENT(IN) :: inQ_i ! ion diamagnetic freq + REAL(r8),INTENT(IN) :: inpr ! mapped to P_perp (see below) +c --- optional arguments + REAL(r8),INTENT(IN) :: inx ! starting q for integration + COMPLEX(r8),INTENT(IN),OPTIONAL :: iny ! override starting W +c --- function result + COMPLEX(r8) :: riccati_del_s + +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER :: neq ! number of equations (=2) + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER :: liw,lrw ! lsode work array sizes + REAL(r8) :: x ! secondary x variable (set from inx) + REAL(r8) :: xout ! target integration endpoint + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance + REAL(r8) :: my_q ! integration variable (large -> small) + REAL(r8) :: P_hat ! normalized P_perp + REAL(r8) :: alpha ! boundary condition coefficient +c --- work arrays + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: W,dW_dq ! W and dW/dq + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork ! lsode int work + REAL(r8), DIMENSION(:), ALLOCATABLE :: atol,rwork ! lsode real work + +c --- configure lsode: stiff BDF method with user Jacobian (mf=21) + neq = 2 + itol = 2 + rtol = 1e-10 + ALLOCATE(atol(neq),W(1),dW_dq(1)) + atol(:) = 1e-10 + itask = 2 + istate = 1 + iopt = 1 ! enable optional inputs (iwork(6)) + mf = 21 ! stiff, user-supplied Jacobian (jac_del_s) + liw = 20*2 + lrw = 22+9*neq+neq**2 ! stiff work array size + ALLOCATE(iwork(liw+neq),rwork(lrw)) + +c --- set maximum internal steps + iwork=0 + iwork(6)=50000 ! MXSTEP: max internal steps per call + rwork=0 + +c --- set starting integration point + my_q=inx ! start backwards integration at large q + xmin=1e-5 + x=inx + xout=xmin + +c --- copy input arguments to module-level globals for w_der_del_s + Q_e = inQ_e + Q_i = inQ_i + P_perp = inpr + + P_hat = P_perp / D_norm**6.0 + +c --- asymptotic boundary condition at large q + alpha = (P_hat/(1+1/tau))**0.5 + W(1) = -alpha*my_q**2 - 0.5 + IF(present(iny)) W(1)=iny + +c --- integrate W(q) from q_start inward to q_min via lsode + IF (riccati_out) THEN +c profile output: step-by-step integration with file writes + istep = 1 + itask = 2 + OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', + $ POSITION='REWIND',FORM='UNFORMATTED') + + OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') + WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" + DO WHILE (my_q>xout) + istep=istep+1 + CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) + WRITE(bin_unit)REAL(my_q,4),REAL(REAL(W),4),REAL(AIMAG(W),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')my_q,REAL(W),AIMAG(W) + ENDDO + CLOSE(bin_unit) + CLOSE(out2_unit) + ELSE +c single-shot integration to q_min + istep = 1 + itask = 1 + CALL lsode(w_der_del_s,neq,W,my_q,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_del_s,mf) + ENDIF + +c --- extract Delta from final W derivative at q_min + CALL w_der_del_s(neq,my_q,W,dW_dq) + riccati_del_s=-( pi/((1+1/tau)**0.5) )*dW_dq(1) + DEALLOCATE(atol,W,dW_dq,iwork,rwork) + + END FUNCTION riccati_del_s +c----------------------------------------------------------------------- +c jac_dummy: placeholder Jacobian for lsode calls whose method flag +c does not use a user-supplied Jacobian (mf=10, mf=22). lsode never +c calls it; it exists so that every lsode call in this module passes +c a subroutine in the JAC position. +c----------------------------------------------------------------------- + SUBROUTINE jac_dummy(neq, x, y, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: x + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + END SUBROUTINE jac_dummy +c----------------------------------------------------------------------- +c jacobian for riccati_del_s(): pd = dF/dW for stiff lsode. +c----------------------------------------------------------------------- + SUBROUTINE jac_del_s(neq, my_q, W, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: my_q + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + pd(1,1) = 1.0/my_q - 2.0d0*W(1)/my_q + END SUBROUTINE jac_del_s +c----------------------------------------------------------------------- +c w_der_del_s: ODE right-hand side dW/dq for riccati_del_s. +c Implements the del_s dispersion relation using normalised +c quantities Q_hat, P_perp_hat, P_tor_hat. +c----------------------------------------------------------------------- + SUBROUTINE w_der_del_s(neq,my_q,W,dW_dq) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: my_q + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dW_dq + REAL(r8) :: Q_hat, P_tor_hat, P_perp_hat + COMPLEX(r8) :: E,F + +c --- normalise physical quantities + Q_hat = (Q_e*(1+tau)/tau) / D_norm**4.0 + P_perp_hat = P_perp / D_norm**6.0 + P_tor_hat = P_tor / D_norm**6.0 +c --- build the E and F dispersion coefficients + E = (-(Q_hat**2)/(1+1/tau)) - ifac*Q_hat*(P_perp_hat+ + $ P_tor_hat)*(my_q**2) + P_perp_hat*P_tor_hat*(my_q**4) + F = P_perp_hat - ifac*Q_hat + (1+1/tau)*P_tor_hat*my_q**2 + +c --- Riccati ODE: dW/dq = W/q - W^2/q + q*E/F + dW_dq(1)=W(1)/my_q - (W(1)**2)/my_q + (my_q*E)/F + RETURN + END SUBROUTINE w_der_del_s +c----------------------------------------------------------------------- +c riccati_f: compute Delta via Fitzpatrick P_perp / P_tor +c Riccati formulation. +c +c Uses a stiff solver (lsode mf=21) with user-supplied Jacobian +c (jac_f). Boundary conditions are set analytically from the +c large-p asymptotic behaviour; the branch depends on whether +c D_norm^2 exceeds iota_e * P_perp / P_tor^(2/3). +c +c----------------------------------------------------------------------- + FUNCTION riccati_f() + +c --- function result + COMPLEX(r8) :: riccati_f + +c --- lsode solver control + INTEGER :: istep ! integration step counter + INTEGER, PARAMETER :: neq = 2 ! number of equations + INTEGER :: itol,itask ! lsode tolerance/task flags + INTEGER :: istate,iopt,mf ! lsode state/option/method flags + INTEGER, PARAMETER :: liw = 42 ! 20*2 + neq + INTEGER, PARAMETER :: lrw = 44 ! 22 + 9*neq + neq**2 + REAL(r8) :: xout ! target integration endpoint + REAL(r8) :: xmin ! inner integration bound + REAL(r8) :: rtol ! relative tolerance + REAL(r8) :: my_p ! integration variable p (large -> small) + REAL(r8) :: bk ! asymptotic coefficient b_k +c --- boundary-condition intermediates + COMPLEX(r8) :: ak ! asymptotic coefficient a_k + COMPLEX(r8) :: ck ! asymptotic coefficient c_k + COMPLEX(r8) :: xk ! asymptotic coefficient x_k + COMPLEX(r8) :: W_bound ! boundary value for W(p_start) +c --- work arrays (fixed-size, stack-allocated to avoid heap overhead) + COMPLEX(r8) :: W(1), dWdp(1) ! W and dW/dp + REAL(r8) :: atol(neq) ! absolute tolerance + INTEGER :: iwork(liw) ! lsode int work + REAL(r8) :: rwork(lrw) ! lsode real work + +c --- configure lsode: stiff BDF method with user Jacobian (mf=21) + itol = 2 + rtol = 1.0d-10 + atol(:) = 1.0d-10 + itask = 2 + istate = 1 + iopt = 1 ! enable optional inputs (iwork(6)) + mf = 21 ! stiff, user-supplied Jacobian (jac_f) + +c --- set maximum internal steps (only iwork(5:6) matter for lsode) + iwork(:) = 0 + iwork(6) = 50000 ! MXSTEP: max internal steps per call + rwork(:) = 0.0d0 + + xmin=1e-6 + xout=xmin + +c --- compute starting p and W boundary condition +c Branch on asymptotic regime: D_norm^2 vs iota_e*P_perp/P_tor^(2/3) +c --- branch 1: D_norm^2 > iota_e * P_perp / P_tor^(2/3) +c large-D_norm regime: p scales with (P_tor*D_norm^2/(iota_e*...)) + IF (D_norm**2 > iota_e*P_perp / P_tor**(2.0d0/3.0d0)) THEN + my_p = ( (P_tor*D_norm**2)/(iota_e*P_tor*P_perp) )**0.25d0 + my_p = MAX(my_p, 6.0d0) + + ak = -(g_tmp + ifac*Q_e) + bk = (iota_e*P_perp*P_tor)/(P_tor*D_norm**2) + + ck = bk*(1 + (g_tmp+ifac*Q_i) + $ *((P_tor+P_perp)/(P_tor*P_perp)) + $ - (P_perp + (g_tmp+ifac*Q_i)*D_norm**2) + $ *(iota_e/(P_tor*D_norm**2))) + + xk = (ck - SQRT(bk)*(1 - SQRT(bk)*ak)) / (2.0d0*SQRT(bk)) + + W_bound = xk - SQRT(bk)*my_p + ELSE +c --- branch 2: D_norm^2 <= iota_e * P_perp / P_tor^(2/3) +c small-D_norm regime: p scales with 1/P_tor^(1/6) + my_p = 1.0d0 / P_tor**(1.0d0/6.0d0) + my_p = MAX(my_p, 6.0d0) + + ak = -(g_tmp + ifac*Q_e) + bk = P_tor + ck = -ifac*(Q_e - Q_i)*(P_tor/P_perp) + (g_tmp + ifac*Q_i) + xk = (ak*bk - ck)/(2.0d0*SQRT(bk)) + + W_bound = -1.0d0 + xk*my_p - SQRT(bk)*my_p**3 + END IF + + W(1) = W_bound + +c --- integrate W(p) from p_start inward to p_min via lsode + IF (riccati_out) THEN +c profile output: step-by-step integration with file writes + istep = 1 + itask = 2 + OPEN(UNIT=bin_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn_str)//'.bin',STATUS='UNKNOWN', + $ POSITION='REWIND',FORM='UNFORMATTED') + + OPEN(UNIT=out2_unit,FILE='slayer_riccati_profile_n'// + $ TRIM(sn_str)//'.out',STATUS='UNKNOWN') + WRITE(out2_unit,'(1x,3(a17))'),"x","RE(y)","IM(y)" + DO WHILE (my_p>xout) + istep=istep+1 + CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) + WRITE(bin_unit)REAL(my_p,4),REAL(REAL(W),4), + $ REAL(AIMAG(W),4) + WRITE(out2_unit,'(1x,3(es17.8e3))')my_p,REAL(W),AIMAG(W) + ENDDO + CLOSE(bin_unit) + CLOSE(out2_unit) + ELSE +c single-shot integration to p_min + istep = 1 + itask = 1 + CALL lsode(w_der_f,neq,W,my_p,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_f,mf) + ENDIF + +c --- extract Delta from final W derivative at p_min + CALL w_der_f(neq,my_p,W,dWdp) + riccati_f = pi / dWdp(1) +c A failed integration (istate < 0) leaves W short of p_min. Return +c NaN so that scans and the root search skip the point instead of +c using a Delta built from an unconverged W. + IF (istate < 0) riccati_f = CMPLX( + $ ieee_value(1.0_r8, ieee_quiet_nan), + $ ieee_value(1.0_r8, ieee_quiet_nan), KIND=r8) + + END FUNCTION riccati_f +c----------------------------------------------------------------------- +c jacobian for riccati_f(): pd = dF/dW for stiff lsode. +c----------------------------------------------------------------------- + SUBROUTINE jac_f(neq, my_p, W, ml, mu, pd, nrpd) + INTEGER, INTENT(IN) :: neq, ml, mu, nrpd + REAL(r8), INTENT(IN) :: my_p + COMPLEX(r8) :: fA_p, denom + REAL(r8) :: p2 + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(nrpd,neq), INTENT(INOUT) :: pd + + p2 = my_p * my_p + denom = g_tmp + ifac*Q_e + p2 + fA_p = (denom - 2.0d0*p2) / denom + + pd(1,1) = (-fA_p/my_p) - (2.0d0*W(1))/my_p + END SUBROUTINE jac_f +c----------------------------------------------------------------------- +c w_der_f: ODE right-hand side dW/dp for riccati_f. +c Implements the Fitzpatrick P_perp / P_tor dispersion relation. +c Coefficients fA, fB, fC are evaluated at the current p. +c----------------------------------------------------------------------- + SUBROUTINE w_der_f(neq,my_p,W,dWdp) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: my_p + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: W + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dWdp + COMPLEX(r8) :: fA, fA_prime, fB, fC + COMPLEX(r8) :: denom ! cached g_tmp + i*Q_e + p^2 + REAL(r8) :: p2, p4 ! cached p^2, p^4 + REAL(r8) :: D2 ! cached D_norm^2 + +c cache powers and shared denominator + p2 = my_p * my_p + p4 = p2 * p2 + D2 = D_norm * D_norm + denom = g_tmp + ifac*Q_e + p2 + +c evaluate coefficients at the current p + fA = p2 / denom + fA_prime = (denom - 2.0d0*p2) / denom ! (g+iQe - p^2)/(g+iQe + p^2) + + fB = g_tmp*(g_tmp + ifac*Q_i) + $ + (g_tmp + ifac*Q_i)*(P_perp + P_tor)*p2 + $ + (P_perp*P_tor)*p4 + + fC = g_tmp + ifac*Q_e + $ + (P_perp + (g_tmp + ifac*Q_i)*D2)*p2 + $ + (1.0d0/iota_e)*P_tor*D2*p4 + + dWdp(1) = -(fA_prime/my_p)*W(1) - W(1)*W(1)/my_p + $ + (fB/(fA*fC))*(p2*my_p) + + RETURN + END SUBROUTINE w_der_f +c----------------------------------------------------------------------- +c W derivative for riccati() +c----------------------------------------------------------------------- + SUBROUTINE w_der(neq,x,y,dy) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: x + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy + COMPLEX(r8) :: G + COMPLEX(r8) :: C1 + COMPLEX(r8) :: C1p + COMPLEX(r8) :: C2 + COMPLEX(r8) :: C3 + COMPLEX(r8) :: C3p + COMPLEX(r8) :: C2p + COMPLEX(r8) :: A1 + COMPLEX(r8) :: A2 + !COMPLEX(r8), PARAMETER :: ifac=(0,1) + + IF (parflow_flag) THEN + C1=((1 + tau)*x**2*pe* + $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i)))) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i)))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2))/ + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + + C1p=((1 + tau)*x**2*pe* + $ ((2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ **2)/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** + $ 3 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (12*ds**2*pr*(1 + tau)*x**2 + + $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** + $ 2 - (2*(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** + $ 2 - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** + $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((12*ds**2*pr*(1 + tau)*x**2 + + $ 2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + $ + (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2 + $ - (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)**2* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**3 + $ + ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ (2*(2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ ((2*c_beta**2 + 12*ds**2*pr*tau*x**2)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ - ((1 + tau)*x**2*pe* + $ (2*pr*x + 2*x*pe + + $ (ds**2*(1 + tau)*x**6*pe**2* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2))/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + + $ ifac*(Q - Q_e))**2) - + $ (6*ds**2*(1 + tau)*x**5*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e) + $ )) - (ifac*(1 + tau)*x**2*pe* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2)*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ **2 + (2*ifac*(1 + tau)*x*pe* + $ Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ *(-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ ) + (ifac*(1 + tau)*x**2*pe* + $ Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ **2 + (2*(1 + tau)*x*pe* + $ (-(((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ **2) + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))+ + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + + C2=((1 + tau)*x**2*pe* + $ ((ds**2*x**4*pe)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ ) - (ifac*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + + C2p=((1 + tau)*x**2*pe* + $ (-((ds**2*x**4*pe* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2))/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + + $ ifac*(Q - Q_e))**2)) + + $ (4*ds**2*x**3*pe)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e) + $ )) + (ifac* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2)*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ **2 - ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4))* + $ (4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x*(c_beta**2 + ifac*ds**2*(Q - Q_i))))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))** + $ 2 + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (-((4*ds**2*pr*(1 + tau)*x**3 + + $ 2*x* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ /(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2* + $ (c_beta**2 + ifac*ds**2*(Q - Q_i))) + $ )/(ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)**2)) + $ /(ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))) + + $ ((2*c_beta**2*x + 4*ds**2*pr*tau*x**3)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ - ((1 + tau)*x**2*pe* + $ (2*pr*x + 2*x*pe + + $ (ds**2*(1 + tau)*x**6*pe**2* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2))/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + + $ ifac*(Q - Q_e))**2) - + $ (6*ds**2*(1 + tau)*x**5*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e) + $ )) - (ifac*(1 + tau)*x**2*pe* + $ (2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2)*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ **2 + (2*ifac*(1 + tau)*x*pe* + $ Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ *((ds**2*x**4*pe)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e) + $ )) - (ifac*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ ) + (ifac*(1 + tau)*x**2*pe* + $ Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ **2 + (2*(1 + tau)*x*pe* + $ ((ds**2*x**4*pe)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e) + $ )) - (ifac*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + $ + ((ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)* + $ (1 - (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + + $ ifac*ds**2*(Q - Q_i)))/ + $ (ifac*Q + c_beta**2*x**2 + ds**2*pr*tau*x**4)))/ + $ (ds**2*pr*(1 + tau)*x**4 + + $ ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i)))))/ + $ (ifac*Q + pr*x**2 + x**2*pe - + $ (ds**2*(1 + tau)*x**6*pe**2)/ + $ (c_beta**2* + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + $ + (ifac*(1 + tau)*x**2*pe*Q_e)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e))) + ELSE + C1=0 + C1p=0 + C2=0 + C2p=0 + ENDIF + + IF (PeOhmOnly_flag) THEN + G=((c_beta**2*pr*x**4 - Q*(Q - Q_i) + + $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) + ELSE + G=(x**2*pe + + $ (c_beta**2*pr*x**4 - Q*(Q - Q_i) + + $ ifac*(c_beta**2 + pr)*x**2*(Q - Q_i))/ + $ (ds**2*pr*(1 + tau)*x**4 + ifac*(Q - Q_e) + + $ x**2*(c_beta**2 + ifac*ds**2*(Q - Q_i))))*(x**2.0) + ENDIF + + C3=x**2/(x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + ifac*(Q - Q_e)) + + C3p=-((x**2*(2*x + (4*ds**2*(1 + tau)*x**3*pe)/ + $ c_beta**2))/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + (0,1)*(Q - Q_e))**2 + $ ) + (2*x)/ + $ (x**2 + (ds**2*(1 + tau)*x**4*pe)/ + $ c_beta**2 + (0,1)*(Q - Q_e)) + + A1=(C1 + (C3p/C3)*(C2 + 1) + C2p)/(C2 + 1) + + A2=(C1p + C1*(C3p/C3) - G/C3)/(C2 + 1) + + dy(1)=(-A1 + 1/x)*y(1) - y(1)*y(1)/x - A2*x + + RETURN + END SUBROUTINE w_der +c----------------------------------------------------------------------- +c riccati integration with simplified version for test. +c----------------------------------------------------------------------- + SUBROUTINE w_der_temp(neq,x,y,dy) + + INTEGER, INTENT(IN) :: neq + REAL(r8), INTENT(IN) :: x + COMPLEX(r8), DIMENSION(neq), INTENT(IN) :: y + COMPLEX(r8), DIMENSION(neq), INTENT(OUT) :: dy + !COMPLEX(r8), PARAMETER :: ifac=(0,1) + + dy(1)=(2.0*x/(ifac*(Q-Q_e)+x**2.0)-1.0/x)*y(1)-y(1)*y(1)/x + $ +x*(ifac*(Q-Q_e)+x**2.0) + $ *(-Q*(Q-Q_i)+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 + $ +pr*c_beta**2.0*x**4.0)/(ifac*(Q-Q_e) + $ +(c_beta**2.0+ifac*(Q-Q_i)*ds**2.0)*x**2.0 + $ +(1+tau)*pr*ds**2.0*x**4.0) + +c dy(1)=-y(1)/x-y(1)*y(1)/x+x*(ifac*(Q-Q_e)) +c $ *(-Q*(Q-Q_i)+ifac*(Q-Q_i)*(pr+c_beta**2.0)*x**2.0 +c $ +pr*c_beta**2.0*x**4.0)/(ifac*(Q-Q_e) +c $ +(c_beta**2.0+ifac*(Q-Q_i)*ds**2.0)*x**2.0 +c $ +(1+tau)*pr*ds**2.0*x**4.0) + + RETURN + END SUBROUTINE w_der_temp +c----------------------------------------------------------------------- +c calculate delta based on direct phi_der (obsolete). +c----------------------------------------------------------------------- + FUNCTION directred(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) + + REAL(r8),INTENT(IN) :: inQ,inQ_e,inQ_i,inpr,inpe,inc_beta,inds + REAL(r8),INTENT(IN) :: intau + COMPLEX(r8) :: directred + + INTEGER :: istep,neq,itol,itask,istate,liw,lrw,iopt,mf + + REAL(r8) :: xintv,x,xout,rtol,xmax + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: y,dy + + INTEGER, DIMENSION(:), ALLOCATABLE :: iwork + REAL(r8), DIMENSION(:), ALLOCATABLE :: xfac,atol,rwork + + Q=inQ + Q_e=inQ_e + Q_i=inQ_i + pr=inpr + pe=inpe + c_beta=inc_beta + ds=inds + tau=intau + + neq = 4 + itol = 2 + rtol = 1e-3 + ALLOCATE(atol(neq),y(2),dy(2)) + atol(:) = 1e-4 + itask = 1 + istate = 1 + iopt = 0 + mf = 10 + liw = 20 + lrw = 22+16*neq + ALLOCATE(iwork(liw),rwork(lrw)) + + xintv = 0.1 + x=0.1 + istep=1 + xout=x+xintv + xmax=1e3 + + y(1)=1e-10 + y(2)=y(1)/(c_beta/(sqrt(1+tau)*ds)*(1+ifac*(Q-Q_e)*x**2.0)*x) + + DO + istep=istep+1 + CALL lsode(phi_der,neq,y,x,xout,itol,rtol,atol, + $ itask,istate,iopt,rwork,lrw,iwork,liw,jac_dummy,mf) + xout=xout+xintv + IF (xmax-xout Date: Thu, 15 Oct 2026 12:24:27 +0200 Subject: [PATCH 212/305] EQUIL - MINOR - get rid of unused regression folder additions --- regression/spline_tests/.gitignore | 1 - regression/spline_tests/makefile | 13 ------- .../spline_tests/test_spline_hermite.f90 | 38 ------------------- 3 files changed, 52 deletions(-) delete mode 100644 regression/spline_tests/.gitignore delete mode 100644 regression/spline_tests/makefile delete mode 100644 regression/spline_tests/test_spline_hermite.f90 diff --git a/regression/spline_tests/.gitignore b/regression/spline_tests/.gitignore deleted file mode 100644 index ca5a5db51..000000000 --- a/regression/spline_tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_spline_hermite diff --git a/regression/spline_tests/makefile b/regression/spline_tests/makefile deleted file mode 100644 index 2582f4ace..000000000 --- a/regression/spline_tests/makefile +++ /dev/null @@ -1,13 +0,0 @@ -# Unit tests for equil/spline.f. Build GPEC first (install/make), then: make run -FC ?= gfortran -GPEC = ../.. -LIBS ?= -L$(LAPACKHOME)/lib -llapack -lblas - -test_spline_hermite: test_spline_hermite.f90 $(GPEC)/deps/lib/libequil.a - $(FC) $(FFLAGS) -I$(GPEC)/equil -o $@ $< $(GPEC)/deps/lib/libequil.a $(LIBS) - -run: test_spline_hermite - ./test_spline_hermite - -clean: - rm -f test_spline_hermite *.o *.mod diff --git a/regression/spline_tests/test_spline_hermite.f90 b/regression/spline_tests/test_spline_hermite.f90 deleted file mode 100644 index d57f1d415..000000000 --- a/regression/spline_tests/test_spline_hermite.f90 +++ /dev/null @@ -1,38 +0,0 @@ -!----------------------------------------------------------------------- -! Unit test for spline_fit_hermite: flagged columns keep the supplied -! knot derivatives; other columns are those of spline_fit. -! Exits with status 1 on failure. -!----------------------------------------------------------------------- -PROGRAM test_spline_hermite - USE spline_mod - IMPLICIT NONE - - INTEGER, PARAMETER :: mx=32 - INTEGER :: ix - REAL(r8), DIMENSION(0:mx) :: slopes - REAL(r8), DIMENSION(0:mx,2) :: fs1_ref - TYPE(spline_type) :: spl - LOGICAL :: ok=.TRUE. - - CALL spline_alloc(spl,mx,2) - spl%xs=(/(ix,ix=0,mx)/)/REAL(mx,r8) - spl%fs(:,1)=SIN(3*spl%xs) - spl%fs(:,2)=spl%fs(:,1) - CALL spline_fit(spl,"extrap") - fs1_ref=spl%fs1 - slopes=3*COS(3*spl%xs) - spl%fs1(:,1)=slopes - CALL spline_fit_hermite(spl,"extrap",(/.TRUE.,.FALSE./)) - IF(ANY(spl%fs1(:,1) /= slopes))THEN - WRITE(*,*)"FAIL: flagged column lost the supplied derivatives" - ok=.FALSE. - ENDIF - IF(ANY(spl%fs1(:,2) /= fs1_ref(:,2)))THEN - WRITE(*,*)"FAIL: column not flagged differs from spline_fit" - ok=.FALSE. - ENDIF - CALL spline_dealloc(spl) - IF(.NOT.ok)STOP 1 - WRITE(*,*)"PASS" - -END PROGRAM test_spline_hermite From a630b4855228ab369a41ab6c554d49053259f5dc Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 12:25:04 +0200 Subject: [PATCH 213/305] RDCON - BUGFIX - make sure psi_N_Zeff is strictly increasing --- rdcon/dcon.f | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rdcon/dcon.f b/rdcon/dcon.f index 77d635e26..c4c9726bf 100644 --- a/rdcon/dcon.f +++ b/rdcon/dcon.f @@ -363,6 +363,8 @@ SUBROUTINE dcon_run IF(.NOT.ALLOCATED(Zeff_) .OR. SIZE(Zeff_) /= SIZE(psi_N_Zeff_) $ .OR. SIZE(Zeff_) < 4) $ CALL program_stop("Zeff, psi_N_Zeff: equal sizes, >= 4") + IF(ANY(psi_N_Zeff_(2:) <= psi_N_Zeff_(:SIZE(psi_N_Zeff_)-1))) + $ CALL program_stop("psi_N_Zeff must be strictly increasing") CALL spline_alloc(mreterms,mpsi,30) mreterms%xs=sq%xs mreterms%fs=0 From 8dc2d0daf56ae0d2f467a2872f638e54fde8852e Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 14:31:38 +0200 Subject: [PATCH 214/305] EQUIL - MINOR - check sign of i-file psi --- equil/read_eq.f | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/equil/read_eq.f b/equil/read_eq.f index fddc3144d..7158aee63 100644 --- a/equil/read_eq.f +++ b/equil/read_eq.f @@ -876,8 +876,11 @@ SUBROUTINE read_eq_ldp_i IF(ios3 == 0)THEN psio=psib(2)-psib(1) sq_in%xs=(psi-psib(1))/psio - IF(.NOT.(sq_in%xs(mx) > 0 .AND. sq_in%xs(mx) <= 1)) - $ CALL program_stop("ldp_i psi bounds disagree with psi") + IF(.NOT.(MINVAL(sq_in%xs) >= 0 .AND. sq_in%xs(mx) <= 1))THEN + WRITE(*,'(1x,a,1p,4es14.6)')"psi_axis, psi_lcfs, psi(0), " + $ //"psi(mx) =",psib,psi(0),psi(mx) + CALL program_stop("ldp_i psi bounds disagree with psi") + ENDIF IF(verbose)WRITE(*,'(1x,a,1p,2es14.6)') $ "ldp_i psi bounds: psi_axis, psi_lcfs =",psib IF(psihigh > sq_in%xs(mx))THEN From 6262fd656b9d9916f1f6323b420cda48df3dea27 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 15:07:05 +0200 Subject: [PATCH 215/305] RMATCH - BUGFIX - rename msing variable getting overwritten due to name collision --- rmatch/match.f | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rmatch/match.f b/rmatch/match.f index 48b7fe8ca..fa0854154 100644 --- a/rmatch/match.f +++ b/rmatch/match.f @@ -585,7 +585,7 @@ END FUNCTION match_delta SUBROUTINE match_solution(eig) COMPLEX(r8), INTENT(IN) :: eig - INTEGER :: galnsol,msing,tot_grids,isol,mhigh,mlow,ip,eff_grids + INTEGER :: galnsol,galmsing,tot_grids,isol,mhigh,mlow,ip,eff_grids INTEGER :: ipert,mpert,nn,ising,tot_g,jsing CHARACTER(100) :: comp_tittle,tmp REAL(r8),PARAMETER :: eps=1e-4 @@ -616,16 +616,16 @@ SUBROUTINE match_solution(eig) c read outer region solutions. c----------------------------------------------------------------------- CALL bin_open(bin_unit,galsol_filename,"OLD","REWIND","none") - READ(bin_unit) galnsol,msing,mpert,mhigh,mlow,nn,psio,tot_grids + READ(bin_unit) galnsol,galmsing,mpert,mhigh,mlow,nn,psio,tot_grids ALLOCATE(psi(0:tot_grids),issing(0:tot_grids),q(0:tot_grids)) - ALLOCATE(qpsifac(msing),qsing(msing)) + ALLOCATE(qpsifac(galmsing),qsing(galmsing)) ALLOCATE(singfac(mpert),outsol(mpert,0:tot_grids,galnsol), $ outtotsol(mpert,0:tot_grids)) ALLOCATE(outsol_cut(mpert,0:tot_grids,galnsol), $ outtotsol_cut(mpert,0:tot_grids), $ tmp_cut(mpert,0:tot_grids),psi_cut(0:tot_grids)) - ALLOCATE(xext(msing,2)) - DO isol=1,msing + ALLOCATE(xext(galmsing,2)) + DO isol=1,galmsing READ(bin_unit) qpsifac(isol),qsing(isol) ENDDO READ(bin_unit) psi,issing,q From b040293236369fee911e000af052ef09c4a572db Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 15:15:08 +0200 Subject: [PATCH 216/305] EXAMPLES - MINOR - change psihigh to usual value to preserve having 4 surfaces in d3d resistive example --- docs/examples/DIIID_resistive_example/equil.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/DIIID_resistive_example/equil.in b/docs/examples/DIIID_resistive_example/equil.in index 49662f125..fa8329ec2 100644 --- a/docs/examples/DIIID_resistive_example/equil.in +++ b/docs/examples/DIIID_resistive_example/equil.in @@ -9,7 +9,7 @@ grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. psilow=1e-4 ! Minimum value of psi, normalized from 0 to 1 - psihigh=0.995 ! Maximum value of psi, normalized from 0 to 1 + psihigh=0.993 ! Maximum value of psi, normalized from 0 to 1 mpsi=128 ! Number of radial grid intervals for equilibrium quantities mtheta=256 ! Number of equally spaced poloidal grid intervals for all splines newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. From ab45209ba4ca4353ebb3225ababee95619adb3ea Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 15:28:30 +0200 Subject: [PATCH 217/305] RDCON - MINOR - add flag that catches if you try to run cholesky + rpec, which are incompatible. --- rdcon/gal.f | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rdcon/gal.f b/rdcon/gal.f index 2cddfde91..48d384945 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -154,6 +154,10 @@ SUBROUTINE gal_alloc(gal,nx,nq,dx0,dx1,dx2,pfac) c resistive perturbed equilibrium solution. c----------------------------------------------------------------------- IF (coil%rpec_flag ) THEN + IF (solver == "cholesky") THEN + WRITE(*,*)"WARNING: rpec_flag needs solver='LU'; switching." + solver="LU" + ENDIF coil%mcoil=mpert coil%m1=2*msing+1 coil%m2=2*msing+coil%mcoil From c62d84e67a151bfd679abb290cf6913c73c03be7 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Thu, 15 Oct 2026 16:53:15 +0200 Subject: [PATCH 218/305] GPEC - IMPROVEMENT - Added correct units and consistent descriptions of singular coupling output quantities --- docs/outputs.rst | 17 ++++++- docs/references.rst | 9 ++++ gpec/gpout.f | 116 ++++++++++++++++++++++++++++---------------- 3 files changed, 99 insertions(+), 43 deletions(-) diff --git a/docs/outputs.rst b/docs/outputs.rst index 22bce6bf7..f9001574f 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -72,6 +72,21 @@ Interfacing with VACUUM Vacuum is the code used in DCON to calculate the vacuum energy matrices, which combine with the plasma energy matrix to describe the full system eigenmodes. The VACUUM code uses CCW :math:`\phi` and downward outboard :math:`\theta`. We thus use the complex conjugate of RH configurations to interface GPEC and VACUUM. +.. _coupling_overlap: + +Resonant Coupling and Dominant Mode Overlap +=========================================== + +Coupling matrices (singcoup_flag) relate area-weighted resonant quantities to the root-area-weighted vacuum field, making their SVD coordinate invariant (Park 2008, Pharr 2026): + +.. math:: + + \bar{b}^r_i = C_{im} \tilde{b}^x_m, \qquad + \tilde{b}^x_m = \frac{1}{\sqrt{A^x}} \oint d\theta\, d\phi \, \delta\mathbf{B}^x\cdot\hat{n} \, \sqrt{\mathcal{J}|\nabla\psi|} \, e^{i(n\phi - m\theta)}. + +Singular values carry the units of each C; right-singular vectors :math:`\mathbf{v}_j` are unitless. + + .. _inputs: Inputs @@ -157,7 +172,7 @@ GPEC_singfld_n#.out **Flag** singcoup_flag - **Info** Additional section showing the overlap field and overlap percentage for each eigenmode in the singcoup_svd output. + **Info** Additional section showing the overlap field (Tesla) and overlap percentage for each eigenmode in the singcoup_svd output. See :ref:`coupling_overlap` for definitions. GPEC_pmod_n#.out diff --git a/docs/references.rst b/docs/references.rst index c12b56188..dc0711dd3 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -30,6 +30,15 @@ Ideal `Park, J.-K., Boozer, A.H. & Glasser, A.H., 2007. Computation of three-dimensional tokamak and spherical torus equilibria. Physics of Plasmas, 14(5), p.052110 `_. +Resonant Coupling +------------------ + +`Park, J.-K., Boozer, A.H., Menard, J.E. & Schaffer, M.J., 2008. Error field correction in ITER. Nuclear Fusion, 48(4), p.045006 `_. + +`Park, J.-K., Boozer, A.H. & Menard, J.E., 2008. Spectral asymmetry due to magnetic coordinates. Physics of Plasmas, 15(6), p.064501 `_. + +`Pharr, M., Bursch, E.M., Logan, N.C., Lunia, P., Park, J.-K. & Paz-Soldan, C., 2026. Coordinate-invariant flux-surface Fourier analysis in tokamaks. Physics of Plasmas, 33(9), p.092503 `_. + Kinetic -------- diff --git a/gpec/gpout.f b/gpec/gpout.f index 0f17d16b4..baee0d5d3 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -1556,7 +1556,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, $ a_id,pp_id,cp_id,wp_id,np_id,dp_id,dd_id,pr_id,wc_id, $ bc_id,ti_id,te_id,ni_id,ne_id,we_id,wi_id,q1_id,rh_id, $ r1_id,ssp_id,lq_id,rt_id,at_id,wmin_id,wsat_id,pm_id, - $ ps_id,pcc_id,astat + $ ps_id,pcc_id,dm_id,astat INTEGER :: itheta,ising,icoup REAL(r8) :: respsi,lpsi,rpsi,shear,hdist,sbnosurf @@ -1569,6 +1569,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, REAL(r8), DIMENSION(msing) :: hw_isl,hw_v, $ chirikov,hw_v_crit,hw_sat,hw_min REAL(r8), DIMENSION(nsingcoup,msing) :: op + REAL(r8), DIMENSION(msing) :: dmo COMPLEX(r8), DIMENSION(msing) :: delta,delcur,singcur, $ singflx,singbwp COMPLEX(r8), DIMENSION(nsingcoup, msing) :: olap @@ -2219,6 +2220,9 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, ENDDO ENDDO op = ABS(olap) / sbnosurf * 1e2 + ! dominant mode overlap (little delta) + dmo = 0.0_r8 + IF (ABS(bt0) > 0.0_r8) dmo = ABS(olap(1,:)) / ABS(bt0) IF(ascii_flag)THEN WRITE(out_unit,*)"Overlap fields, overlap singular "// @@ -2250,52 +2254,62 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, $ (/m_id,i_id/), p_id) ) CALL check( nf90_put_att(mncid, p_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, p_id, "long_name", - $ "Pitch resonant flux overlap") ) + $ "Root-area weighted vacuum field overlap with resonant "// + $ "flux coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "Phi_overlap_norm", $ nf90_double,(/m_id/), pp_id) ) - CALL check( nf90_put_att(mncid, pp_id, "units", "unitless")) + CALL check( nf90_put_att(mncid, pp_id, "units", "%")) CALL check( nf90_put_att(mncid, pp_id, "long_name", - $ "Pitch resonant flux overlap percentage") ) + $ "Normalized resonant flux overlap") ) CALL check( nf90_def_var(mncid, "I_overlap", nf90_double, $ (/m_id,i_id/), c_id) ) - CALL check( nf90_put_att(mncid, c_id, "units", "Amps") ) + CALL check( nf90_put_att(mncid, c_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, c_id, "long_name", - $ "Pitch resonant current overlap") ) + $ "Root-area weighted vacuum field overlap with resonant "// + $ "current coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "I_overlap_norm", - $ nf90_double,(/m_id,i_id/), cp_id) ) - CALL check( nf90_put_att(mncid, cp_id, "units", "unitless")) + $ nf90_double,(/m_id/), cp_id) ) + CALL check( nf90_put_att(mncid, cp_id, "units", "%")) CALL check( nf90_put_att(mncid, cp_id, "long_name", - $ "Pitch resonant current overlap percentage") ) + $ "Normalized resonant current overlap") ) CALL check( nf90_def_var(mncid, "w_overlap", nf90_double, $ (/m_id,i_id/), w_id) ) - CALL check( nf90_put_att(mncid, w_id, "units", "untiless") ) + CALL check( nf90_put_att(mncid, w_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, w_id, "long_name", - $ "Full width of saturated island overlap") ) + $ "Root-area weighted vacuum field overlap with island "// + $ "half-width squared coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "w_overlap_norm", $ nf90_double,(/m_id/), wp_id) ) - CALL check( nf90_put_att(mncid, wp_id, "units", "untiless")) + CALL check( nf90_put_att(mncid, wp_id, "units", "%")) CALL check( nf90_put_att(mncid, wp_id, "long_name", - $ "Full width of saturated island overlap percentage") ) + $ "Normalized resonant island half-width squared overlap") ) CALL check( nf90_def_var(mncid, "Pen_overlap", nf90_double, $ (/m_id, i_id/), n_id) ) - CALL check( nf90_put_att(mncid, n_id, "units", "untiless") ) + CALL check( nf90_put_att(mncid, n_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, n_id, "long_name", - $ "Penetrated flux overlap") ) + $ "Root-area weighted vacuum field overlap with "// + $ "penetrated flux coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "Pen_overlap_norm", $ nf90_double,(/m_id/), np_id) ) - CALL check( nf90_put_att(mncid, np_id, "units", "untiless")) + CALL check( nf90_put_att(mncid, np_id, "units", "%")) CALL check( nf90_put_att(mncid, np_id, "long_name", - $ "Penetrated flux overlap percentage") ) + $ "Normalized resonant penetrated flux overlap") ) CALL check( nf90_def_var(mncid, "Delta_overlap",nf90_double, $ (/m_id, i_id/), d_id) ) - CALL check( nf90_put_att(mncid, d_id, "units", "untiless") ) + CALL check( nf90_put_att(mncid, d_id, "units", "Tesla") ) CALL check( nf90_put_att(mncid, d_id, "long_name", - $ "External Delta prime overlap") ) + $ "Root-area weighted vacuum field overlap with "// + $ "Delta_mn coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "Delta_overlap_norm", $ nf90_double,(/m_id/), dd_id) ) - CALL check( nf90_put_att(mncid, dd_id, "units", "untiless")) + CALL check( nf90_put_att(mncid, dd_id, "units", "%")) CALL check( nf90_put_att(mncid, dd_id, "long_name", - $ "External Delta prime overlap percentage") ) + $ "Normalized resonant Delta_mn overlap") ) + CALL check( nf90_def_var(mncid, "dominant_mode_overlap", + $ nf90_double,(/m_id/), dm_id) ) + CALL check( nf90_put_att(mncid, dm_id, "units", "unitless")) + CALL check( nf90_put_att(mncid, dm_id, "long_name", + $ "Dominant mode overlap delta = |Phi_overlap|/B_T") ) CALL check( nf90_enddef(mncid) ) CALL check( nf90_put_var(mncid, p_id, RESHAPE((/ $ REAL(olap(1,:)),AIMAG(olap(1,:))/), (/msing,2/))) ) @@ -2312,6 +2326,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, CALL check( nf90_put_var(mncid, wp_id, op(3,:)) ) CALL check( nf90_put_var(mncid, np_id, op(4,:)) ) CALL check( nf90_put_var(mncid, dd_id, op(5,:)) ) + CALL check( nf90_put_var(mncid, dm_id, dmo) ) CALL check( nf90_close(mncid) ) ENDIF @@ -2326,6 +2341,8 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, ENDDO ENDDO op = ABS(olap) / sbnosurf * 1e2 + dmo = 0.0_r8 + IF (ABS(bt0) > 0.0_r8) dmo = ABS(olap(1,:)) / ABS(bt0) IF(ascii_flag)THEN WRITE(out_unit,*)"Local overlap fields, overlap "// @@ -2333,8 +2350,8 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, WRITE(out_unit,*) WRITE(out_unit,'(1x,a6,15(1x,a16))') "mode", $ "real(ovf)","imag(ovf)","overlap(%)", ! Resonant flux RSV overlap - $ "real(ovs)","imag(ovs)","overlap(%)", ! Resonant current RSV overlap - $ "real(ovi)","imag(ovi)","overlap(%)", ! Island width RSV overlap + $ "real(ovi)","imag(ovi)","overlap(%)", ! Resonant current RSV overlap + $ "real(ovw)","imag(ovw)","overlap(%)", ! Island width RSV overlap $ "real(ovp)","imag(ovp)","overlap(%)", ! Penetrated flux RSV overlap $ "real(ovd)","imag(ovd)","overlap(%)" ! Unitless Delta RSV overlap DO ising=1,osing @@ -2357,52 +2374,66 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, $ nf90_double,(/m_id,i_id/), p_id) ) CALL check( nf90_put_att(mncid, p_id,"units","Tesla")) CALL check( nf90_put_att(mncid, p_id, "long_name", - $ "Local pitch resonant flux overlap") ) + $ "Root-area weighted vacuum field overlap with local "// + $ "resonant flux coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "Phi_local_overlap_norm", $ nf90_double,(/m_id/), pp_id) ) - CALL check( nf90_put_att(mncid,pp_id,"units","unitless")) + CALL check( nf90_put_att(mncid,pp_id,"units","%")) CALL check( nf90_put_att(mncid, pp_id, "long_name", - $ "Local pitch resonant flux overlap percentage") ) + $ "Normalized local resonant flux overlap") ) CALL check( nf90_def_var(mncid, "I_local_overlap", $ nf90_double,(/m_id,i_id/), c_id) ) - CALL check( nf90_put_att(mncid, c_id,"units","Amps")) + CALL check( nf90_put_att(mncid, c_id,"units","Tesla")) CALL check( nf90_put_att(mncid, c_id, "long_name", - $ "Local pitch resonant current overlap") ) + $ "Root-area weighted vacuum field overlap with local "// + $ "resonant current coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "I_local_overlap_norm", $ nf90_double,(/m_id/), cp_id) ) - CALL check( nf90_put_att(mncid,cp_id,"units","unitless")) + CALL check( nf90_put_att(mncid,cp_id,"units","%")) CALL check( nf90_put_att(mncid, cp_id, "long_name", - $ "Local pitch resonant current overlap percentage") ) + $ "Normalized local resonant current overlap") ) CALL check( nf90_def_var(mncid, "w_local_overlap", $ nf90_double,(/m_id,i_id/), w_id) ) - CALL check( nf90_put_att(mncid, w_id,"units","untiless")) + CALL check( nf90_put_att(mncid, w_id,"units","Tesla")) CALL check( nf90_put_att(mncid, w_id, "long_name", - $ "Local full width of saturated island overlap") ) + $ "Root-area weighted vacuum field overlap with local "// + $ "island half-width squared coupling right-singular "// + $ "vectors") ) CALL check( nf90_def_var(mncid, "w_local_overlap_norm", $ nf90_double,(/m_id/), wp_id) ) - CALL check( nf90_put_att(mncid,wp_id,"units","untiless")) + CALL check( nf90_put_att(mncid,wp_id,"units","%")) CALL check( nf90_put_att(mncid, wp_id, "long_name", - $ "Local full width of saturated island overlap percentage") ) + $ "Normalized local resonant island half-width "// + $ "squared overlap") ) CALL check( nf90_def_var(mncid, "Pen_local_overlap", $ nf90_double,(/m_id, i_id/), n_id) ) - CALL check( nf90_put_att(mncid, n_id,"units","untiless")) + CALL check( nf90_put_att(mncid, n_id,"units","Tesla")) CALL check( nf90_put_att(mncid, n_id, "long_name", - $ "Local penetrated flux overlap") ) + $ "Root-area weighted vacuum field overlap with local "// + $ "penetrated flux coupling right-singular vectors") ) CALL check( nf90_def_var(mncid, "Pen_local_overlap_norm", $ nf90_double,(/m_id/), np_id) ) - CALL check( nf90_put_att(mncid,np_id,"units","untiless")) + CALL check( nf90_put_att(mncid,np_id,"units","%")) CALL check( nf90_put_att(mncid, np_id, "long_name", - $ "Local penetrated flux overlap percentage") ) + $ "Normalized local resonant penetrated flux overlap") ) CALL check( nf90_def_var(mncid, "Delta_local_overlap", $ nf90_double,(/m_id, i_id/), d_id) ) - CALL check( nf90_put_att(mncid, d_id,"units","untiless")) + CALL check( nf90_put_att(mncid, d_id,"units","Tesla")) CALL check( nf90_put_att(mncid, d_id, "long_name", - $ "Local extrenal Delta prime overlap") ) + $ "Root-area weighted vacuum field overlap with local "// + $ "Delta_mn coupling right-singular vectors") ) CALL check( nf90_def_var(mncid,"Delta_local_overlap_norm", $ nf90_double,(/m_id/), dp_id) ) - CALL check( nf90_put_att(mncid,dp_id,"units","untiless")) + CALL check( nf90_put_att(mncid,dp_id,"units","%")) CALL check( nf90_put_att(mncid, dp_id, "long_name", - $ "Local extrenal Delta prime overlap percentage") ) + $ "Normalized local resonant Delta_mn overlap") ) + CALL check( nf90_def_var(mncid, + $ "dominant_mode_local_overlap", + $ nf90_double,(/m_id/), dm_id) ) + CALL check( nf90_put_att(mncid,dm_id,"units","unitless")) + CALL check( nf90_put_att(mncid, dm_id, "long_name", + $ "Local dominant mode overlap delta = "// + $ "|Phi_local_overlap|/B_T") ) CALL check( nf90_enddef(mncid) ) CALL check( nf90_put_var(mncid, p_id, RESHAPE((/ $ REAL(olap(1,:osing)),AIMAG(olap(1,:osing))/), @@ -2424,6 +2455,7 @@ SUBROUTINE gpout_singfld(egnum,xspmn,spots,interpspot,nspot, CALL check( nf90_put_var(mncid, wp_id, op(3,1:osing)) ) CALL check( nf90_put_var(mncid, np_id, op(4,1:osing)) ) CALL check( nf90_put_var(mncid, dp_id, op(5,1:osing)) ) + CALL check( nf90_put_var(mncid, dm_id, dmo(1:osing)) ) CALL check( nf90_close(mncid) ) ENDIF From c27437c36dc6c6e97e753021661104a3c9a20455 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 15 Oct 2026 14:05:27 -0400 Subject: [PATCH 219/305] GPEC - recon: add input guards and rename flags to effective_field/standard_form --- docs/tex/recon/main.tex | 18 ++++---- gpec/gpec.f | 37 ++++++++++++----- gpec/gpeq.f | 91 +++++++++++++++++++++-------------------- gpec/gpglobal.F | 3 +- gpec/gpout.f | 25 +++++------ input/gpec.in | 12 +++--- input/match.in | 2 +- 7 files changed, 103 insertions(+), 85 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 80d812db5..fa36a55b9 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -15,7 +15,7 @@ \usepackage{pdflscape} \numberwithin{equation}{section} -\title{DCON \texttt{recon\_flag} document} +\title{DCON \texttt{effective\_field\_flag} document} \author{Sunjae Lee} \date{\today} @@ -2205,9 +2205,9 @@ \section{Log-output quantities and their exact integrands} This section gives the precise mathematical definition of every scalar printed to the terminal and to \texttt{gpec.log} by the three reconstruction diagnostics. The three blocks correspond to the input flags -\texttt{recon\_flag} (\texttt{GPEC\_RECON}, driver \texttt{gpout\_recon}), -\texttt{recon\_flag2} (\texttt{GPEC\_RECON2}, driver \texttt{gpout\_recon2} -$\to$ \texttt{gpeq\_firstform}), and \texttt{recon\_flag3} +\texttt{effective\_field\_flag} (\texttt{GPEC\_RECON}, driver \texttt{gpout\_recon}), +\texttt{standard\_form\_flag} (\texttt{GPEC\_RECON2}, driver \texttt{gpout\_recon2} +$\to$ \texttt{gpeq\_firstform}), and \texttt{effective\_field\_terms\_flag} (\texttt{GPEC\_RECON3}, driver \texttt{gpout\_recon3} $\to$ \texttt{gpeq\_recon3}). @@ -2225,9 +2225,9 @@ \section{Log-output quantities and their exact integrands} $\delta W_p$ total reported below is built with this scheme, so the choice affects the reported numbers at the level of the quadrature error. -\item \texttt{cveri\_flag} (default \texttt{.FALSE.}) enables the +\item \texttt{curl\_check\_flag} (default \texttt{.FALSE.}) enables the $(\nabla\times\vec C)\cdot\nabla\psi=0$ verification of - \S\ref{par:cveri} alongside \texttt{recon\_flag}. The pointwise + \S\ref{par:cveri} alongside \texttt{effective\_field\_flag}. The pointwise residual is accumulated over all surfaces and reported as a status of \texttt{PASS}, \texttt{WARN} or \texttt{SKIP} against a fixed tolerance of $10^{-10}$, together with its maximum and rms. @@ -2242,7 +2242,7 @@ \section{Log-output quantities and their exact integrands} \chi'=2\pi\psi_o . \end{equation} -\subsection{\texttt{GPEC\_RECON} (\texttt{recon\_flag})} +\subsection{\texttt{GPEC\_RECON} (\texttt{effective\_field\_flag})} This route reconstructs the closed Bernstein form \eqref{eq:ep_bernsteinK}, \begin{equation} @@ -2313,7 +2313,7 @@ \subsection{\texttt{GPEC\_RECON} (\texttt{recon\_flag})} (\partial_\theta C_\zeta-\partial_\zeta C_\theta)$, which must vanish to \texttt{tol}. -\subsection{\texttt{GPEC\_RECON2} (\texttt{recon\_flag2})} +\subsection{\texttt{GPEC\_RECON2} (\texttt{standard\_form\_flag})} This route reconstructs the original ``first form'' of $\delta W_p$, Eq.~\eqref{eq:ep_first}, \begin{equation} @@ -2380,7 +2380,7 @@ \subsection{\texttt{GPEC\_RECON2} (\texttt{recon\_flag2})} Each line is printed as a complex pair (real, imaginary); the imaginary part is a roundoff diagnostic and should vanish. -\subsection{\texttt{GPEC\_RECON3} (\texttt{recon\_flag3})} +\subsection{\texttt{GPEC\_RECON3} (\texttt{effective\_field\_terms\_flag})} This route keeps the same closed form as recon1, Eq.~\eqref{eq:dWp_recon1}, but expands the cross-energy density $|\vec C|^2$ term by term using diff --git a/gpec/gpec.f b/gpec/gpec.f index 28a8ec89f..3a8cc5cfc 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -39,7 +39,8 @@ PROGRAM gpec_main $ arbsurf_flag,angles_flag,surfmode_flag,rzpgrid_flag, $ singcurs_flag,m3d_flag,cas3d_flag,test_flag,nrzeq_flag, $ arzphifun_flag,xbrzphifun_flag,pmodbmn_flag,xclebsch_flag, - $ filter_flag,gal_flag,delpsi_flag,recon_flag,use_res_spot, + $ filter_flag,gal_flag,delpsi_flag,effective_field_flag, + $ use_res_spot, $ singthresh_callen_flag,singthresh_slayer_flag,singthresh_flag LOGICAL, DIMENSION(100) :: ss_flag COMPLEX(r8), DIMENSION(:), POINTER :: finmn,foutmn,xspmn, @@ -74,8 +75,8 @@ PROGRAM gpec_main $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, $ singthresh_slayer_inpr,singthresh_slayer_inpr_prof, - $ recon_flag,recon_int,cveri_flag,recon_out, - $ recon_flag2,recon_flag3 + $ effective_field_flag,recon_int,curl_check_flag,recon_out, + $ standard_form_flag,effective_field_terms_flag NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -201,11 +202,11 @@ PROGRAM gpec_main test_flag=.FALSE. eigm_flag=.FALSE. mutual_test_flag=.FALSE. - recon_flag=.FALSE. - recon_flag2=.FALSE. - recon_flag3=.FALSE. + effective_field_flag=.FALSE. + standard_form_flag=.FALSE. + effective_field_terms_flag=.FALSE. recon_int="spline" - cveri_flag=.FALSE. + curl_check_flag=.FALSE. recon_out=.TRUE. majr=10.0 @@ -243,6 +244,22 @@ PROGRAM gpec_main CALL gpec_stop("gpec_output recon_int must be spline or "// $ "trapezoid") END SELECT + ! recon diagnostics compare against ep(mode), so they need the + ! bare, unregularized DCON eigenmode selected by mode_flag. + IF (effective_field_flag .OR. standard_form_flag .OR. + $ effective_field_terms_flag) THEN + IF (.NOT. mode_flag .OR. reg_flag .OR. coil_flag .OR. + $ data_flag .OR. harmonic_flag) THEN + CALL gpec_stop("effective_field_flag/standard_form_flag/"// + $ "effective_field_terms_flag "// + $ "require mode_flag=t, reg_flag=f, and "// + $ "coil_flag=data_flag=harmonic_flag=f") + ENDIF + ENDIF + IF (curl_check_flag .AND. .NOT. effective_field_flag) THEN + PRINT *,"!! WARNING: curl_check_flag has no effect without "// + $ "effective_field_flag" + ENDIF galsol%gal_flag=gal_flag IF(timeit) CALL gpec_timer(0) c----------------------------------------------------------------------- @@ -633,13 +650,13 @@ PROGRAM gpec_main IF (arzphifun_flag) THEN CALL gpout_arzphifun(mode,xspmn) ENDIF - IF (recon_flag) THEN + IF (effective_field_flag) THEN CALL gpout_recon(mode,xspmn) ENDIF - IF (recon_flag2) THEN + IF (standard_form_flag) THEN CALL gpout_recon2(mode,xspmn) ENDIF - IF (recon_flag3) THEN + IF (effective_field_terms_flag) THEN CALL gpout_recon3(mode,xspmn) ENDIF c----------------------------------------------------------------------- diff --git a/gpec/gpeq.f b/gpec/gpeq.f index c6bd1395a..fb6b44fc6 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -46,6 +46,9 @@ MODULE gpeq_mod USE idcon_mod IMPLICIT NONE +c floor for recon divisors (rfac, |grad psi|^2, B^2) so the +c recon kernels stay finite if called on the magnetic axis. + REAL(r8), PARAMETER, PRIVATE :: recon_tiny = 1.0e-30_r8 CONTAINS c----------------------------------------------------------------------- @@ -692,7 +695,7 @@ SUBROUTINE gpeq_c(psi, ipsi) INTEGER :: itheta, ipert, jpert, m1, dm REAL(r8) :: q, f1raw, p1, chi1, jac - REAL(r8) :: eta, rfac, v21, v22, v23, v33 + REAL(r8) :: eta, rfac, v21, v22, v23, v33, dpsq REAL(r8), DIMENSION(0:mthsurf) :: jacs, r_vec COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun @@ -741,7 +744,7 @@ SUBROUTINE gpeq_c(psi, ipsi) DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) jac = rzphi%f(4) - rfac = SQRT(rzphi%f(1)) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) eta = twopi*(theta(itheta) + rzphi%f(2)) r_vec(itheta) = ro + rfac*COS(eta) jacs(itheta) = jac @@ -791,6 +794,7 @@ SUBROUTINE gpeq_c(psi, ipsi) jac = jacs(itheta) jwt = - f1raw / jac jwz = - p1 * mu0 / chi1 - f1raw * q / jac + dpsq = MAX(delpsi(itheta)**2, recon_tiny) c J C^psi = J Q^psi cwp_fun(itheta)= bwp_fun(itheta) @@ -799,20 +803,20 @@ SUBROUTINE gpeq_c(psi, ipsi) c * [mu0 j^theta g_theta_zeta + mu0 j^zeta g_zeta_zeta] c = J Q^theta + xwp_fun/(J |grad psi|^2) * [...] cwt_fun(itheta) = bmt_fun(itheta) + xwp_fun(itheta) - $ / ((delpsi(itheta)**2) * jac )* + $ / (dpsq * jac )* $ (jwt * g_23(itheta) + jwz * g_33(itheta)) c J C^zeta = J Q^zeta - xi^psi/|grad psi|^2 c * [mu0 j^theta g_theta_theta + mu0 j^zeta g_theta_zeta] c = J Q^zeta - xwp_fun/(J |grad psi|^2) * [...] cwz_fun(itheta) = bmz_fun(itheta) - xwp_fun(itheta) - $ / ((delpsi(itheta)**2) * jac )* + $ / (dpsq * jac )* $ (jwt * g_22(itheta) + jwz * g_23(itheta)) c C_psi = Q_psi + (J xi^psi)/|grad psi|^2 c * [mu0 j^theta (grad psi.grad zeta) - mu0 j^zeta (grad psi.grad theta)] cvp_fun(itheta) = bvp_fun(itheta) + - $ xwp_fun(itheta) / (delpsi(itheta)**2) * + $ xwp_fun(itheta) / dpsq * $ (jwt * dpdz(itheta) - jwz * dpdt(itheta)) c C_theta = Q_theta + (J xi^psi) mu0 j^zeta @@ -869,8 +873,6 @@ SUBROUTINE gpeq_c(psi, ipsi) $ g23(dm)*cwt_mn(jpert)+g33(dm)*cwz_mn(jpert) ENDDO ENDDO -c (xno/bno and theta-space c2v* were dev-time checks; removed) - IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi c----------------------------------------------------------------------- c terminate. @@ -881,7 +883,7 @@ END SUBROUTINE gpeq_c c subprogram 11. gpeq_cveri. c verify the C-vector identity c -c (curl C) · grad(psi) +c (curl C).grad(psi) c = (d C_zeta / d theta - d C_theta / d zeta) / J c c using the single-n Fourier convention @@ -895,7 +897,7 @@ END SUBROUTINE gpeq_c c c and c -c (curl C) · grad(psi) +c (curl C).grad(psi) c = (d_theta C_zeta + 2*pi*i*n*C_theta)/J. c c Assumes gpeq_prep_c(psi) has been called for this psi, so the @@ -926,7 +928,7 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) c----------------------------------------------------------------------- c divide by Jacobian pointwise: -c (curl C) · grad(psi) = (d_theta C_zeta - d_zeta C_theta)/J +c (curl C).grad(psi) = (d_theta C_zeta - d_zeta C_theta)/J c----------------------------------------------------------------------- DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 0) @@ -1210,7 +1212,7 @@ END SUBROUTINE gpeq_dst c----------------------------------------------------------------------- c subprogram 15. gpeq_shear. c compute magnetic shear at a single psi level. -c approach: compute spatial domain first (theta), then FFT to mode space. +c evaluated on the theta grid and returned in real space. c----------------------------------------------------------------------- SUBROUTINE gpeq_shear(psi, shear_fun) REAL(r8), INTENT(IN) :: psi @@ -1231,10 +1233,10 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c----------------------------------------------------------------------- c Allocate spline for shear_temp with 5 components: -c 1: legacy covariant numerator term +c 1: covariant-form numerator (kept for reference, unused below) c 2: (q*dpdt - dpdz) numerator c 3: g_psi_g_psi denominator -c 4: legacy covariant denominator +c 4: covariant-form denominator (kept for reference, unused below) c 5: DCON shear geometry term (component 2 / component 3) c----------------------------------------------------------------------- CALL spline_alloc(shear_temp, mthsurf, 5) @@ -1249,7 +1251,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) CALL bicube_eval(rzphi, psi, theta(itheta), 1) jac = rzphi%f(4) - rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) eta = twopi*(theta_val + rzphi%f(2)) r_val = ro + rfac*COS(eta) @@ -1267,8 +1269,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) w(3,2) = (r_val/(2.0_r8*rfac*jac))* $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) -c Compute covariant basis v_ij = ∂r/∂ξ_j (Cartesian components) -c From recon_metric: +c Compute covariant basis v_ij = dr/dxi_j (Cartesian components) v(1,1) = rzphi%fx(1)/(2.0_r8*rfac*jac) v(1,2) = rzphi%fx(2)*twopi*rfac/jac v(1,3) = rzphi%fx(3)*r_val/jac @@ -1280,7 +1281,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) c Component 33 of covariant basis v(3,3) = twopi*r_val/jac -c Contravariant dot products: dpdp = ∇ψ·∇ψ, etc. +c Contravariant dot products: dpdp = grad psi.grad psi, etc. dpdp = w(1,1)**2 + w(1,2)**2 dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) dpdz = w(1,1)*w(3,1) + w(1,2)*w(3,2) @@ -1294,8 +1295,8 @@ SUBROUTINE gpeq_shear(psi, shear_fun) $ twopi**2 * r_val**2 * (v(2,1)**2 + v(2,2)**2) - shear_temp%fs(itheta, 5) = - $ shear_temp%fs(itheta, 2) / shear_temp%fs(itheta, 3) + shear_temp%fs(itheta, 5) = shear_temp%fs(itheta, 2) / + $ MAX(shear_temp%fs(itheta, 3), recon_tiny) ENDDO c----------------------------------------------------------------------- @@ -1321,7 +1322,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) ENDDO c----------------------------------------------------------------------- -c Step 4: Transform to mode space via FFT. +c Step 4: Release the theta spline. c----------------------------------------------------------------------- CALL spline_dealloc(shear_temp) c----------------------------------------------------------------------- @@ -1330,7 +1331,8 @@ END SUBROUTINE gpeq_shear c----------------------------------------------------------------------- c subprogram 16. gpeq_curvature. c compute curvature in spatial domain. -c κ·∇ψ = (|∇ψ|²/B²)[μ₀p' + (1/2)(∂B²/∂ψ) + (1/2)(∂B²/∂θ)(∇ψ·∇θ)/(∇ψ·∇ψ)] +c kappa.grad psi = (|grad psi|^2/B^2)[mu0 p' + (1/2) dB^2/dpsi +c + (1/2)(dB^2/dtheta)(grad psi.grad theta)/|grad psi|^2] c----------------------------------------------------------------------- SUBROUTINE gpeq_curvature(psi, curv_fun) REAL(r8), INTENT(IN) :: psi @@ -1349,14 +1351,14 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) p1 = sq%f1(2) / mu0 c----------------------------------------------------------------------- -c Step 3: Compute curvature at all theta points. +c Compute curvature at all theta points. c----------------------------------------------------------------------- DO itheta = 0, mthsurf theta_val = theta(itheta) CALL bicube_eval(rzphi, psi, theta_val, 1) jac = rzphi%f(4) - rfac = SQRT(rzphi%f(1)) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) eta = twopi*(theta_val + rzphi%f(2)) r_val = ro + rfac*COS(eta) @@ -1373,21 +1375,21 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) w(3,2) = (r_val/(2.0_r8*rfac*jac))* $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) -c Compute |∇ψ|² and ∇ψ·∇θ +c Compute |grad psi|^2 and grad psi.grad theta delpsi = SQRT(w(1,1)**2 + w(1,2)**2) dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) CALL bicube_eval(eqfun, psi, theta_val, 1) -c Retrieve B² and derivatives - bsq_val = eqfun%f(1) ** 2 +c Retrieve B^2 and derivatives + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) bsq_psi = eqfun%fx(1) * 2 * eqfun%f(1) bsq_theta = eqfun%fy(1) * 2 * eqfun%f(1) -c Compute κ·∇ψ safely +c Compute kappa.grad psi kappa_psi = (delpsi**2 / bsq_val) * $ (p1*mu0 + 0.5_r8*bsq_psi + - $ 0.5_r8*bsq_theta*dpdt/(delpsi**2)) + $ 0.5_r8*bsq_theta*dpdt/MAX(delpsi**2, recon_tiny)) curv_fun(itheta) = CMPLX(kappa_psi, 0.0_r8, r8) @@ -1398,8 +1400,8 @@ END SUBROUTINE gpeq_curvature c----------------------------------------------------------------------- c subprogram 17. gpeq_K. c compute Bernstein K quantity (stability indicator). -c K = |∇ψ_dcon|^2 * σ * S_dcon -c + mu0 * B^2 * σ^2 + 2 p' * κ^psi +c K = |grad psi_dcon|^2 * sigma * S_dcon +c + mu0 * B^2 * sigma^2 + 2 p' * kappa^psi c Uses pre-computed metric from idcon_metric c----------------------------------------------------------------------- SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, @@ -1440,11 +1442,11 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) jac = rzphi%f(4) - rfac = SQRT(rzphi%f(1)) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) eta = twopi*(theta(itheta) + rzphi%f(2)) r_val = ro + rfac*COS(eta) -c |∇ψ| magnitude +c |grad psi| magnitude w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) delpsi=SQRT(w(1,1)**2+w(1,2)**2) @@ -1469,9 +1471,9 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, g33 = (v(3,3)**2) g23 = (v(2,3)*v(3,3)) -c j·B and σ = (j·B)/B² +c j.B and sigma = (j.B)/B^2 CALL bicube_eval(eqfun, psi, theta(itheta), 0) - bsq_val = eqfun%f(1)**2 + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) jdotb_val = g22*bth*jwt + g33*bze*jwz + $ g23*(bth*jwz + bze*jwt) @@ -1479,13 +1481,13 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, sigma_vals(itheta) = sigma jdotb_vals(itheta) = jdotb_val -c Term1: |∇ψ_dcon|^2 * σ * S_dcon +c Term1: |grad psi_dcon|^2 * sigma * S_dcon K_t1(itheta) = (delpsi**2) * sigma * REAL(shear_fun(itheta)) -c Term2: mu0 * B^2 * σ^2 +c Term2: mu0 * B^2 * sigma^2 K_t2(itheta) = bsq_val * sigma**2 * mu0 -c Term3: 2 p' * κ^psi +c Term3: 2 p' * kappa^psi K_t3(itheta) = 2.0_r8 * REAL(curv_fun(itheta)) * p1 ENDDO @@ -1541,7 +1543,7 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, REAL(r8) :: f1raw, p1_local, chi1 REAL(r8) :: A_density, B_density, Iperp_density, Cpar_density, $ epf_density - REAL(r8) :: delpsi_val, bsq_val, sigma_local, jdotb_val + REAL(r8) :: delpsi_val, dpsq, bsq_val, sigma_local, jdotb_val REAL(r8) :: jwt, jwz, bth, bze, g22, g33, g23, r_val REAL(r8) :: xin2, mu0jwt, mu0jwz REAL(r8) :: mu0jpar_t, mu0jpar_z, mu0jper_t, mu0jper_z @@ -1598,15 +1600,16 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, CALL bicube_eval(rzphi, psi, theta(itheta), 1) CALL bicube_eval(eqfun, psi, theta(itheta), 0) jac = rzphi%f(4) - rfac = SQRT(rzphi%f(1)) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) eta = twopi*(theta(itheta) + rzphi%f(2)) r_val = ro + rfac*COS(eta) - bsq_val = eqfun%f(1)**2 + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) c |grad psi| w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) delpsi_val=SQRT(w(1,1)**2+w(1,2)**2) + dpsq = MAX(delpsi_val**2, recon_tiny) c currents and B (idcon_metric convention, same as gpeq_K). jwt = -f1raw/(jac*mu0) @@ -1650,9 +1653,9 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, c mu0 j^i = mu0 * (physical j^i); jwt,jwz already hold physical j^i. mu0jwt = mu0 * jwt mu0jwz = mu0 * jwz - jvt_loc = xwp_fun(itheta) / (delpsi_val**2 * jac) * + jvt_loc = xwp_fun(itheta) / (dpsq * jac) * $ (mu0jwt * g23 + mu0jwz * g33) - jvz_loc = -xwp_fun(itheta) / (delpsi_val**2 * jac) * + jvz_loc = -xwp_fun(itheta) / (dpsq * jac) * $ (mu0jwt * g22 + mu0jwz * g23) c V is contravariant (J V^i); pair with covariant Q_i (bvt,bvz). B_density = 2.0_r8 * REAL( @@ -1678,9 +1681,9 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, mu0jpar_z = mu0 * sigma_local * bze mu0jper_t = mu0jwt - mu0jpar_t mu0jper_z = mu0jwz - mu0jpar_z - jvt_cur = xwp_fun(itheta) / (delpsi_val**2 * jac) * + jvt_cur = xwp_fun(itheta) / (dpsq * jac) * $ (mu0jpar_t * g23 + mu0jpar_z * g33) - jvz_cur = -xwp_fun(itheta) / (delpsi_val**2 * jac) * + jvz_cur = -xwp_fun(itheta) / (dpsq * jac) * $ (mu0jpar_t * g22 + mu0jpar_z * g23) jvt_pre = jvt_loc - jvt_cur jvz_pre = jvz_loc - jvz_cur diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index 50c9c57c1..bc9732e4f 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -19,7 +19,8 @@ MODULE gpglobal_mod $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, - $ cveri_flag,recon_out,recon_flag2,recon_flag3 + $ curl_check_flag,recon_out,standard_form_flag, + $ effective_field_terms_flag INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, diff --git a/gpec/gpout.f b/gpec/gpout.f index 6c213a881..7d9eb231f 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6981,7 +6981,7 @@ SUBROUTINE gpout_recon(mode, xspmn) IF (recon_out) THEN OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") - IF (cveri_flag) THEN + IF (curl_check_flag) THEN OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") ENDIF OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") @@ -6999,7 +6999,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "psi","theta","r","z","shear_re","shear_im" WRITE(ucurv,'(6(1x,a16))') $ "psi","theta","r","z","curv_re","curv_im" - IF (cveri_flag) WRITE(ucveri,'(6(1x,a16))') + IF (curl_check_flag) WRITE(ucveri,'(6(1x,a16))') $ "psi","theta","r","z","cveri_re","cveri_im" WRITE(uk,'(9(1x,a16))') $ "psi","theta","r","z","K_re","K_im", @@ -7038,8 +7038,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ "c2_theta_den","c2_zeta_den","dst1_den","dst2_den", $ "dst3_den" ENDIF - ep_index = 1 - IF (mode_flag) ep_index = mode + ep_index = mode IF (ep_index < 1 .OR. ep_index > mpert) THEN CALL gpec_stop("gpout_recon selected mode is out of range") ENDIF @@ -7084,7 +7083,7 @@ SUBROUTINE gpout_recon(mode, xspmn) CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, $ sigma_vals, jdotb_vals, shear_fun, curv_fun) c Verify (curl C).grad(psi)=0 using the C state prepared above. - IF (cveri_flag) THEN + IF (curl_check_flag) THEN CALL gpeq_cveri(psi, cveri_fun) DO itheta = 0, mthsurf cveri_abs = ABS(cveri_fun(itheta)) @@ -7119,7 +7118,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ rvals(itheta), zvals(itheta), $ REAL(curv_fun(itheta)), $ AIMAG(curv_fun(itheta)) - IF (cveri_flag) WRITE(ucveri,'(6(es17.8e3))') psi, + IF (curl_check_flag) WRITE(ucveri,'(6(es17.8e3))') psi, $ theta(itheta), rvals(itheta), zvals(itheta), $ REAL(cveri_fun(itheta)), AIMAG(cveri_fun(itheta)) WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), @@ -7134,7 +7133,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_terms,*) WRITE(ushear_fun,*) WRITE(ucurv,*) - IF (cveri_flag) WRITE(ucveri,*) + IF (curl_check_flag) WRITE(ucveri,*) WRITE(uk,*) WRITE(uk_sigma,*) @@ -7396,7 +7395,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", $ ep_selected * (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 - IF (cveri_flag) THEN + IF (curl_check_flag) THEN cveri_stat = "PASS" IF (cveri_count > 0) THEN cveri_rms = SQRT(cveri_sumsq / REAL(cveri_count, r8)) @@ -7444,7 +7443,7 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), $ ") = ", ep_selected * $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 - IF (cveri_flag) THEN + IF (curl_check_flag) THEN WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms @@ -7458,7 +7457,7 @@ SUBROUTINE gpout_recon(mode, xspmn) IF (recon_out) THEN CLOSE(ushear_fun) CLOSE(ucurv) - IF (cveri_flag) CLOSE(ucveri) + IF (curl_check_flag) CLOSE(ucveri) CLOSE(uk) CLOSE(uk_sigma) CLOSE(u_int) @@ -7534,8 +7533,7 @@ SUBROUTINE gpout_recon2(mode, xspmn) ENDIF c fail fast on an out-of-range mode selection. - ep_index = 1 - IF (mode_flag) ep_index = mode + ep_index = mode IF (ep_index < 1 .OR. ep_index > mpert) THEN CALL gpec_stop("gpout_recon2 selected mode is out of range") ENDIF @@ -7803,8 +7801,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) ENDIF c fail fast on an out-of-range mode selection. - ep_index = 1 - IF (mode_flag) ep_index = mode + ep_index = mode IF (ep_index < 1 .OR. ep_index > mpert) THEN CALL gpec_stop("gpout_recon3 selected mode is out of range") ENDIF diff --git a/input/gpec.in b/input/gpec.in index 9adf1c7d5..64cf42861 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -90,12 +90,12 @@ verbose=t ! Print run log to terminal - recon_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. - recon_flag2=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. - recon_flag3=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. - cveri_flag=f ! Under recon_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. - recon_out=t ! Under recon_flag/recon_flag2/recon_flag3, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. - recon_int="spline" ! recon_flag radial integration on psifac grid. Options: "spline" or "trapezoid". + effective_field_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + standard_form_flag=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. + effective_field_terms_flag=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. + curl_check_flag=f ! Under effective_field_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. + recon_out=t ! Under effective_field_flag/standard_form_flag/effective_field_terms_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. + recon_int="spline" ! effective_field_flag radial integration on psifac grid. Options: "spline" or "trapezoid". ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. / &GPEC_DIAGNOSE diff --git a/input/match.in b/input/match.in index 31acfa658..c35d54f61 100644 --- a/input/match.in +++ b/input/match.in @@ -5,7 +5,7 @@ ideal_flag=t contour_flag=t - sol_num=1 !DCON eigen function index (1 is most unstable), which will be calculated by MATCH + sol_num=1 !DCON eigenfunction index for MATCH (1 = most unstable); sol_num=n>1 writes solutions.bin res_flag=f ff0=1e6 From 6ce009952c9c31b6c3c8f14b0c2724823ec8cd3f Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Thu, 15 Oct 2026 21:13:07 +0200 Subject: [PATCH 220/305] Keep Cardano cube-root exponent in spline precision Preserve bracketed roots near cell endpoints and test against an independent Hermite bisection oracle. Chris&AI --- equil/spline.f | 4 +- install/TARGETS.inc | 9 ++++ regression/test/spline_root_endpoint.f90 | 57 ++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 regression/test/spline_root_endpoint.f90 diff --git a/equil/spline.f b/equil/spline.f index 4256cfc25..c4db92b4f 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -1693,7 +1693,9 @@ SUBROUTINE spline_roots(spl, iqty, nroots, roots,op_extrap,op_eps) r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0 m = r**2 - q**3 ! discrimenent IF(m > 0)THEN ! one real root - s = -sign(1.0_r8, r) * (abs(r) + sqrt(m))**(1.0/3.0) +c Keep the cube-root exponent in the spline's precision. +c A default-real 1/3 can move a valid root outside its cell. + s = -sign(1.0_r8,r)*(abs(r)+sqrt(m))**(1.0_r8/3.0_r8) t = q / s z1 = s + t - (a / 3.0) z2 = -huge(z2) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index caf48d0e7..6c2ce0ce8 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -450,3 +450,12 @@ check-pentrc-energy-istate: pentrc $(BINDIR)/test_pentrc_energy_istate default ! $(BINDIR)/test_pentrc_energy_istate tight > $(BINDIR)/test_pentrc_energy_istate.log 2>&1 grep -q "ERROR: xintgrl_lsode - LSODE failed" $(BINDIR)/test_pentrc_energy_istate.log + +# A bracketed cubic root must not be dropped near a cell endpoint. +.PHONY: check-spline-root-endpoint +check-spline-root-endpoint: equil + mkdir -p $(BINDIR) + $(FCEQUIL) -I../equil ../regression/test/spline_root_endpoint.f90 \ + -L$(LIBDIR) -lequil -L$(MATHDIR) $(MATHLIBS) $(LDFLAGS) \ + -o $(BINDIR)/test_spline_root_endpoint + $(BINDIR)/test_spline_root_endpoint diff --git a/regression/test/spline_root_endpoint.f90 b/regression/test/spline_root_endpoint.f90 new file mode 100644 index 000000000..a772c1e84 --- /dev/null +++ b/regression/test/spline_root_endpoint.f90 @@ -0,0 +1,57 @@ +! A monotone Hermite cell from a PENTRC trapped orbit. Its unique root lies +! just inside the right endpoint; a rounded Cardano exponent can move the +! initial candidate outside the cell and silently discard the bounce point. +program spline_root_endpoint + use local_mod, only: r8 + use spline_mod, only: spline_type, spline_alloc, spline_dealloc, spline_roots + implicit none + type(spline_type) :: spl + real(r8), parameter :: left = .767578125_r8, right = .76953125_r8 + real(r8), parameter :: fleft = -1.99139065907738555e-3_r8 + real(r8), parameter :: fright = 1.32122017770797129e-8_r8 + real(r8), parameter :: dleft = 1.01883408452285917_r8 + real(r8), parameter :: dright = 1.02042520765343880_r8 + real(r8) :: roots(3), lo, hi, mid, expected, z, value, scale + integer :: i, j, nroots + + ! Independent bisection of the Hermite polynomial with opposite endpoint + ! signs and positive derivative throughout this cell gives its unique root. + lo = 0.0_r8 + hi = 1.0_r8 + do i = 1, 60 + mid = (lo + hi)/2 + if (hermite(mid) < 0) then + lo = mid + else + hi = mid + end if + end do + expected = left + (right - left)*(lo + hi)/2 + do j = -3, 3, 3 + scale = 10.0_r8**j + call spline_alloc(spl, 1, 1) + spl%xs = [left, right] + spl%fs(:, 1) = scale*[fleft, fright] + spl%fs1(:, 1) = scale*[dleft, dright] + call spline_roots(spl, 1, nroots, roots) + print '(a,i0,a,es24.16)', 'roots=', nroots, ' oracle=', expected + if (nroots /= 1) error stop 'Spline lost a bracketed simple root' + if (abs(roots(1) - expected) > 5.0e-14_r8) & + error stop 'Spline root disagrees with independent bracket oracle' + z = (roots(1) - left)/(right - left) + value = hermite(z) + if (z <= 0 .or. z >= 1 .or. abs(value) > 1.0e-13_r8) & + error stop 'Root is outside its bracket or has a large residual' + call spline_dealloc(spl) + end do + print *, 'PASS: bracketed endpoint root survives amplitude scaling' +contains + function hermite(z) result(value) + real(r8), intent(in) :: z + real(r8) :: value + value = (2*z**3 - 3*z**2 + 1)*fleft + & + (-2*z**3 + 3*z**2)*fright + & + (z**3 - 2*z**2 + z)*(right - left)*dleft + & + (z**3 - z**2)*(right - left)*dright + end function hermite +end program spline_root_endpoint From 4a70b919072f312a5978360195ac6cf34542a858 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 15 Oct 2026 15:40:16 -0400 Subject: [PATCH 221/305] GPEC - recon: keep geometry scratch and chi1 local to recon kernels --- gpec/gpeq.f | 21 +++++++++++++++------ gpec/gpout.f | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/gpec/gpeq.f b/gpec/gpeq.f index fb6b44fc6..41670b9ed 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -696,6 +696,7 @@ SUBROUTINE gpeq_c(psi, ipsi) REAL(r8) :: q, f1raw, p1, chi1, jac REAL(r8) :: eta, rfac, v21, v22, v23, v33, dpsq + REAL(r8), DIMENSION(3,3) :: w REAL(r8), DIMENSION(0:mthsurf) :: jacs, r_vec COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun @@ -911,6 +912,7 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: cveri_fun INTEGER :: itheta, ipert + REAL(r8) :: jac COMPLEX(r8), DIMENSION(mpert) :: dth_cvz_mn, dzt_cvt_mn COMPLEX(r8), DIMENSION(mpert) :: curlpsi_mn @@ -971,7 +973,7 @@ SUBROUTINE gpeq_firstform(psi, q2_int, jqx_int, pdiv_int, $ q2_fun, jqx_fun, pdiv_fun, total_fun INTEGER :: itheta - REAL(r8) :: f1raw, p1 + REAL(r8) :: f1raw, p1, chi1, jac, jac1 REAL(r8) :: q2_density REAL(r8) :: mu0jtheta, mu0jzeta COMPLEX(r8) :: jqx_density, total_density, pdiv_density @@ -993,6 +995,7 @@ SUBROUTINE gpeq_firstform(psi, q2_int, jqx_int, pdiv_int, CALL spline_eval(sq, psi, 1) f1raw = sq%f1(1) p1 = sq%f1(2) / mu0 + chi1 = psio * twopi CALL iscdftb(mfac, mpert, xsp_fun, mthsurf, xsp_mn) CALL iscdftb(mfac, mpert, xmp1_fun, mthsurf, xmp1_mn) @@ -1101,7 +1104,7 @@ SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun REAL(r8) :: epf_psi_int, epf_theta_int, epf_zeta_int - REAL(r8) :: dpsi_d, dthe_d, dzet_d + REAL(r8) :: dpsi_d, dthe_d, dzet_d, jac IF(debug_flag) PRINT *, "Entering gpeq_epf" c----------------------------------------------------------------------- @@ -1171,7 +1174,7 @@ SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, INTEGER :: itheta COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xno_fun REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 - REAL(r8) :: xin2_fac, xin2_loc, d1, d2, d3 + REAL(r8) :: xin2_fac, xin2_loc, d1, d2, d3, jac COMPLEX(r8) :: dst1_int, dst2_int, dst3_int c----------------------------------------------------------------------- @@ -1219,8 +1222,9 @@ SUBROUTINE gpeq_shear(psi, shear_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: shear_fun INTEGER :: itheta - REAL(r8) :: r_val, theta_val, q, q1 + REAL(r8) :: r_val, theta_val, q, q1, chi1, jac, rfac, eta REAL(r8) :: dpdp, dpdt, dpdz, shear_deriv + REAL(r8), DIMENSION(3,3) :: w, v REAL(r8) :: shear_contra TYPE(spline_type) :: shear_temp @@ -1230,6 +1234,7 @@ SUBROUTINE gpeq_shear(psi, shear_fun) CALL spline_eval(sq, psi, 1) q = sq%f(4) q1 = sq%f1(4) + chi1 = psio * twopi c----------------------------------------------------------------------- c Allocate spline for shear_temp with 5 components: @@ -1339,8 +1344,9 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: curv_fun INTEGER :: itheta - REAL(r8) :: r_val, theta_val, p1 + REAL(r8) :: r_val, theta_val, p1, jac, rfac, eta REAL(r8) :: delpsi + REAL(r8), DIMENSION(3,3) :: w REAL(r8) :: bsq_val, bsq_psi, bsq_theta REAL(r8) :: dpdt , kappa_psi @@ -1416,7 +1422,8 @@ SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, INTEGER :: itheta REAL(r8) :: f1raw, delpsi, chi1, p1, q REAL(r8) :: jwt, jwz, bth, bze, bsq_val, sigma, jdotb_val - REAL(r8) :: g22, g23, g33, r_val + REAL(r8) :: g22, g23, g33, r_val, jac, rfac, eta + REAL(r8), DIMENSION(3,3) :: w, v REAL(r8), DIMENSION(0:mthsurf) :: K_t1, K_t2, K_t3 REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun @@ -1545,6 +1552,8 @@ SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, $ epf_density REAL(r8) :: delpsi_val, dpsq, bsq_val, sigma_local, jdotb_val REAL(r8) :: jwt, jwz, bth, bze, g22, g33, g23, r_val + REAL(r8) :: jac, rfac, eta + REAL(r8), DIMENSION(3,3) :: w, v REAL(r8) :: xin2, mu0jwt, mu0jwz REAL(r8) :: mu0jpar_t, mu0jpar_z, mu0jper_t, mu0jper_z REAL(r8) :: jsq_val, jpar_val, bmod_val diff --git a/gpec/gpout.f b/gpec/gpout.f index 7d9eb231f..ae43a7210 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7757,7 +7757,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) c recon3 output units, within the reserved recon range 72-99. INTEGER, PARAMETER :: usurf=97, u_int=98, u_log=99 INTEGER :: istep, nint_pts, iint - REAL(r8) :: psi, dpsi_local + REAL(r8) :: psi, dpsi_local, rfac, eta REAL(r8) :: A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr REAL(r8) :: A_total, B_total, Iperp_total, Cpar_total, epf_total REAL(r8) :: dst1_total, dst3_total From 47be71b78158a70474b2346c0dc391bb4642c7cf Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 15 Oct 2026 15:40:16 -0400 Subject: [PATCH 222/305] GPEC - recon: open recon output files with NEWUNIT --- gpec/gpout.f | 75 +++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index ae43a7210..204ba9ac2 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6909,13 +6909,10 @@ SUBROUTINE gpout_recon(mode, xspmn) COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn INTEGER :: ipsi, itheta, ipert -c recon output units: 72-99 are reserved for the recon diagnostics -c (gpout_recon/2/3) and are not opened elsewhere in the GPEC -c executable. - INTEGER, PARAMETER :: ushear_fun=82, ucurv=72, ucveri=74, uk=73, - $ uk_sigma=85, ucw_fun=86, ucw_mn=87, ucv_fun=88, ucv_mn=89, - $ ucv2_fun=90, ucv2_mn=91, u_int=92, u_log=93, u_terms=94, - $ uqv_mn=95 +c recon output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: ushear_fun, ucurv, ucveri, uk, uk_sigma, ucw_fun, + $ ucw_mn, ucv_fun, ucv_mn, ucv2_fun, ucv2_mn, u_int, u_log, + $ u_terms, uqv_mn REAL(r8), DIMENSION(0:mthsurf) :: epf_den_fun, epf_p_fun, $ epf_t_fun, epf_z_fun, dst1_den_fun, dst2_den_fun, $ dst3_den_fun @@ -6979,22 +6976,22 @@ SUBROUTINE gpout_recon(mode, xspmn) terms_file = "gpec_recon_terms_sol"//TRIM(smode)//".out" IF (recon_out) THEN - OPEN(UNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") - OPEN(UNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") IF (curl_check_flag) THEN - OPEN(UNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") ENDIF - OPEN(UNIT=uk, FILE=k_file, STATUS="UNKNOWN") - OPEN(UNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") - CALL ascii_open(ucw_fun, cw_fun_file, "UNKNOWN") - CALL ascii_open(ucw_mn, cw_mn_file, "UNKNOWN") - CALL ascii_open(ucv_fun, cv_fun_file, "UNKNOWN") - CALL ascii_open(ucv_mn, cv_mn_file, "UNKNOWN") - CALL ascii_open(ucv2_fun, cv2_fun_file, "UNKNOWN") - CALL ascii_open(ucv2_mn, cv2_mn_file, "UNKNOWN") - CALL ascii_open(uqv_mn, qv_mn_file, "UNKNOWN") - OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") - OPEN(UNIT=u_terms, FILE=terms_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=uk, FILE=k_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucw_fun, FILE=cw_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucw_mn, FILE=cw_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv_fun, FILE=cv_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv_mn, FILE=cv_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv2_fun, FILE=cv2_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv2_mn, FILE=cv2_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=uqv_mn, FILE=qv_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_terms, FILE=terms_file, STATUS="UNKNOWN") WRITE(ushear_fun,'(6(1x,a16))') $ "psi","theta","r","z","shear_re","shear_im" WRITE(ucurv,'(6(1x,a16))') @@ -7410,7 +7407,7 @@ SUBROUTINE gpout_recon(mode, xspmn) ENDIF IF (recon_out) THEN - OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", $ POSITION="APPEND") WRITE(u_log,'(a)') "GPEC_RECON final results:" WRITE(u_log,'(a,I8)') " mode = ", mode @@ -7462,13 +7459,13 @@ SUBROUTINE gpout_recon(mode, xspmn) CLOSE(uk_sigma) CLOSE(u_int) CLOSE(u_terms) - CALL ascii_close(ucw_fun) - CALL ascii_close(ucw_mn) - CALL ascii_close(ucv_fun) - CALL ascii_close(ucv_mn) - CALL ascii_close(ucv2_fun) - CALL ascii_close(ucv2_mn) - CALL ascii_close(uqv_mn) + CLOSE(ucw_fun) + CLOSE(ucw_mn) + CLOSE(ucv_fun) + CLOSE(ucv_mn) + CLOSE(ucv2_fun) + CLOSE(ucv2_mn) + CLOSE(uqv_mn) ENDIF c----------------------------------------------------------------------- c terminate. @@ -7493,8 +7490,8 @@ SUBROUTINE gpout_recon2(mode, xspmn) COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn INTEGER :: ipsi, itheta -c recon2 output units, within the reserved recon range 72-99. - INTEGER, PARAMETER :: usurf=94, u_int=95, u_log=96 +c recon2 output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: usurf, u_int, u_log INTEGER :: istep, nint_pts, iint REAL(r8) :: psi, eta, rfac, dpsi_local, q2_hr REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals @@ -7522,8 +7519,8 @@ SUBROUTINE gpout_recon2(mode, xspmn) int_file = "gpec_recon2_integration_sol"//TRIM(smode)//".out" IF (recon_out) THEN - OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") - OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") WRITE(usurf,'(12(1x,a16))') $ "psi","theta","r","z","q2_re","q2_im","jqx_re", $ "jqx_im","pdiv_re","pdiv_im","total_re","total_im" @@ -7693,7 +7690,7 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ ep_dcon IF (recon_out) THEN - OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", $ POSITION="APPEND") WRITE(u_log,'(a)') "GPEC_RECON2 final results:" WRITE(u_log,'(a,I8)') " mode = ", mode @@ -7754,8 +7751,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn INTEGER :: ipsi, itheta -c recon3 output units, within the reserved recon range 72-99. - INTEGER, PARAMETER :: usurf=97, u_int=98, u_log=99 +c recon3 output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: usurf, u_int, u_log INTEGER :: istep, nint_pts, iint REAL(r8) :: psi, dpsi_local, rfac, eta REAL(r8) :: A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr @@ -7787,8 +7784,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) int_file = "gpec_recon3_integration_sol"//TRIM(smode)//".out" IF (recon_out) THEN - OPEN(UNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") - OPEN(UNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") WRITE(usurf,'(30(1x,a16))') $ "psi","theta","r","z","A_den","B_den","Iperp_den", $ "Cpar_den","VdotQ_re","VdotQ_im","Bcur_den","Bpre_den", @@ -7988,7 +7985,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) $ ep_dcon IF (recon_out) THEN - OPEN(UNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", $ POSITION="APPEND") WRITE(u_log,'(a)') "GPEC_RECON3 final results:" WRITE(u_log,'(a,I8)') " mode = ", mode From fe40fed88d7cf81d812c442059f1985c5f4969c7 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Thu, 15 Oct 2026 15:40:16 -0400 Subject: [PATCH 223/305] GPEC - recon: judge curl check on residual relative to the cancelling terms --- docs/tex/recon/main.tex | 14 +++++++++++--- gpec/gpeq.f | 16 +++++++++++++++- gpec/gpout.f | 38 ++++++++++++++++++++++++++++---------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index fa36a55b9..bee70334e 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -2228,9 +2228,17 @@ \section{Log-output quantities and their exact integrands} \item \texttt{curl\_check\_flag} (default \texttt{.FALSE.}) enables the $(\nabla\times\vec C)\cdot\nabla\psi=0$ verification of \S\ref{par:cveri} alongside \texttt{effective\_field\_flag}. The pointwise - residual is accumulated over all surfaces and reported as a status - of \texttt{PASS}, \texttt{WARN} or \texttt{SKIP} against a fixed - tolerance of $10^{-10}$, together with its maximum and rms. + residual is accumulated over all surfaces. Because the identity is + a cancellation between $\partial_\theta C_\zeta$ and + $\partial_\zeta C_\theta$, the verdict uses the relative residual + \begin{equation*} + \texttt{rel}=\frac{\max|(\nabla\times\vec C)\cdot\nabla\psi|} + {\max\big(|\partial_\theta C_\zeta|+|\partial_\zeta C_\theta|\big)/|J|}, + \end{equation*} + with both maxima taken over all $(\psi,\theta)$ points. It is + reported as \texttt{PASS} or \texttt{WARN} against a tolerance of + $10^{-8}$ (\texttt{SKIP} if no points were checked), together with + the absolute maximum and rms, the scale, and \texttt{rel}. \end{itemize} The DCON normalization factor that converts the GPEC energy to the diff --git a/gpec/gpeq.f b/gpec/gpeq.f index 41670b9ed..bf32414d7 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -903,18 +903,25 @@ END SUBROUTINE gpeq_c c c Assumes gpeq_prep_c(psi) has been called for this psi, so the c module cvt_mn/cvz_mn hold the covariant C state. +c +c cveri_scale (optional) returns the size of the two terms that +c must cancel, (|d_theta C_zeta| + |d_zeta C_theta|)/|J|, so the +c residual can be judged relative to them. c----------------------------------------------------------------------- - SUBROUTINE gpeq_cveri(psi, cveri_fun) + SUBROUTINE gpeq_cveri(psi, cveri_fun, cveri_scale) c----------------------------------------------------------------------- c declaration. c----------------------------------------------------------------------- REAL(r8), INTENT(IN) :: psi COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: cveri_fun + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ cveri_scale INTEGER :: itheta, ipert REAL(r8) :: jac COMPLEX(r8), DIMENSION(mpert) :: dth_cvz_mn, dzt_cvt_mn COMPLEX(r8), DIMENSION(mpert) :: curlpsi_mn + COMPLEX(r8), DIMENSION(0:mthsurf) :: dth_cvz_fun, dzt_cvt_fun IF(debug_flag) PRINT *, "Entering gpeq_cveri" c----------------------------------------------------------------------- @@ -927,6 +934,10 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) ENDDO CALL iscdftb(mfac, mpert, cveri_fun, mthsurf, curlpsi_mn) + IF (PRESENT(cveri_scale)) THEN + CALL iscdftb(mfac, mpert, dth_cvz_fun, mthsurf, dth_cvz_mn) + CALL iscdftb(mfac, mpert, dzt_cvt_fun, mthsurf, dzt_cvt_mn) + ENDIF c----------------------------------------------------------------------- c divide by Jacobian pointwise: @@ -936,6 +947,9 @@ SUBROUTINE gpeq_cveri(psi, cveri_fun) CALL bicube_eval(rzphi, psi, theta(itheta), 0) jac = rzphi%f(4) cveri_fun(itheta) = cveri_fun(itheta) / jac + IF (PRESENT(cveri_scale)) cveri_scale(itheta) = + $ (ABS(dth_cvz_fun(itheta)) + ABS(dzt_cvt_fun(itheta))) + $ / ABS(jac) ENDDO IF(debug_flag) PRINT *, "->Leaving gpeq_cveri" diff --git a/gpec/gpout.f b/gpec/gpout.f index 204ba9ac2..24d0d3a7f 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6940,7 +6940,8 @@ SUBROUTINE gpout_recon(mode, xspmn) REAL(r8) :: epf_int_total_hr, epf_p_total_hr, $ epf_t_total_hr, epf_z_total_hr REAL(r8) :: cveri_abs, cveri_max, cveri_rms, cveri_sumsq, - $ cveri_tol + $ cveri_tol, cveri_scale_max, cveri_rel + REAL(r8), DIMENSION(0:mthsurf) :: cveri_scale COMPLEX(r8) :: dst_prev, dst_curr, dst1_prev, dst2_prev, $ dst3_prev, dst1_curr, dst2_curr, dst3_curr, $ dst_int_total_k_hr, dst1_total_hr, dst2_total_hr, @@ -7043,7 +7044,13 @@ SUBROUTINE gpout_recon(mode, xspmn) cveri_max = 0.0_r8 cveri_rms = 0.0_r8 cveri_sumsq = 0.0_r8 - cveri_tol = 1.0e-10_r8 + cveri_scale_max = 0.0_r8 + cveri_rel = 0.0_r8 +c curl check is judged on max|residual| relative to the largest +c (|d_theta C_zeta| + |d_zeta C_theta|)/|J|: the identity is a +c cancellation of these two terms, so their size is the natural +c amplitude- and unit-free reference. + cveri_tol = 1.0e-8_r8 cveri_count = 0 c----------------------------------------------------------------------- c main loop over all psi levels. @@ -7081,10 +7088,12 @@ SUBROUTINE gpout_recon(mode, xspmn) $ sigma_vals, jdotb_vals, shear_fun, curv_fun) c Verify (curl C).grad(psi)=0 using the C state prepared above. IF (curl_check_flag) THEN - CALL gpeq_cveri(psi, cveri_fun) + CALL gpeq_cveri(psi, cveri_fun, cveri_scale) DO itheta = 0, mthsurf cveri_abs = ABS(cveri_fun(itheta)) cveri_max = MAX(cveri_max, cveri_abs) + cveri_scale_max = MAX(cveri_scale_max, + $ cveri_scale(itheta)) cveri_sumsq = cveri_sumsq + cveri_abs**2 cveri_count = cveri_count + 1 ENDDO @@ -7396,14 +7405,21 @@ SUBROUTINE gpout_recon(mode, xspmn) cveri_stat = "PASS" IF (cveri_count > 0) THEN cveri_rms = SQRT(cveri_sumsq / REAL(cveri_count, r8)) + IF (cveri_scale_max > 0.0_r8) THEN + cveri_rel = cveri_max / cveri_scale_max + ELSE + cveri_rel = cveri_max + ENDIF + IF (cveri_rel > cveri_tol) cveri_stat = "WARN" ELSE cveri_stat = "SKIP" ENDIF - IF (cveri_max > cveri_tol) cveri_stat = "WARN" WRITE(*,'(a,a)') "GPEC_RECON C verify: ", TRIM(cveri_stat) - WRITE(*,'(a,es17.8e3)') " max = ", cveri_max - WRITE(*,'(a,es17.8e3)') " rms = ", cveri_rms - WRITE(*,'(a,es17.8e3)') " tol = ", cveri_tol + WRITE(*,'(a,es17.8e3)') " max = ", cveri_max + WRITE(*,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(*,'(a,es17.8e3)') " scale = ", cveri_scale_max + WRITE(*,'(a,es17.8e3)') " rel = ", cveri_rel + WRITE(*,'(a,es17.8e3)') " tol = ", cveri_tol ENDIF IF (recon_out) THEN @@ -7442,9 +7458,11 @@ SUBROUTINE gpout_recon(mode, xspmn) $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 IF (curl_check_flag) THEN WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) - WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max - WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms - WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol + WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max + WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(u_log,'(a,es17.8e3)') " scale = ", cveri_scale_max + WRITE(u_log,'(a,es17.8e3)') " rel = ", cveri_rel + WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol ENDIF WRITE(u_log,*) CLOSE(u_log) From a3c2a3ca2a8901e6a9ecf5e597abd95994bdcd62 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 16 Oct 2026 12:20:45 +0200 Subject: [PATCH 224/305] TEST - MINOR - Remove unused spline tests --- .../test/spline_endpoint_polynomials.f90 | 71 ------------------- .../test/test_spline_endpoint_polynomials.py | 46 ------------ 2 files changed, 117 deletions(-) delete mode 100644 regression/test/spline_endpoint_polynomials.f90 delete mode 100644 regression/test/test_spline_endpoint_polynomials.py diff --git a/regression/test/spline_endpoint_polynomials.f90 b/regression/test/spline_endpoint_polynomials.f90 deleted file mode 100644 index f3b674932..000000000 --- a/regression/test/spline_endpoint_polynomials.f90 +++ /dev/null @@ -1,71 +0,0 @@ -program spline_endpoint_polynomials - use spline_mod, only: r8, spline_type, spline_alloc, spline_fit, & - spline_eval, spline_int, spline_dealloc - use cspline_mod, only: cspline_type, cspline_alloc, cspline_fit, & - cspline_dealloc - implicit none - integer :: resolution, degree, m, i - real(r8) :: value_error, derivative_error, integral_error, s, pi - real(r8) :: q0, q1, expected_integral - type(spline_type) :: profile, extended - type(cspline_type) :: cprofile - - pi = acos(-1.0_r8) - do resolution = 1, 2 - m = 256 * 2**resolution - do degree = 0, 3, 3 - call spline_alloc(profile, m, 1) - profile%xs = 1e-4_r8 + (1.0_r8 - 2e-4_r8) * & - sin([(real(i, r8) / m, i = 0, m)] * pi / 2)**2 - profile%fs(:, 1) = polynomial(profile%xs, degree) - call spline_fit(profile, 'extrap') - derivative_error = maxval(abs(profile%fs1(:, 1) - & - derivative(profile%xs, degree))) - call spline_eval(profile, 0.0_r8, 0) - q0 = profile%f(1) - call spline_eval(profile, 1.0_r8, 0) - q1 = profile%f(1) - value_error = max(abs(q0 - polynomial(0.0_r8, degree)), & - abs(q1 - polynomial(1.0_r8, degree))) - call spline_alloc(extended, m + 2, 1) - extended%xs = [0.0_r8, profile%xs, 1.0_r8] - extended%fs(:, 1) = [q0, profile%fs(:, 1), q1] - call spline_fit(extended, 'extrap') - call spline_int(extended) - expected_integral = 1.0_r8 - if (degree == 3) expected_integral = 2.0_r8 + 1.5_r8 + & - 4.0_r8 / 3 + 1.25_r8 - integral_error = abs(extended%fsi(m + 2, 1) - expected_integral) - write(*, '(2i6,3es25.16)') m, degree, derivative_error, & - value_error, integral_error - call spline_dealloc(extended) - call spline_dealloc(profile) - end do - end do - ! complex profile with a large constant offset, as the F, G, K matrices have - do resolution = 1, 2 - m = 256 * 2**resolution - call cspline_alloc(cprofile, m, 1) - cprofile%xs = 1e-4_r8 + (1.0_r8 - 2e-4_r8) * & - sin([(real(i, r8) / m, i = 0, m)] * pi / 2)**2 - cprofile%fs(:, 1) = cmplx(300.0_r8, -2.0_r8, r8) - call cspline_fit(cprofile, 'extrap') - derivative_error = maxval(abs(cprofile%fs1(:, 1))) - write(*, '(a,i6,es25.16)') 'complex', m, derivative_error - call cspline_dealloc(cprofile) - end do -contains - elemental real(r8) function polynomial(x, degree) result(q) - real(r8), intent(in) :: x - integer, intent(in) :: degree - q = 1.0_r8 - if (degree == 3) q = 2.0_r8 + x * (3.0_r8 + x * (4.0_r8 + 5.0_r8*x)) - end function polynomial - - elemental real(r8) function derivative(x, degree) result(dq) - real(r8), intent(in) :: x - integer, intent(in) :: degree - dq = 0.0_r8 - if (degree == 3) dq = 3.0_r8 + x * (8.0_r8 + 15.0_r8*x) - end function derivative -end program spline_endpoint_polynomials diff --git a/regression/test/test_spline_endpoint_polynomials.py b/regression/test/test_spline_endpoint_polynomials.py deleted file mode 100644 index 9349a2ad9..000000000 --- a/regression/test/test_spline_endpoint_polynomials.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Endpoint extrapolation must preserve exact low-degree profiles.""" -import os -from pathlib import Path -import shutil -import subprocess -import tempfile -import unittest - - -class SplineEndpointPolynomials(unittest.TestCase): - @unittest.skipUnless(shutil.which('gfortran'), 'gfortran is required') - def test_clustered_grid_preserves_polynomial_derivatives_and_flux(self): - root = Path(__file__).resolve().parents[2] - scratch = Path(os.environ.get('GPEC_TEST_SCRATCH', root / 'build-test')) - scratch.mkdir(exist_ok=True) - with tempfile.TemporaryDirectory(dir=scratch) as directory: - work = Path(directory) - for name in ('io', 'local', 'utils', 'spline', 'cspline'): - source = root / 'equil' / (name + '.f') - if name == 'spline' and os.environ.get('GPEC_SPLINE_SOURCE'): - source = Path(os.environ['GPEC_SPLINE_SOURCE']) - subprocess.run(['gfortran', '-O2', '-ffixed-line-length-none', - '-c', str(source)], cwd=work, check=True, - capture_output=True) - driver = root / 'regression/test/spline_endpoint_polynomials.f90' - subprocess.run(['gfortran', '-O2', str(driver), - *map(str, sorted(work.glob('*.o'))), - '-llapack', '-lblas', '-o', str(work / 'probe')], - cwd=work, check=True, capture_output=True) - result = subprocess.run([str(work / 'probe')], cwd=work, check=True, - capture_output=True, text=True) - for line in result.stdout.splitlines(): - if line.startswith('complex'): - _, resolution, derivative = line.split() - with self.subTest(resolution=resolution, profile='complex'): - self.assertLess(float(derivative), 1e-7) - continue - resolution, degree, derivative, endpoint, integral = line.split() - with self.subTest(resolution=resolution, degree=degree): - self.assertLess(float(derivative), 1e-7) - self.assertLess(float(endpoint), 1e-8) - self.assertLess(float(integral), 1e-10) - - -if __name__ == '__main__': - unittest.main() From 517afc2938f410a7a37bc36e11e845740dbc3388 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 16 Oct 2026 12:23:44 +0200 Subject: [PATCH 225/305] EQUIL - BUGFIX - fix type of bs, cs, ds in classic complex splines to be appropriately complex --- equil/cspline.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/equil/cspline.f b/equil/cspline.f index b03885647..685f2cddb 100644 --- a/equil/cspline.f +++ b/equil/cspline.f @@ -162,7 +162,7 @@ SUBROUTINE cspline_fit_classic(spl,endmode) REAL(r8), DIMENSION(0:spl%mx) :: xfac INTEGER :: iside,iqty,i - REAL(r8),DIMENSION(spl%nqty) :: bs,cs,ds + COMPLEX(r8),DIMENSION(spl%nqty) :: bs,cs,ds c----------------------------------------------------------------------- c extract powers. c----------------------------------------------------------------------- From d4160a9a6d9988d66d5f323231558494d8d1dbff Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 16 Oct 2026 12:41:56 +0200 Subject: [PATCH 226/305] Delete regression/test/spline_root_endpoint.f90 --- regression/test/spline_root_endpoint.f90 | 57 ------------------------ 1 file changed, 57 deletions(-) delete mode 100644 regression/test/spline_root_endpoint.f90 diff --git a/regression/test/spline_root_endpoint.f90 b/regression/test/spline_root_endpoint.f90 deleted file mode 100644 index a772c1e84..000000000 --- a/regression/test/spline_root_endpoint.f90 +++ /dev/null @@ -1,57 +0,0 @@ -! A monotone Hermite cell from a PENTRC trapped orbit. Its unique root lies -! just inside the right endpoint; a rounded Cardano exponent can move the -! initial candidate outside the cell and silently discard the bounce point. -program spline_root_endpoint - use local_mod, only: r8 - use spline_mod, only: spline_type, spline_alloc, spline_dealloc, spline_roots - implicit none - type(spline_type) :: spl - real(r8), parameter :: left = .767578125_r8, right = .76953125_r8 - real(r8), parameter :: fleft = -1.99139065907738555e-3_r8 - real(r8), parameter :: fright = 1.32122017770797129e-8_r8 - real(r8), parameter :: dleft = 1.01883408452285917_r8 - real(r8), parameter :: dright = 1.02042520765343880_r8 - real(r8) :: roots(3), lo, hi, mid, expected, z, value, scale - integer :: i, j, nroots - - ! Independent bisection of the Hermite polynomial with opposite endpoint - ! signs and positive derivative throughout this cell gives its unique root. - lo = 0.0_r8 - hi = 1.0_r8 - do i = 1, 60 - mid = (lo + hi)/2 - if (hermite(mid) < 0) then - lo = mid - else - hi = mid - end if - end do - expected = left + (right - left)*(lo + hi)/2 - do j = -3, 3, 3 - scale = 10.0_r8**j - call spline_alloc(spl, 1, 1) - spl%xs = [left, right] - spl%fs(:, 1) = scale*[fleft, fright] - spl%fs1(:, 1) = scale*[dleft, dright] - call spline_roots(spl, 1, nroots, roots) - print '(a,i0,a,es24.16)', 'roots=', nroots, ' oracle=', expected - if (nroots /= 1) error stop 'Spline lost a bracketed simple root' - if (abs(roots(1) - expected) > 5.0e-14_r8) & - error stop 'Spline root disagrees with independent bracket oracle' - z = (roots(1) - left)/(right - left) - value = hermite(z) - if (z <= 0 .or. z >= 1 .or. abs(value) > 1.0e-13_r8) & - error stop 'Root is outside its bracket or has a large residual' - call spline_dealloc(spl) - end do - print *, 'PASS: bracketed endpoint root survives amplitude scaling' -contains - function hermite(z) result(value) - real(r8), intent(in) :: z - real(r8) :: value - value = (2*z**3 - 3*z**2 + 1)*fleft + & - (-2*z**3 + 3*z**2)*fright + & - (z**3 - 2*z**2 + z)*(right - left)*dleft + & - (z**3 - z**2)*(right - left)*dright - end function hermite -end program spline_root_endpoint From 89d53a7b156cc7cdd1b1ff2465b97dffa97022d0 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 16 Oct 2026 12:44:12 +0200 Subject: [PATCH 227/305] BUILD - MINOR - Remove PENTRC regression check targets We are not changing the testing setup right before releasing the final minor version of the fortran code (v1.0). --- install/TARGETS.inc | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 6c2ce0ce8..e20d93276 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -431,31 +431,3 @@ mkbin: pentrc dcon rdcon stride match rmatch slayer multi sum gpec cp ../sum/sum $(BINDIR) cp ../gpec/gpec $(BINDIR) $(call print_success,All binaries copied to $(BINDIR)) - -# Focused PENTRC regression; CASE contains a normal readable PENTRC input deck. -.PHONY: check-pentrc-radial -check-pentrc-radial: pentrc - $(FCPENTRC) -I../pentrc ../regression/test/pentrc_radial_profile.f90 \ - $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) \ - -o $(BINDIR)/test_pentrc_radial_profile - @test -n "$(CASE)" || { echo 'Set CASE to a PENTRC case directory'; exit 2; } - cd "$(CASE)" && $(abspath $(BINDIR))/test_pentrc_radial_profile - -# Focused PENTRC regression: LSODE failures in the energy integral must stop. -.PHONY: check-pentrc-energy-istate -check-pentrc-energy-istate: pentrc - $(FCPENTRC) -I../pentrc ../regression/test/pentrc_energy_istate.f90 \ - $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) \ - -o $(BINDIR)/test_pentrc_energy_istate - $(BINDIR)/test_pentrc_energy_istate default - ! $(BINDIR)/test_pentrc_energy_istate tight > $(BINDIR)/test_pentrc_energy_istate.log 2>&1 - grep -q "ERROR: xintgrl_lsode - LSODE failed" $(BINDIR)/test_pentrc_energy_istate.log - -# A bracketed cubic root must not be dropped near a cell endpoint. -.PHONY: check-spline-root-endpoint -check-spline-root-endpoint: equil - mkdir -p $(BINDIR) - $(FCEQUIL) -I../equil ../regression/test/spline_root_endpoint.f90 \ - -L$(LIBDIR) -lequil -L$(MATHDIR) $(MATHLIBS) $(LDFLAGS) \ - -o $(BINDIR)/test_spline_root_endpoint - $(BINDIR)/test_spline_root_endpoint From f8ad163bcde0a714e0109e7942089ddcb81c7b8b Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 16 Oct 2026 12:44:57 +0200 Subject: [PATCH 228/305] EQUIL - MINOR - Remove claude-isms in extra comments Removed comments regarding cube-root exponent precision. --- equil/spline.f | 2 -- 1 file changed, 2 deletions(-) diff --git a/equil/spline.f b/equil/spline.f index c4db92b4f..927f2f7ad 100644 --- a/equil/spline.f +++ b/equil/spline.f @@ -1693,8 +1693,6 @@ SUBROUTINE spline_roots(spl, iqty, nroots, roots,op_extrap,op_eps) r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0 m = r**2 - q**3 ! discrimenent IF(m > 0)THEN ! one real root -c Keep the cube-root exponent in the spline's precision. -c A default-real 1/3 can move a valid root outside its cell. s = -sign(1.0_r8,r)*(abs(r)+sqrt(m))**(1.0_r8/3.0_r8) t = q / s z1 = s + t - (a / 3.0) From 13b97d81b8ad34ad9d1c925b4c7aec3bf975300b Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 13:38:59 -0400 Subject: [PATCH 229/305] DOCS - recon: retitle the theory note after the Bernstein-Chance decomposition --- docs/tex/recon/main.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index bee70334e..d795829c0 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -15,7 +15,7 @@ \usepackage{pdflscape} \numberwithin{equation}{section} -\title{DCON \texttt{effective\_field\_flag} document} +\title{Bernstein--Chance decomposition of the perturbed potential energy in GPEC} \author{Sunjae Lee} \date{\today} From b57c4f179974f75b362694daa71967835ce7143c Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 13:42:15 -0400 Subject: [PATCH 230/305] GPEC - MINOR - recon: state the input guard and the equation source in gpec.in comments --- input/gpec.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/input/gpec.in b/input/gpec.in index 64cf42861..9b5fe3612 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -91,12 +91,12 @@ verbose=t ! Print run log to terminal effective_field_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. - standard_form_flag=f ! Calculate the first-form plasma energy terms from Eq. (36) with the gamma|div xi|^2 term omitted. + standard_form_flag=f ! Calculate the first-form plasma energy terms of Eq. (3.1) in docs/tex/recon/main.tex with the gamma|div xi|^2 term omitted. effective_field_terms_flag=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. curl_check_flag=f ! Under effective_field_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under effective_field_flag/standard_form_flag/effective_field_terms_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. recon_int="spline" ! effective_field_flag radial integration on psifac grid. Options: "spline" or "trapezoid". - ! For DCON eigenmode reconstruction set coil_flag=f, mode_flag=t, choose mode as needed, and keep reg_flag=f. + ! effective_field_flag, standard_form_flag and effective_field_terms_flag require mode_flag=t (choose mode) with coil_flag, data_flag, harmonic_flag and reg_flag all f; GPEC stops at input otherwise. / &GPEC_DIAGNOSE timeit=f ! Print timer splits for major subroutines From df990fce2e3ead6c47b200c80651e6e7b8202ff3 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 14:04:38 -0400 Subject: [PATCH 231/305] GPEC - recon: print the integrated psi range beside the DCON comparison --- docs/tex/recon/main.tex | 5 ++++- gpec/gpout.f | 38 +++++++++++++++++++++++++++++++++++++- input/gpec.in | 2 +- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index d795829c0..670e190bb 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -2224,7 +2224,10 @@ \section{Log-output quantities and their exact integrands} $\psi$ integration on the \texttt{psifac} grid. Every $\delta W_p$ total reported below is built with this scheme, so the choice affects the reported numbers at the level of the quadrature - error. + error. Each route prints the $\psi$ range it integrated over + (\texttt{recon psi range}) next to the DCON range + $[\psi_{\rm low},\psi_{\rm lim}]$ (\texttt{DCON psi range}), so a + truncated integration is visible beside the $\delta W_p$ comparison. \item \texttt{curl\_check\_flag} (default \texttt{.FALSE.}) enables the $(\nabla\times\vec C)\cdot\nabla\psi=0$ verification of \S\ref{par:cveri} alongside \texttt{effective\_field\_flag}. The pointwise diff --git a/gpec/gpout.f b/gpec/gpout.f index 24d0d3a7f..853a4737e 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -6935,6 +6935,7 @@ SUBROUTINE gpout_recon(mode, xspmn) REAL(r8) :: dpsi_local INTEGER :: istep, nint_pts, iint, ep_index REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr, ep_selected + REAL(r8) :: psi_lo, psi_hi REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr REAL(r8) :: epf_int_total_hr, epf_p_total_hr, @@ -7348,6 +7349,8 @@ SUBROUTINE gpout_recon(mode, xspmn) $ REAL(dst3_vals(iint)) ENDDO ENDIF + psi_lo = psi_int_pts(1) + psi_hi = psi_int_pts(nint_pts) DEALLOCATE(psi_int_pts, epf_vals, epf_p_vals, epf_t_vals, $ epf_z_vals, dst_vals, dst1_vals, dst2_vals, dst3_vals) dw_raw_k_hr = 0.5_r8 * @@ -7401,6 +7404,10 @@ SUBROUTINE gpout_recon(mode, xspmn) $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", $ ep_selected * (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim IF (curl_check_flag) THEN cveri_stat = "PASS" IF (cveri_count > 0) THEN @@ -7456,6 +7463,10 @@ SUBROUTINE gpout_recon(mode, xspmn) WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), $ ") = ", ep_selected * $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim IF (curl_check_flag) THEN WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max @@ -7520,7 +7531,7 @@ SUBROUTINE gpout_recon2(mode, xspmn) CHARACTER(8) :: smode CHARACTER(128) :: surf_file, int_file INTEGER :: ep_index - REAL(r8) :: ep_selected, ep_dcon + REAL(r8) :: ep_selected, ep_dcon, psi_lo, psi_hi REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, q2_vals_r COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: q2_vals, jqx_vals, $ pdiv_vals, total_vals @@ -7591,10 +7602,14 @@ SUBROUTINE gpout_recon2(mode, xspmn) c integrate on psifac points inside the spline domain; points c below rzphi%xs(0) are skipped (gpout_recon clamps instead). nint_pts = 0 + psi_lo = 0.0_r8 + psi_hi = 0.0_r8 DO istep = 0, mstep IF (psifac(istep) < rzphi%xs(0)) CYCLE IF (psifac(istep) > rzphi%xs(mpsi)) EXIT nint_pts = nint_pts + 1 + IF (nint_pts == 1) psi_lo = psifac(istep) + psi_hi = psifac(istep) ENDDO ALLOCATE(psi_int_pts(nint_pts), q2_vals_r(nint_pts), $ q2_vals(nint_pts), jqx_vals(nint_pts), pdiv_vals(nint_pts), @@ -7706,6 +7721,10 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", $ ep_dcon + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim IF (recon_out) THEN OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", @@ -7728,6 +7747,10 @@ SUBROUTINE gpout_recon2(mode, xspmn) $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), $ ") = ", ep_dcon + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim WRITE(u_log,*) CLOSE(u_log) ENDIF @@ -7778,6 +7801,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) REAL(r8) :: dst1_total, dst3_total REAL(r8) :: identity_residual REAL(r8) :: dw_raw3, dw_dcon3, ep_selected, ep_dcon + REAL(r8) :: psi_lo, psi_hi INTEGER :: ep_index COMPLEX(r8) :: dst_int_c, dst1_c, dst2_c, dst3_c REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals @@ -7849,10 +7873,14 @@ SUBROUTINE gpout_recon3(mode, xspmn) c integrate on psifac points inside the spline domain; points c below rzphi%xs(0) are skipped (gpout_recon clamps instead). nint_pts = 0 + psi_lo = 0.0_r8 + psi_hi = 0.0_r8 DO istep = 0, mstep IF (psifac(istep) < rzphi%xs(0)) CYCLE IF (psifac(istep) > rzphi%xs(mpsi)) EXIT nint_pts = nint_pts + 1 + IF (nint_pts == 1) psi_lo = psifac(istep) + psi_hi = psifac(istep) ENDDO ALLOCATE(psi_int_pts(nint_pts), A_vals(nint_pts), $ B_vals(nint_pts), Iperp_vals(nint_pts), Cpar_vals(nint_pts), @@ -8001,6 +8029,10 @@ SUBROUTINE gpout_recon3(mode, xspmn) $ ep_selected WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", $ ep_dcon + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim IF (recon_out) THEN OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", @@ -8026,6 +8058,10 @@ SUBROUTINE gpout_recon3(mode, xspmn) $ ") = ", ep_selected WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), $ ") = ", ep_dcon + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim WRITE(u_log,*) CLOSE(u_log) ENDIF diff --git a/input/gpec.in b/input/gpec.in index 9b5fe3612..a4b43aff5 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -95,7 +95,7 @@ effective_field_terms_flag=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. curl_check_flag=f ! Under effective_field_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. recon_out=t ! Under effective_field_flag/standard_form_flag/effective_field_terms_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. - recon_int="spline" ! effective_field_flag radial integration on psifac grid. Options: "spline" or "trapezoid". + recon_int="spline" ! Radial quadrature of all three routes on the psifac grid: "spline" (default) or "trapezoid". The integrated psi range is printed beside the DCON psilow-psilim range. ! effective_field_flag, standard_form_flag and effective_field_terms_flag require mode_flag=t (choose mode) with coil_flag, data_flag, harmonic_flag and reg_flag all f; GPEC stops at input otherwise. / &GPEC_DIAGNOSE From aa88e640db1469c5f96f4949baf19ee9979f5f43 Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 14:04:40 -0400 Subject: [PATCH 232/305] DOCS - recon: document energy decomposition outputs and MATCH sol_num --- docs/namelists.rst | 8 ++++++++ docs/outputs.rst | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/docs/namelists.rst b/docs/namelists.rst index b04b1d7f6..4629dade7 100644 --- a/docs/namelists.rst +++ b/docs/namelists.rst @@ -29,6 +29,14 @@ This namelist sets the inputs, controls the behavior, and determines the outputs .. include:: ../input/gpec.in :literal: +The MATCH Namelist +================== + +This namelist controls the MATCH code, including which DCON eigenfunction (sol_num) it reconstructs. + +.. include:: ../input/match.in + :literal: + The PENTRC Namelist ==================== diff --git a/docs/outputs.rst b/docs/outputs.rst index 22bce6bf7..ddec2b51a 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -208,6 +208,44 @@ A number of output files have a similar structure. Here the * in the file name i **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current from the external fields alone. +Energy Decomposition Outputs + +These outputs reconstruct the ideal plasma energy :math:`\delta W_p` of a single DCON eigenmode on every flux surface and break it into physically distinct terms. They require a pure DCON eigenmode: set mode_flag=t, choose mode (1 is the least stable eigenmode), and keep reg_flag=f and coil_flag=data_flag=harmonic_flag=f. GPEC stops at input if any of the three decomposition flags is set otherwise. The derivations, integrands and the meaning of every printed quantity are documented in :download:`docs/tex/recon/main.tex ` (build with pdflatex and bibtex). + +Each route prints a summary to the terminal that ends with its :math:`\delta W_p` total, the same total in DCON normalization (dW_p(recon-normalize) and its analogues), the DCON eigenvalue ep(mode) it should reproduce, and the :math:`\psi` range it integrated over beside the DCON range psilow to psilim. In the files below is the selected mode. recon_out=f keeps only the terminal summary, and recon_int ("spline", the default, or "trapezoid") selects the radial quadrature. + +gpec_recon_*_sol.out + + **Flag** effective_field_flag + + **Info** Bernstein effective-field form :math:`2\delta W_p = \int |\mathbf{C}|^2/\mu_0 - \int K \xi_n^2`. integration: radial integrands and running totals. terms: per-(psi,theta) densities of the C and K terms. k, k_sigma_check, shear_fun, curvature: the K kernel and its shear, parallel-current and curvature parts on the (psi,theta,R,Z) grid. cwfun/cwmn, cvfun/cvmn, cv2fun/cv2mn: contravariant and covariant components of C in real and mode space. qvmn: covariant components of the bare perturbed field Q. A summary block is appended to gpec.log. + +gpec_recon_cveri_sol.out + + **Flag** curl_check_flag (with effective_field_flag) + + **Info** Pointwise residual of :math:`(\nabla\times\mathbf{C})\cdot\nabla\psi = 0` on the (psi,theta,R,Z) grid. The terminal and gpec.log report PASS or WARN for the maximum residual relative to the size of the two cancelling terms :math:`(|\partial_\theta C_\zeta|+|\partial_\zeta C_\theta|)/|J|`, together with the absolute maximum and rms. + +gpec_recon2_terms_sol.out, gpec_recon2_integration_sol.out + + **Flag** standard_form_flag + + **Info** Standard (first) form of the energy with the :math:`\gamma|\nabla\cdot\xi|^2` term omitted: :math:`|Q|^2/\mu_0`, :math:`\mathbf{j}\cdot(\mathbf{Q}\times\xi^*)`, and :math:`(\nabla\cdot\xi)^*(\xi\cdot\nabla p)` densities per surface (terms) and their radial integrals (integration). + +gpec_recon3_terms_sol.out, gpec_recon3_integration_sol.out + + **Flag** effective_field_terms_flag + + **Info** Split of :math:`|\mathbf{C}|^2 = |\mathbf{Q}|^2 + 2\mathrm{Re}(\mathbf{V}^*\cdot\mathbf{Q}) + |\mathbf{V}|^2` into A, B, Cpar and Iperp, with the identity check epf = A + B + Cpar + Iperp, per surface (terms) and integrated (integration). + +MATCH Outputs + +solutions.bin, solutions.bin + + **Flag** sol_num (match.in) + + **Info** Ideal Euler-Lagrange solution built from DCON eigenfunction sol_num (1 is the most unstable). sol_num=1 writes solutions.bin; sol_num=n>1 writes solutions.bin so several eigenfunctions can be kept side by side. + Binary File Outputs ------------------- From 2616e3f9e77c33123de1689375e939fbeb78615e Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 14:04:42 -0400 Subject: [PATCH 233/305] GPEC - recon: add Soloviev energy decomposition regression example --- .github/workflows/make_test.yml | 18 ++++ .../regression_solovev_recon_example/README | 27 +++++ .../check_recon.py | 66 +++++++++++++ .../regression_solovev_recon_example/dcon.in | 61 ++++++++++++ .../regression_solovev_recon_example/equil.in | 30 ++++++ .../regression_solovev_recon_example/gpec.in | 92 +++++++++++++++++ .../regression_solovev_recon_example/sol.in | 14 +++ .../regression_solovev_recon_example/vac.in | 99 +++++++++++++++++++ 8 files changed, 407 insertions(+) create mode 100644 docs/examples/regression_solovev_recon_example/README create mode 100755 docs/examples/regression_solovev_recon_example/check_recon.py create mode 100644 docs/examples/regression_solovev_recon_example/dcon.in create mode 100644 docs/examples/regression_solovev_recon_example/equil.in create mode 100644 docs/examples/regression_solovev_recon_example/gpec.in create mode 100644 docs/examples/regression_solovev_recon_example/sol.in create mode 100644 docs/examples/regression_solovev_recon_example/vac.in diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index d8077abcf..0e5044617 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -231,6 +231,14 @@ jobs: ulimit -s unlimited ../../../bin/rdcon && ../../../bin/rmatch && ../../../bin/gpec && ../../../bin/stride + - name: Run Soloviev energy decomposition example + run: | + export GPECHOME=$(pwd) + export LD_LIBRARY_PATH=$PREFIX/lib:$LD_LIBRARY_PATH + cd docs/examples/regression_solovev_recon_example + ulimit -s unlimited + ../../../bin/dcon && ../../../bin/gpec && python3 check_recon.py + make_gpec_ifort: runs-on: ubuntu-latest needs: build-netcdf-ifort @@ -310,3 +318,13 @@ jobs: cd docs/examples/regression_solovev_resistive_example ulimit -s unlimited ../../../bin/rdcon && ../../../bin/rmatch && ../../../bin/gpec && ../../../bin/stride + + - name: Run Soloviev energy decomposition example + run: | + source /opt/intel/oneapi/setvars.sh + export GPECHOME=$(pwd) + export LD_LIBRARY_PATH=$PREFIX/lib:$LD_LIBRARY_PATH + export FOR_DISABLE_STACK_TRACE=TRUE + cd docs/examples/regression_solovev_recon_example + ulimit -s unlimited + ../../../bin/dcon && ../../../bin/gpec && python3 check_recon.py diff --git a/docs/examples/regression_solovev_recon_example/README b/docs/examples/regression_solovev_recon_example/README new file mode 100644 index 000000000..4bcd606d9 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/README @@ -0,0 +1,27 @@ +Soloviev Energy Decomposition Regression Example +================================================ + +This example checks the GPEC ideal energy decomposition (effective_field_flag) against the DCON eigenvalue it reconstructs. It reuses the Soloviev equilibrium of regression_solovev_ideal_example (sol.in, equil.in, vac.in are copies). dcon.in differs only in delta_mlow=delta_mhigh=8: the decomposition is built in real space and Fourier truncated, so the default 8 poloidal harmonics leave a ~21% error, while 32 harmonics bring it to 0.4%. + +gpec.in selects the least stable eigenmode (mode_flag=t, mode=1) with reg_flag=f and no external field, turns off the usual outputs, and sets effective_field_flag=t and curl_check_flag=t. + +Run from this directory:: + + ../../../bin/dcon + ../../../bin/gpec + python3 check_recon.py + +check_recon.py reads the last GPEC_RECON block of gpec.log (gpec.log is appended to, so delete it before re-running by hand) and passes when + +- the recon psi range equals the DCON psi range (psilow to psilim), +- dW_p(recon-normalize) matches DCON ep(1) within 2% relative, and +- the curl check reports PASS. + +Reference values (gfortran 15, macOS arm64):: + + recon psi range = 1.0E-04 9.9999E-01 (same as DCON psi range) + dW_p(recon-normalize) = -1.04353063E+001 + DCON ep(1) = -1.03926066E+001 (relative difference 4.1E-03) + C verify: PASS (rel 2.8E-13 against tol 1E-08) + +The 2% tolerance leaves room for compiler and optimization differences while still catching a broken decomposition: the truncated 8-harmonic run above misses by 21%. diff --git a/docs/examples/regression_solovev_recon_example/check_recon.py b/docs/examples/regression_solovev_recon_example/check_recon.py new file mode 100755 index 000000000..55570eb87 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/check_recon.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Regression check for the GPEC energy decomposition (effective_field_flag). + +Run after ``dcon`` and ``gpec`` in this directory. Reads the last +GPEC_RECON block of gpec.log and checks that + + * the recon psi range covers the DCON psi range (psilow to psilim), + * dW_p(recon-normalize) matches DCON ep(mode) within REL_TOL, and + * the curl check reports PASS. + +Exits 0 on success and 1 on failure, so it can be chained in CI. +""" + +import sys + +REL_TOL = 0.02 # reference run agrees to 0.4%; see README +RANGE_TOL = 1e-6 + + +def last_recon_block(path): + with open(path) as f: + lines = f.read().splitlines() + starts = [i for i, l in enumerate(lines) + if l.startswith("GPEC_RECON final results:")] + if not starts: + sys.exit("FAIL: no GPEC_RECON block in " + path) + block = [] + for l in lines[starts[-1] + 1:]: + if l.startswith("GPEC_RECON"): + break + block.append(l) + return block + + +def values(block, key): + for l in block: + if key in l: + return [float(v) for v in l.split("=")[-1].split()] + sys.exit("FAIL: '{}' not found in GPEC_RECON block".format(key)) + + +def main(): + block = last_recon_block("gpec.log") + dw = values(block, "dW_p(recon-normalize)")[0] + ep = values(block, "DCON ep(")[0] + rlo, rhi = values(block, "recon psi range") + dlo, dhi = values(block, "DCON psi range") + rel = abs(dw - ep) / abs(ep) + verdict = [l.split(":")[-1].strip() for l in block if "C verify" in l] + range_ok = abs(rlo - dlo) <= RANGE_TOL and abs(rhi - dhi) <= RANGE_TOL + print("recon psi range = {:.6e} {:.6e}".format(rlo, rhi)) + print("DCON psi range = {:.6e} {:.6e}".format(dlo, dhi)) + print("dW_p(recon-normalize) = {: .8e}".format(dw)) + print("DCON ep = {: .8e}".format(ep)) + print("relative difference = {:.3e} (tol {:.1e})".format(rel, REL_TOL)) + print("curl check = {}".format(verdict[-1] if verdict + else "not run")) + ok = (range_ok and rel <= REL_TOL and bool(verdict) + and verdict[-1] == "PASS") + print("PASS" if ok else "FAIL") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/examples/regression_solovev_recon_example/dcon.in b/docs/examples/regression_solovev_recon_example/dcon.in new file mode 100644 index 000000000..2b6b12189 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/dcon.in @@ -0,0 +1,61 @@ +&DCON_CONTROL + bal_flag=f ! Ideal MHD ballooning criterion for short wavelengths + mat_flag=t ! Construct coefficient matrices for diagnostic purposes + ode_flag=t ! Integrate ODE's for determining stability of internal long-wavelength mode (must be true for GPEC) + vac_flag=t ! Compute plasma, vacuum, and total energies for free-boundary modes + mer_flag=t ! Evaluate the Mercier criterian + + sas_flag=f ! Safety factor (q) limit determined as q_ir+dmlim where q_ir is the equil outermost rational + dmlim=0.2 ! See sas_flag + qlow=1.02 ! Integration initiated at q determined by minimum of qlow and q0 from equil + qhigh=1e3 ! Integration terminated at q limit determined by minimum of qhigh and qa from equil + sing_start=0 ! Start integration at the sing_start'th rational from the axis (psilow) + + nn=1 ! Toroidal mode number + delta_mlow=8 ! Expands lower bound of Fourier harmonics + delta_mhigh=8 ! Expands upper bound of Fourier harmonics + delta_mband=0 ! Integration keeps only this wide a band of solutions along the diagonal in m,m' + mthvac=64 ! Number of points used in splines over poloidal angle at plasma-vacuum interface. Overrides vac.in mth. + thmax0=1 ! Linear multiplier on the automatic choice of theta integration bounds for high-n ideal ballooning stability computation (strictly, -inf to -inf) + + kin_flag = f ! Kinetic EL equation (default: false) + con_flag = f ! Continue integration through layers (default: false) + kinfac1 = 1.0 ! Scale factor for energy contribution (default : 1.0) + kinfac2 = 1.0 ! Scale factor for torque contribution (default : 1.0) + kingridtype = 0 ! Regular grid method (default : 0) + passing_flag = f ! Includes passing particle effects (default: false) + ktanh_flag = t ! Ignore kinetic effects in the core smoothly (default: false) + ktc = 0.1 ! Parameter activated by ktanh_flag: roughly corresponds to core width ignored (default: 0.1) + ktw = 50.0 ! Parameter activated by ktanh_flag: width of hyper-tangential functions (default: 50.0) + ion_flag = t ! Include ion dW_k when kin_flag is true (summed with electron contribution if electron_flag true) + electron_flag = f ! Include electron dW_k when kin_flag is true (summed with ion contribution if ion_flag true) + + tol_nr=1e-6 ! Relative tolerance of dynamic integration steps away from rationals + tol_r=1e-7 ! Relative tolerance of dynamic integration steps near rationals + crossover=1e-2 ! Fractional distance from rational q at which tolerance is switched to tol_r + singfac_min=1e-4 ! Fractional distance from rational q at which ideal jump condition is enforced + ucrit=1e3 ! Maximum fraction of solutions allowed before re-normalized + + wv_farwall_flag = f ! Force nowall gpec calculations while calculating mutual inductance with the wall, when set true. + termbycross_flag = f ! Terminate ODE solver in the event of a zero crossing + + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions +/ +&DCON_OUTPUT + crit_break=t ! Color of the crit curve changes when crossing a singular surface + + ahb_flag=f ! Output normal magnetic field eigenvalues and eigenfunctons at plasma-vacuum interface (must be false for GPEC) + msol_ahb=1 ! Number of eigenfunctions output by ahb_flag=t ? + mthsurf0=1 ! Linear multiplier on number of boundary points used to display surface eigenfunctions for ahgb_flag=t + + bin_euler=t ! Output M psi-by-M euler-lagrange solutions to binary file euler.bin + bin_vac=f ! Output deprecated binary vacuum.bin containing vacuum energy matrices + euler_stride=1 ! Output only every euler_stride'th psi step to binary file + + out_bal1=f ! Ascii output for bal_flag poloidal functions + bin_bal1=f ! Binary output for bal_flag poloidal functions + out_bal2=f ! Ascii output for bal_flag functions + bin_bal2=f ! Binary output for bal_flag functions + + netcdf_out=t ! Replicate ascii dcon.out information in a netcdf file +/ diff --git a/docs/examples/regression_solovev_recon_example/equil.in b/docs/examples/regression_solovev_recon_example/equil.in new file mode 100644 index 000000000..994a522b4 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/equil.in @@ -0,0 +1,30 @@ +&EQUIL_CONTROL + eq_type="soloviev" ! Type of the input 2D equilibrium file. Accepts efit, chease, fluxgrid, transp, jsolver, lar, sol, etc. + eq_filename = "sol.in" ! Path to input file + + jac_type="pest" ! Working coordinate system for all DCON and GPEC calculations. Overrides individual powers. Accepts hamada, pest, boozer, equal_arc + power_bp=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + power_b=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + power_r=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + + grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. + psilow=1e-4 ! Minimum value of psi, normalized from 0 to 1 + psihigh=0.99999 ! Maximum value of psi, normalized from 0 to 1 + mpsi=16 ! Number of radial grid intervals for equilibrium quantities + mtheta=256 ! Number of equally spaced poloidal grid intervals for all splines + newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions + + input_only=f ! Generate information about the input and then quit with no further calculation +/ +&EQUIL_OUTPUT + gse_flag=f ! Produces diagnostic output for accuracy of solution to Grad-Shafranov equation + out_eq_1d=f ! Ascii output of 1D equilibrium file data + bin_eq_1d=f ! Binary output of 1D equilibrium file data + out_eq_2d=f ! Ascii output of 2D equilibrium file data + bin_eq_2d=t ! Binary output of 2D equilibrium file data (set true for GPEC) + out_2d=f ! Ascii output of processed 2D data + bin_2d=f ! Binary output of processed 2D data + dump_flag=f ! Binary dump of basic equilibrium data and 2D rzphi spline + out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. +/ diff --git a/docs/examples/regression_solovev_recon_example/gpec.in b/docs/examples/regression_solovev_recon_example/gpec.in new file mode 100644 index 000000000..b4ff8b6e4 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/gpec.in @@ -0,0 +1,92 @@ +&GPEC_INPUT + dcon_dir = './' ! Path to DCON outputs. Individual file paths are appended to this + idconfile = 'euler.bin' ! Eigenfunctions output by DCON run + ieqfile = 'psi_in.bin' ! Flux surfaces output by DCON run + rdconfile = 'globalsol.bin' ! Galerkin eigenfunctions output by resistive DCON run + + gal_flag = f ! Use the Galerkin eigenfunctions from resistive DCON instead of the ideal DCON shooting method solutions + + jac_in="pest" ! Coordinate system of external perturbation (affects data_flag and harmonic_flag). Options include: pest,boozer,hamada + jsurf_in=0 ! True(1) if external perturbation is area weighted (i.e. flux) + tmag_in =0 ! True(1) if external perturbation has toroidal angle defined by jac_in. False(0) if machine angle. + mthsurf =0 ! Number of poloidal angle gridpoints. Minimum of twice the Nyquist condition from DCON m limits is enforced. + + coil_flag=f ! Calculate external perturbation from coils in coil.in + + data_flag=f ! Apply perturbation from thrid party code output + data_type="surfmn" ! Name of said code. Choose: surfmn,?? + infile="path/to/file" ! Path to said output + nmin=0 ! Extra parameters needed to read surfmn files. + nmax=64 ! Extra parameters needed to read surfmn files. + mmin=-64 ! Extra parameters needed to read surfmn files. + mmax=64 ! Extra parameters needed to read surfmn files. + + harmonic_flag=f ! Apply self-defined perturbations + cosmn(3)=1e-4 ! Real part of m=3 component (n defined by DCON run). + sinmn(6)=1e-4 ! Imaginary part of m=6 component. etc. + + displacement_flag=f ! Perturbations are displacement (m). Default is field (T). + fixed_boundary_flag=f ! Total perturbation = external perturbation + + mode_flag = t ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) + mode = 1 ! DCON eigen function index (1 is least stable) + filter_types = 'w' ! Isolate the filter_modes of energy (w), reluctance (r), permiability (p) or singular coupling (s) eigenvectors/singular-vectors + filter_modes = 0 ! Number of modes isolated, negative values isolate backwards from the last mode +/ +&GPEC_CONTROL + resp_index=0 ! Integer 0-4, determining method of inductance matrix formation. 0 uses energy identity, and is recommended. + sing_spot=5e-4 ! Minimum distance to singular surface in (m-nq) space + sing_resspot=1.0 ! If use_res_spot=t, use inverse cube root of lundquist number scaled by sing_resspot in psi space as spot size + use_res_spot=f ! Use resistive singular layer width instead of fixed sing_spot + sing_interpspot=1.0 ! Scaling of the interpolation width, in units of spot size, for smoothed outputs of singular surface quantities + sing_npsi=100 ! Number of points between rational surfaces used for b-normal splines across sin_spot + reg_flag=f ! Regularize the solution near resonant surfaces + reg_spot=5e-2 ! Regularize by (m-nq)^2/((m-nq)^2+reg_spot^2) + chebyshev_flag=f ! Fit Chebyshev polynomials to the displacement. + nche=20 ! Number of Chebyshev polynomials used + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions +/ +&GPEC_OUTPUT + effective_field_flag=t ! Bernstein effective-field reconstruction of dW_p (needs mode_flag=t, reg_flag=f, coil_flag=f) + curl_check_flag=t ! Check (curl C).grad(psi)=0 and report PASS/WARN + recon_out=t ! Write recon files and the gpec.log summary read by check_recon.py + jac_out="pest" ! Coordinate system of outputs + jsurf_out=0 ! True(1) for area weighted outputs (only appends standard gpec_control output) + tmag_out=1 ! True(1) for magnetic toroidal angle outputs (affects singcoup and singfld, appends gpec_control) + mlim_out=64 ! If positive, Fourier decomposed outputs will span -mlim_out<=m<=mlim_out. Default (<=0) outputs on internal m range. + + resp_flag=f ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices + filter_flag=f ! Outputs energy, reluctance, permeability, and (optionally) the singular-coupling eigenmodes on the control surface + singcoup_flag=f ! Calculate coupling of each m to resonant surfaces + singfld_flag=f ! Output of resonant surface quantities (flux,current,etc.) + vsingfld_flag=f ! Output of vacuum resonant surface quantities + + pmodb_flag=f ! Outputs delta-B_Lagrangian + xbnormal_flag=f ! Outputs normal displacement and field in plasma + vbnormal_flag=f ! Outputs vacuum normal displacement and field in plasma + xclebsch_flag=f ! Outputs clebsch coordinate displacement for PENT + dw_flag=f ! Outputs self-consistent energy and torque profiles (when kin_flag=t from DCON) + + eqbrzphi_flag=f ! Outputs equilibrium field on regular (r,z) grid + brzphi_flag=f ! Outputs perturbed field on regular (r,z) grid + xrzphi_flag=f ! Outputs displacement on regular (r,z) grid + vbrzphi_flag=f ! Outputs field on regular (r,z) grid due to total boundary surface current (not a real field) + vvbrzphi_flag=f ! Outputs field on regular (r,z) grid due to external perturbation surface current (not a real field) + vsbrzphi_flag=f ! Outputs the field on regular (r,z) grid due to singular surface currents + ss_flag(3)=f ! Outputs the field on regular (r,z) grid due to the 3rd singular surface current, etc. + xbrzphifun_flag=f ! Outputs r,z, and perturbation vectors on psi,theta grid + arzphifun_flag=f ! Outputs r,z, and vector potential on psi,theta grid + + nr = 3 ! Number of radial grid points in rzphi outputs + nz = 3 ! Number of vertical grid points in rzphi outputs + fun_flag=f ! Write outputs to (r,z) in addition to default (psi,m) + flux_flag=f ! Write outputs to (psi,theta) in addition to default (psi,m) + bin_flag=f ! Write binary outputs for use with xdraw + bin_2d_flag=f ! Write 2D binary outputs for use with xdraw + max_linesout=0 ! Maximum length of ascii data tables enforced by increasing radial step size (iff .gt. 0). + verbose=t ! Print run log to terminal +/ +&GPEC_DIAGNOSE + timeit=t ! Print timer splits for major subroutines + radvar_flag=f ! Map various radial variables (rho,psi_tor) on psi_n grid +/ diff --git a/docs/examples/regression_solovev_recon_example/sol.in b/docs/examples/regression_solovev_recon_example/sol.in new file mode 100644 index 000000000..e7aba88fe --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/sol.in @@ -0,0 +1,14 @@ +! Construct Soloviev equilibrium +&SOL_INPUT + mr = 128 ! number of radial grid zones + mz = 128 ! number of axial grid zones + ma = 128 ! number of flux grid zones + e = 1.6 ! elongation + a = 0.33 ! minor radius + r0 = 1.0 ! major radius + q0 = 1.9 ! safety factor at the o-point + p0fac=1 ! scale on-axis pressure (P-> P+P0*p0fac. beta changes. Phi,q constant) + b0fac=1 ! scale toroidal field at constant beta (s*Phi,s*f,s^2*P. bt changes. Shape,beta constant) + f0fac=1 ! scale toroidal field at constant pressure (s*f. beta,q changes. Phi,p,bp constant) +/ + diff --git a/docs/examples/regression_solovev_recon_example/vac.in b/docs/examples/regression_solovev_recon_example/vac.in new file mode 100644 index 000000000..7be7c808d --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/vac.in @@ -0,0 +1,99 @@ +! **** Running DCON's input *** + +&MODES + mth = 32 + xiin(1:9) = 0 0 0 0 0 0 0 1 0 + lsymz = .TRUE. + leqarcw = 1 + lzio = 0 + lgato = 0 + lrgato = 0 +/ +&DEBUGS + checkd = .FALSE. + check1 = .FALSE. + check2 = .FALSE. + checke = .FALSE. + checks = .FALSE. + wall = .FALSE. + lkplt = 0 +/ +&VACDAT + ishape = 6 + aw = 0.05 + bw = 1.5 + cw = 0 + dw = 0.5 + tw = 0.05 + nsing = 500 + epsq = 1e-05 + noutv = 37 + idgt = 6 + idot = 0 + idsk = 0 + delg = 15.01 + delfac = 0.001 + cn0 = 1 +/ +&SHAPE + ipshp = 0 + xpl = 100 + apl = 1 + a = 0.2415 + b = 170 + bpl = 1 + dpl = 0 + r = 1 + abulg = 0.932 + bbulg = 17.0 + tbulg = 0.02 + qain = 2.5 +/ +&DIAGNS + lkdis = .FALSE. + ieig = 0 + iloop = 0 + lpsub = 1 + nloop = 128 + nloopr = 0 + nphil = 3 + nphse = 1 + xofsl = 0 + ntloop = 32 + aloop = 0.01 + bloop = 1.6 + dloop = 0.5 + rloop = 1.0 + deloop = 0.001 + mx = 21 + mz = 21 + nph = 0 + nxlpin = 6 + nzlpin = 11 + epslp = 0.02 + xlpmin = 0.7 + xlpmax = 2.7 + zlpmin = -1.5 + zlpmax = 1.5 + linterior = 2 +/ +&SPRK + nminus = 0 + nplus = 0 + mphi = 16 + lwrt11 = 0 + civ = 0.0 + sp2sgn1 = 1 + sp2sgn2 = 1 + sp2sgn3 = 1 + sp2sgn4 = 1 + sp2sgn5 = 1 + sp3sgn1 = -1 + sp3sgn2 = -1 + sp3sgn3 = -1 + sp3sgn4 = 1 + sp3sgn5 = 1 + lff = 0 + ff = 1.6 + fv = 1.6 1.6 1.6 1.6 1.6 1.0 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 +/ From 93770922dc135f0771785ae3c6c33d4fd8f1c82e Mon Sep 17 00:00:00 2001 From: satelite2517 Date: Fri, 16 Oct 2026 14:43:51 -0400 Subject: [PATCH 234/305] GPEC - MINOR - recon: guard the R,Z sqrt and fix a stale routine name in the theory note --- docs/tex/recon/main.tex | 2 +- gpec/gpeq.f | 4 ++-- gpec/gpout.f | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex index 670e190bb..e66099b3c 100644 --- a/docs/tex/recon/main.tex +++ b/docs/tex/recon/main.tex @@ -2209,7 +2209,7 @@ \section{Log-output quantities and their exact integrands} \texttt{standard\_form\_flag} (\texttt{GPEC\_RECON2}, driver \texttt{gpout\_recon2} $\to$ \texttt{gpeq\_firstform}), and \texttt{effective\_field\_terms\_flag} (\texttt{GPEC\_RECON3}, driver \texttt{gpout\_recon3} $\to$ -\texttt{gpeq\_recon3}). +\texttt{gpeq\_epfterms}). Three further switches in \texttt{\&GPEC\_OUTPUT} control how these diagnostics run: diff --git a/gpec/gpeq.f b/gpec/gpeq.f index bf32414d7..c55091b60 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -46,8 +46,8 @@ MODULE gpeq_mod USE idcon_mod IMPLICIT NONE -c floor for recon divisors (rfac, |grad psi|^2, B^2) so the -c recon kernels stay finite if called on the magnetic axis. +c floor for the recon divisors rfac, |grad psi|^2 and B^2, which +c vanish on the magnetic axis; the Jacobian is assumed nonzero. REAL(r8), PARAMETER, PRIVATE :: recon_tiny = 1.0e-30_r8 CONTAINS diff --git a/gpec/gpout.f b/gpec/gpout.f index 853a4737e..7b9e3a583 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7108,7 +7108,7 @@ SUBROUTINE gpout_recon(mode, xspmn) $ dst2_den_fun=dst2_den_fun, dst3_den_fun=dst3_den_fun) DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) - rfac = SQRT(rzphi%f(1)) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) eta = twopi*(theta(itheta) + rzphi%f(2)) rvals(itheta) = ro + rfac*COS(eta) zvals(itheta) = zo + rfac*SIN(eta) @@ -7576,7 +7576,7 @@ SUBROUTINE gpout_recon2(mode, xspmn) IF (recon_out) THEN DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) - rfac = SQRT(rzphi%f(1)) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) eta = twopi*(theta(itheta) + rzphi%f(2)) rvals(itheta) = ro + rfac*COS(eta) zvals(itheta) = zo + rfac*SIN(eta) @@ -7857,7 +7857,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) IF (recon_out) THEN DO itheta = 0, mthsurf CALL bicube_eval(rzphi, psi, theta(itheta), 1) - rfac = SQRT(rzphi%f(1)) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) eta = twopi*(theta(itheta) + rzphi%f(2)) rvals(itheta) = ro + rfac*COS(eta) zvals(itheta) = zo + rfac*SIN(eta) From 76723ee33fce4b47a5041d27cba858942360012b Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sat, 17 Oct 2026 08:07:18 +0200 Subject: [PATCH 235/305] Keep PENTRC small collision damping positive when precession reverses Chris&AI --- install/TARGETS.inc | 7 +++++ pentrc/energy.f90 | 6 ++-- regression/test/pentrc_small_width.f90 | 41 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 regression/test/pentrc_small_width.f90 diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..7f9b2a2e2 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -177,6 +177,13 @@ pentrc: v neededdeps $(PENTRC_OBJS) lsode equil ar -r $(LIBDIR)/libpentrc.a $(PENTRC_OBJS) $(call print_success,pentrc -> $(BINDIR)/pentrc and libpentrc.a -> $(LIBDIR)) +.PHONY: test-pentrc-small-width +test-pentrc-small-width: pentrc + $(FCPENTRC) -I../pentrc -o $(BINDIR)/pentrc_small_width \ + ../regression/test/pentrc_small_width.f90 \ + $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) + OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 $(BINDIR)/pentrc_small_width + # DCON code DCON_LIBS = \ -lpentrc \ diff --git a/pentrc/energy.f90 b/pentrc/energy.f90 index 345d11c39..85e803fe4 100644 --- a/pentrc/energy.f90 +++ b/pentrc/energy.f90 @@ -105,7 +105,7 @@ function xintgrl_lsode(wn,wt,we,wd,wb,nuk,ell,leff,n,psi,lambda,method,op_record ! Module global variable xnutype is used to determine collision ! operator. Default is harmonic, and valid choices are: ! "zero" : collisionless - ! "small" : 1e-6*(we+wd) (krook value ignored) + ! "small" : 1e-5*abs(we) (krook value ignored) ! "krook" : krook operator (unmodified argument) ! "harmoinc" : (1+(l/2)^2)*krook*x^-3/2 ! @@ -335,7 +335,7 @@ subroutine xintgrnd(neq,x,y,ydot) ! Module global variable xnutype is used to determine collision ! operator. Default is harmonic, and valid choices are: ! "zero" : collisionless - ! "small" : 1e-6*(we+wd) (krook value ignored) + ! "small" : 1e-5*abs(we) (krook value ignored) ! "krook" : krook operator (unmodified argument) ! "harmoinc" : (1+(l/2)^2)*krook*x^-3/2 ! @@ -377,7 +377,7 @@ subroutine xintgrnd(neq,x,y,ydot) case ("zero") nux = 0.0 case ("small") - nux = 1e-5*energy_we + nux = 1.0e-5_r8*abs(energy_we) case ("krook") nux = energy_nuk case ("harmonic") diff --git a/regression/test/pentrc_small_width.f90 b/regression/test/pentrc_small_width.f90 new file mode 100644 index 000000000..563836778 --- /dev/null +++ b/regression/test/pentrc_small_width.f90 @@ -0,0 +1,41 @@ +! A causal Krook width stays positive when electric precession reverses. +! With wb = wd = wt = 0 and wn = 1 - we, the native energy integral is +! Gamma(7/2)/(i*we - nu), where nu = 1e-5*abs(we). +program pentrc_small_width + use params, only: r8 + use energy_integration, only: xintgrl_lsode, xatol, xrtol, xnutype, & + xf0type, ximag, xnufac + implicit none + + complex(r8) :: integral(2), expected + real(r8) :: we, width, gamma_seven_halves, relative_error + integer :: i + + xnutype = "small" + xf0type = "maxwellian" + ximag = 0.0_r8 + xnufac = 1.0_r8 + xatol = 1.0e-14_r8 + xrtol = 1.0e-11_r8 + gamma_seven_halves = 15.0_r8*sqrt(acos(-1.0_r8))/8.0_r8 + + do i = 1, 2 + we = real(3 - 2*i, r8) + width = 1.0e-5_r8*abs(we) + expected = gamma_seven_halves/cmplx(-width, we, kind=r8) + integral(i) = xintgrl_lsode(1.0_r8-we, 0.0_r8, we, 0.0_r8, & + 0.0_r8, 1.0_r8, 0, 0.0_r8, 1, 0.5_r8, 0.5_r8, "fgar") + relative_error = abs(real(integral(i), r8)/real(expected, r8)-1.0_r8) + print '(a,f5.1,a,2es23.14,a,es12.3)', "we = ", we, & + " integral = ", integral(i), " dissipative error = ", relative_error + if (real(integral(i), r8) >= 0.0_r8) & + error stop "small collision width gives anti-damping" + if (relative_error > 1.0e-7_r8) & + error stop "small-width dissipative integral differs from analytic oracle" + if (abs(aimag(integral(i))/aimag(expected)-1.0_r8) > 1.0e-9_r8) & + error stop "small-width reactive integral differs from analytic oracle" + end do + + if (abs(integral(2)-conjg(integral(1))) > 1.0e-9_r8*abs(integral(1))) & + error stop "reversing precession violates the causal reflection symmetry" +end program pentrc_small_width From b049ec3a76206e579b5325752cdcdd39e5c07c4b Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 04:43:01 -0700 Subject: [PATCH 236/305] RDCON - BUGFIX - Fill the netcdf cell grid whatever the Galerkin output flags gal_output_solution filled cellinfos (interval_icell, iitvl, etypes, cell_x1, cell_x2) after its early return on bin_delmatch/out_galsol/bin_galsol, but rdcon_netcdf writes cellinfos whenever it is allocated. With those flags off the netcdf got uninitialised memory (interval_icell = -1074790400, ..., cell_x2 ~ 1e300). The fill now runs before the return. DIIID_resistive_example: Delta' and every other variable unchanged; with the flags off the cell variables now equal the flags-on run. Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index 48d384945..ef868a147 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -2078,6 +2078,32 @@ SUBROUTINE gal_output_solution (delta,gal) COMPLEX(r8),DIMENSION(:,:,:), ALLOCATABLE :: sol,sol_cut,dsol TYPE(cell_type), POINTER :: cell c----------------------------------------------------------------------- +c cell grid for the netcdf output, whatever the output flags. +c----------------------------------------------------------------------- + DO iintvl=0,msing + DO icell=1,gal%nx + cell=>gal%intvl(iintvl)%cell(icell) + j=iintvl*gal%nx+icell + cellinfos%etypes(j)=cell%etype + SELECT CASE(cell%etype) + CASE("res") + cellinfos%etypes_int(j)=1 + CASE("ext") + cellinfos%etypes_int(j)=2 + CASE("ext1") + cellinfos%etypes_int(j)=3 + CASE("ext2") + cellinfos%etypes_int(j)=4 + CASE DEFAULT + cellinfos%etypes_int(j)=0 + END SELECT + cellinfos%icell(j)=icell + cellinfos%iintvl(j)=iintvl + cellinfos%x1(j)=cell%x(1) + cellinfos%x2(j)=cell%x(2) + ENDDO + ENDDO +c----------------------------------------------------------------------- c check flag c----------------------------------------------------------------------- IF (.NOT.(bin_delmatch.OR.out_galsol.OR.bin_galsol)) RETURN @@ -2098,33 +2124,23 @@ SUBROUTINE gal_output_solution (delta,gal) DO iintvl=0,msing DO icell=1,gal%nx cell=>gal%intvl(iintvl)%cell(icell) - cellinfos%etypes(iintvl*gal%nx+icell)=cell%etype SELECT CASE(cell%etype) CASE("res") psi(ip:ip+interp_np_res-1)=cell%x(1)+ $ (cell%x(2)-cell%x(1))*xvarres - cellinfos%etypes_int(iintvl*gal%nx+icell)=1 CASE("ext") psi(ip:ip+interp_np_res-1)=cell%x(1)+ $ (cell%x(2)-cell%x(1))*xvarres - cellinfos%etypes_int(iintvl*gal%nx+icell)=2 CASE("ext1") psi(ip:ip+interp_np-1)=cell%x(1)+ $ (cell%x(2)-cell%x(1))*xvar - cellinfos%etypes_int(iintvl*gal%nx+icell)=3 CASE("ext2") psi(ip:ip+interp_np-1)=cell%x(1)+ $ (cell%x(2)-cell%x(1))*xvar - cellinfos%etypes_int(iintvl*gal%nx+icell)=4 CASE("none") psi(ip:ip+interp_np-1)=cell%x(1)+ $ (cell%x(2)-cell%x(1))*xvar - cellinfos%etypes_int(iintvl*gal%nx+icell)=0 END SELECT - cellinfos%icell(iintvl*gal%nx+icell)=icell - cellinfos%iintvl(iintvl*gal%nx+icell)=iintvl - cellinfos%x1(iintvl*gal%nx+icell)=cell%x(1) - cellinfos%x2(iintvl*gal%nx+icell)=cell%x(2) IF (cell%extra=="right".AND.cell%etype=="res") $ issing(ip)=.TRUE. IF (cell%etype=="res".OR.cell%etype=="ext") THEN From f6cfc58d98b5735e7fd3a08826d462f1acd6725a Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sat, 17 Oct 2026 17:14:19 +0200 Subject: [PATCH 237/305] Fix DCON ODE access to empty and exhausted singular surfaces --- dcon/ode.f | 32 ++++++--- regression/test/dcon_no_rational_surface.py | 77 +++++++++++++++++++++ 2 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 regression/test/dcon_no_rational_surface.py diff --git a/dcon/ode.f b/dcon/ode.f index 5acdaa8d1..ecbe21506 100644 --- a/dcon/ode.f +++ b/dcon/ode.f @@ -203,13 +203,15 @@ SUBROUTINE ode_axis_init ELSE DO ising=ising+1 - IF(ising > msing.OR.psilim msing)EXIT + IF(psilim=nn*q)EXIT ENDDO - IF(ising>msing.OR.psilimmsing.OR.singfac_min==0)THEN + psimax=psilim*(1-eps) + next="finish" + ELSEIF(psilim 0)THEN + IF(ising <= kmsing)THEN m1=NINT(nn*kinsing(ising)%q) ELSE m1=NINT(nn*qlim)+NINT(SIGN(one,nn*sq%fs1(mpsi,4))) ENDIF ELSE - IF(msing > 0)THEN + IF(ising <= msing)THEN m1=NINT(nn*sing(ising)%q) ELSE m1=NINT(nn*qlim)+NINT(SIGN(one,nn*sq%fs1(mpsi,4))) @@ -371,11 +373,16 @@ SUBROUTINE ode_sing_init c----------------------------------------------------------------------- DO ising=ising+1 - IF(ising>msing.OR.psilimmsing)EXIT + IF(psilim=nn*q)EXIT ENDDO - IF(ising>msing.OR.psilimmsing)THEN + m1=NINT(nn*qlim)+NINT(SIGN(one,nn*sq%fs1(mpsi,4))) + psimax=psilim*(1-eps) + next="finish" + ELSEIF(psilimmsing.OR.psilimmsing)EXIT + IF(psilim=nn*q)EXIT ENDDO c----------------------------------------------------------------------- c compute conditions at next singular surface. c----------------------------------------------------------------------- - IF(ising>msing.OR.psilimmsing)THEN + psimax=psilim*(1-eps) + m1=NINT(nn*qlim)+NINT(SIGN(one,nn*sq%fs1(mpsi,4))) + next="finish" + ELSEIF(psilim math.floor(nn * qmax), (nn, qmin, qmax) + assert 'psi = 9.900E-01' in result.stdout, 'ODE did not reach the edge' + print(f'PASS: n={nn}, q=[{qmin:.9f}, {qmax:.9f}], no rational surface; ODE reaches .99') + return True + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--repo') + parser.add_argument('--loc') + args = parser.parse_args() + run(args.repo, args.loc) From a1e9413c18faef708b91a56807d381f4357694c0 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 13:14:28 -0700 Subject: [PATCH 238/305] STRIDE - BUGFIX - Split the axis interval into at least 8 pieces ode_set_intervals spreads the spare intervals by estimated compute time, which rates the interval from the axis to the first singular surface as cheap: at the default nIntervalsTot = 33 it often stays whole (always when nIntervalsTot is forced up to its minimum), and then the innermost Delta' is wrong by up to 1e3 (DIII-D n = 2: -111 or 794 against -0.733 converged; n = 1: -1.09e5 against -635). The axis interval is now split into nAxisMin = 8 pieces once every inner region is split, and nIntervalsMin counts them. Converged at 8 pieces in every case tried; DIIID ideal/resistive, a10 and both resistive Solov'ev examples are bit-identical; the ideal Solov'ev ones move <= 4e-6 in Delta'. Co-Authored-By: Claude Opus 5.5 --- stride/ode.F | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/stride/ode.F b/stride/ode.F index 73bd2df80..ca5f214a5 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -475,6 +475,7 @@ END SUBROUTINE ode_run c----------------------------------------------------------------------- SUBROUTINE ode_set_intervals INTEGER :: i, j, jsing, iS, nIntervalsMin + INTEGER, PARAMETER :: nAxisMin = 8 INTEGER :: newItvls, cumulItvls, innerItvls, nbad REAL(r8) :: s1, s2, sLast, alpha REAL(r8), DIMENSION(:), ALLOCATABLE :: aT @@ -484,8 +485,9 @@ SUBROUTINE ode_set_intervals c basic requirement to avoid silly faults c----------------------------------------------------------------------- ! Require at least splitting each inter-singularity - ! edge, msing inner layers, split msing-1 inter-regions, edge - nIntervalsMin = 1 + msing + 2 * (msing -1) + 1 + ! nAxisMin axis pieces, msing inner layers, split msing-1 + ! inter-regions, edge + nIntervalsMin = nAxisMin + msing + 2 * (msing -1) + 1 IF(nIntervalsMin > nIntervalsTot)THEN nIntervalsTot = nIntervalsMin WRITE(*, '(1x,a25,i4, a27)') "Forcing nIntervalsTot to ", @@ -629,6 +631,13 @@ SUBROUTINE ode_set_intervals ENDIF ENDIF ENDDO + ! then split the axis interval into nAxisMin pieces: with + ! fewer, the innermost Delta' can be wrong by 1e3 + IF(j<0 .AND. COUNT(psiPoints(1:nIntervals,2) <= + $ scalc(1)%singEdgesLR(1)) < nAxisMin)THEN + j = MAXLOC(aT(1:nIntervals), DIM=1, MASK= + $ psiPoints(1:nIntervals,2) <= scalc(1)%singEdgesLR(1)) + ENDIF ! then, start breaking up the longest intervals IF(j<0)THEN j = MAXLOC(aT(1:nIntervals), DIM=1, From 8b0543bcd827c71250c846f3b8215c261895f3cb Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 13:25:01 -0700 Subject: [PATCH 239/305] RDCON - BUGFIX - Size the Galerkin elements below a singular surface with n*q1, as above it gal_make_grid places the resonant and extension elements at x0 + dx/(n*q1) above a singular surface but at x1 - dx/q1 below the next one, so for n > 1 the lower-side elements were n times wider in singular-factor units than the upper-side ones. At n = 2 RDCON's innermost Delta' converged in dx to a value that depended on that ratio (-0.7455 vs -0.7374 with the fix; STRIDE -0.733). n = 1 is unchanged (DIIID_resistive_example: identical outputs). Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index 48d384945..f45a55f0f 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -407,7 +407,7 @@ SUBROUTINE gal_make_grid(ising,nx,dx0,dx1,dx2,pfac,intvl) intvl%x(ixmax)=psihigh ELSE x1=sing(ising+1)%psifac - nq1=ABS(sing(ising+1)%q1) + nq1=ABS(nn*sing(ising+1)%q1) intvl%x(nx)=x1 IF (gal_xmin_flag .AND. sing1_flag) THEN CALL sing1_xmin(ising+1,gal_eps_xmin,myxmin) From 1d1cb7d14e34bc5203b88197755ad2ad1e7b7546 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 14:58:57 -0700 Subject: [PATCH 240/305] DCON, RDCON, STRIDE - BUGFIX - Keep the dmlim qlim within qhigh With sas_flag, sing_lim moves qlim to (INT(nn*qlim)+dmlim)/nn and steps it back only while it exceeds qmax, so qhigh = 4 with dmlim 0.2 integrates to q = 4.2. Step back while qlim exceeds MIN(qmax,qhigh), as Julia GPEC does since OpenFUSIONToolkit/GPEC#298. Co-Authored-By: Claude Opus 5.5 --- dcon/sing.f | 2 +- rdcon/sing.f | 2 +- stride/sing.F | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dcon/sing.f b/dcon/sing.f index f4f27fa50..4f3b5bb8a 100644 --- a/dcon/sing.f +++ b/dcon/sing.f @@ -185,7 +185,7 @@ SUBROUTINE sing_lim c----------------------------------------------------------------------- qlim=(INT(nn*qlim)+dmlim)/nn DO - IF(qlim <= qmax)EXIT + IF(qlim <= MIN(qmax,qhigh))EXIT qlim=qlim-1._r8/nn ENDDO ENDIF diff --git a/rdcon/sing.f b/rdcon/sing.f index 05e44921e..e6aa9336b 100644 --- a/rdcon/sing.f +++ b/rdcon/sing.f @@ -199,7 +199,7 @@ SUBROUTINE sing_lim c----------------------------------------------------------------------- qlim=(INT(nn*qlim)+dmlim)/nn DO - IF(qlim <= qmax)EXIT + IF(qlim <= MIN(qmax,qhigh))EXIT qlim=qlim-1._r8/nn ENDDO ENDIF diff --git a/stride/sing.F b/stride/sing.F index cf897e1e5..5e7cf886b 100644 --- a/stride/sing.F +++ b/stride/sing.F @@ -294,7 +294,7 @@ SUBROUTINE sing_lim c----------------------------------------------------------------------- qlim=(INT(nn*qlim)+dmlim)/nn DO - IF(qlim <= qmax)EXIT + IF(qlim <= MIN(qmax,qhigh))EXIT qlim=qlim-1._r8/nn ENDDO ENDIF From 0576ed3bb4a504a140795c002bcba0026f60cfc2 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 17:51:29 -0700 Subject: [PATCH 241/305] EQUIL - BUGFIX - Sample every direct-equilibrium surface at the common straight-fieldline angles direct_run splined each traced surface on its own lsode steps and resampled it onto the theta grid, so the resample error was uncorrelated between surfaces and the psi derivatives amplified it. A second pass of the same trace now finds each target angle on lsode's interpolant (dintdy) and samples the solution there, as OpenFUSIONToolkit/GPEC#398 does in Julia GPEC. Co-Authored-By: Claude Opus 5.5 --- equil/direct.f | 80 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/equil/direct.f b/equil/direct.f index 675b8f534..1b5b3887b 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -57,9 +57,10 @@ SUBROUTINE direct_run REAL(r8), DIMENSION(2, mpsi+1) :: xdx REAL(r8), DIMENSION(3,3) :: v - REAL(r8) :: xm,dx,rholow,rhohigh + REAL(r8) :: xm,dx,rholow,rhohigh,y4tot,theta + REAL(r8), DIMENSION(4) :: dy + REAL(r8), DIMENSION(:,:), ALLOCATABLE :: ys TYPE(direct_bfield_type) :: bf - TYPE(spline_type) :: ff c----------------------------------------------------------------------- c warning. c----------------------------------------------------------------------- @@ -139,17 +140,6 @@ SUBROUTINE direct_run DO ipsi=mpsi,0,-1 CALL direct_fl_int(ipsi,y_out,bf) c----------------------------------------------------------------------- -c fit data to cubic splines. -c----------------------------------------------------------------------- - CALL spline_alloc(ff,istep,4) - ff%xs(0:istep)=y_out(0:istep,4)/y_out(istep,4) - ff%fs(0:istep,1)=y_out(0:istep,2)**2 - ff%fs(0:istep,2)=y_out(0:istep,0)/twopi-ff%xs(0:istep) - ff%fs(0:istep,3)=bf%f* - $ (y_out(0:istep,3)-ff%xs(0:istep)*y_out(istep,3)) - ff%fs(0:istep,4)=y_out(0:istep,1)/y_out(istep,1)-ff%xs - CALL spline_fit(ff,"periodic") -c----------------------------------------------------------------------- c allocate space for rzphi and define grids. c----------------------------------------------------------------------- IF(ipsi == mpsi)THEN @@ -164,15 +154,21 @@ SUBROUTINE direct_run eqfun%title=(/" b0 "," "," " /) eqfun%xs=sq%xs eqfun%ys=(/(itheta,itheta=0,mtheta)/)/REAL(mtheta,r8) + ALLOCATE(ys(0:mtheta,0:4)) ENDIF c----------------------------------------------------------------------- -c interpolate to uniform grid. +c sample the surface at the common straight-fieldline angles. c----------------------------------------------------------------------- + y4tot=y_out(istep,4) + CALL direct_fl_int(ipsi,y_out,bf,y4tot,ys) DO itheta=0,mtheta - CALL spline_eval(ff,rzphi%ys(itheta),1) - rzphi%fs(ipsi,itheta,1:3)=ff%f(1:3) - rzphi%fs(ipsi,itheta,4)=(1+ff%f1(4)) - $ *y_out(istep,1)*twopi*psio + theta=rzphi%ys(itheta) + CALL direct_fl_der(4,ys(itheta,0),ys(itheta,1:4),dy) + rzphi%fs(ipsi,itheta,1)=ys(itheta,2)**2 + rzphi%fs(ipsi,itheta,2)=ys(itheta,0)/twopi-theta + rzphi%fs(ipsi,itheta,3)=bf%f* + $ (ys(itheta,3)-theta*y_out(istep,3)) + rzphi%fs(ipsi,itheta,4)=dy(1)/dy(4)*y4tot*twopi*psio ENDDO c----------------------------------------------------------------------- c store surface quantities. @@ -183,7 +179,6 @@ SUBROUTINE direct_run sq%fs1(ipsi,2)=bf%p1 sq%fs(ipsi,3)=y_out(istep,1)*twopi*psio sq%fs(ipsi,4)=y_out(istep,3)*bf%f/twopi - CALL spline_dealloc(ff) ENDDO c----------------------------------------------------------------------- c close output files. @@ -428,25 +423,29 @@ SUBROUTINE direct_position END SUBROUTINE direct_position c----------------------------------------------------------------------- c subprogram 4. direct_fl_int. -c integrates along field line. +c integrates along field line; with y4tot, a second pass also +c samples y at the straight-fieldline angles rzphi%ys. c----------------------------------------------------------------------- c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE direct_fl_int(ipsi,y_out,bf) + SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) INTEGER, INTENT(IN) :: ipsi REAL(r8), DIMENSION(0:,0:), INTENT(OUT) :: y_out TYPE(direct_bfield_type), INTENT(OUT) :: bf + REAL(r8), INTENT(IN), OPTIONAL :: y4tot + REAL(r8), DIMENSION(0:,0:), INTENT(OUT), OPTIONAL :: ys CHARACTER(64) :: message INTEGER, PARAMETER :: neq=4,liw=30,lrw=22+neq*16 - INTEGER :: iopt,istate,itask,itol,jac,mf,ir + INTEGER :: iopt,istate,itask,itol,jac,mf,ir,js,it,iflag INTEGER, DIMENSION(liw) :: iwork REAL(r8), PARAMETER :: eps=1e-12 REAL(r8) :: atol,rtol,rfac,deta,r,z,eta,err,psi0,psifac,dr - REAL(r8), DIMENSION(neq) :: y + REAL(r8) :: eta0,y40,y4j,t,res + REAL(r8), DIMENSION(neq) :: y,dky REAL(r8), DIMENSION(lrw) :: rwork c----------------------------------------------------------------------- c format statements. @@ -485,6 +484,8 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf) deta=twopi/mtheta y=0 y(2)=SQRT((r-ro)**2+(z-zo)**2) + IF(PRESENT(ys))ys(0,:)=(/eta,y/) + js=1 c----------------------------------------------------------------------- c set up integrator parameters. c----------------------------------------------------------------------- @@ -502,7 +503,7 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf) c----------------------------------------------------------------------- c write header. c----------------------------------------------------------------------- - IF(out_fl)THEN + IF(out_fl .AND. .NOT. PRESENT(ys))THEN WRITE(out_2d_unit,10)ipsi,psifac WRITE(out_2d_unit,20) ENDIF @@ -520,9 +521,9 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf) c----------------------------------------------------------------------- c compute and print output for each step. c----------------------------------------------------------------------- - IF(out_fl)WRITE(out_2d_unit,30) + IF(out_fl .AND. .NOT. PRESENT(ys))WRITE(out_2d_unit,30) $ istep,eta,rwork(11),y(1:2),r,z,bf%psi,err - IF(bin_fl)WRITE(bin_2d_unit) + IF(bin_fl .AND. .NOT. PRESENT(ys))WRITE(bin_2d_unit) $ REAL(eta,4),REAL(y(1:2),4),REAL(r,4),REAL(z,4),REAL(err,4) c----------------------------------------------------------------------- c advance differential equations. @@ -530,11 +531,34 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf) IF(eta >= twopi .OR. istep >= enstep .OR. istate < 0 $ .OR. ABS(err) >= 1)EXIT istep=istep+1 + eta0=eta + y40=y(4) CALL lsode(direct_fl_der,neq,y,eta,twopi,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) +c----------------------------------------------------------------------- +c sample the step at the angles it covers: Newton on lsode's +c interpolant for y(4) = theta*y4tot, then y there. +c----------------------------------------------------------------------- + IF(.NOT. PRESENT(ys))CYCLE + DO WHILE(js < mtheta) + y4j=rzphi%ys(js)*y4tot + IF(y4j > y(4))EXIT + t=eta0+(eta-eta0)*(y4j-y40)/(y(4)-y40) + DO it=1,20 + CALL dintdy(t,0,rwork(21),neq,dky,iflag) + res=dky(4)-y4j + CALL dintdy(t,1,rwork(21),neq,dky,iflag) + t=MIN(MAX(t-res/dky(4),eta0),eta) + IF(ABS(res) <= eps*y4tot)EXIT + ENDDO + CALL dintdy(t,0,rwork(21),neq,dky,iflag) + ys(js,:)=(/t,dky/) + js=js+1 + ENDDO ENDDO - IF(out_fl)WRITE(out_2d_unit,20) - IF(bin_fl)WRITE(bin_2d_unit) + IF(PRESENT(ys))ys(mtheta,:)=(/eta,y/) + IF(out_fl .AND. .NOT. PRESENT(ys))WRITE(out_2d_unit,20) + IF(bin_fl .AND. .NOT. PRESENT(ys))WRITE(bin_2d_unit) c----------------------------------------------------------------------- c abort if istep > enstep. c----------------------------------------------------------------------- From 98c8c102ec0076098d814d1d600ea187c9d8a495 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sat, 17 Oct 2026 18:09:40 -0700 Subject: [PATCH 242/305] EQUIL - MINOR - Sample each lsode step in its own subprogram, direct_fl_sample Same results bit for bit; direct_fl_int keeps only the call. Co-Authored-By: Claude Opus 5.5 --- equil/direct.f | 85 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/equil/direct.f b/equil/direct.f index 1b5b3887b..8dac4243a 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -10,9 +10,10 @@ c 2. direct_get_bfield. c 3. direct_position. c 4. direct_fl_int. -c 5. direct_fl_der. -c 6. direct_refine. -c 7. direct_output. +c 5. direct_fl_sample. +c 6. direct_fl_der. +c 7. direct_refine. +c 8. direct_output. c----------------------------------------------------------------------- c subprogram 0. direct_mod. c module declarations. @@ -440,12 +441,12 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) CHARACTER(64) :: message INTEGER, PARAMETER :: neq=4,liw=30,lrw=22+neq*16 - INTEGER :: iopt,istate,itask,itol,jac,mf,ir,js,it,iflag + INTEGER :: iopt,istate,itask,itol,jac,mf,ir,js INTEGER, DIMENSION(liw) :: iwork REAL(r8), PARAMETER :: eps=1e-12 REAL(r8) :: atol,rtol,rfac,deta,r,z,eta,err,psi0,psifac,dr - REAL(r8) :: eta0,y40,y4j,t,res - REAL(r8), DIMENSION(neq) :: y,dky + REAL(r8) :: eta0,y40 + REAL(r8), DIMENSION(neq) :: y REAL(r8), DIMENSION(lrw) :: rwork c----------------------------------------------------------------------- c format statements. @@ -535,26 +536,8 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) y40=y(4) CALL lsode(direct_fl_der,neq,y,eta,twopi,itol,rtol,atol, $ itask,istate,iopt,rwork,lrw,iwork,liw,jac,mf) -c----------------------------------------------------------------------- -c sample the step at the angles it covers: Newton on lsode's -c interpolant for y(4) = theta*y4tot, then y there. -c----------------------------------------------------------------------- - IF(.NOT. PRESENT(ys))CYCLE - DO WHILE(js < mtheta) - y4j=rzphi%ys(js)*y4tot - IF(y4j > y(4))EXIT - t=eta0+(eta-eta0)*(y4j-y40)/(y(4)-y40) - DO it=1,20 - CALL dintdy(t,0,rwork(21),neq,dky,iflag) - res=dky(4)-y4j - CALL dintdy(t,1,rwork(21),neq,dky,iflag) - t=MIN(MAX(t-res/dky(4),eta0),eta) - IF(ABS(res) <= eps*y4tot)EXIT - ENDDO - CALL dintdy(t,0,rwork(21),neq,dky,iflag) - ys(js,:)=(/t,dky/) - js=js+1 - ENDDO + IF(PRESENT(ys))CALL direct_fl_sample(eta0,eta,y40,y(4), + $ y4tot,rwork(21),js,ys) ENDDO IF(PRESENT(ys))ys(mtheta,:)=(/eta,y/) IF(out_fl .AND. .NOT. PRESENT(ys))WRITE(out_2d_unit,20) @@ -573,7 +556,51 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) RETURN END SUBROUTINE direct_fl_int c----------------------------------------------------------------------- -c subprogram 5. direct_fl_der. +c subprogram 5. direct_fl_sample. +c samples the last lsode step, eta0 to eta1, at the angles +c rzphi%ys it covers: Newton on lsode's interpolant for +c y(4) = theta*y4tot, then y there. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE direct_fl_sample(eta0,eta1,y40,y41,y4tot,yh,js,ys) + + REAL(r8), INTENT(IN) :: eta0,eta1,y40,y41,y4tot + REAL(r8), DIMENSION(*), INTENT(IN) :: yh + INTEGER, INTENT(INOUT) :: js + REAL(r8), DIMENSION(0:,0:), INTENT(INOUT) :: ys + + INTEGER, PARAMETER :: neq=4 + REAL(r8), PARAMETER :: eps=1e-12 + INTEGER :: it,iflag + REAL(r8) :: y4j,eta,res + REAL(r8), DIMENSION(neq) :: dky +c----------------------------------------------------------------------- +c find and sample each angle in the step. +c----------------------------------------------------------------------- + DO WHILE(js < mtheta) + y4j=rzphi%ys(js)*y4tot + IF(y4j > y41)EXIT + eta=eta0+(eta1-eta0)*(y4j-y40)/(y41-y40) + DO it=1,20 + CALL dintdy(eta,0,yh,neq,dky,iflag) + res=dky(4)-y4j + CALL dintdy(eta,1,yh,neq,dky,iflag) + eta=MIN(MAX(eta-res/dky(4),eta0),eta1) + IF(ABS(res) <= eps*y4tot)EXIT + ENDDO + CALL dintdy(eta,0,yh,neq,dky,iflag) + ys(js,:)=(/eta,dky/) + js=js+1 + ENDDO +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE direct_fl_sample +c----------------------------------------------------------------------- +c subprogram 6. direct_fl_der. c contains differential equations for field line averages. c----------------------------------------------------------------------- c----------------------------------------------------------------------- @@ -614,7 +641,7 @@ SUBROUTINE direct_fl_der(neq,eta,y,dy) RETURN END SUBROUTINE direct_fl_der c----------------------------------------------------------------------- -c subprogram 6. direct_refine. +c subprogram 7. direct_refine. c moves a point orthogonally to a specified flux surface. c----------------------------------------------------------------------- c----------------------------------------------------------------------- @@ -662,7 +689,7 @@ SUBROUTINE direct_refine(rfac,eta,psi0) RETURN END SUBROUTINE direct_refine c----------------------------------------------------------------------- -c subprogram 7. direct_output. +c subprogram 8. direct_output. c diagnoses input. c----------------------------------------------------------------------- c----------------------------------------------------------------------- From ddf86616256c01eab94c4f65867233065bd3e079 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 11:57:27 +0200 Subject: [PATCH 243/305] Retain coupling output vectors and export their adjoint weighting --- .github/workflows/make_test.yml | 2 + gpec/gpec.f | 7 ++ gpec/gpout.f | 20 ++-- regression/test/local_coupling_overlap.py | 124 ++++++++++++++++++++++ 4 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 regression/test/local_coupling_overlap.py diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index a121e9d5c..936be9c5a 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -220,6 +220,7 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc + python3 ../../../regression/test/local_coupling_overlap.py --work-dir ../../../local_coupling_regression - name: Run Soloviev kinetic example run: | @@ -296,6 +297,7 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc + python3 ../../../regression/test/local_coupling_overlap.py --work-dir ../../../local_coupling_regression - name: Run Soloviev kinetic example run: | diff --git a/gpec/gpec.f b/gpec/gpec.f index 5342d12e5..99e0e6ed6 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -772,6 +772,13 @@ PROGRAM gpec_main c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- + IF(singcoup_set) THEN +c retain coupling vectors through output and diagnostic calls, +c then release them even when singular-field output is disabled. + DEALLOCATE(singcoup_out_vecs) + IF(osing Normal termination." in (case / "gpec.log").read_text() + if enabled: + check_case(case) + else: + assert not (case / "gpec_singfld_n1.out").exists() + print("PASS: local coupling with singular-field output disabled") + + +if __name__ == "__main__": + main() From e8a1c8169eebe4630aaa071241d75b966becf1ac Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 11:57:27 +0200 Subject: [PATCH 244/305] Evaluate current geometry derivatives for control-surface normals --- .github/workflows/make_test.yml | 2 + gpec/gpout.f | 2 +- regression/test/control_surface_normal.py | 76 +++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 regression/test/control_surface_normal.py diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index a121e9d5c..8a1874111 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -220,6 +220,7 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc + python3 ../../../regression/test/control_surface_normal.py . - name: Run Soloviev kinetic example run: | @@ -296,6 +297,7 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc + python3 ../../../regression/test/control_surface_normal.py . - name: Run Soloviev kinetic example run: | diff --git a/gpec/gpout.f b/gpec/gpout.f index baee0d5d3..bc181e828 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -1418,7 +1418,7 @@ SUBROUTINE gpout_control(mode, ifile,finmn,foutmn,xspmn, CALL iscdftb(mfac,mpert,xoutfun,mthsurf,xoutmn) DO itheta=0,mthsurf - CALL bicube_eval(rzphi,psilim,theta(itheta),0) + CALL bicube_eval(rzphi,psilim,theta(itheta),1) rfac=SQRT(rzphi%f(1)) eta=twopi*(theta(itheta)+rzphi%f(2)) r(itheta)=ro+rfac*COS(eta) diff --git a/regression/test/control_surface_normal.py b/regression/test/control_surface_normal.py new file mode 100644 index 000000000..1af711787 --- /dev/null +++ b/regression/test/control_surface_normal.py @@ -0,0 +1,76 @@ +"""Check control-surface normals against derivatives of exported geometry. + +Run after the native Solovev ideal example. Only Python's standard library +and the same NetCDF C library used by the solver are required. +""" +import argparse +import cmath +import ctypes +from ctypes.util import find_library +import math +from pathlib import Path + + +def read_vectors(path): + lib = ctypes.CDLL(find_library("netcdf")) + ncid = ctypes.c_int() + + def check(status): + if status: + raise RuntimeError(f"NetCDF error {status} reading {path}") + + check(lib.nc_open(str(path).encode(), 0, ctypes.byref(ncid))) + try: + result = [] + for name in ("R", "z", "R_n", "z_n"): + varid, dimid, ndim = ctypes.c_int(), ctypes.c_int(), ctypes.c_int() + size = ctypes.c_size_t() + check(lib.nc_inq_varid(ncid, name.encode(), ctypes.byref(varid))) + check(lib.nc_inq_varndims(ncid, varid, ctypes.byref(ndim))) + assert ndim.value == 1, f"{name} must be a vector" + check(lib.nc_inq_vardimid(ncid, varid, ctypes.byref(dimid))) + check(lib.nc_inq_dimlen(ncid, dimid, ctypes.byref(size))) + data = (ctypes.c_double * size.value)() + check(lib.nc_get_var_double(ncid, varid, data)) + result.append(list(data)) + return result + finally: + check(lib.nc_close(ncid)) + + +def derivative(values): + """Periodic Fourier derivative, independently of bicubic derivatives.""" + n = len(values) + # Remove the constant part before differentiating its sampled DFT. + mean = sum(values) / n + coefficients = [] + for k in range(1, n // 2): + value = sum((x - mean) * cmath.exp(-2j * math.pi * k * j / n) + for j, x in enumerate(values)) / n + coefficients.append((k, value)) + return [2 * sum((1j * k * value * cmath.exp(2j * math.pi * k * j / n)).real + for k, value in coefficients) for j in range(n)] + + +def check_case(path): + r, z, rn, zn = read_vectors(path) + assert len(r) >= 33 and len({len(x) for x in (r, z, rn, zn)}) == 1 + assert all(math.isfinite(x) for a in (r, z, rn, zn) for x in a) + assert math.hypot(r[0] - r[-1], z[0] - z[-1]) < 1e-9 + dr, dz = derivative(r[:-1]), derivative(z[:-1]) + error = 0.0 + for a, b, nr, nz in zip(dr, dz, rn[:-1], zn[:-1]): + length = math.hypot(a, b) + assert length > 0 + error = max(error, math.hypot(nr - b / length, nz + a / length)) + # Differentiating the sampled fitted geometry is independent of the + # analytic bicubic derivative and includes its discretization error. + assert error < 1e-4, f"control-surface normal error {error:.6e}" + print(f"PASS: control-surface normal maximum vector error {error:.6e}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("case", type=Path) + args = parser.parse_args() + check_case(args.case / "gpec_control_output_n1.nc") From dbfa9dc376b7f22f1928e87a922b9f98853acaa0 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Sun, 18 Oct 2026 03:32:43 -0700 Subject: [PATCH 245/305] EQUIL - BUGFIX - Bound grid_type original by psilow and psihigh grid_type = "original" took every node of the input file and ignored psilow and psihigh. On inverse files the grid ran from the axis to the file edge, where the separatrix search in equil_out_sep_find never converges (RDCON and STRIDE hang), and the psilim reform could not move the edge. On direct files sq%xs was assigned mpsi nodes for its mpsi+1 (reallocating it), so the radial spline was corrupt. The grid is now psilow, the file's nodes strictly between psilow and psihigh, and psihigh (new grid_mod function clipspace). Co-Authored-By: Claude Opus 5.5 --- equil/direct.f | 11 +++-------- equil/grid.f90 | 26 ++++++++++++++++++++++++++ equil/inverse.f | 10 ++++++---- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/equil/direct.f b/equil/direct.f index 675b8f534..5783df761 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -82,13 +82,8 @@ SUBROUTINE direct_run c----------------------------------------------------------------------- c prepare new spline type for surface quantities. c----------------------------------------------------------------------- - IF(grid_type == "original" .OR. grid_type == "orig")THEN - IF(sq_in%xs(sq_in%mx) < 1-1e-6)THEN - mpsi=sq_in%mx-1 - ELSE - mpsi=sq_in%mx-2 - ENDIF - ENDIF + IF(grid_type == "original" .OR. grid_type == "orig") + $ mpsi=SIZE(clipspace(sq_in%xs,psilow,psihigh))-1 IF(.NOT. sq%allocated)THEN CALL spline_alloc(sq,mpsi,4) ELSE @@ -114,7 +109,7 @@ SUBROUTINE direct_run CASE("rho") sq%xs=psihigh*(/(ipsi**2,ipsi=1,mpsi+1)/)/(mpsi+1)**2 CASE("original","orig") - sq%xs=sq_in%xs(1:mpsi) + sq%xs=clipspace(sq_in%xs,psilow,psihigh) CASE("mypack") rholow=SQRT(psilow) rhohigh=SQRT(psihigh) diff --git a/equil/grid.f90 b/equil/grid.f90 index 20791fe9e..78cdb35c8 100644 --- a/equil/grid.f90 +++ b/equil/grid.f90 @@ -373,5 +373,31 @@ function logspace(xmin,xmax,pow,num) return end function logspace + !======================================================================= + function clipspace(xs,xmin,xmax) + !----------------------------------------------------------------------- + !*DESCRIPTION: + ! Restrict an existing grid to [xmin,xmax]. + ! + !*ARGUMENTS: + ! xs : real array (in) + ! Increasing grid. + ! xmin : real (in) + ! Grid minimum. + ! xmax : real (in) + ! Grid maximum. + ! + !*RETURNS: + ! clipspace : real array. + ! xmin, the points of xs strictly between xmin and xmax, xmax. + !----------------------------------------------------------------------- + ! declare function and arguments + real(r8), dimension(:), intent(in) :: xs + real(r8), intent(in) :: xmin, xmax + real(r8), dimension(:), allocatable :: clipspace + + clipspace = (/xmin, pack(xs, xs > xmin .and. xs < xmax), xmax/) + return + end function clipspace end module grid_mod diff --git a/equil/inverse.f b/equil/inverse.f index ac19224bb..b210eac66 100644 --- a/equil/inverse.f +++ b/equil/inverse.f @@ -124,7 +124,8 @@ SUBROUTINE inverse_run c----------------------------------------------------------------------- c prepare new spline type for surface quantities. c----------------------------------------------------------------------- - IF(grid_type == "original" .OR. grid_type == "orig")mpsi=sq_in%mx + IF(grid_type == "original" .OR. grid_type == "orig") + $ mpsi=SIZE(clipspace(sq_in%xs,psilow,psihigh))-1 IF(.NOT. sq%allocated)THEN CALL spline_alloc(sq,mpsi,4) ELSE @@ -149,7 +150,7 @@ SUBROUTINE inverse_run CASE("rho") sq%xs=psihigh*(/(ipsi**2,ipsi=1,mpsi+1)/)/(mpsi+1)**2 CASE("original","orig") - sq%xs=sq_in%xs + sq%xs=clipspace(sq_in%xs,psilow,psihigh) CASE("mypack") rholow=SQRT(psilow) rhohigh=SQRT(psihigh) @@ -467,7 +468,8 @@ SUBROUTINE inverse_chease4_run(jaceq) c----------------------------------------------------------------------- c prepare new spline type for surface quantities. c----------------------------------------------------------------------- - IF(grid_type == "original" .OR. grid_type == "orig")mpsi=sq_in%mx + IF(grid_type == "original" .OR. grid_type == "orig") + $ mpsi=SIZE(clipspace(sq_in%xs,psilow,psihigh))-1 IF(.NOT. sq%allocated)THEN CALL spline_alloc(sq,mpsi,4) ELSE @@ -486,7 +488,7 @@ SUBROUTINE inverse_chease4_run(jaceq) CASE("rho") sq%xs=psihigh*(/(ipsi**2,ipsi=1,mpsi+1)/)/(mpsi+1)**2 CASE("original","orig") - sq%xs=sq_in%xs + sq%xs=clipspace(sq_in%xs,psilow,psihigh) CASE("mypack") rholow=SQRT(psilow) rhohigh=SQRT(psihigh) From 87e6ad167c378d93cffd8725e0751c2fe3652bed Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 13:24:32 +0200 Subject: [PATCH 246/305] Preserve forced singular reductions during norm resets --- .github/workflows/make_test.yml | 1 + dcon/dcon.F | 2 + dcon/ode.f | 15 +++-- install/TARGETS.inc | 11 ++++ regression/test/dcon_singular_fixup.f90 | 77 +++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 regression/test/dcon_singular_fixup.f90 diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index a121e9d5c..510315bc8 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -212,6 +212,7 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-fallow-argument-mismatch -O0 -Wall -Wextra -fcheck=all,no-array-temps -fbacktrace -finit-real=snan -finit-integer=-9999 -fstack-protector-all -fsanitize=bounds -Wno-array-temporaries -Wno-unused-variable -Wno-unused-dummy-argument -Wno-compare-reals -Wno-unused-parameter -Wno-unused-label -fno-omit-frame-pointer -frecord-marker=4 -gdwarf-4 -ffloat-store -fbounds-check' make -j$(nproc) + LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-dcon-singular-fixup - name: Run Soloviev ideal example run: | diff --git a/dcon/dcon.F b/dcon/dcon.F index df3dcde7e..c6878e616 100644 --- a/dcon/dcon.F +++ b/dcon/dcon.F @@ -14,6 +14,7 @@ c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- +#ifndef DCON_NO_MAIN PROGRAM dcon USE equil_mod USE equil_out_mod @@ -318,6 +319,7 @@ PROGRAM dcon c----------------------------------------------------------------------- CALL program_stop("Normal termination.") END PROGRAM dcon +#endif c----------------------------------------------------------------------- c subprogram 2. dcon_dealloc. c deallocates internal memory. diff --git a/dcon/ode.f b/dcon/ode.f index 5acdaa8d1..a111af673 100644 --- a/dcon/ode.f +++ b/dcon/ode.f @@ -983,6 +983,7 @@ SUBROUTINE ode_unorm(sing_flag) CHARACTER(64) :: message INTEGER, DIMENSION(1) :: jmax REAL(r8) :: uratio + LOGICAL :: fix_required c----------------------------------------------------------------------- c compute norms of first solution vectors, abort if any are zero. c----------------------------------------------------------------------- @@ -994,19 +995,21 @@ SUBROUTINE ode_unorm(sing_flag) CALL program_stop(message) ENDIF c----------------------------------------------------------------------- -c normalize unorm and perform Gaussian reduction if required. +c preserve singular reductions while resetting norm baselines. c----------------------------------------------------------------------- + fix_required=sing_flag IF(new)THEN new=.FALSE. unorm0(1:msol)=unorm(1:msol) ELSE unorm(1:msol)=unorm(1:msol)/unorm0(1:msol) uratio=MAXVAL(unorm(1:msol))/MINVAL(unorm(1:msol)) - IF(uratio > ucrit .OR. sing_flag)THEN - CALL ode_fixup(sing_flag,.FALSE.) - IF(diagnose)CALL ode_test_fixup - new=.TRUE. - ENDIF + fix_required=fix_required .OR. uratio > ucrit + ENDIF + IF(fix_required)THEN + CALL ode_fixup(sing_flag,.FALSE.) + IF(diagnose)CALL ode_test_fixup + new=.TRUE. ENDIF c----------------------------------------------------------------------- c terminate. diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..bfd47924d 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -199,6 +199,17 @@ dcon: v neededdeps $(DCON_OBJS) equil lsode vacuum pentrc cp ../dcon/dcon $(BINDIR) $(call print_success,dcon -> $(BINDIR)/dcon) +.PHONY: test-dcon-singular-fixup +test-dcon-singular-fixup: dcon + $(FCDCON) -I../dcon -DDCON_NO_MAIN $(MODFLAG) ../dcon \ + -c ../dcon/dcon.F -o ../dcon/dcon_test_support.o + $(FCDCON) -I../dcon -o $(BINDIR)/test_dcon_singular_fixup \ + ../regression/test/dcon_singular_fixup.f90 \ + $(filter-out ../dcon/dcon.o,$(DCON_OBJS)) \ + ../dcon/dcon_test_support.o $(DCON_LIBDIRS) $(DCON_LIBS) $(LDFLAGS) + OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_dcon_singular_fixup 1 + OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_dcon_singular_fixup 2 + # RDCON code ../rdcon/version.inc: @ver="`git describe --tags`"; \ diff --git a/regression/test/dcon_singular_fixup.f90 b/regression/test/dcon_singular_fixup.f90 new file mode 100644 index 000000000..a56179c63 --- /dev/null +++ b/regression/test/dcon_singular_fixup.f90 @@ -0,0 +1,77 @@ +! Link the production DCON objects, replacing only the main program. +! Both scenarios require a native singular reduction record after new=.true. +! Run: make -C install test-dcon-singular-fixup (normal compiler/dependency flags). +program dcon_singular_fixup + use ode_mod, only: r8, ode_unorm, new, u, unorm, unorm0, fixfac, index, & + mpert, msol, mlow, mhigh, istep, psifac, q, ucrit, bin_euler, & + crit_out_unit, euler_bin_unit + implicit none + + complex(r8) :: before(2, 2, 2), saved_fix(2, 2), reconstructed(2, 2) + integer :: scenario, expected_count, count, record_type, saved_msol, status, i + integer :: saved_index(2) + logical :: saved_singular + character(16) :: argument + + call get_command_argument(1, argument) + read(argument, *, iostat=status) scenario + if (status /= 0) error stop 'Specify scenario 1 or 2' + if (scenario < 1 .or. scenario > 2) error stop 'Unknown scenario' + mpert = 2 + msol = 2 + mlow = 1 + mhigh = 2 + istep = 1 + psifac = 0.5_r8 + q = 3.0_r8 + ucrit = 10.0_r8 + bin_euler = .true. + allocate(u(2, 2, 2), unorm(2), unorm0(2), fixfac(2, 2), index(2)) + u = cmplx(0.0_r8, 0.0_r8, r8) + u(:, 1, 1) = [1.0_r8, 0.0_r8] + u(:, 2, 1) = [1.0_r8, 100.0_r8] + unorm0 = 1.0_r8 + open(unit=crit_out_unit, status='scratch', form='formatted') + open(unit=euler_bin_unit, status='scratch', form='unformatted') + + expected_count = 1 + new = .true. + if (scenario == 2) then + new = .false. + call ode_unorm(.false.) + if (.not. new) error stop 'Manufactured ordinary reduction did not occur' + expected_count = 2 + end if + before = u + call ode_unorm(.true.) + rewind(euler_bin_unit) + count = 0 + do + read(euler_bin_unit, iostat=status) record_type + if (status < 0) exit + if (status /= 0) error stop 'Unreadable native reduction record' + if (record_type /= 2) error stop 'Unexpected native record type' + read(euler_bin_unit) saved_singular, saved_msol + if (saved_msol /= 2) error stop 'Wrong reduction solution count' + read(euler_bin_unit) saved_fix, saved_index + count = count + 1 + if (count == expected_count) then + if (.not. saved_singular) error stop 'Singular reduction record missing' + if (any(saved_index < 1) .or. any(saved_index > 2)) & + error stop 'Invalid reduction permutation' + if (saved_index(1) == saved_index(2)) error stop 'Repeated reduction index' + do i = 1, 2 + reconstructed = matmul(before(:, :, i), saved_fix) + if (maxval(abs(reconstructed - u(:, :, i))) > 1.0e-13_r8) & + error stop 'Recorded transform does not reproduce native basis' + end do + else + if (saved_singular) error stop 'Ordinary reduction incorrectly marked singular' + end if + end do + if (count /= expected_count) error stop 'Forced singular reduction record missing' + if (.not. new) error stop 'Forced reduction did not request norm reset' + close(euler_bin_unit) + close(crit_out_unit) + print *, 'PASS: native forced singular reduction and serialized transform', scenario +end program dcon_singular_fixup From 4f57a391929bf74f8b0014635b0b726ecb9850ee Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 13:56:32 +0200 Subject: [PATCH 247/305] Preserve all poloidal points in pmodb NetCDF function exports --- .github/workflows/make_test.yml | 2 + gpec/gpout.f | 34 +++++++----- install/TARGETS.inc | 9 ++++ regression/test/gpec_function_netcdf.f90 | 69 ++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 regression/test/gpec_function_netcdf.f90 diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index a121e9d5c..508cee8e5 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -212,6 +212,7 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-fallow-argument-mismatch -O0 -Wall -Wextra -fcheck=all,no-array-temps -fbacktrace -finit-real=snan -finit-integer=-9999 -fstack-protector-all -fsanitize=bounds -Wno-array-temporaries -Wno-unused-variable -Wno-unused-dummy-argument -Wno-compare-reals -Wno-unused-parameter -Wno-unused-label -fno-omit-frame-pointer -frecord-marker=4 -gdwarf-4 -ffloat-store -fbounds-check' make -j$(nproc) + LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-gpec-function-netcdf - name: Run Soloviev ideal example run: | @@ -286,6 +287,7 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-O0 -warn all -check all -check noarg_temp_created -traceback -fpe3 -fstack-protector -fopenmp' make -j$(nproc) + LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-gpec-function-netcdf - name: Run Soloviev ideal example run: | diff --git a/gpec/gpout.f b/gpec/gpout.f index baee0d5d3..ef4859c73 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -69,6 +69,20 @@ MODULE gpout_mod CONTAINS +c----------------------------------------------------------------------- +c Pack every poloidal point, including the periodic endpoint. +c----------------------------------------------------------------------- + PURE FUNCTION gpout_function_channels(values,imag_sign) + $ RESULT(channels) + COMPLEX(r8), DIMENSION(:,:), INTENT(IN) :: values + REAL(r8), INTENT(IN) :: imag_sign + REAL(r8), DIMENSION(SIZE(values,1),SIZE(values,2),2) :: + $ channels + + channels(:,:,1)=REAL(values,r8) + channels(:,:,2)=imag_sign*AIMAG(values) + END FUNCTION gpout_function_channels + !----------------------------------------------------------------- function str(k,fmt) !----------------------------------------------------------------- @@ -3171,18 +3185,14 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) $ -divxprpmout),AIMAG(-divxprpmout)/),(/mstep,lmpert,2/))) ) CALL check( nf90_put_var(fncid,km_id,RESHAPE((/REAL( $ -curvmout),AIMAG(-curvmout)/),(/mstep,lmpert,2/))) ) - CALL check( nf90_put_var(fncid,be_id,RESHAPE( - $ (/REAL(eulbparfout),-helicity*AIMAG(eulbparfout)/), - $ (/mstep,mthsurf,2/))) ) - CALL check( nf90_put_var(fncid,bl_id,RESHAPE( - $ (/REAL(lagbparfout),-helicity*AIMAG(lagbparfout)/), - $ (/mstep,mthsurf,2/))) ) - CALL check( nf90_put_var(fncid,x_id,RESHAPE( - $ (/REAL(-divxprpfout),-helicity*AIMAG(-divxprpfout)/), - $ (/mstep,mthsurf,2/)))) - CALL check( nf90_put_var(fncid,k_id,RESHAPE( - $ (/REAL(-curvfout),-helicity*AIMAG(-curvfout)/), - $ (/mstep,mthsurf,2/))) ) + CALL check( nf90_put_var(fncid,be_id, + $ gpout_function_channels(eulbparfout,-helicity)) ) + CALL check( nf90_put_var(fncid,bl_id, + $ gpout_function_channels(lagbparfout,-helicity)) ) + CALL check( nf90_put_var(fncid,x_id, + $ gpout_function_channels(-divxprpfout,-helicity)) ) + CALL check( nf90_put_var(fncid,k_id, + $ gpout_function_channels(-curvfout,-helicity)) ) CALL check( nf90_put_var(fncid,b_id,equilbfun) ) CALL check( nf90_close(fncid) ) ENDIF diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..072323442 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -391,6 +391,15 @@ gpec: v neededdeps $(GPEC_OBJS) pentrc lsode equil vacuum slayer coil cp ../gpec/gpec $(BINDIR) $(call print_success,gpec -> $(BINDIR)/gpec) +# Manufactured pmodb function export roundtrip using the production packer. +.PHONY: test-gpec-function-netcdf +test-gpec-function-netcdf: gpec + $(FCGPEC) -I../gpec -o $(BINDIR)/test_gpec_function_netcdf \ + ../regression/test/gpec_function_netcdf.f90 \ + $(filter-out ../gpec/gpec.o,$(GPEC_OBJS)) \ + $(GPEC_LIBDIRS) $(GPEC_LIBS) $(LDFLAGS) + OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_gpec_function_netcdf + # Xdraw XDRAW_LIBS = \ -lm \ diff --git a/regression/test/gpec_function_netcdf.f90 b/regression/test/gpec_function_netcdf.f90 new file mode 100644 index 000000000..9537fa13a --- /dev/null +++ b/regression/test/gpec_function_netcdf.f90 @@ -0,0 +1,69 @@ +! Run: make -C install test-gpec-function-netcdf +! Exercise the production pmodb packer and the native NetCDF interface. +program gpec_function_netcdf + use gpout_mod, only: gpout_function_channels, r8 + use netcdf + implicit none + integer, parameter :: npsi = 3, ntheta = 5 + complex(r8) :: values(npsi, ntheta) + real(r8) :: restored(npsi, ntheta, 2), expected_real, expected_imag + real(r8) :: imag_sign + integer :: i, j, sign_case, field, ncid, pdim, tdim, cdim, varid + integer :: shape_read(3), dimension_ids(3), k + + do j = 1, ntheta - 1 + do i = 1, npsi + values(i, j) = cmplx(10*i + j, -100*i - 3*j, r8) + end do + end do + values(:, ntheta) = values(:, 1) + + ! Both helicities, and both signs of the four pmodb quantities. + do sign_case = -1, 1, 2 + imag_sign = real(sign_case, r8) + do field = -1, 1, 2 + call check(nf90_create('gpec_function_roundtrip.nc', nf90_clobber, ncid)) + call check(nf90_def_dim(ncid, 'psi', npsi, pdim)) + call check(nf90_def_dim(ncid, 'theta', ntheta, tdim)) + call check(nf90_def_dim(ncid, 'complex', 2, cdim)) + call check(nf90_def_var(ncid, 'function', nf90_double, & + [pdim, tdim, cdim], varid)) + call check(nf90_enddef(ncid)) + call check(nf90_put_var(ncid, varid, & + gpout_function_channels(field*values, imag_sign))) + call check(nf90_close(ncid)) + + call check(nf90_open('gpec_function_roundtrip.nc', nf90_nowrite, ncid)) + call check(nf90_inq_varid(ncid, 'function', varid)) + call check(nf90_inquire_variable(ncid, varid, dimids=dimension_ids)) + do k = 1, 3 + call check(nf90_inquire_dimension(ncid, dimension_ids(k), len=shape_read(k))) + end do + if (any(shape_read /= [npsi, ntheta, 2])) error stop 'Wrong NetCDF shape' + call check(nf90_get_var(ncid, varid, restored)) + call check(nf90_close(ncid)) + do j = 1, ntheta + do i = 1, npsi + k = j + if (j == ntheta) k = 1 + ! Independent scalar oracle, including the periodic endpoint. + expected_real = real(field*(10*i + k), r8) + expected_imag = imag_sign*real(field*(-100*i - 3*k), r8) + if (restored(i, j, 1) /= expected_real) error stop 'Real channel corrupted' + if (restored(i, j, 2) /= expected_imag) error stop 'Imaginary channel corrupted' + end do + end do + end do + end do + open(unit=99, file='gpec_function_roundtrip.nc', status='old') + close(99, status='delete') + print *, 'PASS: native pmodb NetCDF channels and periodic endpoint' +contains + subroutine check(status) + integer, intent(in) :: status + if (status /= nf90_noerr) then + print *, trim(nf90_strerror(status)) + error stop 'Native NetCDF call failed' + end if + end subroutine check +end program gpec_function_netcdf From 9f4d7c97018795f207a955207406fd46808a231c Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 14:05:22 +0200 Subject: [PATCH 248/305] Exclude Intel generated interface sources on native Make re-entry --- install/SOURCES.inc | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/install/SOURCES.inc b/install/SOURCES.inc index b6e9c5272..de5e5d1ab 100644 --- a/install/SOURCES.inc +++ b/install/SOURCES.inc @@ -17,43 +17,46 @@ # section below, then add it! See vacuum as an example. #------------------------------------------------------------------------------ +# Intel interface checking emits __genmod.f90 files beside the sources. +# They are compiler artifacts, not additional application translation units. + # EQUIL sources -EQUIL_SOURCES = $(wildcard ../equil/*.f ../equil/*.f90 ../equil/*.F ../equil/*.F90) +EQUIL_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../equil/*.f ../equil/*.f90 ../equil/*.F ../equil/*.F90)) EQUIL_OBJS = $(EQUIL_SOURCES:.f=.o) EQUIL_OBJS := $(EQUIL_OBJS:.f90=.o) EQUIL_OBJS := $(EQUIL_OBJS:.F=.o) EQUIL_OBJS := $(EQUIL_OBJS:.F90=.o) # LSODE sources -LSODE_SOURCES = $(wildcard ../lsode/*.f ../lsode/*.f90 ../lsode/*.F ../lsode/*.F90) +LSODE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../lsode/*.f ../lsode/*.f90 ../lsode/*.F ../lsode/*.F90)) LSODE_OBJS = $(LSODE_SOURCES:.f=.o) LSODE_OBJS := $(LSODE_OBJS:.f90=.o) LSODE_OBJS := $(LSODE_OBJS:.F=.o) LSODE_OBJS := $(LSODE_OBJS:.F90=.o) #ZLANGE sources -ZLANGE_SOURCES = $(wildcard ../zlange/*.f ../zlange/*.f90 ../zlange/*.F ../zlange/*.F90) +ZLANGE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../zlange/*.f ../zlange/*.f90 ../zlange/*.F ../zlange/*.F90)) ZLANGE_OBJS = $(ZLANGE_SOURCES:.f=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.f90=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.F=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.F90=.o) # ZVODE sources -ZVODE_SOURCES = $(wildcard ../zvode/*.f ../zvode/*.f90 ../zvode/*.F ../zvode/*.F90) +ZVODE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../zvode/*.f ../zvode/*.f90 ../zvode/*.F ../zvode/*.F90)) ZVODE_OBJS = $(ZVODE_SOURCES:.f=.o) ZVODE_OBJS := $(ZVODE_OBJS:.f90=.o) ZVODE_OBJS := $(ZVODE_OBJS:.F=.o) ZVODE_OBJS := $(ZVODE_OBJS:.F90=.o) # ORBIT sources -ORBIT_SOURCES = $(wildcard ../orbit/*.f ../orbit/*.f90 ../orbit/*.F ../orbit/*.F90) +ORBIT_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../orbit/*.f ../orbit/*.f90 ../orbit/*.F ../orbit/*.F90)) ORBIT_OBJS = $(ORBIT_SOURCES:.f=.o) ORBIT_OBJS := $(ORBIT_OBJS:.f90=.o) ORBIT_OBJS := $(ORBIT_OBJS:.F=.o) ORBIT_OBJS := $(ORBIT_OBJS:.F90=.o) # VACUUM sources -VACUUM_SOURCES = $(wildcard ../vacuum/*.f ../vacuum/*.f90 ../vacuum/*.F ../vacuum/*.F90 ../vacuum/*.c) +VACUUM_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../vacuum/*.f ../vacuum/*.f90 ../vacuum/*.F ../vacuum/*.F90 ../vacuum/*.c)) VACUUM_OBJS = $(VACUUM_SOURCES:.f=.o) VACUUM_OBJS := $(VACUUM_OBJS:.f90=.o) VACUUM_OBJS := $(VACUUM_OBJS:.F=.o) @@ -61,77 +64,77 @@ VACUUM_OBJS := $(VACUUM_OBJS:.F90=.o) VACUUM_OBJS := $(VACUUM_OBJS:.c=.o) # PENTRC sources -PENTRC_SOURCES = $(wildcard ../pentrc/*.f ../pentrc/*.f90 ../pentrc/*.F ../pentrc/*.F90) +PENTRC_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../pentrc/*.f ../pentrc/*.f90 ../pentrc/*.F ../pentrc/*.F90)) PENTRC_OBJS = $(PENTRC_SOURCES:.f=.o) PENTRC_OBJS := $(PENTRC_OBJS:.f90=.o) PENTRC_OBJS := $(PENTRC_OBJS:.F=.o) PENTRC_OBJS := $(PENTRC_OBJS:.F90=.o) # DCON sources -DCON_SOURCES = $(wildcard ../dcon/*.f ../dcon/*.f90 ../dcon/*.F ../dcon/*.F90) +DCON_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../dcon/*.f ../dcon/*.f90 ../dcon/*.F ../dcon/*.F90)) DCON_OBJS = $(DCON_SOURCES:.f=.o) DCON_OBJS := $(DCON_OBJS:.f90=.o) DCON_OBJS := $(DCON_OBJS:.F=.o) DCON_OBJS := $(DCON_OBJS:.F90=.o) # RDCON sources -RDCON_SOURCES = $(wildcard ../rdcon/*.f ../rdcon/*.f90 ../rdcon/*.F ../rdcon/*.F90) +RDCON_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../rdcon/*.f ../rdcon/*.f90 ../rdcon/*.F ../rdcon/*.F90)) RDCON_OBJS = $(RDCON_SOURCES:.f=.o) RDCON_OBJS := $(RDCON_OBJS:.f90=.o) RDCON_OBJS := $(RDCON_OBJS:.F=.o) RDCON_OBJS := $(RDCON_OBJS:.F90=.o) # MATCH sources -MATCH_SOURCES = $(wildcard ../match/*.f ../match/*.f90 ../match/*.F ../match/*.F90) +MATCH_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../match/*.f ../match/*.f90 ../match/*.F ../match/*.F90)) MATCH_OBJS = $(MATCH_SOURCES:.f=.o) MATCH_OBJS := $(MATCH_OBJS:.f90=.o) MATCH_OBJS := $(MATCH_OBJS:.F=.o) MATCH_OBJS := $(MATCH_OBJS:.F90=.o) # RMATCH sources -RMATCH_SOURCES = $(wildcard ../rmatch/*.f ../rmatch/*.f90 ../rmatch/*.F ../rmatch/*.F90) +RMATCH_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../rmatch/*.f ../rmatch/*.f90 ../rmatch/*.F ../rmatch/*.F90)) RMATCH_OBJS = $(RMATCH_SOURCES:.f=.o) RMATCH_OBJS := $(RMATCH_OBJS:.f90=.o) RMATCH_OBJS := $(RMATCH_OBJS:.F=.o) RMATCH_OBJS := $(RMATCH_OBJS:.F90=.o) # STRIDE sources -STRIDE_SOURCES = $(wildcard ../stride/*.f ../stride/*.f90 ../stride/*.F ../stride/*.F90) +STRIDE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../stride/*.f ../stride/*.f90 ../stride/*.F ../stride/*.F90)) STRIDE_OBJS = $(STRIDE_SOURCES:.f=.o) STRIDE_OBJS := $(STRIDE_OBJS:.f90=.o) STRIDE_OBJS := $(STRIDE_OBJS:.F=.o) STRIDE_OBJS := $(STRIDE_OBJS:.F90=.o) # SUM sources -SUM_SOURCES = $(wildcard ../sum/*.f ../sum/*.f90 ../sum/*.F ../sum/*.F90) +SUM_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../sum/*.f ../sum/*.f90 ../sum/*.F ../sum/*.F90)) SUM_OBJS = $(SUM_SOURCES:.f=.o) SUM_OBJS := $(SUM_OBJS:.f90=.o) SUM_OBJS := $(SUM_OBJS:.F=.o) SUM_OBJS := $(SUM_OBJS:.F90=.o) # MULTI sources -MULTI_SOURCES = $(wildcard ../multi/*.f ../multi/*.f90 ../multi/*.F ../multi/*.F90) +MULTI_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../multi/*.f ../multi/*.f90 ../multi/*.F ../multi/*.F90)) MULTI_OBJS = $(MULTI_SOURCES:.f=.o) MULTI_OBJS := $(MULTI_OBJS:.f90=.o) MULTI_OBJS := $(MULTI_OBJS:.F=.o) MULTI_OBJS := $(MULTI_OBJS:.F90=.o) # SLAYER sources -SLAYER_SOURCES = $(wildcard ../slayer/*.f ../slayer/*.f90 ../slayer/*.F ../slayer/*.F90) +SLAYER_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../slayer/*.f ../slayer/*.f90 ../slayer/*.F ../slayer/*.F90)) SLAYER_OBJS = $(SLAYER_SOURCES:.f=.o) SLAYER_OBJS := $(SLAYER_OBJS:.f90=.o) SLAYER_OBJS := $(SLAYER_OBJS:.F=.o) SLAYER_OBJS := $(SLAYER_OBJS:.F90=.o) # COIL sources -COIL_SOURCES = $(wildcard ../coil/*.f ../coil/*.f90 ../coil/*.F ../coil/*.F90) +COIL_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../coil/*.f ../coil/*.f90 ../coil/*.F ../coil/*.F90)) COIL_OBJS = $(COIL_SOURCES:.f=.o) COIL_OBJS := $(COIL_OBJS:.f90=.o) COIL_OBJS := $(COIL_OBJS:.F=.o) COIL_OBJS := $(COIL_OBJS:.F90=.o) # GPEC sources -GPEC_SOURCES = $(wildcard ../gpec/*.f ../gpec/*.f90 ../gpec/*.F ../gpec/*.F90) +GPEC_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../gpec/*.f ../gpec/*.f90 ../gpec/*.F ../gpec/*.F90)) GPEC_OBJS = $(GPEC_SOURCES:.f=.o) GPEC_OBJS := $(GPEC_OBJS:.f90=.o) GPEC_OBJS := $(GPEC_OBJS:.F=.o) From 651df952932c229a2d4c25eb702eb1a83f9e650d Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 18 Oct 2026 17:56:29 +0200 Subject: [PATCH 249/305] Reject failed radial LSODE integration in PENTRC --- install/TARGETS.inc | 10 ++++++ pentrc/torque.F90 | 6 ++++ regression/test/pentrc_radial_istate.f90 | 42 ++++++++++++++++++++++++ regression/test/pentrc_radial_istate.md | 23 +++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 regression/test/pentrc_radial_istate.f90 create mode 100644 regression/test/pentrc_radial_istate.md diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..470606e25 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -177,6 +177,16 @@ pentrc: v neededdeps $(PENTRC_OBJS) lsode equil ar -r $(LIBDIR)/libpentrc.a $(PENTRC_OBJS) $(call print_success,pentrc -> $(BINDIR)/pentrc and libpentrc.a -> $(LIBDIR)) +# Failure injection uses the production radial wrapper and native PENTRC setup. +# Run the executable in a fresh ordinary PENTRC input directory, passing a +# negative LSODE status. It must exit with the radial failure diagnostic. +.PHONY: build-pentrc-radial-istate-test +build-pentrc-radial-istate-test: pentrc + $(FCPENTRC) -I../pentrc -I../equil \ + -o $(BINDIR)/test_pentrc_radial_istate \ + ../regression/test/pentrc_radial_istate.f90 \ + $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) + # DCON code DCON_LIBS = \ -lpentrc \ diff --git a/pentrc/torque.F90 b/pentrc/torque.F90 index a930b6194..2002c0d2a 100644 --- a/pentrc/torque.F90 +++ b/pentrc/torque.F90 @@ -1287,6 +1287,12 @@ function tintgrl_lsode(psilim,n,nl,zi,mi,wdfac,divxfac,electron,method) xlast = x call lsode(tintgrnd, neqarray, y, x, xout, itol, rtol,& atol,itask,istate, iopt, rwork, lrw, iwork, liw, noj, mf) + if (istate < 0) then + write(*,'(a,i0,a,es24.15,a,a)') & + 'PENTRC radial LSODE ISTATE=',istate, & + ' at psi=',x,' method=',trim(method) + error stop 'ERROR: tintgrl_lsode - radial integration failed.' + endif ! Export the surface response, rather than the derivative of ! LSODE's integral polynomial. At TCRIT, returned x can exceed ! internal TN by a few ulps: DINTDY then leaves dky stale. Direct diff --git a/regression/test/pentrc_radial_istate.f90 b/regression/test/pentrc_radial_istate.f90 new file mode 100644 index 000000000..11d849543 --- /dev/null +++ b/regression/test/pentrc_radial_istate.f90 @@ -0,0 +1,42 @@ +! Run in a normal PENTRC case directory, as for pentrc_radial_profile. +! Replace only the external radial LSODE entry with a deliberate failure. +! The production radial wrapper must reject it before evaluating another RHS +! or publishing a partial integral. Energy/pitch solvers use other entries. +program pentrc_radial_istate + use pentrc_interface, only: initialize_pentrc, r8, nn, zi, mi, & + wdfac, divxfac, electron, nl + use torque, only: tintgrl_lsode, output_ascii, output_netcdf + implicit none + real(r8) :: bounds(2) + complex(r8) :: integral + + call initialize_pentrc + output_ascii = .false. + output_netcdf = .false. + bounds = [0.90_r8, 0.900001_r8] + integral = tintgrl_lsode(bounds, nn, nl, zi, mi, wdfac, divxfac, & + electron, 'fgar') + error stop 'Radial wrapper returned after a failed LSODE call' +end program pentrc_radial_istate + +subroutine lsode(f, neq, y, t, tout, itol, rtol, atol, itask, istate, & + iopt, rwork, lrw, iwork, liw, jac, mf) + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + external :: f, jac + integer, intent(in) :: neq(*), itol, itask, iopt, lrw, liw, mf + integer, intent(inout) :: istate, iwork(liw) + real(dp), intent(in) :: tout, rtol(*), atol(*) + real(dp), intent(inout) :: y(*), t, rwork(lrw) + integer, save :: calls = 0 + character(16) :: argument + integer :: requested_status + + calls = calls + 1 + if (calls > 1) error stop 'Radial wrapper retried failed LSODE' + call get_command_argument(1, argument) + read(argument, *) requested_status + if (requested_status >= 0) error stop 'Test needs a negative LSODE status' + print '(a,i0)', 'Injected radial LSODE status ', requested_status + istate = requested_status +end subroutine lsode diff --git a/regression/test/pentrc_radial_istate.md b/regression/test/pentrc_radial_istate.md new file mode 100644 index 000000000..700a10eef --- /dev/null +++ b/regression/test/pentrc_radial_istate.md @@ -0,0 +1,23 @@ +# Radial LSODE failure injection + +Build the fixture with the normal native compiler/dependency configuration: + +```sh +cd install +make build-pentrc-radial-istate-test +``` + +This target builds the fixture; it does not run it. In a fresh, ordinary +PENTRC case directory containing the same input files needed by `pentrc`, run +`/path/to/gpec/bin/test_pentrc_radial_istate -2`. Exit must be nonzero and the +log must contain `PENTRC radial LSODE ISTATE=-2` and +`radial integration failed.`. It must not contain +`Radial wrapper retried failed LSODE` or +`Radial wrapper returned after a failed LSODE call`. +Repeat for -1 through -6 to cover the documented negative return statuses. + +The fixture replaces only the external radial LSODE entry. Initialization, +the radial wrapper and the energy/pitch integration remain native. The +unrepaired wrapper reaches the explicit retry marker after accepting the +injected failure. Use a fresh case directory for each invocation to keep +initialization outputs separate. No scientific tolerance is changed. From f16a6025c390afdce2a4a3b06ed3ddbb759c57fe Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 01:49:15 -0400 Subject: [PATCH 250/305] REGRESSION - MINOR - Drop standalone pchip unit test (testing setup frozen for v1.0) The test passed locally against scipy's PchipInterpolator before removal; its source remains in the PR #300 description and scripts zip. Co-Authored-By: Claude Opus 5.5 --- regression/spline_tests/.gitignore | 1 - regression/spline_tests/makefile | 13 ----- regression/spline_tests/test_spline_pchip.f90 | 56 ------------------- 3 files changed, 70 deletions(-) delete mode 100644 regression/spline_tests/.gitignore delete mode 100644 regression/spline_tests/makefile delete mode 100644 regression/spline_tests/test_spline_pchip.f90 diff --git a/regression/spline_tests/.gitignore b/regression/spline_tests/.gitignore deleted file mode 100644 index a911dc3cc..000000000 --- a/regression/spline_tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_spline_pchip diff --git a/regression/spline_tests/makefile b/regression/spline_tests/makefile deleted file mode 100644 index 65f493d71..000000000 --- a/regression/spline_tests/makefile +++ /dev/null @@ -1,13 +0,0 @@ -# Unit tests for equil/spline.f. Build GPEC first (install/make), then: make run -FC ?= gfortran -GPEC = ../.. -LIBS ?= -L$(LAPACKHOME)/lib -llapack -lblas - -test_spline_pchip: test_spline_pchip.f90 $(GPEC)/deps/lib/libequil.a - $(FC) $(FFLAGS) -I$(GPEC)/equil -o $@ $< $(GPEC)/deps/lib/libequil.a $(LIBS) - -run: test_spline_pchip - ./test_spline_pchip - -clean: - rm -f test_spline_pchip *.o *.mod diff --git a/regression/spline_tests/test_spline_pchip.f90 b/regression/spline_tests/test_spline_pchip.f90 deleted file mode 100644 index 1f9dd1a1e..000000000 --- a/regression/spline_tests/test_spline_pchip.f90 +++ /dev/null @@ -1,56 +0,0 @@ -!----------------------------------------------------------------------- -! Unit tests for spline_fit_pchip. -! 1. matches scipy.interpolate.PchipInterpolator on a pedestal-like table. -! 2. a 2-point table gives the straight line (flat for equal values). -! Exits with status 1 on failure. -!----------------------------------------------------------------------- -PROGRAM test_spline_pchip - USE spline_mod - IMPLICIT NONE - - INTEGER :: ix - REAL(r8) :: x - REAL(r8), DIMENSION(10), PARAMETER :: px=(/0._r8,.1_r8,.25_r8,.3_r8,.5_r8, & - .7_r8,.85_r8,.9_r8,.95_r8,1._r8/), py=(/1._r8,1.05_r8,1.1_r8,1.1_r8, & - 1.3_r8,1.6_r8,2.4_r8,3.5_r8,3.6_r8,3.62_r8/) - REAL(r8), DIMENSION(5), PARAMETER :: pxe=(/.05_r8,.2_r8,.4_r8,.875_r8,.97_r8/), & - ! scipy 1.x PchipInterpolator(px,py)(pxe) and its derivative - pye=(/1.02701577_r8,1.09154154_r8,1.17_r8,2.98681184_r8,3.61184_r8/), & - pde=(/0.50698198_r8,0.30930931_r8,1.2_r8,29.69419306_r8,0.496_r8/) - TYPE(spline_type) :: spl - LOGICAL :: ok=.TRUE. - - CALL spline_alloc(spl,9,1) - spl%xs=px - spl%fs(:,1)=py - CALL spline_fit_pchip(spl) - DO ix=1,5 - CALL spline_eval(spl,pxe(ix),1) - IF(ABS(spl%f(1)-pye(ix)) > 1e-7_r8 .OR. ABS(spl%f1(1)-pde(ix)) > 1e-6_r8)THEN - WRITE(*,*)"FAIL: pchip differs from scipy at x = ",pxe(ix),spl%f(1),spl%f1(1) - ok=.FALSE. - ENDIF - ENDDO - CALL spline_dealloc(spl) - WRITE(*,*)"pchip vs scipy checked at 5 points" - - CALL spline_alloc(spl,1,2) - spl%xs=(/0._r8,1._r8/) - spl%fs(:,1)=1.52_r8 - spl%fs(:,2)=(/1._r8,3._r8/) - CALL spline_fit_pchip(spl) - DO ix=0,4 - x=ix/4._r8 - CALL spline_eval(spl,x,1) - IF(ABS(spl%f(1)-1.52_r8) > 1e-14_r8 .OR. ABS(spl%f1(1)) > 1e-14_r8 & - .OR. ABS(spl%f(2)-1-2*x) > 1e-14_r8 .OR. ABS(spl%f1(2)-2) > 1e-14_r8)THEN - WRITE(*,*)"FAIL: 2-point pchip is not the straight line at x = ",x,spl%f,spl%f1 - ok=.FALSE. - ENDIF - ENDDO - CALL spline_dealloc(spl) - WRITE(*,*)"2-point tables checked at 5 points" - IF(.NOT.ok)STOP 1 - WRITE(*,*)"PASS" - -END PROGRAM test_spline_pchip From f51f98afcd1a4b0e56954c495eabc405980def44 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 03:49:59 -0400 Subject: [PATCH 251/305] PENTRC - BUG FIX - Stop the collision frequency spline from ringing in from a cold edge node read_kin samples the kinetic table on a regular grid up to psi_n = 1 and cubic-splines nu_i, nu_e and logLambda on it. When the table ends short of psi_n = 1 (or reaches T = 0 there), the edge node is cold: logLambda < 0, nu is huge and negative (or infinite), and the global cubic spline of nu rings inward to psi_n ~ 0.87, flipping the sign of the collisional damping. - Floor T at 1 meV and logLambda at 1 inside the nu and logLambda formulas only (warning when either applies). n and T are not modified. - Refit only the logLambda, nu_i, nu_e columns with local monotone (pchip) node slopes, so one steep node cannot ring inward. n, T, omega_E keep their cubic spline and are bit-identical to before. - error stop on n <= 0 or T < 0 at the sampled nodes (previously NaN and an LSODE abort). Fixes #321. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- pentrc/inputs.f90 | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index 12397fbe2..32d626259 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -31,7 +31,7 @@ module inputs use params, only : r8,xj,mp,me,e,mu0,twopi use utilities, only : get_free_file_unit,readtable,nunique,progressbar,iscdftf,iscdftb - use spline_mod, only : spline_type,spline_alloc,spline_fit,spline_fit_pchip,spline_eval,spline_write1 + use spline_mod, only : spline_type,spline_alloc,spline_dealloc,spline_fit,spline_fit_pchip,spline_eval,spline_write1 use cspline_mod, only : cspline_type,cspline_alloc,cspline_dealloc,& cspline_fit,cspline_write,cspline_eval use fspline_mod, only : fspline_eval @@ -203,7 +203,8 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) integer :: i,out_unit, tshape(2) real(r8) :: psi real(r8), dimension(0:nkin) :: zeff,zpitch,welec,wdian,wdiat,wpefac - type(spline_type) :: tmp + real(r8), dimension(0:nkin,2) :: tkev + type(spline_type) :: tmp,tmp_nu call readtable(file,table,titles,verbose,write_log) tshape = shape(table) @@ -228,19 +229,28 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) kin%xs(i) = psi kin%fs(i,1:5) = tmp%f(1:5) enddo + if(any(kin%fs(:,1:2) <= 0)) error stop "ERROR: read_kin - densities must be positive" + if(any(kin%fs(:,3:4) < 0)) error stop "ERROR: read_kin - temperatures must be non-negative" ! convert temperatures to si units kin%fs(:,3:4) = kin%fs(:,3:4)*1.602e-19 !ev to j ! collisionalities **assumes si units for n,t** + ! floor T (keV) at 1 meV and logLambda at 1 so cold (T -> 0) edges give large positive nu + tkev = max(kin%fs(:,3:4)/1.602e-16, 1.0e-6_r8) + if(any(kin%fs(:,3:4)/1.602e-16 < 1.0e-6_r8)) & + print *, " ! WARNING ! Flooring T at 1e-3 eV in collision frequencies" zeff = zimp-(kin%fs(:,1)/kin%fs(:,2))*zi*(zimp-zi) zpitch = 1.0+(1.0+mimp)/(2.0*mimp)*zimp*(zeff-1.0)/(zimp-zeff) kin%fs(:,6) = 17.3-0.5*log(kin%fs(:,2)/1.0e20) & - +1.5*log(kin%fs(:,4)/1.602e-16) + +1.5*log(tkev(:,2)) + if(any(kin%fs(:,6) < 1)) & + print *, " ! WARNING ! Flooring logLambda at 1 in collision frequencies" + kin%fs(:,6) = max(kin%fs(:,6), 1.0_r8) kin%fs(:,7) = (zpitch/3.5e17)*kin%fs(:,1)*kin%fs(:,6) & - /(sqrt(1.0*mi)*(kin%fs(:,3)/1.602e-16)**1.5) + /(sqrt(1.0*mi)*tkev(:,1)**1.5) kin%fs(:,8) = (zpitch/3.5e17)*kin%fs(:,2)*kin%fs(:,6) & - /(sqrt(me/mp)*(kin%fs(:,4)/1.602e-16)**1.5) + /(sqrt(me/mp)*tkev(:,2)**1.5) kin%fs(:,9) = zeff call spline_fit(kin,"extrap") @@ -275,6 +285,13 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) print *,' by indirect manipulation of omegae profile' endif call spline_fit(kin,"extrap") + ! local monotone slopes for loglam, nu_i, nu_e so steep edge nu cannot ring inward + call spline_alloc(tmp_nu,nkin,3) + tmp_nu%xs = kin%xs + tmp_nu%fs = kin%fs(:,6:8) + call spline_fit_pchip(tmp_nu) + kin%fs1(:,6:8) = tmp_nu%fs1 + call spline_dealloc(tmp_nu) if(write_log) print *,"Reformed kin spline with rotation manipulations" ! write log - designed as check of reading routines From f57677f5d9954e97d91b4395749372ce195d2958 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 03:54:35 -0400 Subject: [PATCH 252/305] BUILD - BUG FIX - Take the embedded version string from release tags only The version.inc rules used `git describe --tags`, which now picks the nearest lightweight tps-pin- tag on develop. The resulting string (e.g. tps-pin-08a74807-2-g1fcba5bf) pushes the generated line past column 72, which breaks the fixed-form sources that INCLUDE it (coil.F, gpout.f, rdcon and stride dcon_mod.f): "Unterminated character constant". Match release tags (v[0-9]*) so the string is v1.5.7-NNN-gSHA again. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- install/TARGETS.inc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index e20d93276..6046fb414 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -148,7 +148,7 @@ vacuum: v neededdeps $(VACUUM_OBJS) equil lsode # PENTRC code ../pentrc/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -201,7 +201,7 @@ dcon: v neededdeps $(DCON_OBJS) equil lsode vacuum pentrc # RDCON code ../rdcon/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -232,7 +232,7 @@ rdcon: v neededdeps $(RDCON_OBJS) equil lsode vacuum # STRIDE code ../stride/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -344,7 +344,7 @@ sum: v neededdeps $(SUM_OBJS) equil # Coil library ../coil/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -362,7 +362,7 @@ coil: v neededdeps $(COIL_OBJS) # note gpec keeps track of its own version, which can differ from the synced dcon, pentrc, and stride ones # this enables faster compiling during development ../gpec/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ From c033ec13b20467ec3e1fb4be5866b55fd4a01f9e Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 04:21:00 -0400 Subject: [PATCH 253/305] GPEC - MINOR - recon: docs headings, truncation wording, _r8 literals - docs/outputs.rst: give "Energy Decomposition Outputs" a section underline and move the MATCH solutions.bin entry under Binary File Outputs. - Describe identity_res as the Fourier truncation error rather than roundoff (gpeq_epfterms header, recon3 terminal/log/file notes). - Document that the standard form matches the DCON eigenvalue at any truncation, while the effective-field forms need extra harmonics, and that a curl-check PASS does not by itself confirm the energy. - 1.0 -> 1.0_r8 in gpeq_c and gpeq_curvature. Co-Authored-By: Claude Opus 5.5 --- .../regression_solovev_recon_example/README | 4 ++-- docs/outputs.rst | 16 ++++++++-------- gpec/gpeq.f | 10 ++++++---- gpec/gpout.f | 10 +++++----- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/examples/regression_solovev_recon_example/README b/docs/examples/regression_solovev_recon_example/README index 4bcd606d9..58b42f60c 100644 --- a/docs/examples/regression_solovev_recon_example/README +++ b/docs/examples/regression_solovev_recon_example/README @@ -1,7 +1,7 @@ Soloviev Energy Decomposition Regression Example ================================================ -This example checks the GPEC ideal energy decomposition (effective_field_flag) against the DCON eigenvalue it reconstructs. It reuses the Soloviev equilibrium of regression_solovev_ideal_example (sol.in, equil.in, vac.in are copies). dcon.in differs only in delta_mlow=delta_mhigh=8: the decomposition is built in real space and Fourier truncated, so the default 8 poloidal harmonics leave a ~21% error, while 32 harmonics bring it to 0.4%. +This example checks the GPEC ideal energy decomposition (effective_field_flag) against the DCON eigenvalue it reconstructs. It reuses the Soloviev equilibrium of regression_solovev_ideal_example (sol.in, equil.in, vac.in are copies). dcon.in differs only in delta_mlow=delta_mhigh=8: the effective-field form relies on identities that hold only for untruncated fields, so the default band leaves a ~21% error, while 8 extra harmonics on each side bring it to 0.4%. (standard_form_flag matches ep(1) to 0.02% even with the default band.) gpec.in selects the least stable eigenmode (mode_flag=t, mode=1) with reg_flag=f and no external field, turns off the usual outputs, and sets effective_field_flag=t and curl_check_flag=t. @@ -15,7 +15,7 @@ check_recon.py reads the last GPEC_RECON block of gpec.log (gpec.log is appended - the recon psi range equals the DCON psi range (psilow to psilim), - dW_p(recon-normalize) matches DCON ep(1) within 2% relative, and -- the curl check reports PASS. +- the curl check reports PASS (a consistency check on C; it passes even when the truncated run misses the energy). Reference values (gfortran 15, macOS arm64):: diff --git a/docs/outputs.rst b/docs/outputs.rst index ddec2b51a..d11f74c1d 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -209,11 +209,14 @@ A number of output files have a similar structure. Here the * in the file name i **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current from the external fields alone. Energy Decomposition Outputs +---------------------------- These outputs reconstruct the ideal plasma energy :math:`\delta W_p` of a single DCON eigenmode on every flux surface and break it into physically distinct terms. They require a pure DCON eigenmode: set mode_flag=t, choose mode (1 is the least stable eigenmode), and keep reg_flag=f and coil_flag=data_flag=harmonic_flag=f. GPEC stops at input if any of the three decomposition flags is set otherwise. The derivations, integrands and the meaning of every printed quantity are documented in :download:`docs/tex/recon/main.tex ` (build with pdflatex and bibtex). Each route prints a summary to the terminal that ends with its :math:`\delta W_p` total, the same total in DCON normalization (dW_p(recon-normalize) and its analogues), the DCON eigenvalue ep(mode) it should reproduce, and the :math:`\psi` range it integrated over beside the DCON range psilow to psilim. In the files below is the selected mode. recon_out=f keeps only the terminal summary, and recon_int ("spline", the default, or "trapezoid") selects the radial quadrature. +The three routes converge differently with the number of poloidal harmonics. The standard form (standard_form_flag) reproduces the DCON eigenvalue at any truncation, because it evaluates the same Galerkin energy DCON minimizes. The two effective-field forms rely on identities that hold only for the untruncated fields, so they need extra harmonics: in the Soloviev regression example they miss ep(1) by about 20% with the default band and agree to 0.4% with delta_mlow=delta_mhigh=8. The curl check and the identity residual measure this truncation; a PASS on the curl check does not by itself confirm the energy. + gpec_recon_*_sol.out **Flag** effective_field_flag @@ -238,14 +241,6 @@ gpec_recon3_terms_sol.out, gpec_recon3_integration_sol.out **Info** Split of :math:`|\mathbf{C}|^2 = |\mathbf{Q}|^2 + 2\mathrm{Re}(\mathbf{V}^*\cdot\mathbf{Q}) + |\mathbf{V}|^2` into A, B, Cpar and Iperp, with the identity check epf = A + B + Cpar + Iperp, per surface (terms) and integrated (integration). -MATCH Outputs - -solutions.bin, solutions.bin - - **Flag** sol_num (match.in) - - **Info** Ideal Euler-Lagrange solution built from DCON eigenfunction sol_num (1 is the most unstable). sol_num=1 writes solutions.bin; sol_num=n>1 writes solutions.bin so several eigenfunctions can be kept side by side. - Binary File Outputs ------------------- @@ -275,3 +270,8 @@ bnormal_spectrum.bin **Info** Surfmn type contours of the normal perturbed magnetic fields as a function of poloidal harmonic number and \Psi_{N}. +solutions.bin, solutions.bin + + **Flag** sol_num (match.in, MATCH) + + **Info** Ideal Euler-Lagrange solution built from DCON eigenfunction sol_num (1 is the most unstable). sol_num=1 writes solutions.bin; sol_num=n>1 writes solutions.bin so several eigenfunctions can be kept side by side. diff --git a/gpec/gpeq.f b/gpec/gpeq.f index c55091b60..04498bb72 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -750,7 +750,7 @@ SUBROUTINE gpeq_c(psi, ipsi) r_vec(itheta) = ro + rfac*COS(eta) jacs(itheta) = jac - w(1,1) = (1.0+ rzphi%fy(2))*twopi**2*rfac*r_vec(itheta)/jac + w(1,1) = (1.0_r8+ rzphi%fy(2))*twopi**2*rfac*r_vec(itheta)/jac w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) w(2,1) = -twopi**2*rfac*r_vec(itheta)*rzphi%fx(2)/jac @@ -1383,7 +1383,7 @@ SUBROUTINE gpeq_curvature(psi, curv_fun) r_val = ro + rfac*COS(eta) c Compute contravariant metrics - w(1,1) = (1.0+ rzphi%fy(2))*(twopi**2)*rfac*r_val/jac + w(1,1) = (1.0_r8+ rzphi%fy(2))*(twopi**2)*rfac*r_val/jac w(1,2) = -rzphi%fy(1)*pi*r_val/(rfac*jac) w(2,1) = -(twopi**2)*rfac*r_val*rzphi%fx(2)/jac @@ -1540,8 +1540,10 @@ END SUBROUTINE gpeq_K c A = int |Q|^2 / mu0 B = int 2 Re(V*.Q) / mu0 c Cpar = int mu0 sigma^2 B^2 xin^2 (= gpeq_dst's dst2) c Iperp = int mu0 p'^2 |grad psi|^2 xin^2 / B^2 -c each on the jac measure, as int (..) J dtheta / mthsurf. The -c terms satisfy epf = A + B + Cpar + Iperp to roundoff. +c each on the jac measure, as int (..) J dtheta / mthsurf. epf +c uses the Fourier-truncated C while the four terms are built +c pointwise, so epf - (A + B + Cpar + Iperp) measures the +c truncation error and shrinks as harmonics are added. c Assumes gpeq_prep_c(psi) has been called for this psi. c----------------------------------------------------------------------- SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, diff --git a/gpec/gpout.f b/gpec/gpout.f index 7b9e3a583..bacaea13c 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -7910,7 +7910,7 @@ SUBROUTINE gpout_recon3(mode, xspmn) WRITE(u_int,'(a)') "c Bernstein |C|^2 decomposition (direct)" WRITE(u_int,'(a)') "c identity: epf = A + B + Cpar + Iperp" WRITE(u_int,'(a)') "c identity_res = epf - A - B - Cpar"// - $ " - Iperp (should be near zero)" + $ " - Iperp (Fourier truncation error)" WRITE(u_int,'(a)') "c Cpar should equal dst2 from recon1"// $ " (compare against gpec_recon_integration_sol*.out)" WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) @@ -8021,8 +8021,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) WRITE(*,'(a,es17.8e3)') " Iperp_total = ", Iperp_total WRITE(*,'(a,es17.8e3)') " epf_total = ", epf_total WRITE(*,'(a,es17.8e3)') " identity_res = ", identity_residual - WRITE(*,'(a)') " (identity_res should be ~ 0. Cpar = dst2"// - $ " from recon1, not checked here.)" + WRITE(*,'(a)') " (identity_res is the Fourier truncation"// + $ " error; it shrinks as harmonics are added.)" WRITE(*,'(a,es17.8e3)') " dW_p(recon3) = ", dw_raw3 WRITE(*,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", dw_dcon3 WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", @@ -8048,8 +8048,8 @@ SUBROUTINE gpout_recon3(mode, xspmn) WRITE(u_log,'(a,es17.8e3)') " epf_total = ", epf_total WRITE(u_log,'(a,es17.8e3)') " identity_res = ", $ identity_residual - WRITE(u_log,'(a)') " (identity_res should be ~0;"// - $ " Cpar should match dst2 from recon1.)" + WRITE(u_log,'(a)') " (identity_res is the Fourier"// + $ " truncation error; Cpar should match dst2.)" WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3) = ", $ dw_raw3 WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", From 10e850d381c992a0c47c6d596cc1ad119e9df990 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 06:14:14 -0400 Subject: [PATCH 254/305] PENTRC - NEW FEATURE - Optional supplied collision rates via kinetic_format="rates-v1" With kinetic_format="rates-v1" in PENT_INPUT, the kinetic_file carries three extra columns (logLambda, nu_i, nu_e in s^-1) that replace the internal collision model. They are interpolated in log on the table so nodes stay positive, and they reuse the pchip nu slopes from the edge-collisionality fix. The default "legacy" path is unchanged. DCON kin_flag passes the same format. Slim replacement for the supplied-rates part of #322 on top of #336. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- dcon/dcon.F | 2 +- input/pentrc.in | 1 + pentrc/inputs.f90 | 37 +++++++++++++++++++++++++++++++------ pentrc/pentrc_interface.f90 | 7 ++++--- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/dcon/dcon.F b/dcon/dcon.F index df3dcde7e..bd125dc9b 100644 --- a/dcon/dcon.F +++ b/dcon/dcon.F @@ -222,7 +222,7 @@ PROGRAM dcon $ twopi*psio,ro,nn,jac_type,mlow,mhigh,mpert,mthvac) ! manually set the kinetic profiles CALL read_kin(kinetic_file,zi,zimp,mi,mimp,nfac, - $ tfac,wefac,wpfac,indebug) + $ tfac,wefac,wpfac,indebug,kinetic_format) ! manually set the perturbed equilibrium displacements ! use false flat xi and xi' for equal weighting ALLOCATE(psitmp(sq%mx+1),mtmp(mpert),xtmp(sq%mx+1,mpert)) diff --git a/input/pentrc.in b/input/pentrc.in index b1ba6e95c..f6f3f4ba9 100644 --- a/input/pentrc.in +++ b/input/pentrc.in @@ -1,6 +1,7 @@ &PENT_INPUT idconfile = "euler.bin" ! Eigenfunctions output by DCON run kinetic_file = "kinetic.txt" ! Ascii profiles. columns: psi_n, ni(m^-3), ne(m^-3), ti(eV), te(eV), omega_EXB(rad/s) + kinetic_format = "legacy" ! "rates-v1" adds columns logLambda, nu_i(s^-1), nu_e(s^-1) that replace the internal collision model data_dir = "" ! Location of pre-formed fnml matrix used in rlar and clar methods. "default" uses $GPECHOME/pentrc. peq_file = "" ! Ascii displacement. columns: psi_n, m, real(xi^psi), imag(xi^psi), real(xi^alpha), imag(xi^alpha). Default uses "gpec_xclebsch_n#.out" diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index 32d626259..db5f28a02 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -156,7 +156,7 @@ end subroutine read_equil !======================================================================= - subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) + subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log,format) !----------------------------------------------------------------------- !*DESCRIPTION: ! Read ascii file containing table of kinetic profiles ni, ne, ti, @@ -168,6 +168,10 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) ! header and/or footer, with the exception that no lines start with ! a number. ! + ! With format="rates-v1" the table has 9 columns, the 6 above plus + ! logLambda, nu_i(s^-1), nu_e(s^-1). These positive supplied values + ! replace the internal collision model (interpolated in log). + ! !*ARGUMENTS: ! file : character(256) (in) ! File path. @@ -185,6 +189,8 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) ! Scaling of rotation profile, done via manipulation of omegaE ! write_log : bool ! Writes kinetic spline to log file + ! format : character (optional) + ! "legacy" (default) or "rates-v1" ! !----------------------------------------------------------------------- @@ -194,26 +200,43 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) integer, intent(in) :: zi,zimp,mi,mimp real(r8), intent(in) :: wefac,wpfac,nfac,tfac logical :: write_log + character(*), intent(in), optional :: format ! declare local variables logical :: warning_printed = .false. real(r8), dimension(:,:), allocatable :: table character(32), dimension(:), allocatable :: titles integer, parameter :: nkin = 100 - integer :: i,out_unit, tshape(2) + integer :: i,out_unit, tshape(2), nq + logical :: supplied real(r8) :: psi real(r8), dimension(0:nkin) :: zeff,zpitch,welec,wdian,wdiat,wpefac real(r8), dimension(0:nkin,2) :: tkev + real(r8), dimension(0:nkin,3) :: nu_supplied type(spline_type) :: tmp,tmp_nu call readtable(file,table,titles,verbose,write_log) tshape = shape(table) if(write_log) print *,"Table shaped ",tshape + ! optional supplied logLambda, nu_i, nu_e columns + supplied = .false. + if(present(format)) supplied = (format == "rates-v1") + if(present(format) .and. .not. supplied .and. format /= "legacy") & + error stop "ERROR: read_kin - kinetic_format must be legacy or rates-v1" + nq = 5 + if(supplied)then + if(tshape(2) /= 9) error stop "ERROR: read_kin - rates-v1 needs 9 columns" + if(any(table(:,7:9) <= 0)) & + error stop "ERROR: read_kin - rates-v1 logLambda and nu must be positive" + table(:,7:9) = log(table(:,7:9)) ! interpolate in log to stay positive + nq = 8 + endif + ! temp spline on experimental grid - call spline_alloc(tmp,tshape(1)-1,5) + call spline_alloc(tmp,tshape(1)-1,nq) tmp%xs(0:) = table(1:,1) - do i=1,5 + do i=1,nq tmp%fs(0:,i) = table(1:,i+1) enddo call spline_fit_pchip(tmp) ! monotone between table points @@ -228,6 +251,7 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) call spline_eval(tmp,psi,0) kin%xs(i) = psi kin%fs(i,1:5) = tmp%f(1:5) + if(supplied) nu_supplied(i,:) = exp(tmp%f(6:8)) enddo if(any(kin%fs(:,1:2) <= 0)) error stop "ERROR: read_kin - densities must be positive" if(any(kin%fs(:,3:4) < 0)) error stop "ERROR: read_kin - temperatures must be non-negative" @@ -238,13 +262,13 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) ! collisionalities **assumes si units for n,t** ! floor T (keV) at 1 meV and logLambda at 1 so cold (T -> 0) edges give large positive nu tkev = max(kin%fs(:,3:4)/1.602e-16, 1.0e-6_r8) - if(any(kin%fs(:,3:4)/1.602e-16 < 1.0e-6_r8)) & + if(any(kin%fs(:,3:4)/1.602e-16 < 1.0e-6_r8) .and. .not. supplied) & print *, " ! WARNING ! Flooring T at 1e-3 eV in collision frequencies" zeff = zimp-(kin%fs(:,1)/kin%fs(:,2))*zi*(zimp-zi) zpitch = 1.0+(1.0+mimp)/(2.0*mimp)*zimp*(zeff-1.0)/(zimp-zeff) kin%fs(:,6) = 17.3-0.5*log(kin%fs(:,2)/1.0e20) & +1.5*log(tkev(:,2)) - if(any(kin%fs(:,6) < 1)) & + if(any(kin%fs(:,6) < 1) .and. .not. supplied) & print *, " ! WARNING ! Flooring logLambda at 1 in collision frequencies" kin%fs(:,6) = max(kin%fs(:,6), 1.0_r8) kin%fs(:,7) = (zpitch/3.5e17)*kin%fs(:,1)*kin%fs(:,6) & @@ -252,6 +276,7 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log) kin%fs(:,8) = (zpitch/3.5e17)*kin%fs(:,2)*kin%fs(:,6) & /(sqrt(me/mp)*tkev(:,2)**1.5) kin%fs(:,9) = zeff + if(supplied) kin%fs(:,6:8) = nu_supplied call spline_fit(kin,"extrap") if(write_log) print *,"Formed kin spline" diff --git a/pentrc/pentrc_interface.f90 b/pentrc/pentrc_interface.f90 index 6814a986c..2bdbe6de0 100644 --- a/pentrc/pentrc_interface.f90 +++ b/pentrc/pentrc_interface.f90 @@ -143,13 +143,14 @@ module pentrc_interface pmodb_file ="none", & data_dir ="." character(32) :: & + kinetic_format = "legacy",& nutype = "harmonic",& f0type = "maxwellian",& jac_in = "default",& moment = "pressure" ! namelists - namelist/pent_input/kinetic_file, gpec_file, peq_file, pmodb_file, idconfile, & + namelist/pent_input/kinetic_file, kinetic_format, gpec_file, peq_file, pmodb_file, idconfile, & data_dir, zi, zimp, mi, mimp, nl, electron, nutype, f0type, & jac_in, jsurf_in, tmag_in, power_bin, power_bpin, power_rin, power_rcin @@ -238,7 +239,7 @@ subroutine initialize_pentrc(op_kin,op_deq,op_peq) ! read (perturbed) equilibrium inputs if(indebug .and. in_kin) print *," read_kin args: ",trim(kinetic_file),zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,indebug if(in_deq) call read_equil(idconfile) - if(in_kin) call read_kin(kinetic_file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,indebug) + if(in_kin) call read_kin(kinetic_file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,indebug,kinetic_format) if(in_peq)then if(peq_file=="")then write(nstr,'(i3)')nn @@ -297,7 +298,7 @@ subroutine get_pentrc(get_nl,get_zi,get_mi,get_wdfac,get_divxfac,& print *,"read_kin args:" print *," ",trim(kinetic_file),zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,tdebug endif - call read_kin(kinetic_file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,indebug) + call read_kin(kinetic_file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,indebug,kinetic_format) ! transfer torque function variables to external program get_nl = nl From 888b5e722faee618fada94d2492af5c3d901710c Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 09:20:11 -0400 Subject: [PATCH 255/305] BUILD - MINOR - Drop the standalone small-width test (testing setup frozen for the release) The fix in energy.f90 stays. The test and its TARGETS.inc target are removed, matching 89d53a7. The analytic check it ran is recorded in the PR #320 review. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- install/TARGETS.inc | 17 ++++------- regression/test/pentrc_small_width.f90 | 41 -------------------------- 2 files changed, 5 insertions(+), 53 deletions(-) delete mode 100644 regression/test/pentrc_small_width.f90 diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 7f9b2a2e2..6046fb414 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -148,7 +148,7 @@ vacuum: v neededdeps $(VACUUM_OBJS) equil lsode # PENTRC code ../pentrc/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -177,13 +177,6 @@ pentrc: v neededdeps $(PENTRC_OBJS) lsode equil ar -r $(LIBDIR)/libpentrc.a $(PENTRC_OBJS) $(call print_success,pentrc -> $(BINDIR)/pentrc and libpentrc.a -> $(LIBDIR)) -.PHONY: test-pentrc-small-width -test-pentrc-small-width: pentrc - $(FCPENTRC) -I../pentrc -o $(BINDIR)/pentrc_small_width \ - ../regression/test/pentrc_small_width.f90 \ - $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) - OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 $(BINDIR)/pentrc_small_width - # DCON code DCON_LIBS = \ -lpentrc \ @@ -208,7 +201,7 @@ dcon: v neededdeps $(DCON_OBJS) equil lsode vacuum pentrc # RDCON code ../rdcon/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -239,7 +232,7 @@ rdcon: v neededdeps $(RDCON_OBJS) equil lsode vacuum # STRIDE code ../stride/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -351,7 +344,7 @@ sum: v neededdeps $(SUM_OBJS) equil # Coil library ../coil/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -369,7 +362,7 @@ coil: v neededdeps $(COIL_OBJS) # note gpec keeps track of its own version, which can differ from the synced dcon, pentrc, and stride ones # this enables faster compiling during development ../gpec/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ diff --git a/regression/test/pentrc_small_width.f90 b/regression/test/pentrc_small_width.f90 deleted file mode 100644 index 563836778..000000000 --- a/regression/test/pentrc_small_width.f90 +++ /dev/null @@ -1,41 +0,0 @@ -! A causal Krook width stays positive when electric precession reverses. -! With wb = wd = wt = 0 and wn = 1 - we, the native energy integral is -! Gamma(7/2)/(i*we - nu), where nu = 1e-5*abs(we). -program pentrc_small_width - use params, only: r8 - use energy_integration, only: xintgrl_lsode, xatol, xrtol, xnutype, & - xf0type, ximag, xnufac - implicit none - - complex(r8) :: integral(2), expected - real(r8) :: we, width, gamma_seven_halves, relative_error - integer :: i - - xnutype = "small" - xf0type = "maxwellian" - ximag = 0.0_r8 - xnufac = 1.0_r8 - xatol = 1.0e-14_r8 - xrtol = 1.0e-11_r8 - gamma_seven_halves = 15.0_r8*sqrt(acos(-1.0_r8))/8.0_r8 - - do i = 1, 2 - we = real(3 - 2*i, r8) - width = 1.0e-5_r8*abs(we) - expected = gamma_seven_halves/cmplx(-width, we, kind=r8) - integral(i) = xintgrl_lsode(1.0_r8-we, 0.0_r8, we, 0.0_r8, & - 0.0_r8, 1.0_r8, 0, 0.0_r8, 1, 0.5_r8, 0.5_r8, "fgar") - relative_error = abs(real(integral(i), r8)/real(expected, r8)-1.0_r8) - print '(a,f5.1,a,2es23.14,a,es12.3)', "we = ", we, & - " integral = ", integral(i), " dissipative error = ", relative_error - if (real(integral(i), r8) >= 0.0_r8) & - error stop "small collision width gives anti-damping" - if (relative_error > 1.0e-7_r8) & - error stop "small-width dissipative integral differs from analytic oracle" - if (abs(aimag(integral(i))/aimag(expected)-1.0_r8) > 1.0e-9_r8) & - error stop "small-width reactive integral differs from analytic oracle" - end do - - if (abs(integral(2)-conjg(integral(1))) > 1.0e-9_r8*abs(integral(1))) & - error stop "reversing precession violates the causal reflection symmetry" -end program pentrc_small_width From 1b2ac1957b3dc4e7a88c6524c81baa847f2d31a5 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 09:20:45 -0400 Subject: [PATCH 256/305] BUILD - MINOR - Restore TARGETS.inc to this branch's base The previous commit took TARGETS.inc from a newer develop, which pulled the version-tag change into this PR's diff. Only the energy.f90 fix should remain. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- install/TARGETS.inc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 6046fb414..e20d93276 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -148,7 +148,7 @@ vacuum: v neededdeps $(VACUUM_OBJS) equil lsode # PENTRC code ../pentrc/version.inc: - @ver="`git describe --tags --match 'v[0-9]*'`"; \ + @ver="`git describe --tags`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -201,7 +201,7 @@ dcon: v neededdeps $(DCON_OBJS) equil lsode vacuum pentrc # RDCON code ../rdcon/version.inc: - @ver="`git describe --tags --match 'v[0-9]*'`"; \ + @ver="`git describe --tags`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -232,7 +232,7 @@ rdcon: v neededdeps $(RDCON_OBJS) equil lsode vacuum # STRIDE code ../stride/version.inc: - @ver="`git describe --tags --match 'v[0-9]*'`"; \ + @ver="`git describe --tags`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -344,7 +344,7 @@ sum: v neededdeps $(SUM_OBJS) equil # Coil library ../coil/version.inc: - @ver="`git describe --tags --match 'v[0-9]*'`"; \ + @ver="`git describe --tags`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ @@ -362,7 +362,7 @@ coil: v neededdeps $(COIL_OBJS) # note gpec keeps track of its own version, which can differ from the synced dcon, pentrc, and stride ones # this enables faster compiling during development ../gpec/version.inc: - @ver="`git describe --tags --match 'v[0-9]*'`"; \ + @ver="`git describe --tags`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ From 66ff5be64ba854be03e3152b6821ea0707534fdf Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 09:48:14 -0400 Subject: [PATCH 257/305] GPEC - BUG FIX - Write the local coupling matrix to C_l*_x_out and drop the standalone test C_l_x_out ("Local ... coupling to external flux") was filled with the transposed local right-singular vectors instead of the local rows of the coupling matrix. It now mirrors the global C__x_out using singcoup_out(i,ol:ou,:). The overlap test and its CI step are removed, since the testing setup is frozen for the release (see 89d53a7). The fixes in gpec.f and gpout.f stay. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- .github/workflows/make_test.yml | 2 - gpec/gpout.f | 2 +- regression/test/local_coupling_overlap.py | 124 ---------------------- 3 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 regression/test/local_coupling_overlap.py diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index 936be9c5a..a121e9d5c 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -220,7 +220,6 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc - python3 ../../../regression/test/local_coupling_overlap.py --work-dir ../../../local_coupling_regression - name: Run Soloviev kinetic example run: | @@ -297,7 +296,6 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc - python3 ../../../regression/test/local_coupling_overlap.py --work-dir ../../../local_coupling_regression - name: Run Soloviev kinetic example run: | diff --git a/gpec/gpout.f b/gpec/gpout.f index ffc5982cd..402001bff 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -1017,7 +1017,7 @@ SUBROUTINE gpout_singcoup(spots,interpspot,nspot,rout,bpout,bout, CALL check( nf90_put_var(mncid,s_id(i), $ singcoup_out_vals(i,:)) ) IF(osing Normal termination." in (case / "gpec.log").read_text() - if enabled: - check_case(case) - else: - assert not (case / "gpec_singfld_n1.out").exists() - print("PASS: local coupling with singular-field output disabled") - - -if __name__ == "__main__": - main() From 0b416d9abaa3c0c905f25be6095d29cfac2ec841 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 10:07:41 -0400 Subject: [PATCH 258/305] BUILD - MINOR - Drop the standalone control-normal test (testing setup frozen for the release) The one-line fix in gpout.f stays. The test and its CI step are removed, matching 89d53a7. The independent check is recorded in the PR #331 review. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- .github/workflows/make_test.yml | 2 - regression/test/control_surface_normal.py | 76 ----------------------- 2 files changed, 78 deletions(-) delete mode 100644 regression/test/control_surface_normal.py diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index 8a1874111..a121e9d5c 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -220,7 +220,6 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc - python3 ../../../regression/test/control_surface_normal.py . - name: Run Soloviev kinetic example run: | @@ -297,7 +296,6 @@ jobs: cd docs/examples/regression_solovev_ideal_example ulimit -s unlimited ../../../bin/dcon && ../../../bin/gpec && ../../../bin/stride && ../../../bin/pentrc - python3 ../../../regression/test/control_surface_normal.py . - name: Run Soloviev kinetic example run: | diff --git a/regression/test/control_surface_normal.py b/regression/test/control_surface_normal.py deleted file mode 100644 index 1af711787..000000000 --- a/regression/test/control_surface_normal.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Check control-surface normals against derivatives of exported geometry. - -Run after the native Solovev ideal example. Only Python's standard library -and the same NetCDF C library used by the solver are required. -""" -import argparse -import cmath -import ctypes -from ctypes.util import find_library -import math -from pathlib import Path - - -def read_vectors(path): - lib = ctypes.CDLL(find_library("netcdf")) - ncid = ctypes.c_int() - - def check(status): - if status: - raise RuntimeError(f"NetCDF error {status} reading {path}") - - check(lib.nc_open(str(path).encode(), 0, ctypes.byref(ncid))) - try: - result = [] - for name in ("R", "z", "R_n", "z_n"): - varid, dimid, ndim = ctypes.c_int(), ctypes.c_int(), ctypes.c_int() - size = ctypes.c_size_t() - check(lib.nc_inq_varid(ncid, name.encode(), ctypes.byref(varid))) - check(lib.nc_inq_varndims(ncid, varid, ctypes.byref(ndim))) - assert ndim.value == 1, f"{name} must be a vector" - check(lib.nc_inq_vardimid(ncid, varid, ctypes.byref(dimid))) - check(lib.nc_inq_dimlen(ncid, dimid, ctypes.byref(size))) - data = (ctypes.c_double * size.value)() - check(lib.nc_get_var_double(ncid, varid, data)) - result.append(list(data)) - return result - finally: - check(lib.nc_close(ncid)) - - -def derivative(values): - """Periodic Fourier derivative, independently of bicubic derivatives.""" - n = len(values) - # Remove the constant part before differentiating its sampled DFT. - mean = sum(values) / n - coefficients = [] - for k in range(1, n // 2): - value = sum((x - mean) * cmath.exp(-2j * math.pi * k * j / n) - for j, x in enumerate(values)) / n - coefficients.append((k, value)) - return [2 * sum((1j * k * value * cmath.exp(2j * math.pi * k * j / n)).real - for k, value in coefficients) for j in range(n)] - - -def check_case(path): - r, z, rn, zn = read_vectors(path) - assert len(r) >= 33 and len({len(x) for x in (r, z, rn, zn)}) == 1 - assert all(math.isfinite(x) for a in (r, z, rn, zn) for x in a) - assert math.hypot(r[0] - r[-1], z[0] - z[-1]) < 1e-9 - dr, dz = derivative(r[:-1]), derivative(z[:-1]) - error = 0.0 - for a, b, nr, nz in zip(dr, dz, rn[:-1], zn[:-1]): - length = math.hypot(a, b) - assert length > 0 - error = max(error, math.hypot(nr - b / length, nz + a / length)) - # Differentiating the sampled fitted geometry is independent of the - # analytic bicubic derivative and includes its discretization error. - assert error < 1e-4, f"control-surface normal error {error:.6e}" - print(f"PASS: control-surface normal maximum vector error {error:.6e}") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("case", type=Path) - args = parser.parse_args() - check_case(args.case / "gpec_control_output_n1.nc") From dc1156d328d6eb53335b8479cddf6f8227822584 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 10:13:28 -0400 Subject: [PATCH 259/305] BUILD - MINOR - Drop the standalone pmodb NetCDF test (testing setup frozen for the release) The gpout.f fix stays. The test, its make target and CI step, and the __genmod.f90 source filter needed only for that target's make re-entry are removed, matching 89d53a7. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- .github/workflows/make_test.yml | 2 - install/SOURCES.inc | 37 ++++++------- install/TARGETS.inc | 9 ---- regression/test/gpec_function_netcdf.f90 | 69 ------------------------ 4 files changed, 17 insertions(+), 100 deletions(-) delete mode 100644 regression/test/gpec_function_netcdf.f90 diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index 508cee8e5..a121e9d5c 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -212,7 +212,6 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-fallow-argument-mismatch -O0 -Wall -Wextra -fcheck=all,no-array-temps -fbacktrace -finit-real=snan -finit-integer=-9999 -fstack-protector-all -fsanitize=bounds -Wno-array-temporaries -Wno-unused-variable -Wno-unused-dummy-argument -Wno-compare-reals -Wno-unused-parameter -Wno-unused-label -fno-omit-frame-pointer -frecord-marker=4 -gdwarf-4 -ffloat-store -fbounds-check' make -j$(nproc) - LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-gpec-function-netcdf - name: Run Soloviev ideal example run: | @@ -287,7 +286,6 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-O0 -warn all -check all -check noarg_temp_created -traceback -fpe3 -fstack-protector -fopenmp' make -j$(nproc) - LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-gpec-function-netcdf - name: Run Soloviev ideal example run: | diff --git a/install/SOURCES.inc b/install/SOURCES.inc index de5e5d1ab..b6e9c5272 100644 --- a/install/SOURCES.inc +++ b/install/SOURCES.inc @@ -17,46 +17,43 @@ # section below, then add it! See vacuum as an example. #------------------------------------------------------------------------------ -# Intel interface checking emits __genmod.f90 files beside the sources. -# They are compiler artifacts, not additional application translation units. - # EQUIL sources -EQUIL_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../equil/*.f ../equil/*.f90 ../equil/*.F ../equil/*.F90)) +EQUIL_SOURCES = $(wildcard ../equil/*.f ../equil/*.f90 ../equil/*.F ../equil/*.F90) EQUIL_OBJS = $(EQUIL_SOURCES:.f=.o) EQUIL_OBJS := $(EQUIL_OBJS:.f90=.o) EQUIL_OBJS := $(EQUIL_OBJS:.F=.o) EQUIL_OBJS := $(EQUIL_OBJS:.F90=.o) # LSODE sources -LSODE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../lsode/*.f ../lsode/*.f90 ../lsode/*.F ../lsode/*.F90)) +LSODE_SOURCES = $(wildcard ../lsode/*.f ../lsode/*.f90 ../lsode/*.F ../lsode/*.F90) LSODE_OBJS = $(LSODE_SOURCES:.f=.o) LSODE_OBJS := $(LSODE_OBJS:.f90=.o) LSODE_OBJS := $(LSODE_OBJS:.F=.o) LSODE_OBJS := $(LSODE_OBJS:.F90=.o) #ZLANGE sources -ZLANGE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../zlange/*.f ../zlange/*.f90 ../zlange/*.F ../zlange/*.F90)) +ZLANGE_SOURCES = $(wildcard ../zlange/*.f ../zlange/*.f90 ../zlange/*.F ../zlange/*.F90) ZLANGE_OBJS = $(ZLANGE_SOURCES:.f=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.f90=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.F=.o) ZLANGE_OBJS := $(ZLANGE_OBJS:.F90=.o) # ZVODE sources -ZVODE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../zvode/*.f ../zvode/*.f90 ../zvode/*.F ../zvode/*.F90)) +ZVODE_SOURCES = $(wildcard ../zvode/*.f ../zvode/*.f90 ../zvode/*.F ../zvode/*.F90) ZVODE_OBJS = $(ZVODE_SOURCES:.f=.o) ZVODE_OBJS := $(ZVODE_OBJS:.f90=.o) ZVODE_OBJS := $(ZVODE_OBJS:.F=.o) ZVODE_OBJS := $(ZVODE_OBJS:.F90=.o) # ORBIT sources -ORBIT_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../orbit/*.f ../orbit/*.f90 ../orbit/*.F ../orbit/*.F90)) +ORBIT_SOURCES = $(wildcard ../orbit/*.f ../orbit/*.f90 ../orbit/*.F ../orbit/*.F90) ORBIT_OBJS = $(ORBIT_SOURCES:.f=.o) ORBIT_OBJS := $(ORBIT_OBJS:.f90=.o) ORBIT_OBJS := $(ORBIT_OBJS:.F=.o) ORBIT_OBJS := $(ORBIT_OBJS:.F90=.o) # VACUUM sources -VACUUM_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../vacuum/*.f ../vacuum/*.f90 ../vacuum/*.F ../vacuum/*.F90 ../vacuum/*.c)) +VACUUM_SOURCES = $(wildcard ../vacuum/*.f ../vacuum/*.f90 ../vacuum/*.F ../vacuum/*.F90 ../vacuum/*.c) VACUUM_OBJS = $(VACUUM_SOURCES:.f=.o) VACUUM_OBJS := $(VACUUM_OBJS:.f90=.o) VACUUM_OBJS := $(VACUUM_OBJS:.F=.o) @@ -64,77 +61,77 @@ VACUUM_OBJS := $(VACUUM_OBJS:.F90=.o) VACUUM_OBJS := $(VACUUM_OBJS:.c=.o) # PENTRC sources -PENTRC_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../pentrc/*.f ../pentrc/*.f90 ../pentrc/*.F ../pentrc/*.F90)) +PENTRC_SOURCES = $(wildcard ../pentrc/*.f ../pentrc/*.f90 ../pentrc/*.F ../pentrc/*.F90) PENTRC_OBJS = $(PENTRC_SOURCES:.f=.o) PENTRC_OBJS := $(PENTRC_OBJS:.f90=.o) PENTRC_OBJS := $(PENTRC_OBJS:.F=.o) PENTRC_OBJS := $(PENTRC_OBJS:.F90=.o) # DCON sources -DCON_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../dcon/*.f ../dcon/*.f90 ../dcon/*.F ../dcon/*.F90)) +DCON_SOURCES = $(wildcard ../dcon/*.f ../dcon/*.f90 ../dcon/*.F ../dcon/*.F90) DCON_OBJS = $(DCON_SOURCES:.f=.o) DCON_OBJS := $(DCON_OBJS:.f90=.o) DCON_OBJS := $(DCON_OBJS:.F=.o) DCON_OBJS := $(DCON_OBJS:.F90=.o) # RDCON sources -RDCON_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../rdcon/*.f ../rdcon/*.f90 ../rdcon/*.F ../rdcon/*.F90)) +RDCON_SOURCES = $(wildcard ../rdcon/*.f ../rdcon/*.f90 ../rdcon/*.F ../rdcon/*.F90) RDCON_OBJS = $(RDCON_SOURCES:.f=.o) RDCON_OBJS := $(RDCON_OBJS:.f90=.o) RDCON_OBJS := $(RDCON_OBJS:.F=.o) RDCON_OBJS := $(RDCON_OBJS:.F90=.o) # MATCH sources -MATCH_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../match/*.f ../match/*.f90 ../match/*.F ../match/*.F90)) +MATCH_SOURCES = $(wildcard ../match/*.f ../match/*.f90 ../match/*.F ../match/*.F90) MATCH_OBJS = $(MATCH_SOURCES:.f=.o) MATCH_OBJS := $(MATCH_OBJS:.f90=.o) MATCH_OBJS := $(MATCH_OBJS:.F=.o) MATCH_OBJS := $(MATCH_OBJS:.F90=.o) # RMATCH sources -RMATCH_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../rmatch/*.f ../rmatch/*.f90 ../rmatch/*.F ../rmatch/*.F90)) +RMATCH_SOURCES = $(wildcard ../rmatch/*.f ../rmatch/*.f90 ../rmatch/*.F ../rmatch/*.F90) RMATCH_OBJS = $(RMATCH_SOURCES:.f=.o) RMATCH_OBJS := $(RMATCH_OBJS:.f90=.o) RMATCH_OBJS := $(RMATCH_OBJS:.F=.o) RMATCH_OBJS := $(RMATCH_OBJS:.F90=.o) # STRIDE sources -STRIDE_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../stride/*.f ../stride/*.f90 ../stride/*.F ../stride/*.F90)) +STRIDE_SOURCES = $(wildcard ../stride/*.f ../stride/*.f90 ../stride/*.F ../stride/*.F90) STRIDE_OBJS = $(STRIDE_SOURCES:.f=.o) STRIDE_OBJS := $(STRIDE_OBJS:.f90=.o) STRIDE_OBJS := $(STRIDE_OBJS:.F=.o) STRIDE_OBJS := $(STRIDE_OBJS:.F90=.o) # SUM sources -SUM_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../sum/*.f ../sum/*.f90 ../sum/*.F ../sum/*.F90)) +SUM_SOURCES = $(wildcard ../sum/*.f ../sum/*.f90 ../sum/*.F ../sum/*.F90) SUM_OBJS = $(SUM_SOURCES:.f=.o) SUM_OBJS := $(SUM_OBJS:.f90=.o) SUM_OBJS := $(SUM_OBJS:.F=.o) SUM_OBJS := $(SUM_OBJS:.F90=.o) # MULTI sources -MULTI_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../multi/*.f ../multi/*.f90 ../multi/*.F ../multi/*.F90)) +MULTI_SOURCES = $(wildcard ../multi/*.f ../multi/*.f90 ../multi/*.F ../multi/*.F90) MULTI_OBJS = $(MULTI_SOURCES:.f=.o) MULTI_OBJS := $(MULTI_OBJS:.f90=.o) MULTI_OBJS := $(MULTI_OBJS:.F=.o) MULTI_OBJS := $(MULTI_OBJS:.F90=.o) # SLAYER sources -SLAYER_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../slayer/*.f ../slayer/*.f90 ../slayer/*.F ../slayer/*.F90)) +SLAYER_SOURCES = $(wildcard ../slayer/*.f ../slayer/*.f90 ../slayer/*.F ../slayer/*.F90) SLAYER_OBJS = $(SLAYER_SOURCES:.f=.o) SLAYER_OBJS := $(SLAYER_OBJS:.f90=.o) SLAYER_OBJS := $(SLAYER_OBJS:.F=.o) SLAYER_OBJS := $(SLAYER_OBJS:.F90=.o) # COIL sources -COIL_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../coil/*.f ../coil/*.f90 ../coil/*.F ../coil/*.F90)) +COIL_SOURCES = $(wildcard ../coil/*.f ../coil/*.f90 ../coil/*.F ../coil/*.F90) COIL_OBJS = $(COIL_SOURCES:.f=.o) COIL_OBJS := $(COIL_OBJS:.f90=.o) COIL_OBJS := $(COIL_OBJS:.F=.o) COIL_OBJS := $(COIL_OBJS:.F90=.o) # GPEC sources -GPEC_SOURCES = $(filter-out %__genmod.f90,$(wildcard ../gpec/*.f ../gpec/*.f90 ../gpec/*.F ../gpec/*.F90)) +GPEC_SOURCES = $(wildcard ../gpec/*.f ../gpec/*.f90 ../gpec/*.F ../gpec/*.F90) GPEC_OBJS = $(GPEC_SOURCES:.f=.o) GPEC_OBJS := $(GPEC_OBJS:.f90=.o) GPEC_OBJS := $(GPEC_OBJS:.F=.o) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 072323442..e20d93276 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -391,15 +391,6 @@ gpec: v neededdeps $(GPEC_OBJS) pentrc lsode equil vacuum slayer coil cp ../gpec/gpec $(BINDIR) $(call print_success,gpec -> $(BINDIR)/gpec) -# Manufactured pmodb function export roundtrip using the production packer. -.PHONY: test-gpec-function-netcdf -test-gpec-function-netcdf: gpec - $(FCGPEC) -I../gpec -o $(BINDIR)/test_gpec_function_netcdf \ - ../regression/test/gpec_function_netcdf.f90 \ - $(filter-out ../gpec/gpec.o,$(GPEC_OBJS)) \ - $(GPEC_LIBDIRS) $(GPEC_LIBS) $(LDFLAGS) - OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_gpec_function_netcdf - # Xdraw XDRAW_LIBS = \ -lm \ diff --git a/regression/test/gpec_function_netcdf.f90 b/regression/test/gpec_function_netcdf.f90 deleted file mode 100644 index 9537fa13a..000000000 --- a/regression/test/gpec_function_netcdf.f90 +++ /dev/null @@ -1,69 +0,0 @@ -! Run: make -C install test-gpec-function-netcdf -! Exercise the production pmodb packer and the native NetCDF interface. -program gpec_function_netcdf - use gpout_mod, only: gpout_function_channels, r8 - use netcdf - implicit none - integer, parameter :: npsi = 3, ntheta = 5 - complex(r8) :: values(npsi, ntheta) - real(r8) :: restored(npsi, ntheta, 2), expected_real, expected_imag - real(r8) :: imag_sign - integer :: i, j, sign_case, field, ncid, pdim, tdim, cdim, varid - integer :: shape_read(3), dimension_ids(3), k - - do j = 1, ntheta - 1 - do i = 1, npsi - values(i, j) = cmplx(10*i + j, -100*i - 3*j, r8) - end do - end do - values(:, ntheta) = values(:, 1) - - ! Both helicities, and both signs of the four pmodb quantities. - do sign_case = -1, 1, 2 - imag_sign = real(sign_case, r8) - do field = -1, 1, 2 - call check(nf90_create('gpec_function_roundtrip.nc', nf90_clobber, ncid)) - call check(nf90_def_dim(ncid, 'psi', npsi, pdim)) - call check(nf90_def_dim(ncid, 'theta', ntheta, tdim)) - call check(nf90_def_dim(ncid, 'complex', 2, cdim)) - call check(nf90_def_var(ncid, 'function', nf90_double, & - [pdim, tdim, cdim], varid)) - call check(nf90_enddef(ncid)) - call check(nf90_put_var(ncid, varid, & - gpout_function_channels(field*values, imag_sign))) - call check(nf90_close(ncid)) - - call check(nf90_open('gpec_function_roundtrip.nc', nf90_nowrite, ncid)) - call check(nf90_inq_varid(ncid, 'function', varid)) - call check(nf90_inquire_variable(ncid, varid, dimids=dimension_ids)) - do k = 1, 3 - call check(nf90_inquire_dimension(ncid, dimension_ids(k), len=shape_read(k))) - end do - if (any(shape_read /= [npsi, ntheta, 2])) error stop 'Wrong NetCDF shape' - call check(nf90_get_var(ncid, varid, restored)) - call check(nf90_close(ncid)) - do j = 1, ntheta - do i = 1, npsi - k = j - if (j == ntheta) k = 1 - ! Independent scalar oracle, including the periodic endpoint. - expected_real = real(field*(10*i + k), r8) - expected_imag = imag_sign*real(field*(-100*i - 3*k), r8) - if (restored(i, j, 1) /= expected_real) error stop 'Real channel corrupted' - if (restored(i, j, 2) /= expected_imag) error stop 'Imaginary channel corrupted' - end do - end do - end do - end do - open(unit=99, file='gpec_function_roundtrip.nc', status='old') - close(99, status='delete') - print *, 'PASS: native pmodb NetCDF channels and periodic endpoint' -contains - subroutine check(status) - integer, intent(in) :: status - if (status /= nf90_noerr) then - print *, trim(nf90_strerror(status)) - error stop 'Native NetCDF call failed' - end if - end subroutine check -end program gpec_function_netcdf From 3e3881fe2efaacea347efb5857cbfccafd0b2dd3 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 10:15:41 -0400 Subject: [PATCH 260/305] BUILD - MINOR - Drop the standalone singular-fixup test (testing setup frozen for the release) The ode.f fix stays. The test, its make target and CI step, and the DCON_NO_MAIN guard in dcon.F that existed only for that target are removed, matching 89d53a7. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- .github/workflows/make_test.yml | 1 - dcon/dcon.F | 2 - install/TARGETS.inc | 11 ---- regression/test/dcon_singular_fixup.f90 | 77 ------------------------- 4 files changed, 91 deletions(-) delete mode 100644 regression/test/dcon_singular_fixup.f90 diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index 510315bc8..a121e9d5c 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -212,7 +212,6 @@ jobs: export CC=/usr/bin/gcc export FFLAGS='-fallow-argument-mismatch -O0 -Wall -Wextra -fcheck=all,no-array-temps -fbacktrace -finit-real=snan -finit-integer=-9999 -fstack-protector-all -fsanitize=bounds -Wno-array-temporaries -Wno-unused-variable -Wno-unused-dummy-argument -Wno-compare-reals -Wno-unused-parameter -Wno-unused-label -fno-omit-frame-pointer -frecord-marker=4 -gdwarf-4 -ffloat-store -fbounds-check' make -j$(nproc) - LD_LIBRARY_PATH=$PREFIX/lib:${LD_LIBRARY_PATH:-} make test-dcon-singular-fixup - name: Run Soloviev ideal example run: | diff --git a/dcon/dcon.F b/dcon/dcon.F index c6878e616..df3dcde7e 100644 --- a/dcon/dcon.F +++ b/dcon/dcon.F @@ -14,7 +14,6 @@ c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- -#ifndef DCON_NO_MAIN PROGRAM dcon USE equil_mod USE equil_out_mod @@ -319,7 +318,6 @@ PROGRAM dcon c----------------------------------------------------------------------- CALL program_stop("Normal termination.") END PROGRAM dcon -#endif c----------------------------------------------------------------------- c subprogram 2. dcon_dealloc. c deallocates internal memory. diff --git a/install/TARGETS.inc b/install/TARGETS.inc index bfd47924d..e20d93276 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -199,17 +199,6 @@ dcon: v neededdeps $(DCON_OBJS) equil lsode vacuum pentrc cp ../dcon/dcon $(BINDIR) $(call print_success,dcon -> $(BINDIR)/dcon) -.PHONY: test-dcon-singular-fixup -test-dcon-singular-fixup: dcon - $(FCDCON) -I../dcon -DDCON_NO_MAIN $(MODFLAG) ../dcon \ - -c ../dcon/dcon.F -o ../dcon/dcon_test_support.o - $(FCDCON) -I../dcon -o $(BINDIR)/test_dcon_singular_fixup \ - ../regression/test/dcon_singular_fixup.f90 \ - $(filter-out ../dcon/dcon.o,$(DCON_OBJS)) \ - ../dcon/dcon_test_support.o $(DCON_LIBDIRS) $(DCON_LIBS) $(LDFLAGS) - OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_dcon_singular_fixup 1 - OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 $(BINDIR)/test_dcon_singular_fixup 2 - # RDCON code ../rdcon/version.inc: @ver="`git describe --tags`"; \ diff --git a/regression/test/dcon_singular_fixup.f90 b/regression/test/dcon_singular_fixup.f90 deleted file mode 100644 index a56179c63..000000000 --- a/regression/test/dcon_singular_fixup.f90 +++ /dev/null @@ -1,77 +0,0 @@ -! Link the production DCON objects, replacing only the main program. -! Both scenarios require a native singular reduction record after new=.true. -! Run: make -C install test-dcon-singular-fixup (normal compiler/dependency flags). -program dcon_singular_fixup - use ode_mod, only: r8, ode_unorm, new, u, unorm, unorm0, fixfac, index, & - mpert, msol, mlow, mhigh, istep, psifac, q, ucrit, bin_euler, & - crit_out_unit, euler_bin_unit - implicit none - - complex(r8) :: before(2, 2, 2), saved_fix(2, 2), reconstructed(2, 2) - integer :: scenario, expected_count, count, record_type, saved_msol, status, i - integer :: saved_index(2) - logical :: saved_singular - character(16) :: argument - - call get_command_argument(1, argument) - read(argument, *, iostat=status) scenario - if (status /= 0) error stop 'Specify scenario 1 or 2' - if (scenario < 1 .or. scenario > 2) error stop 'Unknown scenario' - mpert = 2 - msol = 2 - mlow = 1 - mhigh = 2 - istep = 1 - psifac = 0.5_r8 - q = 3.0_r8 - ucrit = 10.0_r8 - bin_euler = .true. - allocate(u(2, 2, 2), unorm(2), unorm0(2), fixfac(2, 2), index(2)) - u = cmplx(0.0_r8, 0.0_r8, r8) - u(:, 1, 1) = [1.0_r8, 0.0_r8] - u(:, 2, 1) = [1.0_r8, 100.0_r8] - unorm0 = 1.0_r8 - open(unit=crit_out_unit, status='scratch', form='formatted') - open(unit=euler_bin_unit, status='scratch', form='unformatted') - - expected_count = 1 - new = .true. - if (scenario == 2) then - new = .false. - call ode_unorm(.false.) - if (.not. new) error stop 'Manufactured ordinary reduction did not occur' - expected_count = 2 - end if - before = u - call ode_unorm(.true.) - rewind(euler_bin_unit) - count = 0 - do - read(euler_bin_unit, iostat=status) record_type - if (status < 0) exit - if (status /= 0) error stop 'Unreadable native reduction record' - if (record_type /= 2) error stop 'Unexpected native record type' - read(euler_bin_unit) saved_singular, saved_msol - if (saved_msol /= 2) error stop 'Wrong reduction solution count' - read(euler_bin_unit) saved_fix, saved_index - count = count + 1 - if (count == expected_count) then - if (.not. saved_singular) error stop 'Singular reduction record missing' - if (any(saved_index < 1) .or. any(saved_index > 2)) & - error stop 'Invalid reduction permutation' - if (saved_index(1) == saved_index(2)) error stop 'Repeated reduction index' - do i = 1, 2 - reconstructed = matmul(before(:, :, i), saved_fix) - if (maxval(abs(reconstructed - u(:, :, i))) > 1.0e-13_r8) & - error stop 'Recorded transform does not reproduce native basis' - end do - else - if (saved_singular) error stop 'Ordinary reduction incorrectly marked singular' - end if - end do - if (count /= expected_count) error stop 'Forced singular reduction record missing' - if (.not. new) error stop 'Forced reduction did not request norm reset' - close(euler_bin_unit) - close(crit_out_unit) - print *, 'PASS: native forced singular reduction and serialized transform', scenario -end program dcon_singular_fixup From efd4f9045f253b5fddf679ede1fd56e5b0b9a243 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 10:16:57 -0400 Subject: [PATCH 261/305] BUILD - MINOR - Drop the standalone radial ISTATE test (testing setup frozen for the release) The torque.F90 fix stays. The failure-injection fixture, its notes and its make target are removed, matching 89d53a7. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- install/TARGETS.inc | 10 ------ regression/test/pentrc_radial_istate.f90 | 42 ------------------------ regression/test/pentrc_radial_istate.md | 23 ------------- 3 files changed, 75 deletions(-) delete mode 100644 regression/test/pentrc_radial_istate.f90 delete mode 100644 regression/test/pentrc_radial_istate.md diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 470606e25..e20d93276 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -177,16 +177,6 @@ pentrc: v neededdeps $(PENTRC_OBJS) lsode equil ar -r $(LIBDIR)/libpentrc.a $(PENTRC_OBJS) $(call print_success,pentrc -> $(BINDIR)/pentrc and libpentrc.a -> $(LIBDIR)) -# Failure injection uses the production radial wrapper and native PENTRC setup. -# Run the executable in a fresh ordinary PENTRC input directory, passing a -# negative LSODE status. It must exit with the radial failure diagnostic. -.PHONY: build-pentrc-radial-istate-test -build-pentrc-radial-istate-test: pentrc - $(FCPENTRC) -I../pentrc -I../equil \ - -o $(BINDIR)/test_pentrc_radial_istate \ - ../regression/test/pentrc_radial_istate.f90 \ - $(PENTRC_LIBDIRS) -lpentrc $(PENTRC_LIBS) $(LDFLAGS) - # DCON code DCON_LIBS = \ -lpentrc \ diff --git a/regression/test/pentrc_radial_istate.f90 b/regression/test/pentrc_radial_istate.f90 deleted file mode 100644 index 11d849543..000000000 --- a/regression/test/pentrc_radial_istate.f90 +++ /dev/null @@ -1,42 +0,0 @@ -! Run in a normal PENTRC case directory, as for pentrc_radial_profile. -! Replace only the external radial LSODE entry with a deliberate failure. -! The production radial wrapper must reject it before evaluating another RHS -! or publishing a partial integral. Energy/pitch solvers use other entries. -program pentrc_radial_istate - use pentrc_interface, only: initialize_pentrc, r8, nn, zi, mi, & - wdfac, divxfac, electron, nl - use torque, only: tintgrl_lsode, output_ascii, output_netcdf - implicit none - real(r8) :: bounds(2) - complex(r8) :: integral - - call initialize_pentrc - output_ascii = .false. - output_netcdf = .false. - bounds = [0.90_r8, 0.900001_r8] - integral = tintgrl_lsode(bounds, nn, nl, zi, mi, wdfac, divxfac, & - electron, 'fgar') - error stop 'Radial wrapper returned after a failed LSODE call' -end program pentrc_radial_istate - -subroutine lsode(f, neq, y, t, tout, itol, rtol, atol, itask, istate, & - iopt, rwork, lrw, iwork, liw, jac, mf) - use, intrinsic :: iso_fortran_env, only: dp => real64 - implicit none - external :: f, jac - integer, intent(in) :: neq(*), itol, itask, iopt, lrw, liw, mf - integer, intent(inout) :: istate, iwork(liw) - real(dp), intent(in) :: tout, rtol(*), atol(*) - real(dp), intent(inout) :: y(*), t, rwork(lrw) - integer, save :: calls = 0 - character(16) :: argument - integer :: requested_status - - calls = calls + 1 - if (calls > 1) error stop 'Radial wrapper retried failed LSODE' - call get_command_argument(1, argument) - read(argument, *) requested_status - if (requested_status >= 0) error stop 'Test needs a negative LSODE status' - print '(a,i0)', 'Injected radial LSODE status ', requested_status - istate = requested_status -end subroutine lsode diff --git a/regression/test/pentrc_radial_istate.md b/regression/test/pentrc_radial_istate.md deleted file mode 100644 index 700a10eef..000000000 --- a/regression/test/pentrc_radial_istate.md +++ /dev/null @@ -1,23 +0,0 @@ -# Radial LSODE failure injection - -Build the fixture with the normal native compiler/dependency configuration: - -```sh -cd install -make build-pentrc-radial-istate-test -``` - -This target builds the fixture; it does not run it. In a fresh, ordinary -PENTRC case directory containing the same input files needed by `pentrc`, run -`/path/to/gpec/bin/test_pentrc_radial_istate -2`. Exit must be nonzero and the -log must contain `PENTRC radial LSODE ISTATE=-2` and -`radial integration failed.`. It must not contain -`Radial wrapper retried failed LSODE` or -`Radial wrapper returned after a failed LSODE call`. -Repeat for -1 through -6 to cover the documented negative return statuses. - -The fixture replaces only the external radial LSODE entry. Initialization, -the radial wrapper and the energy/pitch integration remain native. The -unrepaired wrapper reaches the explicit retry marker after accepting the -injected failure. Use a fresh case directory for each invocation to keep -initialization outputs separate. No scientific tolerance is changed. From 0db10db2424391cd3eb9b5e217b053f62306caa4 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 10:14:08 -0400 Subject: [PATCH 262/305] BUILD - MINOR - Drop the standalone no-rational-surface test (testing setup frozen for the release) The ode.f fix stays. The test is removed, matching 89d53a7. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- regression/test/dcon_no_rational_surface.py | 77 --------------------- 1 file changed, 77 deletions(-) delete mode 100644 regression/test/dcon_no_rational_surface.py diff --git a/regression/test/dcon_no_rational_surface.py b/regression/test/dcon_no_rational_surface.py deleted file mode 100644 index 3bd8c6524..000000000 --- a/regression/test/dcon_no_rational_surface.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -"""Native DCON regression for an equilibrium with no n=3 rational surface. - -Build with gfortran -O0 -fcheck=all, then execute this module. As in the other -regression/test modules, run(repo, loc) returns True on success. -""" -import argparse -import math -import os -from pathlib import Path -import struct -import subprocess - - -def run(repo=None, loc=None): - repo = Path(repo or Path(__file__).resolve().parents[2]).resolve() - loc = Path(loc or repo / 'regression/runs/dcon_no_rational_surface').resolve() - loc.mkdir(parents=True, exist_ok=True) - (loc / 'lar.in').write_text('''&lar_input - ma=256, mtau=128, lar_r0=6.2, lar_a=.62, beta0=1e-6, - q0=2.1666666666666665, p_pres=2, p_sig=100, sigma_type='default', - zeroth=.true. -/ -''') - (loc / 'equil.in').write_text('''&equil_control - eq_type='lar', eq_filename='lar.in', jac_type='boozer', - grid_type='ldp', psilow=.005, psihigh=.99, - mpsi=32, mtheta=64, newq0=0, etol=1e-7 -/ -&equil_output - gse_flag=.false., bin_eq_2d=.true. -/ -''') - (loc / 'dcon.in').write_text('''&dcon_control - bal_flag=.false., mat_flag=.true., ode_flag=.true., - vac_flag=.false., mer_flag=.false., sas_flag=.false., - psiedge=.99, qlow=1.02, qhigh=30.2, sing_start=0, - reform_eq_with_psilim=.false., nn=3, delta_mlow=0, - delta_mhigh=0, delta_mband=0, parallel_threads=1 -/ -&dcon_output - bin_euler=.true., euler_stride=3, netcdf_out=.false. -/ -''') - euler = loc / 'euler.bin' - if euler.exists(): - euler.unlink() - env = dict(os.environ, OMP_NUM_THREADS='1', OPENBLAS_NUM_THREADS='1') - result = subprocess.run([str(repo / 'bin/dcon')], cwd=loc, env=env, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - timeout=60, text=True) - (loc / 'dcon.log').write_text(result.stdout) - if result.returncode or 'Normal termination.' not in result.stdout: - raise AssertionError('DCON did not finish; see ' + str(loc / 'dcon.log')) - # Independent oracle: n*q spans no integer, read from the native carrier. - # Fortran sequential records use four-byte markers with GNU defaults. - with euler.open('rb') as stream: - records = [] - for _ in range(7): - length = struct.unpack('=i', stream.read(4))[0] - records.append(stream.read(length)) - assert struct.unpack('=i', stream.read(4))[0] == length - nn = struct.unpack('=5i2d', records[0])[2] - qmin, qmax = struct.unpack_from('=21d', records[4])[16:18] - assert nn == 3 and 2.16 < qmin <= qmax < 2.3, (nn, qmin, qmax) - assert math.ceil(nn * qmin) > math.floor(nn * qmax), (nn, qmin, qmax) - assert 'psi = 9.900E-01' in result.stdout, 'ODE did not reach the edge' - print(f'PASS: n={nn}, q=[{qmin:.9f}, {qmax:.9f}], no rational surface; ODE reaches .99') - return True - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--repo') - parser.add_argument('--loc') - args = parser.parse_args() - run(args.repo, args.loc) From 68de6992c08db6095782f4d39d705813738b57b5 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Mon, 19 Oct 2026 23:28:21 +0200 Subject: [PATCH 263/305] DOCS - BUG FIX - State the gauge of the pmodb lagb column and how the pmodb columns relate lagb (gpout_pmodb) is the Lagrangian field strength of xi_perp + lambda B with xi^zeta = 0 in the working (jac_type) chart. It is neither the xi_perp-only value nor Park et al. (2009) Eq. 7 with GPEC's divergence-free xi, and lagb = -(Bdivxprp + Bkxprp) is not an identity: lagb + Bdivxprp + Bkxprp = -(xi_perp^zeta/q) d_theta B - xi_perp.F/B, F = (curl B) x B - mu0 grad p. The docs called lagb "the Lagrangian |B| necessary for NTV post processing" and gpdiag labelled it "with parallel components". docs/pmodb_gauge.rst derives every column in the GPEC chart for any jac_type, the regularisation terms of reg_flag, and what PENTRC and perturbed-Boozer/Hamada consumers need. The identities are proved with the FortSym native zero test (docs/derivations/pmodb_gauge, verdicts JSON); FortSym is needed only to rerun the proofs. No code change. --- docs/derivations/pmodb_gauge/derive.py | 93 ++++ .../pmodb_gauge/fortsym_verdicts.json | 275 ++++++++++ docs/derivations/pmodb_gauge/identities.py | 481 ++++++++++++++++++ docs/outputs.rst | 4 +- docs/pmodb_gauge.rst | 172 +++++++ docs/site.rst | 1 + gpec/gpdiag.f | 4 +- gpec/gpout.f | 3 + 8 files changed, 1029 insertions(+), 4 deletions(-) create mode 100644 docs/derivations/pmodb_gauge/derive.py create mode 100644 docs/derivations/pmodb_gauge/fortsym_verdicts.json create mode 100644 docs/derivations/pmodb_gauge/identities.py create mode 100644 docs/pmodb_gauge.rst diff --git a/docs/derivations/pmodb_gauge/derive.py b/docs/derivations/pmodb_gauge/derive.py new file mode 100644 index 000000000..17337b82d --- /dev/null +++ b/docs/derivations/pmodb_gauge/derive.py @@ -0,0 +1,93 @@ +"""Run the pmodb gauge identities through FortSym and record the verdicts. + +Usage (FortSym checkout with a built C ABI library):: + + PYTHONPATH=$FORTSYM/python python3 docs/derivations/pmodb_gauge/derive.py + +Writes ``fortsym_verdicts.json`` beside this file. ``PROVED`` means FortSym's +native zero decision returned true; ``UNKNOWN`` and ``NONZERO`` fail an +identity and are the required outcome for a negative control. No numerical +probe is accepted as evidence. +""" + +from __future__ import annotations + +import json +import subprocess +import sys +import time +from pathlib import Path + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +import fortsym # noqa: E402 +import fortsym.sympy as fs # noqa: E402 +import identities # noqa: E402 + + +def verdict(expr) -> str: + if not hasattr(expr, "is_zero"): + return "PROVED" if expr == 0 else "NONZERO" + value = expr.is_zero + return {True: "PROVED", False: "NONZERO"}.get(value, "UNKNOWN") + + +def counterexample(expr): + """Exact rational point at which FortSym decides the residual is non-zero.""" + symbols = sorted(expr.free_symbols, key=str) + for attempt in range(1, 6): + point, value = {}, expr + for j, sym in enumerate(symbols): + num = 7 * attempt + 3 * j + 1 + point[str(sym)] = f"{num}/{5 + j}" + value = value.subs(sym, fs.Rational(num, 5 + j)) + if value.is_zero is False: + return point + return None + + +def revision() -> str: + root = Path(fortsym.__file__).resolve().parents[2] + try: + return subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], + capture_output=True, text=True, check=True).stdout.strip() + except (OSError, subprocess.CalledProcessError): + return "unknown" + + +def main() -> int: + records, failed = [], [] + for label, equation, residual in identities.all_identities(fs): + start = time.perf_counter() + v = verdict(residual) + records.append({"label": label, "equation": equation, "verdict": v, + "seconds": round(time.perf_counter() - start, 3)}) + if v != "PROVED": + failed.append(label) + print(f"{label:6s} {v:8s} {equation}", flush=True) + for label, equation, residual in identities.open_gaps(fs): + v = verdict(residual) + records.append({"label": label, "equation": equation, "verdict": v, + "open_gap": True}) + print(f"{label:6s} {v:8s} (open FortSym gap) {equation}", flush=True) + for label, equation, residual in identities.negative_controls(fs): + v = verdict(residual) + records.append({"label": label, "equation": equation, "verdict": v, + "negative_control": True}) + witness = counterexample(residual) if v != "NONZERO" else None + if witness is not None: + records[-1]["counterexample"] = witness + if v == "PROVED" or (v != "NONZERO" and witness is None): + failed.append(label) + print(f"{label:6s} {v:8s} (negative control" + f"{', witness ' + str(witness) if witness else ''}) {equation}", flush=True) + out = {"engine": "fortsym native zero test via C ABI (fortsym.sympy)", + "fortsym_revision": revision(), "checks": records, "failed": failed} + (HERE / "fortsym_verdicts.json").write_text(json.dumps(out, indent=2) + "\n") + print("FAILED: " + " ".join(failed) if failed else "all identities proved") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json new file mode 100644 index 000000000..5f0f0e2fd --- /dev/null +++ b/docs/derivations/pmodb_gauge/fortsym_verdicts.json @@ -0,0 +1,275 @@ +{ + "engine": "fortsym native zero test via C ABI (fortsym.sympy)", + "fortsym_revision": "7e8ef7abdd90c49bb5f75e2c119e7d181656bcc6", + "checks": [ + { + "label": "G1.1", + "equation": "J dB^psi = GPEC bwp (pointwise)", + "verdict": "PROVED", + "seconds": 0.01 + }, + { + "label": "G1.2", + "equation": "J dB^theta = GPEC bwt (pointwise)", + "verdict": "PROVED", + "seconds": 0.004 + }, + { + "label": "G1.3", + "equation": "J dB^zeta = GPEC bwz (pointwise)", + "verdict": "PROVED", + "seconds": 0.007 + }, + { + "label": "G1.4", + "equation": "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "G1.5", + "equation": "xi_s(xi + lam B) = xi_s(xi)", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "G1F.1", + "equation": "bwp = chi1 (m - n q) 2 pi i xsp", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "G1F.2", + "equation": "bwt = -(chi1 xsp1 + 2 pi i n xss)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G1F.3", + "equation": "bwz = -(chi1 (q1 xsp + q xsp1) + 2 pi i m xss)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G2.1", + "equation": "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "G2.2", + "equation": "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1 + q J div xi", + "verdict": "PROVED", + "seconds": 0.005 + }, + { + "label": "G2.3", + "equation": "q xwt - xwz = J xi_s / chi1", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "G3", + "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", + "verdict": "PROVED", + "seconds": 0.013 + }, + { + "label": "G4.1", + "equation": "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", + "verdict": "PROVED", + "seconds": 0.27 + }, + { + "label": "G4.2", + "equation": "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", + "verdict": "PROVED", + "seconds": 0.119 + }, + { + "label": "G4.3", + "equation": "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", + "verdict": "PROVED", + "seconds": 0.012 + }, + { + "label": "G4.4", + "equation": "xi_perp^theta = xms/(chi1 q) + xmz/(J q) - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", + "verdict": "PROVED", + "seconds": 0.183 + }, + { + "label": "G5.1", + "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", + "verdict": "PROVED", + "seconds": 0.232 + }, + { + "label": "G5.2", + "equation": "xi_g^theta = xi_s/(chi1 q)", + "verdict": "PROVED", + "seconds": 0.757 + }, + { + "label": "G5.2b", + "equation": "xi_g^psi = xi^psi", + "verdict": "PROVED", + "seconds": 0.008 + }, + { + "label": "G5.3", + "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", + "verdict": "PROVED", + "seconds": 0.076 + }, + { + "label": "G5.4", + "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", + "verdict": "PROVED", + "seconds": 0.026 + }, + { + "label": "G5.5", + "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", + "verdict": "PROVED", + "seconds": 0.017 + }, + { + "label": "G5.6", + "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", + "verdict": "PROVED", + "seconds": 0.038 + }, + { + "label": "G5.7", + "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", + "verdict": "PROVED", + "seconds": 0.014 + }, + { + "label": "G5.8", + "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", + "verdict": "PROVED", + "seconds": 0.735 + }, + { + "label": "G7.1", + "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_psi", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G7.2", + "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_theta", + "verdict": "PROVED", + "seconds": 0.007 + }, + { + "label": "G7.3", + "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_zeta", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "C1", + "equation": "b.curl(xi_perp x B) + xi_perp.grad B = -B(div xi_perp + kappa.xi_perp)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "C2", + "equation": "B kappa.xi_perp = xi_perp.grad B + xi_perp.((curl B) x B)/B", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "R1", + "equation": "J div xi (reg) = (1 - R)(J R xsp1 + J' xsp)", + "verdict": "PROVED", + "seconds": 0.014 + }, + { + "label": "R2", + "equation": "J div xi (R = 1) = 0", + "verdict": "PROVED", + "seconds": 0.028 + }, + { + "label": "R3", + "equation": "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "K1", + "equation": "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "K3", + "equation": "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "K4", + "equation": "pmodb_file-path drive change = -mu B lam dB/dl / v", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G6", + "equation": "B.dB + xi_perp.grad B^2 + B^2 div xi_perp + xi_perp.(curl B x B) = 0", + "verdict": "UNKNOWN", + "open_gap": true + }, + { + "label": "N1", + "equation": "lagb is the xi_perp-only Lagrangian |B| (false)", + "verdict": "UNKNOWN", + "negative_control": true, + "counterexample": { + "Bm_psi": "8/5", + "Bm_theta": "11/6", + "J_0": "14/7", + "chi1": "17/8", + "g_11_0": "20/9", + "g_12_0": "23/10", + "g_13_0": "26/11", + "g_22_0": "29/12", + "g_23_0": "32/13", + "q_0": "35/14", + "xp_0": "38/15", + "xt_0": "41/16", + "xz_0": "44/17" + } + }, + { + "label": "N2", + "equation": "lagb is Park 2009 Eq. 7 with the div-free xi (false)", + "verdict": "UNKNOWN", + "negative_control": true, + "counterexample": { + "Bm_psi": "8/5", + "Bm_theta": "11/6", + "chi1": "14/7", + "q_0": "17/8", + "xp_0": "20/9", + "xt_0": "23/10", + "xz_0": "26/11" + } + }, + { + "label": "K2neg", + "equation": "fcgl with kapx = -(dbob + divx)/2 equals the CGL density", + "verdict": "UNKNOWN", + "negative_control": true, + "counterexample": { + "D": "8/5", + "K": "11/6" + } + } + ], + "failed": [] +} diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py new file mode 100644 index 000000000..d52b3bcd0 --- /dev/null +++ b/docs/derivations/pmodb_gauge/identities.py @@ -0,0 +1,481 @@ +"""Symbolic identities behind the GPEC pmodb |B| columns. + +Every function returns ``(label, equation, residual)`` triples; a residual must +vanish identically. The module is written against a SymPy-shaped namespace +``sp`` so that the same derivation runs on FortSym (``fortsym.sympy``, the +authority used by ``derive.py``). Labels refer to ``docs/pmodb_gauge.rst``. + +GPEC/DCON coordinates (psi, theta, zeta): angles in turns, general +straight-field-line Jacobian ``J(psi, theta)`` (any ``jac_type``), + + B = chi1 grad(zeta - q theta) x grad(psi), J B^theta = chi1, + J B^zeta = chi1 q, B^psi = 0, + +and the surface displacement (Glasser 2016 Eqs. 9 and 18) + + xi_s = (xi x B)_psi = chi1 (q xi^theta - xi^zeta). + +Pointwise identities are proved on jets: every field is an affine (1-jet) +polynomial in (psi, theta, zeta) about the origin with independent symbolic +coefficients, and the residual is evaluated at the origin after +differentiation. Each identity involves at most first derivatives of the +jetted fields, so it holds for all smooth fields iff it holds for all 1-jets. +The metric is general except for ``det g = J**2``, imposed by solving for +``g_33``. +""" + +from __future__ import annotations + +COORDS = ("psi", "theta", "zeta") + + +class Dual: + """First-order jet at the origin: value ``v`` and derivatives ``d[i]``. + + ``d is None`` marks a value whose own derivatives were not carried (a + derivative of a jet); differentiating it again raises. + """ + + def __init__(self, v, d=(0, 0, 0)): + self.v, self.d = v, (None if d is None else tuple(d)) + + @staticmethod + def lift(x): + return x if isinstance(x, Dual) else Dual(x) + + def _need(self): + if self.d is None: + raise ValueError("second derivative requested from a 1-jet") + return self.d + + def __add__(self, o): + o = Dual.lift(o) + if self.d is None or o.d is None: + return Dual(self.v + o.v, None) + return Dual(self.v + o.v, [a + b for a, b in zip(self.d, o.d)]) + + __radd__ = __add__ + + def __neg__(self): + return Dual(-self.v, None if self.d is None else [-a for a in self.d]) + + def __sub__(self, o): + return self + (-Dual.lift(o)) + + def __rsub__(self, o): + return Dual.lift(o) - self + + def __mul__(self, o): + o = Dual.lift(o) + if self.d is None or o.d is None: + return Dual(self.v * o.v, None) + return Dual(self.v * o.v, [self.v * b + a * o.v for a, b in zip(self.d, o.d)]) + + __rmul__ = __mul__ + + def inv(self): + if self.d is None: + return Dual(1 / self.v, None) + return Dual(1 / self.v, [-a / self.v ** 2 for a in self.d]) + + def __truediv__(self, o): + return self * Dual.lift(o).inv() + + def __rtruediv__(self, o): + return Dual.lift(o) * self.inv() + + def __pow__(self, k): + assert isinstance(k, int) and k >= 1 + out = self + for _ in range(k - 1): + out = out * self + return out + + +class Setup: + """Jetted GPEC fields (1-jets at the origin) in a SymPy-shaped namespace.""" + + def __init__(self, sp, metric=False): + self.sp = sp + self.u = (0, 1, 2) # psi, theta, zeta + self.chi1 = Dual(sp.Symbol("chi1", positive=True)) + self.q = self.jet("q", (0,)) + self.J = self.jet("J", (0, 1)) + # Contravariant displacement components (general, incl. xi_parallel). + self.xi = [self.jet(f"x{c[0]}", self.u) for c in COORDS] + self.Bup = [Dual(0), self.chi1 / self.J, self.chi1 * self.q / self.J] + if metric: + g = {} + for (i, j) in ((0, 0), (0, 1), (0, 2), (1, 1), (1, 2)): + g[(i, j)] = self.jet(f"g_{i + 1}{j + 1}", (0, 1)) + minor = g[(0, 0)] * g[(1, 1)] - g[(0, 1)] ** 2 + rest = (-g[(0, 0)] * g[(1, 2)] ** 2 + 2 * g[(0, 1)] * g[(1, 2)] * g[(0, 2)] + - g[(1, 1)] * g[(0, 2)] ** 2) + g[(2, 2)] = (self.J ** 2 - rest) / minor + self.g = [[g[(min(i, j), max(i, j))] for j in range(3)] for i in range(3)] + + def jet(self, name, deps): + sp = self.sp + return Dual(sp.Symbol(f"{name}_0"), + [sp.Symbol(f"{name}_{COORDS[i]}") if i in deps else 0 for i in range(3)]) + + def d(self, f, i): + f = Dual.lift(f) + return Dual(f._need()[i], None) + + def at0(self, e): + return Dual.lift(e).v + + # --- tensor algebra in the (psi, theta, zeta) chart ----------------------- + @staticmethod + def eps(i, j, k): + return (i - j) * (j - k) * (k - i) // 2 + + def cross_cov(self, a, b): + """(a x b)_i = J eps_ijk a^j b^k for contravariant a, b.""" + return [self.J * sum(self.eps(i, j, k) * a[j] * b[k] + for j in range(3) for k in range(3)) for i in range(3)] + + def curl_up(self, A): + """(curl A)^i = eps^ijk d_j A_k / J for covariant A.""" + return [sum(self.eps(i, j, k) * self.d(A[k], j) + for j in range(3) for k in range(3)) / self.J for i in range(3)] + + def div(self, v): + return sum(self.d(self.J * v[i], i) for i in range(3)) / self.J + + def lower(self, v): + return [sum(self.g[i][j] * v[j] for j in range(3)) for i in range(3)] + + def dot(self, a, b): + return sum(a[i] * b[i] for i in range(3)) + + def xi_s(self, v): + return self.chi1 * (self.q * v[1] - v[2]) + + +def induction(sp): + """G1: GPEC's J dB^i (gpeq.f:107-109) is J curl(xi x B)^i for any Jacobian.""" + S = Setup(sp) + xi, B = S.xi, S.Bup + JdB = [S.J * c for c in S.curl_up(S.cross_cov(xi, B))] + xs = S.xi_s(xi) + d = S.d + gpec = [S.chi1 * (d(xi[0], 1) + S.q * d(xi[0], 2)), + d(xs, 2) - S.chi1 * d(xi[0], 0), + -d(xs, 1) - S.chi1 * d(S.q * xi[0], 0)] + out = [(f"G1.{i + 1}", f"J dB^{COORDS[i]} = GPEC bw{'ptz'[i]} (pointwise)", + S.at0(JdB[i] - gpec[i])) for i in range(3)] + # (xi x B)_psi = xi_s: the surface displacement of Glasser (2016) Eq. 9. + out.append(("G1.4", "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", + S.at0(S.cross_cov(xi, B)[0] - xs))) + # A field-aligned displacement changes neither xi^psi nor xi_s. + lam = S.jet("lam", S.u) + shifted = [xi[i] + lam * B[i] for i in range(3)] + out.append(("G1.5", "xi_s(xi + lam B) = xi_s(xi)", + S.at0(S.xi_s(shifted) - xs))) + return out + + +def fourier_symbols(sp): + """G1F: Fourier symbols of G1 for xi ~ exp(2 pi i (m theta - n zeta)).""" + I, pi = sp.I, sp.pi + m, n, chi1 = sp.Symbol("m"), sp.Symbol("n"), sp.Symbol("chi1", positive=True) + q, q1 = sp.Symbol("q"), sp.Symbol("q1") + X, X1, Xs = sp.Symbol("xsp"), sp.Symbol("xsp1"), sp.Symbol("xss") + dth, dze = 2 * pi * I * m, -2 * pi * I * n + out = [] + out.append(("G1F.1", "bwp = chi1 (m - n q) 2 pi i xsp", + chi1 * (dth + q * dze) * X - chi1 * (m - n * q) * 2 * pi * I * X)) + out.append(("G1F.2", "bwt = -(chi1 xsp1 + 2 pi i n xss)", + (dze * Xs - chi1 * X1) + (chi1 * X1 + 2 * pi * I * n * Xs))) + out.append(("G1F.3", "bwz = -(chi1 (q1 xsp + q xsp1) + 2 pi i m xss)", + (-dth * Xs - chi1 * (q1 * X + q * X1)) + + (chi1 * (q1 * X + q * X1) + 2 * pi * I * m * Xs))) + return out + + +def incompressible_closure(sp): + """G2: GPEC's xi^theta, xi^zeta (gpeq.f gpeq_contra) follow from div xi = 0.""" + S = Setup(sp) + xi, J, q, chi1, d = S.xi, S.J, S.q, S.chi1, S.d + D = sum(d(J * xi[i], i) for i in range(3)) # J div xi + xs = S.xi_s(xi) + Jt, Jz = J * xi[1], J * xi[2] + r1 = (d(Jt, 1) + q * d(Jt, 2)) - d(J * xs, 2) / chi1 + d(J * xi[0], 0) - D + r2 = (d(Jz, 1) + q * d(Jz, 2)) + q * d(J * xi[0], 0) + d(J * xs, 1) / chi1 - q * D + r3 = q * Jt - Jz - J * xs / chi1 + return [("G2.1", "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", + S.at0(r1)), + ("G2.2", "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1" + " + q J div xi", S.at0(r2)), + ("G2.3", "q xwt - xwz = J xi_s / chi1", S.at0(r3))] + + +def columns(sp): + """G3-G5: eulb, xi_perp components, the gauge of lagb, and llagb.""" + S = Setup(sp, metric=True) + xi, B, J, q, chi1 = S.xi, S.Bup, S.J, S.q, S.chi1 + Bm = S.jet("Bm", S.u[:2]) # |B|(psi, theta), axisymmetric + dB = S.curl_up(S.cross_cov(xi, B)) + dBcov = S.lower(dB) + B2 = S.dot(S.lower(B), B) + xs = S.xi_s(xi) + xv = S.lower(xi) # GPEC xvt, xvz (covariant) + xmt, xmz = J * xi[1], J * xi[2] # GPEC xmt, xmz (J xi^i) + sigma = S.dot(xv, B) / B2 + xperp = [xi[i] - sigma * B[i] for i in range(3)] + out = [] + out.append(("G3", "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", + S.at0(S.dot(dBcov, B) - chi1 * (dBcov[1] + q * dBcov[2]) / J))) + out.append(("G4.1", "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", + S.at0(J * xperp[1] - (xmt - chi1 ** 2 / B2 * (xv[1] + q * xv[2]) / J)))) + out.append(("G4.2", "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", + S.at0(J * xperp[2] - (xmz - q * chi1 ** 2 / B2 * (xv[1] + q * xv[2]) / J)))) + # B . xi_perp = 0 as a statement about arbitrary vectors: the metric-jet + # form exceeds FortSym's polynomial-cancellation limit (issue #84), so the + # projection is proved on independent covariant B_i, contravariant B^i, xi^i. + Bl = [sp.Symbol(f"Bl_{i}") for i in range(3)] + Bu = [sp.Symbol(f"Bu_{i}") for i in range(3)] + xu = [sp.Symbol(f"xu_{i}") for i in range(3)] + sig = sum(Bl[i] * xu[i] for i in range(3)) / sum(Bl[i] * Bu[i] for i in range(3)) + out.append(("G4.3", "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", + sum(Bl[i] * (xu[i] - sig * Bu[i]) for i in range(3)))) + coef = xs / (chi1 * q) + xmz / (J * q) - chi1 ** 2 / (J ** 2 * B2) * (xv[1] + q * xv[2]) + out.append(("G4.4", "xi_perp^theta = xms/(chi1 q) + xmz/(J q)" + " - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", + S.at0(xperp[1] - coef))) + # Gauge of lagb: xi_g = xi_perp + lam B with xi_g^zeta = 0. + lam = -xperp[2] / B[2] + xg = [xperp[i] + lam * B[i] for i in range(3)] + out.append(("G5.1", "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", S.at0(xg[2]))) + out.append(("G5.2", "xi_g^theta = xi_s/(chi1 q)", S.at0(xg[1] - xs / (chi1 * q)))) + out.append(("G5.2b", "xi_g^psi = xi^psi", S.at0(xg[0] - xi[0]))) + dBg = S.curl_up(S.cross_cov(xg, B)) + dBp = S.curl_up(S.cross_cov(xperp, B)) + out.append(("G5.3", "curl(xi_g x B) = curl(xi_perp x B) (theta component)", + S.at0(dBg[1] - dBp[1]))) + eul = S.dot(dBcov, B) # |B| * eulb + grad = lambda v: sum(v[i] * S.d(Bm, i) for i in range(3)) + lagb = eul + Bm * (xi[0] * S.d(Bm, 0) + xs / (chi1 * q) * S.d(Bm, 1)) + lagb_g = eul + Bm * grad(xg) + llagb = eul + Bm * grad(xperp) + llagb_code = lagb + Bm * S.d(Bm, 1) * (xmz / (J * q) - chi1 ** 2 / (J ** 2 * B2) + * (xv[1] + q * xv[2])) + out.append(("G5.4", "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", + S.at0(lagb - lagb_g))) + out.append(("G5.5", "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", + S.at0(lagb - llagb + Bm * xperp[2] / q * S.d(Bm, 1)))) + out.append(("G5.6", "llagb (gpdiag.f) = dB_L(xi_perp)", S.at0(llagb_code - llagb))) + # Park (2009) Eq. 7 with the div-free xi (GPEC's own xmt, xmz). + lagb_div_free = eul + Bm * (xi[0] * S.d(Bm, 0) + xi[1] * S.d(Bm, 1)) + out.append(("G5.7", "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", + S.at0(lagb_div_free - lagb - Bm * xmz / (J * q) * S.d(Bm, 1)))) + # Boozer-type closed form: B_i xi_perp^i = 0 and the xi_s definition. + Bc = S.lower(B) + out.append(("G5.8", "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)" + "/(B_theta + q B_zeta)", + S.at0(xperp[2] + (Bc[1] * xs / chi1 + q * Bc[0] * xi[0]) + / (Bc[1] + q * Bc[2])))) + return out + + +def force_balance_identity(sp): + """G6: |B| times (llagb + Bdivxprp + Bkxprp) = -xi_perp . F, F = (curl B) x B - mu0 grad p. + + With pmodb's Bkxprp = xi_perp.grad B + mu0 p' xi^psi / B (force-balance form + of B kappa.xi_perp, gpout.f curvfun) and Bdivxprp = B div xi_perp, this is + B.dB + xi_perp.grad(B^2) + B^2 div xi_perp + xi_perp.((curl B) x B) = 0. + """ + S = Setup(sp, metric=True) + xi, B = S.xi, S.Bup + Bc = S.lower(B) + B2 = S.dot(Bc, B) + sigma = S.dot(S.lower(xi), B) / B2 + xperp = [xi[i] - sigma * B[i] for i in range(3)] + dB = S.curl_up(S.cross_cov(xi, B)) + curlB = S.curl_up(Bc) + jxb = S.cross_cov(curlB, B) # ((curl B) x B)_i + res = (S.dot(S.lower(dB), B) + sum(xperp[i] * S.d(B2, i) for i in range(3)) + + B2 * S.div(xperp) + S.dot(jxb, xperp)) + return [("G6", "B.dB + xi_perp.grad B^2 + B^2 div xi_perp + xi_perp.(curl B x B) = 0", + S.at0(res))] + + +def pentrc_kappa_matrices(sp): + """G7: idcon.f fmodb sband/tband give J B^2 (Bkxprp/B) from xi^psi and xi^alpha. + + PENTRC set_peq (inputs.f90) and GPEC.jl set_perturbation_data! form + jbb_kapx = smat xsp + tmat xi_alpha with xi_alpha = xms/chi1 (gpout_xclebsch). + The statement is algebraic at a point (only the values of d_psi B^2 and + d_theta B^2 enter), so the metric, Jacobian and derivatives are independent + symbols here: + sband = J (mu0 p' + d_psi B^2/2) - chi1^2 (d_theta B^2/(2 B^2)) (g12 + q g13)/J, + tband = chi1^2 (d_theta B^2/(2 B^2)) (g23 + q g33)/J. + """ + chi1, q, J, P, Dp, Dt = sp.symbols("chi1 q J P1 Dp Dt") + # B^2 = B^i B_i = (chi1/J)(B_theta + q B_zeta) needs no det g = J^2, so the + # metric components are independent here. + g11, g12, g13, g22, g23, g33 = sp.symbols("g11 g12 g13 g22 g23 g33") + g = [[g11, g12, g13], [g12, g22, g23], [g13, g23, g33]] + xi = list(sp.symbols("xp xt xz")) + B = [0, chi1 / J, chi1 * q / J] + Bl = [sum(g[i][j] * B[j] for j in range(3)) for i in range(3)] + B2 = sum(Bl[i] * B[i] for i in range(3)) + sigma = sum(Bl[i] * xi[i] for i in range(3)) / B2 + xperp = [xi[i] - sigma * B[i] for i in range(3)] + xs = chi1 * (q * xi[1] - xi[2]) + lhs = J * (xperp[0] * (P + Dp) + xperp[1] * Dt) + sband = J * (P + Dp) - chi1 ** 2 * (Dt / B2) * (g12 + q * g13) / J + tband = chi1 ** 2 * (Dt / B2) * (g23 + q * g33) / J + res = lhs - sband * xi[0] - tband * xs / chi1 + # The residual is linear in xi; proving it for each basis displacement keeps + # every zero test within FortSym's 12-variable polynomial limit (issue #84). + out = [] + for k, name in enumerate(("psi", "theta", "zeta")): + r = res + for j in range(3): + r = r.subs(xi[j], 1 if j == k else 0) + out.append((f"G7.{k + 1}", "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B" + f" = sband xsp + tband xms/chi1, xi = e_{name}", r)) + return out + + +def cartesian_kappa(sp): + """C1, C2: Cartesian 1-jet forms of the kinematic and curvature identities.""" + x, y, z = sp.symbols("x y z") + X = (x, y, z) + beta = sp.Symbol("beta", positive=True) + g = [[sp.Symbol(f"h_{i}{j}") for j in range(3)] for i in range(3)] + B = [[0, 0, beta][i] + sum(g[i][j] * X[j] for j in range(3)) for i in range(3)] + d = lambda f, c: f.diff(c) if hasattr(f, "diff") else 0 + dot = lambda a, b: sum(a[i] * b[i] for i in range(3)) + cross = lambda a, b: [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]] + curl = lambda v: [d(v[2], y) - d(v[1], z), d(v[0], z) - d(v[2], x), + d(v[1], x) - d(v[0], y)] + div = lambda v: sum(d(v[i], X[i]) for i in range(3)) + at0 = lambda e: e.subs(x, 0).subs(y, 0).subs(z, 0) + Bm = sp.sqrt(dot(B, B)) + b = [c / Bm for c in B] + kappa = [sum(b[j] * d(b[i], X[j]) for j in range(3)) for i in range(3)] + a = [sp.Symbol(f"a_{i}") + sum(sp.Symbol(f"a_{i}{j}") * X[j] for j in range(3)) + for i in range(3)] + xp = cross(B, a) + gradB = lambda v: sum(v[j] * d(Bm, X[j]) for j in range(3)) + dBpar = dot(b, curl(cross(xp, B))) + return [("C1", "b.curl(xi_perp x B) + xi_perp.grad B = -B(div xi_perp + kappa.xi_perp)", + at0(dBpar + gradB(xp) + Bm * (div(xp) + dot(kappa, xp)))), + ("C2", "B kappa.xi_perp = xi_perp.grad B + xi_perp.((curl B) x B)/B", + at0(Bm * dot(kappa, xp) - gradB(xp) - dot(xp, cross(curl(B), B)) / Bm))] + + +def regularisation(sp): + """R1-R3: Park-regularised components in GPEC (reg_flag) on one harmonic. + + Diagonal model: one harmonic, J independent of theta. R = s^2/(s^2 + reg^2), + s = m - n q. gpeq_sol uses xmp1 = R xsp1 in bmt/bmz and xms; gpeq_contra + builds xwt/xwz from xmp1 and multiplies by R again; gpout_pmodb uses + R xsp1 explicitly in divxprp. + """ + I, pi = sp.I, sp.pi + m, n, q, chi1 = sp.symbols("m n q chi1") + J0, J1 = sp.symbols("J_0 J_1") # J and dJ/dpsi (theta-independent) + X, X1, Xs, R = sp.symbols("xsp xsp1 xms R") + s = m - n * q + xwt = -(J0 * R * X1 + J1 * X + 2 * pi * I * n / chi1 * J0 * Xs) / (2 * pi * I * s) + xwz = -(q * J0 * R * X1 + q * J1 * X + 2 * pi * I * m / chi1 * J0 * Xs) / (2 * pi * I * s) + xmt, xmz = R * xwt, R * xwz + jdiv = J0 * R * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz + out = [("R1", "J div xi (reg) = (1 - R)(J R xsp1 + J' xsp)", + jdiv - (1 - R) * (J0 * R * X1 + J1 * X))] + jdiv_unreg = (J0 * X1 + J1 * X + 2 * pi * I * m * xwt.subs(R, 1) + - 2 * pi * I * n * xwz.subs(R, 1)) + out.append(("R2", "J div xi (R = 1) = 0", jdiv_unreg)) + # Divergence of the regularised Eulerian field: J div(dB) = d_psi bwp + ... . + bwp1 = 2 * pi * I * chi1 * (s * X1 - n * sp.Symbol("q1") * X) + bmt = -(chi1 * R * X1 + 2 * pi * I * n * Xs) + bmz = -(chi1 * (sp.Symbol("q1") * X + q * R * X1) + 2 * pi * I * m * Xs) + out.append(("R3", "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", + bwp1 + 2 * pi * I * m * bmt - 2 * pi * I * n * bmz + - 2 * pi * I * chi1 * s * (1 - R) * X1)) + return out + + +def cgl(sp): + """K1-K2: the PENTRC/GPEC.jl fcgl integrand against the CGL closure. + + D = div xi_perp, K = kappa.xi_perp, dB/B = -(D + K) (C1). CGL: + dp_perp/p = -D + dB/B, dp_par/p = -3D - 2 dB/B (no xi_par). Logan (2013) + Eq. 4 energy density, -(1/2)[(dp_par - dp_perp) dB/B + dp_par D]/p. + """ + D, K = sp.symbols("D K") + b = -(D + K) + dpe, dpa = -D + b, -3 * D - 2 * b + w = -((dpa - dpe) * b + dpa * D) / 2 + code = lambda k: (sp.Rational(5, 3) * D ** 2 + sp.Rational(1, 3) * (D + 3 * k) ** 2) / 2 + return [("K1", "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", w - code(K)), + ("K2neg", "fcgl with kapx = -(dbob + divx)/2 equals the CGL density", + w - code(-(b + D) / 2))] + + +def drive_gauge(sp): + """K3, K4: gauge (in)variance of the Logan (2013) Eq. 5 bounce drive. + + Along a field line (arc length l, M = 1): v = sqrt(2(E - mu B)), integrand + (1/v)[(2E - 3 mu B) dB/B + (2E - 2 mu B) div xi]. Under xi -> xi + lam B, + dB -> dB + lam B dB/dl and div xi -> div xi + B dlam/dl (C1, Park 2011 Eq. 4). + """ + l, E, mu = sp.Symbol("l"), sp.Symbol("E", positive=True), sp.Symbol("mu", positive=True) + b0, b1, l0, l1 = sp.symbols("b_0 b_1 lam_0 lam_1") + B = b0 + b1 * l + lam = l0 + l1 * l + v = sp.sqrt(2 * (E - mu * B)) + dBdl, dlam = B.diff(l), lam.diff(l) + change = ((2 * E - 3 * mu * B) * lam * dBdl + (2 * E - 2 * mu * B) * B * dlam) / v + total = (v * B * lam).diff(l) + # pmodb_file path: dbob = lagb/B (gauge xi_g), divx = -(lagb + Bkxprp)/B. + change_pmodb = ((2 * E - 3 * mu * B) * lam * dBdl - (2 * E - 2 * mu * B) * lam * dBdl) / v + # Multiplied by v > 0 (away from turning points): FortSym leaves the + # unmultiplied sqrt quotient UNKNOWN. + return [("K3", "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", + ((change - total) * v).subs(l, 0)), + ("K4", "pmodb_file-path drive change = -mu B lam dB/dl / v", + (change_pmodb + mu * B * lam * dBdl / v).subs(l, 0))] + + +def all_identities(sp): + return (induction(sp) + fourier_symbols(sp) + incompressible_closure(sp) + + columns(sp) + pentrc_kappa_matrices(sp) + + cartesian_kappa(sp) + regularisation(sp) + + [c for c in cgl(sp) if not c[0].endswith("neg")] + drive_gauge(sp)) + + +def open_gaps(sp): + """Identities FortSym does not decide; their content is covered elsewhere. + + G6 is the coordinate form of C1 + C2 (proved on Cartesian 1-jets) with + det g = J^2 imposed through g_33. The zero test exceeds the native + polynomial-term limit (lazy-fortran/fortsym#84 class); it is recorded, not + counted as proved. + """ + return force_balance_identity(sp) + + +def negative_controls(sp): + """Residuals that must NOT vanish (the claims they encode are false).""" + S = Setup(sp, metric=True) + xi, B, J, q, chi1 = S.xi, S.Bup, S.J, S.q, S.chi1 + Bm = S.jet("Bm", S.u[:2]) + B2 = S.dot(S.lower(B), B) + sigma = S.dot(S.lower(xi), B) / B2 + xperp = [xi[i] - sigma * B[i] for i in range(3)] + xs = S.xi_s(xi) + lagb_adv = xi[0] * S.d(Bm, 0) + xs / (chi1 * q) * S.d(Bm, 1) + perp_adv = sum(xperp[i] * S.d(Bm, i) for i in range(3)) + out = [("N1", "lagb is the xi_perp-only Lagrangian |B| (false)", + S.at0(lagb_adv - perp_adv))] + out.append(("N2", "lagb is Park 2009 Eq. 7 with the div-free xi (false)", + S.at0(lagb_adv - (xi[0] * S.d(Bm, 0) + xi[1] * S.d(Bm, 1))))) + out += [c for c in cgl(sp) if c[0].endswith("neg")] + return out diff --git a/docs/outputs.rst b/docs/outputs.rst index a0d80b2ad..f642bd747 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -174,11 +174,11 @@ GPEC_singfld_n#.out **Info** Additional section showing the overlap field (Tesla) and overlap percentage for each eigenmode in the singcoup_svd output. See :ref:`coupling_overlap` for definitions. -GPEC_pmod_n#.out +GPEC_pmodb_n#.out **Flag** pmodb_flag - **Info** Eulerian and Lagrangian \left|\mathbf{B}\right|(real and imaginary) for each poloidal mode number at each value of \Psi_{N} output. This output is necessary for NTV post processing. + **Info** Perturbed field strength (real and imaginary, Tesla) for each poloidal mode number at each value of \Psi_{N} output: the Eulerian \delta B (eulb), the Lagrangian \delta B of the displacement with zero \xi^\zeta in the working (jac_type) coordinates (lagb), B\nabla\cdot\xi_\perp (Bdivxprp) and the force-balance form of B\kappa\cdot\xi_\perp (Bkxprp). lagb is not the \xi_\perp-only Lagrangian \delta B; that is -(Bdivxprp+Bkxprp) up to the equilibrium force-balance residual. See :doc:`pmodb_gauge` before using these columns for NTV post processing. GPEC_xbnormal_n#.out diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst new file mode 100644 index 000000000..577e954d3 --- /dev/null +++ b/docs/pmodb_gauge.rst @@ -0,0 +1,172 @@ +.. _pmodb_gauge: + +****************************************** +Perturbed field strength (pmodb) and gauge +****************************************** + +This page states what the columns of ``gpec_pmodb_n#.out`` are, how they are +related, and which of them a field-strength consumer needs. Every identity +quoted here is proved with the FortSym native zero test by +``docs/derivations/pmodb_gauge/derive.py``; the verdicts are stored in +``docs/derivations/pmodb_gauge/fortsym_verdicts.json`` and the labels (G1, +G5.5, ...) refer to that file. + +.. contents:: On this page + :local: + :depth: 1 + + +Coordinates and displacement +============================ + +GPEC works in the DCON chart :math:`(\psi,\theta,\zeta)` with angles in turns, +any straight-field-line Jacobian :math:`\mathcal{J}(\psi,\theta)` selected by +``jac_type``, :math:`\mathcal{J}B^\theta=\chi_1`, +:math:`\mathcal{J}B^\zeta=\chi_1 q` and Fourier factor +:math:`\exp[2\pi i(m\theta-n\zeta)]`. The Euler-Lagrange solution supplies +:math:`\xi^\psi`, its radial derivative and the surface displacement +(Glasser 2016, Eqs. 9 and 18) + +.. math:: + + \xi_s = (\boldsymbol\xi\times\mathbf B)_\psi = \chi_1(q\xi^\theta-\xi^\zeta), + +the in-surface displacement across :math:`\mathbf B` (``xms``; +``gpec_xclebsch`` writes :math:`\xi^\alpha=\xi_s/\chi_1`). A field-aligned +displacement changes neither :math:`\xi^\psi` nor :math:`\xi_s` (G1.5). The +parallel part is fixed in ``gpeq_contra`` by +:math:`\nabla\cdot\boldsymbol\xi=0` (G2), which gives ``xmt`` +:math:`=\mathcal J\xi^\theta` and ``xmz`` :math:`=\mathcal J\xi^\zeta`. + +For every ``jac_type`` (G1, G1F) the Jacobian-weighted perturbed field of +``gpeq_sol`` is :math:`\mathcal J\,\nabla\times(\boldsymbol\xi\times\mathbf B)`. + + +The pmodb columns +================= + +``eulb`` + :math:`\delta B_E=\hat{\mathbf b}\cdot\delta\mathbf B`, the Eulerian + field strength (G3). + +``lagb`` + :math:`\delta B_E+\xi^\psi\partial_\psi B+\xi_s/(\chi_1 q)\,\partial_\theta B`. + This is the Lagrangian field strength of the displacement + :math:`\boldsymbol\xi_g=\boldsymbol\xi_\perp+\lambda\mathbf B` with + :math:`\xi_g^\zeta=0` in the *working* chart (``jac_type``) (G5.1-G5.4). + It is neither the :math:`\boldsymbol\xi_\perp`-only value (negative + control N1) nor Park et al. (2009) Eq. 7 with the divergence-free + :math:`\boldsymbol\xi` that GPEC constructs (N2; G5.7 gives that value as + ``lagb`` :math:`+\,\xi^\zeta/q\,\partial_\theta B`). ``jac_out`` only + relabels the harmonics; it does not change this gauge. + +``Bdivxprp`` + :math:`B\,\nabla\cdot\boldsymbol\xi_\perp`, with + :math:`\boldsymbol\xi_\perp=\boldsymbol\xi-(\boldsymbol\xi\cdot\mathbf B)\mathbf B/B^2` + built from ``xmt``, ``xmz`` and the covariant ``xvt``, ``xvz`` (G4.1-G4.3). + +``Bkxprp`` + :math:`\boldsymbol\xi_\perp\cdot\nabla B+\mu_0p'\xi^\psi/B` (G4.4). This is + :math:`B\boldsymbol\kappa\cdot\boldsymbol\xi_\perp` only through + equilibrium force balance: exactly, + :math:`B\boldsymbol\kappa\cdot\boldsymbol\xi_\perp=\boldsymbol\xi_\perp\cdot\nabla B + +\boldsymbol\xi_\perp\cdot[(\nabla\times\mathbf B)\times\mathbf B]/B` (C2). + The PENTRC/GPEC.jl matrices ``smat``, ``tmat`` (``idcon_build``) form the + same quantity times :math:`\mathcal J B^2` from :math:`\xi^\psi` and + :math:`\xi^\alpha` (G7). + +``llagb`` (``gpec_diagnostics_pmodb``, ``pmodbmn_flag``) + :math:`\delta B_L(\boldsymbol\xi_\perp)=\delta B_E+\boldsymbol\xi_\perp\cdot\nabla B` + (G5.6). ``lllagb`` is :math:`-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)`. + + +Relations between the columns +============================= + +Gauge of ``lagb`` (G5.5, G5.8): + +.. math:: + + \mathrm{lagb}=\delta B_L(\boldsymbol\xi_\perp)-\frac{\xi_\perp^\zeta}{q}\partial_\theta B, + \qquad + \xi_\perp^\zeta=-\frac{B_\theta\xi_s/\chi_1+qB_\psi\xi^\psi}{B_\theta+qB_\zeta}. + +Kinematic identity (C1; any :math:`\boldsymbol\xi_\perp`, no force balance) +and its pmodb form (C2 with G4; the same statement written directly in the +GPEC chart with :math:`\det g=\mathcal J^2`, G6, exceeds the FortSym +polynomial-term limit and is recorded as an open gap, not as proved): + +.. math:: + + \delta B_L(\boldsymbol\xi_\perp)=-B(\nabla\cdot\boldsymbol\xi_\perp + +\boldsymbol\kappa\cdot\boldsymbol\xi_\perp) + =-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})-\boldsymbol\xi_\perp\cdot\mathbf F/B, + \qquad \mathbf F=(\nabla\times\mathbf B)\times\mathbf B-\mu_0\nabla p. + +Hence + +.. math:: + + \mathrm{lagb}+\mathrm{Bdivxprp}+\mathrm{Bkxprp} + =-\frac{\xi_\perp^\zeta}{q}\partial_\theta B-\frac{\boldsymbol\xi_\perp\cdot\mathbf F}{B}. + +``lagb`` :math:`=-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)` is +therefore not an identity. The first term is a gauge term of first order in +the perturbation; it is largest where :math:`B_\theta/(qB_\zeta)`, the +Boozer :math:`B_\psi` and :math:`\partial_\theta B` are large, i.e. toward +the edge. The second term vanishes for an exact equilibrium. + +Regularisation (``reg_flag``, Park et al. 2009 Eq. 10). ``gpeq_sol`` uses +:math:`R\,\partial_\psi\xi^\psi`, :math:`R=s^2/(s^2+\sigma^2)`, +:math:`s=m-nq`, in the field and in ``xms``; ``gpeq_contra`` multiplies +``xwt``, ``xwz`` by :math:`R` again. On one harmonic with +:math:`\theta`-independent :math:`\mathcal J` (R1-R3): + +.. math:: + + \mathcal J\nabla\cdot\boldsymbol\xi=(1-R)(\mathcal J R\,\partial_\psi\xi^\psi+\mathcal J'\xi^\psi),\qquad + \mathcal J\nabla\cdot\delta\mathbf B=2\pi i\chi_1 s(1-R)\,\partial_\psi\xi^\psi. + +Inside :math:`|m-nq|\lesssim\sigma` the regularised displacement is neither +divergence free nor does it produce a divergence-free field, so +``Bdivxprp`` (which carries the explicit :math:`R\,\partial_\psi\xi^\psi`) +and ``lagb`` (which uses only ``xms``) are not related by the identities +above on the resonant harmonics. + + +What consumers need +=================== + +PENTRC, GPEC.jl ``KineticForces`` + The drive of Logan et al. (2013) Eqs. 4-5 needs :math:`\delta B` and + :math:`\nabla\cdot\boldsymbol\xi` of the *same* displacement; the + combination is then invariant under :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B`. + The default PENTRC path (``peq_file`` = ``gpec_xclebsch``, ``set_peq``) + uses :math:`\delta B/B=-(\nabla\cdot\boldsymbol\xi_\perp+\boldsymbol\kappa\cdot\boldsymbol\xi_\perp)` + and :math:`\nabla\cdot\boldsymbol\xi_\perp` from the ``smat``-``zmat`` + matrices, i.e. a consistent :math:`\boldsymbol\xi_\perp` pair. The + ``pmodb_file`` path (``read_pmodb``) pairs ``lagb`` with + :math:`-(\mathrm{lagb}+\mathrm{Bkxprp})/B`, which is not the divergence of + :math:`\boldsymbol\xi_g`. Under :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B` + the consistent drive changes by a total derivative along the field line + (K3), while this pairing changes it by + :math:`-\mu B\lambda\,\partial_l B/v_\parallel` (K4). + +Bounce-averaged codes in perturbed Boozer or Hamada coordinates (NEO-RT, NEO-2, Shaing-type) + need :math:`\delta B` in the perturbed straight-field-line coordinates. + That follows from :math:`\delta B_L(\boldsymbol\xi_\perp)` (``llagb``, or + :math:`-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})` away from rational surfaces) + and :math:`\nabla\cdot\boldsymbol\xi_\perp` by a magnetic differential + equation for the parallel gauge. ``lagb`` must not be used as + :math:`\delta B_L(\boldsymbol\xi_\perp)`. + + +Reproducing the proofs +====================== + +:: + + PYTHONPATH=$FORTSYM/python python3 docs/derivations/pmodb_gauge/derive.py + +FortSym (https://github.com/lazy-fortran/fortsym) is needed only to rerun the +proofs, not to build or run GPEC. diff --git a/docs/site.rst b/docs/site.rst index 7905ced0e..a62a5e2eb 100644 --- a/docs/site.rst +++ b/docs/site.rst @@ -9,6 +9,7 @@ GPEC documentation website site map contact.rst outputs.rst sign_conventions.rst + pmodb_gauge.rst dcon_readme.rst examples.rst releases.rst diff --git a/gpec/gpdiag.f b/gpec/gpdiag.f index d5b6825ef..70d19c6b9 100644 --- a/gpec/gpdiag.f +++ b/gpec/gpdiag.f @@ -1096,8 +1096,8 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) $ mthsurf,curv2_mn) eulbparmns(istep,:)=eulbpar_mn ! p'xsp_mn+bxcdeltamn - lagbparmns(istep,:)=lagbpar_mn ! with parallel components - llagbparmns(istep,:)=llagbpar_mn ! without parallel components + lagbparmns(istep,:)=lagbpar_mn ! xi^zeta=0 gauge (pmodb lagb) + llagbparmns(istep,:)=llagbpar_mn ! xi_perp only divxmns(istep,:)=-divx_mn ! Bdivxprp curvmns(istep,:)=-curv_mn ! Bkxprp divx2mns(istep,:)=divx2_mn ! jb-weighting diff --git a/gpec/gpout.f b/gpec/gpout.f index a02897d0d..0b61d5e0d 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -3038,6 +3038,9 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) eulbparfun(istep,itheta)= $ chi1*(bvt_fun(itheta)+sq%f(4)*bvz_fun(itheta)) $ /(rzphi%f(4)*eqfun%f(1)) +c lagb: Lagrangian dB of xi_perp + lambda B with xi^zeta=0 +c in the working coordinates, not the xi_perp-only value; see +c docs/pmodb_gauge.rst. lagbparfun(istep,itheta)= $ eulbparfun(istep,itheta)+ $ xsp_fun(itheta)*eqfun%fx(1)+ From b0e3f3dec804f84ec2449cdef3246ff1580ef7c5 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Tue, 20 Oct 2026 00:25:54 +0200 Subject: [PATCH 264/305] DOCS - BUG FIX - Complete native proofs and consumer gauge contracts --- docs/derivations/pmodb_gauge/derive.py | 6 +- .../pmodb_gauge/fortsym_verdicts.json | 166 +++++++++++++++--- docs/derivations/pmodb_gauge/identities.py | 143 ++++++++++++++- docs/pmodb_gauge.rst | 150 ++++++++++++++-- 4 files changed, 410 insertions(+), 55 deletions(-) diff --git a/docs/derivations/pmodb_gauge/derive.py b/docs/derivations/pmodb_gauge/derive.py index 17337b82d..d6f90cc3b 100644 --- a/docs/derivations/pmodb_gauge/derive.py +++ b/docs/derivations/pmodb_gauge/derive.py @@ -27,9 +27,7 @@ def verdict(expr) -> str: - if not hasattr(expr, "is_zero"): - return "PROVED" if expr == 0 else "NONZERO" - value = expr.is_zero + value = fs.sympify(expr).is_zero return {True: "PROVED", False: "NONZERO"}.get(value, "UNKNOWN") @@ -85,7 +83,7 @@ def main() -> int: out = {"engine": "fortsym native zero test via C ABI (fortsym.sympy)", "fortsym_revision": revision(), "checks": records, "failed": failed} (HERE / "fortsym_verdicts.json").write_text(json.dumps(out, indent=2) + "\n") - print("FAILED: " + " ".join(failed) if failed else "all identities proved") + print("FAILED: " + " ".join(failed) if failed else "required identities proved; see explicit open-gap records") return 1 if failed else 0 diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json index 5f0f0e2fd..8de71dd18 100644 --- a/docs/derivations/pmodb_gauge/fortsym_verdicts.json +++ b/docs/derivations/pmodb_gauge/fortsym_verdicts.json @@ -1,42 +1,42 @@ { "engine": "fortsym native zero test via C ABI (fortsym.sympy)", - "fortsym_revision": "7e8ef7abdd90c49bb5f75e2c119e7d181656bcc6", + "fortsym_revision": "ae635c80b42c922f5f2028c5cec67859bb8d3929", "checks": [ { "label": "G1.1", "equation": "J dB^psi = GPEC bwp (pointwise)", "verdict": "PROVED", - "seconds": 0.01 + "seconds": 0.001 }, { "label": "G1.2", "equation": "J dB^theta = GPEC bwt (pointwise)", "verdict": "PROVED", - "seconds": 0.004 + "seconds": 0.001 }, { "label": "G1.3", "equation": "J dB^zeta = GPEC bwz (pointwise)", "verdict": "PROVED", - "seconds": 0.007 + "seconds": 0.001 }, { "label": "G1.4", "equation": "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1.5", "equation": "xi_s(xi + lam B) = xi_s(xi)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1F.1", "equation": "bwp = chi1 (m - n q) 2 pi i xsp", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1F.2", @@ -54,103 +54,103 @@ "label": "G2.1", "equation": "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G2.2", "equation": "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1 + q J div xi", "verdict": "PROVED", - "seconds": 0.005 + "seconds": 0.0 }, { "label": "G2.3", "equation": "q xwt - xwz = J xi_s / chi1", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G3", "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", "verdict": "PROVED", - "seconds": 0.013 + "seconds": 0.023 }, { "label": "G4.1", "equation": "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", "verdict": "PROVED", - "seconds": 0.27 + "seconds": 0.006 }, { "label": "G4.2", "equation": "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", "verdict": "PROVED", - "seconds": 0.119 + "seconds": 0.004 }, { "label": "G4.3", "equation": "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", "verdict": "PROVED", - "seconds": 0.012 + "seconds": 0.001 }, { "label": "G4.4", "equation": "xi_perp^theta = xms/(chi1 q) + xmz/(J q) - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", "verdict": "PROVED", - "seconds": 0.183 + "seconds": 0.011 }, { "label": "G5.1", "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", "verdict": "PROVED", - "seconds": 0.232 + "seconds": 0.013 }, { "label": "G5.2", "equation": "xi_g^theta = xi_s/(chi1 q)", "verdict": "PROVED", - "seconds": 0.757 + "seconds": 0.017 }, { "label": "G5.2b", "equation": "xi_g^psi = xi^psi", "verdict": "PROVED", - "seconds": 0.008 + "seconds": 0.0 }, { "label": "G5.3", "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", "verdict": "PROVED", - "seconds": 0.076 + "seconds": 0.049 }, { "label": "G5.4", "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", "verdict": "PROVED", - "seconds": 0.026 + "seconds": 0.032 }, { "label": "G5.5", "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", "verdict": "PROVED", - "seconds": 0.017 + "seconds": 0.027 }, { "label": "G5.6", "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", "verdict": "PROVED", - "seconds": 0.038 + "seconds": 0.027 }, { "label": "G5.7", "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", "verdict": "PROVED", - "seconds": 0.014 + "seconds": 0.015 }, { "label": "G5.8", "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", "verdict": "PROVED", - "seconds": 0.735 + "seconds": 0.011 }, { "label": "G7.1", @@ -162,7 +162,7 @@ "label": "G7.2", "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_theta", "verdict": "PROVED", - "seconds": 0.007 + "seconds": 0.001 }, { "label": "G7.3", @@ -184,27 +184,135 @@ }, { "label": "R1", - "equation": "J div xi (reg) = (1 - R)(J R xsp1 + J' xsp)", + "equation": "J D_effective(reg) = (1-R)(J R xsp1 + J' xsp)", "verdict": "PROVED", - "seconds": 0.014 + "seconds": 0.001 + }, + { + "label": "R1a", + "equation": "J div xi_reconstructed = J(1-R**2)xsp1 + (1-R)J' xsp", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "R4", + "equation": "chi1(q xi_reconstructed^theta-xi_reconstructed^zeta)=R xms", + "verdict": "PROVED", + "seconds": 0.0 }, { "label": "R2", "equation": "J div xi (R = 1) = 0", "verdict": "PROVED", - "seconds": 0.028 + "seconds": 0.002 }, { "label": "R3", "equation": "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "E4.1", + "equation": "d/de J(X + e xi) = d_i(J xi^i) = J div xi", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G8.1", + "equation": "J B**2 div xi_perp = xmat Xp + ymat X + zmat A", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "G8.2", + "equation": "idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2", + "verdict": "PROVED", + "seconds": 0.001 + }, + { + "label": "Q1.hamada", + "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q2.hamada", + "equation": "D w = -T; w_m = -T_m/[2 pi i(m-nq)]", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q3.hamada", + "equation": "delta B_target = delta B_X + w d_theta B (J B**a flux function)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q1.boozer", + "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", + "verdict": "PROVED", "seconds": 0.001 }, + { + "label": "Q2.boozer", + "equation": "D w = -T; w_m = -T_m/[2 pi i(m-nq)]", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q3.boozer", + "equation": "delta B_target = delta B_X + w d_theta B (J B**a flux function)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q4", + "equation": "det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q5", + "equation": "Hamada->Boozer equals direct X->Boozer when w_HB=w_XB-w_XH", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q6", + "equation": "field-aligned input: delta B_E=0, xi_perp=0 => target delta B=0", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q7", + "equation": "xi_perp limit: zero target source implies no gauge correction", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "Q8", + "equation": "delta(J/R**2)/(J/R**2)=div xi-2 xi_R/R", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "K5", + "equation": "Complex CGL pressure work equals [5/3 |D|**2+1/3 |D+3K|**2]/2", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "P1", + "equation": "(Bk - (llagb-eulb))/B = mu0 p' xi^psi/B**2", + "verdict": "PROVED", + "seconds": 0.0 + }, { "label": "K1", "equation": "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "K3", diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py index d52b3bcd0..f38bc384c 100644 --- a/docs/derivations/pmodb_gauge/identities.py +++ b/docs/derivations/pmodb_gauge/identities.py @@ -233,7 +233,7 @@ def columns(sp): out.append(("G4.2", "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", S.at0(J * xperp[2] - (xmz - q * chi1 ** 2 / B2 * (xv[1] + q * xv[2]) / J)))) # B . xi_perp = 0 as a statement about arbitrary vectors: the metric-jet - # form exceeds FortSym's polynomial-cancellation limit (issue #84), so the + # form is larger than necessary, so the # projection is proved on independent covariant B_i, contravariant B^i, xi^i. Bl = [sp.Symbol(f"Bl_{i}") for i in range(3)] Bu = [sp.Symbol(f"Bu_{i}") for i in range(3)] @@ -330,7 +330,7 @@ def pentrc_kappa_matrices(sp): tband = chi1 ** 2 * (Dt / B2) * (g23 + q * g33) / J res = lhs - sband * xi[0] - tband * xs / chi1 # The residual is linear in xi; proving it for each basis displacement keeps - # every zero test within FortSym's 12-variable polynomial limit (issue #84). + # every check independently covers one displacement component. out = [] for k, name in enumerate(("psi", "theta", "zeta")): r = res @@ -386,9 +386,14 @@ def regularisation(sp): xwt = -(J0 * R * X1 + J1 * X + 2 * pi * I * n / chi1 * J0 * Xs) / (2 * pi * I * s) xwz = -(q * J0 * R * X1 + q * J1 * X + 2 * pi * I * m / chi1 * J0 * Xs) / (2 * pi * I * s) xmt, xmz = R * xwt, R * xwz - jdiv = J0 * R * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz - out = [("R1", "J div xi (reg) = (1 - R)(J R xsp1 + J' xsp)", - jdiv - (1 - R) * (J0 * R * X1 + J1 * X))] + jdiv_effective = J0 * R * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz + jdiv_actual = J0 * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz + out = [("R1", "J D_effective(reg) = (1-R)(J R xsp1 + J' xsp)", + jdiv_effective - (1-R)*(J0*R*X1+J1*X)), + ("R1a", "J div xi_reconstructed = J(1-R**2)xsp1 + (1-R)J' xsp", + jdiv_actual-J0*(1-R**2)*X1-(1-R)*J1*X), + ("R4", "chi1(q xi_reconstructed^theta-xi_reconstructed^zeta)=R xms", + chi1*(q*xmt-xmz)/J0-R*Xs)] jdiv_unreg = (J0 * X1 + J1 * X + 2 * pi * I * m * xwt.subs(R, 1) - 2 * pi * I * n * xwz.subs(R, 1)) out.append(("R2", "J div xi (R = 1) = 0", jdiv_unreg)) @@ -444,19 +449,141 @@ def drive_gauge(sp): (change_pmodb + mu * B * lam * dBdl / v).subs(l, 0))] +def divergence_matrices(sp): + """G8: compare projected contravariant divergence with idcon's x/y/z bands. + + U=(g12+q*g13)/(J*B**2), V=(g23+q*g33)/(J*B**2). The perpendicular + projection gives J xi^theta=chi1**2*(-U X+V A), xi^zeta=q xi^theta-A. + U,V and their theta derivatives remain independent jets here. The field + norm supplies the only algebraic metric relation used by the z bands. + """ + J,Jp,B2,c,q,m,n,X,Xp,A,U,Ut,V,Vt=sp.symbols("J Jp B2 c q m n X Xp A U Ut V Vt") + th,ze=2*sp.pi*sp.I*m,-2*sp.pi*sp.I*n + # Differentiate J xi^theta and J xi^zeta with the product rule. + Y=c**2*(-U*X+V*A) + Yt=c**2*(-Ut*X-U*th*X+Vt*A+V*th*A) + Yz=c**2*(-U*ze*X+V*ze*A) + direct=B2*(J*Xp+Jp*X+Yt+q*Yz-J*ze*A) + bands=J*B2*Xp+(Jp*B2-c**2*B2*Ut-2*sp.pi*sp.I*(m-n*q)*c**2*B2*U)*X + bands+=(c**2*B2*Vt+2*sp.pi*sp.I*(c**2*B2*V*(m-n*q)+n*J*B2))*A + out=[("G8.1","J B**2 div xi_perp = xmat Xp + ymat X + zmat A",direct-bands)] + g22,g23,g33,g12,g13=sp.symbols("g22 g23 g33 g12 g13") + norm=c**2*(g22+2*q*g23+q**2*g33)/J**2 + zcode=c**2/J*(m*(g23+q*g33)+n*(g22+q*g23)) + zproj=(c**2*norm*((g23+q*g33)/(J*norm))*(m-n*q)+n*J*norm) + out.append(("G8.2","idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2",zcode-zproj)) + return out + + +def gauge_coordinates(sp): + """Local Jacobian and field-strength gauge equations in angles in turns. + + B.grad = chi1/J (d_theta + q d_zeta). For a target chart J B**a=H(psi), + a=0 (Hamada) or 2 (Boozer), w=chi1 lambda B**a/H obeys D w=-T, + T=div xi + a delta B_L/B, for n!=0 (zero flux-function perturbation). + """ + B, Bt, J, chi, q, lam, Lt, Lz, d, b, c = sp.symbols( + "B Bt J chi q lam Lt Lz d b c") + m,n=sp.symbols("m n") + out=[] + for a,name in ((0,"hamada"),(2,"boozer")): + # First jet of lambda B**a; axisymmetric equilibrium, div B=0. + Ftheta = Lt*B**a + a*lam*B**(a-1)*Bt + Fzeta = Lz*B**a + direct = d + chi/J*(Lt+q*Lz) + a*(b+lam*chi/J*Bt)/B + product = d+a*b/B+chi/(J*B**a)*(Ftheta+q*Fzeta) + out.append(("Q1."+name,"T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a",direct-product)) + # Solving the Fourier equation, with modes exp(2 pi i(m theta-n zeta)). + T,H=sp.symbols("T H") + w=-T/(2*sp.pi*sp.I*(m-n*q)) + out.append(("Q2."+name,"D w = -T; w_m = -T_m/[2 pi i(m-nq)]",2*sp.pi*sp.I*(m-n*q)*w+T)) + # On a target base J B**a=H, correction to field strength is w d_theta B. + lambda_w = w*J/chi + out.append(("Q3."+name,"delta B_target = delta B_X + w d_theta B (J B**a flux function)", + lambda_w*chi/J*Bt-w*Bt)) + # Transform the base angles: theta_B=theta_H+iota h, phi_B=phi_H+h. + h,Dh,iota,Hh,Hb=sp.symbols("h Dh iota Hh Hb") + ht,hp=sp.symbols("ht hp") + determinant=(1+iota*ht)*(1+hp)-ht*iota*hp + out.append(("Q4","det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", + determinant-1-iota*ht-hp)) + # Same physical scalar and relabeling, in the convention of rmp_torque E4.14. + bX,jX,u,v=sp.symbols("bX jX u v") + dbB=bX+u*Bt; dbH=bX+v*Bt + out.append(("Q5","Hamada->Boozer equals direct X->Boozer when w_HB=w_XB-w_XH", + dbH+(u-v)*Bt-dbB)) + # Perpendicular reconstruction is unchanged by adding a field-aligned vector. + out.append(("Q6","field-aligned input: delta B_E=0, xi_perp=0 => target delta B=0", + lam*chi/J*Bt + (-lam*chi/J)*Bt)) + out.append(("Q7","xi_perp limit: zero target source implies no gauge correction", (bX+u*Bt-bX).subs(u,0))) + # PEST J/R**2 is a flux function. Its linearized constraint has radial motion. + R,xiR,divxi,db,dr=sp.symbols("R xiR divxi db dr") + eps=sp.Symbol("eps") + ratio=J*(1+eps*divxi)/(R+eps*xiR)**2 + out.append(("Q8","delta(J/R**2)/(J/R**2)=div xi-2 xi_R/R", + ratio.diff(eps).subs(eps,0)*R**2/J-divxi+2*xiR/R)) + # Complex CGL quadratic form: real and imaginary components independently. + Dr,Di,Kr,Ki=sp.symbols("Dr Di Kr Ki") + pressure=0; code=0 + for D,K in ((Dr,Kr),(Di,Ki)): + b=-(D+K); pp=-D+b; pa=-3*D-2*b + pressure-=((pa-pp)*b+pa*D)/2 + code+=(sp.Rational(5,3)*D**2+sp.Rational(1,3)*(D+3*K)**2)/2 + out.append(("K5","Complex CGL pressure work equals [5/3 |D|**2+1/3 |D+3K|**2]/2",pressure-code)) + # Pressure term in curvature is mu0 p' xi^psi/B, not mu0 p' xi^psi. + P,X=sp.symbols("P X") + out.append(("P1","(Bk - (llagb-eulb))/B = mu0 p' xi^psi/B**2", (P*X/B)/B-P*X/B**2)) + return out + + + +def jacobian_identity(sp): + """Eq. (4.1): d/de det(d(X + e xi)/du) = d_i(J xi^i) (Jacobi formula).""" + u = sp.symbols("u_1 u_2 u_3") + eps = sp.Symbol("epsilon") + c = [[sp.Symbol(f"c_{a}{i}") for i in range(3)] for a in range(3)] + q = [[[sp.Symbol(f"q_{a}{min(i, j)}{max(i, j)}") for j in range(3)] + for i in range(3)] for a in range(3)] + pos = [sum(c[a][i] * u[i] for i in range(3)) + + sum(q[a][i][j] * u[i] * u[j] for i in range(3) for j in range(3)) / 2 + for a in range(3)] + xi_up = [sp.Symbol(f"s_{i}") + sum(sp.Symbol(f"t_{i}{j}") * u[j] for j in range(3)) + for i in range(3)] + e_cov = [[pos[a].diff(u[i]) for a in range(3)] for i in range(3)] + xi_cart = [sum(xi_up[i] * e_cov[i][a] for i in range(3)) for a in range(3)] + moved = [pos[a] + eps * xi_cart[a] for a in range(3)] + + def det3(m): + return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) + - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])) + + def at0(e): + for v in u: + e = e.subs(v, 0) + return e + + # J(X + eps xi) needs no further u-derivative: evaluate entries at u = 0 first. + jac_moved = det3([[at0(moved[a].diff(u[i])) for i in range(3)] for a in range(3)]) + jac = det3([[pos[a].diff(u[i]) for i in range(3)] for a in range(3)]) + first = jac_moved.diff(eps).subs(eps, 0) + divergence_density = at0(sum((jac * xi_up[i]).diff(u[i]) for i in range(3))) + res = first - divergence_density + return [("E4.1", "d/de J(X + e xi) = d_i(J xi^i) = J div xi", res.expand())] + def all_identities(sp): return (induction(sp) + fourier_symbols(sp) + incompressible_closure(sp) + columns(sp) + pentrc_kappa_matrices(sp) + cartesian_kappa(sp) + regularisation(sp) - + [c for c in cgl(sp) if not c[0].endswith("neg")] + drive_gauge(sp)) + + jacobian_identity(sp) + divergence_matrices(sp) + gauge_coordinates(sp) + [c for c in cgl(sp) if not c[0].endswith("neg")] + drive_gauge(sp)) def open_gaps(sp): """Identities FortSym does not decide; their content is covered elsewhere. G6 is the coordinate form of C1 + C2 (proved on Cartesian 1-jets) with - det g = J^2 imposed through g_33. The zero test exceeds the native - polynomial-term limit (lazy-fortran/fortsym#84 class); it is recorded, not + det g = J^2 imposed through g_33. On ae635c80 the zero test still returns UNKNOWN for this large rational + residual; the variable cap removed by #84 is no longer the explanation; it is recorded, not counted as proved. """ return force_balance_identity(sp) diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index 577e954d3..f5eedc0dd 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -5,8 +5,8 @@ Perturbed field strength (pmodb) and gauge ****************************************** This page states what the columns of ``gpec_pmodb_n#.out`` are, how they are -related, and which of them a field-strength consumer needs. Every identity -quoted here is proved with the FortSym native zero test by +related, and which of them a field-strength consumer needs. Verified identities +are proved with the FortSym native zero test by ``docs/derivations/pmodb_gauge/derive.py``; the verdicts are stored in ``docs/derivations/pmodb_gauge/fortsym_verdicts.json`` and the labels (G1, G5.5, ...) refer to that file. @@ -19,6 +19,9 @@ G5.5, ...) refer to that file. Coordinates and displacement ============================ +Assume smooth nested surfaces with B>0 and nonzero J, chi1 and q. +Fourier inversions below additionally exclude nonzero resonant sources. + GPEC works in the DCON chart :math:`(\psi,\theta,\zeta)` with angles in turns, any straight-field-line Jacobian :math:`\mathcal{J}(\psi,\theta)` selected by ``jac_type``, :math:`\mathcal{J}B^\theta=\chi_1`, @@ -92,9 +95,8 @@ Gauge of ``lagb`` (G5.5, G5.8): \xi_\perp^\zeta=-\frac{B_\theta\xi_s/\chi_1+qB_\psi\xi^\psi}{B_\theta+qB_\zeta}. Kinematic identity (C1; any :math:`\boldsymbol\xi_\perp`, no force balance) -and its pmodb form (C2 with G4; the same statement written directly in the -GPEC chart with :math:`\det g=\mathcal J^2`, G6, exceeds the FortSym -polynomial-term limit and is recorded as an open gap, not as proved): +and its pmodb form (C2 with G4; the optional direct contraction in the +GPEC chart with :math:`\det g=\mathcal J^2`, G6, returns UNKNOWN on ae635c80 and is recorded as an open gap, not as proved): .. math:: @@ -112,9 +114,9 @@ Hence ``lagb`` :math:`=-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)` is therefore not an identity. The first term is a gauge term of first order in -the perturbation; it is largest where :math:`B_\theta/(qB_\zeta)`, the -Boozer :math:`B_\psi` and :math:`\partial_\theta B` are large, i.e. toward -the edge. The second term vanishes for an exact equilibrium. +the perturbation. Its size depends on :math:`B_\theta/(qB_\zeta)`, the +Boozer :math:`B_\psi` and :math:`\partial_\theta B` and the displacement spectrum; growth toward the edge is a hypothesis +for a particular equilibrium, not a general bound. The second term vanishes for an exact equilibrium. Regularisation (``reg_flag``, Park et al. 2009 Eq. 10). ``gpeq_sol`` uses :math:`R\,\partial_\psi\xi^\psi`, :math:`R=s^2/(s^2+\sigma^2)`, @@ -124,14 +126,25 @@ Regularisation (``reg_flag``, Park et al. 2009 Eq. 10). ``gpeq_sol`` uses .. math:: - \mathcal J\nabla\cdot\boldsymbol\xi=(1-R)(\mathcal J R\,\partial_\psi\xi^\psi+\mathcal J'\xi^\psi),\qquad + \mathcal J D_{effective}=(1-R)(\mathcal J R\,\partial_\psi\xi^\psi+\mathcal J'\xi^\psi),\qquad \mathcal J\nabla\cdot\delta\mathbf B=2\pi i\chi_1 s(1-R)\,\partial_\psi\xi^\psi. -Inside :math:`|m-nq|\lesssim\sigma` the regularised displacement is neither -divergence free nor does it produce a divergence-free field, so -``Bdivxprp`` (which carries the explicit :math:`R\,\partial_\psi\xi^\psi`) -and ``lagb`` (which uses only ``xms``) are not related by the identities -above on the resonant harmonics. +Here the displayed R1 formula is the *effective* divergence diagnostic, +using :math:`R\partial_\psi\xi^\psi` for its radial derivative. The actual +divergence of the reconstructed displacement is instead (R1a) + +.. math:: + + \mathcal J\nabla\cdot\boldsymbol\xi_{rec} + =\mathcal J(1-R^2)\partial_\psi\xi^\psi+(1-R)\mathcal J'\xi^\psi. + +Also :math:`\chi_1(q\xi_{rec}^\theta-\xi_{rec}^\zeta)=R\xi_s` (R4). +These formulas are for the diagonal model only. Near rational surfaces the +regularized field, displacement and effective derivative therefore do not +form one exact ideal-induction pair. This is a limitation of the smoothing +policy, not evidence of resistive physics. The unconditional smoothing in +``gpout_pmodb`` even with ``reg_flag=f`` is a separate verified output bug. + What consumers need @@ -170,3 +183,112 @@ Reproducing the proofs FortSym (https://github.com/lazy-fortran/fortsym) is needed only to rerun the proofs, not to build or run GPEC. + + +Target gauges in GPEC units (verified Q1-Q8) +============================================ + +Let :math:`D=\partial_\theta+q\partial_\zeta`, so +:math:`\mathbf B\cdot\nabla=(\chi_1/\mathcal J)D`. +For a matching equilibrium chart :math:`\mathcal J B^a=H(\psi)`, +:math:`a=2` is Boozer and :math:`a=0` is Hamada. A displacement with +:math:`b_X=\delta B_L(\boldsymbol\xi_X)` and +:math:`d_X=\nabla\cdot\boldsymbol\xi_X` has target-gauge source + +.. math:: + + T_a=d_X+a b_X/B,\qquad + \boldsymbol\xi_{target}=\boldsymbol\xi_X+\lambda\mathbf B,\qquad + T_a(\boldsymbol\xi_{target})=T_a(\boldsymbol\xi_X) + +\frac{\mathbf B\cdot\nabla(\lambda B^a)}{B^a}. + +For nonaxisymmetric modes the target flux-function perturbation is zero. +With :math:`w=\chi_1\lambda/\mathcal J`, this gives + +.. math:: + + Dw=-T_a,\qquad w_m=\frac{i T_{a,m}}{2\pi(m-nq)},\qquad + b_{target}=b_X+w\partial_\theta B. + +The source is formed in real space before Fourier transformation. A +nonzero source at :math:`m=nq` has no periodic solution. A resonant zero +source leaves an arbitrary homogeneous shift, set to zero here. +The factor :math:`2\pi` is from angles in turns and cancels the same +factor in :math:`\partial_\theta B`. Relabeling equilibrium harmonics +with ``jac_out`` alone does not solve this equation. + +For a PEST chart :math:`\mathcal J/R^2` is a flux function; its +linear perturbation is :math:`\nabla\cdot\boldsymbol\xi-2\xi_R/R` +(Q8). PEST output is not automatically either target gauge. Transform the +equilibrium chart first, then solve the target equation. The Hamada scalar +on Boozer equilibrium angles uses :math:`T_2=2 b_H/B`, matching +rmp_torque c3614334, Eq. 4.14. Q5 proves composition of parallel shifts; +Q4 proves the angular-map determinant. The native regression compares the +finite spectral operator directly with that consumer's kernel. + + +Verified defects and numerical attribution +========================================== + +* ``gpout_pmodb`` smooths the radial derivative even with ``reg_flag=f``; + use ``xmp1_mn`` selected by ``gpeq_sol``. No change for ``reg_flag=t``. +* ``gpdiag_pmodb`` uses a stale ``jac`` in its first theta loop. The local + Jacobian must be assigned before projecting out the parallel component. +* PENTRC ``read_pmodb`` overwrites the supplied divergence with + ``-(lagb+Bkxprp)``. Use the supplied positive ``Bdivxprp`` and + ``Bkxprp`` to form ``b_perp=-(Bdivxprp+Bkxprp)`` instead. +* Both CGL torque paths recover ``kappa.xi_perp`` as + ``-(dbob+divx)/2`` although their input convention is + ``dbob=-(divx+kapx)``. Remove the extra half. K1 and K5 prove the + double-adiabatic pressure work for real and complex perturbations. + +Finding (ii) from the TC24 reconstruction is a missing division by B in +the pressure *regressor*, not a GPEC normalization error (P1). For the +retained ITER n=3 output the original fit gives 0.1677; the corrected +pressure term plus the gauge term fits coefficients (1,1,1) with relative +residual about 1e-4. This is numerical corroboration only. + +Finding (i) combines the gauge term, effective-derivative smoothing, +finite spectral/spline error, and the equilibrium force residual. The +identity above separates them; it does not prove a quantitative allocation +of the originally reported 0.5-19 percent to any one cause. The explicit +pressure term in curvature has coefficient one. Edge growth alone does +not diagnose nonideal plasma physics. Finding (iii) is consistent with the +explicit radial derivative in divergence and its smoothing near rational +surfaces. No change to the smoothing model itself is proposed here. + + +Source map and literature +========================== + +Verified code contracts (line numbers at Fortran base 3622c25c): +``gpec/idcon.f:93`` (chi1=2pi psio), ``:617`` (projection geometry), +``:675`` (pressure in Pa), ``:1026`` (curvature and divergence matrices), +``gpec/gpeq.f:114`` (regularization selection), ``:183`` (surface-displacement +closure), ``gpec/gpout.f:3035`` (eulb/lagb), ``:3047`` (divergence and +pressure curvature), ``gpec/gpdiag.f:1003`` (diagnostic projection), +``:1061`` (llagb), ``pentrc/inputs.f90:469`` (ASCII columns), +``:487`` (old gauge mixing), ``:944`` (perpendicular matrix pair), +``pentrc/torque.F90:379`` (old CGL half). + +Julia base 0304fd54c: ``src/KineticForces/KineticForcesStructs.jl:435`` +uses the correct perpendicular dbob convention; +``src/KineticForces/Torque.jl:295`` repeats the CGL half. +``src/PerturbedEquilibrium/FieldReconstruction.jl``'s xi_s is Glasser's +surface displacement, not xi^zeta. Eq. 18 describes its elimination in the +Euler-Lagrange system; Eq. 9 defines it. + +The physical definitions were checked against Park 2009, PoP 16 056115, +Eqs. 7 and 10; Park 2011, PoP 18 110702, Eqs. 4, 10 and 11; +Park and Logan 2017, PoP 24 032505, Eq. 13 and p. 6; Logan and Park 2013, +PoP 20 122507, Eqs. 4 and 5; and Boozer and Nuehrenberg 2006, +PoP 13 102501, Eqs. 42 and 43. The latter's Delta includes twice the +curvature; its Eq. 43 is Eulerian and is not the perpendicular Lagrangian +scalar. These are source contracts, separate from the native algebra proofs. + +FortSym limits: #84, #85 and #86 are present at ae635c80. The direct G6 +contraction remains UNKNOWN (33 symbols, rational metric jets); no probe +is promoted to proof. #91 (coefficient extraction after zero substitution) +and #92 (radical power reduction) are not exercised by the accepted +polynomial/rational gauge proofs. C1/C2 supply the invariant kinematic +identity independently of G6. From 97716d7e07867738963a7d0239bebd4b3b8bb8b6 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Tue, 20 Oct 2026 00:32:19 +0200 Subject: [PATCH 265/305] DOCS - BUG FIX - Distinguish regularized llagb from a perpendicular projection --- docs/derivations/pmodb_gauge/check_output.py | 120 ++++++++++++++++++ .../pmodb_gauge/fortsym_verdicts.json | 28 ++-- docs/derivations/pmodb_gauge/identities.py | 3 + docs/pmodb_gauge.rst | 15 ++- 4 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 docs/derivations/pmodb_gauge/check_output.py diff --git a/docs/derivations/pmodb_gauge/check_output.py b/docs/derivations/pmodb_gauge/check_output.py new file mode 100644 index 000000000..c4c0f7843 --- /dev/null +++ b/docs/derivations/pmodb_gauge/check_output.py @@ -0,0 +1,120 @@ +"""Numeric oracle for the pmodb gauge identities on the TC24 n=3 GPEC run. + +Usage: PYTHONPATH=$RMP_TORQUE/src python3 check_output.py RUN_DIR + +Requires numpy, scipy, and the gpec_ascii readers from the referenced +rmp_torque Boozer-gauge-total branch. Numerical evidence only; see derive.py +for the native FortSym proofs. +""" +import sys +from pathlib import Path + +import numpy as np +from scipy.interpolate import CubicSpline +from rmp_torque.gpec_ascii import (read_pmodb, read_xclebsch, read_equilibrium_grid, + reconstruct_advective_spectrum) + +R = Path(sys.argv[1]) +MU0 = 4e-7 * np.pi + + +def table(path, ncol): + rows = [] + for line in open(path): + f = line.split() + if len(f) != ncol: + continue + try: + rows.append([float(x) for x in f]) + except ValueError: + pass + return np.array(rows) + + +d = table(R / "gpec_diagnostics_pmodb_n3.out", 16) +psi = np.unique(d[:, 0]); ms = np.unique(d[:, 1]).astype(int) +shape = (psi.size, ms.size) +c = lambda k: (d[:, k] + 1j * d[:, k + 1]).reshape(shape) +lagb, llagb, lll, bdiv, bk = c(2), c(4), c(6), c(8), c(12) +sp = read_pmodb(R / "gpec_pmodb_n3.out") +xc = read_xclebsch(R / "gpec_xclebsch_n3.out") +eq = read_equilibrium_grid(R / "idcon_equil.out") +q = np.interp(psi, eq.psi_poloidal, eq.q) +nres = 3 * q +far = np.abs(nres - np.round(nres)) > 0.15 +rel = lambda a, b: np.linalg.norm(a - b, axis=1) / np.linalg.norm(b, axis=1) +bands = [(0.0, 0.8), (0.8, 0.9), (0.9, 0.98)] + + +def report(name, a, b): + r = rel(a, b) + parts = [] + for lo, hi in bands: + sel = (psi >= lo) & (psi < hi) + parts.append(f"{lo:.1f}-{hi:.2f}: med {np.median(r[sel]):.4f} " + f"(far {np.median(r[sel & far]):.4f})") + print(f"{name:34s}", " | ".join(parts)) + + +print("diag grid", shape, "pmodb grid", sp.lagrangian.shape) +assert np.allclose(sp.psi_poloidal, psi) +report("pmodb lagb vs -(Bdiv+Bk) [cols]", sp.lagrangian, + -(sp.b_div_xi_perp + sp.b_kappa_xi_perp)) +report("diag lagb vs pmodb lagb", lagb, sp.lagrangian) +report("diag lagb vs lllagb=-(Bdiv+Bk)", lagb, lll) +report("diag llagb vs lllagb", llagb, lll) +report("gauge term (lagb-llagb) / llagb", lagb - llagb + llagb, llagb) + +# Finding (ii): the pressure-coefficient fit. +fl = [] +sec = False +for line in open(R / "idcon_equil.out"): + if "Flux functions:" in line: + sec = True + continue + f = line.split() + if sec and len(f) == 6: + try: + fl.append([float(x) for x in f]) + except ValueError: + pass +fl = np.array(fl) +dp = CubicSpline(fl[:, 0], fl[:, 2]).derivative()(xc.psi_poloidal) +th = eq.theta_radians +ph = np.exp(1j * np.outer(xc.modes, th)) +bs = CubicSpline(eq.psi_poloidal, eq.b_magnitude, axis=0)(xc.psi_poloidal) + + +def spec(x): + r = np.fft.fft((x @ ph) / bs, axis=1) / len(th) + return np.stack([r[:, int(m) % len(th)] for m in xc.modes], axis=1) + + +adv = reconstruct_advective_spectrum(xc, eq) # xi_g . grad B (xi^zeta = 0) +A1 = spec(adv) +A2_wrong = spec(MU0 * xc.xi_psi * dp[:, None]) # rmp_torque #56 fit: missing 1/B +_r = np.fft.fft(MU0 * dp[:, None] * (xc.xi_psi @ ph) / bs**2, axis=1) / len(th) +A2 = np.stack([_r[:, int(m) % len(th)] for m in xc.modes], axis=1) # (mu0 p' xi^psi/B)/B +A3 = spec(llagb - lagb) # (xi_perp^zeta/q) d_theta B +y = spec(sp.b_kappa_xi_perp) +sel = far[:, None] & (np.abs(xc.modes) <= 12)[None, :] +for name, cols in (("adv, p/B [#56]", (A1, A2_wrong)), ("adv, p", (A1, A2)), ("adv, p, gauge", (A1, A2, A3))): + M = np.stack([a[sel] for a in cols], axis=1) + coef, *_ = np.linalg.lstsq(M, y[sel], rcond=None) + res = np.linalg.norm(M @ coef - y[sel]) / np.linalg.norm(y[sel]) + print(f"fit Bkxprp ~ {name:14s}: coef", np.round(coef, 4), f"resid {res:.4f}") +M = A1 + A2 + A3 +print("fixed coefficients (1,1,1): resid %.4f" % ( + np.linalg.norm(M[sel] - y[sel]) / np.linalg.norm(y[sel]))) + +print("=== consistency of the regressors (far surfaces, |m|<=12)") +eul = sp.eulerian +def r2(a, b): + return np.linalg.norm(a[sel] - b[sel]) / np.linalg.norm(b[sel]) +print("adv (xclebsch) vs lagb-eulb %.4f" % r2(A1, spec(sp.lagrangian - eul))) +print("p-term vs Bk-(llagb-eulb) %.4f" % r2(A2, spec(sp.b_kappa_xi_perp - llagb + eul))) +print("Bk vs (llagb-eulb)+p-term %.4f" % r2(y, spec(llagb - eul) + A2)) + +T = spec(sp.b_kappa_xi_perp - llagb + eul) +scale = np.vdot(A2[sel], T[sel]) / np.vdot(A2[sel], A2[sel]) +print("pressure coefficient against corrected regressor:", scale) diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json index 8de71dd18..1090e2204 100644 --- a/docs/derivations/pmodb_gauge/fortsym_verdicts.json +++ b/docs/derivations/pmodb_gauge/fortsym_verdicts.json @@ -72,13 +72,13 @@ "label": "G3", "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", "verdict": "PROVED", - "seconds": 0.023 + "seconds": 0.019 }, { "label": "G4.1", "equation": "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", "verdict": "PROVED", - "seconds": 0.006 + "seconds": 0.005 }, { "label": "G4.2", @@ -96,19 +96,19 @@ "label": "G4.4", "equation": "xi_perp^theta = xms/(chi1 q) + xmz/(J q) - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", "verdict": "PROVED", - "seconds": 0.011 + "seconds": 0.008 }, { "label": "G5.1", "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", "verdict": "PROVED", - "seconds": 0.013 + "seconds": 0.008 }, { "label": "G5.2", "equation": "xi_g^theta = xi_s/(chi1 q)", "verdict": "PROVED", - "seconds": 0.017 + "seconds": 0.013 }, { "label": "G5.2b", @@ -120,37 +120,37 @@ "label": "G5.3", "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", "verdict": "PROVED", - "seconds": 0.049 + "seconds": 0.029 }, { "label": "G5.4", "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", "verdict": "PROVED", - "seconds": 0.032 + "seconds": 0.024 }, { "label": "G5.5", "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", "verdict": "PROVED", - "seconds": 0.027 + "seconds": 0.019 }, { "label": "G5.6", "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", "verdict": "PROVED", - "seconds": 0.027 + "seconds": 0.018 }, { "label": "G5.7", "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", "verdict": "PROVED", - "seconds": 0.015 + "seconds": 0.013 }, { "label": "G5.8", "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", "verdict": "PROVED", - "seconds": 0.011 + "seconds": 0.01 }, { "label": "G7.1", @@ -200,6 +200,12 @@ "verdict": "PROVED", "seconds": 0.0 }, + { + "label": "R5", + "equation": "llagb_code - (eulb+xi_rec_perp.grad B) = (1-R) xms Bt/(chi1 q)", + "verdict": "PROVED", + "seconds": 0.001 + }, { "label": "R2", "equation": "J div xi (R = 1) = 0", diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py index f38bc384c..05df76c13 100644 --- a/docs/derivations/pmodb_gauge/identities.py +++ b/docs/derivations/pmodb_gauge/identities.py @@ -394,6 +394,9 @@ def regularisation(sp): jdiv_actual-J0*(1-R**2)*X1-(1-R)*J1*X), ("R4", "chi1(q xi_reconstructed^theta-xi_reconstructed^zeta)=R xms", chi1*(q*xmt-xmz)/J0-R*Xs)] + Bt = sp.Symbol("Bt") + out.append(("R5", "llagb_code - (eulb+xi_rec_perp.grad B) = (1-R) xms Bt/(chi1 q)", + (Xs/(chi1*q)+xmz/(J0*q)-xmt/J0)*Bt-(1-R)*Xs*Bt/(chi1*q))) jdiv_unreg = (J0 * X1 + J1 * X + 2 * pi * I * m * xwt.subs(R, 1) - 2 * pi * I * n * xwz.subs(R, 1)) out.append(("R2", "J div xi (R = 1) = 0", jdiv_unreg)) diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index f5eedc0dd..7c25f69e6 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -48,6 +48,10 @@ For every ``jac_type`` (G1, G1F) the Jacobian-weighted perturbed field of The pmodb columns ================= +The displacement identifications in this section assume unregularized +consistent components. The literal lagb expression is valid for both modes; +the regularized closure defect is given by R4-R5 below. + ``eulb`` :math:`\delta B_E=\hat{\mathbf b}\cdot\delta\mathbf B`, the Eulerian field strength (G3). @@ -139,6 +143,14 @@ divergence of the reconstructed displacement is instead (R1a) =\mathcal J(1-R^2)\partial_\psi\xi^\psi+(1-R)\mathcal J'\xi^\psi. Also :math:`\chi_1(q\xi_{rec}^\theta-\xi_{rec}^\zeta)=R\xi_s` (R4). +The diagnostic llagb also differs from the projection of the reconstructed +contravariant displacement by (R5) + +.. math:: + + \mathrm{llagb}_{code}-(\delta B_E+\boldsymbol\xi_{rec,\perp}\cdot\nabla B) + =(1-R)\frac{\xi_s}{\chi_1 q}\partial_\theta B. + These formulas are for the diagonal model only. Near rational surfaces the regularized field, displacement and effective derivative therefore do not form one exact ideal-induction pair. This is a limitation of the smoothing @@ -246,7 +258,8 @@ Finding (ii) from the TC24 reconstruction is a missing division by B in the pressure *regressor*, not a GPEC normalization error (P1). For the retained ITER n=3 output the original fit gives 0.1677; the corrected pressure term plus the gauge term fits coefficients (1,1,1) with relative -residual about 1e-4. This is numerical corroboration only. +residual about 1e-4. This is numerical corroboration only; ``docs/derivations/pmodb_gauge/check_output.py`` +reproduces the fit from the pmodb, xclebsch, diagnostic and IDCON ASCII tables. Finding (i) combines the gauge term, effective-derivative smoothing, finite spectral/spline error, and the equilibrium force residual. The From e032054c8e5b713d1a6a843e0d01d4a4aa13edc5 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Tue, 20 Oct 2026 00:37:43 +0200 Subject: [PATCH 266/305] DOCS - MINOR - Pin source references to the audited upstream revision --- docs/derivations/pmodb_gauge/identities.py | 4 ++-- docs/pmodb_gauge.rst | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py index 05df76c13..1dadb9506 100644 --- a/docs/derivations/pmodb_gauge/identities.py +++ b/docs/derivations/pmodb_gauge/identities.py @@ -155,7 +155,7 @@ def xi_s(self, v): def induction(sp): - """G1: GPEC's J dB^i (gpeq.f:107-109) is J curl(xi x B)^i for any Jacobian.""" + """G1: GPEC's J dB^i (gpeq.f:114-116) is J curl(xi x B)^i for any Jacobian.""" S = Setup(sp) xi, B = S.xi, S.Bup JdB = [S.J * c for c in S.curl_up(S.cross_cov(xi, B))] @@ -586,7 +586,7 @@ def open_gaps(sp): G6 is the coordinate form of C1 + C2 (proved on Cartesian 1-jets) with det g = J^2 imposed through g_33. On ae635c80 the zero test still returns UNKNOWN for this large rational - residual; the variable cap removed by #84 is no longer the explanation; it is recorded, not + residual; the removed variable cap is no longer the explanation; it is recorded, not counted as proved. """ return force_balance_identity(sp) diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index 7c25f69e6..bfa03b5ac 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -275,16 +275,16 @@ Source map and literature ========================== Verified code contracts (line numbers at Fortran base 3622c25c): -``gpec/idcon.f:93`` (chi1=2pi psio), ``:617`` (projection geometry), -``:675`` (pressure in Pa), ``:1026`` (curvature and divergence matrices), -``gpec/gpeq.f:114`` (regularization selection), ``:183`` (surface-displacement -closure), ``gpec/gpout.f:3035`` (eulb/lagb), ``:3047`` (divergence and -pressure curvature), ``gpec/gpdiag.f:1003`` (diagnostic projection), -``:1061`` (llagb), ``pentrc/inputs.f90:469`` (ASCII columns), -``:487`` (old gauge mixing), ``:944`` (perpendicular matrix pair), +``gpec/idcon.f:93`` (chi1=2pi psio), ``:616`` (projection geometry), +``:677`` (pressure in Pa), ``:1026`` (curvature and divergence matrices), +``gpec/gpeq.f:131`` (regularization selection), ``:208`` (surface-displacement +closure), ``gpec/gpout.f:3041`` (eulb/lagb), ``:3050`` (divergence and +pressure curvature), ``gpec/gpdiag.f:1012`` (diagnostic projection), +``:1061`` (llagb), ``pentrc/inputs.f90:376`` (ASCII columns), +``:396`` (old gauge mixing), ``:856`` (perpendicular matrix pair), ``pentrc/torque.F90:379`` (old CGL half). -Julia base 0304fd54c: ``src/KineticForces/KineticForcesStructs.jl:435`` +Julia base 0304fd54c: ``src/KineticForces/KineticForcesStructs.jl:437`` uses the correct perpendicular dbob convention; ``src/KineticForces/Torque.jl:295`` repeats the CGL half. ``src/PerturbedEquilibrium/FieldReconstruction.jl``'s xi_s is Glasser's From cd0dfc349c8db19ddab6973bc27f6f61f573d4ff Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Tue, 20 Oct 2026 00:45:32 +0200 Subject: [PATCH 267/305] DOCS - NEW FEATURE - Prove the general-J pmodb error budget --- .../pmodb_gauge/fortsym_verdicts.json | 56 +++++++++++-------- docs/derivations/pmodb_gauge/identities.py | 34 ++++++++++- docs/pmodb_gauge.rst | 25 +++++++++ 3 files changed, 92 insertions(+), 23 deletions(-) diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json index 1090e2204..b271512dc 100644 --- a/docs/derivations/pmodb_gauge/fortsym_verdicts.json +++ b/docs/derivations/pmodb_gauge/fortsym_verdicts.json @@ -6,19 +6,19 @@ "label": "G1.1", "equation": "J dB^psi = GPEC bwp (pointwise)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1.2", "equation": "J dB^theta = GPEC bwt (pointwise)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1.3", "equation": "J dB^zeta = GPEC bwz (pointwise)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G1.4", @@ -72,43 +72,43 @@ "label": "G3", "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", "verdict": "PROVED", - "seconds": 0.019 + "seconds": 0.009 }, { "label": "G4.1", "equation": "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", "verdict": "PROVED", - "seconds": 0.005 + "seconds": 0.003 }, { "label": "G4.2", "equation": "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", "verdict": "PROVED", - "seconds": 0.004 + "seconds": 0.002 }, { "label": "G4.3", "equation": "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G4.4", "equation": "xi_perp^theta = xms/(chi1 q) + xmz/(J q) - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", "verdict": "PROVED", - "seconds": 0.008 + "seconds": 0.004 }, { "label": "G5.1", "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", "verdict": "PROVED", - "seconds": 0.008 + "seconds": 0.006 }, { "label": "G5.2", "equation": "xi_g^theta = xi_s/(chi1 q)", "verdict": "PROVED", - "seconds": 0.013 + "seconds": 0.007 }, { "label": "G5.2b", @@ -120,37 +120,37 @@ "label": "G5.3", "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", "verdict": "PROVED", - "seconds": 0.029 + "seconds": 0.017 }, { "label": "G5.4", "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", "verdict": "PROVED", - "seconds": 0.024 + "seconds": 0.012 }, { "label": "G5.5", "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", "verdict": "PROVED", - "seconds": 0.019 + "seconds": 0.008 }, { "label": "G5.6", "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", "verdict": "PROVED", - "seconds": 0.018 + "seconds": 0.008 }, { "label": "G5.7", "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", "verdict": "PROVED", - "seconds": 0.013 + "seconds": 0.009 }, { "label": "G5.8", "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", "verdict": "PROVED", - "seconds": 0.01 + "seconds": 0.005 }, { "label": "G7.1", @@ -162,7 +162,7 @@ "label": "G7.2", "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_theta", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "G7.3", @@ -186,7 +186,7 @@ "label": "R1", "equation": "J D_effective(reg) = (1-R)(J R xsp1 + J' xsp)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "R1a", @@ -204,13 +204,13 @@ "label": "R5", "equation": "llagb_code - (eulb+xi_rec_perp.grad B) = (1-R) xms Bt/(chi1 q)", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "R2", "equation": "J div xi (R = 1) = 0", "verdict": "PROVED", - "seconds": 0.002 + "seconds": 0.001 }, { "label": "R3", @@ -218,6 +218,18 @@ "verdict": "PROVED", "seconds": 0.0 }, + { + "label": "R6", + "equation": "H_parallel = B(X1-U)+(B_theta Delta_zeta-B_zeta Delta_theta)/(J B)", + "verdict": "PROVED", + "seconds": 0.0 + }, + { + "label": "R7", + "equation": "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", + "verdict": "PROVED", + "seconds": 0.001 + }, { "label": "E4.1", "equation": "d/de J(X + e xi) = d_i(J xi^i) = J div xi", @@ -234,7 +246,7 @@ "label": "G8.2", "equation": "idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "Q1.hamada", @@ -258,7 +270,7 @@ "label": "Q1.boozer", "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.0 }, { "label": "Q2.boozer", diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py index 1dadb9506..e268dee93 100644 --- a/docs/derivations/pmodb_gauge/identities.py +++ b/docs/derivations/pmodb_gauge/identities.py @@ -410,6 +410,38 @@ def regularisation(sp): return out +def regularized_column_budget(sp): + """General-J continuum budget, allowing independent field/output derivatives. + + U is the derivative selected by gpeq_sol; V is the derivative used in + gpout divergence. Delta=xi_s-chi1(q xi_rec^theta-xi_rec^zeta). + F denotes xi_rec_perp.((curl B)xB-mu0 grad p)/B, in Tesla. + Metric/field-norm consistency and C1/C2 are assumed; finite truncation + errors are not folded into the physical force residual. + """ + c,J,B,q,Bt,Bt_cov,Delta,Dt,Dz,X1,U,V,D,K,F,Z = sp.symbols( + "chi J B q Bt Btheta Delta Delta_theta Delta_zeta X1 U V D K F Z") + Bz_cov=(J*B**2/c-Bt_cov)/q + # J-weighted induction mismatch: supplied field minus curl(xi_rec x B). + Ht=c*(X1-U)+Dz + Hz=c*q*(X1-U)-Dt + Hpar=(Bt_cov*Ht+Bz_cov*Hz)/(J*B) + curl_delta=(Bt_cov*Dz-Bz_cov*Dt)/(J*B) + out=[("R6", "H_parallel = B(X1-U)+(B_theta Delta_zeta-B_zeta Delta_theta)/(J B)", + Hpar-B*(X1-U)-curl_delta)] + g=Delta*Bt/(c*q) + # C1 for the reconstructed perpendicular displacement, plus the supplied H. + LLrec=-B*(D+K)+Hpar + LLcode=LLrec+g + Lagcode=LLcode-Z*Bt/q + BDcode=B*(D+V-X1) + BKcode=B*K+g-F + budget=-Z*Bt/q+2*g+curl_delta+B*(V-U)-F + out.append(("R7", "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", + Lagcode+BDcode+BKcode-budget)) + return out + + def cgl(sp): """K1-K2: the PENTRC/GPEC.jl fcgl integrand against the CGL closure. @@ -577,7 +609,7 @@ def at0(e): def all_identities(sp): return (induction(sp) + fourier_symbols(sp) + incompressible_closure(sp) + columns(sp) + pentrc_kappa_matrices(sp) - + cartesian_kappa(sp) + regularisation(sp) + + cartesian_kappa(sp) + regularisation(sp) + regularized_column_budget(sp) + jacobian_identity(sp) + divergence_matrices(sp) + gauge_coordinates(sp) + [c for c in cgl(sp) if not c[0].endswith("neg")] + drive_gauge(sp)) diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index bfa03b5ac..07aef9af0 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -158,6 +158,31 @@ policy, not evidence of resistive physics. The unconditional smoothing in ``gpout_pmodb`` even with ``reg_flag=f`` is a separate verified output bug. +A general-J continuum error budget (verified R6-R7) is available without +assuming a diagonal regularizer. Let U be the derivative selected by +``gpeq_sol``, V the derivative used by pmodb divergence, and +:math:`\Delta_s=\xi_s-\chi_1(q\xi_{rec}^\theta-\xi_{rec}^\zeta)`. +Writing :math:`B_\theta,B_\zeta` for covariant components gives + +.. math:: + + \mathrm{lagb}+\mathrm{Bdivxprp}+\mathrm{Bkxprp} + =-\frac{\xi_{rec,\perp}^\zeta}{q}\partial_\theta B + +\frac{2\Delta_s}{\chi_1q}\partial_\theta B + +\frac{B_\theta\partial_\zeta\Delta_s-B_\zeta\partial_\theta\Delta_s} + {\mathcal J B} + +B(V-U)-\frac{\boldsymbol\xi_{rec,\perp}\cdot\mathbf F}{B}. + +This uses consistent continuum geometry and the field norm. Finite Fourier +truncation and spline errors are additional numerical errors, not part of F. +The derivative term cancels when V=U, even if both are regularized; in the +old reg_flag=f output V differs from U. With regularization enabled, +Delta_s can be nonzero and leaves the two explicit closure terms. Thus a +large derivative in Bdivxprp alone does not measure its contribution to the +net mismatch. This budget separates the candidate causes of finding (i), +without claiming a numerical allocation of the original edge residual. + + What consumers need =================== From 8b6f0d61beb7c5bddbf708e64c9bc9633b13dd1a Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 19 Oct 2026 23:39:01 -0400 Subject: [PATCH 268/305] PENTRC - MINOR - Only test kinetic_format when it is present Fortran does not guarantee short-circuit evaluation, so the combined present(format) .and. format /= "legacy" test could read an absent argument. Every caller passes it today; this nests the check. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- pentrc/inputs.f90 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index db5f28a02..4d4f2b7ea 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -221,9 +221,11 @@ subroutine read_kin(file,zi,zimp,mi,mimp,nfac,tfac,wefac,wpfac,write_log,format) ! optional supplied logLambda, nu_i, nu_e columns supplied = .false. - if(present(format)) supplied = (format == "rates-v1") - if(present(format) .and. .not. supplied .and. format /= "legacy") & - error stop "ERROR: read_kin - kinetic_format must be legacy or rates-v1" + if(present(format))then + supplied = (format == "rates-v1") + if(.not. supplied .and. format /= "legacy") & + error stop "ERROR: read_kin - kinetic_format must be legacy or rates-v1" + endif nq = 5 if(supplied)then if(tshape(2) /= 9) error stop "ERROR: read_kin - rates-v1 needs 9 columns" From 1f8abc54408f7b29dad5074c28a61d0efa6bf6ea Mon Sep 17 00:00:00 2001 From: logan-nc Date: Tue, 20 Oct 2026 00:02:56 -0400 Subject: [PATCH 269/305] DCON - BUG FIX - Keep the prior pivot order for a forced singular reduction When the last ordinary fixup before a singular surface falls on the same step, ode_unorm(.TRUE.) runs with new=.TRUE. and unorm holding raw norms. Re-sorting on raw norms changed index(1), so ode_ideal_cross replaced a different column than the reduction it repeated, shifting W_t by ~2% and GPEC resonant fields by O(10). Reusing the prior order makes the second reduction an exact no-op apart from the sing_flag record GPEC needs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- dcon/ode.f | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dcon/ode.f b/dcon/ode.f index a111af673..66cfb3572 100644 --- a/dcon/ode.f +++ b/dcon/ode.f @@ -984,6 +984,7 @@ SUBROUTINE ode_unorm(sing_flag) INTEGER, DIMENSION(1) :: jmax REAL(r8) :: uratio LOGICAL :: fix_required + INTEGER :: isol c----------------------------------------------------------------------- c compute norms of first solution vectors, abort if any are zero. c----------------------------------------------------------------------- @@ -1001,6 +1002,8 @@ SUBROUTINE ode_unorm(sing_flag) IF(new)THEN new=.FALSE. unorm0(1:msol)=unorm(1:msol) +c keep the ordering of the reduction just done at this point. + IF(sing_flag)unorm(index(1:msol))=(/(msol-isol,isol=1,msol)/) ELSE unorm(1:msol)=unorm(1:msol)/unorm0(1:msol) uratio=MAXVAL(unorm(1:msol))/MINVAL(unorm(1:msol)) From 2a551463bfa40fbdde70a4ab7eb768fcb360fc93 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Tue, 20 Oct 2026 01:38:56 -0400 Subject: [PATCH 270/305] RDCON - BUG FIX - Keep forced singular reductions through norm resets Same defect as the DCON fix: rdcon/ode.f duplicates ode_unorm, so a fixup on the last step before a singular surface left new=.TRUE. and skipped the singular reduction, dropping the sing_flag record GPEC needs. Apply the same restructure and pivot-order reuse. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- rdcon/ode.f | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rdcon/ode.f b/rdcon/ode.f index 08be2637c..81a980707 100644 --- a/rdcon/ode.f +++ b/rdcon/ode.f @@ -588,6 +588,7 @@ SUBROUTINE ode_unorm(sing_flag) INTEGER :: isol,ipert,iqty INTEGER, DIMENSION(1) :: jmax REAL(r8) :: uratio,umax,umin + LOGICAL :: fix_required c----------------------------------------------------------------------- c format statements. c----------------------------------------------------------------------- @@ -626,26 +627,30 @@ SUBROUTINE ode_unorm(sing_flag) CALL program_stop(message) ENDIF c----------------------------------------------------------------------- -c normalize unorm and perform Gaussian reduction if required. +c preserve singular reductions while resetting norm baselines. c----------------------------------------------------------------------- + fix_required=sing_flag IF(new)THEN new=.FALSE. unorm0(1:msol)=unorm(1:msol) +c keep the ordering of the reduction just done at this point. + IF(sing_flag)unorm(index(1:msol))=(/(msol-isol,isol=1,msol)/) ELSE unorm(1:msol)=unorm(1:msol)/unorm0(1:msol) umax=MAXVAL(unorm(1:msol)) umin=MINVAL(unorm(1:msol)) uratio=umax/umin - IF(uratio > ucrit .OR. sing_flag)THEN - CALL ode_fixup(sing_flag,.FALSE.) - IF(diagnose1)CALL ode_test_fixup - new=.TRUE. - ENDIF + fix_required=fix_required .OR. uratio > ucrit IF(diagnose_fixup) $ WRITE(fixup_unit)REAL(istep,4),REAL(psifac,4), $ REAL(q,4),REAL(LOG10(singfac),4),REAL(LOG10(umax),4), $ REAL(LOG10(umin),4),REAL(LOG10(uratio),4) ENDIF + IF(fix_required)THEN + CALL ode_fixup(sing_flag,.FALSE.) + IF(diagnose1)CALL ode_test_fixup + new=.TRUE. + ENDIF c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- From 1893b47fba738684974d834b81e88ac4951c8feb Mon Sep 17 00:00:00 2001 From: logan-nc Date: Tue, 20 Oct 2026 23:18:14 -0400 Subject: [PATCH 271/305] INSTALL - CLEANUP - Remove dangling deps/src/harvest gitlink Harvest was removed in v1.5.7, but its gitlink stayed in the tree with no .gitmodules entry. On a fresh clone, git submodule status, init and clone --recursive fail with 'no submodule mapping found'. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- deps/src/harvest | 1 - 1 file changed, 1 deletion(-) delete mode 160000 deps/src/harvest diff --git a/deps/src/harvest b/deps/src/harvest deleted file mode 160000 index 43b9758a2..000000000 --- a/deps/src/harvest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 43b9758a23e269b0d17f88a5f270983b0a4df32d From 2993131bb8cebb4583e570fbf89f7f2fa0ef0d0b Mon Sep 17 00:00:00 2001 From: logan-nc Date: Tue, 20 Oct 2026 23:24:35 -0400 Subject: [PATCH 272/305] RDCON - BUG FIX - Accept coil in gal_output for v1.5 rdcon.in files #204 moved coil from gal_output to rdcon_control, so every v1.5 rdcon.in (which sets coil%rpec_flag in GAL_OUTPUT) stopped with a namelist read error. Accept coil in both groups. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- rdcon/gal.f | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index f45a55f0f..1b091a84c 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1785,9 +1785,10 @@ SUBROUTINE gal_read_input $ dpsi1_intvl,gal_xmin_flag,gal_eps_xmin,gnstep NAMELIST /gal_output/interp_np,interp_np_res,restore_uh, $ restore_us,restore_ul,bin_delmatch,out_galsol,bin_galsol, - $ b_flag,bin_coilsol + $ b_flag,bin_coilsol,coil c----------------------------------------------------------------------- -c read input. +c read input. coil now belongs in rdcon_control, but is also +c accepted here so v1.5 rdcon.in files still run. c----------------------------------------------------------------------- READ(UNIT=in_unit,NML=gal_input) REWIND(UNIT=in_unit) From 154f7bcee8e2e526fc8a5fd69194df7e38c749c1 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Wed, 21 Oct 2026 00:37:31 -0400 Subject: [PATCH 273/305] RDCON - MINOR - Warn when coil%rpec_flag is set in gal_output Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- rdcon/gal.f | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rdcon/gal.f b/rdcon/gal.f index 1b091a84c..ab1d5f249 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1779,6 +1779,7 @@ END SUBROUTINE gal_spline_pack c declarations. c----------------------------------------------------------------------- SUBROUTINE gal_read_input + LOGICAL :: rpec_flag0 NAMELIST/gal_input/nx,nq,dx0,dx1,dx2,pfac,diagnose_map,solver, $ diagnose_grid,diagnose_lsode,ndiagnose,diagnose_integrand, $ diagnose_mat,gal_tol,dx1dx2_flag,cutoff,prefac,dpsi_intvl, @@ -1792,7 +1793,11 @@ SUBROUTINE gal_read_input c----------------------------------------------------------------------- READ(UNIT=in_unit,NML=gal_input) REWIND(UNIT=in_unit) + rpec_flag0=coil%rpec_flag READ(UNIT=in_unit,NML=gal_output) + IF(coil%rpec_flag .NEQV. rpec_flag0)WRITE(*,*) + $ "WARNING: coil%rpec_flag in gal_output is deprecated;"// + $ " set it in rdcon_control instead." c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- From 0f5af90abca56613bec97a0e993517b76279495c Mon Sep 17 00:00:00 2001 From: logan-nc Date: Wed, 21 Oct 2026 00:41:17 -0400 Subject: [PATCH 274/305] ALL - CLEANUP - v1.6 pre-release fixes to templates, debug output and docs - input/slayer.in: drop ncfile (not a slayer_input member), move bal_flag to SLAYER_DIAGNOSE, and write delta_n_p as a complex literal. Standalone slayer now runs on the template. - pentrc/inputs.f90: remove a leftover WRITE(*,*)chi1 debug print. - input/equil.in: document enstep. - docs: mark site.rst :orphan: and rename the install/README label to installations, clearing the two Sphinx warnings. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/site.rst | 2 ++ input/equil.in | 1 + input/slayer.in | 5 ++--- install/README | 2 +- pentrc/inputs.f90 | 1 - 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/site.rst b/docs/site.rst index 7905ced0e..59e5c59c0 100644 --- a/docs/site.rst +++ b/docs/site.rst @@ -1,3 +1,5 @@ +:orphan: + GPEC documentation website site map ==================================== diff --git a/input/equil.in b/input/equil.in index b4bf80214..5c3b2147e 100644 --- a/input/equil.in +++ b/input/equil.in @@ -14,6 +14,7 @@ mtheta=256 ! Number of equally spaced poloidal grid intervals for all splines newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. etol = 1e-7 ! Tolerance for direct equilibrium reconstruction of flux surfaces/coordinates + enstep=16384 ! Maximum number of LSODE steps allowed per field-line integral in the direct equilibrium reconstruction use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions profile_source="values" ! efit, and ldp_i files with FF', p' records. "values" (default): spline the tabulated f, p. "integrate": integrate FF', p' inward from the boundary and keep them as knot derivatives; for g-files with single-precision values; can shift pedestal surfaces diff --git a/input/slayer.in b/input/slayer.in index 65d4d4523..06e3c06e1 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -3,7 +3,6 @@ !!! FOR READING IN ASCII TABLE, SET INPUT_FLAG = TRUE input_flag=f ! reads profile quantities (n_e, t_e, etc.) from an ascii table infile='' ! Path to ascii table of profile quantities read when using input_flag - ncfile='' ! Path to netCDF file of STRIDE outputs !!! FOR MANUALLY ENTERING KINETIC QUANTITIES @ RATIONAL SURFACE, SET PARAMS_FLAG = TRUE params_flag=f ! calculates normalized parameters from profile quantities @@ -34,7 +33,7 @@ inds=6.0 ! normalized ion sound radius intau=1.0 ! ion temperature divided by electron temperature Q0=4.0 ! unnecessary placeholder variable for inQ? - delta_n_p=0 ! delta offset used for jxb calculations. Default is (1e-2,1e-2). + delta_n_p=(0,0) ! complex delta offset used for jxb calculations. Default is (1e-2,1e-2). / &SLAYER_CONTROL inum=400 ! resolution to find error field thresholds. @@ -59,9 +58,9 @@ bin_flag=f ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - bal_flag=f ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file params_check=t ! prints the primary parameters to the terminal when they are calculated + bal_flag=f ! calculate the resonant field penetration threshold from torque balance / diff --git a/install/README b/install/README index 33ec0f31b..992eaeb01 100644 --- a/install/README +++ b/install/README @@ -3,7 +3,7 @@ LICENSE The Generalized Perturbed Equilibrium Code (GPEC) package is available for use under the terms of the standard MIT license. -.. _installation: +.. _installations: Installations ============= diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index 4d4f2b7ea..e98d64a9d 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -150,7 +150,6 @@ subroutine read_equil(file) ! set additional geometry spline call set_geom - WRITE(*,*)chi1 end subroutine read_equil From 367a02497a3a925a6f2de8627d3fc3eb5c44b086 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Wed, 21 Oct 2026 00:42:15 -0400 Subject: [PATCH 275/305] DOCS - CLEANUP - Fix a10_ideal_example slayer.in the same way as the template Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/examples/a10_ideal_example/slayer.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples/a10_ideal_example/slayer.in b/docs/examples/a10_ideal_example/slayer.in index 8d2d5ba03..374beb6cc 100644 --- a/docs/examples/a10_ideal_example/slayer.in +++ b/docs/examples/a10_ideal_example/slayer.in @@ -32,7 +32,7 @@ inds=6.0 ! normalized ion sound radius intau=1.0 ! ion temperature divided by electron temperature Q0=4.0 ! unnecessary placeholder variable for inQ? - delta_n_p=0 ! delta offset used for jxb calculations. Default is (1e-2,1e-2). + delta_n_p=(0,0) ! complex delta offset used for jxb calculations. Default is (1e-2,1e-2). / &SLAYER_CONTROL inum=400 ! resolution to find error field thresholds. @@ -56,9 +56,9 @@ bin_flag=t ! writes results to binary files netcdf_flag=f ! writes results to netcdf files stability_flag=f ! calculate delta dependence on complex Q - bal_flag=t ! calculate the resonant field penetration threshold from torque balance / &SLAYER_DIAGNOSE riccati_out=f ! writes LSDOE Riccati integration to an ascii file params_check=t ! prints the primary parameters to the terminal when they are calculated -/ \ No newline at end of file + bal_flag=t ! calculate the resonant field penetration threshold from torque balance +/ From 6f53c099debc1f35fb3745fb7e53ac0ef69d8417 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Wed, 21 Oct 2026 01:33:28 -0400 Subject: [PATCH 276/305] PENTRC, RMATCH - CLEANUP - Heap-allocate thread-local work arrays, drop spurious imap error print, enable a10 ascii output - pentrc/pitch.f90, energy.f90: fs and xprofile (about 1.6 MB together) were automatic arrays on the OpenMP worker stack, which is 2 MB under ulimit -s unlimited. DCON's threaded kinetic matrix segfaulted on solovev_kinetic_example. They are now allocatable. - rmatch/deltac.f: deltac_assemble_rhs printed 'ERROR: imap error' when skipping the small-solution unknowns that noexp deliberately drops. deltac_assemble_mat skips the same entries silently; now both do. - a10_ideal_example/gpec.in: ascii_flag=t so gpec writes the gpec_xclebsch file that the example's pentrc run reads. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/examples/a10_ideal_example/gpec.in | 2 +- pentrc/energy.f90 | 3 ++- pentrc/pitch.f90 | 3 ++- rmatch/deltac.f | 13 +++---------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/examples/a10_ideal_example/gpec.in b/docs/examples/a10_ideal_example/gpec.in index 9bb5197e3..74c36ee5b 100644 --- a/docs/examples/a10_ideal_example/gpec.in +++ b/docs/examples/a10_ideal_example/gpec.in @@ -78,7 +78,7 @@ flux_flag=f ! Write outputs to (psi,theta) in addition to default (psi,m) netcdf_flag=t ! Write all outputs in netcdf files - ascii_flag=f ! Write all outputs in ascii tables + ascii_flag=t ! Write all outputs in ascii tables max_linesout=0 ! Maximum length of ascii data tables enforced by increasing radial step size (iff .gt. 0). bin_flag=f ! Write binary outputs for use with xdraw bin_2d_flag=f ! Write 2D binary outputs for use with xdraw diff --git a/pentrc/energy.f90 b/pentrc/energy.f90 index 85e803fe4..979ab9d25 100644 --- a/pentrc/energy.f90 +++ b/pentrc/energy.f90 @@ -155,7 +155,7 @@ function xintgrl_lsode(wn,wt,we,wd,wb,nuk,ell,leff,n,psi,lambda,method,op_record logical, intent(in), optional :: op_record ! declare variables integer :: i, j, xout_unit - real(r8), dimension(6,maxstep) :: xprofile + real(r8), dimension(:,:), allocatable :: xprofile logical :: record_this ! declare lsode input variables integer iopt, istate, itask, itol, mf, iflag @@ -170,6 +170,7 @@ function xintgrl_lsode(wn,wt,we,wd,wb,nuk,ell,leff,n,psi,lambda,method,op_record ! set default recording flag i = 0 + allocate(xprofile(6,maxstep)) ! heap, not OpenMP worker stack xprofile = 0.0 record_this = .false. if(present(op_record)) record_this = op_record diff --git a/pentrc/pitch.f90 b/pentrc/pitch.f90 index e52d8fc61..b0a32f221 100644 --- a/pentrc/pitch.f90 +++ b/pentrc/pitch.f90 @@ -165,7 +165,7 @@ function lambdaintgrl_lsode(wn,wt,we,nuk,bobmax,epsr,q,flambda,ell,n,& integer :: istep, ilambda, j, out_unit, ix real(r8) :: lmda,wb,wd,nueff,lnq complex(r8) :: xint - real(r8), dimension(nfs,maxstep) :: fs + real(r8), dimension(:,:), allocatable :: fs real(r8), dimension(turns%nqty) :: turns_f complex(r8), dimension(flambda%nqty) :: flmabda_f ! declare lsode input variables @@ -181,6 +181,7 @@ function lambdaintgrl_lsode(wn,wt,we,nuk,bobmax,epsr,q,flambda,ell,n,& lrw = 20 + 16*neq ! for mf 10 allocate(iwork(liw)) allocate(atol(neq),rtol(neq),rwork(lrw),y(neq),dky(neq)) + allocate(fs(nfs,maxstep)) ! heap, not OpenMP worker stack neqarray(:) = (/ neq /) y(:) = 0 ! initial value of integral x = flambda%xs(0) ! lower bound of integration diff --git a/rmatch/deltac.f b/rmatch/deltac.f index afcedabf8..848ec950f 100644 --- a/rmatch/deltac.f +++ b/rmatch/deltac.f @@ -1175,13 +1175,11 @@ SUBROUTINE deltac_assemble_rhs(gal) INTEGER :: ix,imap,ipert,ip,npp INTEGER :: ix0,np0 - INTEGER :: imap_error TYPE(interval_type), POINTER :: intvl TYPE(cell_type), POINTER :: cell c----------------------------------------------------------------------- c start loops over intervals and grid cells. c----------------------------------------------------------------------- - imap_error=0 gal%rhs=0 intvl => gal%intvl ix0=1 @@ -1206,14 +1204,9 @@ SUBROUTINE deltac_assemble_rhs(gal) DO ip=np0,npp DO ipert=1,mpert imap=cell%map(ipert,ip) - IF (imap>gal%ndim) THEN - imap_error = imap - CYCLE - ENDIF - IF( imap_error>0 ) THEN - WRITE (*,*) "ERROR: imap error. imap=",imap_error, - $ " gal%ndim=",gal%ndim - ENDIF +c skip unknowns beyond ndim, as deltac_assemble_mat does +c (the small-solution rows dropped by noexp). + IF (imap>gal%ndim) CYCLE SELECT CASE (fulldomain) CASE (0) gal%rhs(imap,1)=gal%rhs(imap,1)+cell%rhs(ipert,ip) From bd71f4761c75667d4d32da394d414dc4c006f4c5 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Wed, 21 Oct 2026 01:33:56 -0400 Subject: [PATCH 277/305] PENTRC - BUG FIX - Use the evaluated fnml value in the RLAR kappa integral kappaintgrl calls bicube_eval_external, which returns the value in the local fnml_f, but then read fnml%f(1), the spline's own (unset) value buffer. RLAR torques were therefore zero or garbage, depending on what that memory held. kappadjsum already uses fnml_f correctly. The bug dates to cadf88ec (2019), which made these evaluations external. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- pentrc/pitch.f90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pentrc/pitch.f90 b/pentrc/pitch.f90 index e52d8fc61..f4fdc2967 100644 --- a/pentrc/pitch.f90 +++ b/pentrc/pitch.f90 @@ -499,10 +499,10 @@ function kappaintgrl(n,l,q,marray,dbarray,fnml) ! calculate for forward and backward banana call bicube_eval_external(fnml,kappa,marray(i)-n*q-l,0, & ix, iy, fnml_f, fnml_fx, fnml_fy) - fm(i,1) = fnml%f(1) + fm(i,1) = fnml_f(1) call bicube_eval_external(fnml,kappa,marray(i)-n*q+l,0, & ix, iy, fnml_f, fnml_fx, fnml_fy) - fm(i,2) = fnml%f(1) + fm(i,2) = fnml_f(1) enddo do i = 1,mpert do j = 1,mpert From 0bb870bd76d290bb476639c31834de00440cf1ae Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 21 Oct 2026 15:15:26 +0200 Subject: [PATCH 278/305] GPEC - BUG FIX - Honor reg_flag in pmodb divergence output --- gpec/gpout.f | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index 25c872536..ce376b52d 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -3019,9 +3019,8 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) CALL spline_eval(sq,psifac(istep),1) - singfac=mfac-nn*sq%f(4) - xsp1_mn=xsp1_mn*(singfac**2/(singfac**2+reg_spot**2)) - CALL iscdftb(mfac,mpert,xsp1_fun,mthsurf,xsp1_mn) +c Use the radial derivative selected by reg_flag in gpeq_sol. + CALL iscdftb(mfac,mpert,xsp1_fun,mthsurf,xmp1_mn) DO itheta=0,mthsurf CALL bicube_eval(eqfun,psifac(istep),theta(itheta),0) From 2380bc6622488a767c42f17ada8883065b0987cb Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 21 Oct 2026 15:15:27 +0200 Subject: [PATCH 279/305] PENTRC - BUG FIX - Recover full CGL curvature from perpendicular field strength --- pentrc/torque.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pentrc/torque.F90 b/pentrc/torque.F90 index 2002c0d2a..7e594bd71 100644 --- a/pentrc/torque.F90 +++ b/pentrc/torque.F90 @@ -376,7 +376,7 @@ subroutine tpsi(tpsi_var,psi,n,l,zi,mi,wdfac,divxfac,electron, & jbb = (rzphi_f(4) * eqfun_f(1)**2) dbob = sum( dbob_m_f(:)*expm ) ! dB/B divx = sum( divx_m_f(:)*expm ) ! nabla.xi_perp - kapx = -0.5*(dbob+divx) + kapx = -(dbob+divx) cglspl%xs(i) = theta ! include 0.5 correction for quadratic calculations in complex analysis cglspl%fs(i,1) = rzphi_f(4)*0.5*abs(divx)**2 @@ -386,8 +386,8 @@ subroutine tpsi(tpsi_var,psi,n,l,zi,mi,wdfac,divxfac,electron, & call spline_int(cglspl) ! torque tpsi_var = 2.0*n*xj*kin_f(s)*kin_f(s+2) & ! T = 2nidW - *(0.5*(5.0/3.0)*cglspl%fsi(cglspl%mx,1)& - + 0.5*(1.0/3.0)*cglspl%fsi(cglspl%mx,2)) + *(0.5_r8*(5.0_r8/3.0_r8)*cglspl%fsi(cglspl%mx,1)& + + 0.5_r8*(1.0_r8/3.0_r8)*cglspl%fsi(cglspl%mx,2)) call spline_dealloc(cglspl) else tpsi_var=0 From f44643a80eb34ac0198ec321f3c079cb2cca9c90 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 21 Oct 2026 15:15:28 +0200 Subject: [PATCH 280/305] GPEC - BUG FIX - Use each point's Jacobian in pmodb diagnostics --- gpec/gpdiag.f | 1 + 1 file changed, 1 insertion(+) diff --git a/gpec/gpdiag.f b/gpec/gpdiag.f index d5b6825ef..e029e89fc 100644 --- a/gpec/gpdiag.f +++ b/gpec/gpdiag.f @@ -1009,6 +1009,7 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) CALL bicube_eval(eqfun,psifac(istep),theta(itheta),0) CALL bicube_eval(rzphi,psifac(istep),theta(itheta),0) + jac=rzphi%f(4) cspl%fs(itheta,1)=xmt_fun(itheta)- $ (chi1/eqfun%f(1))**2/jac* $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)) From 2101e69d842324a20363ffa561848996c1057ea3 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Wed, 21 Oct 2026 15:15:27 +0200 Subject: [PATCH 281/305] PENTRC - BUG FIX - Read the perpendicular pmodb drive and Jacobian pair --- pentrc/inputs.f90 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index e98d64a9d..a3a550b10 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -434,9 +434,8 @@ subroutine read_pmodb(file,jac_in,jsurf_in,tmag_in,debug,op_powin) +xj*reshape(table(:,10),(/npsi,nm/),order=(/1,2/)) endif - ! For consistency with GPEC-0.3.0 and smaller values near rationals - if(verbose) print *," -> Using div(xi_perp) = -(dB/B+kappa.xi_perp)" - divxmni = -(lagbmni+kapxmni) + ! Keep the supplied divergence and use its perpendicular gauge. + lagbmni = -(divxmni+kapxmni) ! convert to chebyshev coordinates if(jac_in=="" .or. jac_in=="default")then From cbf20c0a6009d4ff790f56f09acdb9379d43b247 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Thu, 22 Oct 2026 07:17:11 +0200 Subject: [PATCH 282/305] GPEC - BUG FIX - Form the pmodb perpendicular displacement from xi^psi and xi^s gpout_pmodb built xi_perp as xi minus the Fourier-truncated 1/(m-nq) closure xi_par, which left large residues in Bdivxprp and Bkxprp at the resonant and window-edge harmonics. Build J*xi_perp^theta and J*xi_perp^zeta directly from xi^psi and xi^s with the eqfun(2:3) metric combinations, as in the smat..zmat kinetic matrices. Output format is unchanged. --- gpec/gpout.f | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/gpec/gpout.f b/gpec/gpout.f index ce376b52d..8faae6d03 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -2955,7 +2955,7 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) COMPLEX(r8), DIMENSION(mpert) :: eulbpar_mn,lagbpar_mn, $ divxprp_mn,curv_mn COMPLEX(r8), DIMENSION(0:mthsurf) :: xsp_fun,xms_fun, - $ bvt_fun,bvz_fun,xmz_fun,xvt_fun,xvz_fun,xmt_fun,xsp1_fun + $ bvt_fun,bvz_fun,xmz_fun,xmt_fun,xsp1_fun COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: eulbparmns, $ lagbparmns,divxprpmns,curvmns, $ xmp1mns,xspmns,xmsmns,xmtmns,xmzmns @@ -3014,8 +3014,6 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) CALL iscdftb(mfac,mpert,bvz_fun,mthsurf,bvz_mn) CALL iscdftb(mfac,mpert,xmt_fun,mthsurf,xmt_mn) CALL iscdftb(mfac,mpert,xmz_fun,mthsurf,xmz_mn) - CALL iscdftb(mfac,mpert,xvt_fun,mthsurf,xvt_mn) - CALL iscdftb(mfac,mpert,xvz_fun,mthsurf,xvz_mn) CALL spline_eval(sq,psifac(istep),1) @@ -3027,12 +3025,12 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) CALL bicube_eval(rzphi,psifac(istep),theta(itheta),0) jac=rzphi%f(4) - cspl%fs(itheta,1)=xmt_fun(itheta)- - $ (chi1/eqfun%f(1))**2/jac* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)) - cspl%fs(itheta,2)=xmz_fun(itheta)- - $ sq%f(4)*(chi1/eqfun%f(1))**2/jac* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)) +c Form J*xi_perp directly from xi^psi and xi^alpha=xi^s/chi1. + cspl%fs(itheta,1)=chi1**2* + $ (-eqfun%f(2)*xsp_fun(itheta)+ + $ eqfun%f(3)*xms_fun(itheta)/chi1) + cspl%fs(itheta,2)=sq%f(4)*cspl%fs(itheta,1)- + $ jac*xms_fun(itheta)/chi1 ENDDO CALL cspline_fit(cspl,"periodic") @@ -3062,10 +3060,7 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) $ divxprpfun(istep,itheta) curvfun(istep,itheta)=-xsp_fun(itheta)/eqfun%f(1)*sq%f1(2)- $ (xsp_fun(itheta)*eqfun%fx(1)+eqfun%fy(1)* - $ (xms_fun(itheta)/(chi1*sq%f(4))+ - $ xmz_fun(itheta)/(jac*sq%f(4))- - $ (chi1/(jac*eqfun%f(1)))**2* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)))) + $ cspl%f(1)/jac) equilbfun(istep,itheta) = eqfun%f(1) dphi(itheta) = rzphi%f(3) ENDDO From 6c17d27247b38ac8e07dbd8ebcc5824fd34b0a77 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Thu, 22 Oct 2026 12:44:14 -0400 Subject: [PATCH 283/305] STRIDE - ENHANCEMENT - dprime_banded option: banded LU solve of the Delta' BVP The Delta' BVP matrix is block-banded except for the 2*msing big solution rows, which the dense solve puts last. With dprime_banded=t each surface keeps its two big solution rows in its block, so the matrix is banded with kl = ku = 3*mpert-1 and is solved with ZGBTRF/ZGBTRS (residual check from the sparse entries). Default f keeps the dense solve unchanged. Co-Authored-By: Claude Opus 5.5 --- input/stride.in | 1 + stride/ode.F | 76 +++++++++++++++++++++++++++++++++++-------------- stride/stride.F | 2 +- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/input/stride.in b/input/stride.in index ff979efd7..fdbb3507f 100755 --- a/input/stride.in +++ b/input/stride.in @@ -61,6 +61,7 @@ calc_delta_prime=t ! When false, sing_order=2 is better for speed, (with less accuracy). calc_dp_with_vac=t ! When vac_flag=t, this will use vacuum edge conditions to calculate delta prime. big_soln_err_tol=1e-7 ! Threshold error for correct solution to the delta prime bvp. + dprime_banded=f ! Solve the delta prime bvp with a banded LU instead of a dense one: same result, much faster and smaller with many rational surfaces. integrate_riccati=f ! Use Riccati integration for dW. Cannot calculate delta_prime. riccati_bounce=t ! Toggle integration mode to control growth of matrix diff --git a/stride/ode.F b/stride/ode.F index 73bd2df80..5e5dca911 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -60,6 +60,7 @@ MODULE stride_ode_mod LOGICAL :: calc_delta_prime LOGICAL :: kill_big_soln_for_ideal_dW LOGICAL :: calc_dp_with_vac + LOGICAL :: dprime_banded=.FALSE. TYPE(sing_calculator), DIMENSION(:), TARGET, ALLOCATABLE :: scalc REAL(r8) :: axisPsi, outerPsi @@ -1046,8 +1047,8 @@ SUBROUTINE ode_calc_delta_prime REAL(r8), DIMENSION(:,:), ALLOCATABLE :: atol INTEGER :: nMat, nSp, i, j, k, jsing, ksing, s2, m2 - INTEGER :: iChg, info, ipert0, dRow - REAL(r8) :: rcond, mnorm, ZLANGE + INTEGER :: iChg, info, ipert0, dRow, rs, rb, kl, ku + REAL(r8) :: rcond, mnorm, ZLANGE, ZLANGB INTEGER, DIMENSION(:), ALLOCATABLE :: isp, jsp COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: asp, b, x COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: M, MInv, TempMat @@ -1077,6 +1078,11 @@ SUBROUTINE ode_calc_delta_prime $ s2 * (m2-2) + !each side of each sing $ (msing-1) * m2*m2*2 + !intervals sandwiched b/w sings $ s2 !driven big solution coefficients (2sides * msing) + !rows per singular surface: the banded solve keeps each + !surface's two big solution rows in its block (the dense + !solve puts them last), so kl = ku = 3*mpert-1 + rs = 2*m2-2 + IF (dprime_banded) rs = 2*m2 ALLOCATE(b(nMat),x(nMat),isp(nSp),jsp(nSp),asp(nSp)) ALLOCATE(TempMat(m2,m2),ipivTemp(m2),rworkTemp(2*m2), @@ -1108,7 +1114,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,m2 k=k+1 - isp(k) = i + (2*m2-2) * (jsing-1) + isp(k) = i + rs * (jsing-1) jsp(k) = j + mpert + (2*m2) * (jsing-1) asp(k) = s%uShootL(i,j) ENDDO @@ -1120,7 +1126,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 IF (i/=ipert0 .AND. i/=ipert0+mpert) THEN k=k+1 - isp(k) = i + iChg + m2 + (2*m2-2) * (jsing-1) + isp(k) = i + iChg + m2 + rs * (jsing-1) jsp(k) = i + mpert + (2*m2) * (jsing-1) asp(k) = 1.0 @@ -1138,7 +1144,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,m2 k=k+1 - isp(k) = i + (2*m2-2) * jsing + isp(k) = i + rs * jsing jsp(k) = j + mpert + m2 + (2*m2)*(jsing-1) asp(k) = -s%uShootR(i,j) ENDDO @@ -1150,7 +1156,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,mpert k=k+1 - isp(k) = i + (2*m2-2) * msing + isp(k) = i + rs * msing jsp(k) = j + mpert + (2*m2) * msing asp(k) = uEdge(i,j+mpert) ENDDO @@ -1161,14 +1167,16 @@ SUBROUTINE ode_calc_delta_prime DO jsing = 1,msing s => scalc(jsing) ipert0 = s%sing_col + rb = nMat-s2+2*(jsing-1) + IF (dprime_banded) rb = rs*jsing-2 !left side k=k+1 - isp(k) = isp(k-1)+1 + isp(k) = rb+1 jsp(k) = ipert0 + mpert + (2*m2)*(jsing-1) asp(k) = 1.0 !right side k=k+1 - isp(k) = isp(k-1)+1 + isp(k) = rb+2 jsp(k) = ipert0 + mpert + m2 + (2*m2)*(jsing-1) asp(k) = 1.0 ENDDO @@ -1181,16 +1189,30 @@ SUBROUTINE ode_calc_delta_prime c----------------------------------------------------------------------- c check condition number of BVP matrix. c----------------------------------------------------------------------- - ALLOCATE(M(nMat,nMat),MInv(nMat,nMat)) ALLOCATE(ipiv(nMat),uwork(nMat),cwork(2*nMat),rwork(2*nMat)) - M=0.0 - DO k = 1,nSp - M(isp(k),jsp(k)) = asp(k) - ENDDO - MInv = M - CALL ZGETRF(nMat,nMat,MInv,nMat,ipiv,info) - mnorm = ZLANGE('O',nMat,nMat,MInv,nMat,rwork) - CALL ZGECON('O',nMat,MInv,nMat,mnorm,rcond,cwork,rwork,info) + IF (dprime_banded) THEN + kl = MAXVAL(isp-jsp) + ku = MAXVAL(jsp-isp) + ALLOCATE(MInv(2*kl+ku+1,nMat)) + MInv=0.0 + DO k = 1,nSp + MInv(kl+ku+1+isp(k)-jsp(k),jsp(k)) = asp(k) + ENDDO + mnorm = ZLANGB('O',nMat,kl,ku,MInv(kl+1,1),2*kl+ku+1,rwork) + CALL ZGBTRF(nMat,nMat,kl,ku,MInv,2*kl+ku+1,ipiv,info) + CALL ZGBCON('O',nMat,kl,ku,MInv,2*kl+ku+1,ipiv,mnorm,rcond, + $ cwork,rwork,info) + ELSE + ALLOCATE(M(nMat,nMat),MInv(nMat,nMat)) + M=0.0 + DO k = 1,nSp + M(isp(k),jsp(k)) = asp(k) + ENDDO + MInv = M + CALL ZGETRF(nMat,nMat,MInv,nMat,ipiv,info) + mnorm = ZLANGE('O',nMat,nMat,MInv,nMat,rwork) + CALL ZGECON('O',nMat,MInv,nMat,mnorm,rcond,cwork,rwork,info) + ENDIF print *,"Delta' sparse matrix condition number = ",rcond c----------------------------------------------------------------------- c solve BVP matrix without sparse matrix. @@ -1201,15 +1223,19 @@ SUBROUTINE ode_calc_delta_prime b = 0.0 IF (i == 1) THEN !Left side driving term - b(nMat-s2+2*jsing-1) = 1.0 dRow = 2*jsing-1 ELSE !Right side driving term - b(nMat-s2+2*jsing) = 1.0 dRow = 2*jsing ENDIF + b(isp(nSp-s2+dRow)) = 1.0 !its big solution row x = b - CALL ZGETRS('N',nMat,1,MInv,nMat,ipiv,x,nMat,info) + IF (dprime_banded) THEN + CALL ZGBTRS('N',nMat,kl,ku,1,MInv,2*kl+ku+1,ipiv,x, + $ nMat,info) + ELSE + CALL ZGETRS('N',nMat,1,MInv,nMat,ipiv,x,nMat,info) + ENDIF IF (info /= 0) THEN print *,"info was not zero! info=",info ENDIF @@ -1229,7 +1255,15 @@ SUBROUTINE ode_calc_delta_prime c error-check non-sparse matrix solution. c----------------------------------------------------------------------- !Error-check big solution elements by backward solve. - b = ABS(MATMUL(M,x)) + IF (dprime_banded) THEN + b = 0.0 + DO k = 1,nSp + b(isp(k)) = b(isp(k)) + asp(k)*x(jsp(k)) + ENDDO + b = ABS(b) + ELSE + b = ABS(MATMUL(M,x)) + ENDIF IF (ABS(1-SUM(ABS(b))) > big_soln_err_tol) THEN print *,"Big sol'n error, surface #",jsing,"side",i print *,"=",ABS(1-SUM(ABS(b))) diff --git a/stride/stride.F b/stride/stride.F index 585058ce7..76bc43e61 100644 --- a/stride/stride.F +++ b/stride/stride.F @@ -55,7 +55,7 @@ PROGRAM stride NAMELIST/stride_params/grid_packing,asymp_at_sing, $ integrate_riccati,calc_delta_prime,calc_dp_with_vac, $ axis_mid_pt_skew, - $ big_soln_err_tol, kill_big_soln_for_ideal_dW, + $ big_soln_err_tol, kill_big_soln_for_ideal_dW, dprime_banded, $ ric_dt,ric_tol,riccati_bounce,verbose_riccati_output, $ riccati_match_hamiltonian_evals,verbose_performance_output, $ fourfit_metric_parallel,vac_parallel,nIntervalsTot,nThreads From 4d7e2781fcafd78a5899a285cd528f4ba9767638 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Thu, 22 Oct 2026 20:39:10 -0400 Subject: [PATCH 284/305] GPEC - CLEANUP - Use the direct xi_perp form in the pmodb diagnostic gpdiag_pmodb now builds J*xi_perp from xi^psi and xi^s like gpout_pmodb, for the divergence, curvature and llagb columns, so the diagnostic agrees with the main pmodb output. Its llagb vs -(Bdivx+Bkx) residual at resonant and window-edge m drops from O(1-100) to <1e-3 on the Solov'ev examples. Drops the now-unused xmt/xmz (gpout_pmodb) and xmz/xvt/xvz (gpdiag_pmodb) inverse transforms and relabels the PENTRC pmodb debug columns. gpec_pmodb, gpec_pmodb_fun, pmodbmn diagnostic and PENTRC outputs are bit-identical to cbf20c0a. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- gpec/gpdiag.f | 29 +++++++++++------------------ gpec/gpout.f | 4 +--- pentrc/inputs.f90 | 2 +- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/gpec/gpdiag.f b/gpec/gpdiag.f index e029e89fc..9f1c7e3c9 100644 --- a/gpec/gpdiag.f +++ b/gpec/gpdiag.f @@ -936,7 +936,7 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) COMPLEX(r8), DIMENSION(mpert) :: eulbpar_mn,lagbpar_mn, $ llagbpar_mn,divx_mn,curv_mn,divx2_mn,curv2_mn COMPLEX(r8), DIMENSION(0:mthsurf) :: xsp_fun,xms_fun, - $ bvt_fun,bvz_fun,xmt_fun,xmz_fun,xvt_fun,xvz_fun,xmp1_fun + $ bvt_fun,bvz_fun,xmt_fun,xmp1_fun REAL(r8), DIMENSION(:,:), POINTER :: rs,zs,eqfunx,eqfuny,eqfuns COMPLEX(r8), DIMENSION(:,:), POINTER :: eulbparmns,lagbparmns, @@ -989,9 +989,6 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) CALL iscdftb(mfac,mpert,bvt_fun,mthsurf,bvt_mn) CALL iscdftb(mfac,mpert,bvz_fun,mthsurf,bvz_mn) CALL iscdftb(mfac,mpert,xmt_fun,mthsurf,xmt_mn) - CALL iscdftb(mfac,mpert,xmz_fun,mthsurf,xmz_mn) - CALL iscdftb(mfac,mpert,xvt_fun,mthsurf,xvt_mn) - CALL iscdftb(mfac,mpert,xvz_fun,mthsurf,xvz_mn) CALL iscdftb(mfac,mpert,xmp1_fun,mthsurf,xmp1_mn) xspmns(istep,:)=xsp_mn @@ -1010,12 +1007,12 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) CALL bicube_eval(rzphi,psifac(istep),theta(itheta),0) jac=rzphi%f(4) - cspl%fs(itheta,1)=xmt_fun(itheta)- - $ (chi1/eqfun%f(1))**2/jac* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)) - cspl%fs(itheta,2)=xmz_fun(itheta)- - $ sq%f(4)*(chi1/eqfun%f(1))**2/jac* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta)) +c Form J*xi_perp directly from xi^psi and xi^alpha=xi^s/chi1. + cspl%fs(itheta,1)=chi1**2* + $ (-eqfun%f(2)*xsp_fun(itheta)+ + $ eqfun%f(3)*xms_fun(itheta)/chi1) + cspl%fs(itheta,2)=sq%f(4)*cspl%fs(itheta,1)- + $ jac*xms_fun(itheta)/chi1 cspl%fs(itheta,3)=xmt_fun(itheta) ENDDO @@ -1060,10 +1057,9 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) $ xsp_fun(itheta)*eqfun%fx(1)+ $ xms_fun(itheta)/(chi1*sq%f(4))*eqfun%fy(1) llagbparfun(istep,itheta)= - $ lagbparfun(istep,itheta)+eqfun%fy(1)* - $ (xmz_fun(itheta)/(jac*sq%f(4))- - $ (chi1/(jac*eqfun%f(1)))**2* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta))) + $ eulbparfun(istep,itheta)+ + $ xsp_fun(itheta)*eqfun%fx(1)+ + $ eqfun%fy(1)*cspl%f(1)/jac divxfun(istep,itheta)=xmp1_fun(itheta)+ $ (jac1/jac)*xsp_fun(itheta)+ @@ -1074,10 +1070,7 @@ SUBROUTINE gpdiag_pmodb(egnum,xspmn) curvfun(istep,itheta)=-xsp_fun(itheta)/eqfun%f(1)*sq%f1(2)- $ xsp_fun(itheta)*eqfun%fx(1)-eqfun%fy(1)* - $ (xms_fun(itheta)/(chi1*sq%f(4))+ - $ xmz_fun(itheta)/(jac*sq%f(4))- - $ (chi1/(jac*eqfun%f(1)))**2* - $ (xvt_fun(itheta)+sq%f(4)*xvz_fun(itheta))) + $ cspl%f(1)/jac curv2fun(istep,itheta)=-jac*eqfun%f(1)*curvfun(istep,itheta) ENDDO diff --git a/gpec/gpout.f b/gpec/gpout.f index 8faae6d03..069ae1936 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -2955,7 +2955,7 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) COMPLEX(r8), DIMENSION(mpert) :: eulbpar_mn,lagbpar_mn, $ divxprp_mn,curv_mn COMPLEX(r8), DIMENSION(0:mthsurf) :: xsp_fun,xms_fun, - $ bvt_fun,bvz_fun,xmz_fun,xmt_fun,xsp1_fun + $ bvt_fun,bvz_fun,xsp1_fun COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: eulbparmns, $ lagbparmns,divxprpmns,curvmns, $ xmp1mns,xspmns,xmsmns,xmtmns,xmzmns @@ -3012,8 +3012,6 @@ SUBROUTINE gpout_pmodb(egnum,xspmn) CALL iscdftb(mfac,mpert,xms_fun,mthsurf,xms_mn) CALL iscdftb(mfac,mpert,bvt_fun,mthsurf,bvt_mn) CALL iscdftb(mfac,mpert,bvz_fun,mthsurf,bvz_mn) - CALL iscdftb(mfac,mpert,xmt_fun,mthsurf,xmt_mn) - CALL iscdftb(mfac,mpert,xmz_fun,mthsurf,xmz_mn) CALL spline_eval(sq,psifac(istep),1) diff --git a/pentrc/inputs.f90 b/pentrc/inputs.f90 index a3a550b10..d764fffdd 100644 --- a/pentrc/inputs.f90 +++ b/pentrc/inputs.f90 @@ -539,7 +539,7 @@ subroutine read_pmodb(file,jac_in,jsurf_in,tmag_in,debug,op_powin) write(out_unit,'(a8,es16.8e3,2/)') " chi1 = ",chi1 write(out_unit,'(1x,a16,a5,8(1x,a16))')"psi_n","m",& 'real(deltaB/B)','imag(deltaB/B)','real(divxprp)','imag(divxprp)',& - 'real(deltaB)','imag(deltaB)','real(Bdivxprp)','imag(Bdivxprp)' + 'real(deltaBprp)','imag(deltaBprp)','real(Bdivxprp)','imag(Bdivxprp)' do i=0,npsi-1 do j=1,mpert write(out_unit,'(1x,es16.8e3,i5,14(1x,es16.8e3))') & From f2c9cefe11fd6a36dd9affa8f1e8b6e6b54886dc Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Thu, 22 Oct 2026 09:43:28 -0400 Subject: [PATCH 285/305] RDCON - ENHANCEMENT - Galerkin LU: free each element matrix once it is assembled The element matrices (cell%mat, 256 mpert^2 bytes per cell) and the global band (gal%mat) were both resident during assembly, and the band was zeroed up front. With LU, each element matrix is now freed right after it is added to the band, and the band is zeroed column by column only as assembly reaches it, so peak memory is about max(element matrices, band) instead of their sum. Cholesky keeps the element matrices, since its LU fallback reassembles from them; the fallback itself then frees them as above. Delta' is bit-identical. The saving needs an allocator that returns freed blocks to the system. glibc keeps freed ~1 MB blocks once its dynamic mmap threshold has risen above them; MALLOC_MMAP_THRESHOLD_=131072 in rdcon's environment keeps them mmapped. Co-Authored-By: Claude Opus 5.5 --- rdcon/gal.f | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/rdcon/gal.f b/rdcon/gal.f index ab1d5f249..8ee1d7e62 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -184,7 +184,7 @@ SUBROUTINE gal_alloc(gal,nx,nq,dx0,dx1,dx2,pfac) $ gal%mat(gal%ldab,gal%ndim),gal%ipiv(gal%ndim)) gal%rhs=0 gal%sol=0 - gal%mat=0 +c mat is zeroed in gal_assemble_mat as assembly reaches each column. c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -211,7 +211,8 @@ SUBROUTINE gal_dealloc(gal) intvl => gal%intvl(ising) DO ix=1,gal%nx cell => intvl%cell(ix) - DEALLOCATE(cell%map,cell%mat) + DEALLOCATE(cell%map) + IF(ALLOCATED(cell%mat))DEALLOCATE(cell%mat) IF(ALLOCATED(cell%emat))DEALLOCATE(cell%emat) IF(ALLOCATED(cell%rhs))DEALLOCATE(cell%rhs) ENDDO @@ -699,7 +700,7 @@ SUBROUTINE gal_assemble_mat(gal) TYPE(gal_type), INTENT(INOUT) :: gal - INTEGER :: ising,ix,ip,ipert,i,jp,jpert,j,offset + INTEGER :: ising,ix,ip,ipert,i,jp,jpert,j,offset,jzero TYPE(interval_type), POINTER :: intvl TYPE(cell_type), POINTER :: cell c----------------------------------------------------------------------- @@ -710,10 +711,18 @@ SUBROUTINE gal_assemble_mat(gal) ELSEIF (solver == "cholesky") THEN offset=1 ENDIF + jzero=0 DO ising=0,msing intvl => gal%intvl(ising) DO ix=1,gal%nx cell => intvl%cell(ix) +c----------------------------------------------------------------------- +c zero mat (allocated untouched) up to this cell's last column, so +c its memory grows as the element matrices are freed. +c----------------------------------------------------------------------- + j=MAX(MAXVAL(cell%map),cell%emap) + gal%mat(:,jzero+1:j)=0 + jzero=MAX(jzero,j) DO ip=0,np DO ipert=1,mpert i=cell%map(ipert,ip) @@ -762,6 +771,11 @@ SUBROUTINE gal_assemble_mat(gal) c----------------------------------------------------------------------- ENDDO ENDDO +c----------------------------------------------------------------------- +c LU never reassembles, so free the element matrix now (cholesky +c keeps it for its LU fallback). +c----------------------------------------------------------------------- + IF (solver == "LU") DEALLOCATE(cell%mat) IF (cell%extra /= "none") THEN IF (cell%etype == "ext1".OR.cell%etype == "ext2") CYCLE j=cell%emap @@ -769,6 +783,7 @@ SUBROUTINE gal_assemble_mat(gal) ENDIF ENDDO ENDDO + gal%mat(:,jzero+1:)=0 c----------------------------------------------------------------------- c terminate. c----------------------------------------------------------------------- @@ -1435,7 +1450,6 @@ SUBROUTINE gal_solve gal%ldab=2*gal%kl+gal%ku+1 DEALLOCATE(gal%mat) ALLOCATE(gal%mat(gal%ldab,gal%ndim)) - gal%mat=0 CALL gal_assemble_mat(gal) ELSE WRITE(*,*)"Calculating Galerkin matrix solution" From 6548faa7c5dd01ced1920123bac046b8bf208abc Mon Sep 17 00:00:00 2001 From: Stuart Benjamin Date: Mon, 19 Oct 2026 16:34:32 -0700 Subject: [PATCH 286/305] EQUIL - BUGFIX - clipspace drops input nodes within round-off of psilow and psihigh grid_type 'original' builds the radial grid as psilow, the input nodes strictly inside, psihigh. An i-file node at psilow + 1e-16 (OFT's axis_pack puts its first node at psi_N = 1e-5 = psilow, and round-off can land it on either side) survived as a ~1e-16-wide first interval; RDCON and STRIDE then never left the axis (ODE steps of 1e-16, then garbage psi), hanging on every recomputed DIII-D i-file tested. Points within 1e-10 of the range of an end are now dropped. Files without such a node give bitwise-identical output. Co-Authored-By: Claude Opus 5.5 --- equil/grid.f90 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/equil/grid.f90 b/equil/grid.f90 index 78cdb35c8..f9677cd5b 100644 --- a/equil/grid.f90 +++ b/equil/grid.f90 @@ -390,13 +390,17 @@ function clipspace(xs,xmin,xmax) !*RETURNS: ! clipspace : real array. ! xmin, the points of xs strictly between xmin and xmax, xmax. + ! Points within round-off (1e-10 of the range) of xmin or xmax + ! are dropped, so no near-zero-width interval is left at an end. !----------------------------------------------------------------------- ! declare function and arguments real(r8), dimension(:), intent(in) :: xs real(r8), intent(in) :: xmin, xmax real(r8), dimension(:), allocatable :: clipspace + real(r8) :: tol - clipspace = (/xmin, pack(xs, xs > xmin .and. xs < xmax), xmax/) + tol = 1e-10_r8*(xmax-xmin) + clipspace = (/xmin, pack(xs, xs > xmin+tol .and. xs < xmax-tol), xmax/) return end function clipspace From c125d36e847eddae893d52834bdb108b9aa04331 Mon Sep 17 00:00:00 2001 From: StuartBenjamin Date: Thu, 22 Oct 2026 22:50:33 -0400 Subject: [PATCH 287/305] STRIDE - ENHANCEMENT - Always solve the Delta' BVP banded; drop the dprime_banded option The banded LU gives bit-identical Delta' to the dense one, so it replaces it: the dprime_banded namelist key, the dense path and the stride.in line go. Each singular surface's two big solution rows sit at the end of its block of 2*m2 rows, the matrix is factored and solved with ZGBTRF/ZGBCON/ZGBTRS (kl = ku = 3*mpert-1), and the residual check uses the sparse entries. The printed condition number now uses the norm of the matrix itself, as ZGBCON expects (the dense path took the norm after factorization). Co-Authored-By: Claude Opus 5.5 --- input/stride.in | 1 - stride/ode.F | 90 ++++++++++++++++++------------------------------- stride/stride.F | 2 +- 3 files changed, 34 insertions(+), 59 deletions(-) diff --git a/input/stride.in b/input/stride.in index fdbb3507f..ff979efd7 100755 --- a/input/stride.in +++ b/input/stride.in @@ -61,7 +61,6 @@ calc_delta_prime=t ! When false, sing_order=2 is better for speed, (with less accuracy). calc_dp_with_vac=t ! When vac_flag=t, this will use vacuum edge conditions to calculate delta prime. big_soln_err_tol=1e-7 ! Threshold error for correct solution to the delta prime bvp. - dprime_banded=f ! Solve the delta prime bvp with a banded LU instead of a dense one: same result, much faster and smaller with many rational surfaces. integrate_riccati=f ! Use Riccati integration for dW. Cannot calculate delta_prime. riccati_bounce=t ! Toggle integration mode to control growth of matrix diff --git a/stride/ode.F b/stride/ode.F index 5e5dca911..111a82357 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -60,7 +60,6 @@ MODULE stride_ode_mod LOGICAL :: calc_delta_prime LOGICAL :: kill_big_soln_for_ideal_dW LOGICAL :: calc_dp_with_vac - LOGICAL :: dprime_banded=.FALSE. TYPE(sing_calculator), DIMENSION(:), TARGET, ALLOCATABLE :: scalc REAL(r8) :: axisPsi, outerPsi @@ -1047,11 +1046,11 @@ SUBROUTINE ode_calc_delta_prime REAL(r8), DIMENSION(:,:), ALLOCATABLE :: atol INTEGER :: nMat, nSp, i, j, k, jsing, ksing, s2, m2 - INTEGER :: iChg, info, ipert0, dRow, rs, rb, kl, ku - REAL(r8) :: rcond, mnorm, ZLANGE, ZLANGB + INTEGER :: iChg, info, ipert0, dRow, kl, ku + REAL(r8) :: rcond, mnorm, ZLANGB INTEGER, DIMENSION(:), ALLOCATABLE :: isp, jsp COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: asp, b, x - COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: M, MInv, TempMat + COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: MInv, TempMat INTEGER, DIMENSION(:), ALLOCATABLE :: ipiv, ipivTemp COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: uwork, cwork, $ cworkTemp @@ -1078,11 +1077,9 @@ SUBROUTINE ode_calc_delta_prime $ s2 * (m2-2) + !each side of each sing $ (msing-1) * m2*m2*2 + !intervals sandwiched b/w sings $ s2 !driven big solution coefficients (2sides * msing) - !rows per singular surface: the banded solve keeps each - !surface's two big solution rows in its block (the dense - !solve puts them last), so kl = ku = 3*mpert-1 - rs = 2*m2-2 - IF (dprime_banded) rs = 2*m2 + !rows come in blocks of 2*m2 per singular surface, each ending + !with that surface's two big solution rows, so the matrix is + !banded with kl = ku = 3*mpert-1 ALLOCATE(b(nMat),x(nMat),isp(nSp),jsp(nSp),asp(nSp)) ALLOCATE(TempMat(m2,m2),ipivTemp(m2),rworkTemp(2*m2), @@ -1114,7 +1111,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,m2 k=k+1 - isp(k) = i + rs * (jsing-1) + isp(k) = i + (2*m2) * (jsing-1) jsp(k) = j + mpert + (2*m2) * (jsing-1) asp(k) = s%uShootL(i,j) ENDDO @@ -1126,7 +1123,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 IF (i/=ipert0 .AND. i/=ipert0+mpert) THEN k=k+1 - isp(k) = i + iChg + m2 + rs * (jsing-1) + isp(k) = i + iChg + m2 + (2*m2) * (jsing-1) jsp(k) = i + mpert + (2*m2) * (jsing-1) asp(k) = 1.0 @@ -1144,7 +1141,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,m2 k=k+1 - isp(k) = i + rs * jsing + isp(k) = i + (2*m2) * jsing jsp(k) = j + mpert + m2 + (2*m2)*(jsing-1) asp(k) = -s%uShootR(i,j) ENDDO @@ -1156,7 +1153,7 @@ SUBROUTINE ode_calc_delta_prime DO i = 1,m2 DO j = 1,mpert k=k+1 - isp(k) = i + rs * msing + isp(k) = i + (2*m2) * msing jsp(k) = j + mpert + (2*m2) * msing asp(k) = uEdge(i,j+mpert) ENDDO @@ -1167,16 +1164,14 @@ SUBROUTINE ode_calc_delta_prime DO jsing = 1,msing s => scalc(jsing) ipert0 = s%sing_col - rb = nMat-s2+2*(jsing-1) - IF (dprime_banded) rb = rs*jsing-2 !left side k=k+1 - isp(k) = rb+1 + isp(k) = 2*m2*jsing-1 jsp(k) = ipert0 + mpert + (2*m2)*(jsing-1) asp(k) = 1.0 !right side k=k+1 - isp(k) = rb+2 + isp(k) = 2*m2*jsing jsp(k) = ipert0 + mpert + m2 + (2*m2)*(jsing-1) asp(k) = 1.0 ENDDO @@ -1189,30 +1184,18 @@ SUBROUTINE ode_calc_delta_prime c----------------------------------------------------------------------- c check condition number of BVP matrix. c----------------------------------------------------------------------- + kl = MAXVAL(isp-jsp) + ku = MAXVAL(jsp-isp) + ALLOCATE(MInv(2*kl+ku+1,nMat)) ALLOCATE(ipiv(nMat),uwork(nMat),cwork(2*nMat),rwork(2*nMat)) - IF (dprime_banded) THEN - kl = MAXVAL(isp-jsp) - ku = MAXVAL(jsp-isp) - ALLOCATE(MInv(2*kl+ku+1,nMat)) - MInv=0.0 - DO k = 1,nSp - MInv(kl+ku+1+isp(k)-jsp(k),jsp(k)) = asp(k) - ENDDO - mnorm = ZLANGB('O',nMat,kl,ku,MInv(kl+1,1),2*kl+ku+1,rwork) - CALL ZGBTRF(nMat,nMat,kl,ku,MInv,2*kl+ku+1,ipiv,info) - CALL ZGBCON('O',nMat,kl,ku,MInv,2*kl+ku+1,ipiv,mnorm,rcond, - $ cwork,rwork,info) - ELSE - ALLOCATE(M(nMat,nMat),MInv(nMat,nMat)) - M=0.0 - DO k = 1,nSp - M(isp(k),jsp(k)) = asp(k) - ENDDO - MInv = M - CALL ZGETRF(nMat,nMat,MInv,nMat,ipiv,info) - mnorm = ZLANGE('O',nMat,nMat,MInv,nMat,rwork) - CALL ZGECON('O',nMat,MInv,nMat,mnorm,rcond,cwork,rwork,info) - ENDIF + MInv=0.0 + DO k = 1,nSp + MInv(kl+ku+1+isp(k)-jsp(k),jsp(k)) = asp(k) + ENDDO + mnorm = ZLANGB('O',nMat,kl,ku,MInv(kl+1,1),2*kl+ku+1,rwork) + CALL ZGBTRF(nMat,nMat,kl,ku,MInv,2*kl+ku+1,ipiv,info) + CALL ZGBCON('O',nMat,kl,ku,MInv,2*kl+ku+1,ipiv,mnorm,rcond, + $ cwork,rwork,info) print *,"Delta' sparse matrix condition number = ",rcond c----------------------------------------------------------------------- c solve BVP matrix without sparse matrix. @@ -1223,19 +1206,16 @@ SUBROUTINE ode_calc_delta_prime b = 0.0 IF (i == 1) THEN !Left side driving term + b(2*m2*jsing-1) = 1.0 dRow = 2*jsing-1 ELSE !Right side driving term + b(2*m2*jsing) = 1.0 dRow = 2*jsing ENDIF - b(isp(nSp-s2+dRow)) = 1.0 !its big solution row x = b - IF (dprime_banded) THEN - CALL ZGBTRS('N',nMat,kl,ku,1,MInv,2*kl+ku+1,ipiv,x, - $ nMat,info) - ELSE - CALL ZGETRS('N',nMat,1,MInv,nMat,ipiv,x,nMat,info) - ENDIF + CALL ZGBTRS('N',nMat,kl,ku,1,MInv,2*kl+ku+1,ipiv,x,nMat, + $ info) IF (info /= 0) THEN print *,"info was not zero! info=",info ENDIF @@ -1252,18 +1232,14 @@ SUBROUTINE ode_calc_delta_prime delta_prime_mat(dRow,2*ksing) = x(k+ipert0+mpert) ENDDO c----------------------------------------------------------------------- -c error-check non-sparse matrix solution. +c error-check the solution with the sparse matrix. c----------------------------------------------------------------------- !Error-check big solution elements by backward solve. - IF (dprime_banded) THEN - b = 0.0 - DO k = 1,nSp - b(isp(k)) = b(isp(k)) + asp(k)*x(jsp(k)) - ENDDO - b = ABS(b) - ELSE - b = ABS(MATMUL(M,x)) - ENDIF + b = 0.0 + DO k = 1,nSp + b(isp(k)) = b(isp(k)) + asp(k)*x(jsp(k)) + ENDDO + b = ABS(b) IF (ABS(1-SUM(ABS(b))) > big_soln_err_tol) THEN print *,"Big sol'n error, surface #",jsing,"side",i print *,"=",ABS(1-SUM(ABS(b))) diff --git a/stride/stride.F b/stride/stride.F index 76bc43e61..585058ce7 100644 --- a/stride/stride.F +++ b/stride/stride.F @@ -55,7 +55,7 @@ PROGRAM stride NAMELIST/stride_params/grid_packing,asymp_at_sing, $ integrate_riccati,calc_delta_prime,calc_dp_with_vac, $ axis_mid_pt_skew, - $ big_soln_err_tol, kill_big_soln_for_ideal_dW, dprime_banded, + $ big_soln_err_tol, kill_big_soln_for_ideal_dW, $ ric_dt,ric_tol,riccati_bounce,verbose_riccati_output, $ riccati_match_hamiltonian_evals,verbose_performance_output, $ fourfit_metric_parallel,vac_parallel,nIntervalsTot,nThreads From 63328e45d12a0b85b0d5a1db95b6119c967d6f5a Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 23 Oct 2026 10:35:37 +0200 Subject: [PATCH 288/305] STRIDE - BUGFIX - Keep naive grid packing within nIntervalsTot --- stride/ode.F | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/stride/ode.F b/stride/ode.F index ca5f214a5..b2b72b05a 100644 --- a/stride/ode.F +++ b/stride/ode.F @@ -477,6 +477,7 @@ SUBROUTINE ode_set_intervals INTEGER :: i, j, jsing, iS, nIntervalsMin INTEGER, PARAMETER :: nAxisMin = 8 INTEGER :: newItvls, cumulItvls, innerItvls, nbad + INTEGER :: minItvls, restItvls REAL(r8) :: s1, s2, sLast, alpha REAL(r8), DIMENSION(:), ALLOCATABLE :: aT LOGICAL, DIMENSION(:), ALLOCATABLE :: aMask @@ -554,12 +555,18 @@ SUBROUTINE ode_set_intervals newItvls = 1 !Inner layer case ELSE innerItvls = msing-NINT((iS-1)/2.0) !Inner layers - IF (iS .LE. nIntervals) THEN - newItvls = MAX(1,NINT((nIntervalsTot-innerItvls- - $ cumulItvls) * (s2-s1)/(sLast-s1))) + IF (iS == 1) THEN + minItvls = nAxisMin + ELSEIF (psiInters(1+cumulItvls,2) == 1) THEN + minItvls = 2 ELSE - newItvls = nIntervalsTot-cumulItvls + minItvls = 1 ENDIF + restItvls = MAX(0,3*innerItvls-1) + newItvls = MAX(minItvls,NINT((nIntervalsTot- + $ innerItvls-cumulItvls) * (s2-s1)/(sLast-s1))) + newItvls = MIN(newItvls, + $ nIntervalsTot-cumulItvls-restItvls) ENDIF IF (newItvls > 1 .AND. aMask(iS)) THEN !Copy data forward in array to avoid overwriting From 0d6b30f54d0cbb18c21218cc26cf653cb0d0d0bb Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 23 Oct 2026 11:31:16 +0200 Subject: [PATCH 289/305] DOCS - MINOR - Document the bounded original grid and its edge resolution limit Co-Authored-By: Claude Opus 5.5 --- dcon/README | 22 ++++++++++++++-------- input/equil.in | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dcon/README b/dcon/README index b15071f57..8fd416e38 100644 --- a/dcon/README +++ b/dcon/README @@ -279,14 +279,20 @@ rho=sqrt(psi), a generalized radius. The second, grid_type="ldp", uses a method suggested by L. Don Pearlstein (LDP), which packs the grid in the neighborhood of the axis and the edge. I have a slight preference for "ldp", but they both work well. A third type, "original", uses the -same radial grid used in the input data. (Sorry, there's no "extra -crispy" option.) The poloidal grid is uniformly distributed in the -straight-fieldline coordinate theta. Throughout the code, cubic spline -and Fourier representations are used to minimize the effects of grid -discreteness. - -The input parameter psilow is used only for grid_type="ldp". A good -value is 1e-4. For the most part, results are insensitive to this +radial grid of the input data between psilow and psihigh: psilow, then +the input surfaces strictly between them, then psihigh; mpsi is set by +the number of these points. Its accuracy is only as good as the input +file's resolution near the edge. An EFIT g-file's grid is uniform in +psi, which is too coarse at the edge for DCON and STRIDE: with +"original", the DIIID_ideal_example reports an unstable n=1 mode that +"ldp" finds stable. Prefer "ldp" unless the input grid is packed toward +the edge. (Sorry, there's no "extra crispy" option.) The poloidal grid +is uniformly distributed in the straight-fieldline coordinate theta. +Throughout the code, cubic spline and Fourier representations are used +to minimize the effects of grid discreteness. + +The input parameter psilow is used by every grid type except "rho". A +good value is 1e-4. For the most part, results are insensitive to this value, but substantially smaller values cause the code to run longer, and substantially larger values may cause accuracy problems. diff --git a/input/equil.in b/input/equil.in index b4bf80214..0a3968c2e 100644 --- a/input/equil.in +++ b/input/equil.in @@ -7,7 +7,7 @@ power_b=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r power_r=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r - grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. + grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. original uses the input file's surfaces between psilow and psihigh, and is too coarse at the edge for uniform-psi g-files. psilow=1e-2 ! Minimum value of psi, normalized from 0 to 1 psihigh=0.994 ! Maximum value of psi, normalized from 0 to 1 (ldp_i with psi bounds: capped at the last file surface) mpsi=128 ! Number of radial grid intervals for equilibrium quantities From ba52126e4252758c7c30feefe6ab471f131609ed Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Fri, 23 Oct 2026 16:26:59 +0200 Subject: [PATCH 290/305] RDCON - ENHANCEMENT - Set the glibc mmap threshold so freed Galerkin element matrices return to the system --- install/RULES.inc | 4 ++++ rdcon/gal.f | 5 +++++ rdcon/gal_mmap.F | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 rdcon/gal_mmap.F diff --git a/install/RULES.inc b/install/RULES.inc index be30ba9be..5cf187176 100644 --- a/install/RULES.inc +++ b/install/RULES.inc @@ -80,6 +80,10 @@ FCDCON = $(FC) $(FFLAGS) $(OMPFLAG) $(RECURSFLAG) $(LEGACYFLAG) -I$(MATHINC) -I$ # RDCON FCRDCON = $(FC) $(FFLAGS) $(OMPFLAG) $(LEGACYFLAG) -I$(MATHINC) -I$(NETCDFINC) -I../equil -I../vacuum +# glibc's mallopt, used by rdcon/gal_mmap.F; macOS has none +ifeq ($(PLATFORM),Linux) +FCRDCON += -DHAVE_MALLOPT +endif ../rdcon/%.o: ../rdcon/%.f $(FCRDCON) $(MODFLAG) ../rdcon -c $< -o $@ ../rdcon/%.o: ../rdcon/%.F diff --git a/rdcon/gal.f b/rdcon/gal.f index 8ee1d7e62..36cc87360 100644 --- a/rdcon/gal.f +++ b/rdcon/gal.f @@ -1427,6 +1427,11 @@ SUBROUTINE gal_solve WRITE(*,*)"Starting Galerkin method" msol_old=msol msol=2*mpert +c----------------------------------------------------------------------- +c let the element matrices freed in gal_assemble_mat go back to the +c system (see gal_mmap.F). +c----------------------------------------------------------------------- + CALL gal_mmap_threshold CALL gal_alloc(gal,nx,nq,dx0,dx1,dx2,pfac) CALL gal_make_arrays(gal) c----------------------------------------------------------------------- diff --git a/rdcon/gal_mmap.F b/rdcon/gal_mmap.F new file mode 100644 index 000000000..ae037528d --- /dev/null +++ b/rdcon/gal_mmap.F @@ -0,0 +1,43 @@ +c----------------------------------------------------------------------- +c file gal_mmap.F. +c lets glibc return the freed Galerkin element matrices to the +c system. +c----------------------------------------------------------------------- +c gal_assemble_mat frees each element matrix once it is in the +c band. glibc raises its mmap threshold as large blocks are freed, +c so later blocks of that size come from the heap, and their memory +c is kept after they are freed. fixing the threshold at 128 kB, +c below the size of an element matrix, keeps each element matrix in +c its own mapping, which is returned to the system when it is +c freed. this is the same as setting MALLOC_MMAP_THRESHOLD_=131072. +c the build defines HAVE_MALLOPT on Linux. macOS has no mallopt and +c already returns such blocks, so there this does nothing. +c----------------------------------------------------------------------- +c----------------------------------------------------------------------- +c declarations. +c----------------------------------------------------------------------- + SUBROUTINE gal_mmap_threshold +#ifdef HAVE_MALLOPT + USE, INTRINSIC :: iso_c_binding, ONLY: c_int + IMPLICIT NONE + + INTERFACE + FUNCTION mallopt(param,value) BIND(C,name="mallopt") + IMPORT :: c_int + INTEGER(c_int), VALUE :: param,value + INTEGER(c_int) :: mallopt + END FUNCTION mallopt + END INTERFACE + + INTEGER(c_int), PARAMETER :: m_mmap_threshold=-3 + INTEGER(c_int) :: info +c----------------------------------------------------------------------- +c set the threshold. +c----------------------------------------------------------------------- + info=mallopt(m_mmap_threshold,131072_c_int) +#endif +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gal_mmap_threshold From a5081105bea6cb5f4383dae48f8548e3c9c2d253 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 23 Oct 2026 11:01:12 -0400 Subject: [PATCH 291/305] SLAYER - MINOR - American spelling in screen output, NetCDF long_names and template comments Co-Authored-By: Claude Opus 5.5 --- docs/examples/DIIID_ideal_example/slayer.in | 2 +- input/slayer.in | 2 +- slayer/slayer.f | 8 ++++---- slayer/slayer_netcdf.f | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/slayer.in b/docs/examples/DIIID_ideal_example/slayer.in index 74d6a001f..ea0424c63 100644 --- a/docs/examples/DIIID_ideal_example/slayer.in +++ b/docs/examples/DIIID_ideal_example/slayer.in @@ -50,7 +50,7 @@ Q_num=100 ! number of points per axis of the initial Re(Q),Im(Q) scan grid scan_width=1.5 ! half-width of the Re(Q),Im(Q) scan window, in normalized Q AMR_passes=4 ! number of adaptive refinement passes of the scan grid - !scan_center=0.0 ! Re(Q) centre of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan + !scan_center=0.0 ! Re(Q) center of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan msing_max=3 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" read_eq=t ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) diff --git a/input/slayer.in b/input/slayer.in index ec62d0815..8c420c247 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -50,7 +50,7 @@ Q_num=100 ! number of points per axis of the initial Re(Q),Im(Q) scan grid scan_width=1.5 ! half-width of the Re(Q),Im(Q) scan window, in normalized Q AMR_passes=4 ! number of adaptive refinement passes of the scan grid - !scan_center=0.0 ! Re(Q) centre of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan + !scan_center=0.0 ! Re(Q) center of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan msing_max=2 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" read_eq=f ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) diff --git a/slayer/slayer.f b/slayer/slayer.f index f1abce011..c35f22f70 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -270,7 +270,7 @@ PROGRAM slayer knum = 100 ! 2-D scan axis-2 resolution Q_num = 100 ! stability scan Re(Q) resolution scan_width = 2.0 ! half-width for complex-Q scans - ! Re(Q) centre of the scans. Left at HUGE, it is chosen by the + ! Re(Q) center of the scans. Left at HUGE, it is chosen by the ! code: zero, or the middle of the Doppler-shifted branches in ! a coupled scan with rotation. scan_center = HUGE(scan_center) @@ -831,7 +831,7 @@ PROGRAM slayer END IF IF (scan_center >= HUGE(scan_center)) scan_center = 0.0_r8 WRITE(*,'(A,ES11.4,A,ES11.4)') - $ ' Scan window: Re(Q) centre =',scan_center, + $ ' Scan window: Re(Q) center =',scan_center, $ ', half-width =',scan_width c----------------------------------------------------------------------- @@ -1046,7 +1046,7 @@ PROGRAM slayer CALL dispersion_AMR_v2(n_k,sl_in,msing_max,scan_width, $ Q_num,AMR_passes,coupling_flag,scan_center) - ! Zeros of the coupled determinant. Q is normalised by + ! Zeros of the coupled determinant. Q is normalized by ! the first surface's tau_k. The coupled eigenvalue is ! global, so it is reported through roots_out only and ! the per-surface gamma_sol_arr is left as NaN. @@ -1111,7 +1111,7 @@ PROGRAM slayer DO j = 1, Q_num ! This scan passes g = gamma_hat + i*omega_hat ! directly, so its imaginary axis is the frequency - ! and carries the scan-window centre. + ! and carries the scan-window center. ing_coarse = -scan_width + (i - 1) * ing_step iing_coarse = scan_center - scan_width $ + (j - 1) * ing_step diff --git a/slayer/slayer_netcdf.f b/slayer/slayer_netcdf.f index 99942fb50..e2ce42a98 100644 --- a/slayer/slayer_netcdf.f +++ b/slayer/slayer_netcdf.f @@ -339,7 +339,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ nf90_double, (/qsing_dim, i_dim/), dpp_id) ) CALL sl_check( nf90_put_att(ncid, dpp_id, "long_name", $ "Outer-region Delta_prime diagonal, referenced to unit "// - $ "normalised poloidal flux (as written by STRIDE)") ) + $ "normalized poloidal flux (as written by STRIDE)") ) CALL sl_check( nf90_def_var(ncid, "Delta_prime_rs_rational", $ nf90_double, (/qsing_dim, i_dim/), dprs_id) ) CALL sl_check( nf90_put_att(ncid, dprs_id, "long_name", @@ -412,7 +412,7 @@ SUBROUTINE slayer_netcdf_out(msing, m_AMR, est_gamma_flag, $ (/root_dim, nAMR_dim, i_dim/), qpole_id) ) CALL sl_check( nf90_put_att(ncid, qpole_id, "long_name", $ "Poles of the dispersion function, (omega, gamma)*"// - $ "tau_k, AMR cell centres") ) + $ "tau_k, AMR cell centers") ) CALL sl_check( nf90_def_var(ncid, "gamma_roots", $ NF90_DOUBLE, (/root_dim, nAMR_dim/), groot_id) ) CALL sl_check( nf90_put_att(ncid, groot_id, "units", "1/s") ) From ac23400023a7f066d8f507543df20471b014a59c Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 23 Oct 2026 11:06:49 -0400 Subject: [PATCH 292/305] DOCS - MINOR - Refresh DIII-D SLAYER sample output after the develop equilibrium fixes Co-Authored-By: Claude Opus 5.5 --- docs/examples/DIIID_ideal_example/README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/README b/docs/examples/DIIID_ideal_example/README index f699a00f0..c2af43b10 100644 --- a/docs/examples/DIIID_ideal_example/README +++ b/docs/examples/DIIID_ideal_example/README @@ -28,10 +28,10 @@ STRIDE also writes ``stride_output_n1.nc``, which holds the outer-region tearing slayer Calculating growth rate on m = 2 rational surface: - Delta_prime (r_s ref.): 5.93 ... + Delta_prime (r_s ref.): 5.94 ... Delta_crit: 33.3 Dispersion roots found: 1, poles: 1 - root 1: Q= -1.162346E+00 -4.163883E-02 omega[rad/s]= -1.059332E+04 gamma[1/s]= -3.794858E+02 polished=T + root 1: Q= -1.162293E+00 -4.163214E-02 omega[rad/s]= -1.059284E+04 gamma[1/s]= -3.794248E+02 polished=T Delta' is converted from STRIDE's normalized-flux reference length to the minor-radius reference that the layer quantities use before matching. With the default ``dc_type="rfitzp"`` critical Delta, the 2/1, 3/1 and 4/1 tearing modes are all damped here, because Delta_crit exceeds Delta' on each surface. Setting ``dc_type="none"`` drops this stabilization and leaves the 2/1 weakly unstable (gamma of about 80 /s). Each root is reported in its surface's ExB frame. The results, including every root and pole found in the scan, are written to ``slayer_output_n1.nc``. From cae745e046f19b63df44bec7327f0cabd9ae3676 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 23 Oct 2026 22:19:01 +0200 Subject: [PATCH 293/305] GPEC - BUG FIX - Preserve the field-line integration failure diagnostic Fork-PR: https://github.com/krystophny/GPEC/pull/1 Cherry-picked-from: 3df0b5c8c3c200f99806b1f605432083e6828498, bb3f02a40ddef6fc481b118eb3e7d0d7aef972ea (cherry picked from commit c1d9fa634ca54776c77e182897570438b9494353) --- equil/direct.f | 4 +-- regression/test/test_direct_error_message.py | 37 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 regression/test/test_direct_error_message.py diff --git a/equil/direct.f b/equil/direct.f index c81c4aef6..fde78a54a 100644 --- a/equil/direct.f +++ b/equil/direct.f @@ -433,7 +433,7 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) REAL(r8), INTENT(IN), OPTIONAL :: y4tot REAL(r8), DIMENSION(0:,0:), INTENT(OUT), OPTIONAL :: ys - CHARACTER(64) :: message + CHARACTER(128) :: message INTEGER, PARAMETER :: neq=4,liw=30,lrw=22+neq*16 INTEGER :: iopt,istate,itask,itol,jac,mf,ir,js @@ -450,7 +450,7 @@ SUBROUTINE direct_fl_int(ipsi,y_out,bf,y4tot,ys) 20 FORMAT(/2x,"is",5x,"eta",8x,"deta",8x,"s",9x,"rfac",8x,"r",10x, $ "z",9x,"psi",8x,"err"/) 30 FORMAT(i4,1p,8e11.3) - 40 FORMAT(a,i4,a,es10.3,a,i3) + 40 FORMAT(a,i0,a,es10.3,a,i0) c----------------------------------------------------------------------- c find flux surface. c----------------------------------------------------------------------- diff --git a/regression/test/test_direct_error_message.py b/regression/test/test_direct_error_message.py new file mode 100644 index 000000000..41e9c19e5 --- /dev/null +++ b/regression/test/test_direct_error_message.py @@ -0,0 +1,37 @@ +"""Execute the production field-line failure formatter without an MHD solve.""" +import os +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + + +class DirectErrorMessage(unittest.TestCase): + @unittest.skipUnless(shutil.which('gfortran'), 'gfortran is required') + def test_step_limit_preserves_complete_diagnostic(self): + root=Path(__file__).resolve().parents[2] + source=(root/'equil/direct.f').read_text() + routine=source.split(' SUBROUTINE direct_fl_int(',1)[1].split( + ' END SUBROUTINE direct_fl_int',1)[0] + declaration=re.search(r'(?m)^ CHARACTER[^\n]*:: message',routine)[0] + format_line=re.search(r'(?m)^ 40 FORMAT[^\n]*',routine)[0] + write=re.search(r'(?m)^ WRITE\(message,40\)[^\n]*\n \$[^\n]*',routine)[0] + # Compile the actual declaration/FORMAT/WRITE from direct_fl_int. The + # small driver reaches the error path deterministically with native I/O. + program='\n'.join([' PROGRAM diagnostic',' IMPLICIT NONE',declaration, + ' INTEGER :: enstep=10000, ipsi=512',' REAL(8) :: eta=1.0D0', + format_line,write," WRITE(*,'(a)') TRIM(message)",' END','']) + scratch=Path(os.environ.get('TMPDIR',root/'build-test'));scratch.mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(dir=scratch) as directory: + path=Path(directory);(path/'diagnostic.f').write_text(program) + subprocess.run(['gfortran','-ffixed-line-length-none',str(path/'diagnostic.f'), + '-o',str(path/'diagnostic')],check=True,capture_output=True) + result=subprocess.run([str(path/'diagnostic')],capture_output=True,text=True) + self.assertEqual(result.returncode,0,result.stderr) + self.assertIn('direct_int: istep = enstep = 10000',result.stdout) + self.assertIn(' at eta = 1.000E+00, ipsi = 512',result.stdout) + + +if __name__=='__main__':unittest.main() From 1abfd57fde112b543ee28e605c2f46c2361d5467 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 00:59:27 -0400 Subject: [PATCH 294/305] DOCS - CLEANUP - Bring the pmodb gauge page in line with develop #340-#343 merged after this page was written. Describe and prove the current xi_perp construction from xi^psi and xi_s (G4.1, G4.2, G4.4, G5.6), state that both PENTRC input paths now use the xi_perp pair, and reduce the regularisation section to what the output columns use (R3, R8). Replace the list of fixed defects with a short history, and drop material that depends on files outside this repository (check_output.py, the TC24 attribution, rmp_torque and FortSym issue references). derive.py falls back to SymPy when FortSym is absent; the verdicts were regenerated with SymPy 1.9 (57 proved, G6 open, all negative controls nonzero at exact rational witnesses). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/derivations/pmodb_gauge/check_output.py | 120 -------- docs/derivations/pmodb_gauge/derive.py | 56 +++- .../pmodb_gauge/fortsym_verdicts.json | 160 +++++------ docs/derivations/pmodb_gauge/identities.py | 70 +++-- docs/pmodb_gauge.rst | 266 +++++++----------- 5 files changed, 242 insertions(+), 430 deletions(-) delete mode 100644 docs/derivations/pmodb_gauge/check_output.py diff --git a/docs/derivations/pmodb_gauge/check_output.py b/docs/derivations/pmodb_gauge/check_output.py deleted file mode 100644 index c4c0f7843..000000000 --- a/docs/derivations/pmodb_gauge/check_output.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Numeric oracle for the pmodb gauge identities on the TC24 n=3 GPEC run. - -Usage: PYTHONPATH=$RMP_TORQUE/src python3 check_output.py RUN_DIR - -Requires numpy, scipy, and the gpec_ascii readers from the referenced -rmp_torque Boozer-gauge-total branch. Numerical evidence only; see derive.py -for the native FortSym proofs. -""" -import sys -from pathlib import Path - -import numpy as np -from scipy.interpolate import CubicSpline -from rmp_torque.gpec_ascii import (read_pmodb, read_xclebsch, read_equilibrium_grid, - reconstruct_advective_spectrum) - -R = Path(sys.argv[1]) -MU0 = 4e-7 * np.pi - - -def table(path, ncol): - rows = [] - for line in open(path): - f = line.split() - if len(f) != ncol: - continue - try: - rows.append([float(x) for x in f]) - except ValueError: - pass - return np.array(rows) - - -d = table(R / "gpec_diagnostics_pmodb_n3.out", 16) -psi = np.unique(d[:, 0]); ms = np.unique(d[:, 1]).astype(int) -shape = (psi.size, ms.size) -c = lambda k: (d[:, k] + 1j * d[:, k + 1]).reshape(shape) -lagb, llagb, lll, bdiv, bk = c(2), c(4), c(6), c(8), c(12) -sp = read_pmodb(R / "gpec_pmodb_n3.out") -xc = read_xclebsch(R / "gpec_xclebsch_n3.out") -eq = read_equilibrium_grid(R / "idcon_equil.out") -q = np.interp(psi, eq.psi_poloidal, eq.q) -nres = 3 * q -far = np.abs(nres - np.round(nres)) > 0.15 -rel = lambda a, b: np.linalg.norm(a - b, axis=1) / np.linalg.norm(b, axis=1) -bands = [(0.0, 0.8), (0.8, 0.9), (0.9, 0.98)] - - -def report(name, a, b): - r = rel(a, b) - parts = [] - for lo, hi in bands: - sel = (psi >= lo) & (psi < hi) - parts.append(f"{lo:.1f}-{hi:.2f}: med {np.median(r[sel]):.4f} " - f"(far {np.median(r[sel & far]):.4f})") - print(f"{name:34s}", " | ".join(parts)) - - -print("diag grid", shape, "pmodb grid", sp.lagrangian.shape) -assert np.allclose(sp.psi_poloidal, psi) -report("pmodb lagb vs -(Bdiv+Bk) [cols]", sp.lagrangian, - -(sp.b_div_xi_perp + sp.b_kappa_xi_perp)) -report("diag lagb vs pmodb lagb", lagb, sp.lagrangian) -report("diag lagb vs lllagb=-(Bdiv+Bk)", lagb, lll) -report("diag llagb vs lllagb", llagb, lll) -report("gauge term (lagb-llagb) / llagb", lagb - llagb + llagb, llagb) - -# Finding (ii): the pressure-coefficient fit. -fl = [] -sec = False -for line in open(R / "idcon_equil.out"): - if "Flux functions:" in line: - sec = True - continue - f = line.split() - if sec and len(f) == 6: - try: - fl.append([float(x) for x in f]) - except ValueError: - pass -fl = np.array(fl) -dp = CubicSpline(fl[:, 0], fl[:, 2]).derivative()(xc.psi_poloidal) -th = eq.theta_radians -ph = np.exp(1j * np.outer(xc.modes, th)) -bs = CubicSpline(eq.psi_poloidal, eq.b_magnitude, axis=0)(xc.psi_poloidal) - - -def spec(x): - r = np.fft.fft((x @ ph) / bs, axis=1) / len(th) - return np.stack([r[:, int(m) % len(th)] for m in xc.modes], axis=1) - - -adv = reconstruct_advective_spectrum(xc, eq) # xi_g . grad B (xi^zeta = 0) -A1 = spec(adv) -A2_wrong = spec(MU0 * xc.xi_psi * dp[:, None]) # rmp_torque #56 fit: missing 1/B -_r = np.fft.fft(MU0 * dp[:, None] * (xc.xi_psi @ ph) / bs**2, axis=1) / len(th) -A2 = np.stack([_r[:, int(m) % len(th)] for m in xc.modes], axis=1) # (mu0 p' xi^psi/B)/B -A3 = spec(llagb - lagb) # (xi_perp^zeta/q) d_theta B -y = spec(sp.b_kappa_xi_perp) -sel = far[:, None] & (np.abs(xc.modes) <= 12)[None, :] -for name, cols in (("adv, p/B [#56]", (A1, A2_wrong)), ("adv, p", (A1, A2)), ("adv, p, gauge", (A1, A2, A3))): - M = np.stack([a[sel] for a in cols], axis=1) - coef, *_ = np.linalg.lstsq(M, y[sel], rcond=None) - res = np.linalg.norm(M @ coef - y[sel]) / np.linalg.norm(y[sel]) - print(f"fit Bkxprp ~ {name:14s}: coef", np.round(coef, 4), f"resid {res:.4f}") -M = A1 + A2 + A3 -print("fixed coefficients (1,1,1): resid %.4f" % ( - np.linalg.norm(M[sel] - y[sel]) / np.linalg.norm(y[sel]))) - -print("=== consistency of the regressors (far surfaces, |m|<=12)") -eul = sp.eulerian -def r2(a, b): - return np.linalg.norm(a[sel] - b[sel]) / np.linalg.norm(b[sel]) -print("adv (xclebsch) vs lagb-eulb %.4f" % r2(A1, spec(sp.lagrangian - eul))) -print("p-term vs Bk-(llagb-eulb) %.4f" % r2(A2, spec(sp.b_kappa_xi_perp - llagb + eul))) -print("Bk vs (llagb-eulb)+p-term %.4f" % r2(y, spec(llagb - eul) + A2)) - -T = spec(sp.b_kappa_xi_perp - llagb + eul) -scale = np.vdot(A2[sel], T[sel]) / np.vdot(A2[sel], A2[sel]) -print("pressure coefficient against corrected regressor:", scale) diff --git a/docs/derivations/pmodb_gauge/derive.py b/docs/derivations/pmodb_gauge/derive.py index d6f90cc3b..abf835099 100644 --- a/docs/derivations/pmodb_gauge/derive.py +++ b/docs/derivations/pmodb_gauge/derive.py @@ -1,18 +1,21 @@ -"""Run the pmodb gauge identities through FortSym and record the verdicts. +"""Run the pmodb gauge identities through a CAS and record the verdicts. -Usage (FortSym checkout with a built C ABI library):: +Usage:: - PYTHONPATH=$FORTSYM/python python3 docs/derivations/pmodb_gauge/derive.py + python3 docs/derivations/pmodb_gauge/derive.py -Writes ``fortsym_verdicts.json`` beside this file. ``PROVED`` means FortSym's -native zero decision returned true; ``UNKNOWN`` and ``NONZERO`` fail an -identity and are the required outcome for a negative control. No numerical -probe is accepted as evidence. +FortSym (a checkout with a built C ABI library on ``PYTHONPATH``) is used +when it is importable; otherwise plain SymPy is used. Writes +``fortsym_verdicts.json`` beside this file, with the engine recorded. +``PROVED`` means the zero test returned true; ``UNKNOWN`` and ``NONZERO`` +fail an identity and are the required outcome for a negative control. No +numerical probe is accepted as evidence. """ from __future__ import annotations import json +import signal import subprocess import sys import time @@ -21,18 +24,43 @@ HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) -import fortsym # noqa: E402 -import fortsym.sympy as fs # noqa: E402 +try: + import fortsym # noqa: E402 + import fortsym.sympy as fs # noqa: E402 +except ImportError: + fortsym = None + import sympy as fs # noqa: E402 import identities # noqa: E402 +TIMEOUT = 600 # seconds per SymPy zero test + + +class _Timeout(Exception): + pass + + +def _alarm(*_): + raise _Timeout + def verdict(expr) -> str: - value = fs.sympify(expr).is_zero + if fortsym is not None: + value = fs.sympify(expr).is_zero + else: + signal.signal(signal.SIGALRM, _alarm) + signal.alarm(TIMEOUT) + try: + e = fs.expand(expr) + value = True if e == 0 else fs.simplify(fs.together(e)) == 0 or None + except _Timeout: + return "UNKNOWN" + finally: + signal.alarm(0) return {True: "PROVED", False: "NONZERO"}.get(value, "UNKNOWN") def counterexample(expr): - """Exact rational point at which FortSym decides the residual is non-zero.""" + """Exact rational point at which the engine decides the residual is non-zero.""" symbols = sorted(expr.free_symbols, key=str) for attempt in range(1, 6): point, value = {}, expr @@ -46,6 +74,8 @@ def counterexample(expr): def revision() -> str: + if fortsym is None: + return "sympy " + fs.__version__ root = Path(fortsym.__file__).resolve().parents[2] try: return subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], @@ -80,7 +110,9 @@ def main() -> int: failed.append(label) print(f"{label:6s} {v:8s} (negative control" f"{', witness ' + str(witness) if witness else ''}) {equation}", flush=True) - out = {"engine": "fortsym native zero test via C ABI (fortsym.sympy)", + engine = ("fortsym native zero test via C ABI (fortsym.sympy)" if fortsym + else "sympy expand/simplify zero test") + out = {"engine": engine, "fortsym_revision": revision(), "checks": records, "failed": failed} (HERE / "fortsym_verdicts.json").write_text(json.dumps(out, indent=2) + "\n") print("FAILED: " + " ".join(failed) if failed else "required identities proved; see explicit open-gap records") diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json index b271512dc..6c74f984d 100644 --- a/docs/derivations/pmodb_gauge/fortsym_verdicts.json +++ b/docs/derivations/pmodb_gauge/fortsym_verdicts.json @@ -1,42 +1,42 @@ { - "engine": "fortsym native zero test via C ABI (fortsym.sympy)", - "fortsym_revision": "ae635c80b42c922f5f2028c5cec67859bb8d3929", + "engine": "sympy expand/simplify zero test", + "fortsym_revision": "sympy 1.9", "checks": [ { "label": "G1.1", "equation": "J dB^psi = GPEC bwp (pointwise)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.002 }, { "label": "G1.2", "equation": "J dB^theta = GPEC bwt (pointwise)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.003 }, { "label": "G1.3", "equation": "J dB^zeta = GPEC bwz (pointwise)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.006 }, { "label": "G1.4", "equation": "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.001 }, { "label": "G1.5", "equation": "xi_s(xi + lam B) = xi_s(xi)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.001 }, { "label": "G1F.1", "equation": "bwp = chi1 (m - n q) 2 pi i xsp", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.002 }, { "label": "G1F.2", @@ -54,61 +54,61 @@ "label": "G2.1", "equation": "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.001 }, { "label": "G2.2", "equation": "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1 + q J div xi", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.005 }, { "label": "G2.3", "equation": "q xwt - xwz = J xi_s / chi1", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.001 }, { "label": "G3", "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", "verdict": "PROVED", - "seconds": 0.009 + "seconds": 0.052 }, { "label": "G4.1", - "equation": "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", + "equation": "J xi_perp^theta = chi1^2 (-f2 xsp + f3 xms/chi1) (cspl 1)", "verdict": "PROVED", - "seconds": 0.003 + "seconds": 0.469 }, { "label": "G4.2", - "equation": "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", + "equation": "J xi_perp^zeta = q J xi_perp^theta - J xms/chi1 (cspl 2)", "verdict": "PROVED", - "seconds": 0.002 + "seconds": 0.307 }, { "label": "G4.3", "equation": "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.055 }, { "label": "G4.4", - "equation": "xi_perp^theta = xms/(chi1 q) + xmz/(J q) - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", + "equation": "xsp d_psi B + (cspl 1/J) d_theta B = xi_perp.grad B (curvfun)", "verdict": "PROVED", - "seconds": 0.004 + "seconds": 0.325 }, { "label": "G5.1", "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", "verdict": "PROVED", - "seconds": 0.006 + "seconds": 0.0 }, { "label": "G5.2", "equation": "xi_g^theta = xi_s/(chi1 q)", "verdict": "PROVED", - "seconds": 0.007 + "seconds": 0.032 }, { "label": "G5.2b", @@ -120,115 +120,97 @@ "label": "G5.3", "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", "verdict": "PROVED", - "seconds": 0.017 + "seconds": 0.037 }, { "label": "G5.4", "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", "verdict": "PROVED", - "seconds": 0.012 + "seconds": 0.005 }, { "label": "G5.5", "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", "verdict": "PROVED", - "seconds": 0.008 + "seconds": 0.016 }, { "label": "G5.6", "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", "verdict": "PROVED", - "seconds": 0.008 + "seconds": 0.308 }, { "label": "G5.7", "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", "verdict": "PROVED", - "seconds": 0.009 + "seconds": 0.002 }, { "label": "G5.8", "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", "verdict": "PROVED", - "seconds": 0.005 + "seconds": 0.362 }, { "label": "G7.1", "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_psi", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.013 }, { "label": "G7.2", "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_theta", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.013 }, { "label": "G7.3", "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_zeta", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.009 }, { "label": "C1", "equation": "b.curl(xi_perp x B) + xi_perp.grad B = -B(div xi_perp + kappa.xi_perp)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.002 }, { "label": "C2", "equation": "B kappa.xi_perp = xi_perp.grad B + xi_perp.((curl B) x B)/B", "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "R1", - "equation": "J D_effective(reg) = (1-R)(J R xsp1 + J' xsp)", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "R1a", - "equation": "J div xi_reconstructed = J(1-R**2)xsp1 + (1-R)J' xsp", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "R4", - "equation": "chi1(q xi_reconstructed^theta-xi_reconstructed^zeta)=R xms", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "R5", - "equation": "llagb_code - (eulb+xi_rec_perp.grad B) = (1-R) xms Bt/(chi1 q)", - "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.004 }, { "label": "R2", "equation": "J div xi (R = 1) = 0", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.028 }, { "label": "R3", "equation": "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.005 }, { "label": "R6", "equation": "H_parallel = B(X1-U)+(B_theta Delta_zeta-B_zeta Delta_theta)/(J B)", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.01 }, { "label": "R7", "equation": "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", "verdict": "PROVED", - "seconds": 0.001 + "seconds": 0.002 + }, + { + "label": "R8", + "equation": "with V = U and Delta = 0: lagb+Bdivxprp+Bkxprp = -xi_perp^zeta Bt/q - xi_perp.F/B", + "verdict": "PROVED", + "seconds": 0.003 }, { "label": "E4.1", @@ -240,13 +222,13 @@ "label": "G8.1", "equation": "J B**2 div xi_perp = xmat Xp + ymat X + zmat A", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.008 }, { "label": "G8.2", "equation": "idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.006 }, { "label": "Q1.hamada", @@ -270,7 +252,7 @@ "label": "Q1.boozer", "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.006 }, { "label": "Q2.boozer", @@ -288,7 +270,7 @@ "label": "Q4", "equation": "det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.001 }, { "label": "Q5", @@ -312,13 +294,13 @@ "label": "Q8", "equation": "delta(J/R**2)/(J/R**2)=div xi-2 xi_R/R", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.002 }, { "label": "K5", "equation": "Complex CGL pressure work equals [5/3 |D|**2+1/3 |D+3K|**2]/2", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.007 }, { "label": "P1", @@ -330,19 +312,13 @@ "label": "K1", "equation": "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.004 }, { "label": "K3", "equation": "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "K4", - "equation": "pmodb_file-path drive change = -mu B lam dB/dl / v", - "verdict": "PROVED", - "seconds": 0.0 + "seconds": 0.008 }, { "label": "G6", @@ -356,19 +332,18 @@ "verdict": "UNKNOWN", "negative_control": true, "counterexample": { - "Bm_psi": "8/5", - "Bm_theta": "11/6", - "J_0": "14/7", - "chi1": "17/8", - "g_11_0": "20/9", - "g_12_0": "23/10", - "g_13_0": "26/11", - "g_22_0": "29/12", - "g_23_0": "32/13", - "q_0": "35/14", - "xp_0": "38/15", - "xt_0": "41/16", - "xz_0": "44/17" + "Bm_theta": "8/5", + "J_0": "11/6", + "chi1": "14/7", + "g_11_0": "17/8", + "g_12_0": "20/9", + "g_13_0": "23/10", + "g_22_0": "26/11", + "g_23_0": "29/12", + "q_0": "32/13", + "xp_0": "35/14", + "xt_0": "38/15", + "xz_0": "41/16" } }, { @@ -377,13 +352,10 @@ "verdict": "UNKNOWN", "negative_control": true, "counterexample": { - "Bm_psi": "8/5", - "Bm_theta": "11/6", - "chi1": "14/7", - "q_0": "17/8", - "xp_0": "20/9", - "xt_0": "23/10", - "xz_0": "26/11" + "Bm_theta": "8/5", + "q_0": "11/6", + "xt_0": "14/7", + "xz_0": "17/8" } }, { diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py index e268dee93..e8a31a9b3 100644 --- a/docs/derivations/pmodb_gauge/identities.py +++ b/docs/derivations/pmodb_gauge/identities.py @@ -221,17 +221,23 @@ def columns(sp): dBcov = S.lower(dB) B2 = S.dot(S.lower(B), B) xs = S.xi_s(xi) - xv = S.lower(xi) # GPEC xvt, xvz (covariant) - xmt, xmz = J * xi[1], J * xi[2] # GPEC xmt, xmz (J xi^i) - sigma = S.dot(xv, B) / B2 + g = S.g + xmz = J * xi[2] # GPEC xmz (J xi^zeta) + sigma = S.dot(S.lower(xi), B) / B2 xperp = [xi[i] - sigma * B[i] for i in range(3)] + # gpout_pmodb/gpdiag_pmodb cspl: J xi_perp from xi^psi and xi_s with the + # eqfun coefficients f2, f3 (equil/direct.f, covariant metric). + f2 = (g[0][1] + q * g[0][2]) / (J * B2) + f3 = (g[1][2] + q * g[2][2]) / (J * B2) + jxt = chi1 ** 2 * (-f2 * xi[0] + f3 * xs / chi1) + jxz = q * jxt - J * xs / chi1 out = [] out.append(("G3", "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", S.at0(S.dot(dBcov, B) - chi1 * (dBcov[1] + q * dBcov[2]) / J))) - out.append(("G4.1", "J xi_perp^theta = xmt - (chi1/B)^2 (xvt + q xvz)/J", - S.at0(J * xperp[1] - (xmt - chi1 ** 2 / B2 * (xv[1] + q * xv[2]) / J)))) - out.append(("G4.2", "J xi_perp^zeta = xmz - q (chi1/B)^2 (xvt + q xvz)/J", - S.at0(J * xperp[2] - (xmz - q * chi1 ** 2 / B2 * (xv[1] + q * xv[2]) / J)))) + out.append(("G4.1", "J xi_perp^theta = chi1^2 (-f2 xsp + f3 xms/chi1) (cspl 1)", + S.at0(J * xperp[1] - jxt))) + out.append(("G4.2", "J xi_perp^zeta = q J xi_perp^theta - J xms/chi1 (cspl 2)", + S.at0(J * xperp[2] - jxz))) # B . xi_perp = 0 as a statement about arbitrary vectors: the metric-jet # form is larger than necessary, so the # projection is proved on independent covariant B_i, contravariant B^i, xi^i. @@ -241,10 +247,9 @@ def columns(sp): sig = sum(Bl[i] * xu[i] for i in range(3)) / sum(Bl[i] * Bu[i] for i in range(3)) out.append(("G4.3", "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", sum(Bl[i] * (xu[i] - sig * Bu[i]) for i in range(3)))) - coef = xs / (chi1 * q) + xmz / (J * q) - chi1 ** 2 / (J ** 2 * B2) * (xv[1] + q * xv[2]) - out.append(("G4.4", "xi_perp^theta = xms/(chi1 q) + xmz/(J q)" - " - (chi1/(J B))^2 (xvt + q xvz) (curvfun, gpout.f)", - S.at0(xperp[1] - coef))) + out.append(("G4.4", "xsp d_psi B + (cspl 1/J) d_theta B = xi_perp.grad B (curvfun)", + S.at0(xi[0] * S.d(Bm, 0) + jxt / J * S.d(Bm, 1) + - sum(xperp[i] * S.d(Bm, i) for i in range(3))))) # Gauge of lagb: xi_g = xi_perp + lam B with xi_g^zeta = 0. lam = -xperp[2] / B[2] xg = [xperp[i] + lam * B[i] for i in range(3)] @@ -260,8 +265,7 @@ def columns(sp): lagb = eul + Bm * (xi[0] * S.d(Bm, 0) + xs / (chi1 * q) * S.d(Bm, 1)) lagb_g = eul + Bm * grad(xg) llagb = eul + Bm * grad(xperp) - llagb_code = lagb + Bm * S.d(Bm, 1) * (xmz / (J * q) - chi1 ** 2 / (J ** 2 * B2) - * (xv[1] + q * xv[2])) + llagb_code = eul + Bm * (xi[0] * S.d(Bm, 0) + jxt / J * S.d(Bm, 1)) out.append(("G5.4", "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", S.at0(lagb - lagb_g))) out.append(("G5.5", "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", @@ -371,12 +375,12 @@ def cartesian_kappa(sp): def regularisation(sp): - """R1-R3: Park-regularised components in GPEC (reg_flag) on one harmonic. + """R2-R3: Park-regularised components in GPEC (reg_flag) on one harmonic. Diagonal model: one harmonic, J independent of theta. R = s^2/(s^2 + reg^2), s = m - n q. gpeq_sol uses xmp1 = R xsp1 in bmt/bmz and xms; gpeq_contra - builds xwt/xwz from xmp1 and multiplies by R again; gpout_pmodb uses - R xsp1 explicitly in divxprp. + builds xwt/xwz from xmp1. The pmodb columns use xmp1 and xms + (gpout_pmodb), so R2-R3 are the only regularisation identities needed. """ I, pi = sp.I, sp.pi m, n, q, chi1 = sp.symbols("m n q chi1") @@ -385,18 +389,7 @@ def regularisation(sp): s = m - n * q xwt = -(J0 * R * X1 + J1 * X + 2 * pi * I * n / chi1 * J0 * Xs) / (2 * pi * I * s) xwz = -(q * J0 * R * X1 + q * J1 * X + 2 * pi * I * m / chi1 * J0 * Xs) / (2 * pi * I * s) - xmt, xmz = R * xwt, R * xwz - jdiv_effective = J0 * R * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz - jdiv_actual = J0 * X1 + J1 * X + 2 * pi * I * m * xmt - 2 * pi * I * n * xmz - out = [("R1", "J D_effective(reg) = (1-R)(J R xsp1 + J' xsp)", - jdiv_effective - (1-R)*(J0*R*X1+J1*X)), - ("R1a", "J div xi_reconstructed = J(1-R**2)xsp1 + (1-R)J' xsp", - jdiv_actual-J0*(1-R**2)*X1-(1-R)*J1*X), - ("R4", "chi1(q xi_reconstructed^theta-xi_reconstructed^zeta)=R xms", - chi1*(q*xmt-xmz)/J0-R*Xs)] - Bt = sp.Symbol("Bt") - out.append(("R5", "llagb_code - (eulb+xi_rec_perp.grad B) = (1-R) xms Bt/(chi1 q)", - (Xs/(chi1*q)+xmz/(J0*q)-xmt/J0)*Bt-(1-R)*Xs*Bt/(chi1*q))) + out = [] jdiv_unreg = (J0 * X1 + J1 * X + 2 * pi * I * m * xwt.subs(R, 1) - 2 * pi * I * n * xwz.subs(R, 1)) out.append(("R2", "J div xi (R = 1) = 0", jdiv_unreg)) @@ -415,6 +408,10 @@ def regularized_column_budget(sp): U is the derivative selected by gpeq_sol; V is the derivative used in gpout divergence. Delta=xi_s-chi1(q xi_rec^theta-xi_rec^zeta). + This is bookkeeping under the stated model relations (BDcode, BKcode), + not a proof that the code satisfies them. In gpout_pmodb, V = U + (xmp1) and xi_perp is built from xi_s itself (G4.2), so Delta = 0 and + R8 gives the budget of the output columns. F denotes xi_rec_perp.((curl B)xB-mu0 grad p)/B, in Tesla. Metric/field-norm consistency and C1/C2 are assumed; finite truncation errors are not folded into the physical force residual. @@ -439,6 +436,8 @@ def regularized_column_budget(sp): budget=-Z*Bt/q+2*g+curl_delta+B*(V-U)-F out.append(("R7", "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", Lagcode+BDcode+BKcode-budget)) + out.append(("R8", "with V = U and Delta = 0: lagb+Bdivxprp+Bkxprp = -xi_perp^zeta Bt/q - xi_perp.F/B", + (Lagcode+BDcode+BKcode).subs({V: U, Delta: 0, Dt: 0, Dz: 0})+Z*Bt/q+F)) return out @@ -460,7 +459,7 @@ def cgl(sp): def drive_gauge(sp): - """K3, K4: gauge (in)variance of the Logan (2013) Eq. 5 bounce drive. + """K3: gauge invariance of the Logan (2013) Eq. 5 bounce drive. Along a field line (arc length l, M = 1): v = sqrt(2(E - mu B)), integrand (1/v)[(2E - 3 mu B) dB/B + (2E - 2 mu B) div xi]. Under xi -> xi + lam B, @@ -474,14 +473,10 @@ def drive_gauge(sp): dBdl, dlam = B.diff(l), lam.diff(l) change = ((2 * E - 3 * mu * B) * lam * dBdl + (2 * E - 2 * mu * B) * B * dlam) / v total = (v * B * lam).diff(l) - # pmodb_file path: dbob = lagb/B (gauge xi_g), divx = -(lagb + Bkxprp)/B. - change_pmodb = ((2 * E - 3 * mu * B) * lam * dBdl - (2 * E - 2 * mu * B) * lam * dBdl) / v # Multiplied by v > 0 (away from turning points): FortSym leaves the # unmultiplied sqrt quotient UNKNOWN. return [("K3", "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", - ((change - total) * v).subs(l, 0)), - ("K4", "pmodb_file-path drive change = -mu B lam dB/dl / v", - (change_pmodb + mu * B * lam * dBdl / v).subs(l, 0))] + ((change - total) * v).subs(l, 0))] def divergence_matrices(sp): @@ -542,7 +537,7 @@ def gauge_coordinates(sp): determinant=(1+iota*ht)*(1+hp)-ht*iota*hp out.append(("Q4","det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", determinant-1-iota*ht-hp)) - # Same physical scalar and relabeling, in the convention of rmp_torque E4.14. + # Same physical scalar and relabeling, composed through Hamada. bX,jX,u,v=sp.symbols("bX jX u v") dbB=bX+u*Bt; dbH=bX+v*Bt out.append(("Q5","Hamada->Boozer equals direct X->Boozer when w_HB=w_XB-w_XH", @@ -617,9 +612,8 @@ def open_gaps(sp): """Identities FortSym does not decide; their content is covered elsewhere. G6 is the coordinate form of C1 + C2 (proved on Cartesian 1-jets) with - det g = J^2 imposed through g_33. On ae635c80 the zero test still returns UNKNOWN for this large rational - residual; the removed variable cap is no longer the explanation; it is recorded, not - counted as proved. + det g = J^2 imposed through g_33. Neither FortSym nor SymPy decides this + large rational residual; it is recorded, not counted as proved. """ return force_balance_identity(sp) diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index 07aef9af0..7ba8fae94 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -5,11 +5,10 @@ Perturbed field strength (pmodb) and gauge ****************************************** This page states what the columns of ``gpec_pmodb_n#.out`` are, how they are -related, and which of them a field-strength consumer needs. Verified identities -are proved with the FortSym native zero test by -``docs/derivations/pmodb_gauge/derive.py``; the verdicts are stored in -``docs/derivations/pmodb_gauge/fortsym_verdicts.json`` and the labels (G1, -G5.5, ...) refer to that file. +related, and which of them a field-strength consumer needs. The identities +are proved symbolically by ``docs/derivations/pmodb_gauge/derive.py``; the +verdicts are stored in ``docs/derivations/pmodb_gauge/fortsym_verdicts.json`` +and the labels (G1, G5.5, ...) refer to that file. .. contents:: On this page :local: @@ -36,10 +35,12 @@ any straight-field-line Jacobian :math:`\mathcal{J}(\psi,\theta)` selected by the in-surface displacement across :math:`\mathbf B` (``xms``; ``gpec_xclebsch`` writes :math:`\xi^\alpha=\xi_s/\chi_1`). A field-aligned -displacement changes neither :math:`\xi^\psi` nor :math:`\xi_s` (G1.5). The -parallel part is fixed in ``gpeq_contra`` by +displacement changes neither :math:`\xi^\psi` nor :math:`\xi_s` (G1.5), so +:math:`(\xi^\psi,\xi_s)` determine :math:`\boldsymbol\xi_\perp` completely. +The parallel part used elsewhere in GPEC is fixed in ``gpeq_contra`` by :math:`\nabla\cdot\boldsymbol\xi=0` (G2), which gives ``xmt`` :math:`=\mathcal J\xi^\theta` and ``xmz`` :math:`=\mathcal J\xi^\zeta`. +The pmodb columns do not use ``xmt`` or ``xmz``. For every ``jac_type`` (G1, G1F) the Jacobian-weighted perturbed field of ``gpeq_sol`` is :math:`\mathcal J\,\nabla\times(\boldsymbol\xi\times\mathbf B)`. @@ -48,10 +49,6 @@ For every ``jac_type`` (G1, G1F) the Jacobian-weighted perturbed field of The pmodb columns ================= -The displacement identifications in this section assume unregularized -consistent components. The literal lagb expression is valid for both modes; -the regularized closure defect is given by R4-R5 below. - ``eulb`` :math:`\delta B_E=\hat{\mathbf b}\cdot\delta\mathbf B`, the Eulerian field strength (G3). @@ -68,9 +65,21 @@ the regularized closure defect is given by R4-R5 below. relabels the harmonics; it does not change this gauge. ``Bdivxprp`` - :math:`B\,\nabla\cdot\boldsymbol\xi_\perp`, with - :math:`\boldsymbol\xi_\perp=\boldsymbol\xi-(\boldsymbol\xi\cdot\mathbf B)\mathbf B/B^2` - built from ``xmt``, ``xmz`` and the covariant ``xvt``, ``xvz`` (G4.1-G4.3). + :math:`B\,\nabla\cdot\boldsymbol\xi_\perp`. ``gpout_pmodb`` forms + :math:`\mathcal J\boldsymbol\xi_\perp` directly from :math:`\xi^\psi` and + :math:`\xi_s` (G4.1, G4.2), + + .. math:: + + \mathcal J\xi_\perp^\theta=\chi_1^2\left(-f_2\,\xi^\psi+f_3\,\xi_s/\chi_1\right), + \qquad + \mathcal J\xi_\perp^\zeta=q\,\mathcal J\xi_\perp^\theta-\mathcal J\xi_s/\chi_1, + + with the equilibrium coefficients + :math:`f_2=(g_{\psi\theta}+qg_{\psi\zeta})/(\mathcal JB^2)` and + :math:`f_3=(g_{\theta\zeta}+qg_{\zeta\zeta})/(\mathcal JB^2)` (``eqfun`` + columns 2 and 3, covariant metric). The same projection gives the + ``xmat``-``zmat`` matrices of ``idcon_build`` (G8). ``Bkxprp`` :math:`\boldsymbol\xi_\perp\cdot\nabla B+\mu_0p'\xi^\psi/B` (G4.4). This is @@ -78,13 +87,14 @@ the regularized closure defect is given by R4-R5 below. equilibrium force balance: exactly, :math:`B\boldsymbol\kappa\cdot\boldsymbol\xi_\perp=\boldsymbol\xi_\perp\cdot\nabla B +\boldsymbol\xi_\perp\cdot[(\nabla\times\mathbf B)\times\mathbf B]/B` (C2). - The PENTRC/GPEC.jl matrices ``smat``, ``tmat`` (``idcon_build``) form the + The PENTRC matrices ``smat``, ``tmat`` (``idcon_build``) form the same quantity times :math:`\mathcal J B^2` from :math:`\xi^\psi` and :math:`\xi^\alpha` (G7). ``llagb`` (``gpec_diagnostics_pmodb``, ``pmodbmn_flag``) :math:`\delta B_L(\boldsymbol\xi_\perp)=\delta B_E+\boldsymbol\xi_\perp\cdot\nabla B` - (G5.6). ``lllagb`` is :math:`-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)`. + (G5.6). ``lllagb`` is :math:`-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)`, + and ``Bkxprp`` :math:`=` ``llagb`` :math:`-` ``eulb`` :math:`+\mu_0p'\xi^\psi/B` (P1). Relations between the columns @@ -99,8 +109,7 @@ Gauge of ``lagb`` (G5.5, G5.8): \xi_\perp^\zeta=-\frac{B_\theta\xi_s/\chi_1+qB_\psi\xi^\psi}{B_\theta+qB_\zeta}. Kinematic identity (C1; any :math:`\boldsymbol\xi_\perp`, no force balance) -and its pmodb form (C2 with G4; the optional direct contraction in the -GPEC chart with :math:`\det g=\mathcal J^2`, G6, returns UNKNOWN on ae635c80 and is recorded as an open gap, not as proved): +and its pmodb form (C2 with G4): .. math:: @@ -109,7 +118,9 @@ GPEC chart with :math:`\det g=\mathcal J^2`, G6, returns UNKNOWN on ae635c80 and =-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})-\boldsymbol\xi_\perp\cdot\mathbf F/B, \qquad \mathbf F=(\nabla\times\mathbf B)\times\mathbf B-\mu_0\nabla p. -Hence +C1 and C2 are proved on Cartesian 1-jets. Their direct contraction in the +GPEC chart with :math:`\det g=\mathcal J^2` (G6) is too large for either CAS +to decide and is recorded as an open gap, not as proved. Hence .. math:: @@ -118,112 +129,64 @@ Hence ``lagb`` :math:`=-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)` is therefore not an identity. The first term is a gauge term of first order in -the perturbation. Its size depends on :math:`B_\theta/(qB_\zeta)`, the -Boozer :math:`B_\psi` and :math:`\partial_\theta B` and the displacement spectrum; growth toward the edge is a hypothesis -for a particular equilibrium, not a general bound. The second term vanishes for an exact equilibrium. - -Regularisation (``reg_flag``, Park et al. 2009 Eq. 10). ``gpeq_sol`` uses -:math:`R\,\partial_\psi\xi^\psi`, :math:`R=s^2/(s^2+\sigma^2)`, -:math:`s=m-nq`, in the field and in ``xms``; ``gpeq_contra`` multiplies -``xwt``, ``xwz`` by :math:`R` again. On one harmonic with -:math:`\theta`-independent :math:`\mathcal J` (R1-R3): - -.. math:: - - \mathcal J D_{effective}=(1-R)(\mathcal J R\,\partial_\psi\xi^\psi+\mathcal J'\xi^\psi),\qquad - \mathcal J\nabla\cdot\delta\mathbf B=2\pi i\chi_1 s(1-R)\,\partial_\psi\xi^\psi. - -Here the displayed R1 formula is the *effective* divergence diagnostic, -using :math:`R\partial_\psi\xi^\psi` for its radial derivative. The actual -divergence of the reconstructed displacement is instead (R1a) +the perturbation; its size depends on :math:`B_\theta/(qB_\zeta)`, +:math:`B_\psi`, :math:`\partial_\theta B` and the displacement spectrum. +The second term vanishes for an exact equilibrium. In the output, finite +Fourier truncation and spline error add to both. -.. math:: - - \mathcal J\nabla\cdot\boldsymbol\xi_{rec} - =\mathcal J(1-R^2)\partial_\psi\xi^\psi+(1-R)\mathcal J'\xi^\psi. - -Also :math:`\chi_1(q\xi_{rec}^\theta-\xi_{rec}^\zeta)=R\xi_s` (R4). -The diagnostic llagb also differs from the projection of the reconstructed -contravariant displacement by (R5) - -.. math:: - \mathrm{llagb}_{code}-(\delta B_E+\boldsymbol\xi_{rec,\perp}\cdot\nabla B) - =(1-R)\frac{\xi_s}{\chi_1 q}\partial_\theta B. +Regularisation +============== -These formulas are for the diagonal model only. Near rational surfaces the -regularized field, displacement and effective derivative therefore do not -form one exact ideal-induction pair. This is a limitation of the smoothing -policy, not evidence of resistive physics. The unconditional smoothing in -``gpout_pmodb`` even with ``reg_flag=f`` is a separate verified output bug. - - -A general-J continuum error budget (verified R6-R7) is available without -assuming a diagonal regularizer. Let U be the derivative selected by -``gpeq_sol``, V the derivative used by pmodb divergence, and -:math:`\Delta_s=\xi_s-\chi_1(q\xi_{rec}^\theta-\xi_{rec}^\zeta)`. -Writing :math:`B_\theta,B_\zeta` for covariant components gives +With ``reg_flag`` (Park et al. 2009 Eq. 10), ``gpeq_sol`` replaces the +radial derivative by :math:`R\,\partial_\psi\xi^\psi`, +:math:`R=s^2/(s^2+\sigma^2)`, :math:`s=m-nq`, in the field and in ``xms``. +The regularised field is then not divergence free (R3): .. math:: - \mathrm{lagb}+\mathrm{Bdivxprp}+\mathrm{Bkxprp} - =-\frac{\xi_{rec,\perp}^\zeta}{q}\partial_\theta B - +\frac{2\Delta_s}{\chi_1q}\partial_\theta B - +\frac{B_\theta\partial_\zeta\Delta_s-B_\zeta\partial_\theta\Delta_s} - {\mathcal J B} - +B(V-U)-\frac{\boldsymbol\xi_{rec,\perp}\cdot\mathbf F}{B}. - -This uses consistent continuum geometry and the field norm. Finite Fourier -truncation and spline errors are additional numerical errors, not part of F. -The derivative term cancels when V=U, even if both are regularized; in the -old reg_flag=f output V differs from U. With regularization enabled, -Delta_s can be nonzero and leaves the two explicit closure terms. Thus a -large derivative in Bdivxprp alone does not measure its contribution to the -net mismatch. This budget separates the candidate causes of finding (i), -without claiming a numerical allocation of the original edge residual. + \mathcal J\nabla\cdot\delta\mathbf B=2\pi i\chi_1 s(1-R)\,\partial_\psi\xi^\psi. +``Bdivxprp`` uses the same derivative ``xmp1`` as the field, and +:math:`\boldsymbol\xi_\perp` is built from the same ``xms``, so the +regularisation cancels between the columns and the relation above holds +unchanged with the regularised quantities (R6-R8: a bookkeeping +decomposition under the stated model, not a separate proof about the code). +Near rational surfaces the regularised field and displacement do not form +an exact ideal-induction pair. This is a limitation of the smoothing +policy, not evidence of resistive physics. With ``reg_flag=f`` the +columns are unregularised and singular at rational surfaces. What consumers need =================== -PENTRC, GPEC.jl ``KineticForces`` +PENTRC The drive of Logan et al. (2013) Eqs. 4-5 needs :math:`\delta B` and :math:`\nabla\cdot\boldsymbol\xi` of the *same* displacement; the - combination is then invariant under :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B`. - The default PENTRC path (``peq_file`` = ``gpec_xclebsch``, ``set_peq``) - uses :math:`\delta B/B=-(\nabla\cdot\boldsymbol\xi_\perp+\boldsymbol\kappa\cdot\boldsymbol\xi_\perp)` - and :math:`\nabla\cdot\boldsymbol\xi_\perp` from the ``smat``-``zmat`` - matrices, i.e. a consistent :math:`\boldsymbol\xi_\perp` pair. The - ``pmodb_file`` path (``read_pmodb``) pairs ``lagb`` with - :math:`-(\mathrm{lagb}+\mathrm{Bkxprp})/B`, which is not the divergence of - :math:`\boldsymbol\xi_g`. Under :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B` - the consistent drive changes by a total derivative along the field line - (K3), while this pairing changes it by - :math:`-\mu B\lambda\,\partial_l B/v_\parallel` (K4). + combination is then invariant under + :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B` up to a total + derivative along the field line (K3). Both PENTRC input paths use the + :math:`\boldsymbol\xi_\perp` pair + :math:`\delta B/B=-(\nabla\cdot\boldsymbol\xi_\perp+\boldsymbol\kappa\cdot\boldsymbol\xi_\perp)` + and :math:`\nabla\cdot\boldsymbol\xi_\perp`: the default path + (``peq_file`` = ``gpec_xclebsch``, ``set_peq``) from the ``smat``-``zmat`` + matrices, and the ``pmodb_file`` path (``read_pmodb``) from + :math:`-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})` and ``Bdivxprp``. The + ``lagb`` column is not read. Regenerate ``pmodb_file`` inputs with GPEC + 1.6 or later. Bounce-averaged codes in perturbed Boozer or Hamada coordinates (NEO-RT, NEO-2, Shaing-type) need :math:`\delta B` in the perturbed straight-field-line coordinates. That follows from :math:`\delta B_L(\boldsymbol\xi_\perp)` (``llagb``, or :math:`-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})` away from rational surfaces) and :math:`\nabla\cdot\boldsymbol\xi_\perp` by a magnetic differential - equation for the parallel gauge. ``lagb`` must not be used as - :math:`\delta B_L(\boldsymbol\xi_\perp)`. + equation for the parallel gauge (next section). ``lagb`` must not be + used as :math:`\delta B_L(\boldsymbol\xi_\perp)`. -Reproducing the proofs -====================== - -:: - - PYTHONPATH=$FORTSYM/python python3 docs/derivations/pmodb_gauge/derive.py - -FortSym (https://github.com/lazy-fortran/fortsym) is needed only to rerun the -proofs, not to build or run GPEC. - - -Target gauges in GPEC units (verified Q1-Q8) -============================================ +Target gauges in GPEC units (Q1-Q8) +=================================== Let :math:`D=\partial_\theta+q\partial_\zeta`, so :math:`\mathbf B\cdot\nabla=(\chi_1/\mathcal J)D`. @@ -257,64 +220,42 @@ with ``jac_out`` alone does not solve this equation. For a PEST chart :math:`\mathcal J/R^2` is a flux function; its linear perturbation is :math:`\nabla\cdot\boldsymbol\xi-2\xi_R/R` (Q8). PEST output is not automatically either target gauge. Transform the -equilibrium chart first, then solve the target equation. The Hamada scalar -on Boozer equilibrium angles uses :math:`T_2=2 b_H/B`, matching -rmp_torque c3614334, Eq. 4.14. Q5 proves composition of parallel shifts; -Q4 proves the angular-map determinant. The native regression compares the -finite spectral operator directly with that consumer's kernel. - - -Verified defects and numerical attribution -========================================== - -* ``gpout_pmodb`` smooths the radial derivative even with ``reg_flag=f``; - use ``xmp1_mn`` selected by ``gpeq_sol``. No change for ``reg_flag=t``. -* ``gpdiag_pmodb`` uses a stale ``jac`` in its first theta loop. The local - Jacobian must be assigned before projecting out the parallel component. -* PENTRC ``read_pmodb`` overwrites the supplied divergence with - ``-(lagb+Bkxprp)``. Use the supplied positive ``Bdivxprp`` and - ``Bkxprp`` to form ``b_perp=-(Bdivxprp+Bkxprp)`` instead. -* Both CGL torque paths recover ``kappa.xi_perp`` as - ``-(dbob+divx)/2`` although their input convention is - ``dbob=-(divx+kapx)``. Remove the extra half. K1 and K5 prove the - double-adiabatic pressure work for real and complex perturbations. - -Finding (ii) from the TC24 reconstruction is a missing division by B in -the pressure *regressor*, not a GPEC normalization error (P1). For the -retained ITER n=3 output the original fit gives 0.1677; the corrected -pressure term plus the gauge term fits coefficients (1,1,1) with relative -residual about 1e-4. This is numerical corroboration only; ``docs/derivations/pmodb_gauge/check_output.py`` -reproduces the fit from the pmodb, xclebsch, diagnostic and IDCON ASCII tables. - -Finding (i) combines the gauge term, effective-derivative smoothing, -finite spectral/spline error, and the equilibrium force residual. The -identity above separates them; it does not prove a quantitative allocation -of the originally reported 0.5-19 percent to any one cause. The explicit -pressure term in curvature has coefficient one. Edge growth alone does -not diagnose nonideal plasma physics. Finding (iii) is consistent with the -explicit radial derivative in divergence and its smoothing near rational -surfaces. No change to the smoothing model itself is proposed here. +equilibrium chart first, then solve the target equation. Q4 proves the +angular-map determinant and Q5 the composition of parallel shifts. + + +Reproducing the proofs +====================== + +:: + + python3 docs/derivations/pmodb_gauge/derive.py + +This needs only SymPy and takes a few minutes, most of it the G6 attempt. +FortSym (https://github.com/lazy-fortran/fortsym) is used instead when it is +on ``PYTHONPATH``. Neither is needed to build or run GPEC. + + +History +======= + +Before GPEC 1.6.0, ``Bdivxprp`` and ``Bkxprp`` subtracted a +Fourier-truncated parallel displacement, ``gpout_pmodb`` regularised the +divergence even with ``reg_flag=f``, ``gpdiag_pmodb`` used a stale Jacobian, +PENTRC's ``read_pmodb`` paired ``lagb`` with an inconsistent divergence, and +both PENTRC CGL paths halved the curvature term. These were fixed in +1.6.0 (#340-#343). Source map and literature -========================== - -Verified code contracts (line numbers at Fortran base 3622c25c): -``gpec/idcon.f:93`` (chi1=2pi psio), ``:616`` (projection geometry), -``:677`` (pressure in Pa), ``:1026`` (curvature and divergence matrices), -``gpec/gpeq.f:131`` (regularization selection), ``:208`` (surface-displacement -closure), ``gpec/gpout.f:3041`` (eulb/lagb), ``:3050`` (divergence and -pressure curvature), ``gpec/gpdiag.f:1012`` (diagnostic projection), -``:1061`` (llagb), ``pentrc/inputs.f90:376`` (ASCII columns), -``:396`` (old gauge mixing), ``:856`` (perpendicular matrix pair), -``pentrc/torque.F90:379`` (old CGL half). - -Julia base 0304fd54c: ``src/KineticForces/KineticForcesStructs.jl:437`` -uses the correct perpendicular dbob convention; -``src/KineticForces/Torque.jl:295`` repeats the CGL half. -``src/PerturbedEquilibrium/FieldReconstruction.jl``'s xi_s is Glasser's -surface displacement, not xi^zeta. Eq. 18 describes its elimination in the -Euler-Lagrange system; Eq. 9 defines it. +========================= + +Code: ``gpeq_sol`` and ``gpeq_contra`` (``gpec/gpeq.f``, regularisation and +the divergence-free closure), ``gpout_pmodb`` (``gpec/gpout.f``, the pmodb +columns), ``gpdiag_pmodb`` (``gpec/gpdiag.f``, ``llagb`` and ``lllagb``), +``idcon_build`` (``gpec/idcon.f``, ``smat``-``zmat``), ``read_pmodb`` and +``set_peq`` (``pentrc/inputs.f90``), and ``direct_run`` (``equil/direct.f``, +``eqfun``). The physical definitions were checked against Park 2009, PoP 16 056115, Eqs. 7 and 10; Park 2011, PoP 18 110702, Eqs. 4, 10 and 11; @@ -322,11 +263,4 @@ Park and Logan 2017, PoP 24 032505, Eq. 13 and p. 6; Logan and Park 2013, PoP 20 122507, Eqs. 4 and 5; and Boozer and Nuehrenberg 2006, PoP 13 102501, Eqs. 42 and 43. The latter's Delta includes twice the curvature; its Eq. 43 is Eulerian and is not the perpendicular Lagrangian -scalar. These are source contracts, separate from the native algebra proofs. - -FortSym limits: #84, #85 and #86 are present at ae635c80. The direct G6 -contraction remains UNKNOWN (33 symbols, rational metric jets); no probe -is promoted to proof. #91 (coefficient extraction after zero substitution) -and #92 (radical power reduction) are not exercised by the accepted -polynomial/rational gauge proofs. C1/C2 supply the invariant kinematic -identity independently of G6. +scalar. From 09c85cddd9d5783f7c3afbe383772a29ba1322d0 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 01:32:02 -0400 Subject: [PATCH 295/305] DOCS - CLEANUP - Correct stale and wrong statements across the docs for 1.6 Factual pass over the existing pages, each edit checked against the source; no new sections. outputs.rst: euler.bin, lowercase gpec_* file names, the real flags for vpbrzphi/vsbrzphi and bnormal_spectrum.bin, xbnormal columns, output formats and when the control/singfld outputs are written. sign_conventions.rst: the Jacobian powers give 1/J, and the jac_type table gains park and other. installation.rst and install/README: X11_HOME, NETCDF_C_DIR is derived, allf builds the same modules as all, OMPFLAG is automatic. Broken links in namelists.rst and source_documentation.rst; the DIII-D kinetic and resistive READMEs are linked from the index; example READMEs no longer claim rlar output while rlar_flag is off. Template comments: jac_type options, ahb_flag. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/examples.rst | 8 +++++ docs/examples/DIIID_ideal_example/README | 4 +-- docs/examples/DIIID_kinetic_example/README | 2 +- docs/index.rst | 2 ++ docs/installation.rst | 8 ++--- docs/namelists.rst | 4 +-- docs/outputs.rst | 40 +++++++++++----------- docs/sign_conventions.rst | 12 +++++-- docs/source_documentation.rst | 2 +- input/dcon.in | 2 +- input/equil.in | 2 +- install/README | 4 +-- 12 files changed, 54 insertions(+), 36 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index e9f79fa29..f83b9830e 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,3 +3,11 @@ .. _DIIID_ideal_example: .. include:: examples/DIIID_ideal_example/README + +.. _DIIID_kinetic_example: + +.. include:: examples/DIIID_kinetic_example/README + +.. _DIIID_resistive_example: + +.. include:: examples/DIIID_resistive_example/README diff --git a/docs/examples/DIIID_ideal_example/README b/docs/examples/DIIID_ideal_example/README index bc4e65dcb..04b3fbb68 100644 --- a/docs/examples/DIIID_ideal_example/README +++ b/docs/examples/DIIID_ideal_example/README @@ -3,7 +3,7 @@ GPEC DIII-D Ideal Example This example uses a baseline DIII-D-like H-mode. The equilibrium is an artificial one made with TokaMaker to have 0D parameters matching typical DIII-D H-mode values, and the kinetic profiles are self-consistent with the equilibrium. -Note that the coil and pentrc input namelists use relative paths to the package's data files. If you would like to run the example in a different location, you must update those paths appropriately. +Note that the coil input namelist uses a relative path to the package's data files. If you would like to run the example in a different location, you must update that path appropriately. Following a typical workflow we first ``cd`` to the example's directory, then run DCON:: @@ -39,4 +39,4 @@ Finally, we run PENTRC to compute the neoclassical toroidal viscosity (NTV) torq PENTRC START => ... PENTRC STOP => Normal termination. -The full (``fgar``) and trapped-only (``tgar``) torque calculations agree reasonably well, consistent with the expectation that banana-orbit particles dominate the nonambipolar transport. The reduced large-aspect-ratio (``rlar``) result typically differs significantly from the general-aspect-ratio calculations for shaped, high-beta H-mode plasmas. +The full (``fgar``) and trapped-only (``tgar``) torque calculations agree reasonably well, consistent with the expectation that banana-orbit particles dominate the nonambipolar transport. The reduced large-aspect-ratio (``rlar``) result (enabled with ``rlar_flag=t``; off in this example) typically differs significantly from the general-aspect-ratio calculations for shaped, high-beta H-mode plasmas. diff --git a/docs/examples/DIIID_kinetic_example/README b/docs/examples/DIIID_kinetic_example/README index beb77909c..26653414e 100644 --- a/docs/examples/DIIID_kinetic_example/README +++ b/docs/examples/DIIID_kinetic_example/README @@ -30,4 +30,4 @@ Finally, we run PENTRC for the full neoclassical torque calculation:: PENTRC START => ... PENTRC STOP => Normal termination. -The full general-aspect-ratio (``fgar``) and trapped-only (``tgar``) torque results can be compared to assess the trapped-particle dominance. The ``data_dir`` in ``pentrc.in`` points to the precomputed special function matrix used by the reduced large-aspect-ratio (``rlar``) method, which serves as a cross-check. +The full general-aspect-ratio (``fgar``) and trapped-only (``tgar``) torque results can be compared to assess the trapped-particle dominance. The ``data_dir`` in ``pentrc.in`` points to the precomputed special function matrix used by the reduced large-aspect-ratio (``rlar``) method, which can be enabled with ``rlar_flag=t`` as a cross-check. diff --git a/docs/index.rst b/docs/index.rst index aa1df6a0c..09be708fc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,3 +67,5 @@ The GPEC repository contains a number of examples intended to provide both consi Example console commands and output are given below. - :ref:`DIII-D Ideal Example ` +- :ref:`DIII-D Kinetic Example ` +- :ref:`DIII-D Resistive Example ` diff --git a/docs/installation.rst b/docs/installation.rst index f7b488392..43813b04e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -50,7 +50,7 @@ Important make targets ---------------------- * all (default): build all main executables and libraries -* allf: same as all but Fortran-only modules +* allf: currently builds the same modules as all * clean: remove built objects and module files * clear: remove example outputs in docs/examples * realclean: deep clean including dependencies built into deps @@ -82,7 +82,7 @@ netCDF libraries: * NETCDF_FORTRAN_HOME: preferred when C and Fortran installs are split * NETCDFHOME or NETCDF_DIR: netCDF installation prefix * NETCDFINC: netCDF include path -* NETCDF_C_HOME or NETCDF_C_DIR: netCDF-C location if separate +* NETCDF_C_HOME: netCDF-C location if separate Dependency install location: @@ -180,8 +180,8 @@ Then build with:: X11 (xdraw) ^^^^^^^^^^^ -If xdraw fails to link on macOS, ensure XQuartz is installed and add include -and library paths in xdraw/makefile, for example:: +If xdraw fails to link on macOS, ensure XQuartz is installed and set +X11_HOME (read by install/DEFAULTS.inc), for example:: brew install libx11 export X11_HOME=/opt/homebrew diff --git a/docs/namelists.rst b/docs/namelists.rst index 4629dade7..491cd44d2 100644 --- a/docs/namelists.rst +++ b/docs/namelists.rst @@ -1,5 +1,5 @@ -Fully annotated namelist input files can be found in the $GPECHOME/input directory and any of the examples in $GPECHOME/docs/examples directories of any `public installation `. +Fully annotated namelist input files can be found in the $GPECHOME/input directory and any of the examples in $GPECHOME/docs/examples directories of any :ref:`public installation `. The major input files requiring user interaction are as follows. @@ -40,7 +40,7 @@ This namelist controls the MATCH code, including which DCON eigenfunction (sol_n The PENTRC Namelist ==================== -This namelist sets the inputs, controls the behavior, and determines the outputs of the GPEC code. +This namelist sets the inputs, controls the behavior, and determines the outputs of the PENTRC code. .. include:: ../input/pentrc.in :literal: diff --git a/docs/outputs.rst b/docs/outputs.rst index f642bd747..22d35140b 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -125,46 +125,46 @@ The binary GPEC outputs can be viewed using the commandxdraw filenamewhere filen Output Files ============ -The GPEC outputs are entirely specified by flags (bool types t or f) set in the GPEC_output section of GPEC.in. All outputs are ASCII files, and can be grouped into two major categories. +The GPEC outputs are entirely specified by flags (bool types t or f) set in the gpec_output section of gpec.in. Outputs are written as ASCII tables (ascii_flag), xdraw binary files (bin_flag, bin_2d_flag) and netcdf files (netcdf_flag), and can be grouped into the categories below. ASCII File Outputs ------------------ -A number of the flag options in GPEC.in instruct GPEC to output ASCII file data. Some of these outputs are always available. Some, however, require a input error field instead of a hard coded harmonic_flag call. Both groups are listed in detail here. +A number of the flag options in gpec.in instruct GPEC to output ASCII file data. Some of these outputs are always available. Some, however, require an external 3D field input (data_flag, coil_flag, harmonic_flag) or a single eigenmode (mode_flag). Both groups are listed in detail here. Outputs Always Available -These GPEC outputs can always be obtained from a equil.bin file output from DCON. +These GPEC outputs can always be obtained from a euler.bin file output from DCON. -GPEC_response_n#.out +gpec_response_n#.out **Flag** resp_flag **Info** Energy for DCON eigenmodes and stability indices. Eigenvalues and eigenvectors for vacuum and plasma inductance (virtual casing currents to fields), plasma permeability (external fields to total fields), and plasma reluctance. -GPEC_singcoup_matrix_n#.out +gpec_singcoup_matrix_n#.out **Flag** singcoup_flag **Info** The coupling matrix to resonant fields, coupling matrix to singular currents, and to island half-widths is given for each rational surface within the plasma (q=2, 3, etc) for each surface the real and imaginary coupling constants are given for each poloidal mode number on the control surface. -GPEC_singcoup_svd_n#.out +gpec_singcoup_svd_n#.out **Flag** singcoup_flag - **Info** The SVD singular values (s) and eigen vectors for each coupling matrix in GPEC_singcoup_matrix_n#.out. Large s corresponds to large amplification, with the largest (most important mode) listed at the top. The results should be dotted with the unweighted normal field spectrum to give physical meaning. + **Info** The SVD singular values (s) and eigen vectors for each coupling matrix in gpec_singcoup_matrix_n#.out. Large s corresponds to large amplification, with the largest (most important mode) listed at the top. The results should be dotted with the unweighted normal field spectrum to give physical meaning. Outputs Available When Error Field is Provided -These outputs are only available when an external error field file is provided as an input to GPEC. This means GPEC.in must have the data_flag turned on and a infile specified. +These outputs are only available when an external 3D field is provided as an input to GPEC. This means gpec.in must have data_flag (with an infile specified), coil_flag, harmonic_flag, or mode_flag turned on. -GPEC_control_n#.out +gpec_control_n#.out - **Flag** + **Flag** none (always written with ascii_flag) **Info** The Plasma response for an external perturbation on the control surface. This includes the vacuum energy, surface energy, plasma energy, real and imaginary vacuum input \mathbf{B}_{in} and total field on plasma boundary\mathbf{B}_{out}as a function of poloidal mode number. -GPEC_singfld_n#.out +gpec_singfld_n#.out **Flag** singfld_flag @@ -174,21 +174,21 @@ GPEC_singfld_n#.out **Info** Additional section showing the overlap field (Tesla) and overlap percentage for each eigenmode in the singcoup_svd output. See :ref:`coupling_overlap` for definitions. -GPEC_pmodb_n#.out +gpec_pmodb_n#.out **Flag** pmodb_flag **Info** Perturbed field strength (real and imaginary, Tesla) for each poloidal mode number at each value of \Psi_{N} output: the Eulerian \delta B (eulb), the Lagrangian \delta B of the displacement with zero \xi^\zeta in the working (jac_type) coordinates (lagb), B\nabla\cdot\xi_\perp (Bdivxprp) and the force-balance form of B\kappa\cdot\xi_\perp (Bkxprp). lagb is not the \xi_\perp-only Lagrangian \delta B; that is -(Bdivxprp+Bkxprp) up to the equilibrium force-balance residual. See :doc:`pmodb_gauge` before using these columns for NTV post processing. -GPEC_xbnormal_n#.out +gpec_xbnormal_n#.out **Flag** xbnormal_flag - **Info** The normal components of the displacement, magnetic field without the plasma response, and magnetic field with the plasma response included for each poloidal mode number at each value of \Psi_{N} output.?? + **Info** The normal displacement (xno), the normal perturbed magnetic field (bno), and the Jacobian-weighted contravariant psi component of the perturbed field J\mathbf{B}\cdot\nabla\psi (bwp) for each poloidal mode number at each value of \Psi_{N} output. -GPEC_*rzphi_n#.out +gpec_*rzphi_n#.out -A number of output files have a similar structure. Here the * in the file name is replaced by the appropriate leading letters of the corresponding flag. For example the xrzphi_flag for n=1 creates a GPEC_xrzphi_n1.out file. some common properties of these files are: +A number of output files have a similar structure. Here the * in the file name is replaced by the appropriate leading letters of the corresponding flag. For example the xrzphi_flag for n=1 creates a gpec_xrzphi_n1.out file. some common properties of these files are: • real and imaginary components: Output files contain two dimensional data on an \left(r,z\right) grid for a single toroidal harmonic. To translate into three dimensions, perform the transformationB\left(r,z,\phi\right)=B_{real}\left(r,z\right)\cos\left(n\phi\right)+B_{imag}\left(r,z\right)\sin\left(n\phi\right) @@ -211,7 +211,7 @@ A number of output files have a similar structure. Here the * in the file name i **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current composed of both the vacuum component and the plasma response. - **Flag** vpbrzphi_flag + **Flag** pbrzphi_flag (writes gpec_vpbrzphi_n#.out) **Info** The true perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid due to the plasma response alone calculated from the plasma response surface condition. @@ -219,9 +219,9 @@ A number of output files have a similar structure. Here the * in the file name i **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current from the external fields alone. - **Flag** ssbrzphi_flag + **Flag** vsbrzphi_flag (with ss_flag(i) selecting surfaces; requires singfld_flag) - **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current from the external fields alone. + **Info** The vacuum field on the \left(r,z,\phi\right) grid due to the currents on the selected resonant surfaces. Energy Decomposition Outputs ---------------------------- @@ -281,7 +281,7 @@ pflux_re(im)_2d.bin bnormal_spectrum.bin - **Flag** bin_flag + **Flag** bin_2d_flag **Info** Surfmn type contours of the normal perturbed magnetic fields as a function of poloidal harmonic number and \Psi_{N}. diff --git a/docs/sign_conventions.rst b/docs/sign_conventions.rst index 40dfccb45..25290474a 100644 --- a/docs/sign_conventions.rst +++ b/docs/sign_conventions.rst @@ -57,8 +57,8 @@ Toroidal Coordinate :math:`\zeta` and :math:`\phi` Working Coordinate Options -------------------------- -Controlled by ``jac_type`` in ``equil.in``. The Jacobian is -:math:`J \propto B_p^{p_{bp}} \, B^{p_b} \, R^{-p_r}`: +Controlled by ``jac_type`` in ``equil.in``. The Jacobian is set by +:math:`\mathbf{B}\cdot\nabla\theta \propto 1/J \propto B_p^{p_{bp}} \, B^{p_b} \, R^{-p_r}`: .. list-table:: :header-rows: 1 @@ -84,6 +84,14 @@ Controlled by ``jac_type`` in ``equil.in``. The Jacobian is - 1 - 0 - 0 + * - Park + - 0 + - 1 + - 0 + * - Other (powers set explicitly in ``equil.in``) + - + - + - See also the coordinate discussion in :doc:`outputs`. diff --git a/docs/source_documentation.rst b/docs/source_documentation.rst index 038d69188..5c3767513 100644 --- a/docs/source_documentation.rst +++ b/docs/source_documentation.rst @@ -1,6 +1,6 @@ .. _source_documentation: -The recommended post processing for GPEC is now to use the devoted `OMFIT module `. +The recommended post processing for GPEC is now to use the devoted `OMFIT module `_. The deprecated code below has been ported there and improved, but is included here for reference. diff --git a/input/dcon.in b/input/dcon.in index 8db588137..57ec14a12 100644 --- a/input/dcon.in +++ b/input/dcon.in @@ -52,7 +52,7 @@ ahb_flag=f ! Output normal magnetic field eigenvalues and eigenfunctons at plasma-vacuum interface (must be false for GPEC) msol_ahb=1 ! Number of eigenfunctions output by ahb_flag=t ? - mthsurf0=1 ! Linear multiplier on number of boundary points used to display surface eigenfunctions for ahgb_flag=t + mthsurf0=1 ! Linear multiplier on number of boundary points used to display surface eigenfunctions for ahb_flag=t bin_euler=t ! Output M psi-by-M euler-lagrange solutions to binary file euler.bin bin_vac=f ! Output deprecated binary vacuum.bin containing vacuum energy matrices diff --git a/input/equil.in b/input/equil.in index 86f3b7f3c..6189d78c0 100644 --- a/input/equil.in +++ b/input/equil.in @@ -2,7 +2,7 @@ eq_type="efit" ! Type of the input 2D equilibrium file. Accepts efit, chease, fluxgrid, transp, jsolver, lar, sol, etc. eq_filename="mypath" ! Path to input file (geqdsk, etc.) Must specified (no default). - jac_type="hamada" ! Working coordinate system for all DCON and GPEC calculations. Overrides individual powers. Accepts hamada, pest, boozer, equal_arc + jac_type="hamada" ! Working coordinate system for all DCON and GPEC calculations. Overrides individual powers. Accepts hamada, pest, boozer, equal_arc, park, other power_bp=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r power_b=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r power_r=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r diff --git a/install/README b/install/README index 992eaeb01..3b3dd0942 100644 --- a/install/README +++ b/install/README @@ -45,8 +45,8 @@ Personal copies of GPEC should be cloned from the `Github repository &1 | tee make.log + make 2>&1 | tee make.log where the logging part is merely a useful suggestion. Appropriate compiler details for the major fusion clusters on which public installations are maintained is recorded in the $GPECHOME/install/DEFAULTS.inc file annotations. \ No newline at end of file From 459114934f1f17ac8c6ce220b739fa2a000e67d9 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 05:38:29 -0400 Subject: [PATCH 296/305] DOCS - CLEANUP - Drop the pmodb derivation scripts from the release The symbolic proof scripts under docs/derivations are a new verification framework that nothing else in the repo uses, so they are out of scope for 1.6.0. The pmodb_gauge page keeps its statements and equations; only the references to the scripts and their identity labels are removed. The scripts remain in this PR's commit history. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/derivations/pmodb_gauge/derive.py | 123 ---- .../pmodb_gauge/fortsym_verdicts.json | 373 ---------- docs/derivations/pmodb_gauge/identities.py | 637 ------------------ docs/pmodb_gauge.rst | 77 +-- 4 files changed, 29 insertions(+), 1181 deletions(-) delete mode 100644 docs/derivations/pmodb_gauge/derive.py delete mode 100644 docs/derivations/pmodb_gauge/fortsym_verdicts.json delete mode 100644 docs/derivations/pmodb_gauge/identities.py diff --git a/docs/derivations/pmodb_gauge/derive.py b/docs/derivations/pmodb_gauge/derive.py deleted file mode 100644 index abf835099..000000000 --- a/docs/derivations/pmodb_gauge/derive.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Run the pmodb gauge identities through a CAS and record the verdicts. - -Usage:: - - python3 docs/derivations/pmodb_gauge/derive.py - -FortSym (a checkout with a built C ABI library on ``PYTHONPATH``) is used -when it is importable; otherwise plain SymPy is used. Writes -``fortsym_verdicts.json`` beside this file, with the engine recorded. -``PROVED`` means the zero test returned true; ``UNKNOWN`` and ``NONZERO`` -fail an identity and are the required outcome for a negative control. No -numerical probe is accepted as evidence. -""" - -from __future__ import annotations - -import json -import signal -import subprocess -import sys -import time -from pathlib import Path - -HERE = Path(__file__).resolve().parent -sys.path.insert(0, str(HERE)) - -try: - import fortsym # noqa: E402 - import fortsym.sympy as fs # noqa: E402 -except ImportError: - fortsym = None - import sympy as fs # noqa: E402 -import identities # noqa: E402 - -TIMEOUT = 600 # seconds per SymPy zero test - - -class _Timeout(Exception): - pass - - -def _alarm(*_): - raise _Timeout - - -def verdict(expr) -> str: - if fortsym is not None: - value = fs.sympify(expr).is_zero - else: - signal.signal(signal.SIGALRM, _alarm) - signal.alarm(TIMEOUT) - try: - e = fs.expand(expr) - value = True if e == 0 else fs.simplify(fs.together(e)) == 0 or None - except _Timeout: - return "UNKNOWN" - finally: - signal.alarm(0) - return {True: "PROVED", False: "NONZERO"}.get(value, "UNKNOWN") - - -def counterexample(expr): - """Exact rational point at which the engine decides the residual is non-zero.""" - symbols = sorted(expr.free_symbols, key=str) - for attempt in range(1, 6): - point, value = {}, expr - for j, sym in enumerate(symbols): - num = 7 * attempt + 3 * j + 1 - point[str(sym)] = f"{num}/{5 + j}" - value = value.subs(sym, fs.Rational(num, 5 + j)) - if value.is_zero is False: - return point - return None - - -def revision() -> str: - if fortsym is None: - return "sympy " + fs.__version__ - root = Path(fortsym.__file__).resolve().parents[2] - try: - return subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], - capture_output=True, text=True, check=True).stdout.strip() - except (OSError, subprocess.CalledProcessError): - return "unknown" - - -def main() -> int: - records, failed = [], [] - for label, equation, residual in identities.all_identities(fs): - start = time.perf_counter() - v = verdict(residual) - records.append({"label": label, "equation": equation, "verdict": v, - "seconds": round(time.perf_counter() - start, 3)}) - if v != "PROVED": - failed.append(label) - print(f"{label:6s} {v:8s} {equation}", flush=True) - for label, equation, residual in identities.open_gaps(fs): - v = verdict(residual) - records.append({"label": label, "equation": equation, "verdict": v, - "open_gap": True}) - print(f"{label:6s} {v:8s} (open FortSym gap) {equation}", flush=True) - for label, equation, residual in identities.negative_controls(fs): - v = verdict(residual) - records.append({"label": label, "equation": equation, "verdict": v, - "negative_control": True}) - witness = counterexample(residual) if v != "NONZERO" else None - if witness is not None: - records[-1]["counterexample"] = witness - if v == "PROVED" or (v != "NONZERO" and witness is None): - failed.append(label) - print(f"{label:6s} {v:8s} (negative control" - f"{', witness ' + str(witness) if witness else ''}) {equation}", flush=True) - engine = ("fortsym native zero test via C ABI (fortsym.sympy)" if fortsym - else "sympy expand/simplify zero test") - out = {"engine": engine, - "fortsym_revision": revision(), "checks": records, "failed": failed} - (HERE / "fortsym_verdicts.json").write_text(json.dumps(out, indent=2) + "\n") - print("FAILED: " + " ".join(failed) if failed else "required identities proved; see explicit open-gap records") - return 1 if failed else 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/docs/derivations/pmodb_gauge/fortsym_verdicts.json b/docs/derivations/pmodb_gauge/fortsym_verdicts.json deleted file mode 100644 index 6c74f984d..000000000 --- a/docs/derivations/pmodb_gauge/fortsym_verdicts.json +++ /dev/null @@ -1,373 +0,0 @@ -{ - "engine": "sympy expand/simplify zero test", - "fortsym_revision": "sympy 1.9", - "checks": [ - { - "label": "G1.1", - "equation": "J dB^psi = GPEC bwp (pointwise)", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "G1.2", - "equation": "J dB^theta = GPEC bwt (pointwise)", - "verdict": "PROVED", - "seconds": 0.003 - }, - { - "label": "G1.3", - "equation": "J dB^zeta = GPEC bwz (pointwise)", - "verdict": "PROVED", - "seconds": 0.006 - }, - { - "label": "G1.4", - "equation": "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "G1.5", - "equation": "xi_s(xi + lam B) = xi_s(xi)", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "G1F.1", - "equation": "bwp = chi1 (m - n q) 2 pi i xsp", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "G1F.2", - "equation": "bwt = -(chi1 xsp1 + 2 pi i n xss)", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "G1F.3", - "equation": "bwz = -(chi1 (q1 xsp + q xsp1) + 2 pi i m xss)", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "G2.1", - "equation": "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "G2.2", - "equation": "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1 + q J div xi", - "verdict": "PROVED", - "seconds": 0.005 - }, - { - "label": "G2.3", - "equation": "q xwt - xwz = J xi_s / chi1", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "G3", - "equation": "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", - "verdict": "PROVED", - "seconds": 0.052 - }, - { - "label": "G4.1", - "equation": "J xi_perp^theta = chi1^2 (-f2 xsp + f3 xms/chi1) (cspl 1)", - "verdict": "PROVED", - "seconds": 0.469 - }, - { - "label": "G4.2", - "equation": "J xi_perp^zeta = q J xi_perp^theta - J xms/chi1 (cspl 2)", - "verdict": "PROVED", - "seconds": 0.307 - }, - { - "label": "G4.3", - "equation": "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", - "verdict": "PROVED", - "seconds": 0.055 - }, - { - "label": "G4.4", - "equation": "xsp d_psi B + (cspl 1/J) d_theta B = xi_perp.grad B (curvfun)", - "verdict": "PROVED", - "seconds": 0.325 - }, - { - "label": "G5.1", - "equation": "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "G5.2", - "equation": "xi_g^theta = xi_s/(chi1 q)", - "verdict": "PROVED", - "seconds": 0.032 - }, - { - "label": "G5.2b", - "equation": "xi_g^psi = xi^psi", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "G5.3", - "equation": "curl(xi_g x B) = curl(xi_perp x B) (theta component)", - "verdict": "PROVED", - "seconds": 0.037 - }, - { - "label": "G5.4", - "equation": "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", - "verdict": "PROVED", - "seconds": 0.005 - }, - { - "label": "G5.5", - "equation": "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", - "verdict": "PROVED", - "seconds": 0.016 - }, - { - "label": "G5.6", - "equation": "llagb (gpdiag.f) = dB_L(xi_perp)", - "verdict": "PROVED", - "seconds": 0.308 - }, - { - "label": "G5.7", - "equation": "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "G5.8", - "equation": "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)/(B_theta + q B_zeta)", - "verdict": "PROVED", - "seconds": 0.362 - }, - { - "label": "G7.1", - "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_psi", - "verdict": "PROVED", - "seconds": 0.013 - }, - { - "label": "G7.2", - "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_theta", - "verdict": "PROVED", - "seconds": 0.013 - }, - { - "label": "G7.3", - "equation": "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B = sband xsp + tband xms/chi1, xi = e_zeta", - "verdict": "PROVED", - "seconds": 0.009 - }, - { - "label": "C1", - "equation": "b.curl(xi_perp x B) + xi_perp.grad B = -B(div xi_perp + kappa.xi_perp)", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "C2", - "equation": "B kappa.xi_perp = xi_perp.grad B + xi_perp.((curl B) x B)/B", - "verdict": "PROVED", - "seconds": 0.004 - }, - { - "label": "R2", - "equation": "J div xi (R = 1) = 0", - "verdict": "PROVED", - "seconds": 0.028 - }, - { - "label": "R3", - "equation": "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", - "verdict": "PROVED", - "seconds": 0.005 - }, - { - "label": "R6", - "equation": "H_parallel = B(X1-U)+(B_theta Delta_zeta-B_zeta Delta_theta)/(J B)", - "verdict": "PROVED", - "seconds": 0.01 - }, - { - "label": "R7", - "equation": "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "R8", - "equation": "with V = U and Delta = 0: lagb+Bdivxprp+Bkxprp = -xi_perp^zeta Bt/q - xi_perp.F/B", - "verdict": "PROVED", - "seconds": 0.003 - }, - { - "label": "E4.1", - "equation": "d/de J(X + e xi) = d_i(J xi^i) = J div xi", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "G8.1", - "equation": "J B**2 div xi_perp = xmat Xp + ymat X + zmat A", - "verdict": "PROVED", - "seconds": 0.008 - }, - { - "label": "G8.2", - "equation": "idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2", - "verdict": "PROVED", - "seconds": 0.006 - }, - { - "label": "Q1.hamada", - "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q2.hamada", - "equation": "D w = -T; w_m = -T_m/[2 pi i(m-nq)]", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q3.hamada", - "equation": "delta B_target = delta B_X + w d_theta B (J B**a flux function)", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q1.boozer", - "equation": "T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a", - "verdict": "PROVED", - "seconds": 0.006 - }, - { - "label": "Q2.boozer", - "equation": "D w = -T; w_m = -T_m/[2 pi i(m-nq)]", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q3.boozer", - "equation": "delta B_target = delta B_X + w d_theta B (J B**a flux function)", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q4", - "equation": "det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", - "verdict": "PROVED", - "seconds": 0.001 - }, - { - "label": "Q5", - "equation": "Hamada->Boozer equals direct X->Boozer when w_HB=w_XB-w_XH", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q6", - "equation": "field-aligned input: delta B_E=0, xi_perp=0 => target delta B=0", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q7", - "equation": "xi_perp limit: zero target source implies no gauge correction", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "Q8", - "equation": "delta(J/R**2)/(J/R**2)=div xi-2 xi_R/R", - "verdict": "PROVED", - "seconds": 0.002 - }, - { - "label": "K5", - "equation": "Complex CGL pressure work equals [5/3 |D|**2+1/3 |D+3K|**2]/2", - "verdict": "PROVED", - "seconds": 0.007 - }, - { - "label": "P1", - "equation": "(Bk - (llagb-eulb))/B = mu0 p' xi^psi/B**2", - "verdict": "PROVED", - "seconds": 0.0 - }, - { - "label": "K1", - "equation": "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", - "verdict": "PROVED", - "seconds": 0.004 - }, - { - "label": "K3", - "equation": "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", - "verdict": "PROVED", - "seconds": 0.008 - }, - { - "label": "G6", - "equation": "B.dB + xi_perp.grad B^2 + B^2 div xi_perp + xi_perp.(curl B x B) = 0", - "verdict": "UNKNOWN", - "open_gap": true - }, - { - "label": "N1", - "equation": "lagb is the xi_perp-only Lagrangian |B| (false)", - "verdict": "UNKNOWN", - "negative_control": true, - "counterexample": { - "Bm_theta": "8/5", - "J_0": "11/6", - "chi1": "14/7", - "g_11_0": "17/8", - "g_12_0": "20/9", - "g_13_0": "23/10", - "g_22_0": "26/11", - "g_23_0": "29/12", - "q_0": "32/13", - "xp_0": "35/14", - "xt_0": "38/15", - "xz_0": "41/16" - } - }, - { - "label": "N2", - "equation": "lagb is Park 2009 Eq. 7 with the div-free xi (false)", - "verdict": "UNKNOWN", - "negative_control": true, - "counterexample": { - "Bm_theta": "8/5", - "q_0": "11/6", - "xt_0": "14/7", - "xz_0": "17/8" - } - }, - { - "label": "K2neg", - "equation": "fcgl with kapx = -(dbob + divx)/2 equals the CGL density", - "verdict": "UNKNOWN", - "negative_control": true, - "counterexample": { - "D": "8/5", - "K": "11/6" - } - } - ], - "failed": [] -} diff --git a/docs/derivations/pmodb_gauge/identities.py b/docs/derivations/pmodb_gauge/identities.py deleted file mode 100644 index e8a31a9b3..000000000 --- a/docs/derivations/pmodb_gauge/identities.py +++ /dev/null @@ -1,637 +0,0 @@ -"""Symbolic identities behind the GPEC pmodb |B| columns. - -Every function returns ``(label, equation, residual)`` triples; a residual must -vanish identically. The module is written against a SymPy-shaped namespace -``sp`` so that the same derivation runs on FortSym (``fortsym.sympy``, the -authority used by ``derive.py``). Labels refer to ``docs/pmodb_gauge.rst``. - -GPEC/DCON coordinates (psi, theta, zeta): angles in turns, general -straight-field-line Jacobian ``J(psi, theta)`` (any ``jac_type``), - - B = chi1 grad(zeta - q theta) x grad(psi), J B^theta = chi1, - J B^zeta = chi1 q, B^psi = 0, - -and the surface displacement (Glasser 2016 Eqs. 9 and 18) - - xi_s = (xi x B)_psi = chi1 (q xi^theta - xi^zeta). - -Pointwise identities are proved on jets: every field is an affine (1-jet) -polynomial in (psi, theta, zeta) about the origin with independent symbolic -coefficients, and the residual is evaluated at the origin after -differentiation. Each identity involves at most first derivatives of the -jetted fields, so it holds for all smooth fields iff it holds for all 1-jets. -The metric is general except for ``det g = J**2``, imposed by solving for -``g_33``. -""" - -from __future__ import annotations - -COORDS = ("psi", "theta", "zeta") - - -class Dual: - """First-order jet at the origin: value ``v`` and derivatives ``d[i]``. - - ``d is None`` marks a value whose own derivatives were not carried (a - derivative of a jet); differentiating it again raises. - """ - - def __init__(self, v, d=(0, 0, 0)): - self.v, self.d = v, (None if d is None else tuple(d)) - - @staticmethod - def lift(x): - return x if isinstance(x, Dual) else Dual(x) - - def _need(self): - if self.d is None: - raise ValueError("second derivative requested from a 1-jet") - return self.d - - def __add__(self, o): - o = Dual.lift(o) - if self.d is None or o.d is None: - return Dual(self.v + o.v, None) - return Dual(self.v + o.v, [a + b for a, b in zip(self.d, o.d)]) - - __radd__ = __add__ - - def __neg__(self): - return Dual(-self.v, None if self.d is None else [-a for a in self.d]) - - def __sub__(self, o): - return self + (-Dual.lift(o)) - - def __rsub__(self, o): - return Dual.lift(o) - self - - def __mul__(self, o): - o = Dual.lift(o) - if self.d is None or o.d is None: - return Dual(self.v * o.v, None) - return Dual(self.v * o.v, [self.v * b + a * o.v for a, b in zip(self.d, o.d)]) - - __rmul__ = __mul__ - - def inv(self): - if self.d is None: - return Dual(1 / self.v, None) - return Dual(1 / self.v, [-a / self.v ** 2 for a in self.d]) - - def __truediv__(self, o): - return self * Dual.lift(o).inv() - - def __rtruediv__(self, o): - return Dual.lift(o) * self.inv() - - def __pow__(self, k): - assert isinstance(k, int) and k >= 1 - out = self - for _ in range(k - 1): - out = out * self - return out - - -class Setup: - """Jetted GPEC fields (1-jets at the origin) in a SymPy-shaped namespace.""" - - def __init__(self, sp, metric=False): - self.sp = sp - self.u = (0, 1, 2) # psi, theta, zeta - self.chi1 = Dual(sp.Symbol("chi1", positive=True)) - self.q = self.jet("q", (0,)) - self.J = self.jet("J", (0, 1)) - # Contravariant displacement components (general, incl. xi_parallel). - self.xi = [self.jet(f"x{c[0]}", self.u) for c in COORDS] - self.Bup = [Dual(0), self.chi1 / self.J, self.chi1 * self.q / self.J] - if metric: - g = {} - for (i, j) in ((0, 0), (0, 1), (0, 2), (1, 1), (1, 2)): - g[(i, j)] = self.jet(f"g_{i + 1}{j + 1}", (0, 1)) - minor = g[(0, 0)] * g[(1, 1)] - g[(0, 1)] ** 2 - rest = (-g[(0, 0)] * g[(1, 2)] ** 2 + 2 * g[(0, 1)] * g[(1, 2)] * g[(0, 2)] - - g[(1, 1)] * g[(0, 2)] ** 2) - g[(2, 2)] = (self.J ** 2 - rest) / minor - self.g = [[g[(min(i, j), max(i, j))] for j in range(3)] for i in range(3)] - - def jet(self, name, deps): - sp = self.sp - return Dual(sp.Symbol(f"{name}_0"), - [sp.Symbol(f"{name}_{COORDS[i]}") if i in deps else 0 for i in range(3)]) - - def d(self, f, i): - f = Dual.lift(f) - return Dual(f._need()[i], None) - - def at0(self, e): - return Dual.lift(e).v - - # --- tensor algebra in the (psi, theta, zeta) chart ----------------------- - @staticmethod - def eps(i, j, k): - return (i - j) * (j - k) * (k - i) // 2 - - def cross_cov(self, a, b): - """(a x b)_i = J eps_ijk a^j b^k for contravariant a, b.""" - return [self.J * sum(self.eps(i, j, k) * a[j] * b[k] - for j in range(3) for k in range(3)) for i in range(3)] - - def curl_up(self, A): - """(curl A)^i = eps^ijk d_j A_k / J for covariant A.""" - return [sum(self.eps(i, j, k) * self.d(A[k], j) - for j in range(3) for k in range(3)) / self.J for i in range(3)] - - def div(self, v): - return sum(self.d(self.J * v[i], i) for i in range(3)) / self.J - - def lower(self, v): - return [sum(self.g[i][j] * v[j] for j in range(3)) for i in range(3)] - - def dot(self, a, b): - return sum(a[i] * b[i] for i in range(3)) - - def xi_s(self, v): - return self.chi1 * (self.q * v[1] - v[2]) - - -def induction(sp): - """G1: GPEC's J dB^i (gpeq.f:114-116) is J curl(xi x B)^i for any Jacobian.""" - S = Setup(sp) - xi, B = S.xi, S.Bup - JdB = [S.J * c for c in S.curl_up(S.cross_cov(xi, B))] - xs = S.xi_s(xi) - d = S.d - gpec = [S.chi1 * (d(xi[0], 1) + S.q * d(xi[0], 2)), - d(xs, 2) - S.chi1 * d(xi[0], 0), - -d(xs, 1) - S.chi1 * d(S.q * xi[0], 0)] - out = [(f"G1.{i + 1}", f"J dB^{COORDS[i]} = GPEC bw{'ptz'[i]} (pointwise)", - S.at0(JdB[i] - gpec[i])) for i in range(3)] - # (xi x B)_psi = xi_s: the surface displacement of Glasser (2016) Eq. 9. - out.append(("G1.4", "(xi x B)_psi = chi1 (q xi^theta - xi^zeta) = xi_s", - S.at0(S.cross_cov(xi, B)[0] - xs))) - # A field-aligned displacement changes neither xi^psi nor xi_s. - lam = S.jet("lam", S.u) - shifted = [xi[i] + lam * B[i] for i in range(3)] - out.append(("G1.5", "xi_s(xi + lam B) = xi_s(xi)", - S.at0(S.xi_s(shifted) - xs))) - return out - - -def fourier_symbols(sp): - """G1F: Fourier symbols of G1 for xi ~ exp(2 pi i (m theta - n zeta)).""" - I, pi = sp.I, sp.pi - m, n, chi1 = sp.Symbol("m"), sp.Symbol("n"), sp.Symbol("chi1", positive=True) - q, q1 = sp.Symbol("q"), sp.Symbol("q1") - X, X1, Xs = sp.Symbol("xsp"), sp.Symbol("xsp1"), sp.Symbol("xss") - dth, dze = 2 * pi * I * m, -2 * pi * I * n - out = [] - out.append(("G1F.1", "bwp = chi1 (m - n q) 2 pi i xsp", - chi1 * (dth + q * dze) * X - chi1 * (m - n * q) * 2 * pi * I * X)) - out.append(("G1F.2", "bwt = -(chi1 xsp1 + 2 pi i n xss)", - (dze * Xs - chi1 * X1) + (chi1 * X1 + 2 * pi * I * n * Xs))) - out.append(("G1F.3", "bwz = -(chi1 (q1 xsp + q xsp1) + 2 pi i m xss)", - (-dth * Xs - chi1 * (q1 * X + q * X1)) - + (chi1 * (q1 * X + q * X1) + 2 * pi * I * m * Xs))) - return out - - -def incompressible_closure(sp): - """G2: GPEC's xi^theta, xi^zeta (gpeq.f gpeq_contra) follow from div xi = 0.""" - S = Setup(sp) - xi, J, q, chi1, d = S.xi, S.J, S.q, S.chi1, S.d - D = sum(d(J * xi[i], i) for i in range(3)) # J div xi - xs = S.xi_s(xi) - Jt, Jz = J * xi[1], J * xi[2] - r1 = (d(Jt, 1) + q * d(Jt, 2)) - d(J * xs, 2) / chi1 + d(J * xi[0], 0) - D - r2 = (d(Jz, 1) + q * d(Jz, 2)) + q * d(J * xi[0], 0) + d(J * xs, 1) / chi1 - q * D - r3 = q * Jt - Jz - J * xs / chi1 - return [("G2.1", "(d_t + q d_z)(J xi^t) = d_z(J xi_s)/chi1 - d_psi(J xi^psi) + J div xi", - S.at0(r1)), - ("G2.2", "(d_t + q d_z)(J xi^z) = -q d_psi(J xi^psi) - d_t(J xi_s)/chi1" - " + q J div xi", S.at0(r2)), - ("G2.3", "q xwt - xwz = J xi_s / chi1", S.at0(r3))] - - -def columns(sp): - """G3-G5: eulb, xi_perp components, the gauge of lagb, and llagb.""" - S = Setup(sp, metric=True) - xi, B, J, q, chi1 = S.xi, S.Bup, S.J, S.q, S.chi1 - Bm = S.jet("Bm", S.u[:2]) # |B|(psi, theta), axisymmetric - dB = S.curl_up(S.cross_cov(xi, B)) - dBcov = S.lower(dB) - B2 = S.dot(S.lower(B), B) - xs = S.xi_s(xi) - g = S.g - xmz = J * xi[2] # GPEC xmz (J xi^zeta) - sigma = S.dot(S.lower(xi), B) / B2 - xperp = [xi[i] - sigma * B[i] for i in range(3)] - # gpout_pmodb/gpdiag_pmodb cspl: J xi_perp from xi^psi and xi_s with the - # eqfun coefficients f2, f3 (equil/direct.f, covariant metric). - f2 = (g[0][1] + q * g[0][2]) / (J * B2) - f3 = (g[1][2] + q * g[2][2]) / (J * B2) - jxt = chi1 ** 2 * (-f2 * xi[0] + f3 * xs / chi1) - jxz = q * jxt - J * xs / chi1 - out = [] - out.append(("G3", "B.dB = chi1 (bvt + q bvz)/J (eulb times |B|)", - S.at0(S.dot(dBcov, B) - chi1 * (dBcov[1] + q * dBcov[2]) / J))) - out.append(("G4.1", "J xi_perp^theta = chi1^2 (-f2 xsp + f3 xms/chi1) (cspl 1)", - S.at0(J * xperp[1] - jxt))) - out.append(("G4.2", "J xi_perp^zeta = q J xi_perp^theta - J xms/chi1 (cspl 2)", - S.at0(J * xperp[2] - jxz))) - # B . xi_perp = 0 as a statement about arbitrary vectors: the metric-jet - # form is larger than necessary, so the - # projection is proved on independent covariant B_i, contravariant B^i, xi^i. - Bl = [sp.Symbol(f"Bl_{i}") for i in range(3)] - Bu = [sp.Symbol(f"Bu_{i}") for i in range(3)] - xu = [sp.Symbol(f"xu_{i}") for i in range(3)] - sig = sum(Bl[i] * xu[i] for i in range(3)) / sum(Bl[i] * Bu[i] for i in range(3)) - out.append(("G4.3", "B_i xi_perp^i = 0 for xi_perp^i = xi^i - (B_j xi^j / B_k B^k) B^i", - sum(Bl[i] * (xu[i] - sig * Bu[i]) for i in range(3)))) - out.append(("G4.4", "xsp d_psi B + (cspl 1/J) d_theta B = xi_perp.grad B (curvfun)", - S.at0(xi[0] * S.d(Bm, 0) + jxt / J * S.d(Bm, 1) - - sum(xperp[i] * S.d(Bm, i) for i in range(3))))) - # Gauge of lagb: xi_g = xi_perp + lam B with xi_g^zeta = 0. - lam = -xperp[2] / B[2] - xg = [xperp[i] + lam * B[i] for i in range(3)] - out.append(("G5.1", "xi_g^zeta = 0 for lam = -xi_perp^zeta / B^zeta", S.at0(xg[2]))) - out.append(("G5.2", "xi_g^theta = xi_s/(chi1 q)", S.at0(xg[1] - xs / (chi1 * q)))) - out.append(("G5.2b", "xi_g^psi = xi^psi", S.at0(xg[0] - xi[0]))) - dBg = S.curl_up(S.cross_cov(xg, B)) - dBp = S.curl_up(S.cross_cov(xperp, B)) - out.append(("G5.3", "curl(xi_g x B) = curl(xi_perp x B) (theta component)", - S.at0(dBg[1] - dBp[1]))) - eul = S.dot(dBcov, B) # |B| * eulb - grad = lambda v: sum(v[i] * S.d(Bm, i) for i in range(3)) - lagb = eul + Bm * (xi[0] * S.d(Bm, 0) + xs / (chi1 * q) * S.d(Bm, 1)) - lagb_g = eul + Bm * grad(xg) - llagb = eul + Bm * grad(xperp) - llagb_code = eul + Bm * (xi[0] * S.d(Bm, 0) + jxt / J * S.d(Bm, 1)) - out.append(("G5.4", "lagb (gpout.f) = dB_L(xi_g), the xi^zeta = 0 gauge", - S.at0(lagb - lagb_g))) - out.append(("G5.5", "lagb - dB_L(xi_perp) = -(xi_perp^zeta/q) d_theta B", - S.at0(lagb - llagb + Bm * xperp[2] / q * S.d(Bm, 1)))) - out.append(("G5.6", "llagb (gpdiag.f) = dB_L(xi_perp)", S.at0(llagb_code - llagb))) - # Park (2009) Eq. 7 with the div-free xi (GPEC's own xmt, xmz). - lagb_div_free = eul + Bm * (xi[0] * S.d(Bm, 0) + xi[1] * S.d(Bm, 1)) - out.append(("G5.7", "dB_L(xi, div-free) = lagb + (xmz/(J q)) d_theta B", - S.at0(lagb_div_free - lagb - Bm * xmz / (J * q) * S.d(Bm, 1)))) - # Boozer-type closed form: B_i xi_perp^i = 0 and the xi_s definition. - Bc = S.lower(B) - out.append(("G5.8", "xi_perp^zeta = -(B_theta xi_s/chi1 + q B_psi xi^psi)" - "/(B_theta + q B_zeta)", - S.at0(xperp[2] + (Bc[1] * xs / chi1 + q * Bc[0] * xi[0]) - / (Bc[1] + q * Bc[2])))) - return out - - -def force_balance_identity(sp): - """G6: |B| times (llagb + Bdivxprp + Bkxprp) = -xi_perp . F, F = (curl B) x B - mu0 grad p. - - With pmodb's Bkxprp = xi_perp.grad B + mu0 p' xi^psi / B (force-balance form - of B kappa.xi_perp, gpout.f curvfun) and Bdivxprp = B div xi_perp, this is - B.dB + xi_perp.grad(B^2) + B^2 div xi_perp + xi_perp.((curl B) x B) = 0. - """ - S = Setup(sp, metric=True) - xi, B = S.xi, S.Bup - Bc = S.lower(B) - B2 = S.dot(Bc, B) - sigma = S.dot(S.lower(xi), B) / B2 - xperp = [xi[i] - sigma * B[i] for i in range(3)] - dB = S.curl_up(S.cross_cov(xi, B)) - curlB = S.curl_up(Bc) - jxb = S.cross_cov(curlB, B) # ((curl B) x B)_i - res = (S.dot(S.lower(dB), B) + sum(xperp[i] * S.d(B2, i) for i in range(3)) - + B2 * S.div(xperp) + S.dot(jxb, xperp)) - return [("G6", "B.dB + xi_perp.grad B^2 + B^2 div xi_perp + xi_perp.(curl B x B) = 0", - S.at0(res))] - - -def pentrc_kappa_matrices(sp): - """G7: idcon.f fmodb sband/tband give J B^2 (Bkxprp/B) from xi^psi and xi^alpha. - - PENTRC set_peq (inputs.f90) and GPEC.jl set_perturbation_data! form - jbb_kapx = smat xsp + tmat xi_alpha with xi_alpha = xms/chi1 (gpout_xclebsch). - The statement is algebraic at a point (only the values of d_psi B^2 and - d_theta B^2 enter), so the metric, Jacobian and derivatives are independent - symbols here: - sband = J (mu0 p' + d_psi B^2/2) - chi1^2 (d_theta B^2/(2 B^2)) (g12 + q g13)/J, - tband = chi1^2 (d_theta B^2/(2 B^2)) (g23 + q g33)/J. - """ - chi1, q, J, P, Dp, Dt = sp.symbols("chi1 q J P1 Dp Dt") - # B^2 = B^i B_i = (chi1/J)(B_theta + q B_zeta) needs no det g = J^2, so the - # metric components are independent here. - g11, g12, g13, g22, g23, g33 = sp.symbols("g11 g12 g13 g22 g23 g33") - g = [[g11, g12, g13], [g12, g22, g23], [g13, g23, g33]] - xi = list(sp.symbols("xp xt xz")) - B = [0, chi1 / J, chi1 * q / J] - Bl = [sum(g[i][j] * B[j] for j in range(3)) for i in range(3)] - B2 = sum(Bl[i] * B[i] for i in range(3)) - sigma = sum(Bl[i] * xi[i] for i in range(3)) / B2 - xperp = [xi[i] - sigma * B[i] for i in range(3)] - xs = chi1 * (q * xi[1] - xi[2]) - lhs = J * (xperp[0] * (P + Dp) + xperp[1] * Dt) - sband = J * (P + Dp) - chi1 ** 2 * (Dt / B2) * (g12 + q * g13) / J - tband = chi1 ** 2 * (Dt / B2) * (g23 + q * g33) / J - res = lhs - sband * xi[0] - tband * xs / chi1 - # The residual is linear in xi; proving it for each basis displacement keeps - # every check independently covers one displacement component. - out = [] - for k, name in enumerate(("psi", "theta", "zeta")): - r = res - for j in range(3): - r = r.subs(xi[j], 1 if j == k else 0) - out.append((f"G7.{k + 1}", "J B^2 (xi_perp.grad B + mu0 p' xi^psi/B)/B" - f" = sband xsp + tband xms/chi1, xi = e_{name}", r)) - return out - - -def cartesian_kappa(sp): - """C1, C2: Cartesian 1-jet forms of the kinematic and curvature identities.""" - x, y, z = sp.symbols("x y z") - X = (x, y, z) - beta = sp.Symbol("beta", positive=True) - g = [[sp.Symbol(f"h_{i}{j}") for j in range(3)] for i in range(3)] - B = [[0, 0, beta][i] + sum(g[i][j] * X[j] for j in range(3)) for i in range(3)] - d = lambda f, c: f.diff(c) if hasattr(f, "diff") else 0 - dot = lambda a, b: sum(a[i] * b[i] for i in range(3)) - cross = lambda a, b: [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0]] - curl = lambda v: [d(v[2], y) - d(v[1], z), d(v[0], z) - d(v[2], x), - d(v[1], x) - d(v[0], y)] - div = lambda v: sum(d(v[i], X[i]) for i in range(3)) - at0 = lambda e: e.subs(x, 0).subs(y, 0).subs(z, 0) - Bm = sp.sqrt(dot(B, B)) - b = [c / Bm for c in B] - kappa = [sum(b[j] * d(b[i], X[j]) for j in range(3)) for i in range(3)] - a = [sp.Symbol(f"a_{i}") + sum(sp.Symbol(f"a_{i}{j}") * X[j] for j in range(3)) - for i in range(3)] - xp = cross(B, a) - gradB = lambda v: sum(v[j] * d(Bm, X[j]) for j in range(3)) - dBpar = dot(b, curl(cross(xp, B))) - return [("C1", "b.curl(xi_perp x B) + xi_perp.grad B = -B(div xi_perp + kappa.xi_perp)", - at0(dBpar + gradB(xp) + Bm * (div(xp) + dot(kappa, xp)))), - ("C2", "B kappa.xi_perp = xi_perp.grad B + xi_perp.((curl B) x B)/B", - at0(Bm * dot(kappa, xp) - gradB(xp) - dot(xp, cross(curl(B), B)) / Bm))] - - -def regularisation(sp): - """R2-R3: Park-regularised components in GPEC (reg_flag) on one harmonic. - - Diagonal model: one harmonic, J independent of theta. R = s^2/(s^2 + reg^2), - s = m - n q. gpeq_sol uses xmp1 = R xsp1 in bmt/bmz and xms; gpeq_contra - builds xwt/xwz from xmp1. The pmodb columns use xmp1 and xms - (gpout_pmodb), so R2-R3 are the only regularisation identities needed. - """ - I, pi = sp.I, sp.pi - m, n, q, chi1 = sp.symbols("m n q chi1") - J0, J1 = sp.symbols("J_0 J_1") # J and dJ/dpsi (theta-independent) - X, X1, Xs, R = sp.symbols("xsp xsp1 xms R") - s = m - n * q - xwt = -(J0 * R * X1 + J1 * X + 2 * pi * I * n / chi1 * J0 * Xs) / (2 * pi * I * s) - xwz = -(q * J0 * R * X1 + q * J1 * X + 2 * pi * I * m / chi1 * J0 * Xs) / (2 * pi * I * s) - out = [] - jdiv_unreg = (J0 * X1 + J1 * X + 2 * pi * I * m * xwt.subs(R, 1) - - 2 * pi * I * n * xwz.subs(R, 1)) - out.append(("R2", "J div xi (R = 1) = 0", jdiv_unreg)) - # Divergence of the regularised Eulerian field: J div(dB) = d_psi bwp + ... . - bwp1 = 2 * pi * I * chi1 * (s * X1 - n * sp.Symbol("q1") * X) - bmt = -(chi1 * R * X1 + 2 * pi * I * n * Xs) - bmz = -(chi1 * (sp.Symbol("q1") * X + q * R * X1) + 2 * pi * I * m * Xs) - out.append(("R3", "J div dB (reg) = 2 pi i chi1 (m - n q)(1 - R) xsp1", - bwp1 + 2 * pi * I * m * bmt - 2 * pi * I * n * bmz - - 2 * pi * I * chi1 * s * (1 - R) * X1)) - return out - - -def regularized_column_budget(sp): - """General-J continuum budget, allowing independent field/output derivatives. - - U is the derivative selected by gpeq_sol; V is the derivative used in - gpout divergence. Delta=xi_s-chi1(q xi_rec^theta-xi_rec^zeta). - This is bookkeeping under the stated model relations (BDcode, BKcode), - not a proof that the code satisfies them. In gpout_pmodb, V = U - (xmp1) and xi_perp is built from xi_s itself (G4.2), so Delta = 0 and - R8 gives the budget of the output columns. - F denotes xi_rec_perp.((curl B)xB-mu0 grad p)/B, in Tesla. - Metric/field-norm consistency and C1/C2 are assumed; finite truncation - errors are not folded into the physical force residual. - """ - c,J,B,q,Bt,Bt_cov,Delta,Dt,Dz,X1,U,V,D,K,F,Z = sp.symbols( - "chi J B q Bt Btheta Delta Delta_theta Delta_zeta X1 U V D K F Z") - Bz_cov=(J*B**2/c-Bt_cov)/q - # J-weighted induction mismatch: supplied field minus curl(xi_rec x B). - Ht=c*(X1-U)+Dz - Hz=c*q*(X1-U)-Dt - Hpar=(Bt_cov*Ht+Bz_cov*Hz)/(J*B) - curl_delta=(Bt_cov*Dz-Bz_cov*Dt)/(J*B) - out=[("R6", "H_parallel = B(X1-U)+(B_theta Delta_zeta-B_zeta Delta_theta)/(J B)", - Hpar-B*(X1-U)-curl_delta)] - g=Delta*Bt/(c*q) - # C1 for the reconstructed perpendicular displacement, plus the supplied H. - LLrec=-B*(D+K)+Hpar - LLcode=LLrec+g - Lagcode=LLcode-Z*Bt/q - BDcode=B*(D+V-X1) - BKcode=B*K+g-F - budget=-Z*Bt/q+2*g+curl_delta+B*(V-U)-F - out.append(("R7", "lagb+Bdivxprp+Bkxprp = -xi_rec_perp^zeta Bt/q + 2 Delta Bt/(chi1 q) + curl_delta + B(V-U) - xi_rec_perp.F/B", - Lagcode+BDcode+BKcode-budget)) - out.append(("R8", "with V = U and Delta = 0: lagb+Bdivxprp+Bkxprp = -xi_perp^zeta Bt/q - xi_perp.F/B", - (Lagcode+BDcode+BKcode).subs({V: U, Delta: 0, Dt: 0, Dz: 0})+Z*Bt/q+F)) - return out - - -def cgl(sp): - """K1-K2: the PENTRC/GPEC.jl fcgl integrand against the CGL closure. - - D = div xi_perp, K = kappa.xi_perp, dB/B = -(D + K) (C1). CGL: - dp_perp/p = -D + dB/B, dp_par/p = -3D - 2 dB/B (no xi_par). Logan (2013) - Eq. 4 energy density, -(1/2)[(dp_par - dp_perp) dB/B + dp_par D]/p. - """ - D, K = sp.symbols("D K") - b = -(D + K) - dpe, dpa = -D + b, -3 * D - 2 * b - w = -((dpa - dpe) * b + dpa * D) / 2 - code = lambda k: (sp.Rational(5, 3) * D ** 2 + sp.Rational(1, 3) * (D + 3 * k) ** 2) / 2 - return [("K1", "CGL energy density = (1/2)[(5/3) D^2 + (1/3)(D + 3K)^2]", w - code(K)), - ("K2neg", "fcgl with kapx = -(dbob + divx)/2 equals the CGL density", - w - code(-(b + D) / 2))] - - -def drive_gauge(sp): - """K3: gauge invariance of the Logan (2013) Eq. 5 bounce drive. - - Along a field line (arc length l, M = 1): v = sqrt(2(E - mu B)), integrand - (1/v)[(2E - 3 mu B) dB/B + (2E - 2 mu B) div xi]. Under xi -> xi + lam B, - dB -> dB + lam B dB/dl and div xi -> div xi + B dlam/dl (C1, Park 2011 Eq. 4). - """ - l, E, mu = sp.Symbol("l"), sp.Symbol("E", positive=True), sp.Symbol("mu", positive=True) - b0, b1, l0, l1 = sp.symbols("b_0 b_1 lam_0 lam_1") - B = b0 + b1 * l - lam = l0 + l1 * l - v = sp.sqrt(2 * (E - mu * B)) - dBdl, dlam = B.diff(l), lam.diff(l) - change = ((2 * E - 3 * mu * B) * lam * dBdl + (2 * E - 2 * mu * B) * B * dlam) / v - total = (v * B * lam).diff(l) - # Multiplied by v > 0 (away from turning points): FortSym leaves the - # unmultiplied sqrt quotient UNKNOWN. - return [("K3", "v [drive change under xi -> xi + lam B - d/dl (v B lam)] = 0", - ((change - total) * v).subs(l, 0))] - - -def divergence_matrices(sp): - """G8: compare projected contravariant divergence with idcon's x/y/z bands. - - U=(g12+q*g13)/(J*B**2), V=(g23+q*g33)/(J*B**2). The perpendicular - projection gives J xi^theta=chi1**2*(-U X+V A), xi^zeta=q xi^theta-A. - U,V and their theta derivatives remain independent jets here. The field - norm supplies the only algebraic metric relation used by the z bands. - """ - J,Jp,B2,c,q,m,n,X,Xp,A,U,Ut,V,Vt=sp.symbols("J Jp B2 c q m n X Xp A U Ut V Vt") - th,ze=2*sp.pi*sp.I*m,-2*sp.pi*sp.I*n - # Differentiate J xi^theta and J xi^zeta with the product rule. - Y=c**2*(-U*X+V*A) - Yt=c**2*(-Ut*X-U*th*X+Vt*A+V*th*A) - Yz=c**2*(-U*ze*X+V*ze*A) - direct=B2*(J*Xp+Jp*X+Yt+q*Yz-J*ze*A) - bands=J*B2*Xp+(Jp*B2-c**2*B2*Ut-2*sp.pi*sp.I*(m-n*q)*c**2*B2*U)*X - bands+=(c**2*B2*Vt+2*sp.pi*sp.I*(c**2*B2*V*(m-n*q)+n*J*B2))*A - out=[("G8.1","J B**2 div xi_perp = xmat Xp + ymat X + zmat A",direct-bands)] - g22,g23,g33,g12,g13=sp.symbols("g22 g23 g33 g12 g13") - norm=c**2*(g22+2*q*g23+q**2*g33)/J**2 - zcode=c**2/J*(m*(g23+q*g33)+n*(g22+q*g23)) - zproj=(c**2*norm*((g23+q*g33)/(J*norm))*(m-n*q)+n*J*norm) - out.append(("G8.2","idcon zband2/zband3 normalization from B**2=chi1**2*(g22+2qg23+q**2g33)/J**2",zcode-zproj)) - return out - - -def gauge_coordinates(sp): - """Local Jacobian and field-strength gauge equations in angles in turns. - - B.grad = chi1/J (d_theta + q d_zeta). For a target chart J B**a=H(psi), - a=0 (Hamada) or 2 (Boozer), w=chi1 lambda B**a/H obeys D w=-T, - T=div xi + a delta B_L/B, for n!=0 (zero flux-function perturbation). - """ - B, Bt, J, chi, q, lam, Lt, Lz, d, b, c = sp.symbols( - "B Bt J chi q lam Lt Lz d b c") - m,n=sp.symbols("m n") - out=[] - for a,name in ((0,"hamada"),(2,"boozer")): - # First jet of lambda B**a; axisymmetric equilibrium, div B=0. - Ftheta = Lt*B**a + a*lam*B**(a-1)*Bt - Fzeta = Lz*B**a - direct = d + chi/J*(Lt+q*Lz) + a*(b+lam*chi/J*Bt)/B - product = d+a*b/B+chi/(J*B**a)*(Ftheta+q*Fzeta) - out.append(("Q1."+name,"T_a(xi+lambda B) = T_a(xi)+B.grad(lambda B**a)/B**a",direct-product)) - # Solving the Fourier equation, with modes exp(2 pi i(m theta-n zeta)). - T,H=sp.symbols("T H") - w=-T/(2*sp.pi*sp.I*(m-n*q)) - out.append(("Q2."+name,"D w = -T; w_m = -T_m/[2 pi i(m-nq)]",2*sp.pi*sp.I*(m-n*q)*w+T)) - # On a target base J B**a=H, correction to field strength is w d_theta B. - lambda_w = w*J/chi - out.append(("Q3."+name,"delta B_target = delta B_X + w d_theta B (J B**a flux function)", - lambda_w*chi/J*Bt-w*Bt)) - # Transform the base angles: theta_B=theta_H+iota h, phi_B=phi_H+h. - h,Dh,iota,Hh,Hb=sp.symbols("h Dh iota Hh Hb") - ht,hp=sp.symbols("ht hp") - determinant=(1+iota*ht)*(1+hp)-ht*iota*hp - out.append(("Q4","det d(theta_H+iota h,phi_H+h)/d(theta_H,phi_H)=1+D h", - determinant-1-iota*ht-hp)) - # Same physical scalar and relabeling, composed through Hamada. - bX,jX,u,v=sp.symbols("bX jX u v") - dbB=bX+u*Bt; dbH=bX+v*Bt - out.append(("Q5","Hamada->Boozer equals direct X->Boozer when w_HB=w_XB-w_XH", - dbH+(u-v)*Bt-dbB)) - # Perpendicular reconstruction is unchanged by adding a field-aligned vector. - out.append(("Q6","field-aligned input: delta B_E=0, xi_perp=0 => target delta B=0", - lam*chi/J*Bt + (-lam*chi/J)*Bt)) - out.append(("Q7","xi_perp limit: zero target source implies no gauge correction", (bX+u*Bt-bX).subs(u,0))) - # PEST J/R**2 is a flux function. Its linearized constraint has radial motion. - R,xiR,divxi,db,dr=sp.symbols("R xiR divxi db dr") - eps=sp.Symbol("eps") - ratio=J*(1+eps*divxi)/(R+eps*xiR)**2 - out.append(("Q8","delta(J/R**2)/(J/R**2)=div xi-2 xi_R/R", - ratio.diff(eps).subs(eps,0)*R**2/J-divxi+2*xiR/R)) - # Complex CGL quadratic form: real and imaginary components independently. - Dr,Di,Kr,Ki=sp.symbols("Dr Di Kr Ki") - pressure=0; code=0 - for D,K in ((Dr,Kr),(Di,Ki)): - b=-(D+K); pp=-D+b; pa=-3*D-2*b - pressure-=((pa-pp)*b+pa*D)/2 - code+=(sp.Rational(5,3)*D**2+sp.Rational(1,3)*(D+3*K)**2)/2 - out.append(("K5","Complex CGL pressure work equals [5/3 |D|**2+1/3 |D+3K|**2]/2",pressure-code)) - # Pressure term in curvature is mu0 p' xi^psi/B, not mu0 p' xi^psi. - P,X=sp.symbols("P X") - out.append(("P1","(Bk - (llagb-eulb))/B = mu0 p' xi^psi/B**2", (P*X/B)/B-P*X/B**2)) - return out - - - -def jacobian_identity(sp): - """Eq. (4.1): d/de det(d(X + e xi)/du) = d_i(J xi^i) (Jacobi formula).""" - u = sp.symbols("u_1 u_2 u_3") - eps = sp.Symbol("epsilon") - c = [[sp.Symbol(f"c_{a}{i}") for i in range(3)] for a in range(3)] - q = [[[sp.Symbol(f"q_{a}{min(i, j)}{max(i, j)}") for j in range(3)] - for i in range(3)] for a in range(3)] - pos = [sum(c[a][i] * u[i] for i in range(3)) - + sum(q[a][i][j] * u[i] * u[j] for i in range(3) for j in range(3)) / 2 - for a in range(3)] - xi_up = [sp.Symbol(f"s_{i}") + sum(sp.Symbol(f"t_{i}{j}") * u[j] for j in range(3)) - for i in range(3)] - e_cov = [[pos[a].diff(u[i]) for a in range(3)] for i in range(3)] - xi_cart = [sum(xi_up[i] * e_cov[i][a] for i in range(3)) for a in range(3)] - moved = [pos[a] + eps * xi_cart[a] for a in range(3)] - - def det3(m): - return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) - + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])) - - def at0(e): - for v in u: - e = e.subs(v, 0) - return e - - # J(X + eps xi) needs no further u-derivative: evaluate entries at u = 0 first. - jac_moved = det3([[at0(moved[a].diff(u[i])) for i in range(3)] for a in range(3)]) - jac = det3([[pos[a].diff(u[i]) for i in range(3)] for a in range(3)]) - first = jac_moved.diff(eps).subs(eps, 0) - divergence_density = at0(sum((jac * xi_up[i]).diff(u[i]) for i in range(3))) - res = first - divergence_density - return [("E4.1", "d/de J(X + e xi) = d_i(J xi^i) = J div xi", res.expand())] - -def all_identities(sp): - return (induction(sp) + fourier_symbols(sp) + incompressible_closure(sp) - + columns(sp) + pentrc_kappa_matrices(sp) - + cartesian_kappa(sp) + regularisation(sp) + regularized_column_budget(sp) - + jacobian_identity(sp) + divergence_matrices(sp) + gauge_coordinates(sp) + [c for c in cgl(sp) if not c[0].endswith("neg")] + drive_gauge(sp)) - - -def open_gaps(sp): - """Identities FortSym does not decide; their content is covered elsewhere. - - G6 is the coordinate form of C1 + C2 (proved on Cartesian 1-jets) with - det g = J^2 imposed through g_33. Neither FortSym nor SymPy decides this - large rational residual; it is recorded, not counted as proved. - """ - return force_balance_identity(sp) - - -def negative_controls(sp): - """Residuals that must NOT vanish (the claims they encode are false).""" - S = Setup(sp, metric=True) - xi, B, J, q, chi1 = S.xi, S.Bup, S.J, S.q, S.chi1 - Bm = S.jet("Bm", S.u[:2]) - B2 = S.dot(S.lower(B), B) - sigma = S.dot(S.lower(xi), B) / B2 - xperp = [xi[i] - sigma * B[i] for i in range(3)] - xs = S.xi_s(xi) - lagb_adv = xi[0] * S.d(Bm, 0) + xs / (chi1 * q) * S.d(Bm, 1) - perp_adv = sum(xperp[i] * S.d(Bm, i) for i in range(3)) - out = [("N1", "lagb is the xi_perp-only Lagrangian |B| (false)", - S.at0(lagb_adv - perp_adv))] - out.append(("N2", "lagb is Park 2009 Eq. 7 with the div-free xi (false)", - S.at0(lagb_adv - (xi[0] * S.d(Bm, 0) + xi[1] * S.d(Bm, 1))))) - out += [c for c in cgl(sp) if c[0].endswith("neg")] - return out diff --git a/docs/pmodb_gauge.rst b/docs/pmodb_gauge.rst index 7ba8fae94..5da0559f9 100644 --- a/docs/pmodb_gauge.rst +++ b/docs/pmodb_gauge.rst @@ -5,10 +5,7 @@ Perturbed field strength (pmodb) and gauge ****************************************** This page states what the columns of ``gpec_pmodb_n#.out`` are, how they are -related, and which of them a field-strength consumer needs. The identities -are proved symbolically by ``docs/derivations/pmodb_gauge/derive.py``; the -verdicts are stored in ``docs/derivations/pmodb_gauge/fortsym_verdicts.json`` -and the labels (G1, G5.5, ...) refer to that file. +related, and which of them a field-strength consumer needs. .. contents:: On this page :local: @@ -35,14 +32,14 @@ any straight-field-line Jacobian :math:`\mathcal{J}(\psi,\theta)` selected by the in-surface displacement across :math:`\mathbf B` (``xms``; ``gpec_xclebsch`` writes :math:`\xi^\alpha=\xi_s/\chi_1`). A field-aligned -displacement changes neither :math:`\xi^\psi` nor :math:`\xi_s` (G1.5), so +displacement changes neither :math:`\xi^\psi` nor :math:`\xi_s`, so :math:`(\xi^\psi,\xi_s)` determine :math:`\boldsymbol\xi_\perp` completely. The parallel part used elsewhere in GPEC is fixed in ``gpeq_contra`` by -:math:`\nabla\cdot\boldsymbol\xi=0` (G2), which gives ``xmt`` +:math:`\nabla\cdot\boldsymbol\xi=0`, which gives ``xmt`` :math:`=\mathcal J\xi^\theta` and ``xmz`` :math:`=\mathcal J\xi^\zeta`. The pmodb columns do not use ``xmt`` or ``xmz``. -For every ``jac_type`` (G1, G1F) the Jacobian-weighted perturbed field of +For every ``jac_type`` the Jacobian-weighted perturbed field of ``gpeq_sol`` is :math:`\mathcal J\,\nabla\times(\boldsymbol\xi\times\mathbf B)`. @@ -51,23 +48,23 @@ The pmodb columns ``eulb`` :math:`\delta B_E=\hat{\mathbf b}\cdot\delta\mathbf B`, the Eulerian - field strength (G3). + field strength. ``lagb`` :math:`\delta B_E+\xi^\psi\partial_\psi B+\xi_s/(\chi_1 q)\,\partial_\theta B`. This is the Lagrangian field strength of the displacement :math:`\boldsymbol\xi_g=\boldsymbol\xi_\perp+\lambda\mathbf B` with - :math:`\xi_g^\zeta=0` in the *working* chart (``jac_type``) (G5.1-G5.4). - It is neither the :math:`\boldsymbol\xi_\perp`-only value (negative - control N1) nor Park et al. (2009) Eq. 7 with the divergence-free - :math:`\boldsymbol\xi` that GPEC constructs (N2; G5.7 gives that value as - ``lagb`` :math:`+\,\xi^\zeta/q\,\partial_\theta B`). ``jac_out`` only + :math:`\xi_g^\zeta=0` in the *working* chart (``jac_type``). + It is neither the :math:`\boldsymbol\xi_\perp`-only value nor Park et + al. (2009) Eq. 7 with the divergence-free :math:`\boldsymbol\xi` that + GPEC constructs (that value is ``lagb`` + :math:`+\,\xi^\zeta/q\,\partial_\theta B`). ``jac_out`` only relabels the harmonics; it does not change this gauge. ``Bdivxprp`` :math:`B\,\nabla\cdot\boldsymbol\xi_\perp`. ``gpout_pmodb`` forms :math:`\mathcal J\boldsymbol\xi_\perp` directly from :math:`\xi^\psi` and - :math:`\xi_s` (G4.1, G4.2), + :math:`\xi_s`, .. math:: @@ -79,28 +76,28 @@ The pmodb columns :math:`f_2=(g_{\psi\theta}+qg_{\psi\zeta})/(\mathcal JB^2)` and :math:`f_3=(g_{\theta\zeta}+qg_{\zeta\zeta})/(\mathcal JB^2)` (``eqfun`` columns 2 and 3, covariant metric). The same projection gives the - ``xmat``-``zmat`` matrices of ``idcon_build`` (G8). + ``xmat``-``zmat`` matrices of ``idcon_build``. ``Bkxprp`` - :math:`\boldsymbol\xi_\perp\cdot\nabla B+\mu_0p'\xi^\psi/B` (G4.4). This is + :math:`\boldsymbol\xi_\perp\cdot\nabla B+\mu_0p'\xi^\psi/B`. This is :math:`B\boldsymbol\kappa\cdot\boldsymbol\xi_\perp` only through equilibrium force balance: exactly, :math:`B\boldsymbol\kappa\cdot\boldsymbol\xi_\perp=\boldsymbol\xi_\perp\cdot\nabla B - +\boldsymbol\xi_\perp\cdot[(\nabla\times\mathbf B)\times\mathbf B]/B` (C2). + +\boldsymbol\xi_\perp\cdot[(\nabla\times\mathbf B)\times\mathbf B]/B`. The PENTRC matrices ``smat``, ``tmat`` (``idcon_build``) form the same quantity times :math:`\mathcal J B^2` from :math:`\xi^\psi` and - :math:`\xi^\alpha` (G7). + :math:`\xi^\alpha`. ``llagb`` (``gpec_diagnostics_pmodb``, ``pmodbmn_flag``) :math:`\delta B_L(\boldsymbol\xi_\perp)=\delta B_E+\boldsymbol\xi_\perp\cdot\nabla B` - (G5.6). ``lllagb`` is :math:`-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)`, - and ``Bkxprp`` :math:`=` ``llagb`` :math:`-` ``eulb`` :math:`+\mu_0p'\xi^\psi/B` (P1). + ``lllagb`` is :math:`-(`\ ``Bdivxprp``\ :math:`+`\ ``Bkxprp``\ :math:`)`, + and ``Bkxprp`` :math:`=` ``llagb`` :math:`-` ``eulb`` :math:`+\mu_0p'\xi^\psi/B`. Relations between the columns ============================= -Gauge of ``lagb`` (G5.5, G5.8): +Gauge of ``lagb``: .. math:: @@ -108,8 +105,8 @@ Gauge of ``lagb`` (G5.5, G5.8): \qquad \xi_\perp^\zeta=-\frac{B_\theta\xi_s/\chi_1+qB_\psi\xi^\psi}{B_\theta+qB_\zeta}. -Kinematic identity (C1; any :math:`\boldsymbol\xi_\perp`, no force balance) -and its pmodb form (C2 with G4): +Kinematic identity (any :math:`\boldsymbol\xi_\perp`, no force balance) +and its pmodb form: .. math:: @@ -118,9 +115,7 @@ and its pmodb form (C2 with G4): =-(\mathrm{Bdivxprp}+\mathrm{Bkxprp})-\boldsymbol\xi_\perp\cdot\mathbf F/B, \qquad \mathbf F=(\nabla\times\mathbf B)\times\mathbf B-\mu_0\nabla p. -C1 and C2 are proved on Cartesian 1-jets. Their direct contraction in the -GPEC chart with :math:`\det g=\mathcal J^2` (G6) is too large for either CAS -to decide and is recorded as an open gap, not as proved. Hence +Hence .. math:: @@ -141,7 +136,7 @@ Regularisation With ``reg_flag`` (Park et al. 2009 Eq. 10), ``gpeq_sol`` replaces the radial derivative by :math:`R\,\partial_\psi\xi^\psi`, :math:`R=s^2/(s^2+\sigma^2)`, :math:`s=m-nq`, in the field and in ``xms``. -The regularised field is then not divergence free (R3): +The regularised field is then not divergence free: .. math:: @@ -150,8 +145,7 @@ The regularised field is then not divergence free (R3): ``Bdivxprp`` uses the same derivative ``xmp1`` as the field, and :math:`\boldsymbol\xi_\perp` is built from the same ``xms``, so the regularisation cancels between the columns and the relation above holds -unchanged with the regularised quantities (R6-R8: a bookkeeping -decomposition under the stated model, not a separate proof about the code). +unchanged with the regularised quantities. Near rational surfaces the regularised field and displacement do not form an exact ideal-induction pair. This is a limitation of the smoothing policy, not evidence of resistive physics. With ``reg_flag=f`` the @@ -166,7 +160,7 @@ PENTRC :math:`\nabla\cdot\boldsymbol\xi` of the *same* displacement; the combination is then invariant under :math:`\boldsymbol\xi\to\boldsymbol\xi+\lambda\mathbf B` up to a total - derivative along the field line (K3). Both PENTRC input paths use the + derivative along the field line. Both PENTRC input paths use the :math:`\boldsymbol\xi_\perp` pair :math:`\delta B/B=-(\nabla\cdot\boldsymbol\xi_\perp+\boldsymbol\kappa\cdot\boldsymbol\xi_\perp)` and :math:`\nabla\cdot\boldsymbol\xi_\perp`: the default path @@ -185,8 +179,8 @@ Bounce-averaged codes in perturbed Boozer or Hamada coordinates (NEO-RT, NEO-2, used as :math:`\delta B_L(\boldsymbol\xi_\perp)`. -Target gauges in GPEC units (Q1-Q8) -=================================== +Target gauges in GPEC units +=========================== Let :math:`D=\partial_\theta+q\partial_\zeta`, so :math:`\mathbf B\cdot\nabla=(\chi_1/\mathcal J)D`. @@ -218,22 +212,9 @@ factor in :math:`\partial_\theta B`. Relabeling equilibrium harmonics with ``jac_out`` alone does not solve this equation. For a PEST chart :math:`\mathcal J/R^2` is a flux function; its -linear perturbation is :math:`\nabla\cdot\boldsymbol\xi-2\xi_R/R` -(Q8). PEST output is not automatically either target gauge. Transform the -equilibrium chart first, then solve the target equation. Q4 proves the -angular-map determinant and Q5 the composition of parallel shifts. - - -Reproducing the proofs -====================== - -:: - - python3 docs/derivations/pmodb_gauge/derive.py - -This needs only SymPy and takes a few minutes, most of it the G6 attempt. -FortSym (https://github.com/lazy-fortran/fortsym) is used instead when it is -on ``PYTHONPATH``. Neither is needed to build or run GPEC. +linear perturbation is :math:`\nabla\cdot\boldsymbol\xi-2\xi_R/R`. +PEST output is not automatically either target gauge. Transform the +equilibrium chart first, then solve the target equation. History From 7bb7ef6f0b196c0df20f88a99e690ae25cb3b63c Mon Sep 17 00:00:00 2001 From: logan-nc <6198372+logan-nc@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:50:23 +0900 Subject: [PATCH 297/305] TESTS - CLEANUP - Drop the native formatter regression test Keep the field-line diagnostic fix but defer the new source-extracting test architecture until after the v1.6 release. Co-Authored-By: Claude Opus 5.5 --- regression/test/test_direct_error_message.py | 37 -------------------- 1 file changed, 37 deletions(-) delete mode 100644 regression/test/test_direct_error_message.py diff --git a/regression/test/test_direct_error_message.py b/regression/test/test_direct_error_message.py deleted file mode 100644 index 41e9c19e5..000000000 --- a/regression/test/test_direct_error_message.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Execute the production field-line failure formatter without an MHD solve.""" -import os -from pathlib import Path -import re -import shutil -import subprocess -import tempfile -import unittest - - -class DirectErrorMessage(unittest.TestCase): - @unittest.skipUnless(shutil.which('gfortran'), 'gfortran is required') - def test_step_limit_preserves_complete_diagnostic(self): - root=Path(__file__).resolve().parents[2] - source=(root/'equil/direct.f').read_text() - routine=source.split(' SUBROUTINE direct_fl_int(',1)[1].split( - ' END SUBROUTINE direct_fl_int',1)[0] - declaration=re.search(r'(?m)^ CHARACTER[^\n]*:: message',routine)[0] - format_line=re.search(r'(?m)^ 40 FORMAT[^\n]*',routine)[0] - write=re.search(r'(?m)^ WRITE\(message,40\)[^\n]*\n \$[^\n]*',routine)[0] - # Compile the actual declaration/FORMAT/WRITE from direct_fl_int. The - # small driver reaches the error path deterministically with native I/O. - program='\n'.join([' PROGRAM diagnostic',' IMPLICIT NONE',declaration, - ' INTEGER :: enstep=10000, ipsi=512',' REAL(8) :: eta=1.0D0', - format_line,write," WRITE(*,'(a)') TRIM(message)",' END','']) - scratch=Path(os.environ.get('TMPDIR',root/'build-test'));scratch.mkdir(exist_ok=True) - with tempfile.TemporaryDirectory(dir=scratch) as directory: - path=Path(directory);(path/'diagnostic.f').write_text(program) - subprocess.run(['gfortran','-ffixed-line-length-none',str(path/'diagnostic.f'), - '-o',str(path/'diagnostic')],check=True,capture_output=True) - result=subprocess.run([str(path/'diagnostic')],capture_output=True,text=True) - self.assertEqual(result.returncode,0,result.stderr) - self.assertIn('direct_int: istep = enstep = 10000',result.stdout) - self.assertIn(' at eta = 1.000E+00, ipsi = 512',result.stdout) - - -if __name__=='__main__':unittest.main() From a5272d5e04a3691f2547393c9c17f81309bfc616 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 10:09:39 -0400 Subject: [PATCH 298/305] GPEC - BUG FIX - Use each surface's own R for |grad psi| in xbnormal gpout_xbnormal formed |grad psi| from the shared r(:) without setting it, so each surface used whatever surface the previous caller left there: usually one surface behind, and on the first surface whatever gpout_singfld left. Set r(itheta) from the surface's own rs, as the gpeq_* routines do. xno, bno and bwp move by up to 0.4% near the edge and no longer depend on singfld_flag. Supersedes #353. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- gpec/gpout.f | 1 + 1 file changed, 1 insertion(+) diff --git a/gpec/gpout.f b/gpec/gpout.f index 7eb32a60d..897b60bd3 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -3488,6 +3488,7 @@ SUBROUTINE gpout_xbnormal(egnum,xspmn,spots,interpspot,nspot) eta=twopi*(theta(itheta)+rzphi%f(2)) rs(istep,itheta)=ro+rfac*COS(eta) zs(istep,itheta)=zo+rfac*SIN(eta) + r(itheta)=rs(istep,itheta) jac=rzphi%f(4) jacs(itheta)=jac w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r(itheta)/jac From 3aabb99795b78b7433770303f3594d722fb0924d Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 10:25:38 -0400 Subject: [PATCH 299/305] SLAYER - BUG FIX - Initialize lnLamb, use per-surface tau and P_tor in the estimate, and stop on bad layer inputs - sglobal.f: lnLamb was an uninitialized variable set only in params(), so gpec_slayer (which never calls params) ran with lnLamb = 0 and produced infinite S and Q. Initialize it to 17, the old PARAMETER value. - slayer.f: the growth-rate estimate loop set only D_norm per surface; w_der_del_s also reads tau and P_tor, which still held the last surface's values. Set them per surface as the matching loop does. On the DIII-D example the 2/1-5/1 estimates change by 12-25%. - params.f: stop with a message naming the bad value and the surface when n_e, t_e or t_i is not positive, when the shear is zero, or when chi_perp is not positive where the critical Delta-prime needs it. These previously gave NaN or Inf, or an unexplained non-convergence stop. - TARGETS.inc: match only v* tags in the slayer version rule, as the other codes do. On a full clone the tps-pin-* tags gave a version string past column 72, and slayer_netcdf.f failed to compile. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- install/TARGETS.inc | 2 +- slayer/params.f | 24 ++++++++++++++++++++++++ slayer/sglobal.f | 2 +- slayer/slayer.f | 2 ++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/install/TARGETS.inc b/install/TARGETS.inc index 09b715202..8c9a5f1d0 100644 --- a/install/TARGETS.inc +++ b/install/TARGETS.inc @@ -299,7 +299,7 @@ rmatch: v neededdeps $(RMATCH_OBJS) equil lsode # SLAYER ../slayer/version.inc: - @ver="`git describe --tags`"; \ + @ver="`git describe --tags --match 'v[0-9]*'`"; \ if [ ! -f $@ ] || ! grep -F "'$$ver'" $@ >/dev/null 2>&1; then \ echo ">>> Updating version file"; \ echo " CHARACTER(len=*), PARAMETER :: version ='$$ver'" | tee $@; \ diff --git a/slayer/params.f b/slayer/params.f index 459d9b3a5..709cf640e 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -87,6 +87,23 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, REAL(r8) :: K_val, Csq ! kappa/eta and composite quantity c----------------------------------------------------------------------- +c stop on inputs that would give NaN or Inf below. +c----------------------------------------------------------------------- + IF (.NOT. (n_e > 0.0_r8 .AND. t_e > 0.0_r8 .AND. + $ t_i > 0.0_r8)) THEN + WRITE(*,*) 'ERROR: params needs positive n_e, t_e and t_i,' + WRITE(*,*) ' got n_e =',n_e,' [m^-3], t_e =',t_e, + $ ' [eV], t_i =',t_i,' [eV]' + WRITE(*,*) ' at q =',qval,'. Check the kinetic profiles.' + STOP 'params: non-positive density or temperature' + END IF + IF (sval == 0.0_r8) THEN + WRITE(*,*) 'ERROR: params needs a nonzero magnetic shear,' + WRITE(*,*) ' got sval = 0 at q =',qval,'. The Alfven time' + WRITE(*,*) ' and the layer widths are undefined.' + STOP 'params: zero magnetic shear' + END IF +c----------------------------------------------------------------------- c Coulomb logarithm, basic plasma quantities, and Spitzer c resistivity. c----------------------------------------------------------------------- @@ -217,6 +234,13 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- IF (ABS(dr_val) > 0.0_r8) THEN + IF (.NOT. (chis(1) > 0.0_r8)) THEN + WRITE(*,*) 'ERROR: the critical Delta-prime needs' + WRITE(*,*) ' chi_perp > 0, got chi_perp =',chis(1), + $ ' [m^2/s] at q =',qval,'.' + WRITE(*,*) ' Set chi_p_prof in slayer.in.' + STOP 'params: chi_perp must be positive' + END IF vte = SQRT((2.0_r8*(t_e*chag)) / m_e) chi_par_smfp = (1.581_r8*tau_ee*(vte**2.0_r8)) $ / (1.0_r8 + 0.2535_r8*Zeff) diff --git a/slayer/sglobal.f b/slayer/sglobal.f index cc93fa52c..cc7f68613 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -55,7 +55,7 @@ MODULE sglobal_mod REAL(r8) :: tau ! T_i / T_e REAL(r8) :: eta ! Spitzer resistivity [Ohm*m] REAL(r8) :: visc ! anomalous viscosity [m^2/s] - REAL(r8) :: lnLamb ! Coulomb logarithm (updated at runtime) + REAL(r8) :: lnLamb = 17.0_r8 ! Coulomb log, set by params() c --- length scales REAL(r8) :: rho_s ! ion Larmor radius at T_e [m] REAL(r8) :: d_i ! ion skin depth [m] diff --git a/slayer/slayer.f b/slayer/slayer.f index 2678b4b5b..f9bc89c44 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -637,6 +637,8 @@ PROGRAM slayer $ sl_in%qval_arr(k),' rational surface' D_norm = sl_in%D_norm_arr(k) + tau = sl_in%tau_arr(k) + P_tor = sl_in%P_tor_arr(k) ! First arg is Q_e (electron diamagnetic freq), not Q ! (ExB freq). This is intentional per riccati_del_s API. dels_db=riccati_del_s(sl_in%Q_e_arr(k), From 2100323c4a03cfd183ebb42153d327fce793f170 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 25 Oct 2026 00:57:22 +0900 Subject: [PATCH 300/305] SLAYER - BUG FIX - Report unset namelist inputs, set gamma_fac in namelist mode, use per-surface shear, ds and Q in the Br threshold, drop dc_type lar, and carry |s| and n into every path Closes the six items left open in the review of #173. - slayer.f: the namelist has no silent defaults since this branch (zero means unset, or "compute" for inQ, inQ_e, inQ_i). A run that needs an unset input now stops with the variable, the value read and its v1.5 default, instead of running on zeros (inpr=0 gave LSODE atol=0, inds=0 gave 0/0 in the Riccati boundary condition). The dimensional inputs are checked when params() runs on the namelist, the Cole inputs when the single-surface riccati() block runs. That block is now also skipped for br_th_flag, which computes its own deltas per surface like the growth-rate modes. - slayer.f: gamma_fac was never set in namelist mode, so the estimate was 0. It is now rs*Re(delta_prime)/tau_r after params(), as build_inputs forms it from the r_s-referenced Delta_prime. The template with delta_prime=(5,0) estimates 102.7 Hz. - sglobal.f, growthrates.f, layerinputs.f, slayer.f: slayer_inputs_type carries shear_arr, ds_arr and Qexb_arr. The Br threshold loop used the namelist sval on every surface, Q0 = Q_e where the torque balance needs the ExB Q, and the Fitzpatrick D_norm in the Cole ds slot of riccati(). It now uses each surface's shear, ds and Q, as the other riccati() call sites do. On the DIII-D example with read_eq=t it gives finite thresholds on all five surfaces. - params.f, slayer.f: dc_type='lar' is removed. It was the rfitzp formula without the r_s normalization (lar = rfitzp/r_s), so it was dimensionally inconsistent and, corrected, a duplicate. It stops with a message like 'toroidal'. - gslayer.f: the GPEC path uses ABS(sval) in the Alfven time and n in Q_e, Q_i (GPEC passes the n = 1 diamagnetic frequencies), the same fixes ffa95f13 and d43cfb53 made in params.f and build_inputs. params.f: the namelist omega_e, omega_i carry n too. No change at n = 1 with positive shear. - DIIID README: the coupled scan needs msing_max=2 or a wider scan_width, since the Doppler shifts put the 2/1 outside the shipped window. With msing_max=2 the coupled root is omega = 30.93 krad/s, gamma = -379.6 /s. The DIII-D uncoupled run, a10 and the develop template give identical screen output and NetCDF before and after. Co-Authored-By: Claude Fable 5.1 --- docs/examples/DIIID_ideal_example/README | 2 +- docs/examples/DIIID_ideal_example/slayer.in | 2 +- input/slayer.in | 2 +- slayer/growthrates.f | 3 +- slayer/gslayer.f | 10 +- slayer/layerinputs.f | 9 +- slayer/params.f | 14 +-- slayer/sglobal.f | 5 +- slayer/slayer.f | 128 ++++++++++++++++++-- 9 files changed, 148 insertions(+), 27 deletions(-) diff --git a/docs/examples/DIIID_ideal_example/README b/docs/examples/DIIID_ideal_example/README index 59c6638b0..d27014464 100644 --- a/docs/examples/DIIID_ideal_example/README +++ b/docs/examples/DIIID_ideal_example/README @@ -35,7 +35,7 @@ STRIDE also writes ``stride_output_n1.nc``, which holds the outer-region tearing Delta' is converted from STRIDE's normalized-flux reference length to the minor-radius reference that the layer quantities use before matching. With the default ``dc_type="rfitzp"`` critical Delta, the 2/1, 3/1 and 4/1 tearing modes are all damped here, because Delta_crit exceeds Delta' on each surface. Setting ``dc_type="none"`` drops this stabilization and leaves the 2/1 weakly unstable (gamma of about 80 /s). Each root is reported in its surface's ExB frame. The results, including every root and pole found in the scan, are written to ``slayer_output_n1.nc``. -Setting ``coupling_flag=t`` instead solves the coupled multi-surface dispersion relation for the first ``msing_max`` surfaces. Each surface is then Doppler-shifted by its ExB rotation from the kinetic file, so the root is reported in the lab frame. +Setting ``coupling_flag=t`` instead solves the coupled multi-surface dispersion relation for the first ``msing_max`` surfaces. Each surface is then Doppler-shifted by its ExB rotation from the kinetic file, so the root is reported in the lab frame. The Doppler shifts spread the branches apart in Re(Q): with the shipped ``msing_max=3`` and ``scan_width=1.5`` the 2/1 branch falls outside the scan window (SLAYER prints a warning), so for the coupled run set ``msing_max=2`` or widen ``scan_width``. Satisfied with the DCON result, we run GPEC, which computes the ideal plasma response to the external 3D field from the coils defined in ``coil.in``:: diff --git a/docs/examples/DIIID_ideal_example/slayer.in b/docs/examples/DIIID_ideal_example/slayer.in index b7580351f..418990132 100644 --- a/docs/examples/DIIID_ideal_example/slayer.in +++ b/docs/examples/DIIID_ideal_example/slayer.in @@ -52,7 +52,7 @@ AMR_passes=4 ! number of adaptive refinement passes of the scan grid !scan_center=0.0 ! Re(Q) center of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan msing_max=3 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t - dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" + dc_type="rfitzp" ! critical Delta model: "rfitzp" (default) or "none" read_eq=t ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation coupling_flag=f ! solve the coupled multi-surface determinant instead of each surface separately. Requires read_eq=t diff --git a/input/slayer.in b/input/slayer.in index 09ae33cd4..6e03c57a9 100644 --- a/input/slayer.in +++ b/input/slayer.in @@ -52,7 +52,7 @@ AMR_passes=4 ! number of adaptive refinement passes of the scan grid !scan_center=0.0 ! Re(Q) center of the scan window. Unset: 0, or the middle of the Doppler-shifted branches of a coupled scan msing_max=2 ! number of rational surfaces (counted from the core) to scan, and to couple when coupling_flag=t - dc_type="rfitzp" ! critical Delta model: "rfitzp" (default), "lar", or "none" + dc_type="rfitzp" ! critical Delta model: "rfitzp" (default) or "none" read_eq=f ! read the equilibrium and Delta' from STRIDE (ncfile) and profiles from the kinetic file (infile) Pperp_Ptor_flag=t ! Use R. Fitzpatrick formalism (inc. anomalous diffusion) for growth rate calculation coupling_flag=f ! solve the coupled multi-surface determinant instead of each surface separately. Requires read_eq=t diff --git a/slayer/growthrates.f b/slayer/growthrates.f index e76de684c..667e26294 100644 --- a/slayer/growthrates.f +++ b/slayer/growthrates.f @@ -97,7 +97,8 @@ SUBROUTINE allocate_inputs(n_k,sl_in) $ sl_in%Re_dp_psi_arr(n_k),sl_in%Im_dp_psi_arr(n_k), $ sl_in%k_ref_arr(n_k),sl_in%alpha_arr(n_k), $ sl_in%D_fac_arr(n_k),sl_in%q_shift_arr(n_k), - $ sl_in%q_arr(n_k)) + $ sl_in%q_arr(n_k),sl_in%Qexb_arr(n_k),sl_in%shear_arr(n_k), + $ sl_in%ds_arr(n_k)) sl_in%q_shift_arr = 0.0_r8 c namelist inputs are taken as already r_s-referenced: identity c reference-length conversion (K = 1). diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 265316b11..7b097962c 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -169,7 +169,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, b_l=(nrs/mrs)*nrs*sval*bt/R0 ! characteristic magnetic field rho_s=1.02e-4*(mu_i*t_e)**0.5/bt ! ion Larmor radius at T_e - tau_h=R0*(mu0*rho)**0.5/(nns*sval*bt) ! Alfven transit time + tau_h=R0*(mu0*rho)**0.5/(nns*ABS(sval)*bt) ! Alfven time, |s| tau_r=mu0*rs**2.0/eta ! resistive diffusion time tau_v=tau_r/inpr ! viscous diffusion time visc= rho*rs**2.0/tau_v ! back-calculated viscosity @@ -177,10 +177,12 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, lu=tau_r/tau_h ! Lundquist number Qconv=lu**(1.0/3.0)*tau_h ! Q normalisation factor -c --- normalised frequencies +c --- normalised frequencies. GPEC passes the n = 1 diamagnetic +c frequencies (gpout.f); omega_* = n*(dp/dpsi)/(e*n_e), as in +c build_inputs (layerinputs.f). Q=Qconv*omega - Q_e=-Qconv*omega_e - Q_i=-Qconv*omega_i + Q_e=-Qconv*nns*omega_e + Q_i=-Qconv*nns*omega_i ds=lu**(1.0/3.0)*rho_s/rs ! normalised ion sound radius diff --git a/slayer/layerinputs.f b/slayer/layerinputs.f index a5532ea55..84759b5e9 100644 --- a/slayer/layerinputs.f +++ b/slayer/layerinputs.f @@ -414,7 +414,9 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) $ sl_in%Qconv_arr(msing), $ sl_in%Re_dp_psi_arr(msing), sl_in%Im_dp_psi_arr(msing), $ sl_in%k_ref_arr(msing), sl_in%alpha_arr(msing), - $ sl_in%q_shift_arr(msing), sl_in%q_arr(msing)) + $ sl_in%q_shift_arr(msing), sl_in%q_arr(msing), + $ sl_in%Qexb_arr(msing), sl_in%shear_arr(msing), + $ sl_in%ds_arr(msing)) sl_in%q_shift_arr = 0.0_r8 c --- allocate local kinetic arrays (for future NetCDF diagnostic output) @@ -661,6 +663,11 @@ SUBROUTINE build_inputs(infile,ncfile,sl_in,zeff_in,mu_i_in) sl_in%P_perp_arr(ising) = P_perp sl_in%P_tor_arr(ising) = P_tor sl_in%Qconv_arr(ising) = tauk +c the shear, Cole ds and unperturbed ExB Q as params() formed +c them, for the torque-balance (br_th_flag) path + sl_in%shear_arr(ising) = s_fitz + sl_in%ds_arr(ising) = ds + sl_in%Qexb_arr(ising) = REAL(Q,r8) ENDDO c convert the full Deltaprime matrix to the r_s reference length diff --git a/slayer/params.f b/slayer/params.f index 709cf640e..3ee8d8753 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -170,9 +170,14 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c caller does not provide them (l_n <= 0 or l_t <= 0). Callers that c compute omega_e/omega_i by other means must set Q_e and Q_i c themselves afterwards. +c omega_* = k_theta*v_* carries the toroidal mode number through +c m = n*q: the n = 1 expression is multiplied by n, as in +c build_inputs (layerinputs.f). IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN - omega_e = -t_e/(bt*R0)*(1.0_r8/l_n + 1.0_r8/l_t)*qval - omega_i = t_i/(bt*R0)*(1.0_r8/l_n + 1.0_r8/l_t)*qval + omega_e = -REAL(nn,r8)*t_e/(bt*R0) + $ *(1.0_r8/l_n + 1.0_r8/l_t)*qval + omega_i = REAL(nn,r8)*t_i/(bt*R0) + $ *(1.0_r8/l_n + 1.0_r8/l_t)*qval Q_e = -Qconv * omega_e Q_i = -Qconv * omega_i END IF @@ -228,7 +233,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c The island-width Wd is iterated to converge the c short-mfp / long-mfp interpolation for chi_parallel. c dc_type (from sglobal_mod) selects the formula: -c 'lar' -- cylindrical (Lutjens) c 'rfitzp' -- R. Fitzpatrick c default -- dc_tmp = 0 c----------------------------------------------------------------------- @@ -266,10 +270,6 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, END IF SELECT CASE(dc_type) - CASE('lar') - dc_tmp = 0.5_r8*(-dr_val)*(pi**1.5_r8) - $ *((chi_par/chis(1))**0.25_r8) - $ *( (nr*ABS(sval))/(R0*rs) )**0.5_r8 CASE('rfitzp') dc_tmp = -(SQRT(2.0_r8)*(pi**(1.5_r8))*dr_val) / Wd CASE default diff --git a/slayer/sglobal.f b/slayer/sglobal.f index cc7f68613..1abd62098 100644 --- a/slayer/sglobal.f +++ b/slayer/sglobal.f @@ -85,7 +85,7 @@ MODULE sglobal_mod c --- stability / Delta_crit REAL(r8) :: dc_tmp ! computed Delta_crit COMPLEX(r8) :: delta_eff ! (Deltaprime - Delta_crit)/S^(1/3) - CHARACTER(20) :: dc_type ! dc formula: 'rfitzp','lar','none' + CHARACTER(20) :: dc_type ! dc formula: 'rfitzp','none' c --- solver workspace / results COMPLEX(r8) :: g_tmp ! temporary complex growth rate REAL(r8) :: gamma_fac ! growth-rate conversion factor @@ -168,6 +168,9 @@ MODULE sglobal_mod REAL(r8), ALLOCATABLE :: P_perp_arr(:) ! perp Prandtl number REAL(r8), ALLOCATABLE :: P_tor_arr(:) ! toroidal Prandtl REAL(r8), ALLOCATABLE :: omegas_arr(:) ! ExB rotation per unit n [rad/s] + REAL(r8), ALLOCATABLE :: Qexb_arr(:) ! normalised ExB Q + REAL(r8), ALLOCATABLE :: shear_arr(:) ! shear for params() + REAL(r8), ALLOCATABLE :: ds_arr(:) ! Cole normalised rho_s REAL(r8), ALLOCATABLE :: q_shift_arr(:) ! ExB Doppler shift of Q REAL(r8), ALLOCATABLE :: omegas_e_arr(:) ! omega_e [rad/s] REAL(r8), ALLOCATABLE :: omegas_i_arr(:) ! omega_i [rad/s] diff --git a/slayer/slayer.f b/slayer/slayer.f index f9bc89c44..ae809ae21 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -26,6 +26,7 @@ PROGRAM slayer INTEGER :: i,j,k ! general loop indices INTEGER :: inn ! number of input-file surfaces INTEGER :: count ! generic counter + INTEGER :: nbad ! namelist inputs left unset c----------------------------------------------------------------------- c local scalars — numerical resolution. c----------------------------------------------------------------------- @@ -359,19 +360,87 @@ PROGRAM slayer ! Critical-Delta model: fail on a misspelt name rather than ! silently running without the stabilising offset. SELECT CASE (TRIM(dc_type)) - CASE ('rfitzp','lar','none','') + CASE ('rfitzp','none','') CASE ('toroidal') WRITE(*,*) "ERROR: dc_type='toroidal' is not available." WRITE(*,*) " Its geometric factor was not correct in the" WRITE(*,*) " r_s reference length and has been removed." WRITE(*,*) " Use dc_type='rfitzp'." STOP 'slayer: dc_type toroidal removed' + CASE ('lar') + WRITE(*,*) "ERROR: dc_type='lar' is not available." + WRITE(*,*) " It was the rfitzp formula without its r_s" + WRITE(*,*) " normalization (rfitzp/r_s) and has been" + WRITE(*,*) " removed. Use dc_type='rfitzp' or 'none'." + STOP 'slayer: dc_type lar removed' CASE DEFAULT WRITE(*,*) "ERROR: unknown dc_type='",TRIM(dc_type),"'." - WRITE(*,*) " Use 'rfitzp', 'lar' or 'none'." + WRITE(*,*) " Use 'rfitzp' or 'none'." STOP 'slayer: unknown dc_type' END SELECT +c----------------------------------------------------------------------- +c check that the inputs this run needs were set. Since v1.6 the +c namelist has no silent defaults: zero means "not set", or for +c inQ, inQ_e and inQ_i "compute from the profiles". A v1.5 input +c that omitted a variable is reported with the old default. +c----------------------------------------------------------------------- + nbad = 0 + ! dimensional inputs: needed when params() runs on the namelist + IF (.NOT. read_eq .AND. (params_flag .OR. est_gamma_flag .OR. + $ match_gamma_flag .OR. br_th_flag .OR. onscan_flag .OR. + $ otscan_flag .OR. ntscan_flag .OR. nbtscan_flag)) THEN + IF (mm <= 0) CALL slayer_unset('mm',REAL(mm,r8),'2',nbad) + IF (nn <= 0) CALL slayer_unset('nn',REAL(nn,r8),'1',nbad) + IF (.NOT. n_e > 0.0_r8) + $ CALL slayer_unset('n_e',n_e,'1e19 [m^-3]',nbad) + IF (.NOT. t_e > 0.0_r8) + $ CALL slayer_unset('t_e',t_e,'1e3 [eV]',nbad) + IF (.NOT. t_i > 0.0_r8) + $ CALL slayer_unset('t_i',t_i,'1e3 [eV]',nbad) + IF (sval == 0.0_r8) CALL slayer_unset('sval',sval,'2.0',nbad) + IF (.NOT. bt > 0.0_r8) CALL slayer_unset('bt',bt,'1.0',nbad) + IF (.NOT. rs > 0.0_r8) CALL slayer_unset('rs',rs,'0.5',nbad) + IF (.NOT. R0 > 0.0_r8) CALL slayer_unset('R0',R0,'1.0',nbad) + IF (.NOT. mu_i > 0.0_r8) + $ CALL slayer_unset('mu_i',mu_i,'2.0',nbad) + IF (.NOT. zeff > 0.0_r8) + $ CALL slayer_unset('zeff',zeff,'2.0',nbad) + ! the gradient lengths give omega_e, omega_i unless a + ! growth-rate run overrides both Q_e and Q_i + IF (.NOT. ((est_gamma_flag .OR. match_gamma_flag) .AND. + $ inQ_e /= 0.0_r8 .AND. inQ_i /= 0.0_r8)) THEN + IF (.NOT. l_n > 0.0_r8) + $ CALL slayer_unset('l_n',l_n,'0.2 [m]',nbad) + IF (.NOT. l_t > 0.0_r8) + $ CALL slayer_unset('l_t',l_t,'0.2 [m]',nbad) + END IF + END IF + ! Cole-model inputs: needed by the single-surface riccati() call + ! below and the legacy scans, which run unless a growth-rate or + ! Br-threshold mode is on. inpr is a namelist input there. + IF (.NOT. (est_gamma_flag .OR. match_gamma_flag .OR. + $ br_th_flag)) THEN + IF (.NOT. inpr > 0.0_r8) + $ CALL slayer_unset('inpr',inpr,'1.0',nbad) + IF (.NOT. params_flag) THEN + IF (.NOT. inds > 0.0_r8) + $ CALL slayer_unset('inds',inds,'6.0',nbad) + IF (.NOT. intau > 0.0_r8) + $ CALL slayer_unset('intau',intau,'1.0',nbad) + END IF + END IF + IF (nbad > 0) THEN + WRITE(*,*) ' Since v1.6 SLAYER sets none of these silently.' + WRITE(*,*) ' Set them in slayer.in, or set read_eq=t to take' + WRITE(*,*) ' them from STRIDE and the kinetic profile file.' + WRITE(*,*) ' Other v1.5 defaults, for reference: inQ=20,' + WRITE(*,*) ' inQ_e=2, inQ_i=-2.6, inc_beta=0.7, Q0=4,' + WRITE(*,*) ' delta_n_p=(1e-2,1e-2). Zero is a valid choice' + WRITE(*,*) ' for those (no rotation or drift).' + STOP 'slayer: required inputs unset' + END IF + ! Real copies of the mode numbers, read by params() in every ! namelist-driven branch below. build_inputs resets them from ! the STRIDE run when read_eq is set. @@ -415,9 +484,11 @@ PROGRAM slayer c skipped when the matched-gamma path is active (it computes c its own delta internally). c----------------------------------------------------------------------- -c Also skipped for the growth-rate estimate, which does not use -c it and is usually run with these namelist inputs unset. - IF (.NOT. (match_gamma_flag .OR. est_gamma_flag)) THEN +c Also skipped for the growth-rate estimate and the Br threshold, +c which do not use it and are usually run with these namelist +c inputs unset. + IF (.NOT. (match_gamma_flag .OR. est_gamma_flag .OR. + $ br_th_flag)) THEN delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] @@ -601,6 +672,9 @@ PROGRAM slayer ELSE D_norm = slayer_d_norm(D_fac,Q_e,Q_i) END IF + ! Deltaprime -> gamma conversion, formed as in build_inputs + ! from the r_s-referenced Deltaprime and the resistive time + gamma_fac = rs*REAL(delta_prime)/tau_r CALL allocate_inputs(n_k,sl_in) ! growthrates_mod CALL allocate_outputs(n_k,sl_out) ! growthrates_mod @@ -608,7 +682,9 @@ PROGRAM slayer sl_in%qval_arr = (/ mm /) sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) - !sl_in%Q_arr = (/ Q /) + sl_in%Qexb_arr = (/ REAL(Q,r8) /) + sl_in%shear_arr = (/ sval /) + sl_in%ds_arr = (/ ds /) sl_in%Q_e_arr = (/ Q_e /) sl_in%Q_i_arr = (/ Q_i /) sl_in%psi_n_arr = (/ 0.0 /) @@ -744,6 +820,9 @@ PROGRAM slayer ELSE D_norm = slayer_d_norm(D_fac,Q_e,Q_i) END IF + ! Deltaprime -> gamma conversion, formed as in build_inputs + ! from the r_s-referenced Deltaprime and the resistive time + gamma_fac = rs*REAL(delta_prime)/tau_r IF (.NOT. est_gamma_flag) THEN CALL allocate_inputs(n_k,sl_in) @@ -753,7 +832,9 @@ PROGRAM slayer sl_in%qval_arr = (/ mm /) sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) - !sl_in%Q_arr = (/ Q /) + sl_in%Qexb_arr = (/ REAL(Q,r8) /) + sl_in%shear_arr = (/ sval /) + sl_in%ds_arr = (/ ds /) sl_in%Q_e_arr = (/ Q_e /) sl_in%Q_i_arr = (/ Q_i /) sl_in%psi_n_arr = (/ 0.0 /) @@ -1192,6 +1273,9 @@ PROGRAM slayer sl_in%qval_arr = (/ mm /) sl_in%q_arr = (/ qval /) sl_in%omegas_arr = (/ omega /) + sl_in%Qexb_arr = (/ REAL(Q,r8) /) + sl_in%shear_arr = (/ sval /) + sl_in%ds_arr = (/ ds /) sl_in%Q_e_arr = (/ Q_e /) sl_in%Q_i_arr = (/ Q_i /) sl_in%psi_n_arr = (/ 0.0 /) @@ -1230,9 +1314,11 @@ PROGRAM slayer inQ_i = Q_i inpr = sl_in%P_perp_arr(k) inc_beta = sl_in%c_beta_arr(k) - inds = sl_in%D_norm_arr(k) + ! riccati() takes the Cole ds and the torque balance the + ! unperturbed ExB Q, as at the other call sites above + inds = sl_in%ds_arr(k) intau = sl_in%tau_arr(k) - Q0 = sl_in%Q_e_arr(k) + Q0 = sl_in%Qexb_arr(k) ALLOCATE(inQs(0:inum),deltal(0:inum), $ jxbl(0:inum),bal(0:inum)) @@ -1247,7 +1333,7 @@ PROGRAM slayer iloc=MAXLOC(bal) Q_sol=inQs(iloc(1)) br_th=SQRT(MAXVAL(bal)/sl_in%lu_arr(k) - $ *(sval**2.0/2.0)) + $ *(sl_in%shear_arr(k)**2.0/2.0)) sl_out%br_th_arr(k) = br_th sl_out%gamma_sol_arr(k) = 0.0 @@ -1990,5 +2076,27 @@ PROGRAM slayer DEALLOCATE(inQs,bal,js,ks,Q_sols,br_ths) ENDIF + CONTAINS +c----------------------------------------------------------------------- +c report a namelist input that this run needs but was left unset, +c with the default v1.5 used silently in its place. +c----------------------------------------------------------------------- + SUBROUTINE slayer_unset(name,val,v15,nbad) + + CHARACTER(*), INTENT(IN) :: name ! namelist variable + REAL(r8), INTENT(IN) :: val ! value read + CHARACTER(*), INTENT(IN) :: v15 ! v1.5 default + INTEGER, INTENT(INOUT) :: nbad ! running count + + IF (nbad == 0) THEN + WRITE(*,*) 'ERROR: slayer.in leaves required inputs unset:' + END IF + WRITE(*,'(4x,a,a,es10.3,a,a,a)') name,' = ',val, + $ ' (v1.5 default: ',v15,')' + nbad = nbad+1 + + RETURN + END SUBROUTINE slayer_unset + END PROGRAM slayer From b7fb8a497e42e150570380be1761f65eeae780d1 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 25 Oct 2026 02:11:45 +0900 Subject: [PATCH 301/305] SLAYER - BUG FIX - Check qval and the params_flag path in the unset-input report, and say plainly where GPEC's n enters Follow-ups from an independent review of 2100323c: - qval is needed with l_n and l_t for the namelist omega_e, omega_i; without it params() stopped in slayer_d_norm with a Q_e = Q_i message instead of naming qval. It is now in the report (v1.5 default 2.0). - The IF (params_flag) block calls params() on the namelist values whatever read_eq is, and params_flag defaults to true, so the dimensional inputs are now checked whenever params_flag is set, not only when read_eq is false. read_eq=t with params_flag=t and no density now reports n_e instead of failing inside params(). - The report's hint about read_eq applies to the dimensional inputs only; reworded. The Cole-input comment no longer says the legacy scans are skipped by est_gamma_flag (they are not). - gslayer.f, params.f comments: GPEC hands gpec_slayer the bare diamagnetic frequencies with no toroidal mode number attached, and GPEC's own nn multiplies them there; slayer.in is not read by GPEC. The DIII-D run, a10 and the develop template are unchanged. Co-Authored-By: Claude Fable 5.1 --- slayer/gslayer.f | 8 +++++--- slayer/params.f | 4 ++-- slayer/slayer.f | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 7b097962c..3ce664472 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -177,9 +177,11 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, lu=tau_r/tau_h ! Lundquist number Qconv=lu**(1.0/3.0)*tau_h ! Q normalisation factor -c --- normalised frequencies. GPEC passes the n = 1 diamagnetic -c frequencies (gpout.f); omega_* = n*(dp/dpsi)/(e*n_e), as in -c build_inputs (layerinputs.f). +c --- normalised frequencies. GPEC passes the bare diamagnetic +c frequencies (dp/dpsi)/(e*n_e) with no toroidal mode number +c attached (gpout.f); omega_* = k_theta*v_* = n*(dp/dpsi)/(e*n_e), +c so GPEC's n (nns) multiplies them here, as in build_inputs +c (layerinputs.f). Q=Qconv*omega Q_e=-Qconv*nns*omega_e Q_i=-Qconv*nns*omega_i diff --git a/slayer/params.f b/slayer/params.f index 3ee8d8753..d503f66cd 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -171,8 +171,8 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c compute omega_e/omega_i by other means must set Q_e and Q_i c themselves afterwards. c omega_* = k_theta*v_* carries the toroidal mode number through -c m = n*q: the n = 1 expression is multiplied by n, as in -c build_inputs (layerinputs.f). +c m = n*q: the bare gradient-length expression is multiplied by +c n, as in build_inputs (layerinputs.f). IF (l_n > 0.0_r8 .AND. l_t > 0.0_r8) THEN omega_e = -REAL(nn,r8)*t_e/(bt*R0) $ *(1.0_r8/l_n + 1.0_r8/l_t)*qval diff --git a/slayer/slayer.f b/slayer/slayer.f index ae809ae21..941e94292 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -387,9 +387,10 @@ PROGRAM slayer c----------------------------------------------------------------------- nbad = 0 ! dimensional inputs: needed when params() runs on the namelist - IF (.NOT. read_eq .AND. (params_flag .OR. est_gamma_flag .OR. + ! (params_flag does so even with read_eq) + IF (params_flag .OR. (.NOT. read_eq .AND. (est_gamma_flag .OR. $ match_gamma_flag .OR. br_th_flag .OR. onscan_flag .OR. - $ otscan_flag .OR. ntscan_flag .OR. nbtscan_flag)) THEN + $ otscan_flag .OR. ntscan_flag .OR. nbtscan_flag))) THEN IF (mm <= 0) CALL slayer_unset('mm',REAL(mm,r8),'2',nbad) IF (nn <= 0) CALL slayer_unset('nn',REAL(nn,r8),'1',nbad) IF (.NOT. n_e > 0.0_r8) @@ -406,7 +407,7 @@ PROGRAM slayer $ CALL slayer_unset('mu_i',mu_i,'2.0',nbad) IF (.NOT. zeff > 0.0_r8) $ CALL slayer_unset('zeff',zeff,'2.0',nbad) - ! the gradient lengths give omega_e, omega_i unless a + ! the gradient lengths and q give omega_e, omega_i unless a ! growth-rate run overrides both Q_e and Q_i IF (.NOT. ((est_gamma_flag .OR. match_gamma_flag) .AND. $ inQ_e /= 0.0_r8 .AND. inQ_i /= 0.0_r8)) THEN @@ -414,11 +415,13 @@ PROGRAM slayer $ CALL slayer_unset('l_n',l_n,'0.2 [m]',nbad) IF (.NOT. l_t > 0.0_r8) $ CALL slayer_unset('l_t',l_t,'0.2 [m]',nbad) + IF (.NOT. qval > 0.0_r8) + $ CALL slayer_unset('qval',qval,'2.0',nbad) END IF END IF ! Cole-model inputs: needed by the single-surface riccati() call - ! below and the legacy scans, which run unless a growth-rate or - ! Br-threshold mode is on. inpr is a namelist input there. + ! below, which runs unless a growth-rate or Br-threshold mode is + ! on, and by the legacy scans. inpr is a namelist input there. IF (.NOT. (est_gamma_flag .OR. match_gamma_flag .OR. $ br_th_flag)) THEN IF (.NOT. inpr > 0.0_r8) @@ -431,9 +434,10 @@ PROGRAM slayer END IF END IF IF (nbad > 0) THEN - WRITE(*,*) ' Since v1.6 SLAYER sets none of these silently.' - WRITE(*,*) ' Set them in slayer.in, or set read_eq=t to take' - WRITE(*,*) ' them from STRIDE and the kinetic profile file.' + WRITE(*,*) ' Since v1.6 SLAYER sets none of these silently;' + WRITE(*,*) ' set them in slayer.in. (With read_eq=t and' + WRITE(*,*) ' params_flag=f the dimensional inputs come from' + WRITE(*,*) ' STRIDE and the kinetic profile file instead.)' WRITE(*,*) ' Other v1.5 defaults, for reference: inQ=20,' WRITE(*,*) ' inQ_e=2, inQ_i=-2.6, inc_beta=0.7, Q0=4,' WRITE(*,*) ' delta_n_p=(1e-2,1e-2). Zero is a valid choice' From ba7a17f922a74b46d446cb3c60fe4e96a6f644c9 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 25 Oct 2026 02:55:43 +0900 Subject: [PATCH 302/305] SLAYER - BUG FIX - Carry n into the ExB Q, pass ds to the Cole stabscan, guard the Br threshold against NaN, and report Q_sol at the right grid point Closes the remaining points from the independent review of #173 so that 1.6.0 leaves no SLAYER follow-ups behind. - gslayer.f, params.f: the normalised ExB Q now carries the toroidal mode number like Q_e and Q_i already did (Q = Qconv*n*omega_E), the Doppler frequency the layer sees and the convention of the coupled determinant's shift. Before, the torque balance 2*pr*(Q0 - Q) compared an n-free Q0 with n-scaled Q_e for n >= 2. gpec_slayer's omega_sol is divided by n again so it stays a fluid rotation frequency. GPEC passes the bare frequencies and its own nn; slayer.in is never read by GPEC. No change at n = 1. - slayer.f: the stability scan with Pperp_Ptor_flag=f passed the Fitzpatrick D_norm in riccati()'s Cole ds slot; it now gets the surface's ds, as the Br threshold does since 2100323c. - slayer.f: the single-surface baseline riccati() is also skipped for input_flag, whose per-surface results never used it, and the Cole inputs are required whenever a legacy scan will run. - slayer.f: the Br threshold (bal_flag and br_th_flag) ignores non-finite torque-balance entries and reports 0 with a warning when the balance has no positive maximum, as gpec_slayer does, instead of NaN. The a10 example now prints br_th = 0 with the warning. - slayer.f: MAXLOC counts from 1 while the scan arrays start at 0, so every reported Q_sol was one grid step above the maximum. Fixed at all seven sites. gpec_slayer uses the same point only as the centre of its refinement bracket, so it is left as is and GPEC's output is unchanged. - delta.f: riccati()'s inds argument is ds, the normalised ion sound radius, not a shear. The DIII-D run and the develop template are unchanged; the a10 example's Q_sol moves from 0.05 to 0.0 and its br_th from NaN to 0. Co-Authored-By: Claude Fable 5.1 --- slayer/delta.f | 2 +- slayer/gslayer.f | 15 ++++++------ slayer/params.f | 4 +++- slayer/slayer.f | 62 ++++++++++++++++++++++++++++++++++++------------ 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/slayer/delta.f b/slayer/delta.f index 739dc74d1..7eb4500a3 100644 --- a/slayer/delta.f +++ b/slayer/delta.f @@ -52,7 +52,7 @@ FUNCTION riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe, REAL(r8),INTENT(IN) :: inpr ! Prandtl number REAL(r8),INTENT(IN) :: inpe ! electron Prandtl number REAL(r8),INTENT(IN) :: inc_beta ! c_beta parameter - REAL(r8),INTENT(IN) :: inds ! magnetic shear ds + REAL(r8),INTENT(IN) :: inds ! ds, normalised rho_s REAL(r8),INTENT(IN) :: intau ! ion-to-electron temp ratio c --- optional arguments REAL(r8),INTENT(IN),OPTIONAL :: iinQ ! imaginary part of Q diff --git a/slayer/gslayer.f b/slayer/gslayer.f index 3ce664472..fce07358e 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -177,12 +177,13 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, lu=tau_r/tau_h ! Lundquist number Qconv=lu**(1.0/3.0)*tau_h ! Q normalisation factor -c --- normalised frequencies. GPEC passes the bare diamagnetic -c frequencies (dp/dpsi)/(e*n_e) with no toroidal mode number -c attached (gpout.f); omega_* = k_theta*v_* = n*(dp/dpsi)/(e*n_e), -c so GPEC's n (nns) multiplies them here, as in build_inputs -c (layerinputs.f). - Q=Qconv*omega +c --- normalised frequencies. GPEC passes the bare ExB and +c diamagnetic frequencies with no toroidal mode number attached +c (gpout.f). The layer sees the mode at the Doppler frequency +c n*omega_E, and omega_* = k_theta*v_* = n*(dp/dpsi)/(e*n_e), so +c GPEC's n (nns) multiplies all three here, as in build_inputs +c and the coupled determinant's Doppler shift (slayer.f). + Q=Qconv*nns*omega Q_e=-Qconv*nns*omega_e Q_i=-Qconv*nns*omega_i @@ -291,7 +292,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, rhi=xpk+2.0*rdq ENDDO Q_sol=xpk - omega_sol=xpk/Qconv + omega_sol=xpk/(Qconv*nns) ! back to a fluid rotation frequency ! If even the refined nose is non-positive the surface has no ! finite penetration threshold; floor at zero and warn rather ! than propagating a NaN into b_crit/Phi_res_crit downstream. diff --git a/slayer/params.f b/slayer/params.f index d503f66cd..140bcf093 100644 --- a/slayer/params.f +++ b/slayer/params.f @@ -164,7 +164,9 @@ SUBROUTINE params(n_e,t_e,t_i,omega,chis,dr_val,dgeo_val, c----------------------------------------------------------------------- Qconv = lu**(1.0_r8/3.0_r8) * tau_h ! frequency normalisation (Cole) tauk = Qconv ! stored in sglobal_mod - Q = Qconv * omega ! normalised rotation frequency +c the layer sees the mode at the Doppler frequency n*omega_E, as in +c the coupled determinant's shift (slayer.f) and gpec_slayer + Q = Qconv * REAL(nn,r8) * omega ! normalised ExB frequency c Diamagnetic frequencies require gradient lengths; skip when the c caller does not provide them (l_n <= 0 or l_t <= 0). Callers that diff --git a/slayer/slayer.f b/slayer/slayer.f index 941e94292..9d199a3a3 100644 --- a/slayer/slayer.f +++ b/slayer/slayer.f @@ -420,10 +420,16 @@ PROGRAM slayer END IF END IF ! Cole-model inputs: needed by the single-surface riccati() call - ! below, which runs unless a growth-rate or Br-threshold mode is - ! on, and by the legacy scans. inpr is a namelist input there. + ! below, which runs unless a per-surface mode is on, and by the + ! legacy scans, which run unless a mode that stops first is on. + ! inpr is a namelist input in all of them. IF (.NOT. (est_gamma_flag .OR. match_gamma_flag .OR. - $ br_th_flag)) THEN + $ br_th_flag .OR. input_flag) .OR. + $ (.NOT. (match_gamma_flag .OR. br_th_flag) .AND. + $ (bal_flag .OR. stability_flag .OR. riccatiscan_flag .OR. + $ QPscan_flag .OR. QPescan_flag .OR. QPscan2_flag .OR. + $ QDscan2_flag .OR. Qscan_flag .OR. onscan_flag .OR. + $ otscan_flag .OR. ntscan_flag .OR. nbtscan_flag))) THEN IF (.NOT. inpr > 0.0_r8) $ CALL slayer_unset('inpr',inpr,'1.0',nbad) IF (.NOT. params_flag) THEN @@ -488,11 +494,11 @@ PROGRAM slayer c skipped when the matched-gamma path is active (it computes c its own delta internally). c----------------------------------------------------------------------- -c Also skipped for the growth-rate estimate and the Br threshold, -c which do not use it and are usually run with these namelist -c inputs unset. +c Also skipped for the growth-rate estimate, the Br threshold and +c the multi-surface input file, which compute their own deltas per +c surface and are usually run with these namelist inputs unset. IF (.NOT. (match_gamma_flag .OR. est_gamma_flag .OR. - $ br_th_flag)) THEN + $ br_th_flag .OR. input_flag)) THEN delta=riccati(inQ,inQ_e,inQ_i,inpr,inc_beta,inds,intau,inpe) psi0=1.0/ABS(delta+delta_n_p) ! reconnected flux [a.u.] jxb=-AIMAG(1.0/(delta+delta_n_p)) ! j x B torque [a.u.] @@ -568,6 +574,7 @@ PROGRAM slayer ENDDO iloc=MAXLOC(bal) + iloc(1)=iloc(1)-1 ! MAXLOC counts from 1, inQs from 0 Q_soll(k)=inQs(iloc(1)) br_thl(k)=sqrt(MAXVAL(bal)/lu*(svals(k)**2.0/2.0))*1e4 @@ -1086,8 +1093,9 @@ PROGRAM slayer IF (Pperp_Ptor_flag) THEN delta=riccati_f() ELSE + ! the Cole riccati() takes ds, not D_norm delta=riccati(iing_coarse,Q_e,Q_i,P_perp, - $ c_beta,D_norm,tau,pe, + $ c_beta,sl_in%ds_arr(k),tau,pe, $ iinQ=ing_coarse) END IF inQs(i) = ing_coarse @@ -1334,10 +1342,21 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxbl(i) ENDDO - iloc=MAXLOC(bal) - Q_sol=inQs(iloc(1)) - br_th=SQRT(MAXVAL(bal)/sl_in%lu_arr(k) - $ *(sl_in%shear_arr(k)**2.0/2.0)) + ! as in gpec_slayer: ignore non-finite entries (0/0, or a + ! near-zero jxb) and floor at zero with a warning when the + ! balance has no positive maximum, rather than a NaN + iloc=MAXLOC(bal,MASK=(bal==bal .AND. ABS(bal)= 0 .AND. bal(MAX(iloc(1),0))>0.0_r8) THEN + br_th=SQRT(bal(iloc(1))/sl_in%lu_arr(k) + $ *(sl_in%shear_arr(k)**2.0/2.0)) + ELSE + br_th=0.0_r8 + WRITE(*,'(1x,a,i0,a)') "!! WARNING: torque balance "// + $ "has no positive maximum at m=",sl_in%qval_arr(k), + $ "; br_th set to 0" + ENDIF sl_out%br_th_arr(k) = br_th sl_out%gamma_sol_arr(k) = 0.0 @@ -1399,9 +1418,18 @@ PROGRAM slayer CLOSE(out_unit) ENDIF - iloc=MAXLOC(bal) - Q_sol=inQs(iloc(1)) - br_th=sqrt(MAXVAL(bal)/lu*(sval**2.0/2.0))*1e4 + ! as in gpec_slayer: ignore non-finite entries and floor at + ! zero with a warning when the balance has no positive maximum + iloc=MAXLOC(bal,MASK=(bal==bal .AND. ABS(bal)= 0 .AND. bal(MAX(iloc(1),0))>0.0_r8) THEN + br_th=sqrt(bal(iloc(1))/lu*(sval**2.0/2.0))*1e4 + ELSE + br_th=0.0_r8 + WRITE(*,*)"!! WARNING: torque balance has no positive "// + $ "maximum; br_th set to 0" + ENDIF WRITE(*,*)"Q_sol=",Q_sol WRITE(*,*)"br_th=",br_th DEALLOCATE(inQs,deltal,jxbl,bal) @@ -1785,6 +1813,7 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO iloc=MAXLOC(bal) + iloc(1)=iloc(1)-1 ! MAXLOC counts from 1, inQs from 0 Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) @@ -1871,6 +1900,7 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO iloc=MAXLOC(bal) + iloc(1)=iloc(1)-1 ! MAXLOC counts from 1, inQs from 0 Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"t_e=",t_e*ks(j,k),"br_ths=",br_ths(j,k) @@ -1957,6 +1987,7 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO iloc=MAXLOC(bal) + iloc(1)=iloc(1)-1 ! MAXLOC counts from 1, inQs from 0 Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) @@ -2044,6 +2075,7 @@ PROGRAM slayer bal(i)=2.0*inpr*(Q0-inQs(i))/jxb ENDDO iloc=MAXLOC(bal) + iloc(1)=iloc(1)-1 ! MAXLOC counts from 1, inQs from 0 Q_sols(j,k)=inQs(iloc(1)) br_ths(j,k)=sqrt(MAXVAL(bal)/lu)*1e4 WRITE(*,*)"br_ths=",br_ths(j,k) From 076a1e66f330eb22a0e3e6310c92328640fd1b7b Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Sat, 24 Oct 2026 21:37:35 +0200 Subject: [PATCH 303/305] SLAYER - MINOR - rename output file extension to match .out convention --- slayer/gslayer.f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slayer/gslayer.f b/slayer/gslayer.f index fce07358e..76653d162 100644 --- a/slayer/gslayer.f +++ b/slayer/gslayer.f @@ -249,7 +249,7 @@ SUBROUTINE gpec_slayer(n_e,t_e,n_i,t_i,zeff,omega,omega_e, c --- optionally write torque balance curve to ASCII file IF(ascii_flag)THEN OPEN(UNIT=out_unit,FILE="gpec_slayer_torque_balance_m"// - $ TRIM(l_sm)//"_n"//TRIM(l_sn)//".OUT", + $ TRIM(l_sm)//"_n"//TRIM(l_sn)//".out", $ STATUS="UNKNOWN") WRITE(out_unit,'(1x,5(a17))') "inQ","RE(delta)", $ "IM(delta)","jxb","bal" From d9ad5a242dd1c55e60fa87a7afe6b7da6a7da79d Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Sat, 24 Oct 2026 22:02:35 +0200 Subject: [PATCH 304/305] PENTRC - BUGFIX - take complex conjugate of bwspl to fix torque in case of up-down-asymmetric equilibria --- pentrc/torque.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pentrc/torque.F90 b/pentrc/torque.F90 index 7e594bd71..e9354632c 100644 --- a/pentrc/torque.F90 +++ b/pentrc/torque.F90 @@ -786,8 +786,8 @@ subroutine tpsi(tpsi_var,psi,n,l,zi,mi,wdfac,divxfac,electron, & enddo ! build complete action mxm matrices W_X, W_Y, W_Z - wmmt(1,:) = bwspl(1)%fsi(bwspl(1)%mx,:) - wemt(1,:) = bwspl(2)%fsi(bwspl(2)%mx,:) + wmmt(1,:) = conjg(bwspl(1)%fsi(bwspl(1)%mx,:)) + wemt(1,:) = conjg(bwspl(2)%fsi(bwspl(2)%mx,:)) wxmt = matmul(wmmt,xmat) wymt = matmul(wmmt,3*smat+ymat)-2.0*matmul(wemt,smat) wzmt = matmul(wmmt,3*tmat+zmat)-2.0*matmul(wemt,tmat) From cb971298804aaad4c52bea9a1159a4a05b799203 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sat, 24 Oct 2026 16:26:40 -0400 Subject: [PATCH 305/305] ALL - BUMP - Update version to 1.6.0 and document changes in release notes Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMDGyfQKRZQMdk3NJXsKQE --- docs/conf.py | 4 +- docs/releases.rst | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 046fe5f3f..12c00f0a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,9 +61,9 @@ # built documents. # # The short X.Y version. -version = '1.5' +version = '1.6' # The full version, including alpha/beta/rc tags. -release = '1.5.7' +release = '1.6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/releases.rst b/docs/releases.rst index f24f36b92..17d70c578 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -10,6 +10,129 @@ Note, backwards compatibility is defined as the ability to return to a previous The `github release notes `_ are reproduced below. +GPEC v1.6.0 +=========== + +This is planned to be the last minor release of the Fortran GPEC 1.x series. +New feature development continues in the Julia GPEC 2.x code; the 1.6.x line will receive bug-fix patch releases only. +GPEC is now distributed under the MIT license. + +Read the Upgrade notes first: the build system, several binary interface files, and a few namelist and output names changed, and many numerical results change because long-standing bugs were fixed (see Fixes). + +Upgrade notes +------------- +- INSTALL - The build system was rewritten. Build only from ``install/`` with ``make`` (``make -j N`` is supported); the per-directory makefiles are gone. ``FC`` and ``CC`` must be set, git is required at build time, and missing netCDF/HDF5/OpenBLAS are built from bundled git submodules (``git submodule update --init``). ``DEPSINSTALLDIR`` installs them to a shared location; ``make realclean`` is required before switching compilers. +- ALL - Rerun the full chain after upgrading. DCON ``euler.bin`` has a new record (``wv_farwall_flag``), and the resistive interface files ``delta.bin``, ``gal_solution.bin``, ``globalsol.bin`` and ``vmat.bin`` changed format, so RDCON, RMATCH and GPEC must be rerun together. +- DCON, RDCON - ``vacuum.bin`` is no longer written by default (set ``bin_vac=t``). GPEC always recomputes the vacuum matrices and ignores ``ivacuumfile``. ``ahg2msc.out`` is no longer written; vacuum data are passed in memory (``out_ahg2msc`` in ``equil.in`` restores the files). +- RDCON - ``coil%rpec_flag`` moved from ``&GAL_OUTPUT`` to ``&RDCON_CONTROL`` in ``rdcon.in``. Setting it in ``&GAL_OUTPUT`` still works but prints a deprecation warning. +- RMATCH - ``ntor`` is no longer an input (n is read from ``delta.bin``); remove it from any ``rmatch.in`` that sets it. The default inner-layer solver is now ``inps_type="inps"`` with ``nx=512``, and the eigenvalue search uses the ``2*pi*n*Omega`` rotation convention of RPEC. +- DCON, PENTRC, COIL, STRIDE - Thread counts default to ``OMP_NUM_THREADS`` (inputs default to 0). ``parallel_threads`` is renamed ``dcon_kin_threads`` and ``openmp_threads`` is renamed ``pentrc_threads``; the old names still work with a deprecation warning. +- EQUIL - ``newq0 < 0`` (including the undocumented ``newq0=-1`` sign flip) now stops with an error. ``eq_type`` ``marklin_direct``, ``marklin_inverse`` and ``hansen_inverse`` were removed; ``chease2`` is renamed ``chease_ascii`` (the old name still works with a warning). +- GPEC - Renamed outputs: netCDF ``w_isl_crit`` is now ``w_isl_v_crit``; cylindrical ``vcbr``, ``vcbz``, ``vcbp`` are now ``b_r_vacuum``, ``b_z_vacuum``, ``b_p_vacuum``. The ``gpec_singfld_n#.out`` ASCII columns changed (new threshold, island-width and pressure columns). ``I_overlap_norm`` and the DCON/RDCON ``W_v_eigenvalue`` are now 1-D real arrays. +- RDCON - The netCDF output is ``rdcon_output_n#.nc`` (it no longer overwrites DCON's ``dcon_output_n#.nc``). +- PENTRC - With ``pmodb_file``, PENTRC now reads the supplied ``Bdivxi_perp`` column. Regenerate ``gpec_pmodb_n#.out`` with GPEC 1.6 before using it. +- DOCS - All DIII-D examples now use a public TokaMaker DIII-D-like H-mode equilibrium and kinetic profiles in place of the KEFIT 147131 files. + +Adds +------ +- SLAYER - New slab-layer linear drift-MHD module (Park, PoP 2022) with a standalone ``slayer`` executable, ``input/slayer.in`` and an a10 example. +- SLAYER - Tearing-mode growth rates at the STRIDE rational surfaces by matching the inner-layer and outer Delta' (optional critical Delta, ``dc_type``), per surface or coupled, with the E x B Doppler shift (``slayer_output_n#.nc``; see the DIII-D ideal example). +- GPEC - SLAYER resonant-field penetration threshold at each rational surface (``singthresh_slayer_flag``; ``singthresh_callen_flag`` selects the Callen threshold and ``singthresh_flag`` enables both). New netCDF ``Phi_res_crit``. +- GPEC - Prandtl number for the SLAYER threshold set by ``singthresh_slayer_inpr`` (default 5.0) or per rational surface by ``singthresh_slayer_inpr_prof``. +- GPEC - Callen saturated and minimum island widths (``w_isl_sat``, ``w_isl_min``) and the Callen critical resonant field ``Phi_res_crit_callen``. +- GPEC - Estimate of the pressure lost to Callen-saturated islands (``dP_res``, ``P_res`` and the modified pressure profile ``p_mod``). +- GPEC - Bernstein/Chance decomposition of the eigenmode potential energy (``effective_field_flag``, ``standard_form_flag``, ``effective_field_terms_flag``, ``curl_check_flag``, ``recon_out``, ``recon_int``) with ``gpec_recon*`` outputs and a derivation note in ``docs/tex/recon``. +- GPEC - Mutual inductance between the plasma surface and a conformal wall and its inverse (``M_w``, ``M_w_inv``); ``wv_farwall_flag`` in ``dcon.in`` keeps no-wall eigenmodes for GPEC while the wall inductance is computed; ``mutual_test_flag`` diagnostics. +- GPEC - Resistive perturbed equilibria (RPEC) carry the analytic Galerkin derivative d(xi)/dpsi and the RMATCH field through to GPEC. Per-surface resistive sweet spots from the Lundquist number (``use_res_spot``, ``sing_resspot``, ``sing_interpspot``) and new outputs ``sing_spots``, ``Sfac``, ``tauA``, ``tauR``, ``Jbgradpsi_rmatch`` and ``b_pen``. +- GPEC - Dominant-mode overlap outputs ``dominant_mode_overlap`` and ``dominant_mode_local_overlap``. +- GPEC - Full set of DCON plasma attributes and local SLAYER inputs in the profile and control netCDF outputs. +- COIL - Shift and tilt coils from ``coil.in`` (``coil_shift[xyz]``, ``coil_tilt[xyz]``, ``coil_[xyz]nom``, ``cnpert``), with optional netCDF output of the modified geometry (``coil_out``). +- COIL - OpenMP-parallel Biot-Savart fields with a new ``coil_threads`` input; the maximum number of coils is raised from 50 to 500; MAST-U, JET ex-vessel and extra ITER coil sets. +- RDCON - netCDF output ``rdcon_output_n#.nc`` (``netcdf_out``, default true) with the ``dcon.out`` content, the Delta, Delta', Delta_gw and Delta_coil matrices and the Galerkin cell layout. +- RDCON - Optional modified Rutherford equation terms (``MRE_flag``, ``geom_flag``) with a radial Zeff profile (``Zeff``, ``psi_N_Zeff``). +- RDCON - STRIDE-style truncation controls ``qlow``, ``qhigh`` and ``reform_eq_with_psilim``; ``gnstep`` sets the maximum LSODE steps for the Galerkin basis; ``interp_np_res`` refines output in resonant cells. +- MATCH - ``sol_num`` selects which DCON eigenfunction MATCH reconstructs. +- PENTRC, DCON - ``kinetic_format="rates-v1"`` reads a 9-column kinetic file that supplies logLambda, nu_i and nu_e directly (default ``"legacy"`` unchanged). +- PENTRC - Separate energy-integration tolerances ``atol_x``, ``rtol_x`` (tighter by default, so runs take longer); Zeff and ``omega_p`` in the netCDF profile output. +- EQUIL - ``profile_source="integrate"`` builds F and p from the g-file's FF' and p' (recommended only for single-precision g-files such as TokaMaker's); default ``"values"`` is unchanged. +- EQUIL - ``ldp_i`` (TokaMaker i-file) reader accepts F < 0 and an optional ``[psi_axis, psi_lcfs]`` record. +- EQUIL - ``enstep`` sets the maximum field-line integration steps in ``direct_run`` (default 16384, previously fixed at 2048). +- EQUIL - LAR ``sigma_type='wesson'`` current profile; ``chease_ascii`` eq_type. +- VACUUM - New Green's function evaluation (Chance 2007), more accurate at high n; ``use_legacy_greens_function`` restores the old one. +- INSTALL - gfortran (with OpenMP), ifx and ARM macOS support; ``OPENBLASHOME``; ``make deps`` / ``make checkdeps``; build instructions for NERSC, SNU 3DST, PLARE and WSL. +- CI - GitHub Actions builds with gfortran and ifort and runs Solov'ev ideal, kinetic, resistive and energy-decomposition examples on every pull request. +- DOCS - Installation page, sign-conventions reference, resonant coupling, energy-decomposition and pmodb gauge output docs, and the ``docs/tex`` notes (moved from ``tex/``). + +Changes +-------- +- ALL - GPEC is released under the MIT license (``install/LICENSE``), replacing the previous user agreement. +- ALL - Floating-point kinds are standardized on ``REAL(r8)``; LSODE updated to Fortran 2018; duplicate utility, grid, spline and jacobi code consolidated; obsolete files removed. +- ALL - Consistent ``!! WARNING`` terminal messages; GPEC reports CPU and wall time separately for OpenMP runs. +- DCON - Stops with a clear error when ``nn = 0``. +- GPEC - Singular-coupling overlap outputs have correct units (Tesla, or % for ``*_overlap_norm``) and consistent descriptions. +- GPEC - ``singthresh_callen_flag`` forces ``vsingfld_flag`` when coils are present; ``gal_flag`` runs always use the resistive sweet spot. +- RDCON - Free-boundary energies use the DCON vacuum-call pattern and a complex eigen-solve; ``euler.bin`` stores the complex eigenvalues. +- RDCON - ``solver="cholesky"`` switches to LU with a warning when ``rpec_flag=t``. +- RDCON, STRIDE - Modules renamed with ``rdcon_``/``stride_`` prefixes; STRIDE builds without OpenMP and uses ``OMP_SET_MAX_ACTIVE_LEVELS``. +- STRIDE - Banded solve of the Delta' boundary-value problem (faster at high n). +- RDCON - Lower peak memory in the LU Galerkin solve. +- PENTRC - Exact-zero ``omega_E`` knots are replaced by 1e-9 rad/s with a warning. +- PENTRC, STRIDE - LSODE failures in the PENTRC energy and radial integrations and ZVODE failures in STRIDE stop with a nonzero exit status. +- EQUIL - Tabulated Zeff and kinetic profiles use monotone (pchip) fits. +- DOCS - Template and example decks document the new inputs (including ``enstep`` in ``equil.in``), ``rmatch.in`` is fully commented, and the a10 example writes the PENTRC ASCII tables. + +Fixes +------ +- PENTRC - ``fcgl`` recovers all of kappa.xi_perp from dB/B and div(xi_perp), not half. +- PENTRC - Removed a spurious factor of the major radius from the general-aspect-ratio ``omega_D`` (``fgar``, ``tgar``, ``pgar``, ``*mm``); recompute results from these methods. +- PENTRC - Removed spurious factors of ``sqrt(B0)`` from the ``clar`` ``omega_b`` and ``B0`` from its ``omega_D``. +- PENTRC - Fixed OpenMP data races (energy contour flag, ``tpsi``, shared splines), so results no longer depend on the thread count. +- PENTRC - Fixed an out-of-bounds ``bpts`` array and a kinetic-spline length mismatch in ``tpsi``. +- PENTRC - ``rlar`` uses the evaluated ``fnml_f`` in the pitch integrand, so it no longer returns zero torque. +- PENTRC, DCON - Large automatic arrays in the pitch and energy integrations are heap allocated, so OpenMP kinetic runs no longer overflow the worker-thread stacks. +- PENTRC, DCON - The kinetic Euler-Lagrange matrices (``kin_flag``) conjugate the bounce-averaged W vectors, so they act on the actual perturbation instead of its up-down mirror image (up-down asymmetric equilibria only). +- PENTRC - Collision frequencies use a monotone (pchip) fit with temperature and logLambda floors, so they stay positive near a cold edge; non-positive densities and negative temperatures stop with an error. +- PENTRC - Parallel-velocity bounce-root splines use periodic end conditions. +- PENTRC - The ``omega_D`` integrand is no longer padded with ``omega_b`` values near the trapped/passing boundary. +- PENTRC - Fixed anti-damping with ``nutype="small"`` for negative ``omega_E``. +- PENTRC - Radial torque-density export writes the last surface's own value. +- DCON - Fixed the kinetic matrix ``kinfac`` for ``method > 0`` and race conditions in the kinetic matrix loop. +- DCON - Plasmas with q0 >= qa are no longer truncated in the deep core. +- DCON - Fixed out-of-bounds singular-surface access when the q range has no rational surface. +- DCON, RDCON, STRIDE - With ``sas_flag``, ``dmlim`` no longer carries ``qlim`` past ``qhigh``. +- DCON, RDCON, STRIDE - The free-boundary vacuum matrix and eigenvector norm are put in the plasma's Fourier frame, so delta-W no longer depends on the theta=0 origin. +- DCON, RDCON - The singular reduction at a rational surface is kept when a norm reset falls just before it. +- DCON - ``W_v_eigenvalue`` holds the correct real eigenvalues. +- DCON, RDCON, STRIDE - Frobenius exponents at rational surfaces include the trace of the resonant block (no spurious imaginary parts on the Delta' diagonal); Mercier alpha is computed in double precision. +- DCON, RDCON - Fixed Taylor-series slot typos in ``sing_mmat`` (F 6th order; DCON G 3rd order for ``sing_order >= 3``). +- DCON, RDCON, STRIDE, GPEC - Fixed LAPACK and ZVODE argument types, sizes and work arrays (``W_xe_*`` used a stale matrix; ``s_P`` precision; uninitialized ``out_evals`` columns). +- RDCON - Added missing zero initializations, safe deallocations and the correct ``restore_us`` condition in the Galerkin solver; ``vmat.bin`` is written correctly. +- RDCON - Galerkin F is built from the same factored spline as the asymptotics. +- RDCON - Galerkin vacuum matrix keeps its imaginary part; the LU solve is diagonally scaled; a failed Cholesky falls back to LU. +- RDCON - Galerkin elements below a singular surface are sized with ``|n q1|`` instead of ``|q1|``. +- RDCON - Fixed the ``delta_gw.out`` crash with 25 or more singular surfaces and the netCDF failure with ``rpec_flag=f``; ODE integration checks that it reaches psilim. +- RDCON - The netCDF Galerkin cell grid is initialized when the Galerkin output flags are off. +- RDCON, GPEC - Removed spurious shielding at the RPEC control surface; fixed the ``globalsol.bin`` point-count mismatch that blocked field penetration at rational surfaces. +- RMATCH - Removed spurious "imap error" messages with the default ``inps_type="inps"``. +- RMATCH - Fixed an out-of-bounds read in ``match_solution`` when ``msing`` is less than RDCON's surface count, an uninitialized ``delta`` and an undefined-behavior logical in ``inps_ua``. +- GPEC - Control-surface normals ``R_n`` and ``z_n`` use the current geometry derivatives. +- GPEC - ``xbnormal`` normalizes each surface with its own major radius. +- GPEC - pmodb netCDF functions (``b_eul_fun``, ``b_lag_fun``, ``Bdivxi_perp_fun``, ``Bkappaxi_perp_fun``) have correct imaginary parts and include the theta=1 row. +- GPEC - pmodb divergence output honours ``reg_flag=f``; the pmodb diagnostic projection uses the current Jacobian. +- GPEC, PENTRC - pmodb ``Bdivxi_perp`` and ``Bkappaxi_perp`` are formed directly from xi_perp instead of subtracting a Fourier-truncated parallel displacement; PENTRC's ``pmodb_file`` reader uses them instead of ``b_lag``. +- GPEC - Fixed a use-after-free of the local coupling vectors; exported singular vectors use the adjoint area weighting; ``C_l_x_out`` holds the local coupling matrix. +- GPEC - ``q1`` at resonant surfaces is computed instead of set to 0; fixed out-of-bounds access with a single rational surface, a netCDF buffer error when ``coil_flag`` is set without coils, the ``gpout_xbnormal`` array length and long_name attributes written to the wrong files. +- STRIDE - Fixed an OpenMP data race in ZLANGE, stack overflow at large ``mpert``, an out-of-bounds write and a double allocation. +- STRIDE - The interval from the axis to the first rational surface is always split into at least 8 pieces. +- VACUUM - Fixed an off-by-one between ``mthsurf`` and ``mthvac``, a divide by zero in ``defglo`` and out-of-bounds writes; observer-grid coordinates are double precision. +- COIL - Fixed undefined spline memory and an out-of-range write in ``field.F``; zero-amplitude shifts no longer distort the coils. +- EQUIL - Fixed spline "extrap" end derivatives on clustered grids and real-valued classic complex spline coefficients; ``spline_roots`` finds roots near cell endpoints. +- EQUIL - Fixed uninitialized ``ro``/``zo`` for EFIT input, ``eq_type="dump"`` for direct equilibria, an out-of-bounds write for g-files without a q column, a crash re-splining inverse equilibria, and the CHEASE ASCII reader; the field-line integration failure message is printed in full. +- EQUIL - Direct equilibria are now sampled exactly at the common straight-fieldline angles instead of interpolated from each surface's own field-line trace. +- EQUIL - ``grid_type="original"`` honours ``psilow`` and ``psihigh``. +- INSTALL - The embedded version string uses release tags only; bundled dependencies honour ``FC``/``CC`` and embed an rpath; xdraw compiles with strict C99/C11 compilers; added missing build dependencies. + + GPEC v1.5.7 ===========