From 33ef0dca210b5b614a9baa27a2410afb4c2caed1 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 18 Nov 2024 20:09:25 -0500 Subject: [PATCH 1/4] Added the additional value for the tie breaker code of the ldsda. --- pyomo/contrib/gdpopt/ldsda.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/contrib/gdpopt/ldsda.py b/pyomo/contrib/gdpopt/ldsda.py index 002db30314f..7d95f134ba2 100644 --- a/pyomo/contrib/gdpopt/ldsda.py +++ b/pyomo/contrib/gdpopt/ldsda.py @@ -390,6 +390,9 @@ def neighbor_search(self, config): locally_optimal = True best_neighbor = None self.best_direction = None # reset best direction + fmin = float('inf') # Initialize the best objective value + best_dist = 0 # Initialize the best distance + abs_tol = config.integer_tolerance # Use integer_tolerance for objective comparison for direction in self.directions: neighbor = tuple(map(sum, zip(self.current_point, direction))) if self._check_valid_neighbor(neighbor): From 6d9f66f5b37b415075a9fd194f96a1754308d55b Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 18 Nov 2024 23:55:08 -0500 Subject: [PATCH 2/4] Added necessary imports, objective and value for tiebreaker. --- pyomo/contrib/gdpopt/ldsda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/gdpopt/ldsda.py b/pyomo/contrib/gdpopt/ldsda.py index 7d95f134ba2..f5a47b1f7e9 100644 --- a/pyomo/contrib/gdpopt/ldsda.py +++ b/pyomo/contrib/gdpopt/ldsda.py @@ -33,7 +33,7 @@ from pyomo.contrib.gdpopt.nlp_initialization import restore_vars_to_original_values from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning, get_main_elapsed_time from pyomo.contrib.satsolver.satsolver import satisfiable -from pyomo.core import minimize, Suffix, TransformationFactory +from pyomo.core import minimize, Suffix, TransformationFactory, Objective, value from pyomo.opt import SolverFactory from pyomo.opt import TerminationCondition as tc from pyomo.core.expr.logical_expr import ExactlyExpression From d74b3945dd931883864df8ea459f5c05baa45111 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 19 Nov 2024 00:19:04 -0500 Subject: [PATCH 3/4] Modification on the solve_GDP_subproblem. --- pyomo/contrib/gdpopt/ldsda.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/gdpopt/ldsda.py b/pyomo/contrib/gdpopt/ldsda.py index f5a47b1f7e9..afdc41af893 100644 --- a/pyomo/contrib/gdpopt/ldsda.py +++ b/pyomo/contrib/gdpopt/ldsda.py @@ -190,7 +190,7 @@ def _solve_GDP_subproblem(self, external_var_value, search_type, config): 'contrib.deactivate_trivial_constraints' ).apply_to(subproblem, tmp=False, ignore_infeasible=False) except InfeasibleConstraintException: - return False + return False, None minlp_args = dict(config.minlp_solver_args) if config.time_limit is not None and config.minlp_solver == 'gams': elapsed = get_main_elapsed_time(self.timing) @@ -200,16 +200,19 @@ def _solve_GDP_subproblem(self, external_var_value, search_type, config): result = SolverFactory(config.minlp_solver).solve( subproblem, **minlp_args ) + # Retrieve the primal bound (objective value) from the subproblem + obj = next(subproblem.component_data_objects(Objective, active=True)) + primal_bound = value(obj) primal_improved = self._handle_subproblem_result( result, subproblem, external_var_value, config, search_type ) - return primal_improved + return primal_improved, primal_bound except RuntimeError as e: config.logger.warning( "Solver encountered RuntimeError. Treating as infeasible. " "Msg: %s\n%s" % (str(e), traceback.format_exc()) ) - return False + return False, None def _get_external_information(self, util_block, config): """Function that obtains information from the model to perform the reformulation with external variables. From 3c65598cdfe2e002d90192fbbf6dc261c2b42022 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 19 Nov 2024 00:43:14 -0500 Subject: [PATCH 4/4] Neighbor Search add tiebreaker logic --- pyomo/contrib/gdpopt/ldsda.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/gdpopt/ldsda.py b/pyomo/contrib/gdpopt/ldsda.py index afdc41af893..1290f0f1766 100644 --- a/pyomo/contrib/gdpopt/ldsda.py +++ b/pyomo/contrib/gdpopt/ldsda.py @@ -396,18 +396,44 @@ def neighbor_search(self, config): fmin = float('inf') # Initialize the best objective value best_dist = 0 # Initialize the best distance abs_tol = config.integer_tolerance # Use integer_tolerance for objective comparison + + # Loop through all possible directions (neighbors) for direction in self.directions: + # Generate a neighbor point by applying the direction to the current point neighbor = tuple(map(sum, zip(self.current_point, direction))) + + # Check if the neighbor is valid if self._check_valid_neighbor(neighbor): - primal_improved = self._solve_GDP_subproblem( + # Solve the subproblem for this neighbor + primal_improved, primal_bound = self._solve_GDP_subproblem( neighbor, 'Neighbor search', config ) + if primal_improved: locally_optimal = False - best_neighbor = neighbor - self.best_direction = direction + + # --- Tiebreaker Logic --- + if abs(fmin - primal_bound) < abs_tol: + # Calculate the Euclidean distance from the current point + dist = sum((x - y) ** 2 for x, y in zip(neighbor, self.current_point)) + + # Update the best neighbor if this one is farther away + if dist > best_dist: + best_neighbor = neighbor + self.best_direction = direction + best_dist = dist # Update the best distance + else: + # Standard improvement logic: update if the objective is better + fmin = primal_bound # Update the best objective value + best_neighbor = neighbor # Update the best neighbor + self.best_direction = direction # Update the best direction + best_dist = sum((x - y) ** 2 for x, y in zip(neighbor, self.current_point)) + # --- End of Tiebreaker Logic --- + + # Move to the best neighbor if an improvement was found if not locally_optimal: self.current_point = best_neighbor + return locally_optimal def line_search(self, config):