Computational companion / Diverging Paths

Diverging Paths to
Dark-Matter Discovery

Computational companion · v3.1.0

A reading edition of the computational work behind the paper. Follow the retained corpus, candidate profiles, and frozen network metrics through the shared script/notebook workflow and into the figure constructions.

Reading edition. The code is presented for reading; it does not run in the browser. Numerical results and figures are supplied references and have not been recalculated for this edition.
Stages
06
Input tables
06
Graph checks
57
Reference PDFs
09
Explore the workflow
Overview

The analysis in six stages

The preferred 3.1.0 route uses retained inputs and the same functions in its script and clean notebook. Earlier acquisition, extraction, and maintenance sources are available in the source library and remain separate from this route.

The six input tables Supplied separately
TableRoleUncompressed size
papers.parquetPaper metadata and abstracts120.8 MB
papers_with_dm_models.parquetRetained paper-level candidate tags108.3 MB
paper_arxiv_classes.parquetOrdered arXiv classifications1.6 MB
dm_model_candidates_long.parquetCandidate-family mention rows0.3 MB
paper_metrics_long.parquetRetained citation metrics5.1 MB
unigram_yearly.parquetYearly lexical counts1.6 MB

The browser edition omits datasets and network ZIPs. Two large tables are restored from lossless parts by the research package; the six inputs total 237.7 MB.

01

Verify frozen inputs

The restoration helper joins the lossless parts of the two largest tables. The workflow then checks retained data hashes and the identities and member lists of both frozen network archives. This establishes which evidence the later stages use.

Uses
Six local input tables, pinned analysis products, two frozen network ZIPs
Produces
Input and archive-member integrity report
Pythoninput_checks()
def input_checks(root):
    root = Path(root)
    load_local(
        root / "code/materialize_inputs.py", "diverging_paths_materialize"
    ).materialize(root)
    pins = json.loads((root / "checks/input-hashes.json").read_text())
    for rel, digest in pins.items():
        assert hashlib.sha256((root / rel).read_bytes()).hexdigest() == digest, rel
    release = json.loads(
        (root / "provenance/v3.0.0/release_manifest.json").read_text()
    )
    members = 0
    for archive in release["network_archives"]:
        path = root / archive["path"]
        assert path.stat().st_size == archive["bytes"]
        assert hashlib.sha256(path.read_bytes()).hexdigest() == archive["sha256"]
        with zipfile.ZipFile(path) as z:
            assert set(z.namelist()) == set(archive["members"])
            for name, digest in archive["members"].items():
                assert hashlib.sha256(z.read(name)).hexdigest() == digest, name
            members += len(z.namelist())
    return write_report(
        root,
        "input-integrity.json",
        {
            "status": "PASS",
            "pinned_data_products": len(pins),
            "network_archives": len(release["network_archives"]),
            "archive_members": members,
            "python": platform.python_version(),
            "scope": "Integrity only; no holdout annotations read",
        },
    )

Reading format; Python syntax tree matches the source.

PythonLossless input restoration
"""Restore exact frozen tables from lossless transport parts."""

from pathlib import Path
import hashlib, json, os, shutil


def sha256(path):
    h = hashlib.sha256()
    with Path(path).open("rb") as f:
        for block in iter(lambda: f.read(1048576), b""):
            h.update(block)
    return h.hexdigest()


def materialize(root):
    root = Path(root).resolve()
    data = root / "data"
    spec = json.loads((data / "INPUT-STORAGE.json").read_text())
    restored = []
    for name, entry in spec["tables"].items():
        target = data / name
        if target.exists():
            if (
                target.stat().st_size != entry["bytes"]
                or sha256(target) != entry["sha256"]
            ):
                raise ValueError(
                    "Existing input has a different identity; preserve it and use a clean checkout: "
                    + name
                )
            continue
        temporary = target.with_suffix(target.suffix + ".assembling")
        try:
            with temporary.open("wb") as output:
                for part in entry["parts"]:
                    path = data / part["file"]
                    if (
                        path.stat().st_size != part["bytes"]
                        or sha256(path) != part["sha256"]
                    ):
                        raise ValueError(
                            "Transport-part identity differs: " + part["file"]
                        )
                    with path.open("rb") as source:
                        shutil.copyfileobj(source, output)
            if (
                temporary.stat().st_size != entry["bytes"]
                or sha256(temporary) != entry["sha256"]
            ):
                raise ValueError("Restored table identity differs: " + name)
            os.replace(temporary, target)
            restored.append(name)
        finally:
            if temporary.exists():
                temporary.unlink()
    return restored


if __name__ == "__main__":
    print(
        json.dumps(
            {"restored": materialize(Path(__file__).resolve().parents[1])}, indent=2
        )
    )

Reading format; Python syntax tree matches the source.

PythonCorresponding notebook cell
reports["input_checks"] = input_checks(ROOT)
reports["input_checks"]

Reading format; Python syntax tree matches the source.

PythonNotebook setup cell
from pathlib import Path
import sys, os

ROOT = Path.cwd().resolve()
if ROOT.name == "notebooks":
    ROOT = ROOT.parent
assert (ROOT / "config/workspace.json").is_file()
sys.path.insert(0, str(ROOT / "code/modules"))
from workflow import (
    input_checks,
    corpus_checks,
    candidate_checks,
    network_checks,
    figure_checks,
    write_report,
)

reports = {}

Reading format; Python syntax tree matches the source.

Next step
02

Rebuild corpus classifications

Records are restricted to years through 2025. Sorting by class_pos selects each paper’s first arXiv classification. The code rebuilds year-by-class counts, compares them with retained tables, and writes category and class summaries.

The classified subset and full corpus have different denominators. These classifications describe research organization; they do not establish what would count as discovery.

