The Open FUSION Toolkit 1.0.0-8905cc5
Modeling tools for plasma and fusion research and engineering
Loading...
Searching...
No Matches
ThinCurr Python Example: Compute current potential from B-norm

In this example we demonstrate how to compute a current potential from a given B-norm on a toroidal surface. This has application to compute eddy current and sensor reponse to plasma modes.

Note
Running this example requires the h5py python package, which is installable using pip or other standard methods.
import struct
import sys
import os
import h5py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams['figure.figsize']=(6,6)
plt.rcParams['font.weight']='bold'
plt.rcParams['axes.labelweight']='bold'
plt.rcParams['lines.linewidth']=2
plt.rcParams['lines.markeredgewidth']=2
%matplotlib inline
%config InlineBackend.figure_format = "retina"

Load ThinCurr library

To load the ThinCurr python module we need to tell python where to the module is located. This can be done either through the PYTHONPATH environment variable or within a script using sys.path.append() as below, where we look for the environement variable OFT_ROOTPATH to provide the path to where the OpenFUSIONToolkit is installed (/Applications/OFT for binaries on macOS).

thincurr_python_path = os.getenv('OFT_ROOTPATH')
if thincurr_python_path is not None:
sys.path.append(os.path.join(thincurr_python_path,'python'))
from OpenFUSIONToolkit import OFT_env
from OpenFUSIONToolkit.ThinCurr import ThinCurr
from OpenFUSIONToolkit.ThinCurr.meshing import build_torus_bnorm_grid, ThinCurr_periodic_toroid
ThinCurr utilities for mesh generation and manipulation.
Definition meshing.py:1
Python interface for ThinCurr thin-wall E-M functionality.
Definition __init__.py:1

Create 3D mode model

In most cases the mode will be imported and/or converted from another tool (eg. DCON). However, in this case we will generate a simple mode shape with fixed toroidal and poloidal mode number for a simple torus. Using a simple helper function we define an n=2, m=3 mode on an R=1.0 m, a=0.4 m torus.

def create_circular_bnorm(filename,R0,Z0,a,n,m,npts=200):
theta_vals = np.linspace(0.0,2*np.pi,npts,endpoint=False)
with open(filename,'w+') as fid:
fid.write('{0} {1}\n'.format(npts,n))
for theta in theta_vals:
fid.write('{0} {1} {2} {3}\n'.format(
R0+a*np.cos(theta),
Z0+a*np.sin(theta),
np.cos(m*theta),
np.sin(m*theta)
))
# Create n=2, m=3 mode
create_circular_bnorm('tCurr_mode.dat',1.0,0.0,0.4,2,3)

Generate mesh and ThinCurr input from mode file

We can now create a ThinCurr model from the mode definition file by using build_torus_bnorm_grid() to build a uniform grid over one toroidal field period and ThinCurr_periodic_toroid, which provides functionality for working with toroidally periodic meshes. The result is a ThinCurr_periodic_toroid object (plasma_mode) and the normal field at each mesh vertex for the Real and Imaginary components of the mode (bnorm).

ntheta = 40
nphi = 80
r_grid, bnorm, nfp = build_torus_bnorm_grid('tCurr_mode.dat',ntheta,nphi,resample_type='theta',use_spline=False)
plasma_mode = ThinCurr_periodic_toroid(r_grid,nfp,ntheta,nphi)
Loading toroidal plasma mode
  filename = tCurr_mode.dat
  N        = 2
  # of pts = 200
  R0       = (1.0000E+00, -1.7087E-17)
  Mode pair sums -7.6050E-15 -2.9976E-15

Plot resulting mesh

fig = plt.figure(figsize=(12,5))
plasma_mode.plot_mesh(fig)

Save model to HDF5 format

plasma_mode.write_to_file('thincurr_mode.h5')
Saving mesh: thincurr_mode.h5

Compute mode-equivalent current distribution

Setup ThinCurr model

We now create a OFT_env instance for execution using four threads and a ThinCurr instance that utilizes that execution environment. Once created, we setup the model from an existing HDF5 and XML mesh definition using setup_model().

We also initialize I/O for this model using setup_io() to enable output of plotting files for 3D visualization in VisIt or Paraview.

In this case we specify a plasma directory to use for saving I/O files to keep things separate for the other cases to be run in ThinCurr Python Example: Compute frequency-response in a torus.

Note
A warning will be generated that no XML node was found and a default resistivity value is being used. This is OK as we will not use the resitivity matrix in this example.
myOFT = OFT_env(nthreads=4)
tw_mode = ThinCurr(myOFT)
tw_mode.setup_model(mesh_file='thincurr_mode.h5')
tw_mode.setup_io(basepath='plasma/')
#----------------------------------------------
Open FUSION Toolkit Initialized
Development branch:   v1_beta6
Revision id:          681e857
Parallelization Info:
  # of MPI tasks      =    1
  # of NUMA nodes     =    1
  # of OpenMP threads =    4
