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())
Example #2
0
    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
Example #3
0
 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)
Example #4
0
 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
Example #5
0
 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)
Example #6
0
 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)
Example #7
0
 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)
Example #8
0
 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]
Example #10
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
Example #12
0
 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)
Example #13
0
                              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(
Example #14
0
 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))
Example #16
0
    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
Example #17
0
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)
Example #18
0
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
Example #19
0
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()
Example #20
0
    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)
Example #21
0
    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
Example #22
0
    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)