certflow 1.0.1__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {certflow-1.0.1 → certflow-1.0.2}/CHANGELOG.md +22 -0
  2. {certflow-1.0.1 → certflow-1.0.2}/CITATION.cff +1 -0
  3. {certflow-1.0.1 → certflow-1.0.2}/PKG-INFO +7 -4
  4. {certflow-1.0.1 → certflow-1.0.2}/README.md +3 -3
  5. {certflow-1.0.1 → certflow-1.0.2}/pyproject.toml +3 -2
  6. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/__init__.py +1 -1
  7. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/harness.py +3 -0
  8. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/realworld.py +8 -1
  9. {certflow-1.0.1 → certflow-1.0.2}/tests/test_harness.py +29 -0
  10. {certflow-1.0.1 → certflow-1.0.2}/tests/test_realworld.py +32 -2
  11. {certflow-1.0.1 → certflow-1.0.2}/.gitignore +0 -0
  12. {certflow-1.0.1 → certflow-1.0.2}/LICENSE +0 -0
  13. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/baselines.py +0 -0
  14. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/cert.py +0 -0
  15. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/ch.py +0 -0
  16. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/conformal.py +0 -0
  17. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/drift.py +0 -0
  18. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/egraph.py +0 -0
  19. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/episodes.py +0 -0
  20. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/fastgraph.py +0 -0
  21. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/graphcore.py +0 -0
  22. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/movingai.py +0 -0
  23. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/oracle.py +0 -0
  24. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/roadnet.py +0 -0
  25. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/sensing.py +0 -0
  26. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/snapshot.py +0 -0
  27. {certflow-1.0.1 → certflow-1.0.2}/src/certflow/types.py +0 -0
  28. {certflow-1.0.1 → certflow-1.0.2}/tests/test_baselines.py +0 -0
  29. {certflow-1.0.1 → certflow-1.0.2}/tests/test_cert.py +0 -0
  30. {certflow-1.0.1 → certflow-1.0.2}/tests/test_ch.py +0 -0
  31. {certflow-1.0.1 → certflow-1.0.2}/tests/test_conformal.py +0 -0
  32. {certflow-1.0.1 → certflow-1.0.2}/tests/test_drift_oracle.py +0 -0
  33. {certflow-1.0.1 → certflow-1.0.2}/tests/test_egraph.py +0 -0
  34. {certflow-1.0.1 → certflow-1.0.2}/tests/test_episodes.py +0 -0
  35. {certflow-1.0.1 → certflow-1.0.2}/tests/test_fastgraph.py +0 -0
  36. {certflow-1.0.1 → certflow-1.0.2}/tests/test_graphcore.py +0 -0
  37. {certflow-1.0.1 → certflow-1.0.2}/tests/test_movingai.py +0 -0
  38. {certflow-1.0.1 → certflow-1.0.2}/tests/test_movingai_experiment.py +0 -0
  39. {certflow-1.0.1 → certflow-1.0.2}/tests/test_roadnet.py +0 -0
  40. {certflow-1.0.1 → certflow-1.0.2}/tests/test_snapshot.py +0 -0
  41. {certflow-1.0.1 → certflow-1.0.2}/tests/test_state_sync.py +0 -0
  42. {certflow-1.0.1 → certflow-1.0.2}/tests/test_tier1_smoke.py +0 -0
  43. {certflow-1.0.1 → certflow-1.0.2}/tests/test_tier2_smoke.py +0 -0
@@ -4,6 +4,27 @@ All notable changes to CERT-FLOW are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.0.2] - 2026-06-10
8
+
9
+ Packaging and serialization fixes for the freshly published library.
10
+
11
+ ### Fixed
12
+ - `pytest` now discovers the `src/`-layout package on a fresh checkout: the
13
+ `[tool.pytest.ini_options]` `pythonpath` was `["."]` (repo root, no package
14
+ there), so `python -m pytest` failed with `ModuleNotFoundError: certflow`
15
+ unless `PYTHONPATH=src` was set or the package was installed. Set to
16
+ `["src"]`.
17
+ - `EpisodeResult.oracle_cost` is now serialized by `save_results` and restored
18
+ by `load_results`. It was dropped on save, so reloaded Tier-2 results came
19
+ back with `oracle_cost = nan` and any regret analysis silently reported NaN.
20
+ Legacy result files without the field still load (oracle_cost stays nan).
21
+
22
+ ### Added
23
+ - `realworld` optional extra (`pip install 'certflow[realworld]'`) declaring
24
+ the `pandas` and `tables` dependencies the METR-LA / PEMS-BAY traffic
25
+ adapter needs. The core install stays numpy/scipy only; `_load_traffic` now
26
+ raises a clear `ImportError` pointing at the extra when pandas is absent.
27
+
7
28
  ## [1.0.1] - 2026-06-10
