def bind21(reactant1, reactant2, max_helix=True, remote=None): """ Returns a list of reaction pathways which can be produced by 2-1 binding reactions of the argument complexes. The 2-1 binding reaction is the hybridization of two complementary unpaired domains, each in a different complex, to produce a single, unpseudoknotted product complex containing all of the strands contained in either of the original complexes. Note: remote is ineffective, but may be set for convencience """ r1_doms = reactant1.available_domains r2_doms = reactant2.available_domains reactions = [] # Iterate through all the free domains in reactant1 for (dom1, strand_num1, dom_num1) in r1_doms: # For each, find any domains in reactant2 that could bind for (dom2, strand_num2, dom_num2) in r2_doms: # If it can pair, this is one possible reaction (this kind of # reaction cannot possibly produce a pseudoknotted structure) if (dom1.can_pair(dom2)): # combine the two complexes into one, but do not perform the association reactions.append( join_complexes_21(reactant1, (strand_num1, dom_num1), reactant2, (strand_num2, dom_num2))) output = [] for complex, location1, location2 in reactions: # build "before" and "after" loop structures via find_on_loop ... out = find_on_loop( complex, location1, lambda (dom1, struct1, loc1), (dom2, struct2, loc2): loc1 == location1 and loc2 == location2) [(loc1s, before, loc2s, after)] = out # zipper for max-helix semantics if max_helix: loc1s, before, loc2s, after = zipper(complex, loc1s[0], before, loc2s[0], after, filter_bind11) [loc1s, before, loc2s, after] = map(Loop, [loc1s, before, loc2s, after]) (product, rotations) = do_bind11(complex, loc1s.locs, loc2s.locs) try: reaction = PepperReaction(sorted([reactant1, reactant2]), [product], 'bind21') except DSDDuplicationError, e: #assert opening_rate(length) == PepperReaction.dictionary[e.solution].rate reaction = e.existing length = len(loc1s) reaction.rate = bimolecular_binding_rate(length) output.append(reaction)
def test_condense_simple(self): complexes, reactions = read_pil(""" # File generated by peppercorn-v0.5.0 # Domain Specifications length d1 = 15 length t0 = 5 # Resting-set Complexes c1 = t0 d1 c2 = d1( + ) t0* c4 = t0( d1( + ) ) c5 = d1 # Transient Complexes c3 = t0( d1 + d1( + ) ) # Detailed Reactions reaction [bind21 = 100 /M/s ] c1 + c2 -> c3 reaction [open = 50 /s ] c3 -> c1 + c2 reaction [branch-3way = 50 /s ] c3 -> c4 + c5 """) # (rs1) c1 c4 (rs3) # \ / # <---> c3 ----> # / \ # (rs2) c2 c5 (rs4) # RestingSet representation rs1 = PepperMacrostate([complexes['c1']], memorycheck=False) rs2 = PepperMacrostate([complexes['c2']], memorycheck=False) rs3 = PepperMacrostate([complexes['c4']], memorycheck=False) rs4 = PepperMacrostate([complexes['c5']], memorycheck=False) # Frozensets instead of RestingMacrostates fs1 = frozenset([complexes['c1']]) fs2 = frozenset([complexes['c2']]) fs3 = frozenset([complexes['c4']]) fs4 = frozenset([complexes['c5']]) cplx_to_state = { # maps Complex to its RestingMacrostate complexes['c1']: rs1, complexes['c2']: rs2, complexes['c4']: rs3, complexes['c5']: rs4 } cplx_to_fate = { # maps Complex to its SetOfFates complexes['c1']: SetOfFates([[rs1]]), complexes['c2']: SetOfFates([[rs2]]), complexes['c3']: SetOfFates([[rs1, rs2], [rs3, rs4]]), complexes['c4']: SetOfFates([[rs3]]), complexes['c5']: SetOfFates([[rs4]]) } cplx_to_set = { # maps Complex to its frozenset complexes['c1']: fs1, complexes['c2']: fs2, complexes['c4']: fs3, complexes['c5']: fs4 } set_to_fate = { # maps frozenset to the RestingMacrostate fs1: rs1, fs2: rs2, fs3: rs3, fs4: rs4 } cond_react = PepperReaction([rs1, rs2], [rs3, rs4], 'condensed', memorycheck=False) cond_react.rate = 100 * (float(50) / (50 + 50)) enum = Enumerator(complexes.values(), reactions) enum.dry_run() # does not change the rates! enumRG = PepperCondensation(enum) enumRG.condense() self.assertEqual(sorted([rs1, rs2, rs3, rs4]), sorted(enumRG.resting_sets)) self.assertDictEqual(set_to_fate, enumRG.set_to_fate) self.assertDictEqual(cplx_to_fate, enumRG.cplx_to_fate) #self.assertDictEqual(cplx_to_set, info['complexes_to_resting_set']) self.assertEqual([cond_react], enumRG.condensed_reactions) self.assertEqual(cond_react.rate, enumRG.condensed_reactions[0].rate) self.assertEqual(enumRG.condensed_reactions[0].rate, 50)
def condense(self): """ Reaction condensation. """ if self._condensed_reactions is not None: raise CondensationError('condensation called twice') else: self._condensed_reactions = set() enum = self.enumerator for scc in self.SCCs: self.compute_fates(scc) for reaction in enum.reactions: # NOTE: Fast reactions have been handled by the fates if self.is_fast(reaction): continue # Filter reactions with no products/reactants if len(reaction.reactants) == 0 and len(reaction.products) == 0: raise Exception('before we continue, see why that happens...') #continue # Get the corresponding fates (resting sets) reactant_fates = map(self.get_fates, reaction.reactants) product_fates = map(self.get_fates, reaction.products) # Get all combinations of reactant and product fates, new_reactant_combinations = cartesian_sum(reactant_fates) new_product_combinations = cartesian_sum(product_fates) # Make sure each reactant is a resting set: # F(c) is a singleton for each reactant c for e, Fc in enumerate(reactant_fates): if not Fc.is_singleton(): logging.error("Cannot condense reaction {}: ".format(reaction) + \ "reactant {} has multiple fates: F({}) = {}".format( str(reaction.reactants[e]), str(reaction.reactants[e]), str(Fc))) raise CondensationError() # Take all combinations of reactants and products new_reactant_product_combinations = it.product( new_reactant_combinations, new_product_combinations) # Generate new reactions with each non-trivial combination for (reactants, products) in new_reactant_product_combinations: reactants = sorted(reactants) products = sorted(products) # skip the trivial case if reactants == products: continue try: reaction = PepperReaction(reactants, products, rtype='condensed') except DSDDuplicationError, e: logging.debug('duplicating PepperReaction: {}'.format( e.existing)) reaction = e.existing reaction.rate = self.get_condensed_rate(reaction) self._condensed_reactions.add(reaction)
def branch_4way(reactant, max_helix=False, remote=True): """ Returns a list of complex sets that can be created through one iteration of a 4 way branch migration reaction (each set consists of the molecules that result from the iteration; more than one molecule may result because branch migration can liberate strands and complexes). """ reactions = [] structure = reactant.pair_table # We loop through all domains for (strand_index, strand) in enumerate(structure): for (domain_index, domain) in enumerate(strand): # Unbound domains can't participate in branch migration if (structure[strand_index][domain_index] is None): continue start_loc = (strand_index, domain_index) # searches only 5'->3' direction around the loop for a bound domain that # has the same sequence (and therefore can be displaced) # # z _~_ z* (displacing) # ___/ \___> # # <___ ___ # \_ _/ # z* ~ z # # build products results = find_on_loop(reactant, start_loc, filter_4way) for e, (invader, xlinker, target, ylinker) in enumerate(results): if max_helix: invader, _, target, _ = zipper(reactant, invader[0], None, target[0], None, filter_4way) results[e] = map(Loop, [invader, xlinker, target, ylinker]) for (displacing, before, displaced, after) in results: (products, rotations) = do_4way_migration( reactant, displacing.locs, (structure[dl[0]][dl[1]] for dl in displacing.locs), (structure[bl[0]][bl[1]] for bl in displaced.locs), displaced.locs) try: reaction = PepperReaction([reactant], products, 'branch-4way') reaction.meta = (displacing, displaced, before, after) reaction.rotations = rotations except DSDDuplicationError, e: reaction = e.existing # skip remote toehold reactions if not remote: # NOTE: both sides need to be remote! if not ((not after.is_open and after.stems == 1 and after.bases == 0) or (not before.is_open and before.stems == 1 and before.bases == 0)): continue # calculate reaction constant reaction.rate = branch_4way_remote_rate( len(displacing), before, after) reactions.append(reaction)
def branch_3way(reactant, max_helix=True, remote=True): """ Returns a list of reaction pathways that can be created through one iteration of a 3 way branch migration reaction (more than one molecule may be produced by a reaction because branch migration can liberate strands and complexes). """ reactions = [] reactions = [] structure = reactant.pair_table # We iterate through all the domains for (strand_index, strand) in enumerate(structure): for (domain_index, domain) in enumerate(strand): # The displacing domain must be free if (structure[strand_index][domain_index] is not None): continue # search 5'->3' and 3'->5' directions around the loop for a bound # domain that is complementary (and therefore can be displaced) start_loc = (strand_index, domain_index) # build products fwresults = find_on_loop(reactant, start_loc, filter_3way, direction=1) bwresults = find_on_loop(reactant, start_loc, filter_3way, direction=-1) results = [] for (invader, xlinker, target, ylinker) in fwresults: if max_helix: invader, xlinker, target, ylinker = zipper( reactant, invader[0], xlinker, target[0], ylinker, filter_3way) ylinker += invader results.append( map(Loop, [invader[::-1], xlinker, target[::-1], ylinker])) for (invader, xlinker, target, ylinker) in bwresults: if max_helix: invader, xlinker, target, ylinker = zipper( reactant, invader[0], xlinker, target[0], ylinker, filter_3way) ylinker += invader[::-1] results.append(map(Loop, [invader, xlinker, target, ylinker])) for (displacing, before, bound, after) in results: (products, rotations) = do_3way_migration(reactant, list(displacing.locs), list(bound.locs)) try: reaction = PepperReaction([reactant], products, 'branch-3way') reaction.meta = (displacing, bound, before, after) reaction.rotations = rotations except DSDDuplicationError, e: reaction = e.existing # skip remote toehold reactions if directed if not remote: if not (not before.is_open and before.stems == 1 and before.bases == 0): # print "Rejecting... " + reaction.kernel_string # import pdb; pdb.set_trace() continue # calculate reaction constant reaction.rate = branch_3way_remote_rate( len(displacing), before, after) reactions.append(reaction)
def bind11(reactant, max_helix=True, remote=None): """ Returns a list of reaction pathways which can be produced by 1-1 binding reactions of the argument complex. The 1-1 binding reaction is the hybridization of two complementary unpaired domains within a single complex to produce a single unpseudoknotted product complex. Note: Remote is ineffective, but may be set for convencience """ reactions = [] structure = reactant.pair_table # We iterate through all the domains for (strand_index, strand) in enumerate(structure): for (domain_index, domain) in enumerate(strand): # The displacing domain must be free if structure[strand_index][domain_index] is not None: continue start_loc = (strand_index, domain_index) # search both directions around the loop for a bound domain that # has the same sequence (and therefore can be displaced) results = find_on_loop(reactant, start_loc, filter_bind11) if results: assert len(results) == \ len(find_on_loop(reactant, start_loc, filter_bind11, direction=-1)) for e, (invader, xlinker, target, ylinker) in enumerate(results): if max_helix: invader, xlinker, target, ylinker = zipper( reactant, invader[0], xlinker, target[0], ylinker, filter_bind11) results[e] = map(Loop, [invader, xlinker, target, ylinker]) # build products for (loc1s, before, loc2s, after) in results: try: (product, rotations) = do_bind11(reactant, loc1s.locs, loc2s.locs) except AssertionError: continue try: reaction = PepperReaction([reactant], [product], 'bind11') reaction.meta = (loc1s, loc2s, before, after) reaction.rotations = rotations except DSDDuplicationError, e: reaction = e.existing # length of invading domain length = len(loc1s) # calculate reaction constant reaction.rate = binding_rate(length, before, after) reactions.append(reaction)
output = [] for product_set, rotations, length, meta in reactions: try: reaction = PepperReaction([reactant], sorted(product_set), 'open') reaction.rotations = rotations reaction.meta = meta except DSDDuplicationError, e: reaction = e.existing # discard reactions where the release cutoff is greater than the threshold if len(reaction.products) == 1 and length > release_11: continue elif len(reaction.products) > 1 and length > release_1N: continue reaction.rate = opening_rate(length) output.append(reaction) return sorted(list(set(output))) def do_single_open(reactant, loc): struct = reactant.pair_table loc1 = loc loc2 = struct[loc1[0]][loc1[1]] assert struct[loc2[0]][loc2[1]] == loc1 struct[loc1[0]][loc1[1]] = None struct[loc2[0]][loc2[1]] = None newstr = pair_table_to_dot_bracket(struct) try: product = PepperComplex(reactant.sequence, newstr)