snr_bias / code /scripts /grl_reference_context_check.py
cangyeone's picture
Upload GRL reproducibility package
7170296 verified
Raw
History Blame Contribute Delete
8.68 kB
#!/usr/bin/env python3
"""Check SNR/QC literature context and reference consistency."""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "submission_final"
MAIN = ROOT / "main.tex"
BIB = ROOT / "references.bib"
LOG = ROOT / "main.log"
ADDED_KEYS = [
"allen1978",
"allen1982",
"baer1987",
"withers1998",
"kalkan2016",
"distefano2006",
"perol2018convnetquake",
"mousavi2019cred",
"mousavi2019stead",
"michelini2021instance",
"woollam2022seisbench",
"ni2023pnw",
"munchmeyer2022picker",
"myklebust2024regional",
"zhao2023diting",
"zhu2019denoising",
"chen2019snr",
"zhang2018dictionary",
"shapiro2004noise",
"shapiro2005tomography",
"bensen2007ambient",
"yao2008tibet",
"lin2008westernus",
"lin2009eikonal",
"zhang2020dispersion",
"dai2020dispersion",
"wang2021dispersion",
"jiang2023surfnet",
]
def read(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace") if path.exists() else ""
def strip_latex(text: str) -> str:
text = re.sub(r"(?<!\\)%.*", " ", text)
text = text.replace(r"\%", " percent ")
text = re.sub(r"\\(?:cite|citeA|citep|citet)\{[^}]*\}", " ", text)
text = re.sub(r"\\(?:path|texttt|textit)\{([^}]*)\}", r" \1 ", text)
text = re.sub(r"\\[A-Za-z]+\*?(?:\[[^\]]*\])?(?:\{([^{}]*)\})?", r" \1 ", text)
text = re.sub(r"[{}$^_\\]", " ", text)
return text
def word_count(text: str) -> int:
return len(re.findall(r"[A-Za-z0-9]+(?:[-'][A-Za-z0-9]+)?", strip_latex(text)))
def extract_env(text: str, env: str) -> str:
match = re.search(rf"\\begin\{{{re.escape(env)}\}}(.*?)\\end\{{{re.escape(env)}\}}", text, re.S)
return match.group(1) if match else ""
def citation_keys(text: str) -> set[str]:
keys: set[str] = set()
for match in re.finditer(r"\\cite[A-Za-z]*\{([^}]+)\}", text):
for key in match.group(1).split(","):
keys.add(key.strip())
return keys
def bib_keys(text: str) -> set[str]:
return set(re.findall(r"@\w+\{([^,\s]+)", text))
def counted_text_for_pu(text: str) -> str:
parts = [extract_env(text, "abstract")]
body = text.split(r"\begin{document}", 1)[-1].split(r"\bibliography", 1)[0]
body = re.sub(r"\\title\{.*?\}", " ", body, flags=re.S)
body = re.sub(r"\\authors\{.*?\}", " ", body, flags=re.S)
body = re.sub(r"\\affiliation\{.*?\}\{.*?\}", " ", body, flags=re.S)
body = re.sub(r"\\correspondingauthor\{.*?\}\{.*?\}", " ", body, flags=re.S)
body = re.sub(r"\\begin\{keypoints\}.*?\\end\{keypoints\}", " ", body, flags=re.S)
body = re.sub(r"\\section\*\{Keywords\}.*?(?=\\section|\Z)", " ", body, flags=re.S)
captions = re.findall(r"\\caption\{(.*?)\}", body, re.S)
table_file = ROOT / "continuous_assoc_table.tex"
if table_file.exists():
captions.extend(re.findall(r"\\caption\{(.*?)\}", read(table_file), re.S))
body = re.sub(r"\\begin\{figure\}.*?\\end\{figure\}", " ", body, flags=re.S)
body = re.sub(r"\\begin\{table\}.*?\\end\{table\}", " ", body, flags=re.S)
body = re.sub(r"\\begin\{tabular\}.*?\\end\{tabular\}", " ", body, flags=re.S)
return "\n".join([body, *captions, *parts])
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
text = read(MAIN)
bib = read(BIB)
log = read(LOG)
failures: list[str] = []
warnings: list[str] = []
bkeys = bib_keys(bib)
ckeys = citation_keys(text)
total_refs = len(bkeys)
if not (30 <= total_refs <= 45):
failures.append(f"Total reference count is {total_refs}; expected 30--45.")
missing_from_bib = sorted(ckeys - bkeys)
if missing_from_bib:
failures.append(f"Cited keys missing from bibliography: {', '.join(missing_from_bib)}")
missing_added = sorted(set(ADDED_KEYS) - bkeys)
if missing_added:
failures.append(f"Added keys missing from bibliography: {', '.join(missing_added)}")
uncited_added = sorted(set(ADDED_KEYS) - ckeys)
if uncited_added:
failures.append(f"Added bibliography entries not cited: {', '.join(uncited_added)}")
if log:
if "There were undefined references" in log or "There were undefined citations" in log:
failures.append("LaTeX log reports undefined references or citations.")
if re.search(r"LaTeX Warning: (Citation|Reference) `[^']+' undefined", log):
failures.append("LaTeX log contains undefined citation/reference warning.")
else:
warnings.append("main.log is absent; compile before checking unresolved citations.")
intro_match = re.search(r"\\section\{Introduction\}(.*?)(?=\\section\{Data and Methods\})", text, re.S)
intro = intro_match.group(1).lower() if intro_match else ""
concepts = {
"signal-quality measures": "signal-quality measures",
"STA/LTA or amplitude-ratio": ["sta/lta", "amplitude-ratio"],
"SNR or trace-quality metadata": ["snr or trace-quality metadata", "trace-quality metadata"],
"denoising or filtering": ["denoising", "filtering"],
"ambient-noise processing": "ambient-noise processing",
"quality control": "quality control",
"dispersion measurements": "dispersion measurements",
"training-distribution question": "training-distribution question",
}
missing_concepts = []
for label, needles in concepts.items():
if isinstance(needles, str):
needles = [needles]
if not any(needle in intro for needle in needles):
missing_concepts.append(label)
if missing_concepts:
failures.append(f"Introduction missing required SNR/QC concepts: {', '.join(missing_concepts)}")
overclaims = ["all pipelines", "every seismic ai workflow", "always harmful", "proved", "universally"]
found_overclaims = [phrase for phrase in overclaims if phrase in text.lower()]
if found_overclaims:
failures.append(f"Unsupported overclaim phrases remain: {', '.join(found_overclaims)}")
abstract_words = word_count(extract_env(text, "abstract"))
if abstract_words >= 150:
failures.append(f"Abstract has {abstract_words} words; must be <150.")
keypoints = [strip_latex(item).strip() for item in re.findall(r"\\begin\{keypoints\}(.*?)\\end\{keypoints\}", text, re.S)[0].split(r"\item") if item.strip()]
keypoint_lengths = [len(item) for item in keypoints]
if len(keypoints) > 3:
failures.append(f"Found {len(keypoints)} key points; maximum is 3.")
for idx, length in enumerate(keypoint_lengths, 1):
if length > 140:
failures.append(f"Key Point {idx} has {length} characters; maximum is 140.")
counted_words = word_count(counted_text_for_pu(text))
figure_count = len(re.findall(r"\\begin\{figure\}", text))
table_count = len(re.findall(r"\\begin\{table\}", text)) + len(re.findall(r"\\input\{continuous_assoc_table\}", text))
pu = counted_words / 500.0 + figure_count + table_count
if pu > 12:
failures.append(f"Publication units estimate is {pu:.2f}; maximum is 12.")
report = {
"overall_status": "PASS" if not failures else "FAIL",
"total_reference_count": total_refs,
"added_reference_count": len(ADDED_KEYS),
"uncited_added_references": uncited_added,
"missing_cited_keys_from_bib": missing_from_bib,
"abstract_word_count": abstract_words,
"key_point_character_counts": keypoint_lengths,
"publication_units_estimate": round(pu, 3),
"counted_words_estimate": counted_words,
"failures": failures,
"warnings": warnings,
}
(OUT / "snr_reference_context_check.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
lines = [
"# SNR Reference Context Check",
"",
f"- Overall status: `{report['overall_status']}`",
f"- Total references: {total_refs}",
f"- Added references: {len(ADDED_KEYS)}",
f"- Abstract words: {abstract_words}",
f"- Key Point character counts: {', '.join(map(str, keypoint_lengths))}",
f"- Publication unit estimate: {pu:.2f}",
"",
"## Failures",
"",
*([f"- {item}" for item in failures] or ["- None"]),
"",
"## Warnings",
"",
*([f"- {item}" for item in warnings] or ["- None"]),
]
(OUT / "snr_reference_context_check.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2))
return 0 if not failures else 1
if __name__ == "__main__":
raise SystemExit(main())