Skip to content

Commit 1036ba9

Browse files
committed
adding max time on BnB solver so that it will eventually exit
1 parent 50d9420 commit 1036ba9

File tree

2 files changed

+65
-47
lines changed

2 files changed

+65
-47
lines changed

src/Drivers/hiopbbpy/BnBBoDriverEX.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ def _evaluate(self, x: np.ndarray) -> np.ndarray:
4141

4242

4343
if __name__ == "__main__":
44-
nx = 2 # dimension of the problem
4544
parser = argparse.ArgumentParser(prog='myprogram')
4645
parser.add_argument("--nx", type=int, default=2, help="dimension of problem")
46+
parser.add_argument("--bnbtol", type=float, default=0.01, help="tolerance for bnb optimizer")
47+
parser.add_argument("--bnbmaxiter", type=int, default=1000, help="maximum number of bnb iterations")
48+
parser.add_argument("--bnbmaxtime", type=float, default=180., help="maximum time for bnb opt")
4749
args = parser.parse_args()
48-
nx = args.nx
50+
nx = args.nx # dimension of the problem
51+
bnbtol = args.bnbtol # tolerance for bnb optimizer
52+
bnbmaxiter = args.bnbmaxiter
53+
bnbmaxtime = args.bnbmaxtime
4954

5055
acquisition_type = 'LCB'
5156
### parameters
@@ -85,22 +90,21 @@ def _evaluate(self, x: np.ndarray) -> np.ndarray:
8590
else:
8691
acqf = EIacquisition(gp_model)
8792

88-
#evaluator = MPIEvaluator(function_mode=False)
8993

9094
solver_options = {
9195
'epsilon_diam' : 1.e-14,
92-
'epsilon_gap' : 1.e-2,
93-
'max_iter': 100,
94-
'nodes_per_batch' : 5#,
95-
#'evaluator': evaluator
96+
'epsilon_gap' : bnbtol,
97+
'max_iter': bnbmaxiter,
98+
'max_bnbtime': bnbmaxtime,
99+
'nodes_per_batch' : 32
96100
}
97101
bnb = BnBAlgorithm(acqf, options=solver_options)
98102
bnb.initialize()
99103
xstar = np.atleast_2d(bnb.optimize())
100-
print(xstar)
101-
print(xstar.shape)
102104
ystar = acqf.evaluate(xstar)
103-
print(ystar)
105+
num_branches = bnb.num_branches
106+
print("number of branches in bnb algorithm = ", num_branches)
107+
104108

105109
#queue = bnb.queue
106110
##print(queue)
@@ -119,19 +123,20 @@ def _evaluate(self, x: np.ndarray) -> np.ndarray:
119123
##print("{0:d} unique clustering groups".format(len(labels)))
120124
#Xqueue = Xqueue.flatten()
121125
#Yqueue = Yqueue.flatten()
122-
l = problem.xlimits[:, 0].astype(float)
123-
u = problem.xlimits[:, 1].astype(float)
124-
n_plot_pts = 1000
125-
X = np.atleast_2d(np.linspace(l[0], u[0], n_plot_pts)).transpose()
126-
Yacqf = [acqf.evaluate(x)[0] for x in X]
127-
128-
plt.plot(X, Yacqf, 'k--', label=r'' + acquisition_type + '$(x)$')
129-
plt.plot(xstar, ystar, "r*", markersize=14, label=r'bnb minimizer')
130-
#for label in labels:
131-
# args = np.argwhere(label == clustering.labels_)
132-
# plt.plot(Xqueue[args], Yqueue[args], "*", markersize=12)
133-
plt.legend()
134-
plt.show()
126+
if False and nx == 1:
127+
l = problem.xlimits[:, 0].astype(float)
128+
u = problem.xlimits[:, 1].astype(float)
129+
n_plot_pts = 1000
130+
X = np.atleast_2d(np.linspace(l[0], u[0], n_plot_pts)).transpose()
131+
Yacqf = [acqf.evaluate(x)[0] for x in X]
132+
133+
plt.plot(X, Yacqf, 'k--', label=r'' + acquisition_type + '$(x)$')
134+
plt.plot(xstar, ystar, "r*", markersize=14, label=r'bnb minimizer')
135+
#for label in labels:
136+
# args = np.argwhere(label == clustering.labels_)
137+
# plt.plot(Xqueue[args], Yqueue[args], "*", markersize=12)
138+
plt.legend()
139+
plt.show()
135140
##exit()
136141

