Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement calculation of sqrt(g), B^X, B_X, and |B| from R, Z, lambda, iota, phi' #42

Open
lazersos opened this issue Feb 6, 2020 · 17 comments
Assignees
Labels
Announcement Official announcement to each user bug Something isn't working enhancement New feature or request help wanted Extra attention is needed

Comments

@lazersos
Copy link
Collaborator

lazersos commented Feb 6, 2020

Currently VMEC only output the correct values from sqrt(g), B^X, B_X, and |B| when LNYQIST=.true. is set in wrout.f90. When this is done these arrays have a different Fourier spectrum than the R, Z and Lambda arrays. This is going to be addressed in two ways.

  1. Code which can use the larger arrays will be modified
  2. Where possible such quantities will be calcualted directly from R, Z, lambda, dphi/ds and iota.

For reference
sqrt(g) = R*(RuZs-RsZu) where Ru=dR/du
B^u = phiprime*(iota-Lv)/sqrt(g) where Lv=dlambda/dv
B^v = phiprime(1+Lu)/sqrt(g)
|B| = sqrt(B^u2guu+2B^uB^vguv+b^v2*gvv)

Here's what I suggest be worked on:

  • BOOZ_XFORM: Already implemented to handle LNQUIST but we should benchmark this since BOOTSJ, DKES, and NEO rely on these values. (volunteer?)
  • LIBSTELL:
    • STEL_TOOLS: Can handle LNQUIST if you re-sort the R, Z, and L arrays. Will upgrade to use R, Z and lambda to calc the above values on internal grid (@lazersos)
    • VMEC_TOOLS: Straightforward to modify (@lazersos)
    • VIRTUAL_CASING_MOD: Similar situation to STEL_TOOLS (@lazersos)
  • BNORM: Needs to be implemented and checked. (vounteer?)
  • COBRAVMEC: Need to be implemented (@lazersos) - Done
  • STELLOPT
    • The code uses STEL_TOOLS for most everything so once that's been done we should be fine.
  • BEAMS3D/DIAGNO/FIELDLINES: These rely on the LIBSTELL codes so once that's done they'll be fixed as well. (@lazersos)

Did I miss anything?

@lazersos lazersos added bug Something isn't working enhancement New feature or request help wanted Extra attention is needed Announcement Official announcement to each user labels Feb 6, 2020
@krystophny
Copy link
Collaborator

krystophny commented Feb 7, 2020

Perhaps related: the output of xn_nyq modes in wout.nc has opposite sign from xn , no matter if LNYQUIST is true or false. One should check the sign convention of zeta once more, and which of xn or xn_nyq produces the intended one. Correction: seems like this affects only matlabVMEC, where xn switches sign but not xn_nyq

@landreman
Copy link
Collaborator

landreman commented Feb 7, 2020 via email

@krystophny
Copy link
Collaborator

Sorry for the confusion - it was really just in matlabVMEC where this occured, and otherwise the sign was the same directly from wout.nc . No idea why - @lazersos what do you think?

@lazersos
Copy link
Collaborator Author

So there was an issue in read_vmec.m where fields of the structure (xnnyq) were being returned when reading a netCDF file, but we were checking for xn_nyq to change the kernel to (mu+nv) from (mu-nv). This has been updated on the git page for matlabVMEC. Unfortunately I no longer have access to my Princeton account so I can't update the files on the Mathworks File exchange. I have posted a comment however pointing to the repo on GitHub.

@krystophny
Copy link
Collaborator

Thanks! This whole story became a bit more confusing, and I'll try to summarize it here. We made two runs on the same equilibrium, one time with ntor=mpol=6 and one time with ntor=mpol=12 harmonics. Here are the results for |B| on the flux surface s=0.3 .

Column 1: truncated spectrum (LNYQUIST=.false.) from VMEC .nc (xn_nyq=xn, xm_nyq=xm)
Column 2: full spectrum (LNYQUIST=.true.) from VMEC .nc
Column 3: from geometry via R, Z, phi and lambda and iota (as in SIMPLE alpha tracer)

