Skip to content

Nonant bounds buffer #500

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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dd09700
add Field for lower/upper bounds
bknueven Mar 27, 2025
10496da
allow communicator to register its own buffer to receive
bknueven Mar 27, 2025
cd1cadd
promote `Spoke.update_receive_buffers` to SPCommuicator
bknueven Mar 27, 2025
0e7e71c
adding method to update nonant bounds; call that method where appropr…
bknueven Mar 27, 2025
968e494
trying to pass LB/UB from RC Spoke
bknueven Mar 28, 2025
952935b
getting new bounds in hub; doesn't exactly match extension
bknueven Mar 28, 2025
00ab113
partially revert 4e970cdc1fa0541d5d84f293fd1f21c93dca7810
bknueven Mar 31, 2025
912195c
Revert "promote `Spoke.update_receive_buffers` to SPCommuicator"
bknueven Mar 31, 2025
5589d8e
switch to `receive_` paradigm for nonant bounds
bknueven Mar 31, 2025
c8e1363
nonant bound update now matches existing extension
bknueven Mar 31, 2025
a35cd7d
terminate even if signal is stale; faster termination for shuffle bou…
bknueven Mar 31, 2025
1712eac
keep best bounding dual function per variable
bknueven Mar 31, 2025
1c8bcbf
re-do termination synchronization
bknueven Mar 31, 2025
a21143d
Merge branch 'communicator_refactor' into nonant_bounds_buffer
bknueven Apr 2, 2025
988a064
Revert "allow communicator to register its own buffer to receive"
bknueven Apr 2, 2025
a1c640f
cleanup merge; add bound updater for RC spoke
bknueven Apr 2, 2025
42f36e3
Merge branch 'communicator_refactor' into nonant_bounds_buffer
bknueven Apr 2, 2025
be1b31e
Merge branch 'communicator_refactor' into nonant_bounds_buffer
bknueven Apr 25, 2025
2c0b33f
Merge branch 'main' into nonant_bounds_buffer
bknueven Apr 25, 2025
2060a65
Update debug statements and method docstring
bknueven Apr 25, 2025
d638ff8
better reduced costs fixer; remove rc bound tightening
bknueven Apr 15, 2025
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
3 changes: 3 additions & 0 deletions mpisppy/cylinders/fwph_spoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def sync(self):
# Tell the hub about the most recent bound
self.send_bound(self.opt._local_bound)

# Update the nonant bounds, if possible
self.receive_nonant_bounds()

def finalize(self):
# The FWPH spoke can call "finalize" before it
# even starts doing anything, so its possible
Expand Down
80 changes: 4 additions & 76 deletions mpisppy/cylinders/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@
import mpisppy.log

from mpisppy.cylinders.spcommunicator import RecvArray, SPCommunicator
from math import inf

from mpisppy import global_toc

from mpisppy.cylinders.spwindow import Field

