#!/usr/bin/env python
# -*- coding: utf-8 -*-
from padmet.classes.policy import Policy
from padmet.classes.node import Node
from padmet.classes.relation import Relation
from padmet.classes.padmetRef import PadmetRef
from padmet.utils import sbmlPlugin
import libsbml
import os
__all__ = ["PadmetSpec"]
# pylint: disable=superfluous-parens
[docs]
class PadmetSpec:
"""
PadmetSpec is an object representing the metabolic network of a species(organism)
based on a reference database PadmetRef.
contains <Policy>, <Node> and <Relation>
The policy defines the way Node and Relation are associated
A node is an Object that contains information about an element of the network
(can be a pathway, reaction...).
A relation defines how two nodes are connected. In a relation there is
a node "in" and a node "out". (reactionX'in' consumes metaboliteX'out')
PadmetSpec contains 3 attributes:
dicOfNode: a dictionary of node: key=Node's unique id / value = <Node>
dicOfRelationIn: a dictionary of relation with: key= nodeIN id / value = list of <relation>
dicOfRelationOut: a dictionary of relation with: key= nodeOut id / value = list of <relation>
policy: a <policy>
info: a dictionary of informations about the network, the database used...
This dictionary is always represented in the header of a padmet file
"""
def __init__(self, padmetSpec_file=None):
"""
if None, initializes an empty <PadmetSpec>
Parameters
----------
padmetSpec_file: str
pathname of the padmet file
"""
if padmetSpec_file is not None:
if not os.path.exists(padmetSpec_file):
raise FileNotFoundError("No Padmet Spec file accessible at " + padmetSpec_file)
self.loadGraph(padmetSpec_file)
else:
self.dicOfRelationIn = {}
self.dicOfRelationOut = {}
self.dicOfNode = {}
self.policy = Policy()
self.info = {}
# ==============================================================================
# Constructor / getter
# ==============================================================================
[docs]
def setInfo(self, source):
"""
All the information printed in the header of a padmet stocked in a dict.
{"metacyc":{version:XX,...},"ecocyc":{...}...}
set Info from a dictionary or copying from an other padmet
Parameters
----------
source: dict or padmet.classes.PadmetRef/PadmetSpec
may be a dict or an other padmet from where will be copied the info
"""
if isinstance(source, dict):
self.info = source
else:
self.info = source.info
[docs]
def setPolicy(self, source):
"""
Set policy from a list or copying from an other padmet
Parameters
----------
source: list or padmet.classes.PadmetRef/PadmetSpec
may be a list or an other padmet from where will be copied the policy
"""
if isinstance(source, list):
self.policy = Policy(source)
else:
self.policy = source.policy
[docs]
def setDicOfNode(self, source):
"""
Set dicOfNode from a dict or copying from an other padmet
Parameters
----------
source: dict or padmet.classes.PadmetRef/PadmetSpec
may be a dict or an other padmet from where will be copied the dicOfNode
"""
if isinstance(source, dict):
self.dicOfNode = source
else:
self.dicOfNode = source.dicOfNode
[docs]
def setdicOfRelationIn(self, source):
"""
Set dicOfRelationIn from a dict or copying from an other padmet
Parameters
----------
source: dict or padmet.classes.PadmetRef/PadmetSpec
may be a dict or an other padmet from where will be copied the dicOfRelationIn
"""
if isinstance(source, dict):
self.dicOfRelationIn = source
else:
self.dicOfRelationIn = source.dicOfRelationIn
[docs]
def setdicOfRelationOut(self, source):
"""
Set dicOfRelationOut from a dict or copying from an other padmet
Parameters
----------
source: dict or padmet.classes.PadmetRef/PadmetSpec
may be a dict or an other padmet from where will be copied the dicOfRelationOut
"""
if isinstance(source, dict):
self.dicOfRelationOut = source
else:
self.dicOfRelationOut = source.dicOfRelationOut
[docs]
def getAllRelation(self):
"""
return a set of all relations
Returns
-------
set
return a set of all relations
"""
all_relation = set()
for list_rlt in self.dicOfRelationIn.values():
for rlt in list_rlt:
all_relation.add(rlt)
for list_rlt in self.dicOfRelationIn.values():
for rlt in list_rlt:
all_relation.add(rlt)
return all_relation
[docs]
def getRelation(self, rlt_id_in, rlt_type, rlt_id_out):
"""
return a relation
Returns
-------
Relation
return a relation
"""
rlts = [
rlt
for list_rlt in self.dicOfRelationIn.values()
for rlt in list_rlt
if rlt.type == rlt_type
and rlt.id_in == rlt_id_in
and rlt.id_out == rlt_id_out
]
return rlts
[docs]
def getRelationTypeIDIn(self, rlt_type, rlt_id_in):
"""
return a relation
Returns
-------
Relation
return a relation
"""
rlts = [
rlt
for list_rlt in self.dicOfRelationIn.values()
for rlt in list_rlt
if rlt.type == rlt_type
and rlt.id_in == rlt_id_in
]
return rlts
[docs]
def getCompounds(self, get_id=True):
"""
Get all the compounds from a padmet instance.
Parameters
----------
self: padmet
padmet instance
get_id: bool
True for the ID and False for the node corresponding to the compounds
Returns
-------
list
list of compound IDs or nodes
"""
if get_id is True:
return [node.id for node in self.dicOfNode.values() if node.type == "compound"]
else:
return [node for node in self.dicOfNode.values() if node.type == "compound"]
[docs]
def getReactions(self, get_id=True):
"""
Get all the reactions from a padmet instance.
Parameters
----------
self: padmet
padmet instance
get_id: bool
True for the ID and False for the node corresponding to the reactions
Returns
-------
list
list of reactions IDs or nodes
"""
if get_id is True:
return [node.id for node in self.dicOfNode.values() if node.type == "reaction"]
else:
return [node for node in self.dicOfNode.values() if node.type == "reaction"]
[docs]
def getGenes(self, get_id=True):
"""
Get all the genes from a padmet instance.
Parameters
----------
self: padmet
padmet instance
get_id: bool
True for the ID and False for the node corresponding to the genes
Returns
-------
list
list of genes IDs or nodes
"""
if get_id is True:
return [node for node in self.dicOfNode.values() if node.type == "gene"]
else:
return [node.id for node in self.dicOfNode.values() if node.type == "gene"]
[docs]
def getPathways(self, get_id=True):
"""
Get all the pathways from a padmet instance.
Parameters
----------
self: padmet
padmet instance
get_id: bool
True for the ID and False for the node corresponding to the pathways
Returns
-------
list
list of pathways IDs or nodes
"""
if get_id is True:
return [node for node in self.dicOfNode.values() if node.type == "pathway"]
else:
return [node.id for node in self.dicOfNode.values() if node.type == "pathway"]
[docs]
def getPathwaysReactions(self):
"""
Get all the pathways from a padmet instance with their reactions.
Parameters
----------
self: padmet
padmet instance
Returns
-------
dict
dictionary of pathways IDs as key with list of reactions IDs as value
"""
# Iterate through node of type "pathway", then for each node found the relations linking the pathway to the reaction it contains.
return {node.id: [
rlt.id_in for rlt in self.dicOfRelationOut[node.id]
if rlt.type == "is_in_pathway" and self.dicOfNode[rlt.id_in].type == 'reaction'
]
for node in self.dicOfNode.values() if node.type == "pathway"}
[docs]
def loadGraph(self, padmet_file):
"""
Allow to recover all the information of the padmet file.
The section Data Base information corresponds to the self.info
The section Nodes corresponds to the data of each nodes in self.dicOfNode, sep ="\t"
The section Relations corresponds to the data of each relations in self.dicOfRelationIn/Out, sep ="\t"
Parameters
----------
padmet_file: str
the pathname of the padmet file to load.
"""
with open(padmet_file, "r", encoding="utf8") as f:
padmet_in_array = [line for line in f.read().splitlines() if len(line) != 0]
self.policy = Policy()
self.info = {}
self.dicOfNode = {}
self.dicOfRelationIn = {}
self.dicOfRelationOut = {}
try:
info_index = padmet_in_array.index("Data Base informations")
except ValueError:
info_index = None
# Index where start policy
policy_index = padmet_in_array.index("Policy")
# Index where start Nodes
node_index = padmet_in_array.index("Nodes")
# Index where start Relations
relation_index = padmet_in_array.index("Relations")
#
if info_index is not None:
info_section = padmet_in_array[info_index + 1 : policy_index]
#
for line in info_section:
if "\t" not in line:
line = line.replace(":", "")
self.info[line] = {}
current_data = line
else:
line = line.replace("\t", "").split(":")
self.info[current_data][line[0]] = line[1]
#
policy_in_array = [
line.split("\t") for line in padmet_in_array[policy_index + 1 : node_index]
]
self.setPolicy(policy_in_array)
# generator of data to input in Node()
node_section = (
line.split("\t")
for line in padmet_in_array[node_index + 1 : relation_index]
)
#
for data in node_section:
# Create a new Node object (cf node.py)
node_type = data[0]
node_id = data[1]
node_misc = {}
if len(data) > 2:
i = 2
# add the misc data in a dict
while (i + 1) < len(data):
# in case diff information for the same key
try:
node_misc[data[i]].append(data[i + 1])
except KeyError:
node_misc[data[i]] = [data[i + 1]]
i += 2
node = Node(node_type, node_id, node_misc)
# add the node into the dictionary
self.dicOfNode[node.id] = node
# generator of data to input in Relation()
relation_section = (
line.split("\t") for line in padmet_in_array[relation_index + 1 :]
)
# instantiate a new Relation object (cf relation.py)
for data in relation_section:
rlt_id_in = data[0]
rlt_type = data[1]
rlt_id_out = data[2]
rlt_misc = {}
if len(data) > 3:
i = 3
while (i + 1) < len(data):
# in case diff information for the same key
try:
rlt_misc[data[i]].append(data[i + 1])
except KeyError:
rlt_misc[data[i]] = [data[i + 1]]
i += 2
relation = Relation(rlt_id_in, rlt_type, rlt_id_out, rlt_misc)
try:
self.dicOfRelationIn[rlt_id_in].append(relation)
except KeyError:
self.dicOfRelationIn[rlt_id_in] = [relation]
try:
self.dicOfRelationOut[rlt_id_out].append(relation)
except KeyError:
self.dicOfRelationOut[rlt_id_out] = [relation]
[docs]
def updateFromSbml(
self,
sbml_file,
padmetRef=None,
mapping_file=None,
verbose=False,
force=False,
source_tool=None,
source_category=None,
source_id=None,
):
"""
Copy data from sbml to padmet. If the id of the sbml are
different with the padmetRef, need to add a dictionary of associations
between the id in the sbml and the id of the database of reference.
if no padmetRef, will create reactions and compounds based on the given sbml
@param sbml_file: pathname of the sbml file
@param padmetRef: <PadmetRef> padmet of reference.
@param mapping_dict (opt): pathname of the file containing the
association original_id ref_id, sep ="\t"
@param verbose: print info
@param force: if true, allow to copy reactions that are not in padmetRef
by creating new reaction
@type sbml_file, assocIdOriginRef: str
@type padmetRef: PadmetRef
@type verbose, force: bool
@return: _
@rtype: None
"""
if not os.path.exists(sbml_file):
raise FileNotFoundError("No SBML file accessible at " + sbml_file)
file_name = os.path.basename(sbml_file)
file_name = os.path.splitext(file_name)[0]
if not source_id:
source_id = file_name.upper()
# using libSbml to read sbml_file
if verbose:
print("loading sbml file: %s" % sbml_file)
reader = libsbml.SBMLReader()
document = reader.readSBML(sbml_file)
for i in range(document.getNumErrors()):
print(document.getError(i).getMessage())
model = document.getModel()
listOfSpecies = model.getListOfSpecies()
listOfReactions = model.getListOfReactions()
nbReactions = len(listOfReactions)
nbSpecies = len(listOfSpecies)
if verbose:
print("nb species: %s" %nbSpecies)
print(("nb reactions: %s" % nbReactions))
dicOfAssoc = {}
# reading assocIdOriginRef line by line, one line = "origin_id\tref_id\n"
if mapping_file is not None:
if verbose:
print("Parsing %s" % mapping_file)
with open(mapping_file, "r", encoding="utf8") as f: # open the file
dicOfAssoc = dict(
[
line.split("\t")
for line in f.read().splitlines()
if not line.startswith("#")
]
)
rxn_count = 0
for reactionSBML in listOfReactions:
"""
for rxn in listOfReactions:
if padmetRef not given: create new reaction, convert rxn and species ids
else:
if dict given:
if rxn_id in dict:
if rxn in padmetRef: add reaction
else: do Nothing
else:
check all cpds in rxn, if all in dict:
create new reaction
else: do Nothing
else:
if rxn in Ref: add reaction
else:
if force: create new reaction
else: do Nothing
"""
rxn_can_be_created = False
rxn_added = False
rxn_count += 1
rxn_idOrigin = reactionSBML.id
rxn_cname = reactionSBML.getName()
if reactionSBML.getReversible():
reaction_dir = "REVERSIBLE"
else:
reaction_dir = "LEFT-TO-RIGHT"
if verbose:
print("%s/%s %s" % (rxn_count, len(listOfReactions), rxn_idOrigin))
if padmetRef is None:
rxn_idRef = sbmlPlugin.convert_from_coded_id(rxn_idOrigin)[0]
try:
self.dicOfNode[rxn_idRef]
if verbose:
print("\t%s already in padmet" % rxn_idRef)
rxn_added = True
except KeyError:
rxn_can_be_created = True
pass
else:
if mapping_file:
try:
rxn_idRef = dicOfAssoc[rxn_idOrigin]
try:
self.dicOfNode[rxn_idRef]
if verbose:
print("\t%s already in padmet" % rxn_idRef)
rxn_added = True
except KeyError:
if rxn_idRef in list(padmetRef.dicOfNode.keys()):
if verbose:
print("\tCopy %s from padmetRef" % rxn_idRef)
self.copyNode(padmetRef, rxn_idRef)
rxn_added = True
else:
if verbose:
print(
"\tError: idRef %s not found in padmetRef"
% rxn_idRef
)
except KeyError:
rxn_idRef = sbmlPlugin.convert_from_coded_id(rxn_idOrigin)[0]
try:
self.dicOfNode[rxn_idRef]
if verbose:
print("\t%s already in padmet" % rxn_idRef)
rxn_added = True
except KeyError:
all_cpds = set(
[
r.getSpecies()
for r in reactionSBML.getListOfReactants()
]
+ [
p.getSpecies()
for p in reactionSBML.getListOfProducts()
]
)
if len(
[
True
for cpd_id in all_cpds
if cpd_id in list(dicOfAssoc.keys())
]
) == len(all_cpds):
if verbose:
print(
"\t%s can be created from compounds mapping"
% rxn_idOrigin
)
rxn_can_be_created = True
else:
if verbose:
print("\t%s not in mapping file" % rxn_idRef)
else:
rxn_idRef = sbmlPlugin.convert_from_coded_id(rxn_idOrigin)[0]
try:
self.dicOfNode[rxn_idRef]
if verbose:
print("\t%s already in padmet" % rxn_idRef)
rxn_added = True
except KeyError:
if rxn_idRef in list(padmetRef.dicOfNode.keys()):
if verbose:
print("\tCopy %s from padmetRef" % rxn_idRef)
self.copyNode(padmetRef, rxn_idRef)
rxn_added = True
else:
if force:
rxn_can_be_created = True
else:
if verbose:
print(
(
"\t%s not in PadmetRef and can't be created"
% rxn_idRef
)
)
if rxn_can_be_created:
if verbose:
print("\tCreating new reaction %s" % rxn_idRef)
if rxn_cname:
self.createNode(
"reaction",
rxn_idRef,
{"DIRECTION": [reaction_dir], "COMMON-NAME": [rxn_cname]},
)
else:
self.createNode(
"reaction", rxn_idRef, {"DIRECTION": [reaction_dir]}
)
reactants = reactionSBML.getListOfReactants()
for reactant in reactants:
if mapping_file:
reactant_id = dicOfAssoc[reactant.getSpecies()]
else:
reactant_id = sbmlPlugin.convert_from_coded_id(
reactant.getSpecies()
)[0]
# print(reactant_id)
if model.getElementBySId(reactant.getSpecies()).boundary_condition:
reactant_compart = "C-BOUNDARY"
else:
reactant_compart = model.getElementBySId(
reactant.getSpecies()
).getCompartment()
if reactant_compart is None:
if verbose:
print("\t\t%s has no compart, set to 'c'" % reactant_id)
reactant_compart = "c"
reactant_stoich = reactant.getStoichiometry()
consumes_rlt = Relation(
rxn_idRef,
"consumes",
reactant_id,
{
"STOICHIOMETRY": [reactant_stoich],
"COMPARTMENT": [reactant_compart],
},
)
# if reactant id not exist, create new compound node, else just add a new relation
if reactant_id not in list(self.dicOfNode.keys()):
if padmetRef is not None and reactant_id in list(
padmetRef.dicOfNode.keys()
):
self._copyNodeExtend(padmetRef, reactant_id)
else:
if verbose:
print("\t\tCreating new compound: %s" % reactant_id)
reactant_cname = listOfSpecies.getElementBySId(
reactant.getSpecies()
).getName()
if reactant_cname:
self.createNode(
"compound",
reactant_id,
{"COMMON-NAME": [reactant_cname]},
)
else:
self.createNode("compound", reactant_id)
self._addRelation(consumes_rlt)
products = reactionSBML.getListOfProducts()
for product in products:
if mapping_file:
product_id = dicOfAssoc[product.getSpecies()]
else:
product_id = sbmlPlugin.convert_from_coded_id(
product.getSpecies()
)[0]
# print(product_id)
if model.getElementBySId(product.getSpecies()).boundary_condition:
product_compart = "C-BOUNDARY"
else:
product_compart = model.getElementBySId(
product.getSpecies()
).getCompartment()
if product_compart is None:
if verbose:
print("\t\t%s has no compart, set to 'c'" % product)
product_compart = "c"
product_stoich = product.getStoichiometry()
produces_rlt = Relation(
rxn_idRef,
"produces",
product_id,
{
"STOICHIOMETRY": [product_stoich],
"COMPARTMENT": [product_compart],
},
)
if product_id not in list(self.dicOfNode.keys()):
if padmetRef is not None and product_id in list(
padmetRef.dicOfNode.keys()
):
self._copyNodeExtend(padmetRef, product_id)
else:
if verbose:
print("\t\tCreating new compound: %s" % product_id)
product_cname = listOfSpecies.getElementBySId(
product.getSpecies()
).getName()
if product_cname:
self.createNode(
"compound",
product_id,
{"COMMON-NAME": [product_cname]},
)
else:
self.createNode("compound", product_id)
self._addRelation(produces_rlt)
rxn_added = True
if rxn_added:
# Reaction was found in current network or successfully added
# creating SuppData and reconstructionData Nodes
# First, suppData:
suppData_id = rxn_idRef + "_SuppData_" + source_id.upper()
if suppData_id not in list(self.dicOfNode.keys()):
# Extracting all data to create the supplementary data node
# Using sbmlPlugin to recover the formula from the sbml
formula = sbmlPlugin.extractFormula(reactionSBML)
# Using sbmlPlugin to recover the note section from the sbml
notes = sbmlPlugin.parseNotes(reactionSBML)
# data will be stored in a suppData node
if rxn_cname:
suppData = {
"SOURCE": [source_id.upper()],
"ORIGIN_ID": [str(rxn_idOrigin)],
"NAME": [reactionSBML.getName()],
"REVERSIBLE": [str(reactionSBML.getReversible())],
"FORMULA": [formula],
}
else:
suppData = {
"SOURCE": [source_id.upper()],
"ORIGIN_ID": [str(rxn_idOrigin)],
"REVERSIBLE": [str(reactionSBML.getReversible())],
"FORMULA": [formula],
}
# add notes to data
suppData.update(notes)
# create the node suppData and the relation has_suppData
suppData_rlt = Relation(rxn_idRef, "has_suppData", suppData_id)
self.createNode("suppData", suppData_id, suppData, [suppData_rlt])
if verbose:
print("\tCreating suppData %s" % suppData_id)
# reconstructionData:
reconstructionData_id = (
rxn_idRef + "_reconstructionData_" + source_id.upper()
)
if reconstructionData_id not in list(self.dicOfNode.keys()):
reconstructionData = {"SOURCE": [source_id.upper()]}
if source_tool:
reconstructionData.update({"TOOL": [source_tool.upper()]})
if source_category:
reconstructionData.update(
{"CATEGORY": [source_category.upper()]}
)
reconstructionData_rlt = Relation(
rxn_idRef, "has_reconstructionData", reconstructionData_id
)
self.createNode(
"reconstructionData",
reconstructionData_id,
reconstructionData,
[reconstructionData_rlt],
)
if verbose:
print(
"\tCreating reconstructionData %s" % reconstructionData_id
)
# parses gene_association and create gene node or update already existing genes
# Using sbmlPlugin to recover the note section from the sbml
notes = sbmlPlugin.parseNotes(reactionSBML)
if "GENE_ASSOCIATION" in list(notes.keys()):
# Using sbmlPlugin to recover all genes associated to the reaction
listOfGenes = sbmlPlugin.parseGeneAssoc(
notes["GENE_ASSOCIATION"][0]
)
if listOfGenes:
if verbose:
print("\tParsing genes:")
genes_count = 0
nbGenes = len(listOfGenes)
for gene_id in listOfGenes:
genes_count += 1
if verbose:
print(
("\t\t%s/%s %s" % (genes_count, nbGenes, gene_id))
)
try:
# check if gene already in the padmet
self.dicOfNode[gene_id]
except KeyError:
self.createNode("gene", gene_id)
# check if rxn already linked to gene x
try:
linked_rlt = [
rlt
for rlt in self.dicOfRelationIn[rxn_idRef]
if rlt.type == "is_linked_to"
and rlt.id_out == gene_id
][0]
# rxn already linked to gene x, update misc
try:
linked_rlt.misc["SOURCE:ASSIGNMENT"].append(
source_id.upper()
)
except KeyError:
linked_rlt.misc["SOURCE:ASSIGNMENT"] = [
source_id.upper()
]
# rxn not linked to gene x
except IndexError:
linked_rlt = Relation(
rxn_idRef,
"is_linked_to",
gene_id,
{"SOURCE:ASSIGNMENT": [source_id.upper()]},
)
self._addRelation(linked_rlt)
[docs]
def updateFromPadmet(self, padmet):
"""
update padmet from another padmet file
Parameters
----------
padmet: padmet.classes.PadmetRef/PadmetSpec
padmet instance
"""
for k, v in list(padmet.dicOfNode.items()):
already_present = None
try:
self.dicOfNode[k]
already_present = True
except KeyError:
self.dicOfNode[k] = v
if v.type == 'reaction':
for rlt in padmet.dicOfRelationIn[k]:
if rlt.type in ["consumes","produces"]:
self._addRelation(rlt)
if v.type == "reaction" and already_present:
if k in self.dicOfNode:
first_rxn = get_rxn(k, padmet)
second_rxn = get_rxn(k, self)
same_rxn, different_reversibility = compare_rxn(first_rxn, second_rxn)
if same_rxn:
if different_reversibility:
self.dicOfNode[k].misc["DIRECTION"] = ["REVERSIBLE"]
else:
raise Exception("Can't copy reaction {0}: not the same reactants and products in reaction.".format(k))
for rlt in padmet.getAllRelation():
if rlt.type == "is_linked_to":
try:
match_rlt = [
i
for i in self.dicOfRelationIn[rlt.id_in]
if i.id_out == rlt.id_out
][0]
match_rlt.misc["SOURCE:ASSIGNMENT"].extend(
rlt.misc["SOURCE:ASSIGNMENT"]
)
except (IndexError, KeyError) as e:
self._addRelation(rlt)
elif rlt.type in ['produces', 'consumes']:
pass
else:
self._addRelation(rlt)
[docs]
def generateFile(self, output):
"""
Allow to create a padmet file to stock all the data.
Parameters
----------
output: str
path to output file
"""
# Order the dictionary of node by unique id and the tuple of relation
# by the node in id.
dicKeys = list(self.dicOfNode.keys())
dicKeys.sort()
orderedTuplOfRelation = tuple(
sorted(self.getAllRelation(), key=lambda x: x.id_in, reverse=False)
)
with open(output, "w", encoding="utf8") as f:
if len(self.info) != 0:
f.write("Data Base informations\n")
f.write("\n")
for k, data in self.info.items():
f.write(k + ":\n")
for k, v in data.items():
f.write("\t" + k + ":" + v + "\n")
f.write("\n")
# It writes the policy of the padmet file.
f.write("Policy\n")
f.write("\n")
policyInArray = self.policy.getPolicyInArray()
for line in policyInArray:
line = "\t".join(line)
line = line + "\n"
f.write(line)
f.write("\n")
# It writes the nodes of the padmet file.
f.write("Nodes\n")
f.write("\n")
for nodeId in dicKeys:
node = self.dicOfNode[nodeId]
line = node.toString() + "\n"
f.write(line)
f.write("\n")
# It writes the relations of the padmet file.
f.write("Relations\n")
f.write("\n")
for rlt in orderedTuplOfRelation:
line = rlt.toString() + "\n"
f.write(line)
# TODO
[docs]
def network_report(self, output_dir, padmetRef_file=None, verbose=False):
"""
Summurizes the network in a folder (output_dir) of 4 files.
all_pathways.tsv: report on the pathways of the network.
PadmetRef is used to recover the total reactions of a pathways. (sep = "\t")
line = dbRef_id, Common name, Number of reactions found,
Total number of reaction, Ratio (Reaction found / Total)
all_reactions.tsv: report on the reactions of the network. (sep = "\t")
line = dbRef_id, Common name, formula (with id),
formula (with common name), in pathways, associated genes, sources
all_metabolites.tsv: report on the metabolites of the network. (sep = "\t")
line = dbRef_id, Common name, Produced (p), Consumed (c), Both (cp)
all_genes.tsv: report on the genes of the network. (sep= "\t")
line = "id", "Common name", "linked reactions"
Parameters
----------
padmetRef_file: str
pathname of the padmet of reference
output_dir: str
pathname of the folder where to create the reports
verbose: bool
if true print info.
"""
os.system("mkdir -p " + output_dir)
if not output_dir.endswith("/"):
output_dir += "/"
all_pathways = output_dir + "all_pathways.tsv"
all_reactions = output_dir + "all_reactions.tsv"
all_metabolites = output_dir + "all_metabolites.tsv"
all_genes = output_dir + "all_genes.tsv"
if padmetRef_file is not None:
padmetRef = PadmetRef(padmetRef_file)
pathways = set(
[
self.dicOfNode[rlt.id_out]
for rlt in self.getAllRelation()
if rlt.type == "is_in_pathway"
and self.dicOfNode[rlt.id_in].type == "reaction"
]
)
nb_pathways = len(pathways)
reactions = [
node for node in list(self.dicOfNode.values()) if node.type == "reaction"
]
nb_reactions = len(reactions)
metabolites = set(
[
self.dicOfNode[rlt.id_out]
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
]
)
nb_metabolites = len(metabolites)
genes = [node for node in list(self.dicOfNode.values()) if node.type == "gene"]
nb_genes = len(genes)
if padmetRef_file is not None:
with open(all_pathways, "w", encoding="utf8") as f:
header = [
"dbRef_id",
"Common name",
"Taxons",
"Number of reaction found",
"Total number of reaction",
"Ratio (Reaction found / Total)"
]
header = "\t".join(header) + "\n"
f.write(header)
count = 1
for pnode in pathways:
pnode_id = pnode.id
if verbose:
print("Pathway : %s %s/%s" % (pnode_id, count, nb_pathways))
# Recover the first common_name
try:
common_name = pnode.misc["COMMON-NAME"][0]
except KeyError:
common_name = "Unknown"
try:
taxons = ";".join(pnode.misc["TAXONOMIC-RANGE"])
except KeyError:
taxons = "Unknown"
number_of_reaction_found = len(
[
rlt
for rlt in self.dicOfRelationOut[pnode_id]
if rlt.type == "is_in_pathway"
and self.dicOfNode[rlt.id_in].type == "reaction"
]
)
try:
total_number_of_reaction = len(
[
rlt
for rlt in padmetRef.dicOfRelationOut[pnode_id]
if rlt.type == "is_in_pathway"
and padmetRef.dicOfNode[rlt.id_in].type == "reaction"
]
)
# If keyError: pathway not in padmetRef, pathway added manually
except KeyError:
total_number_of_reaction = "NA"
try:
if type(total_number_of_reaction) is int:
ratio = "%.2f" % (
float(number_of_reaction_found)
/ total_number_of_reaction
)
else:
ratio = "NA"
line = [
pnode_id,
common_name,
taxons,
str(number_of_reaction_found),
str(total_number_of_reaction),
str(ratio),
]
line = "\t".join(line) + "\n"
f.write(line)
except ZeroDivisionError:
pass
count += 1
with open(all_reactions, "w", encoding="utf8") as f:
header = [
"dbRef_id",
"Common name",
"formula (with id)",
"formula (with common name)",
"in pathways",
"associated genes",
"categories",
]
header = "\t".join(header) + "\n"
f.write(header)
count = 0
for rnode in reactions:
count += 1
rnode_id = rnode.id
if verbose:
print("Reaction : %s %s/%s" % (rnode_id, count, nb_reactions))
# Recover the first common_name
try:
common_name = rnode.misc["COMMON-NAME"][0]
except KeyError:
common_name = "Unknown"
# Recovering pathways associated
try:
in_pathways = ";".join(
[
rlt.id_out
for rlt in self.dicOfRelationIn.get(rnode_id, None)
if rlt.type == "is_in_pathway"
]
)
except TypeError:
in_pathways = ""
# Recovering the formula
direction = rnode.misc["DIRECTION"][0]
if direction == "UNKNOWN":
direction = " =>/<=> "
elif direction == "REVERSIBLE":
direction = " <=> "
elif direction == "LEFT-TO-RIGHT":
direction = " => "
reactants = [
rlt.misc["STOICHIOMETRY"][0] + " " + rlt.id_out
for rlt in self.dicOfRelationIn[rnode_id]
if rlt.type == "consumes"
]
products = [
rlt.misc["STOICHIOMETRY"][0] + " " + rlt.id_out
for rlt in self.dicOfRelationIn[rnode_id]
if rlt.type == "produces"
]
formula_id = " + ".join(reactants) + direction + " + ".join(products)
try:
reactants = [
rlt.misc["STOICHIOMETRY"][0]
+ " "
+ self.dicOfNode[rlt.id_out].misc["COMMON-NAME"][0]
for rlt in self.dicOfRelationIn[rnode_id]
if rlt.type == "consumes"
]
products = [
rlt.misc["STOICHIOMETRY"][0]
+ " "
+ self.dicOfNode[rlt.id_out].misc["COMMON-NAME"][0]
for rlt in self.dicOfRelationIn[rnode_id]
if rlt.type == "produces"
]
formula_cname = (
" + ".join(reactants) + direction + " + ".join(products)
)
except KeyError:
formula_cname = ""
# Recovering genes associated
try:
gene_assoc = ";".join(
[
rlt.id_out
for rlt in self.dicOfRelationIn[rnode_id]
if rlt.type == "is_linked_to"
]
)
except TypeError:
gene_assoc = ""
sources = []
for rlt in self.dicOfRelationIn[rnode_id]:
if rlt.type == "has_reconstructionData":
src = self.dicOfNode[rlt.id_out].misc["CATEGORY"][0]
sources.append(src)
sources = ";".join(sources)
line = "\t".join(
[
rnode_id,
common_name,
formula_id,
formula_cname,
in_pathways,
gene_assoc,
sources,
]
)
line = line + "\n"
f.write(line)
with open(all_metabolites, "w", encoding="utf8") as f:
header = [
"dbRef_id",
"Common name",
"Produced (p), Consumed (c), Both (cp)",
]
header = "\t".join(header) + "\n"
f.write(header)
count = 0
for mnode in metabolites:
count += 1
mnode_id = mnode.id
is_consumed = False
is_produced = False
if verbose:
print("Metabolite : %s %s/%s" % (mnode_id, count, nb_metabolites))
# Recover the first common_name
try:
common_name = mnode.misc["COMMON-NAME"][0]
except KeyError:
common_name = "Unknown"
rlts_c_and_p = [
(rlt.type, rlt.id_in)
for rlt in self.dicOfRelationOut[mnode_id]
if rlt.type == "consumes" or rlt.type == "produces"
]
if "REVERSIBLE" in [
self.dicOfNode[rxn_id].misc["DIRECTION"][0]
for (rlt_type, rxn_id) in rlts_c_and_p
]:
statut = "cp"
else:
for rlt_type, rxn_id in rlts_c_and_p:
if rlt_type == "consumes":
is_consumed = True
elif rlt_type == "produces":
is_produced = True
if is_consumed and is_produced:
statut = "cp"
elif is_consumed:
statut = "c"
elif is_produced:
statut = "p"
else:
statut = "NA"
line = "\t".join([mnode_id, common_name, statut])
line = line + "\n"
f.write(line)
with open(all_genes, "w", encoding="utf8") as f:
header = ["id", "Common name", "linked reactions"]
header = "\t".join(header) + "\n"
f.write(header)
count = 0
for gnode in genes:
count += 1
gnode_id = gnode.id
if verbose:
print("Gene : %s %s/%s" % (gnode_id, count, nb_genes))
# Recover the first common_name
try:
common_name = gnode.misc["COMMON-NAME"][0]
except KeyError:
common_name = "Unknown"
# Recovering linked reactions
try:
rxn_assoc = ";".join(
[
rlt.id_in
for rlt in self.dicOfRelationOut.get(gnode_id, None)
if rlt.type == "is_linked_to"
]
)
except TypeError:
rxn_assoc = ""
line = "\t".join([gnode_id, common_name, rxn_assoc])
line = line + "\n"
f.write(line)
# ==============================================================================
# For Nodes
# ==============================================================================
def _addNode(self, node):
"""
Allows to add a node, only if the id is not already used.
Parameters
----------
node: padmet.classes.Node
the node to add
Returns
-------
bool
True if added, False if no.
"""
if node.id not in list(self.dicOfNode.keys()):
self.dicOfNode[node.id] = node
return True
else:
return False
def _copyNodeExtend(self, padmet, node_id):
"""
Allows to copy a node from an other padmet with the first children only.
Recursive function, call itself for the relations where the node is "in"
NB: particular case: we don't want to recover the relations "prot catalyses reaction"
do nothing for the relations where the node is "out"
Parameters
----------
padmet: padmet.classes.PadmetRef/PadmetSpec
Padmet from where to copy the node.
node_id: str
the id of the node to copy.
"""
node = padmet.dicOfNode[node_id]
# If true, the node does not exist and was susccessfully added.
if self._addNode(node):
try:
"""
relations_in = (rlt for rlt in padmet.dicOfRelationIn.get(node_id, None)
if rlt.type != "catalyses")
"""
relations_in = (
rlt for rlt in padmet.dicOfRelationIn.get(node_id, None)
)
# For each relation, we add it with addRelation(), then
# call _copyNodeExtend() for the id 'out'.
for rlt in relations_in:
self._addRelation(rlt)
node_out_id = rlt.id_out
node_out_type = padmet.dicOfNode[node_out_id].type
if node_out_type in [
"xref",
"name",
"suppData",
"reconstructionData",
]:
self._addNode(padmet.dicOfNode[node_out_id])
else:
self._copyNodeExtend(padmet, node_out_id)
# TypeError is raise if no relation found
# is found.
except TypeError:
pass
[docs]
def copyNode(self, padmet, node_id):
"""
copyNode() allows to copy a node from an other padmetSpec or padmetRef. It copies all
the relations 'in' and 'out' and it calls the function
_copyNodeExtend() to recover the associated node.
Parameters
----------
Padmet: padmet.classes.PadmetRef/PadmetSpec
padmet from where to copy the node
node_id: str
the id of the node to copy
"""
try:
nodeToCopy = padmet.dicOfNode[node_id]
except KeyError:
raise TypeError("Unable to get the node(s) with id: %s" % node_id)
# True => node successfully added in dictionary of node.
if self._addNode(nodeToCopy):
# Recover all relations of the node
try:
relations_in = (
rlt for rlt in padmet.dicOfRelationIn.get(node_id, None)
)
# Add each relation with _addRelation, then call
# _copyNodeExtend() for the id 'out'.
for rlt in relations_in:
self._addRelation(rlt)
node_out_id = rlt.id_out
node_out_type = padmet.dicOfNode[node_out_id].type
if node_out_type in [
"xref",
"name",
"suppData",
"reconstructionData",
]:
self._addNode(padmet.dicOfNode[node_out_id])
else:
self._copyNodeExtend(padmet, node_out_id)
# TypeError is raise if padmet.getRealtion() is none, no relation
# found. (NoneType not iterable)
except TypeError:
pass
try:
# Recover all relations where the node is out.
"""
relations_out = (rlt for rlt in padmet.dicOfRelationOut.get(node_id, None)
if rlt.type != "catalyses")
"""
relations_out = (
rlt for rlt in padmet.dicOfRelationOut.get(node_id, None)
)
# For each relation, we add it with addRelation, then call
# _copyNodeExtend() for the id 'in'
for rlt in relations_out:
self._addRelation(rlt)
node_in_id = rlt.id_in
self._copyNodeExtend(padmet, node_in_id)
# TypeError is raised if no relation found
# found.
except TypeError:
pass
[docs]
def delNode(self, node_id):
"""
Allows to delete a node, the relations associated to the node, and for
some relations, delete the associated node.
For relations where the node to del is 'in':
if rlt type in ['has_xref','has_name','has_suppData']: delNode out
For relations where the node to del is 'out':
if rlt type in ['consumes','produces']
Parameters
----------
node_id: str
id of node to delete
Returns
-------
bool
True if node successfully deleted, False if node not in dicOfNode
"""
# Delete the node from dicOfNode.
try:
self.dicOfNode.pop(node_id)
except KeyError:
print("The id %s doesnt exist. Unable to delete" % node_id)
return False
# If exist delete the relations 'in' and 'out'
try:
# Recover the relations where the node is "in".
relationsIn = [rlt for rlt in self.dicOfRelationIn.get(node_id, None)]
for rltIn in relationsIn:
# print(rltIn.toString())
self._delRelation(rltIn)
try:
id_out_rlts_in = [
rlt for rlt in self.dicOfRelationIn.get(rltIn.id_out, None)
]
except TypeError:
try:
id_out_rlts_out = [
rlt for rlt in self.dicOfRelationOut.get(rltIn.id_out, None)
]
except TypeError:
# print("%s linked to nothing" %rltIn.id_out)
self.delNode(rltIn.id_out)
except TypeError:
pass
try:
# Recover the relations where the node is "out"
relationsOut = [rlt for rlt in self.dicOfRelationOut.get(node_id, None)]
for rltOut in relationsOut:
self._delRelation(rltOut)
try:
id_in_rlts_in = [
rlt for rlt in self.dicOfRelationIn.get(rltOut.id_in, None)
]
except TypeError:
try:
id_in_rlts_out = [
rlt for rlt in self.dicOfRelationOut.get(rltOut.id_in, None)
]
except TypeError:
# print("%s linked to nothing" %rltOut.id_in)
self.delNode(rltOut.id_in)
except TypeError:
pass
return True
[docs]
def updateNode(self, node_id, data, action):
"""
Allows to update miscellaneous data of a Node.
@param node_id: the id of node to update
@param data: tuple with data[0] refers to the miscellaneous data key
(ex: common_name, direction ...), data[1] is a list of value to add / update.
data[1] can be None if the action is to pop the key
@param action: if == "add": the list data[1] will be added (ex: adding a common_name)
if == "remove": if data[1] is not None, the list data[1] will
be removed (ex: remove just one specific common_name)
if == "update":data[1] is the new value of the key data[0]
ex: updateNode('RXN-5',('direction',['LEFT-TO-RIGHT']),update). The
reaction' direction will be change to left2right
Parameters
----------
node_id: str
the id of the node to update
data: tuple
tuple of data to update, data[0] is the key, data[1] is a value, list or None
action: str
action in ['add','pop','remove','update']. Check description for more information
Returns
-------
bool
True if node successfully updated
"""
try:
node = self.dicOfNode[node_id]
except KeyError:
print("The id %s doesnt exist. Unable to update" % node_id)
return False
if action == "add":
try:
oldData = node.misc[data[0]]
oldData = oldData + data[1]
oldData = set(oldData)
newData = list(oldData)
node.misc[data[0]] = newData
except KeyError:
node.misc[data[0]] = data[1]
elif action == "remove":
try:
newData = [x for x in oldData if x not in data[0]]
if len(newData) != 0:
node.misc[data[0]] = newData
elif len(newData) == oldData:
print("None of the data to deleted is present")
else:
node.misc.pop(data[0])
except KeyError:
print("No data %s associated to this node" % data[0])
return False
elif action == "update":
node.misc[data[0]] = data[1]
else:
print("Unknown action: %s, please check docstring" % action)
return False
self.dicOfNode[node_id] = node
return True
# ==============================================================================
# For Relations:
# ==============================================================================
def _delRelation(self, relation):
"""
Delete a relation from dicOfRelationIn and out
Parameters
----------
relation: padmet.classes.Relation
the relation to delete
Returns
-------
bool
True if succesfully deleted
"""
delete = False
idIn = relation.id_in
idOut = relation.id_out
try:
for rlt in self.dicOfRelationIn[idIn]:
if relation.compare(rlt):
self.dicOfRelationIn[idIn].remove(rlt)
if len(self.dicOfRelationIn[idIn]) == 0:
self.dicOfRelationIn.pop(idIn)
delete = True
break
except KeyError:
pass
try:
for rlt in self.dicOfRelationOut[idOut]:
if relation.compare(rlt):
self.dicOfRelationOut[idOut].remove(rlt)
if len(self.dicOfRelationOut[idOut]) == 0:
self.dicOfRelationOut.pop(idOut)
delete = True
break
except KeyError:
pass
if not delete:
print("Unable to delete this relation, doesn't exist.")
return delete
def _addRelation(self, relation):
"""
AddRelation() allows to add a relation if not already in allRelations.
Parameters
----------
relation: padmet.classes.Relation
the relation to add
Returns
-------
bool
true if relation was successfully added
"""
idIn = relation.id_in
idOut = relation.id_out
rlt_to_compare = list(self.dicOfRelationIn.get(idIn, []))
rlt_to_compare += self.dicOfRelationOut.get(idOut, [])
for rlt in rlt_to_compare:
if relation.compare(rlt):
return False
if idIn in self.dicOfRelationIn:
self.dicOfRelationIn[idIn].append(relation)
else:
self.dicOfRelationIn[idIn] = [relation]
if idOut in self.dicOfRelationOut:
self.dicOfRelationOut[idOut].append(relation)
else:
self.dicOfRelationOut[idOut] = [relation]
return True
# ==============================================================================
# manipulating de novo node:
# ==============================================================================
[docs]
def createNode(self, _type, _id, dicOfMisc={}, listOfRelation=None):
"""
Creation of new node to add in the network.
Parameters
----------
_type: str
type of node (gene, reaction...)
_id: str
id of the node
dicOfMisc: dict
dictionnary of miscellaneous data
listOfRelation: list or None
list of relation
Returns
-------
padmet.classes.Node
new_node
"""
# add the new node
if _id in list(self.dicOfNode.keys()):
raise ValueError("%s is already used, unable to create Node" % (_id))
new_node = Node(_type, _id, dicOfMisc)
self.dicOfNode[_id] = new_node
if listOfRelation is not None:
for rlt in listOfRelation:
self._addRelation(rlt)
return new_node
# ==============================================================================
# manipulating de novo node:
# ==============================================================================
[docs]
def change_compart(self, old_compart, new_compart, verbose=False):
"""
#TODO
"""
rxns_updated = set()
for rlt in [
rlt
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
and rlt.misc["COMPARTMENT"][0] == old_compart
]:
rxns_updated.add(rlt.id_in)
rlt.misc.update({"COMPARTMENT": [new_compart]})
if verbose:
print("%s reactions updated" % (len(rxns_updated)))
[docs]
def get_all_compart(self):
"""
#TODO
"""
all_compart = set(
[
rlt.misc["COMPARTMENT"][0]
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
]
)
return all_compart
[docs]
def delCompart(self, compart, verbose=False):
"""
#TODO
"""
rxns_to_del = set()
for rlt in [
rlt
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
and rlt.misc["COMPARTMENT"][0] == compart
]:
rxns_to_del.add(rlt.id_in)
for rxn_id in rxns_to_del:
self.delNode(rxn_id)
if verbose:
print("%s reactions deleted" % (len(rxns_to_del)))
[docs]
def ko(self, genes, verbose=False):
"""
remove all reactions associated to a given gene or list of genes
Parameters
----------
genes: str or list
one gene or list of genes to remove
verbose: bool
print more info
"""
if isinstance(genes, str):
genes = [genes]
if verbose:
print("%s gene(s) to KO" % (len(genes)))
all_rxn_to_del = set()
for g_id in genes:
if not g_id in [g for g in list(self.dicOfNode.keys())]:
raise ValueError("%s not found" % g_id)
reactions_to_del = [
rlt.id_in
for rlt in self.dicOfRelationOut[g_id]
if rlt.type == "is_linked_to"
]
if verbose:
print("%s reactions associated to %s" % (len(reactions_to_del), g_id))
for rxn_id in reactions_to_del:
if verbose:
print("\t%s" % (rxn_id))
[all_rxn_to_del.add(r) for r in reactions_to_del]
if verbose:
print("%s unique reaction(s)" % len(all_rxn_to_del))
for rxn_id in all_rxn_to_del:
if verbose:
print("removing %s" % rxn_id)
self.delNode(rxn_id)
[docs]
def get_growth_medium(self, b_compart="C-BOUNDARY"):
"""
return set of metabolites corresponding to the growth medium
"""
growth_medium = set(
[
rlt.id_out
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
and rlt.misc.get("COMPARTMENT", [])[0] == b_compart
]
)
if growth_medium:
return growth_medium
else:
return None
[docs]
def remove_growth_medium(self, b_compart="C-BOUNDARY", verbose=False):
"""
#TODO
"""
current_growth_medium = self.get_growth_medium()
if current_growth_medium:
if verbose:
print("current growth medium: %s" % (list(current_growth_medium)))
for seed_id in current_growth_medium:
rxns = set(
[
rlt.id_in
for rlt in self.dicOfRelationOut[seed_id]
if rlt.type in ["consumes", "produces"]
and rlt.misc["COMPARTMENT"][0] == b_compart
]
)
for rxn_id in rxns:
if verbose:
print("Removing %s" % rxn_id)
self.delNode(rxn_id)
new_g_m = self.get_growth_medium()
if not new_g_m:
new_g_m = list()
print("New growth medium: %s" % (new_g_m))
else:
print("No growth medium found")
[docs]
def set_growth_medium(
self,
new_growth_medium=None,
padmetRef=None,
rxn_prefix=set(["TransportSeed", "ExchangeSeed"]),
b_compart="C-BOUNDARY",
e_compart="e",
c_compart="c",
verbose=False,
):
"""
if new_growth_medium is None: just remove the growth medium by deleting reactions starting with rxn_prefix
else: remove and change by the new growth_medium, a list of compounds.
Parameters
----------
new_growth_medium: list or None
list of metabolites ids for the new media
padmetRef: str
path name of the padmet ref file
rxn_prefix: set
set of prefix corresponding to reactions of exchanges (specific to growth medium)
b_compart: str
ID of the boundary compartment, compound
in this compart will have BoundaryCondition True in sbml
verbose: bool
print more info
"""
# get all rxn starting with rxn_prefix
all_rxn_to_del = set(
[
rlt.id_in
for rlt in self.getAllRelation()
if rlt.type in ["consumes", "produces"]
and rlt.misc.get("COMPARTMENT", [])[0] == b_compart
]
)
if len(all_rxn_to_del) == 0:
if verbose:
print("No growth medium found")
else:
for rxn_id in all_rxn_to_del:
if verbose:
print("removing %s" % (rxn_id))
if rxn_id in list(self.dicOfNode.keys()):
self.delNode(rxn_id)
if verbose:
print("%s reactions removed" % (len(all_rxn_to_del)))
# creating new reactions create growth medium
if new_growth_medium is not None:
for seed_id in new_growth_medium:
# check if seed in padmetSpec or in padmetRef
try:
self.dicOfNode[seed_id]
except KeyError:
if verbose:
print("%s not in the network" % seed_id)
try:
if padmetRef is None:
raise KeyError
if verbose:
print("Try to copy from dbref")
self._copyNodeExtend(padmetRef, seed_id)
except KeyError:
if verbose:
print("%s not in the padmetRef" % seed_id)
print("creating a new compound")
# not in padmetRef and self, create compound and transport/exchange rxn
seed_node = Node("compound", seed_id)
self.dicOfNode[seed_id] = seed_node
if verbose:
print("new compound created: id = %s" % seed_id)
exchange_rxn_id = "ExchangeSeed-" + seed_id
if exchange_rxn_id not in list(self.dicOfNode.keys()):
if verbose:
print("creating exchange reaction: id = %s" % exchange_rxn_id)
exchange_rxn_node = Node(
"reaction", exchange_rxn_id, {"DIRECTION": ["REVERSIBLE"]}
)
self.dicOfNode[exchange_rxn_id] = exchange_rxn_node
consumption_rlt = Relation(
exchange_rxn_id,
"consumes",
seed_id,
{"STOICHIOMETRY": [1.0], "COMPARTMENT": [b_compart]},
)
self._addRelation(consumption_rlt)
production_rlt = Relation(
exchange_rxn_id,
"produces",
seed_id,
{"STOICHIOMETRY": [1.0], "COMPARTMENT": [e_compart]},
)
self._addRelation(production_rlt)
# reconstructionData:
reconstructionData_id = exchange_rxn_id + "_reconstructionData_MANUAL"
if reconstructionData_id not in list(self.dicOfNode.keys()) and verbose:
reconstructionData = {
"SOURCE": ["IMPORT_FROM_MEDIUM"],
"COMMENT": [
"Added to manage seeds from boundary to extracellular compartment"
],
"CATEGORY": ["MANUAL"],
}
reconstructionData_rlt = Relation(
exchange_rxn_id, "has_reconstructionData", reconstructionData_id
)
self.createNode(
"reconstructionData",
reconstructionData_id,
reconstructionData,
[reconstructionData_rlt],
)
transport_rxn_id = "TransportSeed-" + seed_id
if transport_rxn_id not in list(self.dicOfNode.keys()):
if verbose:
print("creating trasnport reaction: id = %s" % transport_rxn_id)
transport_rxn_node = Node(
"reaction", transport_rxn_id, {"DIRECTION": ["LEFT-TO-RIGHT"]}
)
self.dicOfNode[transport_rxn_id] = transport_rxn_node
consumption_rlt = Relation(
transport_rxn_id,
"consumes",
seed_id,
{"STOICHIOMETRY": [1.0], "COMPARTMENT": [e_compart]},
)
self._addRelation(consumption_rlt)
production_rlt = Relation(
transport_rxn_id,
"produces",
seed_id,
{"STOICHIOMETRY": [1.0], "COMPARTMENT": [c_compart]},
)
self._addRelation(production_rlt)
# reconstructionData:
reconstructionData_id = transport_rxn_id + "_reconstructionData_MANUAL"
if reconstructionData_id not in list(self.dicOfNode.keys()) and verbose:
reconstructionData = {
"SOURCE": ["IMPORT_FROM_MEDIUM"],
"COMMENT": [
"Added to manage seeds from extracellular to cytosol compartment"
],
"CATEGORY": ["MANUAL"],
}
reconstructionData_rlt = Relation(
transport_rxn_id,
"has_reconstructionData",
reconstructionData_id,
)
self.createNode(
"reconstructionData",
reconstructionData_id,
reconstructionData,
[reconstructionData_rlt],
)
def get_rxn(rxn_id, padmet):
rxn_informaitons = {}
rxn_informaitons['DIRECTION'] = padmet.dicOfNode[rxn_id].misc['DIRECTION']
rxn_informaitons['REACTANTS'] = [(rlt.id_out, rlt.misc['STOICHIOMETRY']) for rlt in padmet.dicOfRelationIn[rxn_id] if rlt.type in ["consumes"]]
rxn_informaitons['PRODUCTS'] = [(rlt.id_out, rlt.misc['STOICHIOMETRY']) for rlt in padmet.dicOfRelationIn[rxn_id] if rlt.type in ["produces"]]
return rxn_informaitons
def compare_rxn(rxn_1, rxn_2):
same_rxn = None
different_reversibility = None
rxn_1_direction = rxn_1['DIRECTION'][0]
rxn_2_direction = rxn_2['DIRECTION'][0]
# First element of each list is the compound ID, the second element is the stoichiometric coefficient.
# TODO: find another way to deal with reactants or products containing the same compounds multiple times (like RXN-2103 in MetaCyc).
# This will need modification of _addRelation and also how we add relation in padmet.
rxn_1_reactants = sorted(set([reactant[0] for reactant in rxn_1['REACTANTS']]))
rxn_1_products = sorted(set([reactant[0] for reactant in rxn_1['PRODUCTS']]))
rxn_2_reactants = sorted(set([reactant[0] for reactant in rxn_2['REACTANTS']]))
rxn_2_products = sorted(set([reactant[0] for reactant in rxn_2['PRODUCTS']]))
if rxn_1_direction == rxn_2_direction:
if rxn_1_reactants == rxn_2_reactants:
if rxn_1_products == rxn_2_products:
same_rxn = True
if rxn_1_reactants == rxn_2_products:
if rxn_1_products == rxn_2_reactants:
different_reversibility = True
same_rxn = True
else:
if 'REVERSIBLE' in [rxn_1_direction, rxn_2_direction]:
different_reversibility = True
if rxn_1_reactants == rxn_2_reactants:
if rxn_1_products == rxn_2_products:
same_rxn = True
if not same_rxn:
if rxn_1_reactants == rxn_2_products:
if rxn_1_products == rxn_2_reactants:
same_rxn = True
if (rxn_1_direction == 'LEFT-TO-RIGHT' and rxn_2_direction == 'RIGHT-TO-LEFT') or (rxn_1_direction == 'RIGHT-TO-LEFT' and rxn_2_direction == 'LEFT-TO-RIGHT'):
different_reversibility = True
if rxn_1_reactants == rxn_2_products:
if rxn_1_products == rxn_2_reactants:
same_rxn = True
return same_rxn, different_reversibility