ntv-numpy.ntv_numpy.xdataset

Created on Thu Mar 7 09:56:11 2024

@author: a lab in the Air

  1# -*- coding: utf-8 -*-
  2"""
  3Created on Thu Mar  7 09:56:11 2024
  4
  5@author: a lab in the Air
  6"""
  7from abc import ABC, abstractmethod
  8import json
  9from json_ntv import Ntv
 10from ntv_numpy.ndarray import Nutil
 11from ntv_numpy.xndarray import Xndarray
 12from ntv_numpy.xconnector import XarrayConnec, ScippConnec, AstropyNDDataConnec
 13
 14
 15class XdatasetCategory(ABC):
 16    ''' category of Xndarray (dynamic tuple of full_name) - see Xdataset docstring'''
 17
 18    xnd: list = NotImplemented
 19    names: list = NotImplemented
 20
 21    @abstractmethod
 22    def dims(self, var, json_name=False):
 23        '''method defined in Xdataset class'''
 24
 25    @property
 26    def global_vars(self):
 27        '''return a tuple of namedarrays or variable Xndarray full_name'''
 28        return tuple(sorted(nda for nda in self.namedarrays + self.variables))
 29
 30    @property
 31    def data_arrays(self):
 32        '''return a tuple of data_arrays Xndarray full_name'''
 33        return tuple(sorted(nda for nda in self.namedarrays if not nda in self.dimensions))
 34
 35    @property
 36    def dimensions(self):
 37        '''return a tuple of dimensions Xndarray full_name'''
 38        dimable = []
 39        for var in self.variables:
 40            dimable += self.dims(var)
 41        return tuple(sorted(set(nda for nda in dimable if nda in self.namedarrays)))
 42
 43    @property
 44    def coordinates(self):
 45        '''return a tuple of coordinates Xndarray full_name'''
 46        dims = set(self.dimensions)
 47        if not dims:
 48            return ()
 49        return tuple(sorted(set(xnda.name for xnda in self.xnd
 50                                if xnda.xtype == 'variable' and set(xnda.links) != dims)))
 51
 52    @property
 53    def data_vars(self):
 54        '''return a tuple of data_vars Xndarray full_name'''
 55        dims = set(self.dimensions)
 56        if not dims:
 57            return self.variables
 58        return tuple(sorted(xnda.name for xnda in self.xnd
 59                            if xnda.xtype == 'variable' and set(xnda.links) == dims))
 60
 61    @property
 62    def namedarrays(self):
 63        '''return a tuple of namedarray Xndarray full_name'''
 64        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'namedarray'))
 65
 66    @property
 67    def variables(self):
 68        '''return a tuple of variables Xndarray full_name'''
 69        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'variable'))
 70
 71    @property
 72    def undef_vars(self):
 73        '''return a tuple of variables Xndarray full_name with inconsistent shape'''
 74        return tuple(sorted([var for var in self.variables if self[var].shape !=
 75                             [len(self[dim]) for dim in self.dims(var)]]))
 76
 77    @property
 78    def undef_links(self):
 79        '''return a tuple of variables Xndarray full_name with inconsistent links'''
 80        return tuple(sorted([link for var in self.variables for link in self[var].links
 81                             if not link in self.names]))
 82
 83    @property
 84    def masks(self):
 85        '''return a tuple of additional Xndarray full_name with boolean ntv_type'''
 86        return tuple(sorted([xnda.full_name for xnda in self.xnd
 87                             if xnda.xtype == 'additional' and xnda.ntv_type == 'boolean']))
 88
 89    @property
 90    def data_add(self):
 91        '''return a tuple of additional Xndarray full_name with not boolean ntv_type'''
 92        return tuple(sorted([xnda.full_name for xnda in self.xnd
 93                             if xnda.xtype == 'additional' and xnda.ntv_type != 'boolean']))
 94
 95    @property
 96    def metadata(self):
 97        '''return a tuple of metadata Xndarray full_name'''
 98        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'metadata'))
 99
