ravimohan19 commited on
Commit
88d6cd1
·
verified ·
1 Parent(s): 95c4c89

Upload optimizers/botorch_optimizer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. optimizers/botorch_optimizer.py +156 -0
optimizers/botorch_optimizer.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """BoTorch-based optimizer backend."""
2
+
3
+ from typing import Dict, List, Optional, Tuple
4
+
5
+ import torch
6
+ from torch import Tensor
7
+
8
+ from botorch.acquisition import (
9
+ ExpectedImprovement,
10
+ UpperConfidenceBound,
11
+ ProbabilityOfImprovement,
12
+ qExpectedImprovement,
13
+ qNoisyExpectedImprovement,
14
+ qKnowledgeGradient,
15
+ )
16
+ from botorch.optim import optimize_acqf
17
+ from botorch.utils.transforms import standardize, normalize, unnormalize
18
+
19
+ from physics_informed_bo.config import AcquisitionType, OptimizationConfig
20
+ from physics_informed_bo.optimizers.base_optimizer import BaseOptimizer
21
+
22
+
23
+ class PhysicsInformedEI(ExpectedImprovement):
24
+ """Custom acquisition that penalizes physically implausible regions.
25
+
26
+ Multiplies standard EI by a feasibility probability derived from
27
+ physics constraints, steering the search toward physically valid regions.
28
+ """
29
+
30
+ def __init__(self, model, best_f, physics_prior=None, penalty_weight=10.0, **kwargs):
31
+ super().__init__(model=model, best_f=best_f, **kwargs)
32
+ self.physics_prior = physics_prior
33
+ self.penalty_weight = penalty_weight
34
+
35
+ def forward(self, X: Tensor) -> Tensor:
36
+ ei = super().forward(X)
37
+
38
+ if self.physics_prior is not None:
39
+ # Compute constraint violation penalty
40
+ X_2d = X.squeeze(1) if X.dim() == 3 else X
41
+ violation = self.physics_prior.constraint_violation(X_2d)
42
+ feasibility = torch.exp(-self.penalty_weight * violation)
43
+ ei = ei * feasibility
44
+
45
+ return ei
46
+
47
+
48
+ class BoTorchOptimizer(BaseOptimizer):
49
+ """BoTorch-based Bayesian optimization backend.
50
+
51
+ Supports standard and physics-informed acquisition functions,
52
+ batch optimization, and constrained optimization.
53
+ """
54
+
55
+ def __init__(self, config: OptimizationConfig):
56
+ super().__init__(config)
57
+ self._acq_function = None
58
+ self._best_f = None
59
+
60
+ def _get_acquisition_function(self, model, best_f: float):
61
+ """Build the acquisition function based on config."""
62
+ acq_type = self.config.acquisition_type
63
+
64
+ if acq_type == AcquisitionType.EXPECTED_IMPROVEMENT:
65
+ return ExpectedImprovement(model=model, best_f=best_f)
66
+
67
+ elif acq_type == AcquisitionType.UPPER_CONFIDENCE_BOUND:
68
+ return UpperConfidenceBound(model=model, beta=2.0)
69
+
70
+ elif acq_type == AcquisitionType.PROBABILITY_OF_IMPROVEMENT:
71
+ return ProbabilityOfImprovement(model=model, best_f=best_f)
72
+
73
+ elif acq_type == AcquisitionType.NOISY_EXPECTED_IMPROVEMENT:
74
+ return qNoisyExpectedImprovement(
75
+ model=model,
76
+ X_baseline=self._X_observed,
77
+ )
78
+
79
+ elif acq_type == AcquisitionType.KNOWLEDGE_GRADIENT:
80
+ return qKnowledgeGradient(model=model, num_fantasies=8)
81
+
82
+ elif acq_type == AcquisitionType.PHYSICS_INFORMED_EI:
83
+ return PhysicsInformedEI(
84
+ model=model,
85
+ best_f=best_f,
86
+ physics_prior=self._physics_prior,
87
+ penalty_weight=self.config.physics_constraint_penalty,
88
+ )
89
+
90
+ else:
91
+ raise ValueError(f"Unsupported acquisition type: {acq_type}")
92
+
93
+ def suggest(
94
+ self,
95
+ n_candidates: int = 1,
96
+ X_observed: Optional[Tensor] = None,
97
+ y_observed: Optional[Tensor] = None,
98
+ ) -> Tensor:
99
+ """Suggest next experiments using BoTorch optimization."""
100
+ if self._surrogate is None:
101
+ raise RuntimeError("Surrogate model not set. Call set_surrogate() first.")
102
+ if self._bounds is None:
103
+ raise RuntimeError("Bounds not set. Call set_bounds() first.")
104
+
105
+ self._X_observed = X_observed
106
+ model = self._surrogate.model
107
+ best_f = float(y_observed.max()) if y_observed is not None else 0.0
108
+ self._best_f = best_f
109
+
110
+ acq_function = self._get_acquisition_function(model, best_f)
111
+
112
+ # Optimize the acquisition function
113
+ candidates, acq_value = optimize_acqf(
114
+ acq_function=acq_function,
115
+ bounds=self._bounds,
116
+ q=n_candidates,
117
+ num_restarts=10,
118
+ raw_samples=256,
119
+ )
120
+
121
+ # Filter through physics constraints
122
+ candidates = self._filter_feasible(candidates)
123
+
124
+ return candidates[:n_candidates]
125
+
126
+ def update(self, X_new: Tensor, y_new: Tensor) -> None:
127
+ """Update is handled by re-fitting the surrogate externally."""
128
+ pass
129
+
130
+ def suggest_batch(
131
+ self,
132
+ batch_size: int,
133
+ X_observed: Tensor,
134
+ y_observed: Tensor,
135
+ sequential: bool = True,
136
+ ) -> Tensor:
137
+ """Suggest a batch of experiments.
138
+
139
+ Args:
140
+ batch_size: Number of experiments to suggest.
141
+ X_observed: All observed inputs.
142
+ y_observed: All observed outputs.
143
+ sequential: If True, use sequential greedy optimization.
144
+ If False, use joint q-batch optimization.
145
+
146
+ Returns:
147
+ Tensor of shape (batch_size, d).
148
+ """
149
+ if sequential:
150
+ candidates = []
151
+ for _ in range(batch_size):
152
+ c = self.suggest(1, X_observed, y_observed)
153
+ candidates.append(c)
154
+ return torch.cat(candidates, dim=0)
155
+ else:
156
+ return self.suggest(batch_size, X_observed, y_observed)