tracegrpo / scripts /audit_pairwise_train_data.py
Dhruv1000's picture
Archive complete TraceGRPO project results and documentation (2026-10-09)
646ccfc verified
Raw History Blame Contribute Delete
5.08 kB
#!/usr/bin/env python3
"""Audit chronological TRAIN vulnerable/fixed pairs for pairwise objectives.
This is a data-structure diagnostic only. It does not read VALID/TEST labels
or train a model. Similarity uses sets and is not a semantic equivalence test.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import statistics
from collections import Counter
from pathlib import Path
import pandas as pd
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1 << 20), b""):
h.update(block)
return h.hexdigest()
def audit(corpus_path: Path, train_split: Path) -> dict:
pair_ids = set(json.loads(train_split.read_text()))
corpus = pd.read_parquet(corpus_path)
train = corpus[corpus["pair_id"].isin(pair_ids)]
group_sizes = train.groupby("pair_id").size()
bad_pairs = []
label_sets = Counter()
line_jaccards, token_jaccards, line_deltas = [], [], []
exact_equal = nonempty_both = 0
token_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*|0[xX][0-9a-fA-F]+|\d+|[^\s]")
for pair_id, group in train.groupby("pair_id", sort=False):
labels = sorted(int(x) for x in group["target"])
label_sets[tuple(labels)] += 1
if len(group) != 2 or labels != [0, 1]:
bad_pairs.append({"pair_id": pair_id, "n_rows": len(group), "labels": labels})
continue
row = group.iloc[0]
vuln = str(row["func_vuln"] or "")
fixed = str(row["func_patch"] or "")
vuln_lines = {line.strip() for line in vuln.splitlines() if line.strip()}
fixed_lines = {line.strip() for line in fixed.splitlines() if line.strip()}
line_union = vuln_lines | fixed_lines
line_jaccards.append(len(vuln_lines & fixed_lines) / len(line_union) if line_union else 1.0)
line_deltas.append(len(vuln_lines ^ fixed_lines))
vuln_tokens = set(token_re.findall(vuln[:200_000]))
fixed_tokens = set(token_re.findall(fixed[:200_000]))
token_union = vuln_tokens | fixed_tokens
token_jaccards.append(len(vuln_tokens & fixed_tokens) / len(token_union)
if token_union else 1.0)
exact_equal += int(vuln == fixed)
nonempty_both += int(bool(vuln.strip() and fixed.strip()))
n_pairs = len(pair_ids)
if len(train) != 2 * n_pairs:
# Report and fail: split integrity is a prerequisite for interpreting pairs.
raise ValueError(f"expected {2*n_pairs} rows for {n_pairs} pair IDs, found {len(train)}")
def summary(values: list[float]) -> dict:
ordered = sorted(values)
return {"mean": statistics.mean(values), "median": statistics.median(values),
"p10": ordered[int(0.10 * len(ordered))],
"p90": ordered[int(0.90 * len(ordered))]}
return {
"analysis": "chronological TRAIN pair structure for exploratory pairwise objective",
"split": str(train_split),
"test_touched": False,
"train_pair_ids": n_pairs,
"train_rows": len(train),
"pair_group_sizes": {str(k): int(v) for k, v in group_sizes.value_counts().items()},
"label_pair_counts": {"/".join(map(str, k)): v for k, v in label_sets.items()},
"invalid_pair_count": len(bad_pairs),
"invalid_pair_examples": bad_pairs[:10],
"code_pair_similarity": {
"exact_function_equal_count": exact_equal,
"both_code_nonempty_fraction": nonempty_both / n_pairs,
"normalized_line_set_jaccard": summary(line_jaccards),
"normalized_token_set_jaccard": summary(token_jaccards),
"line_set_symmetric_difference": {
"median": statistics.median(line_deltas),
"p90": sorted(line_deltas)[int(0.90 * len(line_deltas))],
"fraction_at_most_two": sum(x <= 2 for x in line_deltas) / n_pairs,
},
},
"method_caveat": (
"Line/token set Jaccard ignores token order, repeated tokens, semantics, and patch correctness; "
"it only establishes structural relatedness, not that pairwise supervision will help."
),
"input_sha256": {str(corpus_path): sha256(corpus_path),
str(train_split): sha256(train_split)},
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--corpus", type=Path, default=Path("data/processed/corpus.parquet"))
parser.add_argument("--train-split", type=Path,
default=Path("data/splits/chronological/train.json"))
parser.add_argument("--output", type=Path,
default=Path("results/phase6/pairwise_train_data_audit.json"))
args = parser.parse_args()
report = audit(args.corpus, args.train_split)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()