Source code for smt_optim.acquisition_functions.integrated_variance_reduction

# @autor Paul-Saves
import numpy as np
from scipy.linalg import solve_triangular
from smt.utils.kriging import cross_distances, differences


[docs] def variance_update(model, point, x, inv_block=True): """ Compute the "look-ahead" conditional variance after adding the new point to the model. Parameters ---------- model : Surrogate smt-optim surrogate model (must wrap an SMT Kriging/MFK model). point : np.ndarray Enrichment point (candidate). x : np.ndarray() Evaluation points (Monte-Carlo integration grid). inv_block : bool, optional Compute the augmented correlation matrix using block inversion. Default True. Returns ------- MSE : np.ndarray Look-ahead variance at evaluation points. """ smt_model = getattr(model, "model", model) n_eval = x.shape[0] point.shape[1] # Handle Multi-Fidelity (MFK) vs Mono-Fidelity (KRG) if hasattr(smt_model, "nlvl"): # MFK sigma2 = smt_model.optimal_par[-1]["sigma2"] theta = smt_model.optimal_theta[-1] # Only consider HF points for the model base nt = smt_model.nt # In MFK, training points are grouped by level. HF is None. X_train_hf = smt_model.training_points[None][0][0] nt = len(X_train_hf) X_train = np.vstack([X_train_hf, point]) Cn = smt_model.optimal_par[-1]["C"] else: # KRG sigma2 = smt_model.optimal_par["sigma2"] theta = smt_model.optimal_theta X_train_mono = smt_model.training_points[None][0][0] X_train = np.vstack([X_train_mono, point]) nt = smt_model.nt Cn = smt_model.optimal_par["C"] # Standardization of data using the model's exact scaling X_offset = smt_model.X_offset X_scale = smt_model.X_scale X_normalized = (X_train - X_offset) / X_scale x_normalized = (x - X_offset) / X_scale # Compute componentwise distance dx = differences(x_normalized, X_normalized) d = smt_model._componentwise_distance(dx) # Compute the correlation function smt_model.corr.theta = theta r = smt_model.corr(d).reshape(n_eval, nt + 1) try: nugget = smt_model.options["nugget"] except KeyError: nugget = 1e-10 if inv_block: point_norm = X_normalized[nt, :].reshape(1, -1) doe_normalized = X_normalized[0:nt, :] dx_nu = differences(point_norm, doe_normalized) dnu = smt_model._componentwise_distance(dx_nu) smt_model.corr.theta = theta rn_nu = smt_model.corr(dnu) v = solve_triangular(Cn, rn_nu, lower=True) c2 = (1.0 + nugget) - np.sum(v**2) if c2 <= 1e-6: # The point is already in the dataset (or numerically indistinguishable) # Its variance reduction is 0, so the variance remains sigma2 return current_variance(model, x) c = np.sqrt(c2) C_aug = np.zeros((nt + 1, nt + 1)) C_aug[:nt, :nt] = Cn C_aug[nt, :nt] = v.flatten() C_aug[nt, nt] = c W = solve_triangular(C_aug, r.T, lower=True) res = np.sum(W**2, axis=0) else: # Full inversion dist, ij = cross_distances(X_train) D = smt_model._componentwise_distance(dist) smt_model.corr.theta = theta r_ = smt_model.corr(D) R = np.eye(nt + 1) * (1 + nugget) R[ij[:, 0], ij[:, 1]] = r_[:, 0] R[ij[:, 1], ij[:, 0]] = r_[:, 0] try: C_aug = np.linalg.cholesky(R) W = solve_triangular(C_aug, r.T, lower=True) res = np.sum(W**2, axis=0) except np.linalg.LinAlgError: R_inv = np.linalg.pinv(R) res = np.sum((r @ R_inv) * r, axis=1) # Compute the "look-ahead" conditional variance MSE = sigma2 * (1 - res) return np.maximum(MSE, 0.0)
[docs] def current_variance(model, x): """ Compute the conditional variance of the current model (without new points). """ smt_model = getattr(model, "model", model) n_eval = x.shape[0] if hasattr(smt_model, "nlvl"): # MFK sigma2 = smt_model.optimal_par[-1]["sigma2"] theta = smt_model.optimal_theta[-1] nt = smt_model.nt X_train = smt_model.training_points[None][0][0] Cn = smt_model.optimal_par[-1]["C"] else: # KRG sigma2 = smt_model.optimal_par["sigma2"] theta = smt_model.optimal_theta X_train = smt_model.training_points[None][0][0] nt = smt_model.nt Cn = smt_model.optimal_par["C"] X_offset = smt_model.X_offset X_scale = smt_model.X_scale X_normalized = (X_train - X_offset) / X_scale x_normalized = (x - X_offset) / X_scale dx = differences(x_normalized, X_normalized) d = smt_model._componentwise_distance(dx) smt_model.corr.theta = theta r = smt_model.corr(d).reshape(n_eval, nt) W = solve_triangular(Cn, r.T, lower=True) res = np.sum(W**2, axis=0) MSE = sigma2 * (1 - res) return np.maximum(MSE, 0.0)
[docs] def integrated_variance_reduction( model, points: np.ndarray, integration_points: np.ndarray = None, inv_block: bool = True, ) -> np.ndarray: """ Integrated Variance Reduction (IVR) acquisition function. Evaluates IVR for one or multiple candidate points. Returns the absolute reduction in IMSE: |IMSE_current - IMSE_new|. Parameters ---------- model : Surrogate The smt-optim surrogate model representing the objective function. points : np.ndarray Candidate points for enrichment, shape (num_points, num_dim). integration_points : np.ndarray, optional Monte-Carlo points for integration over the domain, shape (N_mc, num_dim). If None, a 500-point LHS grid is automatically generated (requires model to have xlimits or infers from training points). inv_block : bool, optional Whether to use block matrix inversion (faster). Default True. Returns ------- np.ndarray The IVR values of shape (num_points, 1) (higher is better). """ if points.ndim == 1: points = points.reshape(1, -1) smt_model = getattr(model, "model", model) if integration_points is None: if hasattr(smt_model, "_default_integration_points"): integration_points = smt_model._default_integration_points else: from smt.sampling_methods import LHS xlimits = smt_model.options.get("xlimits") if xlimits is None: if hasattr(smt_model, "nlvl"): X_train = smt_model.training_points[None][0][0] else: X_train = smt_model.training_points[None][0][0] xlimits = np.vstack( (np.min(X_train, axis=0), np.max(X_train, axis=0)) ).T import warnings warnings.warn( "integration_points was not provided and model does not have 'xlimits'. " "Inferring domain bounds from training data, which may lead to inaccurate integration." ) integration_points = LHS(xlimits=xlimits, criterion="ese", seed=42)(500) smt_model._default_integration_points = integration_points num_points = points.shape[0] ivr_vals = np.zeros((num_points, 1)) # Compute current IMSE once imse_current = np.mean(current_variance(model, integration_points)) for i in range(num_points): MSE_new = variance_update( model, points[i : i + 1, :], integration_points, inv_block=inv_block ) imse_new = np.mean(MSE_new) # We want to maximize the reduction (IMSE_current - IMSE_new). # We take the absolute value as requested, though mathematically imse_current >= imse_new. ivr_vals[i, 0] = imse_current - imse_new return ivr_vals