Conversation
There was a problem hiding this comment.
To my understanding, what this PR does is
- compute constraints-only acquisition function multiplier aka 'probability of feasibility'
- instead of multiplying base acq like regular constrained BO, it then uses 1) to generate a model of an inequality constraint on the inputs
Question 1:
Are you sure that same effect cannot be accomplished with a sharper edge function for constraints and multiplying base acquisition the usual way?
Question 2:
Doesn't this effectively double penalize the acquisition function if constraints are applied both to base acquisition and as inequalities? Can the base acquisition function be used directly in this case?
Question 3:
For better performance, is there a neat way to cache the constraint call results to avoid double evaluating them? Seems that would require some surgery on the acquisition functions to store result of apply_constraints, so probably not worth it.
Overall though, no objections. Once this PR is merged, I will be adding variance limits/constraints. We found them very useful to ensure sampling near known locations for machines like Booster where absolutely no bad shots can be permitted during global BE. So far I treated them as just additional fatmoid clipped constraints with special processing, and will explore your inequality approach.
|
|
||
| # Apply nonlinear constraints -- remove f_value point where constraints(x) does not satisfy the constraints | ||
| if nonlinear_inequality_constraints is not None: | ||
| mask = torch.ones(f_values.shape, dtype=torch.bool, device=f_values.device) |
There was a problem hiding this comment.
always force to cpu to be same as mesh_pts (or send all tensors to specific device)?
| logger.debug("getting random initial conditions") | ||
| start = time.time() | ||
| lower, upper = bounds[0], bounds[1] | ||
| rand = torch.rand( |
There was a problem hiding this comment.
In BoTorch gen_batch_initial_conditions, they use sobol random sampling - should we copy that approach?
| assert candidates.shape == torch.Size([ncandidate, ndim]) | ||
|
|
||
| # test nonlinear constraints | ||
| def constraint1(X): |
| function: Callable, | ||
| bounds: Tensor, | ||
| n_candidates: int = 1, | ||
| nonlinear_inequality_constraints: (list[tuple[Callable, bool]] | None) = None, |
| A tensor specifying the bounds for the optimization. It must have the shape [2, ndim]. | ||
| n_candidates : int, optional | ||
| The number of candidates to generate (default is 1). | ||
| nonlinear_inequality_constraints : Optional[list[Callable]] |
|
|
||
| warnings.warn( | ||
| "Nonlinear inequality constraints are provided for LBFGS numerical optimization, " | ||
| "using a random initial condition generator which may take a long time to sample enough points.", |
There was a problem hiding this comment.
Supplying nonlinear_inequality_constraints also switches algo to SLSQP (see here). That might explain why convergence slows down.
|
|
||
| sampler = self._get_sampler(model) | ||
|
|
||
| log_feasibility = qLogProbabilityOfFeasibility( |
There was a problem hiding this comment.
can analytic version be used for better perf?
There was a problem hiding this comment.
probably, will need to check if it can be used for a given model
|
Thanks for the comments @nikitakuklev . In its current implementation, it actually both weights the acquisition function with the feasibility multiplier and restricts the numerical optimization of the acquisition function with a nonlinear feasibility constraint. This might be redundant and adds computational complexity now that I think of it.
To clarify you add in a constraint to acquisition function optimization that measures the model uncertainty? So, in this case the acquisition function will only be optimized in regions where the model has a high degree of confidence? I will take a look at making changes to improve the computational efficiency based on your suggestions in the next few weeks. If you want it faster we can ID a subset of the changes you proposes an merge these features over 2 PRs |

This pull request introduces support for nonlinear inequality constraints in numerical optimization of the acquisition function. The changes include enhancements to the
LBFGSOptimizerandGridOptimizerclasses, updates to acquisition functions, and new testing capabilities. These additions enable using the probability of feasibility as a nonlinear constraint when optimizing the acquisition function.Currently, generating initial points and optimizing the acquisition function is relatively slow for
LBFGSOptimizerso it is not recommended to use this functionality for high-D parameter spaces.Enhancements to Numerical Optimization:
LBFGSOptimizer: Added logic to handle nonlinear constraints, including a random initial condition generator for sampling feasible points. If candidate generation fails, the optimizer falls back to random valid samples. (xopt/numerical_optimizer.py) [1] [2]GridOptimizer: Integrated constraint handling by filtering grid points based on feasibility. Raises an error if no feasible points are found. (xopt/numerical_optimizer.py)Updates to Bayesian Generator:
feasibility_tolerancefield toBayesianGeneratorto enable constrained acquisition function optimization based on predicted probability of feasibility. (xopt/generators/bayesian/bayesian_generator.py)_get_log_feasibilitymethod to calculate feasibility constraints for optimization. (xopt/generators/bayesian/bayesian_generator.py)propose_candidatesto include nonlinear inequality constraints whenfeasibility_toleranceis set. (xopt/generators/bayesian/bayesian_generator.py)Testing Improvements:
LBFGSOptimizerandGridOptimizerto validate handling of nonlinear constraints, including edge cases where no feasible points exist. (xopt/tests/test_numerical_optimizer.py) [1] [2]xopt/tests/test_numerical_optimizer.py)