diff --git a/docs/source/sources.rst b/docs/source/sources.rst index 63fe39dfc..3ab48fc50 100644 --- a/docs/source/sources.rst +++ b/docs/source/sources.rst @@ -78,26 +78,32 @@ ACSets DecaExpr extraction (:py:mod:`mira.sources.acsets.decapodes.deca_expr`) :members: :show-inheritance: -Utility Methods (:py:mod:`mira.sources.util`) ---------------------------------------------- -.. automodule:: mira.sources.util - :members: - :show-inheritance: - -Vensim (:py:mod:`mira.sources.system_dynamics.vensim`) ------------------------------------------------------- +Vensim models (:py:mod:`mira.sources.system_dynamics.vensim`) +------------------------------------------------------------- .. automodule:: mira.sources.system_dynamics.vensim :members: :show-inheritance: -Stella (:py:mod:`mira.sources.system_dynamics.stella`) ------------------------------------------------------- +Stella models (:py:mod:`mira.sources.system_dynamics.stella`) +------------------------------------------------------------- .. automodule:: mira.sources.system_dynamics.stella :members: :show-inheritance: -PYSD Model Parsing (:py:mod:`mira.sources.system_dynamics.pysd`) ----------------------------------------------------------------- +PySD models (:py:mod:`mira.sources.system_dynamics.pysd`) +--------------------------------------------------------- .. automodule:: mira.sources.system_dynamics.pysd :members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + +SIF networks (:py:mod:`mira.sources.sif`) +----------------------------------------- +.. automodule:: mira.sources.sif + :members: + :show-inheritance: + +Utility Methods (:py:mod:`mira.sources.util`) +--------------------------------------------- +.. automodule:: mira.sources.util + :members: + :show-inheritance: diff --git a/mira/modeling/amr/regnet.py b/mira/modeling/amr/regnet.py index 9e87ba039..6c82a0c8a 100644 --- a/mira/modeling/amr/regnet.py +++ b/mira/modeling/amr/regnet.py @@ -42,10 +42,13 @@ def __init__(self, model: Model): self.transitions = [] self.parameters = [] self.model_name = model.template_model.annotations.name if \ + model.template_model.annotations and \ model.template_model.annotations.name else "Model" self.model_description = model.template_model.annotations.description \ - if model.template_model.annotations.description else self.model_name + if model.template_model.annotations and \ + model.template_model.annotations.description else self.model_name self.metadata = {} + self._states_by_id = {} vmap = {} for key, var in model.variables.items(): # Use the variable's concept name if possible but fall back @@ -67,6 +70,7 @@ def __init__(self, model: Model): initial = safe_parse_expr(str(initial)) state_data['initial'] = str(initial) self.states.append(state_data) + self._states_by_id[name] = state_data for idx, transition in enumerate(model.transitions.values()): # Regnets cannot represent conversions (only @@ -77,35 +81,34 @@ def __init__(self, model: Model): # sign on the state so we have special handling for it elif isinstance(transition.template, NaturalDegradation): var = vmap[transition.consumed[0].key] + state_for_var = self._states_by_id.get(var) if transition.template.rate_law: pnames = transition.template.get_parameter_names() if len(pnames) == 1: rate_const = list(pnames)[0] else: rate_const = float(list(pnames)[0]) - for state in self.states: - if state['id'] == var: - state['rate_constant'] = rate_const - state['sign'] = False - else: - state['sign'] = False + if state_for_var: + state_for_var['rate_constant'] = rate_const + if state_for_var: + state_for_var['sign'] = False continue # Controlled production corresponds to an inherent positive # sign on the state so we have special handling for it elif isinstance(transition.template, ControlledProduction): var = vmap[transition.produced[0].key] + state_for_var = self._states_by_id.get(var) if transition.template.rate_law: pnames = transition.template.get_parameter_names() if len(pnames) == 1: rate_const = list(pnames)[0] else: rate_const = float(list(pnames)[0]) - for state in self.states: - if state['id'] == var: - state['rate_constant'] = rate_const - state['sign'] = True - else: - state['sign'] = True + state_for_var = self._states_by_id.get(var) + if state_for_var: + state_for_var['rate_constant'] = rate_const + if state_for_var: + state_for_var['sign'] = True continue # Beyond these, we can assume that the transition is a # form of production or degradation corresponding to diff --git a/mira/sources/sif.py b/mira/sources/sif.py new file mode 100644 index 000000000..2ca9be1a8 --- /dev/null +++ b/mira/sources/sif.py @@ -0,0 +1,96 @@ +"""This module provides functions to create a MIRA TemplateModel from a +Simple Interaction Format (SIF) file. The SIF format is a simple +space-delimited format where each line represents a relationship +between two entities. The first column is the source node, the second +column is the relation, and the third column is the target node. The +relation is a string that represents the type of interaction between +the source and target nodes. SIF files are useful as a minimal representation +of regulatory networks with positive/negative regulation.""" + +__all__ = ['template_model_from_sif_edges', + 'template_model_from_sif_file', + 'template_model_from_sif_url'] + +import requests +from mira.metamodel import * + + +def template_model_from_sif_edges(edges): + """Return TemplateModel from a list of SIF edges. + + Parameters + ---------- + edges : list + A list of tuples of the form (source, rel, target) where source and + target are strings representing the source and target nodes and rel + is a string representing the relation between them. + + Returns + ------- + TemplateModel + A MIRA TemplateModel. + """ + templates = [] + for source, rel, target in edges: + source_concept = Concept(name=source) + target_concept = Concept(name=target) + if rel == 'POSITIVE': + if source == target: + t = ControlledProduction( + controller=source_concept, + outcome=target_concept) + else: + t = GroupedControlledProduction( + controllers=[source_concept, target_concept], + outcome=target_concept) + elif rel == 'NEGATIVE': + if source == target: + t = NaturalDegradation(subject=source_concept) + else: + t = ControlledDegradation( + controller=source_concept, + subject=target_concept) + templates.append(t) + tm = TemplateModel(templates=templates) + return tm + + +def template_model_from_sif_file(fname): + """Return TemplateModel from a SIF file. + + Parameters + ---------- + fname : str + The path to the SIF file. + + Returns + ------- + TemplateModel + A MIRA TemplateModel. + """ + with open(fname, 'r') as fh: + edges = [line.strip().split() + for line in fh.readlines() + if line and not line.startswith('#')] + return template_model_from_sif_edges(edges) + + +def template_model_from_sif_url(url): + """Return TemplateModel from a SIF URL. + + Parameters + ---------- + url : str + The URL to the SIF file. + + Returns + ------- + TemplateModel + A MIRA TemplateModel. + """ + res = requests.get(url) + res.raise_for_status() + edges = [line.strip().split() + for line in res.text.split('\n') + if line and not line.startswith('#')] + return template_model_from_sif_edges(edges) diff --git a/scripts/covid19_diseasemaps/process_covid19_diseasemaps.py b/scripts/covid19_diseasemaps/process_covid19_diseasemaps.py new file mode 100644 index 000000000..e447dd1a8 --- /dev/null +++ b/scripts/covid19_diseasemaps/process_covid19_diseasemaps.py @@ -0,0 +1,25 @@ +import json +import tqdm +from mira.modeling.amr.regnet import template_model_to_regnet_json +from mira.sources.sif import template_model_from_sif_url + + +models = ['Apoptosis', 'Coagulation-pathway', 'ER_Stress', 'ETC', 'E_protein', + 'HMOX1_Pathway', 'IFN-lambda', 'Interferon1', 'JNK_pathway', + 'Kynurenine_pathway', 'NLRP3_Activation', 'Nsp14', 'Nsp4_Nsp6', + 'Nsp9_protein', 'Orf10_Cul2_pathway', 'Orf3a', 'PAMP_signaling', + 'Pyrimidine_deprivation', 'RTC-and-transcription', + 'Renin_angiotensin', 'TGFB_pathway', 'Virus_replication_cycle'] + + +SIF_URL_BASE = ('https://git-r3lab.uni.lu/covid/models/-/raw/master/' + 'Executable%20Modules/SBML_qual_build/sif') + + +if __name__ == "__main__": + for model in tqdm.tqdm(models): + url = f'{SIF_URL_BASE}/{model}_stable.sif' + tm = template_model_from_sif_url(url) + regnet = template_model_to_regnet_json(tm) + with open(f'{model}.json', 'w') as fh: + json.dump(regnet, fh, indent=1) diff --git a/tests/test_sif.py b/tests/test_sif.py new file mode 100644 index 000000000..141d7e47f --- /dev/null +++ b/tests/test_sif.py @@ -0,0 +1,16 @@ +from mira.sources.sif import template_model_from_sif_edges +from mira.modeling.amr.regnet import template_model_to_regnet_json + + +def test_sif_processing(): + # Lotka volterra example + edges = [ + ('prey', 'POSITIVE', 'predator'), + ('predator', 'NEGATIVE', 'prey'), + ('prey', 'POSITIVE', 'prey'), + ('predator', 'NEGATIVE', 'predator') + ] + tm = template_model_from_sif_edges(edges) + assert len(tm.templates) == 4 + regnet = template_model_to_regnet_json(tm) + assert len(regnet['model']['edges']) == 2