Source code for boutpy.boututils.pfile

"""A class object used to interface with Osborne pfiles. """

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)

__all__ = ['pfile']
__date__ = '11152017'
__version__ = '1.0.1'

import os
import re

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate as si
from collections import OrderedDict

from boutpy.boututils.fileio import save2nc


[docs]class pfile(OrderedDict): """A class object used to interface with Osborne pfiles. This class is inheritated from `collections.OrderedDict <https://docs. python.org/2/library/collections.html#collections.OrderedDict>`_. Each variable are loaded as an dict item, its value is an dict object including keys 'data', 'description', 'units', 'psinorm', 'derivative'. Attributes ---------- legacy : bool, default: False If ``legacy=False``, it means the pfile is a new-version which includes the information of impurities. filename : str File name of the pfile. discriptions : dict An dict contains discriptions string about each variable in the pfile. units : dict An dict contains units for each variable in the pfile. Methods ------- load() load pfile data, called at the initiation stage. save(filename=None) save pfile data to ``filename`` if it is given. Otherwise overwrite the original file. plot() plot profiles in the pfile. interpolate() interpolate the profiles to the same psinorm. Return all profiles and ``psin``. Notes ----- This class is modified from `omfit_osborne.py`_ in the `OMFIT project`_. .. _omfit_osborne.py: http://gafusion.github.io/OMFIT-source/_modules/ classes/omfit_osborne.html#OMFITosborneProfile .. _OMFIT project: http://gafusion.github.io/OMFIT-source/index.html """ def __init__(self, filename): OrderedDict.__init__(self) self.legacy = False self.filename = filename.split('/')[-1] self.filename_full = filename self.descriptions = OrderedDict() self.descriptions['ne'] = 'Electron density' self.descriptions['te'] = 'Electron temperature' self.descriptions['ni'] = 'Ion density' self.descriptions['ti'] = 'Ion temperature' self.descriptions['nb'] = 'Fast ion density' self.descriptions['pb'] = 'Fast ion pressure' self.descriptions['ptot'] = 'Total pressure' self.descriptions['omeg'] = 'Toroidal rotation: VTOR/R' self.descriptions['omegp'] = 'Poloidal rotation: Bt * VPOL / (RBp)' self.descriptions['omgvb'] = ( 'VxB rotation term in the ExB rotation frequency: OMEG + OMEGP') self.descriptions['omgpp'] = ( 'Diamagnetic term in the ExB rotation frequency: ' '(P_Carbon)/dpsi / (6*n_Carbon)') self.descriptions['omgeb'] = ( 'ExB rotation frequency: OMGPP + OMGVB = Er/(RBp)') self.descriptions['er'] = ( 'Radial electric field from force balance: OMGEB * RBp') self.descriptions['ommvb'] = ( 'Main ion VXB term of Er/RBp, considered a flux function') self.descriptions['ommpp'] = ( 'Main ion pressure term of Er/RBp, considered a flux function') self.descriptions['omevb'] = ( 'Electron VXB term of Er/RBp, considered a flux function') self.descriptions['omepp'] = ( 'Electron pressure term of Er/RBp, considered a flux function') self.descriptions['kpol'] = ( 'KPOL=VPOL/Bp : V_vector = KPOL*B_vector + OMGEB * PHI_Vector') self.descriptions['N Z A'] = 'N Z A of ION SPECIES' self.descriptions['omghb'] = ( 'Han-Burrell form for the ExB velocity shearing rate: ' 'OMGHB = (RBp)**2/Bt * d (Er/RBp)/dpsi') self.descriptions['nz1'] = 'Density of the 1st impurity species' self.descriptions['vtor1'] = ( 'Toroidal velocity of the 1st impurity species') self.descriptions['vpol1'] = ( 'Poloidal velocity of the 1st impurity species') self.descriptions['nz2'] = 'Density of the 2nd impurity species' self.descriptions['vtor2'] = ( 'Toroidal velocity of the 2nd impurity species') self.descriptions['vpol2'] = ( 'Poloidal velocity of the 2nd impurity species') # There may be more impurity species, but let's stop here for now. self.units = OrderedDict() # The units will be updated according to the pfile self.units['ne'] = '10^20/m^3' self.units['te'] = 'KeV' self.units['ni'] = '10^20/m^3' self.units['ti'] = 'KeV' self.units['nb'] = '10^20/m^3' self.units['pb'] = 'KPa' self.units['ptot'] = 'KPa' self.units['omeg'] = 'kRad/s' self.units['omegp'] = 'kRad/s' self.units['omgvb'] = 'kRad/s' self.units['omgpp'] = 'kRad/s' self.units['omgeb'] = 'kRad/s' self.units['ommvb'] = '' self.units['ommpp'] = '' self.units['omevb'] = '' self.units['omepp'] = '' self.units['er'] = 'kV/m' self.units['kpol'] = 'km/s/T' self.units['N Z A'] = '' self.units['omghb'] = '' self.units['nz1'] = '10^20/m^3' self.units['vtor1'] = 'km/s' self.units['vpol1'] = 'km/s' self.units['nz2'] = '10^20/m^3' self.units['vtor2'] = 'km/s' self.units['vpol2'] = 'km/s' self.load()
[docs] def load(self): """Method used to load the content of the pfile. """ # read the file if not os.path.getsize(self.filename_full): print("empty file!") return with open(self.filename_full, 'r') as f: fl = f.read().strip().splitlines(True) ind = 0 while True: try: line = fl[ind] line.split() fl[ind + 1].split() except IndexError: break pattern = '([0-9]*)\s(.*)\s(.*)\((.*)\)\s(.*)\n' num = int(re.sub('([0-9]*)\s.*', r'\1', line)) xkey = re.sub(pattern, r'\2', line) key = re.sub(pattern, r'\3', line) units = re.sub(pattern, r'\4', line) der = re.sub(pattern, r'\5', line) quants = [xkey, key + '(' + units + ')', der] q = [] for i in range(ind + 1, ind + 1 + num): q.append(map(float, fl[i].split())) q = list(zip(*q)) if key in self.keys(): if sum(np.array(self[key]['data']) != np.array(q[1])): raise( ValueError( '%s already defined, but trying to change ' 'its value' % quants[1])) elif 'N Z A of ION SPECIES' in line: self['N Z A'] = {} self['N Z A']['description'] = 'N Z A of ION SPECIES' self['N Z A']['N'] = np.array(q[0]) self['N Z A']['Z'] = np.array(q[1]) self['N Z A']['A'] = np.array(q[2]) else: self[key] = {} if key in self.descriptions: self[key]['description'] = self.descriptions[key] else: self[key]['description'] = key self.units[key] = units self[key]['units'] = units for qi, quant in enumerate(quants): if qi == 1: self[key]['data'] = np.array(q[qi]) elif ((quant == 'd%s/dpsiN' % key) or (quant == 'd%s/dpsi' % key)): self[key]['derivative'] = np.array(q[qi]) else: self[key][quant] = np.array(q[qi]) ind = ind + 1 + num
[docs] def save(self, filename=None): """Save content of the object to the file in Osborne pfile format. Parameters ---------- filename : str, optional if ``filename=None``, it will overwrite the original pfile. otherwise it will use ``filename`` as file name. """ lines = [] if filename is None: filename = self.filename_full for key in self.keys(): if key == 'N Z A': if self.legacy: break lines.append('%i N Z A of ION SPECIES\n' % (len(self[key]['A']),)) for i in range(len(self[key]['A'])): lines.append( " %f %f %f\n" % (self[key]['N'][i], self[key]['Z'][i], self[key]['A'][i])) else: if len(self[key]['data']) == 1: continue lines.append("%i psinorm %s(%s) d%s/dpsiN\n" % (len(self[key]['data']), key, self[key]['units'], key)) for i in range(len(self[key]['data'])): lines.append( " %f %f %f\n" % (self[key]['psinorm'][i], self[key]['data'][i], self[key]['derivative'][i])) with open(filename, 'w') as f: f.writelines(lines)
[docs] def interpolate(self, npoints=500, psin=None, output=False): """Interpolate profiles to same psin array. Parameters ---------- npoints : int, default: 500 number of data points in interval [0, 1.0], if ``psin==None``. psin : ndarray, optional, default: None interpolate profiles with given psin. output : bool or str, optional, default: False save data to netcdf file. if True, save data to file "`pfilename`_nx`npoints`.nc". if it is an string, then save data to file "`output`.nc" Returns ------- data : dict profiles with same psin. """ data = OrderedDict() if psin is None: psin = np.linspace(0, 1, npoints) else: if np.max(psin) > 1.0 or np.min(psin) < 0.0: raise ValueError("psin outside of [0, 1.0] is not supported!") npoints = np.size(psin) data['psin'] = psin for key in self.keys(): if key == "N Z A": print("WARNING: information about ION SPECIES ignored!") continue data[key] = si.interp1d(self[key]['psinorm'], self[key]['data'], kind='cubic')(psin) if isinstance(output, bool): outputnc = "{}_nx{}".format(self.filename, npoints) elif isinstance(output, str): outputnc = output if output: print("saving data to \n\t{} ...".format(outputnc)) save2nc(outputnc, 'a', **data) return data
[docs] def plot(self): """Method used to plot all profiles, one in a different subplots. """ nplot = len(self) cplot = np.floor(np.sqrt(nplot) / 2.) * 2 rplot = np.ceil(nplot * 1.0 / cplot) plt.subplots_adjust(wspace=0.35, hspace=0.0) plt.ioff() try: for k, item in enumerate(self): r = np.floor(k * 1.0 / cplot) c = k - r * cplot if k == 0: ax1 = plt.subplot(rplot, cplot, r * (cplot) + c + 1) ax = ax1 else: ax = plt.subplot( rplot, cplot, r * (cplot) + c + 1, sharex=ax1) ax.ticklabel_format(style='sci', scilimits=(-3, 3)) if 'psinorm' not in self[item]: print("Can't plot {} because no psinorm attribute" .format(item)) continue x = self[item]['psinorm'] plt.plot(x, self[item]['data'], '-') plt.text(0.5, 0.9, item, horizontalalignment='center', verticalalignment='top', size='medium', transform=ax.transAxes) if k < len(self) - cplot: plt.setp(ax.get_xticklabels(), visible=False) else: plt.xlabel('$\psi$') plt.xlim(min(x), max(x)) finally: plt.draw() plt.ion()