From a78ad7b1ecaa3d27cbe46e4d8e087d9c20f6ccb8 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 4a02d4531238..2b18714a1e57 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 @@ -71,6 +73,7 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev volume: data.volume + (isFirstWellInChunk ? disposalVolume : 0), labware: data.sourceLabware, well: sourceWell, + 'flow-rate': aspirateFlowRateUlSec, offsetFromBottomMm: aspirateOffsetFromBottomMm, }), ...touchTipAfterAspirateCommand, @@ -113,7 +116,9 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev data.mixFirstAspirate.volume, data.mixFirstAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, ) : [] @@ -126,7 +131,9 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev data.volume, 1, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, ) : [] @@ -162,6 +169,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 a1e76cac155a..d65a5169acea 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, @@ -140,7 +143,9 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo data.mixBeforeAspirate.volume, data.mixBeforeAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -152,6 +157,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 f324e59a8e0f..51eb2ab8ed75 100644 --- a/protocol-designer/src/step-generation/mix.js +++ b/protocol-designer/src/step-generation/mix.js @@ -10,6 +10,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, @@ -17,11 +18,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) } @@ -47,6 +50,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 93a91f8613f4..e2c7b4dc1a85 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 @@ -129,6 +129,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, @@ -137,6 +174,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 d4489bfdd808..f0ba6021f222 100644 --- a/protocol-designer/src/step-generation/transfer.js +++ b/protocol-designer/src/step-generation/transfer.js @@ -42,6 +42,8 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS } const { + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, aspirateOffsetFromBottomMm, dispenseOffsetFromBottomMm, } = data @@ -81,7 +83,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS Math.max(subTransferVol), 1, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -93,7 +97,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS data.mixBeforeAspirate.volume, data.mixBeforeAspirate.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -123,7 +129,9 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS data.mixInDestination.volume, data.mixInDestination.times, aspirateOffsetFromBottomMm, - dispenseOffsetFromBottomMm + dispenseOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec ) : [] @@ -136,6 +144,7 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS volume: subTransferVol, labware: data.sourceLabware, well: sourceWell, + 'flow-rate': aspirateFlowRateUlSec, offsetFromBottomMm: aspirateOffsetFromBottomMm, }), ...touchTipAfterAspirateCommands, @@ -144,6 +153,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 e46ac791a81e..ee198cbb3ec0 100644 --- a/protocol-designer/src/step-generation/types.js +++ b/protocol-designer/src/step-generation/types.js @@ -40,6 +40,8 @@ export type TransferLikeFormDataFields = { changeTip: ChangeTipOptions, /** Disposal volume is added to the volume of the first aspirate of each asp-asp-disp cycle */ disposalVolume: ?number, + /** Flow rate in uL/sec for all aspirates */ + aspirateFlowRateUlSec?: ?number, /** offset from bottom of well in mm */ aspirateOffsetFromBottomMm?: ?number, @@ -48,6 +50,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, } @@ -114,6 +118,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 = {| @@ -217,6 +224,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 98fd501bc64d..def7a6fe1345 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, blowout: blowoutLabwareId, 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 fe970cbf0f32..da416c31dc37 100644 --- a/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js +++ b/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js @@ -33,6 +33,9 @@ const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArg const destLabware = hydratedFormData['dispense_labware'] const blowoutLabwareId = hydratedFormData['dispense_blowout_checkbox'] ? hydratedFormData['dispense_blowout_labware'] : null + 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,