track-certify 0.1.0__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 (32) hide show
  1. track_certify-0.1.0/LICENSE +21 -0
  2. track_certify-0.1.0/MANIFEST.in +1 -0
  3. track_certify-0.1.0/PKG-INFO +174 -0
  4. track_certify-0.1.0/README.md +145 -0
  5. track_certify-0.1.0/examples/01_certify_streaming.py +22 -0
  6. track_certify-0.1.0/examples/02_design_budget.py +12 -0
  7. track_certify-0.1.0/examples/03_robust_unknown_sigma.py +19 -0
  8. track_certify-0.1.0/pyproject.toml +47 -0
  9. track_certify-0.1.0/setup.cfg +4 -0
  10. track_certify-0.1.0/src/track_certify/__init__.py +49 -0
  11. track_certify-0.1.0/src/track_certify/_vendor/__init__.py +58 -0
  12. track_certify-0.1.0/src/track_certify/_vendor/assignment_oracle.py +100 -0
  13. track_certify-0.1.0/src/track_certify/_vendor/best_alternative.py +245 -0
  14. track_certify-0.1.0/src/track_certify/_vendor/finite_ray_model.py +198 -0
  15. track_certify-0.1.0/src/track_certify/_vendor/track_and_certify_general.py +368 -0
  16. track_certify-0.1.0/src/track_certify/api.py +354 -0
  17. track_certify-0.1.0/src/track_certify/cli.py +141 -0
  18. track_certify-0.1.0/src/track_certify/design.py +176 -0
  19. track_certify-0.1.0/src/track_certify/instances.py +137 -0
  20. track_certify-0.1.0/src/track_certify/py.typed +0 -0
  21. track_certify-0.1.0/src/track_certify/robust.py +105 -0
  22. track_certify-0.1.0/src/track_certify.egg-info/PKG-INFO +174 -0
  23. track_certify-0.1.0/src/track_certify.egg-info/SOURCES.txt +30 -0
  24. track_certify-0.1.0/src/track_certify.egg-info/dependency_links.txt +1 -0
  25. track_certify-0.1.0/src/track_certify.egg-info/entry_points.txt +2 -0
  26. track_certify-0.1.0/src/track_certify.egg-info/requires.txt +5 -0
  27. track_certify-0.1.0/src/track_certify.egg-info/top_level.txt +1 -0
  28. track_certify-0.1.0/tests/test_cli.py +31 -0
  29. track_certify-0.1.0/tests/test_design.py +70 -0
  30. track_certify-0.1.0/tests/test_equivalence.py +141 -0
  31. track_certify-0.1.0/tests/test_instances_robust.py +55 -0
  32. track_certify-0.1.0/tests/test_packaging.py +37 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The Track-and-Certify authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include examples *.py
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: track-certify
3
+ Version: 0.1.0
4
+ Summary: Anytime joint certification of causal graphs and unknown intervention targets: certify-or-refuse sequential experiments with instance-optimal adaptive sampling
5
+ Author: The Track-and-Certify authors
6
+ License: MIT
7
+ Project-URL: Homepage, https://pypi.org/project/track-certify/
8
+ Keywords: causal-discovery,causal-inference,experimental-design,sequential-testing,e-process,anytime-valid,best-arm-identification,intervention-targets,fixed-confidence
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy>=1.22
25
+ Requires-Dist: scipy>=1.8
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=7; extra == "test"
28
+ Dynamic: license-file
29
+
30
+ # track-certify
31
+
32
+ **Anytime joint certification of causal graphs and unknown intervention
33
+ targets** — a certify-or-refuse layer for sequential causal experiments,
34
+ with instance-optimal adaptive sampling.
35
+
36
+ ```bash
37
+ pip install track-certify
38
+ ```
39
+
40
+ Pure Python; depends only on `numpy` and `scipy`.
41
+
42
+ ## The problem it solves
43
+
44
+ You run interventional experiments (gene perturbations, drug assays, A/B
45
+ interventions) where **the perturbed variables themselves are uncertain**: a
46
+ reagent may act off-target, weakly, or not at all. A causal conclusion is then
47
+ a *joint* claim — the graph **and** each environment's intervention target —
48
+ and every additional batch of samples costs money. Two questions matter more
49
+ than any point estimate:
50
+
51
+ 1. **Where should the next observation be taken?**
52
+ 2. **When is it safe to stop and certify the joint answer at error level δ?**
53
+
54
+ `track-certify` answers both with guarantees: an anytime-valid e-process
55
+ stopping rule (no peeking penalty, no multiplicity correction), a no-regret
56
+ allocation learner that provably attains the instance's information-theoretic
57
+ sample requirement `T*`, and an exact assignment oracle that replaces
58
+ factorial target enumeration with polynomial rectangular assignment. When it
59
+ cannot certify — budget cap reached, or model preconditions infeasible — it
60
+ **refuses rather than guesses**.
61
+
62
+ ## 60 seconds: certify a joint answer
63
+
64
+ ```python
65
+ import numpy as np
66
+ import track_certify as tc
67
+
68
+ # Declare the class: 2 candidate graphs over a known covariance,
69
+ # 2 environments with unknown, distinct targets of unknown amplitude.
70
+ sigma = np.array([[1.0, 0.5],
71
+ [0.5, 1.0]])
72
+ model = tc.build_model(sigma, {"X->Y": (0, 1), "Y->X": (1, 0)})
73
+
74
+ cert = tc.Certifier(model, n_environments=2, delta=0.01, cap=200_000)
75
+
76
+ while True:
77
+ e = cert.next_environment() # which environment to sample now
78
+ y = run_my_experiment(e) # one fresh d-vector from environment e
79
+ out = cert.observe(y)
80
+ if out.stopped:
81
+ break
82
+
83
+ out.certificate # ("X->Y", (0, 1)) — graph + per-environment targets,
84
+ # wrong with probability <= 0.01 under optional stopping;
85
+ # None if out.refused (cap reached: refusal, not a guess)
86
+ ```
87
+
88
+ The certifier never receives the true hypothesis, the amplitudes, `T*`, or
89
+ the optimal allocation.
90
+
91
+ ## Before you sample: how hard is your instance?
92
+
93
+ ```python
94
+ rep = tc.characteristic_time(model, ("X->Y", (0, 1)), amplitudes=(1.5, 1.2))
95
+ rep.tstar # first-order samples per unit log(1/delta)
96
+ rep.w_star # optimal budget split across environments
97
+ rep.active_kinds # which wrong answers dominate: 'graph' / 'target' / 'coupled'
98
+ rep.budget(0.01) # first-order sample estimate at delta = 0.01
99
+
100
+ tax = tc.coupling_tax(model, ("X->Y", (0, 1)), (1.5, 1.2))
101
+ tax.tax # how much harder JOINT certification is than the harder
102
+ # conditional problem — near the identifiability boundary
103
+ # this diverges, and "estimate targets, then certify the
104
+ # graph" becomes unboundedly suboptimal
105
+ ```
106
+
107
+ ## Unknown covariance: certify or refuse, with explicit constants
108
+
109
+ ```python
110
+ obs = collect_observational_samples() # (n0, d), no interventions
111
+ try:
112
+ setup = tc.robust_certifier(
113
+ obs, {"X->Y": (0, 1), "Y->X": (1, 0)},
114
+ n_environments=2, delta=0.01, c_max=3.0)
115
+ except tc.Refusal as r:
116
+ print("infeasible for this n0/delta/d/c_max:", r) # pre-start refusal
117
+ else:
118
+ cert = setup.certifier # threshold already carries the certified
119
+ # envelope inflation — nothing calibrated
120
+ ```
121
+
122
+ ## Benchmark instances with theorem-known difficulty
123
+
124
+ ```python
125
+ inst = tc.solvable_instance(r=0.9) # closed-form T*, coupled alternative
126
+ # provably active (paper Prop. 3)
127
+ inst = tc.scalable_instance(rs=[0.8, 0.9], cs=[1.0, 1.2])
128
+ # additive T*, 2^m graphs (Cor. 1)
129
+ res = tc.simulate(inst.model, inst.truth, inst.amplitudes, delta=1e-3,
130
+ rng=np.random.default_rng(0))
131
+ res.tau, res.correct # compare against inst.tstar_closed_form
132
+ ```
133
+
134
+ Run `track-certify demo` for a self-contained certification demo, or
135
+ `track-certify envelope --n0 500000 --delta 0.01 --d 3 --c-max 1.0` to check
136
+ robust-mode feasibility from the command line.
137
+
138
+ ## Guarantees, precisely
139
+
140
+ * **Validity.** With a correctly declared class and known covariance, the
141
+ returned joint answer is wrong with probability at most `delta`, under
142
+ arbitrary adaptive sampling and optional stopping (Gaussian mixture
143
+ e-process + Ville's inequality; no union bound over hypotheses).
144
+ * **Optimality.** Expected stopping time satisfies
145
+ `E[tau]/log(1/delta) -> T*` as `delta -> 0` — the change-of-measure lower
146
+ bound for *any* delta-correct procedure, attained.
147
+ * **Estimated covariance.** With the split-sample envelope, validity is exact
148
+ whenever the envelope's computable feasibility conditions hold; otherwise
149
+ the workflow refuses before starting. Efficiency degrades by explicit,
150
+ measured constant factors.
151
+ * **Refusal semantics.** A reached cap or an infeasible envelope produces a
152
+ refusal, never a certificate. There is no procedure here that converts
153
+ arbitrary data into a certificate.
154
+
155
+ **Model scope** (checked where checkable, refused when violated): finite
156
+ declared graph class sharing one positive-definite covariance; mean-shift
157
+ interventions with one unknown, distinct target per environment; Gaussian
158
+ noise; no hidden confounding. Duplicate or ray-identical graph declarations
159
+ are refused by default (they make hypotheses mutually uncertifiable).
160
+
161
+ ## Relation to the paper
162
+
163
+ This package accompanies *Track-and-Certify: Anytime Joint Certification of
164
+ Causal Graphs and Unknown Intervention Targets* (under review). The
165
+ statistical core in `track_certify/_vendor/` is vendored **byte-identically**
166
+ from the paper's audited evidence archive — the same code that produced every
167
+ number in the paper — and `tests/test_equivalence.py` proves trajectory
168
+ equivalence (identical actions, decisions, stopping times, and counts) between
169
+ the streaming `Certifier` and the audited driver over shared noise tapes, on
170
+ five instances under two allocation policies.
171
+
172
+ ## License
173
+
174
+ MIT.
@@ -0,0 +1,145 @@
1
+ # track-certify
2
+
3
+ **Anytime joint certification of causal graphs and unknown intervention
4
+ targets** — a certify-or-refuse layer for sequential causal experiments,
5
+ with instance-optimal adaptive sampling.
6
+
7
+ ```bash
8
+ pip install track-certify
9
+ ```
10
+
11
+ Pure Python; depends only on `numpy` and `scipy`.
12
+
13
+ ## The problem it solves
14
+
15
+ You run interventional experiments (gene perturbations, drug assays, A/B
16
+ interventions) where **the perturbed variables themselves are uncertain**: a
17
+ reagent may act off-target, weakly, or not at all. A causal conclusion is then
18
+ a *joint* claim — the graph **and** each environment's intervention target —
19
+ and every additional batch of samples costs money. Two questions matter more
20
+ than any point estimate:
21
+
22
+ 1. **Where should the next observation be taken?**
23
+ 2. **When is it safe to stop and certify the joint answer at error level δ?**
24
+
25
+ `track-certify` answers both with guarantees: an anytime-valid e-process
26
+ stopping rule (no peeking penalty, no multiplicity correction), a no-regret
27
+ allocation learner that provably attains the instance's information-theoretic
28
+ sample requirement `T*`, and an exact assignment oracle that replaces
29
+ factorial target enumeration with polynomial rectangular assignment. When it
30
+ cannot certify — budget cap reached, or model preconditions infeasible — it
31
+ **refuses rather than guesses**.
32
+
33
+ ## 60 seconds: certify a joint answer
34
+
35
+ ```python
36
+ import numpy as np
37
+ import track_certify as tc
38
+
39
+ # Declare the class: 2 candidate graphs over a known covariance,
40
+ # 2 environments with unknown, distinct targets of unknown amplitude.
41
+ sigma = np.array([[1.0, 0.5],
42
+ [0.5, 1.0]])
43
+ model = tc.build_model(sigma, {"X->Y": (0, 1), "Y->X": (1, 0)})
44
+
45
+ cert = tc.Certifier(model, n_environments=2, delta=0.01, cap=200_000)
46
+
47
+ while True:
48
+ e = cert.next_environment() # which environment to sample now
49
+ y = run_my_experiment(e) # one fresh d-vector from environment e
50
+ out = cert.observe(y)
51
+ if out.stopped:
52
+ break
53
+
54
+ out.certificate # ("X->Y", (0, 1)) — graph + per-environment targets,
55
+ # wrong with probability <= 0.01 under optional stopping;
56
+ # None if out.refused (cap reached: refusal, not a guess)
57
+ ```
58
+
59
+ The certifier never receives the true hypothesis, the amplitudes, `T*`, or
60
+ the optimal allocation.
61
+
62
+ ## Before you sample: how hard is your instance?
63
+
64
+ ```python
65
+ rep = tc.characteristic_time(model, ("X->Y", (0, 1)), amplitudes=(1.5, 1.2))
66
+ rep.tstar # first-order samples per unit log(1/delta)
67
+ rep.w_star # optimal budget split across environments
68
+ rep.active_kinds # which wrong answers dominate: 'graph' / 'target' / 'coupled'
69
+ rep.budget(0.01) # first-order sample estimate at delta = 0.01
70
+
71
+ tax = tc.coupling_tax(model, ("X->Y", (0, 1)), (1.5, 1.2))
72
+ tax.tax # how much harder JOINT certification is than the harder
73
+ # conditional problem — near the identifiability boundary
74
+ # this diverges, and "estimate targets, then certify the
75
+ # graph" becomes unboundedly suboptimal
76
+ ```
77
+
78
+ ## Unknown covariance: certify or refuse, with explicit constants
79
+
80
+ ```python
81
+ obs = collect_observational_samples() # (n0, d), no interventions
82
+ try:
83
+ setup = tc.robust_certifier(
84
+ obs, {"X->Y": (0, 1), "Y->X": (1, 0)},
85
+ n_environments=2, delta=0.01, c_max=3.0)
86
+ except tc.Refusal as r:
87
+ print("infeasible for this n0/delta/d/c_max:", r) # pre-start refusal
88
+ else:
89
+ cert = setup.certifier # threshold already carries the certified
90
+ # envelope inflation — nothing calibrated
91
+ ```
92
+
93
+ ## Benchmark instances with theorem-known difficulty
94
+
95
+ ```python
96
+ inst = tc.solvable_instance(r=0.9) # closed-form T*, coupled alternative
97
+ # provably active (paper Prop. 3)
98
+ inst = tc.scalable_instance(rs=[0.8, 0.9], cs=[1.0, 1.2])
99
+ # additive T*, 2^m graphs (Cor. 1)
100
+ res = tc.simulate(inst.model, inst.truth, inst.amplitudes, delta=1e-3,
101
+ rng=np.random.default_rng(0))
102
+ res.tau, res.correct # compare against inst.tstar_closed_form
103
+ ```
104
+
105
+ Run `track-certify demo` for a self-contained certification demo, or
106
+ `track-certify envelope --n0 500000 --delta 0.01 --d 3 --c-max 1.0` to check
107
+ robust-mode feasibility from the command line.
108
+
109
+ ## Guarantees, precisely
110
+
111
+ * **Validity.** With a correctly declared class and known covariance, the
112
+ returned joint answer is wrong with probability at most `delta`, under
113
+ arbitrary adaptive sampling and optional stopping (Gaussian mixture
114
+ e-process + Ville's inequality; no union bound over hypotheses).
115
+ * **Optimality.** Expected stopping time satisfies
116
+ `E[tau]/log(1/delta) -> T*` as `delta -> 0` — the change-of-measure lower
117
+ bound for *any* delta-correct procedure, attained.
118
+ * **Estimated covariance.** With the split-sample envelope, validity is exact
119
+ whenever the envelope's computable feasibility conditions hold; otherwise
120
+ the workflow refuses before starting. Efficiency degrades by explicit,
121
+ measured constant factors.
122
+ * **Refusal semantics.** A reached cap or an infeasible envelope produces a
123
+ refusal, never a certificate. There is no procedure here that converts
124
+ arbitrary data into a certificate.
125
+
126
+ **Model scope** (checked where checkable, refused when violated): finite
127
+ declared graph class sharing one positive-definite covariance; mean-shift
128
+ interventions with one unknown, distinct target per environment; Gaussian
129
+ noise; no hidden confounding. Duplicate or ray-identical graph declarations
130
+ are refused by default (they make hypotheses mutually uncertifiable).
131
+
132
+ ## Relation to the paper
133
+
134
+ This package accompanies *Track-and-Certify: Anytime Joint Certification of
135
+ Causal Graphs and Unknown Intervention Targets* (under review). The
136
+ statistical core in `track_certify/_vendor/` is vendored **byte-identically**
137
+ from the paper's audited evidence archive — the same code that produced every
138
+ number in the paper — and `tests/test_equivalence.py` proves trajectory
139
+ equivalence (identical actions, decisions, stopping times, and counts) between
140
+ the streaming `Certifier` and the audited driver over shared noise tapes, on
141
+ five instances under two allocation policies.
142
+
143
+ ## License
144
+
145
+ MIT.
@@ -0,0 +1,22 @@
1
+ """Streaming certification on a two-graph class (known covariance)."""
2
+ import numpy as np
3
+ import track_certify as tc
4
+ from track_and_certify_general import true_means # vendored helper
5
+
6
+ sigma = np.array([[1.0, 0.5], [0.5, 1.0]])
7
+ model = tc.build_model(sigma, {"X->Y": (0, 1), "Y->X": (1, 0)})
8
+
9
+ # Simulated lab: truth is X->Y with env targets (node0, node1), amplitudes (1.5, 1.2)
10
+ truth, amps = ("X->Y", (0, 1)), (1.5, 1.2)
11
+ rng = np.random.default_rng(7)
12
+ chol = np.linalg.cholesky(sigma)
13
+ means = true_means(model, truth, amps)
14
+
15
+ cert = tc.Certifier(model, n_environments=2, delta=0.01, cap=100_000)
16
+ while True:
17
+ e = cert.next_environment()
18
+ y = rng.normal(size=2) @ chol.T + means[e] # your experiment here
19
+ out = cert.observe(y)
20
+ if out.stopped:
21
+ break
22
+ print("certificate:", out.certificate, "| tau:", out.t, "| counts:", out.counts)
@@ -0,0 +1,12 @@
1
+ """Pre-experiment design: T*, optimal allocation, coupling tax."""
2
+ import track_certify as tc
3
+
4
+ inst = tc.solvable_instance(r=0.9, c1=1.0, c2=1.0)
5
+ rep = tc.characteristic_time(inst.model, inst.truth, inst.amplitudes)
6
+ tax = tc.coupling_tax(inst.model, inst.truth, inst.amplitudes)
7
+ print("T* (LP) :", round(rep.tstar, 4), "| closed form:",
8
+ round(inst.tstar_closed_form, 4))
9
+ print("optimal split :", tuple(round(w, 3) for w in rep.w_star))
10
+ print("active kinds :", rep.active_kinds)
11
+ print("coupling tax :", round(tax.tax, 3))
12
+ print("budget @ 1e-3 :", round(rep.budget(1e-3), 1), "samples (first order)")
@@ -0,0 +1,19 @@
1
+ """Estimated-covariance workflow: certify or refuse with explicit constants."""
2
+ import numpy as np
3
+ import track_certify as tc
4
+
5
+ rng = np.random.default_rng(0)
6
+ sigma_true = np.array([[1.0, 0.5], [0.5, 1.0]])
7
+ n0 = 600_000
8
+ obs = rng.multivariate_normal(np.zeros(2), sigma_true, size=n0)
9
+
10
+ try:
11
+ setup = tc.robust_certifier(obs, {"X->Y": (0, 1), "Y->X": (1, 0)},
12
+ n_environments=2, delta=0.01, c_max=3.0)
13
+ except tc.Refusal as r:
14
+ print("pre-start refusal:", r)
15
+ else:
16
+ env = setup.envelope
17
+ print(f"feasible: eps={env.eps:.4f}, eta_bar={env.eta_bar:.4f}, "
18
+ f"beta_scale={env.beta_scale:.3f}, drift={env.beta_drift:.2e}/step")
19
+ print("certifier ready:", type(setup.certifier).__name__)
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "track-certify"
7
+ version = "0.1.0"
8
+ description = "Anytime joint certification of causal graphs and unknown intervention targets: certify-or-refuse sequential experiments with instance-optimal adaptive sampling"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "The Track-and-Certify authors"}]
13
+ keywords = [
14
+ "causal-discovery", "causal-inference", "experimental-design",
15
+ "sequential-testing", "e-process", "anytime-valid", "best-arm-identification",
16
+ "intervention-targets", "fixed-confidence",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Intended Audience :: Science/Research",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Scientific/Engineering :: Mathematics",
30
+ "Typing :: Typed",
31
+ ]
32
+ dependencies = ["numpy>=1.22", "scipy>=1.8"]
33
+
34
+ [project.optional-dependencies]
35
+ test = ["pytest>=7"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://pypi.org/project/track-certify/"
39
+
40
+ [project.scripts]
41
+ track-certify = "track_certify.cli:main"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.setuptools.package-data]
47
+ track_certify = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,49 @@
1
+ """track-certify: anytime joint certification of causal graphs and unknown
2
+ intervention targets.
3
+
4
+ Given a declared finite class of candidate causal graphs over a shared
5
+ covariance, and K interventional environments whose targets and amplitudes
6
+ are unknown, this package answers three questions with guarantees:
7
+
8
+ * **Where to sample next, and when to stop.** :class:`Certifier` streams:
9
+ it emits the next environment to sample, consumes one fresh observation at
10
+ a time, and stops with a jointly certified (graph, target-vector) answer
11
+ whose error probability is at most ``delta`` under optional stopping -- or
12
+ with a refusal, never a guess.
13
+ * **How hard the problem is, before sampling.** :func:`characteristic_time`
14
+ returns the instance's first-order sample requirement ``T*`` and the
15
+ optimal budget split across environments; :func:`coupling_tax` quantifies
16
+ how much harder joint certification is than its staged relaxations.
17
+ * **What survives an estimated covariance.** :func:`certified_envelope` and
18
+ :func:`robust_certifier` implement the split-sample workflow with fully
19
+ explicit constants, refusing before starting when the envelope is
20
+ infeasible.
21
+
22
+ Ready-made benchmark instances with theorem-known difficulty are in
23
+ :func:`solvable_instance` and :func:`scalable_instance`; the audited
24
+ simulation driver behind the paper's experiments is exposed as
25
+ :func:`simulate`.
26
+
27
+ The statistical core is vendored byte-identically from the audited research
28
+ archive; ``tests/test_equivalence.py`` proves trajectory equivalence between
29
+ the streaming interface and that driver.
30
+ """
31
+
32
+ from .api import (Certifier, Envelope, Refusal, StepOutcome, build_model,
33
+ certified_envelope)
34
+ from .design import (CouplingReport, DesignReport, characteristic_time,
35
+ coupling_tax)
36
+ from .instances import Instance, scalable_instance, solvable_instance
37
+ from .robust import RobustSetup, estimate_covariance, robust_certifier
38
+ from . import _vendor # noqa: F401
39
+ from track_and_certify_general import run_general as simulate # noqa: E402
40
+
41
+ __all__ = [
42
+ "Certifier", "Envelope", "Refusal", "StepOutcome", "build_model",
43
+ "certified_envelope",
44
+ "DesignReport", "CouplingReport", "characteristic_time", "coupling_tax",
45
+ "Instance", "solvable_instance", "scalable_instance",
46
+ "RobustSetup", "estimate_covariance", "robust_certifier",
47
+ "simulate",
48
+ ]
49
+ __version__ = "0.1.0"
@@ -0,0 +1,58 @@
1
+ """Vendored audited modules, byte-identical to the paper's evidence archive.
2
+
3
+ The four modules import one another by their original top-level names
4
+ (``assignment_oracle``, ``best_alternative``, ``finite_ray_model``,
5
+ ``track_and_certify_general``). To keep them byte-identical, this package
6
+ loads them from files in dependency order and registers them in
7
+ ``sys.modules`` under those names -- **without** touching ``sys.path``, so
8
+ nothing else on the import path is shadowed.
9
+
10
+ If one of these names is already imported from somewhere else (a user module
11
+ with the same name), loading raises ``ImportError`` immediately with an
12
+ explanation instead of silently mixing implementations. Each module is also
13
+ registered under ``track_certify._vendor.<name>`` for unambiguous access.
14
+ """
15
+
16
+ import importlib.util
17
+ import os
18
+ import sys
19
+
20
+ _HERE = os.path.dirname(os.path.abspath(__file__))
21
+ _ORDER = (
22
+ "assignment_oracle",
23
+ "best_alternative",
24
+ "finite_ray_model",
25
+ "track_and_certify_general",
26
+ )
27
+
28
+
29
+ def _load(name):
30
+ path = os.path.join(_HERE, name + ".py")
31
+ existing = sys.modules.get(name)
32
+ if existing is not None:
33
+ existing_file = getattr(existing, "__file__", None)
34
+ if existing_file is None or \
35
+ os.path.dirname(os.path.abspath(existing_file)) != _HERE:
36
+ raise ImportError(
37
+ f"track-certify vendors an audited module named {name!r}, "
38
+ f"but a different module with that name is already imported "
39
+ f"from {existing_file!r}. Import track_certify before the "
40
+ f"conflicting module, or rename it.")
41
+ return existing
42
+ spec = importlib.util.spec_from_file_location(name, path)
43
+ module = importlib.util.module_from_spec(spec)
44
+ sys.modules[name] = module
45
+ try:
46
+ spec.loader.exec_module(module)
47
+ except Exception:
48
+ del sys.modules[name]
49
+ raise
50
+ return module
51
+
52
+
53
+ for _name in _ORDER:
54
+ _mod = _load(_name)
55
+ sys.modules[__name__ + "." + _name] = _mod
56
+ globals()[_name] = _mod
57
+
58
+ del _name, _mod
@@ -0,0 +1,100 @@
1
+ """Exact target-assignment oracle for joint graph--target certification.
2
+
3
+ For a fixed graph, each row is an intervention environment and each column is
4
+ a candidate target. The certification loss is additive across rows and the
5
+ main model requires distinct targets, so the best target vector is a
6
+ rectangular linear assignment. No hypothesis is pruned.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Dict, Hashable, Iterable, Optional, Sequence, Tuple
11
+
12
+ import numpy as np
13
+ from scipy.optimize import linear_sum_assignment
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class AssignmentSolution:
18
+ cost: float
19
+ targets: Tuple[int, ...]
20
+
21
+
22
+ def _checked_cost(cost: np.ndarray) -> np.ndarray:
23
+ arr = np.asarray(cost, dtype=float)
24
+ if arr.ndim != 2:
25
+ raise ValueError("cost must be a K-by-d matrix")
26
+ k, d = arr.shape
27
+ if k == 0 or k > d:
28
+ raise ValueError("distinct-target assignment requires 1 <= K <= d")
29
+ if np.isnan(arr).any():
30
+ raise ValueError("cost contains NaN")
31
+ return arr
32
+
33
+
34
+ def best_assignment(
35
+ cost: np.ndarray,
36
+ forbidden_pairs: Iterable[Tuple[int, int]] = (),
37
+ ) -> Optional[AssignmentSolution]:
38
+ """Return the minimum-cost injective target vector, or None if infeasible."""
39
+
40
+ arr = _checked_cost(cost).copy()
41
+ for e, target in forbidden_pairs:
42
+ if not (0 <= e < arr.shape[0] and 0 <= target < arr.shape[1]):
43
+ raise ValueError("forbidden pair is outside the cost matrix")
44
+ arr[e, target] = np.inf
45
+ try:
46
+ rows, cols = linear_sum_assignment(arr)
47
+ except ValueError:
48
+ return None
49
+ if len(rows) != arr.shape[0] or not np.isfinite(arr[rows, cols]).all():
50
+ return None
51
+ targets = np.empty(arr.shape[0], dtype=int)
52
+ targets[rows] = cols
53
+ return AssignmentSolution(float(arr[rows, cols].sum()), tuple(targets.tolist()))
54
+
55
+
56
+ def best_distinct_assignment(
57
+ cost: np.ndarray,
58
+ incumbent_targets: Sequence[int],
59
+ ) -> Optional[AssignmentSolution]:
60
+ """Return the best injective assignment different from the incumbent.
61
+
62
+ Every different assignment omits at least one incumbent row--target pair.
63
+ Solving K assignments, each forbidding one such pair, is therefore exact.
64
+ """
65
+
66
+ arr = _checked_cost(cost)
67
+ incumbent = tuple(int(t) for t in incumbent_targets)
68
+ if len(incumbent) != arr.shape[0] or len(set(incumbent)) != len(incumbent):
69
+ raise ValueError("incumbent_targets must be an injective K-vector")
70
+ if any(t < 0 or t >= arr.shape[1] for t in incumbent):
71
+ raise ValueError("incumbent target is outside the cost matrix")
72
+
73
+ best: Optional[AssignmentSolution] = None
74
+ for e, target in enumerate(incumbent):
75
+ candidate = best_assignment(arr, forbidden_pairs=((e, target),))
76
+ if candidate is not None and (best is None or candidate.cost < best.cost):
77
+ best = candidate
78
+ return best
79
+
80
+
81
+ def best_wrong_hypothesis(
82
+ graph_costs: Dict[Hashable, np.ndarray],
83
+ incumbent_graph: Hashable,
84
+ incumbent_targets: Sequence[int],
85
+ ) -> Tuple[Hashable, AssignmentSolution]:
86
+ """Return the exact lowest-loss graph--target answer excluding incumbent."""
87
+
88
+ if incumbent_graph not in graph_costs:
89
+ raise ValueError("incumbent graph is absent from graph_costs")
90
+ answer = None
91
+ for graph, cost in graph_costs.items():
92
+ if graph == incumbent_graph:
93
+ solution = best_distinct_assignment(cost, incumbent_targets)
94
+ else:
95
+ solution = best_assignment(cost)
96
+ if solution is not None and (answer is None or solution.cost < answer[1].cost):
97
+ answer = (graph, solution)
98
+ if answer is None:
99
+ raise ValueError("the declared class contains no alternative hypothesis")
100
+ return answer