Skip to content

Commit

Permalink
full draft of dry run use case
Browse files Browse the repository at this point in the history
  • Loading branch information
ecormany committed May 1, 2024
1 parent 7356db0 commit d50342a
Showing 1 changed file with 85 additions and 4 deletions.
89 changes: 85 additions & 4 deletions api/docs/v2/parameters/use_case_dry_run.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
Parameter Use Case – Dry Run
****************************

When testing out a new protocol, it's common to perform a dry run to watch your robot go through all the steps without actually handling samples or reagents. This use case explores how to add a single boolean parameter for whether you're performing a dry run.
When testing out a new protocol, it's common to perform a dry run to watch your robot go through all the steps without actually handling samples or reagents. This use case explores how to add a single boolean parameter for whether you're performing a dry run.

The code examples will show how this single value can control:

- Skipping module actions and other delays.
- Skipping module actions and long pauses.
- Reducing mix repetitions to save time.
- Returning tips (that never touched any liquid) to their racks.

Expand All @@ -26,21 +26,102 @@ First, we need to set up the dry run parameter. We want to set up a simple yes/n
parameters.add_bool(
variable_name="dry_run",
display_name="Dry Run",
description="Skip delays, shorten mix steps, and return tips to their racks.",
description=(
"Skip pauses,"
" shorten mix steps,"
" and return tips to their racks."
),
default=False
)

This parameter is set to ``False`` by default, assuming that most runs will be live runs. In other words, during run setup the technician will have to change the parameter setting to perform a dry run. If they leave it as is, the robot will perform a live run.

Additionally, since "dry run" can have different meanings in different contexts, it's important to include a ``description`` that indicates exactly what the parameter will control — in this case, three things. The following sections will show how to accomplish each of those when the dry run parameter is set to ``True``.

Skipping Delays
Skipping Pauses
===============

Many protocols have built-in pauses, either for a module to work or to let a reaction happen passively. Lengthy pauses just get in the way when verifying a protocol with a dry run. So wherever the protocol calls for a pause, we can check the value of ``protocol.params.dry_run`` and make the protocol behave accordingly.

To start, let's consider a simple :py:meth:`.pause` command. We can wrap it in an ``if`` statement such that the pause will only execute when the run is *not* a dry run::

if protocol.params.dry_run == False:
protocol.pause(minutes=5)

You can extend this approach to more complex situations, like module interactions. For example, in a protocol that moves a plate to the Thermocycler for an incubation, you'll want to perform all the movement steps — opening and closing the module lid, and moving the plate to and from the block — but skip the heating and cooling time. The simplest way to do this is, like in the pause example above, to wrap each skippable command::

protocol.move_labware(labware=plate, new_location=tc_mod, use_gripper=True)
if protocol.params.dry_run == False:
tc_mod.set_block_temperature(4)
tc_mod.set_lid_temperature(100)
tc_mod.close_lid()
pcr_profile = [
{"temperature": 68, "hold_time_seconds": 180},
{"temperature": 98, "hold_time_seconds": 180},
]
if protocol.params.dry_run == False:
tc_mod.execute_profile(
steps=pcr_profile, repetitions=1, block_max_volume=50
)
tc_mod.open_lid()

Shortening Mix Steps
====================

Similar to pauses, mix steps can take a long time because they are inherently repetitive actions. Mixing ten times takes ten times as long as mixing once! To save time, set a mix repetitions variable based on the value of ``protocol.params.dry_run`` and pass that to :py:meth:`.mix`::

if protocol.params.dry_run == True:
mix_reps = 1
else:
mix_reps = 10
pipette.mix(repetitions=mix_reps, volume=50, location=plate["A1"].bottom())

Note that this checks whether the dry run parameter is ``True``. If you prefer to set up all your ``if`` statements to check whether it's ``False``, you can reverse the logic::

if protocol.params.dry_run == False:
mix_reps = 10
else:
mix_reps = 1

Returning Tips
==============

Tips used in a dry run should be reusable — for another dry run, if nothing else. It doesn't make sense to dispose of them in a trash container, unless you specifically need to test movement to the trash. You can choose whether to use :py:meth:`.drop_tip` or :py:meth:`.return_tip` based on the value of ``protocol.params.dry_run``. If the protocol doesn't have too many tip drop actions, you can use an ``if`` statement each time::

if protocol.params.dry_run == True:
pipette.drop_tip()
else:
pipette.return_tip()

However, repeating this block every time you handle tips could significantly clutter your code. Instead, you could define it as a function::

def return_or_drop(pipette):
if protocol.params.dry_run == True:
pipette.drop_tip()
else:
pipette.return_tip()

Then call that function throughout your protocol::

pipette.pick_up_tip()
return_or_drop(pipette)

.. note::

It's generally better to define a standalone function, rather than adding a method to the :py:class:`.InstrumentContext` class. This makes your custom, parameterized commands stand out from API methods in your code.

Additionally, if your protocol uses enough tips that you have to replenish tip racks, you'll need separate behavior for dry runs and live runs. In a live run, once you've used all the tips, the rack is empty, because the tips are in the trash. In a dry run, once you've used all the tips in a rack, the rack is *full*, because you returned the tips.

The API has methods to handle both of these situations. To continue using the same tip rack without physically replace it, call :py:meth:`.reset_tipracks`. In the live run, move the empty tip rack off the deck and move a full one into place::

if protocol.params.dry_run == True:
pipette.reset_tipracks()
else:
protocol.move_labware(
labware=tips_1, new_location=chute, use_gripper=True
)
protocol.move_labware(
labware=tips_2, new_location=D3, use_gripper=True
)

You can modify this code for similar cases. You may be moving tip racks by hand, rather than with the gripper. Or you could even mix the two, moving the used (but full) rack off-deck by hand — instead of dropping it down the chute, spilling all the tips — and have the gripper move a new rack into place. Ultimately, it's up to you to fine-tune your dry run behavior, and communicate it to your protocol's users with your parameter descriptions.

0 comments on commit d50342a

Please sign in to comment.