Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storing and restoring tree solution for incumbent finders #260

Merged
merged 1 commit into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions mpisppy/cylinders/lshaped_bounder.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,4 @@ def main(self):
if obj is None:
continue

update = (obj < self.ib) if is_minimizing else (self.ib < obj)
if update:
self.bound = obj
self.ib = obj

## TODO: Save somewhere?
self.update_if_improving(obj)
71 changes: 17 additions & 54 deletions mpisppy/cylinders/spoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import mpisppy.utils.sputils as sputils

from pyomo.environ import ComponentMap, Var
from mpisppy import MPI
from mpisppy.cylinders.spcommunicator import SPCommunicator

Expand Down Expand Up @@ -316,15 +317,9 @@ def __init__(self, spbase_object, fullcomm, strata_comm, cylinder_comm, options=
self.best_inner_bound = math.inf if self.is_minimizing else -math.inf
self.solver_options = None # can be overwritten by derived classes

# NOTE: defaults to True
self.save_tree_solution = True
if ('save_tree_solution' in self.options) and (not self.options['save_tree_solution']):
self.save_tree_solution = False

# set up best nonant cache
# NOTE: should we also cache the tree solution??
# set up best solution cache
for k,s in self.opt.local_scenarios.items():
s._mpisppy_data.best_nonant_cache = None
s._mpisppy_data.best_solution_cache = None

def update_if_improving(self, candidate_inner_bound):
if candidate_inner_bound is None:
Expand All @@ -338,59 +333,27 @@ def update_if_improving(self, candidate_inner_bound):
self.best_inner_bound = candidate_inner_bound
# send to hub
self.bound = candidate_inner_bound
self._cache_best_nonants()
self._cache_best_solution()
return True

def finalize(self):
self._restore_and_fix_best_nonants()
if not self.opt.first_stage_solution_available:
return None
if not self.save_tree_solution:
return None

self.opt.solve_loop(solver_options=self.solver_options,
verbose=False,
tee=False)

## NOTE: this should be feasible here,
## if not we've done something wrong
feasP = self.opt.feas_prob()
if abs(feasP - self.opt.E1) > self.opt.E1_tolerance:
raise RuntimeError(f"Found infeasible solution which was feasible before - feasP={feasP}, E1={self.opt.E1}, E1_tolerance={self.opt.E1_tolerance}")

obj = self.opt.Eobjective(verbose=False)
calculated_worse = obj > self.best_inner_bound if self.is_minimizing else self.best_inner_bound > obj

if calculated_worse and not math.isclose(obj, self.best_inner_bound, rel_tol=1e-08, abs_tol=1e-12):
if self.cylinder_rank == 0:
print(f"WARNING: {self.__class__.__name__} best inner bound is different "
f"from objective calculated in finalize")
print(f"Best inner bound: {self.best_inner_bound}")
print(f"Current objective: {obj}")
for k,s in self.opt.local_scenarios.items():
if s._mpisppy_data.best_solution_cache is None:
return None
for var, value in s._mpisppy_data.best_solution_cache.items():
var.set_value(value, skip_validation=True)

self.opt.first_stage_solution_available = True
self.opt.tree_solution_available = True
self.final_bound = obj
return obj
self.final_bound = self.bound
return self.final_bound

def _cache_best_nonants(self):
def _cache_best_solution(self):
for k,s in self.opt.local_scenarios.items():
scenario_cache = {}
for ndn_i, var in s._mpisppy_data.nonant_indices.items():
scenario_cache[ndn_i] = var.value
s._mpisppy_data.best_nonant_cache = scenario_cache

def _restore_and_fix_best_nonants(self):
for k,s in self.opt.local_scenarios.items():
scenario_cache = s._mpisppy_data.best_nonant_cache
if scenario_cache is None:
return
is_persistent = sputils.is_persistent(s._solver_plugin)
solver = s._solver_plugin
for ndn_i, var in s._mpisppy_data.nonant_indices.items():
var.fix(scenario_cache[ndn_i])
if is_persistent:
solver.update_var(var)
self.opt.first_stage_solution_available = True
scenario_cache = ComponentMap()
for var in s.component_data_objects(Var):
scenario_cache[var] = var.value
s._mpisppy_data.best_solution_cache = scenario_cache


class OuterBoundNonantSpoke(_BoundNonantSpoke):
Expand Down