Uses
Paper identities and years; ordered arXiv classifications
Produces
201,627 corpus records; 143,390 classified records; comparison tables
Pythoncorpus_checks()
def corpus_checks(root):
    root = Path(root)
    data = root / "data"
    out = root / "reproduced/tables"
    out.mkdir(parents=True, exist_ok=True)
    papers = pd.read_parquet(
        data / "papers.parquet", columns=["bibcode", "year", "abstract"]
    )
    papers = papers[papers.year <= 2025]
    assert len(papers) == 201627 and papers.bibcode.nunique() == len(papers)
    classes = pd.read_parquet(data / "paper_arxiv_classes.parquet").sort_values(
        ["bibcode", "class_pos"]
    )
    primary = classes.drop_duplicates("bibcode").merge(
        papers[["bibcode", "year"]], on="bibcode", validate="one_to_one"
    )
    assert len(primary) == 143390
    rebuilt = (
        primary.groupby(["year", "arxiv_class", "arxiv_category"])
        .size()
        .rename("n_papers")
        .reset_index()
    )
    frozen = pd.read_parquet(
        data / "analysis/classification_primary_yearly.parquet"
    )
    joined = rebuilt.merge(
        frozen,
        on=["year", "arxiv_class", "arxiv_category"],
        how="outer",
        suffixes=("_rebuilt", "_frozen"),
        indicator=True,
    )
    assert (
        joined["_merge"].eq("both").all()
        and joined.n_papers_rebuilt.eq(joined.n_papers_frozen).all()
    )
    volume = (
        papers.groupby("year")
        .size()
        .rename("n_papers_rebuilt")
        .reset_index()
        .merge(
            pd.read_parquet(data / "analysis/paper_volume_yearly.parquet"),
            on="year",
            validate="one_to_one",
        )
    )
    assert volume.n_papers_rebuilt.eq(volume.n_papers).all()
    categories = (
        primary.groupby("arxiv_category")
        .size()
        .sort_values(ascending=False)
        .rename("papers")
        .reset_index()
    )
    counts = (
        primary.groupby("arxiv_class")
        .size()
        .sort_values(ascending=False)
        .rename("papers")
        .reset_index()
    )
    for name, table in [
        ("primary-categories", categories),
        ("primary-classes", counts),
    ]:
        table["percent"] = 100 * table.papers / len(primary)
        table.to_csv(out / (name + ".csv"), index=False)
        lines = [
            r"\begin{tabular}{lrr}",
            r"\toprule",
            "Primary assignment & Papers & Share " + r"\\",
            r"\midrule",
        ]
        for _, row in table.head(
            4 if name.endswith("categories") else 9
        ).iterrows():
            label = str(row.iloc[0]).replace("_", r"\_")
            lines.append(
                f"{label} & {int(row.papers):,} & {row.percent:.1f}" + r"\% \\"
            )
        (out / (name + ".tex")).write_text(
            "\n".join(lines + [r"\bottomrule", r"\end{tabular}"]) + "\n"
        )
    expected = {
        "astrophysics": 78673,
        "high-energy physics": 39191,
        "general relativity and quantum cosmology": 14668,
        "physics": 6427,
    }
    actual = categories.set_index("arxiv_category").papers.to_dict()
    assert all(actual.get(k) == v for k, v in expected.items())
    return write_report(
        root,
        "corpus-checks.json",
        {
            "status": "PASS",
            "records": len(papers),
            "classified_records": len(primary),
            "primary_class_year_cells_rebuilt": len(rebuilt),
            "volume_years_rebuilt": len(volume),
            "main_table_counts_match": True,
            "largest_primary_class": counts.iloc[0, 0],
        },
    )

Reading format; Python syntax tree matches the source.

PythonCorresponding notebook cell
reports["corpus_checks"] = corpus_checks(ROOT)
reports["corpus_checks"]

Reading format; Python syntax tree matches the source.

Next step
03

Reconstruct candidate profiles

The retained candidate table contains 27,680 tag rows from 22,247 papers. For astrophysics and high-energy physics, repeated paper–family pairs are removed within each year. The workflow reconstructs proportions with a 0.5 per-family pseudocount, checks log₂ ratios, and verifies the 20-mention display threshold.

Mentions measure organized attention, not endorsement, candidate viability, or discovery. Bootstrap intervals and sensitivity products are retained and hash-checked; this route does not resample them.

Uses
Retained candidate tags for 1995–2025 and thirteen tracked families
Produces
Field-year shares and log₂ prominence ratios; retained threshold checks
Pythoncandidate_checks()
def candidate_checks(root):
    root = Path(root)
    d = root / "data"
    raw = pd.read_parquet(d / "dm_model_candidates_long.parquet")
    raw = raw[raw.year.between(1995, 2025)]
    assert len(raw) == 27680 and raw.bibcode.nunique() == 22247
    tracked = (
        (d / "analysis/tracked_candidate_species.txt").read_text().splitlines()
    )
    assert len(tracked) == 13
    pairs = raw[
        raw.SpeciesLabel.isin(tracked)
        & raw.year.between(1995, 2025)
        & raw.arxiv_category.isin(["astrophysics", "high-energy physics"])
    ].drop_duplicates(["year", "arxiv_category", "bibcode", "SpeciesLabel"])
    rebuilt = (
        pairs.groupby(["year", "SpeciesLabel", "arxiv_category"])
        .size()
        .unstack(fill_value=0)
        .reset_index()
    )
    ref = pd.read_parquet(d / "analysis/candidate_field_yearly.parquet")
    merged = ref.merge(rebuilt, on=["year", "SpeciesLabel"], how="left").fillna(
        {"astrophysics": 0, "high-energy physics": 0}
    )
    assert (merged.astro_mentions == merged["astrophysics"]).all() and (
        merged.hep_mentions == merged["high-energy physics"]
    ).all()
    # Reconstruct smoothed proportions using the retained 0.5 per-family pseudocount.
    n = 13
    astro = (merged.astro_mentions + 0.5) / (
        merged.groupby("year").astro_mentions.transform("sum") + 0.5 * n
    )
    hep = (merged.hep_mentions + 0.5) / (
        merged.groupby("year").hep_mentions.transform("sum") + 0.5 * n
    )
    assert np.allclose(astro, merged.astro_share) and np.allclose(
        hep, merged.hep_share
    )
    assert np.allclose(np.log2(hep / astro), merged.log2_hep_astro_ratio)
    assert (merged.meets_minimum_count == (merged.total_mentions >= 20)).all()
    merged[
        [
            "year",
            "SpeciesLabel",
            "astro_mentions",
            "hep_mentions",
            "astro_share",
            "hep_share",
            "log2_hep_astro_ratio",
        ]
    ].to_csv(
        root / "reproduced/tables/candidate-field-reconstructed.csv", index=False
    )
    return write_report(
        root,
        "candidate-checks.json",
        {
            "status": "PASS",
            "candidate_tag_rows_1995_2025": len(raw),
            "candidate_papers": int(raw.bibcode.nunique()),
            "tracked_families": len(tracked),
            "candidate_year_cells": len(merged),
            "paper_family_deduplication": True,
            "pseudocount": 0.5,
            "display_threshold": 20,
            "bootstrap_intervals": "Retained and hash-verified; not freshly resampled",
            "validation": "No independent accuracy estimate; no untouched holdout answers inspected",
        },
    )

Reading format; Python syntax tree matches the source.

PythonCorresponding notebook cell
reports["candidate_checks"] = candidate_checks(ROOT)
reports["candidate_checks"]

Reading format; Python syntax tree matches the source.

Next step
04

Check frozen network metrics

The reconstruction derives equal-field edge weights and inverse-prevalence distances, checks endpoint rules, and compares the resulting metrics with their archived values. It covers three WIMP definitions and stable, rolling, and full-period windows. Archived GraphML floating-point values and ordering are retained where shortest-path ties matter.

This checks calculations conditional on frozen graphs. It does not validate fresh raw-abstract extraction or recreate the Gephi layout. Node removal is a graph diagnostic, not a historical counterfactual or a causal experiment.

