ravimohan19 commited on
Commit
95c4c89
·
verified ·
1 Parent(s): 0342a1d

Upload optimizers/bofire_optimizer.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. optimizers/bofire_optimizer.py +191 -0
optimizers/bofire_optimizer.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """BoFire optimizer backend for physics-informed BO."""
2
+
3
+ from typing import Callable, Dict, List, Optional, Tuple
4
+
5
+ import torch
6
+ from torch import Tensor
7
+
8
+ from physics_informed_bo.config import OptimizationConfig
9
+ from physics_informed_bo.optimizers.base_optimizer import BaseOptimizer
10
+
11
+
12
+ class BoFireOptimizer(BaseOptimizer):
13
+ """BoFire backend for chemistry/materials-focused Bayesian optimization.
14
+
15
+ BoFire is designed for real-world experimental design in chemistry
16
+ and materials science. It supports:
17
+ - Complex parameter spaces (continuous, categorical, molecular)
18
+ - Mixture constraints (sum-to-one)
19
+ - Multi-objective optimization with Pareto fronts
20
+ - Integration with domain-specific descriptors
21
+
22
+ The physics model is incorporated as a prior mean function in BoFire's
23
+ surrogate model specification.
24
+ """
25
+
26
+ def __init__(self, config: OptimizationConfig):
27
+ super().__init__(config)
28
+ self._domain = None
29
+ self._strategy = None
30
+ self._experiments_df = None
31
+
32
+ def setup_domain(
33
+ self,
34
+ parameters: Dict[str, Dict],
35
+ objectives: Dict[str, Dict],
36
+ constraints: Optional[List[Dict]] = None,
37
+ ) -> None:
38
+ """Set up a BoFire domain with physics-informed features.
39
+
40
+ Args:
41
+ parameters: Dict of parameter specifications.
42
+ Example: {"temp": {"type": "continuous", "bounds": (300, 500)}}
43
+ objectives: Dict of objective specifications.
44
+ Example: {"yield": {"type": "maximize", "weight": 1.0}}
45
+ constraints: Optional list of constraint specifications.
46
+ Example: [{"type": "linear", "features": ["x1", "x2"], "coeffs": [1, 1], "rhs": 1}]
47
+ """
48
+ try:
49
+ from bofire.data_models.domain.api import Domain, Inputs, Outputs
50
+ from bofire.data_models.features.api import (
51
+ ContinuousInput,
52
+ ContinuousOutput,
53
+ CategoricalInput,
54
+ )
55
+ from bofire.data_models.objectives.api import MaximizeObjective, MinimizeObjective
56
+ from bofire.data_models.constraints.api import (
57
+ LinearInequalityConstraint,
58
+ LinearEqualityConstraint,
59
+ )
60
+ except ImportError:
61
+ raise ImportError(
62
+ "BoFire is required for BoFireOptimizer. "
63
+ "Install with: pip install bofire"
64
+ )
65
+
66
+ # Build input features
67
+ input_features = []
68
+ self._feature_names = []
69
+
70
+ for name, spec in parameters.items():
71
+ self._feature_names.append(name)
72
+ if spec["type"] == "continuous":
73
+ lb, ub = spec["bounds"]
74
+ input_features.append(
75
+ ContinuousInput(key=name, bounds=(float(lb), float(ub)))
76
+ )
77
+ elif spec["type"] == "categorical":
78
+ input_features.append(
79
+ CategoricalInput(key=name, categories=spec["categories"])
80
+ )
81
+
82
+ # Build output features
83
+ output_features = []
84
+ for name, spec in objectives.items():
85
+ if spec.get("type", "maximize") == "maximize":
86
+ obj = MaximizeObjective(w=spec.get("weight", 1.0))
87
+ else:
88
+ obj = MinimizeObjective(w=spec.get("weight", 1.0))
89
+ output_features.append(ContinuousOutput(key=name, objective=obj))
90
+
91
+ # Build constraints
92
+ bofire_constraints = []
93
+ if constraints:
94
+ for c in constraints:
95
+ if c["type"] == "linear_inequality":
96
+ bofire_constraints.append(
97
+ LinearInequalityConstraint(
98
+ features=c["features"],
99
+ coefficients=c["coeffs"],
100
+ rhs=c["rhs"],
101
+ )
102
+ )
103
+ elif c["type"] == "linear_equality":
104
+ bofire_constraints.append(
105
+ LinearEqualityConstraint(
106
+ features=c["features"],
107
+ coefficients=c["coeffs"],
108
+ rhs=c["rhs"],
109
+ )
110
+ )
111
+
112
+ self._domain = Domain(
113
+ inputs=Inputs(features=input_features),
114
+ outputs=Outputs(features=output_features),
115
+ constraints=bofire_constraints if bofire_constraints else None,
116
+ )
117
+
118
+ def setup_strategy(self, strategy_type: str = "sobo") -> None:
119
+ """Set up the BoFire optimization strategy.
120
+
121
+ Args:
122
+ strategy_type: One of 'sobo' (single-objective), 'mobo' (multi-objective),
123
+ 'qehvi' (q-Expected Hypervolume Improvement).
124
+ """
125
+ try:
126
+ from bofire.data_models.strategies.api import SoboStrategy, QehviStrategy
127
+ from bofire.data_models.acquisition_functions.api import qEI, qNEI
128
+ import bofire.strategies.api as strategies
129
+ except ImportError:
130
+ raise ImportError("BoFire is required. Install with: pip install bofire")
131
+
132
+ if self._domain is None:
133
+ raise RuntimeError("Call setup_domain() before setup_strategy().")
134
+
135
+ if strategy_type == "sobo":
136
+ strategy_data = SoboStrategy(domain=self._domain, acquisition_function=qEI())
137
+ elif strategy_type in ("mobo", "qehvi"):
138
+ strategy_data = QehviStrategy(domain=self._domain)
139
+ else:
140
+ raise ValueError(f"Unsupported strategy type: {strategy_type}")
141
+
142
+ self._strategy = strategies.map(strategy_data)
143
+
144
+ def suggest(
145
+ self,
146
+ n_candidates: int = 1,
147
+ X_observed: Optional[Tensor] = None,
148
+ y_observed: Optional[Tensor] = None,
149
+ ) -> Tensor:
150
+ """Suggest next experiments using BoFire."""
151
+ if self._strategy is None:
152
+ raise RuntimeError("Call setup_domain() and setup_strategy() first.")
153
+
154
+ import pandas as pd
155
+
156
+ # Tell strategy about existing experiments
157
+ if self._experiments_df is not None:
158
+ self._strategy.tell(self._experiments_df)
159
+
160
+ candidates_df = self._strategy.ask(n_candidates)
161
+ candidates = torch.tensor(
162
+ candidates_df[self._feature_names].values, dtype=torch.float64
163
+ )
164
+
165
+ # Filter through physics constraints
166
+ candidates = self._filter_feasible(candidates)
167
+ return candidates[:n_candidates]
168
+
169
+ def update(self, X_new: Tensor, y_new: Tensor) -> None:
170
+ """Update BoFire with new observations."""
171
+ import pandas as pd
172
+
173
+ data = {}
174
+ for i, name in enumerate(self._feature_names):
175
+ data[name] = X_new[:, i].numpy()
176
+
177
+ # Assume single objective for now
178
+ output_keys = [f.key for f in self._domain.outputs.features]
179
+ for i, key in enumerate(output_keys):
180
+ if y_new.dim() > 1 and y_new.shape[1] > i:
181
+ data[key] = y_new[:, i].numpy()
182
+ else:
183
+ data[key] = y_new.squeeze().numpy()
184
+
185
+ new_df = pd.DataFrame(data)
186
+ if self._experiments_df is None:
187
+ self._experiments_df = new_df
188
+ else:
189
+ self._experiments_df = pd.concat(
190
+ [self._experiments_df, new_df], ignore_index=True
191
+ )