100    @property
101    def additionals(self):
102        '''return a tuple of additionals Xndarray full_name'''
103        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.xtype == 'additional'))
104
105    def var_group(self, name):
106        '''return a tuple of Xndarray full_name with the same name'''
107        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.name == name))
108
109    def add_group(self, add_name):
110        '''return a tuple of Xndarray full_name with the same add_name'''
111        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.add_name == add_name))
112
113
114class XdatasetInterface(ABC):
115    ''' Xdataset interface - see Xdataset docstring'''
116
117    name: str = NotImplemented
118    xnd: list = NotImplemented
119
120    @staticmethod
121    def read_json(jsn, **kwargs):
122        ''' convert json data into a Xdataset.
123
124        *Parameters*
125
126        - **convert** : boolean (default True) - If True, convert json data with
127        non Numpy ntv_type into Xndarray with python type
128        '''
129        option = {'convert': True} | kwargs
130        jso = json.loads(jsn) if isinstance(jsn, str) else jsn
131        value, name = Ntv.decode_json(jso)[:2]
132
133        xnd = [Xndarray.read_json({key: val}, **option)
134               for key, val in value.items()]
135        return Xdataset(xnd, name)
136
137    def to_json(self, **kwargs):
138        ''' convert a Xdataset into json-value.
139
140        *Parameters*
141
142        - **encoded** : Boolean (default False) - json value if False else json text
143        - **header** : Boolean (default True) - including 'xdataset' type
144        - **notype** : list of Boolean (default list of None) - including data type if False
145        - **novalue** : Boolean (default False) - including value if False
146        - **noshape** : Boolean (default True) - if True, without shape if dim < 1
147        - **format** : list of string (default list of 'full') - representation
148        format of the ndarray,
149        '''
150        notype = kwargs['notype'] if ('notype' in kwargs and isinstance(kwargs['notype'], list) and
151                                      len(kwargs['notype']) == len(self)) else [False] * len(self)
152        forma = kwargs['format'] if ('format' in kwargs and isinstance(kwargs['format'], list) and
153                                     len(kwargs['format']) == len(self)) else ['full'] * len(self)
154        noshape = kwargs.get('noshape', True)
155        dic_xnd = {}
156        for xna, notyp, forma in zip(self.xnd, notype, forma):
157            # not_shape = True if len(xna.links) == 1 else noshape
158            dic_xnd |= xna.to_json(notype=notyp, novalue=kwargs.get('novalue', False),
159                                   noshape=noshape, format=forma, header=False)
160        return Nutil.json_ntv(self.name, 'xdataset', dic_xnd,
161                              header=kwargs.get('header', True),
162                              encoded=kwargs.get('encoded', False))
163
164    def to_xarray(self, **kwargs):
165        '''return a DataArray or a Dataset from a Xdataset
166
167        *Parameters*
168
169        - **dataset** : Boolean (default True) - if False and a single data_var, return a DataArray
170        '''
171        return XarrayConnec.xexport(self, **kwargs)
172
173    @staticmethod
174    def from_xarray(xar, **kwargs):
175        '''return a Xdataset from a DataArray or a Dataset'''
176        return XarrayConnec.ximport(xar, Xdataset, **kwargs)
177
178    def to_scipp(self, **kwargs):
179        '''return a sc.DataArray or a sc.Dataset from a Xdataset
180
181        *Parameters*
182
183        - **dataset** : Boolean (default True) - if False and a single data_var,
184        return a DataArray
185        - **datagroup** : Boolean (default True) - if True return a DataGroup with
186        metadata and data_arrays
187        - **ntv_type** : Boolean (default True) - if True add ntv-type to the name
188        '''
189        return ScippConnec.xexport(self, **kwargs)
190
191    @staticmethod
192    def from_scipp(sci, **kwargs):
193        '''return a Xdataset from a scipp object DataArray, Dataset or DataGroup'''
194        return ScippConnec.ximport(sci, Xdataset, **kwargs)
195
196    def to_nddata(self, **kwargs):
197        '''return a NDData from a Xdataset'''
198        return AstropyNDDataConnec.xexport(self, **kwargs)
199
200    @staticmethod
201    def from_nddata(ndd, **kwargs):
202        '''return a Xdataset from a NDData'''
203        return AstropyNDDataConnec.ximport(ndd, Xdataset, **kwargs)
204
205
206class Xdataset(XdatasetCategory, XdatasetInterface):
207    ''' Representation of a multidimensional Dataset
208
209    *Attributes :*
210    - **name** :  String - name of the Xdataset
211    - **xnd**:   list of Xndarray
212
213    *dynamic values (@property)*
214    - `xtype`
215    - `validity`
216    - `dic_xnd`
217    - `partition`
218    - `info`
219
220    *methods*
221    - `parent`
222    - `dims`
223    - `shape_dims`
224    - `to_canonical`
225    - `to_ndarray`
226
227    *XdatasetCategory (@property)*
228    - `names`
229    - `global_vars`
230    - `data_arrays`
231    - `dimensions`
232    - `coordinates`
233    - `data_vars`
234    - `namedarrays`
235    - `variables`
236    - `undef_vars`
237    - `undef_links`
238    - `masks`
239    - `data_add`
240    - `metadata`
241    - `additionals`
242    - `var_group`
243    - `add_group`
244
245    *XdatasetInterface methods *
246    - `read_json` (static)
247    - `to_json`
248    - `from_xarray` (static)
249    - `to_xarray`
250    - `from_scipp` (static)
251    - `to_scipp`
252    - `from_nddata` (static)
253    - `to_nddata`
254    '''
255
256    def __init__(self, xnd=None, name=None):
257        '''Xdataset constructor
258
259            *Parameters*
260
261            - **xnd** : Xdataset/Xndarray/list of Xndarray (default None),
262            - **name** : String (default None) - name of the Xdataset
263        '''
264        self.name = name
265        match xnd:
266            case list():
267                self.xnd = xnd
268            case xdat if isinstance(xdat, Xdataset):
269                self.name = xdat.name
270                self.xnd = xdat.xnd
271            case xnda if isinstance(xnda, Xndarray):
272                self.xnd = [xnda]
273            case _:
274                self.xnd = []
275
276    def __repr__(self):
277        '''return classname and number of value'''
278        return self.__class__.__name__ + '[' + str(len(self)) + ']'
279
280    def __str__(self):
281        '''return json string format'''
282        return json.dumps(self.to_json())
283
284    def __eq__(self, other):
285        '''equal if xnd are equal'''
286        for xnda in self.xnd:
287            if not xnda in other:
288                return False
289        for xnda in other.xnd:
290            if not xnda in self:
291                return False
292        return True
293
294    def __len__(self):
295        '''number of Xndarray'''
296        return len(self.xnd)
297
298    def __contains__(self, item):
299        ''' item of xnd'''
300        return item in self.xnd
301
302    def __getitem__(self, selec):
303        ''' return Xndarray or tuple of Xndarray with selec:
304            - string : name of a xndarray,
305            - integer : index of a xndarray,
306            - index selector : index interval
307            - tuple : names or index '''
308        if selec is None or selec == '' or selec in ([], ()):
309            return self
310        if isinstance(selec, (list, tuple)) and len(selec) == 1:
311            selec = selec[0]
312        if isinstance(selec, tuple):
313            return [self[i] for i in selec]
314        if isinstance(selec, str):
315            return self.dic_xnd[selec]
316        if isinstance(selec, list):
317            return self[selec[0]][selec[1:]]
318        return self.xnd[selec]
319
320    def __delitem__(self, ind):
321        '''remove a Xndarray (ind is index, name or tuple of names).'''
322        if isinstance(ind, int):
323            del self.xnd[ind]
324        elif isinstance(ind, str):
325            del self.xnd[self.names.index(ind)]
326        elif isinstance(ind, tuple):
327            ind_n = [self.names[i] if isinstance(i, int) else i for i in ind]
328            for i in ind_n:
329                del self[i]
330
331    def __copy__(self):
332        ''' Copy all the data '''
333        return self.__class__(self)
334
335    def parent(self, var):
336        '''return the Xndarray parent (where the full_name is equal to the name)'''
337        if var.name in self.names:
338            return self[var.name]
339        return var
340
341    def dims(self, var, json_name=False):
342        '''return the list of parent namedarrays of the links of a Xndarray
343
344        *parameters*
345
346        - **var**: string - full_name of the Xndarray
347        - **json_name**: boolean (defaut False) - if True return json_name else full_name
348        '''
349        if not var in self.names:
350            return None
351        if self[var].add_name and not self[var].links:
352            return self.dims(self[var].name, json_name)
353        if var in self.namedarrays:
354            return [self[var].json_name if json_name else var]
355        if not var in self.variables + self.additionals:
356            return None
357        list_dims = []
358        for link in self[var].links:
359            list_dims += self.dims(link, json_name) if self.dims(link,
360                                                                 json_name) else [link]
361        return list_dims
362
363    def shape_dims(self, var):
364        '''return a shape with the dimensions associated to the var full_name'''
365        return [len(self[dim]) for dim in self.dims(var)
366                ] if set(self.dims(var)) <= set(self.names) else None
367
368    @property
369    def validity(self):
370        '''return the validity state: 'inconsistent', 'undifined' or 'valid' '''
371        for xnda in self:
372            if xnda.mode in ['relative', 'inconsistent']:
373                return 'undefined'
374        if self.undef_links or self.undef_vars:
375            return 'inconsistent'
376        return 'valid'
377
378    @property
379    def xtype(self):
380        '''return the Xdataset type: 'meta', 'group', 'mono', 'multi' '''
381        if self.metadata and not (self.additionals or self.variables or
382                                  self.namedarrays):
383            return 'meta'
384        if self.validity != 'valid':
385            return 'group'
386        match len(self.data_vars):
387            case 0:
388                return 'group'
389            case 1:
390                return 'mono'
391            case _:
392                return 'multi'
393
394    @property
395    def dic_xnd(self):
396        '''return a dict of Xndarray where key is the full_name'''
397        return {xnda.full_name: xnda for xnda in self.xnd}
398
399    @property
400    def names(self):
401        '''return a tuple with the Xndarray full_name'''
402        return tuple(xnda.full_name for xnda in self.xnd)
403
404    @property
405    def partition(self):
406        '''return a dict of Xndarray grouped with category'''
407        dic = {}
408        dic |= {'data_vars': list(self.data_vars)} if self.data_vars else {}
409        dic |= {'data_arrays': list(self.data_arrays)
410                } if self.data_arrays else {}
411        dic |= {'dimensions': list(self.dimensions)} if self.dimensions else {}
412        dic |= {'coordinates': list(self.coordinates)
413                } if self.coordinates else {}
414        dic |= {'additionals': list(self.additionals)
415                } if self.additionals else {}
416        dic |= {'metadata': list(self.metadata)} if self.metadata else {}
417        return dic
418
419    @property
420    def info(self):
421        '''return a dict with Xdataset information '''
422        inf = {'name': self.name, 'xtype': self.xtype} | self.partition
423        inf['validity'] = self.validity
424        inf['length'] = len(self[self.data_vars[0]]) if self.data_vars else 0
425        inf['width'] = len(self)
426        return {key: val for key, val in inf.items() if val}
427
428    def to_canonical(self):
429        '''remove optional links of the included Xndarray'''
430        for name in self.names:
431            if self[name].links in ([self[name].name], [name]):
432                self[name].links = None
433        for add in self.additionals:
434            if self[add].links in [self[self[add].name].links,
435                                   [self[add].name]]:
436                self[add].links = None
437        return self
438
439    def to_ndarray(self, full_name):
440        '''convert a Xndarray from a Xdataset in a np.ndarray'''
441        if self.shape_dims(full_name) is None:
442            data = self[full_name].ndarray
443        else:
444            data = self[full_name].darray.reshape(self.shape_dims(full_name))
445        if data.dtype.name[:8] == 'datetime':
446            data = data.astype('datetime64[ns]')
447        return data
class XdatasetCategory(abc.ABC):
 16class XdatasetCategory(ABC):
 17    ''' category of Xndarray (dynamic tuple of full_name) - see Xdataset docstring'''
 18
 19    xnd: list = NotImplemented
 20    names: list = NotImplemented
 21
 22    @abstractmethod
 23    def dims(self, var, json_name=False):
 24        '''method defined in Xdataset class'''
 25
 26    @property
 27    def global_vars(self):
 28        '''return a tuple of namedarrays or variable Xndarray full_name'''
 29        return tuple(sorted(nda for nda in self.namedarrays + self.variables))
 30
 31    @property
 32    def data_arrays(self):
 33        '''return a tuple of data_arrays Xndarray full_name'''
 34        return tuple(sorted(nda for nda in self.namedarrays if not nda in self.dimensions))
 35
 36    @property
 37    def dimensions(self):
 38        '''return a tuple of dimensions Xndarray full_name'''
 39        dimable = []
 40        for var in self.variables:
 41            dimable += self.dims(var)
 42        return tuple(sorted(set(nda for nda in dimable if nda in self.namedarrays)))
 43
 44    @property
 45    def coordinates(self):
 46        '''return a tuple of coordinates Xndarray full_name'''
 47        dims = set(self.dimensions)
 48        if not dims:
 49            return ()
 50        return tuple(sorted(set(xnda.name for xnda in self.xnd
 51                                if xnda.xtype == 'variable' and set(xnda.links) != dims)))
 52
 53    @property
 54    def data_vars(self):
 55        '''return a tuple of data_vars Xndarray full_name'''
 56        dims = set(self.dimensions)
 57        if not dims:
 58            return self.variables
 59        return tuple(sorted(xnda.name for xnda in self.xnd
 60                            if xnda.xtype == 'variable' and set(xnda.links) == dims))
 61
 62    @property
 63    def namedarrays(self):
 64        '''return a tuple of namedarray Xndarray full_name'''
 65        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'namedarray'))
 66
 67    @property
 68    def variables(self):
 69        '''return a tuple of variables Xndarray full_name'''
 70        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'variable'))
 71
 72    @property
 73    def undef_vars(self):
 74        '''return a tuple of variables Xndarray full_name with inconsistent shape'''
 75        return tuple(sorted([var for var in self.variables if self[var].shape !=
 76                             [len(self[dim]) for dim in self.dims(var)]]))
 77
 78    @property
 79    def undef_links(self):
 80        '''return a tuple of variables Xndarray full_name with inconsistent links'''
 81        return tuple(sorted([link for var in self.variables for link in self[var].links
 82                             if not link in self.names]))
 83
 84    @property
 85    def masks(self):
 86        '''return a tuple of additional Xndarray full_name with boolean ntv_type'''
 87        return tuple(sorted([xnda.full_name for xnda in self.xnd
 88                             if xnda.xtype == 'additional' and xnda.ntv_type == 'boolean']))
 89
 90    @property
 91    def data_add(self):
 92        '''return a tuple of additional Xndarray full_name with not boolean ntv_type'''
 93        return tuple(sorted([xnda.full_name for xnda in self.xnd
 94                             if xnda.xtype == 'additional' and xnda.ntv_type != 'boolean']))
 95
 96    @property
 97    def metadata(self):
 98        '''return a tuple of metadata Xndarray full_name'''
 99        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'metadata'))
