CellNOpt homepage|cellnopt.core 1.0.0 documentation

Source code for cellnopt.core.converter

# -*- python -*-
#
#  This file is part of the cinapps.tcell package
#
#  Copyright (c) 2012-2013 - EMBL-EBI
#
#  File author(s): Thomas Cokelaer (cokelaer@ebi.ac.uk)
#
#  Distributed under the GLPv3 License.
#  See accompanying file LICENSE.txt or copy at
#      http://www.gnu.org/licenses/gpl-3.0.html
#
#  website: www.cellnopt.org
#
##############################################################################
"""This module contains a base class to manipulate reactions

.. testsetup:: converter

    from cellnopt.core import *
    c = Interactions()


.. todo:: merge Interactions and Reactions class together
"""
from __future__ import print_function

from easydev import Logging

import numpy

from tools import CNOError

__all__ = ["Interactions", "Reaction"]


[docs]class Reaction(object): """A Reaction class A Reaction can encode logical AND and OR as well as NOT:: >>> from cellnopt.core import Reaction >>> r = Reaction("A+B=C") # a OR reaction >>> r = Reaction("A^B=C") # an AND reaction >>> r = Reaction("A&B=C") # an AND reaction >>> r = Reaction("C=D") # an activation >>> r = Reaction("!D=E") # a NOT reaction r.name r.rename_species(old, new) r._valid_reaction("a=b") """ valid_symbols = ["+","!", "&", "^"] def __init__(self, reaction=None, strict_rules=True): """ :param str reaction: :param bool strict_rules: if True, reactions cannot start with =, ^ or ^ signs. """ self._strict_rules = strict_rules self._reaction = None if reaction: self.name = reaction def _set_reaction(self, reaction): if reaction: reaction = self._valid_reaction(reaction) self._reaction = reaction[:] def _get_reaction(self): return self._reaction name = property(_get_reaction, _set_reaction) def _get_species(self, reac=None): """ >>> r = Reaction("!a+c^d^e^f+!h=b") >>> r._get_species() ['a' ,'c', 'd', 'r' ,'f' ,'h' ,'b'] """ if reac==None: reac = self.name[:] import re species = re.split("[+|=|^|!]", reac) species = [x for x in species if x] return species
[docs] def rename_species(self, old, new): """ difficulties: (1) if a species is called BAC, replace A by D must not touch BAC names (2) delimiters such as !, +, ^ should be taken into account """ raise NotImplementedError new = self._valid_species(new) lhs, rhs = self.name.split("=") if old == rhs: rhs = new #species = self._get_species(lhs) new_lhs = [] for c in lhs: new_lhs.append(c) print(new_lhs, rhs) reac = "=".join([new_lhs, rhs]) self.name = reac
def _valid_reaction(self, reaction): reaction = reaction.strip() reaction = reaction.replace("&", "^") if self._strict_rules: if reaction[0] in ["=", "^" , "+"]: raise CNOError("Reaction (%s) cannot start with %s" % (reaction, "=, ^, +")) # = sign is compulsary if "=" not in reaction: raise CNOError("Reaction (%s) must contain a = sign" % reaction) lhs, rhs = reaction.split("=") for this in self.valid_symbols: if this in rhs: raise CNOError("Found an unexpected character (%s) in the LHS of reactions %s" % (reaction, self.valid_symbols)) if "&" in lhs or "^" in lhs: if lhs == "": raise CNOError("Found an AND gate without RHS (%s)" % (reaction)) return reaction def _valid_species(self, species): species = species.strip() for c in self.valid_symbols: if c in species: raise CNOError("species must not contains any of the special symbols =, &, ^, +") return species def __str__(self): if self.name: return self.name else: return ""
[docs]class Interactions(object): """Interactions is a Base class to manipulate reactions (e.g., A=B) You can create list of reactions using the **=**, **!**, **+** and **^** characters with the following meaning:: >>> from cellnopt.core import * >>> c = Interactions() >>> c.add_reaction("A+B=C") # a OR reaction >>> c.add_reaction("A^B=C") # an AND reaction >>> c.add_reaction("A&B=C") # an AND reaction >>> c.add_reaction("C=D") # an activation >>> c.add_reaction("!D=E") # a NOT reaction #. The **!** sign indicates a NOT logic. #. The **+** sign indicates a OR. #. The **=** sign indicates a relation. #. The **^** or **&** signs indicate an AND ut **&** are replaced by **^**. .. warning:: meaning of + sign is OR so A+B=C is same as 2 reactions: A=C, B=C Now, we can get the species:: >>> c.specID ['A', 'B', 'C', 'D', 'E'] Remove one:: >>> c.remove_species("A") >>> c.reacID ["B=C", "C=D", "!D=E"] .. seealso:: :class:`cellnopt.core.reactions.Reactions` and :class:`cellnopt.core.sif.SIF` """ valid_symbols = ["+","!", "&", "^"] def __init__(self, format="cno", strict_rules=True): self._reacID = [] self._notMat = None self._interMat = None self._format = format self._logging = Logging("INFO") self._reaction = Reaction(strict_rules=strict_rules) def _reac2spec(self, reac): """simple function to extract species from a reaction """ self._reaction._valid_reaction(reac) reac = reac.replace("!","") # we don't care about the NOT here reac = reac.replace("__or__","+") # just to find the species reac = reac.replace("^","+") # just to find the species lhs, rhs = reac.split("=") specID = [] specID.extend([x for x in lhs.split('+') if x]) specID.extend([x for x in rhs.split('+') if x]) specID = set(specID) return specID def _get_species(self): """Extract the specID out of reacID""" specID = set() # use set to prevent duplicates # extract species from all reacID and add to the set for reac in self.reacID: specID = specID.union(self._reac2spec(reac)) # sort (transformed to a list) specID = sorted(specID) #self._specID = specID return specID namesSpecies = property(fget=_get_species, doc="alias to specID") specID = property(_get_species, doc="return species") def _get_reacID(self): return self._reacID reacID = property(fget=_get_reacID) def _get_lhs_species(self, reac, remove_not=True): """Return independent species reac = "A+!B+C=D" _get_lhs_species returns ['A', 'B', 'C'] used by _build_interMat """ lhs = reac.split('=')[0] # keep only lhs lhs = lhs.split('+') # split reactions lhs = [x for x in lhs if len(x)>0] if remove_not == True: lhs = [x.replace('!','') for x in lhs] return lhs def __str__(self): _str = "reactions: %s " % len(self.reacID) _str += "species: %s " % len(self.specID) return _str
[docs] def remove_species(self, species_to_remove): """Removes species from the reacID list :param str,list species_to_remove: .. note:: If a reaction is "a+b=c" and you remove specy "a", then the reaction is not enterely removed but replace by "b=c" """ # make sure we have a **list** of species to remove if isinstance(species_to_remove, list): pass elif isinstance(species_to_remove, str): species_to_remove = [species_to_remove] else: raise TypeError("species_to_remove must be a list or string") reacIDs_toremove = [] reacIDs_toadd = [] #['pRB' in s2s._reac2spec(x) for x in s2s.reacID if 'pRB' in x] for reac in self.reacID: lhs = self._get_lhs_species(reac) # lhs without ! sign rhs = reac.split("=")[1] # two cases: either a=b or a+d+e=b # if we remove a, the first reaction should be removed but the # second should just be transformed to d+e=b # RHS contains a specy to remove, we do want the reaction if rhs in species_to_remove: reacIDs_toremove.append(reac) continue # otherwise, we need to look at the LHS. If the LHS is of length 1, # we are in the first case (a=b) and it LHS contains specy to # remove, we do not want to keep it. if len(lhs) == 1: if lhs[0] in species_to_remove: reacIDs_toremove.append(reac) continue # Finally, if LHS contains 2 species or more, separated by + sign, # we do no want to remove the entire reaction but only the # relevant specy. So to remove a in "a+b=c", we should return "b=c" # taking care of ! signs. for symbol in ["+", "^"]: if symbol not in reac: continue else: lhs_with_neg = [x for x in reac.split("=")[0].split(symbol)] new_lhs = symbol.join([x for x in lhs_with_neg if x.replace("!", "") not in species_to_remove]) if len(new_lhs): new_reac = new_lhs + "=" + rhs reacIDs_toremove.append(reac) reacIDs_toadd.append(new_reac) level = self._logging.level self._logging.level = "ERROR" for reac in reacIDs_toremove: self.remove_reaction(reac) for reac in reacIDs_toadd: self.add_reaction(reac) self._logging.level = level
[docs] def add_reaction(self, reaction): """Adds a reaction in the list of reactions In logical formalism, the inverted hat stand for OR but there is no such key on standard keyboard so we use the + sign instead. The AND is defined with either the ^ or & sign. Finally the NOT is defined by the ! sign. Valid reactions are therefore:: a=b a+c=d a&b=e a^b=e # same as above !a=e Example:: >>> c = Interactions() >>> c.add_reaction("a=b") >>> assert len(c.reacID) == 1 .. warning & symbol are replaced by ^ internally. """ # remove any trailing white space. Important to get proper species names reaction = self._reaction._valid_reaction(reaction) reaction = self._sort_reaction(reaction) if reaction not in self.reacID: self._reacID.append(reaction) else: self._logging.info("%s already in the list of reactions" % reaction) # update whatever is needed to be updated. #self.specID = sorted(set(self.specID).union(self._reac2spec(reaction)))
def _sort_reaction(self, reaction): """Rearrange species of the LHS in alphabetical order :: >>> s.sort_reaction("b+a=c") "a+b=c" """ lhs, rhs = reaction.split("=") lhs = [x for x in lhs.split("+")] # left species with ! sign ["b","!a","c"] if len(lhs) == 1: return reaction # if more than one specy, we must rearrange them taking care of ! signs species = self._get_lhs_species(reaction) # without ! signs ["b", "a", "c"] sorted_indices = numpy.argsort(species) new_lhs = [] for i in sorted_indices: new_lhs.append(lhs[i]) new_reac = "=".join(["+".join(new_lhs), rhs]) return new_reac
[docs] def remove_reaction(self, reaction): """Remove a reaction from the reacID list >>> c = Interactions() >>> c.add_reaction("a=b") >>> assert len(c.reacID) == 1 >>> c.remove_reaction("a=b") >>> assert len(c.reacID) == 0 """ if reaction in self.reacID: self._reacID.remove(reaction) #self.get_specID_from_reacID()
[docs] def search(self, specy, strict=False, verbose=True): """Prints and returns reactions that contain the specy name Decomposes reactions into species first :param str specy: :param bool strict: decompose reaction search for the provided specy name :return: a Interactions instance with relevant reactions """ r = Interactions() for x in self.reacID: species = self._reac2spec(x) if strict == True: for this in species: if specy.lower() == this.lower(): if verbose: print(x) r.add_reaction(x) else: for this in species: if specy.lower() in this.lower(): if verbose: print(x) r.add_reaction(x) continue return r