Skip to content
346 changes: 173 additions & 173 deletions data/VMX/devices.csv

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions pytac/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


class Element(object):

def __init__(self, name, length, element_type):
"""An element of the ring.

Expand All @@ -25,6 +26,7 @@ def __init__(self, name, length, element_type):
self.families = set()
self._uc = dict()
self._devices = dict()
self._model = None

def __str__(self):
"""Auxiliary function to print out an element.
Expand All @@ -36,6 +38,9 @@ def __str__(self):
"""
return 'Element: {0}, length: {1}, families: {2}'.format(self._name, self._length, self.families)

def set_model(self, model):
self._model = model

def get_fields(self):
"""Get the fields defined on an element.

Expand Down Expand Up @@ -86,7 +91,7 @@ def add_to_family(self, family):
"""
self.families.add(family)

def get_pv_value(self, field, handle, unit=pytac.ENG, sim=False):
def get_value(self, field, handle, unit=pytac.ENG, sim=False):
"""Get the value of a pv.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pv be removed from the comment as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.


Returns the value of a pv on the element. This value is uniquely
Expand Down Expand Up @@ -117,12 +122,12 @@ def get_pv_value(self, field, handle, unit=pytac.ENG, sim=False):
else:
raise PvException("No device associated with field {0}".format(field))
else:
value = self._physics.get_value(field, handle, unit)
value = self._model.get_value(field)
if unit == pytac.ENG:
value = self._uc[field].eng_to_phys(value)
value = self._uc[field].phys_to_eng(value)
return value

def put_pv_value(self, field, value, unit=pytac.ENG, sim=False):
def set_value(self, field, value, unit=pytac.ENG, sim=False):
"""Set the pv value on a uniquely identified device.

This value can be set on the machine or the simulation.
Expand Down Expand Up @@ -150,7 +155,7 @@ def put_pv_value(self, field, value, unit=pytac.ENG, sim=False):
else:
if unit == pytac.ENG:
value = self._uc[field].eng_to_phys(value)
self._physics.put_value(field, value)
self._model.set_value(field, value)

def get_pv_name(self, field, handle='*'):
""" Get a pv name on a device.
Expand All @@ -172,7 +177,7 @@ def get_pv_name(self, field, handle='*'):
try:
return self._devices[field].get_pv_name(handle)
except KeyError:
raise PvException('Element has no device for field {}'.format(field))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this exception be renamed to something more generic, CsException perhaps?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

raise PvException('{} has no device for field {}'.format(self, field))

def get_cs(self, field):
return self._devices[field].get_cs()
18 changes: 12 additions & 6 deletions pytac/lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from pytac.exceptions import PvException
from pytac.exceptions import ElementNotFoundException


class Lattice(object):

def __init__(self, name, control_system, energy):
"""Representation of a lattice.

Expand All @@ -21,6 +23,10 @@ def __init__(self, name, control_system, energy):
self._lattice = []
self._cs = control_system
self._energy = energy
self._model = None

def set_model(self, model):
self._model = model

def get_energy(self):
"""Function to get the total energy of the lattice.
Expand Down Expand Up @@ -142,7 +148,7 @@ def set_family_values(self, family, field, values):
"""Set the pv value of a given family of pvs.

The pvs are determined by family and device. Note that only setpoint
pvs ca be modified.
pvs can be modified.

Args:
family(string): A specific family to set the value of.
Expand All @@ -161,7 +167,7 @@ def set_family_values(self, family, field, values):
"to the number of elements in the lattice")
self._cs.put(pv_names, values)

def get_s(self, given_element):
def get_s(self, element):
"""Find the position of a given element in the lattice.

Note that the given element must exist in the lattice.
Expand All @@ -177,12 +183,12 @@ def get_s(self, given_element):
doesn't exist inside the lattice.
"""
s_pos = 0
for element in self._lattice:
if element is not given_element:
s_pos += element.get_length()
for el in self._lattice:
if el is not element:
s_pos += el.get_length()
else:
return s_pos
raise ElementNotFoundException('Given element does not exist in the lattice')
raise ElementNotFoundException('Element {} does not exist in the lattice'.format(element))

def get_family_s(self, family):
"""Get the positions for a set of elements from the same family.
Expand Down
77 changes: 51 additions & 26 deletions test/test_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,37 @@
import pytac


@pytest.fixture
def test_element(length=0.0, uc=PolyUnitConv([1, 0])):
RB_PV = 'rb_pv'
SP_PV = 'sp_pv'

DUMMY_VALUE_1 = 40.0
DUMMY_VALUE_2 = 4.7
DUMMY_VALUE_3 = -6


def mock_uc():
uc = mock.MagicMock()
uc.phys_to_eng.return_value = DUMMY_VALUE_2
uc.eng_to_phys.return_value = DUMMY_VALUE_3
return uc


@pytest.fixture
def test_element(length=0.0, uc=mock_uc()):
mock_cs = mock.MagicMock()
mock_cs.get.return_value = 40.0
mock_cs.get.return_value = DUMMY_VALUE_1

element = pytac.element.Element('dummy', 1.0, 'Quad')
rb_pv = 'SR22C-DI-EBPM-04:SA:X'
sp_pv = 'SR22C-DI-EBPM-04:SA:Y'
device1 = pytac.device.Device(mock_cs, True, rb_pv, sp_pv)
device2 = pytac.device.Device(mock_cs, True, sp_pv, rb_pv)
device1 = pytac.device.Device(mock_cs, True, RB_PV, SP_PV)
device2 = pytac.device.Device(mock_cs, True, SP_PV, RB_PV)

