From b65c35b389cb4fa6333ad43e03080598067f4b14 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 17:57:10 +0200 Subject: [PATCH 1/8] Change the return type annotation of BaseEmulator.predict_values() to tuple[np.array, np.array] from np.array. Applied formatting script --- swiftemulator/emulators/base.py | 2 +- swiftemulator/emulators/gaussian_process.py | 2 +- .../emulators/gaussian_process_bins.py | 2 +- .../emulators/gaussian_process_mcmc.py | 2 +- .../emulators/gaussian_process_one_dim.py | 4 +++- swiftemulator/emulators/linear_model.py | 2 +- .../emulators/multi_gaussian_process.py | 18 +++++++++++------- swiftemulator/io/swift.py | 4 ++-- 8 files changed, 21 insertions(+), 15 deletions(-) diff --git a/swiftemulator/emulators/base.py b/swiftemulator/emulators/base.py index df2cbbc..6b1e4c8 100644 --- a/swiftemulator/emulators/base.py +++ b/swiftemulator/emulators/base.py @@ -78,7 +78,7 @@ def fit_model( def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process.py b/swiftemulator/emulators/gaussian_process.py index 3db612e..7d40183 100644 --- a/swiftemulator/emulators/gaussian_process.py +++ b/swiftemulator/emulators/gaussian_process.py @@ -229,7 +229,7 @@ def grad_negative_log_likelihood(p): def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_bins.py b/swiftemulator/emulators/gaussian_process_bins.py index 90380ef..90e4110 100644 --- a/swiftemulator/emulators/gaussian_process_bins.py +++ b/swiftemulator/emulators/gaussian_process_bins.py @@ -267,7 +267,7 @@ def grad_negative_log_likelihood(p): def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_mcmc.py b/swiftemulator/emulators/gaussian_process_mcmc.py index 82a3891..b0609a7 100644 --- a/swiftemulator/emulators/gaussian_process_mcmc.py +++ b/swiftemulator/emulators/gaussian_process_mcmc.py @@ -339,7 +339,7 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float], - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_one_dim.py b/swiftemulator/emulators/gaussian_process_one_dim.py index 439ddbf..8f5a9b2 100644 --- a/swiftemulator/emulators/gaussian_process_one_dim.py +++ b/swiftemulator/emulators/gaussian_process_one_dim.py @@ -226,7 +226,9 @@ def grad_negative_log_likelihood(p): return - def predict_values(self, model_parameters: Dict[str, float]) -> np.array: + def predict_values( + self, model_parameters: Dict[str, float] + ) -> tuple[np.array, np.array]: """ Predict a value from the trained emulator contained within this object. returns the value at the input model parameters. diff --git a/swiftemulator/emulators/linear_model.py b/swiftemulator/emulators/linear_model.py index 671e409..94c5507 100644 --- a/swiftemulator/emulators/linear_model.py +++ b/swiftemulator/emulators/linear_model.py @@ -164,7 +164,7 @@ def fit_model( def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/multi_gaussian_process.py b/swiftemulator/emulators/multi_gaussian_process.py index 2b5f45e..764ff13 100644 --- a/swiftemulator/emulators/multi_gaussian_process.py +++ b/swiftemulator/emulators/multi_gaussian_process.py @@ -182,7 +182,7 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float], - ) -> np.array: + ) -> tuple[np.array, np.array]: """ Predict values from the trained emulator contained within this object. @@ -231,12 +231,16 @@ def predict_values( for index, (low, high) in enumerate(self.independent_regions): mask = np.logical_and( - independent > low - if low is not None - else np.ones_like(independent).astype(bool), - independent < high - if high is not None - else np.ones_like(independent).astype(bool), + ( + independent > low + if low is not None + else np.ones_like(independent).astype(bool) + ), + ( + independent < high + if high is not None + else np.ones_like(independent).astype(bool) + ), ) predicted, errors = self.emulators[index].predict_values( diff --git a/swiftemulator/io/swift.py b/swiftemulator/io/swift.py index e9b3247..8429525 100644 --- a/swiftemulator/io/swift.py +++ b/swiftemulator/io/swift.py @@ -103,8 +103,8 @@ def load_pipeline_outputs( "adaptive_mass_function", "histogram", ] - recursive_search = ( - lambda d, k: d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None + recursive_search = lambda d, k: ( + d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None ) line_search = lambda d: recursive_search(d, line_types) From f94c04f2f0050df70f92f90780ea7a4d9d179f9d Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:05:06 +0200 Subject: [PATCH 2/8] Add the method predict_values_no_error() to the emulator object to provide predictions without the associated errors, which can be a lot faster. --- swiftemulator/emulators/base.py | 49 +++++++++++++++++- swiftemulator/emulators/gaussian_process.py | 57 ++++++++++++++++++++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/swiftemulator/emulators/base.py b/swiftemulator/emulators/base.py index 6b1e4c8..1950128 100644 --- a/swiftemulator/emulators/base.py +++ b/swiftemulator/emulators/base.py @@ -80,7 +80,8 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float] ) -> tuple[np.array, np.array]: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator contained + within this object. Parameters ---------- @@ -123,3 +124,49 @@ def predict_values( ) raise NotImplementedError + + def predict_values_no_error( + self, independent: np.array, model_parameters: Dict[str, float] + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. If the emulator is discrete, these are only allowed to be + the discrete independent variables that the emulator was trained at + (disregarding the additional 'independent' model parameters, below.) + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input ``model_parameters``. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction, or when attempting to evaluate the model at + disallowed independent variables. + """ + + if self.emulator is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + raise NotImplementedError diff --git a/swiftemulator/emulators/gaussian_process.py b/swiftemulator/emulators/gaussian_process.py index 7d40183..8ee1fcc 100644 --- a/swiftemulator/emulators/gaussian_process.py +++ b/swiftemulator/emulators/gaussian_process.py @@ -231,7 +231,8 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float] ) -> tuple[np.array, np.array]: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator + contained within this object. Parameters ---------- @@ -253,7 +254,7 @@ def predict_values( of the input model_parameters. dependent_prediction_errors, np.array - Errors on the model predictions. + Errors (variances) on the model predictions. """ if self.emulator is None: @@ -279,3 +280,55 @@ def predict_values( ) return model, errors + + def predict_values_no_error( + self, independent: np.array, model_parameters: Dict[str, float] + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input model_parameters. + """ + + if self.emulator is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + model_parameter_array = np.array( + [model_parameters[parameter] for parameter in self.parameter_order] + ) + + t = np.empty( + (len(independent), len(model_parameter_array) + 1), dtype=np.float32 + ) + + for line, value in enumerate(independent): + t[line][0] = value + t[line][1:] = model_parameter_array + + model = self.emulator.predict( + y=self.dependent_variables, t=t, return_cov=False, return_var=False + ) + + return model From 116affcd221e13ff28d2c8a7e3333dae8c9bcbf3 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:08:59 +0200 Subject: [PATCH 3/8] Revert "Change the return type annotation of BaseEmulator.predict_values() to tuple[np.array, np.array] from np.array. Applied formatting script" This reverts commit b65c35b389cb4fa6333ad43e03080598067f4b14. --- swiftemulator/emulators/base.py | 2 +- swiftemulator/emulators/gaussian_process.py | 2 +- .../emulators/gaussian_process_bins.py | 2 +- .../emulators/gaussian_process_mcmc.py | 2 +- .../emulators/gaussian_process_one_dim.py | 4 +--- swiftemulator/emulators/linear_model.py | 2 +- .../emulators/multi_gaussian_process.py | 18 +++++++----------- swiftemulator/io/swift.py | 4 ++-- 8 files changed, 15 insertions(+), 21 deletions(-) diff --git a/swiftemulator/emulators/base.py b/swiftemulator/emulators/base.py index 1950128..f5666ff 100644 --- a/swiftemulator/emulators/base.py +++ b/swiftemulator/emulators/base.py @@ -78,7 +78,7 @@ def fit_model( def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values and the associated variance from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process.py b/swiftemulator/emulators/gaussian_process.py index 8ee1fcc..3e2689d 100644 --- a/swiftemulator/emulators/gaussian_process.py +++ b/swiftemulator/emulators/gaussian_process.py @@ -229,7 +229,7 @@ def grad_negative_log_likelihood(p): def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values and the associated variance from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_bins.py b/swiftemulator/emulators/gaussian_process_bins.py index 90e4110..90380ef 100644 --- a/swiftemulator/emulators/gaussian_process_bins.py +++ b/swiftemulator/emulators/gaussian_process_bins.py @@ -267,7 +267,7 @@ def grad_negative_log_likelihood(p): def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_mcmc.py b/swiftemulator/emulators/gaussian_process_mcmc.py index b0609a7..82a3891 100644 --- a/swiftemulator/emulators/gaussian_process_mcmc.py +++ b/swiftemulator/emulators/gaussian_process_mcmc.py @@ -339,7 +339,7 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float], - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/gaussian_process_one_dim.py b/swiftemulator/emulators/gaussian_process_one_dim.py index 8f5a9b2..439ddbf 100644 --- a/swiftemulator/emulators/gaussian_process_one_dim.py +++ b/swiftemulator/emulators/gaussian_process_one_dim.py @@ -226,9 +226,7 @@ def grad_negative_log_likelihood(p): return - def predict_values( - self, model_parameters: Dict[str, float] - ) -> tuple[np.array, np.array]: + def predict_values(self, model_parameters: Dict[str, float]) -> np.array: """ Predict a value from the trained emulator contained within this object. returns the value at the input model parameters. diff --git a/swiftemulator/emulators/linear_model.py b/swiftemulator/emulators/linear_model.py index 94c5507..671e409 100644 --- a/swiftemulator/emulators/linear_model.py +++ b/swiftemulator/emulators/linear_model.py @@ -164,7 +164,7 @@ def fit_model( def predict_values( self, independent: np.array, model_parameters: Dict[str, float] - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values from the trained emulator contained within this object. diff --git a/swiftemulator/emulators/multi_gaussian_process.py b/swiftemulator/emulators/multi_gaussian_process.py index 764ff13..2b5f45e 100644 --- a/swiftemulator/emulators/multi_gaussian_process.py +++ b/swiftemulator/emulators/multi_gaussian_process.py @@ -182,7 +182,7 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float], - ) -> tuple[np.array, np.array]: + ) -> np.array: """ Predict values from the trained emulator contained within this object. @@ -231,16 +231,12 @@ def predict_values( for index, (low, high) in enumerate(self.independent_regions): mask = np.logical_and( - ( - independent > low - if low is not None - else np.ones_like(independent).astype(bool) - ), - ( - independent < high - if high is not None - else np.ones_like(independent).astype(bool) - ), + independent > low + if low is not None + else np.ones_like(independent).astype(bool), + independent < high + if high is not None + else np.ones_like(independent).astype(bool), ) predicted, errors = self.emulators[index].predict_values( diff --git a/swiftemulator/io/swift.py b/swiftemulator/io/swift.py index 8429525..e9b3247 100644 --- a/swiftemulator/io/swift.py +++ b/swiftemulator/io/swift.py @@ -103,8 +103,8 @@ def load_pipeline_outputs( "adaptive_mass_function", "histogram", ] - recursive_search = lambda d, k: ( - d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None + recursive_search = ( + lambda d, k: d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None ) line_search = lambda d: recursive_search(d, line_types) From 3230d1dc70358876380d5b5f482b242cf109687b Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:09:28 +0200 Subject: [PATCH 4/8] Applied formatting tool --- .../emulators/multi_gaussian_process.py | 16 ++++++++++------ swiftemulator/io/swift.py | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/swiftemulator/emulators/multi_gaussian_process.py b/swiftemulator/emulators/multi_gaussian_process.py index 2b5f45e..3802a4f 100644 --- a/swiftemulator/emulators/multi_gaussian_process.py +++ b/swiftemulator/emulators/multi_gaussian_process.py @@ -231,12 +231,16 @@ def predict_values( for index, (low, high) in enumerate(self.independent_regions): mask = np.logical_and( - independent > low - if low is not None - else np.ones_like(independent).astype(bool), - independent < high - if high is not None - else np.ones_like(independent).astype(bool), + ( + independent > low + if low is not None + else np.ones_like(independent).astype(bool) + ), + ( + independent < high + if high is not None + else np.ones_like(independent).astype(bool) + ), ) predicted, errors = self.emulators[index].predict_values( diff --git a/swiftemulator/io/swift.py b/swiftemulator/io/swift.py index e9b3247..8429525 100644 --- a/swiftemulator/io/swift.py +++ b/swiftemulator/io/swift.py @@ -103,8 +103,8 @@ def load_pipeline_outputs( "adaptive_mass_function", "histogram", ] - recursive_search = ( - lambda d, k: d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None + recursive_search = lambda d, k: ( + d.get(k[0], recursive_search(d, k[1:])) if len(k) > 0 else None ) line_search = lambda d: recursive_search(d, line_types) From 92407fdee3ad1edce16c4c19a1dfc0809ca375b4 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:30:27 +0200 Subject: [PATCH 5/8] Add the same new method to all the other emulator specialisations. Update the docstring of the regular predict_values() function too. --- swiftemulator/emulators/gaussian_process.py | 14 ++ .../emulators/gaussian_process_bins.py | 97 +++++++++++- .../emulators/gaussian_process_mcmc.py | 85 +++++++++- .../emulators/gaussian_process_one_dim.py | 63 +++++++- swiftemulator/emulators/linear_model.py | 67 +++++++- .../emulators/multi_gaussian_process.py | 148 +++++++++++++++++- 6 files changed, 467 insertions(+), 7 deletions(-) diff --git a/swiftemulator/emulators/gaussian_process.py b/swiftemulator/emulators/gaussian_process.py index 3e2689d..b9a1e2c 100644 --- a/swiftemulator/emulators/gaussian_process.py +++ b/swiftemulator/emulators/gaussian_process.py @@ -255,6 +255,13 @@ def predict_values( dependent_prediction_errors, np.array Errors (variances) on the model predictions. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. """ if self.emulator is None: @@ -307,6 +314,13 @@ def predict_values_no_error( Array of predictions, if the emulator is a function f, these are the predicted values of f(independent) evaluted at the position of the input model_parameters. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. """ if self.emulator is None: diff --git a/swiftemulator/emulators/gaussian_process_bins.py b/swiftemulator/emulators/gaussian_process_bins.py index 90380ef..6bfcd3a 100644 --- a/swiftemulator/emulators/gaussian_process_bins.py +++ b/swiftemulator/emulators/gaussian_process_bins.py @@ -269,7 +269,8 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float] ) -> np.array: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator contained + within this object. Parameters ---------- @@ -365,3 +366,97 @@ def predict_values( np.array(dependent_predictions), np.array(dependent_prediction_errors), ) + + def predict_values_no_error( + self, independent: np.array, model_parameters: Dict[str, float] + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. If the emulator is discrete, these are only allowed to be + the discrete independent variables that the emulator was trained at + (disregarding the additional 'independent' model parameters, below). + These can be found in this object in the ``bin_centers`` attribute. + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input ``model_parameters``. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction, or when attempting to evaluate the model at + disallowed independent variables. + """ + + if self.bin_gaussian_process is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + # First calculate which indices in bin_centers (and hence + # bin_gaussian_processes) correspond to the requested ``independent`` + # variables. + + array_centers = np.array(self.bin_centers) + gpe_ordering = [] + + for requested_independent_variable in independent: + try: + gpe_ordering.append( + np.where(array_centers == requested_independent_variable)[0][0] + ) + except IndexError: + raise AttributeError( + f"Requested independent variable {independent} not valid, ", + f"this instance of GPE Bins is only valid at {array_centers}.", + ) + + model_parameter_array = np.array( + [model_parameters[parameter] for parameter in self.parameter_order] + ) + + # George must predict a value for more than one point at a time, so + # generate two fake points either side of the one of interest. + model_parameter_array_sample = np.append( + 0.98 * model_parameter_array, model_parameter_array + ) + model_parameter_array_sample = np.append( + model_parameter_array_sample, 1.02 * model_parameter_array + ).reshape(3, len(model_parameter_array)) + + dependent_predictions = [] + + for emulator_index in gpe_ordering: + gp = self.bin_gaussian_process[emulator_index] + model_values = self.bin_model_values[emulator_index] + + model = gp.predict( + y=model_values["dependent"], + t=model_parameter_array_sample, + return_cov=False, + return_var=False, + ) + + # Remove fake points required to ensure george returns a prediction. + dependent_predictions.append(model[1]) + + return np.array(dependent_predictions) diff --git a/swiftemulator/emulators/gaussian_process_mcmc.py b/swiftemulator/emulators/gaussian_process_mcmc.py index 82a3891..736e566 100644 --- a/swiftemulator/emulators/gaussian_process_mcmc.py +++ b/swiftemulator/emulators/gaussian_process_mcmc.py @@ -341,7 +341,8 @@ def predict_values( model_parameters: Dict[str, float], ) -> np.array: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator contained + within this object. Parameters ---------- @@ -364,6 +365,16 @@ def predict_values( dependent_prediction_errors, np.array Variance on the model predictions. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. + + ValueError + When the number of subsamples is larger than the number of samples. """ if self.emulator is None: @@ -420,3 +431,75 @@ def predict_values( variance += hyper_variance return model, variance + + def predict_values_no_error( + self, + independent: np.array, + model_parameters: Dict[str, float], + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input model_parameters. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. + + ValueError + When the number of subsamples is larger than the number of samples. + """ + + if self.emulator is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + if ( + len(self.hyperparameter_samples[:, 0]) < self.samples_for_error + and self.use_hyperparameter_error + ): + raise ValueError( + "Number of subsamples must be less then the total number of samples" + ) + + model_parameter_array = np.array( + [model_parameters[parameter] for parameter in self.parameter_order] + ) + + t = np.empty( + (len(independent), len(model_parameter_array) + 1), dtype=np.float32 + ) + + for line, value in enumerate(independent): + t[line][0] = value + t[line][1:] = model_parameter_array + + model = self.emulator.predict( + y=self.dependent_variables, t=t, return_cov=False, return_var=False + ) + + return model diff --git a/swiftemulator/emulators/gaussian_process_one_dim.py b/swiftemulator/emulators/gaussian_process_one_dim.py index 439ddbf..1defae7 100644 --- a/swiftemulator/emulators/gaussian_process_one_dim.py +++ b/swiftemulator/emulators/gaussian_process_one_dim.py @@ -228,8 +228,8 @@ def grad_negative_log_likelihood(p): def predict_values(self, model_parameters: Dict[str, float]) -> np.array: """ - Predict a value from the trained emulator contained within this object. - returns the value at the input model parameters. + Predict a value and associated variance from the trained emulator contained within + this object. Returns the value at the input model parameters. Parameters ---------- @@ -247,7 +247,14 @@ def predict_values(self, model_parameters: Dict[str, float]) -> np.array: of the input model_parameters. dependent_prediction_error, float - Error on the model prediction. + Error (variance) on the model prediction. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. """ if self.emulator is None: @@ -271,3 +278,53 @@ def predict_values(self, model_parameters: Dict[str, float]) -> np.array: ) return model[0], errors[0] + + def predict_values_no_error(self, model_parameters: Dict[str, float]) -> np.array: + """ + Predict a value and associated variance from the trained emulator contained within + this object. Returns the value at the input model parameters. + + Parameters + ---------- + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_prediction, float + Value of predictions, if the emulator is a function f, this + is the predicted value of f(independent) evaluted at the position + of the input model_parameters. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. + """ + + if self.emulator is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + model_parameter_array = np.array( + [model_parameters[parameter] for parameter in self.parameter_order] + ) + + # Create a fake duplicate as george always needs two points to predict + t = np.empty((2, len(model_parameter_array)), dtype=np.float32) + + t[0] = model_parameter_array + t[1] = model_parameter_array + + model = self.emulator.predict( + y=self.dependent_variables, t=t, return_cov=False, return_var=False + ) + + return model[0] diff --git a/swiftemulator/emulators/linear_model.py b/swiftemulator/emulators/linear_model.py index 671e409..eafe28b 100644 --- a/swiftemulator/emulators/linear_model.py +++ b/swiftemulator/emulators/linear_model.py @@ -166,7 +166,8 @@ def predict_values( self, independent: np.array, model_parameters: Dict[str, float] ) -> np.array: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator contained + within this object. Parameters ---------- @@ -190,6 +191,13 @@ def predict_values( dependent_prediction_errors, np.array Errors on the model predictions. For the linear model these are all zeroes, as the errors are unconstrained. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. """ if self.emulator is None: @@ -213,3 +221,60 @@ def predict_values( model = self.emulator.predict(X=t) return model, np.zeros_like(model) + + def predict_values_no_error( + self, independent: np.array, model_parameters: Dict[str, float] + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input model_parameters. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction. + """ + + if self.emulator is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + model_parameter_array = np.array( + [model_parameters[parameter] for parameter in self.parameter_order] + ) + + t = np.empty( + (len(independent), len(model_parameter_array) + 1), dtype=np.float32 + ) + + for line, value in enumerate(independent): + t[line][0] = value + t[line][1:] = model_parameter_array + + model = self.emulator.predict(X=t) + + return model diff --git a/swiftemulator/emulators/multi_gaussian_process.py b/swiftemulator/emulators/multi_gaussian_process.py index 3802a4f..1a93d2c 100644 --- a/swiftemulator/emulators/multi_gaussian_process.py +++ b/swiftemulator/emulators/multi_gaussian_process.py @@ -184,7 +184,8 @@ def predict_values( model_parameters: Dict[str, float], ) -> np.array: """ - Predict values from the trained emulator contained within this object. + Predict values and the associated variance from the trained emulator contained + within this object. Parameters ---------- @@ -208,6 +209,14 @@ def predict_values( dependent_prediction_errors, np.array Errors on the model predictions. + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction, or when attempting to evaluate the model at + disallowed independent variables. + Notes ----- @@ -320,3 +329,140 @@ def predict_values( ] return dependent_predictions, dependent_prediction_errors + + def predict_values_no_error( + self, + independent: np.array, + model_parameters: Dict[str, float], + ) -> np.array: + """ + Predict values from the trained emulator contained within this object. + In cases where the error estimates are not required, this method is + significantly faster than predict_values(). + + Parameters + ---------- + + independent, np.array + Independent continuous variables to evaluate the emulator + at. + + model_parameters: Dict[str, float] + The point in model parameter space to create predicted + values at. + + Returns + ------- + + dependent_predictions, np.array + Array of predictions, if the emulator is a function f, these + are the predicted values of f(independent) evaluted at the position + of the input model_parameters. + + Raises + ------ + + AttributeError + When the model has not been trained before trying to make a + prediction, or when attempting to evaluate the model at + disallowed independent variables. + + Notes + ----- + + This will use the originally defined regions and overlaps will + be calculated by using the weighted linear sum corresponding + to the independent variable's distance to the adjacent boundary. + The errors use a weighted square sum. + """ + + if self.emulators is None: + raise AttributeError( + "Please train the emulator with fit_model before attempting " + "to make predictions." + ) + + # First, do individual predictions. + + inputs = [] + output = [] + + for index, (low, high) in enumerate(self.independent_regions): + mask = np.logical_and( + ( + independent > low + if low is not None + else np.ones_like(independent).astype(bool) + ), + ( + independent < high + if high is not None + else np.ones_like(independent).astype(bool) + ), + ) + + predicted = self.emulators[index].predict_values_no_error( + independent=independent[mask], model_parameters=model_parameters + ) + + inputs.append(list(independent[mask])) + output.append(list(predicted)) + + # Now that we've predicted it all, we need to explicitly deal + # with overlap and non-overlap. + + overlap_ranges = {} + + for index in range(1, len(self.independent_regions)): + left = self.independent_regions[index][0] + right = self.independent_regions[index - 1][1] + + if right is None or left is None: + continue + elif right > left: + overlap_ranges[index - 1] = [left, right] + + dependent_predictions = np.empty_like(independent) + + current_emulator = 0 + + for index, x in enumerate(independent): + if x not in inputs[current_emulator]: + current_emulator += 1 + + # Is it in the prior overlap? + low, high = overlap_ranges.get(current_emulator - 1, [float("inf")] * 2) + + if low <= x <= high: + # We have already counted this independent variable. + continue + + # Is it in this emulator's overlap? + low, high = overlap_ranges.get(current_emulator, [float("inf")] * 2) + + if low <= x <= high: + dependent_index_left = inputs[current_emulator].index(x) + dependent_index_right = inputs[current_emulator + 1].index(x) + + ind_left = inputs[current_emulator][dependent_index_left] + ind_right = inputs[current_emulator + 1][dependent_index_right] + + left_weight = (high - x) / (high - low) + right_weight = (x - low) / (high - low) + + dependent_left = output[current_emulator][dependent_index_left] + dependent_right = output[current_emulator + 1][dependent_index_right] + + dependent_predictions[index] = ( + dependent_left * left_weight + dependent_right * right_weight + ) + dependent_prediction_errors[index] = math.sqrt( + left_weight * dependent_error_left * dependent_error_left + + right_weight * dependent_error_right * dependent_error_right + ) + else: + # Easy! + dependent_index = inputs[current_emulator].index(x) + dependent_predictions[index] = output[current_emulator][dependent_index] + + return dependent_predictions From c80425167fd4c39d8dd5ee98153d5225f78fb692 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:30:47 +0200 Subject: [PATCH 6/8] Update the unit tests to call the new function too --- tests/test_emulator_bins.py | 1 + tests/test_emulator_multiple.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test_emulator_bins.py b/tests/test_emulator_bins.py index a138fc4..76b03cf 100644 --- a/tests/test_emulator_bins.py +++ b/tests/test_emulator_bins.py @@ -66,3 +66,4 @@ def test_basic_emulator_generator(): ) gpe.predict_values([0, 7], {"x": 1.5, "y": 1.0}) + gpe.predict_values_no_error([0, 7], {"x": 1.5, "y": 1.0}) diff --git a/tests/test_emulator_multiple.py b/tests/test_emulator_multiple.py index 1163393..d775235 100644 --- a/tests/test_emulator_multiple.py +++ b/tests/test_emulator_multiple.py @@ -122,3 +122,4 @@ def test_basic_emulator_generator_multiple(): ) gpe.predict_values(np.array([0.2, 0.9, 9.9, 5.0]), {"x": 0.5, "y": 1}) + gpe.predict_values_no_error(np.array([0.2, 0.9, 9.9, 5.0]), {"x": 0.5, "y": 1}) From 7c21b0058d42893f5087dbbe9ac86473552d2f25 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:50:46 +0200 Subject: [PATCH 7/8] Fix webpage of SWIFT project --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index bda262f..429b7e4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -29,7 +29,7 @@ It includes functionality to: SWIFT that processed by VELOCIraptor and the swift-pipeline. Information about `SWIFT` can be found -`here `_, Information about +`here `_, Information about `VELOCIraptor` can be found `here `_ and tnformation about the `SWIFT-pipeline` can be found From 530fdf47f84fad59fb907992d117b43dc68630b4 Mon Sep 17 00:00:00 2001 From: Matthieu Schaller Date: Sat, 10 Aug 2024 18:51:07 +0200 Subject: [PATCH 8/8] Add quick description of the new function in the RTD --- docs/source/getting_started/index.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/source/getting_started/index.rst b/docs/source/getting_started/index.rst index bab1afe..ab635c4 100644 --- a/docs/source/getting_started/index.rst +++ b/docs/source/getting_started/index.rst @@ -198,8 +198,15 @@ model. Which shows that the emulator can predict the model with high accuracy. +Note that the `predict_values()` method also returns the estimated +variance of the values it returns. In cases where the variance is +not required, the method `predict_values_no_error()` can instead be +used. In some cases, this latter function can be much faster than the +one additionally returning the variances. + + .. image:: predict_vs_model.png This covers the most basic way to use SWIFT-Emulator and should give a good baseline for using some of the -additional features it offers. \ No newline at end of file +additional features it offers.