Beispiel #1
0
def solve_intermediate_objective(
        model: cp_model.CpModel,
        solver: cp_model.CpSolver,
        objective,
        hint=True,
        objective_type='min',
        callback: Optional[cp_model.CpSolverSolutionCallback] = None,
        alias=None,
        max_time=None,
        logger=logging):

    if max_time:
        solver.parameters.max_time_in_seconds = max_time

    if objective_type.lower().startswith('min'):
        model.Minimize(objective)
        objective_type = 'min'
    elif objective_type.lower().startswith('max'):
        model.Maximize(objective)
        objective_type = 'max'
    else:
        raise Exception(f'Can not "{objective_type}" objective')

    t0 = dt.datetime.now()
    if callback:
        status = solver.SolveWithSolutionCallback(model, callback)
    else:
        status = solver.Solve(model)
    duration = (dt.datetime.now() - t0).total_seconds()

    if status == cp_model.INFEASIBLE:
        logger.warning(f'INFEASIBLE solving {alias or objective}')
        return None, status
    elif status == cp_model.UNKNOWN:
        logger.warning(f'Time limit reached {alias or objective}')
        return None, status
    result = round(solver.ObjectiveValue())

    if hint:
        hint_solution(model, solver)
    if not isinstance(objective, int):
        if status == cp_model.OPTIMAL:
            logger.debug(
                f'{alias or objective} == {result}, Seconds: {duration:.2f}')
            model.Add(objective == result)
        elif objective_type == 'min':
            logger.debug(
                f'{alias or objective} <= {result}, Seconds: {duration:.2f}')
            model.Add(objective <= result)
        elif objective_type == 'max':
            logger.debug(
                f'{alias or objective} >= {result}, Seconds: {duration:.2f}')
            model.Add(objective >= result)
    return result, status
Beispiel #2
0
class _Solver:
    """Generic solver class."""

    def __init__(self):
        self.model = CpModel()
        self.solver = CpSolver()
        # Ideal number of workers is 8, see https://or.stackexchange.com/a/4126
        self.solver.parameters.num_search_workers = 8
        self._known_names = set()
        self._vars_bool = {}
        self._vars_int = {}
        self._vars_weighted = []
        self._vars_weighted_cost = []
        self.printer = ObjectiveSolutionPrinter()

    def create_variable_bool(self, name: str = None) -> IntVar:
        """Create a boolean variable.

        :param name: The variable human readable name.
        :return: The new variable
        """
        assert name not in self._vars_bool
        assert name not in self._known_names

        var = self.model.NewBoolVar(name)
        self._vars_bool[name] = var
        return var

    def create_variable_int(
        self, key: str, minimum: int, maximum: int, name: str = None
    ) -> IntVar:
        """Create an integer variable.

        :param key: An hashable value to use as an identifier.
        :param minimum: The variable domain minimum value.
        :param maximum: The variable domain maximum value.
        :param name: The variable human readable name.
        :return: The new variable
        """
        name = name or "_".join(key)

        assert key not in self._vars_int
        assert name not in self._known_names

        var = self.model.NewIntVar(minimum, maximum, name)
        self._vars_int[key] = var
        return var

    def get_variable_bool(self, key: Hashable) -> IntVar:
        """Get an already defined boolean variable.

        :param key: A hashable key
        :return: A variable
        :raises KeyError: If no variable exist for the provided key.
        """
        return self._vars_bool[key]

    def add_hard_constraint(self, expr: BoundedLinearExpression) -> Constraint:
        """Add a "hard" constraint. T
        he solve will fail if even once hard constraint fail to be satisfied.

        :param expr: The constraint expression
        :return: A constraint
        """
        return self.model.Add(expr)

    def set_variable_bool_score(self, variable: Constraint, score: int) -> None:
        """Set the cost for a variable.

        :param variable: A variable
        :param score: The cost if the variant is ON
        """
        self._vars_weighted.append(variable)
        self._vars_weighted_cost.append(score)

    def create_soft_constraint_bool(self, name: str, score: int) -> Constraint:
        """Add a "soft" boolean variable with a score.
        The solver will try to maximize it's score.

        :param str name: The variable name
        :param int score: The variable score
        :return: A constraint
        """
        assert name not in self._known_names
        var = self.model.NewBoolVar(name)
        self.set_variable_bool_score(var, score)
        return var

    def create_soft_constraint_int(
        self, name: str, minimum: int, maximum: int, score: int
    ) -> IntVar:
        """Add a "soft" integer variable with a score.
        The solver will try to maximum it's score.

        :param name: The variable name
        :param minimum: The variable domain minimum value
        :param maximum: The variable domain maximum value
        :param score: The variable score
        :return: A constraint
        """
        assert name not in self._known_names
        var = self.model.NewIntVar(minimum, maximum, name)
        self._vars_weighted.append(var)
        self._vars_weighted_cost.append(score)
        return var

    def iter_variables_and_cost(self) -> Generator[Tuple[IntVar, int], None, None]:
        """Yield all variables and their score multipliers."""
        for variable, score in zip(self._vars_weighted, self._vars_weighted_cost):
            yield variable, score

    def solve(self):
        """Solve using provided constraints."""

        self.model.Maximize(
            sum(
                var * cost
                for var, cost in zip(
                    itertools.chain(
                        self._vars_weighted,
                        self._vars_weighted,
                    ),
                    itertools.chain(
                        self._vars_weighted_cost,
                        self._vars_weighted_cost,
                    ),
                )
            )
        )
        status = self.solver.SolveWithSolutionCallback(self.model, self.printer)

        if status not in (OPTIMAL, FEASIBLE):
            raise RuntimeError("No solution found! Status is %s" % status)
        return status