def pgm(self): q = QSetup(self, maxdil=self.maxdilstep, debug=False, mindilvol=60) self.e.addIdleProgram(q.idler) if self.barcoding: # Setup barcode primers for cleaved rounds only self.bcprimers = [[ "BC-%s-R%d_T7" % (inp['ligand'], r + 1) for inp in self.inputs ] if self.rounds[r] == 'C' else None for r in range(len(self.rounds))] for bcp in self.bcprimers: if bcp is not None: for p in ["P-%s" % pp for pp in bcp]: if not reagents.isReagent(p): reagents.add(name=p, conc=4, extraVol=30, plate=decklayout.REAGENTPLATE, well="B2") s = reagents.getsample(p) # Force allocation of a well print "Adding %s to reagents at well %s" % ( p, s.plate.wellname(s.well)) print "BC primers=", self.bcprimers # Add any missing fields to inputs for i in range(len(self.inputs)): if 'ligand' not in self.inputs[i]: self.inputs[i]['ligand'] = None if 'round' not in self.inputs[i]: self.inputs[i]['round'] = None if 'name' not in self.inputs[i]: if self.inputs[i]['ligand'] is None: self.inputs[i]['name'] = '%s_%d_R%d' % ( self.inputs[i]['prefix'], self.inputs[i]['ID'], self.inputs[i]['round']) else: self.inputs[i]['name'] = '%s_%d_R%d_%s' % ( self.inputs[i]['prefix'], self.inputs[i]['ID'], self.inputs[i]['round'], self.inputs[i]['ligand']) # Add templates if self.directT7: self.srcs = self.addTemplates( [inp['name'] for inp in self.inputs], stockconc=self.tmplFinalConc / self.templateDilution, finalconc=self.tmplFinalConc, plate=decklayout.SAMPLEPLATE, looplengths=[inp['looplength'] for inp in self.inputs], initVol=self.t7vol[0] * self.templateDilution, extraVol=0) else: self.srcs = self.addTemplates( [inp['name'] for inp in self.inputs], stockconc=self.tmplFinalConc / self.templateDilution, finalconc=self.tmplFinalConc, plate=decklayout.DILPLATE, looplengths=[inp['looplength'] for inp in self.inputs], extraVol=15) t7in = [s.getsample() for s in self.srcs] if "negative" in self.qpcrStages: q.addSamples(decklayout.SSDDIL, 1, self.allprimers, save=False) # Negative controls if "reference" in self.qpcrStages: q.addReferences(dstep=10, nsteps=5, primers=["T7WX", "MX", "T7X"], ref=reagents.getsample("BT5310"), nreplicates=1) q.addReferences(dstep=10, nsteps=5, primers=["T7WX", "MX", "T7X"], ref=reagents.getsample("BT5310"), nreplicates=1) # Save RT product from first (uncleaved) round and then use it during 2nd (cleaved) round for ligation and qPCR measurements self.rndNum = 0 self.nextID = self.firstID curPrefix = [inp['prefix'] for inp in self.inputs] r1 = t7in for roundType in self.rounds: # Run a single round of roundType with r1 as input # roundType is either "U" for uncleaved, or a new prefix for a cleaved round (with "T" being a T7 prepend) # Set r1 to new output at end # Computed output prefix if roundType == 'U': prefixOut = curPrefix stop = ["Unclvd" for p in curPrefix] else: if roundType == 'T': stop = ['T7%s' % p for p in curPrefix] prefixOut = curPrefix elif any([p == roundType for p in curPrefix]): logging.error( "Round %d is a cleaved round but goes to %s without changing prefix" % (self.rndNum, roundType)) assert (False) else: prefixOut = [roundType for p in curPrefix] stop = prefixOut # May be explicitly overridden for i in range(len(self.inputs)): if 'stop' in self.inputs[i]: if isinstance(self.inputs[i]['stop'], list): assert (len(self.inputs[i]['stop']) == len( self.rounds)) t = self.inputs[i]['stop'][self.rndNum] else: t = self.inputs[i]['stop'] if (roundType == 'U') != (t == 'U'): print "Attempt to override round %d (type %s) with a input-specific round type of %s" % ( self.rndNum, roundType, t) assert (False) if roundType != 'U': if t == 'T': stop[i] = 'T7%s' % curPrefix[i] prefixOut[i] = curPrefix[i] else: stop[i] = t prefixOut[i] = t self.rndNum = self.rndNum + 1 self.finalRound = self.rndNum == len(self.rounds) r1 = self.oneround(q, r1, prefixOut=prefixOut, stop=stop, prefixIn=curPrefix, keepCleaved=(roundType != 'U'), rtvol=self.rtvol[self.rndNum - 1], t7vol=self.t7vol[self.rndNum - 1], cycles=self.pcrcycles[self.rndNum - 1], pcrdil=self.pcrdil[self.rndNum - 1], pcrvol=self.pcrvol[self.rndNum - 1], dolig=self.allLig or (roundType != 'U')) for i in range(len(r1)): r1[i].name = "%s_%d" % (prefixOut[i], self.nextID) if self.inputs[i]['round'] is not None: r1[i].name = "%s_R%d%c" % (r1[i].name, self.inputs[i]['round'] + self.rndNum, roundType) if self.inputs[i]['ligand'] is not None: r1[i].name = "%s_%s" % (r1[i].name, self.inputs[i]['ligand']) print "Used ID ", self.nextID, " for ", r1[i].name, ": ", r1[i] self.nextID += 1 r1[i].conc.final = r1[i].conc.stock * self.templateDilution curPrefix = prefixOut if "finalpcr" in self.qpcrStages: for i in range(len(r1)): if self.singlePrefix: q.addSamples(src=r1[i], needDil=r1[i].conc.stock / self.qConc, primers=["T7X", "MX"]) else: q.addSamples(src=r1[i], needDil=r1[i].conc.stock / self.qConc, primers=["T7X", prefixOut[i] + "X", "MX"]) print "######### qPCR ########### %.0f min" % (clock.elapsed() / 60) self.allprimers = q.allprimers() q.run(confirm=self.qpcrWait)
class Constrict(TRP): # Mix constriction inputs, constrict, PCR, remove barcodes pcreff = 1.98 def __init__(self, inputs, nmolecules, nconstrict, vol): super(Constrict, self).__init__() self.inputs = inputs self.nmolecules = nmolecules self.nconstrict = nconstrict self.qconc = 20e-12 # Target qPCR concentration self.qprimers = ["End"] self.mix_conc = 100e-9 # Concentration of mixdown self.con_dilvol = 100 # Volume to use for constriction dilutions self.con_maxdilperstage = 100 / 3.0 # Maximum dilution/stage self.con_pcr1vol = 100 self.con_pcr1inputvol = 2 self.con_pcr1tgtconc = self.qconc * 4 # Enough to take qPCR without dilutiojn self.con_pcr2dil = 4 self.con_pcr2vol = 50 self.con_pcr2tgtconc = 10e-9 self.regen_predilvol = 100 self.regen_predil = 25 self.regen_dil = 25 self.regen_vol = 100 self.regen_cycles = 10 self.rsrc = [reagents.add("%s-%s-%s" % (inputs[i]['name'], inputs[i]['left'], inputs[i]['right']), decklayout.SAMPLEPLATE, well=inputs[i]['well'] if 'well' in inputs[i] else None, conc=Concentration(stock=inputs[i]['bconc'], units="nM"), initVol=vol, extraVol=0) for i in range(len(inputs))] self.q = None # Defined in pgm() def pgm(self): self.q = QSetup(self, maxdil=16, debug=False, mindilvol=60) # Don't start idler (to minimize tip cross-contamination); last PCR allows plenty of time for doing dilutions without any effect on run time # Will start after first constriction PCR is running #self.q.debug = True # self.e.addIdleProgram(self.q.idler) self.q.addReferences(dstep=10, primers=self.qprimers, ref=reagents.getsample("BT5310"),nreplicates=2) samps=[r.getsample() for r in self.rsrc] for s in samps: self.q.addSamples([s],needDil=max(10,s.conc.stock*1e-9/self.qconc),primers=self.qprimers) print("### Mixdown #### (%.0f min)" % (clock.elapsed() / 60.0)) if len(samps)>1: mixdown = self.mix(samps, [x['weight'] for x in self.inputs]) else: mixdown=samps[0] self.q.addSamples(mixdown, needDil=max(1.0,mixdown.conc.stock * 1e-9 / self.qconc), primers=self.qprimers) print("Mixdown final concentration = %.0f pM" % (mixdown.conc.stock * 1000)) print("### Constriction #### (%.1f min)" % (clock.elapsed() / 60.0)) constricted = self.constrict(mixdown, mixdown.conc.stock * 1e-9) print("### Regeneration #### (%.0f min)" % (clock.elapsed() / 60.0)) prefixes = set([x['left'][0] for x in self.inputs]) self.regenerate(constricted * len(prefixes), [p for p in prefixes for _ in constricted]) print("### qPCR #### (%.0f min)" % (clock.elapsed() / 60.0)) self.q.run(confirm=False, enzName='EvaGreen', waitForPTC=True) print("### qPCR Done #### (%.0f min)" % (clock.elapsed() / 60.0)) worklist.userprompt("qPCR done -- only need to complete final PCR", 300) self.e.waitpgm() print("### Final PCR Done #### (%.0f min)" % (clock.elapsed() / 60.0)) def mix(self, inp, weights,mixvol=100,tgtconc=None,maxinpvol=20): """Mix given inputs according to weights (by moles -- use conc.stock of each input)""" vol = [weights[i] *1.0 / inp[i].conc.stock for i in range(len(inp))] scale = mixvol / sum(vol) conc=sum([inp[i].conc.stock * scale * vol[i] for i in range(len(inp))]) / mixvol if tgtconc is not None and conc>tgtconc: scale*=tgtconc*1.0/conc if max(vol)*scale<4.0: scale=4.1/max(vol) # At least one input with 4ul input vol = [x * scale for x in vol] # Mix to make planned total without water for i in range(len(vol)): # Check if this would require more than available of any input newscale= min(maxinpvol,inp[i].volume-inp[i].plate.unusableVolume()-2)/vol[i] if newscale<1: vol = [x * 1.0 * newscale for x in vol] if tgtconc is not None: mixvol *= newscale # Maintain same target concentration by reducing total volume if min(vol) < 4.0: # Some components are too small; split mixing lowvol=[i for i in range(len(inp)) if vol[i]<4.0] highvol=[i for i in range(len(inp)) if i not in lowvol] assert len(highvol)>0 assert len(lowvol)>0 lowtgtconc=sum([inp[i].conc.stock *1.0/ weights[i] for i in highvol])/len(highvol)*sum([weights[i] for i in lowvol]) print("Running premix of samples "+",".join(["%d"%ind for ind in lowvol])+" with target concentration of %.4f"%lowtgtconc) mix1=self.mix([inp[i] for i in lowvol],[weights[i] for i in lowvol],tgtconc=lowtgtconc,mixvol=mixvol,maxinpvol=maxinpvol) wt1=sum([weights[i] for i in lowvol]) mix2=self.mix([inp[i] for i in highvol]+[mix1],[weights[i] for i in highvol]+[wt1],tgtconc=tgtconc,mixvol=mixvol,maxinpvol=maxinpvol) return mix2 print("Mixing into %.0ful with tgtconc of %s, dil=%.2f"%(mixvol,"None" if tgtconc is None else "%.4f"%tgtconc,mixvol/sum(vol))) for i in range(len(inp)): print("%-30.30s %6.3fnM wt=%5.2f v=%5.2ful"%(inp[i].name,inp[i].conc.stock,weights[i],vol[i])) watervol = mixvol - sum(vol) #print "Mixdown: vols=[", ",".join(["%.2f " % v for v in vol]), "], water=", watervol, ", total=", mixvol, " ul" mixdown = Sample('mixdown', plate=decklayout.SAMPLEPLATE) if watervol < -0.1: print("Total mixdown is %.1f ul, more than planned %.0f ul" % (sum(vol), mixvol)) assert False elif watervol >= 4.0: # Omit if too small self.e.transfer(watervol, decklayout.WATER, mixdown, (False, False)) else: pass ordering=sorted(list(range(len(inp))),key=lambda i: vol[i],reverse=True) for i in ordering: inp[i].conc.final = inp[i].conc.stock * vol[i] / mixvol # Avoid warnings about concentrations not adding up self.e.transfer(vol[i], inp[i], mixdown, (False, False)) self.e.shakeSamples([mixdown]) if not mixdown.wellMixed: self.e.mix(mixdown) mixdown.conc = Concentration(stock=sum([inp[i].conc.stock * vol[i] for i in range(len(inp))]) / mixvol, final=None, units='nM') print("Mix product, %s, is in well %s with %.1ful @ %.2f nM"%(mixdown.name,mixdown.plate.wellname(mixdown.well),mixdown.volume,mixdown.conc.stock)) print("----------") return mixdown def constrict(self, constrictin, conc): """Constrict sample with concentration given by conc (in M)""" # noinspection PyPep8Naming AN = 6.022e23 dil = conc * (self.con_pcr1inputvol * 1e-6) * AN / self.nmolecules nstages = int(math.ceil(math.log(dil) / math.log(self.con_maxdilperstage))) dilperstage = math.pow(dil, 1.0 / nstages) print("Diluting by %.0fx in %.0f stages of %.1f" % (dil, nstages, dilperstage)) s = [decklayout.WATER] + [constrictin] * self.nconstrict + [decklayout.SSDDIL] self.e.sanitize(3, 50) # Heavy sanitize for j in range(nstages): print("Stage ", j, ", conc=", conc) if conc <= self.qconc * 1e-9: self.q.addSamples(s, needDil=1.0, primers=self.qprimers, save=False) s = self.runQPCRDIL(s, self.con_dilvol, dilperstage, dilPlate=True) conc /= dilperstage cycles = int( math.log(self.con_pcr1tgtconc / conc * self.con_pcr1vol / self.con_pcr1inputvol) / math.log(self.pcreff) + 0.5) pcr1finalconc = conc * self.con_pcr1inputvol / self.con_pcr1vol * self.pcreff ** cycles print("Running %d cycle PCR1 -> %.1f pM" % (cycles, pcr1finalconc * 1e12)) s = s + [decklayout.WATER] # Extra control of just water added to PCR mix pcr = self.runPCR(primers=None, src=s, vol=self.con_pcr1vol, srcdil=self.con_pcr1vol * 1.0 / self.con_pcr1inputvol, ncycles=cycles, master="MConstrict", kapa=True) for p in pcr: p.conc = Concentration(stock=pcr1finalconc * 1e9, final=pcr1finalconc / self.con_pcr2dil, units='nM') self.e.addIdleProgram(self.q.idler) # Now that constriction is done, can start on qPCR setup needDil = max(4, pcr1finalconc / self.qconc) print("Running qPCR of PCR1 products using %.1fx dilution" % needDil) self.q.addSamples(pcr, needDil=needDil, primers=self.qprimers, save=True) pcr = pcr[1:-2] # Remove negative controls cycles2 = int(math.log(self.con_pcr2tgtconc / pcr1finalconc * self.con_pcr2dil) / math.log(self.pcreff) + 0.5) pcr2finalconc = pcr1finalconc / self.con_pcr2dil * self.pcreff ** cycles2 if cycles2 > 0: print("Running %d cycle PCR2 -> %.1f nM" % (cycles2, pcr2finalconc * 1e9)) pcr2 = self.runPCR(primers="End", src=pcr, vol=self.con_pcr2vol, srcdil=self.con_pcr2dil, ncycles=cycles2, master="MKapa", kapa=True) self.q.addSamples(pcr2, needDil=pcr2finalconc / self.qconc, primers=self.qprimers, save=True) for p in pcr2: p.conc = Concentration(stock=pcr2finalconc * 1e9, units='nM') self.e.waitpgm() return pcr2 else: return pcr def regenerate(self, inp, prefix): """Regenerate T7 templates without barcodes with each of the given prefixes""" print("Regen Predilute: %.1f nM by %.1fx to %.2f nM" % ( inp[0].conc.stock, self.regen_predil, inp[0].conc.stock / self.regen_predil)) d1 = self.runQPCRDIL(inp, self.regen_predilvol, self.regen_predil, dilPlate=True) inconc = inp[0].conc.stock / self.regen_predil / self.regen_dil print("Regen PCR: %.3f nM with %d cycles -> %.1f nM" % ( inconc, self.regen_cycles, inconc * self.pcreff ** self.regen_cycles)) res = self.runPCR(src=d1, srcdil=self.regen_dil, vol=self.regen_vol, ncycles=self.regen_cycles, primers=["T7%sX" % p for p in prefix], fastCycling=False, master="MKapa", kapa=True) return res
class IDPrep(TRP): # Barcode multiple samples pcreff = 1.98 def __init__(self, inputs): super(IDPrep, self).__init__() self.inputs = inputs self.qconc = 0.020 # Target qPCR concentration in nM self.qprimers = ["End"] self.bc1_inputvol = 2 # ul into PCR1 used = [] for inp in inputs: bc = "%s-%s" % (inp['left'], inp['right']) if bc in used: logging.error("Barcode %s is being reused for %s" % (bc, inp['name'])) used.append(bc) print("used=",used) self.rsrc = [reagents.add("%s-%s-%s" % (inputs[i]['name'], inputs[i]['left'], inputs[i]['right']), decklayout.SAMPLEPLATE, well=inputs[i]['well'] if 'well' in inputs[i] else None, conc=Concentration(stock=inputs[i]['conc'], units="nM"), initVol=self.bc1_inputvol, extraVol=0) for i in range(len(inputs))] self.q = None # Defined in pgm() def pgm(self): self.q = QSetup(self, maxdil=16, debug=False, mindilvol=60) self.q.debug = True self.q.addReferences(dstep=10, primers=self.qprimers, ref=reagents.getsample("BT5310"),nreplicates=2) print("### Barcoding #### (%.0f min)" % (clock.elapsed() / 60.0)) self.idbarcoding(self.rsrc, left=[x['left'] for x in self.inputs], right=[x['right'] for x in self.inputs]) print("### qPCR #### (%.0f min)" % (clock.elapsed() / 60.0)) self.q.run(confirm=False, enzName='EvaGreen') def idbarcoding(self, rsrc, left, right): """Perform barcoding of the given inputs; rsrsc,left,right should all be equal length""" pcrcycles = [4] # Don't need 2nd PCR since this will go directly into constriction #pcr1inputconc = 0.05 # PCR1 concentration final in reaction pcr1inputdil = 10 pcr1vol = 30 pcr1postdil = 100.0 / pcr1vol pcr2dil = 50 pcr2minvol = 50.0 samps = [s.getsample() for s in rsrc] print("Inputs:") for i in range(len(samps)): print("%2s %-10s %8s-%-8s %.1f%s" % ( samps[i].plate.wellname(samps[i].well), self.inputs[i]['name'], left[i], right[i], samps[i].conc.stock,samps[i].conc.units)) # Compute pcr1inputconc such that lowest concentration input ends up with at least 30ul after dilution pcr1inputconc=min([s.conc.stock*s.volume/30.0/pcr1inputdil for s in samps]) print("Diluting inputs so PCR1 final template conc = %.0f pM"%(pcr1inputconc*1000)) wellnum = 5 for s in left + right: primer = "P-" + s if not reagents.isReagent(primer): reagents.add(primer, conc=Concentration(2.67, 0.4, 'uM'), extraVol=30, plate=decklayout.REAGENTPLATE, well=decklayout.REAGENTPLATE.wellname(wellnum)) wellnum += 1 # Run first pass dilution where needed for i in range(len(samps)): # Dilute down to desired conc dil = samps[i].conc.stock / pcr1inputconc / pcr1inputdil dilvol = samps[i].volume * dil if dilvol > 100.0: logging.notice("Dilution of input %s (%.1f ul) by %.2f would require %.1f ul" % ( samps[i].name, samps[i].volume, dil, dilvol)) # Do a first pass dilution into 150ul, then remove enough so second dilution can go into 100ul dil1 = 100.0 / samps[i].volume self.diluteInPlace(tgt=[samps[i]], dil=dil1) print("First pass dilution of %s by %.1f/%.1f (conc now %.3f nM)" % (samps[i].name, dil1, dil, samps[i].conc.stock)) dil /= dil1 # Make sure they are all mixed self.e.shakeSamples(samps) # Final dilution for s in samps: # Dilute down to desired conc dil = s.conc.stock / pcr1inputconc / pcr1inputdil if dil < 1.0: logging.error("Input %s requires dilution of %.2f" % (s.name, dil)) elif dil > 1.0: dilvol = s.volume * dil if dilvol>100: toremove=s.volume-100.0/dil print("Removing %.1f ul from %s to allow enough room for dilution"%(toremove,s.name)) self.e.dispose(toremove, s) self.diluteInPlace(tgt=[s], dil=dil) print("Diluting %s by %.1f" % (s.name, dil)) pcr1 = self.runPCR(src=samps, srcdil=pcr1inputdil, ncycles=pcrcycles[0], vol=pcr1vol, primers=[[left[i], right[i]] for i in range(len(left))], usertime=0, fastCycling=False, inPlace=False, master="MKapa", kapa=True) pcr1finalconc = pcr1inputconc * self.pcreff ** pcrcycles[0] print("PCR1 output concentration = %.3f nM" % pcr1finalconc) if pcr1postdil > 1: pcr1finalconc /= pcr1postdil print("Post dilute PCR1 by %.2fx to %.3f nM " % (pcr1postdil, pcr1finalconc)) self.diluteInPlace(tgt=pcr1, dil=pcr1postdil) for x in pcr1: x.conc = Concentration(stock=pcr1finalconc, units='nM') self.q.addSamples(src=pcr1, needDil=pcr1finalconc / self.qconc, primers=self.qprimers, save=True, nreplicates=1) if len(pcrcycles) > 1: # Second PCR with 235p/236p on mixture (use at least 4ul of prior) pcr2 = self.runPCR(src=pcr1, srcdil=pcr2dil / pcr1postdil, vol=max(pcr2minvol, pcr2dil / pcr1postdil * 4), ncycles=pcrcycles[1], primers="End", fastCycling=False, master="MKapa", kapa=True) pcr2finalconc = min(200, pcr1finalconc / (pcr2dil / pcr1postdil) * self.pcreff ** pcrcycles[1]) print("PCR2 final conc = %.1f nM" % pcr2finalconc) d2 = min(4.0, 150.0 / max([p.volume for p in pcr2])) if d2 > 1: pcr2finalconc /= d2 print("Post-dilute PCR2 by %.1fx to %.3fnM" % (d2, pcr2finalconc)) self.diluteInPlace(tgt=pcr2, dil=d2) self.e.shakeSamples(pcr2) for x in pcr2: x.conc = Concentration(stock=pcr2finalconc, units='nM') self.q.addSamples(src=pcr2, needDil=pcr2finalconc / self.qconc, primers=self.qprimers, save=True, nreplicates=2) res = pcr2 else: res = pcr1 return res
class Barcoding(TRP): """Barcode multiple samples, mix them""" def __init__(self, inputs, pcr1inputconc=0.05, used=None,doqpcr=True,inputPlate=decklayout.SAMPLEPLATE): super(Barcoding, self).__init__() if used is None: used = [] self.inputs = inputs self.qconc = 50e-12 # Target qPCR concentration self.qprimers = ["End"] self.doqpcr=doqpcr self.bc1_inputvol = 4 # ul of input samples self.mix_conc = 100e-9 # Concentration of mixdown self.pcr1inputconc = pcr1inputconc for inp in inputs: bc = "%s-%s" % (inp['left'], inp['right']) if bc in used: logging.error("Barcode %s is being reused for %s" % (bc, inp['name'])) used.append(bc) for x in inputs: if not reagents.isReagent(x['name']): reagents.add(x['name'], inputPlate, well=x['well'] if 'well' in x else None, conc=Concentration(stock=x['conc'], units="nM"), initVol=self.bc1_inputvol, extraVol=0) else: r = reagents.getsample(x['name']) if r.conc.stock != x['conc']: logging.error('Input %s has conflicting concentrations set: %f and %f', x['name'], r.conc.stock, x['conc']) assert False self.q = None # Defined in pgm() def pgm(self): self.q = QSetup(self, maxdil=16, debug=False, mindilvol=100) self.e.addIdleProgram(self.q.idler) if self.doqpcr: self.q.addReferences(dstep=10, primers=self.qprimers, ref=reagents.getsample("BT5310")) print("### Barcoding #### (%.0f min)" % (clock.elapsed() / 60.0)) bcout = self.barcoding(names=[x['name'] for x in self.inputs], left=[x['left'] for x in self.inputs], right=[x['right'] for x in self.inputs]) for i in range(len(self.inputs)): x = self.inputs[i] if 'bconc' in x and x['bconc'] is not None: print("Resetting concentration of %s from expected %.1f to bconc setting of %.1f nM" % \ (x['name'], bcout[i].conc.stock, x['bconc'])) bcout[i].conc.stock = x['bconc'] print("### qPCR #### (%.0f min)" % (clock.elapsed() / 60.0)) self.q.run(confirm=False, enzName='EvaGreen') print("### qPCR Done #### (%.0f min)" % (clock.elapsed() / 60.0)) print("### Final PCR Done #### (%.0f min)" % (clock.elapsed() / 60.0)) worklist.flushQueue() if all(['bconc' in x and x['bconc'] is not None for x in self.inputs]): print("### Mixdown #### (%.0f min)" % (clock.elapsed() / 60.0)) worklist.flushQueue() worklist.comment('Start mixdown only at this point') self.e.sanitize(force=True) # mixdown = self.mix(bcout, [x['weight'] for x in self.inputs]) # Set default mix number to 1 for x in self.inputs: if 'mix' not in x: x['mix']=1 mixes=set([x['mix'] for x in self.inputs]) for m in mixes: sel=[ i for i in range(len(self.inputs)) if self.inputs[i]['mix']==m] print("sel=",sel) mixdown = self.mix([bcout[i] for i in sel], [self.inputs[i]['weight'] for i in sel],prefix="Mix%d_"%m) mixdown.name="Mix%d_Final"%m # self.q.addSamples(mixdown, needDil=mixdown.conc.stock * 1e-9 / self.qconc, primers=self.qprimers,nreplicates=3) else: print("### Not doing mixdown as bconc not set in all inputs") def mix(self, inp, weights, tgtdil=1.0, maxusefrac=0.75, prefix="Mix"): """Mix given inputs according to weights (by moles -- use conc.stock of each input)""" print("Mix: tgtdil=%.2f, inp=" % tgtdil, ",".join( ["%s@%.2f" % (inp[i].name, weights[i]) for i in range(len(inp))])) relvol = [weights[i] * 1.0 / inp[i].conc.stock for i in range(len(inp))] stages=mixsplit(vols=relvol,samps=inp,avail=[(i.volume-15.0)*maxusefrac-1.4 for i in inp],minvol=4.0,minmix=100.0,maxmix=100.0,plate=decklayout.SAMPLEPLATE,debug=False,prefix=prefix) for s in stages: dest=s[0] moles=0 for i in range(len(s[1])-1,-1,-1): src=s[1][i] vol=s[2][i] self.e.transfer(vol,src,dest,(True,False)) if src.conc is not None: moles+=src.conc.stock*vol newconc=moles/sum(s[2]) print("Adjusting concentration of %s from %s to %.2f nM"%(dest.name,str(dest.conc) if dest.conc is not None else -1,newconc)) dest.conc=Concentration(newconc,newconc,units='nM') print(s[0].name,'\n =',"\n + ".join(['%5.1fuL %5.2f %s %s'%(s[2][i],s[1][i].conc.stock if s[1][i].conc is not None else 0,s[1][i].conc.units if s[1][i].conc is not None else " ",s[1][i].name) for i in range(len(s[1]))]),"\n-> %5.1ful %5.2f %s %s"%(dest.volume,dest.conc.stock,dest.conc.units,dest.name)) print() return stages[0][0] def oldmix(self, inp, weights, tgtdil=1.0): """Mix given inputs according to weights (by moles -- use conc.stock of each input)""" print("Mix: tgtdil=%.2f, inp=" % tgtdil, ",".join( ["%s@%.2f" % (inp[i].name, weights[i]) for i in range(len(inp))])) mixvol = 100.0 if len(inp) == 1: if tgtdil > 1.0: vol = [mixvol / tgtdil] if vol[0] < 4.0: vol[0] = 4.0 else: # Special case, just dilute 10x vol = [mixvol / 10] else: relvol = [weights[i] * 1.0 / inp[i].conc.stock for i in range(len(inp))] scale = mixvol / sum(relvol) for i in range(len(inp)): if relvol[i] * scale > inp[i].volume - 16.4: scale = (inp[i].volume - 16.4) / relvol[i] vol = [x * scale for x in relvol] if min(vol) > 4.0 and tgtdil > 1.0: scale = max(1.0 / tgtdil, 4.0 / min(vol)) print("Rescale volumes by %.2f to get closer to target dilution of %.2f" % (scale, tgtdil)) vol = [x * scale for x in vol] print("Mix1: vols=[", ",".join(["%.3f" % v for v in vol]), "]") if min(vol) < 4.0: logging.info("Minimum volume into mixing would be only %.2f ul - staging..." % min(vol)) if max(vol) < 4.0: # All volumes are low, just too much # Split into 2 stages sel = list(range(int(len(inp) / 2))) nsel = list(range(int(len(inp) / 2), len(inp))) else: # Choose a splitting threshold mindilution = 4.0 / min(vol) thresh = np.median(vol) while mixvol / sum([v for v in vol if v < thresh]) < mindilution: thresh = thresh * 0.8 print("Using %.2f as threshold to split mixdown" % thresh) sel = [i for i in range(len(inp)) if vol[i] < thresh] nsel = [i for i in range(len(inp)) if vol[i] >= thresh] print("Mixing ", ",".join([inp[i].name for i in sel]), " with vols [", ",".join( ["%.2f" % vol[i] for i in sel]), "] in separate stage.") tgtdil = float(np.median([vol[i] for i in nsel]) / sum([vol[i] for i in sel])) print("tgtdil=%.2f" % tgtdil) mix1 = self.mix([inp[i] for i in sel], [weights[i] for i in sel], tgtdil) mix2 = self.mix([inp[i] for i in nsel] + [mix1], [weights[i] for i in nsel] + [sum([weights[i] for i in sel])]) return mix2 watervol = mixvol - sum(vol) print("Mixdown: vols=[", ",".join(["%.2f " % v for v in vol]), "], water=", watervol, ", total=", mixvol, " ul") mixdown = Sample('mixdown', plate=decklayout.SAMPLEPLATE) if watervol < -0.1: print("Total mixdown is %.1f ul, more than planned %.0f ul" % (sum(vol), mixvol)) assert False elif watervol > 0.0: self.e.transfer(watervol, decklayout.WATER, mixdown, (False, False)) else: pass for i in range(len(inp)): inp[i].conc.final = inp[i].conc.stock * vol[i] / mixvol # Avoid warnings about concentrations not adding up self.e.transfer(vol[i], inp[i], mixdown, (False, i == len(inp) - 1)) self.e.shakeSamples([mixdown]) mixdown.conc = Concentration(stock=sum([inp[i].conc.stock * vol[i] for i in range(len(inp))]) / mixvol, final=None, units='nM') print("Mixdown final concentration = %.1f nM" % mixdown.conc.stock) return mixdown def barcoding(self, names, left, right): """Perform barcoding of the given inputs; rsrsc,left,right should all be equal length""" pcrcycles = [5, 10] pcr1inputdil = 10 pcr1vol = 30 pcr1postdil = 100.0 / pcr1vol pcr2dil = 10*pcr1postdil pcr2vol = 40.0 samps = [reagents.getsample(s) for s in names] print("Inputs:") for i in range(len(samps)): print("%2s %-10s %8s-%-8s %s" % ( samps[i].plate.wellname(samps[i].well), self.inputs[i]['name'], left[i], right[i], str(samps[i].conc))) wellnum = 5 for s in left + right: primer = "P-" + s if not reagents.isReagent(primer): reagents.add(primer, conc=Concentration(2.67, 0.4, 'uM'), extraVol=30, plate=decklayout.REAGENTPLATE, well=decklayout.REAGENTPLATE.wellname(wellnum)) wellnum += 1 for s in samps: # Dilute down to desired conc dil = s.conc.stock / self.pcr1inputconc / pcr1inputdil if dil < 1.0: logging.error("Input %s requires dilution of %.2f" % (s.name, dil)) elif dil > 1.0: dilvol = s.volume * dil if dilvol > 150.0: maxdil = 150.0 / s.volume logging.info( "Dilution of input %s (%.1f ul) by %.2f would require %.1f ul -- only diluting by %.1fx" % ( s.name, s.volume, dil, dilvol, maxdil)) dil = maxdil self.diluteInPlace(tgt=[s], dil=dil) print("Diluting %s by %.1f" % (s.name, dil)) print("### PCR1 #### (%.0f min)" % (clock.elapsed() / 60.0)) pcr1 = self.runPCR(src=samps, srcdil=[s.conc.stock / self.pcr1inputconc for s in samps], ncycles=pcrcycles[0], vol=pcr1vol, primers=[[left[i], right[i]] for i in range(len(left))], usertime=30, fastCycling=False, inPlace=False, master="MPCR1", kapa=False, annealTemp=57) pcr1finalconc = self.pcr1inputconc * 2 ** pcrcycles[0] print("PCR1 output concentration = %.1f nM" % pcr1finalconc) if pcr1postdil > 1: pcr1finalconc /= pcr1postdil print("Post dilute PCR1 by %.2fx to %.3f nM " % (pcr1postdil, pcr1finalconc)) self.diluteInPlace(tgt=pcr1, dil=pcr1postdil) for x in pcr1: x.conc = Concentration(stock=pcr1finalconc, units='nM') if len(pcrcycles) > 1: # Second PCR with 235p/236p on mixture (use at least 4ul of prior) print("### PCR2 #### (%.0f min)" % (clock.elapsed() / 60.0)) pcr2 = self.runPCR(src=pcr1, srcdil=pcr2dil / pcr1postdil, vol=pcr2vol, ncycles=pcrcycles[1], primers=None, fastCycling=False, master="MPCR2", kapa=True, annealTemp=64) pcr2finalconc = pcr1finalconc / (pcr2dil / pcr1postdil) * 2 ** pcrcycles[1] print("PCR2 final conc = %.1f nM" % pcr2finalconc) if pcr2finalconc > 200: print("Capping at 200nM") pcr2finalconc = 200 for x in pcr2: x.conc = Concentration(stock=pcr2finalconc, units='nM') if self.doqpcr: self.q.addSamples(src=pcr2, needDil=pcr2finalconc * 1e-9 / self.qconc, primers=self.qprimers) res = pcr2 else: self.q.addSamples(src=pcr1, needDil=pcr1finalconc / (self.qconc * 1e9), primers=self.qprimers, save=True, nreplicates=1) res = pcr1 return res
def pgm(self): q = QSetup(self,maxdil=self.maxdilstep,debug=False,mindilvol=60) self.e.addIdleProgram(q.idler) if self.barcoding: # Setup barcode primers for cleaved rounds only self.bcprimers=[["BC-%s-R%d_T7"%(inp['ligand'],r+1) for inp in self.inputs] if self.rounds[r]=='C' else None for r in range(len(self.rounds))] for bcp in self.bcprimers: if bcp is not None: for p in ["P-%s"%pp for pp in bcp]: if not reagents.isReagent(p): reagents.add(name=p,conc=4,extraVol=30,plate=decklayout.REAGENTPLATE,well="B2") s=reagents.getsample(p) # Force allocation of a well print("Adding %s to reagents at well %s"%(p,s.plate.wellname(s.well))) print("BC primers=", self.bcprimers) # Add any missing fields to inputs for i in range(len(self.inputs)): if 'ligand' not in self.inputs[i]: self.inputs[i]['ligand']=None if 'negligand' not in self.inputs[i]: self.inputs[i]['negligand']=None if 'round' not in self.inputs[i]: self.inputs[i]['round']=None if 'name' not in self.inputs[i]: if self.inputs[i]['ligand'] is None: self.inputs[i]['name']='%s_%d_R%d'%(self.inputs[i]['prefix'],self.inputs[i]['ID'],self.inputs[i]['round']) else: self.inputs[i]['name']='%s_%d_R%d_%s'%(self.inputs[i]['prefix'],self.inputs[i]['ID'],self.inputs[i]['round'],self.inputs[i]['ligand']) # Add templates if self.directT7: self.srcs = self.addTemplates([inp['name'] for inp in self.inputs],stockconc=self.tmplFinalConc/self.templateDilution,finalconc=self.tmplFinalConc,plate=decklayout.SAMPLEPLATE,looplengths=[inp['looplength'] for inp in self.inputs],initVol=self.t7vol[0]*self.templateDilution,extraVol=0) else: self.srcs = self.addTemplates([inp['name'] for inp in self.inputs],stockconc=self.tmplFinalConc/self.templateDilution,finalconc=self.tmplFinalConc,plate=decklayout.DILPLATE,looplengths=[inp['looplength'] for inp in self.inputs],extraVol=15) if self.dopcr: # Reserve space for PCR products pcrprods=[ [Sample("R%d-T%s"%(r,inp['ligand']),self.savePlate) for inp in self.inputs] for r in range(len(self.rounds))] else: pcrprods=None t7in = [s.getsample() for s in self.srcs] if "negative" in self.qpcrStages: q.addSamples(decklayout.SSDDIL,1,self.allprimers,save=False) # Negative controls if "reference" in self.qpcrStages: q.addReferences(dstep=10,nsteps=5,primers=["WX","MX","T7X"] if self.useMX else ["WX","T7X"],ref=reagents.getsample("BT5310"),nreplicates=1) # Save RT product from first (uncleaved) round and then use it during 2nd (cleaved) round for ligation and qPCR measurements self.rndNum=0 self.nextID=self.firstID curPrefix=[inp['prefix'] for inp in self.inputs] r1=t7in for roundType in self.rounds: # Run a single round of roundType with r1 as input # roundType is either "U" for uncleaved, or a new prefix for a cleaved round (with "T" being a T7 prepend) # Set r1 to new output at end if self.roundCallback is not None: self.roundCallback(self,self.rndNum,roundType) # Computed output prefix if roundType=='U': prefixOut=curPrefix stop=["Unclvd" for _ in curPrefix] else: if roundType=='T': stop=['T7%s'%p for p in curPrefix] prefixOut=curPrefix elif any([p==roundType for p in curPrefix]): logging.error( "Round %d is a cleaved round but goes to %s without changing prefix"%(self.rndNum, roundType)) assert False else: prefixOut=[roundType for _ in curPrefix] stop=prefixOut # May be explicitly overridden for i in range(len(self.inputs)): if 'stop' in self.inputs[i]: if isinstance(self.inputs[i]['stop'],list): assert(len(self.inputs[i]['stop'])==len(self.rounds)) t=self.inputs[i]['stop'][self.rndNum] else: t=self.inputs[i]['stop'] if (roundType=='U') != (t=='U'): print("Attempt to override round %d (type %s) with a input-specific round type of %s"%(self.rndNum, roundType, t)) assert False if roundType!='U': if t=='T': stop[i]='T7%s'%curPrefix[i] prefixOut[i]=curPrefix[i] else: stop[i]=t prefixOut[i]=t self.rndNum=self.rndNum+1 self.finalRound=self.rndNum==len(self.rounds) db.pushStatus("%s%d"%(roundType,self.rndNum)) [r1,bc1]=self.oneround(q,r1,prefixOut=prefixOut,stop=stop,prefixIn=curPrefix,keepCleaved=(roundType!='U'),rtvol=self.rtvol[self.rndNum-1],t7vol=self.t7vol[self.rndNum-1],cycles=self.pcrcycles[self.rndNum-1],pcrdil=self.pcrdil[self.rndNum-1],pcrvol=self.pcrvol[self.rndNum-1],dolig=self.allLig or (roundType!='U'),pcrtgt=None if pcrprods is None else pcrprods[self.rndNum-1]) db.popStatus() # Add TRefs specified in rnddefs if 'tref' in self.rnddef[self.rndNum-1]: tref=self.rnddef[self.rndNum-1]['tref'] assert len(tref)==len(r1) for i in range(len(r1)): if tref[i] is not None: trefname='TRef%d'%tref[i] print("Adding %s to %s"%(trefname,r1[i].name)) if not reagents.isReagent(trefname): reagents.add(name=trefname,conc=10,extraVol=30,well="E4") trefSamp=reagents.getsample(trefname) oldConc=r1[i].conc.stock oldUnits=r1[i].conc.units oldVol=r1[i].volume self.e.transfer(r1[i].volume/(trefSamp.conc.dilutionneeded()-1),trefSamp,r1[i],mix=(False,False)) # TODO: Check that these end up mixed r1[i].conc=Concentration(stock=oldConc*oldVol/r1[i].volume, units=oldUnits) # Treat TRef as straight dilution print("New conc=",r1[i].conc) for i in range(len(r1)): if self.inputs[i]['round'] is None: r1[i].name="%s_%d"%(prefixOut[i],self.nextID) else: r1[i].name="%d_%s_R%d%c"%(self.nextID,prefixOut[i],self.inputs[i]['round']+self.rndNum,roundType) if self.inputs[i]['ligand'] is not None: r1[i].name="%s_%s"%(r1[i].name,self.inputs[i]['ligand']) print("Used ID ", self.nextID," for ", r1[i].name,": ",r1[i]) self.nextID+=1 r1[i].conc.final=r1[i].conc.stock*self.templateDilution for i in range(len(bc1)): #print("Renaming",bc1[i].name) pts=bc1[i].name.split(".") bc1[i].name="%d_BC_R%d%c"%(self.nextID,self.inputs[i//2]['round']+self.rndNum,roundType) if self.inputs[i//2]['ligand'] is not None: bc1[i].name="%s_%s"%(bc1[i].name,self.inputs[i//2]['ligand']) bc1[i].name+="_"+pts[-2] print("Used ID ", self.nextID," for ", bc1[i].name,":",bc1[i]) self.nextID+=1 curPrefix=prefixOut if "finalpcr" in self.qpcrStages: for i in range(len(r1)): if self.singlePrefix: q.addSamples(src=r1[i],needDil=r1[i].conc.stock/self.qConc,primers=["T7X","MX"] if self.useMX else ["T7X"]) else: # noinspection PyUnboundLocalVariable q.addSamples(src=r1[i],needDil=r1[i].conc.stock/self.qConc,primers=["T7X",prefixOut[i]+"X"]+(["MX"] if self.useMX else [])) # Add TRefs if needed for i in range(len(r1)): if 'tref' in self.inputs[i]: trefname='TRef%d'%self.inputs[i]['tref'] if not reagents.isReagent(trefname): reagents.add(name=trefname,conc=10,extraVol=30) tref=reagents.getsample(trefname) self.e.transfer(r1[i].volume/(tref.conc.dilutionneeded()-1),tref,r1[i],mix=(False,False)) db.pushStatus('qPCR') print("######### qPCR ########### %.0f min"%(clock.elapsed()/60)) self.allprimers=q.allprimers() q.run(confirm=self.qpcrWait) db.popStatus()