diff --git a/Debug_JINTRACMesh.pdf b/Debug_JINTRACMesh.pdf new file mode 100644 index 000000000..e69de29bb diff --git a/Debug_JINTRACMesh.png b/Debug_JINTRACMesh.png new file mode 100644 index 000000000..0e536fb53 Binary files /dev/null and b/Debug_JINTRACMesh.png differ diff --git a/Debug_JINTRACMesh.svg b/Debug_JINTRACMesh.svg new file mode 100644 index 000000000..725e03413 --- /dev/null +++ b/Debug_JINTRACMesh.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/Debug_JINTRACMesh2.png b/Debug_JINTRACMesh2.png new file mode 100644 index 000000000..5dbb05bc9 Binary files /dev/null and b/Debug_JINTRACMesh2.png differ 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/data/_core.py b/tofu/data/_core.py index 4829cf64e..e7290bbdb 100644 --- a/tofu/data/_core.py +++ b/tofu/data/_core.py @@ -2241,18 +2241,11 @@ def nlamb(self): DataCam2D.__signature__ = sig.replace(parameters=lp) - - - - - -##################################################################### -##################################################################### -##################################################################### +# #################################################################### +# #################################################################### # Plasma2D -##################################################################### -##################################################################### - +# #################################################################### +# #################################################################### class Plasma2D(utils.ToFuObject): @@ -2265,20 +2258,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, @@ -3607,9 +3601,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 +3746,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/data/_core_new.py b/tofu/data/_core_new.py new file mode 100644 index 000000000..113c84497 --- /dev/null +++ b/tofu/data/_core_new.py @@ -0,0 +1,1150 @@ +# -*- 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']}, + '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' + + 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) + 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): + # 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()} + + @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 isinstance(Name, str), Name + if include is None: + include = cls._ddef['Id']['include'] + kwdargs.update({'Name': Name, 'include': include}) + return kwdargs + + ########### + # Get largs + ########### + + @staticmethod + def _get_largs_dref(): + largs = ['dref'] + return largs + + @staticmethod + 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() + if kk not in self._reserved_all} + + # 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]): + vtyp = str(type(vv[0])) + msg = "A parameter for %s has the wrong type:\n"%key + 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) + for kk, vv in dref.items()]) + if not c0: + msg = "Provided dref must be dict !\n" + 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()) + 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()]) + if not (cA or cB): + 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 += " '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: + # Convert to cB + drbis = {} + for k0, v0 in dref.items(): + for k1, v1 in v0.items(): + if isinstance(v1, dict): + drbis[k1] = v1 + drbis['group'] = k0 + else: + drbis[k1] = {'data': v1, 'group': k0} + dref = drbis + + # Check cB + for kk, vv in dref.items(): + + # 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']: + 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, list) or isinstance(data, tuple): + try: + data = np.atleast_1d(data).ravel() + size = data.size + except Exception as err: + c0 = False + else: + size = data.__class__.__name__ + else: + if data.ndim != 1: + data = np.atleast_1d(data).ravel() + size = data.size + + if not c0: + 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['lkey'].append(kk) + + # Extract and check parameters + dparams = self._extract_known_params(kk, vv) + + # Fill self._ddata + self._ddata['dict'][kk] = dict(data=data, refs=(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) + 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) + + # 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 '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" + msg += " - 'refs': a tuple indicating refs dependencies\n" + msg += " - 'data': a 1d array containing the data" + raise Exception(msg) + + # 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) + + # Extract data and shape + data = vv['data'] + if not isinstance(data, np.ndarray): + if isinstance(data, list) or isinstance(data, tuple): + try: + data = np.asarray(data) + shape = data.shape + except Exception as err: + 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['refs']): + if rr not in self._dref['lkey']: + msg = "ddata[%s] depends on an unknown ref !\n"%kk + 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) + 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(shaprf)) + raise Exception(msg) + + # 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['lkey'].append(kk) + + def _complement_dgrouprefdata(self): + + # -------------- + # ddata + 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['refs'] if ii not in self._dref['lkey']] + if len(lrefout) != 0: + msg = "ddata[%s]['refs'] has keys not in dref:\n"%k0 + msg += " - " + "\n - ".join(lrefout) + raise Exception(msg) + + # set group + 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 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'] = grps + + # -------------- + # dref + for k0 in self._dref['lkey']: + ldata = [kk for kk in self._ddata['lkey'] + if k0 in self._ddata['dict'][kk]['refs']] + self._dref['dict'][k0]['ldata'] = ldata + assert self._dref['dict'][k0]['group'] in self._dgroup['lkey'] + + # -------------- + # dgroup + 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['dict'][gg]['lref'] = lref + self._dgroup['dict'][gg]['ldata'] = ldata + + # -------------- + # params + lparam = self._ddata['lparam'] + 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) + + 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 + + ########### + # Get keys of dictionnaries + ########### + + @staticmethod + def _get_keys_dgroup(): + lk = [('lkey', []), ('dict', {})] + return lk + + @staticmethod + def _get_keys_dref(): + lk = [('lkey', []), ('dict', {})] + return lk + + @staticmethod + def _get_keys_ddata(): + lk = [('lkey', []), ('dict', {}), ('lparam', [])] + return lk + + ########### + # _init + ########### + + 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(complement=False, **kwddref) + largs = self._get_largs_ddata() + kwddata = self._extract_kwdargs(kwdargs, largs) + self._set_ddata(**kwddata) + self._dstrip['strip'] = 0 + + ########### + # set dictionaries + ########### + + 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() + + # --------------------- + # Methods for adding ref / quantities + # --------------------- + + def add_ref(self, key, data=None, group=None, **kwdargs): + """ Add a reference """ + 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) """ + 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: + 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): + """ Add a data (all associated ref must be added first)) """ + self._set_ddata({key: dict(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: + assert key in self._ddata['dict'].keys() + if propagate: + # Check if associated ref shall be removed too + lref = self._ddata['dict'][key]['refs'] + for kref in lref: + # Remove if key was the only associated data + if self._dref['dict'][kref]['ldata'] == [key]: + self.remove_ref(kref) + del self._ddata['dict'][key] + self._ddata['lkey'].remove(key) + self._lkdata.remove(key) + self._complement_dgrouprefdata() + + ########### + # strip dictionaries + ########### + + def _strip_ddata(self, strip=0, verb=0): + pass + + ########### + # _strip and get/from dict + ########### + + @classmethod + def _strip_init(cls): + cls._dstrip['allowed'] = [0, 1] + nMax = max(cls._dstrip['allowed']) + doc = """ + 1: None + """ + 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(DataHolder, self).strip(strip=strip, verb=verb) + + 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}} + return dout + + def _from_dict(self, fd): + self._dgroup.update(**fd['dgroup']) + self._dref.update(**fd['dref']) + self._ddata.update(**fd['ddata']) + self._complement_dgrouprefdata() + + ########### + # properties + ########### + + @property + 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 + if param is None: + return + assert param in self._ddata['lparam'] + assert returnas in [np.ndarray, dict, list] + + # Get output + if returnas == dict: + out = {kk: self._ddata['dict'][kk][param] + for kk in self._ddata['lkey']} + else: + out = [self._ddata['dict'][kk][param] + for kk in self._ddata['lkey']] + 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._ddata['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['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['dict'][key[ii]][param] = values[ii] + + def add_param(self, param, values=None): + assert isinstance(param, str) + 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._ddata['lparam'] + + self._ddata['lparam'].remove(param) + 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): + """ 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['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 Exception as err: + ind[ii, :] = [self._ddata['dict'][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 = ind, lid[ind.nonzero()[0]] + else: + out = ind, np.array([], dtype=int) + else: + out = ind + return out + + 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._ddata['lkey']),), 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._ddata['lkey'][ii] for ii in out] + + elif lc[1]: + if isinstance(key, str): + key = [key] + if returnas == 'key': + out = key + else: + for kk in key: + 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._ddata['lkey'])) + else: + 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): + """ 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 groups + col0 = ['group name', 'nb. ref', 'nb. data'] + 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, + 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 + 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 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], + 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 + + 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 + + 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 + + # --------------------- + # 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 diff --git a/tofu/version.py b/tofu/version.py index cdbdcdf53..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-52-g6f060e3' +__version__ = '1.4.1-139-g31eeb07'