Skip to content

Commit b1253f8

Browse files
committed
updates, following email conversation with NY
1 parent 5446e97 commit b1253f8

2 files changed

Lines changed: 122 additions & 58 deletions

File tree

src/hiopbbpy/opt/bnbalgorithm.py

Lines changed: 121 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
print("unable to import mpi4py")
1515

1616
import time
17-
17+
import math
1818

1919
# BnBNode
2020
# corners of interval [l, u]
@@ -366,15 +366,48 @@ def compute_acqf_bounds(self, l, u):
366366
#opt_sol = opt_evaluator.run(acqf_minimizer.minimizer_callback, x0)[0]
367367
#assert (np.all(opt_sol[0] >= l) and np.all(opt_sol[0] <= u)), f"acqf minimizer not within bounds"
368368
#acqf_U = opt_sol[1]
369+
if False:
370+
opt_solver = "SLSQP"
371+
opt_solver_options = {'maxiter' : 1000}
372+
constraints = []
373+
box_bounds = [[l[i], u[i]] for i in range(len(l))]
374+
acqf_callback = {'obj' : self.acqf.scalar_evaluate}
375+
if self.acqf.has_gradient:
376+
acqf_callback['grad'] = self.acqf.scalar_eval_g
377+
378+
opt_evaluator = Evaluator()
379+
acqf_minimizer = minimizer_wrapper(acqf_callback, opt_solver, box_bounds, constraints, opt_solver_options)
380+
381+
x0 = [[(l[i] + u[i]) / 2. for i in range(len(u))]]
382+
opt_sol = opt_evaluator.run(acqf_minimizer.minimizer_callback, x0)[0]
383+
assert (np.all(opt_sol[0] >= l) and np.all(opt_sol[0] <= u)), f"acqf minimizer not within bounds"
384+
assert opt_sol[2], "optimizer did not converge"
385+
acqf_U = self.acqf.evaluate(np.atleast_2d(opt_sol[0])).flatten()[0]
386+
else:
387+
n_points = 3 ** self.gpsurrogate.ndim
388+
x_points = np.zeros((n_points, self.gpsurrogate.ndim))
389+
for i in range(n_points):
390+
for j in range(self.gpsurrogate.ndim):
391+
x_points[i, j] = l[j] + (u[j] - l[j]) / 2. * float(int(i / 3**j) % 3)
392+
#n_points = 3
393+
#x_points = np.zeros((n_points, self.gpsurrogate.ndim))
394+
##for i in range(n_points):
395+
## for j in range(self.gpsurrogate.ndim):
396+
## x_points[i, j] = (l[j] + u[j]) / 2.
397+
#x_points[0, :] = l[:]
398+
#x_points[1, :] = (l[:] + u[:]) / 2.
399+
#x_points[2, :] = u[:]
400+
acqf_eval = self.acqf.evaluate(x_points)
401+
acqf_U = min(acqf_eval.flatten())
369402

370403

371-
n_points = 1
372-
x_points = np.zeros((n_points, self.gpsurrogate.ndim))
373-
for i in range(n_points):
374-
for j in range(self.gpsurrogate.ndim):
375-
x_points[i, j] = (l[j] + u[j]) / 2.
376-
acqf_eval = self.acqf.evaluate(x_points)
377-
acqf_U = min(acqf_eval.flatten())
404+
#n_points = 1
405+
#x_points = np.zeros((n_points, self.gpsurrogate.ndim))
406+
#for i in range(n_points):
407+
# for j in range(self.gpsurrogate.ndim):
408+
# x_points[i, j] = (l[j] + u[j]) / 2.
409+
#acqf_eval = self.acqf.evaluate(x_points)
410+
#acqf_U = min(acqf_eval.flatten())
378411
#if acqf_U < acqf_bounds[0]:
379412
# print("ERROR in bound computations U < L")
380413
# print(f"Acquisition function evaluations for node defined by bounds: {l} {u}")
@@ -388,18 +421,21 @@ def compute_acqf_bounds(self, l, u):
388421
# #acqf_U = min(acqf_eval)
389422
# else:
390423
# print("all point evaluations < L")
424+
assert acqf_bounds[0] <= acqf_U, "acqf_U < acqf_L"
391425

