def _create_main_constraints(self): # Depending on what you need, you may want to consider creating any of # the expressions (constraints or objective terms) as an attribute of # the OptimizationModel class (e.g. self.inv_balance_constraints). # That way if, for example, at the end of the optimization you need to check # the slack variables of certain constraints, you know they already exists in your model # ================== Inventory balance constraints ================== self.inv_balance_constraints = self.model.addConstraint( xp.constraint(body=self.inventory_variables[period - 1] + self.production_variables[period] - self.inventory_variables[period], sense=xp.eq, name='inv_balance' + str(period), rhs=value.demand) for period, value in self.input_data.iloc[1:].iterrows()) # inv balance for first period self.first_period_inv_balance_constraints = self.model.addConstraint( xp.constraint(body=self.production_variables[0] - self.inventory_variables[0], sense=xp.eq, name='inv_balance0', rhs=self.input_data.iloc[0].demand - self.input_params['initial_inventory'])) # ================== Production capacity constraints ================== self.production_capacity_constraints = self.model.addConstraint( xp.constraint(body=value, sense=xp.leq, name='prod_cap_month_' + str(index), rhs=self.input_data.iloc[index].production_capacity) for index, value in self.production_variables.items())
def _add_constraint(self, con): if not con.active: return None if is_fixed(con.body): if self._skip_trivial_constraints: return None conname = self._symbol_map.getSymbol(con, self._labeler) if con._linear_canonical_form: xpress_expr, referenced_vars = self._get_expr_from_pyomo_repn( con.canonical_form(), self._max_constraint_degree) else: xpress_expr, referenced_vars = self._get_expr_from_pyomo_expr( con.body, self._max_constraint_degree) if con.has_lb(): if not is_fixed(con.lower): raise ValueError("Lower bound of constraint {0} " "is not constant.".format(con)) if con.has_ub(): if not is_fixed(con.upper): raise ValueError("Upper bound of constraint {0} " "is not constant.".format(con)) if con.equality: xpress_con = xpress.constraint(body=xpress_expr, sense=xpress.eq, rhs=value(con.lower), name=conname) elif con.has_lb() and con.has_ub(): xpress_con = xpress.constraint(body=xpress_expr, sense=xpress.range, lb=value(con.lower), ub=value(con.upper), name=conname) self._range_constraints.add(xpress_con) elif con.has_lb(): xpress_con = xpress.constraint(body=xpress_expr, sense=xpress.geq, rhs=value(con.lower), name=conname) elif con.has_ub(): xpress_con = xpress.constraint(body=xpress_expr, sense=xpress.leq, rhs=value(con.upper), name=conname) else: raise ValueError("Constraint does not have a lower " "or an upper bound: {0} \n".format(con)) self._solver_model.addConstraint(xpress_con) for var in referenced_vars: self._referenced_variables[var] += 1 self._vars_referenced_by_con[con] = referenced_vars self._pyomo_con_to_solver_con_map[con] = xpress_con self._solver_con_to_pyomo_con_map[xpress_con] = con
def force_auxiliary_variables_1(self, doctor, total_work_time): aux_definition1 = xp.constraint( xp.Sum(p.shift.duration_in_hours * self.x[p] for p in self.x if p.doctor == doctor) - doctor.allocation * total_work_time <= self.t[doctor], name=f'Auxiliary 1 for {doctor}') self.model.addConstraint(aux_definition1)
def add_constraint(self, constraint, name=""): """ Add a constraint to the model. :param constraint: A constraint created by via linear or quadratic combination of variables and numbers. Be sure to use Model.sum for summing over iterables. :param name: The name of the constraint. Ignored if falsey. :return: The constraint object associated with the model_type engine API """ if self.model_type == "gurobi": return self.core_model.addConstr( constraint, **({ "name": name } if name else {})) if self.model_type == "cplex": return self.core_model.add_constraint( constraint, **({ "ctname": name } if name else {})) if self.model_type == "xpress": rtn = xpress.constraint(constraint, **({ "name": name } if name else {})) self.core_model.addConstraint(rtn) return rtn
def force_shift_fulfillment(self, shift, hospital): shift_fulfillment = xp.constraint( xp.Sum(self.x[p] for p in self.x if p.shift == shift and p.hospital == hospital) == 1, name= f'Force shift fulfillment for shift {shift} and hospital {hospital}' ) self.model.addConstraint(shift_fulfillment)
def max_time_working(self, doctor, shift_day, day_limit): time_working = xp.constraint( xp.Sum(self.x[p] for p in self.x for j in range(day_limit + 1) if p.doctor == doctor and p.shift.start_time.date() == (shift_day + timedelta(days=j)).date()) <= day_limit, name= f'Max working time for day {shift_day} and doctor {doctor.name}') self.model.addConstraint(time_working)
def force_nightshift_work(self, nightshift, n_days_working): nightshift_work = xp.constraint( xp.Sum(self.x[p] for p in self.x for k in range(n_days_working) if p.doctor == nightshift.doctor and p.shift.start_time. date() == (nightshift.day.date() + timedelta(days=k))) >= 3 * self.y[nightshift], name= f'Force nightshift for doctor {nightshift.doctor} at day {nightshift.day}' ) self.model.addConstraint(nightshift_work)
def force_rest(self, nightshift, n_days_working, n_days_rest): force_rest = xp.constraint( xp.Sum( self.x[p] for p in self.x for k in range(n_days_working, n_days_working + n_days_rest) if p.doctor == nightshift.doctor and p.shift.start_time.date() == (nightshift.day.date() + timedelta(days=k))) <= 3 * (1 - self.y[nightshift]), name= f'Force rest for doctor {nightshift.doctor} at day {nightshift.day}' ) self.model.addConstraint(force_rest)
def qsap_elf(qsap, **kwargs): """ extended linear formulation for the quadratic semi-assignment problem """ n = qsap.n m = qsap.m e = qsap.e c = qsap.c mdl = xp.problem(name='qsap_elf') x = np.array([[xp.var(vartype=xp.binary) for i in range(n)]for j in range(m)]) z = np.array([[[[[[xp.var(vartype=xp.continuous, lb=-xp.infinity) for i in range(n)] for j in range(m)] for k in range(n)] for l in range(m)] for r in range(n)] for s in range(m)]) mdl.addVariable(x, z) cons = [xp.constraint(sum(x[i,k] for k in range(n)) == 1) for i in range(m)] mdl.addConstraint(cons) #add auxiliary constraints for i in range(m-1): for j in range(i+1,m): for k in range(n): for l in range(n): mdl.addConstraint(z[i,k,i,k,j,l] + z[j,l,i,k,j,l] <= 1) mdl.addConstraint(x[i,k] + z[i,k,i,k,j,l] <= 1) mdl.addConstraint(x[j,l] + z[j,l,i,k,j,l] <= 1) mdl.addConstraint(x[i,k] + z[i,k,i,k,j,l] + z[j,l,i,k,j,l] >= 1) mdl.addConstraint(x[j,l] + z[i,k,i,k,j,l] + z[j,l,i,k,j,l] >= 1) #compute quadratic values contirbution to obj constant = 0 quadratic_values = 0 for i in range(m-1): for j in range(i+1,m): for k in range(n): for l in range(n): constant = constant + c[i,k,j,l] quadratic_values = quadratic_values + (c[i,k,j,l]*(z[i,k,i,k,j,l]+z[j,l,i,k,j,l])) linear_values = sum(x[i,k]*e[i,k] for k in range(n) for i in range(m)) mdl.setObjective(linear_values + constant - quadratic_values, sense=xp.maximize) #return model + setup time return [mdl, 0]
def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, solver_cache=None): import xpress as xp c = data[s.C] # objective coefficients dims = dims_to_solver_dict( data[s.DIMS]) # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix # Problem self.prob_ = xp.problem() mstart = makeMstart(A, len(c), 1) varGroups = { } # If origprob is passed, used to tie IIS to original constraints transf2Orig = { } # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # Uses flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'.format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 self.prob_.controls.xslp_log = -1 self.prob_.loadproblem( probname="CVX_xpress_conic", # constraint types qrtypes=['E'] * nrowsEQ + ['L'] * nrowsLEQ, rhs=b, # rhs range=None, # range obj=c, # obj coeff mstart=mstart, # mstart mnel=None, # mnel (unused) # linear coefficients mrwind=A.indices[A.data != 0], # row indices dmatval=A.data[A.data != 0], # coefficients dlb=[-xp.infinity] * len(c), # lower bound dub=[xp.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names # Set variable types for discrete variables self.prob_.chgcoltype( data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow:currow + k].tocsr() b = data[s.B][currow:currow + k] currow += k # Create new (cone) variables and add them to the problem conevar = np.array([ xp.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xp.infinity if i > 0 else 0) for i in range(k) ]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = ['linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k)] # Linear transformation for cone variables <--> original variables self.prob_.addrows( ['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices[A.data != 0], # ind A.data[A.data != 0], # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xp.constraint(constraint=xp.Sum( conevar[i]**2 for i in range(1, k)) <= conevar[0]**2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. self.prob_.setControl({ i: solver_opts[i] for i in solver_opts if i in xp.controls.__dict__ }) if 'bargaptarget' not in solver_opts: self.prob_.controls.bargaptarget = 1e-30 if 'feastol' not in solver_opts: self.prob_.controls.feastol = 1e-9 # If option given, write file before solving if 'write_mps' in solver_opts: self.prob_.write(solver_opts['write_mps']) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = get_status_maps() if 'mip_' in self.prob_.getProbStatusString(): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[ s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not (data[s.BOOL_IDX] or data[s.INT_IDX]): results_dict['y'] = -np.array(self.prob_.getDual()) elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig: name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{ 'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols }] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append( (row, col, rtype, btype, duals, rdcs, isrows, icols)) # Generate solution. solution = {} status_map_lp, status_map_mip = get_status_maps() if data[s.BOOL_IDX] or data[s.INT_IDX]: solution[s.STATUS] = status_map_mip[results_dict['status']] else: solution[s.STATUS] = status_map_lp[results_dict['status']] if solution[s.STATUS] in s.SOLUTION_PRESENT: solution[s.PRIMAL] = results_dict['x'] solution[s.VALUE] = results_dict['obj_value'] if not (data[s.BOOL_IDX] or data[s.INT_IDX]): solution[s.EQ_DUAL] = results_dict['y'][0:dims[s.EQ_DIM]] solution[s.INEQ_DUAL] = results_dict['y'][dims[s.EQ_DIM]:] solution[s.XPRESS_IIS] = results_dict[s.XPRESS_IIS] solution[s.XPRESS_TROW] = results_dict[s.XPRESS_TROW] solution['getObjVal'] = self.prob_.getObjVal() solution[s.SOLVE_TIME] = self.prob_.attributes.time del self.prob_ return solution
def defineMP_artificialVariables(data, initialColumns, LP=True, outputPrint=False, problemName='RMP_artificialVariables', coverConstraint='='): '''Define the problem with artificial variables specified by data and load initial columns.''' '''Unpack data''' for key in data: globals()[key] = data[key] '''Unpack column data''' rosterlines, CK, A, A_W, A_O, A_g, V, D = initialColumns.unpackColumns( data=data) '''Setup''' p = xp.problem(name=problemName) p.controls.outputlog = outputPrint '''Variables''' if LP: vartype = 'continuous' else: vartype = 'binary' # Note: This implementation is approx. 25% slower than when using # lists, but is easier to use lambda-variables l = dict.fromkeys((e, k) for e in Employees for k in rosterlines[e]) for e in Employees: for k in rosterlines[e]: l[(e, k)] = xp.var(vartype=xp.__dict__[vartype], name='lambda({},{})'.format(e, k)) # Over- and undercoverage wPlus = dict.fromkeys((d, s) for d in Days for s in ShiftTypesWorking) wMinus = dict.fromkeys((d, s) for d in Days for s in ShiftTypesWorking) for s in ShiftTypesWorking: for d in Days: wPlus[(d, s)] = xp.var(vartype=xp.continuous, name='w_plus({},{})'.format(d, s), ub=OvercoverageLimit[(d, s)]) wMinus[(d, s)] = xp.var(vartype=xp.continuous, name='w_minus({},{})'.format(d, s), ub=UndercoverageLimit[(d, s)]) # s-variables strict = dict.fromkeys((e, d) for e in Employees for d in Days) for e in Employees: for d in Days: strict[(e, d)] = xp.var(vartype=xp.__dict__[vartype], name='s({},{})'.format(e, d)) # over- and undertime u_plus = dict.fromkeys( (e, d) for e in Employees for d in NormPeriodStartDays) u_minus = dict.fromkeys( (e, d) for e in Employees for d in NormPeriodStartDays) for e in Employees: for d in NormPeriodStartDays: u_plus[(e, d)] = xp.var(vartype=xp.continuous, name='u_plus({},{})'.format(e, d), ub=WMax_plus[e]) u_minus[(e, d)] = xp.var(vartype=xp.continuous, name='u_minus({},{})'.format(e, d), ub=WMax_minus[e]) # m-variables keyVec1 = [(e, pat, d) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)]] keyVec2 = [(e, pat, d) for e in Employees if e in PatternsPenalized.keys() for pat in PatternsPenalized[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)]] keyVec = keyVec1 + keyVec2 m = dict.fromkeys(keyVec) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: m[(e, pat, d)] = xp.var(vartype=xp.__dict__[vartype], name='m({},{},{})'.format(e, pat, d)) if e in PatternsPenalized.keys(): for pat in PatternsPenalized[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: m[(e, pat, d)] = xp.var(vartype=xp.__dict__[vartype], name='m({},{},{})'.format(e, pat, d)) '''Artificial variables''' # One for each constraint # Demand coverage demandCoverage_variables_plus = dict.fromkeys( (d, s) for d in Days for s in ShiftTypesWorking) demandCoverage_variables_minus = dict.fromkeys( (d, s) for d in Days for s in ShiftTypesWorking) for d in Days: for s in ShiftTypesWorking: demandCoverage_variables_plus[(d, s)] = xp.var( vartype=xp.continuous, name='demandCoverage_variables_plus({},{})'.format(d, s)) demandCoverage_variables_minus[(d, s)] = xp.var( vartype=xp.continuous, name='demandCoverage_variables_minus({},{})'.format(d, s)) # Maximum consecutive days working maxConsecutiveDays_variables = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - Nmax) for e in Employees: for d in Days: if d <= nDays - Nmax: maxConsecutiveDays_variables[(e, d)] = xp.var( vartype=xp.continuous, name='maxConsecutiveDays_variables({},{})'.format(e, d)) # Maximum consecutive days working shift group maxConsecutiveDaysGroup_variables = dict.fromkeys( (e, d, g) for g in ShiftGroups for e in Employees for d in Days if d <= nDays - NmaxGroup[g]) for g in ShiftGroups: for e in Employees: for d in Days: if d <= nDays - NmaxGroup[g]: maxConsecutiveDaysGroup_variables[(e, d, g)] = xp.var( vartype=xp.continuous, name='maxConsecutiveDaysGroup_variables({},{},{})'. format(e, d, g)) # Maxiumum number of days with rest penalty maxDaysWithRestPenalty_variables = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - D_R + 1) for e in Employees: for d in Days: if d <= nDays - D_R + 1: maxDaysWithRestPenalty_variables[(e, d)] = xp.var( vartype=xp.continuous, name='maxDaysWithRestPenalty_variables({},{})'.format( e, d)) # Min number of strict days off minStrictDaysOff_variables = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - D_S + 1) for e in Employees: for d in Days: if d <= nDays - D_S + 1: minStrictDaysOff_variables[(e, d)] = xp.var( vartype=xp.continuous, name='minStrictDaysOff_variables({},{})'.format(e, d)) # Workload workload_variables_plus = dict.fromkeys( (e, d) for e in Employees for d in NormPeriodStartDays) workload_variables_minus = dict.fromkeys( (e, d) for e in Employees for d in NormPeriodStartDays) for e in Employees: for d in NormPeriodStartDays: workload_variables_plus[(e, d)] = xp.var( vartype=xp.continuous, name='workload_variables_plus({},{})'.format(e, d)) workload_variables_minus[(e, d)] = xp.var( vartype=xp.continuous, name='workload_variables_minus({},{})'.format(e, d)) # Illegal patterns illegalPatterns_variables = dict.fromkeys((e, pat, d) for e in Employees if e in PatternsIllegal.keys() for pat in PatternsIllegal[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)]) for e in Employees: if e in PatternsIllegal.keys(): for pat in PatternsIllegal[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: illegalPatterns_variables[(e, pat, d)] = xp.var( vartype=xp.continuous, name='illegalPatterns_variables({},{},{})'.format( e, pat, d)) p.addVariable(l, wPlus, wMinus, strict, u_plus, u_minus, m, demandCoverage_variables_plus, demandCoverage_variables_minus, maxConsecutiveDays_variables, maxConsecutiveDaysGroup_variables, maxDaysWithRestPenalty_variables, minStrictDaysOff_variables, workload_variables_plus, workload_variables_minus, illegalPatterns_variables) '''Objective function''' # Sum of artificial variables p.setObjective(xp.Sum([ demandCoverage_variables_plus[(d, s)] for d in Days for s in ShiftTypesWorking ]) + xp.Sum([ demandCoverage_variables_minus[(d, s)] for d in Days for s in ShiftTypesWorking ]) + xp.Sum([ maxConsecutiveDays_variables[(e, d)] for e in Employees for d in Days if d <= nDays - Nmax ]) + xp.Sum([ maxConsecutiveDaysGroup_variables[(e, d, g)] for g in ShiftGroups for e in Employees for d in Days if d <= nDays - NmaxGroup[g] ]) + xp.Sum([ maxDaysWithRestPenalty_variables[(e, d)] for e in Employees for d in Days if d <= nDays - D_R + 1 ]) + xp.Sum([ minStrictDaysOff_variables[(e, d)] for e in Employees for d in Days if d <= nDays - D_S + 1 ]) + xp.Sum([ workload_variables_plus[(e, d)] for e in Employees for d in NormPeriodStartDays ]) + xp.Sum([ workload_variables_minus[(e, d)] for e in Employees for d in NormPeriodStartDays ]) + xp.Sum([ illegalPatterns_variables[(e, pat, d)] for e in Employees if e in PatternsIllegal.keys() for pat in PatternsIllegal[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)] ]), sense=xp.minimize) '''Constraints''' # Demand coverage demandCoverage = dict.fromkeys( (d, s) for d in Days for s in ShiftTypesWorking) for d in Days: for s in ShiftTypesWorking: if coverConstraint == '>=': demandCoverage[(d, s)] = xp.constraint( name='demandCoverage({},{})'.format(d, s), constraint=xp.Sum([ A[(e, k, d, s)] * l[(e, k)] for e in Employees for k in rosterlines[e] ]) + wMinus[(d, s)] - wPlus[(d, s)] >= Demand[(d, s)] + demandCoverage_variables_plus[(d, s)] - demandCoverage_variables_minus[(d, s)]) else: demandCoverage[(d, s)] = xp.constraint( name='demandCoverage({},{})'.format(d, s), constraint=xp.Sum([ A[(e, k, d, s)] * l[(e, k)] for e in Employees for k in rosterlines[e] ]) + wMinus[(d, s)] - wPlus[(d, s)] == Demand[(d, s)] + demandCoverage_variables_plus[(d, s)] - demandCoverage_variables_minus[(d, s)]) # Convexity convexity = dict.fromkeys(e for e in Employees) for e in Employees: convexity[e] = xp.constraint( name='convexity({})'.format(e), constraint=xp.Sum([l[(e, k)] for k in rosterlines[e]]) == 1) # Maximum consecutive days working maxConsecutiveDays = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - Nmax) for e in Employees: for d in Days: if d <= nDays - Nmax: maxConsecutiveDays[(e, d)] = xp.constraint( name='maxConsecutiveDays({},{})'.format(e, d), constraint=xp.Sum([ A_W[(e, k, dd)] * l[(e, k)] for k in rosterlines[e] for dd in range(d, d + Nmax + 1) ]) <= Nmax + maxConsecutiveDays_variables[(e, d)]) # Maximum consecutive days working shift group maxConsecutiveDaysGroup = dict.fromkeys((e, d, g) for g in ShiftGroups for e in Employees for d in Days if d <= nDays - NmaxGroup[g]) for g in ShiftGroups: for e in Employees: for d in Days: if d <= nDays - NmaxGroup[g]: maxConsecutiveDaysGroup[(e, d, g)] = xp.constraint( name='maxConsecutiveDaysGroup({},{},{})'.format( e, d, g), constraint=xp.Sum([ A_g[(e, k, dd, g)] * l[(e, k)] for k in rosterlines[e] for dd in range(d, d + NmaxGroup[g] + 1) ]) <= NmaxGroup[g] + maxConsecutiveDaysGroup_variables[(e, d, g)]) # Maxiumum number of days with rest penalty maxDaysWithRestPenalty = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - D_R + 1) for e in Employees: for d in Days: if d <= nDays - D_R + 1: maxDaysWithRestPenalty[(e, d)] = xp.constraint( name='maxDaysWithRestPenalty({},{})'.format(e, d), constraint=xp.Sum([ V[(e, k, dd)] * l[(e, k)] for k in rosterlines[e] for dd in range(d, d + D_R) ]) <= Nmax_R + maxDaysWithRestPenalty_variables[(e, d)]) # Strict days off # Day off strictDaysOff_dayOff = dict.fromkeys( (e, d) for e in Employees for d in Days) for e in Employees: for d in Days: strictDaysOff_dayOff[(e, d)] = xp.constraint( name='strictDaysOff_dayOff({},{})'.format(e, d), constraint=strict[(e, d)] - xp.Sum([A_O[(e, k, d)] * l[(e, k)] for k in rosterlines[e]]) <= 0) # One strict day off strictDaysOff_one = dict.fromkeys((e, d, s1, s2) for e in Employees for d in Days[1:nDays - 1] for s1 in ShiftTypesWorking if s1 in StrictDayOff1.keys() for s2 in StrictDayOff1[s1]) for e in Employees: for d in Days[1:nDays - 1]: for s1 in ShiftTypesWorking: if s1 in StrictDayOff1.keys(): for s2 in StrictDayOff1[s1]: strictDaysOff_one[(e, d, s1, s2)] = xp.constraint( name='strictDaysOff_one({},{},{},{})'.format( e, d, s1, s2), constraint=xp.Sum([(A[(e, k, d - 1, s1)] + A[ (e, k, d + 1, s2)]) * l[(e, k)] for k in rosterlines[e]]) + strict[(e, d)] <= 2) # Two strict days off strictDaysOff_two = dict.fromkeys((e, d, s1, s2) for e in Employees for d in Days[1:nDays - 2] for s1 in ShiftTypesWorking if s1 in StrictDayOff2.keys() for s2 in StrictDayOff2[s1]) for e in Employees: for d in Days[1:nDays - 2]: for s1 in ShiftTypesWorking: if s1 in StrictDayOff2.keys(): for s2 in StrictDayOff2[s1]: strictDaysOff_two[(e, d, s1, s2)] = xp.constraint( name='strictDaysOff_two({},{},{},{})'.format( e, d, s1, s2), constraint=xp.Sum([(A[(e, k, d - 1, s1)] + A[ (e, k, d + 2, s2)]) * l[(e, k)] for k in rosterlines[e]]) + strict[(e, d)] + strict[(e, d + 1)] <= 3) # Min number of strict days off minStrictDaysOff = dict.fromkeys( (e, d) for e in Employees for d in Days if d <= nDays - D_S + 1) for e in Employees: for d in Days: if d <= nDays - D_S + 1: minStrictDaysOff[(e, d)] = xp.constraint( name='minStrictDaysOff({},{})'.format(e, d), constraint=xp.Sum([ strict[(e, dd)] for dd in range(d, d + D_S) ]) >= Nmin_S - minStrictDaysOff_variables[(e, d)]) # Workload workload = dict.fromkeys( (e, d) for e in Employees for d in NormPeriodStartDays) for e in Employees: for d in NormPeriodStartDays: workload[(e, d)] = xp.constraint( name='workload({},{})'.format(e, d), constraint=xp.Sum( [D[(e, k, d)] * l[(e, k)] for k in rosterlines[e]]) - u_plus[(e, d)] + u_minus[(e, d)] == W_N * H_W[e] + workload_variables_plus[(e, d)] - workload_variables_minus[(e, d)]) # Patterns # Illegal patterns illegalPatterns = dict.fromkeys((e, pat, d) for e in Employees if e in PatternsIllegal.keys() for pat in PatternsIllegal[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)]) for e in Employees: if e in PatternsIllegal.keys(): for pat in PatternsIllegal[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: illegalPatterns[(e, pat, d)] = xp.constraint( name='illegalPatterns({},{},{})'.format(e, pat, d), constraint=xp.Sum([ M[(e, pat, dd, g)] * A_g[(e, k, d + dd - 1, g)] * l[(e, k)] for k in rosterlines[e] for dd in range(1, PatternDuration[(e, pat)] + 1) for g in ShiftGroups ]) <= PatternDuration[(e, pat)] - 1 + illegalPatterns_variables[(e, pat, d)]) # Penalized patterns penalizedPatterns = dict.fromkeys((e, pat, d) for e in Employees if e in PatternsPenalized.keys() for pat in PatternsPenalized[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)]) for e in Employees: if e in PatternsPenalized.keys(): for pat in PatternsPenalized[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: penalizedPatterns[(e, pat, d)] = xp.constraint( name='penalizedPatterns({},{},{})'.format( e, pat, d), constraint=xp.Sum([ M[(e, pat, dd, g)] * A_g[(e, k, d + dd - 1, g)] * l[(e, k)] for k in rosterlines[e] for dd in range(1, PatternDuration[(e, pat)] + 1) for g in ShiftGroups ]) <= PatternDuration[(e, pat)] + m[(e, pat, d)] - 1) # Rewarded patterns rewardedPatterns = dict.fromkeys( (e, pat, d, dd) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)] for dd in range(1, PatternDuration[(e, pat)] + 1)) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: for dd in range(1, PatternDuration[(e, pat)] + 1): rewardedPatterns[(e, pat, d, dd)] = xp.constraint( name='rewardedPatterns({},{},{},{})'.format( e, pat, d, dd), constraint=xp.Sum([ M[(e, pat, dd, g)] * A_g[(e, k, d + dd - 1, g)] * l[(e, k)] for k in rosterlines[e] for g in ShiftGroups ]) >= m[(e, pat, d)]) # Overlapping patterns overlappingPatterns = dict.fromkeys( (e, pat, d) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] if PatternDuration[(e, pat)] > 2 for d in PatternDays[(e, pat)] if d <= nDays - 2 * PatternDuration[(e, pat)] + 3) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: if PatternDuration[(e, pat)] > 2: for d in PatternDays[(e, pat)]: if d <= nDays - 2 * PatternDuration[(e, pat)] + 3: overlappingPatterns[(e, pat, d)] = xp.constraint( name='overlappingPatterns({},{},{})'.format( e, pat, d), constraint=xp.Sum([ m[(e, pat, dd)] for dd in range( d, d + PatternDuration[(e, pat)] - 1) if dd in PatternDays[(e, pat)] ]) <= 1) p.addConstraint(demandCoverage, convexity, maxConsecutiveDays, maxConsecutiveDaysGroup, maxDaysWithRestPenalty, strictDaysOff_dayOff, strictDaysOff_one, strictDaysOff_two, minStrictDaysOff, workload, illegalPatterns, penalizedPatterns, rewardedPatterns, overlappingPatterns) return p
def enforce_hospital_limit(self, doctor): hospital_limit = xp.constraint( xp.Sum(self.x[p] for p in self.x if p.doctor == doctor and p.hospital not in doctor.work_locations) == 0, name=f'Enforce hospital limits for doctor {doctor.name}') self.model.addConstraint(hospital_limit)
vartype=xp.continuous) # Alternative way of creating the variables # production_variables = {i: xp.var(name=f'X{i}', vartype=xp.continuous) # for i in input_df_dict['input_data'].index} # inventory_variables = {i: xp.var(name=f'I{i}', vartype=xp.continuous) # for i in input_df_dict['input_data'].index} model.addVariable(production_variables, inventory_variables) logger.debug(f'var declaration time: {time() - start:.6f}') # ================== Inventory balance constraints ================== model.addConstraint( xp.constraint(body=inventory_variables[period - 1] + production_variables[period] - inventory_variables[period], sense=xp.eq, name='inv_balance' + str(period), rhs=value.demand) for period, value in input_df_dict['input_data'].iloc[1:].iterrows()) # inv balance for first period model.addConstraint( xp.constraint(body=production_variables[0] - inventory_variables[0], sense=xp.eq, name='inv_balance0', rhs=input_df_dict['input_data'].iloc[0].demand - input_param_dict['initial_inventory'])) # ================== Production capacity constraints ================== model.addConstraint( xp.constraint(
def one_shift_limit(self, doctor, shift_day): shift_limit = xp.constraint( xp.Sum(self.x[p] for p in self.x if p.doctor == doctor and p.shift.start_time.date() == shift_day.date()) <= 1, name=f'One shift limit for {doctor.name} and day {shift_day}') self.model.addConstraint(shift_limit)
def demand(i, d): if dem[d][0] == i: # source return 1 elif dem[d][1] == i: # destination return -1 else: return 0 # Flow conservation constraints: total flow balance at node i for each demand d must be 0 if # i is an intermediate node, 1 if i is the source of demand d, and -1 if i is the destination. flow = {(i, d): xp.constraint( constraint=xp.Sum(f[i, j, d] for j in range(n) if (i, j) in arcs) - xp.Sum(f[j, i, d] for j in range(n) if (j, i) in arcs) == demand(i, d), name='cons_{0}_{1}_{2}'.format(i, dem[d][0], dem[d][1])) for d in range(len(dem)) for i in range(n)} # Capacity constraints: weighted sum of flow variables must be contained in the total capacity installed on the arc (i,j) capacity = { (i, j): xp.constraint(constraint=xp.Sum(dem[d][2] * f[i, j, d] for d in range(len(dem))) <= U * x[i, j], name='capacity_{0}_{1}'.format(i, j)) for (i, j) in arcs } p.addConstraint(flow, capacity) p.setObjective(xp.Sum(c[i, j] * x[i, j] for (i, j) in arcs))
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): import xpress if 'no_qp_reduction' in solver_opts.keys( ) and solver_opts['no_qp_reduction'] is True: self.translate_back_QP_ = True c = data[s.C] # objective coefficients dims = dims_to_solver_dict( data[s.DIMS]) # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix # Problem self.prob_ = xpress.problem() mstart = makeMstart(A, len(c), 1) varGroups = { } # If origprob is passed, used to tie IIS to original constraints transf2Orig = { } # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # Uses flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'.format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] self.prob_.loadproblem( "CVXproblem", ['E'] * nrowsEQ + ['L'] * nrowsLEQ, # qrtypes None, # range c, # obj coeff mstart, # mstart None, # mnel A.indices, # row indices A.data, # coefficients [-xpress.infinity] * len(c), # lower bound [xpress.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names x = np.array(self.prob_.getVariable()) # get whole variable vector # Set variable types for discrete variables self.prob_.chgcoltype( data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow:currow + k].tocsr() b = data[s.B][currow:currow + k] currow += k if self.translate_back_QP_: # Conic problem passed by CVXPY is translated back # into a QP problem. The problem is passed to us # as follows: # # min c'x # s.t. Ax <>= b # y[i] = P[i]' * x + b[i] # ||y[i][1:]||_2 <= y[i][0] # # where P[i] is a matrix, b[i] is a vector. Get # rid of the y variables by explicitly rewriting # the conic constraint as quadratic: # # y[i][1:]' * y[i][1:] <= y[i][0]^2 # # and hence # # (P[i][1:]' * x + b[i][1:])^2 <= (P[i][0]' * x + b[i][0])^2 Plhs = A[1:] Prhs = A[0] indRowL, indColL = Plhs.nonzero() indRowR, indColR = Prhs.nonzero() coeL = Plhs.data coeR = Prhs.data lhs = list(b[1:]) rhs = b[0] for i in range(len(coeL)): lhs[indRowL[i]] -= coeL[i] * x[indColL[i]] for i in range(len(coeR)): rhs -= coeR[i] * x[indColR[i]] self.prob_.addConstraint( xpress.Sum([lhs[i]**2 for i in range(len(lhs))]) <= rhs**2) else: # Create new (cone) variables and add them to the problem conevar = np.array([ xpress.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xpress.infinity if i > 0 else 0) for i in range(k) ]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = [ 'linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k) ] # Linear transformation for cone variables <--> original variables self.prob_.addrows( ['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices, # ind A.data, # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xpress.constraint(constraint=xpress.Sum( conevar[i]**2 for i in range(1, k)) <= conevar[0]**2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # Objective. Minimize is by default both here and in CVXOPT self.prob_.setObjective(xpress.Sum(c[i] * x[i] for i in range(len(c)))) # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 if 'solver_opts' in solver_opts.keys(): self.prob_.setControl(solver_opts['solver_opts']) self.prob_.setControl({ i: solver_opts[i] for i in solver_opts.keys() if i in xpress.controls.__dict__.keys() }) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = self.get_status_maps() if self.is_mip(data): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[ s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not (data[s.BOOL_IDX] or data[s.INT_IDX]): results_dict['y'] = self.prob_.getDual() elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig.keys(): name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{ 'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols }] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append( (row, col, rtype, btype, duals, rdcs, isrows, icols)) # Generate solution. solution = {} if results_dict["status"] != s.SOLVER_ERROR: self.prob_ = results_dict['problem'] vartypes = [] self.prob_.getcoltype(vartypes, 0, len(data[s.C]) - 1) status_map_lp, status_map_mip = self.get_status_maps() if data[s.BOOL_IDX] or data[s.INT_IDX]: solution[s.STATUS] = status_map_mip[results_dict['status']] else: solution[s.STATUS] = status_map_lp[results_dict['status']] if solution[s.STATUS] in s.SOLUTION_PRESENT: solution[s.PRIMAL] = results_dict['x'] solution[s.VALUE] = results_dict['obj_value'] if not (data[s.BOOL_IDX] or data[s.INT_IDX]): solution[s.EQ_DUAL] = [-v for v in results_dict['y']] solution[s.XPRESS_IIS] = results_dict[s.XPRESS_IIS] solution[s.XPRESS_TROW] = results_dict[s.XPRESS_TROW] return solution
xp.controls.miprelstop = 0 # # All variables used in this example # v1 = xp.var(lb=0, ub=10, threshold=5, vartype=xp.continuous) v2 = xp.var(lb=1, ub=7, threshold=5, vartype=xp.continuous) v3 = xp.var(lb=7, ub=10, threshold=5, vartype=xp.semicontinuous) v4 = xp.var(lb=1, ub=7, threshold=3, vartype=xp.semiinteger) vb = xp.var(vartype=xp.integer, lb=0, ub=1) v = [xp.var(name="y{0}".format(i), lb=0, ub=2 * N) for i in S] # set name of a variable as cc = xp.constraint(body=v1 - v2, lb=2, ub=15) cc0 = xp.constraint(body=v1 + v2, lb=2, ub=15) m.addVariable( vb, v, v1, v2, v3, v4 ) # adds both v, a vector (list) of variables, and v1 and v2, two scalar variables. m.addConstraint(cc) # Indices of variables can be retrieved both using their name and # their Python object. print("index of v[0] from name: ", m.getIndexFromName(2, "y0")) print("index of v[0]: ", m.getIndex(v[0])) # Indicator constraints consist of a tuple with a condition on a # binary variable and a constraint)
def solveMIP(data, outputPrint = True, problemName = 'MIP', heuristicOnly = False, coverConstraint = '=', outputPickle = None, timeLimit = None, gapLimit = None): '''This is the MIP of the full roster problem in the master thesis of Sander Coates and Vegard Pedersen. The function takes as input instance data and returns feasibility (boolean), the objective value and the solution (in assignment x-variables). ''' # Initialize time start = time.time() '''Unpack data''' for key in data: globals()[key] = data[key] '''Pre-processing''' # Find employee shift types (based on skills of employees and shifts) EmployeeShiftTypes = {} for e in Employees: # Initialize list of shift types that may be assigned to the employee EmployeeShiftTypes[e] = [] # Iterate over working shift types for s in ShiftTypesWorking: # Iterate over the skill qualifications of the shift type for requiredSkill in SkillsShiftType[s]: # If the qualification is amongst the employee's if requiredSkill in SkillsEmployee[e]: # Add the shift type to the list and continue to next shift EmployeeShiftTypes[e].append(s) # Add off shifts that can be worked by all employees EmployeeShiftTypes[e] += [s for s in ShiftTypesOff] '''Setup''' p = xp.problem(name = problemName) p.controls.outputlog = outputPrint # If only heuristic search: set max nodes to 0 in B&B tree if heuristicOnly: p.setControl('maxnode', 0) # If time limit is input, set control if timeLimit != None: p.setControl('maxtime', -timeLimit) # If optimality gap limit is input, set control if gapLimit != None: p.setControl('miprelstop', gapLimit) '''Variables''' # Assignment x = dict.fromkeys((e,d,s) for e in Employees for d in Days for s in ShiftTypes) for e in Employees: for d in Days: for s in ShiftTypes: if s in EmployeeShiftTypes[e]: x[(e,d,s)] = xp.var(vartype = xp.binary, name = 'x({},{},{})'.format(e,d,s)) # Set variable to zero if it cannot be assigned to the employee else: x[(e,d,s)] = xp.var(vartype = xp.binary, name = 'x({},{},{})'.format(e,d,s), ub = 0) # Over- and undercoverage wPlus = dict.fromkeys((d,s) for d in Days for s in ShiftTypesWorking) wMinus = dict.fromkeys((d,s) for d in Days for s in ShiftTypesWorking) for s in ShiftTypesWorking: for d in Days: wPlus[(d,s)] = xp.var(vartype = xp.continuous, name = 'w_plus({},{})'.format(d,s), ub = OvercoverageLimit[(d,s)]) wMinus[(d,s)] = xp.var(vartype = xp.continuous, name = 'w_minus({},{})'.format(d,s), ub = UndercoverageLimit[(d,s)]) # Reduced rest v = dict.fromkeys((e,d) for e in Employees for d in Days) for e in Employees: for d in Days: v[(e,d)] = xp.var(vartype = xp.binary, name = 'v({},{})'.format(e,d)) # s-variables strict = dict.fromkeys((e,d) for e in Employees for d in Days) for e in Employees: for d in Days: strict[(e,d)] = xp.var(vartype = xp.binary, name = 's({},{})'.format(e,d)) # Over- and undertime u_plus = dict.fromkeys((e,d) for e in Employees for d in NormPeriodStartDays) u_minus = dict.fromkeys((e,d) for e in Employees for d in NormPeriodStartDays) for e in Employees: for d in NormPeriodStartDays: u_plus[(e,d)] = xp.var(vartype = xp.continuous, name = 'u_plus({},{})'.format(e,d), ub = WMax_plus[e]) u_minus[(e,d)] = xp.var(vartype = xp.continuous, name = 'u_minus({},{})'.format(e,d), ub = WMax_minus[e]) # m-variables keyVec1 = [(e,pat,d) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] for d in PatternDays[(e,pat)]] keyVec2 = [(e,pat,d) for e in Employees if e in PatternsPenalized.keys() for pat in PatternsPenalized[e] for d in PatternDays[(e,pat)]] keyVec = keyVec1 + keyVec2 m = dict.fromkeys(keyVec) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: for d in PatternDays[(e,pat)]: m[(e,pat,d)] = xp.var(vartype = xp.binary, name = 'm({},{},{})'.format(e,pat,d)) if e in PatternsPenalized.keys(): for pat in PatternsPenalized[e]: for d in PatternDays[(e,pat)]: m[(e,pat,d)] = xp.var(vartype = xp.binary, name = 'm({},{},{})'.format(e,pat,d)) # Add variables to problem p.addVariable(x, wPlus, wMinus, v, strict, u_plus, u_minus, m) '''Objective function''' p.setObjective(sense = xp.minimize, objective = # Cost of assignment to shift xp.Sum([C[(e,d,s)] * x[(e,d,s)] for e in Employees for d in Days for s in EmployeeShiftTypes[e]]) # Cost of overcoverage + xp.Sum([OvercoverageCost[(d,s)] * wPlus[(d,s)] for d in Days for s in ShiftTypesWorking]) # Cost of undercoverage + xp.Sum([UndercoverageCost[(d,s)] * wMinus[(d,s)] for d in Days for s in ShiftTypesWorking]) # Cost of reduced rest + xp.Sum([C_R * v[(e,d)] for e in Employees for d in Days]) # Cost of overtime + xp.Sum([OvertimeCost[e] * u_plus[(e,d)] for e in Employees for d in NormPeriodStartDays]) # Cost of undertime + xp.Sum([UndertimeCost[e] * u_minus[(e,d)] for e in Employees for d in NormPeriodStartDays]) # Cost of penalized patterns + xp.Sum([P[(e,pat)] * m[(e,pat,d)] for e in Employees if e in PatternsPenalized.keys() for pat in PatternsPenalized[e] for d in PatternDays[(e,pat)]]) # (Negative) Cost of rewarded patterns - xp.Sum([R[(e,pat)] * m[(e,pat,d)] for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] for d in PatternDays[(e,pat)]]) ) '''Constraints''' # Demand coverage demandCoverage = dict.fromkeys((d,s) for d in Days for s in ShiftTypesWorking) for d in Days: for s in ShiftTypesWorking: if coverConstraint == '>=': demandCoverage[(d,s)] = xp.constraint( name = 'demandCoverage({},{})'.format(d,s), constraint = xp.Sum([x[(e,d,s)] for e in Employees]) + wMinus[(d,s)] - wPlus[(d,s)] >= Demand[(d,s)] ) else: demandCoverage[(d,s)] = xp.constraint( name = 'demandCoverage({},{})'.format(d,s), constraint = xp.Sum([x[(e,d,s)] for e in Employees]) + wMinus[(d,s)] - wPlus[(d,s)] == Demand[(d,s)] ) # One shift per day oneShift = dict.fromkeys((e,d) for e in Employees for d in Days) for e in Employees: for d in Days: oneShift[(e,d)] = xp.constraint( name = 'oneShift({},{})'.format(e,d), constraint = xp.Sum([x[(e,d,s)] for s in EmployeeShiftTypes[e]]) == 1 ) # Maximum consecutive days working maxConsecutiveDays = dict.fromkeys((e,d) for e in Employees for d in Days if d <= nDays - Nmax) for e in Employees: for d in Days: if d <= nDays - Nmax: maxConsecutiveDays[(e,d)] = xp.constraint( name = 'maxConsecutiveDays({},{})'.format(e,d), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesWorking for dd in range(d,d + Nmax + 1)]) <= Nmax ) # Maximum consecutive days working shift group maxConsecutiveDaysGroup = dict.fromkeys((e,d,g) for g in ShiftGroups for e in Employees for d in Days if d <= nDays - NmaxGroup[g]) for g in ShiftGroups: for e in Employees: for d in Days: if d <= nDays - NmaxGroup[g]: maxConsecutiveDaysGroup[(e,d,g)] = xp.constraint( name = 'maxConsecutiveDaysGroup({},{},{})'.format(e,d,g), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesGroup[g] for dd in range(d,d + NmaxGroup[g] + 1)]) <= NmaxGroup[g] ) # Minimum consecutive days working minConsecutiveDays = dict.fromkeys((e,d) for e in Employees for d in [0] + Days if d <= nDays - Nmin[e]) for e in Employees: for d in Days: if d <= nDays - Nmin[e]: minConsecutiveDays[(e,d)] = xp.constraint( name = 'minConsecutiveDays({},{})'.format(e,d), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesWorking for dd in range(d + 1, d + Nmin[e] + 1)]) >= Nmin[e] * xp.Sum([x[(e,d,s)] - x[(e,d+1,s)] for s in ShiftTypesOff]) ) # Add constraint for day 0 minConsecutiveDays[(e,0)] = xp.constraint( name = 'minConsecutiveDays({},{})'.format(e,0), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesWorking for dd in range(1, Nmin[e] + 1)]) >= Nmin[e] * xp.Sum([1 - x[(e,1,s)] for s in ShiftTypesOff]) ) # Minimum consecutive days working shift group minConsecutiveDaysGroup = dict.fromkeys((e,d,g) for g in ShiftGroups for e in Employees for d in [0] + Days if d <= nDays - NminGroup[(e,g)]) for g in ShiftGroups: for e in Employees: for d in Days: if d <= nDays - NminGroup[(e,g)]: minConsecutiveDaysGroup[(e,d,g)] = xp.constraint( name = 'minConsecutiveDaysGroup({},{},{})'.format(e,d,g), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesGroup[g] for dd in range(d + 1, d + NminGroup[(e,g)] + 1)]) >= NminGroup[(e,g)] * xp.Sum([x[(e,d+1,s)] - x[(e,d,s)] for s in EmployeeShiftTypes[e] if s in ShiftTypesGroup[g]]) ) # Add constraint for day 0 minConsecutiveDaysGroup[(e,0,g)] = xp.constraint( name = 'minConsecutiveDaysGroup({},{},{})'.format(e,0,g), constraint = xp.Sum([x[(e,dd,s)] for s in EmployeeShiftTypes if s in ShiftTypesGroup[g] for dd in range(1, NminGroup[(e,g)] + 1)]) >= NminGroup[(e,g)] * xp.Sum([x[(e,1,s)] - 1 for s in EmployeeShiftTypes[e] if s in ShiftTypesGroup[g]]) ) # Required rest requiredRest = dict.fromkeys((e,d,s1,s2) for e in Employees for d in Days[1:] for s1 in EmployeeShiftTypes[e] if s1 in FollowingShiftsIllegal for s2 in FollowingShiftsIllegal[s1] if s2 in EmployeeShiftTypes[e]) for e in Employees: for d in Days[1:]: for s1 in EmployeeShiftTypes[e]: # Slight equivalent deviation from mathematical model if s1 in FollowingShiftsIllegal: for s2 in FollowingShiftsIllegal[s1]: if s2 in EmployeeShiftTypes[e]: requiredRest[(e,d,s1,s2)] = xp.constraint( name = 'requiredRest({},{},{},{})'.format(e,d,s1,s2), constraint = x[(e,d-1,s1)] + x[(e,d,s2)] <= 1 ) # Reduced rest reducedRest = dict.fromkeys((e,d,s1,s2) for e in Employees for d in Days[1:] for s1 in EmployeeShiftTypes[e] if s1 in FollowingShiftsPenalty for s2 in FollowingShiftsPenalty[s1] if s2 in EmployeeShiftTypes[e]) for e in Employees: for d in Days[1:]: for s1 in EmployeeShiftTypes[e]: # Slight equivalent deviation from mathematical model if s1 in FollowingShiftsPenalty: for s2 in FollowingShiftsPenalty[s1]: if s2 in EmployeeShiftTypes[e]: reducedRest[(e,d,s1,s2)] = xp.constraint( name = 'reducedRest({},{},{},{})'.format(e,d,s1,s2), constraint = x[(e,d-1,s1)] + x[(e,d,s2)] <= 1 + v[(e,d)] ) # Maxiumum number of days with rest penalty maxDaysWithRestPenalty = dict.fromkeys((e,d) for e in Employees for d in Days if d <= nDays - D_R + 1) for e in Employees: for d in Days: if d <= nDays - D_R + 1: maxDaysWithRestPenalty[(e,d)] = xp.constraint( name = 'maxDaysWithRestPenalty({},{})'.format(e,d), constraint = xp.Sum([v[(e,dd)] for dd in range(d, d + D_R)]) <= Nmax_R ) # Weekends # Both or no days in weekend bothOrNoWeekendDays = dict.fromkeys((e,d) for e in Employees for d in DaysOnWeekday['SAT'] if d <= nDays - 1) for e in Employees: for d in DaysOnWeekday['SAT']: if d <= nDays - 1: bothOrNoWeekendDays[(e,d)] = xp.constraint( name = 'bothOrNoWeekendDays({},{})'.format(e,d), constraint = xp.Sum([x[(e,d,s)] - x[(e,d+1,s)] for s in ShiftTypesOff]) == 0 ) # No late Friday shift before weekend off noLateIntoWeekend = dict.fromkeys((e,d) for e in Employees for d in DaysOnWeekday['SAT'] if d <= nDays - 1) for e in Employees: for d in DaysOnWeekday['SAT']: if d >= 2: noLateIntoWeekend[(e,d)] = xp.constraint( name = 'noLateIntoWeekend({},{})'.format(e,d), constraint = xp.Sum([x[(e,d-1,s)] for s in EmployeeShiftTypes[e] if s in ShiftTypesWorking if T_E[s] > H]) + xp.Sum([x[(e,d,s)] for s in ShiftTypesOff]) <= 1 ) # Min weekends off minWeekendsOff = dict.fromkeys((e,w) for e in Employees for w in Weeks if w <= nWeeks - W_W + 1) for e in Employees: for w in Weeks: if w <= nWeeks - W_W + 1: minWeekendsOff[(e,w)] = xp.constraint( name = 'minWeekendsOff({},{})'.format(e,w), constraint = xp.Sum([x[(e,d,s)] for ww in range(w, w + W_W) for s in ShiftTypesOff for d in DaysOnWeekdayInWeek[(ww,'SAT')]]) >= Nmin_W ) # Strict days off # Day off strictDaysOff_dayOff = dict.fromkeys((e,d) for e in Employees for d in Days) for e in Employees: for d in Days: strictDaysOff_dayOff[(e,d)] = xp.constraint( name = 'strictDaysOff_dayOff({},{})'.format(e,d), constraint = strict[(e,d)] - xp.Sum([x[(e,d,s)] for s in ShiftTypesOff]) <= 0 ) # One strict day off strictDaysOff_one = dict.fromkeys((e,d,s1,s2) for e in Employees for d in Days[1:nDays-1] for s1 in ShiftTypesWorking if (s1 in EmployeeShiftTypes[e] and s1 in StrictDayOff1.keys()) for s2 in StrictDayOff1[s1]) for e in Employees: for d in Days[1:nDays-1]: for s1 in ShiftTypesWorking: if (s1 in EmployeeShiftTypes[e] and s1 in StrictDayOff1.keys()): for s2 in StrictDayOff1[s1]: strictDaysOff_one[(e,d,s1,s2)] = xp.constraint( name = 'strictDaysOff_one({},{},{},{})'.format(e,d,s1,s2), constraint = x[(e,d-1,s1)] + x[(e,d+1,s2)] + strict[(e,d)] <= 2 ) # Two strict days off strictDaysOff_two = dict.fromkeys((e,d,s1,s2) for e in Employees for d in Days[1:nDays-2] for s1 in ShiftTypesWorking if (s1 in EmployeeShiftTypes[e] and s1 in StrictDayOff2.keys()) for s2 in StrictDayOff2[s1]) for e in Employees: for d in Days[1:nDays-2]: for s1 in ShiftTypesWorking: if (s1 in EmployeeShiftTypes[e] and s1 in StrictDayOff2.keys()): for s2 in StrictDayOff2[s1]: strictDaysOff_two[(e,d,s1,s2)] = xp.constraint( name = 'strictDaysOff_two({},{},{},{})'.format(e,d,s1,s2), constraint = x[(e,d-1,s1)] + x[(e,d+2,s2)] + strict[(e,d)] + strict[(e,d+1)] <= 3 ) # Min number of strict days off minStrictDaysOff = dict.fromkeys((e,d) for e in Employees for d in Days if d <= nDays - D_S + 1) for e in Employees: for d in Days: if d <= nDays - D_S + 1: minStrictDaysOff[(e,d)] = xp.constraint( name = 'minStrictDaysOff({},{})'.format(e,d), constraint = xp.Sum([strict[(e,dd)] for dd in range(d,d+D_S)]) >= Nmin_S ) # Workload workload = dict.fromkeys((e,d) for e in Employees for d in NormPeriodStartDays) for e in Employees: for d in NormPeriodStartDays: workload[(e,d)] = xp.constraint( name = 'workload({},{})'.format(e,d), constraint = xp.Sum([T[s] * x[(e,dd,s)] for dd in range(d, d + N_N) for s in ShiftTypesWorking if s in EmployeeShiftTypes[e]]) - u_plus[(e,d)] + u_minus[(e,d)] == W_N*H_W[e] ) # Patterns # Illegal patterns illegalPatterns = dict.fromkeys((e,pat,d) for e in Employees if e in PatternsIllegal.keys() for pat in PatternsIllegal[e] for d in PatternDays[(e,pat)]) for e in Employees: if e in PatternsIllegal.keys(): for pat in PatternsIllegal[e]: for d in PatternDays[(e,pat)]: illegalPatterns[(e,pat,d)] = xp.constraint( name = 'illegalPatterns({},{},{})'.format(e,pat,d), constraint = xp.Sum([M[(e,pat,dd,g)] * x[(e,d+dd-1,s)] for dd in range(1,PatternDuration[(e,pat)]+1) for g in ShiftGroups for s in ShiftTypesGroup[g] if s in EmployeeShiftTypes[e]]) <= PatternDuration[(e,pat)] - 1 ) # Penalized patterns penalizedPatterns = dict.fromkeys((e,pat,d) for e in Employees if e in PatternsPenalized.keys() for pat in PatternsPenalized[e] for d in PatternDays[(e,pat)]) for e in Employees: if e in PatternsPenalized.keys(): for pat in PatternsPenalized[e]: for d in PatternDays[(e,pat)]: penalizedPatterns[(e,pat,d)] = xp.constraint( name = 'penalizedPatterns({},{},{})'.format(e,pat,d), constraint = xp.Sum([M[(e,pat,dd,g)] * x[(e,d+dd-1,s)] for dd in range(1,PatternDuration[(e,pat)]+1) for g in ShiftGroups for s in ShiftTypesGroup[g] if s in EmployeeShiftTypes[e]]) <= PatternDuration[(e,pat)] + m[(e,pat,d)] - 1 ) # Rewarded patterns rewardedPatterns = dict.fromkeys((e,pat,d,dd) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] for d in PatternDays[(e,pat)] for dd in range(1,PatternDuration[(e,pat)]+1)) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: for d in PatternDays[(e,pat)]: for dd in range(1,PatternDuration[(e,pat)]+1): rewardedPatterns[(e,pat,d,dd)] = xp.constraint( name = 'rewardedPatterns({},{},{},{})'.format(e,pat,d,dd), constraint = xp.Sum([M[(e,pat,dd,g)] * x[(e,d+dd-1,s)] for g in ShiftGroups for s in ShiftTypesGroup[g] if s in EmployeeShiftTypes[e]]) >= m[(e,pat,d)] ) # Overlapping patterns overlappingPatterns = dict.fromkeys((e,pat,d) for e in Employees if e in PatternsRewarded.keys() for pat in PatternsRewarded[e] for d in PatternDays[(e,pat)] if d <= nDays - 2*PatternDuration[(e,pat)] + 3) for e in Employees: if e in PatternsRewarded.keys(): for pat in PatternsRewarded[e]: for d in PatternDays[(e,pat)]: if d <= nDays - 2*PatternDuration[(e,pat)] + 3: overlappingPatterns[(e,pat,d)] = xp.constraint( name = 'overlappingPatterns({},{},{})'.format(e,pat,d), constraint = xp.Sum([m[(e,pat,dd)] for dd in range(d,d+PatternDuration[(e,pat)]-1) if dd in PatternDays[(e,pat)]]) <= 1 ) p.addConstraint(demandCoverage, oneShift, maxConsecutiveDays, maxConsecutiveDaysGroup, minConsecutiveDays, minConsecutiveDaysGroup, requiredRest, reducedRest, maxDaysWithRestPenalty, bothOrNoWeekendDays, noLateIntoWeekend, minWeekendsOff, strictDaysOff_dayOff, strictDaysOff_one, strictDaysOff_two, minStrictDaysOff, workload, illegalPatterns, penalizedPatterns, rewardedPatterns, overlappingPatterns ) '''Attempt to solve problem and evaluate feasibility. If feasible, store solutions, and objective, if feasible''' # Attempt to solve p.solve() # Assess problem status mipstatus = p.getAttrib('mipstatus') # Check if problem status indicates infeasibility if mipstatus == 4: feasible = 'True' elif mipstatus == 5: feasible = 'False' elif mipstatus == 6: feasible = 'True' else: feasible = 'Unknown' # Check if problem status indicates MIP was completed if mipstatus in [5, 6] and gapLimit == None: completed = True else: completed = False '''Solutions''' # If feasible, return solution if feasible == 'True': # Get objective value objective = p.getObjVal() # Get shift types solution = {} solution['x'] = dict.fromkeys((e,d,s) for e in Employees for d in Days for s in ShiftTypes) for e in Employees: for d in Days: for s in ShiftTypes: solution['x'][(e,d,s)] = p.getSolution(x[(e,d,s)]) # Set lower bound if completed: lowerBound = objective else: lowerBound = p.getAttrib('bestbound') # If feasibility unknown, return lower bound elif feasible == 'Unknown': objective = None solution = None lowerBound = p.getAttrib('bestbound') # If infeasible, return None else: objective = None solution = None lowerBound = None # Calculate time spent computationTime = time.time() - start # Save solution to pickle if specified if outputPickle != None: with open(outputPickle, 'wb') as f: pickle.dump({'completed': completed, 'feasible': feasible, 'objective': objective, 'lower bound': lowerBound, 'solution': solution, 'computationTime': computationTime, 'input parameters': {'coverConstraint': coverConstraint, 'timeLimit': timeLimit, 'gapLimit': gapLimit}}, f) return completed, feasible, objective, lowerBound, solution, computationTime
def max_flow(): global graph global vertices_no global vertices global weight thres = 0.4 # density of network thresdem = 0.8 # density of demand mesh dem = [] for i in range(vertices_no): for j in range(vertices_no): if i != j and np.random.random() < thresdem: dem.append((vertices[i], vertices[j], math.ceil(200 * np.random.random()))) print("This is a random demand for each node", dem) for i in range(vertices_no): for j in range(vertices_no): if graph[i][j] != 0: pair = vertices[i], vertices[j] print(pair) #*********************************************************************************************************************** #flow variables f = {(i, j, d): xp.var(name='f_{0}_{1}_{2}_{3}'.format(i, j, dem[d][0], dem[d][1])) for (i, j) in pair for d in range(len(dem))} # capacity variables x = {(i, j): xp.var(vartype=xp.integer, name='cap_{0}_{1}'.format(i, j)) for (i, j) in pair} print("this is x", x) p = xp.problem('network flow') p.addVariable(f, x) def demand(i, d): if dem[d][0] == i: # source return 1 elif dem[d][1] == i: # destination return -1 else: return 0 # Flow conservation constraints: total flow balance at node i for each demand d # must be 0 if i is an intermediate node, 1 if i is the source of demand d, and # -1 if i is the destination. flow = {(i, d): xp.constraint( constraint=xp.Sum(f[vertices[i], vertices[j], d] for j in range(vertices_no) if (vertices[i], vertices[j]) in weight) - xp.Sum(f[vertices[j], vertices[i], d] for j in range(vertices_no) if (vertices[j], vertices[i]) in weight) == demand( vertices[i], d), name='cons_{0}_{1}_{2}'.format(i, dem[d][0], dem[d][1])) for d in range(len(dem)) for i in range(vertices_no)} # Capacity constraints: weighted sum of flow variables must be contained in the # total capacity installed on the arc (i, j) capacity = { (i, j): xp.constraint(constraint=xp.Sum( dem[d][2] * f[vertices[i], vertices[j], d] for d in range(len(dem))) <= x[vertices[i], vertices[j]], name='capacity_{0}_{1}'.format(vertices[i], vertices[j])) for (i, j) in weight } p.addConstraint(flow, capacity) p.setObjective(xp.Sum(x[i, j] for (i, j) in weight)) p.solve() p.getSolution()
def solve(self, objective, constraints, cached_data, warm_start, verbose, solver_opts): """Returns the result of the call to the solver. Parameters ---------- objective : LinOp The canonicalized objective. constraints : list The list of canonicalized cosntraints. cached_data : dict A map of solver name to cached problem data. warm_start : bool Should the previous solver result be used to warm_start? verbose : bool Should the solver print output? solver_opts : dict Additional arguments for the solver. Returns ------- tuple (status, optimal value, primal, equality dual, inequality dual) """ import xpress verbose = True # Get problem data data = super(XPRESS, self).get_problem_data(objective, constraints, cached_data) origprob = None if 'original_problem' in solver_opts.keys(): origprob = solver_opts['original_problem'] if 'no_qp_reduction' in solver_opts.keys( ) and solver_opts['no_qp_reduction'] is True: self.translate_back_QP_ = True c = data[s.C] # objective coefficients dims = data[s.DIMS] # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix data[s.BOOL_IDX] = solver_opts[s.BOOL_IDX] data[s.INT_IDX] = solver_opts[s.INT_IDX] n = c.shape[0] # number of variables solver_cache = cached_data[self.name()] ########################################################################################### # Allow warm start if all dimensions match, i.e., if the # modified problem has the same number of rows/column and the # same list of cone sizes. Failing that, we can just take the # standard route and build the problem from scratch. if warm_start and \ solver_cache.prev_result is not None and \ n == len(solver_cache.prev_result['obj']) and \ nrows == len(solver_cache.prev_result['rhs']) and \ data[s.DIMS][s.SOC_DIM] == solver_cache.prev_result['cone_ind']: # We are re-solving a problem that was previously solved # Initialize the problem as the same as the previous solve self.prob_ = solver_cache.prev_result['problem'] c0 = solver_cache.prev_result['obj'] A0 = solver_cache.prev_result['mat'] b0 = solver_cache.prev_result['rhs'] vartype0 = solver_cache.prev_result['vartype'] # If there is a parameter in the objective, it may have changed. if len(linutils.get_expr_params(objective)) > 0: dci = numpy.where(c != c0)[0] self.prob_.chgobj(dci, c[dci]) # Get equality and inequality constraints. sym_data = self.get_sym_data(objective, constraints, cached_data) all_constrs, _, _ = self.split_constr(sym_data.constr_map) # If there is a parameter in the constraints, # A or b may have changed. if any( len(linutils.get_expr_params(con.expr)) > 0 for con in constraints): dAi = (A != A0).tocoo( ) # retrieves row/col nonzeros as a tuple of two arrays dbi = numpy.where(b != b0)[0] if dAi.getnnz() > 0: self.prob_.chgmcoef( dAi.row, dAi.col, [A[i, j] for (i, j) in list(zip(dAi.row, dAi.col))]) if len(dbi) > 0: self.prob_.chgrhs(dbi, b[dbi]) vartype = [] self.prob_.getcoltype(vartype, 0, len(data[s.C]) - 1) vti = (numpy.array(vartype) != numpy.array(vartype0)) if any(vti): self.prob_.chgcoltype(numpy.arange(len(c))[vti], vartype[vti]) ############################################################################################ else: # No warm start, create problem from scratch # Problem self.prob_ = xpress.problem() mstart = makeMstart(A, len(c), 1) varGroups = { } # If origprob is passed, used to tie IIS to original constraints transf2Orig = { } # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # From a summary knowledge of origprob.constraints() and # the constraints list, the following seems to hold: # # 1) origprob.constraints is the list as generated by the # user. origprob.constraints[i].size returns the number # of actual rows in each constraint, while .constr_id # returns its id (not necessarily numbered from 0). # # 2) constraints is also a list whose every element # contains fields size and constr_id. These correspond # to the items in origprob.constraints, though the list # is in not in order of constr_id. Also, given that it # refers to the transformed problem, it contains extra # constraints deriving from the cone transformations, # all with a constr_id and a size. # # Given this information, attempt to set names in varnames # and linRownames so that they can be easily identified # Load linear part of the problem. if origprob is not None: # The original problem was passed, we can take a # better guess at the constraints and variable names. nOrigVar = 0 orig_id = [i.id for i in origprob.constraints] varnames = [] for v in origprob.variables(): nOrigVar += v.size[0] if v.size[0] == 1: varnames.append('{0}'.format(v.var_id)) else: varnames.extend([ '{0}_{1:d}'.format(v.var_id, j) for j in range(v.size[0]) ]) varnames.extend([ 'aux_{0:d}'.format(i) for i in range(len(varnames), len(c)) ]) # Construct constraint name list by checking constr_id for each linRownames = [] for con in constraints: if con.constr_id in orig_id: prefix = '' if type(con.constr_id) == int: prefix = 'row_' if con.size[0] == 1: name = '{0}{1}'.format(prefix, con.constr_id) linRownames.append(name) transf2Orig[name] = con.constr_id else: names = [ '{0}{1}_{2:d}'.format(prefix, con.constr_id, j) for j in range(con.size[0]) ] linRownames.extend(names) for i in names: transf2Orig[i] = con.constr_id # Tie auxiliary variables to constraints. Scan all # auxiliary variables in the objective function and in # the corresponding columns of A.indices iObjQuad = 0 # keeps track of quadratic quantities in the objective for i in range(nOrigVar, len(c)): if c[i] != 0: varGroups[varnames[i]] = 'objF_{0}'.format(iObjQuad) iObjQuad += 1 if len(A.indices[mstart[i]:mstart[i + 1]]) > 0: varGroups[varnames[i]] = linRownames[min( A.indices[mstart[i]:mstart[i + 1]])] else: # fall back to flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'.format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] self.prob_.loadproblem( "CVXproblem", ['E'] * nrowsEQ + ['L'] * nrowsLEQ, # qrtypes b, # rhs None, # range c, # obj coeff mstart, # mstart None, # mnel A.indices, # row indices A.data, # coefficients [-xpress.infinity] * len(c), # lower bound [xpress.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names x = numpy.array( self.prob_.getVariable()) # get whole variable vector # Set variable types for discrete variables self.prob_.chgcoltype( data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow:currow + k].tocsr() b = data[s.B][currow:currow + k] currow += k if self.translate_back_QP_: # Conic problem passed by CVXPY is translated back # into a QP problem. The problem is passed to us # as follows: # # min c'x # s.t. Ax <>= b # y[i] = P[i]' * x + b[i] # ||y[i][1:]||_2 <= y[i][0] # # where P[i] is a matrix, b[i] is a vector. Get # rid of the y variables by explicitly rewriting # the conic constraint as quadratic: # # y[i][1:]' * y[i][1:] <= y[i][0]^2 # # and hence # # (P[i][1:]' * x + b[i][1:])^2 <= (P[i][0]' * x + b[i][0])^2 Plhs = A[1:] Prhs = A[0] indRowL, indColL = Plhs.nonzero() indRowR, indColR = Prhs.nonzero() coeL = Plhs.data coeR = Prhs.data lhs = list(b[1:]) rhs = b[0] for i in range(len(coeL)): lhs[indRowL[i]] -= coeL[i] * x[indColL[i]] for i in range(len(coeR)): rhs -= coeR[i] * x[indColR[i]] self.prob_.addConstraint( xpress.Sum([lhs[i]**2 for i in range(len(lhs))]) <= rhs**2) else: # Create new (cone) variables and add them to the problem conevar = numpy.array([ xpress.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xpress.infinity if i > 0 else 0) for i in range(k) ]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = [ 'linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k) ] # Linear transformation for cone variables <--> original variables self.prob_.addrows( ['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices, # ind A.data, # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xpress.constraint(constraint=xpress.Sum( conevar[i]**2 for i in range(1, k)) <= conevar[0]**2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # Objective. Minimize is by default both here and in CVXOPT self.prob_.setObjective( xpress.Sum(c[i] * x[i] for i in range(len(c)))) # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 if 'solver_opts' in solver_opts.keys(): self.prob_.setControl(solver_opts['solver_opts']) self.prob_.setControl({ i: solver_opts[i] for i in solver_opts.keys() if i in xpress.controls.__dict__.keys() }) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = self.get_status_maps() if self.is_mip(data): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[ s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not self.is_mip(data): results_dict['y'] = self.prob_.getDual() elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig.keys(): name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{ 'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols }] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append( (row, col, rtype, btype, duals, rdcs, isrows, icols)) return self.format_results(results_dict, data, cached_data)
def solve(self): ### Params ### self.n_plyrs_per_game = [ self.game_list[i][1] for i in range(len(self.game_list)) ] req_plyrs_per_round = sum([ct * 2 for ct in self.n_plyrs_per_game]) if self.trim_gms & (req_plyrs_per_round > self.n_plyrs): if self.debug: st.text("Removing games to fit the number of passed players") while req_plyrs_per_round > self.n_plyrs: self.game_list = self.game_list[:-1] self.n_plyrs_per_game = [ self.game_list[i][1] for i in range(len(self.game_list)) ] req_plyrs_per_round = sum( [ct * 2 for ct in self.n_plyrs_per_game]) print(req_plyrs_per_round, self.n_plyrs) # max number of times a player can play a given game self.max_gm_plays = max( 2, math.ceil(req_plyrs_per_round * self.n_rounds / self.n_plyrs)) st.text(f'Final game list: {self.game_list}') if not self.max_gm_plays_soft: st.text( f'Max times a player is allowed to play same game: {self.max_gm_plays}' ) self.n_games = len(self.game_list) ## Instantiate problem ## self.model = xp.problem() ################# ### Variables ### ################# # Player-game-round-team plyrs = {} for p in range(self.n_plyrs): plyrs[p] = {} for g in range(self.n_games): plyrs[p][g] = {} for r in range(self.n_rounds): plyr_var_list = [ xp.var(name=f"p{p}_g{g}_r{r}_t{t}", vartype=xp.binary) for t in range(self.n_tms) ] plyrs[p][g][r] = plyr_var_list self.model.addVariable(plyr_var_list) # Team assignments tms = {} for t in range(self.n_tms): tm_var_list = [ xp.var(name=f"p{p}_tm{t}", vartype=xp.binary) for p in range(self.n_plyrs) ] tms[t] = tm_var_list self.model.addVariable(tm_var_list) # Variable set greater_than/equal_to game_played count for all players for all games - this quantity is minimized max_gm_plays_global = xp.var(name='max_gm_plays_global', lb=0) self.model.addVariable(max_gm_plays_global) ################### ### Constraints ### ################### # Correct number of plyrs per game per team ## Why need both less/greater than and not equal to? for g in range(self.n_games): for r in range(self.n_rounds): for t in range(self.n_tms): suff_plyrs_tm_1 = xp.Sum(plyrs[p][g][r][t] for p in range( self.n_plyrs)) >= self.n_plyrs_per_game[g] suff_plyrs_tm_2 = xp.Sum(plyrs[p][g][r][t] for p in range( self.n_plyrs)) <= self.n_plyrs_per_game[g] self.model.addConstraint( xp.constraint(suff_plyrs_tm_1, name=f'gteq_plyrs_tm{t}_rnd{r}_gm{g}')) self.model.addConstraint( xp.constraint(suff_plyrs_tm_2, name=f'lteq_plyrs_tm{t}_rnd{r}_gm{g}')) # One game per time per player for p in range(self.n_plyrs): for r in range(self.n_rounds): for t in range(self.n_tms): one_game_per_round_per_plyr = xp.Sum( plyrs[p][g][r][t] for g in range(self.n_games)) <= 1 self.model.addConstraint( xp.constraint( one_game_per_round_per_plyr, name=f"one_gm_in_rnd{r}_for_plyr{p}_tm{t}")) # Team assignment constraints for p in range(self.n_plyrs): # One team per player tm_lb = xp.Sum(tms[t][p] for t in range(self.n_tms)) <= 1 tm_ub = xp.Sum(tms[t][p] for t in range(self.n_tms)) >= 1 self.model.addConstraint( xp.constraint(tm_lb, name=f'plyr{p}_lteq_1_tm')) self.model.addConstraint( xp.constraint(tm_ub, name=f'plyr{p}_gteq_1_tm')) for t in range(self.n_tms): # Forcing tm variables to be flipped when player selected tm_enforce = xp.Sum( plyrs[p][g][r][t] for g in range(self.n_games) for r in range(self.n_rounds)) <= tms[t][p] * self.n_rounds self.model.addConstraint( xp.constraint(tm_enforce, name=f'plyr{p}_tm{t}_enforce')) # Teams evenly split tms_even_1 = xp.Sum(tms[0][p] for p in range(self.n_plyrs)) - xp.Sum( tms[1][p] for p in range(self.n_plyrs)) <= 1 tms_even_2 = xp.Sum(tms[1][p] for p in range(self.n_plyrs)) - xp.Sum( tms[0][p] for p in range(self.n_plyrs)) <= 1 self.model.addConstraint(tms_even_1) self.model.addConstraint(tms_even_2) # Each player plays each game at most 'self.max_gm_plays' for p in range(self.n_plyrs): for g in range(self.n_games): for t in range(self.n_tms): max_rds_per_game_per_plyr = xp.Sum( plyrs[p][g][r][t] for r in range(self.n_rounds)) if not self.max_gm_plays_soft: self.model.addConstraint( xp.constraint( max_rds_per_game_per_plyr <= self.max_gm_plays, name= f"plyr{p}_plays_gm{g}_max_{self.max_gm_plays}_times_tm{t}" )) self.model.addConstraint( xp.constraint( max_gm_plays_global >= max_rds_per_game_per_plyr, name=f'max_gm_plays_global_gteq_p{p}_g{g}_t{t}')) # Each player plays at most once more than every other player for p1 in range(self.n_plyrs): n_plays = xp.Sum(plyrs[p1][g][r][t] for g in range(self.n_games) for r in range(self.n_rounds) for t in range(self.n_tms)) for p2 in range(p1 + 1, self.n_plyrs): n_plays_ = xp.Sum(plyrs[p2][g][r][t] for g in range(self.n_games) for r in range(self.n_rounds) for t in range(self.n_tms)) self.model.addConstraint( (n_plays - n_plays_) <= self.max_gm_gap) self.model.addConstraint( (n_plays_ - n_plays) <= self.max_gm_gap) # Objective self.model.setObjective(max_gm_plays_global, sense=xp.minimize) self.model.solve() self.check_model_feasibility() return plyrs, tms
def solve(self, objective, constraints, cached_data, warm_start, verbose, solver_opts): """Returns the result of the call to the solver. Parameters ---------- objective : LinOp The canonicalized objective. constraints : list The list of canonicalized cosntraints. cached_data : dict A map of solver name to cached problem data. warm_start : bool Should the previous solver result be used to warm_start? verbose : bool Should the solver print output? solver_opts : dict Additional arguments for the solver. Returns ------- tuple (status, optimal value, primal, equality dual, inequality dual) """ import xpress verbose = True # Get problem data data = super(XPRESS, self).get_problem_data(objective, constraints, cached_data) origprob = None if 'original_problem' in solver_opts.keys(): origprob = solver_opts['original_problem'] if 'no_qp_reduction' in solver_opts.keys() and solver_opts['no_qp_reduction'] is True: self.translate_back_QP_ = True c = data[s.C] # objective coefficients dims = data[s.DIMS] # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix n = c.shape[0] # number of variables solver_cache = cached_data[self.name()] ########################################################################################### # Allow warm start if all dimensions match, i.e., if the # modified problem has the same number of rows/column and the # same list of cone sizes. Failing that, we can just take the # standard route and build the problem from scratch. if warm_start and \ solver_cache.prev_result is not None and \ n == len(solver_cache.prev_result['obj']) and \ nrows == len(solver_cache.prev_result['rhs']) and \ data[s.DIMS][s.SOC_DIM] == solver_cache.prev_result['cone_ind']: # We are re-solving a problem that was previously solved # Initialize the problem as the same as the previous solve self.prob_ = solver_cache.prev_result['problem'] c0 = solver_cache.prev_result['obj'] A0 = solver_cache.prev_result['mat'] b0 = solver_cache.prev_result['rhs'] vartype0 = solver_cache.prev_result['vartype'] # If there is a parameter in the objective, it may have changed. if len(linutils.get_expr_params(objective)) > 0: dci = numpy.where(c != c0)[0] self.prob_.chgobj(dci, c[dci]) # Get equality and inequality constraints. sym_data = self.get_sym_data(objective, constraints, cached_data) all_constrs, _, _ = self.split_constr(sym_data.constr_map) # If there is a parameter in the constraints, # A or b may have changed. if any(len(linutils.get_expr_params(con.expr)) > 0 for con in constraints): dAi = (A != A0).tocoo() # retrieves row/col nonzeros as a tuple of two arrays dbi = numpy.where(b != b0)[0] if dAi.getnnz() > 0: self.prob_.chgmcoef(dAi.row, dAi.col, [A[i, j] for (i, j) in list(zip(dAi.row, dAi.col))]) if len(dbi) > 0: self.prob_.chgrhs(dbi, b[dbi]) vartype = [] self.prob_.getcoltype(vartype, 0, len(data[s.C]) - 1) vti = (numpy.array(vartype) != numpy.array(vartype0)) if any(vti): self.prob_.chgcoltype(numpy.arange(len(c))[vti], vartype[vti]) ############################################################################################ else: # No warm start, create problem from scratch # Problem self.prob_ = xpress.problem() mstart = makeMstart(A, len(c), 1) varGroups = {} # If origprob is passed, used to tie IIS to original constraints transf2Orig = {} # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # From a summary knowledge of origprob.constraints() and # the constraints list, the following seems to hold: # # 1) origprob.constraints is the list as generated by the # user. origprob.constraints[i].size returns the number # of actual rows in each constraint, while .constr_id # returns its id (not necessarily numbered from 0). # # 2) constraints is also a list whose every element # contains fields size and constr_id. These correspond # to the items in origprob.constraints, though the list # is in not in order of constr_id. Also, given that it # refers to the transformed problem, it contains extra # constraints deriving from the cone transformations, # all with a constr_id and a size. # # Given this information, attempt to set names in varnames # and linRownames so that they can be easily identified # Load linear part of the problem. if origprob is not None: # The original problem was passed, we can take a # better guess at the constraints and variable names. nOrigVar = 0 orig_id = [i.id for i in origprob.constraints] varnames = [] for v in origprob.variables(): nOrigVar += v.size[0] if v.size[0] == 1: varnames.append('{0}'. format(v.var_id)) else: varnames.extend(['{0}_{1:d}'. format(v.var_id, j) for j in range(v.size[0])]) varnames.extend(['aux_{0:d}'.format(i) for i in range(len(varnames), len(c))]) # Construct constraint name list by checking constr_id for each linRownames = [] for con in constraints: if con.constr_id in orig_id: prefix = '' if type(con.constr_id) == int: prefix = 'row_' if con.size[0] == 1: name = '{0}{1}'.format(prefix, con.constr_id) linRownames.append(name) transf2Orig[name] = con.constr_id else: names = ['{0}{1}_{2:d}'.format(prefix, con.constr_id, j) for j in range(con.size[0])] linRownames.extend(names) for i in names: transf2Orig[i] = con.constr_id # Tie auxiliary variables to constraints. Scan all # auxiliary variables in the objective function and in # the corresponding columns of A.indices iObjQuad = 0 # keeps track of quadratic quantities in the objective for i in range(nOrigVar, len(c)): if c[i] != 0: varGroups[varnames[i]] = 'objF_{0}'.format(iObjQuad) iObjQuad += 1 if len(A.indices[mstart[i]:mstart[i+1]]) > 0: varGroups[varnames[i]] = linRownames[min(A.indices[mstart[i]:mstart[i+1]])] else: # fall back to flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'. format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] self.prob_.loadproblem("CVXproblem", ['E'] * nrowsEQ + ['L'] * nrowsLEQ, # qrtypes b, # rhs None, # range c, # obj coeff mstart, # mstart None, # mnel A.indices, # row indices A.data, # coefficients [-xpress.infinity] * len(c), # lower bound [xpress.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names x = numpy.array(self.prob_.getVariable()) # get whole variable vector # Set variable types for discrete variables self.prob_.chgcoltype(data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow: currow + k].tocsr() b = data[s.B][currow: currow + k] currow += k if self.translate_back_QP_: # Conic problem passed by CVXPY is translated back # into a QP problem. The problem is passed to us # as follows: # # min c'x # s.t. Ax <>= b # y[i] = P[i]' * x + b[i] # ||y[i][1:]||_2 <= y[i][0] # # where P[i] is a matrix, b[i] is a vector. Get # rid of the y variables by explicitly rewriting # the conic constraint as quadratic: # # y[i][1:]' * y[i][1:] <= y[i][0]^2 # # and hence # # (P[i][1:]' * x + b[i][1:])^2 <= (P[i][0]' * x + b[i][0])^2 Plhs = A[1:] Prhs = A[0] indRowL, indColL = Plhs.nonzero() indRowR, indColR = Prhs.nonzero() coeL = Plhs.data coeR = Prhs.data lhs = list(b[1:]) rhs = b[0] for i in range(len(coeL)): lhs[indRowL[i]] -= coeL[i] * x[indColL[i]] for i in range(len(coeR)): rhs -= coeR[i] * x[indColR[i]] self.prob_.addConstraint(xpress.Sum([lhs[i]**2 for i in range(len(lhs))]) <= rhs**2) else: # Create new (cone) variables and add them to the problem conevar = numpy.array([xpress.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xpress.infinity if i > 0 else 0) for i in range(k)]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = ['linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k)] # Linear transformation for cone variables <--> original variables self.prob_.addrows(['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices, # ind A.data, # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xpress.constraint(constraint=xpress.Sum (conevar[i]**2 for i in range(1, k)) <= conevar[0] ** 2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # Objective. Minimize is by default both here and in CVXOPT self.prob_.setObjective(xpress.Sum(c[i] * x[i] for i in range(len(c)))) # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 if 'solver_opts' in solver_opts.keys(): self.prob_.setControl(solver_opts['solver_opts']) self.prob_.setControl({i: solver_opts[i] for i in solver_opts.keys() if i in xpress.controls.__dict__.keys()}) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = self.get_status_maps() if self.is_mip(data): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not self.is_mip(data): results_dict['y'] = self.prob_.getDual() elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig.keys(): name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols}] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append(( row, col, rtype, btype, duals, rdcs, isrows, icols)) return self.format_results(results_dict, data, cached_data)