"""
CBMPy: CBDataStruct module
==========================
PySCeS Constraint Based Modelling (http://cbmpy.sourceforge.net)
Copyright (C) 2009-2017 Brett G. Olivier, VU University Amsterdam, Amsterdam, The Netherlands
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
Author: Brett G. Olivier
Contact email: bgoli@users.sourceforge.net
Last edit: $Author: bgoli $ ($Id: CBDataStruct.py 575 2017-04-13 12:18:44Z bgoli $)
"""
# preparing for Python 3 port
from __future__ import division, print_function
from __future__ import absolute_import
#from __future__ import unicode_literals
import os, time, copy, re
import numpy
import webbrowser
from . import miriamids
MIRIAM = miriamids.miriamids
#MIRIAM.update({'EC' : {'data_entry': 'http://www.ebi.ac.uk/intenz/query?cmd=SearchEC&ec=$id',
#'example': '1.1.1.1',
#'name': 'Enzyme Nomenclature',
#'pattern': re.compile(r'^\d+\.-\.-\.-|\d+\.\d+\.-\.-|\d+\.\d+\.\d+\.-|\d+\.\d+\.\d+\.(n)?\d+$'),
#'url': 'http://identifiers.org/ec-code/'}
#})
MIRIAM_KEYS = list(MIRIAM)
MIRIAM_KEYS.sort()
MIRIAM_KEYS = tuple(MIRIAM_KEYS)
MIRIAM_KEYSlc = tuple([a.lower() for a in MIRIAM_KEYS])
from .CBConfig import __CBCONFIG__ as __CBCONFIG__
__DEBUG__ = __CBCONFIG__['DEBUG']
__version__ = __CBCONFIG__['VERSION']
cTime = time.time
[docs]class StructMatrix:
"""
This class is specifically designed to store structural matrix information
give it an array and row/col index permutations it can generate its own
row/col labels given the label src.
"""
array = None
ridx = None
cidx = None
row = None
col = None
shape = None
def __init__(self, array, ridx, cidx, row=None, col=None):
"""
Instantiate with array and matching row/col index arrays, optional label arrays
"""
self.array = array
self.ridx = ridx
self.cidx = cidx
self.row = row
self.col = col
self.shape = array.shape
def __call__(self):
return self.array
[docs] def getRowsByIdx(self, *args):
"""Return the rows referenced by index (1,3,5)"""
return self.array.take(args, axis=0)
[docs] def getColsByIdx(self, *args):
"""Return the columns referenced by index (1,3,5)"""
return self.array.take(args, axis=1)
[docs] def setRow(self, src):
"""
Assuming that the row index array is a permutation (full/subset)
of a source label array by supplying that source to setRow it
maps the row labels to ridx and creates self.row (row label list)
"""
self.row = [src[r] for r in self.ridx]
[docs] def setCol(self, src):
"""
Assuming that the col index array is a permutation (full/subset)
of a source label array by supplying that src to setCol
maps the row labels to cidx and creates self.col (col label list)
"""
self.col = [src[c] for c in self.cidx]
[docs] def getRowsByName(self, *args):
"""Return the rows referenced by label ('s','x','d')"""
assert self.row != None, "\nI need row labels"
try:
return self.array.take([self.row.index(l) for l in args], axis=0)
except Exception as ex:
print(ex)
print("\nValid row labels are: {}".format(self.row))
return None
[docs] def getColsByName(self, *args):
"""Return the columns referenced by label ('s','x','d')"""
assert self.col != None, "\nI need column labels"
try:
return self.array.take([self.col.index(l) for l in args], axis=1)
except Exception as ex:
print(ex)
print("Valid column labels are: {}".format(self.col))
return None
[docs] def getLabels(self, axis='all'):
"""Return the matrix labels ([rows],[cols]) where axis='row'/'col'/'all'"""
if axis == 'row': return self.row
elif axis == 'col': return self.col
else: return self.row, self.col
[docs] def getIndexes(self, axis='all'):
"""Return the matrix indexes ([rows],[cols]) where axis='row'/'col'/'all'"""
if axis == 'row': return self.ridx
elif axis == 'col': return self.cidx
else: return self.ridx, self.cidx
def getByIdx(self, row, col):
assert row in self.ridx, '\n%s is an invalid index' % row
assert col in self.cidx, '\n%s is an invalid index' % col
return self.array[row, col]
def getByName(self, row, col):
assert row in self.row, '\n%s is an invalid name' % row
assert col in self.col, '\n%s is an invalid name' % col
return self.array[self.row.index(row), self.col.index(col)]
def setByIdx(self, row, col, val):
assert row in self.ridx, '\n%s is an invalid index' % row
assert col in self.cidx, '\n%s is an invalid index' % col
self.array[row, col] = val
def setByName(self, row, col, val):
assert row in self.row, '\n%s is an invalid name' % row
assert col in self.col, '\n%s is an invalid name' % col
self.array[self.row.index(row), self.col.index(col)] = val
[docs]class StructMatrixLP(StructMatrix):
"""Adds some stuff to StructMatrix that makes it LP friendly"""
RHS = None
operators = None
__array_type__ = None
def __init__(self, array, ridx, cidx, row=None, col=None, rhs=None, operators=None):
"""
Object that holds an LP, stoichiometric constraints, operators, RHS, names etc
- *array* stoichiometric matrix (linear constraints)
- *ridx* a list of indexes typically range(shape[0])
- *cidx* a list of indexes typically range(shape[1])
- *row* a list of row names
- *col* a list of col names
- *rhs* [default=None] a list of rhs values defaults to 0
- *operators* [default=None] a list of constraint senses defaults to 'E' =
"""
StructMatrix.__init__(self, array, ridx, cidx, row=row, col=col)
self.__array_type__ = type(array)
if type(rhs) == type(None):
self.RHS = numpy.zeros(array.shape[0])
else:
assert len(rhs) == array.shape[0], "\nRHS length mismatch"
if type(rhs) == list:
rhs = numpy.array(rhs)
self.RHS = rhs
if operators == None:
self.operators = ['E']*len(self.RHS)
else:
assert len(operators) == len(self.RHS), "\nOperator length mismatch"
self.operators = operators
[docs] def getCopy(self, attr_str, deep=False):
"""
Return a copy of the attribute with name attr_str. Uses the copy module `copy.copy` or `copy.deepcopy`
- *attr_str* a string of the attribute name: 'row', 'col'
- *deep* [default=False] try to do a deepcopy. Use with caution see copy module docstring for details
"""
if attr_str == 'array' or attr_str == 'rhs':
return getattr(self, attr_str).copy()
elif deep:
return copy.deepcopy(getattr(self, attr_str))
else:
return copy.copy(getattr(self, attr_str))
def getLandRHS(self):
RHS = self.RHS.copy()
RHS.resize(len(RHS), 1)
return numpy.hstack([self.array, RHS])
def setRHSbyName(self, name, value):
assert name in self.row, '\n%s is an invalid name' % name
self.RHS[self.row.index(name)] = value
def setRHSbyIdx(self, idx, val):
assert idx in self.ridx, '\n%s is an invalid index' % idx
self.RHS[idx] = val
def setOperatorbyName(self, name, value):
assert name in self.row, '\n%s is an invalid name' % name
value = value.strip()
assert value in ['G','L','E'], '\n%s is not a valid operator' % value
self.operators[self.row.index(name)] = value
def setOperatorbyIdx(self, idx, value):
assert idx in self.ridx, '\n%s is an invalid index' % idx
value = value.strip()
assert value in ['G','L','E'], '\n%s is not a valid operator' % value
self.operators[idx] = value
def getRHSbyName(self, name):
assert name in self.row, '\n%s is an invalid name' % name
return self.RHS[self.row.index(name)]
def getRHSbyIdx(self, idx):
assert idx in self.ridx, '\n%s is an invalid index' % idx
return self.RHS[idx]
def getOperatorbyName(self, name):
assert name in self.row, '\n%s is an invalid name' % name
return self.operators[self.row.index(name)]
def getOperatorbyIdx(self, idx):
assert idx in self.ridx, '\n%s is an invalid index' % idx
return self.operators[idx]
#TODO: this all needs to be redone to allow for nested qualifiers and model/biol qualifers
[docs]class MIRIAMannotation(object):
"""
The MIRIAMannotation class MIRIAM annotations: Biological Qualifiers
"""
MIRIAM = None
MIDS = None
MIDSlc = None
QUALIFIERS = None
# biomodels qualifiers
isA = None
isEncodedBy = None
encodes = None
hasPart = None
hasProperty = None
hasTaxon = None
hasVersion = None
isDescribedBy = None
isHomologTo = None
isPartOf = None
isPropertyOf = None
isVersionOf = None
occursIn = None
isDerivedFrom = None
def __init__(self):
self.MIRIAM = MIRIAM
self.MIDS = MIRIAM_KEYS
self.MIDSlc = MIRIAM_KEYSlc
self.QUALIFIERS = ("isA","isEncodedBy","encodes","hasPart","hasProperty","hasTaxon","hasVersion","isDescribedBy",\
"isHomologTo","isPartOf","isPropertyOf","isVersionOf","occursIn",\
"isDerivedFrom")
[docs] def addIDorgURI(self, qual, uri):
"""
Add a URI directly into a qualifier collection:
- *qual* a Biomodels biological qualifier e.g. "is" "isEncodedBy"
- *uri* the complete identifiers.org uri e.g. http://identifiers.org/chebi/CHEBI:58088
"""
if qual == 'is':
qual = 'isA'
if hasattr(self, qual):
Q = self.__getattribute__(qual)
if Q == None:
Q = []
Q.append(uri)
self.__setattr__(qual, Q)
else:
print('INFO: Invalid qualifier: \"{}\" uri NOT set'.format(qual))
#print(self.getAllMIRIAMUris())
[docs] def checkEntityPattern(self, entity):
"""
For an entity key compile the pattern to a regex, if necessary.
- *entity* a MIRIAM resource entity
"""
if type(self.MIRIAM[entity]['pattern']) == re._pattern_type:
return True
else:
try:
#self.MIRIAM[entity]['pattern'] = re.compile(self.MIRIAM[entity]['pattern'])
return True
except:
print('Invalid pattern (entity={}): \"{}\"'.format(self.MIRIAM[entity]['name'], self.MIRIAM[entity]['pattern']))
return False
[docs] def checkId(self, entity, mid):
"""
Check that a entity id e.g. CHEBI:17158
- *mid* the entity id e.g. CHEBI:17158
"""
if self.checkEntityPattern(entity):
res = re.findall(self.MIRIAM[entity]['pattern'], mid)
if len(res) > 0:
return True
print('INFO: invalid entity ({}) id: \"{}\"'.format(entity, mid))
return False
[docs] def checkEntity(self, entity):
"""
Check an entity entry, this is a MIRIAM resource name: "chEBI". The test is case insensitive and will correct the case
of wrongly capitalised entities automatically. If the entity is not recognised then a list of possible candidates
based on the first letters of the input is displayed.
- *entity* a MIRIAM resource entity e.g. "ChEBI"
"""
if entity.lower() not in self.MIDSlc:
print('\n\"{}\" is not a valid entity were you looking for one of these:\n'.format(entity))
if len(entity) == 0:
print('Need something to work with here')
elif len(entity) == 1:
temp = [a for a in self.MIDS if a[0].lower() == entity[0].lower()]
else:
temp = [a for a in self.MIDS if a[:2].lower() == entity[:2].lower()]
for t_ in temp:
print('\t{}'.format(t_))
return None
else:
return self.MIDS[self.MIDSlc.index(entity.lower())]
[docs] def addMIRIAMannotation(self, qual, entity, mid):
"""
Add a qualified MIRIAM annotation or entity:
- *qual* a Biomodels biological qualifier e.g. "is" "isEncodedBy"
- *entity* a MIRIAM resource entity e.g. "ChEBI"
- *mid* the entity id e.g. CHEBI:17158
"""
if qual == 'is':
qual = 'isA'
if hasattr(self, qual):
Q = self.__getattribute__(qual)
if Q == None:
Q = []
E = self.checkEntity(entity)
if E != None:
if self.checkId(E, mid):
Q.append(self.MIRIAM[E]['url']+mid)
self.__setattr__(qual, Q)
else:
print('INFO: Invalid entity: \"{}\" MIRIAM entity NOT set'.format(entity))
else:
print('INFO: Invalid qualifier: \"{}\" MIRIAM entity NOT set'.format(qual))
[docs] def deleteMIRIAMannotation(self, qual, entity, mid):
"""
Deletes a qualified MIRIAM annotation or entity:
- *qual* a Biomodels biological qualifier e.g. "is" "isEncodedBy"
- *entity* a MIRIAM resource entity e.g. "ChEBI"
- *mid* the entity id e.g. CHEBI:17158
"""
if qual == 'is':
qual = 'isA'
if hasattr(self, qual):
Q = self.__getattribute__(qual)
if Q != None:
E = self.checkEntity(entity)
if E != None:
if self.checkId(E, mid):
annot = self.MIRIAM[E]['url']+mid
if annot in Q:
idx = Q.index(annot)
print('Deleted annotation {}'.format(Q.pop(idx)))
else:
print('INFO: Invalid entity: \"{}\" MIRIAM entry does not exist'.format(mid))
else:
print('INFO: Invalid entity: \"{}\" MIRIAM entry does not exist'.format(mid))
else:
print('INFO: Invalid entity: \"{}\" MIRIAM entity does not exist'.format(entity))
else:
print('INFO: Invalid qualifier: \"{}\" MIRIAM entity does not exist'.format(qual))
[docs] def getMIRIAMUrisForQualifier(self, qual):
"""
Return all list of urls associated with qualifier:
- *qual* the qualifier e.g. "is" or "isEncoded"
"""
if qual == 'is':
qual = 'isA'
if hasattr(self, qual):
Q = self.__getattribute__(qual)
if Q == None:
print('INFO: No \"{}\" qualifiers defined'.format(qual))
return ()
else:
return tuple(Q)
else:
print('INFO: Invalid qualifier: \"{}\" MIRIAM entity NOT set'.format(qual))
return ()
[docs] def getAndViewUrisForQualifier(self, qual):
"""
Retrieve all url's associated with qualifier and attempt to open them all in a new browser tab
- *qual* the qualifier e.g. "is" or "isEncoded"
"""
Q = self.getMIRIAMUrisForQualifier(qual)
if Q != None:
for q_ in Q:
self.viewURL(q_)
[docs] def getAllMIRIAMUris(self):
"""
Return a dictionary of qualifiers that contain ID.org URL'S
"""
out = {}
for q_ in self.QUALIFIERS:
Q = self.__getattribute__(q_)
urls = []
if Q != None:
for u_ in self.getMIRIAMUrisForQualifier(q_):
urls.append(u_)
if q_ == 'isA':
q_ = 'is'
out[q_] = tuple(urls)
return out
[docs] def viewURL(self, url):
"""
This will try to open the URL in a new tab of the default webbrowser
- *url* the url
"""
webbrowser.open_new_tab(url)
#class MIRIAMModelAnnotation(MIRIAMannotation):
#"""
#Derived class with qualifiers for BQmodel
#"""
#isDerivedFrom = None
#def __init__(self):
#self.MIRIAM = MIRIAM
#self.MIDS = MIRIAM_KEYS
#self.MIDSlc = MIRIAM_KEYSlc
#self.QUALIFIERS = ("isA", "isDerivedFrom", "isDescribedBy")
## class SensitivityReaction(object):
## """
## Pseudo reaction class holding sensitivity data
## """
## def __init__(self):
## pass
## class SensitivitySpecies(object):
## """
## Pseudo species class holding sensitivity data
## """
## def __init__(self):
## pass
## class SensitivityData(object):
## """
## A class that holds sensitivity analysis data of an LP object
## """
## def __init__(self, sense_obj, sense_rhs, sense_bnd):
## pass
## def getListOfReactions(self):
## """
## Returns a list of sensitivity reaction objects
## """
## return []
## def getListOfConstraints(self):
## """
## Returns a list of sensitivity constraint objects
## """
## return []