Fortran input file    = /var/folders/52/n5qxh27n4w19qxzqygz2btbw0000gn/T/oft_64897/oftpyin
XML input file        = none
Integer Precisions    =    4   8
Float Precisions      =    4   8  16
Complex Precisions    =    4   8
LA backend            = native
#----------------------------------------------


Creating thin-wall model
 Orientation depth =       12640
  Loading V(t) driver coils
  Loading I(t) driver coils

  # of points    =         6320
  # of edges     =        18960
  # of cells     =        12640
  # of holes     =            3
  # of Vcoils    =            0
  # of closures  =            2
  # of Icoils    =            0

  Building holes


WARNING: Unable to find "thincurr" XML node
WARNING: No "thincurr" XML node, using "eta=mu0" for all regions

Compute self-inductance matrix

With the model setup, we can now compute the self-inductance matrix. A numpy version of the self-inductance matrix will be stored at tw_plate.Lmat. For this case we will use the self-inductance to convert between the surface flux \(\Phi\) corresponding to the normal field computed above (bnorm) and an equivalent surface current

\(\textrm{L} I = \Phi\)

Note
For larger models calculating the self-inductance may take some time due to the \(N^2\) interaction of the elements (see ThinCurr Example: Using HODLR approximation for more information).

condense_matrix() is then used collapse periodic copies into a final inductance matrix for the DOF of the periodic system.

Finally, we compute \(\textrm{L}^{-1}\).

tw_mode.compute_Lmat()
Lmat_new = plasma_mode.condense_matrix(tw_mode.Lmat)
# Get inverse
Linv = np.linalg.inv(Lmat_new)
 Building element<->element self inductance matrix
     Time = 14s          

Compute currents from fluxes and save

Now we can compute the equivalent currents for the Real and Imaginary component of normal field \(B_n\) from above. To do this we need to convert to flux \(\Phi\) by scaling the field by the area of each vertex using scale_va(). As scale_va() acts on all vertices in the grid the plasma_mode.r_map index array is used to map to/from the full and single period spaces.

When computing the current density from the flux nodes_to_unique() is used to map from node points to unique DOFs by adding the toroidal and poloidal holes and removing closure elements.

Finally, when saving the current the output it expanded to the model size using expand_vector().

bnorm_flat = bnorm.reshape((2,bnorm.shape[1]*bnorm.shape[2]))
# Get surface flux from normal field
flux_flat = bnorm_flat.copy()
flux_flat[0,plasma_mode.r_map] = tw_mode.scale_va(bnorm_flat[0,plasma_mode.r_map])
flux_flat[1,plasma_mode.r_map] = tw_mode.scale_va(bnorm_flat[1,plasma_mode.r_map])
tw_mode.save_scalar(bnorm_flat[0,plasma_mode.r_map],'Bn_c')
tw_mode.save_scalar(bnorm_flat[1,plasma_mode.r_map],'Bn_s')
output_full = np.zeros((2,tw_mode.nelems))
output_unique = np.zeros((2,Linv.shape[0]))
for j in range(2):
output_unique[j,:] = np.dot(Linv,plasma_mode.nodes_to_unique(flux_flat[j,:]))
output_full[j,:] = plasma_mode.expand_vector(output_unique[j,:])
tw_mode.save_current(output_full[0,:],'Jc')
tw_mode.save_current(output_full[1,:],'Js')
_ = tw_mode.build_XDMF()
Removing old Xdmf files
  Removed 1 files

Creating output files: oft_xdmf.XXXX.h5
  Found Group: thincurr
    Found Mesh: smesh
      # of blocks: 1

Plot current potential and mode shape

We now plot the resulting current potential (top) and mode shape (bottom) over a single toroidal mode period to show how the fields are related and ensure no errors. The helper method unique_to_nodes_2D() is provided to map between unique DOF and vertices over a single field period, which are then mapped to a uniform 2D grid.

fig, ax = plt.subplots(2,2)
ax[0,0].contour(plasma_mode.unique_to_nodes_2D(output_unique[0,:]),10)
ax[0,1].contour(plasma_mode.unique_to_nodes_2D(output_unique[1,:]),10)
ax[1,0].contour(bnorm[0,:,:].transpose(),10)
_ = ax[1,1].contour(bnorm[1,:,:].transpose(),10)

Save current as a "driver" to HDF5 model file

Finally we save the currents to the model file as a "driver" to utilize in frequency response calculations in ThinCurr Python Example: Compute frequency-response in a torus.

with h5py.File('thincurr_mode.h5', 'r+') as h5_file:
h5_file.create_dataset('thincurr/driver', data=output_full, dtype='f8')