137142

src/hiopbbpy/opt/bnbalgorithm.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
except ImportError:
1414
print("unable to import mpi4py")
1515

16+
import time
17+
18+
1619
# BnBNode
1720
# corners of interval [l, u]
1821
# upper and lower bounds of acquisition function
@@ -282,14 +285,14 @@ def __init__(self, acqf, options = {}):
282285
self.epsilon_prune = 1.e-14
283286
self.max_bnbiter = 2000
284287
self.nodes_per_batch = 1
285-
#self.evaluator = MPIEvaluator(function_mode=False)
288+
self.max_bnbtime = 12 * 60 # 12 minutes
286289

287290
# Set options form command
288291
self.epsilon_gap = options.get('epsilon_gap', self.epsilon_gap)
289292
self.epsilon_diam = options.get('epsilon_diam', self.epsilon_diam)
290293
self.epsilon_prune = options.get('epsilon_prune', self.epsilon_prune)
291294
self.max_bnbiter = options.get('max_iter', self.max_bnbiter)
292-
#self.evaluator = options.get('evaluator', self.evaluator)
295+
self.max_bnbtime = options.get('max_bnbtime', self.max_bnbtime)
293296
self.nodes_per_batch = options.get('nodes_per_batch', self.nodes_per_batch)
294297

295298
if is_running_with_mpi():
@@ -315,7 +318,7 @@ def __init__(self, acqf, options = {}):
315318
self.bfsevaluator = MPIEvaluator(function_mode=False, max_workers = num_bfs_workers)
316319
self.num_bbs_workers = num_bbs_workers
317320
self.num_bfs_workers = num_bfs_workers
318-
self.max_queue_size = 40 * self.num_bbs_workers
321+
self.max_queue_size = 10 * self.num_bbs_workers
319322

320323
# For minimization, we find a feasible function value as the upper bound on the minimum value of the acquisition function.
321324
def compute_acqf_upper_bound(self, l, u):
@@ -440,25 +443,34 @@ def bnboptimize(self, l_init, u_init):
440443

441444
all_bfsnodes = []
442445

443-
num_bbs_tasks_per_loop = np.zeros(10, dtype=np.int32)
444446

445447
# stopping criterion should be on the total maximum number of branched nodes
446-
num_branches = 0
448+
self.num_branches = 0
447449

448450
initial_gap = self.best_node.aq_U - self.best_node.aq_L
449451

450-
i_loop = 0
451-
while num_branches < self.max_bnbiter:
452-
i_loop += 1
452+
max_bbs_node_size = 0
453+
max_bfs_node_size = 0
454+
start_time = time.time()
455+
while self.num_branches < self.max_bnbiter:
453456
# collect nodes to be branched on in list structure
454457
bbsnodes = []
455458
num_submitted_nodes = 0
456459

457-
#if self.bbsevaluator.num_submitted_tasks() + self.bfsevaluator.num_submitted_tasks() > 200:
458-
# continue
459-
460-
if self.bbsevaluator.num_submitted_tasks() < 10:
461-
for i in range(self.nodes_per_batch - 1):
460+
# if the number of submitted jobs is too large then wait for some jobs to be processed
461+
if self.bbsevaluator.num_submitted_tasks() + self.bfsevaluator.num_submitted_tasks() > 10 * (self.num_bbs_workers + self.num_bfs_workers):
462+
if time.time() - start_time > self.max_bnbtime:
463+
print("maximum time has elapsed")
464+
break
465+
else:
466+
print("num submitted bbs tasks = ", self.bbsevaluator.num_submitted_tasks())
467+
print("num submitted bfs tasks = ", self.bfsevaluator.num_submitted_tasks())
468+
time.sleep(1.0) # give time for Evaluators to process jobs
469+
continue
470+
471+
# only submit additional tasks if there aren't too many in the Evaluators queue
472+
if self.bbsevaluator.num_submitted_tasks() < 10 * self.num_bbs_workers:
473+
for i in range(self.nodes_per_batch):
462474
if (not self.queue):
463475
break # no more nodes available to send to evaluator for branching/bound computations
464476
_, _, node = heapq.heappop(self.queue)
@@ -471,8 +483,9 @@ def bnboptimize(self, l_init, u_init):
471483
self.bbsevaluator.submit_tasks(brancher.callback, bbsnodes)
472484

473485
bfsnodes = []
474-
if self.bfsevaluator.num_submitted_tasks() < 10:
475-
for i in range(self.nodes_per_batch - 1):
486+
# only submit additional tasks if there aren't too many in the Evaluators queue
487+
if self.bfsevaluator.num_submitted_tasks() < 10 * self.num_bfs_workers:
488+
for i in range(self.nodes_per_batch):
476489
if len(all_bfsnodes) == 0:
477490
break # no more nodes available to send to evaluator for branching/bound computations
478491
node = all_bfsnodes.pop(0)
@@ -483,10 +496,6 @@ def bnboptimize(self, l_init, u_init):
483496
# asynchronously retrieve results from Evaluator that have been processed
484497
bbschildren = self.bbsevaluator.retrieve_results()
485498

486-
num_bbs_tasks_per_loop[i_loop%10] = len(bbschildren)
487-
#print("num bbs children evaluated = ", len(bbschildren))
488-
#print("number of submitted bbs parents = ", num_submitted_nodes)
489-
490499
# not all children are return, hence children is a ragged array
491500
# need to flatten this ragged list
492501
bbschildren = [item for sublist in bbschildren for item in sublist]
@@ -495,7 +504,7 @@ def bnboptimize(self, l_init, u_init):
495504
bfschildren = [item for sublist in bfschildren for item in sublist]
496505

497506
children = bbschildren + bfschildren # join child lists
498-
num_branches += len(children)
507+
self.num_branches += len(children)
499508
if len(children) == 0:
500509
continue
501510

@@ -520,11 +529,13 @@ def bnboptimize(self, l_init, u_init):
520529
heapq.heappush(self.queue, (child.aq_L, next(self._ctr), child))
521530
else:
522531
all_bfsnodes.append(child)
532+
max_bbs_node_size = max(max_bbs_node_size, len(self.queue))
533+
max_bfs_node_size = max(max_bfs_node_size, len(all_bfsnodes))
523534

524535

525536
# BnB opt progress report
526537
gap = self.best_node.aq_U - self.best_node.aq_L
527-
print(f"\n--- Total number branches {num_branches} ---")
538+
print(f"\n--- Total number branches {self.num_branches} ---")
528539
print(f"Best node bounds: l={self.best_node.l}, u={self.best_node.u}")
529540
print(f"Node acquisition bounds: L={self.best_node.aq_L}, U={self.best_node.aq_U}")
530541
print(f"Current best feasible value (LUB): {self.LUB}")
@@ -539,12 +550,14 @@ def bnboptimize(self, l_init, u_init):
539550
all_bfsnodes = self._prune_node_list(all_bfsnodes, self.LUB, self.epsilon_prune)
540551

541552
if updated_best_node:
542-
if gap / initial_gap < self.epsilon_gap:
543-
print(f"STOP: optimality gap = {gap/initial_gap} < {self.epsilon_gap}")
553+
if gap < self.epsilon_gap:
554+
print(f"STOP: optimality gap = {gap} < {self.epsilon_gap}")
544555
break
545556

546557
print("\n=== Optimization Finished ===")
547-
print(f"Total number of branches: {num_branches}")
558+
print(f"Total number of branches: {self.num_branches}")
559+
print(f"Max BBS node list size: {max_bbs_node_size}")
560+
print(f"Max BFS node list size: {max_bfs_node_size}")
548561
print(f"Best bounds: l={self.best_node.l}, u={self.best_node.u}")
549562
print(f"Best feasible acquisition value (LUB): {self.LUB}")
550563
print(f"Initial gap: {initial_gap}")

0 commit comments

Comments
 (0)