Uses
Frozen node/edge counts, archived GraphML, three WIMP definitions
Produces
Conditional checks of brokerage, importance, and node-removal metrics
PythonReconstruct graph weights and endpoints
def reconstruct_graph(nodes, edges):
    graph = nx.Graph()
    for row in edges:
        weight = 0.5 * (
            int(row["astrophysics_paper_count"])
            / int(row["astrophysics_all_dm_papers"])
            + int(row["hep_paper_count"]) / int(row["hep_all_dm_papers"])
        )
        check_close(
            weight, row["mean_field_cooc_prevalence"], "Equal-field edge weight"
        )
        distance = float(row[DISTANCE])
        check_close(distance, 1 / weight, "Inverse-prevalence distance")
        source, target = row["Source"], row["Target"]
        if graph.has_edge(source, target) or source == target:
            raise ValueError("Duplicate edge or self-loop")
        graph.add_edge(source, target, **{DISTANCE: distance})
    if set(graph) != {row["Id"] for row in nodes}:
        raise ValueError("Canonical node and edge sets disagree")
    astro, hep = [], []
    for row in nodes:
        candidate = row["node_type"] == "candidate"
        ratio = float(row["log10_astro_hep_prevalence_ratio"])
        for name, condition, endpoints in [
            ("is_astro_anchor", not candidate and ratio >= math.log10(2), astro),
            ("is_hep_anchor", not candidate and ratio <= -math.log10(2), hep),
        ]:
            if (row[name] == "True") != condition:
                raise ValueError(f'Endpoint rule mismatch: {row["Id"]}, {name}')
            if condition:
                endpoints.append(row["Id"])
    if not astro or not hep:
        raise ValueError(
            "No endpoints; archive fallback would require explicit treatment"
        )
    return graph, astro, hep

Reading format; Python syntax tree matches the source.

PythonCross-field path efficiency
def efficiency(graph, sources, targets):
    """Mean reciprocal distance; disconnected pairs remain in the denominator."""
    sources, targets = sorted(sources), sorted(targets)
    if not sources or not targets or not set(sources + targets) <= set(graph):
        raise ValueError("Endpoint sets must be nonempty and present in the graph")
    total, reachable = 0.0, 0
    for source in sources:
        lengths = nx.single_source_dijkstra_path_length(
            graph, source, weight=DISTANCE
        )
        for target in targets:
            distance = lengths.get(target)
            if distance is not None and distance > 0:
                total += 1.0 / distance
                reachable += 1
    pairs = len(sources) * len(targets)
    return total / pairs, reachable / pairs

Reading format; Python syntax tree matches the source.

PythonNode-removal diagnostic
def removal(graph, sources, targets, node, baseline):
    if node in set(sources) | set(targets):
        raise ValueError(
            "Removal requires a non-anchor node, keeping endpoints fixed"
        )
    reduced = graph.copy()
    reduced.remove_node(node)
    after, reachability = efficiency(reduced, sources, targets)
    return after, reachability, max(0.0, (baseline - after) / baseline)

Reading format; Python syntax tree matches the source.

PythonRole-specific HITS scores
def spectral_scores(rows):
    """Role-specific HITS via the leading left singular vector, max scaled."""
    candidates = sorted({r["Source"] for r in rows})
    concepts = sorted({r["Target"] for r in rows})
    ci, ti = {v: i for i, v in enumerate(candidates)}, {
        v: i for i, v in enumerate(concepts)
    }
    matrix = np.zeros((len(candidates), len(concepts)))
    seen = set()
    for row in rows:
        edge = row["Source"], row["Target"]
        if edge in seen:
            raise ValueError(f"Duplicate candidate-concept edge {edge}")
        seen.add(edge)
        weight = int(row["paper_count"]) / int(row["candidate_bearing_papers"])
        check_close(
            weight, row["cooc_doc_prevalence_candidate_papers"], "HITS weight"
        )
        matrix[ci[edge[0]], ti[edge[1]]] = weight
    left, _, _ = np.linalg.svd(matrix, full_matrices=False)
    values = np.abs(left[:, 0])
    values /= values.max()
    return dict(zip(candidates, map(float, values)))

Reading format; Python syntax tree matches the source.

