Skip to content
Snippets Groups Projects
Commit 0e07bb48 authored by Peter Schubert's avatar Peter Schubert
Browse files

improving linear pathways

parent 5cef5ab0
No related branches found
No related tags found
No related merge requests found
""" """Implementation of LinearPathway Class.
Peter Schubert, October 2022 Peter Schubert, HHU Duesseldorf, October 2022
""" """
import numpy as np import numpy as np
from modelpruner.graph.pathway_segment import PathwaySegment from modelpruner.graph.pathway_segment import PathwaySegment
from modelpruner.graph.pathway_segment import ReactionLeg
class LinearPathway: class LinearPathway:
...@@ -21,11 +22,48 @@ class LinearPathway: ...@@ -21,11 +22,48 @@ class LinearPathway:
self.init_sid = init_sid self.init_sid = init_sid
self.balance_c2_sids = set(balance_c2_sids) self.balance_c2_sids = set(balance_c2_sids)
self.ng = network_graph self.ng = network_graph
self.net_reactants = {}
self.net_products = {}
# construct the linear pathway
self.segments = [self.get_initial_segment()] self.segments = [self.get_initial_segment()]
self.add_downstream_segments() self.add_downstream_segments()
self.add_upstream_segments() self.add_upstream_segments()
# collect pathway data
self.sids = []
self.rids = [self.segments[0].producing.rid]
self.reversible = self.segments[0].reversible
scale = 1.0
direction = 1.0 if self.segments[0].producing.forward is True else -1.0
self.rids_scale = {self.segments[0].producing.rid: scale * direction}
for pwy_seg in self.segments:
self.sids.append(pwy_seg.sid)
self.rids.append(pwy_seg.consuming.rid)
scale *= pwy_seg.producing.stoic / pwy_seg.consuming.stoic
direction = 1.0 if pwy_seg.consuming.forward is True else -1.0
self.rids_scale[pwy_seg.consuming.rid] = scale * direction
net_stoic = self.get_net_stoichiometry()
self.net_reactants = {sid: -stoic for sid, stoic in net_stoic.items() if stoic < 0.0}
self.net_products = {sid: stoic for sid, stoic in net_stoic.items() if stoic > 0.0}
def get_net_stoichiometry(self):
substrates = set()
for rid in self.rids_scale:
substrates |= set(self.ng.edges[rid].reactants)
substrates |= set(self.ng.edges[rid].products)
net_stoic = {substrate: 0.0 for substrate in substrates}
for rid, scale in self.rids_scale.items():
for sid, stoic in self.ng.edges[rid].reactants.items():
net_stoic[sid] -= stoic * scale
for sid, stoic in self.ng.edges[rid].products.items():
net_stoic[sid] += stoic * scale
return net_stoic
def get_initial_segment(self): def get_initial_segment(self):
"""get initial pathway segment for inital node. """get initial pathway segment for inital node.
...@@ -37,64 +75,56 @@ class LinearPathway: ...@@ -37,64 +75,56 @@ class LinearPathway:
:rtype: PathwaySegment :rtype: PathwaySegment
""" """
self.balance_c2_sids.remove(self.init_sid) self.balance_c2_sids.remove(self.init_sid)
connectivity = self.get_us_ds_neighbors(self.init_sid) connectivity = self.get_us_ds_neighbors(self.init_sid)
pwy_seg = PathwaySegment(connectivity['sid']) return PathwaySegment(connectivity)
pwy_seg.set_upstream(connectivity['p_rid'], connectivity['nb_us'])
pwy_seg.set_downstream(connectivity['c_rid'], connectivity['nb_ds'])
pwy_seg.set_reversible(connectivity['reversible'])
return pwy_seg
def add_downstream_segments(self): def add_downstream_segments(self):
"""add downstream segments """ """add downstream segments """
lcount = 1 lcount = 1
upstream_seg = self.segments[-1] upstream_seg = self.segments[-1]
while upstream_seg.nb_ds is not None and lcount < 50: while ((upstream_seg.consuming is not None) and
(upstream_seg.consuming.nb is not None) and
(lcount < 50)):
lcount += 1 lcount += 1
pwy_rev = upstream_seg.reversible pwy_rev = upstream_seg.reversible
self.balance_c2_sids.remove(upstream_seg.nb_ds) self.balance_c2_sids.remove(upstream_seg.consuming.nb)
connectivity = self.get_ds_neighbor(upstream_seg) connectivity = self.get_ds_neighbor(upstream_seg)
pwy_seg = PathwaySegment(connectivity)
if 'c_rid' in connectivity:
pwy_seg = PathwaySegment(connectivity['sid'])
pwy_seg.set_upstream(upstream_seg.c_rid, upstream_seg.sid)
pwy_seg.set_downstream(connectivity['c_rid'], connectivity['nb_ds'])
pwy_seg.set_reversible(pwy_rev and connectivity['reversible']) pwy_seg.set_reversible(pwy_rev and connectivity['reversible'])
self.segments.append(pwy_seg) self.segments.append(pwy_seg)
if connectivity['nb_ds'] is not None: if connectivity['consuming'] is None:
# treat a reversible pathway that becomes directional (i.e. continue ds) # termination of the pathway at a c1 metabolite (blocked node)
if (pwy_rev is True) and (connectivity['reversible'] is False): break
if connectivity['consuming'].nb is None:
# termination of the pathway with connectivity towards the network
if pwy_rev is True:
if connectivity['reversible'] is False:
# irreversible termination in a reversible pathway
for pwy_seg in self.segments: for pwy_seg in self.segments:
pwy_seg.set_reversible(False) pwy_seg.set_reversible(False)
# in other cases, just continue ds
# cases of path termination
else: else:
# pathway termination of a reversible pathway (reverse pwy_segs and continue ds) # reversible termination in a reversible pathway
if (pwy_rev is True) and (connectivity['reversible'] is True):
for pwy_seg in self.segments: for pwy_seg in self.segments:
pwy_seg.set_reversible(False)
pwy_seg.reverse_direction() pwy_seg.reverse_direction()
self.segments.reverse() self.segments.reverse()
# in other cases, the path termination terminates the while loop
# i.e. no consuming node, but only one producing node if connectivity['consuming'].nb is not upstream_seg.sid:
# we reverse tha pwy_segments and their direction and continue ds # downstream connectivity to a c2 node
if (pwy_rev is True) and (connectivity['reversible'] is False):
for pwy_seg in self.segments:
pwy_seg.set_reversible(False)
elif pwy_rev is True: elif pwy_rev is True:
assert 'p_rid' in connectivity # pathway reversal situation
assert connectivity['reversible'] is False assert connectivity['reversible'] is False
pwy_seg = PathwaySegment(connectivity['sid']) for pwy_seg in self.segments[:-1]:
pwy_seg.set_upstream(upstream_seg.c_rid, upstream_seg.sid)
pwy_seg.set_downstream(connectivity['p_rid'], connectivity['nb_us'])
self.segments.append(pwy_seg)
for pwy_seg in self.segments:
pwy_seg.set_reversible(False) pwy_seg.set_reversible(False)
pwy_seg.reverse_direction() pwy_seg.reverse_direction()
self.segments.reverse() self.segments.reverse()
else: else:
# conditions when directional pathway has producing reaction print(f'inconsistent configuration at {pwy_seg.sid}, starting from {self.init_sid}')
print(f'inconsistent configuration, starting at {self.init_sid}')
break break
upstream_seg = self.segments[-1] upstream_seg = self.segments[-1]
...@@ -102,37 +132,46 @@ class LinearPathway: ...@@ -102,37 +132,46 @@ class LinearPathway:
lcount = 1 lcount = 1
downstream_seg = self.segments[0] downstream_seg = self.segments[0]
while downstream_seg.nb_us in self.balance_c2_sids and lcount < 50: while ((downstream_seg.producing is not None) and
(downstream_seg.producing.nb in self.balance_c2_sids) and
(lcount < 50)):
lcount += 1 lcount += 1
pwy_rev = downstream_seg.reversible pwy_rev = downstream_seg.reversible
self.balance_c2_sids.remove(downstream_seg.nb_us) self.balance_c2_sids.remove(downstream_seg.producing.nb)
connectivity = self.get_us_neighbor(downstream_seg) connectivity = self.get_us_neighbor(downstream_seg)
pwy_seg = PathwaySegment(connectivity)
if 'p_rid' in connectivity:
pwy_seg = PathwaySegment(connectivity['sid'])
pwy_seg.set_downstream(downstream_seg.p_rid, downstream_seg.sid)
pwy_seg.set_upstream(connectivity['p_rid'], connectivity['nb_us'])
pwy_seg.set_reversible(pwy_rev and connectivity['reversible']) pwy_seg.set_reversible(pwy_rev and connectivity['reversible'])
self.segments.insert(0, pwy_seg) self.segments.insert(0, pwy_seg)
if (connectivity['reversible'] is False) and (pwy_rev is True):
if connectivity['producing'] is None:
# termination of the pathway at a c1 metabolite (blocked node)
break
if connectivity['producing'].nb is None:
# termination of the pathway with connectivity towards the network
if (pwy_rev is True) and (connectivity['reversible'] is False):
for pwy_seg in self.segments:
pwy_seg.set_reversible(False)
break
if connectivity['producing'].nb is not downstream_seg.sid:
# upstream connectivity to a c2 node
if (pwy_rev is True) and (connectivity['reversible'] is False):
for pwy_seg in self.segments: for pwy_seg in self.segments:
pwy_seg.set_reversible(False) pwy_seg.set_reversible(False)
elif pwy_rev is True: elif pwy_rev is True:
assert 'c_rid' in connectivity # pathway reversal situation
assert connectivity['reversible'] is False assert connectivity['reversible'] is False
pwy_seg = PathwaySegment(connectivity['sid']) for pwy_seg in self.segments[:-1]:
pwy_seg.set_downstream(downstream_seg.c_rid, downstream_seg.sid) pwy_seg.set_reversible(False)
pwy_seg.set_upstream(connectivity['c_rid'], connectivity['nb_ds'])
self.segments.insert(0, pwy_seg)
for pwy_seg in self.segments:
pwy_seg.reverse_direction() pwy_seg.reverse_direction()
self.segments.reverse() self.segments.reverse()
else: else:
print(f'inconsistent configuration, starting at {self.init_sid}') print(f'inconsistent configuration at {pwy_seg.sid}, starting from {self.init_sid}')
break break
downstream_seg = self.segments[0] downstream_seg = self.segments[0]
def get_neighbor_node(self, substrate_sids): def get_neighbor_node(self, substrates):
"""Get a c2 node from reactants/product sids """Get a c2 node from reactants/product sids
If none of the reactant/products is a c2 node, If none of the reactant/products is a c2 node,
...@@ -141,12 +180,12 @@ class LinearPathway: ...@@ -141,12 +180,12 @@ class LinearPathway:
In case of several c2 nodes, return the first node In case of several c2 nodes, return the first node
after sorting. after sorting.
:param substrate_sids: metabolite ids (reactants or products) :param substrates: substrates with stoichiometry
:type substrate_sids: list-like of str :type substrates: dict (key: sid - str, val: stoichiometry - float)
:return: metabolite id (sid) for neighbor node :return: metabolite id (sid) for neighbor node
:rtype: str or None :rtype: str or None
""" """
nbs = substrate_sids.intersection(self.balance_c2_sids) nbs = set(substrates.keys()).intersection(self.balance_c2_sids)
if len(nbs) == 0: if len(nbs) == 0:
nb = None nb = None
else: else:
...@@ -164,49 +203,57 @@ class LinearPathway: ...@@ -164,49 +203,57 @@ class LinearPathway:
cur_node = self.ng.nodes[init_sid] cur_node = self.ng.nodes[init_sid]
rids = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids)] rids = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids)]
revs = [rev for rev in (cur_node.consuming_rids | cur_node.producing_rids).values()] revs = [val['rev'] for val in (cur_node.consuming_rids | cur_node.producing_rids).values()]
rev = bool(np.all(np.array(revs))) rev = bool(np.all(np.array(revs)))
p_rid = None producing_leg = None
c_rid = None consuming_leg = None
nb_us = None
nb_ds = None
if cur_node.connectivity == 1: if cur_node.connectivity == 1:
# c1 nodes are actually blocked nodes where a pathway ends # c1 nodes are actually blocked nodes where a pathway ends
rid = rids[0] rid = rids[0]
if rid in cur_node.consuming_rids: if rid in cur_node.consuming_rids:
c_rid = rid stoic = cur_node.consuming_rids[rid]['stoic']
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products) nb = self.get_neighbor_node(self.ng.edges[rid].products)
consuming_leg = ReactionLeg(rid, stoic, nb, True)
else: else:
p_rid = rid stoic = cur_node.producing_rids[rid]['stoic']
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants) nb = self.get_neighbor_node(self.ng.edges[rid].reactants)
if rev is False:
# in case of a reversible reaction, configure consuming path producing_leg = ReactionLeg(rid, stoic, nb, True)
if (rev is True) and (c_rid is None): else:
c_rid = p_rid # in case of a reversible producing reaction, configure consuming path
nb_ds = nb_us consuming_leg = ReactionLeg(rid, stoic, nb, False)
p_rid = None
nb_us = None
if cur_node.connectivity == 2: if cur_node.connectivity == 2:
if rev is True: if rev is True:
# process a node with reversible fluxes on both sides # process a node with reversible fluxes on both sides
# one being considered as producing, one as consuming reaction # one being considered as producing, one as consuming reaction
nb_sids = [None, None] nbs = [None, None]
for idx, rid in enumerate(rids): stoics = [0.0, 0.0]
if rid in cur_node.consuming_rids: fwds = [True, True]
nb_sids[idx] = self.get_neighbor_node(self.ng.edges[rid].products)
# arbitrarily select first reaction as producing and second as consuming
if rids[0] in cur_node.consuming_rids:
fwds[0] = False
if rids[1] in cur_node.producing_rids:
fwds[1] = False
for idx in range(2):
if rids[idx] in cur_node.consuming_rids:
stoics[idx] = cur_node.consuming_rids[rids[idx]]['stoic']
nbs[idx] = self.get_neighbor_node(self.ng.edges[rids[idx]].products)
else: else:
nb_sids[idx] = self.get_neighbor_node(self.ng.edges[rid].reactants) stoics[idx] = cur_node.producing_rids[rids[idx]]['stoic']
nbs[idx] = self.get_neighbor_node(self.ng.edges[rids[idx]].reactants)
# if one of the reversible reactions connects to network, let it be the producing side # preferably the producing leg connects to the network (nb = None)
if nb_sids[0] is None: if nbs[0] is None:
p_rid, c_rid = rids producing_leg = ReactionLeg(rids[0], stoics[0], nbs[0], fwds[0])
nb_us, nb_ds = nb_sids consuming_leg = ReactionLeg(rids[1], stoics[1], nbs[1], fwds[1])
else: else:
c_rid, p_rid = rids producing_leg = ReactionLeg(rids[1], stoics[1], nbs[1], not fwds[1])
nb_ds, nb_us = nb_sids consuming_leg = ReactionLeg(rids[0], stoics[0], nbs[0], not fwds[0])
# get neighbors of a node in a directional pathway segment # get neighbors of a node in a directional pathway segment
else: else:
...@@ -221,29 +268,44 @@ class LinearPathway: ...@@ -221,29 +268,44 @@ class LinearPathway:
if drid in cur_node.consuming_rids: if drid in cur_node.consuming_rids:
c_rid = drid c_rid = drid
p_rid = orid p_rid = orid
c_stoic = cur_node.consuming_rids[c_rid]['stoic']
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products) nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products)
c_fwd = True
# the 'producing' reaction can be a producing or a reversible consuming reaction
if p_rid in cur_node.producing_rids: if p_rid in cur_node.producing_rids:
p_stoic = cur_node.producing_rids[p_rid]['stoic']
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants) nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants)
# in case the 2nd reaction is also a consuming reaction, it must be reversible p_fwd = True
else: else:
assert cur_node.consuming_rids[p_rid] is True # case of a consuming reaction run in reverse
assert cur_node.consuming_rids[p_rid]['rev'] is True
p_stoic = cur_node.consuming_rids[p_rid]['stoic']
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].products) nb_us = self.get_neighbor_node(self.ng.edges[p_rid].products)
p_fwd = False
# case, where the selected directional reaction is a producing reaction # case, where the selected directional reaction is a producing reaction
else: else:
p_rid = drid p_rid = drid
c_rid = orid c_rid = orid
p_stoic = cur_node.producing_rids[p_rid]['stoic']
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants) nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants)
p_fwd = True
if c_rid in cur_node.consuming_rids: if c_rid in cur_node.consuming_rids:
c_stoic = cur_node.consuming_rids[c_rid]['stoic']
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products) nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products)
c_fwd = True
else: else:
assert cur_node.producing_rids[c_rid] is True assert cur_node.producing_rids[c_rid]['rev'] is True
c_stoic = cur_node.producing_rids[c_rid]['stoic']
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].reactants) nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].reactants)
c_fwd = False
return {'sid': init_sid, 'p_rid': p_rid, 'nb_us': nb_us, producing_leg = ReactionLeg(p_rid, p_stoic, nb_us, p_fwd)
'c_rid': c_rid, 'nb_ds': nb_ds, 'reversible': rev} consuming_leg = ReactionLeg(c_rid, c_stoic, nb_ds, c_fwd)
return {'sid': init_sid, 'producing': producing_leg, 'consuming': consuming_leg, 'reversible': rev}
def get_ds_neighbor(self, us_pwy_seg): def get_ds_neighbor(self, us_pwy_seg):
"""get downstream neighbor at us_sid coming from p_rid. """get downstream neighbor at us_sid coming from p_rid.
...@@ -263,51 +325,92 @@ class LinearPathway: ...@@ -263,51 +325,92 @@ class LinearPathway:
the upstream pathway is reversible (leading to a directional pathway the upstream pathway is reversible (leading to a directional pathway
in opposite direction) in opposite direction)
""" """
sid = us_pwy_seg.nb_ds sid = us_pwy_seg.consuming.nb
cur_node = self.ng.nodes[sid] cur_node = self.ng.nodes[sid]
revs = [rev for rev in (cur_node.consuming_rids | cur_node.producing_rids).values()] revs = [val['rev'] for val in (cur_node.consuming_rids | cur_node.producing_rids).values()]
rev = bool(np.all(np.array(revs))) rev = bool(np.all(np.array(revs)))
c_rid = None producing_leg = None
nb_ds = None consuming_leg = None
if cur_node.connectivity == 2: # collect information for upstream leg
p_rid = us_pwy_seg.consuming.rid
us_nb = us_pwy_seg.sid
if p_rid in cur_node.producing_rids:
p_stoic = cur_node.producing_rids[p_rid]['stoic']
p_fwd = True
else:
assert cur_node.consuming_rids[p_rid]['rev'] is True
p_stoic = cur_node.consuming_rids[p_rid]['stoic']
p_fwd = False
p_rid = us_pwy_seg.c_rid if cur_node.connectivity == 1:
c_rid = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids) if rid != p_rid][0] producing_leg = ReactionLeg(p_rid, p_stoic, us_nb, p_fwd)
if cur_node.connectivity == 2:
c_rid = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids) if rid != p_rid][0]
if c_rid in cur_node.consuming_rids: if c_rid in cur_node.consuming_rids:
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].products) c_stoic = cur_node.consuming_rids[c_rid]['stoic']
ds_nb = self.get_neighbor_node(self.ng.edges[c_rid].products)
c_fwd = True
else: else:
nb_ds = self.get_neighbor_node(self.ng.edges[c_rid].reactants) # a 'producing' reaction consuming the metabolite
if cur_node.producing_rids[c_rid] is False: c_stoic = cur_node.producing_rids[c_rid]['stoic']
# flow reversal in case the producing reaction is directional ds_nb = self.get_neighbor_node(self.ng.edges[c_rid].reactants)
return {'sid': sid, 'p_rid': c_rid, 'nb_us': nb_ds, 'reversible': rev} c_fwd = False
# check for reversal in case the producing reaction is directional
if (c_rid in cur_node.consuming_rids) or (cur_node.producing_rids[c_rid]['rev'] is True):
producing_leg = ReactionLeg(p_rid, p_stoic, us_nb, p_fwd)
consuming_leg = ReactionLeg(c_rid, c_stoic, ds_nb, c_fwd)
else:
producing_leg = ReactionLeg(c_rid, c_stoic, ds_nb, not c_fwd)
consuming_leg = ReactionLeg(p_rid, p_stoic, us_nb, not p_fwd)
return {'sid': sid, 'c_rid': c_rid, 'nb_ds': nb_ds, 'reversible': rev} return {'sid': sid, 'producing': producing_leg, 'consuming': consuming_leg, 'reversible': rev}
def get_us_neighbor(self, ds_pwy_seg): def get_us_neighbor(self, ds_pwy_seg):
sid = ds_pwy_seg.nb_us sid = ds_pwy_seg.producing.nb
cur_node = self.ng.nodes[sid] cur_node = self.ng.nodes[sid]
revs = [rev for rev in (cur_node.consuming_rids | cur_node.producing_rids).values()] revs = [val['rev'] for val in (cur_node.consuming_rids | cur_node.producing_rids).values()]
rev = bool(np.all(np.array(revs))) rev = bool(np.all(np.array(revs)))
p_rid = None producing_leg = None
nb_us = None consuming_leg = None
if cur_node.connectivity == 2: # collect information for downstream leg
c_rid = ds_pwy_seg.producing.rid
ds_nb = ds_pwy_seg.sid
if c_rid in cur_node.consuming_rids:
c_stoic = cur_node.consuming_rids[c_rid]['stoic']
c_fwd = True
else:
assert cur_node.producing_rids[c_rid]['rev'] is True, f'{sid},{c_rid}'
c_stoic = cur_node.producing_rids[c_rid]['stoic']
c_fwd = False
c_rid = ds_pwy_seg.p_rid if cur_node.connectivity == 1:
p_rid = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids) if rid != c_rid][0] consuming_leg = ReactionLeg(c_rid, c_stoic, ds_nb, c_fwd)
if cur_node.connectivity == 2:
p_rid = [rid for rid in (cur_node.consuming_rids | cur_node.producing_rids) if rid != c_rid][0]
if p_rid in cur_node.producing_rids: if p_rid in cur_node.producing_rids:
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].reactants) p_stoic = cur_node.producing_rids[p_rid]['stoic']
us_nb = self.get_neighbor_node(self.ng.edges[p_rid].reactants)
p_fwd = True
else: else:
nb_us = self.get_neighbor_node(self.ng.edges[p_rid].products) p_stoic = cur_node.consuming_rids[p_rid]['stoic']
if cur_node.consuming_rids[p_rid] is False: us_nb = self.get_neighbor_node(self.ng.edges[p_rid].products)
# flow reversal in case the consuming reaction is directional p_fwd = False
return {'sid': sid, 'c_rid': p_rid, 'nb_ds': nb_us, 'reversible': rev}
# check for reversal in case the producing reaction is directional
if (p_rid in cur_node.producing_rids) or (cur_node.consuming_rids[p_rid]['rev'] is True):
producing_leg = ReactionLeg(p_rid, p_stoic, us_nb, p_fwd)
consuming_leg = ReactionLeg(c_rid, c_stoic, ds_nb, c_fwd)
else:
producing_leg = ReactionLeg(c_rid, c_stoic, ds_nb, not c_fwd)
consuming_leg = ReactionLeg(p_rid, p_stoic, us_nb, not p_fwd)
return {'sid': sid, 'p_rid': p_rid, 'nb_us': nb_us, 'reversible': rev} return {'sid': sid, 'producing': producing_leg, 'consuming': consuming_leg, 'reversible': rev}
def get_reactions(self): def get_reactions(self):
......
""" """Implementation of LinearPathways Class.
Peter Schubert, October 2022 Peter Schubert, HHU Duesseldorf, October 2022
""" """
from modelpruner.graph.linear_pathway import LinearPathway from modelpruner.graph.linear_pathway import LinearPathway
...@@ -8,7 +8,14 @@ from modelpruner.graph.linear_pathway import LinearPathway ...@@ -8,7 +8,14 @@ from modelpruner.graph.linear_pathway import LinearPathway
class LinearPathways: class LinearPathways:
def __init__(self, network_graph, biomass_rid=None): # TODO: rework again to first get lin_pathways from each of the nodes (excluding biomass precursors)
# idea to get as long pathways as possible
# sort for longest pathways
# remove duplicated pathways
# accept longest pathways first and drop pathways that contain already consumed reactions
# check if c2_nodes remain, and for these request again pathways
def __init__(self, network_graph, protected_rids=None):
""" """
Construct linear pathways from metabolites with a connectivity of 2 Construct linear pathways from metabolites with a connectivity of 2
...@@ -19,12 +26,17 @@ class LinearPathways: ...@@ -19,12 +26,17 @@ class LinearPathways:
self.ng = network_graph self.ng = network_graph
if biomass_rid in self.ng.edges: # TODO: biomass protected, external metabolites protected
biomass_sids = self.ng.edges[biomass_rid].reactants | self.ng.edges[biomass_rid].products # metabolites connected to protected reactions get protected
else: protected_sids = set()
biomass_sids = set() if protected_rids is not None:
self.c2_sids = {node.id: None for node in self.ng.nodes.values() for rid in protected_rids:
if node.id not in biomass_sids and node.is_c2_node()} if rid in self.ng.edges:
protected_sids |= set(self.ng.edges[rid].reactants.keys())
protected_sids |= set(self.ng.edges[rid].products.keys())
self.c2_sids = {node.sid: None for node in self.ng.nodes.values()
if node.sid not in protected_sids and node.is_c2_node()}
self.pwy_rids = {} self.pwy_rids = {}
self.pathways = [] self.pathways = []
...@@ -45,8 +57,8 @@ class LinearPathways: ...@@ -45,8 +57,8 @@ class LinearPathways:
def pwy_substrates(self, lin_pwy): def pwy_substrates(self, lin_pwy):
sids = set() sids = set()
for pwy_seg in lin_pwy.segments: for pwy_seg in lin_pwy.segments:
sids |= set(self.ng.edges[pwy_seg.p_rid].reactants) sids |= set(self.ng.edges[pwy_seg.producing.rid].reactants.keys())
sids |= set(self.ng.edges[pwy_seg.p_rid].products) sids |= set(self.ng.edges[pwy_seg.producing.rid].products.keys())
sids |= set(self.ng.edges[pwy_seg.c_rid].reactants) sids |= set(self.ng.edges[pwy_seg.consuming.rid].reactants.keys())
sids |= set(self.ng.edges[pwy_seg.c_rid].products) sids |= set(self.ng.edges[pwy_seg.consuming.rid].products.keys())
return sids return sids
"""Implementation of MetaboliteNode Class.
Peter Schubert, HHU Duesseldorf, October 2022
"""
import numpy as np import numpy as np
...@@ -5,17 +9,17 @@ import numpy as np ...@@ -5,17 +9,17 @@ import numpy as np
class MetaboliteNode: class MetaboliteNode:
def __init__(self, sid): def __init__(self, sid):
self.id = sid self.sid = sid
self.producing_rids = {} self.producing_rids = {}
self.consuming_rids = {} self.consuming_rids = {}
self.connectivity = 0 self.connectivity = 0
def add_producing_rid(self, rid, reversible): def add_producing_rid(self, rid, stoic, reversible):
self.producing_rids[rid] = reversible self.producing_rids[rid] = {'stoic': stoic, 'rev': reversible}
self.connectivity = len(self.producing_rids) + len(self.consuming_rids) self.connectivity = len(self.producing_rids) + len(self.consuming_rids)
def add_consuming_rid(self, rid, reversible): def add_consuming_rid(self, rid, stoic, reversible):
self.consuming_rids[rid] = reversible self.consuming_rids[rid] = {'stoic': stoic, 'rev': reversible}
self.connectivity = len(self.producing_rids) + len(self.consuming_rids) self.connectivity = len(self.producing_rids) + len(self.consuming_rids)
def is_c2_node(self): def is_c2_node(self):
...@@ -23,10 +27,10 @@ class MetaboliteNode: ...@@ -23,10 +27,10 @@ class MetaboliteNode:
if len(self.producing_rids) == 1 and len(self.consuming_rids) == 1: if len(self.producing_rids) == 1 and len(self.consuming_rids) == 1:
return True return True
if len(self.producing_rids) == 2: if len(self.producing_rids) == 2:
if bool(np.any(np.array([val for val in self.producing_rids.values()]))) is True: if bool(np.any(np.array([val['rev'] for val in self.producing_rids.values()]))) is True:
return True return True
if len(self.consuming_rids) == 2: if len(self.consuming_rids) == 2:
if bool(np.any(np.array([val for val in self.consuming_rids.values()]))) is True: if bool(np.any(np.array([val['rev'] for val in self.consuming_rids.values()]))) is True:
return True return True
elif self.connectivity == 1: # blocked metabolite elif self.connectivity == 1: # blocked metabolite
return True return True
......
""" """Implementation of NetworkGraph Class.
Peter Schubert, October 2022 Peter Schubert, HHU Duesseldorf, October 2022
""" """
from modelpruner.graph.metabolite_node import MetaboliteNode from modelpruner.graph.metabolite_node import MetaboliteNode
...@@ -25,8 +25,8 @@ class NetworkGraph: ...@@ -25,8 +25,8 @@ class NetworkGraph:
rid = fba_model.rids[col] rid = fba_model.rids[col]
reversible = bool(fba_model.reversible[col]) reversible = bool(fba_model.reversible[col])
if stoic > 0: if stoic > 0:
self.edges[rid].add_product(sid) self.edges[rid].add_product(sid, stoic)
self.nodes[sid].add_producing_rid(rid, reversible) self.nodes[sid].add_producing_rid(rid, stoic, reversible)
else: else:
self.edges[rid].add_reactant(sid) self.edges[rid].add_reactant(sid, -stoic)
self.nodes[sid].add_consuming_rid(rid, reversible) self.nodes[sid].add_consuming_rid(rid, -stoic, reversible)
"""Implementation of PathwaySegment Class.
Peter Schubert, HHU Duesseldorf, October 2022
""" """
Peter Schubert, October 2022 class ReactionLeg:
"""
def __init__(self, rid, stoic, nb, forward):
self.rid = rid
self.stoic = stoic
self.nb = nb
self.forward = forward
class PathwaySegment: class PathwaySegment:
def __init__(self, sid): def __init__(self, connectivity):
self.sid = sid self.sid = connectivity['sid']
self.reversible = False self.reversible = connectivity.get('reversible', False)
self.p_rid = None self.producing = connectivity.get('producing')
self.nb_us = None self.consuming = connectivity.get('consuming')
self.c_rid = None
self.nb_ds = None
def set_upstream(self, rid, sid): def set_producing(self, reaction_leg):
self.p_rid = rid """Set the producing reaction leg.
self.nb_us = sid
def set_downstream(self, rid, sid): A reversible 'consuming' reaction can be a producing reaction
self.c_rid = rid
self.nb_ds = sid :param reaction_leg: producing reaction leg with stoic,
:type reaction_leg: ReactionLeg
"""
self.producing = reaction_leg
def set_consuming(self, reaction_leg):
self.consuming = reaction_leg
def set_reversible(self, reversible): def set_reversible(self, reversible):
"""Set reversible flag of pathway segment. """Set reversible flag of pathway segment.
...@@ -37,12 +48,12 @@ class PathwaySegment: ...@@ -37,12 +48,12 @@ class PathwaySegment:
self.reversible = reversible self.reversible = reversible
def reverse_direction(self): def reverse_direction(self):
temp_rid = self.p_rid
temp_sid = self.nb_us tmp_leg = self.producing
self.p_rid = self.c_rid self.producing = self.consuming
self.nb_us = self.nb_ds self.consuming = tmp_leg
self.c_rid = temp_rid self.producing.forward = not self.producing.forward
self.nb_ds = temp_sid self.consuming.forward = not self.consuming.forward
def get_reactions(self): def get_reactions(self):
"""Get the reactions connecting the pathway segments """Get the reactions connecting the pathway segments
...@@ -51,8 +62,8 @@ class PathwaySegment: ...@@ -51,8 +62,8 @@ class PathwaySegment:
:rtype: set of str :rtype: set of str
""" """
rids = set() rids = set()
if self.p_rid is not None: if type(self.producing) is ReactionLeg:
rids.add(self.p_rid) rids.add(self.producing.rid)
if self.c_rid is not None: if type(self.consuming) is ReactionLeg:
rids.add(self.c_rid) rids.add(self.consuming.rid)
return rids return rids
"""Implementation of ReactionEdge Class.
Peter Schubert, HHU Duesseldorf, October 2022
"""
class ReactionEdge: class ReactionEdge:
def __init__(self, rid, reversible): def __init__(self, rid, reversible):
self.id = rid self.rid = rid
self.reversible = reversible self.reversible = reversible
self.reactants = set() self.reactants = {}
self.products = set() self.products = {}
def add_product(self, sid): def add_product(self, sid, stoic):
self.products.add(sid) self.products[sid] = stoic
def add_reactant(self, sid): def add_reactant(self, sid, stoic):
self.reactants.add(sid) self.reactants[sid] = stoic
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment