Source code for cameo.util

# Copyright 2013 Novo Nordisk Foundation Center for Biosustainability, DTU.## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.from__future__importabsolute_import,print_functionimportcolorsysimportinspectimportitertoolsimportloggingimportmathimportplatformimportrefromcollectionsimportOrderedDictfromdatetimeimportdatetimefromfunctoolsimportpartialfromitertoolsimportislicefromtimeimporttimefromuuidimportuuid1importnumpyimportpandasimportpipimportsixfromcobra.util.contextimportHistoryManagerfromnumpy.randomimportRandomStatefromsix.movesimportrangelogger=logging.getLogger(__name__)_BIOMASS_RE_=re.compile("biomass",re.IGNORECASE)

[docs]defpopitem(self):raiseAttributeError("'frozendict' object has no attribute 'popitem")

[docs]defpop(self,k,d=None):raiseAttributeError("'frozendict' object has no attribute 'pop")

def__setitem__(self,key,value):raiseAttributeError("'frozendict' object has no attribute '__setitem__")

[docs]defsetdefault(self,k,d=None):raiseAttributeError("'frozendict' object has no attribute 'setdefault")

def__delitem__(self,key):raiseAttributeError("'frozendict' object has no attribute '__delitem__")def__hash__(self):returnhash(tuple(sorted(self.items())))

[docs]defupdate(self,E=None,**F):raiseAttributeError("'frozendict' object has no attribute 'update")

[docs]deffloat_ceil(val,decimals=0):""" Like ceil but the number of decimals can be set. Equivalent of $$round(val + 1e^{-decimals}/2, decimals)$$ val: float, numpy.array The initial value. decimals: int The number of decimal places. Returns ------- float, numpy.array """aux=math.pow(10,-decimals)/2returnnumpy.round(val+aux,decimals)

[docs]deffloat_floor(val,decimals=0):""" Like floor but the number of decimals can be set. Equivalent of $$round(val - 1e^{-decimals}/2, decimals)$$ val: float, numpy.array The initial value. decimals: int The number of decimal places. Returns ------- float, numpy.array """aux=math.pow(10,-decimals)/2returnnumpy.round(val-aux,decimals)

[docs]classProblemCache(object):""" Variable and constraint cache for models. To be used in complex methods that require many extra variables and constraints when one must run simulations with the same method many times. It allows rollback to the previous state in case one iteration fails to build the problem or generates an invalid state. """def__init__(self,model):self.history_manager=Noneself._model=modelself.variables={}self.constraints={}self.objective=Noneself.original_objective=model.solver.objectiveself._contexts=[HistoryManager()]self.transaction_id=None

[docs]defbegin_transaction(self):""" Creates a time point. If rollback is called, the variables and constrains will be reverted to this point. """self._contexts.append(HistoryManager())

[docs]defadd_constraint(self,constraint_id,create,update,*args,**kwargs):""" Adds a new cached constraint. The create and update functions must have the following signatures: >>> create(model, constraint_id, *args) >>> update(model, constraint, *args) "args" in the first example must match args on the second example. Arguments --------- constraint_id : str The identifier of the constraint create : function A function that creates an optlang.interface.Constraint update : function a function that updates an optlang.interface.Constraint """context=self._contexts[-1]ifconstraint_idnotinself.constraints:self._append_constraint(constraint_id,create,*args,**kwargs)context(partial(self._remove_constraint,constraint_id))elifupdateisnotNone:update(self._model,self.constraints[constraint_id],*args,**kwargs)assertconstraint_idinself.constraints

[docs]defadd_variable(self,variable_id,create,update,*args,**kwargs):""" Adds a new cached variable. The create and update functions must have the following signatures: >>> create(model, variable_id, *args) >>> update(model, variable, *args) "args" in the first example must match args on the second example. Arguments --------- variable_id : str The identifier of the constraint create : function A function that creates an optlang.interface.Variable update : function a function that updates an optlang.interface.Variable """context=self._contexts[-1]ifvariable_idnotinself.variables:self._append_variable(variable_id,create,*args,**kwargs)context(partial(self._remove_variable,variable_id))elifupdateisnotNone:# rebuild_function = self._rebuild_variable(self.variables[variable_id])update(self._model,self.variables[variable_id],*args,**kwargs)# context(rebuild_function)assertvariable_idinself.variables

[docs]defreset(self):""" Removes all constraints and variables from the cache. """variables=list(self.variables.keys())constraints=list(self.constraints.keys())whilelen(self._contexts)>0:manager=self._contexts.pop()manager.reset()self._contexts.append(HistoryManager())assertall(var_idnotinself._model.solver.variablesforvar_idinvariables)assertall(const_idnotinself._model.solver.constraintsforconst_idinconstraints)self.variables={}self.constraints={}self._model.objective=self.original_objectiveself.objective=None

[docs]defrollback(self):""" Returns to the previous transaction start point. """iflen(self._contexts)<2:raiseRuntimeError("Start transaction must be called before rollback")self._contexts.pop().reset()

