예제 #1
0
def test_knapsack(solver: str):
    p = [10, 13, 18, 31, 7, 15]
    w = [11, 15, 20, 35, 10, 33]
    c, I = 47, range(len(w))

    m = Model("knapsack", solver_name=solver)

    x = [m.add_var(var_type=BINARY) for i in I]

    m.objective = maximize(xsum(p[i] * x[i] for i in I))

    m += xsum(w[i] * x[i] for i in I) <= c, "cap"

    m.optimize()

    assert m.status == OptimizationStatus.OPTIMAL
    assert round(m.objective_value) == 41

    m.constr_by_name("cap").rhs = 60
    m.optimize()

    assert m.status == OptimizationStatus.OPTIMAL
    assert round(m.objective_value) == 51

    # modifying objective function
    m.objective = m.objective + 10 * x[0] + 15 * x[1]
    assert abs(m.objective.expr[x[0]] - 20) <= 1e-10
    assert abs(m.objective.expr[x[1]] - 28) <= 1e-10
예제 #2
0
    def relax_constraints(cls, relaxed_model: mip.Model, slack_dict: dict) -> mip.Model:
        """this method creates a modification of the model `relaxed_model` where all the constraints in the slack_dict are
        modified in order to add the slack values to make the IIS disappear
        Args:
            relaxed_model (mip.Model): model to relax
            slack_dict (dict): pairs {constraint_name: slack_var.value}
        Returns:
            mip.Model: a modification of the original model where all the constraints are modified with the slack values
        """
        for crt_name in slack_dict.keys():
            crt_original = relaxed_model.constr_by_name(crt_name)

            relax_expr = crt_original.expr + slack_dict[crt_name]
            relaxed_model.add_constr(
                relax_expr, name=crt_original.name, priority=crt_original.priority
            )
            relaxed_model.remove(crt_original)  # remove constraint

        return relaxed_model
예제 #3
0
def test_knapsack(solver: str):
    p = [10, 13, 18, 31, 7, 15]
    w = [11, 15, 20, 35, 10, 33]
    c, I = 47, range(len(w))

    m = Model('knapsack', solver_name=solver)

    x = [m.add_var(var_type=BINARY) for i in I]

    m.objective = maximize(xsum(p[i] * x[i] for i in I))

    m += xsum(w[i] * x[i] for i in I) <= c, 'cap'

    m.optimize()

    assert m.status == OptimizationStatus.OPTIMAL
    assert round(m.objective_value) == 41

    m.constr_by_name('cap').rhs = 60
    m.optimize()

    assert m.status == OptimizationStatus.OPTIMAL
    assert round(m.objective_value) == 51