8
29
 
9
30
  First PyPI release (`pip install certflow`).
@@ -50,5 +71,6 @@ Planning under Drifting Costs (Extended Version)*.
50
71
  sum-aware certificate, impossibility of a tighter lower bound,
51
72
  decision-uniform validity, churn floor).
52
73
 
74
+ [1.0.2]: https://github.com/Archerkattri/CERT-FLOW/releases/tag/v1.0.2
53
75
  [1.0.1]: https://github.com/Archerkattri/CERT-FLOW/releases/tag/v1.0.1
54
76
  [1.0.0]: https://github.com/Archerkattri/CERT-FLOW/releases/tag/v1.0.0
@@ -2,6 +2,7 @@ cff-version: 1.2.0
2
2
  message: "If you use CERT-FLOW in your research, please cite it as below."
3
3
  type: software
4
4
  title: "CERT-FLOW: Certified Route Planning under Drifting Costs"
5
+ version: 1.0.2
5
6
  authors:
6
7
  - family-names: Attri
7
8
  given-names: Krishi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: certflow
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Certified route planning under drifting costs: conformal LB<=OPT<=UB certificates, certificate-directed sensing, proof-gated preprocessing
5
5
  Project-URL: Homepage, https://github.com/Archerkattri/CERT-FLOW
6
6
  Project-URL: Repository, https://github.com/Archerkattri/CERT-FLOW
@@ -26,13 +26,16 @@ Provides-Extra: dev
26
26
  Requires-Dist: pytest>=7.0; extra == 'dev'
27
27
  Provides-Extra: fast
28
28
  Requires-Dist: numba; extra == 'fast'
29
+ Provides-Extra: realworld
30
+ Requires-Dist: pandas>=1.5; extra == 'realworld'
31
+ Requires-Dist: tables>=3.7; extra == 'realworld'
29
32
  Description-Content-Type: text/markdown
30
33
 
31
34
  <p align="center"><img src="assets/banner.svg" alt="CERT-FLOW" width="100%"/></p>
32
35
 
33
36
  <p align="center">
34
37
  <a href="https://pypi.org/project/certflow/"><img alt="PyPI" src="https://img.shields.io/pypi/v/certflow?color=009E73"></a>
35
- <a href="#reproducing-every-number"><img alt="tests" src="https://img.shields.io/badge/tests-223%20passing-0072B2"></a>
38
+ <a href="#reproducing-every-number"><img alt="tests" src="https://img.shields.io/badge/tests-227%20passing-0072B2"></a>
36
39
  <img alt="python" src="https://img.shields.io/badge/python-3.10%2B-56B4E9">
37
40
  <img alt="license" src="https://img.shields.io/badge/license-MIT-1a7f37">
38
41
  <img alt="coverage claim" src="https://img.shields.io/badge/certificate%20coverage-1.000%20measured-D55E00">
@@ -107,8 +110,8 @@ To develop or reproduce the paper numbers, work from a clone:
107
110
  ```bash
108
111
  git clone https://github.com/Archerkattri/CERT-FLOW && cd CERT-FLOW
109
112
  python -m venv cert_env && source cert_env/bin/activate
110
- pip install -e ".[dev,fast]" pandas h5py tables
111
- pytest # full suite: 223 with datasets; data-dependent tests skip cleanly without data/
113
+ pip install -e ".[dev,fast,realworld]" h5py
114
+ pytest # full suite: 227 with datasets; data-dependent tests skip cleanly without data/
112
115
  ```
113
116
 
114
117
  ## Reproducing every number
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p align="center">
4
4
  <a href="https://pypi.org/project/certflow/"><img alt="PyPI" src="https://img.shields.io/pypi/v/certflow?color=009E73"></a>
5
- <a href="#reproducing-every-number"><img alt="tests" src="https://img.shields.io/badge/tests-223%20passing-0072B2"></a>
5
+ <a href="#reproducing-every-number"><img alt="tests" src="https://img.shields.io/badge/tests-227%20passing-0072B2"></a>
6
6
  <img alt="python" src="https://img.shields.io/badge/python-3.10%2B-56B4E9">
7
7
  <img alt="license" src="https://img.shields.io/badge/license-MIT-1a7f37">
8
8
  <img alt="coverage claim" src="https://img.shields.io/badge/certificate%20coverage-1.000%20measured-D55E00">
@@ -77,8 +77,8 @@ To develop or reproduce the paper numbers, work from a clone:
77
77
  ```bash
78
78
  git clone https://github.com/Archerkattri/CERT-FLOW && cd CERT-FLOW
79
79
  python -m venv cert_env && source cert_env/bin/activate
80
- pip install -e ".[dev,fast]" pandas h5py tables
81
- pytest # full suite: 223 with datasets; data-dependent tests skip cleanly without data/
80
+ pip install -e ".[dev,fast,realworld]" h5py
81
+ pytest # full suite: 227 with datasets; data-dependent tests skip cleanly without data/
82
82
  ```
83
83
 
84
84
  ## Reproducing every number
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "certflow"
3
- version = "1.0.1"
3
+ version = "1.0.2"
4
4
  authors = [{name = "Krishi Attri", email = "krishiattriwork@gmail.com"}]
5
5
  license = {text = "MIT"}
6
6
  description = "Certified route planning under drifting costs: conformal LB<=OPT<=UB certificates, certificate-directed sensing, proof-gated preprocessing"
@@ -41,6 +41,7 @@ Issues = "https://github.com/Archerkattri/CERT-FLOW/issues"
41
41
  [project.optional-dependencies]
42
42
  fast = ["numba"]
43
43
  dev = ["pytest>=7.0"]
44
+ realworld = ["pandas>=1.5", "tables>=3.7"]
44
45
 
45
46
  [build-system]
46
47
  requires = ["hatchling"]
@@ -61,5 +62,5 @@ include = [
61
62
  ]
62
63
 
63
64
  [tool.pytest.ini_options]
64
- pythonpath = ["."]
65
+ pythonpath = ["src"]
65
66
  testpaths = ["tests"]
@@ -26,7 +26,7 @@ from certflow.cert import CertPlanner, PlannerConfig
26
26
  from certflow.conformal import ACITracker, ConformalScorer
27
27
  from certflow.types import Certificate, EdgeBelief, World
28
28
 
29
- __version__ = "1.0.1"
29
+ __version__ = "1.0.2"
30
30
 
31
31
  __all__ = [
32
32
  "ACITracker",
@@ -426,6 +426,7 @@ def _episode_result_to_dict(ep: EpisodeResult) -> dict[str, Any]:
426
426
  "travel_cost": _float_to_json(ep.travel_cost),
427
427
  "sense_cost": _float_to_json(ep.sense_cost),
428
428
  "reached_goal": ep.reached_goal,
429
+ "oracle_cost": _float_to_json(ep.oracle_cost),
429
430
  }
430
431
 
431
432
 
@@ -435,6 +436,8 @@ def _episode_result_from_dict(d: dict[str, Any]) -> EpisodeResult:
435
436
  travel_cost=float(_float_from_json(d["travel_cost"])),
436
437
  sense_cost=float(_float_from_json(d["sense_cost"])),
437
438
  reached_goal=bool(d["reached_goal"]),
439
+ # tolerate legacy files written before oracle_cost was serialised
440
+ oracle_cost=float(_float_from_json(d.get("oracle_cost", _NAN_SENTINEL))),
438
441
  )
439
442
 
440
443
 
@@ -28,7 +28,14 @@ DATA_DIR = Path(__file__).resolve().parents[2] / "data"
28
28
  @lru_cache(maxsize=2)
29
29
  def _load_traffic(dataset: str) -> tuple[list[str], np.ndarray, dict[tuple[str, str], float]]:
30
30
  """(sensor_ids, speeds[bins x sensors] mph with missing filled, distances m)."""
31
- import pandas as pd
31
+ try:
32
+ import pandas as pd
33
+ except ImportError as exc: # pragma: no cover - exercised via a stubbed import
34
+ raise ImportError(
35
+ "The real-world traffic adapter (METR-LA / PEMS-BAY) needs pandas and "
36
+ "PyTables, which are not part of the core install. Install them with:\n"
37
+ " pip install 'certflow[realworld]'"
38
+ ) from exc
32
39
 
33
40
  if dataset == "metr-la":
34
41
  h5, dist_csv = DATA_DIR / "metr-la/metr_la.h5", DATA_DIR / "metr-la/distances_la_2012.csv"
@@ -368,6 +368,35 @@ class TestSaveLoad:
368
368
  path = save_results(result, tmp)
369
369
  assert path.name == f"{config.config_id()}.json"
370
370
 
371
+ def test_oracle_cost_round_trips(self):
372
+ """Tier-2 regret depends on oracle_cost surviving save/load (not nan)."""
373
+ config = ExperimentConfig(n_seeds=1, base_seed=0)
374
+ ep = EpisodeResult(
375
+ rounds=[],
376
+ travel_cost=12.5,
377
+ sense_cost=1.0,
378
+ reached_goal=True,
379
+ oracle_cost=9.75,
380
+ )
381
+ result = ExperimentResult(config=config, episodes=[ep], seeds=[7])
382
+ with tempfile.TemporaryDirectory() as tmp:
383
+ path = save_results(result, tmp)
384
+ loaded = load_results(path)
385
+
386
+ loaded_oracle = loaded.episodes[0].oracle_cost
387
+ assert not math.isnan(loaded_oracle)
388
+ assert math.isclose(loaded_oracle, 9.75)
389
+
390
+ def test_oracle_cost_nan_round_trips(self):
391
+ """Tier-1 episodes leave oracle_cost as nan; that must survive too."""
392
+ config = ExperimentConfig(n_seeds=1, base_seed=0)
393
+ ep = EpisodeResult(rounds=[], travel_cost=1.0, sense_cost=0.0, reached_goal=False)
394
+ result = ExperimentResult(config=config, episodes=[ep], seeds=[0])
395
+ with tempfile.TemporaryDirectory() as tmp:
396
+ path = save_results(result, tmp)
397
+ loaded = load_results(path)
398
+ assert math.isnan(loaded.episodes[0].oracle_cost)
399
+
371
400
  def test_sensed_edge_tuple_roundtrip(self):
372
401
  round_log = RoundLog(
373
402
  t=0.0,
@@ -1,11 +1,37 @@
1
1
  """Smoke tests for the METR-LA replay world (skipped if data absent)."""
2
- import math
2
+ import builtins
3
3
  from pathlib import Path
4
4
 
5
5
  import pytest
6
6
 
7
7
  DATA = Path(__file__).resolve().parents[1] / "data/metr-la/metr_la.h5"
8
- pytestmark = pytest.mark.skipif(not DATA.exists(), reason="METR-LA not downloaded")
8
+
9
+
10
+ def test_core_import_does_not_need_pandas():
11
+ """Importing certflow core (and realworld module) must not require pandas."""
12
+ import certflow # noqa: F401
13
+ import certflow.realworld # noqa: F401
14
+
15
+
16
+ def test_load_traffic_clear_message_without_pandas(monkeypatch):
17
+ """If pandas is absent, the adapter points users at certflow[realworld]."""
18
+ from certflow import realworld
19
+
20
+ real_import = builtins.__import__
21
+
22
+ def _no_pandas(name, *args, **kwargs):
23
+ if name == "pandas" or name.startswith("pandas."):
24
+ raise ImportError("No module named 'pandas'")
25
+ return real_import(name, *args, **kwargs)
26
+
27
+ monkeypatch.setattr(builtins, "__import__", _no_pandas)
28
+ with pytest.raises(ImportError) as exc:
29
+ realworld._load_traffic("metr-la")
30
+ assert "certflow[realworld]" in str(exc.value)
31
+
32
+
33
+ # The data-dependent smoke tests below skip cleanly when METR-LA is absent.
34
+ _skip_no_data = pytest.mark.skipif(not DATA.exists(), reason="METR-LA not downloaded")
9
35
 
10
36
 
11
37
  @pytest.fixture(scope="module")
@@ -15,6 +41,7 @@ def world():
15
41
  return TrafficWorld(seed=0, n_bins=144) # half a day
16
42
 
17
43
 
44
+ @_skip_no_data
18
45
  def test_graph_and_costs_sane(world):
19
46
  n_edges = sum(len(nbrs) for nbrs in world.graph.values())
20
47
  assert n_edges > 500
@@ -26,6 +53,7 @@ def test_graph_and_costs_sane(world):
26
53
  assert abs(world.true_cost(e, 299.0) - world.true_cost(e, 301.0)) < 5.0
27
54
 
28
55
 
56
+ @_skip_no_data
29
57
  def test_determinism_and_noise(world):
30
58
  from certflow.realworld import TrafficWorld
31
59
 
@@ -37,11 +65,13 @@ def test_determinism_and_noise(world):
37
65
  assert abs(sum(obs) / len(obs) - truth) < 2.0 # mean ~ truth (scale 5s)
38
66
 
39
67
 
68
+ @_skip_no_data
40
69
  def test_a1_violation_rate_measured(world):
41
70
  # rho is the p95 of |dc/dt|: violations exist by construction (~5%)
42
71
  assert 0.0 < world.a1_violation_rate < 0.15
43
72
 
44
73
 
74
+ @_skip_no_data
45
75
  def test_planner_runs_on_traffic(world):
46
76
  from certflow.cert import CertPlanner
47
77
  from certflow.realworld import far_endpoints, traffic_planner_config
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes