def catalyze_convert(sub1, sub2, product, klist, site='bf'): """Automation of the Sub1 + Sub2 <> Sub1:Sub2 >> Prod two-step reaction. Because product is created by the function, it must be fully specified. """ # Make sure that the substrates have the site: macros._verify_sites(sub1, site) macros._verify_sites(sub2, site) components = macros._macro_rule( 'bind', sub1({site: None}) + sub2({site: None}) | sub1({site: 1}) % sub2({site: 1}), klist[0:2], ['kf', 'kr']) components |= macros._macro_rule( 'convert', sub1({site: 1}) % sub2({site: 1}) >> product, [klist[2]], ['kc']) return components
def catalyze_convert(sub1, sub2, product, klist, site='bf'): """Automation of the Sub1 + Sub2 <> Sub1:Sub2 >> Prod two-step reaction. Because product is created by the function, it must be fully specified. """ # Make sure that the substrates have the site: macros._verify_sites(sub1, site) macros._verify_sites(sub2, site) components = macros._macro_rule('bind', sub1({site: None}) + sub2({site: None}) <> sub1({site: 1}) % sub2({site: 1}), klist[0:2], ['kf', 'kr']) components |= macros._macro_rule('convert', sub1({site: 1}) % sub2({site: 1}) >> product, [klist[2]], ['kc']) return components
def displace(lig1, lig2, target, k): """Generate unidirectional displacement reaction L1 + L2:T >> L1:T + L2. The signature can be remembered with the following formula: "lig1 displaces lig2 from target." """ return macros._macro_rule( 'displace', lig1({'bf': None}) + lig2({'bf': 1}) % target({'bf': 1}) >> lig1({'bf': 1}) % target({'bf': 1}) + lig2({'bf': None}), [k], ['kf'])
def displace(lig1, lig2, target, k): """Generate unidirectional displacement reaction L1 + L2:T >> L1:T + L2. The signature can be remembered with the following formula: "lig1 displaces lig2 from target." """ return macros._macro_rule('displace', lig1({'bf':None}) + lig2({'bf':1}) % target({'bf':1}) >> lig1({'bf':1}) % target({'bf':1}) + lig2({'bf':None}), [k], ['kf'])
def one_step_conv(sub1, sub2, product, klist, site='bf'): """ Bind sub1 and sub2 to form one product: sub1 + sub2 <> product. """ kf, kr = klist # Make sure that the substrates have the site: macros._verify_sites(sub1, site) macros._verify_sites(sub2, site) return macros._macro_rule( 'convert', sub1({site: None}) + sub2({site: None}) | product, klist, ['kf', 'kr'])
def assemble_pore_spontaneous(subunit, klist): """Generate the order-4 assembly reaction 4*Subunit <> Pore.""" # This is a function that is passed to macros._macro_rule to generate # the name for the pore assembly rule. It follows the pattern of, # e.g., "BaxA_to_BaxA4" for a Bax pore of size 4. def pore_rule_name(rule_expression): react_p = rule_expression.reactant_pattern mp = react_p.complex_patterns[0].monomer_patterns[0] subunit_name = macros._monomer_pattern_label(mp) pore_name = mp.monomer.name return '%s_to_%s%d' % (subunit_name, mp.monomer.name, 4) # Alias for a subunit that is capable of forming a pore free_subunit = subunit(s1=None, s2=None) # Create the pore formation rule macros._macro_rule('spontaneous_pore', free_subunit + free_subunit + free_subunit + free_subunit | subunit(s1=1, s2=4) % subunit(s1=2, s2=1) % \ subunit(s1=3, s2=2) % subunit(s1=4, s2=3), klist, ['kf', 'kr'], name_func=pore_rule_name)
def one_step_conv(sub1, sub2, product, klist, site='bf'): """ Bind sub1 and sub2 to form one product: sub1 + sub2 <> product. """ kf, kr = klist # Make sure that the substrates have the site: macros._verify_sites(sub1, site) macros._verify_sites(sub2, site) return macros._macro_rule('convert', sub1({site: None}) + sub2({site: None}) <> product, klist, ['kf', 'kr'])
def displace_reversibly(lig1, lig2, target, klist): """Generate reversible displacement reaction L1 + L2:T <> L1:T + L2. The signature can be remembered with the following formula: "lig1 displaces lig2 from target." The first rate given in in klist specifies the forward rate of this reaction; the second specifies the reverse rate. """ return macros._macro_rule('displace', lig1({'bf':None}) + lig2({'bf':1}) % target({'bf':1}) <> lig1({'bf':1}) % target({'bf':1}) + lig2({'bf':None}), klist, ['fwd_kf', 'rev_kf'])
def assemble_pore_spontaneous(subunit, klist): """Generate the order-4 assembly reaction 4*Subunit <> Pore.""" # This is a function that is passed to macros._macro_rule to generate # the name for the pore assembly rule. It follows the pattern of, # e.g., "BaxA_to_BaxA4" for a Bax pore of size 4. def pore_rule_name(rule_expression): react_p = rule_expression.reactant_pattern mp = react_p.complex_patterns[0].monomer_patterns[0] subunit_name = macros._monomer_pattern_label(mp) pore_name = mp.monomer.name return '%s_to_%s%d' % (subunit_name, mp.monomer.name, 4) # Alias for a subunit that is capable of forming a pore free_subunit = subunit(s1=None, s2=None) # Create the pore formation rule macros._macro_rule('spontaneous_pore', free_subunit + free_subunit + free_subunit + free_subunit <> subunit(s1=1, s2=4) % subunit(s1=2, s2=1) % \ subunit(s1=3, s2=2) % subunit(s1=4, s2=3), klist, ['kf', 'kr'], name_func=pore_rule_name)
def assemble_unstructured_pore(subunit, pore, order, klist): """Generate the order-n assembly reaction n*Subunit <> Pore.""" # This is a function that is passed to macros._macro_rule to generate # the name for the pore assembly rule. It follows the pattern of, # e.g., "BaxA4_to_Pore" for a Bax pore of size 4. def pore_rule_name(rule_expression): react_p = rule_expression.reactant_pattern mp = react_p.complex_patterns[0].monomer_patterns[0] subunit_name = _monomer_pattern_label(mp) pore_name = mp.monomer.name return '%s%d_to_%s' % (subunit_name, order, mp.monomer.name) # Assemble rule lhs lhs_pattern = subunit for i in range(1, order): lhs_pattern = lhs_pattern + subunit # Create the pore formation rule _macro_rule('unstructured_pore', lhs_pattern <> pore, klist, ['kf', 'kr'], name_func=pore_rule_name)
def displace_reversibly(lig1, lig2, target, klist): """Generate reversible displacement reaction L1 + L2:T <> L1:T + L2. The signature can be remembered with the following formula: "lig1 displaces lig2 from target." The first rate given in in klist specifies the forward rate of this reaction; the second specifies the reverse rate. """ return macros._macro_rule( 'displace', lig1({'bf': None}) + lig2({'bf': 1}) % target({'bf': 1}) | lig1({'bf': 1}) % target({'bf': 1}) + lig2({'bf': None}), klist, ['fwd_kf', 'rev_kf'])
def pore_bind(subunit, sp_site1, sp_site2, sc_site, size, cargo, c_site, klist): """Generate rules to bind a monomer to a circular homomeric pore. The pore structure is defined by the `pore_species` macro -- `subunit` monomers bind to each other from `sp_site1` to `sp_site2` to form a closed ring. The binding reaction takes the form pore + cargo <> pore:cargo. Parameters ---------- subunit : Monomer or MonomerPattern Subunit of which the pore is composed. sp_site1, sp_site2 : string Names of the sites where one copy of `subunit` binds to the next. sc_site : string Name of the site on `subunit` where it binds to the cargo `cargo`. size : integer Number of subunits in the pore at which binding will occur. cargo : Monomer or MonomerPattern Cargo that binds to the pore complex. c_site : string Name of the site on `cargo` where it binds to `subunit`. klist : list of Parameters or numbers List containing forward and reverse rate constants for the binding reaction (in that order). Rate constants should either be both Parameter objects or both numbers. If Parameters are passed, they will be used directly in the generated Rules. If numbers are passed, Parameters will be created with automatically generated names based on <TODO> and these parameters will be included at the end of the returned component list. """ macros._verify_sites(subunit, sc_site) macros._verify_sites(cargo, c_site) def pore_bind_rule_name(rule_expression, size): # Get ReactionPatterns react_p = rule_expression.reactant_pattern prod_p = rule_expression.product_pattern # Build the label components # Pore is always first complex of LHS due to how we build the rules subunit = react_p.complex_patterns[0].monomer_patterns[0].monomer if len(react_p.complex_patterns) == 2: # This is the complexation reaction cargo = react_p.complex_patterns[1].monomer_patterns[0] else: # This is the dissociation reaction cargo = prod_p.complex_patterns[1].monomer_patterns[0] return '%s_%d_%s' % (subunit.name, size, macros._monomer_pattern_label(cargo)) components = ComponentSet() # Set up some aliases that are invariant with pore size subunit_free = subunit({sc_site: None}) cargo_free = cargo({c_site: None}) #for size, klist in zip(range(min_size, max_size + 1), ktable): # More aliases which do depend on pore size pore_free = macros.pore_species(subunit_free, sp_site1, sp_site2, size) # This one is a bit tricky. The pore:cargo complex must only introduce # one additional bond even though there are multiple subunits in the # pore. We create partial patterns for bound pore and cargo, using a # bond number that is high enough not to conflict with the bonds within # the pore ring itself. # Start by copying pore_free, which has all cargo binding sites empty pore_bound = pore_free.copy() # Get the next bond number not yet used in the pore structure itself cargo_bond_num = size + 1 # Assign that bond to the first subunit in the pore pore_bound.monomer_patterns[0].site_conditions[sc_site] = cargo_bond_num # Create a cargo source pattern with that same bond cargo_bound = cargo({c_site: cargo_bond_num}) # Finally we can define the complex trivially; the bond numbers are # already present in the patterns pc_complex = pore_bound % cargo_bound # Create the rules name_func = functools.partial(pore_bind_rule_name, size=size) components |= macros._macro_rule('pore_bind', pore_free + cargo_free | pc_complex, klist[0:2], ['kf', 'kr'], name_func=name_func) return components
def pore_bind(subunit, sp_site1, sp_site2, sc_site, size, cargo, c_site, klist): """Generate rules to bind a monomer to a circular homomeric pore. The pore structure is defined by the `pore_species` macro -- `subunit` monomers bind to each other from `sp_site1` to `sp_site2` to form a closed ring. The binding reaction takes the form pore + cargo <> pore:cargo. Parameters ---------- subunit : Monomer or MonomerPattern Subunit of which the pore is composed. sp_site1, sp_site2 : string Names of the sites where one copy of `subunit` binds to the next. sc_site : string Name of the site on `subunit` where it binds to the cargo `cargo`. size : integer Number of subunits in the pore at which binding will occur. cargo : Monomer or MonomerPattern Cargo that binds to the pore complex. c_site : string Name of the site on `cargo` where it binds to `subunit`. klist : list of Parameters or numbers List containing forward and reverse rate constants for the binding reaction (in that order). Rate constants should either be both Parameter objects or both numbers. If Parameters are passed, they will be used directly in the generated Rules. If numbers are passed, Parameters will be created with automatically generated names based on <TODO> and these parameters will be included at the end of the returned component list. """ macros._verify_sites(subunit, sc_site) macros._verify_sites(cargo, c_site) def pore_bind_rule_name(rule_expression, size): # Get ReactionPatterns react_p = rule_expression.reactant_pattern prod_p = rule_expression.product_pattern # Build the label components # Pore is always first complex of LHS due to how we build the rules subunit = react_p.complex_patterns[0].monomer_patterns[0].monomer if len(react_p.complex_patterns) == 2: # This is the complexation reaction cargo = react_p.complex_patterns[1].monomer_patterns[0] else: # This is the dissociation reaction cargo = prod_p.complex_patterns[1].monomer_patterns[0] return '%s_%d_%s' % (subunit.name, size, macros._monomer_pattern_label(cargo)) components = ComponentSet() # Set up some aliases that are invariant with pore size subunit_free = subunit({sc_site: None}) cargo_free = cargo({c_site: None}) #for size, klist in zip(range(min_size, max_size + 1), ktable): # More aliases which do depend on pore size pore_free = macros.pore_species(subunit_free, sp_site1, sp_site2, size) # This one is a bit tricky. The pore:cargo complex must only introduce # one additional bond even though there are multiple subunits in the # pore. We create partial patterns for bound pore and cargo, using a # bond number that is high enough not to conflict with the bonds within # the pore ring itself. # Start by copying pore_free, which has all cargo binding sites empty pore_bound = pore_free.copy() # Get the next bond number not yet used in the pore structure itself cargo_bond_num = size + 1 # Assign that bond to the first subunit in the pore pore_bound.monomer_patterns[0].site_conditions[sc_site] = cargo_bond_num # Create a cargo source pattern with that same bond cargo_bound = cargo({c_site: cargo_bond_num}) # Finally we can define the complex trivially; the bond numbers are # already present in the patterns pc_complex = pore_bound % cargo_bound # Create the rules name_func = functools.partial(pore_bind_rule_name, size=size) components |= macros._macro_rule('pore_bind', pore_free + cargo_free <> pc_complex, klist[0:2], ['kf', 'kr'], name_func=name_func) return components
def cleave_complex(enzyme, e_site, substrate, s_site, product, klist, m1=None, m2=None): """Generate the two-step cleaving reaction E + S:S2 | E:S:S2 >> E + S + S2, while allowing complexes to serve as enzyme, substrate and/or product. E:S1 + S:S2 | E:S1:S:S2 >> E:S1 + S + S2 Parameters ---------- enzyme, substrate, product : Monomer, MonomerPattern, or ComplexPattern Monomers or complexes participating in the binding reaction. e_site, s_site : string The names of the sites on `enzyme` and `substrate` (respectively) where they bind each other to form the E:S complex. klist : list of 3 Parameters or list of 3 numbers Forward, reverse and cleaving rate constants (in that order). If Parameters are passed, they will be used directly in the generated Rules. If numbers are passed, Parameters will be created with automatically generated names based on the names and states of enzyme, substrate and product and these parameters will be included at the end of the returned component list. m1, m2 : Monomer or MonomerPattern If enzyme or substrate binding site is present in multiple monomers within a complex, the specific monomer desired for binding must be specified. Notes ----- Binding site between enzyme and substrate is using edge 50, so this edge must be free to use. Returns ------- components : ComponentSet The generated components. Contains the bidirectional binding Rule and optionally three Parameters if klist was given as numbers. """ if isinstance(m1, Monomer): m1 = m1() if isinstance(m2, Monomer): m2 = m2() def build_complex(s1, site1, m1): _verify_sites_complex(s1, site1) # Retrieve a dictionary specifying the MonomerPattern within the # complex that contains the given binding site. specsitesdict = _verify_sites_complex(s1, site1) s1complexpatub, s1complexpatb = check_sites_comp_build( s1, site1, m1, specsitesdict) return s1complexpatb, s1complexpatub def check_sites_comp_build(s1, site1, m1, specsitesdict): # Return error if binding site exists on multiple monomers and a # monomer for binding (m1) hasn't been specified. if len(specsitesdict) > 1 and m1 is None: raise ValueError( f"Binding site {site1} present in more than one monomer in complex {s1}." # noqa: E501 "Specify variable m1, the monomer used for binding within the complex." ) if not s1.is_concrete: raise ValueError("Complex '%s' must be concrete." % (s1)) # If the given binding site is only present in one monomer in the complex: if m1 is None: # Build up ComplexPattern for use in rule # (with state of given binding site specified). s1complexpatub = list(specsitesdict.keys())[0]({site1: None}) s1complexpatb = list(specsitesdict.keys())[0]({site1: 50}) for monomer in s1.monomer_patterns: if monomer not in specsitesdict.keys(): s1complexpatub %= monomer s1complexpatb %= monomer # If the binding site is present on more than one monomer in the # complex, the monomer must be specified by the user. Use specified m1 # to build ComplexPattern. else: # Make sure binding states of MonomerPattern m1 match those of the # monomer within the ComplexPattern s1 (ComplexPattern monomer # takes precedence if not). i = 0 identical_monomers = [] for mon in s1.monomer_patterns: # Only change the binding site for the first monomer that # matches. Keep any others unchanged to add to final complex # that is returned. if mon.monomer.name == m1.monomer.name: i += 1 if i == 1: s1complexpatub = m1({site1: None}) s1complexpatb = m1({site1: 50}) else: identical_monomers.append(mon) # Build up ComplexPattern for use in rule (with state of given # binding site on m1 specified). for mon in s1.monomer_patterns: if mon.monomer.name != m1.monomer.name: s1complexpatub %= mon s1complexpatb %= mon if identical_monomers: for i in range(len(identical_monomers)): s1complexpatub %= identical_monomers[i] s1complexpatb %= identical_monomers[i] return s1complexpatub, s1complexpatb # If no complexes exist in the reaction, revert to catalyze(). if (isinstance(enzyme, MonomerPattern) or isinstance(enzyme, Monomer)) and (isinstance( substrate, MonomerPattern) or isinstance(substrate, Monomer)): _verify_sites(enzyme, e_site) _verify_sites(substrate, s_site) return catalyze( enzyme, e_site, substrate, s_site, product, klist, ) # Build E:S if isinstance(enzyme, ComplexPattern): enzymepatb, enzyme_free = build_complex(enzyme, e_site, m1) else: enzymepatb, enzyme_free = enzyme({e_site: 50}), enzyme({e_site: None}) if isinstance(substrate, ComplexPattern): substratepatb, substratepatub = build_complex(substrate, s_site, m2) else: substratepatb = substrate({s_site: 50}) es_complex = enzymepatb % substratepatb if isinstance(product, ReactionPattern): final_product = product final_product += enzyme_free else: final_product = enzyme_free + product # Use bind complex to binding rule. components = bind_complex(enzyme, e_site, substrate, s_site, klist[0:2], m1, m2) components |= _macro_rule("cleave", es_complex >> final_product, [klist[2]], ["kc"]) return components