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

Schematic of LCLS facility, with branch names (red) and branching devices (blue) labeled.

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.