Skip to content

Commit 8bcc42c

Browse files
committed
abs - skipping duplicate training points during gp calibration
1 parent 5483a1f commit 8bcc42c

3 files changed

Lines changed: 82 additions & 27 deletions

File tree

modules/performUQ/common/adaptive_doe.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import numpy as np
99
import uq_utilities
10+
from gp_model import remove_duplicate_inputs
1011
from scipy.stats import qmc
1112

1213

@@ -131,6 +132,11 @@ def select_training_points(
131132
selected_points : np.ndarray of shape (n_points, d)
132133
Selected new training points.
133134
"""
135+
x_train = np.atleast_2d(x_train)
136+
x_train, _ = remove_duplicate_inputs(
137+
x_train, np.zeros((x_train.shape[0], 1))
138+
)
139+
134140
selected_points = []
135141

136142
# 1. Fixed candidate pool
@@ -173,6 +179,7 @@ def select_training_points(
173179
corr_matrix = k_matrix / kernel_var # element-wise division
174180

175181
beta = 2.0 * scaled_x_train.shape[1]
182+
# beta = 1.0 * scaled_x_train.shape[1]
176183

177184
# Vectorized IMSEw: (corr^beta) * pred_var → mean over MCI samples
178185
weighted_var = (

modules/performUQ/common/gp_ab_algorithm.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -505,12 +505,16 @@ def _calculate_gcv(self, weights=None):
505505
"""
506506
# start_time = time.time()
507507
self.loginfo('Calculating leave-one-out cross validation error measure gCV.')
508+
x_train_unique, y_train_unique = (
509+
self.current_gp_model.x_train, # type: ignore
510+
self.current_gp_model.y_train, # type: ignore
511+
)
508512
loo_predictions = self.current_gp_model.loo_predictions( # type: ignore
509-
self.inputs, self.outputs
513+
x_train_unique, y_train_unique
510514
)
511515
loocv_measure = convergence_metrics.calculate_gcv(
512516
loo_predictions,
513-
self.outputs,
517+
y_train_unique,
514518
self.output_length_list,
515519
weights=weights,
516520
weight_combination=(2 / 3, 1 / 3),
@@ -521,21 +525,20 @@ def _calculate_gcv(self, weights=None):
521525

522526
def _get_exploitation_candidates(self):
523527
"""
524-
Assemble candidate training points for exploitation by sampling from TMCMC stages
525-
after the warm start, proportionally to the provided stage weights.
528+
Assemble candidate training points for exploitation.
529+
530+
This is done by sampling from TMCMC stages after the warm start,
531+
proportionally to the provided stage weights.
526532
527533
Returns
528534
-------
529535
np.ndarray:
530536
The exploitation candidate training points of shape (n_candidates, n_parameters).
531537
dict:
532538
Dictionary mapping stage number to the number of points sampled from that stage.
533-
""" # noqa: D205
534-
# self.loginfo('Assembling candidate training points for exploitation.')
535-
stage_weights = np.asarray(
536-
self.stage_weights
537-
) # aligned with stages_after_warm_start
538-
stage_weights = stage_weights / np.sum(stage_weights)
539+
"""
540+
stage_weights = np.asarray(self.stage_weights)
541+
stage_weights /= np.sum(stage_weights) # Normalize weights
539542

540543
n_candidates = self.num_candidate_training_points
541544
candidate_training_points_exploitation = []
@@ -544,26 +547,29 @@ def _get_exploitation_candidates(self):
544547
for stage_idx, stage_num in enumerate(self.stages_after_warm_start):
545548
samples_stage = self.results['model_parameters_dict'][stage_num]
546549
n_available = samples_stage.shape[0]
550+
547551
n_stage_samples = int(np.round(stage_weights[stage_idx] * n_candidates))
552+
if n_stage_samples == 0:
553+
continue
548554

549-
if n_stage_samples >= n_available:
550-
selected = samples_stage
551-
n_selected = n_available
552-
else:
555+
if n_stage_samples <= n_available:
553556
selected_indices = np.random.choice(
554557
n_available, size=n_stage_samples, replace=False
555558
)
556559
selected = samples_stage[selected_indices]
557-
n_selected = n_stage_samples
560+
else:
561+
# Use all available samples
562+
selected = samples_stage
558563

559564
candidate_training_points_exploitation.append(selected)
560-
stage_sample_counts[stage_num] = n_selected
565+
stage_sample_counts[stage_num] = selected.shape[0]
561566

567+
# Combine all stage samples
562568
candidate_training_points_exploitation = np.vstack(
563569
candidate_training_points_exploitation
564570
)
565571

566-
# Trim if needed
572+
# Optional: globally truncate if total exceeds n_candidates (e.g., due to rounding)
567573
if candidate_training_points_exploitation.shape[0] > n_candidates:
568574
trim_indices = np.random.choice(
569575
candidate_training_points_exploitation.shape[0],
@@ -573,9 +579,7 @@ def _get_exploitation_candidates(self):
573579
candidate_training_points_exploitation = (
574580
candidate_training_points_exploitation[trim_indices]
575581
)
576-
# self.loginfo(
577-
# 'Completed candidate training point selection for exploitation.'
578-
# )
582+
579583
return candidate_training_points_exploitation, stage_sample_counts
580584

581585
def _summarize_iteration(self):
@@ -801,8 +805,13 @@ def _step_1_posterior_approximation(
801805
self.gp_prediction_mean, _ = self.current_gp_model.predict( # type: ignore
802806
model_parameters
803807
)
808+
# self.loo_predictions = self.current_gp_model.loo_predictions( # type: ignore
809+
# model_parameters, model_outputs
810+
# )
811+
804812
self.loo_predictions = self.current_gp_model.loo_predictions( # type: ignore
805-
model_parameters, model_outputs
813+
self.current_gp_model.x_train, # type: ignore
814+
self.current_gp_model.y_train, # type: ignore
806815
)
807816

808817
self.current_response_approximation = partial(
@@ -856,7 +865,9 @@ def _step_2_bayesian_updating(self, log_prior_fn, log_like_fn):
856865

857866
with self.log_step('Evaluating warm-start of TMCMC.'):
858867
if self.iteration_number > 0:
859-
weights = self.kde.evaluate(self.inputs) # type: ignore
868+
weights = self.kde.evaluate( # type: ignore
869+
self.current_gp_model.x_train # type: ignore
870+
) # inputs deduplicated in the gp model
860871
self.gcv = self._calculate_gcv(weights)
861872
self.warm_start_possible = self.gcv <= self.gcv_threshold
862873

@@ -1058,7 +1069,7 @@ def _step_4_adaptive_training_point_selection(self):
10581069
(0, self.input_dimension)
10591070
)
10601071

1061-
current_inputs = self.inputs.copy()
1072+
current_inputs = self.current_gp_model.x_train.copy() # type: ignore
10621073
if self.n_exploit > 0:
10631074
self.exploitation_training_points = (
10641075
current_doe.select_training_points(

modules/performUQ/common/gp_model.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,31 @@
3131
# =========================================================
3232

3333

34+
def remove_duplicate_inputs(
35+
x: np.ndarray, y: np.ndarray
36+
) -> tuple[np.ndarray, np.ndarray]:
37+
"""
38+
Remove duplicate rows in X and corresponding rows in Y.
39+
40+
Keeps only the first occurrence.
41+
42+
Parameters
43+
----------
44+
x : np.ndarray
45+
Input array of shape (n_samples, n_features).
46+
y : np.ndarray
47+
Output array of shape (n_samples, n_outputs).
48+
49+
Returns
50+
-------
51+
x_unique, y_unique : tuple[np.ndarray, np.ndarray]
52+
Deduplicated input-output arrays.
53+
"""
54+
_, unique_indices = np.unique(x, axis=0, return_index=True)
55+
sorted_indices = np.sort(unique_indices)
56+
return x[sorted_indices], y[sorted_indices]
57+
58+
3459
class GaussianProcessModelSettings(BaseModel):
3560
"""Settings for creating a Gaussian Process Model."""
3661

@@ -227,8 +252,14 @@ def initialize(
227252
num_random_restarts : int, optional
228253
Number of random restarts for optimization (default is 10).
229254
"""
230-
self.x_train = x_train
231-
self.y_train = y_train
255+
orig_n = x_train.shape[0]
256+
inputs, outputs = remove_duplicate_inputs(x_train, y_train)
257+
deduped_n = inputs.shape[0]
258+
if self.logger and deduped_n < orig_n:
259+
self.logger.info(f'Removed {orig_n - deduped_n} duplicate input points.')
260+
261+
self.x_train = inputs
262+
self.y_train = outputs
232263
self._fit_pca_and_create_models(
233264
reoptimize=reoptimize, num_random_restarts=num_random_restarts
234265
)
@@ -254,8 +285,14 @@ def update_training_dataset(
254285
msg = 'GP model not initialized. Call `initialize` first.'
255286
raise ValueError(msg)
256287

257-
self.x_train = x_train
258-
self.y_train = y_train
288+
orig_n = x_train.shape[0]
289+
inputs, outputs = remove_duplicate_inputs(x_train, y_train)
290+
deduped_n = inputs.shape[0]
291+
if self.logger and deduped_n < orig_n:
292+
self.logger.info(f'Removed {orig_n - deduped_n} duplicate input points.')
293+
294+
self.x_train = inputs
295+
self.y_train = outputs
259296

260297
self.x_train_scaled = self.apply_input_scaling(self.x_train, fit=True)
261298

0 commit comments

Comments
 (0)