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 before_solve(self, context):
        mdl = self._model
        auto_publish_details = is_auto_publishing_solve_details(context)

        # step 1 : notify start solve
        mdl.notify_start_solve()

        the_env = get_environment()
        if is_in_docplex_worker() and auto_publish_details:
            # do not use lambda here
            def env_kpi_hookfn(kpd):
                the_env.update_solve_details(kpd)

            mdl.kpi_recorder = _KpiRecorder(
                mdl,
                clock=context.solver.kpi_reporting.filter_level,
                publish_hook=env_kpi_hookfn)
            mdl.add_progress_listener(mdl.kpi_recorder)

        # connect progress listeners (if any) if problem is mip
        mdl._connect_progress_listeners()

        # call notifyStart on progress listeners
        mdl._fire_start_solve_listeners()

        # The following block used to be published only if auto_publish_details.
        # It is now modified so that notify start is always performed,
        # then we update solve details only if they need to be published
        # [[[
        self_stats = mdl.statistics
        kpis = make_new_kpis_dict(
            allkpis=mdl._allkpis[:],
            int_vars=self_stats.number_of_integer_variables,
            continuous_vars=self_stats.number_of_continuous_variables,
            linear_constraints=self_stats.number_of_linear_constraints,
            bin_vars=self_stats.number_of_binary_variables,
            quadratic_constraints=self_stats.number_of_quadratic_constraints,
            total_constraints=self_stats.number_of_constraints,
            total_variables=self_stats.number_of_variables)
        # implementation for https://github.ibm.com/IBMDecisionOptimization/dd-planning/issues/2491
        problem_type = mdl._get_cplex_problem_type()
        kpis['STAT.cplex.modelType'] = problem_type
        kpis['MODEL_DETAIL_OBJECTIVE_SENSE'] = mdl._objective_sense.verb

        the_env.notify_start_solve(kpis)
        if auto_publish_details:
            the_env.update_solve_details(kpis)
        # ]]]

        # ---
        #  parameters override if necessary...
        # --
        self_params = mdl.context._get_raw_cplex_parameters()
        parameters = apply_thread_limitations(context)
        if self_params and parameters is not self_params:
            self._saved_params = {p: p.get() for p in self_params}
        else:
            self._saved_params = {}
        # ctx_params = context.cplex_parameters
        # mdl_params = mdl.parameters
        # corrected_threads = compute_overwrite_nb_threads(ctx_params, context.solver)
        # params_to_use = ctx_params or mdl_params
        # if corrected_threads is not None:
        #     params_to_use = params_to_use.copy()
        #     params_to_use.threads = corrected_threads

        # if parameters are overridden, save original values for restoration at solve end
        # if params_to_use is not mdl_params:
        #     self._saved_params = {p: p.get() for p in mdl_params}
        # else:
        #     self._saved_params = {}

        # returns the parameters group to use.
        return parameters