392426
return acqf_bounds[0], acqf_U
393427
def _prune_queue(self, queue, lub, eps):
394428
"""Keep only nodes whose lower-bound is not greater or equal least upper-bound + eps; then re-heapify."""
395429
# queue items are (L, counter, node)
396-
pruned = [(L, c, n) for (L, c, n) in queue if L < lub + eps]
397-
heapq.heapify(pruned)
398-
return pruned
430+
pruned_queue = [(L, c, n) for (L, c, n) in queue if L < lub + eps]
431+
pruned_nodes = [n for (L, c, n) in queue if L >= lub + eps] # for now return the nodes that are pruned for analysis
432+
heapq.heapify(pruned_queue)
433+
return pruned_queue, pruned_nodes
399434
def _prune_node_list(self, node_list, lub, eps):
400435
"""Keep only nodes whose lower-bound is not greater or equal least upper-bound + eps."""
401436
pruned_node_list = [node for node in node_list if node.aq_L < lub + eps]
402-
return pruned_node_list
437+
pruned_nodes = [node for node in node_list if node.aq_L >= lub + eps]
438+
return pruned_node_list, pruned_nodes
403439
def optimize(self):
404440
opt = self.bnboptimize(self.gpsurrogate.xlimits[:,0], self.gpsurrogate.xlimits[:,1])
405441
lopt = opt[0]
@@ -454,12 +490,16 @@ def bnboptimize(self, l_init, u_init):
454490
heapq.heapify(self.queue)
455491

456492
all_bfsnodes = []
493+
494+
all_prunednodes = []
495+
total_prunedvol = 0.
457496

458497

459498
# stopping criterion should be on the total maximum number of branched nodes
460499
self.num_branches = 0
461500

462501
initial_gap = self.best_node.aq_U - self.best_node.aq_L
502+
initial_vol = math.prod(self.best_node.u - self.best_node.l)
463503

464504
max_bbs_node_size = 0
465505
max_bfs_node_size = 0
@@ -486,13 +526,9 @@ def bnboptimize(self, l_init, u_init):
486526

487527
children = bbschildren + bfschildren # join child lists
488528
if len(children) == 0:
489-
if len(self.queue) == 0 and len(all_bfsnodes) == 0:
490-
if self.bbsevaluator.num_submitted_tasks() == 0 and self.bfsevaluator.num_submitted_tasks() == 0:
491-
print("no child nodes recovered from evaluators")
492-
print("no nodes in bfs/bbs queue lists")
493-
print("evaluators have no tasks to be evaluated")
494-
print("exiting")
495-
exit()
529+
assert True, "trivial check"
530+
#if len(self.queue) == 0 and len(all_bfsnodes) == 0:
531+
# assert self.bbsevaluator.num_submitted_tasks() + self.bfsevaluator.num_submitted_tasks() > 0, "node lists empty and Evaluators have no tasks submitted"
496532
else:
497533
self.num_branches += len(children)
498534
print(f"elapsed time: {time.time() - start_time}")
@@ -515,35 +551,42 @@ def bnboptimize(self, l_init, u_init):
515551

516552
# pre-prune
517553
children_lower_bounds = [child.aq_L for child in children]
518-
args = np.argwhere(np.array(children_lower_bounds) < self.LUB + self.epsilon_prune).flatten()
519-
print(f"{len(args)} children to be appended to bbs/bfs lists")
520-
children = [children[arg] for arg in args]
554+
#args = np.argwhere(np.array(children_lower_bounds) < self.LUB + self.epsilon_prune).flatten()
555+
#children = [children[arg] for arg in args]
556+
#print(f"{len(children)} children to be appended to bbs/bfs lists")
521557

522558
# now move pruned children to data structs for (potential) future evaluation
523559
children_lower_bounds = [child.aq_L for child in children]
524560
# sort the children in order of increasing acqf lower-bounds
525561
args = np.argsort(children_lower_bounds)
526562
children = [children[arg] for arg in args]
563+
# sort children into bbs and bfs lists
527564
for child in children:
528-
if len(self.queue) < 100 * self.num_bbs_workers:
565+
if len(self.queue) < 10 * self.num_bbs_workers:
529566
heapq.heappush(self.queue, (child.aq_L, next(self._ctr), child))
530567
else:
531568
all_bfsnodes.append(child)
532569
max_bbs_node_size = max(max_bbs_node_size, len(self.queue))
533570
max_bfs_node_size = max(max_bfs_node_size, len(all_bfsnodes))
534571

535572
# reprune
536-
self.queue = self._prune_queue(self.queue, self.LUB, self.epsilon_prune)
537-
all_bfsnodes = self._prune_node_list(all_bfsnodes, self.LUB, self.epsilon_prune)
538-
573+
self.queue, pruned_bbsnodes = self._prune_queue(self.queue, self.LUB, self.epsilon_prune)
574+
all_bfsnodes, pruned_bfsnodes = self._prune_node_list(all_bfsnodes, self.LUB, self.epsilon_prune)
575+
pruned_nodes = pruned_bbsnodes + pruned_bfsnodes
576+
for node in pruned_nodes:
577+
all_prunednodes.append(node)
578+
total_prunedvol += math.prod(node.u - node.l)
579+
539580