PythonFull 57-combination comparison
def verify(release_root, pins_path):
    pins = json.loads(pins_path.read_text())
    manifest_path = release_root / "provenance/v3.0.0/release_manifest.json"
    if not manifest_path.is_file():
        manifest_path = release_root / "release_manifest.json"
    if sha256(manifest_path) not in pins["accepted_release_manifest_sha256"]:
        raise ValueError("Release manifest does not match the pinned v3.0.0 input")
    manifest = json.loads(manifest_path.read_text())
    entry = next(
        x for x in manifest["network_archives"] if x["path"] == pins["archive_path"]
    )
    archive_path = release_root / entry["path"]
    digest = sha256(archive_path)
    if digest != pins["archive_sha256"] or digest != entry["sha256"]:
        raise ValueError("Frozen archive SHA-256 mismatch")
    result = {
        "status": "passed",
        "python": platform.python_version(),
        "networkx": nx.__version__,
        "numpy": np.__version__,
        "archive_sha256": digest,
        "release_manifest_sha256": sha256(manifest_path),
        "scope": "Conditional recalculation from frozen graphs and count tables; not extraction validation",
        "windows": [],
        "common_endpoints": [],
    }
    with zipfile.ZipFile(archive_path) as archive:
        if set(archive.namelist()) != set(entry["members"]):
            raise ValueError("Archive member list differs from release manifest")
        for name, expected in entry["members"].items():
            if hashlib.sha256(archive.read(name)).hexdigest() != expected:
                raise ValueError(f"Archive member SHA-256 mismatch: {name}")
        result["archive_members_verified"] = len(entry["members"])
        nodes = grouped(read_rows(archive, BROKERAGE + "nodes_by_window.csv"))
        edges = grouped(read_rows(archive, BROKERAGE + "edges_by_window.csv"))
        trajectories = grouped(
            read_rows(archive, BROKERAGE + "wimp_brokerage_trajectory.csv")
        )
        removals = grouped(
            read_rows(archive, BROKERAGE + "node_removal_metrics.csv")
        )
        importance_edges = grouped(
            [
                r
                for r in read_rows(archive, IMPORTANCE + "edges_by_window.csv")
                if r["evidence_scope"] == "abstract"
            ]
        )
        importance_nodes = grouped(
            read_rows(archive, IMPORTANCE + "candidate_metrics_by_window.csv")
        )
        importance_trajectory = grouped(
            read_rows(archive, IMPORTANCE + "wimp_importance_trajectory.csv")
        )
        if not (
            set(nodes)
            == set(edges)
            == set(trajectories)
            == set(importance_edges)
            == set(importance_nodes)
            == set(importance_trajectory)
        ):
            raise ValueError("Window/definition groups differ across input tables")
        stable = {}
        removal_checks = 0
        for key in sorted(nodes):
            definition, kind, window = key
            graph, astro, hep = reconstruct_graph(nodes[key], edges[key])
            if kind == "stable":
                suffix = "" if definition == "published_umbrella" else "robustness/"
                name = (
                    BROKERAGE
                    + f'graphs/{suffix}{definition}--{window.replace("-", "_")}.graphml'
                )
                saved = nx.read_graphml(io.BytesIO(archive.read(name)))
                if set(saved) != set(graph) or {
                    frozenset(e) for e in saved.edges
                } != {frozenset(e) for e in graph.edges}:
                    raise ValueError(f"GraphML topology mismatch: {key}")
                for a, b, attrs in graph.edges(data=True):
                    check_close(
                        attrs[DISTANCE], saved[a][b][DISTANCE], "GraphML distance"
                    )
                # Keep the archived GraphML floats/order for exact shortest-path tie handling.
                graph = saved
            scores = nx.betweenness_centrality_subset(
                graph, sorted(astro), sorted(hep), normalized=True, weight=DISTANCE
            )
            candidates = {
                r["Id"]: scores[r["Id"]]
                for r in nodes[key]
                if r["node_type"] == "candidate"
            }
            for row in nodes[key]:
                check_close(
                    scores[row["Id"]],
                    row["cross_field_betweenness_prevalence"],
                    f'Betweenness {key} {row["Id"]}',
                )
            focal = FOCAL[definition]
            archived = trajectories[key][0]
            baseline, reachable = efficiency(graph, astro, hep)
            after, after_reachable, loss = removal(
                graph, astro, hep, focal, baseline
            )
            check_close(
                loss,
                archived["removal_effect_efficiency_prevalence"],
                f"WIMP removal {key}",
            )
            check_close(
                scores[focal],
                archived["cross_field_betweenness_prevalence"],
                f"WIMP trajectory {key}",
            )
            for row in removals[key]:
                if kind != "stable" and row["node_id"] != focal:
                    continue
                after_node, reach_node, loss_node = removal(
                    graph, astro, hep, row["node_id"], baseline
                )
                for value, column in [
                    (baseline, "baseline_efficiency"),
                    (after_node, "removed_efficiency"),
                    (reachable, "baseline_reachable_share"),
                    (reach_node, "removed_reachable_share"),
                    (loss_node, "relative_efficiency_loss"),
                ]:
                    check_close(
                        value, row[column], f'{column} {key} {row["node_id"]}'
                    )
                removal_checks += 1
            hits = spectral_scores(importance_edges[key])
            for row in importance_nodes[key]:
                check_close(
                    hits[row["Id"]],
                    row["hits_score_prevalence"],
                    f'HITS {key} {row["Id"]}',
                )
            hits_pct = percentile(hits, focal)
            check_close(
                hits_pct,
                importance_trajectory[key][0]["hits_candidate_percentile"],
                f"HITS percentile {key}",
            )
            result["windows"].append(
                {
                    "definition": definition,
                    "kind": kind,
                    "window": window,
                    "nodes": len(graph),
                    "edges": graph.number_of_edges(),
                    "astro_endpoints": len(astro),
                    "hep_endpoints": len(hep),
                    "wimp_betweenness": scores[focal],
                    "wimp_candidate_rank": 1
                    + sum(v > scores[focal] for v in candidates.values()),
                    "candidate_count": len(candidates),
                    "wimp_removal_effect": loss,
                    "wimp_hits_score": hits[focal],
                    "wimp_hits_percentile": hits_pct,
                }
            )
            if kind == "stable" and definition == "published_umbrella":
                stable[window] = graph, set(astro), set(hep), candidates
            print(f"Checked {definition} {kind} {window}", flush=True)
        common_astro = set.intersection(*(v[1] for v in stable.values()))
        common_hep = set.intersection(*(v[2] for v in stable.values()))
        for row in read_rows(
            archive, BROKERAGE + "common_endpoint_sensitivity.csv"
        ):
            graph, _, _, candidates = stable[row["window"]]
            scores = nx.betweenness_centrality_subset(
                graph,
                sorted(common_astro),
                sorted(common_hep),
                normalized=True,
                weight=DISTANCE,
            )
            baseline, reachable = efficiency(graph, common_astro, common_hep)
            focal = FOCAL["published_umbrella"]
            after, after_reachable, loss = removal(
                graph, common_astro, common_hep, focal, baseline
            )
            rank = 1 + sum(scores[n] > scores[focal] for n in candidates)
            for value, column in [
                (len(common_astro), "common_astro_endpoint_count"),
                (len(common_hep), "common_hep_endpoint_count"),
                (scores[focal], "common_endpoint_betweenness"),
                (rank, "common_endpoint_candidate_rank"),
                (baseline, "common_endpoint_baseline_efficiency"),
                (after, "common_endpoint_removed_efficiency"),
                (loss, "common_endpoint_removal_effect"),
                (reachable, "common_endpoint_baseline_reachable_share"),
                (after_reachable, "common_endpoint_removed_reachable_share"),
            ]:
                check_close(
                    value, row[column], f'Common endpoints {row["window"]} {column}'
                )
            result["common_endpoints"].append(
                {
                    "window": row["window"],
                    "astro_endpoints": len(common_astro),
                    "hep_endpoints": len(common_hep),
                    "betweenness": scores[focal],
                    "candidate_rank": rank,
                    "removal_effect": loss,
                }
            )
        result["node_removal_rows_recalculated"] = removal_checks
        result["betweenness_nodes_checked"] = sum(map(len, nodes.values()))
        result["hits_candidates_checked"] = sum(map(len, importance_nodes.values()))
    return result

Reading format; Python syntax tree matches the source.

PythonWorkflow entry for network checks
def network_checks(root):
    root = Path(root)
    mod = load_local(
        root / "code/network-reconstruction/recompute_metrics.py",
        "damadi_network_recompute",
    )
    report = mod.verify(root, root / "code/network-reconstruction/inputs.json")
    assert report["status"] == "passed" and len(report["windows"]) == 57
    path = root / "reproduced/network-metrics.json"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n")
    return {
        "status": "PASS",
        "windows": len(report["windows"]),
        "nodes_checked": report["betweenness_nodes_checked"],
        "removal_rows": report["node_removal_rows_recalculated"],
        "scope": report["scope"],
    }

Reading format; Python syntax tree matches the source.

PythonCorresponding notebook cell
reports["network_checks"] = network_checks(ROOT)
reports["network_checks"]

Reading format; Python syntax tree matches the source.

Next step
05

Render figures and check exports

The main R source constructs Figures 1–3 at their designated dimensions. The SI source renders statistical companions. Network presentation code preserves non-text drawing operations and source label positions while changing typography to STIX.

The gallery below contains nine supplied normalized PDF references. Four statistical SI references remain retained manuscript artwork and are distinct from regenerated SI companions. Figure 5 is a supplied conceptual assessment, outside this computational export route.

