Example #1
0
    def relax(self, mdl, relax_mode=None, **kwargs):
        """ Runs the relaxation loop.

        Args:
            mdl: The model to be relaxed.
            relax_mode: the relaxation mode. Accept either None (in which case the default mode is
                used, or an instance of ``RelaxationMode`` enumerated type, or a string
                that can be translated to a relaxation mode.
            kwargs: Accepts named arguments similar to ``solve``.

        Returns:
            If the relaxation succeeds, the method returns a solution object, an instance of ``SolveSolution``; otherwise returns None.

        See Also:
            :func:`docplex.mp.model.Model.solve`,
            :class:`docplex.mp.solution.SolveSolution`,
            :class:`docplex.mp.constants.RelaxationMode`

        """
        self._reset()

        # 1. build a dir {priority : cts}
        priority_map = defaultdict(list)
        nb_prioritized_cts = 0
        mdl_priorities = set()
        mandatory_justifier = None
        nb_mandatories = 0
        for ct in mdl.iter_constraints():
            prio = self._prioritizer.get_priority_internal(ct)
            if prio.is_mandatory():
                nb_mandatories += 1
                if mandatory_justifier is None:
                    mandatory_justifier = ct
            else:
                priority_map[prio].append(ct)
                nb_prioritized_cts += 1
                mdl_priorities.add(prio)

        sorted_priorities = sorted(list(mdl_priorities), key=lambda p: p.value)

        if 0 == nb_prioritized_cts:
            mdl.error(
                "Relaxation algorithm found no relaxable constraints - exiting"
            )
            return None
        if nb_mandatories:
            assert mandatory_justifier is not None
            mdl.warning('{0} constraint(s) will not be relaxed (e.g.: {1!s})',
                        nb_mandatories, mandatory_justifier)

        temp_relax_verbose = kwargs.pop('verbose', False)
        if temp_relax_verbose != self._verbose:
            # install/deinstall listener for this relaxation only
            self.set_verbose_listener_from_flag(temp_relax_verbose)

        # relaxation loop
        all_groups = []
        all_relaxable_cts = []
        is_cumulative = self._cumulative
        # -- relaxation mode
        if relax_mode is None:
            used_relax_mode = self._default_mode
        else:
            used_relax_mode = RelaxationMode.parse(relax_mode)
        if not mdl.is_optimized():
            used_relax_mode = RelaxationMode.get_no_optimization_mode(
                used_relax_mode)

        # save this for restore later
        saved_context_log_output = mdl.context.solver.log_output
        saved_log_output_stream = mdl.log_output
        saved_context = mdl.context

        # take into account local argument overrides
        relax_context = mdl.prepare_actual_context(**kwargs)

        forced_docloud = context_must_use_docloud(relax_context, **kwargs)
        have_credentials = context_has_docloud_credentials(relax_context)
        transient_engine = False
        relax_engine = mdl.get_engine()

        if forced_docloud:  # pragma: no cover
            if have_credentials:
                # create new docloud engine on the fly
                relax_engine = mdl._new_docloud_engine(relax_context)
                transient_engine = True
            else:
                mdl.fatal(
                    "DOcplexcloud context has no valid credentials: {0!s}",
                    relax_context.solver.docloud)

        if temp_relax_verbose:
            print("-- starting relaxation. mode: {0!s}, precision={1}".format(
                used_relax_mode.name, self._precision))

        try:
            # mdl.context has been saved in saved_context above
            mdl.context = relax_context
            mdl.set_log_output(mdl.context.solver.log_output)

            # engine parameters, if needed to
            parameters = apply_thread_limitations(relax_context,
                                                  relax_context.solver)

            mdl._apply_parameters_to_engine(parameters)

            relaxed_sol = None
            for prio in sorted_priorities:
                if prio in priority_map:
                    cts = priority_map[prio]
                    if not cts:
                        # this should not happen...
                        continue  # pragma: no cover

                    pref = prio.cplex_preference
                    # build a new group
                    relax_group = _TRelaxableGroup(pref, cts)

                    # relaxing new batch of cts:
                    if not is_cumulative:  #  pragma: no cover
                        # if not cumulative reset the groupset
                        all_groups = [relax_group]
                        all_relaxable_cts = cts
                    else:
                        all_groups.append(relax_group)
                        all_relaxable_cts += cts

                    # at this stage we have a sequence of groups
                    # a group is itself a sequence of two components
                    # - a preference factor
                    # - a sequence of constraints
                    for l in self._listeners:
                        l.notify_start_relaxation(prio, all_relaxable_cts)

                    # ----
                    # call the engine.
                    # ---

                    try:
                        relaxed_sol = relax_engine.solve_relaxed(
                            mdl, prio.name, all_groups, used_relax_mode)
                    finally:
                        self._last_relaxation_details = relax_engine.get_solve_details(
                        )
                    # ---

                    if relaxed_sol is not None:
                        relax_obj = relaxed_sol.objective_value
                        self._last_successful_relaxed_priority = prio
                        self._last_relaxation_status = True
                        self._last_relaxation_objective = relaxed_sol.objective_value

                        # filter irrelevant relaxations below some threshold
                        for ct in all_relaxable_cts:
                            raw_infeas = relaxed_sol.get_infeasibility(ct)
                            if self._accept_violation(raw_infeas):
                                self._relaxations[ct] = raw_infeas
                        if not self._relaxations:
                            mdl.warning(
                                "Relaxation of model `{0}` found one relaxed solution, but no relaxed constraints - check"
                                .format(mdl.name))

                        for l in self._listeners:
                            l.notify_successful_relaxation(
                                prio, all_relaxable_cts, relax_obj,
                                self._relaxations)
                        # now get out
                        break
                    else:
                        # TODO: maybe issue a warning that relaxation has failed?
                        # relaxation has failed, notify the listeners
                        for l in self._listeners:
                            l.notify_failed_relaxation(prio, all_relaxable_cts)

            mdl.notify_solve_relaxed(relaxed_sol,
                                     relax_engine.get_solve_details())

            # write relaxation table.write_output_table() handles everything related to
            # whether the table should be published etc...
            if self.is_publishing_output_table(mdl.context):
                output_table = to_output_table(self,
                                               self.output_table_using_df)
                self.write_output_table(output_table, mdl.context)
        finally:
            # --- restore context, log_output if set.
            if saved_log_output_stream != mdl.log_output:
                mdl.set_log_output_as_stream(saved_log_output_stream)
            if saved_context_log_output != mdl.context.solver.log_output:
                mdl.context.solver.log_output = saved_context_log_output
            mdl.context = saved_context
            if transient_engine:  # pragma: no cover
                del relax_engine
            if temp_relax_verbose != self._verbose:
                # realign listener with flag
                self.set_verbose_listener_from_flag(self._verbose)

        return relaxed_sol