def__enter__(self):""" Allows problem cache to be used with a _with_ statement. Examples -------- You want to run room/lmoma for every single knockout. >>> with ProblemCache(model) as cache: >>> for reaction in reactions: >>> reaction.knock_out() >>> result = lmoma(model, reference=reference, cache=cache) Returns ------- ProblemCache returns itself """returnselfdef__exit__(self,exc_type,exc_val,exc_tb):self.reset()

[docs]classTimeMachine(object):"""Travel back and forth in time."""def__init__(self):super(TimeMachine,self).__init__()self.history=OrderedDict()def__call__(self,do=None,undo=None,bookmark=None):output=do()current_time=time()ifbookmarkisNone:entry_id=uuid1()else:entry_id=bookmark# make sure that entry is added to the end of historyself.history.pop(entry_id,None)self.history[entry_id]={'unix_epoch':current_time,'undo':undo,'redo':do}returnentry_id,outputdef__str__(self):info='\n'foriteminsix.iteritems(self.history):info+=self._history_item_to_str(item)returninfodef__enter__(self):returnselfdef__exit__(self,type,value,traceback):self.reset()@staticmethoddef_history_item_to_str(item):info=''uuid,entry=iteminfo+=datetime.fromtimestamp(entry['unix_epoch']).strftime('%Y-%m-%d %H:%M:%S')+'\n'undo_entry=entry['undo']try:# partial (if .keywords is None print {} instead)elements=undo_entry.func,undo_entry.args,undo_entry.keywordsor{}info+='undo: '+' '.join([str(elem)foreleminelements])+'\n'exceptAttributeError:# normal python functioninfo+='undo: '+undo_entry.__name__+'\n'redo_entry=entry['redo']try:elements=redo_entry.func,redo_entry.args,redo_entry.keywordsor{}# partialinfo+='redo: '+' '.join([str(elem)foreleminelements])+'\n'exceptAttributeError:info+='redo: '+redo_entry.__name__+'\n'returninfo

[docs]defundo(self,bookmark=None):ifbookmarkisNone:try:(uuid,entry)=self.history.popitem()entry['undo']()exceptKeyError:# history is emptypasselifbookmarkinlist(self.history.keys()):uuid=Falsewhileuuidisnotbookmark:(uuid,entry)=self.history.popitem()entry['undo']()else:raiseException('Provided bookmark %s cannot be found in the time machine.')

[docs]definheritdocstring(name,bases,attrs):"""Use as metaclass to inherit class and method docstrings from parent. Adapted from http://stackoverflow.com/questions/13937500/inherit-a-parent-class-docstring-as-doc-attribute"""temp=type('temporaryclass',bases,{})if'__doc__'notinattrsornotattrs["__doc__"]:# create a temporary 'parent' to (greatly) simplify the MRO searchforclsininspect.getmro(temp):ifcls.__doc__isnotNone:attrs['__doc__']=cls.__doc__breakforattr_name,attrinattrs.items():ifnotattr.__doc__:forclsininspect.getmro(temp):try:ifgetattr(cls,attr_name).__doc__isnotNone:attr.__doc__=getattr(cls,attr_name).__doc__breakexcept(AttributeError,TypeError):continuereturntype(name,bases,attrs)

[docs]defin_ipnb():""" Check if it is running inside an IPython Notebook (updated for new notebooks) """returnpandas.core.common.in_ipython_frontend()

[docs]defstr_to_valid_variable_name(s):"""Adapted from http://stackoverflow.com/a/3303361/280182"""# Remove invalid characterss=re.sub('[^0-9a-zA-Z_]','_',s)# Remove leading characters until we find a letter or underscores=re.sub('^[^a-zA-Z_]+','',s)returns

[docs]defzip_repeat(long_iter,short_iter):""" Zips two iterable objects but repeats the second one if it is shorter than the first one. Parameters ---------- long_iter: iterable short_iter: iterable Returns ------- generator """fori,jinzip(long_iter,itertools.cycle(short_iter)):yieldi,j

[docs]defpick_one(iterable):""" Helper function that returns an element of an iterable (it the iterable is ordered this will be the first element). """it=iter(iterable)returnnext(it)

[docs]defreduce_reaction_set(reaction_set,groups):""" Reduces a set of reactions according to a number of groups of reactions. The reduction will be performed so that the resulting set will contain no more than 1 reaction from each group. Reactions that are not in any of the groups will remain in the set. Parameters ---------- reaction_set: Set groups: Iterable of sets Returns ------- Set """reaction_set=set(reaction_set)# Make a shallow copyresult=[]forgroupingroups:intersection=group&reaction_setifintersection:# If any elements of group are in reaction_set, add one of these to resultresult.append(pick_one(intersection))reaction_set=reaction_set-intersectionresult=set(result)|reaction_set# Add the remaining reactions to resultreturnresult

[docs]defcurrent_solver_name(model):"""Give a string representation for an optlang interface. Parameters ---------- model : cobra.Model A model Returns ------- string The name of the interface as a string """interface=model.solver.interface.__name__returnre.sub(r"optlang.|.interface","",interface)