def run(self, reactions, matrix, lb, ub, fbaParams): """ analyze objective function, and solve the LP/QP/NLP Keyword arguments: reactions -- dictionary of all reactions { name : matrix column } or { name : tuple of indices } for split fluxes matrix -- the stoichiometric matrix of the metabolic network lb -- list of lower bounds (indexed like matrix columns) ub -- list of upper bounds (indexed like matrix columns) fbaParams -- optimization parameters (incl. objective function) Returns: obj_value, solution obj_value -- optimal value of objective function solution -- a solution where the objective function assumes obj_value """ maxmin, objStr, numIter = (fbaParams.maxmin, fbaParams.objStr, fbaParams.numIter) # Multiply by -1 for maximization maxmin_factor = -1. if maxmin == True else 1. nCols = len(lb) threshold = 0.95 nReactions = len(lb) # Get additional linear equality and inequality constraints try: Aeq, beq, Aineq, bineq = self.makeConstraintMatrices( matrix, *ParamParser.linConstraintsToVectors(fbaParams.linConstraints, reactions, nCols)) except ValueError: # If any linear constraint is contradictory, return empty solution return 0., [] try: objective = ParamParser.convertObjFuncToLinVec( objStr, reactions, nCols, maxmin) except Exception, strerror: print( "Error while trying to build coefficient vector of " "linear objective function:") print strerror exit()
def checkLinConstraints(solution, model, linConstraints): """ check if flux distribution 'solution' violates any of the given linear equality and inequality constraints Keyword arguments: solution -- flux distribution (MetabolicFlux object) model -- MetabolicModel (for generating flux and coefficient vectors) linConstraints -- list of LinearConstraint objects Returns: eqErr, ineqErr eqErr -- dict {index in linConstraints : deviation} for each equality constraint ineqErr -- dict {index in linConstraints : deviation} for each inequality constraint - if deviation is negative, constraint is satisfied """ solutionVec = solution.getVecOrderedByModel(model) eqs, ineqs = ParamParser.linConstraintsToVectors(linConstraints, model.reactionDict) eqIndex, ineqIndex = [], [] for i in range(len(linConstraints)): if linConstraints[i].isEq: eqIndex.append(i) else: ineqIndex.append(i) eqErr, ineqErr = {}, {} if eqs: A = [row[0] for row in eqs] b = [row[1] for row in eqs] dotVec = dot(A, solutionVec) for i in range(len(b)): eqErr[eqIndex[i]] = abs(dotVec[i] - b[i]) if ineqs: A = [row[0] for row in ineqs] b = [row[1] for row in ineqs] dotVec = dot(A, solutionVec) for i in range(len(b)): ineqErr[ineqIndex[i]] = dotVec[i] - b[i] return eqErr, ineqErr
def runOnModel(self, model, fbaParams, threshp=.95, objFuncVal=None, rmDeadEnds=True): """ perform metabolite flux minimization on the given model with objective value <= threshp * maximum Keyword arguments: model -- the MetabolicModel fbaParams -- FBA parameters threshp -- threshold percentage (objective_value <= thresp*maximum) objFuncVal -- FBA optimum for objective function (optional) rmDeadEnds -- if True, remove all reactions with dead ends before analysis (faster and gives an optimal solution, as well) Returns: minVec, dimReduced minVec -- list of minimum values, indexed like metabolites dimReduced -- pair (nRows, nColumns) with dimensions of reduced matrix """ if rmDeadEnds: deadReactions = model.findDeadEnds(True)[1] modelRed = model.getSubModelByExcludeList(deadReactions) cbz = model.canBeZero(deadReactions) nonZeroDeadEnds = [ deadReactions[i] for i in range(len(deadReactions)) if not cbz[i] ] if nonZeroDeadEnds: print( "The following blocked reactions are constrained to a " "non-zero flux:\n " + "\n ".join(nonZeroDeadEnds) + "\nThe problem is infeasible.") return [], array(modelRed.getStoichiometricMatrix()).shape else: modelRed = model matrix = array(modelRed.getStoichiometricMatrix()) dimReduced = matrix.shape lb, ub = modelRed.getBounds() # Split fluxes into non-negative components matrixSplit, reactionsSplit, lbSplit, ubSplit = \ FbAnalyzer.splitFluxes(matrix, modelRed.getReactionNames(), lb, ub) # Build (negative) objective function vector for split fluxes objective = ParamParser.convertObjFuncToLinVec(fbaParams.objStr, reactionsSplit, len(lbSplit), fbaParams.maxmin) maxmin_factor = -1. if fbaParams.maxmin else 1. # If the optimum of the objective function is not given, perform FBA if objFuncVal is None: fba = FbAnalyzer(self.solver) objFuncVal, sFlux = fba.run(reactionsSplit, matrixSplit, lbSplit, ubSplit, fbaParams) if len(sFlux) == 0: return [], dimReduced # Use negative threshold (objective value >= threshold is equivalent to # -objective value <= -threshold, and objective already has coefficient # -1 due to maximization) threshold = maxmin_factor * objFuncVal * threshp print "obj func. opt:", objFuncVal print "Threshold: ", threshold try: eqs, ineqs = ParamParser.linConstraintsToVectors( fbaParams.linConstraints, modelRed.reactionDict, len(lbSplit)) except ValueError, e: # If any linear constraint is contradictory, report error print "Optimization not possible due to contradictory constraints:" print " " + e exit()
def runOnModel(self, model, wtSolution, linConstraints=[], numIter=1, weights=None, blockedReactions=[]): """ construct and solve the quadratic optimization problem - this function runs directly on a MetabolicModel and a MetabolicFlux Keyword arguments: model -- the MetabolicModel wtSolution -- FBA solution for the wildtype (given as MetabolicFlux) linConstraints -- list of LinearConstraint objects numIter -- number of iterations of NLP to perform weights -- weight vector for weighted MOMA (None -> perform regular MOMA, else: weight flux i with weights[i]) blockedReactions -- remove the given blocked reactions before analysis (faster and gives an optimal solution, as well) Returns: distance, solution, status, dimReduced distance -- minimum possible distance from wtSolution with the given matrix & constraints solution -- a solution with minimal distance to wtSolution (as MetabolicFlux) status -- SolverStatus after optimization dimReduced -- pair (nRows, nColumns) with dimensions of reduced matrix """ if blockedReactions: modelRed = model.getSubModelByExcludeList(blockedReactions) cbz = model.canBeZero(blockedReactions) nonZeroDeadEnds = [ blockedReactions[i] for i in range(len(blockedReactions)) if not cbz[i] ] if nonZeroDeadEnds: print( "The following blocked reactions are constrained to a " "non-zero flux:\n " + "\n ".join(nonZeroDeadEnds) + "\nThe problem is infeasible.") return (nan, MetabolicFlux(), SolverStatus.PRIM_INFEAS, array(modelRed.getStoichiometricMatrix()).shape) reactionsRed = set(modelRed.getReactionNames()) if weights is None: weightsRed = None else: weightsRed = [0.] * len(modelRed) for rea in wtSolution: if rea in reactionsRed: weightsRed[modelRed.reactionDict[rea]] = \ weights[model.reactionDict[rea]] weightsRed = array(weightsRed) else: modelRed = model weightsRed = weights matrix = array(modelRed.getStoichiometricMatrix()) dimReduced = matrix.shape lb, ub = map(array, modelRed.getBounds()) try: eqs, ineqs = ParamParser.linConstraintsToVectors( linConstraints, modelRed.reactionDict) except ValueError: # If any linear constraint is contradictory, return empty solution return (nan, MetabolicFlux(), SolverStatus.PRIM_INFEAS, array(modelRed.getStoichiometricMatrix()).shape) distance, solution, status = self.run( matrix, lb, ub, wtSolution.getVecOrderedByModel(modelRed), eqs, ineqs, numIter, weightsRed) flux = MetabolicFlux(modelRed, solution) # Add removed reactions with flux 0. and original bounds to solution if len(modelRed) != len(model) and solution != []: reactionsRed = set(modelRed.getReactionNames()) for rea in model: if rea.name not in reactionsRed: flux.fluxDict[rea.name] = 0. flux.boundsDict[rea.name] = (rea.lb, rea.ub) return distance, flux, status, dimReduced
else: obj_value, sFlux = fba.run(modelRed.getReactionNames(), matrix, lb, ub, fbaParams) if len(sFlux) == 0: lbFull, ubFull = map(array, model.getBounds()) return [], sFlux, lbFull, ubFull, dimReduced # Use negative threshold (objective value >= threshold is equivalent to # -objective value <= -threshold, and objective already has coefficient # -1 due to maximization) threshold = maxmin_factor * obj_value * threshp print "obj func. opt:", obj_value print "Threshold: ", threshold try: eqs, ineqs = ParamParser.linConstraintsToVectors( fbaParams.linConstraints, modelRed.reactionDict) except ValueError, e: # If any linear constraint is contradictory, report error print "Optimization not possible due to contradictory constraints:" print " " + e exit() minmax = self.run(objective, matrix, lb, ub, threshold, eqs, ineqs) # Add removed reactions with min = max = 0. to minmax and solution if len(modelRed) != len(model): minmaxFull, sFluxFull = [], [] reactionsRed = set(modelRed.getReactionNames()) for rea in model: if rea.name in reactionsRed:
def runWithTotalFluxMinimization(self, reactions, matrix, lb, ub, fbaParams, start_value_total=None, tolerance_total=EPSILON, tolerance_obj=EPSILON): """ perform FBA with LP iteratively to find a flux distribution with optimal value of objective function value and minimum total flux This only works with non-negative flux variables, i.e. split fluxes! The objective function and all constraints must be linear. This function limits the total flux to a parameter and then iteratively fits this parameter until either the change in total flux is less than tolerance_total or the change in the objective function is less than tolerance_obj Keyword arguments: reactions -- dictionary { name : tuple of indices } for split fluxes matrix -- the stoichiometric matrix of the metabolic network lb -- list of lower bounds (indexed like matrix columns) ub -- list of upper bounds (indexed like matrix columns) fbaParams -- optimization parameters (incl. objective function) start_value_total -- initial flux limit (if None: set to len(reactions)) tolerance_total -- stopping criterion: change in flux limit below this tolerance_obj -- stopping criterion: change in objective function value below this Returns: limit, obj_value, solution, nSteps limit -- limit of total flux obj_value -- optimal value of objective function solution -- a solution where the objective function assumes obj_value, obtained with limited total flux nSteps -- number of steps until optimal limit was found """ maxmin, objStr = (fbaParams.maxmin, fbaParams.objStr) # Multiply by -1 for maximization maxmin_factor = -1. if maxmin == True else 1. nCols = len(lb) for value in lb: if value < 0.: print( "Error: Minimization of total flux requires non-negative" " flux variables.") exit() # Get additional linear equality and inequality constraints try: Aeq, beq, Aineq, bineq = self.makeConstraintMatrices( matrix, *ParamParser.linConstraintsToVectors(fbaParams.linConstraints, reactions, nCols)) except ValueError: # If any linear constraint is contradictory, return empty solution return 0., [] # Prepare additional constraint: total flux < threshold if Aineq is None: Aineq, bineq = array([[1.] * nCols]), [] else: # Aineq has extra line for total flux Aineq = vstack((Aineq, [[1.] * nCols])) # Convert bineq to list (allows easy addition of extra value) bineq = list(bineq) # Build linear objective function (given as coefficient vector) try: objective = ParamParser.convertObjFuncToLinVec( objStr, reactions, nCols, maxmin) except Exception, strerror: print( "Error while trying to build coefficient vector of " "linear objective function:") print strerror exit()
def run(self, reactions, matrix, lb, ub, fbaParams): """ analyze objective function, and solve the LP/QP/NLP Keyword arguments: reactions -- dictionary of all reactions { name : matrix column } or { name : tuple of indices } for split fluxes matrix -- the stoichiometric matrix of the metabolic network lb -- list of lower bounds (indexed like matrix columns) ub -- list of upper bounds (indexed like matrix columns) fbaParams -- optimization parameters (incl. objective function) Returns: obj_value, solution obj_value -- optimal value of objective function solution -- a solution where the objective function assumes obj_value """ maxmin, objStr, numIter = (fbaParams.maxmin, fbaParams.objStr, fbaParams.numIter) # Multiply by -1 for maximization maxmin_factor = -1. if maxmin == True else 1. nCols = len(lb) # Get additional linear equality and inequality constraints try: Aeq, beq, Aineq, bineq = self.makeConstraintMatrices(matrix, *ParamParser.linConstraintsToVectors(fbaParams.linConstraints, reactions, nCols)) except ValueError: # If any linear constraint is contradictory, return empty solution return 0., [] if objStr.lower().startswith("per_flux"): # special nonlinear objective function: single flux per flux unit # e.g. "per_flux(Biomass)" try: str_perflux, str_flux = objStr.split('(', 2) except ValueError: print ("Error in scenario file in objective function " "definition.\nPER_FLUX syntax: " "PER_FLUX(<reaction_name>)") exit() if str_perflux.strip().lower() != "per_flux": print ("Error: Objective function '%s' is not a reaction name " "or a PER_FLUX definition.\n" "Currently, objective function must be a linear " "combination of reaction fluxes or PER_FLUX(<reaction>)." % objStr) exit() rea_name = str_flux.split(')', 1)[0].strip() try: biomass_index = reactions[rea_name][0] except TypeError: biomass_index = reactions[rea_name] obj_func = lambda x : -biomass_per_flux(x, biomass_index) if numIter < 0: numIter = FbaParam.DEFAULT_NUMITER try: ps = NonLinearProblem(Aeq, beq, Aineq, bineq, lb, ub, self.solver) ps.setObjective(obj_func) ps.setObjGrad(lambda x : neg_grad_biomass_per_flux(x, biomass_index)) if fbaParams.nlc != []: ps.setNonlinearConstraints(fbaParams.nlc) ps.setNlcGrad(fbaParams.nlc_grad) spIter = StartPointIterator(nCols, numIter) spIter.setRange(-1., 1.) ps.setStartPointIterator(spIter) except ValueError, strerror: print strerror exit()