1import numpy as np2import pytest3 4from delivery.baselines import DistanceLinear, HistoricalAverage5from delivery.data import load_fixture6from delivery.instances import generate_instance7from delivery.nn import TrainConfig, TravelTimeNN, numerical_grad_check, train8from delivery.travel_time import TimeModel9 10 11@pytest.fixture(scope="module")12def fixture_data():13 return load_fixture()14 15 16def test_backprop_matches_finite_differences():17 assert numerical_grad_check() < 1e-518 19 20def test_fixture_schema_and_filters(fixture_data):21 trips, zones, dist = fixture_data22 for c in ("pu", "do", "hour", "weekday", "duration_min", "distance_mi", "split", "date"):23 assert c in trips.columns24 assert trips.duration_min.between(1, 120).all()25 assert (trips.weekday < 5).all()26 assert len(zones) == 20 and dist.shape == (20, 20)27 # split by day: no date appears in two splits and the test week comes last28 by = trips.groupby("split").date.agg(["min", "max"])29 assert by.loc["train", "max"] < by.loc["val", "min"] <= by.loc["val", "max"] < by.loc["test", "min"]30 31 32def test_nn_trains_and_beats_global_mean_on_fixture(fixture_data, tmp_path):33 trips, zones, dist = fixture_data34 tr, va, te = (trips[trips.split == s] for s in ("train", "val", "test"))35 m = train(tr, va, len(zones), dist, TrainConfig(epochs=25, patience=5, batch=128), verbose=False)36 args = [te[c].values for c in ("pu", "do", "hour", "weekday")]37 nn_mae = np.abs(m.predict(*args) - te.duration_min.values).mean()38 mean_mae = np.abs(tr.duration_min.mean() - te.duration_min.values).mean()39 assert nn_mae < mean_mae40 m.save(tmp_path / "m.npz")41 m2 = TravelTimeNN.load(tmp_path / "m.npz")42 assert np.allclose(m.predict(*args), m2.predict(*args))43 44 45def test_baselines_fit_and_back_off(fixture_data):46 trips, zones, dist = fixture_data47 tr = trips[trips.split == "train"]48 h = HistoricalAverage(20).fit(tr)49 assert np.isfinite(h.table).all()50 lin = DistanceLinear(dist).fit(tr)51 assert lin.slope_ > 0 # longer typical distance -> longer trips52 53 54def test_generator_is_seeded():55 a = generate_instance(20, 7).to_dict()56 b = generate_instance(20, 7).to_dict()57 c = generate_instance(20, 8).to_dict()58 assert a == b and a != c59 assert all(o["latest"] > o["earliest"] for o in a["orders"])60 61 62def test_time_model_interpolation_is_fifo_and_exact_at_knots():63 M = np.stack([np.full((2, 2), 10.0 + h) for h in range(24)])64 tm = TimeModel(M, factor=1.2)65 assert tm.tt(0, 1, 8 * 60 + 30) == pytest.approx(18 * 1.2)66 assert tm.tt(0, 1, 9 * 60) == pytest.approx(18.5 * 1.2)67 assert tm.is_fifo()68 assert (tm.lower_bound_matrix(600) <= tm.M[10]).all()