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
# Return a simple simulated path
In [2]: path = lightpath.tests.path()
# Return a mock lcls facility
In [3]: lcls = lightpath.tests.lcls()
You can look at all the devices in the path, either by looking at the objects
themselves or using the BeamPath.show_devices()
In [4]: path.show_devices()
+-------+--------+----------+----------------+-----------------+---------+
| Name | Prefix | Position | Input Branches | Output Branches | State |
+-------+--------+----------+----------------+-----------------+---------+
| zero | zero | 0.00000 | ['TST'] | ['TST'] | Removed |
| one | one | 2.00000 | ['TST'] | ['TST'] | Removed |
| two | two | 9.00000 | ['TST'] | ['TST'] | Removed |
| three | three | 15.00000 | ['TST'] | ['TST'] | Removed |
| four | four | 16.00000 | ['TST'] | ['TST', 'SIM'] | Removed |
| five | five | 24.00000 | ['TST'] | ['TST'] | Removed |
| six | six | 24.20000 | ['TST'] | ['TST'] | Removed |
| seven | seven | 24.40000 | ['TST'] | ['TST'] | Removed |
| eight | eight | 24.60000 | ['TST'] | ['TST'] | Removed |
| nine | nine | 24.80000 | ['TST'] | ['TST'] | Removed |
| ten | ten | 30.00000 | ['TST'] | ['TST'] | Removed |
+-------+--------+----------+----------------+-----------------+---------+
In [5]: path.devices
Out[5]:
(IPIMB(prefix='eight', name='eight', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='five', name='five', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Crystal(prefix='four', name='four', read_attrs=['current_state', 'current_transmission', 'current_destination', '_inserted_branch'], configuration_attrs=[]),
IPIMB(prefix='nine', name='nine', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Valve(prefix='one', name='one', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='seven', name='seven', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='six', name='six', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Valve(prefix='ten', name='ten', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Valve(prefix='three', name='three', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Stopper(prefix='two', name='two', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
Valve(prefix='zero', name='zero', read_attrs=['current_state', 'current_transmission', 'current_destination'], 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
.
Note that these simulated devices have .insert()
and .remove()
methods for
convenience of testing, but real devices may not.
In [6]: path.cleared
Out[6]: True
In [7]: path.five.insert()
Out[7]: DeviceStatus(device=five, done=True, success=True)
In [8]: path.six.insert()
Out[8]: DeviceStatus(device=six, done=True, success=True)
In [9]: path.cleared
Out[9]: True
In [10]: path.incident_devices
Out[10]:
[IPIMB(prefix='five', name='five', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='six', name='six', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[])]
In [11]: path.blocking_devices
Out[11]: []
In [12]: path.show_devices()
+-------+--------+----------+----------------+-----------------+----------+
| Name | Prefix | Position | Input Branches | Output Branches | State |
+-------+--------+----------+----------------+-----------------+----------+
| zero | zero | 0.00000 | ['TST'] | ['TST'] | Removed |
| one | one | 2.00000 | ['TST'] | ['TST'] | Removed |
| two | two | 9.00000 | ['TST'] | ['TST'] | Removed |
| three | three | 15.00000 | ['TST'] | ['TST'] | Removed |
| four | four | 16.00000 | ['TST'] | ['TST', 'SIM'] | Removed |
| five | five | 24.00000 | ['TST'] | ['TST'] | Inserted |
| six | six | 24.20000 | ['TST'] | ['TST'] | Inserted |
| seven | seven | 24.40000 | ['TST'] | ['TST'] | Removed |
| eight | eight | 24.60000 | ['TST'] | ['TST'] | Removed |
| nine | nine | 24.80000 | ['TST'] | ['TST'] | Removed |
| ten | ten | 30.00000 | ['TST'] | ['TST'] | Removed |
+-------+--------+----------+----------------+-----------------+----------+
The most upstream blocking device by checking the BeamPath.impediment
In [13]: path.impediment == path.six
Out[13]: False
In [14]: path.two.insert()
Out[14]: DeviceStatus(device=two, done=True, success=True)
In [15]: path.impediment == path.two
Out[15]: True
In [16]: path.blocking_devices
Out[16]:
[Stopper(prefix='two', name='two', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='five', name='five', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[]),
IPIMB(prefix='six', name='six', read_attrs=['current_state', 'current_transmission', 'current_destination'], configuration_attrs=[])]
Branching Logic, and Graph Construction
In order to determine the state of the “beamline”, Lightpath must first understand where the various devices lie along the beam path. Lightpath does this by constructing a Directed Graph with the devices as nodes. This formulation allows Lightpath to find any and all valid paths to any given device, given the state of the facility at that time.
In order to do this, each device considered by lightpath must carry with it the following metadata:
input branches
output branches
z-position
This is the minimum information needed to place a device in the facility graph. To simplify matters for the user, lightpath orders devices on the same branch by their z-position, or distance from the light source.
For more implementation details (device API, happi database information) see the Device Interface section
LCLS Specific Notes
The way Lightpath organizes devices (ordering devices on the same branch by z position) is greatly motivated by the LCLS facility and its naming conventions. LCLS recently adopted a device naming convention that relies heavily on this concept of branches, with branch names changing at branching devices.