Repository navigation
benchmark: add reproducible CE vs PCMCI harness and issue #18 table - #56
Conversation
…Complex-Systems-Science#18 table Implements the benchmark harness proposed on Center-For-Complex-Systems-Science#18 (nil-vn design, approved by kslote1): canonical linear-Gaussian generator (rho=0.7, p=0.2), exact (source, target, lag) matching, serial Gaussian oCSE vs PCMCI+ParCorr, median runtime + micro-averaged accuracy, profiles quick/small/full with CSV/JSON/Markdown outputs. Full table (n=5,10,20, T=500, 5 seeds) reproduces the ~32x gap at comparable accuracy. CI covers a tiny smoke path only.
|
Hi everyone! This addition is great. I get the sense that once this addition is merged, it will become our benchmark harness going forward. Is everyone happy with the design? |
|
@kslote1 the design looks good to me. Keeping the generator, adapters, metrics and runner separate makes it easy to reuse for #55. I ran it on ad6d35e and found three things worth fixing before it becomes the standard harness:
Script for 1 and 3 (run from the repo root of this branch)# Checks for #56. Run from the repo root of the PR branch: python pr56_review.py
import contextlib
import io
import numpy as np
import benchmarks.run as bench_run
from benchmarks.adapters import run_causation_entropy, run_pcmci_parcorr
from benchmarks.metrics import normalize_ce_edges, normalize_pcmci_edges
from causationentropy.graph.utils import pcmci_to_networkx
# [1] run_profile when tigramite can't be imported
def missing_tigramite(*args, **kwargs):
raise ImportError("tigramite is required for the PCMCI baseline.")
bench_run.run_pcmci_parcorr = missing_tigramite
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
rows = bench_run.run_profile(
sizes=[(5, 200), (10, 200)],
seeds=[0, 1, 2],
max_lag=2,
n_shuffles=10,
pc_alpha=0.05,
skip_pcmci=False,
verbose=False,
)
print(f"[1] without tigramite: {len(rows)} CE row(s) for 6 requested cases")
# [2] self-lags: VAR(1), autoregression 0.6 on every node, chain 0 -> 1 -> 2 -> 3
n, T = 4, 500
A = np.diag([0.6] * n) # A[i, j] != 0 means j drives i
A[1, 0] = A[2, 1] = A[3, 2] = 0.4
truth = {(j, i, 1) for i in range(n) for j in range(n) if A[i, j] != 0}
for seed in range(3):
rng = np.random.default_rng(seed)
X = np.zeros((T, n))
for t in range(1, T):
X[t] = A @ X[t - 1] + 0.1 * rng.standard_normal(n)
with contextlib.redirect_stdout(io.StringIO()):
G, _ = run_causation_entropy(X, max_lag=1, seed=seed, n_shuffles=50)
ce = normalize_ce_edges(G, [f"X{k}" for k in range(n)])
res, _ = run_pcmci_parcorr(X, tau_max=1)
pc = normalize_pcmci_edges(pcmci_to_networkx(res))
for name, found in (("CE", ce), ("PCMCI", pc)):
self_tp = sum(1 for e in found & truth if e[0] == e[1])
cross_tp = sum(1 for e in found & truth if e[0] != e[1])
print(f"[2] seed {seed} {name:5s}: self-lags {self_tp}/4, cross-links {cross_tp}/3")I used an AI assistant's help to draft this comment. I have personally checked the details, but I thought it's better to just inform you beforehand. |
…d, self-lag split) 1. run_profile no longer returns early when tigramite is missing; it warns once and continues CE-only for all remaining cases. 2. test_benchmark_harness.py skips via importorskip when the top-level benchmarks package is not installed. 3. score_edges/aggregate/markdown report cross-links as the main metric with self-lags separately (standard oCSE conditions on the target's own lags and cannot discover them). Regenerated RESULTS_issue18.md from a fresh full-profile run.
|
Thanks @Parva-26 for the review, all three fixed and verified:
|
08e2697
into
Center-For-Complex-Systems-Science:main
Implements the benchmark harness proposed on #18 (nil-vn design, approved by kslote1): canonical linear-Gaussian generator (rho=0.7, p=0.2), exact (source, target, lag) matching, serial Gaussian oCSE vs PCMCI+ParCorr, median runtime + micro-averaged accuracy, profiles quick/small/full with CSV/JSON/Markdown outputs. Full table (n=5,10,20, T=500, 5 seeds) reproduces the ~32x gap at comparable accuracy. CI covers a tiny smoke path only.