def testSegmentNeighborhood2(self): enum = self.SLC_enumerator complex_set = [ self.complexes['Cat'], self.complexes['C2'], self.complexes['I1'], self.complexes['I2'], self.complexes['I3'], self.complexes['SP'] ] reaction_set = [] # multi-molecular reactions never appear in segment neighborhood arguments #reaction_set.append(ReactionPathway('bind21', [self.complexes['Cat'], self.complexes['C2']], [self.complexes['I1']])) #reaction_set.append(ReactionPathway('bind21', [self.complexes['I3'], self.complexes['SP']], [self.complexes['I2']])) reaction_set.append( ReactionPathway('open', [self.complexes['I1']], [self.complexes['Cat'], self.complexes['C2']])) reaction_set.append( ReactionPathway('open', [self.complexes['I2']], [self.complexes['I3'], self.complexes['SP']])) reaction_set.append( ReactionPathway('branch_3way', [self.complexes['I1']], [self.complexes['I2']])) reaction_set.append( ReactionPathway('branch_3way', [self.complexes['I2']], [self.complexes['I1']])) res = enum.segment_neighborhood(complex_set, reaction_set) exp = { 'resting_states': sorted([ RestingState('0', [self.complexes['Cat']]), RestingState('1', [self.complexes['C2']]), RestingState('2', [self.complexes['SP']]), RestingState('3', [self.complexes['I3']]) ]), 'resting_state_complexes': sorted([ self.complexes['Cat'], self.complexes['C2'], self.complexes['SP'], self.complexes['I3'] ]), 'transient_state_complexes': sorted([self.complexes['I1'], self.complexes['I2']]) } assert res == exp
def rxn(string): """ Dummy function for generating reactions between formal species """ reactants, products = string.split('->') reactants = reactants.split('+') products = products.split('+') reactants = [cplx(x) for x in reactants] products = [cplx(x) for x in products] reactions.add(ReactionPathway('dummy', reactants, products))
def testSegmentNeighborhood3(self): enum = self.three_arm_enumerator complex_set = [ self.complexes2['IABC'], self.complexes2['I'], self.complexes2['ABC'] ] reaction_set = [] reaction_set.append( ReactionPathway('branch_3way', [self.complexes2['IABC']], [self.complexes2['I'], self.complexes2['IABC']])) res = enum.segment_neighborhood(complex_set, reaction_set) exp = { 'resting_states': sorted([ RestingState('0', [self.complexes2['I']]), RestingState('1', [self.complexes2['ABC']]) ]), 'resting_state_complexes': sorted([self.complexes2['I'], self.complexes2['ABC']]), 'transient_state_complexes': sorted([self.complexes2['IABC']]) } assert res == exp
def testEnumeration3(self): enum = Enumerator(self.SLC_enumerator.domains, self.SLC_enumerator.strands, [ self.complexes['Cat'], self.complexes['C1'], self.complexes['C2'] ]) enum.enumerate() # shortcut because of large number of uses c = self.complexes exp_initial_complexes = sorted([ self.complexes['Cat'], self.complexes['C1'], self.complexes['C2'] ]) res_initial_complexes = sorted(enum.initial_complexes) assert exp_initial_complexes == res_initial_complexes reaction_set = [] reaction_set.append( ReactionPathway('bind21', [c['Cat'], c['C2']], [c['I1']])) reaction_set.append( ReactionPathway('open', [c['I1']], [c['Cat'], c['C2']])) reaction_set.append( ReactionPathway('branch_3way', [c['I1']], [c['I2']])) reaction_set.append( ReactionPathway('branch_3way', [c['I2']], [c['I1']])) reaction_set.append( ReactionPathway('open', [c['I2']], [c['SP'], c['I3']])) reaction_set.append( ReactionPathway('bind21', [c['SP'], c['I3']], [c['I2']])) reaction_set.append( ReactionPathway('bind21', [c['I3'], c['C1']], [c['I4']])) reaction_set.append( ReactionPathway('open', [c['I4']], [c['I3'], c['C1']])) reaction_set.append( ReactionPathway('branch_3way', [c['I4']], [c['OP'], c['I5']])) I6 = copy.deepcopy(c['I5']) Cat_index = I6.strand_index('Cat') BS_index = I6.strand_index('BS') PS_index = I6.strand_index('PS') I6.structure[Cat_index][0] = None I6.structure[BS_index][1] = (PS_index, 4) I6.structure[PS_index][4] = (BS_index, 1) self.complexes['I6'] = I6 I6._name = 'I6' reaction_set.append( ReactionPathway('branch_3way', [c['I5']], [c['I6']])) reaction_set.append( ReactionPathway('branch_3way', [c['I6']], [c['I5']])) reaction_set.append( ReactionPathway('open', [c['I6']], [c['W'], c['Cat']])) reaction_set.append( ReactionPathway('bind21', [c['W'], c['Cat']], [c['I6']])) I7 = copy.deepcopy(c['I4']) Cat_index = I7.strand_index('Cat') BS_index = I7.strand_index('BS') PS_index = I7.strand_index('PS') I7.structure[Cat_index][0] = None I7.structure[BS_index][1] = (PS_index, 4) I7.structure[PS_index][4] = (BS_index, 1) self.complexes['I7'] = I7 I7._name = 'I7' reaction_set.append( ReactionPathway('branch_3way', [c['I4']], [c['I7']])) reaction_set.append( ReactionPathway('branch_3way', [c['I7']], [c['I4']])) reaction_set.append( ReactionPathway('branch_3way', [c['I7']], [c['OP'], c['I6']])) I8 = Complex( 'I8', [self.strands['BS'], self.strands['OP'], self.strands['PS']], [[None, (2, 4), (2, 3), None, None, None], [(2, 2), (2, 1), (2, 0), None], [(1, 2), (1, 1), (1, 0), (0, 2), (0, 1)]]) self.complexes['I8'] = I8 I8._name = 'I8' reaction_set.append( ReactionPathway('open', [c['I7']], [c['I8'], c['Cat']])) reaction_set.append( ReactionPathway('branch_3way', [c['I8']], [c['OP'], c['W']])) exp_reactions = sorted(reaction_set) res_reactions = sorted(enum.reactions) assert exp_reactions == res_reactions exp_resting_states = sorted([ RestingState('0', [self.complexes['Cat']]), RestingState('1', [self.complexes['C2']]), RestingState('2', [self.complexes['SP']]), RestingState('3', [self.complexes['I3']]), RestingState('4', [self.complexes['C1']]), RestingState('5', [self.complexes['OP']]), RestingState('6', [self.complexes['W']]) ]) res_resting_states = sorted(enum.resting_states) assert exp_resting_states == res_resting_states exp_resting_complexes = sorted([ self.complexes['Cat'], self.complexes['C2'], self.complexes['SP'], self.complexes['I3'], self.complexes['C1'], self.complexes['OP'], self.complexes['W'] ]) assert sorted(enum.resting_complexes) == exp_resting_complexes exp_transient_complexes = sorted([ self.complexes['I1'], self.complexes['I2'], self.complexes['I4'], self.complexes['I5'], self.complexes['I6'], self.complexes['I7'], self.complexes['I8'] ]) res_transient_complexes = sorted(enum.transient_complexes) assert res_transient_complexes == exp_transient_complexes assert sorted(exp_transient_complexes + exp_resting_complexes) == sorted(enum.complexes)
def testEnumeration2(self): enum = Enumerator(self.SLC_enumerator._domains, self.SLC_enumerator._strands, [self.complexes['I1']]) enum.enumerate() exp_initial_complexes = [self.complexes['I1']] assert exp_initial_complexes == enum.initial_complexes reaction_set = [] reaction_set.append( ReactionPathway('bind21', [self.complexes['Cat'], self.complexes['C2']], [self.complexes['I1']])) reaction_set.append( ReactionPathway('bind21', [self.complexes['I3'], self.complexes['SP']], [self.complexes['I2']])) reaction_set.append( ReactionPathway('open', [self.complexes['I1']], [self.complexes['Cat'], self.complexes['C2']])) reaction_set.append( ReactionPathway('open', [self.complexes['I2']], [self.complexes['I3'], self.complexes['SP']])) reaction_set.append( ReactionPathway('branch_3way', [self.complexes['I1']], [self.complexes['I2']])) reaction_set.append( ReactionPathway('branch_3way', [self.complexes['I2']], [self.complexes['I1']])) reaction_set.sort() exp_reactions = reaction_set res_reactions = sorted(enum.reactions) assert exp_reactions == res_reactions exp_resting_states = sorted([ RestingState('0', [self.complexes['Cat']]), RestingState('1', [self.complexes['C2']]), RestingState('2', [self.complexes['SP']]), RestingState('3', [self.complexes['I3']]) ]) assert exp_resting_states == enum.resting_states exp_complexes = sorted([ self.complexes['I1'], self.complexes['I2'], self.complexes['Cat'], self.complexes['C2'], self.complexes['SP'], self.complexes['I3'] ]) res_complexes = sorted(enum.complexes) assert res_complexes == exp_complexes exp_resting_complexes = sorted([ self.complexes['Cat'], self.complexes['C2'], self.complexes['SP'], self.complexes['I3'] ]) res_resting_complexes = sorted(enum.resting_complexes) assert res_resting_complexes == exp_resting_complexes exp_transient_complexes = sorted( [self.complexes['I1'], self.complexes['I2']]) res_transient_complexes = sorted(enum.transient_complexes) assert res_transient_complexes == exp_transient_complexes
def setUp(self): self.domains = domains = { 'a': Domain('a', 'long'), 'b': Domain('b', 'long'), 'c': Domain('c', 'long'), 'd': Domain('d', 'long'), 'e': Domain('e', 'long'), 'f': Domain('f', 'long'), 'g': Domain('g', 'long'), 'h': Domain('h', 'long'), 'i': Domain('i', 'long'), 'j': Domain('j', 'long'), } self.strands = strands = { 's1': Strand('s1', [domains['a']]), 's2': Strand('s2', [domains['b']]), 's3': Strand('s3', [domains['c']]), 's4': Strand('s4', [domains['d']]), 's5': Strand('s5', [domains['e']]), 's6': Strand('s6', [domains['f']]), 's7': Strand('s7', [domains['g']]), 's8': Strand('s8', [domains['h']]), 's9': Strand('s9', [domains['i']]), 's10': Strand('s10', [domains['j']]), } self.complexes = complexes = { 'A': Complex('A', [strands['s1']], [[None]]), 'B': Complex('B', [strands['s2']], [[None]]), 'C': Complex('C', [strands['s3']], [[None]]), 'D': Complex('D', [strands['s4']], [[None]]), 'E': Complex('E', [strands['s5']], [[None]]), 'F': Complex('F', [strands['s6']], [[None]]), 'G': Complex('G', [strands['s7']], [[None]]), 'H': Complex('H', [strands['s8']], [[None]]), 'I': Complex('I', [strands['s9']], [[None]]), 'J': Complex('J', [strands['s10']], [[None]]) } self.reactions = reactions = { # cycle 1: A -> B -> C -> D -> A 'A->B': ReactionPathway('bind11', [complexes['A']], [complexes['B']]), 'B->C': ReactionPathway('bind11', [complexes['B']], [complexes['C']]), 'C->D': ReactionPathway('bind11', [complexes['C']], [complexes['D']]), 'D->A': ReactionPathway('bind11', [complexes['D']], [complexes['A']]), # cycle 2: E -> F -> G ->E 'E->F': ReactionPathway('bind11', [complexes['E']], [complexes['F']]), 'F->G': ReactionPathway('bind11', [complexes['F']], [complexes['G']]), 'G->E': ReactionPathway('bind11', [complexes['G']], [complexes['E']]), # fast transition: cycle 1 -> cycle 2 'D->E': ReactionPathway('bind11', [complexes['D']], [complexes['E']]), # non-deterministic choice: 'E->H': ReactionPathway('bind11', [complexes['E']], [complexes['H']]), 'E->I': ReactionPathway('bind11', [complexes['E']], [complexes['I']]), # bimolecular reaction 'C+D->E': ReactionPathway('bind21', [complexes['C'], complexes['D']], [complexes['E']]), # reversible reactions 'B->A': ReactionPathway('bind11', [complexes['B']], [complexes['A']]), 'C->B': ReactionPathway('bind11', [complexes['C']], [complexes['B']]), 'D->C': ReactionPathway('bind11', [complexes['D']], [complexes['C']]), 'A->D': ReactionPathway('bind11', [complexes['A']], [complexes['D']]), 'A->E': ReactionPathway('bind11', [complexes['A']], [complexes['E']]), 'E->A': ReactionPathway('bind11', [complexes['E']], [complexes['A']]), 'E->C': ReactionPathway('bind11', [complexes['E']], [complexes['C']]), 'E->F': ReactionPathway('bind11', [complexes['E']], [complexes['F']]), 'F->D': ReactionPathway('bind11', [complexes['F']], [complexes['D']]) } enum = Enum(complexes.values(), reactions.values()) self.enumerator = self.enum = enum self.neighborhood_abcd = pluck(complexes, ['A', 'B', 'C', 'D']) self.neighborhood_e = [complexes['E']]
def testCondenseGraph6(self): self.fate_example = input.input_pil( 'tests/files/examples/fate-example.pil') self.fate_example.MAX_COMPLEX_SIZE = 10 self.fate_example.MAX_REACTION_COUNT = 1000 self.fate_example.MAX_COMPLEX_COUNT = 200 self.fate_example.RELEASE_CUTOFF = 7 self.fate_example.enumerate() enumerator = self.fate_example condensed = condense_resting_states(self.fate_example) # Domains domains = { '2': Domain('2', 8, is_complement=False, sequence='NNNNNNNN'), '2*': Domain('2', 8, is_complement=True, sequence='NNNNNNNN'), '3': Domain('3', 8, is_complement=False, sequence='NNNNNNNN'), '3*': Domain('3', 8, is_complement=True, sequence='NNNNNNNN'), 'a': Domain('a', 8, is_complement=False, sequence='NNNNNNNN'), 'a*': Domain('a', 8, is_complement=True, sequence='NNNNNNNN'), 'b': Domain('b', 8, is_complement=False, sequence='NNNNNNNN'), 'b*': Domain('b', 8, is_complement=True, sequence='NNNNNNNN'), 'c': Domain('c', 8, is_complement=False, sequence='NNNNNNNN'), 'c*': Domain('c', 8, is_complement=True, sequence='NNNNNNNN'), 't': Domain('t', 4, is_complement=False, sequence='NNNN'), 't*': Domain('t', 4, is_complement=True, sequence='NNNN') } assert set(domains.values()) == set(enumerator.domains) # Strands strands = { '3a': Strand('3a', [domains['3*'], domains['a*']]), '23': Strand('23', [domains['2'], domains['3']]), 'gate': Strand('gate', [ domains['a*'], domains['b*'], domains['c'], domains['b'], domains['a'], domains['2*'], domains['t*'] ]), 't23': Strand('t23', [domains['t'], domains['2'], domains['3']]) } assert set(strands.values()) == set(enumerator.strands) # Complexes complexes = { '2': Complex('2', [strands['23'], strands['3a'], strands['gate']], [[(2, 5), (1, 0)], [(0, 1), None], [(2, 4), (2, 3), None, (2, 1), (2, 0), (0, 0), None]]), '5': Complex('5', [ strands['23'], strands['3a'], strands['gate'], strands['t23'] ], [[(2, 5), (1, 0)], [(0, 1), (2, 4)], [None, (2, 3), None, (2, 1), (1, 1), (0, 0), (3, 0)], [(2, 6), None, None]]), '6': Complex('6', [ strands['23'], strands['3a'], strands['gate'], strands['t23'] ], [[(2, 5), (1, 0)], [(0, 1), None], [(2, 4), (2, 3), None, (2, 1), (2, 0), (0, 0), (3, 0)], [(2, 6), None, None]]), '12': Complex('12', [strands['gate'], strands['t23']], [[(0, 4), (0, 3), None, (0, 1), (0, 0), (1, 1), (1, 0)], [(0, 6), (0, 5), None]]), '13': Complex('13', [strands['23'], strands['3a']], [[None, (1, 0)], [(0, 1), None]]), '17': Complex('17', [ strands['23'], strands['3a'], strands['gate'], strands['t23'] ], [[None, (1, 0)], [(0, 1), (2, 4)], [None, (2, 3), None, (2, 1), (1, 1), (3, 1), (3, 0)], [(2, 6), (2, 5), None]]), '20': Complex('20', [strands['3a'], strands['gate'], strands['t23']], [[(2, 2), (1, 4)], [None, (1, 3), None, (1, 1), (0, 1), (2, 1), (2, 0)], [(1, 6), (1, 5), (0, 0)]]), '21': Complex('21', [strands['23']], [[None, None]]), '26': Complex('26', [strands['3a'], strands['gate'], strands['t23']], [[(2, 2), None], [(1, 4), (1, 3), None, (1, 1), (1, 0), (2, 1), (2, 0)], [(1, 6), (1, 5), (0, 0)]]), 'gate': Complex('gate', [strands['23'], strands['3a'], strands['gate']], [[(2, 5), (1, 0)], [(0, 1), (2, 4)], [None, (2, 3), None, (2, 1), (1, 1), (0, 0), None]]), 't23': Complex('t23', [strands['t23']], [[None, None, None]]) } assert set(complexes.values()) == set(enumerator.complexes) # Reactions reactions = { ReactionPathway('bind21', [complexes['gate'], complexes['t23']], [complexes['5']]), ReactionPathway('bind21', [complexes['2'], complexes['t23']], [complexes['6']]), ReactionPathway('branch_3way', [complexes['gate']], [complexes['2']]), ReactionPathway('branch_3way', [complexes['2']], [complexes['gate']]), ReactionPathway('branch_3way', [complexes['6']], [complexes['13'], complexes['12']]), ReactionPathway('branch_3way', [complexes['6']], [complexes['5']]), ReactionPathway('branch_3way', [complexes['5']], [complexes['17']]), ReactionPathway('branch_3way', [complexes['5']], [complexes['6']]), ReactionPathway('branch_3way', [complexes['17']], [complexes['21'], complexes['20']]), ReactionPathway('branch_3way', [complexes['17']], [complexes['13'], complexes['12']]), ReactionPathway('branch_3way', [complexes['17']], [complexes['5']]), ReactionPathway('branch_3way', [complexes['20']], [complexes['26']]), ReactionPathway('branch_3way', [complexes['26']], [complexes['20']]), ReactionPathway('open', [complexes['6']], [complexes['2'], complexes['t23']]), ReactionPathway('open', [complexes['5']], [complexes['gate'], complexes['t23']]) } assert set(reactions) == set(enumerator.reactions) # Resting states resting_states = { '40': RestingState('40', [complexes['12']]), '42': RestingState('42', [complexes['13']]), '43': RestingState('43', [complexes['21']]), '41': RestingState('41', [complexes['26'], complexes['20']]), '38': RestingState('38', [complexes['2'], complexes['gate']]), '39': RestingState('39', [complexes['t23']]) } assert set(resting_states.values()) == set(condensed['resting_states']) # Condensed Reactions condensed_reactions = { ReactionPathway('condensed', [resting_states['38'], resting_states['39']], [resting_states['43'], resting_states['41']]), ReactionPathway('condensed', [resting_states['38'], resting_states['39']], [resting_states['42'], resting_states['40']]) } assert set(condensed_reactions) == set(condensed['reactions'])
def testCondenseGraph2(self): reactions = pluck( self.reactions, ['A->B', 'B->C', 'C->D', 'D->A', 'E->F', 'F->G', 'G->E', 'C+D->E']) complexes = pluck(self.complexes, ['A', 'B', 'C', 'D', 'E', 'F', 'G']) enum = Enum(complexes, reactions) out = condense_graph(enum) resting_states = out['resting_state_map'] resting_state_targets = out['resting_state_targets'] reactions = out['condensed_reactions'] # Resting states: print "Resting states: " print str(resting_states) print resting_state_A = (RestingState('3', [ self.complexes['A'], self.complexes['B'], self.complexes['C'], self.complexes['D'] ]), ) resting_state_E = (RestingState( '4', [self.complexes['E'], self.complexes['F'], self.complexes['G']]), ) expected_resting_states = { frozenset([ self.complexes['B'], self.complexes['C'], self.complexes['D'], self.complexes['A'] ]): RestingState('3', [ self.complexes['A'], self.complexes['B'], self.complexes['C'], self.complexes['D'] ]), frozenset([ self.complexes['G'], self.complexes['E'], self.complexes['F'] ]): RestingState('4', [ self.complexes['E'], self.complexes['F'], self.complexes['G'] ]) } assert resting_states == expected_resting_states # Resting state targets: print "Resting state targets: " print_dict(resting_state_targets) print print "Expected resting state targets: " expected_resting_state_targets = { self.complexes['A']: SetOfFates([resting_state_A]), self.complexes['B']: SetOfFates([resting_state_A]), self.complexes['C']: SetOfFates([resting_state_A]), self.complexes['D']: SetOfFates([resting_state_A]), self.complexes['E']: SetOfFates([resting_state_E]), self.complexes['F']: SetOfFates([resting_state_E]), self.complexes['G']: SetOfFates([resting_state_E]), } print_dict(expected_resting_state_targets) print assert_dict_eq(resting_state_targets, expected_resting_state_targets) assert resting_state_targets == expected_resting_state_targets # Reactions: print "Reactions: " print_set(reactions, fmt=repr) print "Expected Reactions: " expected_reactions = set([ ReactionPathway('condensed', [resting_state_A[0], resting_state_A[0]], [resting_state_E[0]]) ]) print_set(expected_reactions, fmt=repr) print assert sorted(reactions) == sorted(expected_reactions)
def condense_graph(enumerator, compute_rates=True, k_fast=0.0): """ Condenses the reaction graph for the given `enumerator`. :param enumerator.Enumerator enumerator: The enumerator object to condense; `enumerate` must already have been called. :param bool compute_rates: True to compute rates for the condensed reactions; requires numpy and slows calculation. Returns an object of the following form:: { 'resting_states': list of resting states 'resting_state_map': dict mapping names to resting states, 'resting_state_targets': dict mapping each complex to its fate, 'condensed_reactions': list of ReactionPathway objects representing the condensed reactions, 'reactions': same as 'condensed_reactions' } """ # Approach: compute SCCs using Tarjan's algorithm, including only fast # 1-1 reactions as edges. For each complex in the SCC, compute the # set of _fates_ of that complex (a fate is a multiset of resting states # that are reachable from that complex by fast reactions). For each # detailed reaction, generate one condensed reaction for each combination # of the fates of the products. # # Detailed description: # A fate of a complex is the multiset of resting states that can be reached # from the complex by a series of fast reactions. For instance, in the # network A -> B -> C, C -> D, D -> E + F where all reactions are fast, # there are three resting states: ^D = { D }, ^E = { E }, ^F = { F }, and # the complex A has two fates: { ^D } and { ^E, ^F }. We define F(x) for # some complex x to be the _set_ of fates of x; therefore # F(A) = { { ^D }, { ^E, ^F } }. # # Relatedly, for some reaction r = (A, (b1, b2, ...)) where A is the set of # reactants and B is the set of products, let the fate of the reaction # R(r) = F(b1) [+] F(b2) [+] ..., where [+] is the cartesian sum. Finally, # let S(x) be the SCC containing complex x. Note that S(x) may or may not # be a resting state. # # With these definitions, we can calculate F(x) by a recursion; # # F(x) = { { S(x) } : if S(x) is a resting state # { U_{r in R_o} R(r) : if S(x) is not a resting state and R_o # represents the set of outgoing reactions # from S(x) # # Once we have calculated F(x) (which can be done by a DFS on the DAG formed # by the SCCs of the fast, detailed, 1-1 reactions---see the paper for # details on why this forms a DAG and therefore F(x) can be calculated in # finite time), the set of condensed reactions can be easily computed. We # generate one condensed reaction for each combination of fates of the # products. # # ------------------------------------------------------------------------ # F(x) : Stores mapping between a complex x and the set of its possible # fates complex_fates = {} # Stores mappings between an SCC and the associated resting state resting_states = {} # S(x) : Stores mappings between each complex x and the SCC S(x) which # contains x SCC_containing = {} # Remembers which SCCs we've processed (those SCCs for which we've # computed the fates) processed_SCCs = set() # The following dicts map SCCs to the various matrices used to # calculate the condensed reaction rates: # For resting state SCCs: # stationary_distributions[SCC] = s^ : maps complexes to stationary # probabilities stationary_distributions = {} # For transient SCCs: # exit_probabilities[SCC] = B : maps (incoming complex, outgoing reaction) tuples to exit # probabilities exit_probabilities = {} # decay_probabilities[(x,F)] = P( x decays to F ) = P( x -> F ), where F is a fate of # complex x decay_probabilities = collections.defaultdict( float) # default to zero if no entry # reaction_decay_probabilities[(r,F)] = P( products of r decay to F ) = P( r -> F ) # where F is a fate of reaction r reaction_decay_probabilities = collections.defaultdict( float) # default to zero if no entry # Define helper functions for calculating condensed reaction rates def get_stationary_distribution(scc): scc_set = frozenset(scc) scc_list = sorted(scc) L = len(scc) # assign a numerical index to each complex complex_indices = {c: i for (i, c) in enumerate(scc_list)} # find all reactions between complexes in this SCC (non-outgoing 1,1 # reactions) reactions = [ r for c in scc for r in reactions_consuming[c] if (r.arity == (1, 1) and not is_outgoing(r, scc_set)) ] # T is the transition rate matrix, defined as follows: # T_{ij} = { rate(j -> i) if i != j # { - sum_{j'} T_{j'i} if i == j T = np.zeros((L, L)) # add transition rates for each reaction to T for r in reactions: # r : a -> b # T_{b,a} = rate(r : a -> b) a = r.reactants[0] b = r.products[0] T[complex_indices[b]][complex_indices[a]] = r.rate() T0 = np.copy(T) # compute diagonal elements of T T_diag = np.sum(T, axis=0) # sum over columns for i in xrange(L): T[i][i] = -T_diag[i] # calculate eigenvalues (w, v) = np.linalg.eig(T) # w is array of eigenvalues # v is array of eigenvectors, where column v[:,i] is eigenvector # corresponding to the eigenvalue w[i]. # find eigenvector corresponding to eigenvalue zero (or nearly 0) epsilon = 1e-5 i = np.argmin(np.abs(w)) if abs(w[i]) > epsilon: logging.warn(( "Bad stationary distribution for resting state transition matrix. " + "Eigenvalue found %f has magnitude greater than epsilon = %f. " + "Markov chain may be periodic, or epsilon may be too high. Eigenvalues: %s" ) % (w(i), epsilon, str(w))) s = v[:, i] # check that the stationary distribution is good assert (s >= 0).all() or (s <= 0).all( ), "Stationary distribution of resting state complex should not be an eigenvector of mixed sign." s = s / np.sum(s) assert abs( np.sum(s) - 1 ) < epsilon, "Stationary distribution of resting state complex should sum to 1 after normalization" # return dict mapping complexes to stationary probabilities return {c: s[i] for (c, i) in complex_indices.iteritems()} def get_exit_probabilities(scc): # build set and list of elements in SCC; assign a numerical index to # each complex scc_set = frozenset(scc) scc_list = sorted(scc) complex_indices = {c: i for (i, c) in enumerate(scc_list)} # find all fast reactions involving complexes in this SCC reactions = [ r for c in scc for r in reactions_consuming[c] if is_fast(r) ] # sort reactions into internal and outgoing; assign a numerical index # to each reaction r_outgoing = sorted([r for r in reactions if is_outgoing(r, scc_set)]) r_internal = sorted( [r for r in reactions if not is_outgoing(r, scc_set)]) exit_indices = {r: i for (i, r) in enumerate(r_outgoing)} # L = # of complexes in SCC L = len(scc) # e = # of exit pathways e = len(r_outgoing) # add transition rates for each internal reaction T = np.zeros((L, L)) for r in r_internal: a = r.reactants[0] b = r.products[0] T[complex_indices[a]][complex_indices[b]] = r.rate() # add transition rates for each outgoing reaction Te = np.zeros((L, e)) for r in r_outgoing: a = r.reactants[0] Te[complex_indices[a]][exit_indices[r]] = r.rate() # the full transition matrix P_{L+e x L+e} would be # # P = ( Q_{LxL} R_{Lxe} ) # ( 0_{exL} I_{exe} ) # # but we really only care about Q to calculate the fundamental matrix, # so we construct # # P = (T_{LxL} Te_{Lxe}) P = np.hstack((T, Te)) # then normalize P along each row, to get the overall transition # probabilities, e.g. P_ij = P(i -> j), where i,j in 0...L+e P = P / np.sum(P, 1)[:, np.newaxis] # extract the interior transition probabilities (Q_{LxL}) Q = P[:, 0:L] # extract the exit probabilities (R_{Lxe}) R = P[:, L:L + e] # calculate the fundamental matrix (N = (I_L - Q)^-1) N = np.linalg.inv(np.eye(L) - Q) # make sure all elements of fundamental matrix are >= 0 assert (N >= 0).all() # --- commented out by EW (temporarily) # calculate the absorption matrix (B = NR) B = np.dot(N, R) # --- added by EW as a weaker surrugate for the above, when necessary assert (B >= 0).all() # return dict mapping tuples of (incoming complex, outgoing reaction) # to exit probabilities return {(c, r): B[i, j] for (c, i) in complex_indices.iteritems() for (r, j) in exit_indices.iteritems()} # Define helper functions def is_fast(reaction): """ Current heuristic to determine if reaction is fast: unimolecular in reactants AND rate constant > k_fast """ # return (reaction.arity == (1,1) or reaction.arity == (1,2)) and # reaction.rate() > k_fast return (reaction.arity[0] == 1) and reaction.rate() > k_fast def get_fates(complex): """ Returns the set of possible fates for this complex, calling compute_fates if necessary """ if (complex not in complex_fates): compute_fates(SCC_containing[complex]) return complex_fates[complex] def get_resting_state(complex): """ Returns the resting state associated with this complex, if one exists. """ scc = SCC_containing[complex] scc_set = frozenset(scc) if (scc_set not in resting_states): compute_fates(scc) return resting_states[scc_set] def calculate_reaction_decay_probabilities(r, fate, combinations=None): if combinations is None: combinations = cartesian_product(map(get_fates, r.products)) for fates in (combination for combination in combinations if tuple_sum(combination) == fate): # each combination (`fates`) that sums to `fate` # constitutes a possible way this reaction can # decay to `fate`, and therefore contributes to # P(r -> fate). This contribution is the joint # probability that each product `d` of r decays to # the corresponding fate `f` in `fates`. reaction_decay_probabilities[(r, fate)] += times( decay_probabilities[(d, f)] for (d, f) in zip(r.products, fates)) def compute_fates(scc): """ Processes a single SCC neighborhood, generating resting state multisets for each complex, and storing the mappings in the outer-scope `complex_fates` dict """ # Convert to a set for fast lookup scc_set = frozenset(scc) # Check that we haven't been here already if (scc_set in processed_SCCs): return outgoing_reactions = [ r for c in scc for r in reactions_consuming[c] if (is_fast(r) and is_outgoing(r, scc_set)) ] # Remember that we've processed this neighborhood processed_SCCs.add(scc_set) # If this SCC is a resting state: if (len(outgoing_reactions) == 0): # build new resting state resting_state = RestingState(get_auto_name(), scc) resting_states[scc_set] = resting_state # calculate stationary distribution if compute_rates: stationary_distributions[ resting_state] = get_stationary_distribution(scc) # assign fate to each complex in the SCC fate = (resting_state, ) fates = frozenset([fate]) for c in scc: if c in complex_fates: raise Exception() # frozenset([ (get_resting_state(c),) ]) complex_fates[c] = fates # all complexes in this SCC decay to this SCC with probability 1, # e.g. P(c -> SCC) = 1 for all c in this SCC decay_probabilities[(c, fate)] = 1.0 # Otherwise, if there are outgoing fast reactions: else: # Compute all possible combinations of the fates of each product of # r reaction_fate_combinations = [ cartesian_product(map(get_fates, r.products)) for r in outgoing_reactions ] # Compute the fates of each of the outgoing reactions by summing each element above # This is a list, with each element corresponding to the frozenset # of fates for one reaction. reaction_fates = [ sorted(map(tuple_sum_sort, combination)) for combination in reaction_fate_combinations ] # note that these two are equivalent; the intermediate reaction_fate_combinations is only # calculated for the sake of the rates assert reaction_fates == [ sorted(cartesian_sum(map(get_fates, r.products))) for r in outgoing_reactions ] if compute_rates: # calculate the exit probabilities exit_probabilities[scc_set] = get_exit_probabilities(scc) # calculate the probability of each fate # for each outgoing reaction, `r` for (i, r) in enumerate(outgoing_reactions): # for each possible `fate` of that reaction `r` for fate in reaction_fates[i]: fate = tuple(sorted(fate)) # calculate the probability that the products of `r` # decay to `fate`, e.g. P(r -> fate) # not necessary since using defaultdict # # initialize to zero # if (r,fate) not in reaction_decay_probabilities: # reaction_decay_probabilities[(r,fate)] = 0 # iterate over all combinations of the fates of `r` # that sum to equal `fate` combinations = reaction_fate_combinations[i] calculate_reaction_decay_probabilities( r, fate, combinations) # calculate_reaction_decay_probabilities(r,fate) # the decay probability will be different for different # complexes in the SCC for c in scc: # P(x decays to F) = P(SCC exits via r | SCC was # entered via c) * P(r decays to F) if (c, fate) not in decay_probabilities: decay_probabilities[(c, fate)] = 0 decay_probabilities[( c, fate)] += exit_probabilities[scc_set][ (c, r)] * reaction_decay_probabilities[ (r, fate)] # The set of fates for the complexes in this SCC is the union of # the fates for all outgoing reactions. # Note that frozenset().union(*X) === X[0] U X[1] U X[2] ... # where X[i] is a frozenset and a U b represents the union of # frozensets a and b set_of_fates = frozenset().union(*reaction_fates) for c in scc: if c in complex_fates: raise Exception() complex_fates[c] = set_of_fates # Cache some things for speed reactions_consuming = get_reactions_consuming(enumerator.complexes, enumerator.reactions) # Compute list of SCCs SCCs = tarjans(enumerator.complexes, enumerator.reactions, reactions_consuming, is_fast) # Map each complex to the SCC which contains the complex (each complex # should be in 1 SCC) SCC_containing.update({c: scc for scc in SCCs for c in scc}) # Generate resting state multisets by processing each SCC. Outer loop # guarantees that all SCCs will be processed even if DFS recursion # misses some for scc in SCCs: compute_fates(scc) # Map each complex to the set of multisets of resting states which it can # reach complex_fates = { complex: SetOfFates(complex_fates[complex]) for complex in complex_fates } # Generate condensed reactions condensed_reactions = set() for reaction in enumerator.reactions: # Filter reactions which are fast (these have been handled by the # fates) if (is_fast(reaction)): continue # Filter reactions with no products/reactants if (len(reaction.reactants) == 0) and (len(reaction.products) == 0): continue reactant_fates = map(get_fates, reaction.reactants) product_fates = map(get_fates, reaction.products) # Take cartesian sum of reactant fates new_reactant_combinations = cartesian_sum(reactant_fates) # Take cartesian sum of reaction product fates to get all # combinations of resting states new_product_combinations = cartesian_sum(product_fates) # make sure each reactant is a resting state (F(xn) is a singleton for # each reactant xn) for (i, f_xn) in enumerate(reactant_fates): if (not f_xn.is_singleton()): logging.error( "Cannot condense reaction %r: reactant %s has multiple fates: F(%s) = %s" % (reaction, str(reaction.reactants[i]), str(reaction.reactants[i]), str(f_xn))) raise Exception() # Now, we'll take all of the combinations of reactants and products new_reactant_product_combinations = it.product( new_reactant_combinations, new_product_combinations) # And generate new reactions with each combination which is not trivial # (reactants == products) for (reactants, products) in new_reactant_product_combinations: reactants = tuple(sorted(reactants)) products = tuple(sorted(products)) # Prune trivial reactions if (reactants != products): reaction = ReactionPathway('condensed', list(reactants), list(products)) if compute_rates: # calculate reaction rate by summing over all # representative detailed reactions detailed_reactions_consuming = set([ r for reactant in reactants for c in reactant.complexes for r in reactions_consuming[c] ]) reaction_rate = 0.0 for r in detailed_reactions_consuming: # calculate the probability that this detailed reaction decays to the products # P(r -> F), where F = products. Used in next step calculate_reaction_decay_probabilities(r, products) # probability that the products of this detailed reaction decay into fates # that yield the condensed reaction product_probability = reaction_decay_probabilities[( r, products)] assert product_probability >= 0 # probability that the resting states comprising the reactants of the condensed reaction will be in the right # configuration to perform the detailed reaction reactant_probabilities = times( stationary_distributions[resting_states[frozenset( SCC_containing[a])]][a] for a in r.reactants) assert reactant_probabilities >= 0 # rate of the detailed reaction k = r.rate() assert k >= 0 # overall contribution of detailed reaction r to rate of the condensed reaction ^r = # P(reactants of ^r are present as reactants of r) * # k_r * P(products of r decay to products of ^r) reaction_rate += reactant_probabilities * k * product_probability if isinstance(reaction_rate, complex): if reaction_rate.imag > 0: logging.warn(( "Detailed reaction %s contributes a complex rate of %f + %fj " + " to condensed reaction %s.") % (r, reaction_rate.real, reaction_rate.imag, reaction)) reaction._const = reaction_rate condensed_reactions.add(reaction) return { 'resting_states': resting_states.values(), 'resting_state_map': resting_states, 'resting_state_targets': complex_fates, 'condensed_reactions': list(condensed_reactions), 'reactions': list(condensed_reactions), 'complexes_to_resting_states': dict((c, rs) for rs in resting_states for c in rs) }
def load_json(filename): """ Loads a saved enumerator from a JSON output file at filename. """ fin = open(filename, 'r') saved = json.load(fin) saved_domains = saved['domains'] domains = {} for saved_domain in saved_domains: if 'sequence' not in saved_domain: saved_domain['sequence'] = None if (saved_domain['is_complement']): saved_domain['name'] = saved_domain['name'][:-1] new_dom = utils.Domain(saved_domain['name'], saved_domain['length'], is_complement=saved_domain['is_complement'], sequence=saved_domain['sequence']) domains[new_dom.name] = new_dom saved_strands = saved['strands'] strands = {} for saved_strand in saved_strands: doms = [] for domain in saved_strand['domains']: doms.append(domains[domain]) new_strand = utils.Strand(saved_strand['name'], doms) strands[saved_strand['name']] = new_strand complexes = {} resting_complexes = {} saved_resting_complexes = saved['resting_complexes'] for saved_complex in saved_resting_complexes: c_strands = [] for strand in saved_complex['strands']: c_strands.append(strands[strand]) new_structure = [] for strand in saved_complex['structure']: new_strand = [] for tup in strand: if (tup is None): new_strand.append(None) else: new_strand.append(tuple(tup)) new_structure.append(new_strand) new_complex = utils.Complex(saved_complex['name'], c_strands, new_structure) resting_complexes[saved_complex['name']] = new_complex complexes[saved_complex['name']] = new_complex transient_complexes = {} saved_transient_complexes = saved['transient_complexes'] for saved_complex in saved_transient_complexes: c_strands = [] for strand in saved_complex['strands']: c_strands.append(strands[strand]) new_structure = [] for strand in saved_complex['structure']: new_strand = [] for tup in strand: if (tup is None): new_strand.append(None) else: new_strand.append(tuple(tup)) new_structure.append(new_strand) new_complex = utils.Complex(saved_complex['name'], c_strands, new_structure) transient_complexes[saved_complex['name']] = new_complex complexes[saved_complex['name']] = new_complex saved_reactions = saved['reactions'] reactions = [] for saved_reaction in saved_reactions: reactants = [] for reactant in saved_reaction['reactants']: reactants.append(complexes[reactant]) products = [] for product in saved_reaction['products']: products.append(complexes[product]) reaction = ReactionPathway(saved_reaction['name'], reactants, products) reactions.append(reaction) resting_states = [] for resting_state in saved['resting_states']: comps = [] for complex in resting_state['complexes']: comps.append(complexes[complex]) resting_states.append(utils.RestingState(resting_state['name'], comps)) initial_complexes = {} for saved_complex in saved['initial_complexes']: c_strands = [] for strand in saved_complex['strands']: c_strands.append(strands[strand]) new_structure = [] for strand in saved_complex['structure']: new_strand = [] for tup in strand: if (tup is None): new_strand.append(None) else: new_strand.append(tuple(tup)) new_structure.append(new_strand) new_complex = utils.Complex(saved_complex['name'], c_strands, new_structure) initial_complexes[saved_complex['name']] = new_complex enumerator = peppercornenumerator.Enumerator(domains.values(), strands.values(), initial_complexes.values()) enumerator._complexes = complexes.values() enumerator._resting_states = resting_states enumerator._transient_complexes = transient_complexes.values() enumerator._resting_complexes = resting_complexes.values() enumerator._reactions = reactions return enumerator