From 92899ea1616a017bde8ecf3507d51aaaf65c5742 Mon Sep 17 00:00:00 2001 From: IanLondon Date: Wed, 5 Dec 2018 17:12:19 -0500 Subject: [PATCH] fix(protocol-designer): finish implementing flow rate in PD Closes #2773 --- .../src/step-generation/aspirate.js | 4 ++ .../src/step-generation/consolidate.js | 12 +++- .../src/step-generation/dispense.js | 4 ++ .../src/step-generation/distribute.js | 8 ++- protocol-designer/src/step-generation/mix.js | 15 +++-- .../test-with-flow/aspirate.test.js | 66 ++++++++++++++----- .../test-with-flow/dispense.test.js | 65 +++++++++++++++--- .../fixtures/commandFixtures.js | 12 +--- .../test-with-flow/mix.test.js | 41 ++++++++++++ .../src/step-generation/transfer.js | 16 ++++- .../src/step-generation/types.js | 8 +++ .../formLevel/stepFormToArgs/mixFormToArgs.js | 10 ++- .../stepFormToArgs/transferLikeFormToArgs.js | 5 ++ 13 files changed, 218 insertions(+), 48 deletions(-) diff --git a/protocol-designer/src/step-generation/aspirate.js b/protocol-designer/src/step-generation/aspirate.js index 6598da0eb707..0db30e9f8614 100644 --- a/protocol-designer/src/step-generation/aspirate.js +++ b/protocol-designer/src/step-generation/aspirate.js @@ -7,6 +7,7 @@ import type {RobotState, CommandCreator, CommandCreatorError, AspirateDispenseAr /** Aspirate with given args. Requires tip. */ const aspirate = (args: AspirateDispenseArgs): CommandCreator => (prevRobotState: RobotState) => { const {pipette, volume, labware, well, offsetFromBottomMm} = args + const flowRateUlSec = args['flow-rate'] const actionName = 'aspirate' let errors: Array = [] @@ -48,6 +49,9 @@ const aspirate = (args: AspirateDispenseArgs): CommandCreator => (prevRobotState offsetFromBottomMm: offsetFromBottomMm == null ? undefined : offsetFromBottomMm, + 'flow-rate': flowRateUlSec == null + ? undefined + : flowRateUlSec, }, }] diff --git a/protocol-designer/src/step-generation/consolidate.js b/protocol-designer/src/step-generation/consolidate.js index 3f438fccd1d9..c7d453fc4b0e 100644 --- a/protocol-designer/src/step-generation/consolidate.js +++ b/protocol-designer/src/step-generation/consolidate.js @@ -36,6 +36,8 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev } const { + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, } = data @@ -64,6 +66,7 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev volume: data.volume, labware: data.sourceLabware, well: sourceWell, + 'flow-rate': aspirateFlowRateUlSec, offsetFromBottomMm: aspirateOffsetFromBottomMm, }), ...touchTipAfterAspirateCommand, @@ -96,7 +99,9 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev data.mixFirstAspirate.volume, data.mixFirstAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, ) : [] @@ -109,7 +114,9 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev data.volume, 1, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, ) : [] @@ -144,6 +151,7 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev volume: data.volume * sourceWellChunk.length, labware: data.destLabware, well: data.destWell, + 'flow-rate': dispenseFlowRateUlSec, offsetFromBottomMm: dispenseOffsetFromBottomMm, }), ...touchTipAfterDispenseCommands, diff --git a/protocol-designer/src/step-generation/dispense.js b/protocol-designer/src/step-generation/dispense.js index beead2cb9c1c..b0d477616ed3 100644 --- a/protocol-designer/src/step-generation/dispense.js +++ b/protocol-designer/src/step-generation/dispense.js @@ -6,6 +6,7 @@ import type {RobotState, CommandCreator, CommandCreatorError, AspirateDispenseAr /** Dispense with given args. Requires tip. */ const dispense = (args: AspirateDispenseArgs): CommandCreator => (prevRobotState: RobotState) => { const {pipette, volume, labware, well, offsetFromBottomMm} = args + const flowRateUlSec = args['flow-rate'] const actionName = 'dispense' let errors: Array = [] @@ -32,6 +33,9 @@ const dispense = (args: AspirateDispenseArgs): CommandCreator => (prevRobotState offsetFromBottomMm: offsetFromBottomMm == null ? undefined : offsetFromBottomMm, + 'flow-rate': flowRateUlSec == null + ? undefined + : flowRateUlSec, }, }] diff --git a/protocol-designer/src/step-generation/distribute.js b/protocol-designer/src/step-generation/distribute.js index 334ba05d0d76..4e8748733e42 100644 --- a/protocol-designer/src/step-generation/distribute.js +++ b/protocol-designer/src/step-generation/distribute.js @@ -40,6 +40,8 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo } const { + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, } = data @@ -96,6 +98,7 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo volume: data.volume, labware: data.destLabware, well: destWell, + 'flow-rate': dispenseFlowRateUlSec, offsetFromBottomMm: dispenseOffsetFromBottomMm, }), ...touchTipAfterDispenseCommand, @@ -141,7 +144,9 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo data.mixBeforeAspirate.volume, data.mixBeforeAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -153,6 +158,7 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo volume: data.volume * destWellChunk.length + disposalVolume, labware: data.sourceLabware, well: data.sourceWell, + 'flow-rate': aspirateFlowRateUlSec, offsetFromBottomMm: aspirateOffsetFromBottomMm, }), ...touchTipAfterAspirateCommand, diff --git a/protocol-designer/src/step-generation/mix.js b/protocol-designer/src/step-generation/mix.js index b0bb3800af30..b998f4d733bc 100644 --- a/protocol-designer/src/step-generation/mix.js +++ b/protocol-designer/src/step-generation/mix.js @@ -9,6 +9,7 @@ import * as errorCreators from './errorCreators' import type {MixFormData, RobotState, CommandCreator, CompoundCommandCreator} from './' /** Helper fn to make mix command creators w/ minimal arguments */ +// TODO IMMEDIATELY: use named params not ordered args export function mixUtil ( pipette: string, labware: string, @@ -16,11 +17,13 @@ export function mixUtil ( volume: number, times: number, aspirateOffsetFromBottomMm?: ?number, - dispenseOffsetFromBottomMm?: ?number + dispenseOffsetFromBottomMm?: ?number, + aspirateFlowRateUlSec?: ?number, + dispenseFlowRateUlSec?: ?number, ): Array { return repeatArray([ - aspirate({pipette, volume, labware, well, offsetFromBottomMm: aspirateOffsetFromBottomMm}), - dispense({pipette, volume, labware, well, offsetFromBottomMm: dispenseOffsetFromBottomMm}), + aspirate({pipette, volume, labware, well, offsetFromBottomMm: aspirateOffsetFromBottomMm, 'flow-rate': aspirateFlowRateUlSec}), + dispense({pipette, volume, labware, well, offsetFromBottomMm: dispenseOffsetFromBottomMm, 'flow-rate': dispenseFlowRateUlSec}), ], times) } @@ -46,6 +49,8 @@ const mix = (data: MixFormData): CompoundCommandCreator => (prevRobotState: Robo changeTip, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, } = data // Errors @@ -102,7 +107,9 @@ const mix = (data: MixFormData): CompoundCommandCreator => (prevRobotState: Robo volume, times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) return [ diff --git a/protocol-designer/src/step-generation/test-with-flow/aspirate.test.js b/protocol-designer/src/step-generation/test-with-flow/aspirate.test.js index 269bad4664a0..04b705d702d1 100644 --- a/protocol-designer/src/step-generation/test-with-flow/aspirate.test.js +++ b/protocol-designer/src/step-generation/test-with-flow/aspirate.test.js @@ -55,25 +55,55 @@ describe('aspirate', () => { tipracks: [300, 300], }) - test('aspirate with tip', () => { - const result = aspirate({ - pipette: 'p300SingleId', - volume: 50, - labware: 'sourcePlateId', - well: 'A1', - })(robotStateWithTip) - - expect(result.commands).toEqual([{ - command: 'aspirate', - params: { - pipette: 'p300SingleId', - volume: 50, - labware: 'sourcePlateId', - well: 'A1', + describe('aspirate normally (with tip)', () => { + const optionalArgsCases = [ + { + description: 'no optional args', + expectInParams: false, + args: {}, }, - }]) - - expect(result.robotState).toMatchObject(robotStateWithTipNoLiquidState) + { + description: 'null optional args', + expectInParams: false, + args: { + offsetFromBottomMm: null, + 'flow-rate': null, + }, + }, + { + description: 'all optional args', + expectInParams: true, + args: { + offsetFromBottomMm: 5, + 'flow-rate': 6, + }, + }, + ] + + optionalArgsCases.forEach(testCase => { + test(testCase.description, () => { + const result = aspirate({ + pipette: 'p300SingleId', + volume: 50, + labware: 'sourcePlateId', + well: 'A1', + ...testCase.args, + })(robotStateWithTip) + + expect(result.commands).toEqual([{ + command: 'aspirate', + params: { + pipette: 'p300SingleId', + volume: 50, + labware: 'sourcePlateId', + well: 'A1', + ...(testCase.expectInParams ? testCase.args : {}), + }, + }]) + + expect(result.robotState).toMatchObject(robotStateWithTipNoLiquidState) + }) + }) }) test('aspirate with volume > tip max volume should throw error', () => { diff --git a/protocol-designer/src/step-generation/test-with-flow/dispense.test.js b/protocol-designer/src/step-generation/test-with-flow/dispense.test.js index cac47ba9c766..394e4821dd67 100644 --- a/protocol-designer/src/step-generation/test-with-flow/dispense.test.js +++ b/protocol-designer/src/step-generation/test-with-flow/dispense.test.js @@ -43,22 +43,69 @@ describe('dispense', () => { }) describe('tip tracking & commands:', () => { - test('dispense with tip', () => { - const result = dispense({ + describe('dispense normally (with tip)', () => { + const optionalArgsCases = [ + { + description: 'no optional args', + expectInParams: false, + args: {}, + }, + { + description: 'null optional args', + expectInParams: false, + args: { + offsetFromBottomMm: null, + 'flow-rate': null, + }, + }, + { + description: 'all optional args', + expectInParams: true, + args: { + offsetFromBottomMm: 5, + 'flow-rate': 6, + }, + }, + ] + optionalArgsCases.forEach(testCase => { + test(testCase.description, () => { + const result = dispense({ + pipette: 'p300SingleId', + volume: 50, + labware: 'sourcePlateId', + well: 'A1', + ...testCase.args, + })(robotStateWithTip) + + expect(result.commands).toEqual([{ + command: 'dispense', + params: { + pipette: 'p300SingleId', + volume: 50, + labware: 'sourcePlateId', + well: 'A1', + ...(testCase.expectInParams ? testCase.args : {}), + }, + }]) + }) + }) + }) + + test('dispense normally (with tip) and optional args', () => { + const args = { pipette: 'p300SingleId', volume: 50, labware: 'sourcePlateId', well: 'A1', - })(robotStateWithTip) + offsetFromBottomMm: 5, + 'flow-rate': 6, + } + + const result = dispense(args)(robotStateWithTip) expect(result.commands).toEqual([{ command: 'dispense', - params: { - pipette: 'p300SingleId', - volume: 50, - labware: 'sourcePlateId', - well: 'A1', - }, + params: args, }]) }) diff --git a/protocol-designer/src/step-generation/test-with-flow/fixtures/commandFixtures.js b/protocol-designer/src/step-generation/test-with-flow/fixtures/commandFixtures.js index 8ead332e7fc3..52dac99c8630 100644 --- a/protocol-designer/src/step-generation/test-with-flow/fixtures/commandFixtures.js +++ b/protocol-designer/src/step-generation/test-with-flow/fixtures/commandFixtures.js @@ -1,6 +1,6 @@ // @flow import {tiprackWellNamesFlat} from '../../data' -import type {Command} from '../../types' +import type {AspirateDispenseArgs, Command} from '../../types' export const replaceTipCommands = (tip: number | string): Array => [ dropTip('A1'), @@ -49,10 +49,7 @@ export const touchTip = ( export const aspirate = ( well: string, volume: number, - params?: {| - pipette?: string, - labware?: string, - |} + params?: $Shape ): Command => ({ command: 'aspirate', params: { @@ -67,10 +64,7 @@ export const aspirate = ( export const dispense = ( well: string, volume: number, - params?: {| - pipette?: string, - labware?: string, - |} + params?: $Shape ): Command => ({ command: 'dispense', params: { diff --git a/protocol-designer/src/step-generation/test-with-flow/mix.test.js b/protocol-designer/src/step-generation/test-with-flow/mix.test.js index 73a02a1c8095..9ae0ec8d41a6 100644 --- a/protocol-designer/src/step-generation/test-with-flow/mix.test.js +++ b/protocol-designer/src/step-generation/test-with-flow/mix.test.js @@ -130,6 +130,43 @@ describe('mix: advanced options', () => { const times = 2 const blowoutLabwareId = 'destPlateId' + test('flow rate', () => { + const ASPIRATE_OFFSET = 11 + const DISPENSE_OFFSET = 12 + const ASPIRATE_FLOW_RATE = 3 + const DISPENSE_FLOW_RATE = 6 + const args: MixFormData = { + ...mixinArgs, + volume, + times, + wells: ['A1'], + changeTip: 'once', + aspirateOffsetFromBottomMm: ASPIRATE_OFFSET, + dispenseOffsetFromBottomMm: DISPENSE_OFFSET, + aspirateFlowRateUlSec: ASPIRATE_FLOW_RATE, + dispenseFlowRateUlSec: DISPENSE_FLOW_RATE, + } + + const aspirateParams = { + 'flow-rate': ASPIRATE_FLOW_RATE, + offsetFromBottomMm: ASPIRATE_OFFSET, + } + const dispenseParams = { + 'flow-rate': DISPENSE_FLOW_RATE, + offsetFromBottomMm: DISPENSE_OFFSET, + } + + const result = mix(args)(robotInitialState) + expect(result.commands).toEqual([ + ...cmd.replaceTipCommands(0), + {...cmd.aspirate('A1', volume, aspirateParams)}, + {...cmd.dispense('A1', volume, dispenseParams)}, + + {...cmd.aspirate('A1', volume, aspirateParams)}, + {...cmd.dispense('A1', volume, dispenseParams)}, + ]) + }) + test('touch tip (after each dispense)', () => { const args: MixFormData = { ...mixinArgs, @@ -138,6 +175,10 @@ describe('mix: advanced options', () => { changeTip: 'always', touchTip: true, wells: ['A1', 'B1', 'C1'], + aspirateOffsetFromBottomMm: null, + dispenseOffsetFromBottomMm: null, + aspirateFlowRateUlSec: null, + dispenseFlowRateUlSec: null, } const result = mix(args)(robotInitialState) diff --git a/protocol-designer/src/step-generation/transfer.js b/protocol-designer/src/step-generation/transfer.js index df8b0dff58b5..29fa3ff2cb13 100644 --- a/protocol-designer/src/step-generation/transfer.js +++ b/protocol-designer/src/step-generation/transfer.js @@ -43,6 +43,8 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS } const { + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, } = data @@ -82,7 +84,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS Math.max(subTransferVol), 1, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -94,7 +98,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS data.mixBeforeAspirate.volume, data.mixBeforeAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -124,7 +130,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS data.mixInDestination.volume, data.mixInDestination.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -146,6 +154,7 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS volume: subTransferVol, labware: data.sourceLabware, well: sourceWell, + 'flow-rate': aspirateFlowRateUlSec, offsetFromBottomMm: aspirateOffsetFromBottomMm, }), ...touchTipAfterAspirateCommands, @@ -154,6 +163,7 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS volume: subTransferVol, labware: data.destLabware, well: destWell, + 'flow-rate': dispenseFlowRateUlSec, offsetFromBottomMm: dispenseOffsetFromBottomMm, }), ...touchTipAfterDispenseCommands, diff --git a/protocol-designer/src/step-generation/types.js b/protocol-designer/src/step-generation/types.js index b847fdec8860..0701679f940f 100644 --- a/protocol-designer/src/step-generation/types.js +++ b/protocol-designer/src/step-generation/types.js @@ -38,6 +38,8 @@ export type TransferLikeFormDataFields = { touchTipAfterAspirateOffsetMmFromBottom?: ?number, /** changeTip is interpreted differently by different Step types */ changeTip: ChangeTipOptions, + /** Flow rate in uL/sec for all aspirates */ + aspirateFlowRateUlSec?: ?number, /** offset from bottom of well in mm */ aspirateOffsetFromBottomMm?: ?number, @@ -46,6 +48,8 @@ export type TransferLikeFormDataFields = { touchTipAfterDispense: boolean, /** Optional offset for touch tip after dispense (if null, use PD default) */ touchTipAfterDispenseOffsetMmFromBottom?: ?number, + /** Flow rate in uL/sec for all dispenses */ + dispenseFlowRateUlSec?: ?number, /** offset from bottom of well in mm */ dispenseOffsetFromBottomMm?: ?number, } @@ -118,6 +122,9 @@ export type MixFormData = { /** offset from bottom of well in mm */ aspirateOffsetFromBottomMm?: ?number, dispenseOffsetFromBottomMm?: ?number, + /** flow rates in uL/sec */ + aspirateFlowRateUlSec?: ?number, + dispenseFlowRateUlSec?: ?number, } export type PauseFormData = {| @@ -221,6 +228,7 @@ export type AspirateDispenseArgs = {| ...PipetteLabwareFields, volume: number, offsetFromBottomMm?: ?number, + 'flow-rate'?: ?number, |} export type Command = {| diff --git a/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js b/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js index cdbcd1a2f121..214d17bb1249 100644 --- a/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js +++ b/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js @@ -29,10 +29,14 @@ const mixFormToArgs = (hydratedFormData: FormData): MixStepArgs => { const volume = Number(hydratedFormData.volume) || 0 const times = Number(hydratedFormData.times) || 0 + + const aspirateFlowRateUlSec = hydratedFormData['aspirate_flowRate'] + const dispenseFlowRateUlSec = hydratedFormData['dispense_flowRate'] + // NOTE: for mix, there is only one tip offset field, // and it applies to both aspirate and dispense - const aspirateOffsetFromBottomMm = Number(hydratedFormData['mix_mmFromBottom']) - const dispenseOffsetFromBottomMm = Number(hydratedFormData['mix_mmFromBottom']) + const aspirateOffsetFromBottomMm = hydratedFormData['mix_mmFromBottom'] + const dispenseOffsetFromBottomMm = hydratedFormData['mix_mmFromBottom'] // It's radiobutton, so one should always be selected. const changeTip = hydratedFormData['aspirate_changeTip'] || DEFAULT_CHANGE_TIP_OPTION @@ -52,6 +56,8 @@ const mixFormToArgs = (hydratedFormData: FormData): MixStepArgs => { changeTip, blowoutLocation, pipette: pipette.id, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, } diff --git a/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js b/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js index 5faa116af08f..fe8a94c2c223 100644 --- a/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js +++ b/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js @@ -31,6 +31,9 @@ const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArg const sourceLabware = hydratedFormData['aspirate_labware'] const destLabware = hydratedFormData['dispense_labware'] + const aspirateFlowRateUlSec = hydratedFormData['aspirate_flowRate'] + const dispenseFlowRateUlSec = hydratedFormData['dispense_flowRate'] + const aspirateOffsetFromBottomMm = hydratedFormData['aspirate_mmFromBottom'] const dispenseOffsetFromBottomMm = hydratedFormData['dispense_mmFromBottom'] @@ -75,6 +78,8 @@ const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArg sourceLabware: sourceLabware.id, destLabware: destLabware.id, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm,