diff --git a/src/hiopbbpy/opt/boalgorithm.py b/src/hiopbbpy/opt/boalgorithm.py index e96a1601d..35541d609 100644 --- a/src/hiopbbpy/opt/boalgorithm.py +++ b/src/hiopbbpy/opt/boalgorithm.py @@ -64,7 +64,7 @@ def getOptimalPoint(self): # Method to return the optimal objective def getOptimalObjective(self): y_opt = np.array(self.y_opt, copy=True) - return y_opt + return y_opt[0] # A subclass of BOAlgorithmBase implementing a full Bayesian Optimization workflow class BOAlgorithm(BOAlgorithmBase): @@ -351,8 +351,17 @@ def minimizer_callback(self, x0s): xopt = y.x yopt = y.fun elif self.method == "trust-constr": - nonlinear_constraint = NonlinearConstraint(self.constraints['cons'], self.constraints['cl'], self.constraints['cu'], jac=self.constraints['jac']) - y = minimize(self.fun['obj'], x0, method=self.method, bounds=self.bounds, constraints=[nonlinear_constraint], options=self.solver_options) + constraints = [] + if self.constraints: # non-empty dict → constrained problem + nonlinear_constraint = NonlinearConstraint( + self.constraints['cons'], + self.constraints['cl'], + self.constraints['cu'], + jac=self.constraints.get('jac', None) + ) + constraints.append(nonlinear_constraint) + + y = minimize(self.fun['obj'], x0, method=self.method, bounds=self.bounds, constraints=constraints, options=self.solver_options) success = y.success if not success: msg = y.message diff --git a/src/hiopbbpy/opt/optproblem.py b/src/hiopbbpy/opt/optproblem.py index 84cef50ee..84a87e136 100644 --- a/src/hiopbbpy/opt/optproblem.py +++ b/src/hiopbbpy/opt/optproblem.py @@ -35,14 +35,21 @@ def __init__(self, objective, gradient, constraint:Union[Dict, List[Dict]], xbou self.eval_g = gradient self.xl = [b[0] for b in xbounds] self.xu = [b[1] for b in xbounds] - self.cl = [] - self.cu = [] self.nvar = len(xbounds) self.ipopt_options = solver_options self.ipopt_options['sb'] = 'yes' - if isinstance(self.cons, list): + unconstrained = ( + constraint is None + or constraint == {} + or constraint == [] + ) + if unconstrained: + self.cl = [] + self.cu = [] + self.ncon = 0 + elif isinstance(self.cons, list): # constraints is provided as a list of dict, supported by SLSQP and Ipopt for con in self.cons: check_required_keys(con,['type', 'fun']) @@ -60,7 +67,7 @@ def __init__(self, objective, gradient, constraint:Union[Dict, List[Dict]], xbou self.cl = constraint['cl'] self.cu = constraint['cu'] else: - raise ValueError("constraints must be provided as a dict of a list of dict.") + raise ValueError("constraints must be None, {}, [], a dict, or a list of dict.") self.ncon = len(self.cl) cyipopt = _require_cyipopt() @@ -80,10 +87,13 @@ def gradient(self, x): return self.eval_g(x) def constraints(self, x): + if self.ncon == 0: + return np.zeros((0,), dtype=float) + if isinstance(self.cons, list): return np.array([con['fun'](x) for con in self.cons]) else: - return self.cons['cons'](x) + return np.asarray(self.cons['cons'](x), dtype=float) def jacobian(self, x): if isinstance(self.cons, list): diff --git a/src/hiopbbpy/utils/util.py b/src/hiopbbpy/utils/util.py index 1ef038e6b..0dae35a1b 100644 --- a/src/hiopbbpy/utils/util.py +++ b/src/hiopbbpy/utils/util.py @@ -120,16 +120,18 @@ def __init__(self, name='hiopbbpy'): # Create a logger instance with a given name self._logger = logging.getLogger(name) + self._logger.propagate = False # prevent double logging # Create a console output handler - ch = logging.StreamHandler() + if not self._logger.handlers: + ch = logging.StreamHandler() - # Define the output format: logger name, and message - formatter = logging.Formatter('%(name)s %(message)s') + # Define the output format: logger name, and message + formatter = logging.Formatter('%(name)s %(message)s') - # Add the handle - ch.setFormatter(formatter) - self._logger.addHandler(ch) + # Add the handle + ch.setFormatter(formatter) + self._logger.addHandler(ch) def setlevel(self, level_str): level = getattr(logging, str(level_str).upper(), logging.INFO)