100
101    @property
102    def additionals(self):
103        '''return a tuple of additionals Xndarray full_name'''
104        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.xtype == 'additional'))
105
106    def var_group(self, name):
107        '''return a tuple of Xndarray full_name with the same name'''
108        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.name == name))
109
110    def add_group(self, add_name):
111        '''return a tuple of Xndarray full_name with the same add_name'''
112        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.add_name == add_name))

category of Xndarray (dynamic tuple of full_name) - see Xdataset docstring

xnd: list = NotImplemented
names: list = NotImplemented
@abstractmethod
def dims(self, var, json_name=False):
22    @abstractmethod
23    def dims(self, var, json_name=False):
24        '''method defined in Xdataset class'''

method defined in Xdataset class

global_vars
26    @property
27    def global_vars(self):
28        '''return a tuple of namedarrays or variable Xndarray full_name'''
29        return tuple(sorted(nda for nda in self.namedarrays + self.variables))

return a tuple of namedarrays or variable Xndarray full_name

data_arrays
31    @property
32    def data_arrays(self):
33        '''return a tuple of data_arrays Xndarray full_name'''
34        return tuple(sorted(nda for nda in self.namedarrays if not nda in self.dimensions))

return a tuple of data_arrays Xndarray full_name

dimensions
36    @property
37    def dimensions(self):
38        '''return a tuple of dimensions Xndarray full_name'''
39        dimable = []
40        for var in self.variables:
41            dimable += self.dims(var)
42        return tuple(sorted(set(nda for nda in dimable if nda in self.namedarrays)))

