Skip to content

Commit 623f6d4

Browse files
committed
Fix unit tests
1 parent 72956f9 commit 623f6d4

7 files changed

Lines changed: 213 additions & 37 deletions

File tree

codes/benchmark/bench_fcts.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,10 @@ def evaluate_iterative_predictions(
375375
else:
376376
batch_size = conf["batch_size"]
377377

378-
# container for the piecewise predictions
378+
# container for the piecewise predictions; seed t=0 with ground truth so errors
379+
# are computed only on actual predictions for t>=1 while keeping shape intact
379380
iterative_preds = np.zeros_like(targets)
381+
iterative_preds[:, 0, :] = targets[:, 0, :]
380382

381383
# number of chunks
382384
n_chunks = (n_timesteps + iter_interval - 1) // iter_interval
@@ -429,7 +431,9 @@ def evaluate_iterative_predictions(
429431
preds_chunk, _ = model.predict(
430432
data_loader=train_loader, leave_log=True, leave_norm=True
431433
)
432-
iterative_preds[:, start:end, :] = (
434+
# We predict steps 1..(chunk_len-1) relative to the provided init state (index 0).
435+
# Map these to global indices [start+1 .. end] inclusively.
436+
iterative_preds[:, start + 1 : end + 1, :] = (
433437
preds_chunk[:, 1 : model.n_timesteps, :].detach().cpu().numpy()
434438
)
435439

test/test_bench_compare.py

Lines changed: 186 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ def make_metrics_for_main(surr_names, n_timesteps=4, n_quantities=3):
1313
for i, name in enumerate(surr_names):
1414
metrics[name] = {
1515
"timesteps": np.zeros(n_timesteps),
16-
"accuracy": {"absolute_errors": np.zeros((1, n_timesteps, n_quantities))},
16+
"accuracy": {
17+
"absolute_errors": np.zeros((1, n_timesteps, n_quantities)),
18+
"absolute_errors_log": np.zeros((1, n_timesteps, n_quantities)),
19+
},
1720
"n_params": 123 + i,
1821
}
1922
return metrics
@@ -47,7 +50,10 @@ def make_metrics_for_dynamic(surr_names, n_timesteps=4, n_quantities=2):
4750
metrics = {}
4851
for name in surr_names:
4952
metrics[name] = {
50-
"accuracy": {"absolute_errors": np.zeros((1, n_timesteps, n_quantities))},
53+
"accuracy": {
54+
"absolute_errors": np.zeros((1, n_timesteps, n_quantities)),
55+
"absolute_errors_log": np.zeros((1, n_timesteps, n_quantities)),
56+
},
5157
"gradients": {
5258
"gradients": np.ones((1, n_timesteps, n_quantities)),
5359
"avg_correlation": 0.5,
@@ -80,6 +86,75 @@ def make_metrics_for_generalization(surr_names):
8086
return base
8187

8288

89+
def make_metrics_for_interpolation(surr_names):
90+
base = {}
91+
for name in surr_names:
92+
base[name] = {
93+
"interpolation": {
94+
"intervals": np.array([1, 2, 4]),
95+
"model_errors": np.array([0.1, 0.2, 0.25]),
96+
}
97+
}
98+
return base
99+
100+
101+
def make_metrics_for_extrapolation(surr_names, timesteps_len=5):
102+
base = {}
103+
for name in surr_names:
104+
base[name] = {
105+
"extrapolation": {
106+
"cutoffs": np.array([2, timesteps_len]),
107+
"model_errors": np.array([0.3, 0.22]),
108+
}
109+
}
110+
return base
111+
112+
113+
def make_metrics_for_sparse(surr_names):
114+
base = {}
115+
for name in surr_names:
116+
base[name] = {
117+
"sparse": {
118+
"n_train_samples": np.array([100, 50, 25]),
119+
"model_errors": np.array([0.15, 0.2, 0.28]),
120+
}
121+
}
122+
return base
123+
124+
125+
def make_metrics_for_batchsize(surr_names):
126+
base = {}
127+
for name in surr_names:
128+
base[name] = {
129+
"batch_size": {
130+
"batch_elements": np.array([32, 64, 128]),
131+
"model_errors": np.array([0.12, 0.11, 0.13]),
132+
}
133+
}
134+
return base
135+
136+
137+
def make_metrics_for_UQ(surr_names, timesteps):
138+
base = {}
139+
T = len(timesteps)
140+
for name in surr_names:
141+
uq_std = np.full((1, T, 1), 0.25)
142+
uq_err = np.full((1, T, 1), 0.2)
143+
base[name] = {
144+
"timesteps": np.array(timesteps),
145+
"accuracy": {"absolute_errors_log": np.full((1, T, 1), 0.18)},
146+
"UQ": {
147+
"pred_uncertainty_log": uq_std,
148+
"absolute_errors_log": uq_err,
149+
"axis_max": 1,
150+
"max_counts": 1,
151+
"correlation_metrics_log": 0.4,
152+
"targets_log": np.zeros((1, T, 1)),
153+
},
154+
}
155+
return base
156+
157+
83158
@pytest.fixture(autouse=True)
84159
def stub_plots_and_io(monkeypatch):
85160
calls = []
@@ -90,12 +165,13 @@ def stub_plots_and_io(monkeypatch):
90165
"plot_generalization_error_comparison",
91166
"plot_uncertainty_over_time_comparison",
92167
"plot_comparative_error_correlation_heatmaps",
168+
"plot_mean_deltadex_over_time_main_vs_ensemble",
169+
"plot_catastrophic_detection_curves",
170+
"plot_errors_over_time",
93171
"plot_error_distribution_comparative",
94-
"plot_uncertainty_confidence",
95172
"plot_loss_comparison",
96173
"plot_loss_comparison_equal",
97174
"plot_loss_comparison_train_duration",
98-
"plot_relative_errors",
99175
"plot_error_distribution_comparative",
100176
]:
101177
monkeypatch.setattr(bf, fn, lambda *a, _n=fn, **k: calls.append((_n, a, k)))
@@ -146,22 +222,35 @@ def load(self, *args, **kw):
146222
]
147223

148224

149-
def test_compare_relative_errors(stub_plots_and_io, cfg):
225+
def test_compare_errors(stub_plots_and_io, cfg):
150226
timesteps = [0.0, 1.0, 2.0]
151227
metrics = make_metrics_for_relative(["M1"], timesteps)
152-
bf.compare_relative_errors(metrics, cfg)
228+
# also include deltadex branch to verify both paths
229+
metrics["M1"]["accuracy"]["absolute_errors_log"] = np.arange(
230+
len(timesteps)
231+
).reshape(1, len(timesteps), 1)
232+
233+
bf.compare_errors(metrics, cfg)
153234
# mean and median come from np.mean/median over rel errors
154235
mean_err = np.mean(metrics["M1"]["accuracy"]["relative_errors"], axis=(0, 2))
155236
median_err = np.median(metrics["M1"]["accuracy"]["relative_errors"], axis=(0, 2))
156-
# first call to plot_relative_errors
237+
# first call to plot_errors_over_time (relative)
157238
_n, args, kw = stub_plots_and_io[0]
158-
assert _n == "plot_relative_errors"
239+
assert _n == "plot_errors_over_time"
159240
# args = ( mean_dict, median_dict, timesteps, cfg )
160241
assert pytest.approx(list(args[0].values())[0]) == mean_err
161242
assert pytest.approx(list(args[1].values())[0]) == median_err
162243
assert np.all(args[2] == timesteps)
244+
assert kw.get("mode") == "relative"
163245
# second call
164246
assert stub_plots_and_io[1][0] == "plot_error_distribution_comparative"
247+
assert stub_plots_and_io[1][2].get("mode") == "relative"
248+
249+
# third and fourth calls should be for Δdex branch
250+
assert stub_plots_and_io[2][0] == "plot_errors_over_time"
251+
assert stub_plots_and_io[2][2].get("mode") == "deltadex"
252+
assert stub_plots_and_io[3][0] == "plot_error_distribution_comparative"
253+
assert stub_plots_and_io[3][2].get("mode") == "deltadex"
165254

166255

167256
def test_compare_inference_time(stub_plots_and_io, cfg):
@@ -197,38 +286,112 @@ def test_compare_gradients(stub_plots_and_io, cfg):
197286
assert kw.get("show_title", False) is True
198287

199288

200-
def test_compare_UQ_and_confidence(stub_plots_and_io, cfg, monkeypatch):
201-
base = make_metrics_for_generalization(["M1"])
202-
# ADD a dummy timesteps array
203-
base["M1"]["timesteps"] = np.array([0.0, 1.0])
289+
def test_compare_interpolation(stub_plots_and_io, cfg):
290+
m = make_metrics_for_interpolation(["M1", "M2"])
291+
bf.compare_interpolation(m, cfg)
292+
name, args, kw = stub_plots_and_io[0]
293+
assert name == "plot_generalization_error_comparison"
294+
surrogates, intervals, model_errors, xlabel, filename, conf = args
295+
assert surrogates == ["M1", "M2"]
296+
assert xlabel == "Interpolation Interval"
297+
assert filename == "errors_interpolation.png"
298+
assert all(isinstance(arr, np.ndarray) for arr in intervals)
299+
assert all(isinstance(arr, np.ndarray) for arr in model_errors)
300+
assert conf is cfg
301+
assert kw.get("show_title", False) is True
204302

205-
# stub out plot_uncertainty_confidence to return known scores
206-
monkeypatch.setattr(bf, "plot_uncertainty_confidence", lambda *a, **k: {"M1": 0.42})
207303

208-
bf.compare_UQ(base, cfg)
304+
def test_compare_extrapolation(stub_plots_and_io, cfg):
305+
m = make_metrics_for_extrapolation(["M1"])
306+
bf.compare_extrapolation(m, cfg)
307+
name, args, kw = stub_plots_and_io[0]
308+
assert name == "plot_generalization_error_comparison"
309+
surrogates, cutoffs, model_errors, xlabel, filename, conf = args
310+
assert surrogates == ["M1"]
311+
assert xlabel == "Extrapolation Cutoff"
312+
assert filename == "errors_extrapolation.png"
313+
assert isinstance(cutoffs[0], np.ndarray)
314+
assert isinstance(model_errors[0], np.ndarray)
315+
assert conf is cfg
316+
209317

210-
# after compare_UQ, confidence_scores should exist in metrics
211-
assert base["M1"]["UQ"]["confidence_scores"] == 0.42
318+
def test_compare_sparse(stub_plots_and_io, cfg):
319+
m = make_metrics_for_sparse(["M1"])
320+
bf.compare_sparse(m, cfg)
321+
name, args, kw = stub_plots_and_io[0]
322+
assert name == "plot_generalization_error_comparison"
323+
surrogates, n_train_samples, model_errors, xlabel, filename, conf = args
324+
assert surrogates == ["M1"]
325+
assert xlabel == "Number of Training Samples"
326+
assert filename == "errors_sparse.png"
327+
assert isinstance(n_train_samples[0], np.ndarray)
328+
assert isinstance(model_errors[0], np.ndarray)
329+
assert conf is cfg
330+
331+
332+
def test_compare_batchsize(stub_plots_and_io, cfg):
333+
m = make_metrics_for_batchsize(["M1"])
334+
bf.compare_batchsize(m, cfg)
335+
name, args, kw = stub_plots_and_io[0]
336+
assert name == "plot_generalization_error_comparison"
337+
surrogates, batch_elements, model_errors, xlabel, filename, conf = args
338+
assert surrogates == ["M1"]
339+
assert xlabel == "Batch Size"
340+
assert filename == "errors_batch_size.png"
341+
assert isinstance(batch_elements[0], np.ndarray)
342+
assert isinstance(model_errors[0], np.ndarray)
343+
assert conf is cfg
344+
345+
346+
def test_compare_UQ(stub_plots_and_io, cfg):
347+
timesteps = [0.0, 1.0, 2.0]
348+
m = make_metrics_for_UQ(["M1"], timesteps)
349+
bf.compare_UQ(m, cfg)
350+
names = [c[0] for c in stub_plots_and_io]
351+
assert names[:4] == [
352+
"plot_mean_deltadex_over_time_main_vs_ensemble",
353+
"plot_uncertainty_over_time_comparison",
354+
"plot_comparative_error_correlation_heatmaps",
355+
"plot_catastrophic_detection_curves",
356+
]
212357

213358

214359
def test_tabular_comparison_creates_files(tmp_path, stub_plots_and_io, monkeypatch):
215360
metrics = {
216361
"M1": {
217362
"accuracy": {
218-
"mean_squared_error": 0.1,
219-
"mean_absolute_error": 0.2,
363+
"root_mean_squared_error_real": 0.1,
364+
"mean_absolute_error_real": 0.2,
365+
"median_absolute_error_real": 0.15,
366+
"percentile_absolute_error_real": 0.25,
367+
"root_mean_squared_error_log": 0.05,
368+
"mean_absolute_error_log": 0.04,
369+
"median_absolute_error_log": 0.035,
370+
"percentile_absolute_error_log": 0.06,
220371
"mean_relative_error": 0.3,
372+
"median_relative_error": 0.25,
373+
"percentile_relative_error": 0.35,
221374
"main_model_epochs": 4,
222375
"main_model_training_time": 7.0,
376+
"error_percentile": 99,
223377
}
224378
},
225379
"M2": {
226380
"accuracy": {
227-
"mean_squared_error": 0.01,
228-
"mean_absolute_error": 0.02,
229-
"mean_relative_error": 0.03,
381+
"root_mean_squared_error_real": 0.08,
382+
"mean_absolute_error_real": 0.18,
383+
"median_absolute_error_real": 0.14,
384+
"percentile_absolute_error_real": 0.22,
385+
"root_mean_squared_error_log": 0.04,
386+
"mean_absolute_error_log": 0.03,
387+
"median_absolute_error_log": 0.028,
388+
"percentile_absolute_error_log": 0.05,
389+
"mean_relative_error": 0.25,
390+
"median_relative_error": 0.2,
391+
"percentile_relative_error": 0.3,
230392
"main_model_epochs": 5,
231393
"main_model_training_time": 3.0,
394+
"error_percentile": 99,
232395
}
233396
},
234397
}
@@ -244,6 +407,7 @@ def test_tabular_comparison_creates_files(tmp_path, stub_plots_and_io, monkeypat
244407
"sparse": {"enabled": False},
245408
"batch_scaling": {"enabled": False},
246409
"verbose": False,
410+
"iterative": False,
247411
}
248412

249413
# run inside tmp_path

test/test_bench_main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, *, train_duration=2.5, n_quantities=2):
1414
def load(self, training_id, surr_name, model_identifier):
1515
self.load_calls.append((training_id, surr_name, model_identifier))
1616

17-
def predict(self, *, data_loader, leave_log=None, leave_norm=None):
17+
def predict(self, data_loader, leave_log=None, leave_norm=None):
1818
"""
1919
Return preds and targets of shape [1, 3, n_quantities] where
2020
preds are always 2x targets.
@@ -193,7 +193,7 @@ def __init__(self, n_timesteps, n_quantities):
193193
def load(self, training_id, surr_name, model_identifier):
194194
self.load_calls.append((training_id, surr_name, model_identifier))
195195

196-
def predict(self, *, data_loader, leave_log=None, leave_norm=None):
196+
def predict(self, data_loader, leave_log: bool = False, leave_norm=None):
197197
# preds == targets == ones
198198
shape = (1, self.n_timesteps, self.n_quantities)
199199
ones = torch.ones(shape, dtype=torch.float32)
@@ -219,14 +219,15 @@ def denormalize(self, arr, leave_log=None, leave_norm=None):
219219
surr_name=surr,
220220
timesteps=timesteps,
221221
val_loader="dummy_val_loader",
222+
val_params=None,
222223
conf=simple_conf,
223224
labels=["q1", "q2"],
224225
)
225226

226227
# ensure we loaded the main model
227228
assert model.load_calls == [("TID", surr, f"{surr.lower()}_main")]
228229

229-
# since preds == targets, all errors should be zero
230+
# ensure that each m
230231
for key in [
231232
"root_mean_squared_error_log",
232233
"mean_absolute_error_log",
@@ -235,6 +236,7 @@ def denormalize(self, arr, leave_log=None, leave_norm=None):
235236
"mean_absolute_error",
236237
"absolute_errors",
237238
]:
239+
print(metrics[key])
238240
assert metrics[key] == pytest.approx(0.0)
239241

240242
# array shapes should be (1, T, Q)

test/test_bench_modalities.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,21 @@
1717
class DummyModel:
1818
def __init__(self, device, n_quantities, n_timesteps, n_parameters, config):
1919
self._loads = []
20+
self.n_timesteps = n_timesteps
2021

2122
def load(self, training_id, surr_name, model_identifier):
2223
self._loads.append(model_identifier)
2324

24-
def predict(self, data_loader):
25+
def predict(self, data_loader, leave_log=None, leave_norm=None):
2526
# targets always zero. Shape (batch=2, timesteps=4, quantities=1).
2627
preds = torch.rand(2, 4, 1)
2728
targets = torch.rand(2, 4, 1)
2829
return preds, targets
2930

31+
def __call__(self, inputs):
32+
# Return a dummy output tensor with 3 dims (B, T, Q)
33+
return torch.zeros(1, self.n_timesteps, 1), None
34+
3035

3136
# Two standalone fakes: one for heatmap (returns tuple), one for all others (returns None)
3237
def _fake_heatmap(*args, **kwargs):
@@ -82,7 +87,8 @@ def test_modality_variations(raw_vals, cfg_key, func, main_bs, expected_nums):
8287
cfg["batch_size"] = [main_bs]
8388

8489
timesteps = np.arange(4)
85-
loader = object()
90+
# minimal iterable loader; evaluate_batchsize will call next(iter(loader))
91+
loader = [np.zeros((1,))]
8692
labels = ["q"] if func is evaluate_interpolation else None
8793

8894
model = DummyModel(None, 1, len(timesteps), 0, {})
@@ -133,4 +139,4 @@ def test_modality_variations(raw_vals, cfg_key, func, main_bs, expected_nums):
133139
for num in expected_nums:
134140
assert f"{prefix} {num}" in metrics
135141
else:
136-
assert "average_uncertainty" in metrics
142+
assert "average_uncertainty_log" in metrics

0 commit comments

Comments
 (0)