def __array_finalize__(self, obj): # according to numpy documentation: # __array__finalize__(self, obj) is called whenever the system # internally allocates a new array from obj, where obj is a # subclass (subtype) of the (big)ndarray. It can be used to # change attributes of self after construction (so as to ensure # a 2-d matrix for example), or to update meta-information from # the parent. Subclasses inherit a default implementation of # this method that does nothing. if obj is None: return # Define _info attribute # - getattr() searches obj for the '_info' attribute. If the # attribute exists, then it's returned. If the attribute does # NOT exist, then the 3rd arg is returned as a default value. self._info = getattr( obj, '_info', { 'hdf file': None, 'dataset name': None, 'dataset path': None, 'configuration name': None, 'adc': None, 'bit': None, 'sample rate': (None, 'MHz'), 'sample average': None, 'shot average': None, 'board': None, 'channel': None, 'voltage offset': None, 'probe name': None, 'port': (None, None), 'signal units': '', 'added controls': [] }) # Define plasma attribute self._plasma = getattr( obj, '_plasma', { 'Bo': None, 'kT': None, 'kTe': None, 'kTi': None, 'gamma': core.FloatUnit(1.0, 'arb'), 'm_e': core.ME, 'm_i': None, 'n': None, 'n_e': None, 'n_i': None, 'Z': None })
def __array_finalize__(self, obj): # This should only be True during explicit construction # if obj is None: if obj is None or obj.__class__ is np.ndarray: return # Define _info attribute self._info = getattr( obj, "_info", { "source file": None, "device group path": None, "device dataset path": None, "configuration name": None, "adc": None, "bit": None, "clock rate": None, "sample average": None, "shot average": None, "board": None, "channel": None, "voltage offset": None, "probe name": None, "port": (None, None), "signal units": None, "controls": {}, }, ) # Define plasma attribute self._plasma = getattr( obj, "_plasma", { "Bo": None, "kT": None, "kTe": None, "kTi": None, "gamma": core.FloatUnit(1.0, "arb"), "m_e": core.ME, "m_i": None, "n": None, "n_e": None, "n_i": None, "Z": None, }, ) # pragma: no cover
def __array_finalize__(self, obj): # This should only be True during explicit construction # if obj is None: if obj is None or obj.__class__ is np.ndarray: return # Define _info attribute self._info = getattr( obj, '_info', { 'source file': None, 'device group path': None, 'device dataset path': None, 'configuration name': None, 'adc': None, 'bit': None, 'clock rate': None, 'sample average': None, 'shot average': None, 'board': None, 'channel': None, 'voltage offset': None, 'probe name': None, 'port': (None, None), 'signal units': None, 'controls': {}, }) # Define plasma attribute self._plasma = getattr( obj, '_plasma', { 'Bo': None, 'kT': None, 'kTe': None, 'kTi': None, 'gamma': core.FloatUnit(1.0, 'arb'), 'm_e': core.ME, 'm_i': None, 'n': None, 'n_e': None, 'n_i': None, 'Z': None }) # pragma: no cover
def set_plasma_value(self, key, value): # pragma: no cover """ Re-define one of the base plasma values (Bo, gamma, kT, kTe, kTi, m_i, n, n_e, or Z) in the :attr:`plasma` dictionary. :param str key: one of the base plasma values :param value: value for key """ # set plasma value if key == 'Bo': self._plasma['Bo'] = core.FloatUnit(value, 'G') elif key == 'gamma': self._plasma['gamma'] = core.FloatUnit(value, 'arb') elif key in ['kT', 'kTe', 'kTi']: self._plasma[key] = core.FloatUnit(value, 'eV') if key == 'kTe' and self._plasma['kt'] is None: self._plasma['kT'] = self._plasma[key] elif key == 'm_i': self._plasma[key] = core.FloatUnit(value, 'g') elif key in ['n', 'n_e']: self._plasma[key] = core.FloatUnit(value, 'cm^-3') # re-calc n_i and n if key == 'n_e': self._plasma['n_i'] = core.FloatUnit( self._plasma['n_e'] / self._plasma['Z'], 'cm^-3') if self._plasma['n'] is None: self._plasma['n'] = self._plasma['n_e'] elif key == 'Z': self._plasma[key] = core.IntUnit(value, 'arb') # re-calc n_i self._plasma['n_i'] = \ core.FloatUnit(self._plasma['n_e'] / self._plasma['Z'], 'cm^-3') # update key plasma constants self._update_plasma_constants()
def set_plasma_value(self, key, value): # pragma: no cover """ Re-define one of the base plasma values (Bo, gamma, kT, kTe, kTi, m_i, n, n_e, or Z) in the :attr:`plasma` dictionary. :param str key: one of the base plasma values :param value: value for key """ # set plasma value if key == "Bo": self._plasma["Bo"] = core.FloatUnit(value, "G") elif key == "gamma": self._plasma["gamma"] = core.FloatUnit(value, "arb") elif key in ["kT", "kTe", "kTi"]: self._plasma[key] = core.FloatUnit(value, "eV") if key == "kTe" and self._plasma["kt"] is None: self._plasma["kT"] = self._plasma[key] elif key == "m_i": self._plasma[key] = core.FloatUnit(value, "g") elif key in ["n", "n_e"]: self._plasma[key] = core.FloatUnit(value, "cm^-3") # re-calc n_i and n if key == "n_e": self._plasma["n_i"] = core.FloatUnit( self._plasma["n_e"] / self._plasma["Z"], "cm^-3") if self._plasma["n"] is None: self._plasma["n"] = self._plasma["n_e"] elif key == "Z": self._plasma[key] = core.IntUnit(value, "arb") # re-calc n_i self._plasma["n_i"] = core.FloatUnit( self._plasma["n_e"] / self._plasma["Z"], "cm^-3") # update key plasma constants self._update_plasma_constants()
def set_plasma(self, Bo, kTe, kTi, m_i, n_e, Z, gamma=None, **kwargs): # pragma: no cover """ Set :attr:`plasma` and add key frequency, length, and velocity parameters. (all quantities in cgs except temperature is in eV) :param float Bo: magnetic field (in Gauss) :param float kTe: electron temperature (in eV) :param float kTi: ion temperature (in eV) :param float m_i: ion mass (in g) :param float n_e: electron number density (in cm^-3) :param int Z: ion charge number :param float gamma: adiabatic index (arb.) """ # define base values self._plasma['Bo'] = core.FloatUnit(Bo, 'G') self._plasma['kTe'] = core.FloatUnit(kTe, 'eV') self._plasma['kTi'] = core.FloatUnit(kTi, 'eV') self._plasma['m_i'] = core.FloatUnit(m_i, 'g') self._plasma['n_e'] = core.FloatUnit(n_e, 'cm^-3') self._plasma['Z'] = core.IntUnit(Z, 'arb') # define ion number density self._plasma['n_i'] = core.FloatUnit( self._plasma['n_e'] / self._plasma['Z'], 'cm^-3') # define gamma (adiabatic index) # - default = 1.0 if gamma is not None: self._plasma['gamma'] = core.FloatUnit(gamma, 'arb') # define plasma temperature # - if omitted then assumed kTe # TODO: double check assumption if 'kT' in kwargs: self._plasma['kT'] = core.FloatUnit(kwargs['kT'], 'eV') else: self._plasma['kT'] = core.FloatUnit(kTe, 'eV') # define plasma number density # - if omitted then assumed n_e if 'n' in kwargs: self._plasma['n'] = core.FloatUnit(kwargs['n'], 'cm^-3') else: self._plasma['n'] = core.FloatUnit(n_e, 'cm^-3') # add key plasma constants self._update_plasma_constants()
def __new__(cls, hdf_file: File, board: int, channel: int, index=slice(None), shotnum=slice(None), digitizer=None, config_name=None, adc=None, keep_bits=False, add_controls=None, intersection_set=True, **kwargs): """ :param hdf_file: HDF5 file object :param board: analog-digital-converter board number :param channel: analog-digital-converter channel number :param index: dataset row indices to be sliced (overridden by :code:`shotnum`) :type index: Union[int, List[int], slice, numpy.ndarray] :param shotnum: HDF5 file shot number(s) indicating data entries to be extracted (overrides :code:`index`) :type shotnum: Union[int, List[int], slice, numpy.ndarray] :param str digitizer: digitizer name :param str adc: name of analog-digital-converter :param str config_name: name of the digitizer configuration :param bool keep_bits: set :code:`True` to keep data in bits, :code:`False` (DEFAULT) to convert data to voltage :param add_controls: a list indicating the desired control device names and their configuration name (if more than one configuration exists) :type controls: Union[str, Iterable[str, Tuple[str, Any]]] :param bool intersection_set: :code:`True` (DEFAULT) will force the returned shot numbers to be the intersection of :data:`shotnum` and the shot numbers contained in each control device and digitizer dataset. :code:`False` will return the union of shot numbers. Behavior of :data:`index`, :data:`shotnum` and :data:`intersection_set`: .. note:: * The :data:`shotnum` keyword will always override the :data:`index` keyword, but, due to extra overhead required for identifying shot number locations in the digitizer dataset, the :data:`index` keyword will always execute quicker than the :data:`shotnum` keyword. """ # initialize timing tt = [] if 'timeit' in kwargs: # pragma: no cover timeit = kwargs['timeit'] if timeit: tt.append(time.time()) else: timeit = False else: timeit = False # ---- Condition hdf_file ---- # - `hdf_file` is a lapd.File object # if not isinstance(hdf_file, File): raise TypeError("`hdf_file` is NOT type `" + File.__module__ + "." + File.__qualname__ + "`") # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - `hdf_file` conditioning: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Examine file map object ---- # grab instance of `HDFMap` _fmap = hdf_file.file_map # ---- Condition `add_controls` ---- # Check for non-empty controls if bool(add_controls) and not bool(_fmap.controls): raise ValueError('There are no control devices in the HDF5 file.') # condition controls if bool(add_controls): controls = condition_controls(hdf_file, add_controls) else: controls = [] # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - `add_controls` conditioning: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Condition `digitizer` keyword ---- if not bool(_fmap.digitizers): raise ValueError("There are no digitizers in the HDF5 file.") elif digitizer is None: if not bool(_fmap.main_digitizer): raise ValueError("No main digitizer is identified..." "need to specify `digitizer` kwarg") why = ("Digitizer not specified so assuming the " "'main_digitizer' " "({})".format(_fmap.main_digitizer.device_name) + " defined in the mappings.") warn(why) _dmap = _fmap.main_digitizer else: try: _dmap = _fmap.digitizers[digitizer] except KeyError: raise ValueError("Specified Digitizer '{}'".format(digitizer) + " is not among known digitizers " "({})".format(list(_fmap.digitizers))) # ---- Gather Digi Dataset Info ---- # # Note: _dmap.construct_dataset_name has conditioning for # board, channel, adc, and # # dname - digitizer dataset name # dhname - digitizer header dataset name # dpath - full path to digitizer group # dset - digitizer h5py.Dataset object # dheader - dset associated header dataset # shotnumkey - field name for shot number column in dheader # # Build kwargs for construct_dataset_name() kwargs = {'return_info': True} if config_name is not None: kwargs['config_name'] = config_name if adc is not None: kwargs['adc'] = adc # Get datasets dname, d_info = _dmap.construct_dataset_name(board, channel, **kwargs) dhname = _dmap.construct_header_dataset_name(board, channel, **kwargs) dpath = _dmap.info['group path'] + '/' dset = hdf_file.get(dpath + dname) dheader = hdf_file.get(dpath + dhname) # define `config_name` if config_name is None: config_name = _dmap.active_configs[0] # define `shotnumkey` shotnumkey = \ _dmap.configs[config_name]['shotnum']['dset field'][0] # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - get dset and dheader: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Condition shots, index, and shotnum ---- # index -- row index of digitizer dataset # ~ indexed at 0 # ~ supersedes any other indexing keywords # shotnum -- global HDF5 file shot number # ~ this is the index used to link values between # datasets # ~ overridden by `index` # # Through conditioning the following are (re-)defined # index -- row index of digitizer dataset (dset) # ~ numpy.ndarray # ~ dtype = np.integer # ~ shape = (num_of_indices,) # # shotnum -- global HDF5 shot numbers # ~ index at 1 # ~ will be a filtered version of input kwarg shotnum # based on intersection_set # ~ numpy.ndarray # ~ dtype = np.uint32 # ~ shape = (sn_size, ) # # sni -- bool array for providing a one-to-one mapping # between shotnum and index # ~ shotnum[sni] = dheader[index, shotnumkey] # ~ data['signal'][sni, ...] = dset[index, ...] # ~ data['singal'][np.logical_not(sni), ...] = np.nan # ~ numpy.ndarray # ~ dtype = np.bool # ~ shape = (sn_size, ) # ~ np.count_nonzero(arr[0,...]) = num_of_indices # # - Indexing behavior: (depends on intersection_set) # # ~ intersection_set = True (DEFAULT) # * the returned array will only contain shot numbers that # are in the intersection of shotnum, the digitizer # dataset, and all the specified control device datasets # # ~ intersection_set = False # * the returned array will contain all shot numbers # specified by shotnum (>= 1) # * if a dataset does not included a shot number contained # in shotnum, then its entry in the returned array will # be given a NULL value depending on the dtype # # Determine if indexing w.r.t. `index` or `shotnum` index_with = 'index' if isinstance(index, slice): if index == slice(None): if not isinstance(shotnum, slice): index_with = 'shotnum' elif shotnum != slice(None): index_with = 'shotnum' # Condition `index` and `shotnum` keywords # - Valid indexing types are: int, list(int), slice(), and # np.ndarray # if index_with == 'index': # Condition `index` keyword # # Note: I'm letting the slicing of dset[index, shotnumkey] # throw the appropriate errors # # Define `shotnum` # - Note: h5py datasets can NOT be sliced using numpy arrays # # convert `index` to np.ndarray sn_size = dheader.size if isinstance(index, int): index = np.array([index], dtype=np.int32) elif isinstance(index, list): index = np.array(index, dtype=np.int32) elif isinstance(index, slice): start, stop, step = index.indices(sn_size) index = np.arange(start, stop, step, dtype=np.int32) elif isinstance(index, type(Ellipsis)): index = np.arange(0, sn_size, 1, dtype=np.int32) elif isinstance(index, np.ndarray): pass else: raise TypeError("Valid `index` type not passed.") # convert (VALID) negative indices to positive neg_index_mask = np.where((index < 0) & (index >= -sn_size), True, False) if np.any(neg_index_mask): adj_ii = index[neg_index_mask] % sn_size index[neg_index_mask] = adj_ii index = np.unique(index) # define `shotnum` shotnum = dheader[index.tolist(), shotnumkey] # define sni sni = np.ones(shotnum.shape[0], dtype=np.bool) # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - condition index: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) else: # Condition `shotnum` keyword # # convert `shotnum` to np.ndarray ''' if isinstance(shotnum, slice): # determine largest possible shot number last_sn = dheader[-1, shotnumkey] if shotnum.stop is not None: stop_sn = max(shotnum.stop, last_sn + 1) else: stop_sn = last_sn + 1 # get the start, stop, and step for the shot number # array start, stop, step = shotnum.indices(stop_sn) # determine smallest possible shot number # - intersection_set = True # * start = max of first_sn and shotnum.start # - intersection_set = False # * start = min of first_sn and shotnum.start first_sn = [dheader[0, shotnumkey]] if shotnum.start is not None: # ensure shot numbers are >= 1 if start <= 0: start = 1 else: # start wasn't specified in slice object start = min(first_sn) # adjust start for intersection_set if intersection_set: first_sn.append(start) start = max(first_sn) # re-define shotnum as a list shotnum = np.arange(start, stop, step).tolist() elif isinstance(shotnum, int): shotnum = [shotnum] elif isinstance(shotnum, list): # ensure all elements are int if not all(isinstance(sn, int) for sn in shotnum): raise ValueError('Valid `shotnum` not passed') else: raise ValueError('Valid `shotnum` not passed') ''' # perform `shotnum` conditioning # - `shotnum` is returned as a numpy array shotnum = condition_shotnum(shotnum, {'digi': dheader}, {'digi': shotnumkey}) # Calc. the corresponding `index` and `sni` # - `shotnum` will be converted from list to np.array # - `index` and `sni` will be np.array's ''' index, shotnum, sni = \ condition_shotnum(shotnum, dheader, shotnumkey, intersection_set) ''' index, sni = build_sndr_for_simple_dset(shotnum, dheader, shotnumkey) # perform intersection if intersection_set: shotnum, sni_dict, index_dict = \ do_shotnum_intersection(shotnum, {'digi': sni}, {'digi': index}) sni = sni_dict['digi'] index = index_dict['digi'] # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - condition shotnum: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Retrieve Control Data ---- # 1. retrieve the numpy array for control data # 2. re-filter shotnum if intersection_set=True s.t. only # shotnum's w/ control data are returned # # grab control device dataset # # - this will ensure cdata.shape == data.shape all the time # - shotnum should always be a ndarray at this point # if len(controls) != 0: cdata = HDFReadControls(hdf_file, controls, assume_controls_conditioned=True, shotnum=shotnum, intersection_set=intersection_set) # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - read in cdata (control data): ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # re-filter index, shotnum, and sni # - only need to be filtered if intersection_set=True # - for intersection_set=True, shotnum and index are # one-to-one # if intersection_set: new_sn_mask = np.isin(shotnum, cdata['shotnum']) shotnum = shotnum[new_sn_mask] index = index[new_sn_mask] sni = np.ones(shotnum.shape[0], dtype=bool) else: cdata = None # ---- Build `obj` ---- # Define dtype and shape # - 1st column of the digi data header contains the global HDF5 # file shot number # - shotkey = is the field name/key of the dheader shot number # column sigtype = np.float32 if not keep_bits else dset.dtype shape = shotnum.shape dtype = [('shotnum', np.uint32, 1), ('signal', sigtype, dset.shape[1]), ('xyz', np.float32, 3)] if len(controls) != 0: for subdtype in cdata.dtype.descr: if subdtype[0] not in [d[0] for d in dtype]: dtype.append(subdtype) # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - define dtype: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # Initialize data array data = np.empty(shape, dtype=dtype) # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - initialize data np.ndarray: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # fill 'shotnum' field of data array data['shotnum'] = shotnum # fill 'signal' fields of data array index = index.tolist() if intersection_set: # fill signal data['signal'] = dset[index, ...] else: # fill signal data['signal'][sni] = dset[index, ...] if np.issubdtype(data['signal'].dtype, np.integer): data['signal'][np.logical_not(sni)] = 0 else: # dtype is np.floating data['signal'][np.logical_not(sni)] = np.nan # fill fields related to controls if len(controls) != 0: # Note: shot numbers of cdata and data are one-to-one # by this point so intersection_set is irrelevant # if not np.array_equal(data['shotnum'], cdata['shotnum']): # pragma: no cover # this should never happen raise ValueError("data['shotnum'] and cdata['shotnum'] are not" " equal") # fill xyz if 'xyz' in cdata.dtype.names: data['xyz'] = cdata['xyz'] else: data['xyz'] = np.nan # fill remaining controls for field in cdata.dtype.names: if field not in ('shotnum', 'xyz'): data[field] = cdata[field] else: # fill xyz data['xyz'] = np.nan # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - fill data array: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # Define obj to be returned obj = data.view(cls) # get voltage offset try: voffset = dheader[0, 'Offset'] * u.volt except ValueError: warn("Digitizer header dataset is missing the voltage " "'Offset' field. ") voffset = None # assign dataset meta-info obj._info = { 'source file': os.path.abspath(hdf_file.filename), 'device group path': _dmap.info['group path'], 'device dataset path': dpath + dname, 'digitizer': d_info['digitizer'], 'configuration name': d_info['configuration name'], 'adc': d_info['adc'], 'bit': d_info['bit'], 'clock rate': d_info['clock rate'], 'sample average': d_info['sample average (hardware)'], 'shot average': d_info['shot average (software)'], 'board': board, 'channel': channel, 'voltage offset': voffset, 'probe name': None, 'port': (None, None), 'signal units': u.bit, } if cdata is not None: obj._info['controls'] = \ copy.deepcopy(cdata.info['controls']) else: obj._info['controls'] = {} # plasma parameter dict obj._plasma = { 'Bo': None, 'kT': None, 'kTe': None, 'kTi': None, 'gamma': core.FloatUnit(1.0, 'arb'), 'm_e': core.ME, 'm_i': None, 'n': None, 'n_e': None, 'n_i': None, 'Z': None } # pragma: no cover # convert to voltage # - 'signal' dtype is assigned based on keep_bit # # obj['signal'] = obj['signal'].astype(np.float32, copy=False) # if not keep_bits: if obj.dv is None: warn("Unable to calculated voltage step size..." "'signal' remains as bits") else: # define offset offset = abs(obj.info['voltage offset'].value) # calc voltage obj['signal'] = (obj.dv.value * obj['signal']) - offset # update 'signal units' obj._info['signal units'] = u.volt # print execution timing if timeit: # pragma: no cover tt.append(time.time()) print('tt - execution time: ' '{} ms'.format((tt[-1] - tt[0]) * 1.E3)) # return obj return obj
def set_plasma(self, Bo, kTe, kTi, m_i, n_e, Z, gamma=None, **kwargs): # pragma: no cover """ Set :attr:`plasma` and add key frequency, length, and velocity parameters. (all quantities in cgs except temperature is in eV) :param float Bo: magnetic field (in Gauss) :param float kTe: electron temperature (in eV) :param float kTi: ion temperature (in eV) :param float m_i: ion mass (in g) :param float n_e: electron number density (in cm^-3) :param int Z: ion charge number :param float gamma: adiabatic index (arb.) """ # define base values self._plasma["Bo"] = core.FloatUnit(Bo, "G") self._plasma["kTe"] = core.FloatUnit(kTe, "eV") self._plasma["kTi"] = core.FloatUnit(kTi, "eV") self._plasma["m_i"] = core.FloatUnit(m_i, "g") self._plasma["n_e"] = core.FloatUnit(n_e, "cm^-3") self._plasma["Z"] = core.IntUnit(Z, "arb") # define ion number density self._plasma["n_i"] = core.FloatUnit( self._plasma["n_e"] / self._plasma["Z"], "cm^-3") # define gamma (adiabatic index) # - default = 1.0 if gamma is not None: self._plasma["gamma"] = core.FloatUnit(gamma, "arb") # define plasma temperature # - if omitted then assumed kTe # TODO: double check assumption if "kT" in kwargs: self._plasma["kT"] = core.FloatUnit(kwargs["kT"], "eV") else: self._plasma["kT"] = core.FloatUnit(kTe, "eV") # define plasma number density # - if omitted then assumed n_e if "n" in kwargs: self._plasma["n"] = core.FloatUnit(kwargs["n"], "cm^-3") else: self._plasma["n"] = core.FloatUnit(n_e, "cm^-3") # add key plasma constants self._update_plasma_constants()
def __new__(cls, hdf_file, board, channel, index=slice(None), shotnum=slice(None), digitizer=None, adc=None, config_name=None, keep_bits=False, add_controls=None, intersection_set=True, silent=False, **kwargs): """ When inheriting from numpy, the object creation and initialization is handled by __new__ instead of __init__. :param hdf_file: object instance of the HDF5 file :type hdf_file: :class:`bapsflib.lapdhdf.files.File` :param int board: board number of data to be extracted :param int channel: channel number of data to be extracted :param index: row index/indices of dataset to be extracted (overridden by :code:`shotnum`) :type index: :code:`None`, int, list(int), or slice() :param shotnum: global HDF5 shot number (overrides :code:`index`) :type shotnum: :code:`None`, int, list(int), or slice() :param str digitizer: name of digitizer for which board and channel belong to :param str adc: name of analog-digital-converter in the digitizer for which board and channel belong to :param str config_name: name of the digitizer configuration to be used :param bool keep_bits: set :code:`True` to keep data in bits, :code:`False` (default) convert data to voltage :param add_controls: list of control devices whose data will be matched with the digitizer data :type add_controls: list of strings and/or 2-element tuples. If an element is a string, then the string is the control device name. If an element is a 2-element tuple, then tuple[0] is the control device name and tuple[1] is a unique specifier for that control device. :param bool intersection_set: :param bool silent: set :code:`True` to suppress command line print out of soft warnings .. note:: Keyword :code:`shots` was renamed to :code:`index` in version 0.1.3.dev1. Keyword :code:`shots` will still work, but will be deprecated in the future. """ # # numpy uses __new__ to initialize objects, so an __init__ is # not necessary # # initialize timing tt = [] if 'timeit' in kwargs: timeit = kwargs['timeit'] if timeit: tt.append(time.time()) else: timeit = False else: timeit = False # initiate warning string warn_str = '' # ---- Condition hdf_file ---- # Check hdf_file is a lapdhdf.File object try: file_map = hdf_file.file_map except AttributeError: raise AttributeError('hdf_file needs to be of type lapdhdf.File') # Condition digitizer keyword if digitizer is None: warn_str = "** Warning: Digitizer not specified so " \ + "assuming the 'main_digitizer' ({})".format( file_map.main_digitizer.info[ 'group name']) \ + " defined in the mappings." digi_map = file_map.main_digitizer else: try: digi_map = hdf_file.file_map.digitizers[digitizer] except KeyError: raise ValueError('Specified Digitizer is not among ' 'known digitizers') # print execution timing if timeit: tt.append(time.time()) print('tt - hdf_file conditioning: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Check for Control Device Addition --- # condition controls if add_controls is not None: controls = condition_controls(hdf_file, add_controls, silent=silent) # check controls is not empty if not controls: warn_str = '\n** Warning: no valid controls passed, ' \ 'none added to array' controls = [] else: controls = [] # print execution timing if timeit: tt.append(time.time()) print('tt - add_controls conditioning: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Gather Digi Dataset Info ---- # # Note: digi_map.construct_dataset_name has conditioning for # board, channel, adc, and # # dname - digitizer dataset name # dhname - digitizer header dataset name # dpath - full path to digitizer group # dset - digitizer h5py.Dataset object (data is still on # disk) # dheader - header dataset for the digitizer dataset, this # has the shot number values # shotnumkey - field name for shot number column in the digi # header dataset (dheader) # # Build kwargs for construct_dataset_name() kwargs = {'return_info': True, 'silent': silent} if config_name is not None: kwargs['config_name'] = config_name if adc is not None: kwargs['adc'] = adc # Get dataset dname, d_info = digi_map.construct_dataset_name( board, channel, **kwargs) dhname = digi_map.construct_header_dataset_name( board, channel, **kwargs) dpath = digi_map.info['group path'] + '/' dset = hdf_file.get(dpath + dname) dheader = hdf_file.get(dpath + dhname) shotnumkey = digi_map.shotnum_field # print execution timing if timeit: tt.append(time.time()) print('tt - get dset and dheader: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Condition `keep_bits` ---- if 'Offset' not in dheader.dtype.names: # there's no voltage offset value to calculate dv if not keep_bits: warn('Could not find voltage offset, calculating ' 'voltage without offset') # force keep_bits True keep_bits = True # print execution timing if timeit: tt.append(time.time()) print('tt - condition keep_bits kwarg: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Condition shots, index, and shotnum ---- # shots -- same as index (legacy, do NOT use) # ~ overridden by index and shotnum # ~ this is kept for backwards compatibility # ~ 'shots' was renamed to 'index' in v0.1.3dev1 # index -- row index of digitizer dataset # ~ indexed at 0 # ~ supersedes any other indexing keywords # shotnum -- global HDF5 file shot number # ~ this is the index used to link values between # datasets # ~ overridden by `index` # # Through conditioning the following are defined (whether index # or shotnum is given): # index -- row index of digitizer dataset (dset) # ~ @ start: int, list(int), or slice # ~ @ end: np.array with dtype == np.integer # shotnum -- int array of global HDF5 shot numbers to be added # to data # ~ data['shotnum'] = shotnum # ~ @ start: int, list(int), or slice # ~ @ end: np.array with dtype == np.integer # sni -- array for mapping index to shotnum # ~ data['shotnum'][sni] = dheader[index, shotnumkey] # ~ data['signal'][sni, ...] = dset[index, ...] # ~ data['singal'][np.logical_not(sni), ...] = np.nan # ~ @ start: not defined # ~ @ end: np.array with dtype == np.bool # # - Indexing behavior: (depends on intersection_set) # # ~ intersection_set is only considered if add_controls # is not None # # ~ intersection_set = True (DEFAULT) # * will ensure the returned array will only contain shot # numbers (shotnum) that has data in the digitizer dataset # and all specified control device datasets # * index # > will be the row index of the digitizer dataset # > may be trimmed to enforce shot number intersection of # all datasets # * shotnum # > will be the desired global shot numbers # > may be trimmed to enforce shot number intersection of # all datasets # # ~ intersection_set = False # * does not enforce shot number intersection of all # datasets. Instead, if a dataset does not include a # specified shot number, then that entry will be given a # numpy.nan value # * index # > will be the row index of the digitizer dataset # * shotnum # > will be the desired global shot numbers # # rename 'shots' to 'index' if 'shots' in kwargs and index is None: index = kwargs['shots'] # Determine if indexing w.r.t. `index` or `shotnum` index_with = 'shotnum' \ if shotnum != slice(None) and index == slice(None)\ else 'index' # Condition `index` and `shotnum` keywords # - Valid indexing types are: int, list(int), and slice() # if index_with == 'index': # Condition `index` keyword # # Note: I'm letting the slicing of dset[index, shotnumkey] # to throw the appropriate errors # # Define `shotnum` shotnum = dheader[index, shotnumkey].view() if shotnum.shape == () and shotnum.size == 1: shotnum = np.array([shotnum]).view() # define sni sni = np.ones(shotnum.shape[0], dtype=bool) # convert `index` to np.ndarray if type(index) is int: index = np.array([index]) elif type(index) is list: index = np.array(index) elif type(index) is slice: start, stop, step = index.indices(dheader.shape[0]) index = np.arange(start, stop, step) # print execution timing if timeit: tt.append(time.time()) print('tt - condition index: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) else: # Condition `shotnum` keyword # # convert `shotnum` to list if isinstance(shotnum, slice): # determine largest possible shot number last_sn = dheader[-1, shotnumkey] if shotnum.stop is not None: stop_sn = max(shotnum.stop, last_sn + 1) else: stop_sn = last_sn + 1 # get the start, stop, and step for the shot number # array start, stop, step = shotnum.indices(stop_sn) # determine smallest possible shot number # - intersection_set = True # * start = max of first_sn and shotnum.start # - intersection_set = False # * start = min of first_sn and shotnum.start first_sn = [dheader[0, shotnumkey]] if shotnum.start is not None: # ensure shot numbers are >= 1 if start <= 0: start = 1 else: # start wasn't specified in slice object start = min(first_sn) # adjust start for intersection_set if intersection_set: first_sn.append(start) start = max(first_sn) # re-define shotnum as a list shotnum = np.arange(start, stop, step).tolist() elif isinstance(shotnum, int): shotnum = [shotnum] elif isinstance(shotnum, list): # ensure all elements are int if not all(isinstance(sn, int) for sn in shotnum): raise ValueError('Valid `shotnum` not passed') else: raise ValueError('Valid `shotnum` not passed') # Calc. the corresponding `index` and `sni` # - `shotnum` will be converted from list to np.array # - `index` and `sni` will be np.array's index, shotnum, sni = \ condition_shotnum(shotnum, dheader, shotnumkey, intersection_set) # print execution timing if timeit: tt.append(time.time()) print('tt - condition shotnum: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # ---- Retrieve Control Data --- # 1. retrieve the numpy array for control data # 2. re-filter shotnum if intersection_set=True s.t. only # shotnum's w/ control data are returned # # grab control device dataset # # - this will ensure cdata.shape == data.shape all the time # - shotnum should always be a ndarray at this point # if len(controls) != 0: cdata = hdfReadControl(hdf_file, controls, assume_controls_conditioned=True, shotnum=shotnum.tolist(), intersection_set=intersection_set, silent=silent) # print execution timing if timeit: tt.append(time.time()) print('tt - read in cdata (control data): ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # re-filter index, shotnum, and sni # - only need to be filtered if intersection_set=True # - for intersection_set=True, shotnum and index are # one-to-one # if intersection_set: new_sn_mask = np.isin(shotnum, cdata['shotnum']) if True not in new_sn_mask: raise ValueError( 'Input shotnum would result in a null array') else: shotnum = shotnum[new_sn_mask] index = index[new_sn_mask] sni = np.ones(shotnum.shape[0], dtype=bool) else: cdata = None # ---- Construct obj --- # - obj will be a numpy record array # # Define dtype for obj # - 1st column of the digi data header contains the global HDF5 # file shot number # - shotkey = is the field name/key of the dheader shot number # column sigtype = '<f4' if not keep_bits else dset.dtype shape = shotnum.shape[0] dtype = [('shotnum', '<u4'), ('signal', sigtype, dset.shape[1]), ('xyz', '<f4', 3)] if len(controls) != 0: for subdtype in cdata.dtype.descr: if subdtype[0] not in [d[0] for d in dtype]: dtype.append(subdtype) # Define numpy array data = np.empty(shape, dtype=dtype) # print execution timing if timeit: tt.append(time.time()) print('tt - define data: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # make sure index is not an ndarray if type(index) is np.ndarray: index = index.tolist() # fill 'shotnum' field of data array data['shotnum'] = shotnum # fill 'signal' fields of data array if intersection_set: # fill signal data['signal'] = dset[index, :] else: # fill signal data['signal'][sni] = dset[index, :] if np.issubdtype(data['signal'].dtype, np.integer): data['signal'][np.logical_not(sni)] = -99999 else: # dtype is np.floating data['signal'][np.logical_not(sni)] = np.nan # fill fields related to controls if len(controls) != 0: # Note: shot numbers of cdata and data are one-to-one # by this point so intersection_set is irrelevant # if not np.array_equal(data['shotnum'], cdata['shotnum']): raise ValueError("data['shotnum'] and cdata['shotnum'] are not" " equal") # fill xyz if 'xyz' in cdata.dtype.names: data['xyz'] = cdata['xyz'] else: data['xyz'] = np.nan # fill remaining controls for field in cdata.dtype.names: if field not in ['shotnum', 'xyz']: data[field] = cdata[field] else: # fill xyz data['xyz'] = np.nan # print execution timing if timeit: tt.append(time.time()) print('tt - fill data array: ' '{} ms'.format((tt[-1] - tt[-2]) * 1.E3)) # Define obj to be returned obj = data.view(cls) # get voltage offset try: voffset = dheader[0, 'Offset'] except ValueError: voffset = None # assign dataset meta-info obj._info = { 'hdf file': hdf_file.filename.split('/')[-1], 'dataset name': dname, 'dataset path': dpath, 'digitizer': d_info['digitizer'], 'configuration name': d_info['configuration name'], 'adc': d_info['adc'], 'bit': d_info['bit'], 'sample rate': d_info['sample rate'], 'sample average': d_info['sample average (hardware)'], 'shot average': d_info['shot average (software)'], 'board': board, 'channel': channel, 'voltage offset': voffset, 'probe name': None, 'port': (None, None), 'signal units': 'bits', 'added controls': controls } # plasma parameter dict obj._plasma = { 'Bo': None, 'kT': None, 'kTe': None, 'kTi': None, 'gamma': core.FloatUnit(1.0, 'arb'), 'm_e': core.ME, 'm_i': None, 'n': None, 'n_e': None, 'n_i': None, 'Z': None } # convert to voltage # - 'signal' dtype is assigned based on keep_bit # # obj['signal'] = obj['signal'].astype(np.float32, copy=False) # if not keep_bits: # define offset offset = abs(obj.info['voltage offset']) # calc voltage obj['signal'] = (obj.dv * obj['signal']) - offset # update 'signal units' obj._info['signal units'] = 'V' # print warnings if not silent and warn_str != '': print(warn_str) # print execution timing if timeit: tt.append(time.time()) print('tt - execution time: ' '{} ms'.format((tt[-1] - tt[0]) * 1.E3)) # return obj return obj