Skip to content

Commit

Permalink
Merge pull request #439 from bknueven/no_error_no_solution
Browse files Browse the repository at this point in the history
Lagrangian does not raise error if no solution is available
  • Loading branch information
bknueven authored Oct 10, 2024
2 parents e26ea81 + 0cbf311 commit b3aa8e8
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 33 deletions.
17 changes: 9 additions & 8 deletions mpisppy/cylinders/lagrangian_bounder.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def lagrangian_prep(self):
self.opt._reenable_W()
self.opt._create_solvers()

def lagrangian(self):
def lagrangian(self, need_solution=True):
verbose = self.opt.options['verbose']
# This is sort of a hack, but might help folks:
if "ipopt" in self.opt.options["solver_name"]:
Expand All @@ -32,7 +32,8 @@ def lagrangian(self):
dtiming=False,
gripe=True,
tee=teeme,
verbose=verbose
verbose=verbose,
need_solution=need_solution,
)
''' DTM (dlw edits): This is where PHBase Iter0 checks for scenario
probabilities that don't sum to one and infeasibility and
Expand All @@ -53,11 +54,11 @@ class LagrangianOuterBound(_LagrangianMixin, mpisppy.cylinders.spoke.OuterBoundW

converger_spoke_char = 'L'

def _set_weights_and_solve(self):
def _set_weights_and_solve(self, need_solution=True):
self.opt.W_from_flat_list(self.localWs) # Sets the weights
return self.lagrangian()
return self.lagrangian(need_solution=need_solution)

def main(self):
def main(self, need_solution=False):
verbose = self.opt.options['verbose']
extensions = self.opt.extensions is not None

Expand All @@ -66,7 +67,7 @@ def main(self):
if extensions:
self.opt.extobject.pre_iter0()
self.dk_iter = 1
self.trivial_bound = self.lagrangian()
self.trivial_bound = self.lagrangian(need_solution=need_solution)
if extensions:
self.opt.extobject.post_iter0()

Expand All @@ -80,7 +81,7 @@ def main(self):
if self.new_Ws:
if extensions:
self.opt.extobject.miditer()
bound = self._set_weights_and_solve()
bound = self._set_weights_and_solve(need_solution=need_solution)
if extensions:
self.opt.extobject.enditer()
if bound is not None:
Expand All @@ -92,6 +93,6 @@ def main(self):
# compute a subgradient step
self.opt.Compute_Xbar(verbose)
self.opt.Update_W(verbose)
bound = self.lagrangian()
bound = self.lagrangian(need_solution=need_solution)
if bound is not None:
self.bound = bound
14 changes: 10 additions & 4 deletions mpisppy/cylinders/reduced_costs_spoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

class ReducedCostsSpoke(LagrangianOuterBound):

converger_spoke_char = 'R'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bound_tol = self.opt.options['rc_bound_tol']
self.consensus_threshold = np.sqrt(self.bound_tol)
self.converger_spoke_char = 'R'


def make_windows(self):
if not hasattr(self.opt, "local_scenarios"):
Expand Down Expand Up @@ -82,8 +82,10 @@ def lagrangian_prep(self):
s.rc = pyo.Suffix(direction=pyo.Suffix.IMPORT)
self.opt._create_solvers(presolve=False)

def lagrangian(self):
bound = super().lagrangian()
def lagrangian(self, need_solution=True):
if not need_solution:
raise RuntimeError("ReducedCostsSpoke always needs a solution to work")
bound = super().lagrangian(need_solution=need_solution)
if bound is not None:
self.extract_and_store_reduced_costs(bound)
return bound
Expand Down Expand Up @@ -135,3 +137,7 @@ def extract_and_store_reduced_costs(self, outer_bound):
rcg = np.zeros(self.nonant_length)
self.cylinder_comm.Allreduce(rc, rcg, op=MPI.SUM)
self.rc = rcg

def main(self):
# need the solution for ReducedCostsSpoke
super().main(need_solution=True)
23 changes: 15 additions & 8 deletions mpisppy/phbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,8 @@ def solve_loop(self, solver_options=None,
gripe=False,
disable_pyomo_signal_handling=False,
tee=False,
verbose=False):
verbose=False,
need_solution=True):
""" Loop over `local_subproblems` and solve them in a manner
dicated by the arguments.
Expand Down Expand Up @@ -558,6 +559,9 @@ def solve_loop(self, solver_options=None,
If True, displays solver output. Default False.
verbose (boolean, optional):
If True, displays verbose output. Default False.
need_solution (boolean, optional):
If True, raises an exception if a solution is not available.
Default True
"""

""" Developer notes:
Expand All @@ -580,13 +584,16 @@ def solve_loop(self, solver_options=None,
if self._prox_approx and (not self.prox_disabled):
self._update_prox_approx()

super().solve_loop(solver_options,
use_scenarios_not_subproblems,
dtiming,
gripe,
disable_pyomo_signal_handling,
tee,
verbose)
super().solve_loop(
solver_options,
use_scenarios_not_subproblems,
dtiming,
gripe,
disable_pyomo_signal_handling,
tee,
verbose,
need_solution,
)

if dis_W and dis_prox:
self.reenable_W_and_prox()
Expand Down
44 changes: 31 additions & 13 deletions mpisppy/spopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ def solve_one(self, solver_options, k, s,
tee=False,
verbose=False,
disable_pyomo_signal_handling=False,
update_objective=True):
update_objective=True,
need_solution=True):
""" Solve one subproblem.
Args:
Expand All @@ -126,6 +127,9 @@ def solve_one(self, solver_options, k, s,
update_objective (boolean, optional):
If True, and a persistent solver is used, update
the persistent solver's objective
need_solution (boolean, optional):
If True, raises an exception if a solution is not available.
Default True
Returns:
float:
Expand Down Expand Up @@ -209,10 +213,14 @@ def _vb(msg):
raise solver_exception

else:
if sputils.is_persistent(s._solver_plugin):
s._solver_plugin.load_vars()
else:
s.solutions.load_from(results)
try:
if sputils.is_persistent(s._solver_plugin):
s._solver_plugin.load_vars()
else:
s.solutions.load_from(results)
except Exception as e: # catch everything
if need_solution:
raise e
if self.is_minimizing:
s._mpisppy_data.outer_bound = results.Problem[0].Lower_bound
s._mpisppy_data.inner_bound = results.Problem[0].Upper_bound
Expand Down Expand Up @@ -244,7 +252,8 @@ def solve_loop(self, solver_options=None,
gripe=False,
disable_pyomo_signal_handling=False,
tee=False,
verbose=False):
verbose=False,
need_solution=True):
""" Loop over `local_subproblems` and solve them in a manner
dicated by the arguments.
Expand All @@ -269,6 +278,9 @@ def solve_loop(self, solver_options=None,
If True, displays solver output. Default False.
verbose (boolean, optional):
If True, displays verbose output. Default False.
need_solution (boolean, optional):
If True, raises an exception if a solution is not available.
Default True
"""

""" Developer notes:
Expand Down Expand Up @@ -300,13 +312,19 @@ def _vb(msg):
logger.debug(" in loop solve_loop k={}, rank={}".format(k, self.cylinder_rank))
if tee:
print(f"Tee solve for {k} on global rank {self.global_rank}")
pyomo_solve_times.append(self.solve_one(solver_options, k, s,
dtiming=dtiming,
verbose=verbose,
tee=tee,
gripe=gripe,
disable_pyomo_signal_handling=disable_pyomo_signal_handling
))
pyomo_solve_times.append(
self.solve_one(
solver_options,
k,
s,
dtiming=dtiming,
verbose=verbose,
tee=tee,
gripe=gripe,
disable_pyomo_signal_handling=disable_pyomo_signal_handling,
need_solution=need_solution,
)
)

if self.extensions is not None:
self.extobject.post_solve_loop()
Expand Down

0 comments on commit b3aa8e8

Please sign in to comment.