Skip to content

Commit

Permalink
add the information of external variables in the log
Browse files Browse the repository at this point in the history
  • Loading branch information
ZedongPeng committed Feb 5, 2024
1 parent c4721b5 commit 688a990
Showing 1 changed file with 94 additions and 47 deletions.
141 changes: 94 additions & 47 deletions pyomo/contrib/gdpopt/ldsda.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,22 @@ def _log_citation(self, config):
)

def _solve_gdp(self, model, config):
"""Solve the GDP model.
Parameters
----------
model : ConcreteModel
The GDP model to be solved
config : ConfigBlock
GDPopt configuration block
"""
logger = config.logger
self.explored_nodes = 0
self.log_formatter = (
'{:>9} {:>15} {:>20} {:>11.5f} {:>11.5f} {:>8.2%} {:>7.2f} {}'
)
self.best_direction = None
self.current_point = tuple(config.starting_point)
self.explored_point_set = set()

# Create utility block on the original model so that we will be able to
# copy solutions between
Expand All @@ -106,9 +120,7 @@ def _solve_gdp(self, model, config):
add_algebraic_variable_list(util_block)
add_boolean_variable_lists(util_block)

# We will use the working_model to clone the model and perform
# to transform the ordered boolean variables into external integer variables,
# fix the disjunctions.
# We will use the working_model to perform the LDSDA search.
self.working_model = model.clone()
# TODO: I don't like the name way, try something else?
self.working_model_util_block = self.working_model.component(util_block.name)
Expand All @@ -127,38 +139,47 @@ def _solve_gdp(self, model, config):
self.directions = self._get_directions(
self.number_of_external_variables, config
)
self.best_direction = None
self.current_point = config.starting_point
self.explored_point_set = set()

# Add the BigM suffix if it does not already exist. Used later during
# nonlinear constraint activation.
if not hasattr(self.working_model_util_block, 'BigM'):
self.working_model_util_block.BigM = Suffix()

# Solve the initial point
self.fix_disjunctions_with_external_var(
self.working_model_util_block, self.current_point
)
_ = self._solve_GDP_subproblem(self.working_model, config, 'Initial point')
_ = self._solve_GDP_subproblem(self.current_point, 'Initial point', config)

# Main loop
locally_optimal = False
while not locally_optimal:
self.iteration += 1
if self.any_termination_criterion_met(config):
break
locally_optimal = self.neighbor_search(self.working_model, config)
locally_optimal = self.neighbor_search(config)
if not locally_optimal:
self.line_search(self.working_model, config)

print("Optimal solution", self.current_point)
self.line_search(config)

def any_termination_criterion_met(self, config):
return self.reached_iteration_limit(config) or self.reached_time_limit(config)

def _solve_GDP_subproblem(self, model, config, search_type):
subproblem = model.clone()
def _solve_GDP_subproblem(self, external_var_value, search_type, config):
"""Solve the GDP subproblem with disjunctions fixed according to the external variable.
Parameters
----------
external_var_value : list
The values of the external variables to be evaluated
search_type : str
The type of search, neighbor search or line search
config : ConfigBlock
GDPopt configuration block
Returns
-------
bool
weather the primal bound is improved
"""
self.fix_disjunctions_with_external_var(external_var_value)
subproblem = self.working_model.clone()
TransformationFactory('core.logical_to_linear').apply_to(subproblem)
TransformationFactory('gdp.bigm').apply_to(subproblem)

Expand Down Expand Up @@ -186,7 +207,7 @@ def _solve_GDP_subproblem(self, model, config, search_type):
subproblem, **minlp_args
)
primal_improved = self._handle_subproblem_result(
result, subproblem, config, search_type
result, subproblem, external_var_value, config, search_type
)
return primal_improved
except RuntimeError as e:
Expand Down Expand Up @@ -244,18 +265,17 @@ def _get_external_information(self, util_block, config):
for external_var_info in util_block.external_var_info_list
)