return a tuple of dimensions Xndarray full_name

coordinates
44    @property
45    def coordinates(self):
46        '''return a tuple of coordinates Xndarray full_name'''
47        dims = set(self.dimensions)
48        if not dims:
49            return ()
50        return tuple(sorted(set(xnda.name for xnda in self.xnd
51                                if xnda.xtype == 'variable' and set(xnda.links) != dims)))

return a tuple of coordinates Xndarray full_name

data_vars
53    @property
54    def data_vars(self):
55        '''return a tuple of data_vars Xndarray full_name'''
56        dims = set(self.dimensions)
57        if not dims:
58            return self.variables
59        return tuple(sorted(xnda.name for xnda in self.xnd
60                            if xnda.xtype == 'variable' and set(xnda.links) == dims))

return a tuple of data_vars Xndarray full_name

namedarrays
62    @property
63    def namedarrays(self):
64        '''return a tuple of namedarray Xndarray full_name'''
65        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'namedarray'))

return a tuple of namedarray Xndarray full_name

variables
67    @property
68    def variables(self):
69        '''return a tuple of variables Xndarray full_name'''
70        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'variable'))

return a tuple of variables Xndarray full_name

undef_vars
72    @property
73    def undef_vars(self):
74        '''return a tuple of variables Xndarray full_name with inconsistent shape'''
75        return tuple(sorted([var for var in self.variables if self[var].shape !=
76                             [len(self[dim]) for dim in self.dims(var)]]))

return a tuple of variables Xndarray full_name with inconsistent shape

masks
84    @property
85    def masks(self):
86        '''return a tuple of additional Xndarray full_name with boolean ntv_type'''
87        return tuple(sorted([xnda.full_name for xnda in self.xnd
88                             if xnda.xtype == 'additional' and xnda.ntv_type == 'boolean']))

return a tuple of additional Xndarray full_name with boolean ntv_type

data_add
90    @property
91    def data_add(self):
92        '''return a tuple of additional Xndarray full_name with not boolean ntv_type'''
93        return tuple(sorted([xnda.full_name for xnda in self.xnd
94                             if xnda.xtype == 'additional' and xnda.ntv_type != 'boolean']))

return a tuple of additional Xndarray full_name with not boolean ntv_type

metadata
96    @property
97    def metadata(self):
98        '''return a tuple of metadata Xndarray full_name'''
99        return tuple(sorted(xnda.name for xnda in self.xnd if xnda.xtype == 'metadata'))

return a tuple of metadata Xndarray full_name

additionals
101    @property
102    def additionals(self):
103        '''return a tuple of additionals Xndarray full_name'''
104        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.xtype == 'additional'))