예제 #4
0
class InterfaceToSolver:
    """A wrapper for the mip model class, allows interaction with mip using pd.DataFrames."""
    def __init__(self, solver_name='CBC'):
        self.variables = {}
        self.linear_mip_variables = {}

        self.solver_name = solver_name
        if solver_name == 'CBC':
            self.mip_model = Model("market", solver_name=CBC)
            self.linear_mip_model = Model("market", solver_name=CBC)
        elif solver_name == 'GUROBI':
            self.mip_model = Model("market", solver_name=GUROBI)
            self.linear_mip_model = Model("market", solver_name=GUROBI)
        else:
            raise ValueError("Solver '{}' not recognised.")

        self.mip_model.verbose = 0
        self.mip_model.solver.set_mip_gap_abs(1e-10)
        self.mip_model.solver.set_mip_gap(1e-20)
        self.mip_model.lp_method = LP_Method.DUAL

        self.linear_mip_model.verbose = 0
        self.linear_mip_model.solver.set_mip_gap_abs(1e-10)
        self.linear_mip_model.solver.set_mip_gap(1e-20)
        self.linear_mip_model.lp_method = LP_Method.DUAL

    def add_variables(self, decision_variables):
        """Add decision variables to the model.

        Examples
        --------
        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1],
        ...   'lower_bound': [0.0, 0.0],
        ...   'upper_bound': [6.0, 1.0],
        ...   'type': ['continuous', 'binary']})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        The underlying mip_model should now have 2 variables.

        >>> print(si.mip_model.num_cols)
        2

        The first one should have the following properties.

        >>> print(si.mip_model.var_by_name('0').var_type)
        C

        >>> print(si.mip_model.var_by_name('0').lb)
        0.0

        >>> print(si.mip_model.var_by_name('0').ub)
        6.0

        The second one should have the following properties.

        >>> print(si.mip_model.var_by_name('1').var_type)
        B

        >>> print(si.mip_model.var_by_name('1').lb)
        0.0

        >>> print(si.mip_model.var_by_name('1').ub)
        1.0

        """
        # Create a mapping between the nempy level names for variable types and the mip representation.
        variable_types = {'continuous': CONTINUOUS, 'binary': BINARY}
        # Add each variable to the mip model.
        for variable_id, lower_bound, upper_bound, variable_type in zip(
                list(decision_variables['variable_id']),
                list(decision_variables['lower_bound']),
                list(decision_variables['upper_bound']),
                list(decision_variables['type'])):
            self.variables[variable_id] = self.mip_model.add_var(
                lb=lower_bound,
                ub=upper_bound,
                var_type=variable_types[variable_type],
                name=str(variable_id))

            self.linear_mip_variables[
                variable_id] = self.linear_mip_model.add_var(
                    lb=lower_bound,
                    ub=upper_bound,
                    var_type=variable_types[variable_type],
                    name=str(variable_id))

    def add_sos_type_2(self, sos_variables, sos_id_columns, position_column):
        """Add groups of special ordered sets of type 2 two the mip model.

        Examples
        --------

        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> sos_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'sos_id': ['A', 'A', 'A', 'B', 'B', 'B'],
        ...   'position': [0, 1, 2, 0, 1, 2]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_sos_type_2(sos_variables, 'sos_id', 'position')

        """

        # Function that adds sets to mip model.
        def add_sos_vars(sos_group):
            self.mip_model.add_sos(
                list(zip(sos_group['vars'], sos_group[position_column])), 2)

        # For each variable_id get the variable object from the mip model
        sos_variables['vars'] = sos_variables['variable_id'].apply(
            lambda x: self.variables[x])
        # Break up the sets based on their id and add them to the model separately.
        sos_variables.groupby(sos_id_columns).apply(add_sos_vars)
        # This is a hack to make sure mip knows there are binary constraints.
        self.mip_model.add_var(var_type=BINARY, obj=0.0)

    def add_sos_type_1(self, sos_variables):
        # Function that adds sets to mip model.
        def add_sos_vars(sos_group):
            self.mip_model.add_sos(
                list(
                    zip(sos_group['vars'],
                        [1.0 for i in range(len(sos_variables['vars']))])), 1)

        # For each variable_id get the variable object from the mip model
        sos_variables['vars'] = sos_variables['variable_id'].apply(
            lambda x: self.variables[x])
        # Break up the sets based on their id and add them to the model separately.
        sos_variables.groupby('sos_id').apply(add_sos_vars)
        # This is a hack to make mip knows there are binary constraints.
        self.mip_model.add_var(var_type=BINARY, obj=0.0)

    def add_objective_function(self, objective_function):
        """Add the objective function to the mip model.

        Examples
        --------

        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> objective_function = pd.DataFrame({
        ...   'variable_id': [0, 1, 3, 4, 5],
        ...   'cost': [1.0, 2.0, -1.0, 5.0, 0.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_objective_function(objective_function)

        >>> print(si.mip_model.var_by_name('0').obj)
        1.0

        >>> print(si.mip_model.var_by_name('5').obj)
        0.0

        """
        objective_function = objective_function.sort_values('variable_id')
        objective_function = objective_function.set_index('variable_id')
        obj = minimize(
            xsum(objective_function['cost'][i] * self.variables[i]
                 for i in list(objective_function.index)))
        self.mip_model.objective = obj
        self.linear_mip_model.objective = obj

    def add_constraints(self, constraints_lhs, constraints_type_and_rhs):
        """Add constraints to the mip model.

        Examples
        --------
        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> constraints_lhs = pd.DataFrame({
        ...   'constraint_id': [1, 1, 2, 2],
        ...   'variable_id': [0, 1, 3, 4],
        ...   'coefficient': [1.0, 0.5, 1.0, 2.0]})

        >>> constraints_type_and_rhs = pd.DataFrame({
        ...   'constraint_id': [1, 2],
        ...   'type': ['<=', '='],
        ...   'rhs': [10.0, 20.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs)

        >>> print(si.mip_model.constr_by_name('1'))
        1: +1.0 0 +0.5 1 <= 10.0

        >>> print(si.mip_model.constr_by_name('2'))
        2: +1.0 3 +2.0 4 = 20.0

        """

        constraints_lhs = constraints_lhs.groupby(
            ['constraint_id', 'variable_id'],
            as_index=False).agg({'coefficient': 'sum'})
        rows = constraints_lhs.groupby(['constraint_id'], as_index=False)

        # Make a dictionary so constraint rhs values can be accessed using the constraint id.
        rhs = dict(
            zip(constraints_type_and_rhs['constraint_id'],
                constraints_type_and_rhs['rhs']))
        # Make a dictionary so constraint type can be accessed using the constraint id.
        enq_type = dict(
            zip(constraints_type_and_rhs['constraint_id'],
                constraints_type_and_rhs['type']))
        var_ids = constraints_lhs['variable_id'].to_numpy()
        vars = np.asarray([
            self.variables[k] if k in self.variables.keys() else None
            for k in range(0,
                           max(var_ids) + 1)
        ])
        coefficients = constraints_lhs['coefficient'].to_numpy()
        for row_id, row in rows.indices.items():
            # Use the variable_ids to get mip variable objects present in the constraints
            lhs_variables = vars[var_ids[row]]
            # Use the positions of the non nan values to the lhs coefficients.
            lhs = coefficients[row]
            # Multiply and the variables by their coefficients and sum to create the lhs of the constraint.
            exp = lhs_variables * lhs
            exp = exp.tolist()
            exp = xsum(exp)
            # Add based on inequality type.
            if enq_type[row_id] == '<=':
                new_constraint = exp <= rhs[row_id]
            elif enq_type[row_id] == '>=':
                new_constraint = exp >= rhs[row_id]
            elif enq_type[row_id] == '=':
                new_constraint = exp == rhs[row_id]
            else:
                raise ValueError(
                    "Constraint type not recognised should be one of '<=', '>=' or '='."
                )
            self.mip_model.add_constr(new_constraint, name=str(row_id))
            self.linear_mip_model.add_constr(new_constraint, name=str(row_id))

    def optimize(self):
        """Optimize the mip model.

        If an optimal solution cannot be found and the investigate_infeasibility flag is set to True then remove
        constraints until a feasible solution is found.

        Examples
        --------
        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> constraints_lhs = pd.DataFrame({
        ...   'constraint_id': [1, 1, 2, 2],
        ...   'variable_id': [0, 1, 3, 4],
        ...   'coefficient': [1.0, 0.5, 1.0, 2.0]})

        >>> constraints_type_and_rhs = pd.DataFrame({
        ...   'constraint_id': [1, 2],
        ...   'type': ['<=', '='],
        ...   'rhs': [10.0, 20.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs)

        >>> si.optimize()

        >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables)

        >>> print(decision_variables)
           variable_id  lower_bound  upper_bound        type  value
        0            0          0.0          5.0  continuous    0.0
        1            1          0.0          5.0  continuous    0.0
        2            2          0.0         10.0  continuous    0.0
        3            3          0.0         10.0  continuous   10.0
        4            4          0.0          5.0  continuous    5.0
        5            5          0.0          5.0  continuous    0.0
        """
        status = self.mip_model.optimize()
        if status != OptimizationStatus.OPTIMAL:
            # Attempt find constraint causing infeasibility.
            print('Model infeasible attempting to find problem constraint.')
            con_index = find_problem_constraint(self.mip_model)
            print(
                'Couldn\'t find an optimal solution, but removing con {} fixed INFEASIBLITY'
                .format(con_index))
            raise ValueError('Linear program infeasible')

    def get_optimal_values_of_decision_variables(self, variable_definitions):
        """Get the optimal values for each decision variable.

        Examples
        --------

        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> constraints_lhs = pd.DataFrame({
        ...   'constraint_id': [1, 1, 2, 2],
        ...   'variable_id': [0, 1, 3, 4],
        ...   'coefficient': [1.0, 0.5, 1.0, 2.0]})

        >>> constraints_type_and_rhs = pd.DataFrame({
        ...   'constraint_id': [1, 2],
        ...   'type': ['<=', '='],
        ...   'rhs': [10.0, 20.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs)

        >>> si.optimize()

        >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables)

        >>> print(decision_variables)
           variable_id  lower_bound  upper_bound        type  value
        0            0          0.0          5.0  continuous    0.0
        1            1          0.0          5.0  continuous    0.0
        2            2          0.0         10.0  continuous    0.0
        3            3          0.0         10.0  continuous   10.0
        4            4          0.0          5.0  continuous    5.0
        5            5          0.0          5.0  continuous    0.0

        """
        values = variable_definitions['variable_id'].apply(
            lambda x: self.mip_model.var_by_name(str(x)).x, self.mip_model)
        return values

    def get_optimal_values_of_decision_variables_lin(self,
                                                     variable_definitions):
        values = variable_definitions['variable_id'].apply(
            lambda x: self.linear_mip_model.var_by_name(str(x)).x,
            self.mip_model)
        return values

    def get_slack_in_constraints(self, constraints_type_and_rhs):
        """Get the slack values in each constraint.

        Examples
        --------

        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> constraints_lhs = pd.DataFrame({
        ...   'constraint_id': [1, 1, 2, 2],
        ...   'variable_id': [0, 1, 3, 4],
        ...   'coefficient': [1.0, 0.5, 1.0, 2.0]})

        >>> constraints_type_and_rhs = pd.DataFrame({
        ...   'constraint_id': [1, 2],
        ...   'type': ['<=', '='],
        ...   'rhs': [10.0, 20.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs)

        >>> si.optimize()

        >>> constraints_type_and_rhs['slack'] = si.get_slack_in_constraints(constraints_type_and_rhs)

        >>> print(constraints_type_and_rhs)
           constraint_id type   rhs  slack
        0              1   <=  10.0   10.0
        1              2    =  20.0    0.0

        """
        slack = constraints_type_and_rhs['constraint_id'].apply(
            lambda x: self.mip_model.constr_by_name(str(x)).slack,
            self.mip_model)
        return slack

    def price_constraints(self, constraint_ids_to_price):
        """For each constraint_id find the marginal value of the constraint.

        This is done by incrementing the constraint by a value of 1.0 and re-optimizing the model, the marginal cost
        of the constraint is increase in the objective function value between model runs.

        Examples
        --------

        >>> decision_variables = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'lower_bound': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ...   'upper_bound': [5.0, 5.0, 10.0, 10.0, 5.0, 5.0],
        ...   'type': ['continuous', 'continuous', 'continuous',
        ...            'continuous', 'continuous', 'continuous']})

        >>> objective_function = pd.DataFrame({
        ...   'variable_id': [0, 1, 2, 3, 4, 5],
        ...   'cost': [1.0, 3.0, 10.0, 8.0, 9.0, 7.0]})

        >>> constraints_lhs = pd.DataFrame({
        ...   'constraint_id': [1, 1, 1, 1],
        ...   'variable_id': [0, 1, 3, 4],
        ...   'coefficient': [1.0, 1.0, 1.0, 1.0]})

        >>> constraints_type_and_rhs = pd.DataFrame({
        ...   'constraint_id': [1],
        ...   'type': ['='],
        ...   'rhs': [20.0]})

        >>> si = InterfaceToSolver()

        >>> si.add_variables(decision_variables)

        >>> si.add_constraints(constraints_lhs, constraints_type_and_rhs)

        >>> si.add_objective_function(objective_function)

        >>> si.optimize()

        >>> si.linear_mip_model.optimize()
        <OptimizationStatus.OPTIMAL: 0>

        >>> prices = si.price_constraints([1])

        >>> print(prices)
        {1: 8.0}

        >>> decision_variables['value'] = si.get_optimal_values_of_decision_variables(decision_variables)

        >>> print(decision_variables)
           variable_id  lower_bound  upper_bound        type  value
        0            0          0.0          5.0  continuous    5.0
        1            1          0.0          5.0  continuous    5.0
        2            2          0.0         10.0  continuous    0.0
        3            3          0.0         10.0  continuous   10.0
        4            4          0.0          5.0  continuous    0.0
        5            5          0.0          5.0  continuous    0.0

        """
        costs = {}
        for id in constraint_ids_to_price:
            costs[id] = self.linear_mip_model.constr_by_name(str(id)).pi
        return costs

    def update_rhs(self, constraint_id, violation_degree):
        constraint = self.linear_mip_model.constr_by_name(str(constraint_id))
        constraint.rhs += violation_degree

    def update_variable_bounds(self, new_bounds):
        for variable_id, lb, ub in zip(new_bounds['variable_id'],
                                       new_bounds['lower_bound'],
                                       new_bounds['upper_bound']):
            self.mip_model.var_by_name(str(variable_id)).lb = lb
            self.mip_model.var_by_name(str(variable_id)).ub = ub

    def disable_variables(self, variables):
        for var_id in variables['variable_id']:
            var = self.linear_mip_model.var_by_name(str(var_id))
            var.lb = 0.0
            var.ub = 0.0
