116 lines
2.8 KiB
Python
116 lines
2.8 KiB
Python
'''
|
||
Python interface for GRAY
|
||
'''
|
||
|
||
# output files handling
|
||
import numpy as np
|
||
import collections
|
||
import io
|
||
from configparser import ConfigParser
|
||
|
||
# standard GRAY simulation
|
||
from pathlib import Path
|
||
|
||
|
||
# Type of a parsed EQDSK file
|
||
Eqdsk = collections.namedtuple('Eqdsk', ['flux', 'boundary', 'limiter', 'q'])
|
||
|
||
|
||
def read_eqdsk(filepath: Path) -> Eqdsk:
|
||
'''
|
||
Loads a full G-EQDSK file and returns:
|
||
R, z, ψ(R, z), q(ρ)
|
||
'''
|
||
def read(file, n):
|
||
'''
|
||
Reads n records of a G-EQDSK file
|
||
'''
|
||
x = np.empty(n)
|
||
for i in range(n):
|
||
x[i] = float(file.read(16).strip())
|
||
return x
|
||
|
||
def skip(file, n):
|
||
'''
|
||
Skip n records of a G-EQDSK file
|
||
'''
|
||
file.seek(file.tell() + 16 * n)
|
||
|
||
with open(filepath) as file:
|
||
file = io.StringIO(''.join(file.read().splitlines()))
|
||
|
||
# comment string
|
||
file.read(48)
|
||
|
||
# number of (R, z) grid points
|
||
_, nr, nz = [int(i) for i in file.read(4 * 3).split()]
|
||
|
||
# build (R, z) grid
|
||
r_dim, z_dim, _, r_left, z_mid = read(file, 5)
|
||
r = np.linspace(r_left, r_left + r_dim, nr)
|
||
z = np.linspace(z_mid - z_dim/2, z_mid + z_dim/2, nz)
|
||
|
||
# ψ on the axis/boundary
|
||
psi_min, psi_max = read(file, 5)[2:4]
|
||
|
||
# skip other MHD quantities
|
||
skip(file, 5 * 2)
|
||
skip(file, nr * 4)
|
||
|
||
# read flux and safety factor
|
||
psi = read(file, nr * nz).reshape(nr, nz)
|
||
q = read(file, nr)
|
||
|
||
# normalise flux
|
||
psi = abs(psi - psi_min) / abs(psi_max - psi_min)
|
||
|
||
# plasma boundary and limiter contour
|
||
nbound, nlim = [int(i) for i in file.read(5 * 2).split()]
|
||
rbound, zbound = read(file, nbound * 2).reshape(nbound, 2).T
|
||
rlim, zlim = read(file, nlim * 2).reshape(nlim, 2).T
|
||
|
||
return Eqdsk(flux=(r, z, psi),
|
||
boundary=(rbound, zbound),
|
||
limiter=(rlim, zlim),
|
||
q=(np.sqrt(np.linspace(0, 1, nr)), abs(q)))
|
||
|
||
|
||
def read_conf(fname: Path):
|
||
'''
|
||
Parses gray.ini configuration file
|
||
'''
|
||
config = ConfigParser()
|
||
with open(fname) as ini:
|
||
config.read_file(ini)
|
||
return config
|
||
|
||
|
||
def read_table(fname: Path):
|
||
'''
|
||
Loads a GRAY output unit file as a structured numpy array
|
||
'''
|
||
return np.genfromtxt(fname, skip_header=21, names=True)
|
||
|
||
|
||
def get_limiter(conf: ConfigParser, inputs: Path) -> np.array:
|
||
'''
|
||
Returns the limiter contour
|
||
'''
|
||
file = conf['equilibrium']['filenm'].strip('"')
|
||
|
||
if conf['equilibrium'].get('iequil') == 'EQ_ANALYTICAL':
|
||
return np.loadtxt(inputs / file, skiprows=4).T
|
||
else:
|
||
eqdsk = read_eqdsk(inputs / file)
|
||
return eqdsk.limiter
|
||
|
||
|
||
def decode_index_rt(i: int) -> (str, int):
|
||
'''
|
||
Given the index_rt of a beam, returns the polarisation
|
||
mode ('O' or 'X') and the number of passes
|
||
'''
|
||
mode = ['X', 'O'][i % 2]
|
||
passes = int(np.floor(np.log2(1 + i)))
|
||
return mode, passes
|