element.add_device('x', device1, uc)
element.add_device('y', device2, uc)

mock_model = mock.MagicMock()
mock_model.get_value.return_value = DUMMY_VALUE_2
element.set_model(mock_model)

return element


Expand All @@ -38,14 +54,23 @@ def test_add_element_to_family():
assert 'fam' in e.families


@pytest.mark.parametrize('pv_type', ['readback', 'setpoint'])
def test_get_pv_value(pv_type, test_element):
# Tests to get/set pv names and/or values
# The default unit conversion is identity
assert test_element.get_pv_value('x', pv_type, unit=pytac.PHYS) == 40.0
assert test_element.get_pv_value('x', pv_type, unit=pytac.ENG) == 40.0
assert test_element.get_pv_value('y', pv_type, unit=pytac.PHYS) == 40.0
assert test_element.get_pv_value('y', pv_type, unit=pytac.ENG) == 40.0
def test_get_value_uses_cs_if_sim_False(test_element):
test_element.get_value('x', handle=pytac.SP, sim=False)
test_element.get_device('x')._cs.get.assert_called_with(SP_PV)
test_element.get_value('x', handle=pytac.RB, sim=False)
test_element.get_device('x')._cs.get.assert_called_with(RB_PV)


def test_get_value_uses_uc_if_necessary_for_cs_call(test_element):
test_element.get_value('x', handle=pytac.SP, unit=pytac.PHYS, sim=False)
test_element._uc['x'].eng_to_phys.assert_called_with(DUMMY_VALUE_1)
test_element.get_device('x')._cs.get.assert_called_with(SP_PV)


def test_get_value_uses_uc_if_necessary_for_sim_call(test_element):
test_element.get_value('x', handle=pytac.SP, unit=pytac.ENG, sim=True)
test_element._uc['x'].phys_to_eng.assert_called_with(DUMMY_VALUE_2)
test_element._model.get_value.assert_called_with('x')


@pytest.mark.parametrize('pv_type', ['readback', 'setpoint'])
Expand All @@ -56,32 +81,32 @@ def test_get_pv_name(pv_type, test_element):
assert isinstance(test_element.get_pv_name('y', pv_type), str)


def test_put_pv_value(test_element):
test_element.put_pv_value('x', 40.3)
test_element.get_device('x')._cs.put.assert_called_with('SR22C-DI-EBPM-04:SA:Y', 40.3)
def test_set_value(test_element):
test_element.set_value('x', DUMMY_VALUE_2)
test_element.get_device('x')._cs.put.assert_called_with(SP_PV, DUMMY_VALUE_2)

test_element.put_pv_value('x', 40.3, unit=pytac.PHYS)
test_element.get_device('x')._cs.put.assert_called_with('SR22C-DI-EBPM-04:SA:Y', 40.3)
test_element.set_value('x', DUMMY_VALUE_2, unit=pytac.PHYS)
test_element.get_device('x')._cs.put.assert_called_with(SP_PV, DUMMY_VALUE_2)

with pytest.raises(PvException):
test_element.put_pv_value('non_existent', 40.0)
test_element.set_value('non_existent', 40.0)


def test_get_pv_exceptions(test_element):
with pytest.raises(PvException):
test_element.get_pv_value('setpoint', 'unknown_field')
test_element.get_value('setpoint', 'unknown_field')
with pytest.raises(PvException):
test_element.get_pv_value('unknown_handle', 'y')
test_element.get_value('unknown_handle', 'y')
with pytest.raises(PvException):
test_element.get_pv_name('unknown_handle')


def test_identity_conversion():
uc_id = PolyUnitConv([1, 0])
element = test_element(uc=uc_id)
value_physics = element.get_pv_value('x', 'setpoint', pytac.PHYS)
value_machine = element.get_pv_value('x', 'setpoint', pytac.ENG)
assert value_machine == 40.0
value_physics = element.get_value('x', 'setpoint', pytac.PHYS)
value_machine = element.get_value('x', 'setpoint', pytac.ENG)
assert value_machine == DUMMY_VALUE_1
assert value_physics == 40.0


Expand Down
8 changes: 6 additions & 2 deletions test/test_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ def test_load_quad_family(lattice):
def test_load_correctors(lattice):
hcm = lattice.get_elements('HSTR')
vcm = lattice.get_elements('VSTR')
assert len(hcm) == 173
assert len(vcm) == 173
# these are the same elements with both devices on each
assert hcm == vcm
for element in hcm:
# each one has both a0 (VSTR) and b0 (HSTR) as fields
assert set(('a0', 'b0')).issubset(element.get_fields())


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in some transfer lines or the injectors there are steerers in a single plane.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module is designed to check specifically the loading of the VMX lattice, as opposed to something more generic. It's pretty useful for testing that everything is as it should be for Diamond, but it's generally OK because I think bundling the data for a machine is useful for anyone else using this. We could make this a bit clearer in the module name and comments.

@pytest.mark.parametrize('field', ('x', 'y'))
def test_bpm_unitconv(lattice, field):
Expand Down
2 changes: 2 additions & 0 deletions utils/load_mml.m
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ function insertpvs(index, pvs)
field = 'b1';
elseif strcmp(type, 'SEXT')
field = 'b2';
elseif strcmp(type, 'VSTR')
field = 'a0';
else

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more robust to check the type here as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This was a shortcut because everything left was either a bending magnet or a horizontal steerer.

field = 'b0';
end
Expand Down