Example #2
0
    def refine_conflict(self, mdl, preferences=None, groups=None, display=False, **kwargs):
        """ Starts the conflict refiner on the model.

        Args:
            mdl: The model to be relaxed.
            preferences: A dictionary defining constraint preferences.
            groups: A list of ConstraintsGroups.
            display: a boolean flag (default is True); if True, displays the result at the end.
            kwargs: Accepts named arguments similar to solve.

        Returns:
            An object of type `ConflictRefinerResut` which holds all information about
            the minimal conflict.

        See Also:
            :class:`ConflictRefinerResult`

        """

        if mdl.has_multi_objective():
            mdl.fatal("Conflict refiner is not supported for multi-objective")

        # take into account local argument overrides
        context = mdl.prepare_actual_context(**kwargs)

        # log stuff
        saved_context_log_output = mdl.context.solver.log_output
        saved_log_output_stream = mdl.log_output

        try:
            mdl.set_log_output(context.solver.log_output)

            forced_docloud = context_must_use_docloud(context, **kwargs)

            results = None

            have_credentials = False
            if context.solver.docloud:
                have_credentials, error_message = check_credentials(context.solver.docloud)
                if error_message is not None:
                    warnings.warn(error_message, stacklevel=2)
            if forced_docloud:
                if have_credentials:
                    results = self._refine_conflict_cloud(mdl, context, preferences, groups)
                else:
                    mdl.fatal("DOcplexcloud context has no valid credentials: {0!s}", context.solver.docloud)
            # from now on docloud_context is None
            elif mdl.environment.has_cplex:
                # if CPLEX is installed go for it
                results = self._refine_conflict_local(mdl, context, preferences, groups)
            elif have_credentials:
                # no context passed as argument, no Cplex installed, try model's own context
                results = self._refine_conflict_cloud(mdl, context, preferences, groups)
            else:
                # no way to solve.. really
                return mdl.fatal("CPLEX DLL not found: please provide DOcplexcloud credentials")

            # write conflicts table.write_output_table() handles everything related to
            # whether the table should be published etc...
            if self.is_publishing_output_table(mdl.context):
                self.write_output_table(results.as_output_table(self.output_table_using_df), mdl.context)
            if display:
                results.display_stats()
            return results
        finally:
            if saved_log_output_stream != mdl.log_output:
                mdl.set_log_output_as_stream(saved_log_output_stream)
            if saved_context_log_output != mdl.context.solver.log_output:
                mdl.context.solver.log_output = saved_context_log_output
