class LinearModel(SecondOrderCache): ''' Core math matrix object for linear optimization modeling in reflux Uses the Second Order Cache object as its foundation Three main parts: 1)Sparce matrix of coeffecents 2)Row and column limits 3)Objective vector Minimize: Z = c*x Subject to: S*x <=> b l < x < u Extended Linear Control Model With additional components: 4)Annotations 5)Targets 6)ControlMap 6)NaturalObjective 7)SyntheticObjective ''' def __init__(self): self.verbose = False #Data Containers self.modelName = '' self.data = SecondOrderCache() self.rowLimits = {} self.columnLimits = {} self.mipColumns = IndexedCache() self.objective = {} #Report of model annotation values (dict) self.annotation = None #Control self.targets = None self.controlMap = None self.controlClusters = None self.naturalObjective = None self.syntheticObjective = None #Variables self.scale = 1 #! to be removed self.defaultLowerLimit = None self.defaultUpperLimit = None def __str__(self): return self.data.__str__() def _addString(self,other): print "string" def _addScalar(self,other): print "scalar" def _addDict(self,other): print "dict" def _addLinearModel(self,other): print "model" def __add__(self,other): if type(other) == type(""): self._addString(other) if type(other) == type(0) or type(other) ==type(0.0): self._addScalar(other) if type(other) == type({}): self._addDict(other) if type(other) == type(self): self._addLinearModel(other) return self def __eq__(self,value): if type(self) != type(value): return False a = self.data.equals(value.data) b = self.rowLimits == value.rowLimits c = self.columnLimits == value.columnLimits d = self.objective == value.objective e = self.mipColumns == value.mipColumns result = a and b and c and d and e return result def _scaleTuple(self,value): ''' scales a par of values used in scaling of whole matrix @type value: float ''' (v1,v2) = value if v1 != None: v1 = float(v1*self.scale) if v2 != None: v2 = float(v2*self.scale) return (v1, v2) def _getDefaultColumnLimits(self): ''' returns the column limits with defaults for values not given ''' columnNames = set(self.getColumnNames()) limitNames = set(self.columnLimits.keys()) defaultingNames = columnNames.difference(limitNames) result = {} for name in defaultingNames: if not (self.defaultLowerLimit == None and self.defaultUpperLimit == None): result[name] = (self.defaultLowerLimit,self.defaultUpperLimit) return result def _getDefaultRowLimits(self): ''' returns the row limits with defaults for values not given ''' columnNames = set(self.getRowNames()) limitNames = set(self.rowLimits.keys()) defaultingNames = columnNames.difference(limitNames) result = {} for name in defaultingNames: if not (self.defaultLowerLimit == None and self.defaultUpperLimit == None): result[name] = (self.defaultLowerLimit,self.defaultUpperLimit) return result def _annotateString(self,value,annotationMap,regex,nsep=" "): result = '' tags = re.findall(regex,value) for s in tags: if s in annotationMap.keys(): r = annotationMap[s] else: r = s result += nsep + r result = result[len(nsep):] return result def _annotateMap(self,data,annotationMap,regex): result = {} for (key,value) in data.items(): ikey = self._annotateString(key, annotationMap, regex) result[ikey] = value return result def _annotateList(self,data,annotationMap,regex): result = [] for value in data: ivalue = self._annotateString(value, annotationMap, regex) result.append(ivalue) return result def annotateGeneList(self,data,annotationName = "bnumber", regex="[a-zA-Z0-9\(\)]+"): result = data if self.annotation == None: return result if annotationName in self.annotation.keys(): annotationMap = self.annotation[annotationName] gMap = annotationMap.getColumn("gene") if annotationMap != None: result = self._annotateList(data,gMap,regex) return result def annotateGenes(self,objective,annotationName = "bnumber", regex="[a-zA-Z0-9\(\)]+"): result = objective if self.annotation == None: return result if annotationName in self.annotation.keys(): annotationMap = self.annotation[annotationName] gMap = annotationMap.getColumn("gene") if annotationMap != None: result = self._annotateMap(objective,gMap,regex) return result def getGeneTargetMap(self): if self.controlMap == None: return None result = {} for (r,gs) in self.controlMap.items(): for g in gs: if g not in result.keys(): result[g] = set() result[g].add(r) return result def setProperty(self,name,value): self.properties[name] = value def getProperty(self,name): return self.properties[name] def setScalex(self,scale): ''' Sets the scale for the matrix Not entirely checked for completeness and usage @param scale: the scaling factor for the limits @type scale: float ''' self.scale = scale def addRowName(self,name): ''' Adds a row name to matrix @type name: string ''' self.data.rowCache.addValue(name) return None def addColumnName(self,name): ''' Adds column name to matrix @type name: string ''' self.data.columnCache.addValue(name) return None def setMipColumnName(self,name): ''' Sets column with value name as a integer column @type name: string ''' self.mipColumns.addValue(name) def setMipColumnNames(self,names,tag="%s"): ''' @type names: string[] ''' for name in names: iname = tag % name self.setMipColumnName(iname) return None def getMipColumnNames(self): ''' Returns array of strings of column names which are set to integers @rtype: string[] ''' return self.mipColumns.getValues() def getRowIndex(self,name): ''' Returns index of row of value name @type name: string @rtype: int ''' return self.data.rowCache.getindex(name) def getColumnIndex(self,name): ''' Returns index of column of value name @type name: string @rtype: int ''' return self.data.columnCache.getindex(name) def getRowNames(self): ''' Returns an list of the row names @rtype: string[] ''' return self.data.rowCache.getValues() def getColumnNames(self): ''' Returns a list of the column names @rtype: string[] ''' return self.data.columnCache.getValues() def addRowLimit(self,rowName,limit): ''' Sets limits for selected row @type rowName: string @type limit (float,float) ''' self.addRowName(rowName) self.rowLimits[rowName] = self._scaleTuple(limit) def addColumnLimit(self,columnName,limit): ''' Sets limits for selected column @columnName: string @type limit (float,float) ''' self.addColumnName(columnName) self.columnLimits[columnName] = self._scaleTuple(limit) def getRowLimit(self,name): ''' returns limit for row @rtype: (float,float) ''' if name in self.rowLimits: return self.rowLimits[name] else: return (None,None) def getColumnLimit(self,name): ''' returns limit for column @rtype: (float,float) ''' if name in self.columnLimits: return self.columnLimits[name] else: return (None,None) def addRowLimits(self,limits): ''' Sets row limits from map @type limits: {string:(float,float)} ''' for key in limits.keys(): (lower,upper) = limits[key] self.addRowLimit(key,(lower,upper)) def addColumnLimits(self,limits): ''' Sets colum limits from map @type limits: {string:(float,float)} ''' for key in limits.keys(): (lower,upper) = limits[key] self.addColumnLimit(key,(lower,upper)) def getRowLimits(self): ''' returns map of row limits @rtype: {name:(float,float)} ''' result = self.rowLimits.copy() defaultLimits = self._getDefaultRowLimits() result.update(defaultLimits) return result def getColumnLimits(self): ''' returns map of column limits @rtype: {name:(float,float)} ''' result = self.columnLimits.copy() defaultLimits = self._getDefaultColumnLimits() result.update(defaultLimits) return result def addObjective(self,columnName, value): ''' sets objective coeffecent for a column @type columnName: string @type value: float ''' self.objective[columnName] = value def setObjective(self,objectiveMap): ''' Sets objective coeffecents from map @type objectiveMap: {sting:float} ''' self.objective = {} for key in objectiveMap.keys(): value = objectiveMap[key] self.objective[key]=value def getObjective(self): ''' returns objective map @rtype {string,float} ''' return self.objective def getRowValueMap(self,rowName): ''' returns a dict of row values (column name: value) @type rowName: string @rtype {string,float} ''' r = self.data.getRow(rowName) #return r result = {} for key in self.data.keys(): if key[0] == rowName: colName = key[1] value = self.data.getValue(key[0],key[1]) result[colName] = value if r != result: pass return result def getColumnValueMap(self,colName): ''' returns a dict of row values (column name: value) @type rowName: string @rtype {string,float} ''' r = self.data.getColumn(colName) #return r result = {} for key in self.data.keys(): if key[1] == colName: rowName = key[0] value = self.data.getValue(key[0],key[1]) #self.getValue(key[0],key[1]) result[rowName] = value if r != result: pass return result def getRowByValue(self,name,function): result = {} values = self.getRowValueMap(name) for (k,v) in values.items(): if function(v): result[k] = v return result def getColumnByValue(self,name,function): result = {} values = self.getColumnValueMap(name) for (k,v) in values.items(): if function(v): result[k] = v return result def getRowValuesFromPred(self,name,values): result = {} rvalues = self.getRowValueMap(name) for (k,v) in rvalues.items(): vi = None if k in values: vi = values[k] result[k] = (v,vi) return result def addData(self,rowName,columnName,value): ''' Add data value to model matrix @type rowName: string @type columnName: string @type value: string ''' if value == None or value == 0.0: return None floatValue = float(value) self.data.addValue(rowName,columnName,floatValue) #self.addValue(rowName,columnName,floatValue) return (0,0) def addDataCache(self,data): ''' Adds a data cach object to the coeffecent matrix @type data: SecondOrderCache {(string,string):float} ''' self.data.extend(data) #self.data.rowCache.extend(data.rowCache) #self.data.columnCache.extend(data.columnCache) def getData(self,rowName,columnName): ''' Get the coeffecent of the matrix @type rowName: string @type columnName: string ''' return self.data.getValue(rowName,columnName) #return self.getValue(rowName,columnName) def removeData(self,rowName,columnName): ''' removes a datapoint by row and column name @type rowName: string @type columnName: string ''' self.data.removeValue(rowName,columnName) def removeRow(self,rowName): columnNames = self.data.rowMap[rowName] self.data.removeRow(rowName) if rowName in self.rowLimits: del self.rowLimits[rowName] for columnName in columnNames: rCount = len(self.data.columnMap[columnName]) if rCount == 0: self.removeColumn(columnName) #if self.verbose: print "removing column [%s]" % (columnName) if columnName in self.columnLimits.keys(): del self.columnLimits[columnName] return None def removeColumn(self, columnName): rowNames = self.data.columnMap[columnName] self.data.removeColumn(columnName) if columnName in self.columnLimits: del self.columnLimits[columnName] if columnName in self.mipColumns.dataArray: self.mipColumns.removeValue(columnName) if self.targets != None: if columnName in self.targets: self.targets.removeValue(columnName) if self.controlMap != None: if columnName in self.controlMap.keys(): del self.controlMap[columnName] #print "checking rownames [%s]" % (rowNames) for rowName in rowNames: cNames = self.data.rowMap[rowName] if len(cNames) == 0: self.removeRow(rowName) #if self.verbose: print "removing row [%s]" % (rowName) if rowName in self.rowLimits.keys(): del self.rowLimits[rowName] return None def addRow(self,rowName,data): ''' add a row in the form of a dictonary @type rowName: string @type data: {string,float} ''' for key in data.keys(): value = data[key] self.addData(rowName,key,value) def addColumn(self,columnName,modelVector): ''' @type columnName: string @type data: {string:float} ''' for key in modelVector.keys(): value = modelVector[key] self.addData(key,columnName,value) def getSparseMatrix(self): ''' @rtype (int,int,float)[] ''' return self.data.getSparseMatrix() def getSparseMatrixMap(self): ''' return the index matrix ''' return self.data.getIndexMatrix() def addConstraints(self,model): ''' Add a linear constraints to the current model @type model: LinearModel ''' self.data.extend(model.data) rowLimits = model.getRowLimits() columnLimits = model.getColumnLimits() self.addRowLimits(rowLimits) self.addColumnLimits(columnLimits) self.mipColumns.extend(model.mipColumns) return None def addModel(self,model): ''' Add a linear model to the current model @type model: LinearModel ''' self.data.extend(model.data) rowLimits = model.getRowLimits() columnLimits = model.getColumnLimits() self.addRowLimits(rowLimits) self.addColumnLimits(columnLimits) self.mipColumns.extend(model.mipColumns) return None def extend(self,model): ''' Adda linear model and update the objective function @type model: LinearModel ''' self.addModel(model) self.objective = model.objective def multiply(self,modelMatrix,value): ''' Multiply the values of the model by a scalar @type value: float ''' result = LinearModel() result.rowCache.extend(modelMatrix.rowCache) result.columnCache.extend(modelMatrix.columnCache) result.data.extend(modelMatrix.data.multiply(value)) rowLimits = modelMatrix.getRowLimits() columnLimits = modelMatrix.getColumnLimits() result.addRowLimits(rowLimits) result.addColumnLimits(columnLimits) def transpose(self): ''' Get the transpose of the current model @rtype: LinearModel ''' result = LinearModel() result.scale=self.scale result.rowCache=self.columnCache result.columnCache=self.rowCache result.data = self.data.getTranspose() return result def getIndexMatrix(self): ''' Returns the index matrix of the model @rtype: (int,int,float)[] ''' return self.data.getIndexMatrix() def _reportRow(self,dataValues,limit,predValues=None): result = "" for (k,v) in dataValues.items(): p = None if predValues != None: if k in predValues: p = predValues[k] r = "%s(%s)[%s] + " %(v,p,k) result += r result = result[:-2] result += " = (%s,%s)" % (limit[0],limit[1]) return result def _vectorValue(self,v1,v2): result = 0 for k in set(v1.keys()).intersection(v2.keys()): result += v1[k] * v2[k] return result def floatLimit(self,limit): if limit[0] != None: r1 = limit[0] else: r1 = float("-inf") if limit[1] != None: r2 = limit[1] else: r2 = float("inf") result = (r1,r2) return result def modelReport(self,dir=1,prediction=None): report = Report() delta = 1e-6 for rowName in self.getRowNames(): rLimit = self.floatLimit(self.getRowLimit(rowName)) rValues =self.getRowValueMap(rowName) rString = self._reportRow(rValues,rLimit,prediction) report.addElement(rowName,"Type","row") report.addElement(rowName,"Equation",rString) rLimitS = "(%s,%s)" % (rLimit[0],rLimit[1]) report.addElement(rowName,"Limit",rLimitS) if prediction != None: rValue = self._vectorValue(rValues, prediction) rValue = round(rValue,6) rValid = rLimit[0]-delta < rValue < rLimit[1] + delta report.addElement(rowName,"Value",rValue) report.addElement(rowName,"Valid",rValid) for colName in self.getColumnNames(): cLimit = self.floatLimit(self.getColumnLimit(colName)) cValues =self.getColumnValueMap(colName) cString = self._reportRow(cValues,cLimit,prediction) report.addElement(colName,"Type","column") report.addElement(colName,"Equation",cString) if prediction != None: if colName in prediction.keys(): cValue = prediction[colName] cValid = cLimit[0]-delta < cValue < cLimit[1] + delta report.addElement(colName,"Value",cValue) report.addElement(colName,"Valid",cValid) return report def _convertToGeneNames(self,reactionName): if reactionName not in self.controlMap.keys(): return None geneNames = self.controlMap[reactionName] if type(geneNames) == type(""): return set([geneNames]) else: return geneNames def _convertToGeneTag(self,reactionName,geneClusters = None): geneNames = self._convertToGeneNames(reactionName) if geneNames == None: return reactionName iGeneNames = set(geneNames) for geneName in geneNames: if geneClusters != None: if geneName in geneClusters.keys(): for iGeneName in geneClusters[geneName]: iGeneNames.add(iGeneName) geneTag = '' for iGeneName in iGeneNames: geneTag = geneTag + " " + iGeneName geneTag = geneTag[1:] return geneTag def getEnzymeControlMap(self): result = {} for (key,values) in self.controlMap.items(): for v in values: if v not in result.keys(): result[v] = set() result[v].add(key) return result def getControlsForNames(self,variableNames): result = set() for name in variableNames: geneNames = self._convertToGeneNames(name) if geneNames != None: for gName in geneNames: result.add(gName) return result def printGeneObjective(self,iObjective,geneClusters=None): iGeneObjective = {} iOtherObjective = {} igControl = {} for rxnName in iObjective.keys(): rControl = iObjective[rxnName] if rxnName not in self.controlMap.keys(): iOtherObjective[rxnName] = rControl else: geneTag = self._convertToGeneTag(rxnName, geneClusters) if geneTag not in iGeneObjective: igControl[geneTag] = [] igControl[geneTag].append(rControl) for k in igControl.keys(): iGeneObjective[k] = mean(igControl[k]) return (iGeneObjective,iOtherObjective)
class Report: """ Creates a 2nd matrix of data. Largely designed for reading and writing from files. """ def __init__(self, blank='N/A'): self.data = SecondOrderCache() self.blank = blank def __setitem__(self,key,value): self.addColumnHash(key,value) def __getitem__(self,key): result = {} for k in self.data.keys(): if k[1] == key: value = self.data[k] result[k[0]] = value return result def setEmpty(self,blank): """ @param blank: the value to be used for blank fields @type blank: string self.blank = blank """ self.blank = blank def add(self,rowName,columnName,value): """ @param rowName: name of row @type rowName: string @param columnName: name of column @type columnName: string @param value: the value to be added @type value: string """ self.data.addValue(rowName,columnName,str(value)) return None def addElement(self,rowName,columnName,value): return self.add(rowName,columnName,value) def get(self,rowName,columnName): """ @param rowName: name of row @type rowName: string @param columnName: name of column @type columnName: string @param value: the value to be added @type value: string """ result = self.data.getValue(rowName,columnName) return result def getElement(self,rowName,columnName): return self.get(rowName,columnName) def addPairList(self,data): for ((rowName,columnName),value) in data.items(): self.addElement(rowName,columnName,value) return None def addColumnHash(self,columnName,columnMap): """ @param columnName: name of column to be added @type columnName: string @param columnMap: values to be added @type columnMap: dict """ if columnMap == None: return None for rowName in columnMap.keys(): value = columnMap[rowName] if type(value) != type(""): value = str(value) self.data.addValue(rowName,columnName,columnMap[rowName]) return None def extend(self,report): """ @param report: report to be appendent to this one @type report: Report """ self.data.extend(report.data) def getRowNames(self): """ @return: the names of all the rows in the report @rtype: string[] """ return self.data.getRowKeys() def returnRowNames(self): return self.getRowNames() def getColumnNames(self): """ @return: the names of all the columns in the report @rtype: string[] """ return self.data.getColumnKeys() def returnColumnNames(self): return self.getColumnNames() def returnRowArray(self,rowName): """ @param rowName: the name of the row of interest @type rowName: string @return: the values of the row in order from the report @rtype: string[] """ columnNames = self.returnColumnNames() result = [] for name in columnNames: value = self.data.getValue(rowName,name) if value == None: result.append(self.blank) else: result.append(value) return result def getRow(self,name): ''' @param name: name of column to return @return: the values of column of report @rtype: {string:string} ''' result = {} if name not in self.getRowNames(): return None for columnName in self.getColumnNames(): value = self.data.getValue(name,columnName) if value == None: continue else: result[columnName] = value return result def getColumn(self,name): ''' @param name: name of column to return @return: the values of column of report @rtype: {string:string} ''' result = {} if name not in self.getColumnNames(): return None for rowName in self.getRowNames(): value = self.data.getValue(rowName,name) if value == None: continue else: result[rowName] = value return result