def emit(scale, unit): v = self.value * scale if v >= 100: return pretty.format('{0:.0n}{1}', v, unit) if v >= 10: return pretty.format('{0:.1n}{1}', v, unit) if v >= 1: return pretty.format('{0:.2n}{1}', v, unit) return pretty.format('{0:.3n}{1}', v, unit)
def verify_well_locations(well_list: List[EnhancedWellV1], pipette: EnhancedPipetteV1): picked_tip = False if not pipette.tip_attached: pipette.pick_up_tip() picked_tip = True for well in well_list: move_to_loc = well.top() pipette.move_to(move_to_loc) # well_top_coords_absolute = well.top_coords_absolute() _, top_coords = well_vector(well.top()) _, move_to_coords = well_vector(move_to_loc) intended_coords = well_top_coords_absolute + (move_to_coords - top_coords) tip_coords = pipette.tip_coords_absolute() # robot.pause( pretty.format('verify location: {0} in {1} loc={2} tip={3}', well.get_name(), well.parent.get_name(), intended_coords, tip_coords)) if picked_tip: pipette.return_tip( ) # we didn't dirty it, we can always re-use it todo: enhance return_tip() to adjust iterator so that next pick can pick up again
def __str__(self) -> str: def test(v, scale): return not is_close(int(v * scale + 0.5), 0) def emit(scale, unit): v = self.value * scale if v >= 100: return pretty.format('{0:.0n}{1}', v, unit) if v >= 10: return pretty.format('{0:.1n}{1}', v, unit) if v >= 1: return pretty.format('{0:.2n}{1}', v, unit) return pretty.format('{0:.3n}{1}', v, unit) if self.flavor == Concentration.Flavor.Molar: if self.value == 0: return emit(1, 'M') elif test(self.value, 1): return emit(1, 'M') elif test(self.value, 1e3): return emit(1e3, 'mM') elif test(self.value, 1e6): return emit(1e6, 'uM') elif test(self.value, 1e9): return emit(1e9, 'nM') elif test(self.value, 1e12): return emit(1e12, 'pM') else: return emit(1e15, 'fM') elif self.flavor == Concentration.Flavor.X: return pretty.format('{0:.3n}x', self.value) else: return 'DC'
def done_tip(self): # a handy little utility that looks at self.config.trash_control if self.has_tip: if self.get_current_volume() > 0: info(pretty.format('{0} has {1:n} uL remaining', self.get_name(), self.get_current_volume())) if self.config.trash_control: self.drop_tip() else: self.return_tip()
def __str__(self) -> str: if self.is_empty: return '{}' else: result = '{ ' is_first = True total_volume = self.volume for liquid, volume in self.liquids.items(): if not is_first: result += ', ' if is_scalar(total_volume) and liquid.concentration.flavor != Concentration.Flavor.DontCare: dilution_factor = volume / total_volume concentration = liquid.concentration * dilution_factor result += pretty.format('{0}:{1:n}={2}', liquid.name, volume, concentration) else: result += pretty.format('{0}:{1:n}', liquid.name, volume) is_first = False result += ' }' return result
def formatted(self): result = 'well "{0:s}"'.format(self.target.get_name()) if not getattr(self.target, 'has_labelled_well_name', False): if self.liquid is not None: result += ' ("{0:s}")'.format(self.liquid) result += ':' result += pretty.format(' lo={0:n} hi={1:n} cur={2:n} taken={3:n} mix={4:s}\n', self.liquid_volume.lo_volume, self.liquid_volume.hi_volume, self.liquid_volume.current_volume, infimum(self.liquid_volume.hi_volume) - infimum(self.liquid_volume.current_volume), self.mixture.__str__()) return result
def dwell(self, seconds=0, minutes=0): # like delay() but synchronous with the back-end msg = pretty.format('Dwelling for {0:n}m {1:n}s', minutes, seconds) minutes += int(seconds / 60) seconds = seconds % 60 seconds += float(minutes * 60) def do_dwell(): self.robot.pause() self.robot._driver.delay(seconds) self.robot.resume() log_while_core(msg, do_dwell)
def dwell(self, seconds=0, minutes=0): # like delay() but synchronous with the back-end msg = pretty.format('Dwelling for {0:n}m {1:n}s', minutes, seconds) minutes += int(seconds / 60) seconds = seconds % 60 seconds += float(minutes * 60) def do_dwell(): self.config.protocol_context.pause() self._hw_manager.enhanced_hardware().dwell(seconds) self.config.protocol_context.resume() log_while_core(msg, do_dwell)
def _dispense_plunger_position(self, ul): assert tls.dispense_params mm_from_vol = super()._dispense_plunger_position( ul) # retrieve position historically used if self.config.enable_enhancements and \ (tls.dispense_params.full_dispense_from_dispense or (tls.dispense_params_transfer and tls.dispense_params_transfer.full_dispense_transfer)): mm_from_blow = self._get_plunger_position('blow_out') info( pretty.format( 'full dispensing to mm={0:n} instead of mm={1:n}', mm_from_blow, mm_from_vol)) tls.dispense_params.fully_dispensed = True return mm_from_blow else: tls.dispense_params.fully_dispensed = False return mm_from_vol
def _pre_wet(self, well: EnhancedWell, volume, location, rate, pre_wet: bool): if pre_wet is None: if tls.aspirate_params_transfer: pre_wet = tls.aspirate_params_transfer.pre_wet_transfer if pre_wet is None: pre_wet = self.config.aspirate.pre_wet.default if pre_wet and self.config.enable_enhancements: if self.tip_wetness is TipWetness.DRY: pre_wet_volume = min( self.get_max_volume() * self.config.aspirate.pre_wet.max_volume_fraction, max(volume, well.liquid_volume.available_volume_min)) pre_wet_rate = self.config.aspirate.pre_wet.rate_func(rate) self.tip_wetness = TipWetness.WETTING def do_pre_wet(): for i in range(self.config.aspirate.pre_wet.count): self.aspirate(volume=pre_wet_volume, location=location, rate=pre_wet_rate, pre_wet=False, ms_pause=0) self.dispense(volume=pre_wet_volume, location=location, rate=pre_wet_rate, full_dispense=(i+1 == self.config.aspirate.pre_wet.count)) info_while(pretty.format('prewetting tip in well {0} vol={1:n}', well.get_name(), pre_wet_volume), do_pre_wet) self.tip_wetness = TipWetness.WET
def plunger_position(self, instr: HwPipette, ul: float, action: str, call_super: Callable) -> float: mm_from_vol = call_super() result = mm_from_vol if action == 'dispense': assert tls.dispense_params if self.config.enable_enhancements and \ (tls.dispense_params.full_dispense_from_dispense or (tls.dispense_params_transfer and tls.dispense_params_transfer.full_dispense_transfer)): result = mm_from_blow = instr.config.blow_out info( pretty.format( 'full dispensing to mm={0:n} instead of mm={1:n}', mm_from_blow, mm_from_vol)) tls.dispense_params.fully_dispensed = True else: result = call_super(action) tls.dispense_params.fully_dispensed = False return result
def createMasterMix(): if manually_make_master_mix: note_liquid(location=master_mix, name='Master Mix', initially=master_mix_vol) log('Creating Master Mix') info( pretty.format( 'Master Mix recipe: water={0:n} buffer={1:n} EvaGreen={2:n} total={3:n} (extra={4}%)', master_mix_common_water_vol, master_mix_buffer_vol, master_mix_evagreen_vol, master_mix_vol, 100.0 * (mm_overhead_factor - 1))) user_prompt('Ensure master mix manually present and mixed') else: # Mostly just for fun, we put the ingredients for the master mix in a nice warm place to help them melt temp_slot = 11 temp_module = modules_manager.load('tempdeck', slot=temp_slot) screwcap_rack = labware_manager.load( 'opentrons_24_aluminumblock_generic_2ml_screwcap', slot=temp_slot, label='screwcap_rack', share=True, well_geometry=IdtTubeWellGeometry) buffers = list(zip(screwcap_rack.rows(0), buffer_volumes)) evagreens = list(zip(screwcap_rack.rows(1), evagreen_volumes)) for buffer in buffers: note_liquid(location=buffer[0], name='Buffer', initially=buffer[1], concentration=buffer_source_concentration) for evagreen in evagreens: note_liquid(location=evagreen[0], name='Evagreen', initially=evagreen[1], concentration=evagreen_source_concentration) note_liquid(location=master_mix, name='Master Mix') # Buffer was just unfrozen. Mix to ensure uniformity. EvaGreen doesn't freeze, no need to mix p50.layered_mix([buffer for buffer, __ in buffers], incr=2) # transfer from multiple source wells, each with a current defined volume def transfer_multiple(msg, xfer_vol_remaining, tubes, dest, new_tip, *args, **kwargs): tube_index = 0 cur_well = None cur_vol = 0 min_vol = 0 while xfer_vol_remaining > 0: if xfer_vol_remaining < p50_min_vol: warn( "remaining transfer volume of %f too small; ignored" % xfer_vol_remaining) return # advance to next tube if there's not enough in this tube while cur_well is None or cur_vol <= min_vol: if tube_index >= len(tubes): fatal('%s: more reagent needed' % msg) cur_well = tubes[tube_index][0] cur_vol = tubes[tube_index][1] min_vol = max( p50_min_vol, cur_vol / config. min_aspirate_factor_hack, # tolerance is proportional to specification of volume. can probably make better guess cur_well.geometry.min_aspiratable_volume) tube_index = tube_index + 1 this_vol = min(xfer_vol_remaining, cur_vol - min_vol) assert this_vol >= p50_min_vol # TODO: is this always the case? log('%s: xfer %f from %s in %s to %s in %s' % (msg, this_vol, cur_well, cur_well.parent, dest, dest.parent)) p50.transfer(this_vol, cur_well, dest, trash=config.trash_control, new_tip=new_tip, **kwargs) xfer_vol_remaining -= this_vol cur_vol -= this_vol def mix_master_mix(): log('Mixing Master Mix') p50.layered_mix( [master_mix], incr=2, initial_turnover=master_mix_evagreen_vol * 1.2, max_tip_cycles=config.layered_mix.max_tip_cycles_large) log('Creating Master Mix: Water') p50.transfer(master_mix_common_water_vol, waterB, master_mix, trash=config.trash_control) log('Creating Master Mix: Buffer') transfer_multiple( 'Creating Master Mix: Buffer', master_mix_buffer_vol, buffers, master_mix, new_tip='once', keep_last_tip=True ) # 'once' because we've only got water & buffer in context p50.done_tip() # EvaGreen needs a new tip log('Creating Master Mix: EvaGreen') transfer_multiple( 'Creating Master Mix: EvaGreen', master_mix_evagreen_vol, evagreens, master_mix, new_tip='always', keep_last_tip=True ) # 'always' to avoid contaminating the Evagreen source w/ buffer mix_master_mix()
def aspirate(self, volume=None, location=None, rate: float = 1.0, pre_wet: bool = None, ms_pause: float = None, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): if not helpers.is_number(volume): # recapitulate super if volume and not location: location = volume volume = self._working_volume - self.current_volume location = location if location else self.previous_placeable well, _ = well_vector(location) if top_clearance is None: if tls.aspirate_params_transfer: top_clearance = tls.aspirate_params_transfer.top_clearance_transfer if top_clearance is None: top_clearance = self.config.aspirate.top_clearance if bottom_clearance is None: if tls.aspirate_params_transfer: bottom_clearance = tls.aspirate_params_transfer.bottom_clearance_transfer if bottom_clearance is None: bottom_clearance = self.config.aspirate.bottom_clearance if manual_liquid_volume_allowance is None: if tls.aspirate_params_transfer: manual_liquid_volume_allowance = tls.aspirate_params_transfer.manual_manufacture_tolerance_transfer if manual_liquid_volume_allowance is None: manual_liquid_volume_allowance = self.config.aspirate.manual_liquid_volume_allowance current_liquid_volume = well.liquid_volume.current_volume_min needed_liquid_volume = well.geometry.min_aspiratable_volume + volume if current_liquid_volume < needed_liquid_volume: msg = pretty.format( 'aspirating too much from well={0} have={1:n} need={2:n}', well.get_name(), current_liquid_volume, needed_liquid_volume) warn(msg) self._pre_wet(well, volume, location, rate, pre_wet) location = self._adjust_location_to_liquid_top( location=location, aspirate_volume=volume, top_clearance=top_clearance, bottom_clearance=bottom_clearance, manual_liquid_volume_allowance=manual_liquid_volume_allowance) def call_super(): super(EnhancedPipette, self).aspirate(volume=volume, location=location, rate=rate) self._update_pose_tree_in_place(call_super) self.pause_after_aspirate(ms_pause) # finish up todo: what if we're doing an air gap well, __ = well_vector(location) well.liquid_volume.aspirate(volume) if volume != 0: self.prev_aspirated_well = well
def _run_transfer_plan(self, tips, plan, **kwargs): air_gap = kwargs.get('air_gap', 0) touch_tip = kwargs.get('touch_tip', False) is_distribute = kwargs.get('mode', 'transfer') == 'distribute' total_transfers = len(plan) seen_aspirate = False assert len(plan) == 0 or plan[0].get( 'aspirate') # first step must be an aspirate step_info_map = dict() with AspirateParamsTransfer(self.config.aspirate): with DispenseParamsTransfer(self.config.dispense): for i, step in enumerate(plan): aspirate = step.get('aspirate') dispense = step.get('dispense') if aspirate: # *always* record on aspirates so we can test has_disposal_vol on subsequent dispenses have_disposal_vol = self.has_disposal_vol( plan, i, step_info_map, **kwargs) # we might have overspill from a previous transfer. if self.current_volume > 0: info( pretty.format( 'carried over {0:n} uL from prev operation', self.current_volume)) if not seen_aspirate: assert i == 0 if kwargs.get('pre_wet', None) and kwargs.get( 'mix_before', None): warn( "simultaneous use of 'pre_wet' and 'mix_before' is not tested" ) if (kwargs.get('allow_overspill', self.config.allow_overspill_default) and self.config.enable_enhancements ) and zeroify(self.current_volume) > 0: this_aspirated_well, __ = well_vector( aspirate['location']) if self.prev_aspirated_well is this_aspirated_well: if have_disposal_vol: # try to remove current volume from this aspirate new_aspirate_vol = zeroify( aspirate.get('volume') - self.current_volume) if new_aspirate_vol == 0 or new_aspirate_vol >= self.min_volume: aspirate[ 'volume'] = new_aspirate_vol info( pretty.format( 'reduced this aspirate by {0:n} uL', self.current_volume)) extra = 0 # can't blow out since we're relying on its presence in pipette else: extra = self.current_volume - aspirate[ 'volume'] assert zeroify(extra) > 0 else: info( pretty.format( "overspill of {0:n} uL isn't for disposal", self.current_volume)) extra = self.current_volume else: # different locations; can't re-use info( 'this aspirate is from location different than current pipette contents' ) extra = self.current_volume if zeroify(extra) > 0: # quiet_log('blowing out overspill of %s uL' % format_number(self.current_volume)) self._blowout_during_transfer( loc=None, **kwargs) # loc isn't actually used elif zeroify(self.current_volume) > 0: info( pretty.format( 'blowing out unexpected overspill of {0:n} uL', self.current_volume)) self._blowout_during_transfer( loc=None, **kwargs) # loc isn't actually used seen_aspirate = True self._add_tip_during_transfer(tips, **kwargs) # Previous blow-out elisions that don't reduce their adjacent aspirates (because they're not # carrying disposal_vols) might eventually catch up with us in the form of blowing the capacity # of the pipette. When they do, we give in, and carry out the blow-out. This still can be a net # win, in that we reduce the overall number of blow-outs. We might be tempted here to reduce # the capacity of the overflowing aspirate, but that would reduce precision (we still *could* # do that if it has disposal_vol, but that doesn't seem worth it). if self.current_volume + aspirate[ 'volume'] > self._working_volume: info( pretty.format( 'current {0:n} uL with aspirate(has_disposal={1}) of {2:n} uL would overflow capacity', self.current_volume, self.has_disposal_vol( plan, i, step_info_map, **kwargs), aspirate['volume'])) self._blowout_during_transfer( loc=None, **kwargs) # loc isn't actually used tls.aspirate_params_transfer.sequester(kwargs) self._aspirate_during_transfer(aspirate['volume'], aspirate['location'], **kwargs) if dispense: if self.current_volume < dispense['volume']: warn( pretty.format( 'current {0:n} uL will truncate dispense of {1:n} uL', self.current_volume, dispense['volume'])) can_full_dispense = self.current_volume - dispense[ 'volume'] <= 0 tls.dispense_params_transfer.sequester( kwargs, can_full_dispense) self._dispense_during_transfer(dispense['volume'], dispense['location'], **kwargs) do_touch = touch_tip or touch_tip is 0 is_last_step = step is plan[-1] if is_last_step or plan[i + 1].get('aspirate'): do_drop = not is_last_step or not ( kwargs.get('keep_last_tip', False) and self.config.enable_enhancements) # original always blew here. there are several reasons we could still be forced to blow do_blow = not is_distribute # other modes (are there any?) we're not sure about do_blow = do_blow or kwargs.get( 'blow_out', False) # for compatibility do_blow = do_blow or do_touch # for compatibility do_blow = do_blow or not ( kwargs.get( 'allow_blow_elision', self.config.allow_blow_elision_default) and self.config.enable_enhancements) if not do_blow: if is_last_step: if self.current_volume > 0: if not (kwargs.get( 'allow_overspill', self.config. allow_overspill_default ) and self.config.enable_enhancements): do_blow = True elif self.current_volume > kwargs.get( 'disposal_vol', 0): warn( pretty.format( 'carried over {0:n} uL to next operation', self.current_volume)) else: info( pretty.format( 'carried over {0:n} uL to next operation', self.current_volume)) else: # if we can, account for any overspill in the next aspirate if self.current_volume > 0: if self.has_disposal_vol( plan, i + 1, step_info_map, **kwargs): next_aspirate = plan[i + 1].get( 'aspirate') assert next_aspirate next_aspirated_well, __ = well_vector( next_aspirate['location']) if self.prev_aspirated_well is next_aspirated_well: new_aspirate_vol = zeroify( next_aspirate.get('volume') - self.current_volume) if new_aspirate_vol == 0 or new_aspirate_vol >= self.min_volume: next_aspirate[ 'volume'] = new_aspirate_vol info( pretty.format( 'reduced next aspirate by {0:n} uL', self.current_volume )) else: do_blow = True else: do_blow = True # different aspirate locations else: # Next aspirate doesn't *want* our overspill, so we don't reduce his # volume. But it's harmless to just leave the overspill present; might # be useful down the line pass else: pass # currently empty if do_touch: self.touch_tip(touch_tip) if do_blow: self._blowout_during_transfer( dispense['location'], **kwargs) if do_drop: tips = self._drop_tip_during_transfer( tips, i, total_transfers, **kwargs) else: if air_gap: self.air_gap(air_gap) if do_touch: self.touch_tip(touch_tip)
def aspirate( self, volume: float = None, location: Union[types.Location, EnhancedWellV2] = None, rate: float = 1.0, # remainder are added params pre_wet: bool = None, ms_pause: float = None, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): # figure out where we're aspirating from # recapitulate super if isinstance(location, WellV2): point, well = location.bottom() dest = types.Location( point + types.Point(0, 0, self.well_bottom_clearance.aspirate), well) elif isinstance(location, types.Location): dest = location elif location is not None: raise TypeError( 'location should be a Well or Location, but it is {}'.format( location)) elif self._ctx.location_cache: dest = self._ctx.location_cache else: raise RuntimeError( "If aspirate is called without an explicit location, another method that moves to a location (such as move_to or dispense) must previously have been called so the robot knows where it is." ) location = dest # no need for new variable assert isinstance(location, types.Location) point, well = location if top_clearance is None: if tls.aspirate_params_transfer: top_clearance = tls.aspirate_params_transfer.top_clearance_transfer if top_clearance is None: top_clearance = self.well_top_clearance.aspirate if bottom_clearance is None: if tls.aspirate_params_transfer: bottom_clearance = tls.aspirate_params_transfer.bottom_clearance_transfer if bottom_clearance is None: bottom_clearance = self.well_bottom_clearance.aspirate if manual_liquid_volume_allowance is None: if tls.aspirate_params_transfer: manual_liquid_volume_allowance = tls.aspirate_params_transfer.manual_manufacture_tolerance_transfer if manual_liquid_volume_allowance is None: manual_liquid_volume_allowance = self.config.aspirate.manual_liquid_volume_allowance current_liquid_volume = well.liquid_volume.current_volume_min needed_liquid_volume = well.geometry.min_aspiratable_volume + volume if current_liquid_volume < needed_liquid_volume: msg = pretty.format( 'aspirating too much from well={0} have={1:n} need={2:n}', well.get_name(), current_liquid_volume, needed_liquid_volume) warn(msg) self._pre_wet(well, volume, location, rate, pre_wet) location = self._adjust_location_to_liquid_top( location=location, aspirate_volume=volume, top_clearance=top_clearance, bottom_clearance=bottom_clearance, manual_liquid_volume_allowance=manual_liquid_volume_allowance) def call_super(): super(EnhancedPipette, self).aspirate(volume=volume, location=location, rate=rate) self.use_self_while(call_super) self.pause_after_aspirate(ms_pause) # finish up todo: what if we're doing an air gap well.liquid_volume.aspirate(volume) if volume != 0: self.prev_aspirated_well = well
def diluteStrands(): if manually_dilute_strands: note_liquid(location=diluted_strand_a, name='Diluted StrandA', initially=strand_dilution_vol) note_liquid(location=diluted_strand_b, name='Diluted StrandB', initially=strand_dilution_vol) log('Diluting Strands') info( pretty.format( 'Diluted Strand A recipe: water={0:n} strandA={1:n} vol={2:n}', strand_dilution_water_vol, strand_dilution_source_vol, strand_dilution_vol)) info( pretty.format( 'Diluted Strand B recipe: water={0:n} strandB={1:n} vol={2:n}', strand_dilution_water_vol, strand_dilution_source_vol, strand_dilution_vol)) user_prompt('Ensure diluted strands manually present and mixed') else: strand_a = eppendorf_1_5_rack['A1'] strand_b = eppendorf_1_5_rack['B1'] assert strand_a_min_vol >= strand_dilution_source_vol + strand_a.geometry.min_aspiratable_volume assert strand_b_min_vol >= strand_dilution_source_vol + strand_b.geometry.min_aspiratable_volume note_liquid(location=strand_a, name='StrandA', concentration=strand_a_conc, initially_at_least=strand_a_min_vol ) # i.e.: we have enough, just not specified how much note_liquid(location=strand_b, name='StrandB', concentration=strand_b_conc, initially_at_least=strand_b_min_vol) # ditto note_liquid(location=diluted_strand_a, name='Diluted StrandA') note_liquid(location=diluted_strand_b, name='Diluted StrandB') # We used to auto-mix, but now, even when auto-diluting, we rely on user to have mixed on the vortexer # p50.layered_mix([strand_a]) # p50.layered_mix([strand_b]) # Create dilutions of strands log('Moving water for diluting Strands A and B') p50.transfer( strand_dilution_water_vol, waterA, [diluted_strand_a, diluted_strand_b], new_tip= 'once', # can reuse for all diluent dispensing since dest tubes are initially empty trash=config.trash_control) log('Diluting Strand A') p50.transfer(strand_dilution_source_vol, strand_a, diluted_strand_a, trash=config.trash_control, keep_last_tip=True) p50.layered_mix([diluted_strand_a]) log('Diluting Strand B') p50.transfer(strand_dilution_source_vol, strand_b, diluted_strand_b, trash=config.trash_control, keep_last_tip=True) p50.layered_mix([diluted_strand_b])
for i in range(num_masses): vol = (i + 1) * mass_incr_vol for j in range(num_replicates): well = eppendorf_1_5_rack.cols(i).wells(j) well.mass_vol = vol mass_wells.append(well) for well in mass_wells: Eppendorf1Point5MlTubeGeometry(well) log('Liquid Names') note_liquid(location=water, name='Water', initially_at_least=15000) # volume is rough guess for well in mass_wells: note_liquid(location=well, name=pretty.format('mass_vol={0:n}', well.mass_vol)) # Clean up namespace del well, i, j ######################################################################################################################## # Off to the races ######################################################################################################################## for well in mass_wells: p.transfer(well.mass_vol, water, well, new_tip='once', trash=config.trash_control, allow_blow_elision=True,
def _layered_mix_one(self, well: EnhancedWellV1, msg, **kwargs): def fetch(name, default=None): if default is None: default = getattr(self.config.layered_mix, name, None) result = kwargs.get(name, default) if result is None: result = default # replace any explicitly stored 'None' with default return result count = fetch('count') min_incr = fetch('min_incr') incr = fetch('incr') count_per_incr = fetch('count_per_incr') volume = fetch('volume', self.get_max_volume()) ms_pause = fetch('ms_pause') ms_final_pause = fetch('ms_final_pause') aspirate_rate = fetch('aspirate_rate', self.config.layered_mix.aspirate_rate_factor) dispense_rate = fetch('dispense_rate', self.config.layered_mix.dispense_rate_factor) initial_turnover = fetch('initial_turnover') max_tip_cycles = fetch('max_tip_cycles', infinity) pre_wet = fetch('pre_wet', False) # not much point in pre-wetting during mixing; save some time, simpler. but we do so if asked top_clearance = fetch('top_clearance') bottom_clearance = fetch('bottom_clearance') current_liquid_volume = well.liquid_volume.current_volume_min liquid_depth = well.geometry.liquid_depth_from_volume(current_liquid_volume) liquid_depth_after_asp = well.geometry.liquid_depth_from_volume(current_liquid_volume - volume) msg = pretty.format("{0:s} well='{1:s}' cur_vol={2:n} well_depth={3:n} after_aspirate={4:n}", msg, well.get_name(), current_liquid_volume, liquid_depth, liquid_depth_after_asp) def do_one(): count_ = count y_min = y = bottom_clearance y_max = self._top_clearance(liquid_depth=liquid_depth_after_asp, clearance=top_clearance) if count_ is not None: if count_ <= 1: y_max = y_min y_incr = 1 # just so we only go one time through the loop else: y_incr = (y_max - y_min) / (count_-1) y_incr = max(y_incr, min_incr) else: assert incr is not None y_incr = incr def do_layer(y_layer): return y_layer <= y_max or is_close(y_layer, y_max) first = True tip_cycles = 0 looped = False while do_layer(y): looped = True if not first and ms_pause > 0: self.dwell(seconds=ms_pause / 1000.0) # pause to let dispensed liquid disperse # if first and initial_turnover is not None: count_ = int(0.5 + (initial_turnover / volume)) count_ = max(count_, count_per_incr) else: count_ = count_per_incr if not self.has_tip: self.pick_up_tip() radial_clearance_func = self.radial_clearance_manager.get_clearance_function(self, well) radial_clearance = 0 if radial_clearance_func is None or not self.config.layered_mix.enable_radial_randomness else radial_clearance_func(y_max) radial_clearance = max(0, radial_clearance - max(well.geometry.radial_clearance_tolerance, self.config.layered_mix.radial_clearance_tolerance)) for i in range(count_): tip_cycles += 1 need_new_tip = tip_cycles >= max_tip_cycles full_dispense = need_new_tip or (not do_layer(y + y_incr) and i == count_ - 1) theta = random.random() * (2 * math.pi) _, dispense_coordinates = well.bottom(y_max) random_offset = (radial_clearance * math.cos(theta), radial_clearance * math.sin(theta), 0) dispense_location = (well, dispense_coordinates + random_offset) self.aspirate(volume, well.bottom(y), rate=aspirate_rate, pre_wet=pre_wet) self.move_to(well.bottom(y_max)) # ascend vertically from aspirate location self.dispense(volume, dispense_location, rate=dispense_rate, full_dispense=full_dispense) self.move_to(well.bottom(y_max)) # prepare for vertical descent on a subsequent aspirate if need_new_tip: self.done_tip() tip_cycles = 0 # y += y_incr first = False if looped and ms_final_pause > 0: self.dwell(seconds=ms_final_pause / 1000.0) info_while(msg, do_one)