540581
# BnB opt progress report
541582
gap = self.best_node.aq_U - self.best_node.aq_L
542583
print(f"\n--- Total number branches {self.num_branches} ---")
543-
print(f"Best node bounds: l={self.best_node.l}, u={self.best_node.u}")
584+
print(f"Corners of best node region: l={self.best_node.l}, u={self.best_node.u}")
544585
print(f"Node acquisition bounds: L={self.best_node.aq_L}, U={self.best_node.aq_U}")
545-
print(f"Current best feasible value (LUB): {self.LUB}")
546586
print(f"gap = {gap}")
587+
#print(f"Current best feasible value (LUB): {self.LUB}")
588+
print(f"total pruned vol: {total_prunedvol}")
589+
print(f"domain vol: {initial_vol}")
547590
print(f"size of bbs queue = {len(self.queue)}")
548591
print(f"size of bfs node list = {len(all_bfsnodes)}")
549592
print(f"number of submitted jobs (bbs): {self.bbsevaluator.num_submitted_tasks()}")
@@ -565,9 +608,11 @@ def bnboptimize(self, l_init, u_init):
565608
#if self.bbsevaluator.num_submitted_tasks() + self.bfsevaluator.num_submitted_tasks() > 10 * (self.num_bbs_workers + self.num_bfs_workers):
566609
# collect nodes to be branched on in list structure
567610
# only submit additional tasks if there aren't too many in the Evaluators queue
568-
if self.bbsevaluator.num_submitted_tasks() < 10 * self.num_bbs_workers:
611+
num_bbs_tasks_to_submit = 10 * self.num_bbs_workers - self.bbsevaluator.num_submitted_tasks()
612+
#num_bbs_tasks_to_submit = len(self.queue)
613+
if num_bbs_tasks_to_submit > 0:
569614
bbsnodes = []
570-
for i in range(self.nodes_per_batch):
615+
for i in range(num_bbs_tasks_to_submit):
571616
if (not self.queue):
572617
break # no more nodes available to send to evaluator for branching/bound computations
573618
_, _, node = heapq.heappop(self.queue)
@@ -580,9 +625,11 @@ def bnboptimize(self, l_init, u_init):
580625
self.bbsevaluator.submit_tasks(brancher.callback, bbsnodes)
581626

582627
# only submit additional tasks if there aren't too many in the Evaluators queue
583-
if self.bfsevaluator.num_submitted_tasks() < 10 * self.num_bfs_workers:
628+
num_bfs_tasks_to_submit = 10 * self.num_bfs_workers - self.bfsevaluator.num_submitted_tasks()
629+
#num_bfs_tasks_to_submit = len(all_bfsnodes)
630+
if num_bfs_tasks_to_submit > 0:
584631
bfsnodes = []
585-
for i in range(self.nodes_per_batch):
632+
for i in range(num_bfs_tasks_to_submit):
586633
if len(all_bfsnodes) == 0:
587634
break # no more nodes available to send to evaluator for branching/bound computations
588635
node = all_bfsnodes.pop(0)
@@ -591,6 +638,10 @@ def bnboptimize(self, l_init, u_init):
591638
if len(bfsnodes) > 0:
592639
self.bfsevaluator.submit_tasks(brancher.callback, bfsnodes)
593640

641+
# TODO: sync step and prune
642+
# get final data
643+
644+
594645
print("\n=== Optimization Finished ===")
595646
print(f"Total number of branches: {self.num_branches}")
596647
print(f"Max BBS node list size: {max_bbs_node_size}")
@@ -803,26 +854,40 @@ def compute_acqf_bounds(self, l, u):
803854
var = np.array([var_U, var_L])
804855
acqf_bounds = self.acqf.evaluate_meansig2(mu, var)
805856