Uses
Frozen statistical products, saved network presentations, bundled STIX fonts
Produces
Nine generated PDF companions; dimension, source, and table checks
Pythonfigure_checks()
def figure_checks(root, render=True):
    root = Path(root)
    if render:
        subprocess.run(
            ["Rscript", str(root / "code/04-build-figures.R")], cwd=root, check=True
        )
        mod = load_local(
            root / "code/modules/network_graphics.py", "damadi_network_graphics"
        )
        mod.export_all(root)
    from pypdf import PdfReader

    files = sorted((root / "reproduced/stepwise-figures").glob("*.pdf")) + sorted(
        (root / "reproduced/figures").glob("*.pdf")
    )
    assert len(files) == 9, len(files)
    for file in files:
        assert len(PdfReader(file).pages) == 1
    config = json.loads((root / "config/main-figures.json").read_text())
    assert (
        hashlib.sha256(
            (root / "code/02-main-figures-stepwise.R").read_bytes()
        ).hexdigest()
        == config["active_script_sha256"]
    )
    for name, dimensions in config["figures"].items():
        page = PdfReader(root / "reproduced/stepwise-figures" / name).pages[0]
        actual = [
            float(page.mediabox.width) * 25.4 / 72,
            float(page.mediabox.height) * 25.4 / 72,
        ]
        assert np.allclose(actual, dimensions, atol=0.02), (name, actual)
    for name, digest in config["table_sha256"].items():
        assert (
            hashlib.sha256(
                (root / "reproduced/tables" / name).read_bytes()
            ).hexdigest()
            == digest
        ), name
    return write_report(
        root,
        "figure-checks.json",
        {
            "status": "PASS",
            "pdfs": len(files),
            "statistical_renderings": 7,
            "network_presentations": 2,
            "network_geometry": "Unchanged",
            "main_figure_source_sha256": config["source_script_sha256"],
            "main_figure_dimensions_mm": config["figures"],
            "main_tables_byte_identical_to_retained_fragments": True,
            "authority": "Main Figures 1–3 regenerated from the user-designated script at final arXiv dimensions, with the preprint class colors fixed explicitly and bundled STIX typography. Four SI manuscript figures remain retained preprint assets; separate SI R companions remain available.",
        },
    )

Reading format; Python syntax tree matches the source.

RR · assemble research classifications
composition_plot_wide_guide <- (
  (broad_plot +
     one_row_guides +
     theme(legend.position = "top", legend.justification = "left")) +
    (paper_plot_wide +
       one_row_guides +
       theme(legend.position = "top", legend.justification = "left")) +
    (papers_strip + theme(legend.position = "none")) +
    guide_area() +
    plot_layout(
      design = "
      DD
      AB
      CC
    ",
      widths = c(1, 1.55),
      heights = c(0.5, 9.3, 0.7),
      guides = "keep",
      axes = "collect"
    ) +
    plot_annotation(
      title = "Frequency of primary research classifications",
      subtitle = "arXiv class and category − 2010-2025",
    )
) &
  theme(
    legend.box = "horizontal",
    legend.direction = "horizontal",
    legend.byrow = TRUE,
    #legend.justification = "center",
    legend.key.spacing = grid::unit(1, "pt"),
    legend.spacing.x = grid::unit(3, "pt"),
    legend.text = element_text(size = 9, margin = margin(l = 3, r = 10)),
    legend.margin = margin(0, 0, 0, 0),
    
    plot.title = element_text(
      size = 11, family = REPORT_FONT,
      hjust = 0.05, vjust = 0.0
    ),
    plot.subtitle = element_text(
      size = 9, family = REPORT_FONT,
      hjust = 0.045, vjust = 0.0
    ),
    plot.tag = element_text(size = 7, family = REPORT_FONT),
    plot.tag.position = c(0.01, 0.98),
    axis.title.x = element_blank(),
    plot.margin = margin(0, 7, 1, 0)
  )

composition_plot_wide_guide <- composition_plot_wide_guide &
  theme(
    plot.background = element_blank(),
    panel.background = element_blank(),
    legend.background = element_blank(),
    legend.box.background = element_blank()
  )

if (interactive()) print(composition_plot_wide_guide)
if (SAVE_OUTPUTS) {
  save_stepwise_pdf(composition_plot_wide_guide, "fig-research-classifications.pdf", 165, 90, bg = "transparent")
}

Exact source text.

RR · candidate prominence plot
MIN_TOTAL_MENTIONS <- 20L

candidate_field_plot <- candidate_field |>
  mutate(SpeciesLabel = stringr::str_replace(SpeciesLabel, fixed("Sterile nu"), "Sterile ν"))

stopifnot(all(
  candidate_field_plot$meets_minimum_count ==
    (candidate_field_plot$total_mentions >= MIN_TOTAL_MENTIONS)
))

