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