MFXCarrier Module Order Influences Indexing Access?

Hi everyone,

I’ve noticed a curious behaviour with the Hamilton-based MFXCarrier class, specifically during instantiation of a MFX_CAR_L5_base object:

Scenario 1

plate_holder = MFX_DWP_module_flat(name="plate_holder") 

MFX_TIP_module_1 = MFX_TIP_module(name="MFX_TIP_module_1")
MFX_TIP_module_2 = MFX_TIP_module(name="MFX_TIP_module_2")
MFX_TIP_module_3 = MFX_TIP_module(name="MFX_TIP_module_3")
MFX_TIP_module_4 = MFX_TIP_module(name="MFX_TIP_module_4")

mfx_tip_carrier_1 = MFX_CAR_L5_base(
  name="mfx_tip_carrier_1",
  modules={
      4: process_plate_holder,
      3: MFX_TIP_module_4,
      2: MFX_TIP_module_3,
      1: MFX_TIP_module_2,
      0: MFX_TIP_module_1,
  }
)
mfx_tip_carrier_1[1] = HTF(name=f'tip_1000ul_01')
mfx_tip_carrier_1[0] = HTF(name=f'tip_1000ul_00')

lh.deck.assign_child_resource(mfx_tip_carrier_1, rails=7)

…raises…

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[11], line 19
      8 mfx_tip_carrier_1 = MFX_CAR_L5_base(
      9   name="mfx_tip_carrier_1",
     10   modules={
   (...)
     16   }
     17 )
     18 mfx_tip_carrier_1[1] = HTF(name=f'tip_1000ul_01')
---> 19 mfx_tip_carrier_1[0] = HTF(name=f'tip_1000ul_00')
     21 lh.deck.assign_child_resource(mfx_tip_carrier_1, rails=7)

File c:\users\cmoschner\desktop\pylabrobot\pylabrobot\resources\carrier.py:101, in Carrier.__setitem__(self, idx, resource)
     99     self.unassign_child_resource(assigned_resource)
    100 else:
--> 101   self.assign_resource_to_site(resource, spot=idx)

File c:\users\cmoschner\desktop\pylabrobot\pylabrobot\resources\carrier.py:75, in Carrier.assign_resource_to_site(self, resource, spot)
     74 def assign_resource_to_site(self, resource: Resource, spot: int):
---> 75   if self.sites[spot].resource is not None:
     76     raise ValueError(f"spot {spot} already has a resource")
     77   self.sites[spot].assign_child_resource(resource)

KeyError: 0

Scenario 2

But, when changing the order of the modules, this error is not raised:

plate_holder = MFX_DWP_module_flat(name="plate_holder") 

MFX_TIP_module_1 = MFX_TIP_module(name="MFX_TIP_module_1")
MFX_TIP_module_2 = MFX_TIP_module(name="MFX_TIP_module_2")
MFX_TIP_module_3 = MFX_TIP_module(name="MFX_TIP_module_3")
MFX_TIP_module_4 = MFX_TIP_module(name="MFX_TIP_module_4")

mfx_tip_carrier_1 = MFX_CAR_L5_base(
  name="mfx_tip_carrier_1",
  modules={
      0: MFX_TIP_module_1,
      4: plate_holder,
      3: MFX_TIP_module_4,
      2: MFX_TIP_module_3,
      1: MFX_TIP_module_2,
  }
)
mfx_tip_carrier_1[1] = HTF(name=f'tip_1000ul_01')
mfx_tip_carrier_1[0] = HTF(name=f'tip_1000ul_00')

lh.deck.assign_child_resource(mfx_tip_carrier_1, rails=7)

…and instead the behaviour is as expected.

Scenario 1 Troubleshooting

Investigating, the mfx_tip_carrier_1 object:

for i, module in {
      4: process_plate_holder,
      3: MFX_TIP_module_4,
      2: MFX_TIP_module_3,
      1: MFX_TIP_module_2,
      0: MFX_TIP_module_1,
  }.items():
    print(i, module)
# 4 PlateHolder(name=process_plate_holder, location=Coordinate(000.000, 389.000, 018.195), size_x=134.0, size_y=92.1, size_z=66.4, category=plate_holder)
# 3 ResourceHolder(name=MFX_TIP_module_4, location=Coordinate(000.000, 293.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)
# 2 ResourceHolder(name=MFX_TIP_module_3, location=Coordinate(000.000, 197.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)
# 1 ResourceHolder(name=MFX_TIP_module_2, location=Coordinate(000.000, 101.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)
# 0 ResourceHolder(name=MFX_TIP_module_1, location=Coordinate(000.000, 005.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)

…as expected.

mfx_tip_carrier_1.children
# [PlateHolder(name=process_plate_holder, location=Coordinate(000.000, 389.000, 018.195), size_x=134.0, size_y=92.1, size_z=66.4, category=plate_holder),
# ResourceHolder(name=MFX_TIP_module_4, location=Coordinate(000.000, 293.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# ResourceHolder(name=MFX_TIP_module_3, location=Coordinate(000.000, 197.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# ResourceHolder(name=MFX_TIP_module_2, location=Coordinate(000.000, 101.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# ResourceHolder(name=MFX_TIP_module_1, location=Coordinate(000.000, 005.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)]

…as expected

But…

mfx_tip_carrier_1.sites
# {4: ResourceHolder(name=MFX_TIP_module_1, location=Coordinate(000.000, 005.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# 3: ResourceHolder(name=MFX_TIP_module_4, location=Coordinate(000.000, 293.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# 2: ResourceHolder(name=MFX_TIP_module_3, location=Coordinate(000.000, 197.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder),
# 1: ResourceHolder(name=MFX_TIP_module_2, location=Coordinate(000.000, 101.000, 018.195), size_x=135.0, size_y=94.0, size_z=96.60500000000002, category=resource_holder)}

…is missing site 0 in scenario_1 and has the indexes all scrambled up :eyes: .

Does anyone have any ideas where it was lost? :slight_smile:

This is odd because the use of a dict for carrier sites handling and modules handling should avoid such a problem.

1 Like

issue was this

idx = spot or len(self.sites)

when spot is 0, it evaluates to False.

fix:

idx = spot if spot is not None else len(self.sites)
1 Like

Thank you, @rickwierenga!

A 13 min bug fix - PLR dev is just lightning :rocket:

1 Like

added a test so it never breaks again in the future

1 Like