Skip to content

Commit c1cb59a

Browse files
flux surface example working
1 parent c2ca7ca commit c1cb59a

File tree

5 files changed

+142
-4
lines changed

5 files changed

+142
-4
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ test_1d_rodft11_*
3131
axis_R.dat
3232
axis_R_symm.dat
3333
axis_R_halfGrid.dat
34+
lcfs_R.dat
35+
lcfs_Z.dat
3436

3537
# auxiliary tests
3638
test_util

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,12 @@ Inserting this into the Fourier series for, e.g., the *R* coordinate, leads to a
15931593
The *R* coordinate is a real-valued quantity
15941594
which implies that a two-dimensional `c2r` DFT provided by FFTW can be used to
15951595
perform the backward transform in order to evaluate the flux surface geometry.
1596-
1596+
One further issue consists in the fact that the definition
1597+
of the Fourier geometry employed in VMEC uses an angle argument *m θ - n ζ*
1598+
where the two-dimensional DFT written out above uses *m θ + n ζ*.
1599+
The Fourier coefficients coming from VMEC
1600+
have to be inserted into the positions corresponding to the reverse sign of *n*
1601+
in the input array given to FFTW in order to resolve this pecularity.
15971602

15981603

15991604

src/app_flux_surface.c

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,86 @@ void app_flux_surface(int n_theta, int n_zeta) {
4747
return;
4848
}
4949

50+
// number of Fourier modes that can be represented with n_theta and n_zeta
51+
// without violating Nyquist criterion
52+
int nyq_pol = n_theta / 2 + 1;
53+
int nyq_tor = n_zeta / 2 + 1;
5054

55+
if (nyq_pol < mpol) {
56+
printf("n_theta too small\n");
57+
return;
58+
}
5159

60+
if (nyq_tor < ntor) {
61+
printf("n_zeta too small\n");
62+
return;
63+
}
5264

65+
fftw_complex *in_R = fftw_alloc_complex(n_zeta * nyq_pol);
66+
fftw_complex *in_Z = fftw_alloc_complex(n_zeta * nyq_pol);
67+
double *out_R = fftw_alloc_real(n_zeta * n_theta);
68+
double *out_Z = fftw_alloc_real(n_zeta * n_theta);
69+
70+
fftw_plan p_R = fftw_plan_dft_c2r_2d(n_zeta, n_theta, in_R, out_R, FFTW_ESTIMATE);
71+
fftw_plan p_Z = fftw_plan_dft_c2r_2d(n_zeta, n_theta, in_Z, out_Z, FFTW_ESTIMATE);
72+
73+
fill_zero_2d_cplx(n_zeta, nyq_pol, in_R);
74+
fill_zero_2d_cplx(n_zeta, nyq_pol, in_Z);
75+
76+
// copy LCFS Fourier coefficients into input array
77+
int idx_vmec = 0, idx_in;
78+
int m = 0;
79+
for (int n = 0; n <= ntor; ++n) {
80+
81+
// compute target index for input array
82+
if (n <= 0) {
83+
idx_in = -n * nyq_pol + m;
84+
} else {
85+
idx_in = (n_zeta - n) * nyq_pol + m;
86+
}
87+
88+
// VMEC: for m=0, only positive ntor --> summed up within VMEC definition with negative-n --> 2*0.5 = 1
89+
// also for sin-parity quantities: m=0 would imply (m theta -n zeta) == (-n zeta), but VMEC uses (n zeta) for m=0
90+
// --> does not matter for cos-parity since cos(-n zeta) = cos(n zeta), but sin(-n zeta) = -sin(n zeta)
91+
// and hence the additional -1 cancels out by compensating the -1 from the complex multiply in the DFT definition
92+
in_R[idx_in] = rmnc[idx_vmec];
93+
in_Z[idx_in] = I * zmns[idx_vmec];
94+
95+
idx_vmec++;
96+
}
5397