The two observations are

  1. In the 6x6 truncation causes a big inconsistency, while in 12x12 fields are optically the same
  2. Artificial smoothing filter by truncating the spectrum in 6x6 leads to a field that matches the more accurate 12x12 calculation (by chance?).

This means that when using low resolution VMEC runs such as 6x6, one might get results matching reality better by artificially truncating / filtering the spectrum of the B field, even though it's not consistent with low-resolution R,Z geometry. It would be interesting to investigate this in detail.

6x6

truncated full geometry
b6x6_nonyq b6x6_nyq b6x6_simple

12x12

truncated full geometry
b12x12_nonyq b12x12_nyq b12x12_simple

@lazersos
Copy link
Collaborator Author

lazersos commented Feb 17, 2020

@krystophny Interesting. I've been reading through bit more of VMEC to better understand how the GMNC array is constructed. One thing that VMEC does internally is to separate the even and odd modes of R,Z, and Lambda. They then divide the odd modes by sqrt(s), then transform. So to compose a value in real space it looks like

r=r_e+sqrt(s)*r_o

where the r_e and r_o are the real space Fourier transforms of the even and odd modes separately. This has an effect that to calculate df/ds (on the half grid) you get something like

dfds(i) = (ns-1)*(f_e(i)-f_e(i-1) + sqrt(s)*(f_o(i)-f_o(i-1))) + 0.25*(f_o(i)+f_o(i-1))/sqrt(s)

where I've taken sqrt(s) to be on the half grid for simplicity. This third term is a combination of the chain rule applied to sqrt(s)*f_o and an averaging of f_o onto the half grid (hence the 0.25 coefficient instead of 0.5).

This has implication for the computation of the cross product. Essentially it has extra terms as can be seen in jacobian.f. Using some matlab pseudo-code I'm able to calculate sqrt(g), B^u, and B^v in real space form R, Z, and Lambda. There is a small error when comparing to the LNYQUIST=T version of the GMNC, BSUPUMNC, and BSUPVMNC arrays but I suspect this is roundoff and not significant.

I've raised the issue with the ORNL/VMEC GitHub site and am tracking it there with their developers. The end goal will be to only use the basic quantities from VMEC in the future so as to be LNYQUIST ambivalent.

@lazersos
Copy link
Collaborator Author

lazersos commented Mar 6, 2020

I just wanted to give a quick explanation of why this is an issue. The VMEC jacobian looks like
R*(dR/du*dZ/ds-dR/ds*dZ/du). Because R and Z have opposite parity (cosine vs sine) the convolutions of dR/du*dZ/ds give terms which go like cos^2 and sin^2. However, to properly Fourier transform these terms back into Fourier space you need twice the harmonic content as before. Hence our need for the Nyquist sized array for these values.

@jonathanschilling
Copy link
Collaborator

This third term is a combination of the chain rule applied to sqrt(s)*f_o and an averaging of f_o onto the half grid (hence the 0.25 coefficient instead of 0.5).

@lazersos Thanks for pointing this out! I always wondered about the 0.25 in Compute_Currents in read_wout_mod.f90.

@zhucaoxiang
Copy link
Collaborator

@lazersos You also changed TERPSICHORE to use mnmax_nyq. There is a bug of not broadcasting the mnmax_nyq and it is now fixed in #90 . But I am not quite sure if it is necessary to use the Nyquist number in TERPSCHORE.

@lazersos
Copy link
Collaborator Author

@zhucaoxiang it is because TERPSICHORE needs the Jacobian which is NYQUIST sized.

@lazersos
Copy link
Collaborator Author

lazersos commented Mar 4, 2022

Just an update to this issue. The even and odd modes of R, Z, and Lambda do need to be interpolated differently. The even modes can be linearly interpolated in flux but the Odd modes must be interpolated in rho, so the harmonics need to be multiplied by 1/sqrt(s). Doing this one can properly interpolate R, Z, and lambda. However, the radial derivatives still have issues as we approach the magnetic axis. Essentially the Jacobian becomes singular which messes everything up. So I wasn't able to switch GetBcyl over to using R,Z and lambda solely. However, I did fix the interpolation of the Jacobian which improves calculation of B near the axis.

