Source code for lightlab.equipment.abstract_drivers.electrical_sources

import numpy as np

from lightlab.util.io import ChannelError, RangeError
from lightlab import logger


[docs]class MultiModalSource(object): ''' Checks modes for sources with multiple ways to specify. Also checks ranges Default class constants come from NI PCI source array ''' supportedModes = {'milliamp', 'amp', 'mwperohm', 'wattperohm', 'volt', 'baseunit'} baseUnitBounds = [0, 1] # Scaled voltage baseToVoltCoef = 10 # This ends up setting the volt bounds v2maCoef = 4 # current (milliamps) = v2maCoef * voltage (volts) exceptOnRangeError = False # If False, it will constrain it and print a warning
[docs] @classmethod def enforceRange(cls, val, mode): ''' Returns clipped value. Raises RangeError ''' bnds = [cls.baseUnit2val(vBnd, mode) for vBnd in cls.baseUnitBounds] enforcedValue = np.clip(val, *bnds) if enforcedValue != val: logger.warning('Warning: value out of range was constrained.\n' 'Requested %s.' 'Allowed range is %s in %s s.', val, bnds, mode) if cls.exceptOnRangeError: if val < min(bnds): violation_direction = 'low' elif val > max(bnds): violation_direction = 'high' else: violation_direction = None raise RangeError('Current sources requested out of range.', violation_direction) return enforcedValue
@classmethod def _checkMode(cls, mode): ''' Returns mode in lower case ''' if mode not in cls.supportedModes: raise TypeError('Invalid mode: ' + str(mode) + '. Valid: ' + str(cls.supportedModes)) return mode.lower()
[docs] @classmethod def val2baseUnit(cls, value, mode): """Converts to the voltage value that will be applied at the PCI board Depends on the current mode state of the instance Args: value (float or dict) """ mode = cls._checkMode(mode) valueWasDict = isinstance(value, dict) if not valueWasDict: value = {-1: value} baseVal = dict() for ch, vEl in value.items(): if mode == 'baseunit': baseVal[ch] = vEl if mode == 'volt': baseVal[ch] = vEl / cls.baseToVoltCoef elif mode == 'milliamp': baseVal[ch] = cls.val2baseUnit(vEl / cls.v2maCoef, 'volt') elif mode == 'amp': baseVal[ch] = cls.val2baseUnit(vEl * 1e3, 'milliamp') elif mode == 'wattperohm': baseVal[ch] = cls.val2baseUnit(np.sign(vEl) * np.sqrt(abs(vEl)), 'amp') elif mode == 'mwperohm': baseVal[ch] = cls.val2baseUnit(vEl / 1e3, 'wattperohm') if valueWasDict: return baseVal else: return baseVal[-1]
[docs] @classmethod def baseUnit2val(cls, baseVal, mode): """Converts the voltage value that will be applied at the PCI board back into the units of th instance This is useful for bounds checking Args: baseVal (float or dict) """ mode = cls._checkMode(mode) baseValWasDict = isinstance(baseVal, dict) if not baseValWasDict: baseVal = {-1: baseVal} value = dict() for ch, bvEl in baseVal.items(): if mode == 'baseunit': value[ch] = bvEl elif mode == 'volt': value[ch] = bvEl * cls.baseToVoltCoef elif mode == 'milliamp': value[ch] = cls.baseUnit2val(bvEl, 'volt') * cls.v2maCoef elif mode == 'amp': value[ch] = cls.baseUnit2val(bvEl, 'milliamp') * 1e-3 elif mode == 'wattperohm': value[ch] = np.sign(bvEl) * (cls.baseUnit2val(bvEl, 'amp')) ** 2 elif mode == 'mwperohm': value[ch] = cls.baseUnit2val(bvEl, 'wattperohm') * 1e3 if baseValWasDict: return value else: return value[-1]
[docs]class MultiChannelSource(object): """ This thing basically holds a dict state and provides some critical methods There is no mode Checks for channel compliance. Handles range exceptions """ maxChannel = None # number of dimensions that the current sources are expecting def __init__(self, useChans=None, **kwargs): if useChans is None: logger.warning('No useChans specified for MultichannelSource') useChans = list() self.useChans = useChans self.stateDict = dict([ch, 0] for ch in self.useChans) # Check that the requested channels are available to be blocked out if self.maxChannel is not None: if any(ch > self.maxChannel - 1 for ch in self.useChans): raise ChannelError( 'Requested channel is more than there are available') super().__init__(**kwargs) @property def elChans(self): ''' Returns the blocked out channels as a list ''' return self.useChans
[docs] def setChannelTuning(self, chanValDict): ''' Sets a number of channel values and updates hardware Args: chanValDict (dict): A dictionary specifying {channel: value} waitTime (float): time in ms to wait after writing, default (None) is defined in the class Returns: (bool): was there a change in value ''' if type(chanValDict) is not dict: raise TypeError( 'The argument for setChannelTuning must be a dictionary') # Check channels for chan in chanValDict.keys(): if chan not in self.stateDict.keys(): raise ChannelError('Channel index not blocked out. ' + 'Requested ' + str(chan) + ', Available ' + str(self.stateDict.keys())) # Change our *internal* state self.stateDict.update(chanValDict)
[docs] def getChannelTuning(self): ''' The inverse of setChannelTuning Args: mode (str): units of the value in ('mwperohm', 'milliamp', 'volt') Returns: (dict): the full state of blocked out channels in units determined by mode argument ''' return self.stateDict.copy()
[docs] def off(self, *setArgs): """Turn all voltages to zero, but maintain the session """ self.setChannelTuning(dict([ch, 0] for ch in self.stateDict.keys()), *setArgs)