Simulation
Every liquid handler class has a matching mock. The mock has the same interface as the real device (same methods, same deck API, same liquid tracking) but executes locally, without network calls and without touching hardware. You can run a full protocol against a mock and inspect every well's volume when it is done.
Mocks are available for each supported handler — MicrolabSTARMock, BravoMock, and so on — all importable from unitelabs.liquid_handling.testing. They are drop-in replacements: swap MicrolabSTAR for MicrolabSTARMock and the rest of your protocol code is unchanged.
from unitelabs.liquid_handling.testing import MicrolabSTARMock
hamilton = MicrolabSTARMock()
# The same setup/build-deck/pipetting code that runs against real hardware.
What the mock validates
The mock is a functional model of the device, not a full physics simulation. It is good at catching structural and state errors:
- Deck conflicts — placing two resources at the same position
- Volume errors — aspirating more than a well contains, overfilling a well
- Tip state errors — aspirating without tips, dropping tips that were never picked up
- Wrong resource types — placing a plate in a tip carrier slot
- Liquid tracking — every aspirate and dispense updates volumes, so you can assert the final state
This is enough to find off-by-one errors, wrong well indexing, mis-ordered pickups, and volume arithmetic mistakes before any liquid is touched.
What the mock does not validate
The mock deliberately stops short of simulating physics. It will not catch:
- Physical reachability or collision detection — whether the arm can actually move to a position without hitting something
- Timing and speed parameters — flow rates, velocities, and dwell times do not produce physical effects
- Hardware-specific errors — pressure, clot detection, optical verifications
- Channel movement state — channel positions between commands are not tracked
Use real hardware for these.
Why this boundary matters
The boundary is deliberate. A simulator that tries to model physics produces false confidence when the model is wrong and false alarms when the model is too conservative. A simulator that only models the state machine — what the SDK knows — is honest about what it covers.
Running protocols against the mock first catches the class of bugs that are cheapest to fix: arithmetic, indexing, sequencing. The bugs that remain are the ones that need hardware anyway.
Switching between mock and real
The two objects are interface-compatible. Most protocols gate the choice behind a flag:
if mock_run:
hamilton = MicrolabSTARMock()
else:
hamilton = MicrolabSTAR(name="Microlab STAR", client=client)
All downstream code — pipetting commands, movement, module activation — works identically against either.
Where to go next
- Simulation — the procedural how-to, including mock configuration, module activation in simulation, and inspecting liquid state
- Your First Protocol — a runnable mock-first example