# Could also pass, e.g., sys.stdout instead of a filename
mpisppy.log.setup_logger("mpisppy.cylinders.Hub",
mpisppy.log.setup_logger(__name__,
"hub.log",
level=logging.CRITICAL)
logger = logging.getLogger("mpisppy.cylinders.Hub")
logger = logging.getLogger(__name__)

class Hub(SPCommunicator):

send_fields = (*SPCommunicator.send_fields, Field.SHUTDOWN, Field.BEST_OBJECTIVE_BOUNDS,)
receive_fields = (*SPCommunicator.receive_fields, Field.OBJECTIVE_INNER_BOUND, Field.OBJECTIVE_OUTER_BOUND, )
receive_fields = (*SPCommunicator.receive_fields,)

_hub_algo_best_bound_provider = False

Expand All @@ -37,16 +36,10 @@ def __init__(self, spbase_object, fullcomm, strata_comm, cylinder_comm, communic
logger.debug(f"Built the hub object on global rank {fullcomm.Get_rank()}")
# for logging
self.print_init = True
self.latest_ib_char = None
self.latest_ob_char = None
self.last_ib_idx = None
self.last_ob_idx = None
# for termination based on stalling out
self.stalled_iter_cnt = 0
self.last_gap = float('inf') # abs_gap tracker

self.initialize_bound_values()

return

@abc.abstractmethod
Expand Down Expand Up @@ -175,72 +168,6 @@ def hub_finalize(self):
global_toc("Statistics at termination", True)
self.screen_trace()

def receive_innerbounds(self):
""" Get inner bounds from inner bound spokes
NOTE: Does not check if there _are_ innerbound spokes
(but should be harmless to call if there are none)
"""
logging.debug("Hub is trying to receive from InnerBounds")
for idx, cls, recv_buf in self.receive_field_spcomms[Field.OBJECTIVE_INNER_BOUND]:
is_new = self.get_receive_buffer(recv_buf, Field.OBJECTIVE_INNER_BOUND, idx)
if is_new:
bound = recv_buf[0]
logging.debug("!! new InnerBound to opt {}".format(bound))
self.BestInnerBound = self.InnerBoundUpdate(bound, cls, idx)
logging.debug("ph back from InnerBounds")

def receive_outerbounds(self):
""" Get outer bounds from outer bound spokes
NOTE: Does not check if there _are_ outerbound spokes
(but should be harmless to call if there are none)
"""
logging.debug("Hub is trying to receive from OuterBounds")
for idx, cls, recv_buf in self.receive_field_spcomms[Field.OBJECTIVE_OUTER_BOUND]:
is_new = self.get_receive_buffer(recv_buf, Field.OBJECTIVE_OUTER_BOUND, idx)
if is_new:
bound = recv_buf[0]
logging.debug("!! new OuterBound to opt {}".format(bound))
self.BestOuterBound = self.OuterBoundUpdate(bound, cls, idx)
logging.debug("ph back from OuterBounds")

def OuterBoundUpdate(self, new_bound, cls=None, idx=None, char='*'):
current_bound = self.BestOuterBound
if self._outer_bound_update(new_bound, current_bound):
if cls is None:
self.latest_ob_char = char
self.last_ob_idx = 0
else:
self.latest_ob_char = cls.converger_spoke_char
self.last_ob_idx = idx
return new_bound
else:
return current_bound

def InnerBoundUpdate(self, new_bound, cls=None, idx=None, char='*'):
current_bound = self.BestInnerBound
if self._inner_bound_update(new_bound, current_bound):
if cls is None:
self.latest_ib_char = char
self.last_ib_idx = 0
else:
self.latest_ib_char = cls.converger_spoke_char
self.last_ib_idx = idx
return new_bound
else:
return current_bound

def initialize_bound_values(self):
if self.opt.is_minimizing:
self.BestInnerBound = inf
self.BestOuterBound = -inf
self._inner_bound_update = lambda new, old : (new < old)
self._outer_bound_update = lambda new, old : (new > old)
else:
self.BestInnerBound = -inf
self.BestOuterBound = inf
self._inner_bound_update = lambda new, old : (new > old)
self._outer_bound_update = lambda new, old : (new < old)

def _populate_boundsout_cache(self, buf):
""" Populate a given buffer with the current bounds
"""
Expand Down Expand Up @@ -292,6 +219,7 @@ def send_terminate(self):
return

def sync_bounds(self):
self.receive_nonant_bounds()
self.receive_outerbounds()
self.receive_innerbounds()
self.send_boundsout()
Expand Down
24 changes: 15 additions & 9 deletions mpisppy/cylinders/lagrangian_bounder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def lagrangian_prep(self):
self.opt._create_solvers()

def lagrangian(self, need_solution=True):
# update the nonant bounds, if possible, for a tighter relaxation
self.receive_nonant_bounds()
verbose = self.opt.options['verbose']
# This is sort of a hack, but might help folks:
if "ipopt" in self.opt.options["solver_name"]:
Expand Down Expand Up @@ -54,12 +56,21 @@ class LagrangianOuterBound(_LagrangianMixin, mpisppy.cylinders.spoke.OuterBoundW

converger_spoke_char = 'L'

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

def do_while_waiting_for_new_Ws(self, need_solution):
if self.opt.options.get("subgradient_while_waiting", False):
# compute a subgradient step
self.opt.Compute_Xbar(self.verbose)
self.opt.Update_W(self.verbose)
bound = self.lagrangian(need_solution=need_solution)
if bound is not None:
self.send_bound(bound)

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

self.lagrangian_prep()
Expand Down Expand Up @@ -89,10 +100,5 @@ def main(self, need_solution=False):
if extensions:
self.opt.extobject.enditer_after_sync()
self.dk_iter += 1
elif self.opt.options.get("subgradient_while_waiting", False):
# compute a subgradient step
self.opt.Compute_Xbar(verbose)
self.opt.Update_W(verbose)
bound = self.lagrangian(need_solution=need_solution)
if bound is not None:
self.send_bound(bound)
else:
self.do_while_waiting_for_new_Ws(need_solution=need_solution)
Loading
Loading