예제 #5
0
def dispatch(decision_variables, constraints_lhs, constraints_rhs_and_type,
             market_rhs_and_type, constraints_dynamic_rhs_and_type,
             objective_function):
    """Create and solve a linear program, returning prices of the market constraints and decision variables values.

    0. Create the problem instance as a mip-python object instance
    1. Create the decision variables
    2. Create the objective function
    3. Create the constraints
    4. Solve the problem
    5. Retrieve optimal values of each variable
    6. Retrieve the shadow costs of market constraints

    :param decision_variables: dict of DataFrames each with the following columns
        variable_id: int
        lower_bound: float
        upper_bound: float
        type: str one of 'continuous', 'integer' or 'binary'
    :param constraints_lhs_coefficient: dict of DataFrames each with the following columns
        variable_id: int
        constraint_id: int
        coefficient: float
    :param constraints_rhs_and_type: dict of DataFrames each with the following columns
        constraint_id: int
        type: str one of '=', '<=', '>='
        rhs: float
    :param market_constraints_lhs_coefficients: dict of DataFrames each with the following columns
        variable_id: int
        constraint_id: int
        coefficient: float
    :param market_rhs_and_type: dict of DataFrames each with the following columns
        constraint_id: int
        type: str one of '=', '<=', '>='
        rhs: float
    :param objective_function: dict of DataFrames each with the following columns
        variable_id: int
        cost: float
    :return:
        decision_variables: dict of DataFrames each with the following columns
            variable_id: int
            lower_bound: float
            upper_bound: float
            type: str one of 'continuous', 'integer' or 'binary'
            value: float
        market_rhs_and_type: dict of DataFrames each with the following columns
            constraint_id: int
            type: str one of '=', '<=', '>='
            rhs: float
            price: float
    """

    # 0. Create the problem instance as a mip-python object instance
    prob = Model("market")
    prob.verbose = 0

    sos_variables = None
    if 'interpolation_weights' in decision_variables.keys():
        sos_variables = decision_variables['interpolation_weights']

    # 1. Create the decision variables
    decision_variables = pd.concat(decision_variables)
    lp_variables = {}
    variable_types = {'continuous': CONTINUOUS, 'binary': BINARY}
    for variable_id, lower_bound, upper_bound, variable_type in zip(
            list(decision_variables['variable_id']),
            list(decision_variables['lower_bound']),
            list(decision_variables['upper_bound']),
            list(decision_variables['type'])):
        lp_variables[variable_id] = prob.add_var(
            lb=lower_bound,
            ub=upper_bound,
            var_type=variable_types[variable_type],
            name=str(variable_id))

    def add_sos_vars(sos_group):
        prob.add_sos(list(zip(sos_group['vars'], sos_group['loss_segment'])),
                     2)

    if sos_variables is not None:
        sos_variables['vars'] = sos_variables['variable_id'].apply(
            lambda x: lp_variables[x])
        sos_variables.groupby('interconnector').apply(add_sos_vars)

    # 2. Create the objective function
    if len(objective_function) > 0:
        objective_function = pd.concat(list(objective_function.values()))
        objective_function = objective_function.sort_values('variable_id')
        objective_function = objective_function.set_index('variable_id')
        prob.objective = minimize(
            xsum(objective_function['cost'][i] * lp_variables[i]
                 for i in list(objective_function.index)))

    # 3. Create the constraints
    sos_constraints = []
    if len(constraints_rhs_and_type) > 0:
        constraints_rhs_and_type = pd.concat(
            list(constraints_rhs_and_type.values()))
    else:
        constraints_rhs_and_type = pd.DataFrame({})

    if len(constraints_dynamic_rhs_and_type) > 0:
        constraints_dynamic_rhs_and_type = pd.concat(
            list(constraints_dynamic_rhs_and_type.values()))
        constraints_dynamic_rhs_and_type['rhs'] = constraints_dynamic_rhs_and_type.\
            apply(lambda x: lp_variables[x['rhs_variable_id']], axis=1)
        rhs_and_type = pd.concat([constraints_rhs_and_type] +
                                 list(market_rhs_and_type.values()) +
                                 [constraints_dynamic_rhs_and_type])
    else:
        rhs_and_type = pd.concat([constraints_rhs_and_type] +
                                 list(market_rhs_and_type.values()))

    constraint_matrix = constraints_lhs.pivot('constraint_id', 'variable_id',
                                              'coefficient')
    constraint_matrix = constraint_matrix.sort_index(axis=1)
    constraint_matrix = constraint_matrix.sort_index()
    column_ids = np.asarray(constraint_matrix.columns)
    row_ids = np.asarray(constraint_matrix.index)
    constraint_matrix_np = np.asarray(constraint_matrix)

    # if len(constraint_matrix.columns) != max(decision_variables['variable_id']) + 1:
    #     raise check.ModelBuildError("Not all variables used in constraint matrix")

    rhs = dict(zip(rhs_and_type['constraint_id'], rhs_and_type['rhs']))
    enq_type = dict(zip(rhs_and_type['constraint_id'], rhs_and_type['type']))
    #var_list = np.asarray([lp_variables[k] for k in sorted(list(lp_variables))])
    #t0 = time()
    for row, id in zip(constraint_matrix_np, row_ids):
        new_constraint = make_constraint(lp_variables, row, rhs[id],
                                         column_ids, enq_type[id])
        prob.add_constr(new_constraint, name=str(id))
    #print(time() - t0)
    # for row_index in sos_constraints:
    #     sos_set = get_sos(var_list, constraint_matrix_np[row_index], column_ids)
    #   prob.add_sos(list(zip(sos_set, [0 for var in sos_set])), 1)

    # 4. Solve the problem
    k = prob.add_var(var_type=BINARY, obj=1.0)
    #tc = 0
    #t0 = time()
    status = prob.optimize()
    #tc += time() - t0
    #print(tc)
    if status != OptimizationStatus.OPTIMAL:
        raise ValueError('Linear program infeasible')

    # 5. Retrieve optimal values of each variable
    #t0 = time()
    decision_variables = decision_variables.droplevel(1)
    decision_variables['lp_variables'] = [
        lp_variables[i] for i in decision_variables['variable_id']
    ]
    decision_variables['value'] = decision_variables['lp_variables'].apply(
        lambda x: x.x)
    decision_variables = decision_variables.drop('lp_variables', axis=1)
    split_decision_variables = {}
    for variable_group in decision_variables.index.unique():
        split_decision_variables[variable_group] = \
            decision_variables[decision_variables.index == variable_group].reset_index(drop=True)
    #print('get values {}'.format(time() - t0))

    # 6. Retrieve the shadow costs of market constraints
    start_obj = prob.objective.x
    #initial_solution = [(v, v.x) for v in list(sos_variables['vars']) if v.x > 0.01]
    #print(initial_solution)
    #prob.start = initial_solution
    #prob.validate_mip_start()
    for constraint_group in market_rhs_and_type.keys():
        cg = constraint_group
        market_rhs_and_type[cg]['price'] = 0.0
        for id in list(market_rhs_and_type[cg]['constraint_id']):
            constraint = prob.constr_by_name(str(id))
            constraint.rhs += 1.0
            #t0 = time()
            prob.optimize()
            #tc += time() - t0
            marginal_cost = prob.objective.x - start_obj
            market_rhs_and_type[cg].loc[
                market_rhs_and_type[cg]['constraint_id'] == id,
                'price'] = marginal_cost
            constraint.rhs -= 1.0
        # market_rhs_and_type[constraint_group]['price'] = \
        #     market_rhs_and_type[constraint_group].apply(lambda x: get_price(x['constraint_id'], prob), axis=1)
    #print(tc)
    return split_decision_variables, market_rhs_and_type