Introduction

Device Manipulation

The lightpath abstracts control of multiple devices into a single beampath object. Actual device instantiation should be handled else where the lightpath just assumes that you give it a list of devices that match the LightInterface. For now we can demonstrate the basic features of the API by using a simulated path.

In [1]: import lightpath.tests
********************************************************************************
* Loading PyDM Data Plugins
********************************************************************************
Looking for PyDM Data Plugins at: /home/travis/miniconda/envs/test-environment/lib/python3.6/site-packages/pydm/data_plugins
	Trying to load epics_plugin.py...
	Trying to load archiver_plugin.py...
	Trying to load fake_plugin.py...
	Trying to load local_plugin.py...
Looking for PyDM Data Plugins at: /home/travis/miniconda/envs/test-environment/lib/python3.6/site-packages/pydm/data_plugins/epics_plugins

#Return the simulated path
In [2]: path = lightpath.tests.path()

You can look at all the devices in the path, either by looking at the objects themselves or using the BeamPath.show_devices()

In [3]: path.show_devices()
+-------+--------+----------+----------+---------+
| Name  | Prefix | Position | Beamline | Removed |
+-------+--------+----------+----------+---------+
| zero  | zero   |  0.00000 |      TST |    True |
| one   | one    |  2.00000 |      TST |    True |
| two   | two    |  9.00000 |      TST |    True |
| three | three  | 15.00000 |      TST |    True |
| four  | four   | 16.00000 |      TST |    True |
| five  | five   | 24.00000 |      TST |    True |
| six   | six    | 30.00000 |      TST |    True |
+-------+--------+----------+----------+---------+

In [4]: path.devices
Out[4]: 
(IPIMB(prefix='five', name='five', read_attrs=[], configuration_attrs=[]),
 Crystal(prefix='four', name='four', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='one', name='one', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='six', name='six', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='three', name='three', read_attrs=[], configuration_attrs=[]),
 Stopper(prefix='two', name='two', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='zero', name='zero', read_attrs=[], configuration_attrs=[]))

Now lets insert some devices and see how we can quickly find what is blocking the instrument. The lightpath module differentiates between two kinds of inserted devices, blocking and incident. The first is a device that will prevent beam from transmitting through it i.e a stopper or YAG. The second includes slightly more complex, but essentially is any device that can be inserted in to the beam, but won’t greatly affect operation i.e an IPIMB. The difference between the two is determined by comparing the devices transmission attribute against BeamPath.minimum_transmission

In [5]: path.cleared
Out[5]: True

In [6]: path.five.insert()
Out[6]: DeviceStatus(device=five, done=True, success=True)

In [7]: path.six.insert()
Out[7]: DeviceStatus(device=six, done=True, success=True)

In [8]: path.cleared
Out[8]: False

In [9]: path.incident_devices
Out[9]: 
[IPIMB(prefix='five', name='five', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='six', name='six', read_attrs=[], configuration_attrs=[])]

In [10]: path.blocking_devices
Out[10]: [Valve(prefix='six', name='six', read_attrs=[], configuration_attrs=[])]

The most upstream blocking device by checking the BeamPath.impediment

In [11]: path.impediment == path.six
Out[11]: True

In [12]: path.two.insert()
Out[12]: DeviceStatus(device=two, done=True, success=True)

In [13]: path.impediment == path.two
Out[13]: True

In [14]: path.blocking_devices
Out[14]: 
[Stopper(prefix='two', name='two', read_attrs=[], configuration_attrs=[]),
 Valve(prefix='six', name='six', read_attrs=[], configuration_attrs=[])]

Each device can be accessed and removed individually, or you can use the BeamPath.clear(). The method has a few hooks to control which devices you actually want to remove.

In [15]: path.clear(passive=False)
Out[15]: 
[DeviceStatus(device=six, done=True, success=True),
 DeviceStatus(device=two, done=True, success=True)]

In [16]: path.incident_devices
Out[16]: [IPIMB(prefix='five', name='five', read_attrs=[], configuration_attrs=[])]

In [17]: path.clear(passive=True)
Out[17]: [DeviceStatus(device=five, done=True, success=True)]

In [18]: path.incident_devices
Out[18]: []

Branching Logic

The most complicated logic within the lightpath is the determination of the state of optics which control pointing between different LCLS hutches. Obviously, whether the pointing is accurately delivered to each hutch is beyond the scope of this module, the lightpath does try to generally determine where beam is possible by looking at higher level EPICS variables. The device capable of steering beam between forks in the path can be found with BeamPath.branches. Each should implement; branches, all possible beamline destinations for the optic, and destination, a list of current beamlines the device could be sending beam along. When the BeamPath object finds an incontinuous beamline, it checks a list of upstream optics to make sure that they all agree upon the destination. For instance, to deliver beam down the MFX line, both XRT M1 and XRT M2 must have MFX in their list of destinations. After this split, these optics are ignored so that each branching device only has to list the possible destinations that come immediattely after it. For example, the XPP LODCM should not have to report that it is ready to deliver beam to every possible FEH destination, only whether it is inserted for XPP operations or out of the HXR beampath.

MPS Information