2024-10-05 15:03:11 +02:00
|
|
|
|
'''
|
|
|
|
|
Script to quickly visualise both the inputs and output files of GRAY
|
|
|
|
|
Usage: python -m scripts.gray_visual -h
|
|
|
|
|
'''
|
2022-11-13 01:55:25 +01:00
|
|
|
|
|
2024-10-05 15:03:11 +02:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
from matplotlib.collections import LineCollection
|
|
|
|
|
from matplotlib.contour import ContourSet
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
import numpy as np
|
|
|
|
|
import scripts.gray as gray
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cli_args() -> argparse.Namespace:
|
|
|
|
|
'''
|
|
|
|
|
Command line arguments
|
|
|
|
|
'''
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
prog='gray_visual',
|
|
|
|
|
description='visualises the results of a GRAY simulation')
|
|
|
|
|
parser.add_argument('inputs', type=Path,
|
|
|
|
|
help='filepath of the inputs directory '
|
|
|
|
|
'(containing gray.ini)')
|
|
|
|
|
parser.add_argument('outputs', type=Path,
|
|
|
|
|
help='filepath of the outputs directory')
|
2024-10-17 15:20:23 +02:00
|
|
|
|
parser.add_argument('--kind', '-k', default=['raytracing'], nargs='+',
|
2024-10-05 15:03:11 +02:00
|
|
|
|
choices=['raytracing', 'beam', 'profiles', 'inputs'],
|
|
|
|
|
help='which kind of information to visualise')
|
|
|
|
|
parser.add_argument('--outfile', '-o',
|
|
|
|
|
metavar='FILE', type=Path, default=None,
|
|
|
|
|
help='save the plot figure to FILE')
|
|
|
|
|
parser.add_argument('--interactive', '-i', action='store_true',
|
|
|
|
|
help='show the plot in an interactive window')
|
|
|
|
|
parser.add_argument('--legend',
|
|
|
|
|
action=argparse.BooleanOptionalAction, default=True,
|
|
|
|
|
help='whether to show plot legend')
|
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def align_yaxis(*axes: [plt.Axes]):
|
|
|
|
|
'''
|
|
|
|
|
Aligns the origins of two axes
|
|
|
|
|
'''
|
|
|
|
|
extrema = np.array([ax.get_ylim() for ax in axes])
|
|
|
|
|
tops = extrema[:, 1] / (extrema[:, 1] - extrema[:, 0])
|
|
|
|
|
|
|
|
|
|
# Ensure that plots (intervals) are ordered bottom to top:
|
|
|
|
|
if tops[0] > tops[1]:
|
|
|
|
|
axes, extrema, tops = [a[::-1] for a in (axes, extrema, tops)]
|
|
|
|
|
|
|
|
|
|
# How much would the plot overflow if we kept current zoom levels?
|
|
|
|
|
tot_span = tops[1] + 1 - tops[0]
|
|
|
|
|
|
|
|
|
|
extrema[0, 1] = extrema[0, 0] + tot_span * (extrema[0, 1] - extrema[0, 0])
|
|
|
|
|
extrema[1, 0] = extrema[1, 1] + tot_span * (extrema[1, 0] - extrema[1, 1])
|
|
|
|
|
[axes[i].set_ylim(*extrema[i]) for i in range(2)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_alt_axis(ax: plt.Axes, x1: np.array, x2: np.array,
|
|
|
|
|
label: str) -> plt.Axes:
|
|
|
|
|
'''
|
|
|
|
|
Adds an alternative x axis to the top of a plot.
|
|
|
|
|
x1: existing variable
|
|
|
|
|
x2: new variable
|
|
|
|
|
'''
|
|
|
|
|
from matplotlib.ticker import AutoMinorLocator
|
|
|
|
|
from scipy.interpolate import interp1d
|
|
|
|
|
|
|
|
|
|
ax.xaxis.set_minor_locator(AutoMinorLocator())
|
|
|
|
|
ax.grid()
|
|
|
|
|
f = interp1d(x1, x2, fill_value='extrapolate')
|
|
|
|
|
g = interp1d(x2, x1, fill_value='extrapolate')
|
|
|
|
|
alt = ax.secondary_xaxis('top', functions=(f, g))
|
|
|
|
|
alt.set_xlabel(label, labelpad=8)
|
|
|
|
|
alt.xaxis.set_minor_locator(AutoMinorLocator())
|
|
|
|
|
return alt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_circle(ax: plt.Axes, radius: float, **args) -> plt.Circle:
|
|
|
|
|
'''
|
|
|
|
|
Plots a circle at the origin
|
|
|
|
|
'''
|
|
|
|
|
c = plt.Circle((0, 0), radius, linewidth=1.5,
|
|
|
|
|
fill=False, **args)
|
|
|
|
|
ax.add_patch(c)
|
|
|
|
|
ax.autoscale_view()
|
|
|
|
|
return c
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_line_color(ax: plt.Axes, x: np.array, y: np.array,
|
|
|
|
|
c: np.array, **args) -> LineCollection:
|
|
|
|
|
'''
|
|
|
|
|
Plots a line with variable color
|
|
|
|
|
'''
|
|
|
|
|
points = np.array([x, y]).T.reshape(-1, 1, 2)
|
|
|
|
|
segments = np.concatenate([points[:-1], points[1:]], axis=1)
|
|
|
|
|
|
|
|
|
|
lines = LineCollection(segments, **args)
|
|
|
|
|
lines.set_array(c)
|
|
|
|
|
ax.add_collection(lines)
|
|
|
|
|
ax.autoscale_view()
|
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_poloidal(inputs: Path, outputs: Path, ax: plt.Axes):
|
|
|
|
|
'''
|
|
|
|
|
Plots raytracing projected in the poloidal plane
|
|
|
|
|
'''
|
|
|
|
|
conf = gray.read_conf(inputs / 'gray.ini')
|
|
|
|
|
central_ray = gray.read_table(outputs / 'central-ray.4.txt')
|
|
|
|
|
|
|
|
|
|
ax.set_title('poloidal view', loc='right')
|
|
|
|
|
ax.set_xlabel('$R$ / m')
|
|
|
|
|
ax.set_ylabel('$z$ / m')
|
|
|
|
|
|
|
|
|
|
# load flux surfaces
|
|
|
|
|
surfaces = gray.read_table(outputs / 'flux-surfaces.71.txt')
|
|
|
|
|
surfaces = surfaces.reshape(-1, int(surfaces['i'].max()))
|
|
|
|
|
|
|
|
|
|
# plot plasma boundary
|
|
|
|
|
bound = np.argmin(1 - surfaces['ψ_n'][:, 0])
|
|
|
|
|
ax.plot(surfaces[bound]['R'], surfaces[bound]['z'],
|
|
|
|
|
c='xkcd:slate grey', label='plasma boundary')
|
|
|
|
|
|
|
|
|
|
# manual contourplot of ψ(R,z)
|
|
|
|
|
for i, surf in enumerate(surfaces[:bound]):
|
|
|
|
|
ax.plot(surf['R'], surf['z'], c='xkcd:grey',
|
|
|
|
|
label='flux surfaces' if i == 0 else '')
|
|
|
|
|
|
|
|
|
|
# label the rational surfaces
|
|
|
|
|
labels = ['q=2', 'q=3/2', 'q=1']
|
|
|
|
|
for label, surf in zip(labels, surfaces[1+bound:][::-1]):
|
|
|
|
|
line = np.vstack([surf['R'], surf['z']]).T
|
|
|
|
|
cont = ContourSet(ax, [1], [[line]], filled=False,
|
|
|
|
|
colors='xkcd:ocean blue',
|
|
|
|
|
linestyles='--')
|
|
|
|
|
ax.clabel(cont, [1], inline=True,
|
|
|
|
|
fontsize=10, fmt={1: label})
|
|
|
|
|
ax.plot(np.nan, np.nan, '--', color='xkcd:ocean blue',
|
|
|
|
|
label='rational surfaces')
|
|
|
|
|
|
|
|
|
|
# load limiter
|
|
|
|
|
limiter = gray.get_limiter(conf, inputs)
|
|
|
|
|
ax.plot(*limiter, c='xkcd:black', label='limiter')
|
|
|
|
|
|
|
|
|
|
# plot cold resonance curve
|
2022-11-13 01:55:25 +01:00
|
|
|
|
try:
|
2024-10-05 15:03:11 +02:00
|
|
|
|
resonance = gray.read_table(outputs / 'ec-resonance.70.txt')
|
|
|
|
|
for n in np.unique(resonance['n']):
|
|
|
|
|
harm = resonance['n'] == n
|
|
|
|
|
ax.plot(resonance['R'][harm], resonance['z'][harm],
|
|
|
|
|
c='xkcd:orange', alpha=n/resonance['n'].max(),
|
|
|
|
|
label=f"resonance $n={int(n)}$")
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# central ray
|
|
|
|
|
plot_line_color(ax, central_ray['R'], central_ray['z'],
|
|
|
|
|
central_ray['α'] * np.exp(-central_ray['τ']),
|
|
|
|
|
cmap='plasma', label='rays')
|
|
|
|
|
|
|
|
|
|
# plot outer rays
|
2022-11-13 01:55:25 +01:00
|
|
|
|
try:
|
2024-10-05 15:03:11 +02:00
|
|
|
|
outer_rays = gray.read_table(outputs / 'outer-rays.33.txt')
|
|
|
|
|
for k in np.unique(outer_rays['k']):
|
|
|
|
|
ray = outer_rays[outer_rays['k'] == k]
|
|
|
|
|
plot_line_color(ax, ray['R'], ray['z'], ray['α']*np.exp(-ray['τ']),
|
|
|
|
|
cmap='plasma', alpha=0.3)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_toroidal(inputs: Path, outputs: Path, ax: plt.Axes):
|
|
|
|
|
'''
|
|
|
|
|
Plots raytracing projected in the poloidal plane
|
|
|
|
|
'''
|
|
|
|
|
conf = gray.read_conf(inputs / 'gray.ini')
|
|
|
|
|
central_ray = gray.read_table(outputs / 'central-ray.4.txt')
|
|
|
|
|
|
|
|
|
|
ax.set_title('toroidal view', loc='right')
|
|
|
|
|
ax.set_xlabel('$x$ / m')
|
|
|
|
|
ax.set_ylabel('$y$ / m')
|
|
|
|
|
|
|
|
|
|
limiter = gray.get_limiter(conf, inputs)
|
|
|
|
|
|
|
|
|
|
# plot plasma boundary
|
|
|
|
|
surfaces = gray.read_table(outputs / 'flux-surfaces.71.txt')
|
|
|
|
|
boundary = surfaces[np.isclose(surfaces['ψ_n'], 1, 1e-3)]
|
|
|
|
|
|
|
|
|
|
# plot plasma boundary
|
|
|
|
|
plot_circle(ax, radius=boundary['R'].min(), color='xkcd:slate gray')
|
|
|
|
|
plot_circle(ax, radius=boundary['R'].max(), color='xkcd:slate gray')
|
|
|
|
|
|
|
|
|
|
# plot limiter
|
|
|
|
|
if limiter[0].size > 0:
|
|
|
|
|
Rmax = limiter[0].max() * 1.1
|
|
|
|
|
plot_circle(ax, radius=limiter[0].min(),
|
|
|
|
|
color='xkcd:black')
|
|
|
|
|
plot_circle(ax, radius=limiter[0].max(),
|
|
|
|
|
color='xkcd:black')
|
|
|
|
|
# set bounds
|
|
|
|
|
ax.set_xlim(-Rmax, Rmax)
|
|
|
|
|
ax.set_ylim(-Rmax, Rmax)
|
|
|
|
|
|
|
|
|
|
# plot cold resonance curve
|
2022-11-13 01:55:25 +01:00
|
|
|
|
try:
|
2024-10-05 15:03:11 +02:00
|
|
|
|
resonance = gray.read_table(outputs / 'ec-resonance.70.txt')
|
|
|
|
|
for n in np.unique(resonance['n']):
|
|
|
|
|
harm = resonance['n'] == n
|
|
|
|
|
plot_circle(ax, radius=resonance['R'][harm].max(),
|
|
|
|
|
color='xkcd:orange', alpha=n/resonance['n'].max())
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# central ray
|
|
|
|
|
for rt in np.unique(central_ray['index_rt']):
|
|
|
|
|
ray = central_ray[central_ray['index_rt'] == rt]
|
|
|
|
|
x = ray['R'] * np.cos(np.radians(ray['φ']))
|
|
|
|
|
y = ray['R'] * np.sin(np.radians(ray['φ']))
|
|
|
|
|
dPds = ray['α'] * np.exp(-ray['τ'])
|
|
|
|
|
plot_line_color(ax, x, y, dPds, cmap='plasma')
|
|
|
|
|
|
|
|
|
|
# outer rays
|
2022-11-13 01:55:25 +01:00
|
|
|
|
try:
|
2024-10-05 15:03:11 +02:00
|
|
|
|
outer_rays = gray.read_table(outputs / 'outer-rays.33.txt')
|
|
|
|
|
for k in np.unique(outer_rays['k']):
|
|
|
|
|
for rt in np.unique(outer_rays['index_rt']):
|
|
|
|
|
ray = outer_rays[ (outer_rays['k'] == k)
|
|
|
|
|
& (outer_rays['index_rt'] == rt)]
|
|
|
|
|
dPds = ray['α'] * np.exp(-ray['τ'])
|
|
|
|
|
plot_line_color(ax, ray['x'], ray['y'], dPds,
|
|
|
|
|
cmap='plasma', alpha=0.3)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_beam(outputs: Path, axes: [plt.Axes]):
|
|
|
|
|
'''
|
|
|
|
|
Plots the outermost rays in the local beam frame
|
|
|
|
|
'''
|
|
|
|
|
rays = gray.read_table(outputs / 'beam-shape.8.txt')
|
|
|
|
|
nrays = int(max(rays['k']))
|
|
|
|
|
|
|
|
|
|
cm = 0.01 # 1cm in m
|
|
|
|
|
cmap = plt.cm.viridis
|
|
|
|
|
|
|
|
|
|
# side view of rays, y(z)
|
|
|
|
|
axes['A'].set_title('rays, side view', loc='right')
|
|
|
|
|
axes['A'].set_ylabel('$y$ / cm')
|
|
|
|
|
|
|
|
|
|
# top view of rays, x(z)
|
|
|
|
|
axes['B'].set_title('rays, top view', loc='right')
|
|
|
|
|
axes['B'].set_ylabel('$x$ / cm')
|
|
|
|
|
axes['B'].set_xlabel('$z$ / m')
|
|
|
|
|
|
|
|
|
|
# 3D view
|
|
|
|
|
axes['C'].set_title('beam surface', loc='right')
|
|
|
|
|
axes['C'].set_xlabel('$z$ / m', labelpad=11)
|
|
|
|
|
axes['C'].set_ylabel('$x$ / cm', labelpad=-2)
|
|
|
|
|
axes['C'].set_zlabel('$y$ / cm', labelpad=-2)
|
|
|
|
|
axes['C'].set_box_aspect(aspect=(2.5, 1, 1), zoom=1.2)
|
|
|
|
|
axes['C'].view_init(azim=-44)
|
|
|
|
|
|
|
|
|
|
# plot the beam envelope
|
|
|
|
|
try:
|
|
|
|
|
size = gray.read_table(outputs / 'beam-size.12.txt')
|
|
|
|
|
for i in ['A', 'B']:
|
|
|
|
|
style = dict(c='xkcd:grey', ls=':', lw=1)
|
|
|
|
|
axes[i].plot(size['s']*cm, +size['r_max'], **style)
|
|
|
|
|
axes[i].plot(size['s']*cm, -size['r_max'], **style)
|
|
|
|
|
axes[i].plot(size['s']*cm, +size['r_min'], **style)
|
|
|
|
|
axes[i].plot(size['s']*cm, -size['r_min'], **style)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
for k in np.arange(1, nrays+1):
|
|
|
|
|
ray = rays[rays['k'] == k]
|
|
|
|
|
color = cmap(k / max(rays['k']))
|
|
|
|
|
z = (ray['s'] + ray['z']) * cm
|
|
|
|
|
# top projections
|
|
|
|
|
axes['B'].plot(z, ray['x'], alpha=0.6, c=color, lw=1)
|
|
|
|
|
axes['C'].plot(z, ray['x'], zs=rays['y'].min(), zdir='z',
|
|
|
|
|
alpha=0.6, c=color, lw=1)
|
|
|
|
|
# side projections
|
|
|
|
|
axes['A'].plot(z, ray['y'], alpha=0.8, c=color, lw=1)
|
|
|
|
|
axes['C'].plot(z, ray['y'], zs=rays['x'].max(), zdir='y',
|
|
|
|
|
alpha=0.6, c=color, lw=1)
|
|
|
|
|
|
|
|
|
|
# adjust ticks spacing
|
|
|
|
|
plt.setp(axes['C'].get_yticklabels(), rotation=-25, ha='left')
|
|
|
|
|
axes['C'].tick_params(axis='y', pad=-5)
|
|
|
|
|
axes['C'].tick_params(axis='z', pad=0)
|
|
|
|
|
|
|
|
|
|
# wrap arrays into 2D meshes for plot_surface
|
|
|
|
|
k = rays['k'].reshape(-1, nrays)
|
|
|
|
|
x = rays['x'].reshape(-1, nrays)
|
|
|
|
|
y = rays['y'].reshape(-1, nrays)
|
|
|
|
|
z = (rays['s'] + rays['z']).reshape(-1, nrays) * cm
|
|
|
|
|
|
|
|
|
|
# make the xy slices closed
|
|
|
|
|
x = np.column_stack([x, x[:, 0]])
|
|
|
|
|
y = np.column_stack([y, y[:, 0]])
|
|
|
|
|
z = np.column_stack([z, z[:, 0]])
|
|
|
|
|
|
|
|
|
|
# plot beam surface, colored by ray indices
|
|
|
|
|
axes['C'].plot_surface(z, x, y, facecolors=cmap(k/nrays), alpha=0.6)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_profiles(outputs: Path, axes: [plt.Axes]):
|
|
|
|
|
'''
|
|
|
|
|
Plots the power and current profiles
|
|
|
|
|
'''
|
|
|
|
|
profiles = gray.read_table(outputs / 'ec-profiles.48.txt')
|
|
|
|
|
|
|
|
|
|
first = profiles['index_rt'] == profiles['index_rt'].min()
|
|
|
|
|
ρ_p, ρ_t = profiles[first]['ρ_p'], profiles[first]['ρ_t']
|
|
|
|
|
|
|
|
|
|
for _, ax in axes.items():
|
|
|
|
|
# set ρ_t to the x axis
|
|
|
|
|
ax.set_xlabel('$ρ_t$')
|
|
|
|
|
ax.set_xlim(-0.05, 1.05)
|
|
|
|
|
ax.ticklabel_format(useMathText=True)
|
|
|
|
|
|
|
|
|
|
# add secondary x axis for ρ_p
|
|
|
|
|
add_alt_axis(ax, ρ_t, ρ_p, label='$ρ_p$')
|
|
|
|
|
|
|
|
|
|
cur_dens = axes['A']
|
|
|
|
|
cur_dens.set_title('current density', loc='right')
|
|
|
|
|
cur_dens.set_ylabel(r'$J_\text{CD} \;/\;$ kA/m³')
|
|
|
|
|
|
|
|
|
|
pow_dens = axes['B']
|
|
|
|
|
pow_dens.set_title('power density', loc='right')
|
|
|
|
|
pow_dens.set_ylabel(r'$dP/dV \;/\;$ MW/m³')
|
|
|
|
|
|
|
|
|
|
cur_ins = axes['C']
|
|
|
|
|
cur_ins.set_title('current within $ρ$', loc='right')
|
|
|
|
|
cur_ins.set_ylabel(r'$I_\text{CD,inside} \;/\;$ kA')
|
|
|
|
|
|
|
|
|
|
pow_ins = axes['D']
|
|
|
|
|
pow_ins.set_title('power within $ρ$', loc='right')
|
|
|
|
|
pow_ins.set_ylabel(r'$P_\text{inside} \;/\;$ MW')
|
|
|
|
|
|
|
|
|
|
styles = [':', '-', '--', '-.']
|
|
|
|
|
colors = dict(O='xkcd:orange', X='xkcd:ocean blue')
|
|
|
|
|
|
|
|
|
|
for i in np.unique(profiles['index_rt']):
|
|
|
|
|
mode, passes = gray.decode_index_rt(int(i))
|
|
|
|
|
style = dict(c=colors[mode], ls=styles[passes % 4])
|
|
|
|
|
|
|
|
|
|
slice = profiles[ (profiles['index_rt'] == i)
|
|
|
|
|
& (profiles['dPdV'] > 1e-15)]
|
|
|
|
|
cur_dens.plot(slice['ρ_p'], slice['J_cdb'], **style)
|
|
|
|
|
pow_dens.plot(slice['ρ_p'], slice['dPdV'], **style)
|
|
|
|
|
|
|
|
|
|
slice = profiles[ (profiles['index_rt'] == i)
|
|
|
|
|
& (profiles['P_inside'] > 1e-15)]
|
|
|
|
|
cur_ins.plot(slice['ρ_p'], slice['I_cd_inside'], **style)
|
|
|
|
|
pow_ins.plot(slice['ρ_p'], slice['P_inside'], **style)
|
|
|
|
|
|
|
|
|
|
# legend
|
|
|
|
|
plt.plot(np.nan, np.nan, label='O mode', c='xkcd:orange')
|
|
|
|
|
plt.plot(np.nan, np.nan, label='X mode', c='xkcd:ocean blue')
|
|
|
|
|
plt.plot(np.nan, np.nan, label='1° pass', c='xkcd:black', ls=styles[1])
|
|
|
|
|
plt.plot(np.nan, np.nan, label='2° pass', c='xkcd:black', ls=styles[2])
|
|
|
|
|
plt.plot(np.nan, np.nan, label='3° pass', c='xkcd:black', ls=styles[3])
|
|
|
|
|
plt.plot(np.nan, np.nan, label='4° pass', c='xkcd:black', ls=styles[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_inputs(inputs: Path, outputs: Path, axes: [plt.Axes]):
|
|
|
|
|
'''
|
|
|
|
|
Plot the input plasma profiles and MHD equilibrium
|
|
|
|
|
'''
|
|
|
|
|
from matplotlib.colors import TwoSlopeNorm
|
|
|
|
|
|
|
|
|
|
maps = gray.read_table(outputs / 'inputs-maps.72.txt')
|
|
|
|
|
|
|
|
|
|
# filter valid points
|
|
|
|
|
maps = maps[maps['ψ_n'] > 0]
|
|
|
|
|
flux = maps['R'], maps['z'], maps['ψ_n']
|
|
|
|
|
|
|
|
|
|
# contour levels
|
|
|
|
|
inner_levels = np.linspace(0, 0.99, 8)
|
|
|
|
|
outer_levels = np.linspace(0.9991, maps['ψ_n'].max()*0.99, 6)
|
|
|
|
|
all_levels = np.concatenate([inner_levels, outer_levels])
|
|
|
|
|
|
|
|
|
|
# contour style
|
|
|
|
|
norm = TwoSlopeNorm(vmin=0, vcenter=1, vmax=maps['ψ_n'].max())
|
|
|
|
|
cmap = plt.cm.RdYlGn_r
|
|
|
|
|
borders = dict(colors='xkcd:grey', linestyles='-', linewidths=0.8)
|
|
|
|
|
|
|
|
|
|
# interpolated equilibrium
|
|
|
|
|
interp = axes['B']
|
|
|
|
|
interp.set_title('poloidal flux', loc='right')
|
|
|
|
|
interp.set_xlabel('$R$ / m')
|
|
|
|
|
interp.set_ylabel('$z$ / m')
|
|
|
|
|
interp.tricontourf(*flux, levels=all_levels, norm=norm, cmap=cmap)
|
|
|
|
|
interp.tricontour(*flux, levels=all_levels, **borders)
|
|
|
|
|
interp.tricontour(*flux, levels=[1], colors='xkcd:slate gray')
|
|
|
|
|
interp.plot(np.nan, np.nan, c='xkcd:slate gray', label='plasma boundary')
|
|
|
|
|
|
|
|
|
|
# add limiter
|
|
|
|
|
conf = gray.read_conf(inputs / 'gray.ini')
|
|
|
|
|
limiter = gray.get_limiter(conf, inputs)
|
|
|
|
|
interp.plot(*limiter, c='xkcd:black', label='limiter')
|
|
|
|
|
|
|
|
|
|
# original MHD equilibrium
|
|
|
|
|
orig = axes['A']
|
|
|
|
|
if conf['equilibrium'].get('iequil') != 'EQ_ANALYTICAL':
|
|
|
|
|
file = conf['equilibrium']['filenm'].strip('"')
|
|
|
|
|
eqdsk = gray.read_eqdsk(inputs / file)
|
|
|
|
|
orig.set_title('input G-EQDSK flux', loc='right')
|
|
|
|
|
orig.set_xlabel('$R$ / m')
|
|
|
|
|
orig.set_ylabel('$z$ / m')
|
|
|
|
|
orig.contourf(*eqdsk.flux, levels=inner_levels, norm=norm, cmap=cmap)
|
|
|
|
|
orig.contour(*eqdsk.flux, levels=inner_levels, **borders)
|
|
|
|
|
orig.plot(*eqdsk.limiter, c='xkcd:black')
|
|
|
|
|
orig.plot(*eqdsk.boundary, c='xkcd:slate gray')
|
2022-11-13 01:55:25 +01:00
|
|
|
|
else:
|
2024-10-05 15:03:11 +02:00
|
|
|
|
orig.axis('off')
|
|
|
|
|
|
|
|
|
|
# colorbar
|
|
|
|
|
bar = plt.colorbar(plt.cm.ScalarMappable(norm, cmap), cax=axes['C'])
|
|
|
|
|
bar.set_label(label='normalised poloidal flux', labelpad=10)
|
|
|
|
|
|
|
|
|
|
# Plasma radial profiles
|
|
|
|
|
profiles = gray.read_table(outputs / 'kinetic-profiles.55.txt')
|
|
|
|
|
|
|
|
|
|
axes['D'].set_title('plasma profiles', loc='right')
|
|
|
|
|
add_alt_axis(axes['D'], profiles['ρ_t'], profiles['ψ_n']**0.5,
|
|
|
|
|
label='$ρ_p$')
|
|
|
|
|
|
|
|
|
|
# density
|
|
|
|
|
dens = axes['D']
|
|
|
|
|
dens.set_xlabel('$ρ_t$')
|
|
|
|
|
dens.set_ylabel('$n_e$ / 10²⁰m⁻³', color='xkcd:ocean blue')
|
|
|
|
|
dens.plot(profiles['ρ_t'], profiles['n_e'], c='xkcd:ocean blue')
|
|
|
|
|
|
|
|
|
|
# temperature
|
|
|
|
|
temp = axes['D'].twinx()
|
|
|
|
|
temp.set_ylabel('$T_e$ / keV', color='xkcd:orange')
|
|
|
|
|
temp.plot(profiles['ρ_t'], profiles['T_e'], c='xkcd:orange')
|
|
|
|
|
|
|
|
|
|
# safety factor
|
|
|
|
|
inside = profiles['ρ_t'] < 1
|
|
|
|
|
saft = axes['D'].twinx()
|
|
|
|
|
saft.set_ylabel('$q$', color='xkcd:moss')
|
|
|
|
|
saft.plot(profiles['ρ_t'][inside], profiles['q'][inside], c='xkcd:moss')
|
|
|
|
|
saft.spines["right"].set_position(("axes", 1.11))
|
|
|
|
|
|
|
|
|
|
align_yaxis(dens, temp)
|
|
|
|
|
align_yaxis(temp, saft)
|
|
|
|
|
|
|
|
|
|
# original data points
|
|
|
|
|
if conf['profiles']['iprof'] == 'PROF_NUMERIC':
|
|
|
|
|
# load input profiles
|
|
|
|
|
orig = np.loadtxt(inputs / conf['profiles']['filenm'].strip('"'),
|
|
|
|
|
skiprows=1)
|
|
|
|
|
|
|
|
|
|
# covert 1st column to ρ_t
|
|
|
|
|
var = conf['profiles']['irho']
|
|
|
|
|
if var == 'RHO_TOR':
|
|
|
|
|
prof_ρ = orig[:, 0]
|
|
|
|
|
elif var == 'RHO_POL':
|
|
|
|
|
prof_ρ = np.interp(orig[:, 0], profiles['ψ_n']**0.5,
|
|
|
|
|
profiles['ρ_t'])
|
|
|
|
|
elif var == 'RHO_PSI':
|
|
|
|
|
prof_ρ = np.interp(orig[:, 0], profiles['ψ_n'], profiles['ρ_t'])
|
|
|
|
|
|
|
|
|
|
saft_ρ = np.interp(eqdsk.q[0], profiles['ψ_n']**0.5, profiles['ρ_t'])
|
|
|
|
|
|
|
|
|
|
# add to existing plot
|
|
|
|
|
style = dict(marker='o', s=2)
|
|
|
|
|
temp.scatter(prof_ρ, orig[:, 1], c='xkcd:orange', **style)
|
|
|
|
|
dens.scatter(prof_ρ, orig[:, 2], c='xkcd:ocean blue', **style)
|
|
|
|
|
saft.scatter(saft_ρ, eqdsk.q[1], c='xkcd:moss', **style)
|
|
|
|
|
|
|
|
|
|
# legend
|
|
|
|
|
axes['D'].plot(np.nan, np.nan, label='spline', c='k')
|
|
|
|
|
axes['D'].scatter(np.nan, np.nan, label='data', c='k', **style)
|
|
|
|
|
axes['D'].legend(loc='center left')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(kind: str, args: argparse.Namespace) -> (plt.Figure, [float]):
|
|
|
|
|
'''
|
|
|
|
|
Draws a single figure of a given kind
|
|
|
|
|
'''
|
|
|
|
|
cm = 0.3937 # 1cm in inches
|
|
|
|
|
rect = [0, 0, 1, 1] # default layout rect
|
|
|
|
|
|
|
|
|
|
if kind == 'raytracing':
|
|
|
|
|
fig, axes = plt.subplots(
|
|
|
|
|
1, 2, figsize=(19.8*cm, 11*cm),
|
|
|
|
|
tight_layout=True,
|
|
|
|
|
subplot_kw=dict(aspect='equal'))
|
|
|
|
|
plot_poloidal(args.inputs, args.outputs, axes[0])
|
|
|
|
|
plot_toroidal(args.inputs, args.outputs, axes[1])
|
|
|
|
|
|
|
|
|
|
if args.legend:
|
|
|
|
|
rect = [0.2, 0, 1, 1]
|
|
|
|
|
fig.tight_layout(rect=rect)
|
|
|
|
|
fig.legend(loc='upper left')
|
|
|
|
|
|
|
|
|
|
elif kind == 'beam':
|
|
|
|
|
fig, axes = plt.subplot_mosaic(
|
|
|
|
|
'ACD;BCD', figsize=(24*cm, 10*cm),
|
|
|
|
|
per_subplot_kw={'C': dict(projection='3d')},
|
|
|
|
|
width_ratios=[1, 1, 0.21])
|
|
|
|
|
|
|
|
|
|
# manually adjust spacing (tight_layout is failing)
|
|
|
|
|
axes['D'].axis('off')
|
|
|
|
|
plt.subplots_adjust(hspace=0.5)
|
|
|
|
|
plot_beam(args.outputs, axes)
|
|
|
|
|
|
|
|
|
|
elif kind == 'profiles':
|
|
|
|
|
fig, axes = plt.subplot_mosaic(
|
|
|
|
|
'AB;CD', figsize=(23.11*cm, 13*cm),
|
|
|
|
|
tight_layout=True)
|
|
|
|
|
plot_profiles(args.outputs, axes)
|
|
|
|
|
|
|
|
|
|
if args.legend:
|
|
|
|
|
rect = [0.12, 0, 1, 1]
|
|
|
|
|
fig.tight_layout(rect=rect)
|
|
|
|
|
fig.legend(loc='upper left')
|
|
|
|
|
|
|
|
|
|
elif kind == 'inputs':
|
|
|
|
|
fig, axes = plt.subplot_mosaic(
|
|
|
|
|
'ABC;DDD', figsize=(19.8*cm, 20*cm),
|
|
|
|
|
tight_layout=True,
|
|
|
|
|
height_ratios=[1.5, 1],
|
|
|
|
|
width_ratios=[1, 1, 0.05],
|
|
|
|
|
per_subplot_kw={'AB': dict(aspect='equal')})
|
|
|
|
|
plot_inputs(args.inputs, args.outputs, axes)
|
|
|
|
|
|
|
|
|
|
return fig, rect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
args = cli_args()
|
|
|
|
|
|
|
|
|
|
def on_resize(event):
|
|
|
|
|
'''
|
|
|
|
|
Update layout on window resizes
|
|
|
|
|
'''
|
|
|
|
|
fig.tight_layout(rect=rect)
|
|
|
|
|
fig.canvas.draw()
|
|
|
|
|
|
|
|
|
|
if args.interactive:
|
|
|
|
|
plt.ion()
|
|
|
|
|
|
|
|
|
|
for kind in args.kind:
|
|
|
|
|
fig, rect = main(kind, args)
|
|
|
|
|
if args.interactive:
|
|
|
|
|
fig.canvas.mpl_connect('resize_event', on_resize)
|
|
|
|
|
if args.outfile is not None:
|
|
|
|
|
if len(args.kind) > 1:
|
|
|
|
|
# append plot kind to the output name
|
|
|
|
|
fname = args.outfile.with_stem(args.outfile.stem + '-' + kind)
|
|
|
|
|
else:
|
|
|
|
|
fname = args.outfile
|
|
|
|
|
fig.savefig(fname, bbox_inches='tight')
|
|
|
|
|
|
|
|
|
|
if args.interactive:
|
|
|
|
|
input('press enter to quit')
|