return a tuple of additionals Xndarray full_name

def var_group(self, name):
106    def var_group(self, name):
107        '''return a tuple of Xndarray full_name with the same name'''
108        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.name == name))

return a tuple of Xndarray full_name with the same name

def add_group(self, add_name):
110    def add_group(self, add_name):
111        '''return a tuple of Xndarray full_name with the same add_name'''
112        return tuple(sorted(xnda.full_name for xnda in self.xnd if xnda.add_name == add_name))

return a tuple of Xndarray full_name with the same add_name

class XdatasetInterface(abc.ABC):
115class XdatasetInterface(ABC):
116    ''' Xdataset interface - see Xdataset docstring'''
117
118    name: str = NotImplemented
119    xnd: list = NotImplemented
120
121    @staticmethod
122    def read_json(jsn, **kwargs):
123        ''' convert json data into a Xdataset.
124
125        *Parameters*
126
127        - **convert** : boolean (default True) - If True, convert json data with
128        non Numpy ntv_type into Xndarray with python type
129        '''
130        option = {'convert': True} | kwargs
131        jso = json.loads(jsn) if isinstance(jsn, str) else jsn
132        value, name = Ntv.decode_json(jso)[:2]
133
134        xnd = [Xndarray.read_json({key: val}, **option)
135               for key, val in value.items()]
136        return Xdataset(xnd, name)
137
138    def to_json(self, **kwargs):
139        ''' convert a Xdataset into json-value.
140
141        *Parameters*
142
143        - **encoded** : Boolean (default False) - json value if False else json text
144        - **header** : Boolean (default True) - including 'xdataset' type
145        - **notype** : list of Boolean (default list of None) - including data type if False
146        - **novalue** : Boolean (default False) - including value if False
147        - **noshape** : Boolean (default True) - if True, without shape if dim < 1
148        - **format** : list of string (default list of 'full') - representation
149        format of the ndarray,
150        '''
151        notype = kwargs['notype'] if ('notype' in kwargs and isinstance(kwargs['notype'], list) and
152                                      len(kwargs['notype']) == len(self)) else [False] * len(self)
153        forma = kwargs['format'] if ('format' in kwargs and isinstance(kwargs['format'], list) and
154                                     len(kwargs['format']) == len(self)) else ['full'] * len(self)
155        noshape = kwargs.get('noshape', True)
156        dic_xnd = {}
157        for xna, notyp, forma in zip(self.xnd, notype, forma):
158            # not_shape = True if len(xna.links) == 1 else noshape
159            dic_xnd |= xna.to_json(notype=notyp, novalue=kwargs.get('novalue', False),
160                                   noshape=noshape, format=forma, header=False)
161        return Nutil.json_ntv(self.name, 'xdataset', dic_xnd,
162                              header=kwargs.get('header', True),
163                              encoded=kwargs.get('encoded', False))
164
165    def to_xarray(self, **kwargs):
166        '''return a DataArray or a Dataset from a Xdataset
167
168        *Parameters*
169
170        - **dataset** : Boolean (default True) - if False and a single data_var, return a DataArray
171        '''
172        return XarrayConnec.xexport(self, **kwargs)
173
174    @staticmethod
175    def from_xarray(xar, **kwargs):
176        '''return a Xdataset from a DataArray or a Dataset'''
177        return XarrayConnec.ximport(xar, Xdataset, **kwargs)
178
179    def to_scipp(self, **kwargs):
180        '''return a sc.DataArray or a sc.Dataset from a Xdataset
181
182        *Parameters*
183
184        - **dataset** : Boolean (default True) - if False and a single data_var,
185        return a DataArray
186        - **datagroup** : Boolean (default True) - if True return a DataGroup with
187        metadata and data_arrays
188        - **ntv_type** : Boolean (default True) - if True add ntv-type to the name
189        '''
190        return ScippConnec.xexport(self, **kwargs)
191
192    @staticmethod
193    def from_scipp(sci, **kwargs):
194        '''return a Xdataset from a scipp object DataArray, Dataset or DataGroup'''
195        return ScippConnec.ximport(sci, Xdataset, **kwargs)
196
197    def to_nddata(self, **kwargs):
198        '''return a NDData from a Xdataset'''
199        return AstropyNDDataConnec.xexport(self, **kwargs)
200
201    @staticmethod
202    def from_nddata(ndd, **kwargs):
203        '''return a Xdataset from a NDData'''
204        return AstropyNDDataConnec.ximport(ndd, Xdataset, **kwargs)

Xdataset interface - see Xdataset docstring

name: str = NotImplemented
xnd: list = NotImplemented
@staticmethod
def read_json(jsn, **kwargs):
121    @staticmethod
122    def read_json(jsn, **kwargs):
123        ''' convert json data into a Xdataset.
124
125        *Parameters*
126
127        - **convert** : boolean (default True) - If True, convert json data with
128        non Numpy ntv_type into Xndarray with python type
129        '''
130        option = {'convert': True} | kwargs
131        jso = json.loads(jsn) if isinstance(jsn, str) else jsn
132        value, name = Ntv.decode_json(jso)[:2]
133
134        xnd = [Xndarray.read_json({key: val}, **option)
135               for key, val in value.items()]
136        return Xdataset(xnd, name)

convert json data into a Xdataset.

Parameters

  • convert : boolean (default True) - If True, convert json data with non Numpy ntv_type into Xndarray with python type
