import assert from 'assert' ; import flatMap from 'lodash/flatMap' ; import mapValues from 'lodash/mapValues' ; import range from 'lodash/range' ; import reduce from 'lodash/reduce' ; import { getIsTiprack , getLabwareDefURI , getWellsDepth , getWellNamePerMultiTip , } from '@opentrons/shared-data' ; import { blowout } from '../commandCreators/atomic/blowout' ; import { curryCommandCreator } from './curryCommandCreator' ; import type { LabwareDefinition2 } from '@opentrons/shared-data' ; import type { BlowoutParams } from '@opentrons/shared-data/protocol/types/schemaV4' ; import type { CurriedCommandCreator , InvariantContext , LabwareEntity , LocationLiquidState , PipetteEntity , RobotState , SourceAndDest , } from '../types' ; export const AIR = '__air__' ; export const SOURCE_WELL_BLOWOUT_DESTINATION = 'source_well' ; export const DEST_WELL_BLOWOUT_DESTINATION = 'dest_well' ; export function repeatArray < T > ( array , repeats ) { return flatMap ( range ( repeats ) , ( i ) : T [ ] => array ) ; } interface Vol { volume : number ; } /** Total volume of a location ("air" is not included in the sum) */ export function getLocationTotalVolume ( loc ) { return reduce ( loc , ( acc , ingredState , ingredId ) => { return ingredId === AIR ? acc : acc + ingredState . volume ; } , 0 ) ; } /** Breaks a liquid volume state into 2 parts. Assumes all liquids are evenly mixed. */ export function splitLiquid ( volume , sourceLiquidState ) { const totalSourceVolume = getLocationTotalVolume ( sourceLiquidState ) ; if ( totalSourceVolume === 0 ) { return { source : sourceLiquidState , dest : { [ AIR ] : { volume , } , } , } ; } if ( volume > totalSourceVolume ) { return { source : mapValues ( sourceLiquidState , ( ) => ( { volume : 0 , } ) ) , dest : { ... sourceLiquidState , [ AIR ] : { volume : volume - totalSourceVolume , } , } , } ; } const ratios = reduce ( sourceLiquidState , ( acc , ingredState , ingredId ) => ( { ... acc , [ ingredId ] : ingredState . volume / totalSourceVolume , } ) , { } ) ; return Object . keys ( sourceLiquidState ) . reduce ( ( acc , ingredId ) => { const destVol = ratios [ ingredId ] * volume ; return { source : { ... acc . source , [ ingredId ] : { volume : sourceLiquidState [ ingredId ] . volume - destVol , } , } , dest : { ... acc . dest , [ ingredId ] : { volume : destVol , } , } , } ; } , { source : { } , dest : { } , } ) ; } /** The converse of splitLiquid. Adds all of one liquid to the other.
 * The args are called 'source' and 'dest', but here they're interchangable.
 */ export function mergeLiquid ( source , dest ) { return { ... dest , ... reduce < LocationLiquidState , LocationLiquidState > ( source , ( acc , ingredState , ingredId ) => { const isCommonIngred = ingredId in dest ; const ingredVolume = isCommonIngred ? ingredState . volume + dest [ ingredId ] . volume : ingredState . volume ; return { ... acc , [ ingredId ] : { volume : ingredVolume , } , } ; } , { } ) , } ; } export function getWellsForTips ( channels , labwareDef , well ) { const wellsForTips = channels === 1 ? [ well ] : getWellNamePerMultiTip ( labwareDef , well ) ; if ( ! wellsForTips ) { console . warn ( channels === 1 ? `Invalid well: ${ well }` : `For labware def (URI ${ getLabwareDefURI ( labwareDef ) }), with primary well ${ well }, no wells are accessible by 8-channel's 1st tip` ) ; return { wellsForTips : [ ] , allWellsShared : false , } ; } const allWellsShared = wellsForTips . every ( w => w && w === wellsForTips [ 0 ] ) ; return { wellsForTips , allWellsShared , } ; } export const blowoutUtil = ( args ) : CurriedCommandCreator [ ] => { const { pipette , sourceLabwareId , sourceWell , destLabwareId , destWell , blowoutLocation , flowRate , offsetFromTopMm , invariantContext , } = args ; if ( ! blowoutLocation ) return [ ] ; let labware ; let well ; if ( blowoutLocation === SOURCE_WELL_BLOWOUT_DESTINATION ) { labware = invariantContext . labwareEntities [ sourceLabwareId ] ; well = sourceWell ; } else if ( blowoutLocation === DEST_WELL_BLOWOUT_DESTINATION ) { labware = invariantContext . labwareEntities [ destLabwareId ] ; well = destWell ; } else { labware = invariantContext . labwareEntities ?. [ blowoutLocation ] ; well = 'A1' ; if ( ! labware ) { assert ( false , `expected a labwareId for blowoutUtil's "blowoutLocation", got ${ blowoutLocation }` ) ; return [ ] ; } } const offsetFromBottomMm = getWellsDepth ( labware . def , [ well ] ) + offsetFromTopMm ; return [ curryCommandCreator ( blowout , { pipette : pipette , labware : labware . id , well , flowRate , offsetFromBottomMm , } ) , ] ; } ; export function createEmptyLiquidState ( invariantContext ) { const { labwareEntities , pipetteEntities } = invariantContext ; return { pipettes : reduce ( pipetteEntities , ( acc , pipette , id ) => { const pipetteSpec = pipette . spec ; return { ... acc , [ id ] : createTipLiquidState ( pipetteSpec . channels , { } ) } ; } , { } ) , labware : reduce ( labwareEntities , ( acc , labware , id ) => { return { ... acc , [ id ] : mapValues ( labware . def . wells , ( ) => ( { } ) ) } ; } , { } ) , } ; } export function createTipLiquidState < T > ( channels , contents ) { return range ( channels ) . reduce ( ( tipIdAcc , tipId ) => ( { ... tipIdAcc , [ tipId ] : contents } ) , { } ) ; } export const getDispenseAirGapLocation = ( args ) : { dispenseAirGapLabware : string ; dispenseAirGapWell : string ; } => { const { blowoutLocation , sourceLabware , destLabware , sourceWell , destWell , } = args ; return blowoutLocation === SOURCE_WELL_BLOWOUT_DESTINATION ? { dispenseAirGapLabware : sourceLabware , dispenseAirGapWell : sourceWell , } : { dispenseAirGapLabware : destLabware , dispenseAirGapWell : destWell , } ; } ; export function makeInitialRobotState ( args ) { const { invariantContext , labwareLocations , moduleLocations , pipetteLocations , } = args ; return { labware : labwareLocations , modules : moduleLocations , pipettes : pipetteLocations , liquidState : createEmptyLiquidState ( invariantContext ) , tipState : { pipettes : reduce ( pipetteLocations , ( acc , pipetteTemporalProperties , id ) => pipetteTemporalProperties . mount ? { ... acc , [ id ] : false } : acc , { } ) , tipracks : reduce ( labwareLocations , ( acc , _ , labwareId ) => { const def = invariantContext . labwareEntities [ labwareId ] . def ; if ( ! getIsTiprack ( def ) ) return acc ; const tipState = mapValues ( def . wells , ( ) => true ) ; return { ... acc , [ labwareId ] : tipState } ; } , { } ) , } , } ; }