def fix_disjunctions_with_external_var(self, util_block, external_var_values_list):
"""Function that fixes the disjunctions in the model using the values of the external variables.
def fix_disjunctions_with_external_var(self, external_var_values_list):
"""Function that fixes the disjunctions in the working_model using the values of the external variables.
Parameters
----------
util_block : Block
The GDPOPT utility block of the model.
external_var_values_list : List
The list of values of the external variables
"""
for external_variable_value, external_var_info in zip(
external_var_values_list, util_block.external_var_info_list
external_var_values_list,
self.working_model_util_block.external_var_info_list,
):
for idx, (boolean_var, disjunct) in enumerate(
zip(external_var_info.Boolean_vars, external_var_info.Disjuncts)
Expand Down Expand Up @@ -298,6 +318,11 @@ def _get_directions(self, dimension, config):
directions = list(it.product([-1, 0, 1], repeat=dimension))
directions.remove((0,) * dimension)
return directions
else:
raise ValueError(
"The direction_norm option must be 'L2' or 'Linf', "
"but received %s" % config.direction_norm
)

def _check_valid_neighbor(self, neighbor):
"""Function that checks if a given neighbor is valid.
Expand Down Expand Up @@ -325,28 +350,22 @@ def _check_valid_neighbor(self, neighbor):
else:
return False

def neighbor_search(self, model, config):
def neighbor_search(self, config):
"""Function that evaluates a group of given points and returns the best
Parameters
----------
model : ConcreteModel
the subproblem model
config : ConfigBlock
GDPopt configuration block
"""
locally_optimal = True
best_neighbor = None
# reset best direction
self.best_direction = None
self.best_direction = None # reset best direction
for direction in self.directions:
neighbor = tuple(map(sum, zip(self.current_point, direction)))
if self._check_valid_neighbor(neighbor):
self.fix_disjunctions_with_external_var(
self.working_model_util_block, neighbor
)
primal_improved = self._solve_GDP_subproblem(
model, config, 'Neighbor search'
neighbor, 'Neighbor search', config
)
if primal_improved:
locally_optimal = False
Expand All @@ -356,45 +375,43 @@ def neighbor_search(self, model, config):
self.current_point = best_neighbor
return locally_optimal

def line_search(self, model, config):
"""Function that performs a line search in a given direction.
def line_search(self, config):
"""Function that performs a line search in the best direction.
Parameters
----------
model : ConcreteModel
the subproblem model
config : ConfigBlock
GDPopt configuration block
"""
primal_improved = True
while primal_improved:
next_point = tuple(map(sum, zip(self.current_point, self.best_direction)))
if self._check_valid_neighbor(next_point):
self.fix_disjunctions_with_external_var(
self.working_model_util_block, next_point
)
primal_improved = self._solve_GDP_subproblem(
model, config, 'Line search'
next_point, 'Line search', config
)
if primal_improved:
self.current_point = next_point
else:
break
print("Line search finished.")

def _handle_subproblem_result(
self, subproblem_result, subproblem, config, search_type
self, subproblem_result, subproblem, external_var_value, config, search_type
):
"""Function that handles the result of the subproblem
Parameters
----------
subproblem : ConcreteModel
the subproblem model
subproblem_result : tuple
the result of the subproblem
subproblem : ConcreteModel
the subproblem model
external_var_value : list
the values of the external variables
config : ConfigBlock
GDPopt configuration block
search_type : str
the type of search, neighbor search or line search
Returns
-------
Expand All @@ -418,7 +435,10 @@ def _handle_subproblem_result(
else subproblem_result.problem.lower_bound
)
primal_improved = self._update_bounds_after_solve(
search_type, primal=primal_bound, logger=config.logger
search_type,
primal=primal_bound,
logger=config.logger,
current_point=external_var_value,
)
if primal_improved:
self.update_incumbent(
Expand All @@ -430,15 +450,42 @@ def _handle_subproblem_result(
def _log_header(self, logger):
logger.info(
'================================================================='
'============================'
'===================================='
)
logger.info(
'{:^9} | {:^15} | {:^11} | {:^11} | {:^8} | {:^7}\n'.format(
'{:^9} | {:^15} | {:^20} | {:^11} | {:^11} | {:^8} | {:^7}\n'.format(
'Iteration',
'Search Type',
'External Variables',
'Lower Bound',
'Upper Bound',
' Gap ',
'Time(s)',
)
)

def _log_current_state(
self, logger, search_type, current_point, primal_improved=False
):
star = "*" if primal_improved else ""
logger.info(
self.log_formatter.format(
self.iteration,
search_type,
str(current_point),
self.LB,
self.UB,
self.relative_gap(),
get_main_elapsed_time(self.timing),
star,
)
)

def _update_bounds_after_solve(
self, search_type, primal=None, dual=None, logger=None, current_point=None
):
primal_improved = self._update_bounds(primal, dual)
if logger is not None:
self._log_current_state(logger, search_type, current_point, primal_improved)

return primal_improved

0 comments on commit 688a990

Please sign in to comment.