def to_json(self, **kwargs):
138    def to_json(self, **kwargs):
139        ''' convert a Xdataset into json-value.
140
141        *Parameters*
142
143        - **encoded** : Boolean (default False) - json value if False else json text
144        - **header** : Boolean (default True) - including 'xdataset' type
145        - **notype** : list of Boolean (default list of None) - including data type if False
146        - **novalue** : Boolean (default False) - including value if False
147        - **noshape** : Boolean (default True) - if True, without shape if dim < 1
148        - **format** : list of string (default list of 'full') - representation
149        format of the ndarray,
150        '''
151        notype = kwargs['notype'] if ('notype' in kwargs and isinstance(kwargs['notype'], list) and
152                                      len(kwargs['notype']) == len(self)) else [False] * len(self)
153        forma = kwargs['format'] if ('format' in kwargs and isinstance(kwargs['format'], list) and
154                                     len(kwargs['format']) == len(self)) else ['full'] * len(self)
155        noshape = kwargs.get('noshape', True)
156        dic_xnd = {}
157        for xna, notyp, forma in zip(self.xnd, notype, forma):
158            # not_shape = True if len(xna.links) == 1 else noshape
159            dic_xnd |= xna.to_json(notype=notyp, novalue=kwargs.get('novalue', False),
160                                   noshape=noshape, format=forma, header=False)
161        return Nutil.json_ntv(self.name, 'xdataset', dic_xnd,
162                              header=kwargs.get('header', True),
163                              encoded=kwargs.get('encoded', False))

convert a Xdataset into json-value.

Parameters

  • encoded : Boolean (default False) - json value if False else json text
  • header : Boolean (default True) - including 'xdataset' type
  • notype : list of Boolean (default list of None) - including data type if False
  • novalue : Boolean (default False) - including value if False
  • noshape : Boolean (default True) - if True, without shape if dim < 1
  • format : list of string (default list of 'full') - representation format of the ndarray,
def to_xarray(self, **kwargs):
165    def to_xarray(self, **kwargs):
166        '''return a DataArray or a Dataset from a Xdataset
167
168        *Parameters*
169
170        - **dataset** : Boolean (default True) - if False and a single data_var, return a DataArray
171        '''
172        return XarrayConnec.xexport(self, **kwargs)

return a DataArray or a Dataset from a Xdataset

Parameters

  • dataset : Boolean (default True) - if False and a single data_var, return a DataArray
@staticmethod
def from_xarray(xar, **kwargs):
174    @staticmethod
175    def from_xarray(xar, **kwargs):
176        '''return a Xdataset from a DataArray or a Dataset'''
177        return XarrayConnec.ximport(xar, Xdataset, **kwargs)

return a Xdataset from a DataArray or a Dataset

def to_scipp(self, **kwargs):
179    def to_scipp(self, **kwargs):
180        '''return a sc.DataArray or a sc.Dataset from a Xdataset
181
182        *Parameters*
183
184        - **dataset** : Boolean (default True) - if False and a single data_var,
185        return a DataArray
186        - **datagroup** : Boolean (default True) - if True return a DataGroup with
187        metadata and data_arrays
188        - **ntv_type** : Boolean (default True) - if True add ntv-type to the name
189        '''
190        return ScippConnec.xexport(self, **kwargs)

return a sc.DataArray or a sc.Dataset from a Xdataset

Parameters

  • dataset : Boolean (default True) - if False and a single data_var, return a DataArray
  • datagroup : Boolean (default True) - if True return a DataGroup with metadata and data_arrays
  • ntv_type : Boolean (default True) - if True add ntv-type to the name
@staticmethod
def from_scipp(sci, **kwargs):
192    @staticmethod
193    def from_scipp(sci, **kwargs):
194        '''return a Xdataset from a scipp object DataArray, Dataset or DataGroup'''
195        return ScippConnec.ximport(sci, Xdataset, **kwargs)

return a Xdataset from a scipp object DataArray, Dataset or DataGroup

def to_nddata(self, **kwargs):
197    def to_nddata(self, **kwargs):
198        '''return a NDData from a Xdataset'''
199        return AstropyNDDataConnec.xexport(self, **kwargs)

return a NDData from a Xdataset

@staticmethod
def from_nddata(ndd, **kwargs):
201    @staticmethod
202    def from_nddata(ndd, **kwargs):
203        '''return a Xdataset from a NDData'''
204        return AstropyNDDataConnec.ximport(ndd, Xdataset, **kwargs)

return a Xdataset from a NDData

