Using the simulator#

In this notebook you will learn how to use the simulator to test out methods.

Setting up a connection with the robot#

As described in the basic liquid handling tutorial, we will use the LiquidHandler class to control the robot. This time, however, instead of using the Hamilton STAR backend, we are using virtual the SimulatorBackend backend. This means that liquid handling will work exactly the same, but the commands are sent to the simulator instead of a real physical robot.

%load_ext autoreload
%autoreload 2
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends.simulation.simulator_backend import SimulatorBackend
from pylabrobot.resources.hamilton import STARLetDeck
sb = SimulatorBackend(open_browser=False)
lh = LiquidHandler(backend=sb, deck=STARLetDeck())

Calling setup() will set up the simulation server and open it in a new browser tab.

await lh.setup()
Websocket server started at http://127.0.0.1:2121
File server started at http://127.0.0.1:1337 . Open this URL in your browser.

For the optimal experience, we recommend that you run the notebook and simulator side by side.

sb.wait_for_connection()

The empty simulator

Assigning plates and tips#

With the simulator, assigning resources has the additional affect of placing the resources on the simulated deck. They will appear immediately.

from pylabrobot.resources import (
    TIP_CAR_480_A00,
    PLT_CAR_L5AC_A00,
    Cos_96_DW_1mL,
    HTF_L
)
tip_car = TIP_CAR_480_A00(name='tip carrier')
tip_car[0] = tips = HTF_L(name='tips_01')
tip_car[1] = HTF_L(name='tips_02')
tip_car[2] = HTF_L(name='tips_03')
tip_car[3] = HTF_L(name='tips_04')
tip_car[4] = HTF_L(name='tips_05')
lh.deck.assign_child_resource(tip_car, rails=15)
plt_car = PLT_CAR_L5AC_A00(name='plate carrier')
plt_car[0] = plate_1 = Cos_96_DW_1mL(name='plate_01')
plt_car[1] = plate_2 = Cos_96_DW_1mL(name='plate_02')
plt_car[2] = plate_3 = Cos_96_DW_1mL(name='plate_03')
lh.deck.assign_child_resource(plt_car, rails=8)

The simulator after the resources have been assigned

Build the deck layout: placing resources#

Where you would manually place the resources like tips and liquid on the deck when using a physical system, with the Simulator you can add them using code.

Tips#

Let’s use fill_tip_rack() to place tips at all spots in the tip rack in location 0.

tiprack = lh.get_resource("tips_01")
tiprack
TipRack(name=tips_01, size_x=122.4, size_y=82.6, size_z=20.0, location=(000.000, 000.000, 000.000))
await sb.fill_tip_rack(tiprack)

You can precisely control the presence of tips using edit_tips().

tips4 = lh.get_resource("tips_04")
await sb.edit_tips(tips4, pattern=[[True]*6 + [False]*6]*8)
await sb.edit_tips(lh.get_resource("tips_03"), pattern=[[True, False]*6]*8)
await sb.edit_tips(lh.get_resource("tips_02"), pattern=[[True, True, False, False]*3]*8)

Liquids#

Adding liquid to wells works similarly. You can use adjust_well_volume() to adjust the volume of individual wells in each resource. Note that the opacity of the well matches the volume of the well.

plate_1_liquids = [[(None, 500)]]*96
await sb.adjust_wells_liquids(plate_1, liquids=plate_1_liquids)
plate_2_liquids = [[(None, 100)], [(None, 500)]]*(96//2)
await sb.adjust_wells_liquids(plate_2, liquids=plate_2_liquids)

Using the simulator backend we have adjusted the volume in the simulator, which you can best compare to adding liquid in reality. Now we need to update the wells (that live in Python) to reflect how much volume is in them, so that LiquidHandler can validate your actions. This is done using set_well_liquids(). Note that this can be done in all liquid handling protocols, not just the simulator.

plate_1.set_well_liquids(plate_1_liquids)
plate_2.set_well_liquids(plate_2_liquids)

Simulator after the tips have been placed and the volumes adjusted

Liquid handling#

Once the layout is complete, you can run the same commands as described in the basic liquid handling tutorial.

Picking up tips#

tip_0 = lh.get_resource("tips_01")
await lh.pick_up_tips(tip_0["A1", "B2", "C3", "D4"])
await lh.drop_tips(tip_0["A1", "B2", "C3", "D4"])

Aspirating and dispensing#

await lh.pick_up_tips(tip_0["A1"])
plate = lh.get_resource("plate_01")
await lh.aspirate(plate["A2"], vols=[300])
await lh.dispense(plate_2["A1"], vols=[300])
await lh.drop_tips(tip_0["A1"])

Aspirating using CoRe 96#

The CoRe 96 head supports liquid handling operations for 96 channels at once. Here’s how to use:

await lh.pick_up_tips96(tiprack)
await lh.aspirate_plate(plt_car[0].resource, volume=200)
await lh.dispense_plate(plt_car[2].resource, volume=200)
await lh.drop_tips96(tiprack)

The simulator after the liquid handling operations completed

Shutting down#

When you’re done, remember to shut down the simulator by calling stop().

await lh.stop()