From a6d22f7ec44491a4ab36d0c42960875504394288 Mon Sep 17 00:00:00 2001 From: PrasannaPal21 Date: Thu, 19 Feb 2026 17:05:56 +0530 Subject: [PATCH 1/3] add bottle_matchup tests to test_calibration.py --- tests/test_calibration.py | 73 ++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/tests/test_calibration.py b/tests/test_calibration.py index 38fe6bf..12c1cc6 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -1,11 +1,66 @@ -from glidertools.calibration import ( # noqa - bottle_matchup, - model_figs, - model_metrics, - robust_linear_fit, -) +import numpy as np +from glidertools.calibration import bottle_matchup -def test_dummy(): - """WE REALLY NEED TO ADD TESTS!!! THESE JUST TEST THE BASIC IMPORT!!!""" - assert 1 == 1 + +def test_bottle_matchup_match(): + # one dive, 5 depth points, bottle sample close in time and depth + gld_dives = np.array([1.0, 1.0, 1.0, 1.0, 1.0]) + gld_depth = np.array([10.0, 20.0, 30.0, 40.0, 50.0]) + base = np.datetime64("2020-01-01T12:00") + gld_time = np.array([base + np.timedelta64(i, "m") for i in range(5)]) + + # bottle at ~30m, taken 10 min after glider start + btl_depth = np.array([30.0]) + btl_time = np.array([base + np.timedelta64(10, "m")]) + btl_values = np.array([99.9]) + + result = bottle_matchup( + gld_dives, gld_depth, gld_time, + btl_depth, btl_time, btl_values, + ) + + # should match at index 2 (depth=30) + assert result[2] == 99.9 + # everything else should be nan + assert np.isnan(result[0]) + assert np.isnan(result[1]) + assert np.isnan(result[3]) + assert np.isnan(result[4]) + + +def test_bottle_matchup_no_match_time(): + # bottle sample too far in time (>120 min default) + gld_dives = np.array([1.0, 1.0, 1.0]) + gld_depth = np.array([10.0, 20.0, 30.0]) + base = np.datetime64("2020-01-01T12:00") + gld_time = np.array([base + np.timedelta64(i, "m") for i in range(3)]) + + btl_depth = np.array([20.0]) + btl_time = np.array([base + np.timedelta64(200, "m")]) # 200 min away + btl_values = np.array([50.0]) + + result = bottle_matchup( + gld_dives, gld_depth, gld_time, + btl_depth, btl_time, btl_values, + ) + # nothing should match + assert np.all(np.isnan(result)) + + +def test_bottle_matchup_no_match_depth(): + # bottle close in time but depth diff > 5m threshold + gld_dives = np.array([1.0, 1.0, 1.0]) + gld_depth = np.array([10.0, 20.0, 30.0]) + base = np.datetime64("2020-01-01T12:00") + gld_time = np.array([base + np.timedelta64(i, "m") for i in range(3)]) + + btl_depth = np.array([100.0]) # way deeper than any glider point + btl_time = np.array([base + np.timedelta64(1, "m")]) + btl_values = np.array([50.0]) + + result = bottle_matchup( + gld_dives, gld_depth, gld_time, + btl_depth, btl_time, btl_values, + ) + assert np.all(np.isnan(result)) From 96e10a98fea56cd8173540cf4974b0ec0d11d3ea Mon Sep 17 00:00:00 2001 From: PrasannaPal21 Date: Fri, 20 Feb 2026 00:13:11 +0530 Subject: [PATCH 2/3] add model_metrics, model_figs and robust_linear_fit tests --- tests/test_calibration.py | 95 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/tests/test_calibration.py b/tests/test_calibration.py index 12c1cc6..4e78fb0 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -1,6 +1,13 @@ import numpy as np +import pytest -from glidertools.calibration import bottle_matchup +from glidertools.calibration import ( + bottle_matchup, + model_figs, + model_metrics, + robust_linear_fit, +) +from glidertools.helpers import GliderToolsError def test_bottle_matchup_match(): @@ -64,3 +71,89 @@ def test_bottle_matchup_no_match_depth(): btl_depth, btl_time, btl_values, ) assert np.all(np.isnan(result)) + + +def _fit_huber(x, y): + """quick helper so we dont repeat the fitting boilerplate""" + from sklearn.linear_model import HuberRegressor + m = HuberRegressor(fit_intercept=False) + m.fit(x.reshape(-1, 1), y) + return m + + +def test_model_metrics_keys(): + x = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + y = 2.0 * x + model = _fit_huber(x, y) + result = model_metrics(x, y, model) + + # check that all the keys we expect are present + for k in ("model_type", "model_slope", "model_intercept", + "r2_all", "r2_robust", "rmse_all", "rmse_robust"): + assert k in result + + +def test_model_metrics_perfect_fit(): + # perfect y = 2x, so r2 should be 1 and rmse 0 + x = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + y = 2.0 * x + model = _fit_huber(x, y) + result = model_metrics(x, y, model) + + assert result["r2_all"] == pytest.approx(1.0, abs=1e-6) + assert result["rmse_all"] == pytest.approx(0.0, abs=1e-6) + assert result["model_slope"] == pytest.approx(2.0, abs=1e-4) + + +def test_model_figs_returns_axes(): + import matplotlib + matplotlib.use("Agg") + from matplotlib.axes import Axes + + x = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + y = 2.0 * x + model = _fit_huber(x, y) + + ax = model_figs(y, x, model) + assert isinstance(ax, Axes) + + +def test_robust_linear_fit_slope(): + gld_var = np.arange(1.0, 11.0) + + # simulate bottle matchup output - mostly nans with a few real values + gld_var_cal = np.full(10, np.nan) + gld_var_cal[0] = 2.0 + gld_var_cal[4] = 10.0 + gld_var_cal[9] = 20.0 + + model = robust_linear_fit(gld_var, gld_var_cal, return_figures=False) + assert model.coef_[0] == pytest.approx(2.0, abs=0.1) + + +def test_robust_linear_fit_nan_predict(): + gld_var = np.arange(1.0, 11.0) + gld_var_cal = np.full(10, np.nan) + gld_var_cal[0] = 2.0 + gld_var_cal[4] = 10.0 + gld_var_cal[9] = 20.0 + + model = robust_linear_fit(gld_var, gld_var_cal, return_figures=False) + + test_input = np.array([1.0, np.nan, 3.0, np.nan, 5.0]) + out = model.predict(test_input) + + # nans in, nans out + assert np.isnan(out[1]) + assert np.isnan(out[3]) + assert not np.isnan(out[0]) + assert not np.isnan(out[2]) + assert not np.isnan(out[4]) + + +def test_robust_linear_fit_all_nan_raises(): + gld_var = np.arange(1.0, 6.0) + gld_var_cal = np.full(5, np.nan) + + with pytest.raises(GliderToolsError): + robust_linear_fit(gld_var, gld_var_cal, return_figures=False) From b7da25942bd762f43f86b9ff9f5c7c312f78fe7e Mon Sep 17 00:00:00 2001 From: PrasannaPal21 Date: Fri, 20 Feb 2026 00:39:01 +0530 Subject: [PATCH 3/3] add remaining calibration tests --- tests/test_calibration.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/test_calibration.py b/tests/test_calibration.py index 4e78fb0..1ac5058 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -23,8 +23,12 @@ def test_bottle_matchup_match(): btl_values = np.array([99.9]) result = bottle_matchup( - gld_dives, gld_depth, gld_time, - btl_depth, btl_time, btl_values, + gld_dives, + gld_depth, + gld_time, + btl_depth, + btl_time, + btl_values, ) # should match at index 2 (depth=30) @@ -48,8 +52,12 @@ def test_bottle_matchup_no_match_time(): btl_values = np.array([50.0]) result = bottle_matchup( - gld_dives, gld_depth, gld_time, - btl_depth, btl_time, btl_values, + gld_dives, + gld_depth, + gld_time, + btl_depth, + btl_time, + btl_values, ) # nothing should match assert np.all(np.isnan(result)) @@ -67,8 +75,12 @@ def test_bottle_matchup_no_match_depth(): btl_values = np.array([50.0]) result = bottle_matchup( - gld_dives, gld_depth, gld_time, - btl_depth, btl_time, btl_values, + gld_dives, + gld_depth, + gld_time, + btl_depth, + btl_time, + btl_values, ) assert np.all(np.isnan(result)) @@ -76,6 +88,7 @@ def test_bottle_matchup_no_match_depth(): def _fit_huber(x, y): """quick helper so we dont repeat the fitting boilerplate""" from sklearn.linear_model import HuberRegressor + m = HuberRegressor(fit_intercept=False) m.fit(x.reshape(-1, 1), y) return m @@ -88,8 +101,15 @@ def test_model_metrics_keys(): result = model_metrics(x, y, model) # check that all the keys we expect are present - for k in ("model_type", "model_slope", "model_intercept", - "r2_all", "r2_robust", "rmse_all", "rmse_robust"): + for k in ( + "model_type", + "model_slope", + "model_intercept", + "r2_all", + "r2_robust", + "rmse_all", + "rmse_robust", + ): assert k in result @@ -107,6 +127,7 @@ def test_model_metrics_perfect_fit(): def test_model_figs_returns_axes(): import matplotlib + matplotlib.use("Agg") from matplotlib.axes import Axes