Variables, Calculations, and Loops: Making Your Venus Method Flexible
The difference between a script and a method
A static sequence of pipetting steps, pick up tips, aspirate from A1, dispense to B1, eject, repeat, is a script. It does one thing, in one way, every time it runs. Change the sample count, change the volume, change the plate layout, and you need a new script.
A method is different. It uses variables, calculations, and loops to adapt its behaviour at runtime: processing a different number of samples, using a volume the operator entered, branching to different logic depending on what it finds. Variables and loops are what separate a rigid pipeline from a reusable instrument method.
This article covers the General Steps in Venus, the non-hardware steps that handle logic, and shows how they combine with sequence manipulation and channel patterns to build methods that flex without rewriting.
Variables in Venus
A variable in Venus is a named storage location that holds a single value. That value can be a number (integer or float), a string of text, or a Boolean (true/false). Variables are declared in the method editor and can be read, written, and calculated throughout the method.
Assignment
The Assignment step writes a value to a variable. It's the simplest use of variables and the most common.
sampleCount = 48
transferVolume = 200.0
reagentName = "Buffer A"
Variables set by Assignment can be used in any subsequent step that accepts a variable, including sequence position parameters, volume fields in aspirate/dispense steps, loop counts, and channel pattern strings.
Assignment with Calculation
Assignment with Calculation evaluates a mathematical expression and stores the result. Venus supports standard arithmetic operators and comparisons.
Examples:
totalVolume = sampleCount * transferVolume
deadVolumeBuffer = totalVolume * 1.1
wellsPerColumn = sampleCount / 8
remainingWells = 96 - sampleCount
Calculations can reference other variables, allowing you to build derived values. The order of steps matters: the variable being referenced must have been assigned before the calculation runs.
Common use case, dynamic dead volume calculation:
sampleCount = [user input or file read]
transferVolume = 50.0
totalTransferVolume = sampleCount * transferVolume
reagentRequired = totalTransferVolume * 1.1
This reagentRequired value can then be displayed to the operator via User Output before the run begins, prompting them to confirm sufficient reagent is in the trough. See dead volume in Hamilton reagent reservoirs for how to calculate that buffer from geometry rather than a flat percentage.
Loops
The Loop step repeats a block of steps a specified number of times. Between the Loop step and its matching End Loop, you place the steps that should repeat.
Loop (sampleCount / 8) times:
Tip Pick Up (8 channels)
Aspirate from sourceSequence (8 channels)
Dispense to destSequence (8 channels)
Tip Eject
End Loop
In this example, sampleCount / 8 calculates the number of column-wise iterations needed for a given sample count. For 96 samples: 12 iterations. For 48 samples: 6 iterations. The same loop structure handles both.
Loop counter variable
The Loop step automatically maintains a loop counter variable (the loop index). This counter starts at 0 (or 1, depending on configuration) and increments with each iteration. You can read this counter inside the loop to make decisions based on which iteration is running, for example to change the channel pattern on the last iteration, or to display progress to the operator.
Loop: Break
Loop: Break exits the current loop immediately, regardless of whether the iteration count has been reached. Use this in combination with If, Else to exit a loop when a condition is met, for example when an LLD error is detected, when a barcode match fails, or when a calculated remaining volume falls below a threshold.
Conditional branching: If, Else
The If, Else step evaluates a condition and routes the method to different steps depending on whether the condition is true or false.
If (remainingVolume < deadVolumeThreshold):
User Output: "Warning: reagent volume may be insufficient."
Abort
Else:
Continue with aspiration
End If
Conditions can compare variables to hard-coded values, or compare two variables to each other. Comparison operators: =, not equal, <, >, <=, >=.
If, Else blocks can be nested for more complex branching. Deep nesting becomes hard to read; if you find yourself nesting more than two levels deep, consider restructuring the logic with intermediate variables or submethods.
Arrays
An array is an ordered list of values stored under a single variable name, accessed by index. Arrays are useful when you need to store multiple values of the same type: a list of barcode strings, a list of volumes for variable-volume transfers, a list of sample IDs.
Venus General Steps for arrays:
Array: Declare / Set Size: initialise an array with a defined number of slotsArray: Set At: write a value to a specific index positionArray: Get At: read the value at a specific index positionArray: Get Size: return the current number of populated elementsArray: Copy: duplicate an array into another array variable
Common use case, variable volume transfers from a file:
[File Read step reads volumes from CSV into an array: volumes[]]
Loop 48 times:
i = [loop counter]
currentVolume = volumes[i]
Aspirate currentVolume µL from sourceSequence
Dispense currentVolume µL to destSequence
End Loop
The aspirate and dispense volume fields reference the currentVolume variable, which is updated from the array on each iteration. The result is a method that transfers a different volume to each well, all specified in an external file. Reading volumes from a file is covered fully in the final article in this series.
Channel patterns as variables
The channel pattern for a pipetting step can be passed as a string variable. This is one of the most practically useful advanced features in Venus, and it's frequently overlooked.
A channel pattern string is 8 characters of 1 or 0. As a variable, it can be calculated or read from a file at runtime.
Use case: variable sample count with partial last column
If you have 76 samples in a 96-well column-wise layout, the first 9 columns are full (72 wells) and the last column has 4 samples (A through D of column 10). A hard-coded 11111111 channel pattern on the last aspiration would attempt to pick up tips for all 8 channels, but only 4 tip positions are available.
With a variable channel pattern:
remainingSamples = 76 - (9 * 8) // = 4
channelPattern = "11110000"
The last column aspiration uses channelPattern as its channel pattern parameter, picking up on channels 1 to 4 only.
Venus doesn't have a built-in string-construction operator, but you can build channel pattern strings using Assignment steps with string concatenation (the + operator on string variables) in a loop. For each active channel, concatenate "1"; for each inactive channel, concatenate "0".
For methods that always process complete columns, hard-coding 11111111 is fine. For methods with variable sample counts, the dynamic channel pattern is the clean solution.
User Input and User Output: operator interaction
User Input
The User Input step pauses the method and displays a dialog box with a prompt. The operator types a value, which is stored in a variable and used in subsequent method steps.
This is how you make a method that asks at runtime: "How many samples?" or "What is the transfer volume?"
User Input: "Enter sample count (max 96):"
sampleCount = [operator input]
If (sampleCount > 96 OR sampleCount < 1):
User Output: "Invalid sample count. Method aborted."
Abort
End If
Input validation after a User Input step is important. The robot will accept whatever value the operator enters. If the value is out of range or of the wrong type, downstream steps will fail with cryptic errors unless you check the value explicitly.
User Output
The User Output step displays a message to the operator. It can display static text or include variable values. Use it to:
- Prompt the operator to load a new tip rack, add reagent, or remove a plate
- Display calculated values before a run begins (reagent required, expected run time)
- Confirm a step was completed ("Thermal cycling complete. Ready to pipette.")
- Report a warning condition without aborting the method
User Output pauses the method until the operator clicks OK (unless configured as a non-blocking message). Use it at transition points, between load and run, between incubation and next dispense, where operator confirmation is appropriate.
Timers
Venus includes four timer steps:
Timer: Start: begins a named timer running in the backgroundTimer: Wait for: pauses the method until a specified duration has elapsed on the named timerTimer: Read Elapsed Time: reads the elapsed time into a variableTimer: Restart: resets and restarts the timer
Timers enable incubation steps within a method without using a fixed sleep (which consumes instrument time without allowing other actions). The pattern:
Timer: Start ("incubationTimer")
[Pipette other samples while waiting]
Timer: Wait for ("incubationTimer", 1800) // wait until 30 minutes have elapsed
[Continue with next step]
This is the correct way to implement timed incubation steps in Venus. A simple pause step would freeze the entire method: no arm movement, no status updates, no ability to run other pipetting steps in parallel. Timer: Wait for blocks only when the elapsed time hasn't been reached, allowing other logic to execute in the meantime. Running pipetting and timers in parallel is covered in the next article.
Submethods: organising complex methods
As a method grows beyond 20 to 30 steps, organising it into submethods becomes important for readability and reuse. A submethod is a named block of steps that can be called from the main method (or from other submethods).
Submethods are defined in Venus as separate method blocks within the same file. They're called using the standard method step mechanism. Parameters can be passed to submethods as variables.
Benefits of submethods:
- A complex method becomes a readable series of named operations:
InitialiseInstrument(),LoadSamples(),DistributeReagent(),TransferSamples(),Unload() - Common operations (tip pickup with a specific pattern, washing steps) are written once and called multiple times
- Debugging is easier; you can test a submethod independently before integrating it
For any method longer than 40 steps, extract repeated operation groups into submethods. Name them clearly.
Putting it together: a variable-count sample transfer method
Here's the structure of a simple but flexible method that transfers a variable number of samples from a source plate to a destination plate, using variables, loops, and conditional branching.
// Step 1: Get sample count from operator
User Input: "Enter number of samples (1-96):"
sampleCount = [operator input]
// Step 2: Validate
If (sampleCount < 1 OR sampleCount > 96):
User Output: "Invalid sample count."
Abort
End If
// Step 3: Calculate iterations and remainder
fullColumns = sampleCount / 8
remainderSamples = sampleCount MOD 8
// Step 4: Set sequence end positions to match sample count
Sequence: Set End Position (sourcePlate, sampleCount)
Sequence: Set End Position (destPlate, sampleCount)
// Step 5: Process full columns
Loop fullColumns times:
Tip Pick Up (11111111)
Aspirate from sourcePlate (11111111)
Dispense to destPlate (11111111)
Tip Eject
End Loop
// Step 6: Process partial last column if remainder exists
If (remainderSamples > 0):
channelPattern = [build from remainderSamples]
Tip Pick Up (channelPattern)
Aspirate from sourcePlate (channelPattern)
Dispense to destPlate (channelPattern)
Tip Eject
End If
// Step 7: Confirm completion
User Output: "Transfer complete. [sampleCount] samples processed."
This method handles any sample count from 1 to 96 with no modification. The operator enters the sample count at runtime. The rest adapts.
The final article in this series opens the method up to the outside world: reading worklists, writing results, and running other programs from within a method. See file I/O and advanced integration.
ProtocolPilot generates optimised, ready-to-run scripts for Tecan and Hamilton from your assay parameters — flagging dead volume, labware, and liquid class gaps before you run a single tip.
Try ProtocolPilot →