def get_gel_extraction_steps(self): p = stepwise.Protocol() names = self._cluster_names_by_molecule() desired = oxford_comma(x) if (x := self.desired_bands) else 'desired' n = plural(max(len(self.desired_bands), len(self.samples))) f = "Based on Fitzy's DNA PAGE purification protocol, [Nilson2013], and [Petrov2013]." p += pl(f"Cut the {desired} {n:band/s} out of the gel{p.add_footnotes(f)}.", ul( "Place the gel over a TLC plate.", "Use a UV light to visualize the RNA (dark spot).", "Consider visualizing remaining gel to ensure that all desired RNA was excised.", )) p += pl(f"Crush the gel {n:slice/s}.", ul( "Poke a hole in the bottom of a 0.65 mL tube with a 27 g needle.", "Place gel slice inside the 0.65 mL tube.", "Place the 0.65 mL tube inside a 1.5 mL tube.", "Spin at max speed for 5 min.", )) eb = elution_buffer() eb.hold_ratios.volume = 500 * len(self.samples), 'µL' p += pl( "Resuspend gel in 400 µL PAGE elution buffer:", eb, ) if names['RNA']: p += f"Incubate {join_if(names['RNA'], names['DNA'])}at 4°C overnight with end-over-end mixing." if names['DNA']: p += f"Incubate {join_if(names['DNA'], names['RNA'])}at 55°C overnight with 800 rpm mixing." return p
def _format_stage(self, prefix): class SkipStage(Exception): pass def has(attr): return hasattr(self, f'{prefix}_{attr}') def get(attr, *args): return getattr(self, f'{prefix}_{attr}', *args) def have_repeats(): n = get('repeats') return n > 1 if isinstance(n, int) else bool(n) def format_repeats(): n = get('repeats') if isinstance(n, int): n = f'{n}x' return f"Repeat {n}:" def format_submerge(): if not has('buffer'): raise SkipStage return f"Submerge gel in ≈{get('volume_mL', 30)} mL {get('buffer')}." def format_microwave(): if get('microwave', False): return f"Microwave until almost boiling (≈{format_sec(get('microwave_time_s', 45))})." def format_incubate(): return f"Shake gently for {format_min(get('time_min'))}." step_templates = get('steps', ['submerge', 'microwave', 'incubate']) step_formatters = { 'submerge': format_submerge, 'microwave': format_microwave, 'incubate': format_incubate, } out = steps = ul() for template in step_templates: formatter = step_formatters.get(template, lambda: template) try: steps += formatter() except SkipStage: return if have_repeats(): out = ul(pl(format_repeats(), steps, br='\n')) if n := get('rinse_repeats', False): out += f"Rinse {plural(n)://#x }with water."
def get_prep_step(self): def both_or_neither(key1, key2): has_key1 = has_key2 = True try: value1 = getattr(self, key1) except AttributeError: has_key1 = False try: value2 = getattr(self, key2) except AttributeError: has_key2 = False if has_key1 and not has_key2: raise ConfigError(f"specified {key1!r} but not {key2!r}") if has_key2 and not has_key1: raise ConfigError(f"specified {key2!r} but not {key1!r}") if has_key1 and has_key2: return value1, value2 else: return False s = pl( f"Prepare {plural(self.num_samples):# sample/s} for {self.title}:", self.sample_mix, ) if x := both_or_neither('incubate_temp_C', 'incubate_time_min'): temp_C, time_min = x s += ul(f"Incubate at {temp_C:g}°C for {time_min:g} min.")
def get_protocol(self): p = stepwise.Protocol() s = ul() if self.names: p += pl( f"Purify {','.join(self.names)} by ethanol precipitation [1,2]:", s) else: p += pl("Perform an ethanol precipitation [1,2]:", s) s += f"""\ Add {self.cation_name} to {self.cation_conc}.""" s += f"""\ Add {self.carrier_name} to {self.carrier_conc}.""" s += f"""\ Add {plural(self.solvent_volume):# volume/s} {self.solvent_name} and mix well.""" s += f"""\ If necessary, divide the sample between microfuge tubes such that none holds more than 400 µL.""" if self.incubation and (t := self.incubation_time): incubation_time = "overnight" if t == 'overnight' else f"for {t}" s += f"""\
def get_protocol(self): transfer = self.volume * (f := self.inv_factor) / (1 - f) initial_volume = self.volume + transfer conc_high_str = format_quantity(self.conc_high, self.conc_unit, 'g') material_str = f'{conc_high_str} {self.material}'.lstrip() conc_table = [[i, format_quantity(conc, self.conc_unit, 'e')] for i, conc in enumerate(self.concentrations, 1)] num_tubes = self.steps if self.include_zero else self.steps - 1 each_tube = 'each tube *except the last*' if self.include_zero else 'each tube' protocol = stepwise.Protocol() protocol += pl( "Perform a serial dilution [1]:", ul( f"Put {initial_volume:.2f} μL {material_str} in a tube.", f"Put {self.volume:.2f} μL {self.diluent} in {plural(num_tubes):# adjacent tube/s}.", f"Transfer {transfer:.2f} μL between {each_tube} to make {self.steps} {self.factor:.2g}-fold dilutions.", ), ) protocol.footnotes[1] = pl( "The final concentrations will be:", pre(stepwise.tabulate(conc_table, align='>>')), br='\n', ) return protocol
def get_protocol(self): # Maybe this should be the getter function for the assembly param... p = stepwise.Protocol() rxn = self.reaction n = rxn.num_reactions n_frags = self.num_fragments f = 'https://tinyurl.com/yaa5mqz5' p += pl( f"Setup {plural(n):# Golden Gate assembl/y/ies}{p.add_footnotes(f)}:", rxn, ) if n_frags <= 2: p += pl( "Run the following thermocycler protocol:", ul("37°C for 5 min", ), "Or, to maximize the number of transformants:", ul( "37°C for 60 min", "60°C for 5 min", ), ) elif n_frags <= 4: p += pl( "Run the following thermocycler protocol:", ul( "37°C for 60 min", "60°C for 5 min", ), ) elif n_frags <= 10: p += pl( "Run the following thermocycler protocol:", ul( "Repeat 30 times:", ul( "37°C for 1 min", "16°C for 1 min", ), "60°C for 5 min", ), ) else: p += pl( "Run the following thermocycler protocol:", ul( "Repeat 30 times:", ul( "37°C for 5 min", "16°C for 5 min", ), "60°C for 5 min", ), ) return p
def get_protocol(self): p = stepwise.Protocol() rxn = self.reaction p += pl( f"Setup {plural(self.num_reactions):# {self.title} reaction/s}{p.add_footnotes(self.setup_footnote)}:", rxn, ul(*self.setup_instructions), ) if self.incubation_time != '0': p += f"Incubate at {self.incubation_temp_C:g}°C for {self.incubation_time}{p.add_footnotes(self.incubation_footnote)}." return p
def get_protocol(self): p = stepwise.Protocol() p += pl( "Remove unligated linker by ultrafiltration:", s := ul( "Bring reaction to 500 µL with 8M urea.", f"Load onto a {self.mwco_kDa} kDa MWCO spin-filter [1].", "Spin 14000g, 15 min.", "Wash with 500 µL 8M urea.", "Wash with 500 µL nuclease-free water.", "Wash with water again.", "Wash with water again, but spin for 30 min.", "Invert the filter into a clean tube and spin 1000g, 2 min to collect ligated product in a volume of ≈15 µL.", ), )
def get_protocol(self): p = stepwise.Protocol() rxn = self.reaction p += pl( f"Setup {plural(rxn.num_reactions):# ligation reaction/s}{p.add_footnotes('https://tinyurl.com/y7gxfv5m')}:", rxn, ) p += pl( "Incubate at the following temperatures:", ul( "25°C for 15 min", "65°C for 10 min", ), ) return p
def get_protocol(self): p = stepwise.Protocol() rxn = self.reaction rxn_name = 'ligation' if self.use_ligase else 'negative control' n = rxn.num_reactions p += stepwise.pl( f"Setup {plural(n):# {rxn_name} reaction/s}:", rxn, ) if self.incubate: p += pl( f"Incubate the {plural(n):ligation reaction/s} as follows:", s := ul( f"{self.incubate_temp} for {self.incubate_time}." ), ) if self.quench:
def get_product_recovery_steps(self): p = stepwise.Protocol() names = self._cluster_names_by_molecule() # Filter material: # - Corning has a good guide on which material to select: # https://www.corning.com/catalog/cls/documents/selection-guides/t_filterselectionguide.pdf # # - I want 0.22 µm, because that's the standard size for filter # sterilizing biological buffers (that's not my application here, but # I can see myself wanting to do that). # # - I want cellulose acetate filters. Nylon and cellulose nitrate have # high DNA binding, which will cause me to lose material. The # downside to cellulose acetate is that it has a wetting agent that # will end up in the sample. However, this will be removed by the # Zymo column in the subsequent step. # # - Product number: 8161 (non sterile) # # Centrifugation speed: # - Fitzy's DNA PAGE purification protocol calls for 4 min at 7000 rpm # - The Corning guide (see above) includes an agarose gel purification # protocol, which calls for 13,000g for 5-20 min. But this protocol # has no incubation step, so I gather that the spin is supposed to # pull the solvent out of the gel. I probably don't need to go so # fast, but why not go as fast as the columns can tolerate? f = "Nylon and cellulose nitrate have high DNA-binding: https://tinyurl.com/3pkyc8dr" p += pl( "Remove gel debris by spin filtration:", ul( f"Load samples onto a 0.22 µm cellose-acetate Spin-X column{p.add_footnotes(f)}.", "Spin at 13,000g for 5 min." ), ) if self.cleanup: if names['RNA']: p += cleanup(self.rna_cleanup_preset, names['RNA'], names['DNA']) if names['DNA']: p += cleanup(self.dna_cleanup_preset, names['DNA'], names['RNA']) return p
def get_protocol(self): p = stepwise.Protocol() footnotes = [x] if (x := self.protocol_link) else [] footnotes += self.footnotes s = pl(f"Stain gel with {self.title}{p.add_footnotes(*footnotes)}:") if self.light_sensitive: #s += "Keep gel in the dark in all following steps." s += ul("Keep the stain protected from light.") #s += ul("In all following steps, protect the stain from light.") s += self._format_stage('prep') s += self._format_stage('stain') s += self._format_stage('destain') p += s if self.imaging_cmd: p += stepwise.load(self.imaging_cmd) return p
def get_protocol(self): p = stepwise.Protocol() rxn = self.reaction n = rxn.num_reactions p += pl( f"Setup {plural(n):# annealing reaction/s} [1]:", rxn, ) p.footnotes[1] = """\ Using 0.6x linker reduces the amount of unligated linker, see expt #1.""" p += pl( f"Perform the {plural(n):annealing reaction/s}:", ul( "Incubate at 95°C for 2 min.", "Cool at room temperature.", ), ) return p
def get_protocol(self): p = stepwise.Protocol() if self.dnase_treatment == 'pre': mm = stepwise.MasterMix("""\ Reagent Stock Volume MM? ====================== ======= ======== === nuclease-free water to 50 µL + DNA digestion buffer 10x 5 µL + DNase I [1] 1 U/µL 5 µL + RNA sample <10 µg 40 µL - """) step = "Setup a DNase I reaction for each sample:" if self.num_samples: step = f"Setup {plural(self.num_samples):# DNase I reaction/s}:" mm.num_reactions = self.num_samples if self.sample_volume_uL: mm['RNA sample'].volume = self.sample_volume_uL, 'µL' p += pl(step, mm) p += "Incubate at room temperature for 15 min." p.footnotes[ 1] = "Reconstitute lyophilized DNase I (#E1009-A; 250U) with 275 µL nuclease-free water and mix by gentle inversion. Store frozen aliquots." if self.dnase_treatment == 'post': mm = stepwise.MasterMix("""\ Reagent Stock Volume MM? ====================== ======= ======== === DNA digestion buffer 10x 75 µL + DNase I [1] 1 U/µL 5 µL + """) if self.num_samples: mm.num_reactions = self.num_samples p += pl("Prepare 80 µL DNase I reaction mix for each sample:", mm) s = ul() p += pl("Purify RNA using Zymo Clean & Concentrator spin columns:", s) if self.sample_volume_uL: if self.sample_volume_uL < 50: s += f"Add {50 - self.sample_volume_uL:g} nuclease-free water to each sample" v = max(50, self.sample_volume_uL) s += f"Add {2*v:g} µL RNA binding buffer; mix." s += f"Add {3*v:g} µL >95% ethanol; mix." else: s += "If necessary, bring each sample to 50 µL." s += "Add 2 volumes RNA binding buffer; mix." s += "Add 3 volumes >95% ethanol; mix." s += f"Load sample on spin column." s += f"Spin 1 min, 16000g; discard flow-through." if self.dnase_treatment == 'post': s += f"Add 400 µL RNA wash buffer." s += f"Spin 30s, 16000g; discard flow-through." s += f"Add 80 µL DNase I reaction mix." s += f"Incubate at room temperature for 15 min." s += f"Add 400 µL RNA prep buffer." s += f"Spin 30s, 16000g; discard flow-through." s += f"Add 700 µL RNA wash buffer." s += f"Spin 30s, 16000g; discard flow-through." s += f"Add 400 µL RNA wash buffer." s += f"Spin 60s, 16000g; discard flow-through." s += f"Add {self.elute_volume_uL:g} µL nuclease-free water." s += f"Spin 30s, 16000g." return p
if x := self.gel_percent: gel.gel_percent = x if x := self.gel_run_volts: gel.run_volts = x if x := self.gel_run_time_min: gel.run_time_min = x try: gel.load_volume_uL = unanimous( x.load_volume_per_lane_uL for x in self.samples ) except ValueError: gel.load_volume_uL = 0 p += pl(f"Load the {n:sample/s} in the gel as follows:", u := ul()) for sample in self.samples: num_lanes = f" in each of {m} lanes" if (m := sample.num_lanes) != 1 else "" u += f"{sample.load_volume_per_lane_uL:.2g} µL {sample.name}{num_lanes}" p += gel.run_step return p def get_gel_extraction_steps(self): p = stepwise.Protocol() names = self._cluster_names_by_molecule() desired = oxford_comma(x) if (x := self.desired_bands) else 'desired' n = plural(max(len(self.desired_bands), len(self.samples))) f = "Based on Fitzy's DNA PAGE purification protocol, [Nilson2013], and [Petrov2013]."
def get_protocol(self): from itertools import groupby from operator import itemgetter protocol = stepwise.Protocol() rxn = self.reaction rxn_type = (self.enzymes[0]['name'] if len(self.enzymes) == 1 else 'restriction') def incubate(temp_getter, time_getter, time_formatter=lambda x: f'{x} min'): incubate_params = [ (k, max(time_getter(x) for x in group)) for k, group in groupby(self.enzymes, temp_getter) ] return [ f"{temp}°C for {time_formatter(time)}" for temp, time in sorted(incubate_params) ] if self.time: digest_steps = incubate( itemgetter('incubateTemp'), lambda x: self.time, lambda x: x, ) else: digest_steps = incubate( itemgetter('incubateTemp'), lambda x: 15 if x['timeSaver'] else 60, lambda x: '5–15 min' if x == 15 else '1 hour', ) inactivate_steps = incubate( itemgetter('heatInactivationTemp'), itemgetter('heatInactivationTime'), ) protocol += pl( f"Setup {plural(rxn.num_reactions):# {rxn_type} digestion/s} [1,2]:", rxn, ) protocol += pl( f"Incubate at the following temperatures [3]:", ul(*digest_steps, *inactivate_steps), ) urls = [x['url'] for x in self.enzymes if x.get('url')] protocol.footnotes[1] = pl(*urls, br='\n') protocol.footnotes[2] = """\ NEB recommends 5–10 units of enzyme per µg DNA (10–20 units for genomic DNA). Enzyme volume should not exceed 10% of the total reaction volume to prevent star activity due to excess glycerol. """ protocol.footnotes[3] = """\ The heat inactivation step is not necessary if the DNA will be purified before use. """ return protocol
""") rxn.num_reactions = 2 rxn.extra_percent = 0 rxn.hold_ratios.volume = '3 µL' p += pl( f"Setup {rxn.num_reactions} click reactions:", rxn, ) p += "Divide each reaction in three." p += pl( "Incubate the reactions as follows:", ul( "−20°C overnight", "25°C for 4h, −20°C overnight", "25°C overnight", ), ) p += "Label the product: o236" p += stepwise.load(f"gel urea {3 * rxn.num_reactions + 2} -c 1730 -S") p.insert_footnotes( pl( "The product (o236) has MW = 17285 g/mol, so:", "100 µM = 1730 ng/µL", br='\n', ), ) p += stepwise.load("stain sybr-green-ii/page -I") p += stepwise.load("laser blue red")
# PCR reaction setup: if self.footnote: # Before the footnote on resuspending the primers, if it hasn't # been added to the protocol already. footnotes.insert(0, self.footnote) if x := pcr['template DNA'].stock_conc: footnotes.append(pl( f"For diluting template DNA to {x}:", f"Dilute 1 µL twice into {sqrt(1000/x.value):.1g}*sqrt([DNA]) µL", br='\n', )) title = 'qPCR' if self.qpcr else 'PCR' instructions = ul() protocol += pl( f"Setup {plural(pcr.num_reactions):# {title} reaction/s}{protocol.add_footnotes(*footnotes)}:", pcr, instructions, ) if pcr.volume > '50 µL': instructions += f"Split each reaction into {ceil(pcr.volume.value / 50)} tubes." if pcr.num_reactions > 1: instructions += "Use any extra master mix as a negative control." # Thermocycler protocol: protocol += pl( "Run the following thermocycler protocol:",