def gurobi_qp(self, qp): n = qp.H.shape[1] model = Model() x = { i: model.addVar( vtype=GRB.CONTINUOUS, name='x_%d' % i, lb=qp.lb, ub=qp.ub) for i in range(n) } model.update() obj = QuadExpr() rows, cols = qp.H.nonzero() for i, j in zip(rows, cols): obj += 0.5 * x[i] * qp.H[i, j] * x[j] for i in range(n): obj += qp.f[i] * x[i] model.setObjective(obj, GRB.MINIMIZE) if qp.A is not None: A_nonzero_rows = get_nonzero_rows(qp.A) for i, row in A_nonzero_rows.items(): model.addConstr(quicksum(qp.A[i, j] * x[j] for j in row) <= qp.b[i]) if qp.Aeq is not None: A_nonzero_rows = get_nonzero_rows(qp.Aeq) for i, row in A_nonzero_rows.items(): model.addConstr(quicksum(qp.Aeq[i, j] * x[j] for j in row) == qp.beq[i]) model.optimize() self.solution = empty(n) for i in range(n): self.solution[i] = model.getVarByName('x_%d' % i).x
def read_assignment(slots: typing.Collection[Slot], flights: typing.Collection[Flight], model: grb.Model): assignments = {} for f in flights: for s in slots: if isfeasible(slot=s, flight=f): var = model.getVarByName(assignvarname(slot=s, flight=f)) if abs(var.getAttr("X") - 1.0) < 0.001: assignments[f] = s return assignments
def get_new_flight_schedule(flights: typing.Iterable[Flight], n_slots: int, ip_model: gurobipy.Model) -> \ typing.Set[Flight]: new_flights = set() for f in flights: found = False for t in range(0, n_slots): sched_var = ip_model.getVarByName('F' + str(f.flight_id) + 'T' + str(t)) if sched_var is not None and abs(sched_var.getAttr("X") - 1.0) < 0.0001: if not found: found = True new_flights.add(f.copy_reschedule(t)) else: print("Error: flight is double scheduled") if not found: remove_var = ip_model.getVarByName('R' + str(f.flight_id)) if remove_var is None or remove_var.getAttr("X") < 0.000001: print("Error: flight is neither scheduled nor removed") return new_flights
def reset_delay_costs(model: gurobipy.Model, delay_costs: typing.Mapping[typing.Tuple[int, int], float], move_costs: typing.Mapping[typing.Tuple[int, int], float], flights: typing.Iterable[flightsched.Flight], n_slots: int): for f in flights: for t in range(0, n_slots): next_reschedule_var = model.getVarByName('F' + str(f.flight_id) + 'T' + str(t)) if next_reschedule_var is not None: next_reschedule_var.setAttr(gurobipy.GRB.attr.Obj, -1 * (move_costs[f.flight_id, t] + delay_costs[f.flight_id, t])) model.update() return
def find_feasible_start(n_colors, h, statespace, conflicts, verbose=False): model = Model("TimeFeasibility") p = len(h) y = {} # y[i,k] = if color i gets slot l for i in range(n_colors): for l in range(p): y[i,l] = model.addVar(vtype=GRB.BINARY, name="y_%s_%s" % (i,l)) model.update() # Building constraints... # c1: all get one for i in range(n_colors): model.addConstr( quicksum([ y[i, l] for l in range(p) ]) == 1, "c1") # c2: each slot needs to be used tops once for l in range(p): model.addConstr( quicksum([ y[i, l] for i in range(n_colors) ]) <= 1, "c2") ### c3: statespace constraints for i in range(n_colors): #print l, h[l], i, [s for s in statespace] model.addConstr( quicksum([ y[i, l] for l in range(p) if h[l] not in statespace[i] ]) == 0, "c3") # objective: minimize conflicts #obj = quicksum([ y[i,l] * y[j,l] for l in range(p) for i in range(n_colors) for j in range(i+1, n_colors) ]) obj = quicksum([ sum(y[i,l] for i in range(n_colors)) for l in range(p) ]) #obj = 0 model.setObjective(obj, GRB.MINIMIZE) if not verbose: model.params.OutputFlag = 0 model.optimize() # return best room schedule color_schedule = [] if model.status == GRB.INFEASIBLE: return color_schedule for i in range(n_colors): for l in range(p): v = model.getVarByName("y_%s_%s" % (i,l)) if v.x == 1: color_schedule.append(h[l]) break return color_schedule
class GurobiSolver(Solver): """ Implements the solver interface using gurobipy. """ def __init__(self): Solver.__init__(self) self.problem = GurobiModel() def __getstate__(self): tmp_file = tempfile.mktemp(suffix=".lp") self.problem.update() self.problem.write(tmp_file) cplex_form = open(tmp_file).read() repr_dict = {'var_ids': self.var_ids, 'constr_ids': self.constr_ids, 'cplex_form': cplex_form} return repr_dict def __setstate__(self, repr_dict): tmp_file = tempfile.mktemp(suffix=".lp") open(tmp_file, 'w').write(repr_dict['cplex_form']) self.problem = read(tmp_file) self.var_ids = repr_dict['var_ids'] self.constr_ids = repr_dict['constr_ids'] def add_variable(self, var_id, lb=None, ub=None, vartype=VarType.CONTINUOUS, persistent=True, update_problem=True): """ Add a variable to the current problem. Arguments: var_id : str -- variable identifier lb : float -- lower bound ub : float -- upper bound vartype : VarType -- variable type (default: CONTINUOUS) persistent : bool -- if the variable should be reused for multiple calls (default: true) update_problem : bool -- update problem immediately (default: True) """ lb = lb if lb is not None else -GRB.INFINITY ub = ub if ub is not None else GRB.INFINITY map_types = {VarType.BINARY: GRB.BINARY, VarType.INTEGER: GRB.INTEGER, VarType.CONTINUOUS: GRB.CONTINUOUS} if var_id in self.var_ids: var = self.problem.getVarByName(var_id) var.setAttr('lb', lb) var.setAttr('ub', ub) var.setAttr('vtype', map_types[vartype]) else: self.problem.addVar(name=var_id, lb=lb, ub=ub, vtype=map_types[vartype]) self.var_ids.append(var_id) if not persistent: self.temp_vars.add(var_id) if update_problem: self.problem.update() def add_constraint(self, constr_id, lhs, sense='=', rhs=0, persistent=True, update_problem=True): """ Add a variable to the current problem. Arguments: constr_id : str -- constraint identifier lhs : list [of (str, float)] -- variables and respective coefficients sense : {'<', '=', '>'} -- default '=' rhs : float -- right-hand side of equation (default: 0) persistent : bool -- if the variable should be reused for multiple calls (default: True) update_problem : bool -- update problem immediately (default: True) """ grb_sense = {'=': GRB.EQUAL, '<': GRB.LESS_EQUAL, '>': GRB.GREATER_EQUAL} if constr_id in self.constr_ids: constr = self.problem.getConstrByName(constr_id) self.problem.remove(constr) expr = quicksum([coeff * self.problem.getVarByName(r_id) for r_id, coeff in lhs if coeff]) self.problem.addConstr(expr, grb_sense[sense], rhs, constr_id) self.constr_ids.append(constr_id) if not persistent: self.temp_constrs.add(constr_id) if update_problem: self.problem.update() def remove_variable(self, var_id): """ Remove a variable from the current problem. Arguments: var_id : str -- variable identifier """ if var_id in self.var_ids: self.problem.remove(self.problem.getVarByName(var_id)) self.var_ids.remove(var_id) def remove_constraint(self, constr_id): """ Remove a constraint from the current problem. Arguments: constr_id : str -- constraint identifier """ if constr_id in self.constr_ids: self.problem.remove(self.problem.getConstrByName(constr_id)) self.constr_ids.remove(constr_id) def update(self): """ Update internal structure. Used for efficient lazy updating. """ self.problem.update() def solve_lp(self, objective, model=None, constraints=None, get_shadow_prices=False, get_reduced_costs=False): """ Solve an LP optimization problem. Arguments: objective : dict (of str to float) -- reaction ids in the objective function and respective coefficients, the sense is maximization by default model : ConstraintBasedModel -- model (optional, leave blank to reuse previous model structure) constraints : dict (of str to (float, float)) -- environmental or additional constraints (optional) get_shadow_prices : bool -- return shadow price information if available (optional, default: False) get_reduced_costs : bool -- return reduced costs information if available (optional, default: False) Returns: Solution """ return self._generic_solve(None, objective, GRB.MAXIMIZE, model, constraints, get_shadow_prices, get_reduced_costs) def solve_qp(self, quad_obj, lin_obj, model=None, constraints=None, get_shadow_prices=False, get_reduced_costs=False): """ Solve an LP optimization problem. Arguments: quad_obj : dict (of (str, str) to float) -- map reaction pairs to respective coefficients lin_obj : dict (of str to float) -- map single reaction ids to respective linear coefficients model : ConstraintBasedModel -- model (optional, leave blank to reuse previous model structure) constraints : dict (of str to (float, float)) -- overriding constraints (optional) get_shadow_prices : bool -- return shadow price information if available (default: False) get_reduced_costs : bool -- return reduced costs information if available (default: False) Returns: Solution """ return self._generic_solve(quad_obj, lin_obj, GRB.MINIMIZE, model, constraints, get_shadow_prices, get_reduced_costs) def _generic_solve(self, quad_obj, lin_obj, sense, model=None, constraints=None, get_shadow_prices=False, get_reduced_costs=False): if model: self.build_problem(model) problem = self.problem if constraints: old_constraints = {} for r_id, (lb, ub) in constraints.items(): lpvar = problem.getVarByName(r_id) old_constraints[r_id] = (lpvar.lb, lpvar.ub) lpvar.lb = lb if lb is not None else -GRB.INFINITY lpvar.ub = ub if ub is not None else GRB.INFINITY problem.update() #create objective function quad_obj_expr = [q * problem.getVarByName(r_id1) * problem.getVarByName(r_id2) for (r_id1, r_id2), q in quad_obj.items() if q] if quad_obj else [] lin_obj_expr = [f * problem.getVarByName(r_id) for r_id, f in lin_obj.items() if f] if lin_obj else [] obj_expr = quicksum(quad_obj_expr + lin_obj_expr) problem.setObjective(obj_expr, sense) problem.update() # from datetime import datetime # self.problem.write("problem_{}.lp".format(str(datetime.now()))) #run the optimization problem.optimize() status = status_mapping[problem.status] if problem.status in status_mapping else Status.UNKNOWN message = str(problem.status) if status == Status.OPTIMAL: fobj = problem.ObjVal values = OrderedDict([(r_id, problem.getVarByName(r_id).X) for r_id in self.var_ids]) #if metabolite is disconnected no constraint will exist shadow_prices = OrderedDict([(m_id, problem.getConstrByName(m_id).Pi) for m_id in self.constr_ids if problem.getConstrByName(m_id)]) if get_shadow_prices else None reduced_costs = OrderedDict([(r_id, problem.getVarByName(r_id).RC) for r_id in self.var_ids]) if get_reduced_costs else None solution = Solution(status, message, fobj, values, shadow_prices, reduced_costs) else: solution = Solution(status, message) #reset old constraints because temporary constraints should not be persistent if constraints: for r_id, (lb, ub) in old_constraints.items(): lpvar = problem.getVarByName(r_id) lpvar.lb, lpvar.ub = lb, ub problem.update() return solution
def find_ranking(comparisons, verbose=False): """ Find the least changes to a set of comparisons so that they are consistent (transitive), it returns a topological ranking. comparisons A dictionary with tuple keys in the form of (i, j), values are scalars indicating the probability of i >= j. It is assumed that comparisons are symmetric. The only way this function it can handle '=' is by setting the probability to 0.5. In this case the MILP can treat it as > or < (but not as =). verbose Whether to print gurobi's progress. Returns: A tuple of size two: 0) Ranking derived from topological sort (list of ranks in order of nodes); 1) Sum of absolute changes to the comparisons. """ # remove unnecessary variables comparisons = {(i, j) if i < j else (j, i): value if i < j else 1 - value for (i, j), value in comparisons.items()} nodes = np.unique([i for ij in comparisons.keys() for i in ij]) # define variables model = Model('comparison') model.setParam('OutputFlag', verbose) values = np.fromiter(comparisons.values(), dtype=float) assert values.max() <= 1 and values.min() >= 0 # variables to encode the error of comparisons E_ij = model.addVars(comparisons.keys(), name='e_ij', vtype=GRB.CONTINUOUS, ub=1.0 - values, lb=-values) # variable to encode hard choice of >= and <= Z_ij = model.addVars(comparisons.keys(), name='z_ij', vtype=GRB.BINARY) # variables to help with transitivity in non-fully connected graphs R_i = model.addVars(nodes, name='r_i', vtype=GRB.CONTINUOUS, lb=0, ub=len(nodes)) # variables to emulate abs T_ij_pos = {} T_ij_neg = {} index = (values != 1) & (values != 0) T_ij_pos = model.addVars( (ij for ij, value in comparisons.items() if value not in [0.0, 1.0]), vtype=GRB.CONTINUOUS, name='T_ij_pos', lb=0, ub=1 - values[index]) T_ij_neg = model.addVars( (ij for ij, value in comparisons.items() if value not in [0.0, 1.0]), vtype=GRB.CONTINUOUS, name='T_ij_neg', lb=0, ub=values[index]) model.update() # emulate abs for non-binary comparisons: E_ij = T_ij_pos - T_ij_neg model.addConstrs((E_ij[ij] == T_ij_pos[ij] - T_ij_neg[ij] for ij in T_ij_pos), 'E_ij = T_ij_pos - T_ij_neg') # hard decision of >= and <=: z_ij == 1 <-> i > j model.addConstrs((E_ij[ij] + comparisons[ij] - 0.5 >= -1 + z_ij for ij, z_ij in Z_ij.items()), 'z_ij_upper_bound') model.addConstrs((E_ij[ij] + comparisons[ij] - 0.5 <= z_ij for ij, z_ij in Z_ij.items()), 'z_ij_lower_bound') # transitivity for (i, j), a in Z_ij.items(): for k in nodes: j_, k_ = j, k if j > k: j_, k_ = k, j b = Z_ij.get((j_, k_), None) if b is None: continue elif j_ != j: b = 1 - b i_, k_ = i, k if i > k: i_, k_ = k, i c = Z_ij.get((i_, k_), None) if c is None: continue elif i_ != i: c = 1 - c # a <= b and b <= c -> a <= c model.addLConstr(a + b, GRB.LESS_EQUAL, 1 + c, f'transitivity_ge_{i},{j},{k}') # a >= b and b >= c -> a >= c model.addLConstr(a + b, GRB.GREATER_EQUAL, c, f'transitivity_le_{i},{j},{k}') # transitivity helper (for not-fully connected graphs) # also provides a latent rank big_m = len(nodes) model.addConstrs(((1 - z_ij) * big_m + R_i[i] >= R_i[j] + 1 for (i, j), z_ij in Z_ij.items()), 'rank_transitivity_larger') model.addConstrs( (z_ij * big_m + R_i[j] >= R_i[i] + 1 for (i, j), z_ij in Z_ij.items()), 'rank_transitivity_smaller') # objective function objective = LinExpr() for ij, value in comparisons.items(): if value == 1.0: objective += -E_ij[ij] elif value == 0.0: objective += E_ij[ij] else: objective += T_ij_pos[ij] + T_ij_neg[ij] model.setObjective(objective, GRB.MINIMIZE) # solve model.optimize() # verify abs emulation: one T_ij has to be 0 for ij, value in T_ij_pos.items(): assert value.X == 0 or T_ij_neg[ij] == 0, \ f'T_{ij} pos {value.X} neg {T_ij_neg[ij]}' # find minimal Rs model_ = Model('comparison') model_.setParam('OutputFlag', verbose) R_i = model_.addVars(nodes, name='r_i', vtype=GRB.CONTINUOUS, lb=0, ub=len(nodes)) for (i, j), z_ij in Z_ij.items(): if z_ij.x == 1: model_.addConstr(R_i[i] >= R_i[j] + 1) else: model_.addConstr(R_i[j] >= R_i[i] + 1) model_.setObjective(R_i.sum(), GRB.MINIMIZE) model_.optimize() return [model_.getVarByName(f'r_i[{i}]').X for i in range(len(nodes))], \ model.objVal
class SQModel(object): ''' classdocs ''' # Private model object __model = [] # Private model variables __z0 = {} __z = {} __q = {} # Private model parameters __BackupCapacity = {} __bBackupLink = {} __links = [] __nodes = [] __capacity = [] __epsilon = 1 __impSample = {} __N = 1 def __init__(self,imp_samp,nodes,links,capacity,epsilon,N,backup_link,link_capacity): ''' Constructor ''' self.__links = links self.__nodes = nodes self.__capacity = capacity self.__epsilon = epsilon self.__N = N self.__loadModel(imp_samp,backup_link,link_capacity) def __loadModel(self,imp_samp, backup_link,link_capacity): # Create optimization model self.__model = Model('Backup') for i,j in self.__links: for k in range(self.__N): self.__z[k,i,j] = self.__model.addVar(lb=0,name='z[%s][%s][%s]' % (k,i,j)) self.__model.update() for i,j in self.__links: self.__z0[i,j] = self.__model.addVar(lb=-GRB.INFINITY,name='z0[%s][%s]' %(i,j)) self.__model.update() for i,j in self.__links: self.__q[i,j] = self.__model.addVar(lb=-GRB.INFINITY,name='q[%s][%s]' %(i,j)) self.__model.update() self.__model.modelSense = GRB.MINIMIZE self.__model.setObjective(quicksum(self.__q[i,j] for i,j in self.__links)) self.__model.update() #------------------------------------------------------------------------# # Constraints definition # # # # # #------------------------------------------------------------------------# # Buffer probability I for i,j in self.__links: self.__model.addConstr(self.__z0[i,j] + 1/(self.__N*self.__epsilon)*quicksum(self.__z[k,i,j]*imp_samp[k] for (k) in range(self.__N)) <= self.__q[i,j],'[CONST]Buffer_Prob_I[%s][%s]'%(i,j)) self.__model.update() # Link capacity constraints for i,j in self.__links: for k in range(self.__N): self.__model.addConstr((quicksum(backup_link[i,j,s,d]*self.__capacity[k,s,d] for s,d in self.__links) - link_capacity[i,j] - self.__z0[i,j]) <= self.__z[k,i,j],'[CONST]Buffer_Prob_II[%s][%s][%s]' % (k,i,j)) self.__model.update() # Link capacity constraints for i,j in self.__links: for k in range(self.__N): self.__model.addConstr(self.__z[k,i,j] >= 0,'[CONST]Buffer_Prob_III[%s][%s][%s]' % (k,i,j)) self.__model.update() def optimize(self,MipGap, TimeLimit, LogLevel = None): self.__model.write('quantile.lp') if MipGap != None: self.__model.params.MIPGap = MipGap if TimeLimit != None: self.__model.params.timeLimit = TimeLimit # Compute optimal solution self.__model.optimize() # Print solution if self.__model.status == GRB.Status.OPTIMAL: #SuperQuantileSolution = self.__model.getAttr('x', self.__z0) SuperQuantileSolution = {} OptimalZnot = {} for i,j in self.__links: name='q[%s][%s]'%(i,j) v = self.__model.getVarByName(name) SuperQuantileSolution[i,j]=v.x name='z0[%s][%s]'%(i,j) v = self.__model.getVarByName(name) OptimalZnot[i,j]=v.x if LogLevel == 1: for v in self.__model.getVars(): print('%s %g' % (v.varName, v.x)) else: print('Optimal value not found!\n') SuperQuantileSolution = {} OptimalZnot={} return SuperQuantileSolution, OptimalZnot def reset(self): ''' Reset model solution ''' self.__model.reset()
class GurobiSolver(Solver): """ Implements the gurobi solver interface. """ def __init__(self, model=None, env=None): Solver.__init__(self) if env: self.problem = GurobiModel(env=env) else: self.problem = GurobiModel() self.set_logging(False) self.set_parameters(default_parameters) if model: self.build_problem(model) def add_variable(self, var_id, lb=-inf, ub=inf, vartype=VarType.CONTINUOUS, update=True): """ Add a variable to the current problem. Arguments: var_id (str): variable identifier lb (float): lower bound ub (float): upper bound vartype (VarType): variable type (default: CONTINUOUS) update (bool): update problem immediately (default: True) """ lb = infinity_fix(lb) ub = infinity_fix(ub) if var_id in self.var_ids: var = self.problem.getVarByName(var_id) var.setAttr('lb', lb) var.setAttr('ub', ub) var.setAttr('vtype', vartype_mapping[vartype]) else: self.problem.addVar(name=var_id, lb=lb, ub=ub, vtype=vartype_mapping[vartype]) self.var_ids.append(var_id) if update: self.problem.update() def add_constraint(self, constr_id, lhs, sense='=', rhs=0, update=True): """ Add a constraint to the current problem. Arguments: constr_id (str): constraint identifier lhs (dict): variables and respective coefficients sense (str): constraint sense (any of: '<', '=', '>'; default '=') rhs (float): right-hand side of equation (default: 0) update (bool): update problem immediately (default: True) """ grb_sense = { '=': GRB.EQUAL, '<': GRB.LESS_EQUAL, '>': GRB.GREATER_EQUAL } if constr_id in self.constr_ids: constr = self.problem.getConstrByName(constr_id) self.problem.remove(constr) expr = quicksum(coeff * self.problem.getVarByName(r_id) for r_id, coeff in lhs.items() if coeff) self.problem.addConstr(expr, grb_sense[sense], rhs, constr_id) self.constr_ids.append(constr_id) if update: self.problem.update() def remove_variable(self, var_id): """ Remove a variable from the current problem. Arguments: var_id (str): variable identifier """ self.remove_variables([var_id]) def remove_variables(self, var_ids): """ Remove variables from the current problem. Arguments: var_ids (list): variable identifiers """ for var_id in var_ids: if var_id in self.var_ids: self.problem.remove(self.problem.getVarByName(var_id)) self.var_ids.remove(var_id) def remove_constraint(self, constr_id): """ Remove a constraint from the current problem. Arguments: constr_id (str): constraint identifier """ self.remove_constraints([constr_id]) def remove_constraints(self, constr_ids): """ Remove constraints from the current problem. Arguments: constr_ids (list): constraint identifiers """ for constr_id in constr_ids: if constr_id in self.constr_ids: self.problem.remove(self.problem.getConstrByName(constr_id)) self.constr_ids.remove(constr_id) def set_bounds(self, bounds_dict): for var_id, bounds in bounds_dict.items(): lpvar = self.problem.getVarByName(var_id) lpvar.lb = bounds[0] if bounds[0] is not None else GRB.INFINITY lpvar.ub = bounds[1] if bounds[1] is not None else GRB.INFINITY def update(self): """ Update internal structure. Used for efficient lazy updating. """ self.problem.update() def set_objective(self, linear=None, quadratic=None, minimize=True): """ Set a predefined objective for this problem. Args: linear (dict): linear coefficients (optional) quadratic (dict): quadratic coefficients (optional) minimize (bool): solve a minimization problem (default: True) Notes: Setting the objective is optional. It can also be passed directly when calling **solve**. """ lin_obj = [] quad_obj = [] if linear: if isinstance(linear, str): lin_obj = [1.0 * self.problem.getVarByName(linear)] if linear not in self.var_ids: warn( f"Objective variable not previously declared: {linear}" ) else: lin_obj = [] for r_id, val in linear.items(): if r_id not in self.var_ids: warn( f"Objective variable not previously declared: {r_id}" ) elif val != 0: lin_obj.append(val * self.problem.getVarByName(r_id)) if quadratic: quad_obj = [] for (r_id1, r_id2), val in quadratic.items(): if r_id1 not in self.var_ids: warn( f"Objective variable not previously declared: {r_id1}") elif r_id2 not in self.var_ids: warn( f"Objective variable not previously declared: {r_id2}") elif val != 0: quad_obj.append(val * self.problem.getVarByName(r_id1) * self.problem.getVarByName(r_id2)) obj_expr = quicksum(quad_obj + lin_obj) sense = GRB.MINIMIZE if minimize else GRB.MAXIMIZE self.problem.setObjective(obj_expr, sense) def solve(self, linear=None, quadratic=None, minimize=None, model=None, constraints=None, get_values=True, shadow_prices=False, reduced_costs=False, pool_size=0, pool_gap=None): """ Solve the optimization problem. Arguments: linear (str or dict): linear coefficients (or a single variable to optimize) quadratic (dict): quadratic objective (optional) minimize (bool): solve a minimization problem (default: True) model (CBModel): model (optional, leave blank to reuse previous model structure) constraints (dict): additional constraints (optional) get_values (bool or list): set to false for speedup if you only care about the objective (default: True) shadow_prices (bool): return shadow prices if available (default: False) reduced_costs (bool): return reduced costs if available (default: False) pool_size (int): calculate solution pool of given size (only for MILP problems) pool_gap (float): maximum relative gap for solutions in pool (optional) Returns: Solution: solution """ if model: self.build_problem(model) problem = self.problem if constraints: old_constraints = {} for r_id, x in constraints.items(): lb, ub = x if isinstance(x, tuple) else (x, x) if r_id in self.var_ids: lpvar = problem.getVarByName(r_id) old_constraints[r_id] = (lpvar.lb, lpvar.ub) lpvar.lb = infinity_fix(lb) lpvar.ub = infinity_fix(ub) else: warn( f"Constrained variable '{r_id}' not previously declared" ) problem.update() self.set_objective(linear, quadratic, minimize) # run the optimization if pool_size <= 1: problem.optimize() status = status_mapping.get(problem.status, Status.UNKNOWN) message = str(problem.status) if status == Status.OPTIMAL: fobj = problem.ObjVal values, s_prices, r_costs = None, None, None if get_values: if isinstance(get_values, Iterable): get_values = list(get_values) values = { r_id: problem.getVarByName(r_id).X for r_id in get_values } else: values = { r_id: problem.getVarByName(r_id).X for r_id in self.var_ids } if shadow_prices: s_prices = { m_id: problem.getConstrByName(m_id).Pi for m_id in self.constr_ids } if reduced_costs: r_costs = { r_id: problem.getVarByName(r_id).RC for r_id in self.var_ids } solution = Solution(status, message, fobj, values, s_prices, r_costs) else: solution = Solution(status, message) else: problem.setParam(GRB.Param.PoolSearchMode, 2) self.set_parameter(Parameter.POOL_SIZE, pool_size) if pool_gap: self.set_parameter(Parameter.POOL_GAP, pool_gap) problem.optimize() status = status_mapping.get(problem.status, Status.UNKNOWN) if status == Status.OPTIMAL or status == Status.UNKNOWN: solution = self.get_solution_pool() else: solution = [] # restore values of temporary constraints if constraints: for r_id, (lb, ub) in old_constraints.items(): lpvar = problem.getVarByName(r_id) lpvar.lb, lpvar.ub = lb, ub problem.update() return solution def get_solution_pool(self, get_values=True): """ Return a solution pool for MILP problems. Must be called after using solve with pool_size argument > 0. Arguments: get_values (bool or list): set to false for speedup if you only care about the objective (default: True) Returns: list: list of Solution objects """ solutions = [] for i in range(self.problem.SolCount): self.problem.setParam(GRB.param.SolutionNumber, i) obj = self.problem.PoolObjVal if get_values: if isinstance(get_values, Iterable): get_values = list(get_values) values = { r_id: self.problem.getVarByName(r_id).Xn for r_id in get_values } else: values = { r_id: self.problem.getVarByName(r_id).Xn for r_id in self.var_ids } else: values = None sol = Solution(fobj=obj, values=values) solutions.append(sol) return solutions def set_parameter(self, parameter, value): """ Set a parameter value for this optimization problem Arguments: parameter (Parameter): parameter type value (float): parameter value """ if parameter in parameter_mapping: grb_param = parameter_mapping[parameter] self.problem.setParam(grb_param, value) else: raise Exception('Parameter unknown (or not yet supported).') def set_logging(self, enabled=False): """ Enable or disable log output: Arguments: enabled (bool): turn logging on (default: False) """ self.problem.setParam('OutputFlag', 1 if enabled else 0) def write_to_file(self, filename): """ Write problem to file: Arguments: filename (str): file path """ self.problem.write(filename)
class SofdaiLp(object): def __init__(self, G, source, destination, Vms, VNFS): """ This method is used for initialization Args: G: networkx graph source: Source node in the network destination: destination node in the network Vms: List of nodes which where we can place Vms VNFS: number of Vnfs """ self.model = Model() # self.enabled_vm = dict() # self.edge = dict() self.Vars = dict() self.F = list() # #Parameters self.G = G self.source = source self.destination = destination self.Vms = Vms self.VNFS = VNFS def check_source(self): """ It check that source is the part of the network or invalid source. if invalid then raise exception Return: True or false """ source_bool = False for a in G.nodes(): if a in self.source: source_bool = True if source_bool == False: raise Exception('Source is not in Network') return source_bool def check_destination(self): """ It check that destination is the part of the network or invalid destination. if invalid then raise exception Return: True or false """ destination_bool = {} destinations_bool = False for b in self.destination: destination_bool[b] = False for a in G.nodes(): if a in self.destination: destination_bool[a] = True for c in destination_bool: if destination_bool[c] == False: raise Exception('Destination is not in Network') else: destinations_bool = True return destinations_bool def check_Vms(self): """ It check that Vms is the part of the network. if invalid then raise exception. Return: True or false """ vm_bool = {} vms_bool = False for b in self.Vms: vm_bool[b] = False for a in G.nodes(): if a in self.Vms: vm_bool[a] = True for c in vm_bool: if vm_bool[c] == False: raise Exception('Node for Vm assignment is not in Network') if c in self.source: raise Exception('Source is assgined as VM') if c in self.destination: raise Exception('Destination is assnied as VM') else: vms_bool = True return vms_bool def Creatvarriables(self): # for creating decision varriables q,x,y,p """ This method creates Gurobi Decision Varriables r_: denote if node u is assigned as the enabled VM for VNF f in the walk to destination d. lemda : denote if edge e u,v is located in the walk connecting the enabled VM of VNF f and the enabled VM of the next VNF fN. lemda2 : denote if edge e v,u is located in the walk connecting the enabled VM of VNF f and the enabled VM of the next VNF fN. T_: if edge e u,v is located in the forest. o_: represents if node u is assigned as the enabled VM of service f for the whole service forest. """ self.F = self.source + self.VNFS self.r_template = "r_{:s}_{:s}_{:s}" self.lembda_template = "lambda_{:s}_{:s}_{:s}_{:s}" self.T_template = "T_{:s}_{:s}_{:s}" self.o_template = "o_{:s}_{:s}" self.lambda2_template = "lambda2_{:s}_{:s}_{:s}_{:s}" self.r1_template = "r_{:s}_{:s}_{:s}" for d in self.destination: for f in G.nodes(): for u in G.nodes(): name = self.r_template.format(d, f, u) self.Vars[name] = self.model.addVar(lb=0, vtype=GRB.BINARY, name=name) for u, v in G.edges(): name_lembda = self.lembda_template.format(d, f, u, v) self.Vars[name_lembda] = self.model.addVar( lb=0, vtype=GRB.BINARY, name=name_lembda) for u, v in G.edges(): name_T = self.T_template.format(f, u, v) self.Vars[name_T] = self.model.addVar(lb=0, vtype=GRB.BINARY, name=name_T) for u in G.nodes(): name_o = self.o_template.format(f, u) self.Vars[name_o] = self.model.addVar(lb=0, vtype=GRB.BINARY, name=name_o) for u, v in G.edges(): name_lambda2 = self.lambda2_template.format(d, f, v, u) self.Vars[name_lambda2] = self.model.addVar( lb=0, vtype=GRB.BINARY, name=name_lambda2) for d in self.destination: for fn in self.VNFS: for u in G.nodes(): name_r1 = self.r1_template.format(d, f, u) self.Vars[name_r1] = self.model.addVar(lb=0, vtype=GRB.BINARY, name=name_r1) self.model.update() def Source_Selection(self): """ Constraint 1 ensures that each destination chooses one source s in S as its service source. Returns: None """ self.add_fs = 0 for d in self.destination: for f in self.source: for s in self.source: name_r = self.r_template.format(d, f, s) add1 = self.model.getVarByName(name_r) self.add_fs = self.add_fs + add1 self.model.addConstr(self.add_fs, GRB.EQUAL, 1) def enabled_VM(self): """ Constraint 2 finds a node u from M as the enaled VM of each VNF f for each destination. Returns: None """ self.add = 0 for d in self.destination: for f in self.source: for u in self.Vms: name_r = self.r_template.format(d, f, u) add1 = self.model.getVarByName(name_r) self.add = self.add + add1 self.model.addConstr(self.add, GRB.EQUAL, 1) def destination_assignment1(self): """ There are two constraints which deals with assignment of destinations for Function Fd. Constraint 3 is the 1st one in that Constraint 3 assign only one destination for Function Fd Returns: None """ self.assign = 0 for d in self.destination: for f in self.destination: for u in self.destination: name_r = self.r_template.format(d, f, u) self.assign = self.model.getVarByName(name_r) self.model.addConstr(self.assign, GRB.EQUAL, 1) def destination_assignment2(self): """ The 2nd constraint for destination assignment Contraint 4 assign only one destination for Function Fd Returns: None """ self.add1 = 0 for d in self.destination: for f in self.source: for u in self.Vms: name_r = self.r_template.format(d, f, u) add2 = self.model.getVarByName(name_r) self.add1 = add2 self.model.addConstr(self.add1, GRB.EQUAL, 0) def assignment_of_enabled_VM(self): """ Contraint 5 assign u as the enabled VM of VNF f for the whole service forest if u has been selected by atleast one destination d for VNF f Returns: None """ for d in self.destination: for f in self.VNFS: for u in G.nodes(): name_r = self.r_template.format(d, f, u) name_o = self.o_template.format(f, u) LHS = self.model.getVarByName(name_r) RHS = self.model.getVarByName(name_o) self.model.addConstr(LHS, GRB.LESS_EQUAL, RHS) def atmost_one_VNF(self): """ Contraint 6 ensures that each node u is in charge of at most one VNF. Returns: None """ self.sum_o = 0 for f in self.VNFS: for u in G.nodes(): name_o = self.o_template.format(f, u) RHS = self.model.getVarByName(name_o) self.sum_o += RHS self.model.addConstr(self.sum_o, GRB.LESS_EQUAL, 1) def routing_of_service_chain(self): """ Contraint 7 It first finds the routing of the service chain for each destination d. It ensures that at least one edge eu;v incident from u is selected for the service chain because no edge e v;u incident to u is chosen Returns: None """ add_lemda1 = 0 add_lemda2 = 0 self.final_lemda = 0 for d in self.destination: for f in self.F: for u, v in G.edges(): name_lembda = self.lembda_template.format(d, f, u, v) LS = self.model.getVarByName(name_lembda) add_lemda1 += LS for d in self.destination: for f in self.F: for u, v in G.edges(): name_lembda2 = self.lambda2_template.format(d, f, v, u) RS = self.model.getVarByName(name_lembda2) add_lemda2 += RS self.final_lemda = add_lemda1 - add_lemda2 for d in self.destination: for f in self.F: for fn in self.VNFS: for u in G.nodes(): name_r = self.r_template.format(d, f, u) name_r1 = self.r1_template.format(d, fn, u) LS1 = self.model.getVarByName(name_r) RS1 = self.model.getVarByName(name_r1) final_r = LS1 - RS1 self.model.addConstr(self.final_lemda, GRB.GREATER_EQUAL, final_r) def edge_inthe_service_forest(self): """ Contraint 8 states that any edge e u;v is in the service forest if it is in the service chain for at least one destination d.. Returns: None """ for d in self.destination: for f in self.F: for u, v in G.edges(): name_lembda = self.lembda_template.format(d, f, u, v) name_T = self.T_template.format(f, u, v) LS = self.model.getVarByName(name_lembda) RS = self.model.getVarByName(name_T) self.model.addConstr(LS, GRB.LESS_EQUAL, RS) def optimzation(self): cost_nodes = 0 cost_edges = 0 for f in self.VNFS: for u in G.nodes: name_o = self.o_template.format(f, u) LS = G.nodes[u]['Cost'] * self.model.getVarByName(name_o) cost_nodes += LS for f in self.VNFS: for u, v in G.edges(): name_T = self.T_template.format(f, u, v) RS = G.edges[u, v]['Cost'] * self.model.getVarByName(name_T) cost_edges += RS final_cost = cost_nodes + cost_edges self.model.setObjective(final_cost, GRB.MINIMIZE) self.model.optimize() status = self.model.status return status def build(self): self.check_source() self.check_destination() self.check_Vms() self.Creatvarriables() self.Source_Selection() self.enabled_VM() self.destination_assignment1() self.destination_assignment2() self.assignment_of_enabled_VM() self.atmost_one_VNF() self.routing_of_service_chain() self.edge_inthe_service_forest()
def _objective_function_for_delta_weight(D, delta_weight, d1, d2): global _time_limit_per_model, _round, _pr_dataset, _tardiness_objective_dataset m = Model("model_for_supplier_assignment") m.setParam('OutputFlag', False) m.params.timelimit = _time_limit_per_model # m.params.IntFeasTol = 1e-7 x = {} q = {} for (r, s, p) in D.supplier_project_shipping: x[r, s, p] = m.addVar(vtype=GRB.BINARY, name="x_%s_%s_%s" % (r, s, p)) q[r, s, p] = m.addVar(vtype=GRB.CONTINUOUS, name="q_%s_%s_%s" % (r, s, p)) AT = {} for j in range(D.project_n): for k in [r for r, p in D.resource_project_demand if p == D.project_list[j]]: AT[j, k] = m.addVar(vtype=GRB.CONTINUOUS, name="AT_%s_%s" % (j, k)) m.update() ## define constraints # equation 2 for (r, s) in D.resource_supplier_capacity: m.addConstr(quicksum(q[r, s, D.project_list[j]] for j in range(D.project_n)), GRB.LESS_EQUAL, D.resource_supplier_capacity[r, s], name="constraint_3_resource_%s_supplier_%s" % (r, s)) # constraint 21(4) 23(6) for (r, p) in D.resource_project_demand: # equation 5 m.addConstr(quicksum(x[r, i, p] for i in D.resource_supplier_list[r]), GRB.EQUAL, 1, name="constraint_6_resource_%s_project_%s" % (r, p)) # equation 3 m.addConstr(quicksum(q[r, i, p] for i in D.resource_supplier_list[r]), GRB.GREATER_EQUAL, D.resource_project_demand[r, p], name="constraint_4_resource_%s_project_%s" % (r, p)) # constraint 22(5) for (i, j, k) in q: # i resource, j supplier, k project # equation 4 m.addConstr(q[i, j, k], GRB.LESS_EQUAL, D.M * x[i, j, k], name="constraint_5_resource_%s_supplier_%s_project_%s" % (i, j, k)) # constraint 7 shipping_cost_expr = LinExpr() for (i, j, k) in q: shipping_cost_expr.addTerms(D.c[i, j, k], q[i, j, k]) # equation 6 m.addConstr(shipping_cost_expr, GRB.LESS_EQUAL, D.B, name="constraint_7") # constraint 8 # equation 26 for j in range(D.project_n): p = D.project_list[j] project_resources = [r for (r, p_) in D.resource_project_demand.keys() if p_ == p] for r in project_resources: suppliers = D.resource_supplier_list[r] m.addConstr( quicksum( x[r, s, p] * (D.resource_supplier_release_time[r, s] + D.supplier_project_shipping[r, s, p]) for s in suppliers), GRB.LESS_EQUAL, AT[j, r], name="constraint_8_project_%d_resource_%s_deliver" % (j, r)) m.update() expr = LinExpr() for j in range(D.project_n): p = D.project_list[j] for r in [r for (r, p_) in D.resource_project_demand.keys() if p_ == p]: expr.add(delta_weight[j, r] * AT[j, r]) m.setObjective(expr, GRB.MINIMIZE) m.update() ########################################## # m.params.presolve = 1 m.update() # Solve # m.params.presolve=0 m.optimize() _exit_if_infeasible(m) m.write(join(_result_output_path, "round_%d_supplier_assign.lp" % _round)) m.write(join(_result_output_path, "round_%d_supplier_assign.sol" % _round)) with open(join(log_output_path, 'shipping_cost.txt'), 'a') as fout: fout.write('shipping cost: %f\n' % shipping_cost_expr.getValue()) _logger.info('shipping cost: %f' % shipping_cost_expr.getValue()) print('status', m.status) # m.write(join(_output_path, 'delta_weight.sol')) # m.write(join(_output_path, 'delta_weight.lp')) X_ = {} for (i, j, k) in D.supplier_project_shipping: v = m.getVarByName("x_%s_%s_%s" % (i, j, k)) if v.X == 1: X_[i, j, k] = 1 AT_ = {} for j, r in AT: val = AT[j, r].X if val > 0: AT_[j, r] = val tardiness_obj_val, skj, sj = _objective_function_for_tardiness(X_, AT_, D) new_delta_weight = {} # delta_weight_keys = list(delta_weight.keys()) # delta_weight_keys.sort(key=lambda x: x[1]) # delta_weight_keys.sort(key=lambda x: x[0]) for j, r in delta_weight.keys(): new_delta_weight[j, r] = delta_weight[j, r] * (1 + d1 * (d2 + sj.get(j, 0)) * skj.get((j, r), 0)) # print('j', type(j), j) # print('r', type(r), r) # print('previous weight', type(delta_weight[j, r]), delta_weight[j, r]) # print('d1', type(d1), d1) # print('d2', type(d2), d2) # print('sj', type(sj.get(j, 0)), sj.get(j, 0)) # print('skj', type(skj.get((j, r))), skj.get((j, r))) # print('new weight', type(new_delta_weight[j, r]), new_delta_weight[j, r]) _logger.info( 'r[%d,%s] = %f *(1+%f*(%f+%f)*%f) = %f' % ( j, r, delta_weight[j, r], d1, d2, sj.get(j, 0), skj.get((j, r), 0), new_delta_weight[j, r])) # new_delta_weight[j, r] = 1 _normalize(new_delta_weight) for j, r in new_delta_weight.keys(): # _logger.info('j:' + str(j)) # _logger.info('r:' + str(r)) # _logger.info(str([_round, j, r, new_delta_weight[j, r]])) _weight_dataset.loc[_weight_dataset.shape[0]] = [_round, j, r, new_delta_weight[j, r]] for j in range(D.project_n): _pr_dataset.loc[_pr_dataset.shape[0]] = [_round, j, sj.get(j, 0)] _tardiness_objective_dataset.loc[_tardiness_objective_dataset.shape[0]] = [_round, tardiness_obj_val] return new_delta_weight
def _objective_function_for_delta_weight(D, delta_weight): m = Model("model_for_supplier_assignment") x = {} q = {} for (r, s, p) in D.supplier_project_shipping: # i resource, j supplier, k project x[r, s, p] = m.addVar(vtype=GRB.BINARY, name="x_%s_%s_%s" % (r, s, p)) q[r, s, p] = m.addVar(vtype=GRB.CONTINUOUS, name="q_%s_%s_%s" % (r, s, p)) AT = {} for j in range(D.project_n): for k in sorted([r for r, p in D.resource_project_demand if p == D.project_list[j]]): AT[j, k] = m.addVar(vtype=GRB.CONTINUOUS, name="AT_%s_%s" % (j, k)) m.update() ## define constraints # constraint 20(3) for (r, s) in D.resource_supplier_capacity: m.addConstr(quicksum(q[r, s, D.project_list[j]] for j in range(D.project_n)), GRB.LESS_EQUAL, D.resource_supplier_capacity[r, s], name="constraint_3_resource_%s_supplier_%s" % (r, s)) # constraint 21(4) 23(6) for (r, p) in D.resource_project_demand: m.addConstr(quicksum(x[r, i, p] for i in D.resource_supplier_list[r]), GRB.EQUAL, 1, name="constraint_6_resource_%s_project_%s" % (r, p)) m.addConstr(quicksum(q[r, i, p] for i in D.resource_supplier_list[r]), GRB.GREATER_EQUAL, D.resource_project_demand[r, p], name="constraint_4_resource_%s_project_%s" % (r, p)) # constraint 22(5) for (i, j, k) in q: # i resource, j supplier, k project m.addConstr(q[i, j, k], GRB.LESS_EQUAL, D.M * x[i, j, k], name="constraint_5_resource_%s_supplier_%s_project_%s" % (i, j, k)) # constraint 7 expr = LinExpr() for (i, j, k) in q: expr = expr + D.c[i, j, k] * q[i, j, k] m.addConstr(expr, GRB.LESS_EQUAL, D.B, name="constraint_7") # constraint 8 for j in range(D.project_n): p = D.project_list[j] project_resources = sorted([r for (r, p_) in D.resource_project_demand.keys() if p_ == p]) for r in project_resources: suppliers = D.resource_supplier_list[r] # print(list(D.supplier_project_shipping.keys())[:10]) # print(D.supplier_project_shipping['NK0g77', 'S1671', 'P1']) # print(list(x.keys())[:10]) # print(x['NK0g77', 'S1671', 'P1']) m.addConstr( quicksum( x[r, s, p] * (D.resource_supplier_release_time[r, s] + D.supplier_project_shipping[r, s, p]) for s in suppliers), GRB.LESS_EQUAL, AT[j, r], name="constraint_8_project_%d_resource_%s_deliver" % (j, r)) m.update() expr = LinExpr() for j in range(D.project_n): for r in sorted([r for (r, p_) in D.resource_project_demand.keys() if p_ == p]): expr.add(delta_weight[_delta_project_idx[j, r]] * AT[j, r]) m.setObjective(expr, GRB.MINIMIZE) m.update() ########################################## m.params.presolve = 1 m.update() # Solve # m.params.presolve=0 m.optimize() print(m.Status) X_ = {} for (i, j, k) in D.supplier_project_shipping: v = m.getVarByName("x_%s_%s_%s" % (i, j, k)) print(v) if v.X == 1: X_[i, j, k] = 1 AT_ = {} for j, r in AT: val = AT[j, r].X if val > 0: AT_[j, r] = val return -_objective_function_for_tardiness(X_, AT_, D),
def gurobi_solve_qp(P, q, G=None, h=None, A=None, b=None, initvals=None): """ Solve a Quadratic Program defined as: minimize (1/2) * x.T * P * x + q.T * x subject to G * x <= h A * x == b using Gurobi <http://www.gurobi.com/>. Parameters ---------- P : array, shape=(n, n) Primal quadratic cost matrix. q : array, shape=(n,) Primal quadratic cost vector. G : array, shape=(m, n) Linear inequality constraint matrix. h : array, shape=(m,) Linear inequality constraint vector. A : array, shape=(meq, n), optional Linear equality constraint matrix. b : array, shape=(meq,), optional Linear equality constraint vector. initvals : array, shape=(n,), optional Warm-start guess vector (not used). Returns ------- x : array, shape=(n,) Solution to the QP, if found, otherwise ``None``. """ if initvals is not None: print("Gurobi: note that warm-start values are ignored by wrapper") n = P.shape[1] model = Model() x = { i: model.addVar( vtype=GRB.CONTINUOUS, name='x_%d' % i, lb=-GRB.INFINITY, ub=+GRB.INFINITY) for i in xrange(n) } model.update() # integrate new variables # minimize # 1/2 x.T * P * x + q * x obj = QuadExpr() rows, cols = P.nonzero() for i, j in zip(rows, cols): obj += 0.5 * x[i] * P[i, j] * x[j] for i in xrange(n): obj += q[i] * x[i] model.setObjective(obj, GRB.MINIMIZE) # subject to # G * x <= h if G is not None: G_nonzero_rows = get_nonzero_rows(G) for i, row in G_nonzero_rows.iteritems(): model.addConstr(quicksum(G[i, j] * x[j] for j in row) <= h[i]) # subject to # A * x == b if A is not None: A_nonzero_rows = get_nonzero_rows(A) for i, row in A_nonzero_rows.iteritems(): model.addConstr(quicksum(A[i, j] * x[j] for j in row) == b[i]) model.optimize() a = empty(n) for i in xrange(n): a[i] = model.getVarByName('x_%d' % i).x return a
class GurobiWrapper(SolverWrapper): def __init__(self): self.model = Model() def solve(self): self.model.optimize() def add_variable(self, name, lb, ub, vtype): args = {'name': name} if ub is not None: args['ub'] = ub if lb is not None: args['lb'] = lb if vtype is not None: args['vtype'] = self._var_types_mapping(vtype) self.model.addVar(**args) def add_variables(self, name, lb, ub, vtype): args = {'name': name} if ub is not None: args['ub'] = ub if lb is not None: args['lb'] = lb if vtype is not None: args['vtype'] = [self._var_types_mapping(t) for t in vtype] self.model.addVars(len(name), **args) def add_constraint(self, var, coeff, sense, rs): args = { 'lhs': LinExpr(coeff, self._gurobi_variables(var)), 'sense': self._sense_mapping(sense), 'rhs': rs } self.model.addConstr(**args) def add_constraints(self, var, coeff, sense, rs): for constr in zip(var, coeff, sense, rs): self.add_constraint(*constr) def set_objective_sense(self, sense): mapping = { Objective.maximize: GRB.MAXIMIZE, Objective.minimize: GRB.MINIMIZE } self.model.ModelSense = mapping[sense] def set_objective(self, var, coeff): lin_expr = LinExpr(coeff, self._gurobi_variables(var)) self.model.setObjective(lin_expr) def write_to_file(self, name): self.model.write(name) def get_solution_status(self): """ :type solution_type: SolutionType :type additional_data: Dict[{'status', 'status_str'}] :return solution_type, additional_data: """ status = self.model.Status solution_type = SolutionType.feasible if status == 1 else SolutionType.infeasible additional_data = { 'status': status, 'status_str': self._solution_strings()[status] } return solution_type, additional_data def get_objective_value(self): return self.model.ObjVal def get_solution(self): return {var.VarName: var.X for var in self.model.getVars()} def _gurobi_variable(self, name): return self.model.getVarByName(name) def _gurobi_variables(self, names): return [self._gurobi_variable(n) for n in names] @staticmethod def _sense_mapping(sense): mapping = { Sense.lt: GRB.LESS_EQUAL, Sense.gt: GRB.GREATER_EQUAL, Sense.eq: GRB.EQUAL } return mapping[sense] @staticmethod def _var_types_mapping(vtype): mapping = { VariableTypes.int: GRB.INTEGER, VariableTypes.continuous: GRB.CONTINUOUS } return mapping[vtype] @staticmethod def _solution_strings(): return { 1: 'LOADED', 2: 'OPTIMAL', 3: 'INFEASIBLE', 4: 'INF_OR_UNBD', 5: 'UNBOUNDED', 6: 'CUTOFF', 7: 'ITERATION_LIMIT', 8: 'NODE_LIMIT', 9: 'TIME_LIMIT', 10: 'SOLUTION_LIMIT', 11: 'INTERRUPTED', 12: 'NUMERIC', 13: 'SUBOPTIMAL', 14: 'INPROGRESS', 15: 'USER_OBJ_LIMIT' }
def schedule_rooms_in_period_raumsperren(exams_to_schedule, period, data, verbose = False): #print period ''' schedule_rooms needs to be called for every single period schedule_rooms tries to schedule a given set of exams which are written in the same period on the rooms avialable for the given period ''' # TODO: Initialise using meaningful values # ... #verbose = True n = len(exams_to_schedule) r = data['r'] c = data['c'] T = data['T'] s = data['s'] z = {} exam_rooms_index = data['exam_rooms_index'] model = Model("RoomPlanner") # z[i,k] = if exam i is written in room k for i in exams_to_schedule: for k in exam_rooms_index[i]: if period == -1 or T[k][period] == 1: z[i,k] = model.addVar(vtype=GRB.BINARY, name="z_%s_%s" % (i,k)) model.update() # Building constraints... # c1: seats for all students for i in exams_to_schedule: model.addConstr( quicksum([ z[i, k] * c[k] for k in range(r) if period == -1 or T[k][period] == 1 ]) >= s[i], "c1") # c2: only one exam per room for k in range(r): if period == -1 or T[k][period] == 1: model.addConstr( quicksum([ z[i, k] for i in exams_to_schedule ]) <= 1, "c2") # objective: minimize number of used rooms obj1 = quicksum([ z[i,k] for i in exams_to_schedule for k in exam_rooms_index[i] if T[k][period] == 1 ]) model.setObjective( obj1, GRB.MINIMIZE) if not verbose: model.params.OutputFlag = 0 model.optimize() # return best room schedule try: z=defaultdict(int) for i in exams_to_schedule: for k in exam_rooms_index[i]: if period == -1 or T[k][period] == 1: v = model.getVarByName("z_%s_%s" % (i,k)) z[i,k] = v.x return z except GurobiError: return None
class GurobiSolver(Solver): """ Implements the solver interface using gurobipy. """ def __init__(self, model=None): Solver.__init__(self) self.problem = GurobiModel() self.set_logging() self.set_parameters(default_parameters) if model: self.build_problem(model) def add_variable(self, var_id, lb=None, ub=None, vartype=VarType.CONTINUOUS, persistent=True, update_problem=True): """ Add a variable to the current problem. Arguments: var_id (str): variable identifier lb (float): lower bound ub (float): upper bound vartype (VarType): variable type (default: CONTINUOUS) persistent (bool): if the variable should be reused for multiple calls (default: true) update_problem (bool): update problem immediately (default: True) """ lb = lb if lb is not None else -GRB.INFINITY ub = ub if ub is not None else GRB.INFINITY if var_id in self.var_ids: var = self.problem.getVarByName(var_id) var.setAttr('lb', lb) var.setAttr('ub', ub) var.setAttr('vtype', vartype_mapping[vartype]) else: self.problem.addVar(name=var_id, lb=lb, ub=ub, vtype=vartype_mapping[vartype]) self.var_ids.append(var_id) if not persistent: self.temp_vars.add(var_id) if update_problem: self.problem.update() def add_constraint(self, constr_id, lhs, sense='=', rhs=0, persistent=True, update_problem=True): """ Add a constraint to the current problem. Arguments: constr_id (str): constraint identifier lhs (dict): variables and respective coefficients sense (str): constraint sense (any of: '<', '=', '>'; default '=') rhs (float): right-hand side of equation (default: 0) persistent (bool): if the variable should be reused for multiple calls (default: True) update_problem (bool): update problem immediately (default: True) """ grb_sense = {'=': GRB.EQUAL, '<': GRB.LESS_EQUAL, '>': GRB.GREATER_EQUAL} if constr_id in self.constr_ids: constr = self.problem.getConstrByName(constr_id) self.problem.remove(constr) expr = quicksum(coeff * self.problem.getVarByName(r_id) for r_id, coeff in lhs.items() if coeff) self.problem.addConstr(expr, grb_sense[sense], rhs, constr_id) self.constr_ids.append(constr_id) if not persistent: self.temp_constrs.add(constr_id) if update_problem: self.problem.update() def remove_variable(self, var_id): """ Remove a variable from the current problem. Arguments: var_id (str): variable identifier """ if var_id in self.var_ids: self.problem.remove(self.problem.getVarByName(var_id)) self.var_ids.remove(var_id) def remove_constraint(self, constr_id): """ Remove a constraint from the current problem. Arguments: constr_id (str): constraint identifier """ if constr_id in self.constr_ids: self.problem.remove(self.problem.getConstrByName(constr_id)) self.constr_ids.remove(constr_id) def update(self): """ Update internal structure. Used for efficient lazy updating. """ self.problem.update() def set_objective(self, linear=None, quadratic=None, minimize=True): """ Set a predefined objective for this problem. Args: linear (dict): linear coefficients (optional) quadratic (dict): quadratic coefficients (optional) minimize (bool): solve a minimization problem (default: True) Notes: Setting the objective is optional. It can also be passed directly when calling **solve**. """ lin_obj = [] quad_obj = [] if linear: lin_obj = [f * self.problem.getVarByName(r_id) for r_id, f in linear.items() if f] if quadratic: quad_obj = [q * self.problem.getVarByName(r_id1) * self.problem.getVarByName(r_id2) for (r_id1, r_id2), q in quadratic.items() if q] obj_expr = quicksum(quad_obj + lin_obj) sense = GRB.MINIMIZE if minimize else GRB.MAXIMIZE self.problem.setObjective(obj_expr, sense) def solve(self, linear=None, quadratic=None, minimize=None, model=None, constraints=None, get_values=True, get_shadow_prices=False, get_reduced_costs=False): """ Solve the optimization problem. Arguments: linear (dict): linear objective (optional) quadratic (dict): quadratic objective (optional) minimize (bool): solve a minimization problem (default: True) model (CBModel): model (optional, leave blank to reuse previous model structure) constraints (dict): additional constraints (optional) get_values (bool): set to false for speedup if you only care about the objective value (default: True) get_shadow_prices (bool): return shadow prices if available (default: False) get_reduced_costs (bool): return reduced costs if available (default: False) Returns: Solution: solution """ if model: self.build_problem(model) problem = self.problem if constraints: old_constraints = {} for r_id, x in constraints.items(): lb, ub = x if isinstance(x, tuple) else (x, x) if r_id in self.var_ids: lpvar = problem.getVarByName(r_id) old_constraints[r_id] = (lpvar.lb, lpvar.ub) lpvar.lb = lb if lb is not None else -GRB.INFINITY lpvar.ub = ub if ub is not None else GRB.INFINITY else: warnings.warn("Constrained variable '{}' not previously declared".format(r_id), RuntimeWarning) problem.update() self.set_objective(linear, quadratic, minimize) #run the optimization problem.optimize() status = status_mapping[problem.status] if problem.status in status_mapping else Status.UNKNOWN message = str(problem.status) if status == Status.OPTIMAL: fobj = problem.ObjVal values, shadow_prices, reduced_costs = None, None, None if get_values: values = OrderedDict([(r_id, problem.getVarByName(r_id).X) for r_id in self.var_ids]) if get_shadow_prices: shadow_prices = OrderedDict([(m_id, problem.getConstrByName(m_id).Pi) for m_id in self.constr_ids]) if get_reduced_costs: reduced_costs = OrderedDict([(r_id, problem.getVarByName(r_id).RC) for r_id in self.var_ids]) solution = Solution(status, message, fobj, values, shadow_prices, reduced_costs) else: solution = Solution(status, message) #reset old constraints because temporary constraints should not be persistent if constraints: for r_id, (lb, ub) in old_constraints.items(): lpvar = problem.getVarByName(r_id) lpvar.lb, lpvar.ub = lb, ub problem.update() return solution #TODO: 2_program_MMsolver.prof def set_lower_bounds(self, bounds_dict): for var_id, lb in bounds_dict.iteritems(): lpvar = self.problem.getVarByName(var_id) lpvar.lb = lb if lb is not None else GRB.INFINITY #TODO: 2_program_MMsolver.prof def set_upper_bounds(self, bounds_dict): for var_id, ub in bounds_dict.iteritems(): lpvar = self.problem.getVarByName(var_id) lpvar.ub = ub if ub is not None else GRB.INFINITY #TODO: 2_program_MMsolver.prof def set_bounds(self, bounds_dict): for var_id, bounds in bounds_dict.iteritems(): lpvar = self.problem.getVarByName(var_id) lpvar.lb = bounds[0] if bounds[0] is not None else GRB.INFINITY lpvar.ub = bounds[1] if bounds[1] is not None else GRB.INFINITY def set_parameter(self, parameter, value): """ Set a parameter value for this optimization problem Arguments: parameter (Parameter): parameter type value (float): parameter value """ if parameter in parameter_mapping: grb_param = parameter_mapping[parameter] self.problem.setParam(grb_param, value) else: raise Exception('Parameter unknown (or not yet supported).') def set_logging(self, enabled=False): """ Enable or disable log output: Arguments: enabled (bool): turn logging on (default: False) """ self.problem.setParam('OutputFlag', 1 if enabled else 0) def write_to_file(self, filename): """ Write problem to file: Arguments: filename (str): file path """ self.problem.write(filename)
def update_R_c(rep, rep_norm, solver='cvxopt', tol=1e-6): """ Function to update R and c while leaving the network parameters fixed in a block coordinate optimization. Using quadratic programming of cvxopt. """ assert solver in ('cvxopt', 'gurobi') n, d = rep.shape # Define QP P = (2 * np.dot(rep, rep.T)).astype(np.double) q = (-(rep_norm**2)).astype(np.double) G = (np.concatenate((np.eye(n), -np.eye(n)), axis=0)).astype(np.double) h = (np.concatenate( ((1 / (Cfg.nu.get_value() * n)) * np.ones(n), np.zeros(n)), axis=0)).astype(np.double) A = (np.ones(n)).astype(np.double) b = 1 if solver == 'cvxopt': from cvxopt import matrix from cvxopt.solvers import qp # Solve QP sol = qp(matrix(P), matrix(q), matrix(G), matrix(h), matrix(A).trans(), matrix(b, tc='d'))['x'] a = np.array(sol).reshape(n) print("Sum of the elements of alpha: {:.3f}".format(np.sum(a))) if solver == 'gurobi': # Gurobi Python wrapper from https://github.com/stephane-caron/qpsolvers/blob/master/qpsolvers/gurobi_.py from gurobipy import Model, QuadExpr, GRB, quicksum # setup model model = Model() x = { i: model.addVar(vtype=GRB.CONTINUOUS, name='x_%d' % i, lb=-GRB.INFINITY, ub=+GRB.INFINITY) for i in xrange(n) } model.update() # minimize 1/2 x.T * P * x + q * x obj = QuadExpr() rows, cols = P.nonzero() for i, j in zip(rows, cols): obj += 0.5 * x[i] * P[i, j] * x[j] for i in xrange(n): obj += q[i] * x[i] model.setObjective(obj, GRB.MINIMIZE) # subject to G * x <= h G_nonzero_rows = get_nonzero_rows(G) for i, row in G_nonzero_rows.iteritems(): model.addConstr(quicksum(G[i, j] * x[j] for j in row) <= h[i]) # subject to A * x == b A_nonzero_rows = get_nonzero_rows(A) for i, row in A_nonzero_rows.iteritems(): model.addConstr(quicksum(A[i, j] * x[j] for j in row) == b[i]) # Solve QP model.optimize() a = np.empty(n) for i in xrange(n): a[i] = model.getVarByName('x_%d' % i).x # Set new center c and radius R c = np.dot(a, rep).reshape(d).astype(np.float32) # Recover R (using the specified numeric tolerance on the range) n_svs = 0 # number of support vectors while n_svs == 0: lower = tol * (1 / (Cfg.nu.get_value() * n)) upper = (1 - tol) * (1 / (Cfg.nu.get_value() * n)) idx_svs = (a > lower) & (a < upper) n_svs = np.sum(idx_svs) tol /= 10 # decrease tolerance if there are still no support vectors found print("Number of Support Vectors: {}".format(n_svs)) R = np.mean(np.sum((rep[idx_svs] - c)**2, axis=1)).astype(np.float32) return R, c
def gurobi_solve_qp(P, q, G=None, h=None, A=None, b=None, initvals=None, verbose=False): """ Solve a Quadratic Program defined as: .. math:: \\begin{split}\\begin{array}{ll} \\mbox{minimize} & \\frac{1}{2} x^T P x + q^T x \\\\ \\mbox{subject to} & G x \\leq h \\\\ & A x = h \\end{array}\\end{split} using `Gurobi <http://www.gurobi.com/>`_. Parameters ---------- P : array, shape=(n, n) Primal quadratic cost matrix. q : array, shape=(n,) Primal quadratic cost vector. G : array, shape=(m, n) Linear inequality constraint matrix. h : array, shape=(m,) Linear inequality constraint vector. A : array, shape=(meq, n), optional Linear equality constraint matrix. b : array, shape=(meq,), optional Linear equality constraint vector. initvals : array, shape=(n,), optional Warm-start guess vector (not used). verbose : bool, optional Set to `True` to print out extra information. Returns ------- x : array, shape=(n,) Solution to the QP, if found, otherwise ``None``. """ setParam('OutputFlag', 1 if verbose else 0) if initvals is not None: print("Gurobi: note that warm-start values are ignored by wrapper") n = P.shape[1] model = Model() x = { i: model.addVar(vtype=GRB.CONTINUOUS, name='x_%d' % i, lb=-GRB.INFINITY, ub=+GRB.INFINITY) for i in range(n) } model.update() # integrate new variables # minimize # 1/2 x.T * P * x + q * x obj = QuadExpr() rows, cols = P.nonzero() for i, j in zip(rows, cols): obj += 0.5 * x[i] * P[i, j] * x[j] for i in range(n): obj += q[i] * x[i] model.setObjective(obj, GRB.MINIMIZE) # subject to # G * x <= h if G is not None: G_nonzero_rows = get_nonzero_rows(G) for i, row in G_nonzero_rows.items(): model.addConstr(quicksum(G[i, j] * x[j] for j in row) <= h[i]) # subject to # A * x == b if A is not None: A_nonzero_rows = get_nonzero_rows(A) for i, row in A_nonzero_rows.items(): model.addConstr(quicksum(A[i, j] * x[j] for j in row) == b[i]) model.optimize() a = empty(n) for i in range(n): a[i] = model.getVarByName('x_%d' % i).x return a
def find_ranking(comparisons, equal_width=0.2, max_rank=-1, verbose=False): """ Find the least changes to a set of comparisons so that they are consistent (transitive), it returns a topological ranking. comparisons A dictionary with tuple keys in the form of (i, j), values are scalars indicating the probability of i > j. It is assumed that comparisons are symmetric. Use 0 for i < j, 0.5 for i == j, and 1 for i > j (and any value in between). equal_width 0..0.5-equal_width/2 is considered '<=' and 0.5..0.5+equal_width/2 is considered '>='. In between it is considered to be '=='. max_rank Maximal rank, a low value forces the model to choose more equal cases. verbose Whether to print gurobi's progress. Returns: A tuple of size two: 0) Ranking derived from topological sort (list of ranks in order of nodes); 1) Sum of absolute changes to the comparisons. """ # remove unnecessary variables comparisons = {(i, j) if i < j else (j, i): value if i < j else 1 - value for (i, j), value in comparisons.items()} nodes = np.unique([i for ij in comparisons.keys() for i in ij]) # define variables model = Model('comparison') model.setParam('OutputFlag', verbose) values = np.fromiter(comparisons.values(), dtype=float) assert values.max() <= 1 and values.min() >= 0 # variables to encode the error of comparisons E_ij = model.addVars(comparisons.keys(), name='e_ij', vtype=GRB.CONTINUOUS, ub=1.0 - values, lb=-values) # variables to encode hard choice of >=, <=, == Ge_ij = model.addVars(comparisons.keys(), name='ge_ij', vtype=GRB.BINARY) Le_ij = model.addVars(comparisons.keys(), name='le_ij', vtype=GRB.BINARY) Eq_ij = model.addVars(comparisons.keys(), name='eq_ij', vtype=GRB.BINARY) # variables to help with transitivity in non-fully connected graphs if max_rank < 1: max_rank = len(nodes) R_i = model.addVars(nodes, name='r_i', vtype=GRB.CONTINUOUS, lb=0, ub=max_rank) # variables to emulate abs T_ij_pos = {} T_ij_neg = {} index = (values != 1) & (values != 0) T_ij_pos = model.addVars( (ij for ij, value in comparisons.items() if value not in [0.0, 1.0]), vtype=GRB.CONTINUOUS, name='T_ij_pos', lb=0, ub=1 - values[index]) T_ij_neg = model.addVars( (ij for ij, value in comparisons.items() if value not in [0.0, 1.0]), vtype=GRB.CONTINUOUS, name='T_ij_neg', lb=0, ub=values[index]) model.update() # emulate abs for non-binary comparisons: E_ij = T_ij_pos - T_ij_neg model.addConstrs((E_ij[ij] == T_ij_pos[ij] - T_ij_neg[ij] for ij in T_ij_pos), 'E_ij = T_ij_pos - T_ij_neg') # hard decision of >=, <=, and == lower_bound = 0.5 - equal_width / 2.0 upper_bound = 0.5 + equal_width / 2.0 # <= model.addConstrs((E_ij[ij] + comparisons[ij] - upper_bound <= ge_ij for ij, ge_ij in Ge_ij.items()), 'ge_ij_lower_bound') model.addConstrs((E_ij[ij] + comparisons[ij] - upper_bound >= -1 + ge_ij for ij, ge_ij in Ge_ij.items()), 'ge_ij_upper_bound') # >= model.addConstrs((E_ij[ij] + comparisons[ij] - lower_bound >= -le_ij for ij, le_ij in Le_ij.items()), 'le_ij_lower_bound') model.addConstrs((E_ij[ij] + comparisons[ij] - lower_bound <= 1 - le_ij for ij, le_ij in Le_ij.items()), 'le_ij_upper_bound') # == model.addConstrs(( le + eq + ge == 1 for le, eq, ge in zip(Le_ij.values(), Eq_ij.values(), Ge_ij.values())), 'eq_ij') # transitivity for (i, j), eq_a in Eq_ij.items(): le_a = Le_ij[i, j] ge_a = Ge_ij[i, j] for k in nodes: j_, k_ = j, k if j > k: j_, k_ = k, j eq_b = Eq_ij.get((j_, k_), None) if eq_b is None: continue else: le_b = Le_ij[j_, k_] ge_b = Ge_ij[j_, k_] if j_ != j: le_b, ge_b = ge_b, le_b i_, k_ = i, k if i > k: i_, k_ = k, i eq_c = Eq_ij.get((i_, k_), None) if eq_c is None: continue else: le_c = Le_ij[i_, k_] ge_c = Ge_ij[i_, k_] if i_ != i: le_c, ge_c = ge_c, le_c # a <= b and b <= c -> a <= c model.addLConstr(ge_a + ge_b, GRB.LESS_EQUAL, 1 + ge_c, f'transitivity_ge_{i},{j},{k}') # a >= b and b >= c -> a >= c model.addLConstr(le_a + le_b, GRB.LESS_EQUAL, 1 + le_c, f'transitivity_le_{i},{j},{k}') # a <= b and b == c -> a <= c model.addLConstr(le_a + eq_b, GRB.LESS_EQUAL, 1 + le_c, f'transitivity_leeq_{i},{j},{k}') # a == b and b <= c -> a <= c model.addLConstr(eq_a + le_b, GRB.LESS_EQUAL, 1 + le_c, f'transitivity_eqle_{i},{j},{k}') # a >= b and b == c --> a >= c model.addLConstr(ge_a + eq_b, GRB.LESS_EQUAL, 1 + ge_c, f'transitivity_geeq_{i},{j},{k}') # a == b and b >= c --> a >= c model.addLConstr(eq_a + ge_b, GRB.LESS_EQUAL, 1 + ge_c, f'transitivity_eqge_{i},{j},{k}') # a == b and b == c --> a == c model.addLConstr(eq_a + eq_b, GRB.LESS_EQUAL, 1 + eq_c, f'transitivity_eq_{i},{j},{k}') # transitivity helper (for not-fully connected graphs) # also provides a latent rank big_m = max_rank model.addConstrs(((1 - ge_ij) * big_m + R_i[i] >= R_i[j] + 1 for (i, j), ge_ij in Ge_ij.items()), 'rank_transitivity_larger') model.addConstrs(((1 - le_ij) * big_m + R_i[j] >= R_i[i] + 1 for (i, j), le_ij in Le_ij.items()), 'rank_transitivity_smaller') model.addConstrs(((1 - eq_ij) * big_m + R_i[j] >= R_i[i] for (i, j), eq_ij in Eq_ij.items()), 'rank_transitivity_equal1') model.addConstrs(((1 - eq_ij) * big_m + R_i[i] >= R_i[j] for (i, j), eq_ij in Eq_ij.items()), 'rank_transitivity_equal2') # objective function objective = LinExpr() for ij, value in comparisons.items(): if value == 1.0: objective += -E_ij[ij] elif value == 0.0: objective += E_ij[ij] else: objective += T_ij_pos[ij] + T_ij_neg[ij] model.setObjective(objective, GRB.MINIMIZE) # solve model.optimize() # verify abs emulation: one T_ij has to be 0 for ij, value in T_ij_pos.items(): assert value.X == 0 or T_ij_neg[ij] == 0, \ f'T_{ij} pos {value.X} neg {T_ij_neg[ij]}' # find minimal Rs model_ = Model('comparison') model_.setParam('OutputFlag', verbose) R_i = model_.addVars(nodes, name='r_i', vtype=GRB.CONTINUOUS, lb=0, ub=len(nodes)) for ((i, j), ge_ij), le_ij in zip(Ge_ij.items(), Le_ij.values()): if ge_ij.x == 1: model_.addConstr(R_i[i] >= R_i[j] + 1) elif le_ij.x == 1: model_.addConstr(R_i[j] >= R_i[i] + 1) else: model_.addConstr(R_i[j] == R_i[i]) model_.setObjective(R_i.sum(), GRB.MINIMIZE) model_.optimize() return [model_.getVarByName(f'r_i[{i}]').X for i in range(len(nodes))], \ model.objVal
def gurobi_solve_qp(P, q, G=None, h=None, A=None, b=None, initvals=None): """ Solve a Quadratic Program defined as: minimize (1/2) * x.T * P * x + q.T * x subject to G * x <= h A * x == b using Gurobi <http://www.gurobi.com/>. Parameters ---------- P : array, shape=(n, n) Primal quadratic cost matrix. q : array, shape=(n,) Primal quadratic cost vector. G : array, shape=(m, n) Linear inequality constraint matrix. h : array, shape=(m,) Linear inequality constraint vector. A : array, shape=(meq, n), optional Linear equality constraint matrix. b : array, shape=(meq,), optional Linear equality constraint vector. initvals : array, shape=(n,), optional Warm-start guess vector (not used). Returns ------- x : array, shape=(n,) Solution to the QP, if found, otherwise ``None``. """ if initvals is not None: print("Gurobi: note that warm-start values are ignored by wrapper") n = P.shape[1] model = Model() x = { i: model.addVar( vtype=GRB.CONTINUOUS, name='x_%d' % i, lb=-GRB.INFINITY, ub=+GRB.INFINITY) for i in range(n) } model.update() # integrate new variables # minimize # 1/2 x.T * P * x + q * x obj = QuadExpr() rows, cols = P.nonzero() for i, j in zip(rows, cols): obj += 0.5 * x[i] * P[i, j] * x[j] for i in range(n): obj += q[i] * x[i] model.setObjective(obj, GRB.MINIMIZE) # subject to # G * x <= h if G is not None: G_nonzero_rows = get_nonzero_rows(G) for i, row in G_nonzero_rows.items(): model.addConstr(quicksum(G[i, j] * x[j] for j in row) <= h[i]) # subject to # A * x == b if A is not None: A_nonzero_rows = get_nonzero_rows(A) for i, row in A_nonzero_rows.items(): model.addConstr(quicksum(A[i, j] * x[j] for j in row) == b[i]) model.optimize() a = empty(n) for i in range(n): a[i] = model.getVarByName('x_%d' % i).x return a