Example #3
0
    def refine_conflict(self, mdl, preferences=None, groups=None, **kwargs):
        """ Starts the conflict refiner on the model.

        Args:
            mdl: The model to be relaxed.
            preferences: A dictionary defining constraint preferences.
            groups: A list of ConstraintsGroups.
            kwargs: Accepts named arguments similar to solve.

        Returns:
            A list of ``TConflictConstraint`` namedtuples, each tuple corresponding to a constraint that is
            involved in the conflict.
            The fields of the ``TConflictConstraint`` namedtuple are:

                - the name of the constraint or None if the constraint corresponds to a variable lower or upper bound.
                - a reference to the constraint or to a wrapper representing a Var upper or lower bound.
                - a ``docplex.mp.constants.ConflictStatus`` object that indicates the
                  conflict status type (Excluded, Possible_member, Member...).

            This list is empty if no conflict is found by the conflict refiner.
        """

        if mdl.has_multi_objective():
            mdl.fatal("Conflict refiner is not supported for multi-objective")

        # take into account local argument overrides
        context = mdl.prepare_actual_context(**kwargs)

        # log stuff
        saved_context_log_output = mdl.context.solver.log_output
        saved_log_output_stream = mdl.log_output

        try:
            mdl.set_log_output(context.solver.log_output)

            forced_docloud = context_must_use_docloud(context, **kwargs)

            results = None

            have_credentials = False
            if context.solver.docloud:
                have_credentials, error_message = check_credentials(
                    context.solver.docloud)
                if error_message is not None:
                    warnings.warn(error_message, stacklevel=2)
            if forced_docloud:
                if have_credentials:
                    results = self._refine_conflict_cloud(
                        mdl, context, preferences, groups)
                else:
                    mdl.fatal(
                        "DOcplexcloud context has no valid credentials: {0!s}",
                        context.solver.docloud)
            # from now on docloud_context is None
            elif mdl.environment.has_cplex:
                # if CPLEX is installed go for it
                results = self._refine_conflict_local(mdl, context,
                                                      preferences, groups)
            elif have_credentials:
                # no context passed as argument, no Cplex installed, try model's own context
                results = self._refine_conflict_cloud(mdl, context,
                                                      preferences, groups)
            else:
                # no way to solve.. really
                return mdl.fatal(
                    "CPLEX DLL not found: please provide DOcplexcloud credentials"
                )

            # write conflicts table.write_output_table() handles everything related to
            # whether the table should be published etc...
            if self.is_publishing_output_table(mdl.context):
                self.write_output_table(
                    to_output_table(results, self.output_table_using_df),
                    mdl.context)
            return results
        finally:
            if saved_log_output_stream != mdl.log_output:
                mdl.set_log_output_as_stream(saved_log_output_stream)
            if saved_context_log_output != mdl.context.solver.log_output:
                mdl.context.solver.log_output = saved_context_log_output