From 7a2812eb1053137a232070558e43f93ea40c3a9b Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Wed, 2 Oct 2019 15:36:13 +0200 Subject: [PATCH 01/23] [Issue205] Created debugging figures in tofu root --- Debug_JINTRACMesh.pdf | Bin 0 -> 1205 bytes Debug_JINTRACMesh.png | Bin 0 -> 2412 bytes Debug_JINTRACMesh.svg | 21 +++++++++++++++++++++ tofu/imas2tofu/_core.py | 22 ++++++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 Debug_JINTRACMesh.pdf create mode 100644 Debug_JINTRACMesh.png create mode 100644 Debug_JINTRACMesh.svg diff --git a/Debug_JINTRACMesh.pdf b/Debug_JINTRACMesh.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0c74dc189c9bb03a1282562d1fde50fe90c7a6dc GIT binary patch literal 1205 zcmZux%Wl&^6y+hh@S-d$Slvz3R)9TY$8J(p6*Or|1*lOH(MmkT#GWKBiN~5uP|A)) z7i=)-3uFEKqmw72L7&!oeGRj_2Ha=bU5bs^!WJYUYjHx1aF!yFr0~ zXdW6H8(>vgoA<#+qy|>wDCS_5m||VghRscbM}gj0m?3Nr)lOY8#UY=PDk4(gq|a5R z6qgaWdTCfyDf~KD@BsUiD_EZo6SFn~8 zpN>Nb0^o%c9N{CHe+jZ$RVlNZ2ylPk_y-!G3=`j^|h3bY)uuW>(*P)|Sylz?3 z7p9PHq+o7?fsYQ&a^j!`P3AvE4O9N=jJre0TLuB@7-VvR=emvyEtp^wWzrW!6U=i_ z`3&afF-Td=!vJNmB4#s#dD`17=AdpFjCkl{26GDYJ#6xyS?u|JYjoxuO0uxaW%7gB mjamW{8D#l*~DnuCo literal 0 HcmV?d00001 diff --git a/Debug_JINTRACMesh.png b/Debug_JINTRACMesh.png new file mode 100644 index 0000000000000000000000000000000000000000..0e536fb530ea40bc82a9395940755ad27a030609 GIT binary patch literal 2412 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_Vkvg=4B-HR8jh3>AfL0q zBeIx*fj<$18CTdZ&jboumbgZg1m~xflqVLYGUO(f6y)TWFXmD=;ars>zi`_Ji)-ganjSpF{EP7+iQl53<^943=Zsn{*b9pjjO8c z{23hv2BUNDfi~SUYG7b6WD{UeIL5%la74m^p+SO~gFzsffsrAJM}fhihmnP$!*Ep5 vXb_C1g3*jHS{95Jhod#ZXr)Nkc7dK+CHtP`%Qqzf+bay7u6{1-oD!M + + + + + + + + + + + + diff --git a/tofu/imas2tofu/_core.py b/tofu/imas2tofu/_core.py index 44440a544..c3a6bcb6b 100644 --- a/tofu/imas2tofu/_core.py +++ b/tofu/imas2tofu/_core.py @@ -2253,6 +2253,28 @@ def _checkformat_mesh(nodes, indfaces, ids=None): indface[::2,:] = indfaces[:,:3] indface[1::2,:-1] = indfaces[:,2:] indface[1::2,-1] = indfaces[:,0] + + #### DB (begin) + import ipdb # DB + ipdb.set_trace() # DB + + faces = np.concatenate((nodes[indfaces,:], nodes[indfaces,:][:,0:1,:], + np.full((3936,1,2),np.nan)), axis=1) + faces = np.reshape(np.swapaxes(np.swapaxes(faces, 0,2), 1,2), (2,6*3936)) + import matplotlib.pyplot as plt + plt.switch_backend('Qt5Agg') + plt.ioff() + plt.figure() + plt.plot(faces[0,:], faces[1,:]) + plt.gca().set_aspect('equal') + plt.show() + plt.gcf().savefig('Debug_JINTRACMesh.pdf', format='pdf') + plt.gcf().savefig('Debug_JINTRACMesh.svg', format='svg') + plt.gcf().savefig('Debug_JINTRACMesh.png', format='png') + + + #### DB (end) + indfaces = indface meshtype = 'quadtri' ntri = 2 From 24abc3879593f5590014aeb69618f960e88310fd Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Wed, 2 Oct 2019 15:43:17 +0200 Subject: [PATCH 02/23] [Issue205] Added non-empty png figure --- Debug_JINTRACMesh2.png | Bin 0 -> 167746 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Debug_JINTRACMesh2.png diff --git a/Debug_JINTRACMesh2.png b/Debug_JINTRACMesh2.png new file mode 100644 index 0000000000000000000000000000000000000000..5dbb05bc93ddd85711fabbdeb7e39335ca3c7eb9 GIT binary patch literal 167746 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 Date: Sun, 6 Oct 2019 01:13:55 +0200 Subject: [PATCH 03/23] [Issue208] Started DataHolder class in tofu/data/_core_new.py --- Debug_JINTRACMesh.pdf | Bin 1205 -> 0 bytes tofu/imas2tofu/_core.py | 14 ++++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Debug_JINTRACMesh.pdf b/Debug_JINTRACMesh.pdf index 0c74dc189c9bb03a1282562d1fde50fe90c7a6dc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 1205 zcmZux%Wl&^6y+hh@S-d$Slvz3R)9TY$8J(p6*Or|1*lOH(MmkT#GWKBiN~5uP|A)) z7i=)-3uFEKqmw72L7&!oeGRj_2Ha=bU5bs^!WJYUYjHx1aF!yFr0~ zXdW6H8(>vgoA<#+qy|>wDCS_5m||VghRscbM}gj0m?3Nr)lOY8#UY=PDk4(gq|a5R z6qgaWdTCfyDf~KD@BsUiD_EZo6SFn~8 zpN>Nb0^o%c9N{CHe+jZ$RVlNZ2ylPk_y-!G3=`j^|h3bY)uuW>(*P)|Sylz?3 z7p9PHq+o7?fsYQ&a^j!`P3AvE4O9N=jJre0TLuB@7-VvR=emvyEtp^wWzrW!6U=i_ z`3&afF-Td=!vJNmB4#s#dD`17=AdpFjCkl{26GDYJ#6xyS?u|JYjoxuO0uxaW%7gB mjamW{8D#l*~DnuCo diff --git a/tofu/imas2tofu/_core.py b/tofu/imas2tofu/_core.py index c3a6bcb6b..709d13a79 100644 --- a/tofu/imas2tofu/_core.py +++ b/tofu/imas2tofu/_core.py @@ -2255,23 +2255,21 @@ def _checkformat_mesh(nodes, indfaces, ids=None): indface[1::2,-1] = indfaces[:,0] #### DB (begin) - import ipdb # DB - ipdb.set_trace() # DB - faces = np.concatenate((nodes[indfaces,:], nodes[indfaces,:][:,0:1,:], np.full((3936,1,2),np.nan)), axis=1) faces = np.reshape(np.swapaxes(np.swapaxes(faces, 0,2), 1,2), (2,6*3936)) import matplotlib.pyplot as plt plt.switch_backend('Qt5Agg') plt.ioff() - plt.figure() + fig = plt.figure() plt.plot(faces[0,:], faces[1,:]) plt.gca().set_aspect('equal') plt.show() - plt.gcf().savefig('Debug_JINTRACMesh.pdf', format='pdf') - plt.gcf().savefig('Debug_JINTRACMesh.svg', format='svg') - plt.gcf().savefig('Debug_JINTRACMesh.png', format='png') - + fig.savefig('Debug_JINTRACMesh.pdf', format='pdf') + fig.savefig('Debug_JINTRACMesh.svg', format='svg') + fig.savefig('Debug_JINTRACMesh.png', format='png') + import ipdb # DB + ipdb.set_trace() # DB #### DB (end) From 6840fff942ecbf0ed6ba1ab6653c6f8134cf4b97 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Sun, 6 Oct 2019 18:33:41 +0200 Subject: [PATCH 04/23] [Issue208] Added tofu/data/_core_new.py --- tofu/data/_core_new.py | 1788 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1788 insertions(+) create mode 100644 tofu/data/_core_new.py diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py new file mode 100644 index 000000000..355dbdd46 --- /dev/null +++ b/tofu/data/_core_new.py @@ -0,0 +1,1788 @@ +# -*- coding: utf-8 -*- + +# Built-in +import sys +import os +# import itertools as itt +import copy +import warnings +from abc import ABCMeta, abstractmethod +if sys.version[0] == '3': + import inspect +else: + # Python 2 back-porting + import funcsigs as inspect + +# Common +import numpy as np +# import scipy.interpolate as scpinterp +# import matplotlib.pyplot as plt +# from matplotlib.tri import Triangulation as mplTri + + +# tofu +from tofu import __version__ as __version__ +import tofu.pathfile as tfpf +import tofu.utils as utils +try: + import tofu.data._comp as _comp + import tofu.data._plot as _plot + import tofu.data._def as _def + import tofu.data._physics as _physics +except Exception: + from . import _comp as _comp + from . import _plot as _plot + from . import _def as _def + from . import _physics as _physics + +__all__ = ['DataHolder']#, 'Plasma0D'] + +_SAVEPATH = os.path.abspath('./') + + +_INTERPT = 'zero' + + + +############################################# +############################################# +# Abstract Parent class +############################################# +############################################# + + + +class DataHolder(utils.ToFuObject): + """ A generic class for handling data + + Provides methods for: + - introspection + - plateaux finding + - visualization + + """ + # Fixed (class-wise) dictionary of default properties + _ddef = {'Id':{'include':['Mod', 'Cls', + 'Name', 'version']}} + _lparams = ['origin', 'dim', 'quant', 'name', 'units'] + + # Does not exist before Python 3.6 !!! + def __init_subclass__(cls, **kwdargs): + # Python 2 + super(DataHolder, cls).__init_subclass__(**kwdargs) + # Python 3 + #super().__init_subclass__(**kwdargs) + cls._ddef = copy.deepcopy(DataHolder._ddef) + #cls._dplot = copy.deepcopy(Struct._dplot) + #cls._set_color_ddef(cls._color) + + + def __init__(self, dref=None, ddata=None, + Id=None, Name=None, + fromdict=None, SavePath=os.path.abspath('./'), + SavePath_Include=tfpf.defInclude): + + # To replace __init_subclass__ for Python 2 + if sys.version[0]=='2': + self._dstrip = utils.ToFuObjectBase._dstrip.copy() + self.__class__._strip_init() + + # Create a dplot at instance level + #self._dplot = copy.deepcopy(self.__class__._dplot) + + kwdargs = locals() + del kwdargs['self'] + # super() + super(DataHolder, self).__init__(**kwdargs) + + def _reset(self): + # super() + super(Plasma2D,self)._reset() + self._dgroup = dict.fromkeys(self._get_keys_dgroup()) + self._dindref = dict.fromkeys(self._get_keys_dindref()) + self._ddata = dict.fromkeys(self._get_keys_ddata()) + self._dgeom = dict.fromkeys(self._get_keys_dgeom()) + + @classmethod + def _checkformat_inputs_Id(cls, Id=None, Name=None, + include=None, **kwdargs): + if Id is not None: + assert isinstance(Id,utils.ID) + Name = Id.Name + assert type(Name) is str, Name + if include is None: + include = cls._ddef['Id']['include'] + if 'shot' in include: + include.remove('shot') + if 'Exp' in include: + include.remove('Exp') + if 'Diag' in include: + include.remove('Diag') + kwdargs.update({'Name':Name, 'include':include}) + return kwdargs + + ########### + # Get largs + ########### + + @staticmethod + def _get_largs_dindrefdatagroup(): + largs = ['dtime', 'dradius', 'dmesh', 'd0d', 'd1d', 'd2d'] + return largs + + @staticmethod + def _get_largs_dgeom(): + largs = ['config'] + return largs + + ########### + # Get check and format inputs + ########### + + #--------------------- + # Methods for checking and formatting inputs + #--------------------- + + @classmethod + def _checkformat_ref(ref): + assert type(ref) is dict + assert all([type(kk) is str for kk in dref.keys()]) + for kk, vv in dref.values(): + assert type(vv) is dict + assert 'group' in vv.keys() and type(vv['group']) is str + + + + @staticmethod + def _extract_dnd(dnd, k0, + dim_=None, quant_=None, name_=None, + origin_=None, units_=None): + # Set defaults + dim_ = k0 if dim_ is None else dim_ + quant_ = k0 if quant_ is None else quant_ + name_ = k0 if name_ is None else name_ + origin_ = 'unknown' if origin_ is None else origin_ + units_ = 'a.u.' if units_ is None else units_ + + # Extrac + dim = dnd[k0].get('dim', None) + if dim is None: + dim = dim_ + quant = dnd[k0].get('quant', None) + if quant is None: + quant = quant_ + origin = dnd[k0].get('origin', None) + if origin is None: + origin = origin_ + name = dnd[k0].get('name', None) + if name is None: + name = name_ + units = dnd[k0].get('units', None) + if units is None: + units = units_ + return dim, quant, origin, name, units + + @staticmethod + def _checkformat_dtrm(dtime=None, dradius=None, dmesh=None, + d0d=None, d1d=None, d2d=None): + + dd = {'dtime':dtime, 'dradius':dradius, 'dmesh':dmesh, + 'd0d':d0d, 'd1d':d1d, 'd2d':d2d} + + # Define allowed keys for each dict + lkok = ['data', 'dim', 'quant', 'name', 'origin', 'units', + 'depend'] + lkmeshmax = ['type','ftype','nodes','faces', + 'nfaces','nnodes','mpltri','size','ntri'] + lkmeshmin = ['type','ftype','nodes','faces'] + dkok = {'dtime': {'max':lkok, 'min':['data'], 'ndim':[1]}, + 'dradius':{'max':lkok, 'min':['data'], 'ndim':[1,2]}, + 'd0d':{'max':lkok, 'min':['data'], 'ndim':[1,2,3]}, + 'd1d':{'max':lkok, 'min':['data'], 'ndim':[1,2]}, + 'd2d':{'max':lkok, 'min':['data'], 'ndim':[1,2]}} + dkok['dmesh'] = {'max':lkok + lkmeshmax, 'min':lkmeshmin} + + # Check each dict independently + for dk, dv in dd.items(): + if dv is None or len(dv) == 0: + dd[dk] = {} + continue + c0 = type(dv) is not dict or any([type(k0) is not str + for k0 in dv.keys()]) + c0 = any([type(k0) is not str or type(v0) is not dict + for k0, v0 in dv.items()]) + if c0: + msg = "Arg %s must be a dict with:\n" + msg += " - (key, values) of type (str, dict)" + raise Exception(msg) + + for k0, v0 in dv.items(): + c0 = any([k1 not in dkok[dk]['max'] for k1 in v0.keys()]) + c0 = c0 or any([v0.get(k1,None) is None + for k1 in dkok[dk]['min']]) + if c0: + msg = "Arg %s[%s] must be a dict with keys in:\n"%(dk,k0) + msg += " - %s\n"%str(dkok[dk]['max']) + msg += "And with at least the following keys:\n" + msg += " - %s\n"%str(dkok[dk]['min']) + msg += "Provided:\n" + msg += " - %s\n"%str(v0.keys()) + msg += "Missing:\n" + msg += " - %s\n"%str(set(dkok[dk]['min']).difference(v0.keys())) + msg += "Non-valid:\n" + msg += " - %s"%str(set(v0.keys()).difference(dkok[dk]['max'])) + raise Exception(msg) + if 'data' in dkok[dk]['min']: + dd[dk][k0]['data'] = np.atleast_1d(np.squeeze(v0['data'])) + if dd[dk][k0]['data'].ndim not in dkok[dk]['ndim']: + msg = "%s[%s]['data'] has wrong dimensions:\n"%(dk,k0) + msg += " - Expected: %s\n"%str(dkok[dk]['ndim']) + msg += " - Provided: %s"%str(dd[dk][k0]['data'].ndim) + raise Exception(msg) + if dk == 'dmesh': + dd[dk][k0]['nodes'] = np.atleast_2d(v0['nodes']).astype(float) + dd[dk][k0]['faces'] = np.atleast_2d(v0['faces']).astype(int) + nnodes = dd[dk][k0]['nodes'].shape[0] + nfaces = dd[dk][k0]['faces'].shape[0] + + # Test for duplicates + nodesu = np.unique(dd[dk][k0]['nodes'], axis=0) + facesu = np.unique(dd[dk][k0]['faces'], axis=0) + lc = [nodesu.shape[0] != nnodes, + facesu.shape[0] != nfaces] + if any(lc): + msg = "Non-valid mesh %s[%s]:\n"%(dk,k0) + if lc[0]: + msg += " Duplicate nodes: %s\n"%str(nnodes - nodesu.shape[0]) + msg += " - nodes.shape: %s\n"%str(dd[dk][k0]['nodes'].shape) + msg += " - unique nodes.shape: %s\n"%str(nodesu.shape) + if lc[1]: + msg += " Duplicate faces: %s\n"%str(nfaces - facesu.shape[0]) + msg += " - faces.shape: %s\n"%str(dd[dk][k0]['faces'].shape) + msg += " - unique faces.shape: %s"%str(facesu.shape) + raise Exception(msg) + + # Test for unused nodes + facesu = np.unique(facesu) + c0 = np.all(facesu>=0) and facesu.size == nnodes + if not c0: + indnot = [ii for ii in range(0,nnodes) + if ii not in facesu] + msg = "Some nodes not used in mesh %s[%s]:\n"(dk,k0) + msg += " - unused nodes indices: %s"%str(indnot) + warnings.warn(msg) + + + dd[dk][k0]['nnodes'] = dd[dk][k0].get('nnodes', nnodes) + dd[dk][k0]['nfaces'] = dd[dk][k0].get('nfaces', nfaces) + + assert dd[dk][k0]['nodes'].shape == (v0['nnodes'],2) + assert np.max(dd[dk][k0]['faces']) < v0['nnodes'] + # Only triangular meshes so far + assert v0['type'] in ['tri', 'quadtri'], v0['type'] + + if 'tri' in v0['type']: + assert dd[dk][k0]['faces'].shape == (v0['nfaces'],3) + if v0.get('mpltri', None) is None: + dd[dk][k0]['mpltri'] = mplTri(dd[dk][k0]['nodes'][:,0], + dd[dk][k0]['nodes'][:,1], + dd[dk][k0]['faces']) + assert isinstance(dd[dk][k0]['mpltri'], mplTri) + assert dd[dk][k0]['ftype'] in [0,1] + ntri = dd[dk][k0]['ntri'] + if dd[dk][k0]['ftype'] == 1: + dd[dk][k0]['size'] = dd[dk][k0]['nnodes'] + else: + dd[dk][k0]['size'] = int(dd[dk][k0]['nfaces']/ntri) + + # Check unicity of all keys + lk = [list(dv.keys()) for dv in dd.values()] + lk = list(itt.chain.from_iterable(lk)) + lku = sorted(set(lk)) + lk = ['%s : %s times'%(kk, str(lk.count(kk))) for kk in lku if lk.count(kk) > 1] + if len(lk) > 0: + msg = "Each key of (dtime,dradius,dmesh,d0d,d1d,d2d) must be unique !\n" + msg += "The following keys are repeated :\n" + msg += " - " + "\n - ".join(lk) + raise Exception(msg) + + dtime, dradius, dmesh = dd['dtime'], dd['dradius'], dd['dmesh'] + d0d, d1d, d2d = dd['d0d'], dd['d1d'], dd['d2d'] + + return dtime, dradius, dmesh, d0d, d1d, d2d + + + ########### + # Get keys of dictionnaries + ########### + + @staticmethod + def _get_keys_dindref(): + lk = [] + return lk + + @staticmethod + def _get_keys_ddata(): + lk = [] + return lk + + ########### + # _init + ########### + + def _init(self, dtime=None, dradius=None, dmesh=None, + d0d=None, d1d=None, d2d=None, + config=None, **kwargs): + kwdargs = locals() + kwdargs.update(**kwargs) + largs = self._get_largs_dindrefdatagroup() + kwdindrefdatagroup = self._extract_kwdargs(kwdargs, largs) + largs = self._get_largs_dgeom() + kwdgeom = self._extract_kwdargs(kwdargs, largs) + self._set_dindrefdatagroup(**kwdindrefdatagroup) + self.set_dgeom(**kwdgeom) + self._dstrip['strip'] = 0 + + + ########### + # set dictionaries + ########### + + @staticmethod + def _find_lref(shape=None, k0=None, dd=None, ddstr=None, + dindref=None, lrefname=['t','radius']): + if 'depend' in dd[k0].keys(): + lref = dd[k0]['depend'] + else: + lref = [[kk for kk, vv in dindref.items() + if vv['size'] == sh and vv['group'] in lrefname] + for sh in shape] + lref = list(itt.chain.from_iterable(lref)) + if len(lref) < len(shape): + msg = "Maybe not enoough references for %s[%s]:\n"%(ddstr,k0) + msg += " - shape: %s\n"%str(shape) + msg += " - lref: %s"%str(lref) + warnings.warn(msg) + + if len(lref) > len(shape): + msg = "Too many references for %s[%s]:\n"%(ddstr,k0) + msg += " - shape: %s\n"%str(shape) + msg += " - lref: %s"%str(lref) + raise Exception(msg) + return lref + + + + + def _set_dindrefdatagroup(self, dtime=None, dradius=None, dmesh=None, + d0d=None, d1d=None, d2d=None): + + # Check dtime is not None + out = self._checkformat_dtrm(dtime=dtime, dradius=dradius, dmesh=dmesh, + d0d=d0d, d1d=d1d, d2d=d2d) + dtime, dradius, dmesh, d0d, d1d, d2d = out + + dgroup, dindref, ddata = {}, {}, {} + empty = {} + # Get indt + if dtime is not None: + for k0 in dtime.keys(): + out = self._extract_dnd(dtime,k0, + dim_='time', quant_='t', + name_=k0, units_='s') + dim, quant, origin, name, units = out + + assert k0 not in dindref.keys() + dtime[k0]['data'] = np.atleast_1d(np.squeeze(dtime[k0]['data'])) + assert dtime[k0]['data'].ndim == 1 + + dindref[k0] = {'size':dtime[k0]['data'].size, + 'group':'time'} + + assert k0 not in ddata.keys() + ddata[k0] = {'data':dtime[k0]['data'], + 'dim':dim, 'quant':quant, 'name':name, + 'origin':origin, 'units':units, 'depend':(k0,)} + + # d0d + if d0d is not None: + for k0 in d0d.keys(): + out = self._extract_dnd(d0d,k0) + dim, quant, origin, name, units = out + + # data + d0d[k0]['data'] = np.atleast_1d(np.squeeze(d0d[k0]['data'])) + assert d0d[k0]['data'].ndim >= 1 + + depend = self._find_lref(d0d[k0]['data'].shape, k0, dd=d0d, + ddstr='d0d', dindref=dindref, + lrefname=['t']) + assert len(depend) == 1 and dindref[depend[0]]['group']=='time' + assert k0 not in ddata.keys() + ddata[k0] = {'data':d0d[k0]['data'], + 'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin, 'depend':depend} + + # get radius + if dradius is not None: + for k0 in dradius.keys(): + out = self._extract_dnd(dradius, k0, name_=k0) + dim, quant, origin, name, units = out + assert k0 not in dindref.keys() + data = np.atleast_1d(np.squeeze(dradius[k0]['data'])) + assert data.ndim in [1,2] + + if len(dradius[k0].get('depend',[1])) == 1: + assert data.ndim == 1 + size = data.size + else: + lkt = [k for k in dtime.keys() if k in dradius[k0]['depend']] + assert len(lkt) == 1 + axist = dradius[k0]['depend'].index(lkt[0]) + size = data.shape[1-axist] + dindref[k0] = {'size':size, + 'group':'radius'} + + assert k0 not in ddata.keys() + depend = self._find_lref(data.shape, k0, dd=dradius, + ddstr='dradius', dindref=dindref, + lrefname=['t','radius']) + ddata[k0] = {'data':data, + 'dim':dim, 'quant':quant, 'name':name, + 'origin':origin, 'units':units, 'depend':depend} + + + # Get d1d + if d1d is not None: + for k0 in d1d.keys(): + out = self._extract_dnd(d1d,k0) + dim, quant, origin, name, units = out + + d1d[k0]['data'] = np.atleast_2d(np.squeeze(d1d[k0]['data'])) + assert d1d[k0]['data'].ndim == 2 + + # data + depend = self._find_lref(d1d[k0]['data'].shape, k0, dd=d1d, + ddstr='d1d', dindref=dindref, + lrefname=['t','radius']) + assert k0 not in ddata.keys() + ddata[k0] = {'data':d1d[k0]['data'], + 'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin, 'depend':depend} + + # dmesh ref + if dmesh is not None: + for k0 in dmesh.keys(): + out = self._extract_dnd(dmesh, k0, dim_='mesh') + dim, quant, origin, name, units = out + + assert k0 not in dindref.keys() + dindref[k0] = {'size':dmesh[k0]['size'], + 'group':'mesh'} + + assert k0 not in ddata.keys() + ddata[k0] = {'data':dmesh[k0], + 'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin, 'depend':(k0,)} + + # d2d + if d2d is not None: + for k0 in d2d.keys(): + out = self._extract_dnd(d2d,k0) + dim, quant, origin, name, units = out + + d2d[k0]['data'] = np.atleast_2d(np.squeeze(d2d[k0]['data'])) + assert d2d[k0]['data'].ndim == 2 + + depend = self._find_lref(d2d[k0]['data'].shape, k0, dd=d2d, + ddstr='d2d', dindref=dindref, + lrefname=['t','mesh']) + assert k0 not in ddata.keys() + ddata[k0] = {'data':d2d[k0]['data'], + 'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin, 'depend':depend} + + # dgroup + dgroup = {} + if len(dtime) > 0: + dgroup['time'] = {'dref':list(dtime.keys())[0]} + if len(dradius) > 0: + dgroup['radius'] = {'dref':list(dradius.keys())[0]} + if len(dmesh) > 0: + dgroup['mesh'] = {'dref':list(dmesh.keys())[0]} + + # Update dict + self._dgroup = dgroup + self._dindref = dindref + self._ddata = ddata + # Complement + self._complement() + + + + def _complement(self): + + # -------------- + # ddata + for k0, v0 in self.ddata.items(): + lindout = [ii for ii in v0['depend'] if ii not in self.dindref.keys()] + if not len(lindout) == 0: + msg = "ddata[%s]['depend'] has keys not in dindref:\n"%k0 + msg += " - " + "\n - ".join(lindout) + raise Exception(msg) + + self.ddata[k0]['lgroup'] = [self.dindref[ii]['group'] + for ii in v0['depend']] + type_ = type(v0['data']) + shape = tuple([self.dindref[ii]['size'] for ii in v0['depend']]) + + # if only one dim => mesh or iterable or unspecified + if len(shape) == 1 or type_ is dict: + c0 = type_ is dict and 'mesh' in self.ddata[k0]['lgroup'] + c1 = not c0 and len(v0['data']) == shape[0] + if not (c0 or c1): + msg = k0+'\n' + msg += str([c0, c1, type_, len(v0['data']), shape]) + msg += "\n" + str(v0['data']) + else: + assert type(v0['data']) is np.ndarray + assert v0['data'].shape == shape + + # -------------- + # dindref + for k0 in self.dindref.keys(): + self.dindref[k0]['ldata'] = [kk for kk, vv in self.ddata.items() + if k0 in vv['depend']] + assert self.dindref[k0]['group'] in self.dgroup.keys() + + # -------------- + # dgroup + for gg, vg in self.dgroup.items(): + lindref = [id_ for id_,vv in self.dindref.items() + if vv['group'] == gg] + ldata = [id_ for id_ in self.ddata.keys() + if any([id_ in self.dindref[vref]['ldata'] + for vref in lindref])] + #assert vg['depend'] in lidindref + self.dgroup[gg]['lindref'] = lindref + self.dgroup[gg]['ldata'] = ldata + + + def set_dgeom(self, config=None): + config = self._checkformat_inputs_dgeom(config=config) + self._dgeom = {'config':config} + + + ########### + # strip dictionaries + ########### + + def _strip_ddata(self, strip=0): + pass + + + def _strip_dgeom(self, strip=0, force=False, verb=True): + if self._dstrip['strip']==strip: + return + + if strip in [0] and self._dstrip['strip'] in [1]: + config = None + if self._dgeom['config'] is not None: + assert type(self._dgeom['config']) is str + config = utils.load(self._dgeom['config'], verb=verb) + + self._set_dgeom(config=config) + + elif strip in [1] and self._dstrip['strip'] in [0]: + if self._dgeom['config'] is not None: + path = self._dgeom['config'].Id.SavePath + name = self._dgeom['config'].Id.SaveName + pfe = os.path.join(path, name+'.npz') + lf = os.listdir(path) + lf = [ff for ff in lf if name+'.npz' in ff] + exist = len(lf)==1 + if not exist: + msg = """BEWARE: + You are about to delete the config object + Only the path/name to saved a object will be kept + + But it appears that the following object has no + saved file where specified (obj.Id.SavePath) + Thus it won't be possible to retrieve it + (unless available in the current console:""" + msg += "\n - {0}".format(pfe) + if force: + warning.warn(msg) + else: + raise Exception(msg) + self._dgeom['config'] = pfe + + ########### + # _strip and get/from dict + ########### + + @classmethod + def _strip_init(cls): + cls._dstrip['allowed'] = [0,1] + nMax = max(cls._dstrip['allowed']) + doc = """ + 1: dgeom pathfiles + """ + doc = utils.ToFuObjectBase.strip.__doc__.format(doc,nMax) + if sys.version[0]=='2': + cls.strip.__func__.__doc__ = doc + else: + cls.strip.__doc__ = doc + + def strip(self, strip=0, verb=True): + # super() + super(Plasma2D,self).strip(strip=strip, verb=verb) + + def _strip(self, strip=0, verb=True): + self._strip_dgeom(strip=strip, verb=verb) + + def _to_dict(self): + dout = {'dgroup':{'dict':self._dgroup, 'lexcept':None}, + 'dindref':{'dict':self._dindref, 'lexcept':None}, + 'ddata':{'dict':self._ddata, 'lexcept':None}, + 'dgeom':{'dict':self._dgeom, 'lexcept':None}} + return dout + + def _from_dict(self, fd): + self._dgroup.update(**fd['dgroup']) + self._dindref.update(**fd['dindref']) + self._ddata.update(**fd['ddata']) + self._dgeom.update(**fd['dgeom']) + + + ########### + # properties + ########### + + @property + def dgroup(self): + return self._dgroup + @property + def dindref(self): + return self._dindref + @property + def ddata(self): + return self._ddata + @property + def dtime(self): + return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() + if vv['group'] == 'time']) + @property + def dradius(self): + return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() + if vv['group'] == 'radius']) + @property + def dmesh(self): + return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() + if vv['group'] == 'mesh']) + @property + def config(self): + return self._dgeom['config'] + + #--------------------- + # Read-only for internal use + #--------------------- + + @property + def _lquantboth(self): + """ Return list of quantities available both in 1d and 2d """ + lq1 = [self._ddata[vd]['quant'] for vd in self._dgroup['radius']['ldata']] + lq2 = [self._ddata[vd]['quant'] for vd in self._dgroup['mesh']['ldata']] + lq = list(set(lq1).intersection(lq2)) + return lq + + def _get_ldata(self, dim=None, quant=None, name=None, + units=None, origin=None, + indref=None, group=None, log='all', return_key=True): + assert log in ['all','any','raw'] + lid = np.array(list(self._ddata.keys())) + ind = np.ones((7,len(lid)),dtype=bool) + if dim is not None: + ind[0,:] = [self._ddata[id_]['dim'] == dim for id_ in lid] + if quant is not None: + ind[1,:] = [self._ddata[id_]['quant'] == quant for id_ in lid] + if name is not None: + ind[2,:] = [self._ddata[id_]['name'] == name for id_ in lid] + if units is not None: + ind[3,:] = [self._ddata[id_]['units'] == units for id_ in lid] + if origin is not None: + ind[4,:] = [self._ddata[id_]['origin'] == origin for id_ in lid] + if indref is not None: + ind[5,:] = [depend in self._ddata[id_]['depend'] for id_ in lid] + if group is not None: + ind[6,:] = [group in self._ddata[id_]['lgroup'] for id_ in lid] + + if log == 'all': + ind = np.all(ind, axis=0) + elif log == 'any': + ind = np.any(ind, axis=0) + + if return_key: + if np.any(ind): + out = lid[ind.nonzero()[0]] + else: + out = np.array([],dtype=int) + else: + out = ind, lid + return out + + def _get_keyingroup(self, str_, group=None, msgstr=None, raise_=False): + + if str_ in self._ddata.keys(): + lg = self._ddata[str_]['lgroup'] + if group is None or group in lg: + return str_, None + else: + msg = "Required data key does not have matching group:\n" + msg += " - ddata[%s]['lgroup'] = %s"%(str_, lg) + msg += " - Expected group: %s"%group + if raise_: + raise Exception(msg) + + ind, akeys = self._get_ldata(dim=str_, quant=str_, name=str_, units=str_, + origin=str_, group=group, log='raw', + return_key=False) + # Remove indref and group + ind = ind[:5,:] & ind[-1,:] + + # Any perfect match ? + nind = np.sum(ind, axis=1) + sol = (nind == 1).nonzero()[0] + key, msg = None, None + if sol.size > 0: + if np.unique(sol).size == 1: + indkey = ind[sol[0],:].nonzero()[0] + key = akeys[indkey][0] + else: + lstr = "[dim,quant,name,units,origin]" + msg = "Several possible unique matches in %s for %s"(lstr,str_) + else: + lstr = "[dim,quant,name,units,origin]" + msg = "No unique match in %s for %s in group %s"%(lstr,str_,group) + + if msg is not None: + msg += "\n\nRequested %s could not be identified !\n"%msgstr + msg += "Please provide a valid (unique) key/name/quant/dim:\n\n" + msg += self.get_summary(verb=False, return_='msg') + if raise_: + raise Exception(msg) + return key, msg + + + #--------------------- + # Methods for showing data + #--------------------- + + def get_summary(self, sep=' ', line='-', just='l', + table_sep=None, verb=True, return_=False): + """ Summary description of the object content """ + # # Make sure the data is accessible + # msg = "The data is not accessible because self.strip(2) was used !" + # assert self._dstrip['strip']<2, msg + + # ----------------------- + # Build for ddata + col0 = ['group key', 'nb. indref'] + ar0 = [(k0, len(v0['lindref'])) for k0,v0 in self._dgroup.items()] + + # ----------------------- + # Build for ddata + col1 = ['indref key', 'group', 'size'] + ar1 = [(k0, v0['group'], v0['size']) for k0,v0 in self._dindref.items()] + + # ----------------------- + # Build for ddata + col2 = ['data key', 'origin', 'dim', 'quant', + 'name', 'units', 'shape', 'depend', 'lgroup'] + ar2 = [] + for k0,v0 in self._ddata.items(): + if type(v0['data']) is np.ndarray: + shape = str(v0['data'].shape) + else: + shape = v0['data'].__class__.__name__ + lu = [k0, v0['origin'], v0['dim'], v0['quant'], v0['name'], + v0['units'], shape, + str(v0['depend']), str(v0['lgroup'])] + ar2.append(lu) + + return self._get_summary([ar0,ar1,ar2], [col0, col1, col2], + sep=sep, line=line, table_sep=table_sep, + verb=verb, return_=return_) + + + #--------------------- + # Methods for adding ref / quantities + #--------------------- + + def add_ref(self, key=None, data=None, group=None, + dim=None, quant=None, units=None, origin=None, name=None): + """ Add a reference """ + assert type(key) is str and key not in self._ddata.keys() + assert type(data) in [np.ndarray, dict] + out = self._extract_dnd({key:{'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin}}, key) + dim, quant, origin, name, units = out + assert group in self._dgroup.keys() + if type(data) is np.ndarray: + size = data.shape[0] + else: + assert data['ftype'] in [0,1] + size = data['nnodes'] if data['ftype'] == 1 else data['nfaces'] + + self._dindref[key] = {'group':group, 'size':size, 'ldata':[key]} + + self._ddata[key] = {'data':data, + 'dim':dim, 'quant':quant, 'units':units, + 'origin':origin, 'name':name, + 'depend':(key,), 'lgroup':[group]} + self._complement() + + def add_quantity(self, key=None, data=None, depend=None, + dim=None, quant=None, units=None, + origin=None, name=None): + """ Add a quantity """ + c0 = type(key) is str and key not in self._ddata.keys() + if not c0: + msg = "key must be a str not already in self.ddata.keys()!\n" + msg += " - Provided: %s"%str(key) + raise Exception(msg) + if type(data) not in [np.ndarray, dict]: + msg = "data must be either:\n" + msg += " - np.ndarray\n" + msg += " - dict (mesh)\n" + msg += "\n Provided: %s"%str(type(data)) + raise Exception(msg) + out = self._extract_dnd({key:{'dim':dim, 'quant':quant, 'name':name, + 'units':units, 'origin':origin}}, key) + dim, quant, origin, name, units = out + assert type(depend) in [list,str,tuple] + if type(depend) is str: + depend = (depend,) + for ii in range(0,len(depend)): + assert depend[ii] in self._dindref.keys() + lgroup = [self._dindref[dd]['group'] for dd in depend] + self._ddata[key] = {'data':data, + 'dim':dim, 'quant':quant, 'units':units, + 'origin':origin, 'name':name, + 'depend':tuple(depend), 'lgroup':lgroup} + self._complement() + + + #--------------------- + # Method for getting time of a quantity + #--------------------- + + def get_time(self, key): + """ Return the time vector associated to a chosen quantity (identified + by its key)""" + + if key not in self._ddata.keys(): + msg = "Provided key not in self.ddata.keys() !\n" + msg += " - Provided: %s\n"%str(key) + msg += " - Available: %s\n"%str(self._ddata.keys()) + raise Exception(msg) + + indref = self._ddata[key]['depend'][0] + t = [kk for kk in self._dindref[indref]['ldata'] + if (self._ddata[kk]['depend'] == (indref,) + and self._ddata[kk]['quant'] == 't')] + if len(t) != 1: + msg = "No / several macthing time vectors were identified:\n" + msg += " - Provided: %s\n"%key + msg += " - Found: %s"%str(t) + raise Exception(msg) + return t[0] + + + def get_time_common(self, lkeys, choose=None): + """ Return the common time vector to several quantities + + If they do not have a common time vector, a reference one is choosen + according to criterion choose + """ + # Check all data have time-dependency + dout = {kk: {'t':self.get_time(kk)} for kk in lkeys} + dtu = dict.fromkeys(set([vv['t'] for vv in dout.values()])) + for kt in dtu.keys(): + dtu[kt] = {'ldata':[kk for kk in lkeys if dout[kk]['t'] == kt]} + if len(dtu) == 1: + tref = list(dtu.keys())[0] + else: + lt, lres = zip(*[(kt,np.mean(np.diff(self._ddata[kt]['data']))) + for kt in dtu.keys()]) + if choose is None: + choose = 'min' + if choose == 'min': + tref = lt[np.argmin(lres)] + return dout, dtu, tref + + @staticmethod + def _get_time_common_arrays(dins, choose=None): + dout = dict.fromkeys(dins.keys()) + dtu = {} + for k, v in dins.items(): + c0 = type(k) is str + c0 = c0 and all([ss in v.keys() for ss in ['val','t']]) + c0 = c0 and all([type(v[ss]) is np.ndarray for ss in ['val','t']]) + c0 = c0 and v['t'].size in v['val'].shape + if not c0: + msg = "dins must be a dict of the form (at least):\n" + msg += " dins[%s] = {'val': np.ndarray,\n"%str(k) + msg += " 't': np.ndarray}\n" + msg += "Provided: %s"%str(dins) + raise Exception(msg) + + kt, already = id(v['t']), True + if kt not in dtu.keys(): + lisclose = [kk for kk, vv in dtu.items() + if (vv['val'].shape == v['t'].shape + and np.allclose(vv['val'],v['t']))] + assert len(lisclose) <= 1 + if len(lisclose) == 1: + kt = lisclose[0] + else: + already = False + dtu[kt] = {'val':np.atleast_1d(v['t']).ravel(), + 'ldata':[k]} + if already: + dtu[kt]['ldata'].append(k) + assert dtu[kt]['val'].size == v['val'].shape[0] + dout[k] = {'val':v['val'], 't':kt} + + if len(dtu) == 1: + tref = list(dtu.keys())[0] + else: + lt, lres = zip(*[(kt,np.mean(np.diff(dtu[kt]['val']))) + for kt in dtu.keys()]) + if choose is None: + choose = 'min' + if choose == 'min': + tref = lt[np.argmin(lres)] + return dout, dtu, tref + + def _interp_on_common_time(self, lkeys, + choose='min', interp_t=None, t=None, + fill_value=np.nan): + """ Return a dict of time-interpolated data """ + dout, dtu, tref = self.get_time_common(lkeys) + if type(t) is np.ndarray: + tref = np.atleast_1d(t).ravel() + tr = tref + ltu = dtu.keys() + else: + if type(t) is str: + tref = t + tr = self._ddata[tref]['data'] + ltu = set(dtu.keys()) + if tref in dtu.keys(): + ltu = ltu.difference([tref]) + + if interp_t is None: + interp_t = _INTERPT + + # Interpolate + for tt in ltu: + for kk in dtu[tt]['ldata']: + dout[kk]['val'] = scpinterp.interp1d(self._ddata[tt]['data'], + self._ddata[kk]['data'], + kind=interp_t, axis=0, + bounds_error=False, + fill_value=fill_value)(tr) + + if type(tref) is not np.ndarray and tref in dtu.keys(): + for kk in dtu[tref]['ldata']: + dout[kk]['val'] = self._ddata[kk]['data'] + + return dout, tref + + def _interp_on_common_time_arrays(self, dins, + choose='min', interp_t=None, t=None, + fill_value=np.nan): + """ Return a dict of time-interpolated data """ + dout, dtu, tref = self._get_time_common_arrays(dins) + if type(t) is np.ndarray: + tref = np.atleast_1d(t).ravel() + tr = tref + ltu = dtu.keys() + else: + if type(t) is str: + assert t in dout.keys() + tref = dout[t]['t'] + tr = dtu[tref]['val'] + ltu = set(dtu.keys()).difference([tref]) + + if interp_t is None: + interp_t = _INTERPT + + # Interpolate + for tt in ltu: + for kk in dtu[tt]['ldata']: + dout[kk]['val'] = scpinterp.interp1d(dtu[tt]['val'], + dout[kk]['val'], + kind=interp_t, axis=0, + bounds_error=False, + fill_value=fill_value)(tr) + return dout, tref + + def interp_t(self, dkeys, + choose='min', interp_t=None, t=None, + fill_value=np.nan): + # Check inputs + assert type(dkeys) in [list,dict] + if type(dkeys) is list: + dkeys = {kk:{'val':kk} for kk in dkeys} + lc = [(type(kk) is str + and type(vv) is dict + and type(vv.get('val',None)) in [str,np.ndarray]) + for kk,vv in dkeys.items()] + assert all(lc), str(dkeys) + + # Separate by type + dk0 = dict([(kk,vv) for kk,vv in dkeys.items() + if type(vv['val']) is str]) + dk1 = dict([(kk,vv) for kk,vv in dkeys.items() + if type(vv['val']) is np.ndarray]) + assert len(dkeys) == len(dk0) + len(dk1), str(dk0) + '\n' + str(dk1) + + + if len(dk0) == len(dkeys): + lk = [v['val'] for v in dk0.values()] + dout, tref = self._interp_on_common_time(lk, choose=choose, + t=t, interp_t=interp_t, + fill_value=fill_value) + dout = {kk:{'val':dout[vv['val']]['val'], 't':dout[vv['val']]['t']} + for kk,vv in dk0.items()} + elif len(dk1) == len(dkeys): + dout, tref = self._interp_on_common_time_arrays(dk1, choose=choose, + t=t, interp_t=interp_t, + fill_value=fill_value) + + else: + lk = [v['val'] for v in dk0.values()] + if type(t) is np.ndarray: + dout, tref = self._interp_on_common_time(lk, choose=choose, + t=t, interp_t=interp_t, + fill_value=fill_value) + dout1, _ = self._interp_on_common_time_arrays(dk1, choose=choose, + t=t, interp_t=interp_t, + fill_value=fill_value) + else: + dout0, dtu0, tref0 = self.get_time_common(lk, + choose=choose) + dout1, dtu1, tref1 = self._get_time_common_arrays(dk1, + choose=choose) + if type(t) is str: + lc = [t in dtu0.keys(), t in dout1.keys()] + if not any(lc): + msg = "if t is str, it must refer to a valid key:\n" + msg += " - %s\n"%str(dtu0.keys()) + msg += " - %s\n"%str(dout1.keys()) + msg += "Provided: %s"%t + raise Exception(msg) + if lc[0]: + t0, t1 = t, self._ddata[t]['data'] + else: + t0, t1 = dtu1[dout1[t]['t']]['val'], t + tref = t + else: + if choose is None: + choose = 'min' + if choose == 'min': + t0 = self._ddata[tref0]['data'] + t1 = dtu1[tref1]['val'] + dt0 = np.mean(np.diff(t0)) + dt1 = np.mean(np.diff(t1)) + if dt0 < dt1: + t0, t1, tref = tref0, t0, tref0 + else: + t0, t1, tref = t1, tref1, tref1 + + dout, tref = self._interp_on_common_time(lk, choose=choose, + t=t0, interp_t=interp_t, + fill_value=fill_value) + dout = {kk:{'val':dout[vv['val']]['val'], + 't':dout[vv['val']]['t']} + for kk,vv in dk0.items()} + dout1, _ = self._interp_on_common_time_arrays(dk1, choose=choose, + t=t1, interp_t=interp_t, + fill_value=fill_value) + dout.update(dout1) + + return dout, tref + + #--------------------- + # Methods for computing additional plasma quantities + #--------------------- + + + def _fill_dins(self, dins): + for k in dins.keys(): + if type(dins[k]['val']) is str: + assert dins[k]['val'] in self._ddata.keys() + else: + dins[k]['val'] = np.atleast_1d(dins[k]['val']) + assert dins[k]['t'] is not None + dins[k]['t'] = np.atleast_1d(dins[k]['t']).ravel() + assert dins[k]['t'].size == dins[k]['val'].shape[0] + return dins + + @staticmethod + def _checkformat_shapes(dins): + shape = None + for k in dins.keys(): + dins[k]['shape'] = dins[k]['val'].shape + if shape is None: + shape = dins[k]['shape'] + if dins[k]['shape'] != shape: + if dins[k]['val'].ndim > len(shape): + shape = dins[k]['shape'] + + # Check shape consistency for broadcasting + assert len(shape) in [1,2] + if len(shape) == 1: + for k in dins.keys(): + assert dins[k]['shape'][0] in [1,shape[0]] + if dins[k]['shape'][0] < shape[0]: + dins[k]['val'] = np.full((shape[0],), dins[k]['val'][0]) + dins[k]['shape'] = dins[k]['val'].shape + + elif len(shape) == 2: + for k in dins.keys(): + if len(dins[k]['shape']) == 1: + if dins[k]['shape'][0] not in [1]+list(shape): + msg = "Non-conform shape for dins[%s]:\n"%k + msg += " - Expected: (%s,...) or (1,)\n"%str(shape[0]) + msg += " - Provided: %s"%str(dins[k]['shape']) + raise Exception(msg) + if dins[k]['shape'][0] == 1: + dins[k]['val'] = dins[k]['val'][None,:] + elif dins[k]['shape'][0] == shape[0]: + dins[k]['val'] = dins[k]['val'][:,None] + else: + dins[k]['val'] = dins[k]['val'][None,:] + else: + assert dins[k]['shape'] == shape + dins[k]['shape'] = dins[k]['val'].shape + return dins + + + + def compute_bremzeff(self, Te=None, ne=None, zeff=None, lamb=None, + tTe=None, tne=None, tzeff=None, t=None, + interp_t=None): + """ Return the bremsstrahlung spectral radiance at lamb + + The plasma conditions are set by: + - Te (eV) + - ne (/m3) + - zeff (adim.) + + The wavelength is set by the diagnostics + - lamb (m) + + The vol. spectral emis. is returned in ph / (s.m3.sr.m) + + The computation requires an intermediate : gff(Te, zeff) + """ + dins = {'Te':{'val':Te, 't':tTe}, + 'ne':{'val':ne, 't':tne}, + 'zeff':{'val':zeff, 't':tzeff}} + lc = [vv['val'] is None for vv in dins.values()] + if any(lc): + msg = "All fields should be provided:\n" + msg += " - %s"%str(dins.keys()) + raise Exception(msg) + dins = self._fill_dins(dins) + dins, t = self.interp_t(dins, t=t, interp_t=interp_t) + lamb = np.atleast_1d(lamb) + dins['lamb'] = {'val':lamb} + dins = self._checkformat_shapes(dins) + + val, units = _physics.compute_bremzeff(dins['Te']['val'], + dins['ne']['val'], + dins['zeff']['val'], + dins['lamb']['val']) + return val, t, units + + def compute_fanglev(self, BR=None, BPhi=None, BZ=None, + ne=None, lamb=None, t=None, interp_t=None, + tBR=None, tBPhi=None, tBZ=None, tne=None): + """ Return the vector faraday angle at lamb + + The plasma conditions are set by: + - BR (T) , array of R component of B + - BRPhi (T) , array of phi component of B + - BZ (T) , array of Z component of B + - ne (/m3) + + The wavelength is set by the diagnostics + - lamb (m) + + The vector faraday angle is returned in T / m + """ + dins = {'BR': {'val':BR, 't':tBR}, + 'BPhi':{'val':BPhi, 't':tBPhi}, + 'BZ': {'val':BZ, 't':tBZ}, + 'ne': {'val':ne, 't':tne}} + dins = self._fill_dins(dins) + dins, t = self.interp_t(dins, t=t, interp_t=interp_t) + lamb = np.atleast_1d(lamb) + dins['lamb'] = {'val':lamb} + dins = self._checkformat_shapes(dins) + + val, units = _physics.compute_fangle(BR=dins['BR']['val'], + BPhi=dins['BPhi']['val'], + BZ=dins['BZ']['val'], + ne=dins['ne']['val'], + lamb=dins['lamb']['val']) + return val, t, units + + + + #--------------------- + # Methods for interpolation + #--------------------- + + + def _get_quantrefkeys(self, qq, ref1d=None, ref2d=None): + + # Get relevant lists + kq, msg = self._get_keyingroup(qq, 'mesh', msgstr='quant', raise_=False) + if kq is not None: + k1d, k2d = None, None + else: + kq, msg = self._get_keyingroup(qq, 'radius', msgstr='quant', raise_=True) + if ref1d is None and ref2d is None: + msg = "quant %s needs refs (1d and 2d) for interpolation\n"%qq + msg += " => ref1d and ref2d cannot be both None !" + raise Exception(msg) + if ref1d is None: + ref1d = ref2d + k1d, msg = self._get_keyingroup(ref1d, 'radius', + msgstr='ref1d', raise_=False) + if k1d is None: + msg += "\n\nInterpolation of %s:\n"%qq + msg += " ref could not be identified among 1d quantities\n" + msg += " - ref1d : %s"%ref1d + raise Exception(msg) + if ref2d is None: + ref2d = ref1d + k2d, msg = self._get_keyingroup(ref2d, 'mesh', + msgstr='ref2d', raise_=False) + if k2d is None: + msg += "\n\nInterpolation of %s:\n" + msg += " ref could not be identified among 2d quantities\n" + msg += " - ref2d: %s"%ref2d + raise Exception(msg) + + q1d, q2d = self._ddata[k1d]['quant'], self._ddata[k2d]['quant'] + if q1d != q2d: + msg = "ref1d and ref2d must be of the same quantity !\n" + msg += " - ref1d (%s): %s\n"%(ref1d, q1d) + msg += " - ref2d (%s): %s"%(ref2d, q2d) + raise Exception(msg) + + return kq, k1d, k2d + + + def _get_indtmult(self, idquant=None, idref1d=None, idref2d=None): + + # Get time vectors and bins + idtq = self._ddata[idquant]['depend'][0] + tq = self._ddata[idtq]['data'] + tbinq = 0.5*(tq[1:]+tq[:-1]) + if idref1d is not None: + idtr1 = self._ddata[idref1d]['depend'][0] + tr1 = self._ddata[idtr1]['data'] + tbinr1 = 0.5*(tr1[1:]+tr1[:-1]) + if idref2d is not None and idref2d != idref1d: + idtr2 = self._ddata[idref2d]['depend'][0] + tr2 = self._ddata[idtr2]['data'] + tbinr2 = 0.5*(tr2[1:]+tr2[:-1]) + + # Get tbinall and tall + if idref1d is None: + tbinall = tbinq + tall = tq + else: + if idref2d is None: + tbinall = np.unique(np.r_[tbinq,tbinr1]) + else: + tbinall = np.unique(np.r_[tbinq,tbinr1,tbinr2]) + tall = np.r_[tbinall[0] - 0.5*(tbinall[1]-tbinall[0]), + 0.5*(tbinall[1:]+tbinall[:-1]), + tbinall[-1] + 0.5*(tbinall[-1]-tbinall[-2])] + + # Get indtqr1r2 (tall with respect to tq, tr1, tr2) + indtq, indtr1, indtr2 = None, None, None + indtq = np.digitize(tall, tbinq) + if idref1d is None: + assert np.all(indtq == np.arange(0,tall.size)) + if idref1d is not None: + indtr1 = np.digitize(tall, tbinr1) + if idref2d is not None: + indtr2 = np.digitize(tall, tbinr2) + + ntall = tall.size + return tall, tbinall, ntall, indtq, indtr1, indtr2 + + @staticmethod + def _get_indtu(t=None, tall=None, tbinall=None, + idref1d=None, idref2d=None, + indtr1=None, indtr2=None): + # Get indt (t with respect to tbinall) + indt, indtu = None, None + if t is not None: + indt = np.digitize(t, tbinall) + indtu = np.unique(indt) + + # Update + tall = tall[indtu] + if idref1d is not None: + assert indtr1 is not None + indtr1 = indtr1[indtu] + if idref2d is not None: + assert indtr2 is not None + indtr2 = indtr2[indtu] + ntall = tall.size + return tall, ntall, indt, indtu, indtr1, indtr2 + + def get_tcommon(self, lq, prefer='finer'): + """ Check if common t, else choose according to prefer + + By default, prefer the finer time resolution + + """ + if type(lq) is str: + lq = [lq] + t = [] + for qq in lq: + ltr = [kk for kk in self._ddata[qq]['depend'] + if self._dindref[kk]['group'] == 'time'] + assert len(ltr) <= 1 + if len(ltr) > 0 and ltr[0] not in t: + t.append(ltr[0]) + assert len(t) >= 1 + if len(t) > 1: + dt = [np.nanmean(np.diff(self._ddata[tt]['data'])) for tt in t] + if prefer == 'finer': + ind = np.argmin(dt) + else: + ind = np.argmax(dt) + else: + ind = 0 + return t[ind], t + + def _get_tcom(self, idquant=None, idref1d=None, + idref2d=None, idq2dR=None): + if idquant is not None: + out = self._get_indtmult(idquant=idquant, + idref1d=idref1d, idref2d=idref2d) + else: + out = self._get_indtmult(idquant=idq2dR) + return out + + + def _get_finterp(self, + idquant=None, idref1d=None, idref2d=None, + idq2dR=None, idq2dPhi=None, idq2dZ=None, + interp_t='nearest', interp_space=None, + fill_value=np.nan, ani=False, Type=None): + + # Get idmesh + if idquant is not None: + if idref1d is None: + lidmesh = [qq for qq in self._ddata[idquant]['depend'] + if self._dindref[qq]['group'] == 'mesh'] + else: + lidmesh = [qq for qq in self._ddata[idref2d]['depend'] + if self._dindref[qq]['group'] == 'mesh'] + else: + assert idq2dR is not None + lidmesh = [qq for qq in self._ddata[idq2dR]['depend'] + if self._dindref[qq]['group'] == 'mesh'] + assert len(lidmesh) == 1 + idmesh = lidmesh[0] + + # Get mesh + mpltri = self._ddata[idmesh]['data']['mpltri'] + trifind = mpltri.get_trifinder() + + # Get common time indices + if interp_t == 'nearest': + out = self._get_tcom(idquant,idref1d, idref2d, idq2dR) + tall, tbinall, ntall, indtq, indtr1, indtr2= out + + # # Prepare output + + # Interpolate + # Note : Maybe consider using scipy.LinearNDInterpolator ? + if idquant is not None: + vquant = self._ddata[idquant]['data'] + if self._ddata[idmesh]['data']['ntri'] > 1: + vquant = np.repeat(vquant, + self._ddata[idmesh]['data']['ntri'], axis=0) + else: + vq2dR = self._ddata[idq2dR]['data'] + vq2dPhi = self._ddata[idq2dPhi]['data'] + vq2dZ = self._ddata[idq2dZ]['data'] + + if interp_space is None: + interp_space = self._ddata[idmesh]['data']['ftype'] + + # get interpolation function + if ani: + # Assuming same mesh and time vector for all 3 components + func = _comp.get_finterp_ani(self, idq2dR, idq2dPhi, idq2dZ, + interp_t=interp_t, + interp_space=interp_space, + fill_value=fill_value, + idmesh=idmesh, vq2dR=vq2dR, + vq2dZ=vq2dZ, vq2dPhi=vq2dPhi, + tall=tall, tbinall=tbinall, + ntall=ntall, + indtq=indtq, trifind=trifind, + Type=Type, mpltri=mpltri) + else: + func = _comp.get_finterp_isotropic(self, idquant, idref1d, idref2d, + interp_t=interp_t, + interp_space=interp_space, + fill_value=fill_value, + idmesh=idmesh, vquant=vquant, + tall=tall, tbinall=tbinall, + ntall=ntall, mpltri=mpltri, + indtq=indtq, indtr1=indtr1, + indtr2=indtr2, trifind=trifind) + + + return func + + + def _checkformat_qr12RPZ(self, quant=None, ref1d=None, ref2d=None, + q2dR=None, q2dPhi=None, q2dZ=None): + lc0 = [quant is None, ref1d is None, ref2d is None] + lc1 = [q2dR is None, q2dPhi is None, q2dZ is None] + if np.sum([all(lc0), all(lc1)]) != 1: + msg = "Please provide either (xor):\n" + msg += " - a scalar field (isotropic emissivity):\n" + msg += " quant : scalar quantity to interpolate\n" + msg += " if quant is 1d, intermediate reference\n" + msg += " fields are necessary for 2d interpolation\n" + msg += " ref1d : 1d reference field on which to interpolate\n" + msg += " ref2d : 2d reference field on which to interpolate\n" + msg += " - a vector (R,Phi,Z) field (anisotropic emissivity):\n" + msg += " q2dR : R component of the vector field\n" + msg += " q2dPhi: R component of the vector field\n" + msg += " q2dZ : Z component of the vector field\n" + msg += " => all components have teh same time and mesh !\n" + raise Exception(msg) + + # Check requested quant is available in 2d or 1d + if all(lc1): + idquant, idref1d, idref2d = self._get_quantrefkeys(quant, ref1d, ref2d) + idq2dR, idq2dPhi, idq2dZ = None, None, None + ani = False + else: + idq2dR, msg = self._get_keyingroup(q2dR, 'mesh', msgstr='quant', + raise_=True) + idq2dPhi, msg = self._get_keyingroup(q2dPhi, 'mesh', msgstr='quant', + raise_=True) + idq2dZ, msg = self._get_keyingroup(q2dZ, 'mesh', msgstr='quant', + raise_=True) + idquant, idref1d, idref2d = None, None, None + ani = True + return idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani + + + def get_finterp2d(self, quant=None, ref1d=None, ref2d=None, + q2dR=None, q2dPhi=None, q2dZ=None, + interp_t=None, interp_space=None, + fill_value=np.nan, Type=None): + """ Return the function interpolating (X,Y,Z) pts on a 1d/2d profile + + Can be used as input for tf.geom.CamLOS1D/2D.calc_signal() + + """ + # Check inputs + msg = "Only 'nearest' available so far for interp_t!" + assert interp_t == 'nearest', msg + out = self._checkformat_qr12RPZ(quant=quant, ref1d=ref1d, ref2d=ref2d, + q2dR=q2dR, q2dPhi=q2dPhi, q2dZ=q2dZ) + idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani = out + + + # Interpolation (including time broadcasting) + func = self._get_finterp(idquant=idquant, idref1d=idref1d, + idref2d=idref2d, idq2dR=idq2dR, + idq2dPhi=idq2dPhi, idq2dZ=idq2dZ, + interp_t=interp_t, interp_space=interp_space, + fill_value=fill_value, ani=ani, Type=Type) + return func + + + def interp_pts2profile(self, pts=None, vect=None, t=None, + quant=None, ref1d=None, ref2d=None, + q2dR=None, q2dPhi=None, q2dZ=None, + interp_t=None, interp_space=None, + fill_value=np.nan, Type=None): + """ Return the value of the desired profiles_1d quantity + + For the desired inputs points (pts): + - pts are in (R,Z) coordinates + - space interpolation is linear on the 1d profiles + At the desired input times (t): + - using a nearest-neighbourg approach for time + + """ + # Check inputs + # msg = "Only 'nearest' available so far for interp_t!" + # assert interp_t == 'nearest', msg + + # Check requested quant is available in 2d or 1d + out = self._checkformat_qr12RPZ(quant=quant, ref1d=ref1d, ref2d=ref2d, + q2dR=q2dR, q2dPhi=q2dPhi, q2dZ=q2dZ) + idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani = out + + # Check the pts is (2,...) array of floats + if pts is None: + if ani: + idmesh = [id_ for id_ in self._ddata[idq2dR]['depend'] + if self._dindref[id_]['group'] == 'mesh'][0] + else: + if idref1d is None: + idmesh = [id_ for id_ in self._ddata[idquant]['depend'] + if self._dindref[id_]['group'] == 'mesh'][0] + else: + idmesh = [id_ for id_ in self._ddata[idref2d]['depend'] + if self._dindref[id_]['group'] == 'mesh'][0] + pts = self.dmesh[idmesh]['data']['nodes'] + pts = np.array([pts[:,0], np.zeros((pts.shape[0],)), pts[:,1]]) + + pts = np.atleast_2d(pts) + if pts.shape[0] != 3: + msg = "pts must be np.ndarray of (X,Y,Z) points coordinates\n" + msg += "Can be multi-dimensional, but the 1st dimension is (X,Y,Z)\n" + msg += " - Expected shape : (3,...)\n" + msg += " - Provided shape : %s"%str(pts.shape) + raise Exception(msg) + + # Check t + lc = [t is None, type(t) is str, type(t) is np.ndarray] + assert any(lc) + if lc[1]: + assert t in self._ddata.keys() + t = self._ddata[t]['data'] + + # Interpolation (including time broadcasting) + # this is the second slowest step (~0.08 s) + func = self._get_finterp(idquant=idquant, idref1d=idref1d, idref2d=idref2d, + idq2dR=idq2dR, idq2dPhi=idq2dPhi, idq2dZ=idq2dZ, + interp_t=interp_t, interp_space=interp_space, + fill_value=fill_value, ani=ani, Type=Type) + + # This is the slowest step (~1.8 s) + val, t = func(pts, vect=vect, t=t) + return val, t + + + def calc_signal_from_Cam(self, cam, t=None, + quant=None, ref1d=None, ref2d=None, + q2dR=None, q2dPhi=None, q2dZ=None, + Brightness=True, interp_t=None, + interp_space=None, fill_value=np.nan, + res=0.005, DL=None, resMode='abs', method='sum', + ind=None, out=object, plot=True, dataname=None, + fs=None, dmargin=None, wintit=None, invert=True, + units=None, draw=True, connect=True): + + if 'Cam' not in cam.__class__.__name__: + msg = "Arg cam must be tofu Camera instance (CamLOS1D, CamLOS2D...)" + raise Exception(msg) + + return cam.calc_signal_from_Plasma2D(self, t=t, + quant=quant, ref1d=ref1d, ref2d=ref2d, + q2dR=q2dR, q2dPhi=q2dPhi, + q2dZ=q2dZ, + Brightness=Brightness, + interp_t=interp_t, + interp_space=interp_space, + fill_value=fill_value, res=res, + DL=DL, resMode=resMode, + method=method, ind=ind, out=out, + pot=plot, dataname=dataname, + fs=fs, dmargin=dmargin, + wintit=wintit, invert=intert, + units=units, draw=draw, + connect=connect) + + + #--------------------- + # Methods for getting data + #--------------------- + + def get_dextra(self, dextra=None): + lc = [dextra is None, dextra == 'all', type(dextra) is dict, + type(dextra) is str, type(dextra) is list] + assert any(lc) + if dextra is None: + dextra = {} + + if dextra == 'all': + dextra = [k for k in self._dgroup['time']['ldata'] + if (self._ddata[k]['lgroup'] == ['time'] + and k not in self._dindref.keys())] + + if type(dextra) is str: + dextra = [dextra] + + # get data + if type(dextra) is list: + for ii in range(0,len(dextra)): + if type(dextra[ii]) is tuple: + ee, cc = dextra[ii] + else: + ee, cc = dextra[ii], None + ee, msg = self._get_keyingroup(ee, 'time', raise_=True) + if self._ddata[ee]['lgroup'] != ['time']: + msg = "time-only dependent signals allowed in dextra!\n" + msg += " - %s : %s"%(ee,str(self._ddata[ee]['lgroup'])) + raise Exception(msg) + idt = self._ddata[ee]['depend'][0] + key = 'data' if self._ddata[ee]['data'].ndim == 1 else 'data2D' + dd = {key: self._ddata[ee]['data'], + 't': self._ddata[idt]['data'], + 'label': self._ddata[ee]['name'], + 'units': self._ddata[ee]['units']} + if cc is not None: + dd['c'] = cc + dextra[ii] = (ee, dd) + dextra = dict(dextra) + return dextra + + def get_Data(self, lquant, X=None, ref1d=None, ref2d=None, + remap=False, res=0.01, interp_space=None, dextra=None): + + try: + import tofu.data as tfd + except Exception: + from .. import data as tfd + + # Check and format input + assert type(lquant) in [str,list] + if type(lquant) is str: + lquant = [lquant] + nquant = len(lquant) + + # Get X if common + c0 = type(X) is str + c1 = type(X) is list and (len(X) == 1 or len(X) == nquant) + if not (c0 or c1): + msg = "X must be specified, either as :\n" + msg += " - a str (name or quant)\n" + msg += " - a list of str\n" + msg += " Provided: %s"%str(X) + raise Exception(msg) + if c1 and len(X) == 1: + X = X[0] + + if type(X) is str: + idX, msg = self._get_keyingroup(X, 'radius', msgstr='X', raise_=True) + + # prepare remap pts + if remap: + assert self.config is not None + refS = list(self.config.dStruct['dObj']['Ves'].values())[0] + ptsRZ, x1, x2, extent = refS.get_sampleCross(res, mode='imshow') + dmap = {'t':None, 'data2D':None, 'extent':extent} + if ref is None and X in self._lquantboth: + ref = X + + # Define Data + dcommon = dict(Exp=self.Id.Exp, shot=self.Id.shot, + Diag='profiles1d', config=self.config) + + # dextra + dextra = self.get_dextra(dextra) + + # Get output + lout = [None for qq in lquant] + for ii in range(0,nquant): + qq = lquant[ii] + if remap: + # Check requested quant is available in 2d or 1d + idq, idrefd1, idref2d = self._get_quantrefkeys(qq, ref1d, ref2d) + else: + idq, msg = self._get_keyingroup(qq, 'radius', + msgstr='quant', raise_=True) + if idq not in self._dgroup['radius']['ldata']: + msg = "Only 1d quantities can be turned into tf.data.Data !\n" + msg += " - %s is not a radius-dependent quantity"%qq + raise Exception(msg) + idt = self._ddata[idq]['depend'][0] + + if type(X) is list: + idX, msg = self._get_keyingroup(X[ii], 'radius', + msgstr='X', raise_=True) + + dlabels = {'data':{'name': self._ddata[idq]['name'], + 'units': self._ddata[idq]['units']}, + 'X':{'name': self._ddata[idX]['name'], + 'units': self._ddata[idX]['units']}, + 't':{'name': self._ddata[idt]['name'], + 'units': self._ddata[idt]['units']}} + + dextra_ = dict(dextra) + if remap: + dmapii = dict(dmap) + val, tii = self.interp_pts2profile(qq, ptsRZ=ptsRZ, ref=ref, + interp_space=interp_space) + dmapii['data2D'], dmapii['t'] = val, tii + dextra_['map'] = dmapii + lout[ii] = DataCam1D(Name = qq, + data = self._ddata[idq]['data'], + t = self._ddata[idt]['data'], + X = self._ddata[idX]['data'], + dextra = dextra_, dlabels=dlabels, **dcommon) + if nquant == 1: + lout = lout[0] + return lout + + + #--------------------- + # Methods for plotting data + #--------------------- + + def plot(self, lquant, X=None, + ref1d=None, ref2d=None, + remap=False, res=0.01, interp_space=None, + sharex=False, bck=True): + lDat = self.get_Data(lquant, X=X, remap=remap, + ref1d=ref1d, ref2d=ref2d, + res=res, interp_space=interp_space) + if type(lDat) is list: + kh = lDat[0].plot_combine(lDat[1:], sharex=sharex, bck=bck) + else: + kh = lDat.plot(bck=bck) + return kh + + def plot_combine(self, lquant, lData=None, X=None, + ref1d=None, ref2d=None, + remap=False, res=0.01, interp_space=None, + sharex=False, bck=True): + """ plot combining several quantities from the Plasma2D itself and + optional extra list of Data instances """ + lDat = self.get_Data(lquant, X=X, remap=remap, + ref1d=ref1d, ref2d=ref2d, + res=res, interp_space=interp_space) + if lData is not None: + if type(lDat) is list: + lData = lDat[1:] + lData + else: + lData = lDat[1:] + [lData] + kh = lDat[0].plot_combine(lData, sharex=sharex, bck=bck) + return kh From eb4ff4e351f8ec639267c8341962f99bc31f0cc4 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Sun, 6 Oct 2019 23:01:57 +0200 Subject: [PATCH 05/23] [Issue208] DataHolder ok up to properties. TODO: select() --- tofu/data/_core_new.py | 712 ++++++++++++++--------------------------- 1 file changed, 235 insertions(+), 477 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 355dbdd46..667be83aa 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -64,7 +64,11 @@ class DataHolder(utils.ToFuObject): # Fixed (class-wise) dictionary of default properties _ddef = {'Id':{'include':['Mod', 'Cls', 'Name', 'version']}} - _lparams = ['origin', 'dim', 'quant', 'name', 'units'] + _dparams = {'origin':(str, 'unknown'), + 'dim': (str, 'unknown'), + 'quant': (str, 'unknown'), + 'name': (str, 'unknown'), + 'units': (str, 'a.u.')} # Does not exist before Python 3.6 !!! def __init_subclass__(cls, **kwdargs): @@ -83,7 +87,7 @@ def __init__(self, dref=None, ddata=None, SavePath_Include=tfpf.defInclude): # To replace __init_subclass__ for Python 2 - if sys.version[0]=='2': + if sys.version[0] == '2': self._dstrip = utils.ToFuObjectBase._dstrip.copy() self.__class__._strip_init() @@ -96,28 +100,22 @@ def __init__(self, dref=None, ddata=None, super(DataHolder, self).__init__(**kwdargs) def _reset(self): + # Run by the parent class __init__() # super() - super(Plasma2D,self)._reset() + super(DataHolder, self)._reset() self._dgroup = dict.fromkeys(self._get_keys_dgroup()) - self._dindref = dict.fromkeys(self._get_keys_dindref()) + self._dref = dict.fromkeys(self._get_keys_dref()) self._ddata = dict.fromkeys(self._get_keys_ddata()) - self._dgeom = dict.fromkeys(self._get_keys_dgeom()) @classmethod def _checkformat_inputs_Id(cls, Id=None, Name=None, include=None, **kwdargs): if Id is not None: - assert isinstance(Id,utils.ID) + assert isinstance(Id, utils.ID) Name = Id.Name - assert type(Name) is str, Name + assert isinstance(Name, str), Name if include is None: include = cls._ddef['Id']['include'] - if 'shot' in include: - include.remove('shot') - if 'Exp' in include: - include.remove('Exp') - if 'Diag' in include: - include.remove('Diag') kwdargs.update({'Name':Name, 'include':include}) return kwdargs @@ -126,15 +124,16 @@ def _checkformat_inputs_Id(cls, Id=None, Name=None, ########### @staticmethod - def _get_largs_dindrefdatagroup(): - largs = ['dtime', 'dradius', 'dmesh', 'd0d', 'd1d', 'd2d'] + def _get_largs_dref(): + largs = ['dref'] return largs @staticmethod - def _get_largs_dgeom(): - largs = ['config'] + def _get_largs_ddata(): + largs = ['ddata'] return largs + ########### # Get check and format inputs ########### @@ -143,173 +142,177 @@ def _get_largs_dgeom(): # Methods for checking and formatting inputs #--------------------- - @classmethod - def _checkformat_ref(ref): - assert type(ref) is dict - assert all([type(kk) is str for kk in dref.keys()]) - for kk, vv in dref.values(): - assert type(vv) is dict - assert 'group' in vv.keys() and type(vv['group']) is str + def _extract_known_params(self, key, dd): + # Check no reserved key is used + lkout = ['group', 'depend', 'ref', 'refs', 'groups', + 'lref', 'ldata'] + lkind = [kk in dd.keys() for kk in lkout] + if any(lkind): + msg = "The following keys are reserved for internal use:\n" + msg += " %s\n"%str(lkout) + msg += " => Please do not use them !" + raise Exception(msg) + dparams = {kk:vv for kk, vv in dd.items() if kk != 'data'} + dparams.update({kk: dd.get(kk, vv[1]) for kk, vv in self._dparams.items()}) + for kk, vv in dparams.items(): + if not (vv is None or isinstance(vv, self._dparams[kk][0])): + msg = "A parameter for %s has the wrong type:\n"%key + msg += " - Provided: type(%s) = %s\n"%(kk, str(type(vv))) + msg += " - Expected %s"%str(self._dparams[kk][0]) + raise Exception(msg) + return dparams - @staticmethod - def _extract_dnd(dnd, k0, - dim_=None, quant_=None, name_=None, - origin_=None, units_=None): - # Set defaults - dim_ = k0 if dim_ is None else dim_ - quant_ = k0 if quant_ is None else quant_ - name_ = k0 if name_ is None else name_ - origin_ = 'unknown' if origin_ is None else origin_ - units_ = 'a.u.' if units_ is None else units_ - - # Extrac - dim = dnd[k0].get('dim', None) - if dim is None: - dim = dim_ - quant = dnd[k0].get('quant', None) - if quant is None: - quant = quant_ - origin = dnd[k0].get('origin', None) - if origin is None: - origin = origin_ - name = dnd[k0].get('name', None) - if name is None: - name = name_ - units = dnd[k0].get('units', None) - if units is None: - units = units_ - return dim, quant, origin, name, units + def _checkformat_dref(self, dref): + c0 = isinstance(dref, dict) + c0 = c0 and all([isinstance(kk, str) for kk in dref.keys()]) + if not c0: + msg = "Provided dref must be dict !\n" + msg += "All its keys must be str !" + raise Exception(msg) - @staticmethod - def _checkformat_dtrm(dtime=None, dradius=None, dmesh=None, - d0d=None, d1d=None, d2d=None): - - dd = {'dtime':dtime, 'dradius':dradius, 'dmesh':dmesh, - 'd0d':d0d, 'd1d':d1d, 'd2d':d2d} - - # Define allowed keys for each dict - lkok = ['data', 'dim', 'quant', 'name', 'origin', 'units', - 'depend'] - lkmeshmax = ['type','ftype','nodes','faces', - 'nfaces','nnodes','mpltri','size','ntri'] - lkmeshmin = ['type','ftype','nodes','faces'] - dkok = {'dtime': {'max':lkok, 'min':['data'], 'ndim':[1]}, - 'dradius':{'max':lkok, 'min':['data'], 'ndim':[1,2]}, - 'd0d':{'max':lkok, 'min':['data'], 'ndim':[1,2,3]}, - 'd1d':{'max':lkok, 'min':['data'], 'ndim':[1,2]}, - 'd2d':{'max':lkok, 'min':['data'], 'ndim':[1,2]}} - dkok['dmesh'] = {'max':lkok + lkmeshmax, 'min':lkmeshmin} - - # Check each dict independently - for dk, dv in dd.items(): - if dv is None or len(dv) == 0: - dd[dk] = {} - continue - c0 = type(dv) is not dict or any([type(k0) is not str - for k0 in dv.keys()]) - c0 = any([type(k0) is not str or type(v0) is not dict - for k0, v0 in dv.items()]) - if c0: - msg = "Arg %s must be a dict with:\n" - msg += " - (key, values) of type (str, dict)" + for kk, vv in dref.items(): + c0 = isinstance(vv, dict) + c0 = c0 and 'group' in vv.keys() and isinstance(vv['group'], str) + c0 = c0 and 'data' in vv.keys() + if not c0: + msg = "dref must contain dict with at least the keys:\n" + msg += " - 'group': a str indicating the group of refs\n" + msg += " - 'data': a 1d array containing the data" raise Exception(msg) - for k0, v0 in dv.items(): - c0 = any([k1 not in dkok[dk]['max'] for k1 in v0.keys()]) - c0 = c0 or any([v0.get(k1,None) is None - for k1 in dkok[dk]['min']]) - if c0: - msg = "Arg %s[%s] must be a dict with keys in:\n"%(dk,k0) - msg += " - %s\n"%str(dkok[dk]['max']) - msg += "And with at least the following keys:\n" - msg += " - %s\n"%str(dkok[dk]['min']) - msg += "Provided:\n" - msg += " - %s\n"%str(v0.keys()) - msg += "Missing:\n" - msg += " - %s\n"%str(set(dkok[dk]['min']).difference(v0.keys())) - msg += "Non-valid:\n" - msg += " - %s"%str(set(v0.keys()).difference(dkok[dk]['max'])) - raise Exception(msg) - if 'data' in dkok[dk]['min']: - dd[dk][k0]['data'] = np.atleast_1d(np.squeeze(v0['data'])) - if dd[dk][k0]['data'].ndim not in dkok[dk]['ndim']: - msg = "%s[%s]['data'] has wrong dimensions:\n"%(dk,k0) - msg += " - Expected: %s\n"%str(dkok[dk]['ndim']) - msg += " - Provided: %s"%str(dd[dk][k0]['data'].ndim) - raise Exception(msg) - if dk == 'dmesh': - dd[dk][k0]['nodes'] = np.atleast_2d(v0['nodes']).astype(float) - dd[dk][k0]['faces'] = np.atleast_2d(v0['faces']).astype(int) - nnodes = dd[dk][k0]['nodes'].shape[0] - nfaces = dd[dk][k0]['faces'].shape[0] - - # Test for duplicates - nodesu = np.unique(dd[dk][k0]['nodes'], axis=0) - facesu = np.unique(dd[dk][k0]['faces'], axis=0) - lc = [nodesu.shape[0] != nnodes, - facesu.shape[0] != nfaces] - if any(lc): - msg = "Non-valid mesh %s[%s]:\n"%(dk,k0) - if lc[0]: - msg += " Duplicate nodes: %s\n"%str(nnodes - nodesu.shape[0]) - msg += " - nodes.shape: %s\n"%str(dd[dk][k0]['nodes'].shape) - msg += " - unique nodes.shape: %s\n"%str(nodesu.shape) - if lc[1]: - msg += " Duplicate faces: %s\n"%str(nfaces - facesu.shape[0]) - msg += " - faces.shape: %s\n"%str(dd[dk][k0]['faces'].shape) - msg += " - unique faces.shape: %s"%str(facesu.shape) + if vv['group'] not in self._dgroup.keys(): + self._dgroup[vv['group']] = {} + + if kk in self._ddata.keys(): + msg = "key '%s' already used !\n"%kk + msg += " => each key must be unique !" + raise Exception(msg) + + data = vv['data'] + if not isinstance(data, np.ndarray): + if isinstance(data, dict): + size = '?' + elif issubclass(data.__class__, ToFuObject): + size = '?' + else: + try: + data = np.atleast_1d(data).ravel() + size = data.size + except: + msg = "Could not convert dref[%s]['data'] to array"%kk raise Exception(msg) + else: + if data.ndim != 1: + data = np.atleast_1d(data).ravel() + size = data.size - # Test for unused nodes - facesu = np.unique(facesu) - c0 = np.all(facesu>=0) and facesu.size == nnodes - if not c0: - indnot = [ii for ii in range(0,nnodes) - if ii not in facesu] - msg = "Some nodes not used in mesh %s[%s]:\n"(dk,k0) - msg += " - unused nodes indices: %s"%str(indnot) - warnings.warn(msg) - - - dd[dk][k0]['nnodes'] = dd[dk][k0].get('nnodes', nnodes) - dd[dk][k0]['nfaces'] = dd[dk][k0].get('nfaces', nfaces) - - assert dd[dk][k0]['nodes'].shape == (v0['nnodes'],2) - assert np.max(dd[dk][k0]['faces']) < v0['nnodes'] - # Only triangular meshes so far - assert v0['type'] in ['tri', 'quadtri'], v0['type'] - - if 'tri' in v0['type']: - assert dd[dk][k0]['faces'].shape == (v0['nfaces'],3) - if v0.get('mpltri', None) is None: - dd[dk][k0]['mpltri'] = mplTri(dd[dk][k0]['nodes'][:,0], - dd[dk][k0]['nodes'][:,1], - dd[dk][k0]['faces']) - assert isinstance(dd[dk][k0]['mpltri'], mplTri) - assert dd[dk][k0]['ftype'] in [0,1] - ntri = dd[dk][k0]['ntri'] - if dd[dk][k0]['ftype'] == 1: - dd[dk][k0]['size'] = dd[dk][k0]['nnodes'] - else: - dd[dk][k0]['size'] = int(dd[dk][k0]['nfaces']/ntri) - - # Check unicity of all keys - lk = [list(dv.keys()) for dv in dd.values()] - lk = list(itt.chain.from_iterable(lk)) - lku = sorted(set(lk)) - lk = ['%s : %s times'%(kk, str(lk.count(kk))) for kk in lku if lk.count(kk) > 1] - if len(lk) > 0: - msg = "Each key of (dtime,dradius,dmesh,d0d,d1d,d2d) must be unique !\n" - msg += "The following keys are repeated :\n" - msg += " - " + "\n - ".join(lk) + dref[kk] = {'size':size, 'group':vv['group']} + + dparams = self._extract_known_params(kk, vv) + self._ddata[kk] = {'data':data, 'ref':(kk,), + 'shape':(size,), **dparams} + + def _checkformat_ddata(self, ddata): + c0 = isinstance(ddata, dict) + c0 = c0 and all([isinstance(kk, str) for kk in ddata.keys()]) + if not c0: + msg = "Provided ddata must be dict !\n" + msg += "All its keys must be str !" raise Exception(msg) - dtime, dradius, dmesh = dd['dtime'], dd['dradius'], dd['dmesh'] - d0d, d1d, d2d = dd['d0d'], dd['d1d'], dd['d2d'] + # Start check on each key + for kk, vv in ddata.items(): + + # Check value is a dict with proper keys + c0 = isinstance(vv, dict) + c0 = c0 and 'ref' in vv.keys() and isinstance(vv['ref'], tuple) + c0 = c0 and 'data' in vv.keys() + if not c0: + msg = "ddata must contain dict with at least the keys:\n" + msg += " - 'ref': a str indicating the ref(s) dependencies\n" + msg += " - 'data': a 1d array containing the data" + raise Exception(msg) + + # Check key unicity + if kk in self._ddata.keys(): + msg = "key '%s' already used !\n"%kk + msg += " => each key must be unique !" + raise Exception(msg) + + # Extract data and shape + data = vv['data'] + if not isinstance(data, np.ndarray): + try: + data = np.asarray(data) + shape = data.shape + except: + assert type(data) in [list, tuple] + shape = (len(data), '?') + else: + data = np.atleast_1d(np.squeeze(data)) + shape = data.shape + + # Check proper ref (existence and shape / size) + for ii, rr in enumerate(vv['ref']): + if rr not in self._dref.keys(): + msg = "ddata[%s] depends on an unknown ref !\n"%kk + msg += " - ddata[%s]['ref'] = %s\n"%(kk, rr) + msg += " => %s not in self.dref !\n"%rr + msg += " => self.add_ref( %s ) first !"%rr + raise Exception(msg) + shaperef = (self._dref[rr]['size'] for rr in vv['ref']) + if not shape == shaperef: + msg = "Inconsistency between data shape and ref size !\n" + msg += " - ddata[%s]['data'] shape: %s\n"%(kk, str(shape)) + msg += " - sizes of refs: %s"%(str(shaperef)) + raise Exception(msg) + + # Extract params and set self._ddata + dparams = self._extract_known_params(kk, vv) + self._ddata[kk] = {'data':data, 'ref':vv['ref'], + 'shape':shape, + **dparams} + + + def _complement_dgrouprefdata(self): + + # -------------- + # ddata + for k0, v0 in self._ddata.items(): + + # Check all ref are in dref + lrefout = [ii for ii in v0['ref'] if ii not in self._dref.keys()] + if len(lrefout) != 0: + msg = "ddata[%s]['ref'] has keys not in dref:\n"%k0 + msg += " - " + "\n - ".join(lrefout) + raise Exception(msg) + + # set group + groups = (self._dref[rr]['group'] for rr in v0['ref']) + assert all([gg in self._dgroup.keys() for gg in groups]) + self._ddata[k0]['group'] = groups + + # -------------- + # dref + for k0 in self._dref.keys(): + self._dref[k0]['ldata'] = [kk for kk, vv in self._ddata.items() + if k0 in vv['ref']] + assert self._dref[k0]['group'] in self._dgroup.keys() + + # -------------- + # dgroup + for gg, vg in self._dgroup.items(): + lref = [rr for rr, vv in self._dref.items() + if vv['group'] == gg] + ldata = [dd for dd in self._ddata.keys() + if any([dd in self._dref[vref]['ldata'] + for vref in lref])] + #assert vg['depend'] in lidindref + self._dgroup[gg]['lref'] = lref + self._dgroup[gg]['ldata'] = ldata - return dtime, dradius, dmesh, d0d, d1d, d2d ########### @@ -317,30 +320,32 @@ def _checkformat_dtrm(dtime=None, dradius=None, dmesh=None, ########### @staticmethod - def _get_keys_dindref(): - lk = [] + def _get_keys_dgroup(): + lk = ['lref', 'ldata'] + return lk + + @staticmethod + def _get_keys_dref(): + lk = ['group', 'size', 'ldata'] return lk @staticmethod def _get_keys_ddata(): - lk = [] + lk = ['data', 'ref', 'shape', 'group'] return lk ########### # _init ########### - def _init(self, dtime=None, dradius=None, dmesh=None, - d0d=None, d1d=None, d2d=None, - config=None, **kwargs): - kwdargs = locals() - kwdargs.update(**kwargs) - largs = self._get_largs_dindrefdatagroup() - kwdindrefdatagroup = self._extract_kwdargs(kwdargs, largs) - largs = self._get_largs_dgeom() - kwdgeom = self._extract_kwdargs(kwdargs, largs) - self._set_dindrefdatagroup(**kwdindrefdatagroup) - self.set_dgeom(**kwdgeom) + def _init(self, dref=None, ddata=None, **kwargs): + kwdargs = {'dref':dref, 'ddata':ddata, **kwargs} + largs = self._get_largs_dref() + kwddref = self._extract_kwdargs(kwdargs, largs) + self._set_dref(**kwddref, complement=False) + largs = self._get_largs_ddata() + kwddata = self._extract_kwdargs(kwdargs, largs) + self._set_ddata(**kwddata) self._dstrip['strip'] = 0 @@ -348,275 +353,51 @@ def _init(self, dtime=None, dradius=None, dmesh=None, # set dictionaries ########### - @staticmethod - def _find_lref(shape=None, k0=None, dd=None, ddstr=None, - dindref=None, lrefname=['t','radius']): - if 'depend' in dd[k0].keys(): - lref = dd[k0]['depend'] - else: - lref = [[kk for kk, vv in dindref.items() - if vv['size'] == sh and vv['group'] in lrefname] - for sh in shape] - lref = list(itt.chain.from_iterable(lref)) - if len(lref) < len(shape): - msg = "Maybe not enoough references for %s[%s]:\n"%(ddstr,k0) - msg += " - shape: %s\n"%str(shape) - msg += " - lref: %s"%str(lref) - warnings.warn(msg) - - if len(lref) > len(shape): - msg = "Too many references for %s[%s]:\n"%(ddstr,k0) - msg += " - shape: %s\n"%str(shape) - msg += " - lref: %s"%str(lref) - raise Exception(msg) - return lref - - - - - def _set_dindrefdatagroup(self, dtime=None, dradius=None, dmesh=None, - d0d=None, d1d=None, d2d=None): - - # Check dtime is not None - out = self._checkformat_dtrm(dtime=dtime, dradius=dradius, dmesh=dmesh, - d0d=d0d, d1d=d1d, d2d=d2d) - dtime, dradius, dmesh, d0d, d1d, d2d = out - - dgroup, dindref, ddata = {}, {}, {} - empty = {} - # Get indt - if dtime is not None: - for k0 in dtime.keys(): - out = self._extract_dnd(dtime,k0, - dim_='time', quant_='t', - name_=k0, units_='s') - dim, quant, origin, name, units = out - - assert k0 not in dindref.keys() - dtime[k0]['data'] = np.atleast_1d(np.squeeze(dtime[k0]['data'])) - assert dtime[k0]['data'].ndim == 1 - - dindref[k0] = {'size':dtime[k0]['data'].size, - 'group':'time'} - - assert k0 not in ddata.keys() - ddata[k0] = {'data':dtime[k0]['data'], - 'dim':dim, 'quant':quant, 'name':name, - 'origin':origin, 'units':units, 'depend':(k0,)} - - # d0d - if d0d is not None: - for k0 in d0d.keys(): - out = self._extract_dnd(d0d,k0) - dim, quant, origin, name, units = out - - # data - d0d[k0]['data'] = np.atleast_1d(np.squeeze(d0d[k0]['data'])) - assert d0d[k0]['data'].ndim >= 1 - - depend = self._find_lref(d0d[k0]['data'].shape, k0, dd=d0d, - ddstr='d0d', dindref=dindref, - lrefname=['t']) - assert len(depend) == 1 and dindref[depend[0]]['group']=='time' - assert k0 not in ddata.keys() - ddata[k0] = {'data':d0d[k0]['data'], - 'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin, 'depend':depend} - - # get radius - if dradius is not None: - for k0 in dradius.keys(): - out = self._extract_dnd(dradius, k0, name_=k0) - dim, quant, origin, name, units = out - assert k0 not in dindref.keys() - data = np.atleast_1d(np.squeeze(dradius[k0]['data'])) - assert data.ndim in [1,2] - - if len(dradius[k0].get('depend',[1])) == 1: - assert data.ndim == 1 - size = data.size - else: - lkt = [k for k in dtime.keys() if k in dradius[k0]['depend']] - assert len(lkt) == 1 - axist = dradius[k0]['depend'].index(lkt[0]) - size = data.shape[1-axist] - dindref[k0] = {'size':size, - 'group':'radius'} - - assert k0 not in ddata.keys() - depend = self._find_lref(data.shape, k0, dd=dradius, - ddstr='dradius', dindref=dindref, - lrefname=['t','radius']) - ddata[k0] = {'data':data, - 'dim':dim, 'quant':quant, 'name':name, - 'origin':origin, 'units':units, 'depend':depend} - - - # Get d1d - if d1d is not None: - for k0 in d1d.keys(): - out = self._extract_dnd(d1d,k0) - dim, quant, origin, name, units = out - - d1d[k0]['data'] = np.atleast_2d(np.squeeze(d1d[k0]['data'])) - assert d1d[k0]['data'].ndim == 2 - - # data - depend = self._find_lref(d1d[k0]['data'].shape, k0, dd=d1d, - ddstr='d1d', dindref=dindref, - lrefname=['t','radius']) - assert k0 not in ddata.keys() - ddata[k0] = {'data':d1d[k0]['data'], - 'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin, 'depend':depend} - - # dmesh ref - if dmesh is not None: - for k0 in dmesh.keys(): - out = self._extract_dnd(dmesh, k0, dim_='mesh') - dim, quant, origin, name, units = out - - assert k0 not in dindref.keys() - dindref[k0] = {'size':dmesh[k0]['size'], - 'group':'mesh'} - - assert k0 not in ddata.keys() - ddata[k0] = {'data':dmesh[k0], - 'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin, 'depend':(k0,)} - - # d2d - if d2d is not None: - for k0 in d2d.keys(): - out = self._extract_dnd(d2d,k0) - dim, quant, origin, name, units = out - - d2d[k0]['data'] = np.atleast_2d(np.squeeze(d2d[k0]['data'])) - assert d2d[k0]['data'].ndim == 2 - - depend = self._find_lref(d2d[k0]['data'].shape, k0, dd=d2d, - ddstr='d2d', dindref=dindref, - lrefname=['t','mesh']) - assert k0 not in ddata.keys() - ddata[k0] = {'data':d2d[k0]['data'], - 'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin, 'depend':depend} - - # dgroup - dgroup = {} - if len(dtime) > 0: - dgroup['time'] = {'dref':list(dtime.keys())[0]} - if len(dradius) > 0: - dgroup['radius'] = {'dref':list(dradius.keys())[0]} - if len(dmesh) > 0: - dgroup['mesh'] = {'dref':list(dmesh.keys())[0]} - - # Update dict - self._dgroup = dgroup - self._dindref = dindref - self._ddata = ddata - # Complement - self._complement() + def _set_dref(self, dref, complement=True): + self._checkformat_dref(dref) + if complement: + self._complement_dgrouprefdata() + def _set_ddata(self, ddata): + self._checkformat_ddata(ddata) + self._complement_dgrouprefdata() + def add_ref(self, key, data=None, group=None, **kwdargs): + self._set_dref({key:{'data':data, 'group':group, **kwdargs}}) - def _complement(self): + def remove_ref(self, key): + assert key in self._dref.keys() + lkdata = [] + del self._dref[key] + for kk in lkdata: + del self._ddata[kk] + self._complement_dgrouprefdata() - # -------------- - # ddata - for k0, v0 in self.ddata.items(): - lindout = [ii for ii in v0['depend'] if ii not in self.dindref.keys()] - if not len(lindout) == 0: - msg = "ddata[%s]['depend'] has keys not in dindref:\n"%k0 - msg += " - " + "\n - ".join(lindout) - raise Exception(msg) + def add_data(self, key, data=None, ref=None, **kwdargs): + self._set_ddata({key: {'data':data, 'ref':ref, **kwdargs}}) - self.ddata[k0]['lgroup'] = [self.dindref[ii]['group'] - for ii in v0['depend']] - type_ = type(v0['data']) - shape = tuple([self.dindref[ii]['size'] for ii in v0['depend']]) - - # if only one dim => mesh or iterable or unspecified - if len(shape) == 1 or type_ is dict: - c0 = type_ is dict and 'mesh' in self.ddata[k0]['lgroup'] - c1 = not c0 and len(v0['data']) == shape[0] - if not (c0 or c1): - msg = k0+'\n' - msg += str([c0, c1, type_, len(v0['data']), shape]) - msg += "\n" + str(v0['data']) - else: - assert type(v0['data']) is np.ndarray - assert v0['data'].shape == shape - - # -------------- - # dindref - for k0 in self.dindref.keys(): - self.dindref[k0]['ldata'] = [kk for kk, vv in self.ddata.items() - if k0 in vv['depend']] - assert self.dindref[k0]['group'] in self.dgroup.keys() - - # -------------- - # dgroup - for gg, vg in self.dgroup.items(): - lindref = [id_ for id_,vv in self.dindref.items() - if vv['group'] == gg] - ldata = [id_ for id_ in self.ddata.keys() - if any([id_ in self.dindref[vref]['ldata'] - for vref in lindref])] - #assert vg['depend'] in lidindref - self.dgroup[gg]['lindref'] = lindref - self.dgroup[gg]['ldata'] = ldata - - - def set_dgeom(self, config=None): - config = self._checkformat_inputs_dgeom(config=config) - self._dgeom = {'config':config} + def remove_data(self, key, propagate=True): + if key in self._dref.keys(): + self.remove_ref(key) + else: + assert key in self._ddata.keys() + if propagate: + # Check if associated ref shall be removed too + lref = self._ddata[key]['ref'] + for kref in lref: + # Remove if key was the only associated data + if self._dref[kref]['ldata'] == [key]: + del self._dref[kref] + del self._ddata[key] ########### # strip dictionaries ########### - def _strip_ddata(self, strip=0): + def _strip_ddata(self, strip=0, verb=0): pass - - def _strip_dgeom(self, strip=0, force=False, verb=True): - if self._dstrip['strip']==strip: - return - - if strip in [0] and self._dstrip['strip'] in [1]: - config = None - if self._dgeom['config'] is not None: - assert type(self._dgeom['config']) is str - config = utils.load(self._dgeom['config'], verb=verb) - - self._set_dgeom(config=config) - - elif strip in [1] and self._dstrip['strip'] in [0]: - if self._dgeom['config'] is not None: - path = self._dgeom['config'].Id.SavePath - name = self._dgeom['config'].Id.SaveName - pfe = os.path.join(path, name+'.npz') - lf = os.listdir(path) - lf = [ff for ff in lf if name+'.npz' in ff] - exist = len(lf)==1 - if not exist: - msg = """BEWARE: - You are about to delete the config object - Only the path/name to saved a object will be kept - - But it appears that the following object has no - saved file where specified (obj.Id.SavePath) - Thus it won't be possible to retrieve it - (unless available in the current console:""" - msg += "\n - {0}".format(pfe) - if force: - warning.warn(msg) - else: - raise Exception(msg) - self._dgeom['config'] = pfe - ########### # _strip and get/from dict ########### @@ -626,33 +407,32 @@ def _strip_init(cls): cls._dstrip['allowed'] = [0,1] nMax = max(cls._dstrip['allowed']) doc = """ - 1: dgeom pathfiles + 1: None """ doc = utils.ToFuObjectBase.strip.__doc__.format(doc,nMax) - if sys.version[0]=='2': + if sys.version[0] == '2': cls.strip.__func__.__doc__ = doc else: cls.strip.__doc__ = doc def strip(self, strip=0, verb=True): # super() - super(Plasma2D,self).strip(strip=strip, verb=verb) + super(DataHolder, self).strip(strip=strip, verb=verb) def _strip(self, strip=0, verb=True): - self._strip_dgeom(strip=strip, verb=verb) + self._strip_ddata(strip=strip, verb=verb) def _to_dict(self): dout = {'dgroup':{'dict':self._dgroup, 'lexcept':None}, - 'dindref':{'dict':self._dindref, 'lexcept':None}, - 'ddata':{'dict':self._ddata, 'lexcept':None}, - 'dgeom':{'dict':self._dgeom, 'lexcept':None}} + 'dref':{'dict':self._dref, 'lexcept':None}, + 'ddata':{'dict':self._ddata, 'lexcept':None}} return dout def _from_dict(self, fd): self._dgroup.update(**fd['dgroup']) - self._dindref.update(**fd['dindref']) + self._dref.update(**fd['dref']) self._ddata.update(**fd['ddata']) - self._dgeom.update(**fd['dgeom']) + self._complement_dgrouprefdata() ########### @@ -663,39 +443,17 @@ def _from_dict(self, fd): def dgroup(self): return self._dgroup @property - def dindref(self): - return self._dindref + def dref(self): + return self._dref @property def ddata(self): return self._ddata - @property - def dtime(self): - return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() - if vv['group'] == 'time']) - @property - def dradius(self): - return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() - if vv['group'] == 'radius']) - @property - def dmesh(self): - return dict([(kk, self._ddata[kk]) for kk,vv in self._dindref.items() - if vv['group'] == 'mesh']) - @property - def config(self): - return self._dgeom['config'] #--------------------- # Read-only for internal use #--------------------- - @property - def _lquantboth(self): - """ Return list of quantities available both in 1d and 2d """ - lq1 = [self._ddata[vd]['quant'] for vd in self._dgroup['radius']['ldata']] - lq2 = [self._ddata[vd]['quant'] for vd in self._dgroup['mesh']['ldata']] - lq = list(set(lq1).intersection(lq2)) - return lq - + # Replace with select !!! def _get_ldata(self, dim=None, quant=None, name=None, units=None, origin=None, indref=None, group=None, log='all', return_key=True): From 08a421da3f6df9c24e1894e5fbfc39636db4e86a Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Mon, 7 Oct 2019 11:39:49 +0200 Subject: [PATCH 06/23] [Issue208] Continued DataHolder up to interpolation (excluded) => start testing --- tofu/data/_core_new.py | 390 ++++++++++++++++++++++++----------------- tofu/version.py | 2 +- 2 files changed, 228 insertions(+), 164 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 667be83aa..343b16eaf 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -70,6 +70,9 @@ class DataHolder(utils.ToFuObject): 'name': (str, 'unknown'), 'units': (str, 'a.u.')} + _show_in_summary = 'all' + + # Does not exist before Python 3.6 !!! def __init_subclass__(cls, **kwdargs): # Python 2 @@ -185,7 +188,7 @@ def _checkformat_dref(self, dref): if vv['group'] not in self._dgroup.keys(): self._dgroup[vv['group']] = {} - if kk in self._ddata.keys(): + if kk in self.__lkdata: msg = "key '%s' already used !\n"%kk msg += " => each key must be unique !" raise Exception(msg) @@ -213,6 +216,8 @@ def _checkformat_dref(self, dref): dparams = self._extract_known_params(kk, vv) self._ddata[kk] = {'data':data, 'ref':(kk,), 'shape':(size,), **dparams} + self.__lkdata.append(kk) + self.__lparams = set(self.__lparams).update(dparams.keys()) def _checkformat_ddata(self, ddata): c0 = isinstance(ddata, dict) @@ -222,6 +227,8 @@ def _checkformat_ddata(self, ddata): msg += "All its keys must be str !" raise Exception(msg) + # ind = np.array([self._ddata[kk]['index'] for kk in self._ddata.keys()]) + # Start check on each key for kk, vv in ddata.items(): @@ -274,12 +281,15 @@ def _checkformat_ddata(self, ddata): self._ddata[kk] = {'data':data, 'ref':vv['ref'], 'shape':shape, **dparams} + self.__lkdata.append(kk) + self.__lparams = set(self.__lparams).update(dparams.keys()) def _complement_dgrouprefdata(self): # -------------- # ddata + assert len(self.__lkdata) == len(self._ddata.keys()) for k0, v0 in self._ddata.items(): # Check all ref are in dref @@ -331,7 +341,7 @@ def _get_keys_dref(): @staticmethod def _get_keys_ddata(): - lk = ['data', 'ref', 'shape', 'group'] + lk = ['data', 'ref', 'shape', 'group', 'index'] return lk ########### @@ -340,6 +350,8 @@ def _get_keys_ddata(): def _init(self, dref=None, ddata=None, **kwargs): kwdargs = {'dref':dref, 'ddata':ddata, **kwargs} + self.__lkdata = [] + self.__lparams = set() largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) self._set_dref(**kwddref, complement=False) @@ -362,21 +374,34 @@ def _set_ddata(self, ddata): self._checkformat_ddata(ddata) self._complement_dgrouprefdata() + #--------------------- + # Methods for adding ref / quantities + #--------------------- + def add_ref(self, key, data=None, group=None, **kwdargs): + """ Add a reference """ self._set_dref({key:{'data':data, 'group':group, **kwdargs}}) def remove_ref(self, key): + """ Remove a reference (all data depending on it are removed too) """ assert key in self._dref.keys() lkdata = [] del self._dref[key] for kk in lkdata: - del self._ddata[kk] + del self.__ddata[kk] + self.__lkdata.remove(kk) self._complement_dgrouprefdata() def add_data(self, key, data=None, ref=None, **kwdargs): + """ Add a data (all associated ref must be added first)) """ self._set_ddata({key: {'data':data, 'ref':ref, **kwdargs}}) def remove_data(self, key, propagate=True): + """ Remove a data + + Any associated ref reated to this data only is removed too (useless) + + """ if key in self._dref.keys(): self.remove_ref(key) else: @@ -387,8 +412,10 @@ def remove_data(self, key, propagate=True): for kref in lref: # Remove if key was the only associated data if self._dref[kref]['ldata'] == [key]: - del self._dref[kref] + self.remove_ref(kref) del self._ddata[key] + self.__lkdata.remove(key) + self._complement_dgrouprefdata() ########### @@ -439,104 +466,205 @@ def _from_dict(self, fd): # properties ########### + @property + def dconfig(self): + """ The dict of configs """ + return self._dconfig @property def dgroup(self): + """ The dict of groups """ return self._dgroup @property def dref(self): + """ the dict of references """ return self._dref @property def ddata(self): + """ the dict of data """ return self._ddata + def lkdata(self): + """ The list of current data keys """ + return self.__lkdata + def lparam(self): + """ The list of current parameters """ + return self.__lparams + + + #--------------------- + # Add / remove params + #--------------------- + + + def get_param(self, param=None, returnas=np.ndarray): + + # Check inputs and trivial cases + if param is None: + return + assert param in self.__lparam + assert returnas in [np.ndarray, dict, list] + + # Get output + if returnas == dict: + out = {kk:self._ddata[kk][param] for kk in self.__lkdata} + else: + out = [self._ddata[kk][param] for kk in self.__lkdata] + if returnas == np.ndarray: + try: + out = np.asarray(out) + except Exception as err: + msg = "Could not convert %s to array !" + warnings.warn(msg) + return out + + def set_param(self, param=None, values=None, ind=None, key=None): + + # Check and format input + if param is None: + return + assert param in self.__lparam + + # Update all keys with common value + ltypes = [str, int, np.int, float, np.float, tuple] + lc = [any([isinstance(values, tt) for tt in ltypes]), + isinstance(values, list), isinstance(values, np.ndarray)] + if not any(lc): + msg = "Accepted types for values include:\n" + msg += " - %s: common to all\n"%str(ltypes) + msg += " - list, np.ndarray: key by key" + raise Exception(msg) + + if lc0: + key = self._ind_tofrom_key(ind=ind, key=key, out='key') + for kk in key: + self._ddata[kk]][param] = values + + # Update relevant keys with corresponding values + else: + key = self._ind_tofrom_key(ind=ind, key=key, out='key') + assert len(key) == len(values) + for kk in range(len(key)): + self._ddata[key[ii]][param] = values[ii] + + def add_param(self, param, values=None): + assert isinstance(param) str + assert param not in self.__lparams + self.__lparams.append(param) + self.set_param(param=param, values=values) + + def remove_param(self, param=None): + # Check and format input + if param is None: + return + assert param in self.__lparam + + self.__lparams.remove(param) + for kk in self.__lkdata: + del self._ddata[kk][param] + #--------------------- # Read-only for internal use #--------------------- - # Replace with select !!! - def _get_ldata(self, dim=None, quant=None, name=None, - units=None, origin=None, - indref=None, group=None, log='all', return_key=True): - assert log in ['all','any','raw'] - lid = np.array(list(self._ddata.keys())) - ind = np.ones((7,len(lid)),dtype=bool) - if dim is not None: - ind[0,:] = [self._ddata[id_]['dim'] == dim for id_ in lid] - if quant is not None: - ind[1,:] = [self._ddata[id_]['quant'] == quant for id_ in lid] - if name is not None: - ind[2,:] = [self._ddata[id_]['name'] == name for id_ in lid] - if units is not None: - ind[3,:] = [self._ddata[id_]['units'] == units for id_ in lid] - if origin is not None: - ind[4,:] = [self._ddata[id_]['origin'] == origin for id_ in lid] - if indref is not None: - ind[5,:] = [depend in self._ddata[id_]['depend'] for id_ in lid] - if group is not None: - ind[6,:] = [group in self._ddata[id_]['lgroup'] for id_ in lid] + def select(self, group=None, ref=None, log='all', return_key=True, + **kwdargs): + """ Return the indices / keys of data matching criteria + + The selection is done comparing the value of all provided parameters + The result is a boolean indices array, optionally with the keys list + It can include: + - log = 'all': only the data matching all criteria + - log = 'any': the data matching any criterion + If log = 'raw', a dict of indices arrays is returned, showing the + details for each criterion + + """ + + # Format and check input + assert log in ['all', 'any', 'raw'] + if log == 'raw': + assert not return_key + + # Get list of relevant criteria + lk = ['group', 'ref'] + list(kwdargs.keys()) + lcrit = [ss for ss in lk if ss is not None] + ncrit = len(lcrit) + + # Prepare array of bool indices and populate + ind = np.ones((ncrit, len(self._ddata)), dtype=bool) + for ii in range(ncrit): + critval = eval(lcrit[ii]) + try: + par = self.get_param(lcrit[ii], returnas=np.ndarray) + ind[ii,:] = par == critval + except: + ind[ii,:] = [self._ddata[kk][param] == critval + for kk in self.__lkata] + + # Format output ind if log == 'all': ind = np.all(ind, axis=0) elif log == 'any': ind = np.any(ind, axis=0) + else: + ind = {lcrit[ii]: ind[ii,:] for ii in range(ncrit)} + # Also return the list of keys if required if return_key: if np.any(ind): - out = lid[ind.nonzero()[0]] + out = ind, lid[ind.nonzero()[0]] else: - out = np.array([],dtype=int) + out = ind, np.array([],dtype=int) else: - out = ind, lid + out = ind return out - def _get_keyingroup(self, str_, group=None, msgstr=None, raise_=False): - - if str_ in self._ddata.keys(): - lg = self._ddata[str_]['lgroup'] - if group is None or group in lg: - return str_, None + def _ind_tofrom_key(self, ind=None, key=None, returnas=int): + + # Check / format input + assert returnas in [int, bool, 'key'] + lc = [ind is not None, key is not None] + assert np.sum(lc) <= 1 + + # Initialize output + out = np.zeros((len(self.__lkdata),), dtype=bool) + + # Test + if lc[0]: + ind = np.atleast_1d(ind).ravel() + assert ind.dtype == np.int or ind.dtype == np.bool + out[ind] = True + if returnas in [int, 'key']: + out = out.nonzero()[0] + if returnas == 'key': + out = [self.__lkdata[ii] for ii in out] + + elif lc[1]: + if isinstance(key, str): + key = [key] + if returnas == 'key': + out = key else: - msg = "Required data key does not have matching group:\n" - msg += " - ddata[%s]['lgroup'] = %s"%(str_, lg) - msg += " - Expected group: %s"%group - if raise_: - raise Exception(msg) - - ind, akeys = self._get_ldata(dim=str_, quant=str_, name=str_, units=str_, - origin=str_, group=group, log='raw', - return_key=False) - # Remove indref and group - ind = ind[:5,:] & ind[-1,:] - - # Any perfect match ? - nind = np.sum(ind, axis=1) - sol = (nind == 1).nonzero()[0] - key, msg = None, None - if sol.size > 0: - if np.unique(sol).size == 1: - indkey = ind[sol[0],:].nonzero()[0] - key = akeys[indkey][0] - else: - lstr = "[dim,quant,name,units,origin]" - msg = "Several possible unique matches in %s for %s"(lstr,str_) + for kk in key: + out[self.__lkdata.index(kk)] = True + if returnas == int: + out = out.nonzero()[0] else: - lstr = "[dim,quant,name,units,origin]" - msg = "No unique match in %s for %s in group %s"%(lstr,str_,group) - - if msg is not None: - msg += "\n\nRequested %s could not be identified !\n"%msgstr - msg += "Please provide a valid (unique) key/name/quant/dim:\n\n" - msg += self.get_summary(verb=False, return_='msg') - if raise_: - raise Exception(msg) - return key, msg + if returnas == bool: + out[:] = True + elif returnas == int: + out = np.arange(0, len(self.__lkdata)) + else: + out = self.__lkdata + return out #--------------------- # Methods for showing data #--------------------- - def get_summary(self, sep=' ', line='-', just='l', + def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', table_sep=None, verb=True, return_=False): """ Summary description of the object content """ # # Make sure the data is accessible @@ -544,28 +672,42 @@ def get_summary(self, sep=' ', line='-', just='l', # assert self._dstrip['strip']<2, msg # ----------------------- - # Build for ddata - col0 = ['group key', 'nb. indref'] - ar0 = [(k0, len(v0['lindref'])) for k0,v0 in self._dgroup.items()] + # Build for groups + col0 = ['group name', 'nb. ref', 'nb. data'] + ar0 = [(k0, len(v0['lref']), len(v0['ldata'])) + for k0,v0 in self._dgroup.items()] # ----------------------- - # Build for ddata - col1 = ['indref key', 'group', 'size'] - ar1 = [(k0, v0['group'], v0['size']) for k0,v0 in self._dindref.items()] + # Build for refs + col1 = ['ref key', 'group', 'size', 'nb. data'] + ar1 = [(k0, v0['group'], v0['size'], len(v0['ldata'])) + for k0,v0 in self._dref.items()] # ----------------------- # Build for ddata - col2 = ['data key', 'origin', 'dim', 'quant', - 'name', 'units', 'shape', 'depend', 'lgroup'] + col2 = ['data key'] + if show_core is None: + show_core = self._show_in_summary_core + if isinstance(show_core, str): + show_core = [show_core] + lkcore = ['shape', 'group', 'ref'] + assert all([ss in self._lparams + lkcore for ss in show_core]) + col2 += show_core + + if show is None: + show = self._show_in_summary + if show == 'all': + col2 += self._lparams + else: + if isinstance(show, str): + show = [show] + assert all([ss in self._lparams for ss in show]) + col2 += show + ar2 = [] - for k0,v0 in self._ddata.items(): - if type(v0['data']) is np.ndarray: - shape = str(v0['data'].shape) - else: - shape = v0['data'].__class__.__name__ - lu = [k0, v0['origin'], v0['dim'], v0['quant'], v0['name'], - v0['units'], shape, - str(v0['depend']), str(v0['lgroup'])] + for k0 in self._lkdata: + v0 = self._ddata[k0] + lu = [k0] + [str(v0[cc]) for cc in col2[1:]] ar2.append(lu) return self._get_summary([ar0,ar1,ar2], [col0, col1, col2], @@ -573,89 +715,11 @@ def get_summary(self, sep=' ', line='-', just='l', verb=verb, return_=return_) - #--------------------- - # Methods for adding ref / quantities - #--------------------- - - def add_ref(self, key=None, data=None, group=None, - dim=None, quant=None, units=None, origin=None, name=None): - """ Add a reference """ - assert type(key) is str and key not in self._ddata.keys() - assert type(data) in [np.ndarray, dict] - out = self._extract_dnd({key:{'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin}}, key) - dim, quant, origin, name, units = out - assert group in self._dgroup.keys() - if type(data) is np.ndarray: - size = data.shape[0] - else: - assert data['ftype'] in [0,1] - size = data['nnodes'] if data['ftype'] == 1 else data['nfaces'] - - self._dindref[key] = {'group':group, 'size':size, 'ldata':[key]} - - self._ddata[key] = {'data':data, - 'dim':dim, 'quant':quant, 'units':units, - 'origin':origin, 'name':name, - 'depend':(key,), 'lgroup':[group]} - self._complement() - - def add_quantity(self, key=None, data=None, depend=None, - dim=None, quant=None, units=None, - origin=None, name=None): - """ Add a quantity """ - c0 = type(key) is str and key not in self._ddata.keys() - if not c0: - msg = "key must be a str not already in self.ddata.keys()!\n" - msg += " - Provided: %s"%str(key) - raise Exception(msg) - if type(data) not in [np.ndarray, dict]: - msg = "data must be either:\n" - msg += " - np.ndarray\n" - msg += " - dict (mesh)\n" - msg += "\n Provided: %s"%str(type(data)) - raise Exception(msg) - out = self._extract_dnd({key:{'dim':dim, 'quant':quant, 'name':name, - 'units':units, 'origin':origin}}, key) - dim, quant, origin, name, units = out - assert type(depend) in [list,str,tuple] - if type(depend) is str: - depend = (depend,) - for ii in range(0,len(depend)): - assert depend[ii] in self._dindref.keys() - lgroup = [self._dindref[dd]['group'] for dd in depend] - self._ddata[key] = {'data':data, - 'dim':dim, 'quant':quant, 'units':units, - 'origin':origin, 'name':name, - 'depend':tuple(depend), 'lgroup':lgroup} - self._complement() - #--------------------- - # Method for getting time of a quantity + # Method for interpolating on ref #--------------------- - def get_time(self, key): - """ Return the time vector associated to a chosen quantity (identified - by its key)""" - - if key not in self._ddata.keys(): - msg = "Provided key not in self.ddata.keys() !\n" - msg += " - Provided: %s\n"%str(key) - msg += " - Available: %s\n"%str(self._ddata.keys()) - raise Exception(msg) - - indref = self._ddata[key]['depend'][0] - t = [kk for kk in self._dindref[indref]['ldata'] - if (self._ddata[kk]['depend'] == (indref,) - and self._ddata[kk]['quant'] == 't')] - if len(t) != 1: - msg = "No / several macthing time vectors were identified:\n" - msg += " - Provided: %s\n"%key - msg += " - Found: %s"%str(t) - raise Exception(msg) - return t[0] - def get_time_common(self, lkeys, choose=None): """ Return the common time vector to several quantities diff --git a/tofu/version.py b/tofu/version.py index cdbdcdf53..0e7b80896 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-52-g6f060e3' +__version__ = '1.4.1-122-geb4ff4e' From c17094a208c44b255f96baab79337e568a9f512f Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Mon, 7 Oct 2019 11:41:01 +0200 Subject: [PATCH 07/23] [Issue208] Added _core_new on tofu/data/__init__.py --- tofu/data/__init__.py | 1 + tofu/version.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tofu/data/__init__.py b/tofu/data/__init__.py index 46db6ee41..5fcff8409 100644 --- a/tofu/data/__init__.py +++ b/tofu/data/__init__.py @@ -3,3 +3,4 @@ Provide data handling class and methods (storing, processing, plotting...) """ from tofu.data._core import * +from tofu.data._core_new import * diff --git a/tofu/version.py b/tofu/version.py index 0e7b80896..e1959ac44 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-122-geb4ff4e' +__version__ = '1.4.1-123-g08a421d' From 00bc95f64361a573c4184754a0601c87d81713c9 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Mon, 7 Oct 2019 19:20:41 +0200 Subject: [PATCH 08/23] [Issue208] Debugging and cleaning DataHolder up to add_param() --- tofu/data/_core_new.py | 317 +++++++++++++++++++++++++---------------- tofu/version.py | 2 +- 2 files changed, 196 insertions(+), 123 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 343b16eaf..068b946f8 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -63,13 +63,16 @@ class DataHolder(utils.ToFuObject): """ # Fixed (class-wise) dictionary of default properties _ddef = {'Id':{'include':['Mod', 'Cls', - 'Name', 'version']}} - _dparams = {'origin':(str, 'unknown'), - 'dim': (str, 'unknown'), - 'quant': (str, 'unknown'), - 'name': (str, 'unknown'), - 'units': (str, 'a.u.')} - + 'Name', 'version']}i, + 'dgroup':['lref'], + 'dref': ['group', 'size', 'ldata'], + 'ddata': ['refs', 'shape', 'groups', 'data'], + 'params':{'origin':(str, 'unknown'), + 'dim': (str, 'unknown'), + 'quant': (str, 'unknown'), + 'name': (str, 'unknown'), + 'units': (str, 'a.u.')} + _reserved_all = _ddef['dgroup'] + _ddef['dref'] + _ddef['ddata'] _show_in_summary = 'all' @@ -106,9 +109,9 @@ def _reset(self): # Run by the parent class __init__() # super() super(DataHolder, self)._reset() - self._dgroup = dict.fromkeys(self._get_keys_dgroup()) - self._dref = dict.fromkeys(self._get_keys_dref()) - self._ddata = dict.fromkeys(self._get_keys_ddata()) + self._dgroup = {kd[0]:kd[1] for kd in self._get_keys_dgroup()} + self._dref = {kd[0]:kd[1] for kd in self._get_keys_dref()} + self._ddata = {kd[0]:kd[1] for kd in self._get_keys_ddata()} @classmethod def _checkformat_inputs_Id(cls, Id=None, Name=None, @@ -147,77 +150,120 @@ def _get_largs_ddata(): def _extract_known_params(self, key, dd): # Check no reserved key is used - lkout = ['group', 'depend', 'ref', 'refs', 'groups', - 'lref', 'ldata'] - lkind = [kk in dd.keys() for kk in lkout] + lkind = [kk in dd.keys() for kk in self._reserved_all] if any(lkind): msg = "The following keys are reserved for internal use:\n" - msg += " %s\n"%str(lkout) + msg += " %s\n"%str(self._reserved_all) msg += " => Please do not use them !" raise Exception(msg) - dparams = {kk:vv for kk, vv in dd.items() if kk != 'data'} - dparams.update({kk: dd.get(kk, vv[1]) for kk, vv in self._dparams.items()}) - for kk, vv in dparams.items(): - if not (vv is None or isinstance(vv, self._dparams[kk][0])): - msg = "A parameter for %s has the wrong type:\n"%key - msg += " - Provided: type(%s) = %s\n"%(kk, str(type(vv))) - msg += " - Expected %s"%str(self._dparams[kk][0]) - raise Exception(msg) + # Ectract relevant parameters + dparams = dict(dd) + + # Add minimum default parameters if not already included + for kk, vv in self._ddef['params'].items(): + if kk not in dparams.keys(): + dparams[kk] = vv[1] + else: + # Check type if already included + if not isinstance(dparams[kk], vv[0]): + msg = "A parameter for %s has the wrong type:\n"%key + msg += " - Provided: type(%s) = %s\n"%(kk, str(type(vv))) + msg += " - Expected %s"%str(self._dparams[kk][0]) + raise Exception(msg) return dparams def _checkformat_dref(self, dref): c0 = isinstance(dref, dict) - c0 = c0 and all([isinstance(kk, str) for kk in dref.keys()]) + c0 = c0 and all([isinstance(kk, str) and isinstance(vv, dict) + for kk, vv in dref.keys()]) if not c0: msg = "Provided dref must be dict !\n" - msg += "All its keys must be str !" + msg += "All its keys must be str !\n" + msg += "All its values must be dict !" + raise Exception(msg) + + # Two options: + # (A) - {'group0':{'t0':{'data':t0, 'units':'s'}, 't1':...}} + # (B) - {'t0':{'data':t0, 'units':'s', 'group':'group0'}, 't1':...} + + cA = all([all([isinstance(v1, dict) and 'group' not in v1.keys() + for v1 in v0.values()]) + and 'group' not in v0.keys() for v0 in dref.values()]) + cB = all([isinstance(v0.get('group', None), str) for v0 in dref.values()]) + if not (cA or cB): + msg = "Provided dref must formatted either as:\n\n" + msg += " - a dict of group keys with a dict of key ref:\n" + msg += " {'group0':{'t0':{'data':t0, 'units':'s'},\n" + msg += " 't1':{'data':t1, 'units':'h'}},\n" + msg += " 'group1':{'t2':{'data':t2, 'units':'min'}}}\n\n" + msg += " - a dict of key ref with a dict containing the group:\n" + msg += " {'t0':{'data':t0, 'units':'s', 'group':'group0'},\n" + msg += " 't1':{'data':t1, 'units':'h', 'group':'group0'},\n" + msg += " 't2':{'data':t2, 'units':'min', 'group':'group1'}" raise Exception(msg) + if cA: + # Convert to cB + drbis = {} + for k0, v0 in dref.items(): + for k1, v1 in v0.items(): + drbis[k1] = v1 + drbis['group'] = k0 + dref = drbis + + # Check cB for kk, vv in dref.items(): - c0 = isinstance(vv, dict) - c0 = c0 and 'group' in vv.keys() and isinstance(vv['group'], str) - c0 = c0 and 'data' in vv.keys() - if not c0: - msg = "dref must contain dict with at least the keys:\n" - msg += " - 'group': a str indicating the group of refs\n" - msg += " - 'data': a 1d array containing the data" - raise Exception(msg) - if vv['group'] not in self._dgroup.keys(): - self._dgroup[vv['group']] = {} + # Check if new group + if vv['group'] not in self._dgroup['lkey']: + self._dgroup['dict'][vv['group']] = {} - if kk in self.__lkdata: + # Check key unicity + if kk in self._ddata['lkey']: msg = "key '%s' already used !\n"%kk msg += " => each key must be unique !" raise Exception(msg) + # Check data + c0 = 'data' in vv.keys() data = vv['data'] if not isinstance(data, np.ndarray): - if isinstance(data, dict): - size = '?' - elif issubclass(data.__class__, ToFuObject): - size = '?' - else: + if isinstance(data, list) or isinstance(data, tuple): try: data = np.atleast_1d(data).ravel() size = data.size except: - msg = "Could not convert dref[%s]['data'] to array"%kk - raise Exception(msg) + c0 = False + else: + size = data.__class__.__name__ else: if data.ndim != 1: data = np.atleast_1d(data).ravel() size = data.size - dref[kk] = {'size':size, 'group':vv['group']} + if not c0: + msg = "Each dict in dref must hold an array-convertibe 'data'\n" + msg += "The following array conversion failed:\n" + msg += " - np.atleast_1d(dref[%s]['data']).ravel()"%kk + raise Exception(msg) + + # Fill self._dref + self._dref['dict'][kk] = {'size':size, 'group':vv['group']} + # Extract and check parameters dparams = self._extract_known_params(kk, vv) - self._ddata[kk] = {'data':data, 'ref':(kk,), - 'shape':(size,), **dparams} - self.__lkdata.append(kk) - self.__lparams = set(self.__lparams).update(dparams.keys()) + + # Fill self._ddata + self._ddata['dict'][kk] = {'data':data, 'ref':(kk,), + 'shape':(size,), **dparams} + self._ddata['lkey'].append(kk) + + # ------------- DB (start) + def __repr__(self): + return self.__class__.__name__ + # ------------- DB (end) def _checkformat_ddata(self, ddata): c0 = isinstance(ddata, dict) @@ -227,14 +273,12 @@ def _checkformat_ddata(self, ddata): msg += "All its keys must be str !" raise Exception(msg) - # ind = np.array([self._ddata[kk]['index'] for kk in self._ddata.keys()]) - # Start check on each key for kk, vv in ddata.items(): # Check value is a dict with proper keys c0 = isinstance(vv, dict) - c0 = c0 and 'ref' in vv.keys() and isinstance(vv['ref'], tuple) + c0 = c0 and 'refs' in vv.keys() and isinstance(vv['refs'], tuple) c0 = c0 and 'data' in vv.keys() if not c0: msg = "ddata must contain dict with at least the keys:\n" @@ -243,7 +287,7 @@ def _checkformat_ddata(self, ddata): raise Exception(msg) # Check key unicity - if kk in self._ddata.keys(): + if kk in self._ddata['lkey']: msg = "key '%s' already used !\n"%kk msg += " => each key must be unique !" raise Exception(msg) @@ -251,25 +295,28 @@ def _checkformat_ddata(self, ddata): # Extract data and shape data = vv['data'] if not isinstance(data, np.ndarray): - try: - data = np.asarray(data) - shape = data.shape - except: - assert type(data) in [list, tuple] - shape = (len(data), '?') + if isinstance(data, list) or isinstance(data, tuple): + try: + data = np.asarray(data) + shape = data.shape + except: + assert type(data) in [list, tuple] + shape = (len(data),) + else: + shape = data.__class__.__name__ else: data = np.atleast_1d(np.squeeze(data)) shape = data.shape # Check proper ref (existence and shape / size) for ii, rr in enumerate(vv['ref']): - if rr not in self._dref.keys(): + if rr not in self._dref['lkey']: msg = "ddata[%s] depends on an unknown ref !\n"%kk msg += " - ddata[%s]['ref'] = %s\n"%(kk, rr) msg += " => %s not in self.dref !\n"%rr msg += " => self.add_ref( %s ) first !"%rr raise Exception(msg) - shaperef = (self._dref[rr]['size'] for rr in vv['ref']) + shaperef = tuple(self._dref[rr]['size'] for rr in vv['ref']) if not shape == shaperef: msg = "Inconsistency between data shape and ref size !\n" msg += " - ddata[%s]['data'] shape: %s\n"%(kk, str(shape)) @@ -278,51 +325,65 @@ def _checkformat_ddata(self, ddata): # Extract params and set self._ddata dparams = self._extract_known_params(kk, vv) - self._ddata[kk] = {'data':data, 'ref':vv['ref'], - 'shape':shape, - **dparams} - self.__lkdata.append(kk) - self.__lparams = set(self.__lparams).update(dparams.keys()) + self._ddata['dict'][kk] = {'data':data, 'ref':vv['ref'], + 'shape':shape, **dparams} + self._ddata['dict'].append(kk) def _complement_dgrouprefdata(self): # -------------- # ddata - assert len(self.__lkdata) == len(self._ddata.keys()) - for k0, v0 in self._ddata.items(): + assert len(self._data['lkey']) == len(self._ddata['dict'].keys()) + for k0 in self._ddata['lkey']: + v0 = self._ddata['dict'][k0] # Check all ref are in dref - lrefout = [ii for ii in v0['ref'] if ii not in self._dref.keys()] + lrefout = [ii for ii in v0['ref'] if ii not in self._dref['lkey']] if len(lrefout) != 0: msg = "ddata[%s]['ref'] has keys not in dref:\n"%k0 msg += " - " + "\n - ".join(lrefout) raise Exception(msg) # set group - groups = (self._dref[rr]['group'] for rr in v0['ref']) - assert all([gg in self._dgroup.keys() for gg in groups]) - self._ddata[k0]['group'] = groups + groups = (self._dref['dict'][rr]['group'] for rr in v0['ref']) + assert all([gg in self._dgroup['lkey'] for gg in groups]) + self._ddata['dict'][k0]['group'] = groups # -------------- # dref - for k0 in self._dref.keys(): - self._dref[k0]['ldata'] = [kk for kk, vv in self._ddata.items() - if k0 in vv['ref']] - assert self._dref[k0]['group'] in self._dgroup.keys() + for k0 in self._dref['lkey']: + ldata = [kk for kk in self._ddata['lkey'] + if k0 in self._ddata['dict'][kk]['ref']] + self._dref['dict'][k0]['ldata'] = ldata + assert self._dref['dict'][k0]['group'] in self._dgroup['lkey'] # -------------- # dgroup - for gg, vg in self._dgroup.items(): - lref = [rr for rr, vv in self._dref.items() - if vv['group'] == gg] - ldata = [dd for dd in self._ddata.keys() - if any([dd in self._dref[vref]['ldata'] + for gg in self._dgroup['lkey']: + vg = self._dgroup['dict'][gg] + lref = [rr for rr in self._dref['lkey'] + if self._dref['dict'][rr]['group'] == gg] + ldata = [dd for dd in self._ddata['lkey'] + if any([dd in self._dref['dict'][vref]['ldata'] for vref in lref])] #assert vg['depend'] in lidindref - self._dgroup[gg]['lref'] = lref - self._dgroup[gg]['ldata'] = ldata + self._dgroup['dict'][gg]['lref'] = lref + self._dgroup['dict'][gg]['ldata'] = ldata + + # -------------- + # params + lparam = self._ddata['lparam'] + for kk in self._dddata['lkey']: + for pp in self._ddata['dict'][kk].keys(): + if pp not in self._reserved_all and pp not in lparam: + lparam.append(pp) + for kk in self._ddata['lkey']: + for pp in lparam: + if pp not in self._ddata['dict'][kk].keys(): + self._ddata[kk][pp] = None + self._ddata['lparam'] = lparam ########### @@ -331,17 +392,17 @@ def _complement_dgrouprefdata(self): @staticmethod def _get_keys_dgroup(): - lk = ['lref', 'ldata'] + lk = [('lkey', []), ('dict', {})] return lk @staticmethod def _get_keys_dref(): - lk = ['group', 'size', 'ldata'] + lk = [('lkey', []), ('dict', {})] return lk @staticmethod def _get_keys_ddata(): - lk = ['data', 'ref', 'shape', 'group', 'index'] + lk = [('lkey', []), ('dict', {}), ('lparam', [])] return lk ########### @@ -350,8 +411,6 @@ def _get_keys_ddata(): def _init(self, dref=None, ddata=None, **kwargs): kwdargs = {'dref':dref, 'ddata':ddata, **kwargs} - self.__lkdata = [] - self.__lparams = set() largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) self._set_dref(**kwddref, complement=False) @@ -384,12 +443,14 @@ def add_ref(self, key, data=None, group=None, **kwdargs): def remove_ref(self, key): """ Remove a reference (all data depending on it are removed too) """ - assert key in self._dref.keys() - lkdata = [] - del self._dref[key] + assert key in self._dref['lkey'] + lkdata = self._dref['dict'][key]['ldata'] + del self._dref['dict'][key] + self._dref['lkey'].remove(key) for kk in lkdata: - del self.__ddata[kk] - self.__lkdata.remove(kk) + if self._ddata['dict'][kk]['refs'] == (key,): + del self._ddata['dict'][kk] + self._ddata['lkey'].remove(kk) self._complement_dgrouprefdata() def add_data(self, key, data=None, ref=None, **kwdargs): @@ -405,16 +466,17 @@ def remove_data(self, key, propagate=True): if key in self._dref.keys(): self.remove_ref(key) else: - assert key in self._ddata.keys() + assert key in self._ddata['dict'].keys() if propagate: # Check if associated ref shall be removed too - lref = self._ddata[key]['ref'] + lref = self._ddata['dict'][key]['refs'] for kref in lref: # Remove if key was the only associated data - if self._dref[kref]['ldata'] == [key]: + if self._dref['dict'][kref]['ldata'] == [key]: self.remove_ref(kref) - del self._ddata[key] - self.__lkdata.remove(key) + del self._ddata['dict'][key] + self._ddata['lkey'].remove(key) + self._lkdata.remove(key) self._complement_dgrouprefdata() @@ -473,41 +535,52 @@ def dconfig(self): @property def dgroup(self): """ The dict of groups """ - return self._dgroup + return self._dgroup['dict'] + @property + def lgroup(self): + """ The dict of groups """ + return self._dgroup['lkey'] @property def dref(self): """ the dict of references """ - return self._dref + return self._dref['dict'] + @property + def lref(self): + """ the dict of references """ + return self._dref['lkey'] @property def ddata(self): """ the dict of data """ - return self._ddata - def lkdata(self): - """ The list of current data keys """ - return self.__lkdata + return self._ddata['dict'] + @property + def ldata(self): + """ the dict of data """ + return self._ddata['lkey'] + @property def lparam(self): - """ The list of current parameters """ - return self.__lparams - + """ the dict of data """ + return self._ddata['lparam'] #--------------------- # Add / remove params #--------------------- + # UP TO HERE + def get_param(self, param=None, returnas=np.ndarray): # Check inputs and trivial cases if param is None: return - assert param in self.__lparam + assert param in self._lparam assert returnas in [np.ndarray, dict, list] # Get output if returnas == dict: - out = {kk:self._ddata[kk][param] for kk in self.__lkdata} + out = {kk:self._ddata[kk][param] for kk in self._lkdata} else: - out = [self._ddata[kk][param] for kk in self.__lkdata] + out = [self._ddata[kk][param] for kk in self._lkdata] if returnas == np.ndarray: try: out = np.asarray(out) @@ -521,7 +594,7 @@ def set_param(self, param=None, values=None, ind=None, key=None): # Check and format input if param is None: return - assert param in self.__lparam + assert param in self._lparam # Update all keys with common value ltypes = [str, int, np.int, float, np.float, tuple] @@ -536,7 +609,7 @@ def set_param(self, param=None, values=None, ind=None, key=None): if lc0: key = self._ind_tofrom_key(ind=ind, key=key, out='key') for kk in key: - self._ddata[kk]][param] = values + self._ddata[kk][param] = values # Update relevant keys with corresponding values else: @@ -546,19 +619,19 @@ def set_param(self, param=None, values=None, ind=None, key=None): self._ddata[key[ii]][param] = values[ii] def add_param(self, param, values=None): - assert isinstance(param) str - assert param not in self.__lparams - self.__lparams.append(param) + assert isinstance(param, str) + assert param not in self._lparam + self._lparam.append(param) self.set_param(param=param, values=values) def remove_param(self, param=None): # Check and format input if param is None: return - assert param in self.__lparam + assert param in self._lparam - self.__lparams.remove(param) - for kk in self.__lkdata: + self._lparam.remove(param) + for kk in self._lkdata: del self._ddata[kk][param] @@ -628,7 +701,7 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): assert np.sum(lc) <= 1 # Initialize output - out = np.zeros((len(self.__lkdata),), dtype=bool) + out = np.zeros((len(self._lkdata),), dtype=bool) # Test if lc[0]: @@ -638,7 +711,7 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): if returnas in [int, 'key']: out = out.nonzero()[0] if returnas == 'key': - out = [self.__lkdata[ii] for ii in out] + out = [self._lkdata[ii] for ii in out] elif lc[1]: if isinstance(key, str): @@ -647,16 +720,16 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): out = key else: for kk in key: - out[self.__lkdata.index(kk)] = True + out[self._lkdata.index(kk)] = True if returnas == int: out = out.nonzero()[0] else: if returnas == bool: out[:] = True elif returnas == int: - out = np.arange(0, len(self.__lkdata)) + out = np.arange(0, len(self._lkdata)) else: - out = self.__lkdata + out = self._lkdata return out @@ -675,7 +748,7 @@ def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', # Build for groups col0 = ['group name', 'nb. ref', 'nb. data'] ar0 = [(k0, len(v0['lref']), len(v0['ldata'])) - for k0,v0 in self._dgroup.items()] + for k0, v0 in self._dgroup.items()] # ----------------------- # Build for refs diff --git a/tofu/version.py b/tofu/version.py index e1959ac44..58c47829b 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-123-g08a421d' +__version__ = '1.4.1-124-gc17094a' From 016079730d9743f3d5ceea9b3cf0f969e2faf12f Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Mon, 7 Oct 2019 22:52:52 +0200 Subject: [PATCH 09/23] [Issue208] DataHolder operational up to get_summary() and test add / remove ref, data, param --- tofu/data/_core_new.py | 119 +++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 068b946f8..5eed27d18 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -63,7 +63,7 @@ class DataHolder(utils.ToFuObject): """ # Fixed (class-wise) dictionary of default properties _ddef = {'Id':{'include':['Mod', 'Cls', - 'Name', 'version']}i, + 'Name', 'version']}, 'dgroup':['lref'], 'dref': ['group', 'size', 'ldata'], 'ddata': ['refs', 'shape', 'groups', 'data'], @@ -71,7 +71,7 @@ class DataHolder(utils.ToFuObject): 'dim': (str, 'unknown'), 'quant': (str, 'unknown'), 'name': (str, 'unknown'), - 'units': (str, 'a.u.')} + 'units': (str, 'a.u.')}} _reserved_all = _ddef['dgroup'] + _ddef['dref'] + _ddef['ddata'] _show_in_summary = 'all' @@ -149,16 +149,9 @@ def _get_largs_ddata(): #--------------------- def _extract_known_params(self, key, dd): - # Check no reserved key is used - lkind = [kk in dd.keys() for kk in self._reserved_all] - if any(lkind): - msg = "The following keys are reserved for internal use:\n" - msg += " %s\n"%str(self._reserved_all) - msg += " => Please do not use them !" - raise Exception(msg) - - # Ectract relevant parameters - dparams = dict(dd) + # Extract relevant parameters + dparams = {kk:vv for kk, vv in dd.items() + if kk not in self._reserved_all} # Add minimum default parameters if not already included for kk, vv in self._ddef['params'].items(): @@ -169,7 +162,7 @@ def _extract_known_params(self, key, dd): if not isinstance(dparams[kk], vv[0]): msg = "A parameter for %s has the wrong type:\n"%key msg += " - Provided: type(%s) = %s\n"%(kk, str(type(vv))) - msg += " - Expected %s"%str(self._dparams[kk][0]) + msg += " - Expected %s"%str(self._ddef['params'][kk][0]) raise Exception(msg) return dparams @@ -177,7 +170,7 @@ def _extract_known_params(self, key, dd): def _checkformat_dref(self, dref): c0 = isinstance(dref, dict) c0 = c0 and all([isinstance(kk, str) and isinstance(vv, dict) - for kk, vv in dref.keys()]) + for kk, vv in dref.items()]) if not c0: msg = "Provided dref must be dict !\n" msg += "All its keys must be str !\n" @@ -188,7 +181,8 @@ def _checkformat_dref(self, dref): # (A) - {'group0':{'t0':{'data':t0, 'units':'s'}, 't1':...}} # (B) - {'t0':{'data':t0, 'units':'s', 'group':'group0'}, 't1':...} - cA = all([all([isinstance(v1, dict) and 'group' not in v1.keys() + cA = all([all([(isinstance(v1, dict) and 'group' not in v1.keys()) + or not isinstance(v1, dict) for v1 in v0.values()]) and 'group' not in v0.keys() for v0 in dref.values()]) cB = all([isinstance(v0.get('group', None), str) for v0 in dref.values()]) @@ -209,8 +203,11 @@ def _checkformat_dref(self, dref): drbis = {} for k0, v0 in dref.items(): for k1, v1 in v0.items(): - drbis[k1] = v1 - drbis['group'] = k0 + if isinstance(v1, dict): + drbis[k1] = v1 + drbis['group'] = k0 + else: + drbis[k1] = {'data':v1, 'group':k0} dref = drbis # Check cB @@ -219,6 +216,7 @@ def _checkformat_dref(self, dref): # Check if new group if vv['group'] not in self._dgroup['lkey']: self._dgroup['dict'][vv['group']] = {} + self._dgroup['lkey'].append(vv['group']) # Check key unicity if kk in self._ddata['lkey']: @@ -251,12 +249,13 @@ def _checkformat_dref(self, dref): # Fill self._dref self._dref['dict'][kk] = {'size':size, 'group':vv['group']} + self._dref['lkey'].append(kk) # Extract and check parameters dparams = self._extract_known_params(kk, vv) # Fill self._ddata - self._ddata['dict'][kk] = {'data':data, 'ref':(kk,), + self._ddata['dict'][kk] = {'data':data, 'refs':(kk,), 'shape':(size,), **dparams} self._ddata['lkey'].append(kk) @@ -309,14 +308,14 @@ def _checkformat_ddata(self, ddata): shape = data.shape # Check proper ref (existence and shape / size) - for ii, rr in enumerate(vv['ref']): + for ii, rr in enumerate(vv['refs']): if rr not in self._dref['lkey']: msg = "ddata[%s] depends on an unknown ref !\n"%kk - msg += " - ddata[%s]['ref'] = %s\n"%(kk, rr) + msg += " - ddata[%s]['refs'] = %s\n"%(kk, rr) msg += " => %s not in self.dref !\n"%rr msg += " => self.add_ref( %s ) first !"%rr raise Exception(msg) - shaperef = tuple(self._dref[rr]['size'] for rr in vv['ref']) + shaperef = tuple(self._dref['dict'][rr]['size'] for rr in vv['refs']) if not shape == shaperef: msg = "Inconsistency between data shape and ref size !\n" msg += " - ddata[%s]['data'] shape: %s\n"%(kk, str(shape)) @@ -325,36 +324,43 @@ def _checkformat_ddata(self, ddata): # Extract params and set self._ddata dparams = self._extract_known_params(kk, vv) - self._ddata['dict'][kk] = {'data':data, 'ref':vv['ref'], + self._ddata['dict'][kk] = {'data':data, 'refs':vv['refs'], 'shape':shape, **dparams} - self._ddata['dict'].append(kk) + self._ddata['lkey'].append(kk) def _complement_dgrouprefdata(self): # -------------- # ddata - assert len(self._data['lkey']) == len(self._ddata['dict'].keys()) + assert len(self._ddata['lkey']) == len(self._ddata['dict'].keys()) for k0 in self._ddata['lkey']: v0 = self._ddata['dict'][k0] # Check all ref are in dref - lrefout = [ii for ii in v0['ref'] if ii not in self._dref['lkey']] + lrefout = [ii for ii in v0['refs'] if ii not in self._dref['lkey']] if len(lrefout) != 0: - msg = "ddata[%s]['ref'] has keys not in dref:\n"%k0 + msg = "ddata[%s]['refs'] has keys not in dref:\n"%k0 msg += " - " + "\n - ".join(lrefout) raise Exception(msg) # set group - groups = (self._dref['dict'][rr]['group'] for rr in v0['ref']) - assert all([gg in self._dgroup['lkey'] for gg in groups]) + groups = tuple(self._dref['dict'][rr]['group'] for rr in v0['refs']) + gout = [gg for gg in groups if gg not in self._dgroup['lkey']] + if len(gout) > 0: + lg = self._dgroup['lkey'] + msg = "Inconsistent groups from self.ddata[%s]['refs']:\n"%k0 + msg += " - groups = %s\n"%str(groups) + msg += " - self._dgroup['lkey'] = %s\n"%str(lg) + msg += " - self.dgroup.keys() = %s"%str(self.dgroup.keys()) + raise Exception(msg) self._ddata['dict'][k0]['group'] = groups # -------------- # dref for k0 in self._dref['lkey']: ldata = [kk for kk in self._ddata['lkey'] - if k0 in self._ddata['dict'][kk]['ref']] + if k0 in self._ddata['dict'][kk]['refs']] self._dref['dict'][k0]['ldata'] = ldata assert self._dref['dict'][k0]['group'] in self._dgroup['lkey'] @@ -374,7 +380,7 @@ def _complement_dgrouprefdata(self): # -------------- # params lparam = self._ddata['lparam'] - for kk in self._dddata['lkey']: + for kk in self._ddata['lkey']: for pp in self._ddata['dict'][kk].keys(): if pp not in self._reserved_all and pp not in lparam: lparam.append(pp) @@ -573,14 +579,16 @@ def get_param(self, param=None, returnas=np.ndarray): # Check inputs and trivial cases if param is None: return - assert param in self._lparam + assert param in self._ddata['lparam'] assert returnas in [np.ndarray, dict, list] # Get output if returnas == dict: - out = {kk:self._ddata[kk][param] for kk in self._lkdata} + out = {kk:self._ddata['dict'][kk][param] + for kk in self._ddata['lkey']} else: - out = [self._ddata[kk][param] for kk in self._lkdata] + out = [self._ddata['dict'][kk][param] + for kk in self._ddata['lkey']] if returnas == np.ndarray: try: out = np.asarray(out) @@ -594,7 +602,7 @@ def set_param(self, param=None, values=None, ind=None, key=None): # Check and format input if param is None: return - assert param in self._lparam + assert param in self._ddata['lparam'] # Update all keys with common value ltypes = [str, int, np.int, float, np.float, tuple] @@ -609,30 +617,30 @@ def set_param(self, param=None, values=None, ind=None, key=None): if lc0: key = self._ind_tofrom_key(ind=ind, key=key, out='key') for kk in key: - self._ddata[kk][param] = values + self._ddata['dict'][kk][param] = values # Update relevant keys with corresponding values else: key = self._ind_tofrom_key(ind=ind, key=key, out='key') assert len(key) == len(values) for kk in range(len(key)): - self._ddata[key[ii]][param] = values[ii] + self._ddata['dict'][key[ii]][param] = values[ii] def add_param(self, param, values=None): assert isinstance(param, str) - assert param not in self._lparam - self._lparam.append(param) + assert param not in self._ddata['lparam'] + self._ddata['lparam'].append(param) self.set_param(param=param, values=values) def remove_param(self, param=None): # Check and format input if param is None: return - assert param in self._lparam + assert param in self._ddata['lparam'] - self._lparam.remove(param) - for kk in self._lkdata: - del self._ddata[kk][param] + self._ddata['lparam'].remove(param) + for kk in self._ddata['lkey']: + del self._ddata['dict'][kk][param] #--------------------- @@ -665,14 +673,14 @@ def select(self, group=None, ref=None, log='all', return_key=True, ncrit = len(lcrit) # Prepare array of bool indices and populate - ind = np.ones((ncrit, len(self._ddata)), dtype=bool) + ind = np.ones((ncrit, len(self._ddata['lkey'])), dtype=bool) for ii in range(ncrit): critval = eval(lcrit[ii]) try: par = self.get_param(lcrit[ii], returnas=np.ndarray) ind[ii,:] = par == critval except: - ind[ii,:] = [self._ddata[kk][param] == critval + ind[ii,:] = [self._ddata['dict'][kk][param] == critval for kk in self.__lkata] # Format output ind @@ -701,7 +709,7 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): assert np.sum(lc) <= 1 # Initialize output - out = np.zeros((len(self._lkdata),), dtype=bool) + out = np.zeros((len(self._ddata['lkey']),), dtype=bool) # Test if lc[0]: @@ -711,7 +719,7 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): if returnas in [int, 'key']: out = out.nonzero()[0] if returnas == 'key': - out = [self._lkdata[ii] for ii in out] + out = [self._ddata['lkey'][ii] for ii in out] elif lc[1]: if isinstance(key, str): @@ -720,16 +728,16 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): out = key else: for kk in key: - out[self._lkdata.index(kk)] = True + out[self._ddata['lkey'].index(kk)] = True if returnas == int: out = out.nonzero()[0] else: if returnas == bool: out[:] = True elif returnas == int: - out = np.arange(0, len(self._lkdata)) + out = np.arange(0, len(self._ddata['lkey'])) else: - out = self._lkdata + out = self._ddata['lkey'] return out @@ -747,14 +755,19 @@ def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', # ----------------------- # Build for groups col0 = ['group name', 'nb. ref', 'nb. data'] - ar0 = [(k0, len(v0['lref']), len(v0['ldata'])) - for k0, v0 in self._dgroup.items()] + ar0 = [(k0, + len(self._dgroup['dict'][k0]['lref']), + len(self._dgroup['dict'][k0]['ldata'])) + for k0 in self._dgroup['lkey']] # ----------------------- # Build for refs col1 = ['ref key', 'group', 'size', 'nb. data'] - ar1 = [(k0, v0['group'], v0['size'], len(v0['ldata'])) - for k0,v0 in self._dref.items()] + ar1 = [(k0, + self._dref['dict'][k0]['group'], + self._dref['dict'][k0]['size'], + len(self._dref['dict'][k0]['ldata'])) + for k0,v0 in self._dref['lkey']] # ----------------------- # Build for ddata From 72535dcc2d1c07785eff65f18a540f525eb96f16 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 07:50:12 +0200 Subject: [PATCH 10/23] [Issue209] Debugged, there was an wrong interp_t was None and not set --- tofu/data/_core.py | 7 +++++-- tofu/version.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tofu/data/_core.py b/tofu/data/_core.py index 4829cf64e..849cc94d6 100644 --- a/tofu/data/_core.py +++ b/tofu/data/_core.py @@ -3607,9 +3607,12 @@ def _get_tcom(self, idquant=None, idref1d=None, def _get_finterp(self, idquant=None, idref1d=None, idref2d=None, idq2dR=None, idq2dPhi=None, idq2dZ=None, - interp_t='nearest', interp_space=None, + interp_t=None, interp_space=None, fill_value=np.nan, ani=False, Type=None): + if interp_t is None: + interp_t = 'nearest' + # Get idmesh if idquant is not None: if idref1d is None: @@ -3749,7 +3752,7 @@ def interp_pts2profile(self, pts=None, vect=None, t=None, """ Return the value of the desired profiles_1d quantity For the desired inputs points (pts): - - pts are in (R,Z) coordinates + - pts are in (X,Y,Z) coordinates - space interpolation is linear on the 1d profiles At the desired input times (t): - using a nearest-neighbourg approach for time diff --git a/tofu/version.py b/tofu/version.py index 58c47829b..2e5ddc958 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-124-gc17094a' +__version__ = '1.4.1-126-g0160797' From 45756c70a3f2d9bbff1a70f197072d9817acbb5e Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:03:17 +0200 Subject: [PATCH 11/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 1 --- tofu/data/_core_new.py | 44 ++++++++++++++++++------------------------ tofu/version.py | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 5eed27d18..01c0e0820 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -35,15 +35,12 @@ from . import _def as _def from . import _physics as _physics -__all__ = ['DataHolder']#, 'Plasma0D'] +__all__ = ['DataHolder'] # , 'Plasma0D'] _SAVEPATH = os.path.abspath('./') - - _INTERPT = 'zero' - ############################################# ############################################# # Abstract Parent class @@ -51,7 +48,6 @@ ############################################# - class DataHolder(utils.ToFuObject): """ A generic class for handling data @@ -62,30 +58,28 @@ class DataHolder(utils.ToFuObject): """ # Fixed (class-wise) dictionary of default properties - _ddef = {'Id':{'include':['Mod', 'Cls', - 'Name', 'version']}, - 'dgroup':['lref'], - 'dref': ['group', 'size', 'ldata'], - 'ddata': ['refs', 'shape', 'groups', 'data'], - 'params':{'origin':(str, 'unknown'), - 'dim': (str, 'unknown'), - 'quant': (str, 'unknown'), - 'name': (str, 'unknown'), - 'units': (str, 'a.u.')}} + _ddef = {'Id': {'include': ['Mod', 'Cls', + 'Name', 'version']}, + 'dgroup': ['lref'], + 'dref': ['group', 'size', 'ldata'], + 'ddata': ['refs', 'shape', 'groups', 'data'], + 'params': {'origin':(str, 'unknown'), + 'dim': (str, 'unknown'), + 'quant': (str, 'unknown'), + 'name': (str, 'unknown'), + 'units': (str, 'a.u.')}} _reserved_all = _ddef['dgroup'] + _ddef['dref'] + _ddef['ddata'] _show_in_summary = 'all' - - # Does not exist before Python 3.6 !!! def __init_subclass__(cls, **kwdargs): + # Does not exist before Python 3.6 !!! # Python 2 super(DataHolder, cls).__init_subclass__(**kwdargs) # Python 3 - #super().__init_subclass__(**kwdargs) + # super().__init_subclass__(**kwdargs) cls._ddef = copy.deepcopy(DataHolder._ddef) - #cls._dplot = copy.deepcopy(Struct._dplot) - #cls._set_color_ddef(cls._color) - + # cls._dplot = copy.deepcopy(Struct._dplot) + # cls._set_color_ddef(cls._color) def __init__(self, dref=None, ddata=None, Id=None, Name=None, @@ -98,7 +92,7 @@ def __init__(self, dref=None, ddata=None, self.__class__._strip_init() # Create a dplot at instance level - #self._dplot = copy.deepcopy(self.__class__._dplot) + # self._dplot = copy.deepcopy(self.__class__._dplot) kwdargs = locals() del kwdargs['self'] @@ -109,9 +103,9 @@ def _reset(self): # Run by the parent class __init__() # super() super(DataHolder, self)._reset() - self._dgroup = {kd[0]:kd[1] for kd in self._get_keys_dgroup()} - self._dref = {kd[0]:kd[1] for kd in self._get_keys_dref()} - self._ddata = {kd[0]:kd[1] for kd in self._get_keys_ddata()} + self._dgroup = {kd[0]: kd[1] for kd in self._get_keys_dgroup()} + self._dref = {kd[0]: kd[1] for kd in self._get_keys_dref()} + self._ddata = {kd[0]: kd[1] for kd in self._get_keys_ddata()} @classmethod def _checkformat_inputs_Id(cls, Id=None, Name=None, diff --git a/tofu/version.py b/tofu/version.py index 2e5ddc958..4d16afa94 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-126-g0160797' +__version__ = '1.4.1-127-g72535dc' From d22925756e67a4dccfe6a48d3a5577bdb40f04f8 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:18:00 +0200 Subject: [PATCH 12/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 2 --- tofu/data/_core_new.py | 70 +++++++++++++++++++++--------------------- tofu/version.py | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 01c0e0820..ca1c51e6c 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -63,11 +63,11 @@ class DataHolder(utils.ToFuObject): 'dgroup': ['lref'], 'dref': ['group', 'size', 'ldata'], 'ddata': ['refs', 'shape', 'groups', 'data'], - 'params': {'origin':(str, 'unknown'), - 'dim': (str, 'unknown'), - 'quant': (str, 'unknown'), - 'name': (str, 'unknown'), - 'units': (str, 'a.u.')}} + 'params': {'origin': (str, 'unknown'), + 'dim': (str, 'unknown'), + 'quant': (str, 'unknown'), + 'name': (str, 'unknown'), + 'units': (str, 'a.u.')}} _reserved_all = _ddef['dgroup'] + _ddef['dref'] + _ddef['ddata'] _show_in_summary = 'all' @@ -104,8 +104,8 @@ def _reset(self): # super() super(DataHolder, self)._reset() self._dgroup = {kd[0]: kd[1] for kd in self._get_keys_dgroup()} - self._dref = {kd[0]: kd[1] for kd in self._get_keys_dref()} - self._ddata = {kd[0]: kd[1] for kd in self._get_keys_ddata()} + self._dref = {kd[0]: kd[1] for kd in self._get_keys_dref()} + self._ddata = {kd[0]: kd[1] for kd in self._get_keys_ddata()} @classmethod def _checkformat_inputs_Id(cls, Id=None, Name=None, @@ -116,7 +116,7 @@ def _checkformat_inputs_Id(cls, Id=None, Name=None, assert isinstance(Name, str), Name if include is None: include = cls._ddef['Id']['include'] - kwdargs.update({'Name':Name, 'include':include}) + kwdargs.update({'Name': Name, 'include': include}) return kwdargs ########### @@ -133,18 +133,17 @@ def _get_largs_ddata(): largs = ['ddata'] return largs - ########### # Get check and format inputs ########### - #--------------------- + # --------------------- # Methods for checking and formatting inputs - #--------------------- + # --------------------- def _extract_known_params(self, key, dd): # Extract relevant parameters - dparams = {kk:vv for kk, vv in dd.items() + dparams = {kk: vv for kk, vv in dd.items() if kk not in self._reserved_all} # Add minimum default parameters if not already included @@ -154,13 +153,13 @@ def _extract_known_params(self, key, dd): else: # Check type if already included if not isinstance(dparams[kk], vv[0]): + vtyp = str(type(vv[0])) msg = "A parameter for %s has the wrong type:\n"%key - msg += " - Provided: type(%s) = %s\n"%(kk, str(type(vv))) + msg += " - Provided: type(%s) = %s\n"%(kk, vtyp) msg += " - Expected %s"%str(self._ddef['params'][kk][0]) raise Exception(msg) return dparams - def _checkformat_dref(self, dref): c0 = isinstance(dref, dict) c0 = c0 and all([isinstance(kk, str) and isinstance(vv, dict) @@ -179,17 +178,18 @@ def _checkformat_dref(self, dref): or not isinstance(v1, dict) for v1 in v0.values()]) and 'group' not in v0.keys() for v0 in dref.values()]) - cB = all([isinstance(v0.get('group', None), str) for v0 in dref.values()]) + cB = all([isinstance(v0.get('group', None), str) + for v0 in dref.values()]) if not (cA or cB): - msg = "Provided dref must formatted either as:\n\n" - msg += " - a dict of group keys with a dict of key ref:\n" - msg += " {'group0':{'t0':{'data':t0, 'units':'s'},\n" + msg = "Provided dref must formatted either as a dict with:\n\n" + msg += " - keys = group, values = {ref: data}:\n" + msg += " {'g0':{'t0':{'data':t0, 'units':'s'},\n" msg += " 't1':{'data':t1, 'units':'h'}},\n" - msg += " 'group1':{'t2':{'data':t2, 'units':'min'}}}\n\n" - msg += " - a dict of key ref with a dict containing the group:\n" - msg += " {'t0':{'data':t0, 'units':'s', 'group':'group0'},\n" - msg += " 't1':{'data':t1, 'units':'h', 'group':'group0'},\n" - msg += " 't2':{'data':t2, 'units':'min', 'group':'group1'}" + msg += " 'g1':{'t2':{'data':t2, 'units':'min'}}}\n\n" + msg += " - keys = ref, values = {data, group}:\n" + msg += " {'t0':{'data':t0, 'units':'s', 'group':'g0'},\n" + msg += " 't1':{'data':t1, 'units':'h', 'group':'g0'},\n" + msg += " 't2':{'data':t2, 'units':'min', 'group':'g1'}" raise Exception(msg) if cA: @@ -201,7 +201,7 @@ def _checkformat_dref(self, dref): drbis[k1] = v1 drbis['group'] = k0 else: - drbis[k1] = {'data':v1, 'group':k0} + drbis[k1] = {'data': v1, 'group': k0} dref = drbis # Check cB @@ -226,7 +226,7 @@ def _checkformat_dref(self, dref): try: data = np.atleast_1d(data).ravel() size = data.size - except: + except Exception as err: c0 = False else: size = data.__class__.__name__ @@ -236,21 +236,21 @@ def _checkformat_dref(self, dref): size = data.size if not c0: - msg = "Each dict in dref must hold an array-convertibe 'data'\n" + msg = "dref[%s]['data'] must be array-convertible\n"%kk msg += "The following array conversion failed:\n" msg += " - np.atleast_1d(dref[%s]['data']).ravel()"%kk raise Exception(msg) # Fill self._dref - self._dref['dict'][kk] = {'size':size, 'group':vv['group']} + self._dref['dict'][kk] = {'size': size, 'group': vv['group']} self._dref['lkey'].append(kk) # Extract and check parameters dparams = self._extract_known_params(kk, vv) # Fill self._ddata - self._ddata['dict'][kk] = {'data':data, 'refs':(kk,), - 'shape':(size,), **dparams} + self._ddata['dict'][kk] = {'data': data, 'refs': (kk,), + 'shape': (size,), **dparams} self._ddata['lkey'].append(kk) # ------------- DB (start) @@ -260,7 +260,7 @@ def __repr__(self): def _checkformat_ddata(self, ddata): c0 = isinstance(ddata, dict) - c0 = c0 and all([isinstance(kk, str) for kk in ddata.keys()]) + c0 = c0 and all([isinstance(kk, str) for kk in ddata.keys()]) if not c0: msg = "Provided ddata must be dict !\n" msg += "All its keys must be str !" @@ -275,7 +275,7 @@ def _checkformat_ddata(self, ddata): c0 = c0 and 'data' in vv.keys() if not c0: msg = "ddata must contain dict with at least the keys:\n" - msg += " - 'ref': a str indicating the ref(s) dependencies\n" + msg += " - 'refs': a tuple indicating refs dependencies\n" msg += " - 'data': a 1d array containing the data" raise Exception(msg) @@ -292,7 +292,7 @@ def _checkformat_ddata(self, ddata): try: data = np.asarray(data) shape = data.shape - except: + except Exception as err: assert type(data) in [list, tuple] shape = (len(data),) else: @@ -309,11 +309,11 @@ def _checkformat_ddata(self, ddata): msg += " => %s not in self.dref !\n"%rr msg += " => self.add_ref( %s ) first !"%rr raise Exception(msg) - shaperef = tuple(self._dref['dict'][rr]['size'] for rr in vv['refs']) - if not shape == shaperef: + shapref = tuple(self._dref['dict'][rr]['size'] for rr in vv['refs']) + if not shape == shapref: msg = "Inconsistency between data shape and ref size !\n" msg += " - ddata[%s]['data'] shape: %s\n"%(kk, str(shape)) - msg += " - sizes of refs: %s"%(str(shaperef)) + msg += " - sizes of refs: %s"%(str(shapref)) raise Exception(msg) # Extract params and set self._ddata diff --git a/tofu/version.py b/tofu/version.py index 4d16afa94..3ddaad3d0 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-127-g72535dc' +__version__ = '1.4.1-128-g45756c7' From cd3d89506aa945c449297345bd0b4da6b2cffff0 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:32:31 +0200 Subject: [PATCH 13/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 3 --- tofu/data/_core_new.py | 111 ++++++++++++++++++++--------------------- tofu/version.py | 2 +- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index ca1c51e6c..9af2618d4 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -309,20 +309,19 @@ def _checkformat_ddata(self, ddata): msg += " => %s not in self.dref !\n"%rr msg += " => self.add_ref( %s ) first !"%rr raise Exception(msg) - shapref = tuple(self._dref['dict'][rr]['size'] for rr in vv['refs']) - if not shape == shapref: + shaprf = tuple(self._dref['dict'][rr]['size'] for rr in vv['refs']) + if not shape == shaprf: msg = "Inconsistency between data shape and ref size !\n" msg += " - ddata[%s]['data'] shape: %s\n"%(kk, str(shape)) - msg += " - sizes of refs: %s"%(str(shapref)) + msg += " - sizes of refs: %s"%(str(shaprf)) raise Exception(msg) # Extract params and set self._ddata dparams = self._extract_known_params(kk, vv) - self._ddata['dict'][kk] = {'data':data, 'refs':vv['refs'], - 'shape':shape, **dparams} + self._ddata['dict'][kk] = {'data': data, 'refs': vv['refs'], + 'shape': shape, **dparams} self._ddata['lkey'].append(kk) - def _complement_dgrouprefdata(self): # -------------- @@ -339,16 +338,16 @@ def _complement_dgrouprefdata(self): raise Exception(msg) # set group - groups = tuple(self._dref['dict'][rr]['group'] for rr in v0['refs']) - gout = [gg for gg in groups if gg not in self._dgroup['lkey']] + grps = tuple(self._dref['dict'][rr]['group'] for rr in v0['refs']) + gout = [gg for gg in grps if gg not in self._dgroup['lkey']] if len(gout) > 0: lg = self._dgroup['lkey'] - msg = "Inconsistent groups from self.ddata[%s]['refs']:\n"%k0 - msg += " - groups = %s\n"%str(groups) + msg = "Inconsistent grps from self.ddata[%s]['refs']:\n"%k0 + msg += " - grps = %s\n"%str(grps) msg += " - self._dgroup['lkey'] = %s\n"%str(lg) msg += " - self.dgroup.keys() = %s"%str(self.dgroup.keys()) raise Exception(msg) - self._ddata['dict'][k0]['group'] = groups + self._ddata['dict'][k0]['group'] = grps # -------------- # dref @@ -367,7 +366,7 @@ def _complement_dgrouprefdata(self): ldata = [dd for dd in self._ddata['lkey'] if any([dd in self._dref['dict'][vref]['ldata'] for vref in lref])] - #assert vg['depend'] in lidindref + # assert vg['depend'] in lidindref self._dgroup['dict'][gg]['lref'] = lref self._dgroup['dict'][gg]['ldata'] = ldata @@ -385,7 +384,6 @@ def _complement_dgrouprefdata(self): self._ddata[kk][pp] = None self._ddata['lparam'] = lparam - ########### # Get keys of dictionnaries ########### @@ -410,7 +408,7 @@ def _get_keys_ddata(): ########### def _init(self, dref=None, ddata=None, **kwargs): - kwdargs = {'dref':dref, 'ddata':ddata, **kwargs} + kwdargs = {'dref': dref, 'ddata': ddata, **kwargs} largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) self._set_dref(**kwddref, complement=False) @@ -419,7 +417,6 @@ def _init(self, dref=None, ddata=None, **kwargs): self._set_ddata(**kwddata) self._dstrip['strip'] = 0 - ########### # set dictionaries ########### @@ -433,13 +430,13 @@ def _set_ddata(self, ddata): self._checkformat_ddata(ddata) self._complement_dgrouprefdata() - #--------------------- + # --------------------- # Methods for adding ref / quantities - #--------------------- + # --------------------- def add_ref(self, key, data=None, group=None, **kwdargs): """ Add a reference """ - self._set_dref({key:{'data':data, 'group':group, **kwdargs}}) + self._set_dref({key:{'data': data, 'group': group, **kwdargs}}) def remove_ref(self, key): """ Remove a reference (all data depending on it are removed too) """ @@ -455,7 +452,7 @@ def remove_ref(self, key): def add_data(self, key, data=None, ref=None, **kwdargs): """ Add a data (all associated ref must be added first)) """ - self._set_ddata({key: {'data':data, 'ref':ref, **kwdargs}}) + self._set_ddata({key: {'data': data, 'ref': ref, **kwdargs}}) def remove_data(self, key, propagate=True): """ Remove a data @@ -479,7 +476,6 @@ def remove_data(self, key, propagate=True): self._lkdata.remove(key) self._complement_dgrouprefdata() - ########### # strip dictionaries ########### @@ -512,9 +508,9 @@ def _strip(self, strip=0, verb=True): self._strip_ddata(strip=strip, verb=verb) def _to_dict(self): - dout = {'dgroup':{'dict':self._dgroup, 'lexcept':None}, - 'dref':{'dict':self._dref, 'lexcept':None}, - 'ddata':{'dict':self._ddata, 'lexcept':None}} + dout = {'dgroup': {'dict': self._dgroup, 'lexcept': None}, + 'dref': {'dict': self._dref, 'lexcept': None}, + 'ddata': {'dict': self._ddata, 'lexcept': None}} return dout def _from_dict(self, fd): @@ -523,7 +519,6 @@ def _from_dict(self, fd): self._ddata.update(**fd['ddata']) self._complement_dgrouprefdata() - ########### # properties ########### @@ -532,42 +527,48 @@ def _from_dict(self, fd): def dconfig(self): """ The dict of configs """ return self._dconfig + @property def dgroup(self): """ The dict of groups """ return self._dgroup['dict'] + @property def lgroup(self): """ The dict of groups """ return self._dgroup['lkey'] + @property def dref(self): """ the dict of references """ return self._dref['dict'] + @property def lref(self): """ the dict of references """ return self._dref['lkey'] + @property def ddata(self): """ the dict of data """ return self._ddata['dict'] + @property def ldata(self): """ the dict of data """ return self._ddata['lkey'] + @property def lparam(self): """ the dict of data """ return self._ddata['lparam'] - #--------------------- + # --------------------- # Add / remove params - #--------------------- + # --------------------- # UP TO HERE - def get_param(self, param=None, returnas=np.ndarray): # Check inputs and trivial cases @@ -578,7 +579,7 @@ def get_param(self, param=None, returnas=np.ndarray): # Get output if returnas == dict: - out = {kk:self._ddata['dict'][kk][param] + out = {kk: self._ddata['dict'][kk][param] for kk in self._ddata['lkey']} else: out = [self._ddata['dict'][kk][param] @@ -636,10 +637,9 @@ def remove_param(self, param=None): for kk in self._ddata['lkey']: del self._ddata['dict'][kk][param] - - #--------------------- + # --------------------- # Read-only for internal use - #--------------------- + # --------------------- def select(self, group=None, ref=None, log='all', return_key=True, **kwdargs): @@ -672,10 +672,10 @@ def select(self, group=None, ref=None, log='all', return_key=True, critval = eval(lcrit[ii]) try: par = self.get_param(lcrit[ii], returnas=np.ndarray) - ind[ii,:] = par == critval - except: - ind[ii,:] = [self._ddata['dict'][kk][param] == critval - for kk in self.__lkata] + ind[ii, :] = par == critval + except Exception as err: + ind[ii, :] = [self._ddata['dict'][kk][param] == critval + for kk in self.__lkata] # Format output ind if log == 'all': @@ -683,14 +683,14 @@ def select(self, group=None, ref=None, log='all', return_key=True, elif log == 'any': ind = np.any(ind, axis=0) else: - ind = {lcrit[ii]: ind[ii,:] for ii in range(ncrit)} + ind = {lcrit[ii]: ind[ii, :] for ii in range(ncrit)} # Also return the list of keys if required if return_key: if np.any(ind): out = ind, lid[ind.nonzero()[0]] else: - out = ind, np.array([],dtype=int) + out = ind, np.array([], dtype=int) else: out = ind return out @@ -734,10 +734,9 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): out = self._ddata['lkey'] return out - - #--------------------- + # --------------------- # Methods for showing data - #--------------------- + # --------------------- def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', table_sep=None, verb=True, return_=False): @@ -761,7 +760,7 @@ def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', self._dref['dict'][k0]['group'], self._dref['dict'][k0]['size'], len(self._dref['dict'][k0]['ldata'])) - for k0,v0 in self._dref['lkey']] + for k0, v0 in self._dref['lkey']] # ----------------------- # Build for ddata @@ -790,16 +789,14 @@ def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', lu = [k0] + [str(v0[cc]) for cc in col2[1:]] ar2.append(lu) - return self._get_summary([ar0,ar1,ar2], [col0, col1, col2], - sep=sep, line=line, table_sep=table_sep, - verb=verb, return_=return_) + return self._get_summary( + [ar0, ar1, ar2], [col0, col1, col2], + sep=sep, line=line, table_sep=table_sep, + verb=verb, return_=return_) - - - #--------------------- + # --------------------- # Method for interpolating on ref - #--------------------- - + # --------------------- def get_time_common(self, lkeys, choose=None): """ Return the common time vector to several quantities @@ -1017,9 +1014,9 @@ def interp_t(self, dkeys, return dout, tref - #--------------------- + # --------------------- # Methods for computing additional plasma quantities - #--------------------- + # --------------------- def _fill_dins(self, dins): @@ -1146,9 +1143,9 @@ def compute_fanglev(self, BR=None, BPhi=None, BZ=None, - #--------------------- + # --------------------- # Methods for interpolation - #--------------------- + # --------------------- def _get_quantrefkeys(self, qq, ref1d=None, ref2d=None): @@ -1523,9 +1520,9 @@ def calc_signal_from_Cam(self, cam, t=None, connect=connect) - #--------------------- + # --------------------- # Methods for getting data - #--------------------- + # --------------------- def get_dextra(self, dextra=None): lc = [dextra is None, dextra == 'all', type(dextra) is dict, @@ -1655,9 +1652,9 @@ def get_Data(self, lquant, X=None, ref1d=None, ref2d=None, return lout - #--------------------- + # --------------------- # Methods for plotting data - #--------------------- + # --------------------- def plot(self, lquant, X=None, ref1d=None, ref2d=None, diff --git a/tofu/version.py b/tofu/version.py index 3ddaad3d0..98b296925 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-128-g45756c7' +__version__ = '1.4.1-129-gd229257' From 1a88ce11be1c31c4c03bd885d675e9e00cca7c60 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:40:48 +0200 Subject: [PATCH 14/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 4 --- tofu/data/_core_new.py | 53 +++++++++++++++++++++--------------------- tofu/version.py | 2 +- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 9af2618d4..7426c7f3c 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -436,7 +436,7 @@ def _set_ddata(self, ddata): def add_ref(self, key, data=None, group=None, **kwdargs): """ Add a reference """ - self._set_dref({key:{'data': data, 'group': group, **kwdargs}}) + self._set_dref({key: {'data': data, 'group': group, **kwdargs}}) def remove_ref(self, key): """ Remove a reference (all data depending on it are removed too) """ @@ -489,12 +489,12 @@ def _strip_ddata(self, strip=0, verb=0): @classmethod def _strip_init(cls): - cls._dstrip['allowed'] = [0,1] + cls._dstrip['allowed'] = [0, 1] nMax = max(cls._dstrip['allowed']) doc = """ 1: None """ - doc = utils.ToFuObjectBase.strip.__doc__.format(doc,nMax) + doc = utils.ToFuObjectBase.strip.__doc__.format(doc, nMax) if sys.version[0] == '2': cls.strip.__func__.__doc__ = doc else: @@ -738,7 +738,8 @@ def _ind_tofrom_key(self, ind=None, key=None, returnas=int): # Methods for showing data # --------------------- - def get_summary(self, show=None, show_core=None, sep=' ', line='-', just='l', + def get_summary(self, show=None, show_core=None, + sep=' ', line='-', just='l', table_sep=None, verb=True, return_=False): """ Summary description of the object content """ # # Make sure the data is accessible @@ -805,17 +806,17 @@ def get_time_common(self, lkeys, choose=None): according to criterion choose """ # Check all data have time-dependency - dout = {kk: {'t':self.get_time(kk)} for kk in lkeys} + dout = {kk: {'t': self.get_time(kk)} for kk in lkeys} dtu = dict.fromkeys(set([vv['t'] for vv in dout.values()])) for kt in dtu.keys(): - dtu[kt] = {'ldata':[kk for kk in lkeys if dout[kk]['t'] == kt]} + dtu[kt] = {'ldata': [kk for kk in lkeys if dout[kk]['t'] == kt]} if len(dtu) == 1: tref = list(dtu.keys())[0] else: - lt, lres = zip(*[(kt,np.mean(np.diff(self._ddata[kt]['data']))) + lt, lres = zip(*[(kt, np.mean(np.diff(self._ddata[kt]['data']))) for kt in dtu.keys()]) if choose is None: - choose = 'min' + choose = 'min' if choose == 'min': tref = lt[np.argmin(lres)] return dout, dtu, tref @@ -826,8 +827,8 @@ def _get_time_common_arrays(dins, choose=None): dtu = {} for k, v in dins.items(): c0 = type(k) is str - c0 = c0 and all([ss in v.keys() for ss in ['val','t']]) - c0 = c0 and all([type(v[ss]) is np.ndarray for ss in ['val','t']]) + c0 = c0 and all([ss in v.keys() for ss in ['val', 't']]) + c0 = c0 and all([type(v[ss]) is np.ndarray for ss in ['val', 't']]) c0 = c0 and v['t'].size in v['val'].shape if not c0: msg = "dins must be a dict of the form (at least):\n" @@ -840,26 +841,26 @@ def _get_time_common_arrays(dins, choose=None): if kt not in dtu.keys(): lisclose = [kk for kk, vv in dtu.items() if (vv['val'].shape == v['t'].shape - and np.allclose(vv['val'],v['t']))] + and np.allclose(vv['val'], v['t']))] assert len(lisclose) <= 1 if len(lisclose) == 1: kt = lisclose[0] else: already = False - dtu[kt] = {'val':np.atleast_1d(v['t']).ravel(), - 'ldata':[k]} + dtu[kt] = {'val': np.atleast_1d(v['t']).ravel(), + 'ldata': [k]} if already: dtu[kt]['ldata'].append(k) assert dtu[kt]['val'].size == v['val'].shape[0] - dout[k] = {'val':v['val'], 't':kt} + dout[k] = {'val': v['val'], 't': kt} if len(dtu) == 1: tref = list(dtu.keys())[0] else: - lt, lres = zip(*[(kt,np.mean(np.diff(dtu[kt]['val']))) + lt, lres = zip(*[(kt, np.mean(np.diff(dtu[kt]['val']))) for kt in dtu.keys()]) if choose is None: - choose = 'min' + choose = 'min' if choose == 'min': tref = lt[np.argmin(lres)] return dout, dtu, tref @@ -895,7 +896,7 @@ def _interp_on_common_time(self, lkeys, if type(tref) is not np.ndarray and tref in dtu.keys(): for kk in dtu[tref]['ldata']: - dout[kk]['val'] = self._ddata[kk]['data'] + dout[kk]['val'] = self._ddata[kk]['data'] return dout, tref @@ -932,30 +933,30 @@ def interp_t(self, dkeys, choose='min', interp_t=None, t=None, fill_value=np.nan): # Check inputs - assert type(dkeys) in [list,dict] + assert type(dkeys) in [list, dict] if type(dkeys) is list: - dkeys = {kk:{'val':kk} for kk in dkeys} + dkeys = {kk: {'val': kk} for kk in dkeys} lc = [(type(kk) is str and type(vv) is dict - and type(vv.get('val',None)) in [str,np.ndarray]) - for kk,vv in dkeys.items()] + and type(vv.get('val', None)) in [str, np.ndarray]) + for kk, vv in dkeys.items()] assert all(lc), str(dkeys) # Separate by type - dk0 = dict([(kk,vv) for kk,vv in dkeys.items() + dk0 = dict([(kk, vv) for kk, vv in dkeys.items() if type(vv['val']) is str]) - dk1 = dict([(kk,vv) for kk,vv in dkeys.items() + dk1 = dict([(kk, vv) for kk, vv in dkeys.items() if type(vv['val']) is np.ndarray]) assert len(dkeys) == len(dk0) + len(dk1), str(dk0) + '\n' + str(dk1) - if len(dk0) == len(dkeys): lk = [v['val'] for v in dk0.values()] dout, tref = self._interp_on_common_time(lk, choose=choose, t=t, interp_t=interp_t, fill_value=fill_value) - dout = {kk:{'val':dout[vv['val']]['val'], 't':dout[vv['val']]['t']} - for kk,vv in dk0.items()} + dout = {kk: {'val': dout[vv['val']]['val'], + 't': dout[vv['val']]['t']} + for kk, vv in dk0.items()} elif len(dk1) == len(dkeys): dout, tref = self._interp_on_common_time_arrays(dk1, choose=choose, t=t, interp_t=interp_t, diff --git a/tofu/version.py b/tofu/version.py index 98b296925..2c6a3000b 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-129-gd229257' +__version__ = '1.4.1-130-gcd3d895' From 9814d7db4f91b3d20c0b284a992f9952c0611c6e Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:49:33 +0200 Subject: [PATCH 15/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 5 --- tofu/data/_core_new.py | 578 ++--------------------------------------- tofu/version.py | 2 +- 2 files changed, 23 insertions(+), 557 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 7426c7f3c..3310f380f 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -958,24 +958,24 @@ def interp_t(self, dkeys, 't': dout[vv['val']]['t']} for kk, vv in dk0.items()} elif len(dk1) == len(dkeys): - dout, tref = self._interp_on_common_time_arrays(dk1, choose=choose, - t=t, interp_t=interp_t, - fill_value=fill_value) + dout, tref = self._interp_on_common_time_arrays( + dk1, choose=choose, t=t, + interp_t=interp_t, fill_value=fill_value) else: lk = [v['val'] for v in dk0.values()] if type(t) is np.ndarray: - dout, tref = self._interp_on_common_time(lk, choose=choose, - t=t, interp_t=interp_t, - fill_value=fill_value) - dout1, _ = self._interp_on_common_time_arrays(dk1, choose=choose, - t=t, interp_t=interp_t, - fill_value=fill_value) + dout, tref = self._interp_on_common_time( + lk, choose=choose, t=t, + interp_t=interp_t, fill_value=fill_value) + dout1, _ = self._interp_on_common_time_arrays( + dk1, choose=choose, t=t, + interp_t=interp_t, fill_value=fill_value) else: dout0, dtu0, tref0 = self.get_time_common(lk, choose=choose) - dout1, dtu1, tref1 = self._get_time_common_arrays(dk1, - choose=choose) + dout1, dtu1, tref1 = self._get_time_common_arrays( + dk1, choose=choose) if type(t) is str: lc = [t in dtu0.keys(), t in dout1.keys()] if not any(lc): @@ -1002,192 +1002,21 @@ def interp_t(self, dkeys, else: t0, t1, tref = t1, tref1, tref1 - dout, tref = self._interp_on_common_time(lk, choose=choose, - t=t0, interp_t=interp_t, - fill_value=fill_value) - dout = {kk:{'val':dout[vv['val']]['val'], - 't':dout[vv['val']]['t']} - for kk,vv in dk0.items()} - dout1, _ = self._interp_on_common_time_arrays(dk1, choose=choose, - t=t1, interp_t=interp_t, - fill_value=fill_value) - dout.update(dout1) - - return dout, tref - - # --------------------- - # Methods for computing additional plasma quantities - # --------------------- - - - def _fill_dins(self, dins): - for k in dins.keys(): - if type(dins[k]['val']) is str: - assert dins[k]['val'] in self._ddata.keys() - else: - dins[k]['val'] = np.atleast_1d(dins[k]['val']) - assert dins[k]['t'] is not None - dins[k]['t'] = np.atleast_1d(dins[k]['t']).ravel() - assert dins[k]['t'].size == dins[k]['val'].shape[0] - return dins - - @staticmethod - def _checkformat_shapes(dins): - shape = None - for k in dins.keys(): - dins[k]['shape'] = dins[k]['val'].shape - if shape is None: - shape = dins[k]['shape'] - if dins[k]['shape'] != shape: - if dins[k]['val'].ndim > len(shape): - shape = dins[k]['shape'] - - # Check shape consistency for broadcasting - assert len(shape) in [1,2] - if len(shape) == 1: - for k in dins.keys(): - assert dins[k]['shape'][0] in [1,shape[0]] - if dins[k]['shape'][0] < shape[0]: - dins[k]['val'] = np.full((shape[0],), dins[k]['val'][0]) - dins[k]['shape'] = dins[k]['val'].shape - - elif len(shape) == 2: - for k in dins.keys(): - if len(dins[k]['shape']) == 1: - if dins[k]['shape'][0] not in [1]+list(shape): - msg = "Non-conform shape for dins[%s]:\n"%k - msg += " - Expected: (%s,...) or (1,)\n"%str(shape[0]) - msg += " - Provided: %s"%str(dins[k]['shape']) - raise Exception(msg) - if dins[k]['shape'][0] == 1: - dins[k]['val'] = dins[k]['val'][None,:] - elif dins[k]['shape'][0] == shape[0]: - dins[k]['val'] = dins[k]['val'][:,None] - else: - dins[k]['val'] = dins[k]['val'][None,:] - else: - assert dins[k]['shape'] == shape - dins[k]['shape'] = dins[k]['val'].shape - return dins - - - - def compute_bremzeff(self, Te=None, ne=None, zeff=None, lamb=None, - tTe=None, tne=None, tzeff=None, t=None, - interp_t=None): - """ Return the bremsstrahlung spectral radiance at lamb + dout, tref = self._interp_on_common_time( + lk, choose=choose, t=t0, + interp_t=interp_t, fill_value=fill_value) - The plasma conditions are set by: - - Te (eV) - - ne (/m3) - - zeff (adim.) + dout = {kk: {'val': dout[vv['val']]['val'], + 't': dout[vv['val']]['t']} + for kk, vv in dk0.items()} - The wavelength is set by the diagnostics - - lamb (m) - - The vol. spectral emis. is returned in ph / (s.m3.sr.m) - - The computation requires an intermediate : gff(Te, zeff) - """ - dins = {'Te':{'val':Te, 't':tTe}, - 'ne':{'val':ne, 't':tne}, - 'zeff':{'val':zeff, 't':tzeff}} - lc = [vv['val'] is None for vv in dins.values()] - if any(lc): - msg = "All fields should be provided:\n" - msg += " - %s"%str(dins.keys()) - raise Exception(msg) - dins = self._fill_dins(dins) - dins, t = self.interp_t(dins, t=t, interp_t=interp_t) - lamb = np.atleast_1d(lamb) - dins['lamb'] = {'val':lamb} - dins = self._checkformat_shapes(dins) - - val, units = _physics.compute_bremzeff(dins['Te']['val'], - dins['ne']['val'], - dins['zeff']['val'], - dins['lamb']['val']) - return val, t, units - - def compute_fanglev(self, BR=None, BPhi=None, BZ=None, - ne=None, lamb=None, t=None, interp_t=None, - tBR=None, tBPhi=None, tBZ=None, tne=None): - """ Return the vector faraday angle at lamb - - The plasma conditions are set by: - - BR (T) , array of R component of B - - BRPhi (T) , array of phi component of B - - BZ (T) , array of Z component of B - - ne (/m3) - - The wavelength is set by the diagnostics - - lamb (m) - - The vector faraday angle is returned in T / m - """ - dins = {'BR': {'val':BR, 't':tBR}, - 'BPhi':{'val':BPhi, 't':tBPhi}, - 'BZ': {'val':BZ, 't':tBZ}, - 'ne': {'val':ne, 't':tne}} - dins = self._fill_dins(dins) - dins, t = self.interp_t(dins, t=t, interp_t=interp_t) - lamb = np.atleast_1d(lamb) - dins['lamb'] = {'val':lamb} - dins = self._checkformat_shapes(dins) + dout1, _ = self._interp_on_common_time_arrays( + dk1, choose=choose, t=t1, + interp_t=interp_t, fill_value=fill_value) - val, units = _physics.compute_fangle(BR=dins['BR']['val'], - BPhi=dins['BPhi']['val'], - BZ=dins['BZ']['val'], - ne=dins['ne']['val'], - lamb=dins['lamb']['val']) - return val, t, units - - - - # --------------------- - # Methods for interpolation - # --------------------- - - - def _get_quantrefkeys(self, qq, ref1d=None, ref2d=None): - - # Get relevant lists - kq, msg = self._get_keyingroup(qq, 'mesh', msgstr='quant', raise_=False) - if kq is not None: - k1d, k2d = None, None - else: - kq, msg = self._get_keyingroup(qq, 'radius', msgstr='quant', raise_=True) - if ref1d is None and ref2d is None: - msg = "quant %s needs refs (1d and 2d) for interpolation\n"%qq - msg += " => ref1d and ref2d cannot be both None !" - raise Exception(msg) - if ref1d is None: - ref1d = ref2d - k1d, msg = self._get_keyingroup(ref1d, 'radius', - msgstr='ref1d', raise_=False) - if k1d is None: - msg += "\n\nInterpolation of %s:\n"%qq - msg += " ref could not be identified among 1d quantities\n" - msg += " - ref1d : %s"%ref1d - raise Exception(msg) - if ref2d is None: - ref2d = ref1d - k2d, msg = self._get_keyingroup(ref2d, 'mesh', - msgstr='ref2d', raise_=False) - if k2d is None: - msg += "\n\nInterpolation of %s:\n" - msg += " ref could not be identified among 2d quantities\n" - msg += " - ref2d: %s"%ref2d - raise Exception(msg) - - q1d, q2d = self._ddata[k1d]['quant'], self._ddata[k2d]['quant'] - if q1d != q2d: - msg = "ref1d and ref2d must be of the same quantity !\n" - msg += " - ref1d (%s): %s\n"%(ref1d, q1d) - msg += " - ref2d (%s): %s"%(ref2d, q2d) - raise Exception(msg) + dout.update(dout1) - return kq, k1d, k2d + return dout, tref def _get_indtmult(self, idquant=None, idref1d=None, idref2d=None): @@ -1288,369 +1117,6 @@ def _get_tcom(self, idquant=None, idref1d=None, return out - def _get_finterp(self, - idquant=None, idref1d=None, idref2d=None, - idq2dR=None, idq2dPhi=None, idq2dZ=None, - interp_t='nearest', interp_space=None, - fill_value=np.nan, ani=False, Type=None): - - # Get idmesh - if idquant is not None: - if idref1d is None: - lidmesh = [qq for qq in self._ddata[idquant]['depend'] - if self._dindref[qq]['group'] == 'mesh'] - else: - lidmesh = [qq for qq in self._ddata[idref2d]['depend'] - if self._dindref[qq]['group'] == 'mesh'] - else: - assert idq2dR is not None - lidmesh = [qq for qq in self._ddata[idq2dR]['depend'] - if self._dindref[qq]['group'] == 'mesh'] - assert len(lidmesh) == 1 - idmesh = lidmesh[0] - - # Get mesh - mpltri = self._ddata[idmesh]['data']['mpltri'] - trifind = mpltri.get_trifinder() - - # Get common time indices - if interp_t == 'nearest': - out = self._get_tcom(idquant,idref1d, idref2d, idq2dR) - tall, tbinall, ntall, indtq, indtr1, indtr2= out - - # # Prepare output - - # Interpolate - # Note : Maybe consider using scipy.LinearNDInterpolator ? - if idquant is not None: - vquant = self._ddata[idquant]['data'] - if self._ddata[idmesh]['data']['ntri'] > 1: - vquant = np.repeat(vquant, - self._ddata[idmesh]['data']['ntri'], axis=0) - else: - vq2dR = self._ddata[idq2dR]['data'] - vq2dPhi = self._ddata[idq2dPhi]['data'] - vq2dZ = self._ddata[idq2dZ]['data'] - - if interp_space is None: - interp_space = self._ddata[idmesh]['data']['ftype'] - - # get interpolation function - if ani: - # Assuming same mesh and time vector for all 3 components - func = _comp.get_finterp_ani(self, idq2dR, idq2dPhi, idq2dZ, - interp_t=interp_t, - interp_space=interp_space, - fill_value=fill_value, - idmesh=idmesh, vq2dR=vq2dR, - vq2dZ=vq2dZ, vq2dPhi=vq2dPhi, - tall=tall, tbinall=tbinall, - ntall=ntall, - indtq=indtq, trifind=trifind, - Type=Type, mpltri=mpltri) - else: - func = _comp.get_finterp_isotropic(self, idquant, idref1d, idref2d, - interp_t=interp_t, - interp_space=interp_space, - fill_value=fill_value, - idmesh=idmesh, vquant=vquant, - tall=tall, tbinall=tbinall, - ntall=ntall, mpltri=mpltri, - indtq=indtq, indtr1=indtr1, - indtr2=indtr2, trifind=trifind) - - - return func - - - def _checkformat_qr12RPZ(self, quant=None, ref1d=None, ref2d=None, - q2dR=None, q2dPhi=None, q2dZ=None): - lc0 = [quant is None, ref1d is None, ref2d is None] - lc1 = [q2dR is None, q2dPhi is None, q2dZ is None] - if np.sum([all(lc0), all(lc1)]) != 1: - msg = "Please provide either (xor):\n" - msg += " - a scalar field (isotropic emissivity):\n" - msg += " quant : scalar quantity to interpolate\n" - msg += " if quant is 1d, intermediate reference\n" - msg += " fields are necessary for 2d interpolation\n" - msg += " ref1d : 1d reference field on which to interpolate\n" - msg += " ref2d : 2d reference field on which to interpolate\n" - msg += " - a vector (R,Phi,Z) field (anisotropic emissivity):\n" - msg += " q2dR : R component of the vector field\n" - msg += " q2dPhi: R component of the vector field\n" - msg += " q2dZ : Z component of the vector field\n" - msg += " => all components have teh same time and mesh !\n" - raise Exception(msg) - - # Check requested quant is available in 2d or 1d - if all(lc1): - idquant, idref1d, idref2d = self._get_quantrefkeys(quant, ref1d, ref2d) - idq2dR, idq2dPhi, idq2dZ = None, None, None - ani = False - else: - idq2dR, msg = self._get_keyingroup(q2dR, 'mesh', msgstr='quant', - raise_=True) - idq2dPhi, msg = self._get_keyingroup(q2dPhi, 'mesh', msgstr='quant', - raise_=True) - idq2dZ, msg = self._get_keyingroup(q2dZ, 'mesh', msgstr='quant', - raise_=True) - idquant, idref1d, idref2d = None, None, None - ani = True - return idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani - - - def get_finterp2d(self, quant=None, ref1d=None, ref2d=None, - q2dR=None, q2dPhi=None, q2dZ=None, - interp_t=None, interp_space=None, - fill_value=np.nan, Type=None): - """ Return the function interpolating (X,Y,Z) pts on a 1d/2d profile - - Can be used as input for tf.geom.CamLOS1D/2D.calc_signal() - - """ - # Check inputs - msg = "Only 'nearest' available so far for interp_t!" - assert interp_t == 'nearest', msg - out = self._checkformat_qr12RPZ(quant=quant, ref1d=ref1d, ref2d=ref2d, - q2dR=q2dR, q2dPhi=q2dPhi, q2dZ=q2dZ) - idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani = out - - - # Interpolation (including time broadcasting) - func = self._get_finterp(idquant=idquant, idref1d=idref1d, - idref2d=idref2d, idq2dR=idq2dR, - idq2dPhi=idq2dPhi, idq2dZ=idq2dZ, - interp_t=interp_t, interp_space=interp_space, - fill_value=fill_value, ani=ani, Type=Type) - return func - - - def interp_pts2profile(self, pts=None, vect=None, t=None, - quant=None, ref1d=None, ref2d=None, - q2dR=None, q2dPhi=None, q2dZ=None, - interp_t=None, interp_space=None, - fill_value=np.nan, Type=None): - """ Return the value of the desired profiles_1d quantity - - For the desired inputs points (pts): - - pts are in (R,Z) coordinates - - space interpolation is linear on the 1d profiles - At the desired input times (t): - - using a nearest-neighbourg approach for time - - """ - # Check inputs - # msg = "Only 'nearest' available so far for interp_t!" - # assert interp_t == 'nearest', msg - - # Check requested quant is available in 2d or 1d - out = self._checkformat_qr12RPZ(quant=quant, ref1d=ref1d, ref2d=ref2d, - q2dR=q2dR, q2dPhi=q2dPhi, q2dZ=q2dZ) - idquant, idref1d, idref2d, idq2dR, idq2dPhi, idq2dZ, ani = out - - # Check the pts is (2,...) array of floats - if pts is None: - if ani: - idmesh = [id_ for id_ in self._ddata[idq2dR]['depend'] - if self._dindref[id_]['group'] == 'mesh'][0] - else: - if idref1d is None: - idmesh = [id_ for id_ in self._ddata[idquant]['depend'] - if self._dindref[id_]['group'] == 'mesh'][0] - else: - idmesh = [id_ for id_ in self._ddata[idref2d]['depend'] - if self._dindref[id_]['group'] == 'mesh'][0] - pts = self.dmesh[idmesh]['data']['nodes'] - pts = np.array([pts[:,0], np.zeros((pts.shape[0],)), pts[:,1]]) - - pts = np.atleast_2d(pts) - if pts.shape[0] != 3: - msg = "pts must be np.ndarray of (X,Y,Z) points coordinates\n" - msg += "Can be multi-dimensional, but the 1st dimension is (X,Y,Z)\n" - msg += " - Expected shape : (3,...)\n" - msg += " - Provided shape : %s"%str(pts.shape) - raise Exception(msg) - - # Check t - lc = [t is None, type(t) is str, type(t) is np.ndarray] - assert any(lc) - if lc[1]: - assert t in self._ddata.keys() - t = self._ddata[t]['data'] - - # Interpolation (including time broadcasting) - # this is the second slowest step (~0.08 s) - func = self._get_finterp(idquant=idquant, idref1d=idref1d, idref2d=idref2d, - idq2dR=idq2dR, idq2dPhi=idq2dPhi, idq2dZ=idq2dZ, - interp_t=interp_t, interp_space=interp_space, - fill_value=fill_value, ani=ani, Type=Type) - - # This is the slowest step (~1.8 s) - val, t = func(pts, vect=vect, t=t) - return val, t - - - def calc_signal_from_Cam(self, cam, t=None, - quant=None, ref1d=None, ref2d=None, - q2dR=None, q2dPhi=None, q2dZ=None, - Brightness=True, interp_t=None, - interp_space=None, fill_value=np.nan, - res=0.005, DL=None, resMode='abs', method='sum', - ind=None, out=object, plot=True, dataname=None, - fs=None, dmargin=None, wintit=None, invert=True, - units=None, draw=True, connect=True): - - if 'Cam' not in cam.__class__.__name__: - msg = "Arg cam must be tofu Camera instance (CamLOS1D, CamLOS2D...)" - raise Exception(msg) - - return cam.calc_signal_from_Plasma2D(self, t=t, - quant=quant, ref1d=ref1d, ref2d=ref2d, - q2dR=q2dR, q2dPhi=q2dPhi, - q2dZ=q2dZ, - Brightness=Brightness, - interp_t=interp_t, - interp_space=interp_space, - fill_value=fill_value, res=res, - DL=DL, resMode=resMode, - method=method, ind=ind, out=out, - pot=plot, dataname=dataname, - fs=fs, dmargin=dmargin, - wintit=wintit, invert=intert, - units=units, draw=draw, - connect=connect) - - - # --------------------- - # Methods for getting data - # --------------------- - - def get_dextra(self, dextra=None): - lc = [dextra is None, dextra == 'all', type(dextra) is dict, - type(dextra) is str, type(dextra) is list] - assert any(lc) - if dextra is None: - dextra = {} - - if dextra == 'all': - dextra = [k for k in self._dgroup['time']['ldata'] - if (self._ddata[k]['lgroup'] == ['time'] - and k not in self._dindref.keys())] - - if type(dextra) is str: - dextra = [dextra] - - # get data - if type(dextra) is list: - for ii in range(0,len(dextra)): - if type(dextra[ii]) is tuple: - ee, cc = dextra[ii] - else: - ee, cc = dextra[ii], None - ee, msg = self._get_keyingroup(ee, 'time', raise_=True) - if self._ddata[ee]['lgroup'] != ['time']: - msg = "time-only dependent signals allowed in dextra!\n" - msg += " - %s : %s"%(ee,str(self._ddata[ee]['lgroup'])) - raise Exception(msg) - idt = self._ddata[ee]['depend'][0] - key = 'data' if self._ddata[ee]['data'].ndim == 1 else 'data2D' - dd = {key: self._ddata[ee]['data'], - 't': self._ddata[idt]['data'], - 'label': self._ddata[ee]['name'], - 'units': self._ddata[ee]['units']} - if cc is not None: - dd['c'] = cc - dextra[ii] = (ee, dd) - dextra = dict(dextra) - return dextra - - def get_Data(self, lquant, X=None, ref1d=None, ref2d=None, - remap=False, res=0.01, interp_space=None, dextra=None): - - try: - import tofu.data as tfd - except Exception: - from .. import data as tfd - - # Check and format input - assert type(lquant) in [str,list] - if type(lquant) is str: - lquant = [lquant] - nquant = len(lquant) - - # Get X if common - c0 = type(X) is str - c1 = type(X) is list and (len(X) == 1 or len(X) == nquant) - if not (c0 or c1): - msg = "X must be specified, either as :\n" - msg += " - a str (name or quant)\n" - msg += " - a list of str\n" - msg += " Provided: %s"%str(X) - raise Exception(msg) - if c1 and len(X) == 1: - X = X[0] - - if type(X) is str: - idX, msg = self._get_keyingroup(X, 'radius', msgstr='X', raise_=True) - - # prepare remap pts - if remap: - assert self.config is not None - refS = list(self.config.dStruct['dObj']['Ves'].values())[0] - ptsRZ, x1, x2, extent = refS.get_sampleCross(res, mode='imshow') - dmap = {'t':None, 'data2D':None, 'extent':extent} - if ref is None and X in self._lquantboth: - ref = X - - # Define Data - dcommon = dict(Exp=self.Id.Exp, shot=self.Id.shot, - Diag='profiles1d', config=self.config) - - # dextra - dextra = self.get_dextra(dextra) - - # Get output - lout = [None for qq in lquant] - for ii in range(0,nquant): - qq = lquant[ii] - if remap: - # Check requested quant is available in 2d or 1d - idq, idrefd1, idref2d = self._get_quantrefkeys(qq, ref1d, ref2d) - else: - idq, msg = self._get_keyingroup(qq, 'radius', - msgstr='quant', raise_=True) - if idq not in self._dgroup['radius']['ldata']: - msg = "Only 1d quantities can be turned into tf.data.Data !\n" - msg += " - %s is not a radius-dependent quantity"%qq - raise Exception(msg) - idt = self._ddata[idq]['depend'][0] - - if type(X) is list: - idX, msg = self._get_keyingroup(X[ii], 'radius', - msgstr='X', raise_=True) - - dlabels = {'data':{'name': self._ddata[idq]['name'], - 'units': self._ddata[idq]['units']}, - 'X':{'name': self._ddata[idX]['name'], - 'units': self._ddata[idX]['units']}, - 't':{'name': self._ddata[idt]['name'], - 'units': self._ddata[idt]['units']}} - - dextra_ = dict(dextra) - if remap: - dmapii = dict(dmap) - val, tii = self.interp_pts2profile(qq, ptsRZ=ptsRZ, ref=ref, - interp_space=interp_space) - dmapii['data2D'], dmapii['t'] = val, tii - dextra_['map'] = dmapii - lout[ii] = DataCam1D(Name = qq, - data = self._ddata[idq]['data'], - t = self._ddata[idt]['data'], - X = self._ddata[idX]['data'], - dextra = dextra_, dlabels=dlabels, **dcommon) - if nquant == 1: - lout = lout[0] - return lout # --------------------- diff --git a/tofu/version.py b/tofu/version.py index 2c6a3000b..70e6b7c9c 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-130-gcd3d895' +__version__ = '1.4.1-131-g1a88ce1' From 243a3c8e1b6c555e6ac8e6cecd447d8285cb3758 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 08:52:17 +0200 Subject: [PATCH 16/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 6 --- tofu/data/_core_new.py | 14 +++++--------- tofu/version.py | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 3310f380f..cbd604256 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -1002,12 +1002,12 @@ def interp_t(self, dkeys, else: t0, t1, tref = t1, tref1, tref1 - dout, tref = self._interp_on_common_time( + dout, tref = self._interp_on_common_time( lk, choose=choose, t=t0, interp_t=interp_t, fill_value=fill_value) dout = {kk: {'val': dout[vv['val']]['val'], - 't': dout[vv['val']]['t']} + 't': dout[vv['val']]['t']} for kk, vv in dk0.items()} dout1, _ = self._interp_on_common_time_arrays( @@ -1018,7 +1018,6 @@ def interp_t(self, dkeys, return dout, tref - def _get_indtmult(self, idquant=None, idref1d=None, idref2d=None): # Get time vectors and bins @@ -1040,9 +1039,9 @@ def _get_indtmult(self, idquant=None, idref1d=None, idref2d=None): tall = tq else: if idref2d is None: - tbinall = np.unique(np.r_[tbinq,tbinr1]) + tbinall = np.unique(np.r_[tbinq, tbinr1]) else: - tbinall = np.unique(np.r_[tbinq,tbinr1,tbinr2]) + tbinall = np.unique(np.r_[tbinq, tbinr1, tbinr2]) tall = np.r_[tbinall[0] - 0.5*(tbinall[1]-tbinall[0]), 0.5*(tbinall[1:]+tbinall[:-1]), tbinall[-1] + 0.5*(tbinall[-1]-tbinall[-2])] @@ -1051,7 +1050,7 @@ def _get_indtmult(self, idquant=None, idref1d=None, idref2d=None): indtq, indtr1, indtr2 = None, None, None indtq = np.digitize(tall, tbinq) if idref1d is None: - assert np.all(indtq == np.arange(0,tall.size)) + assert np.all(indtq == np.arange(0, tall.size)) if idref1d is not None: indtr1 = np.digitize(tall, tbinr1) if idref2d is not None: @@ -1116,9 +1115,6 @@ def _get_tcom(self, idquant=None, idref1d=None, out = self._get_indtmult(idquant=idq2dR) return out - - - # --------------------- # Methods for plotting data # --------------------- diff --git a/tofu/version.py b/tofu/version.py index 70e6b7c9c..b7dc5159c 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-131-g1a88ce1' +__version__ = '1.4.1-132-g9814d7d' From 557280b82e2d56a61ce1e6cd7cecf280406db373 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 09:01:20 +0200 Subject: [PATCH 17/23] [Issue209] Trying to get back python 2.7 compatibility --- tofu/data/_core_new.py | 16 ++++++++-------- tofu/version.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index cbd604256..fb6bde60c 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -104,8 +104,8 @@ def _reset(self): # super() super(DataHolder, self)._reset() self._dgroup = {kd[0]: kd[1] for kd in self._get_keys_dgroup()} - self._dref = {kd[0]: kd[1] for kd in self._get_keys_dref()} - self._ddata = {kd[0]: kd[1] for kd in self._get_keys_ddata()} + self._dref = {kd[0]: kd[1] for kd in self._get_keys_dref()} + self._ddata = {kd[0]: kd[1] for kd in self._get_keys_ddata()} @classmethod def _checkformat_inputs_Id(cls, Id=None, Name=None, @@ -249,8 +249,8 @@ def _checkformat_dref(self, dref): dparams = self._extract_known_params(kk, vv) # Fill self._ddata - self._ddata['dict'][kk] = {'data': data, 'refs': (kk,), - 'shape': (size,), **dparams} + self._ddata['dict'][kk] = dict('data': data, 'refs': (kk,), + 'shape': (size,), **dparams) self._ddata['lkey'].append(kk) # ------------- DB (start) @@ -318,8 +318,8 @@ def _checkformat_ddata(self, ddata): # Extract params and set self._ddata dparams = self._extract_known_params(kk, vv) - self._ddata['dict'][kk] = {'data': data, 'refs': vv['refs'], - 'shape': shape, **dparams} + self._ddata['dict'][kk] = dict('data': data, 'refs': vv['refs'], + 'shape': shape, **dparams) self._ddata['lkey'].append(kk) def _complement_dgrouprefdata(self): @@ -436,7 +436,7 @@ def _set_ddata(self, ddata): def add_ref(self, key, data=None, group=None, **kwdargs): """ Add a reference """ - self._set_dref({key: {'data': data, 'group': group, **kwdargs}}) + self._set_dref({key: dict('data': data, 'group': group, **kwdargs)}) def remove_ref(self, key): """ Remove a reference (all data depending on it are removed too) """ @@ -452,7 +452,7 @@ def remove_ref(self, key): def add_data(self, key, data=None, ref=None, **kwdargs): """ Add a data (all associated ref must be added first)) """ - self._set_ddata({key: {'data': data, 'ref': ref, **kwdargs}}) + self._set_ddata({key: dict('data': data, 'ref': ref, **kwdargs)}) def remove_data(self, key, propagate=True): """ Remove a data diff --git a/tofu/version.py b/tofu/version.py index b7dc5159c..030baef98 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-132-g9814d7d' +__version__ = '1.4.1-133-g243a3c8' From 0840858176426a147b47058bfaa56b2efa8ff8e3 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 09:08:01 +0200 Subject: [PATCH 18/23] [Issue209] PEP8 compliance in tofu/data/_core.py 1 --- tofu/data/_core.py | 26 +++++++++++++------------- tofu/version.py | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tofu/data/_core.py b/tofu/data/_core.py index 849cc94d6..eb9ff6c2c 100644 --- a/tofu/data/_core.py +++ b/tofu/data/_core.py @@ -2246,12 +2246,11 @@ def nlamb(self): -##################################################################### -##################################################################### -##################################################################### +# #################################################################### +# #################################################################### # Plasma2D -##################################################################### -##################################################################### +# #################################################################### +# #################################################################### @@ -2265,20 +2264,21 @@ class Plasma2D(utils.ToFuObject): """ # Fixed (class-wise) dictionary of default properties - _ddef = {'Id':{'include':['Mod','Cls','Exp','Diag', - 'Name','shot','version']}, - 'dtreat':{'order':['mask','interp-indt','interp-indch','data0','dfit', - 'indt', 'indch', 'indlamb', 'interp-t']}} + _ddef = {'Id': {'include': ['Mod', 'Cls', 'Exp', 'Diag', + 'Name', 'shot', 'version']}, + 'dtreat': {'order': ['mask', 'interp-indt', 'interp-indch', + 'data0', 'dfit', + 'indt', 'indch', 'indlamb', 'interp-t']}} - # Does not exist before Python 3.6 !!! def __init_subclass__(cls, **kwdargs): + # Does not exist before Python 3.6 !!! # Python 2 super(Plasma2D,cls).__init_subclass__(**kwdargs) # Python 3 - #super().__init_subclass__(**kwdargs) + # super().__init_subclass__(**kwdargs) cls._ddef = copy.deepcopy(Plasma2D._ddef) - #cls._dplot = copy.deepcopy(Struct._dplot) - #cls._set_color_ddef(cls._color) + # cls._dplot = copy.deepcopy(Struct._dplot) + # cls._set_color_ddef(cls._color) def __init__(self, dtime=None, dradius=None, d0d=None, d1d=None, diff --git a/tofu/version.py b/tofu/version.py index 030baef98..2ad53d421 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-133-g243a3c8' +__version__ = '1.4.1-134-g557280b' From d5bf3d81128f3dffe3bee646d7db6aab9f716d94 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 09:10:43 +0200 Subject: [PATCH 19/23] [Issue209] Debuuged py27-compatible dict concatenation in DataHolder --- tofu/data/_core_new.py | 12 ++++++------ tofu/version.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index fb6bde60c..a0d186e4c 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -249,8 +249,8 @@ def _checkformat_dref(self, dref): dparams = self._extract_known_params(kk, vv) # Fill self._ddata - self._ddata['dict'][kk] = dict('data': data, 'refs': (kk,), - 'shape': (size,), **dparams) + self._ddata['dict'][kk] = dict(data=data, refs=(kk,), + shape=(size,), **dparams) self._ddata['lkey'].append(kk) # ------------- DB (start) @@ -318,8 +318,8 @@ def _checkformat_ddata(self, ddata): # Extract params and set self._ddata dparams = self._extract_known_params(kk, vv) - self._ddata['dict'][kk] = dict('data': data, 'refs': vv['refs'], - 'shape': shape, **dparams) + self._ddata['dict'][kk] = dict(data=data, refs=vv['refs'], + shape=shape, **dparams) self._ddata['lkey'].append(kk) def _complement_dgrouprefdata(self): @@ -436,7 +436,7 @@ def _set_ddata(self, ddata): def add_ref(self, key, data=None, group=None, **kwdargs): """ Add a reference """ - self._set_dref({key: dict('data': data, 'group': group, **kwdargs)}) + self._set_dref({key: dict(data=data, group=group, **kwdargs)}) def remove_ref(self, key): """ Remove a reference (all data depending on it are removed too) """ @@ -452,7 +452,7 @@ def remove_ref(self, key): def add_data(self, key, data=None, ref=None, **kwdargs): """ Add a data (all associated ref must be added first)) """ - self._set_ddata({key: dict('data': data, 'ref': ref, **kwdargs)}) + self._set_ddata({key: dict(data=data, ref=ref, **kwdargs)}) def remove_data(self, key, propagate=True): """ Remove a data diff --git a/tofu/version.py b/tofu/version.py index 2ad53d421..c60608d43 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-134-g557280b' +__version__ = '1.4.1-135-g0840858' From ad1956d0b9e327890f736517a6924fa472ff3fd2 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 09:12:59 +0200 Subject: [PATCH 20/23] [Issue209] Removed debugging in tofu/imas2tofu/_core.py --- tofu/imas2tofu/_core.py | 20 -------------------- tofu/version.py | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/tofu/imas2tofu/_core.py b/tofu/imas2tofu/_core.py index 709d13a79..44440a544 100644 --- a/tofu/imas2tofu/_core.py +++ b/tofu/imas2tofu/_core.py @@ -2253,26 +2253,6 @@ def _checkformat_mesh(nodes, indfaces, ids=None): indface[::2,:] = indfaces[:,:3] indface[1::2,:-1] = indfaces[:,2:] indface[1::2,-1] = indfaces[:,0] - - #### DB (begin) - faces = np.concatenate((nodes[indfaces,:], nodes[indfaces,:][:,0:1,:], - np.full((3936,1,2),np.nan)), axis=1) - faces = np.reshape(np.swapaxes(np.swapaxes(faces, 0,2), 1,2), (2,6*3936)) - import matplotlib.pyplot as plt - plt.switch_backend('Qt5Agg') - plt.ioff() - fig = plt.figure() - plt.plot(faces[0,:], faces[1,:]) - plt.gca().set_aspect('equal') - plt.show() - fig.savefig('Debug_JINTRACMesh.pdf', format='pdf') - fig.savefig('Debug_JINTRACMesh.svg', format='svg') - fig.savefig('Debug_JINTRACMesh.png', format='png') - import ipdb # DB - ipdb.set_trace() # DB - - #### DB (end) - indfaces = indface meshtype = 'quadtri' ntri = 2 diff --git a/tofu/version.py b/tofu/version.py index c60608d43..5874aeac5 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-135-g0840858' +__version__ = '1.4.1-136-gd5bf3d8' From bd12ae5fd37f87e6de9d93d83d7b07b767882054 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 09:46:12 +0200 Subject: [PATCH 21/23] [Issue209] One remaining py27-incompatible dict in tofu/data/_core_new.py --- tofu/data/_core.py | 6 ------ tofu/data/_core_new.py | 2 +- tofu/version.py | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tofu/data/_core.py b/tofu/data/_core.py index eb9ff6c2c..e7290bbdb 100644 --- a/tofu/data/_core.py +++ b/tofu/data/_core.py @@ -2241,11 +2241,6 @@ def nlamb(self): DataCam2D.__signature__ = sig.replace(parameters=lp) - - - - - # #################################################################### # #################################################################### # Plasma2D @@ -2253,7 +2248,6 @@ def nlamb(self): # #################################################################### - class Plasma2D(utils.ToFuObject): """ A generic class for handling 2D (and 1D) plasma profiles diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index a0d186e4c..0cf5b0546 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -408,7 +408,7 @@ def _get_keys_ddata(): ########### def _init(self, dref=None, ddata=None, **kwargs): - kwdargs = {'dref': dref, 'ddata': ddata, **kwargs} + kwdargs = dict(dref= dref, ddata=ddata, **kwargs) largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) self._set_dref(**kwddref, complement=False) diff --git a/tofu/version.py b/tofu/version.py index 5874aeac5..ab9fb86c4 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-136-gd5bf3d8' +__version__ = '1.4.1-137-gad1956d' From 31eeb0727106f6b1c772e48c67bf328a066160d2 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 10:06:51 +0200 Subject: [PATCH 22/23] [Issue209] PEP8 compliance in tofu/data/_core_new.py 7 --- tofu/data/_core_new.py | 2 +- tofu/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 0cf5b0546..8a5029cb1 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -408,7 +408,7 @@ def _get_keys_ddata(): ########### def _init(self, dref=None, ddata=None, **kwargs): - kwdargs = dict(dref= dref, ddata=ddata, **kwargs) + kwdargs = dict(dref=dref, ddata=ddata, **kwargs) largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) self._set_dref(**kwddref, complement=False) diff --git a/tofu/version.py b/tofu/version.py index ab9fb86c4..51f51132b 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-137-gad1956d' +__version__ = '1.4.1-138-gbd12ae5' From 55f8314714b72799095bc9501dbd70a37e44a9b4 Mon Sep 17 00:00:00 2001 From: VEZINET Didier Date: Tue, 8 Oct 2019 10:20:34 +0200 Subject: [PATCH 23/23] [Issue209] py27 compatibility in tofu/data/_core_new.py --- tofu/data/_core_new.py | 2 +- tofu/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tofu/data/_core_new.py b/tofu/data/_core_new.py index 8a5029cb1..113c84497 100644 --- a/tofu/data/_core_new.py +++ b/tofu/data/_core_new.py @@ -411,7 +411,7 @@ def _init(self, dref=None, ddata=None, **kwargs): kwdargs = dict(dref=dref, ddata=ddata, **kwargs) largs = self._get_largs_dref() kwddref = self._extract_kwdargs(kwdargs, largs) - self._set_dref(**kwddref, complement=False) + self._set_dref(complement=False, **kwddref) largs = self._get_largs_ddata() kwddata = self._extract_kwdargs(kwdargs, largs) self._set_ddata(**kwddata) diff --git a/tofu/version.py b/tofu/version.py index 51f51132b..f73a586e5 100644 --- a/tofu/version.py +++ b/tofu/version.py @@ -1,2 +1,2 @@ # Do not edit, pipeline versioning governed by git tags! -__version__ = '1.4.1-138-gbd12ae5' +__version__ = '1.4.1-139-g31eeb07'