diff --git a/xopt/generators/bayesian/bayesian_generator.py b/xopt/generators/bayesian/bayesian_generator.py index 2d29a754..f9ced1f4 100644 --- a/xopt/generators/bayesian/bayesian_generator.py +++ b/xopt/generators/bayesian/bayesian_generator.py @@ -411,9 +411,12 @@ def generate(self, n_candidates: int): # dict to track runtimes timing_results = {} + training_data = self.get_training_data(self.data) + self._validate_contextual_variables_no_nan(training_data) + # update internal model with internal data start_time = time.perf_counter() - model = self.train_model(self.get_training_data(self.data)) + model = self.train_model(training_data) timing_results["training"] = time.perf_counter() - start_time # propose candidates given model @@ -935,6 +938,20 @@ def get_model_input_bounds(self, data: pd.DataFrame) -> Dict[str, List[float]]: return variable_bounds + def _validate_contextual_variables_no_nan(self, data: pd.DataFrame): + """check to make sure that the last row of data does not contain NaN in any of the contextual variable columns""" + last_row = data.iloc[-1] + contextual_with_nan = [ + name + for name in self.contextual_variables + if name in data.columns and pd.isna(last_row[name]) + ] + if contextual_with_nan: + raise ValueError( + "latest row contains NaN in contextual variable columns: " + + ", ".join(contextual_with_nan) + ) + @property def _candidate_names(self): """variable names corresponding to generated candidates""" diff --git a/xopt/tests/generators/bayesian/test_contextual_bo.py b/xopt/tests/generators/bayesian/test_contextual_bo.py index ba98f994..8fae5297 100644 --- a/xopt/tests/generators/bayesian/test_contextual_bo.py +++ b/xopt/tests/generators/bayesian/test_contextual_bo.py @@ -137,6 +137,23 @@ def test_contextual_variable_bounds_from_data(self): assert np.isclose(bounds["x2"][0], 0.3 - padding) assert np.isclose(bounds["x2"][1], 0.7 + padding) + def test_contextual_variable_nan_in_last_row_raises(self): + generator = UpperConfidenceBoundGenerator(vocs=self.vocs) + data = pd.DataFrame( + { + "x1": np.linspace(0.0, 1.0, 5), + "x2": np.array([0.3, 0.4, 0.5, 0.6, np.nan]), + "y": np.ones(5), + } + ) + generator.add_data(data) + + with pytest.raises( + ValueError, + match="latest row contains NaN in contextual variable columns", + ): + generator.generate(1) + def test_contextual_variable_explicit_domain_overrides_data(self): vocs = VOCS( variables={"x1": [0, 1], "x2": ContextualVariable(domain=[0.2, 0.4])},