try:
# framework is running
from .startup_choice import *
except ImportError as _excp:
# class is imported by itself
if (
'attempted relative import with no known parent package' in str(_excp)
or 'No module named \'omfit_classes\'' in str(_excp)
or "No module named '__main__.startup_choice'" in str(_excp)
):
from startup_choice import *
else:
raise
from omfit_classes.omfit_ascii import OMFITascii
from omfit_classes.omfit_nc import OMFITnc
from omfit_classes import namelist
from collections import OrderedDict
__all__ = ['OMFIToedgeInput', 'OMFIToedgeRun', 'OMFIToedgeNC']
def interpret(x):
x = x.split('\'')
for k in range(1, len(x), 1)[::2]:
x[k] = '\'' + x[k] + '\''
x = [_f for _f in x if _f]
xx = []
for k in x:
if k.startswith('\''):
xx.append(k)
else:
xx.extend(list(map(namelist.interpreter, [_f for _f in re.split(' |\t', k) if _f])))
data = []
inline_comment = ''
for k in range(len(xx)):
if not len(inline_comment) and is_numeric(xx[k]):
data.append(xx[k])
elif not len(inline_comment) and xx[k][0] == '\'':
data.append(xx[k][1:-1])
else:
inline_comment += ' ' + str(xx[k])
inline_comment = inline_comment.strip(' \'')
return data, inline_comment
[docs]class OMFIToedgeRun(SortedDict):
pass
def __init__(self, casename=None, caseloc=None, runloc=None, plot_file_name=None, grid=None, background=None, fc=None, divref=None):
self.casename = casename
self.caseloc = caseloc
self.runloc = runloc
self.basestr = ''
self['casename'] = self.casename
self['plot'] = self.plot_file_name
self['grid'] = self.casename
self['casename'] = self.casename
def __call__(self, *args):
# Allow the run object to return the string representation of its tree elements
# by checking case is eval(case()) in the calling routine - can verify object is loaded where expected
if len(args) == 0:
return self.basestr
else:
tmp = self.basestr
for arg in args:
tmp = tmp + "['" + arg + "']"
return tmp
[docs]class OMFIToedgeNC(OMFITnc):
"""OEDGE output NC data file"""
plot_types_available = ['contour', 'along ring']
# possible future plots to be added
# plot_types_available = ['contour','along ring','diagnostic','along wall','along target']
# background plasma quantities
# present for cases with a background plasma (basically everything)
bg_plots = OrderedDict()
bg_plots['ne'] = {
'data': ['KNBS'],
'targ': ['KNDS'],
'label': 'Density',
'units': 'm-3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['Te'] = {
'data': ['KTEBS'],
'targ': ['KTEDS'],
'label': 'Te',
'units': 'eV',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['Ti'] = {
'data': ['KTIBS'],
'targ': ['KTIDS'],
'label': 'Ti',
'units': 'eV',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['Vb'] = {
'data': ['KVHS'],
'targ': ['KVDS'],
'label': 'Vpara',
'units': 'm/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
'scale': '1/QTIM',
}
bg_plots['Plasma'] = {
'data': ['ne', 'Te', 'Ti', 'Vb'],
'label': 'Background Plasma',
'lineaxes': ['P', 'S'],
'group': True,
'notebook': False,
}
bg_plots['E'] = {
'data': ['KES'],
'targ': ['KEDS'],
'label': 'Epara',
'units': 'V/m',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['ExB Pol'] = {
'data': ['E_POL'],
'targ': None,
'label': 'Epol',
'units': 'V/m',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['ExB Rad'] = {
'data': ['E_RAD'],
'targ': None,
'label': 'Epol',
'units': 'V/m',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['Efields'] = {
'data': ['E', 'ExB Pol', 'ExB Rad'],
'label': 'Electric Fields',
'lineaxes': ['P', 'S'],
'group': True,
'notebook': False,
}
bg_plots['V_pol'] = {
'data': ['EXB_P'],
'targ': None,
'label': 'Vpol',
'units': 'm/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['V_rad'] = {
'data': ['EXB_R'],
'targ': None,
'label': 'Vrad',
'units': 'm/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
bg_plots['Drifts'] = {'data': ['V_pol', 'V_rad'], 'label': 'Drifts', 'lineaxes': ['P', 'S'], 'group': True, 'notebook': False}
# Data present for cases that ran EIRENE to generate hydrogenic quantities
h_plots = OrderedDict()
h_plots['H Power'] = {
'data': ['HPOWLS'],
'targ': None,
'label': 'Hydrogen Radiated Power',
'units': 'W/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Line'] = {
'data': ['HLINES'],
'targ': None,
'label': 'Hydrogen Line Radiation',
'units': 'W/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Dalpha'] = {
'data': ['PINALP'],
'targ': None,
'label': 'Hydrogen Dalpha Emission',
'units': 'ph/m3/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Ionization'] = {
'data': ['PINION'],
'targ': None,
'label': 'Hydrogen Ionization',
'units': '/m3/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Recomb'] = {
'data': ['PINREC'],
'targ': None,
'label': 'Hydrogen Recombination',
'units': '/m3/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H2 Density'] = {
'data': ['PINMOL'],
'targ': None,
'label': 'Hydrogen Molecule Density',
'units': '/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H0 Density'] = {
'data': ['PINATO'],
'targ': None,
'label': 'Hydrogen Atom Density',
'units': '/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Atom Temp'] = {
'data': ['PINENA'],
'targ': None,
'label': 'Hydrogen Atom Temperature',
'units': 'eV',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Mol Temp'] = {
'data': ['PINENM'],
'targ': None,
'label': 'Hydrogen Molecule Temperature',
'units': 'eV',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Ion Energy Loss'] = {
'data': ['PINQI'],
'targ': None,
'label': 'Hydrogen-Ion Energy Loss Term',
'units': 'W/m3(?)',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Elec Energy Loss'] = {
'data': ['PINQE'],
'targ': None,
'label': 'Hydrogen-Electron Energy Loss Term',
'units': 'W/m3(?)',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
h_plots['H Quantities'] = {
'data': ['H0 Density', 'H2 Density', 'H Ionization', 'H Recomb'],
'label': 'Hydrogen Quantities',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
# Data present for cases that ran impurities
imp_plots = OrderedDict()
imp_plots['Imp Density'] = {
'data': ['DDLIMS'],
'targ': None,
'label': 'Impurity Density',
'units': '/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
'scale': 'ABSFAC',
}
imp_plots['Imp Temperature'] = {
'data': ['DDTS'],
'targ': None,
'label': 'Impurity Temperature',
'units': 'eV',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
}
imp_plots['Imp Ionization'] = {
'data': ['TIZS'],
'targ': None,
'label': 'Impurity Ionization',
'units': '/m3/s',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
'scale': 'ABSFAC',
}
imp_plots['Imp Radiated Power'] = {
'data': ['POWLS'],
'targ': None,
'label': 'Impurity Radiated Power',
'units': 'W/m3',
'lineaxes': ['P', 'S'],
'group': False,
'notebook': False,
'scale': 'ABSFAC',
}
imp_plots['Impurity Quantities'] = {
'data': ['Imp Density', 'Imp Temperature', 'Imp Ionization', 'Imp Radiated Power'],
'label': 'Impurity Quantities',
'lineaxes': ['P', 'S'],
'group': True,
'notebook': False,
}
# Plots along surfaces (?) separate category or make a different dict entry under appropriate sections?
surface_plots = OrderedDict()
# Synthetic diagnostic plots are a separate category since they will require special treatment
diagnostic_plots = OrderedDict()
# available plot types - contour/false colour plots, line plots along ring, line plots along surfaces (use different plotting axes)
plot_types = ['contour-polygon', 'contour-interpolate', 'along ring', 'surface']
# default_contour = 'contour-polygon'
all_plots = OrderedDict()
all_plots.update(bg_plots)
all_plots.update(h_plots)
all_plots.update(imp_plots)
all_plots.update(surface_plots)
plot_categories = ['background', 'hydrogen', 'impurity', 'surface', 'diagnostic']
def __init__(self, filename, **kw):
OMFITnc.__init__(self, filename, **kw)
self.dynaLoad = True
[docs] @dynaLoad
def load(self):
# empty the dictionary in case this is a reload
self.clear()
# print('Load netcdf:')
OMFITnc.load(self)
#
# Do some post-processing after loading
#
# add anything else it should automatically do with the data on load ...
# should these be done in the load routine or in the constructor?
# Presumably the constructor calls load()
# print('Load self variables:')
self.load_simulation_data()
self.plots_available()
[docs] def load_simulation_data(self):
# This routine labels specific geometry data from the NC file into local variables
# This data is frequently used in the plots .. since it appears that one case object
# handles all the plotting then it is worthwhile adding some efficiencies
# Certain data must be present to make plots possible ... if it is not available for
# some reason then flag it as an error.
# load wall and create path object to define inside and outside the vessel
# This is used in the tricontourf plots to eliminate triangles outside the vessel
debug = True
# vessel wall definition
self.nves = self['NVES']['data']
self.rves = self['RVES']['data'][: self.nves]
self.zves = self['ZVES']['data'][: self.nves]
self.wall = [[self.rves[i], self.zves[i]] for i in range(self.nves)]
self.wall_path = matplotlib.path.Path(self.wall)
# load the computational mesh polygon data into a PolyCollection for plotting code results
# remove zero volume polygons from the list and also exclude when loading simulation data
self.nvertp = self['NVERTP']['data']
self.rvertp = self['RVERTP']['data']
self.zvertp = self['ZVERTP']['data']
self.korpg = self['KORPG']['data']
# cell centers
self.rs = self['RS']['data']
self.zs = self['ZS']['data']
# Significant surfaces in the computational mesh
self.nrs = self['NRS']['data']
self.nks = self['NKS']['data']
self.irsep = self['IRSEP']['data']
# no irsep2 in netcdf
# self.irsep2 = self['IRSEP2']['data']
self.irwall = self['IRWALL']['data']
self.irwall2 = self['IRWALL2']['data']
self.irtrap = self['IRTRAP']['data']
self.irtrap2 = self['IRTRAP2']['data']
# grid connection map data
self.ikins = self['IKINS']['data']
self.ikouts = self['IKOUTS']['data']
self.irins = self['IRINS']['data']
self.irouts = self['IROUTS']['data']
# properties of the grid and target
self.area = self['KAREAS']['data']
# idds is an index array mapping from (targ,ir) -> (target index)
# However, the index is from fortran arrays and numbers from 1
# so when mapped to python arrays you need to subtract 1 which
# should work if idds is a np array
self.idds = self['IDDS']['data'].copy() - 1
self.smax = self['KSMAXS']['data']
self.pmax = self['KPMAXS']['data']
self.kss = self['KSS']['data']
self.kps = self['KPS']['data']
mesh = []
count = 0
debug = True
for ir in range(self.nrs):
for ik in range(self.nks[ir]):
index = self.korpg[ir, ik] - 1
if self.area[ir, ik] != 0.0:
mesh.append(list(zip(self.rvertp[index][0:4], self.zvertp[index][0:4])))
count = count + 1
# check to see that the cell center point is inside the set of vertices for the cell
# this verifies that the grid is loading correctly
if debug and self.area[ir, ik] != 0.0:
vert = list(zip(self.rvertp[index][0:4], self.zvertp[index][0:4]))
cell = matplotlib.path.Path(vert)
r = self['RS']['data'][ir, ik]
z = self['ZS']['data'][ir, ik]
if not cell.contains_point([r, z]):
print("grid loading error:")
print("vert:", vert)
print("test:", ir, ik, r, z, cell.contains_point([r, z]))
# save the number of non-zero volume cells in the mesh
# save the mesh polygons as in grid
self.num_cells = count
self.grid = mesh
# The grid is used to create a polygon plot
# coll = PolyCollection(self.grid,array=data,cmap=pyplot.cm.jet,edgecolors='none')
#
# The above created a mesh of polygons from the computational grid
# Also needed is a mesh composed of the cell center points both with and without the
# target coordinates. These meshes have two uses:
# 1) Interpolated triangular meshes which are based on cell center and target data
# 2) Interpolation of the OEDGE results to generate an ERO plasma file.
#
self.num_points = self.num_cells + (self.nrs - self.irsep + 1) * 2
self.r_cells = np.zeros(self.num_cells)
self.z_cells = np.zeros(self.num_cells)
self.r_points = np.zeros(self.num_points)
self.z_points = np.zeros(self.num_points)
cp = 0
cc = 0
for ir in range(self.nrs):
for ik in range(self.nks[ir]):
if ik == 0 and ir >= self.irsep - 1:
# add first target point
id0 = self.idds[1, ir]
self.r_points[cp] = self['RP']['data'][id0]
self.z_points[cp] = self['ZP']['data'][id0]
cp = cp + 1
if self.area[ir, ik] != 0.0:
self.r_points[cp] = self.rs[ir][ik]
self.z_points[cp] = self.zs[ir][ik]
cp = cp + 1
self.r_cells[cc] = self.rs[ir][ik]
self.z_cells[cc] = self.zs[ir][ik]
cc = cc + 1
if ik == self.nks[ir] - 1 and ir >= self.irsep - 1:
# add last target point
id1 = self.idds[0, ir]
self.r_points[cp] = self['RP']['data'][id1]
self.z_points[cp] = self['ZP']['data'][id1]
cp = cp + 1
# Impurity scaling factor if available
# All impurity results are stored scaled to 1 part/m-tor/s entering the system
# to get absolute values the results are multipled by absfac (absolute scaling factor)
if 'ABSFAC' in self:
self.absfac = self['ABSFAC']['data']
else:
self.absfac = 1.0
# Simulation time steps
# ion time step
if 'QTIM' in self:
self.qtim = self['QTIM']['data']
else:
self.qtim = 1.0
# neutral time step
if 'FSRATE' in self:
self.fsrate = self['FSRATE']['data']
else:
self.fsrate = 1.0
# print('qtim:',self.qtim,self.fsrate)
if 'NIZS' in self:
self.nizs = self['NIZS']['data']
else:
self.nizs = None
[docs] def plots_available(self):
# this routine returns a list of quantities that this object can plot
# background plasma plots
self.plots_avail = []
self.bg_plots_avail = []
self.h_plots_avail = []
self.imp_plots_avail = []
self.surf_plots_avail = []
for plotkey in list(OMFIToedgeNC.all_plots.keys()):
# Check to see that all the required datasets are available in the NC file for each class of plots
avail = True
for datakey in OMFIToedgeNC.all_plots[plotkey]['data']:
dataname, targname, label, scalef = self.get_names(plotkey, datakey)
if dataname not in self:
avail = False
if targname is not None:
if targname not in self:
avail = False
# if all elements of a group plot are available then that plot is available
if avail:
self.plots_avail.append(plotkey)
if plotkey in OMFIToedgeNC.bg_plots:
self.bg_plots_avail.append(plotkey)
elif plotkey in OMFIToedgeNC.h_plots:
self.h_plots_avail.append(plotkey)
elif plotkey in OMFIToedgeNC.imp_plots:
self.imp_plots_avail.append(plotkey)
elif plotkey in OMFIToedgeNC.surface_plots:
self.surf_plots_avail.append(plotkey)
[docs] def get_plot_categories(self):
# return a list of the available plot categories
return self.plot_categories
[docs] def get_plots_available(self, kind=None):
# return a list of the plots that are available in the current dataset
# Since the plot list is getting long - allow for splitting by type so that the UI
# can have more concise drop down lists
# print("get_plots_available:",kind)
if kind is None:
return self.plots_avail
elif kind == 'background':
return self.bg_plots_avail
elif kind == 'hydrogen':
return self.h_plots_avail
elif kind == 'impurity':
return self.imp_plots_avail
else:
return []
[docs] def get_plot_types(self):
# this routine returns a list of the supported plot types
return self.plot_types
[docs] def get_along_ring_axis_types(self, plot_select):
# look up the selected plot and return the along ring or linear axis types available
if plot_select in OMFIToedgeNC.all_plots:
return OMFIToedgeNC.all_plots[plot_select]['lineaxes']
# if this fails for some reason then return None ... but it shouldn't
return None
[docs] def need_ionization_state(self, selection):
# Check to see if a charge state is needed for the specified plot
res = False
if selection in OMFIToedgeNC.imp_plots:
res = True
return res
[docs] def need_ring_number(self, selection):
# Check to see if a ring number is needed for specified plot type
res = False
if selection == 'along ring':
res = True
return res
[docs] def get_data_2d(self, dataname, targname=None, charge=None, scalef=1.0):
# load a set of 2D data
#
rawdata = self[dataname]['data']
count = 0
#
# Note: targname should always be None for cases where charge is specified
# charge is a leading index into the data array mapping.
# if targname is specified then add the target data at the ends of the rings
if targname is None:
# leave out target data when plotting a polygon mesh
data = np.zeros(self.num_cells)
else:
# add space to include target values in the mesh - used in triangulation interpolation plots
data = np.zeros(self.num_points)
# loop through data arrays
# Note ... all of this is required because the rows of the array are of inconsistent lengths .. it isn't a rectangular grid
# in terms of indexing ... core, sol and pfz rings may all have different numbers of cells though nsol_cells=ncore+npfz is usually true
# along a ring
count = 0
for ir in range(self.nrs):
for ik in range(self.nks[ir]):
if targname is not None and ik == 0 and ir >= self.irsep - 1:
# add first target point
id0 = self.idds[1, ir]
data[count] = self[targname]['data'][id0]
count = count + 1
if self.area[ir, ik] != 0.0:
# include index for charge state if specified - this requires an offset
if charge is None:
data[count] = rawdata[ir][ik] * scalef
else:
# Note: the results array indexing goes from -1:nizs for charge states.
# -1 maps to 0 in the python array ... charge 0 is in index 1 - so add one to charge
# to get correct indexing
data[count] = rawdata[charge + 1][ir][ik] * scalef
count = count + 1
if targname is not None and ik == self.nks[ir] - 1 and ir >= self.irsep - 1:
# add last target point
id1 = self.idds[0, ir]
data[count] = self[targname]['data'][id1]
count = count + 1
return data
[docs] def plot(self, plot_select, plot_type, axis_type='S', ring_range=[], charge_range=[], zoom=None):
# produce a contour plot
# Either interpolated or polygon
# print('plot inputs1:',plot_select,plot_type,axis_type,ring_range,charge_range,zoom)
# Set inputs not needed for specific plots to default values
# Argument ELIMINATION doesn't propagate ... while argument object MANIPULATION would propagate back to calling routine
# This is needed since I am not updating the GUI when updating ring or charge ranges ... so these variables keep
# values from previous plot calls that may not be relevant
# I could just reload the GUI to avoid this but I am not sure the overhead is worth it
if not self.need_ring_number(plot_type):
ring_range = []
if not self.need_ionization_state(plot_select):
charge_range = []
# print('plot inputs2:',plot_select,plot_type,axis_type,ring_range,charge_range,zoom)
if plot_type == 'contour-polygon' or plot_type == 'contour-interpolate':
self.plot_contour(plot_select, plot_type, charge_range, zoom)
elif plot_type == 'along ring':
if len(ring_range) == 0:
print("ERROR: Rings not specified correctly for along ring plots")
else:
self.plot_along_ring(plot_select, axis_type, ring_range, charge_range, zoom)
elif plot_type == 'surface':
print("Plot not yet implemented")
return
elif plot_type == 'diagnostic':
print("Plot not yet implemented")
return
else:
print("ERROR: Specified plot type: `%s` not found." % plot_type)
[docs] def plot_contour(self, plot_select, plot_type, charge_range=[], zoom=None):
# This produces polygon contour plots matching the
# computationals mesh of the data included in the plot list
# If nplots is 1 then the 'data' reference contains a self[] reference otherwise it contains a list of all_plot dictionary entries
# set up subplots
#
# If charge states are specified for an impurity quantity two things happen
# 1) The figures go into a figure notebook - one/charge state
# 2) The get_data_2d routine in the specific contour plotting routines will require the charge state to be specified.
#
if len(charge_range) == 0:
# if no charge states then just plot the figure
# Note: OMFITx.py Figure is intended to put a figure inside an OMFIT GUI element
# fig = Figure(returnFigure=True)
fig = figure()
self.plot_contour_fig(fig, plot_select, plot_type, charge=None, zoom=zoom)
else:
# use a figure notebook
# For lists of charge states
from omfit_plot import FigureNotebook
fig_notebook = FigureNotebook(nfig=0, name='Contour plots by charge state')
# loop through charge states
for charge in charge_range:
fig = fig_notebook.add_figure(label='IZ={}'.format(charge))
self.plot_contour_fig(fig, plot_select, plot_type, charge, zoom=zoom)
fig.canvas.draw()
[docs] def plot_contour_fig(self, fig, plot_select, plot_type, charge=None, zoom=None):
# given the figure - add the appropriate contour plots
plot_list = OMFIToedgeNC.all_plots[plot_select]['data']
nplts = len(plot_list)
# get figure layout for number of plots on the page
nrows, ncols = self.calculate_layout(nplts)
pyplot.figure(num=fig.number)
for cnt in range(len(plot_list)):
ax = pyplot.subplot(nrows, ncols, cnt + 1)
dataname, targname, label, scalef = self.get_names(plot_select, plot_list[cnt])
if plot_type == 'contour-polygon':
# print("plot_contour_polygon_call:",scalef)
self.plot_contour_polygon(fig, ax, dataname, charge, zoom, scalef=scalef)
ax.set_title(label)
elif plot_type == 'contour-interpolate':
self.plot_contour_interpolate(fig, ax, dataname, targname, charge, zoom, scalef=scalef)
ax.set_title(label)
# set the figure title
fig.suptitle(plot_select)
[docs] def plot_contour_polygon(self, fig, ax, dataname, charge=None, zoom=None, scalef=1.0):
# polygon plots never use the target data
# produce polygon contour plot
if charge is None:
data = self.get_data_2d(dataname, scalef=scalef)
else:
data = self.get_data_2d(dataname, targname=None, charge=charge, scalef=scalef)
coll = matplotlib.collections.PolyCollection(self.grid, array=data, cmap=pyplot.cm.jet, edgecolors='none')
ax.add_collection(coll)
ax.plot(self.rves, self.zves, linewidth=1)
# ax.plot(wall,linewidth=1)
ax.autoscale_view()
ax.set_aspect("equal")
fig.colorbar(coll, ax=ax)
[docs] def plot_contour_interpolate(self, fig, ax, dataname, targname=None, charge=None, zoom=None, scalef=1.0):
# produce interpolated contour plot
# not all 2D data has target data
if charge is None:
data = self.get_data_2d(dataname, targname, scalef=scalef)
else:
data = self.get_data_2d(dataname, targname, charge, scalef=scalef)
if targname == None:
r = self.r_cells
z = self.z_cells
else:
r = self.r_points
z = self.z_points
triang = matplotlib.tri.Triangulation(r, z)
# Mask off unwanted triangles by finding ones with center points outside of wall
# This could be changed to ones with any corner outside wall but then you might get
# missing triangle gaps inside the wall
rmid = r[triang.triangles].mean(axis=1)
zmid = z[triang.triangles].mean(axis=1)
points = [[rmid[i], zmid[i]] for i in range(len(rmid))]
res = self.wall_path.contains_points(points)
mask = np.where(res == False, 1, 0)
# print("mask",mask)
# triangles to be plotted should be set now
triang.set_mask(mask)
# ax.set_cmap("jet")
ax.set_aspect('equal')
ax.plot(self.rves, self.zves, linewidth=1)
# contour levels are set to 20 but this could be pulled into a settable parameter
cs = ax.tricontourf(triang, data, 20, cmap=pyplot.cm.jet)
fig.colorbar(cs, ax=ax)
[docs] def calculate_layout(self, nplts):
#
# Calculate a layout that will hold all the figures
#
# Handle special cases - then use generic algorithm
#
if nplts == 3:
nrows = 1
ncols = 3
elif nplts == 7:
nrows = 2
ncols = 4
else:
nrows = np.ceil(np.sqrt(nplts))
ncols = np.ceil(np.sqrt(nplts))
# if number of plots will fit in a smaller grid remove a row
if ncols * (nrows - 1) >= nplts:
nrows = nrows - 1
# return the layout
return nrows, ncols
[docs] def get_names(self, plot_select, plot_name):
# This returns the names from the NC dictionary to be plotted based on the OMFIToedgeNC.all_plots summary
# It also returns the label associated with the individual plot element
dataname = None
targname = None
label = None
scale_desc = None
# each of the dictionary entries contains a list - this list is either a single reference to
# the NC datafile or a list of reference to other plots. Plot_name should always be a singular plot if it is present
if plot_select in OMFIToedgeNC.all_plots:
if plot_name in self:
# Need to return plot_name as dataname and get targname from the plot select reference
dataname = plot_name
if 'targ' in OMFIToedgeNC.all_plots[plot_select]:
if OMFIToedgeNC.all_plots[plot_select]['targ'] is not None:
targname = OMFIToedgeNC.all_plots[plot_select]['targ'][0]
# get label
if 'label' in OMFIToedgeNC.all_plots[plot_select]:
if OMFIToedgeNC.all_plots[plot_select]['label'] is not None:
label = OMFIToedgeNC.all_plots[plot_select]['label']
if 'units' in OMFIToedgeNC.all_plots[plot_select]:
if OMFIToedgeNC.all_plots[plot_select]['units'] is not None:
label = label + ' ' + OMFIToedgeNC.all_plots[plot_select]['units']
# get scaling factor if any
if 'scale' in OMFIToedgeNC.all_plots[plot_select]:
if OMFIToedgeNC.all_plots[plot_select]['scale'] is not None:
scale_desc = OMFIToedgeNC.all_plots[plot_select]['scale']
elif plot_name in OMFIToedgeNC.all_plots:
# Need to load both dataname and targname from the plot list
if 'data' in OMFIToedgeNC.all_plots[plot_name]:
if OMFIToedgeNC.all_plots[plot_name]['data'] is not None:
dataname = OMFIToedgeNC.all_plots[plot_name]['data'][0]
if 'targ' in OMFIToedgeNC.all_plots[plot_name]:
if OMFIToedgeNC.all_plots[plot_name]['targ'] is not None:
targname = OMFIToedgeNC.all_plots[plot_name]['targ'][0]
# get data label
if 'label' in OMFIToedgeNC.all_plots[plot_name]:
if OMFIToedgeNC.all_plots[plot_name]['label'] is not None:
label = OMFIToedgeNC.all_plots[plot_name]['label']
if 'units' in OMFIToedgeNC.all_plots[plot_name]:
if OMFIToedgeNC.all_plots[plot_name]['units'] is not None:
label = label + ' ' + OMFIToedgeNC.all_plots[plot_name]['units']
# get scaling factor if any
if 'scale' in OMFIToedgeNC.all_plots[plot_name]:
if OMFIToedgeNC.all_plots[plot_name]['scale'] is not None:
scale_desc = OMFIToedgeNC.all_plots[plot_name]['scale']
#
# Comment out - this code is used both to get plots AND to check whether they exist in the nc file
#
# else:
# print("ERROR: plot_name must be either a reference to self or a plot in OMFIToedgeNC.all_plots",plot_select,plot_name)
# set numerical scaling factor for the volumetric data
# Scaling factor is set 1.0 by default ... only changed if some other quantity is specified
if scale_desc == 'ABSFAC':
scalef = self.absfac
elif scale_desc == '1/QTIM':
scalef = 1.0 / self.qtim
else:
scalef = 1.0
return dataname, targname, label, scalef
[docs] def get_data_along_ring(self, ir, dataname=None, targname=None, charge=None, scalef=1.0):
# loads a set of 2D data
# get along ring data
# print("get_data_along_ring:",ir,dataname,targname,charge)
if dataname is not None:
if charge is None:
data = self[dataname]['data'][ir][: self.nks[ir]] * scalef
else:
# Note: the results array indexing goes from -1:nizs for charge states.
# -1 maps to 0 in the python array ... charge 0 is in index 1 - so add one to charge
# to get correct indexing
data = self[dataname]['data'][charge + 1][ir][: self.nks[ir]] * scalef
# print("data:",dataname,data)
# Insert the target data at the beginning and end when it is available
# Targt data is only available on sol and pfz rings
if targname is not None and ir >= self.irsep - 1:
# Target data does not need the scaling
# get target indices in the target data arrays
id0 = self.idds[1, ir]
id1 = self.idds[0, ir]
# print('id0,id1:',id0,id1,self.idds[1][ir],self.idds[0][ir])
# print('idds:',self.idds)
# print('targ:',targname,self[targname]['data'])
# print('targ2 id:',targname,self.idds[0][:])
# print('targ1 id:',targname,self.idds[1][:])
# print('targ data1:',targname,id0,self[targname]['data'][id0])
data = np.insert(data, 0, [self[targname]['data'][id0]])
# print('targ data2:',targname,id1,self[targname]['data'][id1])
data = np.append(data, [self[targname]['data'][id1]])
# print('data2:',data)
# return the data
return data
[docs] def get_axis(self, ir, axis_type='S', targname=None):
# This loads the particular axis for the along ring plot (either S or P)
# It also adds the target elements if targname is not None
if axis_type == 'P':
axis = self.kps[ir][: self.nks[ir]]
label = 'P (m)'
if targname is not None and ir > self.irsep - 1:
axis = np.insert(axis, 0, 0.0)
axis = np.append(axis, self.pmax[ir])
else: # if axis_type == 'S': # if someone forgets to set axis_type treat it as 'S'
axis = self.kss[ir][: self.nks[ir]]
label = 'S (m)'
if targname is not None and ir >= self.irsep - 1:
axis = np.insert(axis, 0, 0.0)
axis = np.append(axis, self.smax[ir])
return axis, label
[docs] def plot_ring(self, ir, fig, plot_select, axis_type='S', charge_range=[]):
# plot selected data for specified ring on figure specified
if ir < 1 or ir > self.nrs:
print("ERROR: Ring specified is out of range available: %d" % ir)
return
else:
# ring references are in 1 .. nrs from fortran while python arrays index from zero
ir_ref = ir - 1
plot_list = OMFIToedgeNC.all_plots[plot_select]['data']
nplts = len(plot_list)
# If nplots is 1 then the 'data' reference contains a self[] reference otherwise it contains a list of all_plot dictionary entries
# set up subplots
nrows, ncols = self.calculate_layout(nplts)
pyplot.figure(num=fig.number)
for cnt in range(len(plot_list)):
ax = pyplot.subplot(nrows, ncols, cnt + 1)
if len(charge_range) == 0:
dataname, targname, label, scalef = self.get_names(plot_select, plot_list[cnt])
axis, x_label = self.get_axis(ir_ref, axis_type, targname)
data = self.get_data_along_ring(ir_ref, dataname, targname, scalef=scalef)
# add a marker for the end points if targname is not None
if targname is not None:
markers_on = [0, len(axis) - 1]
else:
markers_on = []
# plot the data
ax.plot(axis, data, linestyle='-', marker='o', markevery=markers_on, linewidth=1)
ax.set_xlabel(x_label)
ax.set_ylabel(label)
ax.set_xlim(0.0, axis[-1])
else:
for charge in charge_range:
dataname, targname, label, scalef = self.get_names(plot_select, plot_list[cnt])
axis, x_label = self.get_axis(ir_ref, axis_type, targname)
data = self.get_data_along_ring(ir_ref, dataname, targname, charge, scalef=scalef)
# add a marker for the end points if targname is not None
if targname is not None:
markers_on = [0, len(axis) - 1]
else:
markers_on = []
# plot the data
ax.plot(axis, data, label='IZ={}'.format(charge), linestyle='-', marker='o', markevery=markers_on, linewidth=1)
ax.set_xlabel(x_label)
ax.set_ylabel(label)
ax.set_xlim(0.0, axis[-1])
ax.legend()
[docs] def plot_along_ring(self, plot_select, axis_type, ring_range, charge_range=[], zoom=None):
# Routine plots the quantities along ring ... number of plots/page is controlled by number
# of items in the plot list.
# Single figures
# Single ring/single quantity
# single ring/multiple quantities
#
# Figure notebook spanning ring range
# multi rings/single quantity
# multi rings/multiple quantities
#
# If more than one ring is specified then a figure notebook is used
#
# If more than one charge - try to put them on the same axes
#
# plot
nrings = len(ring_range)
if nrings <= 0:
# error condition
print("ERROR: No rings specified for the plot")
return
minring = 1
maxring = self.nrs
# print('rings:',nrings,ring_range)
res = [ring_range[i] < minring or ring_range[i] > maxring for i in range(len(ring_range))]
# print('res:',res)
out_of_bounds = np.where(res == True)
# print('out_of_bounds:',len(out_of_bounds),)
# print('test:',ring_range[out_of_bounds])
# Check to see if at least some rings are in range
if len(out_of_bounds) >= nrings:
print("ERROR: No valid rings numbers specified: min=%d max=%d range=%s" % (minring, maxring, str(ring_range)))
return
plot_list = OMFIToedgeNC.all_plots[plot_select]['data']
nplts = len(plot_list)
# if nplts is 1 then the list contains a self reference ... if greater than 1 then it contains a list of plot references
# Do everything in a figure notebook since there can be 1+ figures generated
# print('rings:',nrings,ring_range,plot_select,axis_type)
# use figure notebook since it supports an unknown number of plots
from omfit_plot import FigureNotebook
fig_notebook = FigureNotebook(nfig=0, name='Along Ring Plots')
for ir in ring_range:
fig = fig_notebook.add_figure(label='IR={}'.format(ir))
self.plot_ring(ir, fig, plot_select, axis_type, charge_range)
fig.canvas.draw()