Looking at VMEC, internally the code recomputes lambda on the half grid before outputting (for back-compatibility with old version). However, this may not be done correctly near the axis (or may be hiding special treatment).

@jcschmitt
Copy link
Collaborator

jcschmitt commented Mar 12, 2022

Similar to using TERPSICHORE, when using stellopt or vmec with cobravmec, LNYQUIST=F must be specified in the vmec-portion of the input namelist.

On branch Lazerson with commit 98b1667 (HEAD -> Lazerson, origin/Lazerson)

I did a benchmarking test of vmec, stellopt and cobravmec through the various ways that the code(s) handle the LNYQUIST flag, and through the various ways that that codes can be run: vmec stand-alone, stellopt in 'single_iter' mode, and stellopt in standard 'lmdiff_bounded' mode with niter=1, (similar to lmdiff, but with bounds), and 'lmdiff_bounded' mode with niter>1.

So, I have 12 types of code paths:

  1. A standalone vmec run, with LNYQUIST=F, followed by a standalone cobravmec run.
  2. A standalone vmec run, with LNYQUIST=F, followed by a standalone cobravmec run.
  3. A standalone vmec run, with LNYQUIST UNSPECIFIED (commented out), followed by a standalone cobravmec run.
  4. A stellopt run with opt_type = 'one_iter', followed by a standalone cobravmec run using the wout file from the stellopt run with LNYQUIST=F in the vmec portion of the input namelist
  5. Same as 4, with LNYQUIST=T
  6. Same as 4 with LNYQUIST UNSPECIFIED (commented out)
  7. A stellopt run with opt_type = 'lmdiff_bounded' and NITER=1, and I inspect the cost function output file (stellopt.whatever) to determine the actually ballooning growth rates. LNYQUIST=F in the vmec portion of input namelist.
  8. Same as 7, with LNYQUIST=T
  9. Same as 7, LNYQUIST UNSPECIFIED (commented out)
  10. Same as 7, with NITER = 10 (just to make sure that stellopt runs with multiple iterations)
  11. Same as 8, with NITER = 10
  12. Same as 9, with NITER = 10

Only the runs with LNYQUIST=False produce the correct results.
This is the same situation as using TERPSICHORE.
So: LNYQUIST must be specified and it must be set to FALSE (or f, or 0, or .false., or whatever logical flag works in that spot)

cobravmec nyquist checks.pdf

@lazersos
Copy link
Collaborator Author

@jcschmitt I can see in COBRAVMEC at order_input the change which needs to happen. However, I disagree that any result obtained with LYQUIST=F is 'correct'. When LNYQUIST=F is set the modes for anything other than the R, Z and Lambda harmonics are artificially truncated. I will make the change to order_input with the correct behavior. This change will propagate to STELLOPT as well.

@jcschmitt
Copy link
Collaborator

jcschmitt commented Mar 12, 2022 via email

@lazersos
Copy link
Collaborator Author

@jcschmitt If they're not using the full nyquist sized results then it's isn't 'correct'. I've pushed a branch which should fix the issue for COBRAVMEC, but initial tests indicate it's slower since the searches over Lambda now have to deal with the larger mode structure.

@lazersos
Copy link
Collaborator Author

@jcschmitt In the branch I've pushed #147 the code now properly handles LNYQUIST sized arrays. However, I've noticed that inside of COBRAVMEC all arrays are linearly interpolated from the half to full mesh. This isn't entirely correct as the odd modes should go as sqrt(s). I'm going to open this as a new issue since I think the proper handling of interpolation half/full and calculation of df/ds affects many codes.

@zhucaoxiang
Copy link
Collaborator

@lazersos Hmm... Can we still trust all the previous results?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Announcement Official announcement to each user bug Something isn't working enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants