From 6658e825be8582b1ab8a1053545319f5e503230c Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 14 Mar 2024 13:00:07 -0700 Subject: [PATCH] bug fixed and new license agreement --- LEGAL.md | 9 ++++-- LICENSE | 4 +-- gpcam/autonomous_experimenter.py | 16 +++------- gpcam/gp_optimizer.py | 55 +++++++++++--------------------- gpcam/surrogate_model.py | 4 ++- requirements.txt | 8 ++--- tests/test_gpCAM.py | 4 +-- 7 files changed, 40 insertions(+), 60 deletions(-) diff --git a/LEGAL.md b/LEGAL.md index aa2e18f..9d565f5 100644 --- a/LEGAL.md +++ b/LEGAL.md @@ -1,4 +1,6 @@ -gpCAM Copyright (c) 2021, The Regents of the University of California, +**************************** + +gpCAM Copyright (c) 2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. @@ -11,4 +13,7 @@ of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative -works, and perform publicly and display publicly, and to permit others to do so. \ No newline at end of file +works, and perform publicly and display publicly, and to permit others to do so. + + +*************************** diff --git a/LICENSE b/LICENSE index 7f94754..910fd98 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ GPL v3 License -gpCAM Copyright (c) 2021, The Regents of the University of California, +gpCAM Copyright (c) 2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. @@ -24,7 +24,7 @@ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF diff --git a/gpcam/autonomous_experimenter.py b/gpcam/autonomous_experimenter.py index acf6393..5ec1ca4 100755 --- a/gpcam/autonomous_experimenter.py +++ b/gpcam/autonomous_experimenter.py @@ -26,12 +26,12 @@ class AutonomousExperimenterGP: The default is a random draw from a uniform distribution within hyperparameter_bounds, with a shape appropriate for the default kernel (D + 1), which is an anisotropic Matern - kernel with automatic relevance determination (ARD). If sparse_node or gp2Scale is + kernel with automatic relevance determination (ARD). If gp2Scale is enabled, the default kernel changes to the anisotropic Wendland kernel. hyperparameter_bounds : np.ndarray, optional A 2d numpy array of shape (N x 2), where N is the number of needed hyperparameters. The default is None, in which case the hyperparameter_bounds are estimated from the domain size - and the initial y_data. If normalize_y is True or the data changes significantly, + and the initial y_data. If the data changes significantly, the hyperparameters and the bounds should be changed/retrained. Initial hyperparameters and bounds can also be set in the train calls. The default only works for the default kernels. instrument_function : Callable, optional @@ -191,11 +191,9 @@ def __init__(self, store_inv=False, training_dask_client=None, acq_func_opt_dask_client=None, - sparse_mode=False, gp2Scale=False, gp2Scale_dask_client=None, gp2Scale_batch_size=10000, - normalize_y=False, ram_economy=True, info=False, args=None @@ -259,11 +257,9 @@ def __init__(self, gp_noise_function_grad=None, gp_mean_function=prior_mean_function, gp_mean_function_grad=None, - sparse_mode=sparse_mode, gp2Scale=gp2Scale, gp2Scale_dask_client=gp2Scale_dask_client, gp2Scale_batch_size=gp2Scale_batch_size, - normalize_y=normalize_y, store_inv=store_inv, ram_economy=ram_economy, args=args, @@ -686,12 +682,12 @@ class AutonomousExperimenterFvGP(AutonomousExperimenterGP): The default is a random draw from a uniform distribution within hyperparameter_bounds, with a shape appropriate for the default kernel (D + 1), which is an anisotropic Matern - kernel with automatic relevance determination (ARD). If sparse_node or gp2Scale is + kernel with automatic relevance determination (ARD). If gp2Scale is enabled, the default kernel changes to the anisotropic Wendland kernel. hyperparameter_bounds : np.ndarray, optional A 2d numpy array of shape (N x 2), where N is the number of needed hyperparameters. The default is None, in which case the hyperparameter_bounds are estimated from the domain size - and the initial y_data. If normalize_y is True or the data changes significantly, + and the initial y_data. If the data changes significantly, the hyperparameters and the bounds should be changed/retrained. Initial hyperparameters and bounds can also be set in the train calls. The default only works for the default kernels. instrument_function : Callable, optional @@ -833,11 +829,9 @@ def __init__(self, store_inv=False, training_dask_client=None, acq_func_opt_dask_client=None, - sparse_mode=False, gp2Scale=False, gp2Scale_dask_client=None, gp2Scale_batch_size=10000, - normalize_y=False, ram_economy=True, info=False, args=None @@ -899,11 +893,9 @@ def __init__(self, gp_noise_function_grad=None, gp_mean_function=prior_mean_function, gp_mean_function_grad=None, - sparse_mode=sparse_mode, gp2Scale=gp2Scale, gp2Scale_dask_client=gp2Scale_dask_client, gp2Scale_batch_size=gp2Scale_batch_size, - normalize_y=normalize_y, store_inv=store_inv, ram_economy=ram_economy, args=args, diff --git a/gpcam/gp_optimizer.py b/gpcam/gp_optimizer.py index 3a186a4..4403435 100755 --- a/gpcam/gp_optimizer.py +++ b/gpcam/gp_optimizer.py @@ -133,12 +133,12 @@ class GPOptimizer(GP): The default is a random draw from a uniform distribution within hyperparameter_bounds, with a shape appropriate for the default kernel (D + 1), which is an anisotropic Matern - kernel with automatic relevance determination (ARD). If sparse_node or gp2Scale is + kernel with automatic relevance determination (ARD). If gp2Scale is enabled, the default kernel changes to the anisotropic Wendland kernel. hyperparameter_bounds : np.ndarray, optional A 2d numpy array of shape (N x 2), where N is the number of needed hyperparameters. The default is None, in which case the hyperparameter_bounds are estimated from the domain size - and the initial y_data. If normalize_y is True or the data changes significantly, + and the initial y_data. If the data changes significantly, the hyperparameters and the bounds should be changed/retrained. Initial hyperparameters and bounds can also be set in the train calls. The default only works for the default kernels. noise_variances : np.ndarray, optional @@ -210,17 +210,6 @@ class GPOptimizer(GP): hyperparameters. If `gp_noise_function` is provided but no gradient function, a finite-difference approximation will be used. The same rules regarding ram economy as for the kernel definition apply here. - normalize_y : bool, optional - If True, the data values `y_data` will be normalized to max(y_data) = 1, min(y_data) = 0. - The default is False. - Variances will be updated accordingly. - sparse_mode : bool, optional - When sparse_mode is enabled, the algorithm will use a user-defined kernel - function or, if that's not provided, an anisotropic Wendland kernel - and check for sparsity in the prior covariance. If sparsity is present, - sparse operations will be used to speed up computations. - Caution: the covariance is still stored at first in a dense format. - For more extreme scaling, check out the gp2Scale option. gp2Scale: bool, optional Turns on gp2Scale. This will distribute the covariance computations across multiple workers. This is an advanced feature for HPC GPs up to 10 @@ -320,11 +309,9 @@ def __init__( gp_noise_function_grad=None, gp_mean_function=None, gp_mean_function_grad=None, - sparse_mode=False, gp2Scale=False, gp2Scale_dask_client=None, gp2Scale_batch_size=10000, - normalize_y=False, store_inv=True, ram_economy=False, args=None, @@ -354,11 +341,9 @@ def __init__( gp_noise_function_grad=gp_noise_function_grad, gp_mean_function=gp_mean_function, gp_mean_function_grad=gp_mean_function_grad, - sparse_mode=sparse_mode, gp2Scale=gp2Scale, gp2Scale_dask_client=gp2Scale_dask_client, gp2Scale_batch_size=gp2Scale_batch_size, - normalize_y=normalize_y, store_inv=store_inv, ram_economy=ram_economy, args=args, @@ -420,7 +405,7 @@ def evaluate_acquisition_function(self, x, acquisition_function="variance", orig logger.error("Evaluating the acquisition function was not successful.") raise Exception("Evaluating the acquisition function was not successful.", ex) - def tell(self, x, y, noise_variances=None): + def tell(self, x, y, noise_variances=None, overwrite=True): """ This function can tell() the gp_optimizer class the data that was collected. The data will instantly be used to update the gp data. @@ -436,8 +421,10 @@ def tell(self, x, y, noise_variances=None): noise_variances : np.ndarray, optional Point value variances (of shape U x 1 or U) to be communicated to the Gaussian Process. If not provided, the GP will 1% of the y values as variances. + overwrite : bool, optional + The default is True. Indicates if all previous data should be overwritten. """ - super().update_gp_data(x, y, noise_variances=noise_variances) + super().update_gp_data(x, y, noise_variances=noise_variances, overwrite=overwrite) ############################################################## def train( @@ -786,6 +773,8 @@ def ask(self, logger.info("bounds:\n{}", bounds) logger.info("acq func: {}", acquisition_function) + assert isinstance(vectorized, bool) + #check for bounds or candidate set if bounds is not None and candidates is not None: raise Exception("Bounds and candidates provided. Only one should be given.") @@ -803,6 +792,8 @@ def ask(self, bounds = new_optimization_bounds if acquisition_function != "total correlation" and acquisition_function != "relative information entropy": acquisition_function = "total correlation" + warnings.warn("You specified n>1 and method != 'hgdl' in ask(). The acquisition function \ + has therefore been changed to 'total correlation'") if acquisition_function == "total correlation" or acquisition_function == "relative information entropy": vectorized = False @@ -997,17 +988,6 @@ class fvGPOptimizer(fvGP): If `gp_noise_function` is provided but no gradient function, a finite-difference approximation will be used. The same rules regarding ram economy as for the kernel definition apply here. - normalize_y : bool, optional - If True, the data values `y_data` will be normalized to max(y_data) = 1, min(y_data) = 0. - The default is False. - Variances will be updated accordingly. - sparse_mode : bool, optional - When sparse_mode is enabled, the algorithm will use a user-defined kernel function or, - if that's not provided, an anisotropic Wendland kernel - and check for sparsity in the prior covariance. If sparsity is present, - sparse operations will be used to speed up computations. - Caution: the covariance is still stored at first in a dense format. For more extreme scaling, - check out the gp2Scale option. gp2Scale: bool, optional Turns on gp2Scale. This will distribute the covariance computations across multiple workers. This is an advanced feature for HPC GPs up to 10 @@ -1033,7 +1013,7 @@ class fvGPOptimizer(fvGP): True. Note, the training will always use Cholesky or LU decomposition instead of the inverse for stability reasons. Storing the inverse is a good option when the dataset is not too large and the posterior covariance is heavily used. - If sparse_mode or gp2Scale is used, store_inv will be set to False. + If gp2Scale is used, store_inv will be set to False. ram_economy : bool, optional Only of interest if the gradient and/or Hessian of the marginal log_likelihood is/are used for the training. @@ -1115,11 +1095,9 @@ def __init__( gp_noise_function_grad=None, gp_mean_function=None, gp_mean_function_grad=None, - sparse_mode=False, gp2Scale=False, gp2Scale_dask_client=None, gp2Scale_batch_size=10000, - normalize_y=False, store_inv=True, ram_economy=False, args=None, @@ -1159,11 +1137,9 @@ def __init__( gp_noise_function_grad=gp_noise_function_grad, gp_mean_function=gp_mean_function, gp_mean_function_grad=gp_mean_function_grad, - sparse_mode=sparse_mode, gp2Scale=gp2Scale, gp2Scale_dask_client=gp2Scale_dask_client, gp2Scale_batch_size=gp2Scale_batch_size, - normalize_y=normalize_y, store_inv=store_inv, ram_economy=ram_economy, args=args, @@ -1230,7 +1206,7 @@ def evaluate_acquisition_function(self, x, x_out, acquisition_function="variance raise Exception("Evaluating the acquisition function was not successful.", ex) ############################################################################ - def tell(self, x, y, noise_variances=None, output_positions=None): + def tell(self, x, y, noise_variances=None, output_positions=None, overwrite=True): """ This function can tell() the gp_optimizer class the data that was collected. The data will instantly be used to update the GP data. @@ -1251,8 +1227,11 @@ def tell(self, x, y, noise_variances=None, output_positions=None): are clearly defined by their positions in the output space. The default is np.array([[0],[1],[2],[3],...,[output_number - 1]]) for each point in the input space. The default is only permissible if output_dim is 1. + overwrite : bool, optional + The default is True. Indicates if all previous data should be overwritten. """ - super().update_gp_data(x, y, noise_variances=noise_variances, output_positions=output_positions) + super().update_gp_data(x, y, noise_variances=noise_variances, + output_positions=output_positions, overwrite=overwrite) ############################################################## def train(self, @@ -1611,6 +1590,8 @@ def ask(self, bounds = new_optimization_bounds if acquisition_function != "total correlation" and acquisition_function != "relative information entropy": acquisition_function = "total correlation" + warnings.warn("You specified n>1 and method != 'hgdl' in ask(). The acquisition function \ + has therefore been changed to 'total correlation'") if acquisition_function == "total correlation" or acquisition_function == "relative information entropy": vectorized = False if method != "global": vectorized = False diff --git a/gpcam/surrogate_model.py b/gpcam/surrogate_model.py index f39500b..3722f4c 100755 --- a/gpcam/surrogate_model.py +++ b/gpcam/surrogate_model.py @@ -30,6 +30,7 @@ def find_acquisition_function_maxima(gp, acquisition_function, x_out=None, dask_client=None, info=False): + if candidates is None and optimization_bounds is None: raise Exception("optimization bounds or candidates have to be provided") bounds = optimization_bounds @@ -179,7 +180,7 @@ def evaluate_acquisition_function(x, gp=None, acquisition_function=None, origin= ########################################################## if isinstance(x, np.ndarray) and np.ndim(x) == 1: x = x.reshape(-1, gp.input_dim) if x_out is not None and np.ndim(x_out) != 2: raise Exception( - "x_out in evaluate_acquisition_function has to be a 2d umpy array.") + "x_out in evaluate_acquisition_function has to be a 2d numpy array.") if cost_function is not None and origin is not None: cost_eval = cost_function(origin, x, cost_function_parameters) @@ -288,6 +289,7 @@ def differential_evolution(func, constraints=(), disp=False, vectorized=True): + res = devo(partial(acq_function_vectorization_wrapper, func=func, vectorized=vectorized), bounds, tol=tol, x0=x0, maxiter=max_iter, popsize=popsize, polish=False, disp=disp, constraints=constraints, vectorized=vectorized) diff --git a/requirements.txt b/requirements.txt index 7fae579..b364d1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ wheel numpy == 1.23.5 -scipy == 1.11.2 +scipy == 1.12.0 matplotlib pandas ophyd -dask >= 2021.6.2 -distributed >= 2021.6.2 +dask == 2024.1.0 +distributed == 2024.1.0 zmq -fvgp == 4.1.1 +fvgp == 4.1.2 plotly notebook loguru diff --git a/tests/test_gpCAM.py b/tests/test_gpCAM.py index 58a014d..9593182 100755 --- a/tests/test_gpCAM.py +++ b/tests/test_gpCAM.py @@ -154,7 +154,7 @@ def test_fvae(self): my_ae.kill_training() #...and run. That's it. You successfully executed an autonomous experiment. - my_ae.go(N = 20) + my_ae.go(N = 10) @@ -163,7 +163,7 @@ def test_acq_funcs(self): from gpcam.gp_optimizer import GPOptimizer #initialize some data - x_data = np.random.uniform(size = (100,3)) + x_data = np.random.uniform(size = (10,3)) y_data = np.sin(np.linalg.norm(x_data, axis = 1))