806-
#opt_solver = "SLSQP"
807-
#opt_solver_options = {'maxiter' : 200}
808-
#constraints = []
809-
#box_bounds = [[l[i], u[i]] for i in range(len(l))]
810-
#acqf_callback = {'obj' : self.acqf.scalar_evaluate}
811-
#if self.acqf.has_gradient:
812-
# acqf_callback['grad'] = self.acqf.scalar_eval_g
813-
#
814-
#opt_evaluator = Evaluator()
815-
#acqf_minimizer = minimizer_wrapper(acqf_callback, opt_solver, box_bounds, constraints, opt_solver_options)
816-
817-
#x0 = [[(l[i] + u[i]) / 2. for i in range(len(u))]]
818-
#opt_sol = opt_evaluator.run(acqf_minimizer.minimizer_callback, x0)[0]
819-
#assert (np.all(opt_sol[0] >= l) and np.all(opt_sol[0] <= u)), f"acqf minimizer not within bounds"
820-
#acqf_U = opt_sol[1]
857+
if False:
858+
opt_solver = "SLSQP"
859+
opt_solver_options = {'maxiter' : 1000}
860+
constraints = []
861+
box_bounds = [[l[i], u[i]] for i in range(len(l))]
862+
acqf_callback = {'obj' : self.acqf.scalar_evaluate}
863+
if self.acqf.has_gradient:
864+
acqf_callback['grad'] = self.acqf.scalar_eval_g
865+
866+
opt_evaluator = Evaluator()
867+
acqf_minimizer = minimizer_wrapper(acqf_callback, opt_solver, box_bounds, constraints, opt_solver_options)
868+
869+
x0 = [[(l[i] + u[i]) / 2. for i in range(len(u))]]
870+
opt_sol = opt_evaluator.run(acqf_minimizer.minimizer_callback, x0)[0]
871+
assert (np.all(opt_sol[0] >= l) and np.all(opt_sol[0] <= u)), f"acqf minimizer not within bounds"
872+
assert opt_sol[2], "optimizer did not converge"
873+
acqf_U = self.acqf.evaluate(np.atleast_2d(opt_sol[0])).flatten()[0]
874+
else:
875+
n_points = 3 ** self.gpsurrogate.ndim
876+
x_points = np.zeros((n_points, self.gpsurrogate.ndim))
877+
for i in range(n_points):
878+
for j in range(self.gpsurrogate.ndim):
879+
x_points[i, j] = l[j] + (u[j] - l[j]) / 2. * float(int(i / 3**j) % 3)
880+
#x_points[i, j] = (l[j] + u[j]) / 2.
881+
#x_points[0, :] = l[:]
882+
#x_points[1, :] = (l[:] + u[:]) / 2.
883+
#x_points[2, :] = u[:]
884+
acqf_eval = self.acqf.evaluate(x_points)
885+
acqf_U = min(acqf_eval.flatten())
821886
#n_points = 3 ** self.gpsurrogate.ndim
822887
#x_points = np.zeros((n_points, self.gpsurrogate.ndim))
823888
#for i in range(n_points):
824889
# for j in range(self.gpsurrogate.ndim):
825-
# x_points[i, j] = l[j] + ((u[j] - l[j])/2.) * np.floor(i / (3**j)).astype(int) % 3
890+
# x_points[i, j] = l[j] + ((u[j] - l[j]) / 2.) * np.floor(i / (3**j)).astype(int) % 3
826891
#n_points = 1
827892
#x_points = np.zeros((n_points, self.gpsurrogate.ndim))
828893
#for j in range(self.gpsurrogate.ndim):
@@ -832,13 +897,13 @@ def compute_acqf_bounds(self, l, u):
832897
#for i in range(n_points):
833898
# for j in range(self.gpsurrogate.ndim):
834899
# x_points[i, j] = l[j] + (u[j] - l[j]) * float((i + j) % self.gpsurrogate.ndim) / float(self.gpsurrogate.ndim)
835-
n_points = 1
836-
x_points = np.zeros((n_points, self.gpsurrogate.ndim))
837-
for i in range(n_points):
838-
for j in range(self.gpsurrogate.ndim):
839-
x_points[i, j] = (l[j] + u[j]) / 2.
840-
acqf_eval = self.acqf.evaluate(x_points)
841-
acqf_U = min(acqf_eval.flatten())
900+
#n_points = 1
901+
#x_points = np.zeros((n_points, self.gpsurrogate.ndim))
902+
#for i in range(n_points):
903+
# for j in range(self.gpsurrogate.ndim):
904+
# x_points[i, j] = (l[j] + u[j]) / 2.
905+
#acqf_eval = self.acqf.evaluate(x_points)
906+
#acqf_U = min(acqf_eval.flatten())
842907
#if acqf_bounds[0] > acqf_U:
843908
# print("ERROR in bound computations U < L")
844909
# print(f"Acquisition function evaluations for node defined by bounds: {l} {u}")
@@ -865,7 +930,7 @@ def compute_acqf_bounds(self, l, u):
865930
#opt_output = opt_evaluator.run(acqf_minimizer.minimizer_callback, x0_pts)[0]
866931
#assert opt_output[2], f"local optimizer failed"
867932

868-
933+
assert acqf_bounds[0] <= acqf_U, "acqf_U < acqf_L"
869934
return acqf_bounds[0], acqf_U
870935
def callback(self, nodes):
871936
output = []

src/hiopbbpy/opt/boalgorithm.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ def getOptimalPoint(self):
6363

6464
# Method to return the optimal objective
6565
def getOptimalObjective(self):
66-
y_opt = np.array(self.y_opt, copy=True)
67-
return y_opt
66+
return self.y_opt[0]
6867

6968
# A subclass of BOAlgorithmBase implementing a full Bayesian Optimization workflow
7069
class BOAlgorithm(BOAlgorithmBase):

0 commit comments

Comments
 (0)