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
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
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