Boozer transform#

Lets apply the Boozer transform to the equilibrium computed in the elliptic stellarator tutorial.

In GVEC, we use a poloidal angle \(\vartheta\) and toroidal angle \(\zeta\), from which we can easily sample quantities on a regular grid. The transform to Boozer angles is

\[\begin{align*} \vartheta_B(\rho,\vartheta,\zeta) &= \vartheta + \lambda(\rho,\vartheta,\zeta) + \iota(\rho)\nu(\rho,\vartheta,\zeta)\\ \zeta_B(\rho,\vartheta,\zeta) &= \zeta + \nu(\rho,\vartheta,\zeta) \end{align*}\]

The Boozer transform is described in the theory section. In short:

  • \(\lambda\) is recomputed on a higher Fourier resolution (by default MNfactor=5 is used).

  • \(\nu\) can be deduced from \(\lambda\), and is computed with the same resolution as \(\lambda\).

  • Evaluations on a regular grid in Boozer angles \((\vartheta_B)_i,(\zeta_B)_j\) means that we have to first find the corresponding \((\vartheta)_{ij},(\zeta)_{ij}\) positions, using a 2D Newton search, and then evaluate on these positions.

import matplotlib.pyplot as plt
import numpy as np

import gvec

Load previously computed state#

We load the state from the previously computed equilibrium by specifying a runpath which we provide to gvec.find_state.

# path to the previous run
runpath = "run_ellipstell"
state = gvec.find_state(runpath)

Boozer transform#

Now we can construct the grid we want to evaluate on, and call gvec.evaluate_sfl, which computes the Boozer transform and evaluates on a regular grid in Boozer angles.

nfp = state.nfp
# select 4 radial positions
rho = [0.2, 0.5, 0.8, 1.0]
theta_B = np.linspace(0, 2 * np.pi, 51)
zeta_B = np.linspace(0, 2 * np.pi / nfp, 51)
varlist = ["mod_B", "B_contra_t", "B_contra_z", "B_contra_t_B", "B_contra_z_B"]

evb = state.evaluate_sfl(*varlist, rho=rho, theta=theta_B, zeta=zeta_B, sfl="boozer")

Visualization in Boozer angles#

Lets visualize first the transform as a grid in \(\vartheta_B\) and \(\zeta_B\).

fig, axs = plt.subplots(2, 2, figsize=(8, 8), sharey=True, sharex=True, tight_layout=True)
for i, ax in enumerate(axs.flatten()):
    evi = evb.isel(rad=i)
    ax.contour(
        evi.theta_B / (2 * np.pi),
        evi.zeta_B / (2 * np.pi),
        evi.theta.T,
        np.linspace(-2 * np.pi, 2 * np.pi, 20),
        colors="black",
    )
    ax.contour(
        evi.theta_B / (2 * np.pi),
        evi.zeta_B / (2 * np.pi),
        evi.zeta.T,
        np.linspace(-2 * np.pi / 3, 2 * np.pi / 3, 20),
        colors="red",
    )
    ax.set(
        xlabel=r"$\vartheta_B/(2\pi)$",
        ylabel=r"$\zeta_B/(2\pi)$",
        title=f"$\\rho = {evi.rho.data:.2f}$",
    )
fig.legend(
    handles=[
        plt.Line2D([0], [0], color="black", label=r"$\vartheta=$const."),
        plt.Line2D([0], [0], color="red", label=r"$\zeta=$const."),
    ]
)
fig.suptitle(
    "Contours of logical GVEC coordinates ($\\vartheta$,$\\zeta$)\n in Boozer coordinates ($\\vartheta_B$,$\\zeta_B$)"
);
../../_images/40d51b03cb1340ccb7729098bdb2a19c91b47111e1a2a9584d43bc933e7d0c62.png

Finally, we can visualise the \(|B|\) contours in Boozer coordinates.

fig, axs = plt.subplots(2, 2, figsize=(8, 8), sharey=True, sharex=True, tight_layout=True)
for i, ax in enumerate(axs.flatten()):
    evi = evb.isel(rad=i)
    c = ax.contour(evi.theta_B / (2 * np.pi), evi.zeta_B / (2 * np.pi), evi.mod_B.T, levels=20)
    fig.colorbar(c, ax=ax)
    ax.set(
        xlabel=r"$\vartheta_B/(2\pi)$",
        ylabel=r"$\zeta_B/(2\pi)$",
        title=f"$\\rho = {evi.rho.data:.2f}$",
    )

fig.suptitle("Contours of |B| in Boozer coordinates ($\\vartheta_B$,$\\zeta_B$)");
../../_images/43e3af4f3cc674b5724509c9c0017a978178e3726542fed4ba4e99cb50c376e5.png