class Xdataset(XdatasetCategory, XdatasetInterface):
207class Xdataset(XdatasetCategory, XdatasetInterface):
208    ''' Representation of a multidimensional Dataset
209
210    *Attributes :*
211    - **name** :  String - name of the Xdataset
212    - **xnd**:   list of Xndarray
213
214    *dynamic values (@property)*
215    - `xtype`
216    - `validity`
217    - `dic_xnd`
218    - `partition`
219    - `info`
220
221    *methods*
222    - `parent`
223    - `dims`
224    - `shape_dims`
225    - `to_canonical`
226    - `to_ndarray`
227
228    *XdatasetCategory (@property)*
229    - `names`
230    - `global_vars`
231    - `data_arrays`
232    - `dimensions`
233    - `coordinates`
234    - `data_vars`
235    - `namedarrays`
236    - `variables`
237    - `undef_vars`
238    - `undef_links`
239    - `masks`
240    - `data_add`
241    - `metadata`
242    - `additionals`
243    - `var_group`
244    - `add_group`
245
246    *XdatasetInterface methods *
247    - `read_json` (static)
248    - `to_json`
249    - `from_xarray` (static)
250    - `to_xarray`
251    - `from_scipp` (static)
252    - `to_scipp`
253    - `from_nddata` (static)
254    - `to_nddata`
255    '''
256
257    def __init__(self, xnd=None, name=None):
258        '''Xdataset constructor
259
260            *Parameters*
261
262            - **xnd** : Xdataset/Xndarray/list of Xndarray (default None),
263            - **name** : String (default None) - name of the Xdataset
264        '''
265        self.name = name
266        match xnd:
267            case list():
268                self.xnd = xnd
269            case xdat if isinstance(xdat, Xdataset):
270                self.name = xdat.name
271                self.xnd = xdat.xnd
272            case xnda if isinstance(xnda, Xndarray):
273                self.xnd = [xnda]
274            case _:
275                self.xnd = []
276
277    def __repr__(self):
278        '''return classname and number of value'''
279        return self.__class__.__name__ + '[' + str(len(self)) + ']'
280
281    def __str__(self):
282        '''return json string format'''
283        return json.dumps(self.to_json())
284
285    def __eq__(self, other):
286        '''equal if xnd are equal'''
287        for xnda in self.xnd:
288            if not xnda in other:
289                return False
290        for xnda in other.xnd:
291            if not xnda in self:
292                return False
293        return True
294
295    def __len__(self):
296        '''number of Xndarray'''
297        return len(self.xnd)
298
299    def __contains__(self, item):
300        ''' item of xnd'''
301        return item in self.xnd
302
303    def __getitem__(self, selec):
304        ''' return Xndarray or tuple of Xndarray with selec:
305            - string : name of a xndarray,
306            - integer : index of a xndarray,
307            - index selector : index interval
308            - tuple : names or index '''
309        if selec is None or selec == '' or selec in ([], ()):
310            return self
311        if isinstance(selec, (list, tuple)) and len(selec) == 1:
312            selec = selec[0]
313        if isinstance(selec, tuple):
314            return [self[i] for i in selec]
315        if isinstance(selec, str):
316            return self.dic_xnd[selec]
317        if isinstance(selec, list):
318            return self[selec[0]][selec[1:]]
319        return self.xnd[selec]
320
321    def __delitem__(self, ind):
322        '''remove a Xndarray (ind is index, name or tuple of names).'''
323        if isinstance(ind, int):
324            del self.xnd[ind]
325        elif isinstance(ind, str):
326            del self.xnd[self.names.index(ind)]
327        elif isinstance(ind, tuple):
328            ind_n = [self.names[i] if isinstance(i, int) else i for i in ind]
329            for i in ind_n:
330                del self[i]
331
332    def __copy__(self):
333        ''' Copy all the data '''
334        return self.__class__(self)
335
336    def parent(self, var):
337        '''return the Xndarray parent (where the full_name is equal to the name)'''
338        if var.name in self.names:
339            return self[var.name]
340        return var
341
342    def dims(self, var, json_name=False):
343        '''return the list of parent namedarrays of the links of a Xndarray
344
345        *parameters*
346
347        - **var**: string - full_name of the Xndarray
348        - **json_name**: boolean (defaut False) - if True return json_name else full_name
349        '''
350        if not var in self.names:
351            return None
352        if self[var].add_name and not self[var].links:
353            return self.dims(self[var].name, json_name)
354        if var in self.namedarrays:
355            return [self[var].json_name if json_name else var]
356        if not var in self.variables + self.additionals:
357            return None
358        list_dims = []
359        for link in self[var].links:
360            list_dims += self.dims(link, json_name) if self.dims(link,
361                                                                 json_name) else [link]
362        return list_dims
363
364    def shape_dims(self, var):
365        '''return a shape with the dimensions associated to the var full_name'''
366        return [len(self[dim]) for dim in self.dims(var)
367                ] if set(self.dims(var)) <= set(self.names) else None
368
369    @property
370    def validity(self):
371        '''return the validity state: 'inconsistent', 'undifined' or 'valid' '''
372        for xnda in self:
373            if xnda.mode in ['relative', 'inconsistent']:
374                return 'undefined'
375        if self.undef_links or self.undef_vars:
376            return 'inconsistent'
377        return 'valid'
378
379    @property
380    def xtype(self):
381        '''return the Xdataset type: 'meta', 'group', 'mono', 'multi' '''
382        if self.metadata and not (self.additionals or self.variables or
383                                  self.namedarrays):
384            return 'meta'
385        if self.validity != 'valid':
386            return 'group'
387        match len(self.data_vars):
388            case 0:
389                return 'group'
390            case 1:
391                return 'mono'
392            case _:
393                return 'multi'
394
395    @property
396    def dic_xnd(self):
397        '''return a dict of Xndarray where key is the full_name'''
398        return {xnda.full_name: xnda for xnda in self.xnd}
399
400    @property
401    def names(self):
402        '''return a tuple with the Xndarray full_name'''
403        return tuple(xnda.full_name for xnda in self.xnd)
404
405    @property
406    def partition(self):
407        '''return a dict of Xndarray grouped with category'''
408        dic = {}
409        dic |= {'data_vars': list(self.data_vars)} if self.data_vars else {}
410        dic |= {'data_arrays': list(self.data_arrays)
411                } if self.data_arrays else {}
412        dic |= {'dimensions': list(self.dimensions)} if self.dimensions else {}
413        dic |= {'coordinates': list(self.coordinates)
414                } if self.coordinates else {}
415        dic |= {'additionals': list(self.additionals)
416                } if self.additionals else {}
417        dic |= {'metadata': list(self.metadata)} if self.metadata else {}
418        return dic
419
420    @property
421    def info(self):
422        '''return a dict with Xdataset information '''
423        inf = {'name': self.name, 'xtype': self.xtype} | self.partition
424        inf['validity'] = self.validity
425        inf['length'] = len(self[self.data_vars[0]]) if self.data_vars else 0
426        inf['width'] = len(self)
427        return {key: val for key, val in inf.items() if val}
428
429    def to_canonical(self):
430        '''remove optional links of the included Xndarray'''
431        for name in self.names:
432            if self[name].links in ([self[name].name], [name]):
433                self[name].links = None
434        for add in self.additionals:
435            if self[add].links in [self[self[add].name].links,
436                                   [self[add].name]]:
437                self[add].links = None
438        return self
439
440    def to_ndarray(self, full_name):
441        '''convert a Xndarray from a Xdataset in a np.ndarray'''
442        if self.shape_dims(full_name) is None:
443            data = self[full_name].ndarray
444        else:
445            data = self[full_name].darray.reshape(self.shape_dims(full_name))
446        if data.dtype.name[:8] == 'datetime':
447            data = data.astype('datetime64[ns]')
448        return data