98+
for (m = 1; m < mpol; ++m) {
99+
for (int n = -ntor; n <= ntor; ++n) {
54100

101+
// compute target index for input array
102+
if (n <= 0) {
103+
idx_in = -n * nyq_pol + m;
104+
} else {
105+
idx_in = (n_zeta - n) * nyq_pol + m;
106+
}
55107

108+
// scale coefficients by 0.5 since FFTW implies
109+
// that coeffs for m<0 were present in the logically equivalent DFT input
110+
// which is not the case for VMEC output
111+
in_R[idx_in] = 0.5 * rmnc[idx_vmec];
112+
in_Z[idx_in] = -0.5 * I * zmns[idx_vmec];
56113

114+
idx_vmec++;
115+
}
116+
}
57117

118+
fftw_execute(p_R);
119+
fftw_execute(p_Z);
58120

121+
dump_2d_real("lcfs_R.dat", n_zeta, n_theta, out_R);
122+
dump_2d_real("lcfs_Z.dat", n_zeta, n_theta, out_Z);
59123

60-
61-
62-
124+
fftw_destroy_plan(p_R);
125+
fftw_destroy_plan(p_Z);
126+
fftw_free(in_R);
127+
fftw_free(in_Z);
128+
fftw_free(out_R);
129+
fftw_free(out_Z);
63130
}
64131

65132
int main(int argc, char** argv) {

src/plot_lcfs_realspace.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Created on Tue Mar 16 17:53:03 2021
5+
6+
@author: jonathan
7+
"""
8+
9+
import os
10+
import numpy as np
11+
from mayavi import mlab
12+
13+
folder = ".."
14+
15+
# load output from FFTW example 'app_flux_surface.c'
16+
lcfs_R = np.loadtxt(os.path.join(folder, "lcfs_R.dat"))
17+
lcfs_Z = np.loadtxt(os.path.join(folder, "lcfs_Z.dat"))
18+
n_zeta, n_theta = np.shape(lcfs_R)
19+
20+
# assume W7-X
21+
nfp = 5
22+
23+
# regular grid in phi = zeta/nfp
24+
phiGrid = 2.0*np.pi*np.arange(n_zeta)/(n_zeta*nfp)
25+
26+
# compute X,Y from R,phi
27+
lcfs_X = np.zeros([n_zeta, n_theta])
28+
lcfs_Y = np.zeros([n_zeta, n_theta])
29+
for i in range(n_zeta):
30+
for j in range(n_theta):
31+
lcfs_X[i,j] = lcfs_R[i,j]*np.cos(phiGrid[i])
32+
lcfs_Y[i,j] = lcfs_R[i,j]*np.sin(phiGrid[i])
33+
34+
# plot real-space geometry of flux surface using Mayavi
35+
mlab.mesh(lcfs_X, lcfs_Y, lcfs_Z)
36+
mlab.show()

src/util.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,34 @@
99
#include <complex.h>
1010
#include <fftw3.h>
1111

12+
void fill_zero_1d_real(int n, double *arr) {
13+
for (int i = 0; i < n; ++i) {
14+
arr[i] = 0.0;
15+
}
16+
}
17+
18+
void fill_zero_1d_cplx(int n, fftw_complex *arr) {
19+
for (int i = 0; i < n; ++i) {
20+
arr[i] = 0.0;
21+
}
22+
}
23+
24+
void fill_zero_2d_real(int rows, int cols, double *arr) {
25+
for (int i = 0; i < rows; ++i) {
26+
for (int j = 0; j < cols; ++j) {
27+
arr[i * cols + j] = 0.0;
28+
}
29+
}
30+
}
31+
32+
void fill_zero_2d_cplx(int rows, int cols, fftw_complex *arr) {
33+
for (int i = 0; i < rows; ++i) {
34+
for (int j = 0; j < cols; ++j) {
35+
arr[i * cols + j] = 0.0;
36+
}
37+
}
38+
}
39+
1240
void fill_random_1d_real(int n, double *arr) {
1341
for (int i = 0; i < n; ++i) {
1442
arr[i] = rand() / ((double) RAND_MAX);

0 commit comments

Comments
 (0)