species_order <- candidate_field_plot |>
  filter(year >= 1995, year <= 2025, total_mentions >= MIN_TOTAL_MENTIONS) |>
  group_by(SpeciesLabel) |>
  summarise(
    mean_ratio = weighted.mean(log2_hep_astro_ratio, w = total_mentions, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(mean_ratio) |>
  pull(SpeciesLabel)

log_ratio_data <- candidate_field_plot |>
  filter(year >= 1995, year <= 2025, SpeciesLabel %in% species_order) |>
  mutate(
    SpeciesLabel = factor(SpeciesLabel, levels = species_order),
    shown_ratio = if_else(total_mentions >= MIN_TOTAL_MENTIONS, log2_hep_astro_ratio, NA_real_)
  )

div <- c(
  "#00429d", "#2b57a7", "#426cb0", "#5681b9", "#6997c2",
  "#7daeca", "#93c4d2", "#abdad9", "#caefdf", "#ffffe0",
  "#ffe2ca", "#ffc4b4", "#ffa59e", "#f98689", "#ed6976",
  "#dd4c65", "#ca2f55", "#b11346", "#93003a"
)


log_ratio_plot <- log_ratio_data |>
  ggplot(aes(year, SpeciesLabel, fill = shown_ratio)) +
  geom_tile(color = "white", linewidth = 0.15, alpha = 0.85) +
  scale_x_continuous(
    breaks = seq(1995, 2025, 2),
    expand = expansion(mult = c(0.01, 0))
  ) +
  scale_fill_gradientn(
    colours = div,
    name = "Share ratio",
    limits = c(-log2(10), log2(10)),
    breaks = c(-log2(10), -log2(3), 0, log2(3), log2(10)),
    labels = c("10× Astro", "3× Astro", "1×", "3× HEP", "10× HEP"),
    oob = scales::squish,
    na.value = NA,
    guide = guide_colorbar(
      direction = "horizontal",
      title.position = "top",
      title.hjust = 0,
      title.vjust = -0.3,
      barwidth = grid::unit(70, "mm"),
      barheight = grid::unit(3.5, "mm"),
      ticks.colour = "white"
    )
  ) +
  labs(
    x = NULL,
    y = NULL,
    title = "Relative prominence of candidates",
    subtitle = "Log2 ratio of HEP to astrophysics shares, 1995-2025"
  ) +
  theme_minimal(base_family = REPORT_FONT, base_size = 11) +
  theme(
    plot.title = element_text(size = 11, family = REPORT_FONT, hjust = 0),
    plot.subtitle = element_text(
      size = 8, family = REPORT_FONT, hjust = 0, vjust = 2.0),
    plot.margin = margin(2, 5, 5, 0),
    panel.grid.major = element_line(color = "#282828", linewidth = 0.05),
    panel.grid.minor = element_blank(),
    legend.margin = margin(-7, 0, 0, 0),
    legend.text = element_text(size = 8, family = REPORT_FONT, color = "black"),
    legend.title = element_text(size = 8, family = REPORT_FONT, color = "black"),
    legend.position = "bottom",
    plot.background = element_blank(),
    panel.background = element_blank(),
    legend.background = element_blank(),
    legend.box.background = element_blank()
  )

if (interactive()) print(log_ratio_plot)
if (SAVE_OUTPUTS) {
  save_stepwise_pdf(log_ratio_plot, "fig-candidate-prominence.pdf", 156, 80, bg = "transparent")
}

Exact source text.

RR · candidate composition plot
tracked_species <- stringr::str_replace(tracked_species, fixed("Sterile nu"), "Sterile ν")
candidate_norm <- candidates |>
  mutate(SpeciesLabel = stringr::str_replace(SpeciesLabel, fixed("Sterile nu"), "Sterile ν")) |>
  filter(
    year >= 1995,
    year <= 2025,
    arxiv_category %in% names(FIELD_LABELS),
    SpeciesLabel %in% tracked_species
  ) |>
  distinct(year, arxiv_category, bibcode, SpeciesLabel) |>
  count(year, arxiv_category, SpeciesLabel, name = "n") |>
  group_by(year, arxiv_category) |>
  mutate(share_tracked_candidates = n / sum(n)) |>
  ungroup() |>
  mutate(
    field = recode(arxiv_category, !!!FIELD_LABELS),
    SpeciesLabel = factor(SpeciesLabel, levels = tracked_species)
  )

candidate_colors <- setNames(
  khroma::color("batlow", type = "qualitative", reverse = TRUE)(length(tracked_species)),
  tracked_species
)

p_norm <- candidate_norm |>
  ggplot(aes(year, share_tracked_candidates, fill = SpeciesLabel)) +
  geom_area(position = "fill", alpha = 0.89, linewidth = 0.15, color = "white") +
  facet_wrap(~ field, ncol = 2, scales = "fixed") +
  scale_fill_manual(values = candidate_colors, name = NULL, drop = FALSE) +
  scale_x_continuous(
    breaks = seq(1995, 2025, 5),
    expand = expansion(mult = c(0.01, 0))
  ) +
  scale_y_continuous(
    labels = scales::percent_format(accuracy = 1),
    expand = expansion(mult = c(0.01, 0))
  ) +
  labs(x = NULL, 
       title = "Share of paper-candidate-family mentions", 
       subtitle = "Normalized to total mentions in each field, 1995-2025",
       y = NULL) +
  theme_minimal(base_size = 11, base_family = REPORT_FONT) +
  theme(
    panel.spacing = grid::unit(1.5, "lines"),
    plot.margin = margin(0, 0, 0, 0),
    strip.background = element_rect(color = NA, fill = NA),
    strip.text = element_text(color = "#141414", size = 10, family = REPORT_FONT),
    strip.text.x.bottom = element_text(hjust = 0.5, vjust = 0.5),
    axis.ticks.x = element_blank(),
    legend.position = "bottom",
    legend.key.spacing.y = grid::unit(0.2, "lines")
  ) +
  guides(fill = guide_legend(
    nrow = 1,
    byrow = TRUE,
    title.position = "bottom",
    title.hjust = 0.5,
    label.position = "bottom",
    override.aes = list(alpha = 1, size = 1.5, shape = 15),
    keywidth = grid::unit(5, "pt"),
    keyheight = grid::unit(10, "pt")
  ))

candidate_robustness <- p_norm +
  patchwork::plot_layout(widths = 1, axes = "collect", guides = "keep") &
  theme(
    plot.title = element_text(size = 10, color = "black", hjust = 0, vjust = -2.8),
    plot.subtitle = element_text(size = 9, color = "black", hjust = 0, vjust = -1.0),
    plot.margin = margin(2, 12, 5, 0),
    legend.margin = margin(-5, 0, 0, 0),
    legend.position = "bottom",
    legend.box = "horizontal",
    #legend.box.just = "left",
    panel.grid.major = element_line(color = "black", linewidth = 0.15),
    panel.grid.minor = element_blank(),
    plot.background = element_blank(),
    panel.background = element_blank(),
    legend.background = element_blank(),
    legend.box.background = element_blank(),
    legend.key.spacing = grid::unit(2.0, "pt"),
    legend.text = element_text(size = 7.7, margin = margin(t = 2.5, l = 0.7, r = 0.7, unit = "pt")),
    legend.title = element_text(size = 9, family = BASE_FONT, color = "black", hjust = 0)
  )

if (interactive()) print(candidate_robustness)
if (SAVE_OUTPUTS) {

Exact source text.

PythonPreserve network geometry; retype labels
def normalize_one(source, destination, font_dir):
    source = Path(source)
    destination = Path(destination)
    font_dir = Path(font_dir)
    original = fitz.open(source)
    page = original[0]
    assert len(original) == 1 and page.rotation == 0
    overlays = []
    for block in page.get_text("dict")["blocks"]:
        for line in block.get("lines", []):
            assert abs(line["dir"][1]) < 1e-8 and line["dir"][0] > 0
            for span in line["spans"]:
                font = span["font"]
                face = (
                    "math"
                    if "Math" in font
                    else (
                        "italic"
                        if "Italic" in font
                        else (
                            "medium"
                            if "IBM" in font or "Bold" in font or "Medium" in font
                            else "regular"
                        )
                    )
                )
                overlays.append(
                    {
                        "text": span["text"],
                        "origin": span["origin"],
                        "bbox": span["bbox"],
                        "size": span["size"],
                        "color": span["color"],
                        "face": face,
                        "source_font": font,
                    }
                )
    reader = PdfReader(source)
    writer = PdfWriter()
    writer.clone_document_from_reader(reader)
    fingerprints = []
    visited = set()

    def clean(stream, resources, label):
        key = id(stream)
        if key in visited:
            return
        visited.add(key)
        content = ContentStream(stream, writer)
        before = []
        after = []
        kept = []
        for operands, op in content.operations:
            if op == b"Tr":
                assert (
                    float(operands[0]) < 4
                ), "Text clipping requires explicit treatment"
            if op not in TEXT_OPS:
                before.append((repr(operands), op.decode("latin1")))
                kept.append((operands, op))
                after.append((repr(operands), op.decode("latin1")))
        assert before == after
        fingerprints.append(
            {
                "stream": label,
                "nontext_operations": len(before),
                "sha256": hashlib.sha256(repr(before).encode()).hexdigest(),
            }
        )
        content.operations = kept
        stream.set_data(content.get_data())
        if "/Font" in resources:
            del resources["/Font"]
        for name, value in resources.get("/XObject", {}).items():
            obj = value.get_object()
            if obj.get("/Subtype") == "/Form":
                clean(
                    obj,
                    obj.get("/Resources", resources).get_object(),
                    label + "/" + str(name),
                )

    pdfpage = writer.pages[0]
    # Flatten the page content array into one stream without changing drawing operations.
    contents = ContentStream(pdfpage.get_contents(), writer)
    pdfpage[NameObject("/Contents")] = writer._add_object(contents)
    clean(contents, pdfpage["/Resources"].get_object(), "page")
    overlay = fitz.open()
    newpage = overlay.new_page(width=page.rect.width, height=page.rect.height)
    faces = {
        "regular": "STIXTwoText-Regular.ttf",
        "italic": "STIXTwoText-Italic.ttf",
        "medium": "STIXTwoText-Medium.ttf",
        "math": "STIXTwoMath-Regular.otf",
    }
    fonts = {k: fitz.Font(fontfile=str(font_dir / v)) for k, v in faces.items()}
    for k, v in faces.items():
        newpage.insert_font(fontname="DAMADI" + k, fontfile=str(font_dir / v))
    for span in overlays:
        text = span["text"]
        face = span["face"]
        font = fonts[face]
        assert all(c.isspace() or font.has_glyph(ord(c)) for c in text), (
            text,
            face,
        )
        width = font.text_length(text, fontsize=span["size"])
        source_width = span["bbox"][2] - span["bbox"][0]
        factor = source_width / width if width > 0 else 1
        origin = fitz.Point(span["origin"])
        rgb = fitz.sRGB_to_pdf(span["color"])
        newpage.insert_text(
            origin,
            text,
            fontsize=span["size"],
            fontname="DAMADI" + face,
            color=rgb,
            morph=(origin, fitz.Matrix(factor, 1)),
        )
        span["horizontal_scale"] = factor
    overlay_writer = PdfWriter()
    overlay_writer.clone_document_from_reader(
        PdfReader(io.BytesIO(overlay.tobytes()))
    )
    # MuPDF's CFF insertion needs an explicit UTF-16 mapping for non-BMP math letters.
    math_chars = sorted(
        {c for span in overlays if span["face"] == "math" for c in span["text"]}
    )
    if math_chars:
        pairs = [
            f'<{fonts["math"].has_glyph(ord(c)):04X}> <{c.encode("utf-16-be").hex().upper()}>'
            for c in math_chars
        ]
        cmap = (
            "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /DAMADI-Math-Unicode def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n"
            + str(len(pairs))
            + " beginbfchar\n"
            + "\n".join(pairs)
            + "\nendbfchar\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n"
        )
        stream = DecodedStreamObject()
        stream.set_data(cmap.encode())
        overlay_writer.pages[0]["/Resources"]["/Font"]["/DAMADImath"].get_object()[
            NameObject("/ToUnicode")
        ] = overlay_writer._add_object(stream)
    pdfpage.merge_page(overlay_writer.pages[0])
    writer.add_metadata(
        {
            "/Title": "DAMADI STIX presentation of retained network figure",
            "/Author": "Simon Allzén",
            "/Subject": "Original graph geometry and source label positions retained; STIX typography only",
        }
    )
    destination.parent.mkdir(parents=True, exist_ok=True)
    with destination.open("wb") as output:
        writer.write(output)
    rendered = fitz.open(destination)
    expected = "".join(x["text"] for x in overlays)
    # Verify every source label's characters remain in the presentation text layer.
    actual = rendered[0].get_text()
    from collections import Counter

    assert Counter(c for c in expected if not c.isspace()) == Counter(
        c for c in actual if not c.isspace()
    )
    return {
        "source": source.name,
        "pdf": destination.name,
        "label_spans": overlays,
        "nontext_stream_fingerprints": fingerprints,
        "all_nontext_operations_unchanged": True,
        "source_label_characters_retained": True,
        "font_sizes_and_origins_retained": True,
        "width_mm": page.rect.width * 25.4 / 72,
        "height_mm": page.rect.height * 25.4 / 72,
    }

Reading format; Python syntax tree matches the source.

RBatch wrapper for the stepwise R sources
#!/usr/bin/env Rscript
# Thin batch wrapper; the stepwise routes contain the complete plotting workflow.
arg <- grep("^--file=", commandArgs(FALSE), value = TRUE)
code_dir <- if (length(arg)) dirname(normalizePath(gsub("~+~", " ", sub("^--file=", "", arg[[1]]), fixed = TRUE))) else file.path(getwd(), "code")
for (name in c("02-main-figures-stepwise.R", "03-si-figures-stepwise.R")) {
  source(file.path(code_dir, name), local = new.env(parent = globalenv()))
}

Exact source text.

PythonCorresponding notebook cell
if os.environ.get("DAMADI_ANALYSIS_ONLY") == "1":
    reports["figure_checks"] = {
        "status": "PASS",
        "skipped": True,
        "reason": "Numerical-only mode; no graphical checks claimed",
    }
else:
    reports["figure_checks"] = figure_checks(ROOT)
reports["figure_checks"]

Reading format; Python syntax tree matches the source.

Next step
06

Record completion and scope

The command-line route gathers the stage reports and records their scope. The clean notebook invokes the same functions and asserts completion before writing its own report. Numerical-only mode explicitly skips graphical checks.

No research analysis has been executed for this HTML edition. The displayed numbers and PDFs are supplied reference products; the checks performed here concern source identity and presentation.

Uses
Reports from input, corpus, candidate, network, and optional graphics stages
Produces
reproduction.json or notebook-checks.json in a separate verification directory
PythonShared command-line orchestration
def run(root, render=True):
    root = Path(root).resolve()
    reports = {}
    for name, fn in [
        ("inputs", input_checks),
        ("corpus", corpus_checks),
        ("candidates", candidate_checks),
        ("network", network_checks),
    ]:
        print("STEP:", name, flush=True)
        reports[name] = fn(root)
    reports["figures"] = (
        figure_checks(root)
        if render
        else {
            "status": "PASS",
            "skipped": True,
            "reason": "Numerical-only mode; no graphical checks claimed",
        }
    )
    return write_report(
        root,
        "reproduction.json",
        {
            "status": "PASS",
            "entry": "E.04--MS-DA-CR",
            "reports": reports,
            "limits": "Frozen-input reconstruction. No new retrieval, raw-abstract NLP extraction, new annotation, holdout inspection, causal validation or bootstrap resampling.",
        },
    )

Reading format; Python syntax tree matches the source.

PythonWrite a verification report
def write_report(root, name, value):
    path = Path(root) / "verification" / name
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n"
    )
    return value

Reading format; Python syntax tree matches the source.

PythonCommand-line entry point
#!/usr/bin/env python3
from pathlib import Path
import sys, os

root = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(root / "code/modules"))
from workflow import run

print(run(root, render=os.environ.get("DAMADI_ANALYSIS_ONLY") != "1")["status"])

Reading format; Python syntax tree matches the source.

PythonCorresponding notebook cell
assert all(item["status"] == "PASS" for item in reports.values())
write_report(
    ROOT,
    "notebook-checks.json",
    {"status": "PASS", "clean_kernel": True, "reports": reports},
)

Reading format; Python syntax tree matches the source.

View supplied results
Reference results

What the retained products report

Counts and contrasts come from the supplied workflow and analysis summary. Their display here does not certify a fresh numerical reconstruction.

QuantitySupplied value
Full corpus through 2025201,627
Primary-classified papers143,390
Candidate papers · 1995–202522,247
Candidate tag rows · 1995–202527,680
Tracked candidate families13
Frozen graph/window combinations57
Candidate-profile contrast
ComparisonSupplied Jensen–Shannon divergence
1995-20040.32563
2016-20250.29295
Late minus early-0.03268
Retained contrast interval-0.09546 to -0.01795

The retained late-minus-early contrast is negative. The result does not support increasing candidate-profile divergence. Different field profiles and declining WIMP prominence should not be conflated with growing statistical separation.

Scope of the network checks

The reconstruction compares 57 definition/window combinations with archived metrics. Its three focal definitions are explicit generic WIMP, the published WIMP umbrella, and canonical WIMP core. It checks cross-field betweenness, role-specific importance, and removal diagnostics conditional on frozen graphs and count tables.

Reference figures

From products to presentation

The original normalized PDFs are displayed directly, with neutral colors adapted to the archive theme. Downloads preserve the supplied PDF bytes. Main figures appear below; supplementary references are available in the disclosure.

Main figure 1 · normalized reference

Research classifications

Open original reference PDF ↗

Annual primary arXiv research classifications and their composition. Primary assignments and broader assignment counts remain distinguishable.

Main figure 2 · normalized reference

Candidate prominence

Open original reference PDF ↗

Field-year prominence contrasts between high-energy physics and astrophysics. The supplied code retains the 0.5 pseudocount and minimum-count display rule.

Main figure 3 · normalized reference

Candidate composition

Open original reference PDF ↗

Shares of paper–candidate-family mentions, normalized within each field. These profiles describe attention across thirteen tracked families.

Main figure 4 · normalized presentation

Cross-field brokerage

Open original reference PDF ↗

Saved network geometry and label positions are preserved in the supplied STIX presentation. The graph represents discourse relations rather than evidential or causal dependence.

Supplementary references · five PDFs
SI S1 · retained manuscript reference

Classification sensitivity

Open original reference PDF ↗

The supplied manuscript reference is retained unchanged. The R source separately constructs the statistical companion from frozen products.

SI S2 · retained manuscript reference

Candidate counts

Open original reference PDF ↗

Retained raw-count display for the candidate analysis. The reference PDF and the separate R construction remain distinguishable.

SI S3 · retained manuscript reference

Candidate-profile distances

Open original reference PDF ↗

Retained candidate-distribution distances and uncertainty. Existing bootstrap products are presented as supplied, not resampled for this edition.

SI S4 · retained manuscript reference

Lexical comparisons

Open original reference PDF ↗

Retained lexical-comparison figure. The upstream lexical extraction is separate from the normalized frozen-input checking route.

SI S5 · normalized presentation

Node-removal diagnostics

Open original reference PDF ↗

Saved node-removal presentation with its geometry retained. Removing a node probes a frozen graph; it is not a historical counterfactual.

Source library

Read the implementation

49 Python, R, shell, and C files are available, including 15 active workflow, setup, and checking sources. Earlier pipeline sources are labeled separately. Individual reading views load when opened; original source downloads retain their exact bytes.

49 files
FileRoleLanguageLines
Numerical entry point
Active workflow / checks
Python7
Main statistical figure construction
Active workflow / checks
R630
Supplementary statistical companions
Active workflow / checks
R882
Stepwise R batch wrapper
Active workflow / checks
R7
Private clean-kernel notebook runner
Active workflow / checks
Python21
Restore exact split inputs
Active workflow / checks
Python34
Network geometry and STIX presentation
Active workflow / checks
Python82
Process-local Quartz font registration
Active workflow / checks
R20
CoreText helper
Active workflow / checks
C16
Shared six-stage checking workflow
Active workflow / checks
Python107
Frozen graph metrics and comparison checks
Active workflow / checks
Python285
Deterministic reconstruction tests
Active workflow / checks
Python72
Fixed-package identity check
Active workflow / checks
Python16
Analysis/full/notebook mode driver
Active workflow / checks
Shell28
Pinned Python environment setup
Active workflow / checks
Shell10
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Shell4
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
R242
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
R523
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
R506
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
R889
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python463
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python69
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python401
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python350
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python284
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python183
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python1733
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Shell27
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python1
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python299
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python43
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python87
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python92
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python165
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python1
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python1
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python20
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Shell4
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Shell5
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python79
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python24
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python415
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python554
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python303
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python2236
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python406
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python369
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Python323
Earlier acquisition, extraction, analysis, or maintenance source
Earlier pipeline / support
Shell71
Supplied reference tables

Record-level datasets, saved-output upstream notebooks, large keyness tables, network archives, and validation-answer files are omitted. These remain separate from this reading edition.

Edition & reproduction

Identify the material

Supplied source package
diverging-paths · 3.1.0
Scientific baseline
Zenodo v3.0.0
Analysis cutoff
2025
Source code license
MIT

The scientific baseline DOI ↗ identifies the earlier deposited evidence. The concept DOI ↗ identifies the version lineage. This reading edition uses the supplied 3.1.0 source folder and does not assign it a new version DOI.

Reproduction requirements and limits
  • The preferred package environment uses Python 3.11. The numerical-only route does not need R; the approved graphical route uses macOS Quartz, R packages, compiler support, and bundled STIX fonts.
  • The clean notebook has six numbered stages plus setup, seven code cells, and no saved outputs.
  • No acquisition, raw-abstract extraction, new annotation, independent accuracy estimate, unused holdout evaluation, ontology change, or bootstrap resampling is performed for this edition.
  • The normalized references and retained v3.0.0 artwork remain distinct. The conceptual assessment in main Figure 5 is supplied artwork rather than a computational result.
Source integrity and presentation checks

Copied files are checked against the supplied release manifest and their recorded hashes. Python reading formatting is syntax-tree checked. These checks protect the reading edition; they do not reproduce the scientific analysis.

Inspected source checkout
efae0a78dff372acf32025287e423dc523248f40
Source checkout state
Clean at inspection
Source release manifest SHA-256
737c4b65853a28b065e9c5a0cfb86b0215a07ed3b18d7b6632770129155b9ee6
Open the reading-edition integrity record ↗
Source browser

Source file

Download original ↓