Representation of a multidimensional Dataset

Attributes :

  • name : String - name of the Xdataset
  • xnd: list of Xndarray

dynamic values (@property)

methods

XdatasetCategory (@property)

*XdatasetInterface methods *

Xdataset(xnd=None, name=None)
257    def __init__(self, xnd=None, name=None):
258        '''Xdataset constructor
259
260            *Parameters*
261
262            - **xnd** : Xdataset/Xndarray/list of Xndarray (default None),
263            - **name** : String (default None) - name of the Xdataset
264        '''
265        self.name = name
266        match xnd:
267            case list():
268                self.xnd = xnd
269            case xdat if isinstance(xdat, Xdataset):
270                self.name = xdat.name
271                self.xnd = xdat.xnd
272            case xnda if isinstance(xnda, Xndarray):
273                self.xnd = [xnda]
274            case _:
275                self.xnd = []

Xdataset constructor

Parameters

  • xnd : Xdataset/Xndarray/list of Xndarray (default None),
  • name : String (default None) - name of the Xdataset
name = NotImplemented
def parent(self, var):
336    def parent(self, var):
337        '''return the Xndarray parent (where the full_name is equal to the name)'''
338        if var.name in self.names:
339            return self[var.name]
340        return var

return the Xndarray parent (where the full_name is equal to the name)

def dims(self, var, json_name=False):
342    def dims(self, var, json_name=False):
343        '''return the list of parent namedarrays of the links of a Xndarray
344
345        *parameters*
346
347        - **var**: string - full_name of the Xndarray
348        - **json_name**: boolean (defaut False) - if True return json_name else full_name
349        '''
350        if not var in self.names:
351            return None
352        if self[var].add_name and not self[var].links:
353            return self.dims(self[var].name, json_name)
354        if var in self.namedarrays:
355            return [self[var].json_name if json_name else var]
356        if not var in self.variables + self.additionals:
357            return None
358        list_dims = []
359        for link in self[var].links:
360            list_dims += self.dims(link, json_name) if self.dims(link,
361                                                                 json_name) else [link]
362        return list_dims

return the list of parent namedarrays of the links of a Xndarray

parameters

  • var: string - full_name of the Xndarray
  • json_name: boolean (defaut False) - if True return json_name else full_name
def shape_dims(self, var):
364    def shape_dims(self, var):
365        '''return a shape with the dimensions associated to the var full_name'''
366        return [len(self[dim]) for dim in self.dims(var)
367                ] if set(self.dims(var)) <= set(self.names) else None

return a shape with the dimensions associated to the var full_name

validity
369    @property
370    def validity(self):
371        '''return the validity state: 'inconsistent', 'undifined' or 'valid' '''
372        for xnda in self:
373            if xnda.mode in ['relative', 'inconsistent']:
374                return 'undefined'
375        if self.undef_links or self.undef_vars:
376            return 'inconsistent'
377        return 'valid'

return the validity state: 'inconsistent', 'undifined' or 'valid'

xtype
379    @property
380    def xtype(self):
381        '''return the Xdataset type: 'meta', 'group', 'mono', 'multi' '''
382        if self.metadata and not (self.additionals or self.variables or
383                                  self.namedarrays):
384            return 'meta'
385        if self.validity != 'valid':
386            return 'group'
387        match len(self.data_vars):
388            case 0:
389                return 'group'
390            case 1:
391                return 'mono'
392            case _:
393                return 'multi'

return the Xdataset type: 'meta', 'group', 'mono', 'multi'

dic_xnd
395    @property
396    def dic_xnd(self):
397        '''return a dict of Xndarray where key is the full_name'''
398        return {xnda.full_name: xnda for xnda in self.xnd}

return a dict of Xndarray where key is the full_name

names
400    @property
401    def names(self):
402        '''return a tuple with the Xndarray full_name'''
403        return tuple(xnda.full_name for xnda in self.xnd)

return a tuple with the Xndarray full_name

partition
405    @property
406    def partition(self):
407        '''return a dict of Xndarray grouped with category'''
408        dic = {}
409        dic |= {'data_vars': list(self.data_vars)} if self.data_vars else {}
410        dic |= {'data_arrays': list(self.data_arrays)
411                } if self.data_arrays else {}
412        dic |= {'dimensions': list(self.dimensions)} if self.dimensions else {}
413        dic |= {'coordinates': list(self.coordinates)
414                } if self.coordinates else {}
415        dic |= {'additionals': list(self.additionals)
416                } if self.additionals else {}
417        dic |= {'metadata': list(self.metadata)} if self.metadata else {}
418        return dic

return a dict of Xndarray grouped with category

info
420    @property
421    def info(self):
422        '''return a dict with Xdataset information '''
423        inf = {'name': self.name, 'xtype': self.xtype} | self.partition
424        inf['validity'] = self.validity
425        inf['length'] = len(self[self.data_vars[0]]) if self.data_vars else 0
426        inf['width'] = len(self)
427        return {key: val for key, val in inf.items() if val}

return a dict with Xdataset information

def to_canonical(self):
429    def to_canonical(self):
430        '''remove optional links of the included Xndarray'''
431        for name in self.names:
432            if self[name].links in ([self[name].name], [name]):
433                self[name].links = None
434        for add in self.additionals:
435            if self[add].links in [self[self[add].name].links,
436                                   [self[add].name]]:
437                self[add].links = None
438        return self

remove optional links of the included Xndarray

def to_ndarray(self, full_name):
440    def to_ndarray(self, full_name):
441        '''convert a Xndarray from a Xdataset in a np.ndarray'''
442        if self.shape_dims(full_name) is None:
443            data = self[full_name].ndarray
444        else:
445            data = self[full_name].darray.reshape(self.shape_dims(full_name))
446        if data.dtype.name[:8] == 'datetime':
447            data = data.astype('datetime64[ns]')
448        return data

convert a Xndarray from a Xdataset in a np.ndarray