diff --git a/docs/conf.py b/docs/conf.py index 8d24a259..e1513c05 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,9 @@ 'sphinx.ext.viewcode', ] +# Include both class and __init__() docstrings. +autoclass_content = 'both' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/pytac/device.py b/pytac/device.py index 5121997a..6fb160e1 100644 --- a/pytac/device.py +++ b/pytac/device.py @@ -19,23 +19,20 @@ class Device(object): """ def __init__(self, name, cs, enabled=True, rb_suffix=None, sp_suffix=None): - """Device constructor. - + """ Args: name: prefix of EPICS PVs for this device cs (ControlSystem): Control system object used to get and set the value of a pv. enabled (bool-like): Whether the device is enabled. May be a PvEnabler object. - rb_suffix (string): suffix of EPICS readback pv - sp_suffix (string): suffix of EPICS setpoint pv + rb_suffix (str): suffix of EPICS readback pv + sp_suffix (str): suffix of EPICS setpoint pv """ self.name = name self._cs = cs - if rb_suffix is not None: - self.rb_pv = name + rb_suffix - if sp_suffix is not None: - self.sp_pv = name + sp_suffix + self.rb_pv = name + rb_suffix if rb_suffix is not None else None + self.sp_pv = name + sp_suffix if sp_suffix is not None else None self._enabled = True def is_enabled(self): @@ -50,16 +47,15 @@ def put_value(self, value): """Set the device value. Args: - value(Number): The value to set on the pv. + value (float): The value to set on the pv. Raises: PvException: An exception occured when no setpoint pv exists. """ - try: - self._cs.put(self.sp_pv, value) - except AttributeError: + if self.sp_pv is None: raise PvException("""This device {0} has no setpoint pv.""" .format(self.name)) + self._cs.put(self.sp_pv, value) def get_value(self, handle): """Read the value of a readback or setpoint pv. @@ -67,11 +63,11 @@ def get_value(self, handle): If neither readback or setpoint pvs exist then a PvException is raised. Args: - handle(string): Handle used to get the value off a readback or setpoint + handle (str): Handle used to get the value off a readback or setpoint pv. Returns: - Number: The value of the pv. + float: The value of the pv. Raises: PvException: In case the requested pv doesn't exist. @@ -88,18 +84,19 @@ def get_pv_name(self, handle): """Get a pv name on a specified handle. Args: - handle(string): The readback or setpoint handle to be returned. + handle (str): The readback or setpoint handle to be returned. Returns: - string: A readback or setpoint pv. + str: A readback or setpoint pv. """ - if handle == '*': - return [self.rb_pv, self.sp_pv] - elif handle == pytac.RB: + if handle == pytac.RB and self.rb_pv: return self.rb_pv - elif handle == pytac.SP: + elif handle == pytac.SP and self.sp_pv: return self.sp_pv + raise PvException("""This device {0} has no {1} pv.""" + .format(self.name, handle)) + def get_cs(self): return self._cs @@ -112,8 +109,8 @@ def __init__(self, pv, enabled_value, cs): and False otherwise. Args: - pv(string): pv name - enabled_value(string): value for pv for which the device should + pv (str): pv name + enabled_value (str): value for pv for which the device should be considered enabled cs: Control system object """ @@ -127,7 +124,7 @@ def __nonzero__(self): Support for Python 2.7. Returns: - boolean: True if the device should be considered enabled + bool: True if the device should be considered enabled """ pv_value = self._cs.get(self._pv) return self._enabled_value == str(int(float(pv_value))) @@ -138,6 +135,6 @@ def __bool__(self): Support for Python 3.x. Returns: - boolean: True if the device should be considered enabled + bool: True if the device should be considered enabled """ return self.__nonzero__() diff --git a/pytac/element.py b/pytac/element.py index e20b3b05..57e41093 100644 --- a/pytac/element.py +++ b/pytac/element.py @@ -14,22 +14,25 @@ class Element(object): name (str): name identifying the element type_ (str): type of the element length (number): length of the element in metres + s (float): the element's start position within the lattice in metres cell (int): the element's cell within the lattice families (set): the families this element is a member of """ - def __init__(self, name, length, element_type, cell=None): - """An element of the ring. - + def __init__(self, name, length, element_type, s=None, cell=None): + """ Args: name (int): Unique identifier for the element in the ring. length (float): The length of the element. - element_type (string): Type of the element. + element_type (str): Type of the element. + s (float): Position of the start of the element in the ring + cell (int): lattice cell this element is wihin """ self.name = name self.type_ = element_type self.length = length + self.s = s self.cell = cell self.families = set() self._uc = dict() @@ -42,9 +45,14 @@ def __str__(self): Return a representation of an element, as a string. Returns: - string: A representation of an element. + str: A representation of an element. """ - return 'Element: {0}, length: {1}, families: {2}'.format(self.name, self.length, self.families) + repn = '' + return repn.format(self.name, + self.length, + ', '.join(f for f in self.families)) + + __repr__ = __str__ def set_model(self, model): self._model = model @@ -61,10 +69,10 @@ def add_device(self, field, device, uc): """Add device and unit conversion objects to a given field. Args: - field (string): The key to store the unit conversion and device + field (str): The key to store the unit conversion and device objects. device (Device): Represents a device stored on an element. - uc (PolyUnitConv/PchipUnitConv): Represents a unit conversion object stored for a + uc (UnitConv): Represents a unit conversion object used for this device. """ self._devices[field] = device @@ -74,7 +82,7 @@ def get_device(self, field): """Get the device for the given field. Args: - field (string): The lookup key to find the device on an element. + field (str): The lookup key to find the device on an element. Returns: Device: The device on the given field. @@ -88,7 +96,7 @@ def add_to_family(self, family): """Add the element to the specified family. Args: - family (string): Represents the name of the family + family (str): Represents the name of the family """ self.families.add(family) @@ -101,11 +109,11 @@ def get_value(self, field, handle, unit=pytac.ENG, model=pytac.LIVE): or simulated values. Args: - field (string): Choose which device to use. - handle (string): Can take as value either 'setpoint' or 'readback'. - unit (string): Specify either engineering or physics units to be + field (str): Choose which device to use. + handle (str): Can take as value either 'setpoint' or 'readback'. + unit (str): Specify either engineering or physics units to be returned. - model (string): Set whether real or simulated values to be returned. + model (str): Set whether real or simulated values to be returned. Returns: Number: A number that corresponds to the value of the identified @@ -136,10 +144,10 @@ def set_value(self, field, value, unit=pytac.ENG, model=pytac.LIVE): can be engineering or physics. Args: - field (string): The key used to identify a device. + field (str): The key used to identify a device. value (float): The value set on the device. - unit (string): Can be engineering or physics units. - model (string): The type of model: simulation or live + unit (str): Can be engineering or physics units. + model (str): The type of model: simulation or live Raises: PvException: An exception occured accessing a field with @@ -158,18 +166,17 @@ def set_value(self, field, value, unit=pytac.ENG, model=pytac.LIVE): value = self._uc[field].eng_to_phys(value) self._model.set_value(field, value) - def get_pv_name(self, field, handle='*'): + def get_pv_name(self, field, handle): """ Get a pv name on a device. Can return the readback and setpoint pvs if no handle is specified. Args: - field (string): Uniquely identifies a device. - handle(string): Can be 'readback' or 'setpoint' to return each pv. - If neither is specified then both pvs are returned. + field (str): Uniquely identifies a device. + handle (str): pytac.RB or pytac.SP Returns: - string: A readback or setpoint pv associated with the identified device. + str: A readback or setpoint pv associated with the identified device. Raises: PvException: An exception occured accessing a field with diff --git a/pytac/load_csv.py b/pytac/load_csv.py index 78c64dd7..c3ed1e71 100644 --- a/pytac/load_csv.py +++ b/pytac/load_csv.py @@ -25,8 +25,8 @@ def load_unitconv(directory, mode, lattice): """Load the unit conversion objects from a file. Args: - directory(string): The directory where the data is stored. - mode(string): The name of the mode that is used. + directory (str): The directory where the data is stored. + mode (str): The name of the mode that is used. lattice(Lattice): The lattice object that will be used. """ data = collections.defaultdict(list) @@ -65,10 +65,10 @@ def load(mode, control_system=None, directory=None): """Load the elements of a lattice from a directory. Args: - mode(string): The name of the mode to be loaded. - control_system(ControlSystem): The control system to be used. If none is provided + mode (str): The name of the mode to be loaded. + control_system (ControlSystem): The control system to be used. If none is provided an EpicsControlSystem will be created. - directory(string): Directory where to load the files from. If no directory is given + directory (str): Directory where to load the files from. If no directory is given the data directory at the root of the repository is used. Returns: @@ -89,14 +89,16 @@ def load(mode, control_system=None, directory=None): directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') lat = lattice.Lattice(mode, control_system, 3000) + s = 0 with open(os.path.join(directory, mode, 'elements.csv')) as elements: csv_reader = csv.DictReader(elements) for item in csv_reader: + length = float(item['length']) cell = int(item['cell']) if item['cell'] else None - e = element.Element(item['name'], float(item['length']), - item['type'], cell) + e = element.Element(item['name'], length, item['type'], s, cell) e.add_to_family(item['type']) lat.add_element(e) + s += length with open(os.path.join(directory, mode, 'devices.csv')) as devices: csv_reader = csv.DictReader(devices) @@ -104,8 +106,8 @@ def load(mode, control_system=None, directory=None): name = item['name'] enable_pv = item['enable_pv'] enable_value = item['enable_value'] - get_pv = item['get_pv'] - set_pv = item['set_pv'] + get_pv = item['get_pv'] if item['get_pv'] else None + set_pv = item['set_pv'] if item['set_pv'] else None pve = True if enable_pv and enable_value: pve = device.PvEnabler(enable_pv, enable_value, control_system) diff --git a/pytac/units.py b/pytac/units.py index e607e046..4e18fe0b 100644 --- a/pytac/units.py +++ b/pytac/units.py @@ -9,26 +9,28 @@ def unit_function(value): """Default value for the pre and post functions used in unit conversion. Args: - value(Number): The value to be converted. + value (float): The value to be converted. Returns: - value(Number): The result of the conversion. + value (float): The result of the conversion. """ return value class UnitConv(object): - def __init__(self, post_eng_to_phys=unit_function, pre_phys_to_eng=unit_function): - """Class to convert between physics and engineering units. + """Class to convert between physics and engineering units. - The two arguments to this function represent functions that are - applied to the result of the initial conversion. One happens after - the conversion, the other happens before the conversion back. + The two arguments to this function represent functions that are + applied to the result of the initial conversion. One happens after + the conversion, the other happens before the conversion back. + """ + def __init__(self, post_eng_to_phys=unit_function, pre_phys_to_eng=unit_function): + """ Args: - post_eng_to_phys(function): Function to be applied post the initial + post_eng_to_phys (function): Function to be applied after the initial conversion. - pre_phys_to_eng(function): Function to be applied preceding the initial + pre_phys_to_eng (function): Function to be applied before the initial conversion. """ self._post_eng_to_phys = post_eng_to_phys @@ -38,7 +40,8 @@ def _raw_eng_to_phys(self, value): """Function to be implemented by child classes. Args: - value(Number): Value to be converted from engineering to physics untits. + value (float): Value to be converted from engineering to physics + units. """ raise NotImplementedError() @@ -49,10 +52,10 @@ def eng_to_phys(self, value): be casted on the initial conversion. Args: - value(Number): Value to be converted from engineering to physics units. + value (float): Value to be converted from engineering to physics units. Returns: - result(Number): The result value. + result (float): The result value. """ x = self._raw_eng_to_phys(value) result = self._post_eng_to_phys(x) @@ -62,7 +65,7 @@ def _raw_phys_to_eng(self, value): """Function to be implemented by child classes. Args: - value(Number): Value to be converted from physics to engineering units. + value (float): Value to be converted from physics to engineering units. """ raise NotImplementedError() @@ -73,10 +76,10 @@ def phys_to_eng(self, value): be casted on the initial conversion. Args: - value(Number): Value to be converted from physics to engineering units. + value (float): Value to be converted from physics to engineering units. Returns: - result(Number): The result value. + result (float): The result value. """ x = self._pre_phys_to_eng(value) result = self._raw_phys_to_eng(x) @@ -88,7 +91,7 @@ def __init__(self, coef, post_eng_to_phys=unit_function, pre_phys_to_eng=unit_fu """Linear interpolation for converting between physics and engineering units. Args: - coef(array_like): The polynomial's coefficients, in decreasing powers. + coef (array_like): The polynomial's coefficients, in decreasing powers. """ super(self.__class__, self).__init__(post_eng_to_phys, pre_phys_to_eng) self.p = numpy.poly1d(coef) @@ -97,7 +100,7 @@ def _raw_eng_to_phys(self, eng_value): """Convert between engineering and physics units. Args: - eng_value(float): The engineering value to be converted to the engineering unit. + eng_value (float): The engineering value to be converted to the engineering unit. Returns: float: The physics value determined using the engineering value. diff --git a/test/data/dummy/elements.csv b/test/data/dummy/elements.csv index d4f586ef..08bc8a1a 100644 --- a/test/data/dummy/elements.csv +++ b/test/data/dummy/elements.csv @@ -1,5 +1,5 @@ id,name,type,length,cell 1,d1,drift,1.0, -2,q1,quad,0.5, -3,s1,sext,0.3, +2,q1,quad,0.5,1 +3,s1,sext,0.3,2 4,d2,drift,0.8, diff --git a/test/test_element.py b/test/test_element.py index 39353ca9..916122e9 100644 --- a/test/test_element.py +++ b/test/test_element.py @@ -83,8 +83,6 @@ def test_get_value_uses_uc_if_necessary_for_model_call(test_element): @pytest.mark.parametrize('pv_type', ['readback', 'setpoint']) def test_get_pv_name(pv_type, test_element): - assert isinstance(test_element.get_pv_name('x'), list) - assert isinstance(test_element.get_pv_name('y'), list) assert isinstance(test_element.get_pv_name('x', pv_type), str) assert isinstance(test_element.get_pv_name('y', pv_type), str) @@ -105,8 +103,6 @@ def test_get_pv_exceptions(test_element): test_element.get_value('setpoint', 'unknown_field') with pytest.raises(PvException): test_element.get_value('unknown_handle', 'y') - with pytest.raises(PvException): - test_element.get_pv_name('unknown_handle') def test_identity_conversion(): @@ -121,6 +117,7 @@ def test_identity_conversion(): def test_get_fields(test_element): assert set(test_element.get_fields()) == set(['y', 'x']) + def test_element_representation(test_element): s = str(test_element) assert test_element.name in s diff --git a/test/test_load.py b/test/test_load.py index 4b568151..d3938823 100644 --- a/test/test_load.py +++ b/test/test_load.py @@ -20,6 +20,12 @@ def test_elements_loaded(lattice): assert lattice.get_length() == 2.6 +def test_element_details_loaded(lattice): + quad = lattice.get_elements('quad')[0] + assert quad.cell == 1 + assert quad.s == 1.0 + + def test_devices_loaded(lattice): quads = lattice.get_elements('quad') assert len(quads) == 1 diff --git a/test/test_machine.py b/test/test_machine.py index bbd11072..3f4a0a6f 100644 --- a/test/test_machine.py +++ b/test/test_machine.py @@ -8,7 +8,7 @@ import re import mock import numpy -from pytac.exceptions import UniqueSolutionException +from pytac.exceptions import PvException, UniqueSolutionException EPS = 1e-8 @@ -58,6 +58,9 @@ def test_load_bpms(ring_mode, n_bpms): bpms = lattice.get_elements('BPM') for bpm in bpms: assert set(bpm.get_fields()) == set(('x', 'y')) + assert re.match('SR.*BPM.*X', bpm.get_pv_name('x', pytac.RB)) + with pytest.raises(PvException): + bpm.get_pv_name('x', pytac.SP) assert len(bpms) == n_bpms