From 04edd37a0d5f5b2b3549fa729aa9b9dade591cef Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Sun, 13 Sep 2026 14:51:43 -0700 Subject: [PATCH 1/7] Survey hygiene: DOI-aware citation keys, denominators and coded reporting rates (#42) - S-CD.1: build_survey.py reuses a references.bib key only when the DOIs agree (existing_dois, assign_keys); the volcano study of Obermann et al. (2013, JGR) was cited as the GJI depth-sensitivity paper that shares its key. It is now Obermann2013b in survey.bib and the appendix table, and both papers appear in the rendered reference list (2013a, 2013b). Test in tests/test_build_survey.py. - Counts (S-CD.1 text part, SCI-08): Appendix C states 103 rows for 103 publications with one DOI each, the seven that do not measure dv/v (marked n/a in the estimator column and excluded from the rates), and the wavefield split (81 ambient noise, 9 earthquake coda alone, the rest both) among the 96 that do; the Results sentence and the table caption say the same; "publications" is the noun for rows throughout. - S-CD.3: paper/survey_rates.py codes the uncertainty column of the 75 full-text publications that measure dv/v into a stated error (57), a quality rule only (11) and none (7), with the keyword rule and the eight hand-coded cells recorded in the script; band 68, window 59, estimator 74 of 75 reported; the 21 abstract-derived rows are excluded; counts written to paper/data/survey_rates.json and quoted in Appendix C. - Search rules: Appendix C states the documented provenance (six scans in June 2026, 117 rows deduplicated by DOI and checked against Crossref, four DOIs corrected, one row dropped) and the inclusion rule the table applies; the author confirms dates and queries in #48. Co-Authored-By: Claude Opus 5 --- paper/appendix_table.tex | 4 +- paper/build_survey.py | 197 +++++++++++++++++++++++++++-------- paper/data/survey_rates.json | 121 +++++++++++++++++++++ paper/manuscript_marine.qmd | 77 +++++++++----- paper/manuscript_marine.tex | 104 +++++++++++------- paper/survey.bib | 10 ++ paper/survey_rates.py | 155 +++++++++++++++++++++++++++ tests/test_build_survey.py | 76 ++++++++++++++ 8 files changed, 635 insertions(+), 109 deletions(-) create mode 100644 paper/data/survey_rates.json create mode 100644 paper/survey_rates.py create mode 100644 tests/test_build_survey.py diff --git a/paper/appendix_table.tex b/paper/appendix_table.tex index bf61a58..d927522 100644 --- a/paper/appendix_table.tex +++ b/paper/appendix_table.tex @@ -3,7 +3,7 @@ \setlength{\tabcolsep}{3pt} \renewcommand{\arraystretch}{1.15} \begin{longtable}{@{}p{2.1cm} >{\raggedright\arraybackslash}p{1.9cm} >{\raggedright\arraybackslash}p{1.5cm} >{\raggedright\arraybackslash}p{1.3cm} >{\raggedright\arraybackslash}p{1.5cm} >{\raggedright\arraybackslash}p{2.4cm} >{\raggedright\arraybackslash}p{3.0cm}@{}} -\caption{Processing choices of the 103 surveyed \dvv\ studies (the literature survey underpinning this paper). Every study is cited; \texttt{n/r} = not reported in the source. Frequency band, coda window, estimator, reference/stack scheme and uncertainty treatment are the choices Section~\ref{sec:results} shows to control the result.}\label{tab:survey}\\ +\caption{Processing choices of the 103 surveyed publications, one row each (the literature survey underpinning this paper). Every publication is cited; \texttt{n/r} = not reported in the source; \texttt{n/a} in the estimator column marks a publication that does not itself measure \dvv\ (theory, kernels, a companion method) and is kept for the choices it documents. Frequency band, coda window, estimator, reference/stack scheme and uncertainty treatment are the choices Section~\ref{sec:results} shows to control the result.}\label{tab:survey}\\ \toprule \textbf{Study} & \textbf{Site / region} & \textbf{Freq (Hz)} & \textbf{Coda (s)} & \textbf{Method} & \textbf{Reference / stack} & \textbf{Uncertainty treatment} \\ \midrule @@ -19,7 +19,7 @@ \endlastfoot \citet{Brenguier2008} & Piton de la Fournaise, La Réunion & 0.1-0.9 & n/r (slope of dtau vs tau; short scanning window) & MWCS (doublet) & Daily stacks referenced to long-term stack & linear-slope uncertainty; exclude >0.04\% \\ \hline \citet{Duputel2009} & Piton de la Fournaise, La Réunion & 0.1–1 & n/r & Other & Quasi-real-time stacks vs reference & n/r \\ \hline -\citet{Obermann2013} & Piton de la Fournaise, La Réunion & 0.1-1.0 & 10-45 & stretching & Daily stacks, referenced; coda at increasing lapse times & Weaver et al. coherence-based error formula \\ \hline +\citet{Obermann2013b} & Piton de la Fournaise, La Réunion & 0.1-1.0 & 10-45 & stretching & Daily stacks, referenced; coda at increasing lapse times & Weaver et al. coherence-based error formula \\ \hline \citet{HotovecEllis2014} & Mount St. Helens, WA, USA & 1-10 (also 1-5, 5-10) & windows after S arrival; >=5 with CCC>0.65 & doublet (Snieder 2002) & Stack across several hundred repeating-earthquake families (multiplets) & \textasciitilde{}0.1\% from SD of velocity fit \\ \hline \citet{HotovecEllis2015} & Mount St. Helens, WA, USA & 0.5-10 & 15 s window starting 2 s after first motion & stretching & Combines repeating-earthquake CWI with ambient-noise interferometry & reduced similarity (CC) in stretching window \\ \hline \citet{Rivet2015} & Piton de la Fournaise, La Réunion & 0.25-2 & n/r & MWCS & Daily stacks vs reference; network-averaged dv/v & coherency + linear-regression error \\ \hline diff --git a/paper/build_survey.py b/paper/build_survey.py index 77aacfd..e2502c7 100644 --- a/paper/build_survey.py +++ b/paper/build_survey.py @@ -42,13 +42,35 @@ def doi_of(url: str) -> str | None: # Combining diacritics -> LaTeX accent command (so bibtex, which is byte-based, # can abbreviate accented given names without splitting a multibyte character). _COMBINING = { - "́": "'", "̀": "`", "̈": '"', "̂": "^", "̃": "~", - "̄": "=", "̆": "u", "̇": ".", "̊": "r", "̋": "H", - "̌": "v", "̧": "c", "̨": "k", "̣": "d", + "́": "'", + "̀": "`", + "̈": '"', + "̂": "^", + "̃": "~", + "̄": "=", + "̆": "u", + "̇": ".", + "̊": "r", + "̋": "H", + "̌": "v", + "̧": "c", + "̨": "k", + "̣": "d", +} +_SPECIAL = { + "ø": r"{\o}", + "Ø": r"{\O}", + "ß": r"{\ss}", + "æ": r"{\ae}", + "Æ": r"{\AE}", + "œ": r"{\oe}", + "Œ": r"{\OE}", + "ł": r"{\l}", + "Ł": r"{\L}", + "đ": r"{\dj}", + "ð": r"{\dh}", + "þ": r"{\th}", } -_SPECIAL = {"ø": r"{\o}", "Ø": r"{\O}", "ß": r"{\ss}", "æ": r"{\ae}", - "Æ": r"{\AE}", "œ": r"{\oe}", "Œ": r"{\OE}", "ł": r"{\l}", - "Ł": r"{\L}", "đ": r"{\dj}", "ð": r"{\dh}", "þ": r"{\th}"} def latexify(text: str) -> str: @@ -69,7 +91,9 @@ def latexify(text: str) -> str: base = f"{{\\{acc}{base}}}" out.append(base) else: # unmapped: fall back to ASCII transliteration - out.append(unicodedata.normalize("NFKD", ch).encode("ascii", "ignore").decode()) + out.append( + unicodedata.normalize("NFKD", ch).encode("ascii", "ignore").decode() + ) return "".join(out) @@ -94,20 +118,35 @@ def clean_html(text: str) -> str: Crossref returns magnitude notation as ``Mw``; render the sub/superscripts properly and drop italic/bold tags. """ - text = re.sub(r"(.*?)", - lambda m: r"\textsubscript{" + re.sub(r"<[^>]+>", "", m.group(1)) + "}", - text, flags=re.I | re.S) - text = re.sub(r"(.*?)", - lambda m: r"\textsuperscript{" + re.sub(r"<[^>]+>", "", m.group(1)) + "}", - text, flags=re.I | re.S) + text = re.sub( + r"(.*?)", + lambda m: r"\textsubscript{" + re.sub(r"<[^>]+>", "", m.group(1)) + "}", + text, + flags=re.I | re.S, + ) + text = re.sub( + r"(.*?)", + lambda m: r"\textsuperscript{" + re.sub(r"<[^>]+>", "", m.group(1)) + "}", + text, + flags=re.I | re.S, + ) return re.sub(r"<[^>]+>", "", text) def tex_escape(s: str) -> str: s = str(s or "") - for a, b in [("\\", r"\textbackslash{}"), ("&", r"\&"), ("%", r"\%"), - ("$", r"\$"), ("#", r"\#"), ("_", r"\_"), ("{", r"\{"), - ("}", r"\}"), ("~", r"\textasciitilde{}"), ("^", r"\textasciicircum{}")]: + for a, b in [ + ("\\", r"\textbackslash{}"), + ("&", r"\&"), + ("%", r"\%"), + ("$", r"\$"), + ("#", r"\#"), + ("_", r"\_"), + ("{", r"\{"), + ("}", r"\}"), + ("~", r"\textasciitilde{}"), + ("^", r"\textasciicircum{}"), + ]: s = s.replace(a, b) return s @@ -142,13 +181,63 @@ def existing_keys() -> set[str]: return set(re.findall(r"@\w+\{([^,]+),", REFS.read_text())) +def existing_dois(text: str | None = None) -> dict[str, str | None]: + """``key -> doi`` for every entry of references.bib (``None`` without a doi).""" + if text is None: + if not REFS.exists(): + return {} + text = REFS.read_text() + out: dict[str, str | None] = {} + for m in re.finditer(r"@\w+\{([^,]+),(.*?)(?=\n@|\Z)", text, flags=re.S): + key, body = m.group(1).strip(), m.group(2) + d = re.search(r"doi\s*=\s*[{\"]\s*([^}\"]+?)\s*[}\"]", body, flags=re.I) + out[key] = doi_of(d.group(1)) if d else None + return out + + +def assign_keys( + rows: list[dict], reuse: dict[str, str | None] +) -> list[tuple[str, dict, bool]]: + """Give every survey row a citation key; returns ``(key, row, reused)`` in order. + + A row reuses a ``references.bib`` key only when the surname and year match + *and* the DOIs agree (or the bib entry carries no DOI). Two different + papers with the same surname and year get ``Key2013`` and ``Key2013b`` + (then ``c`` and so on), whichever of them the narrative already cites. + Before the 2026-09 revision a key that existed in references.bib was + reused without the DOI check, so the volcano study of Obermann et al. + (2013, JGR) was cited as the depth-sensitivity paper (GJI) that shares + its key (review finding S-CD.1). + """ + used: dict[str, str] = {} # key -> doi assigned in this run + out: list[tuple[str, dict, bool]] = [] + + def _taken(k: str, doi: str) -> bool: + if k in used: + return used[k] != doi + if k in reuse: + bib_doi = reuse[k] + return bib_doi is not None and bib_doi != doi + return False + + for row in rows: + doi = doi_of(row["doi_url"]) or row["doi_url"] + key = make_key(row["authors_year"], row.get("year", "")) + while _taken(key, doi): + key += "b" if key[-1].isdigit() else chr(ord(key[-1]) + 1) + used[key] = doi + out.append((key, row, key in reuse)) + return out + + def bib_entry(key: str, row: dict, rec: dict | None) -> str: """One BibTeX entry. Falls back to @misc when Crossref metadata is absent.""" doi = doi_of(row["doi_url"]) or "" year = row.get("year", "") if rec and rec.get("title"): fields = { - "author": bib_authors(rec) and latexify(bib_authors(rec)) + "author": bib_authors(rec) + and latexify(bib_authors(rec)) or authors_from_label(row["authors_year"]), "title": latexify(clean_html(rec["title"].rstrip(". "))), "journal": latexify(clean_html(rec.get("container", ""))), @@ -187,29 +276,49 @@ def bib_entry(key: str, row: dict, rec: dict | None) -> str: def appendix_table(entries: list[tuple[str, dict]]) -> str: """A longtable cataloguing every study's processing choices (all \\citet).""" - colspec = "@{}p{2.1cm} " + " ".join( - f">{{\\raggedright\\arraybackslash}}p{{{w}}}" for *_, w in COLS) + "@{}" - head = "\\textbf{Study} & " + " & ".join( - f"\\textbf{{{h}}}" for _, h, _ in COLS) + r" \\" + colspec = ( + "@{}p{2.1cm} " + + " ".join(f">{{\\raggedright\\arraybackslash}}p{{{w}}}" for *_, w in COLS) + + "@{}" + ) + head = ( + "\\textbf{Study} & " + + " & ".join(f"\\textbf{{{h}}}" for _, h, _ in COLS) + + r" \\" + ) out = [ r"% Auto-generated by paper/build_survey.py -- do not edit by hand.", r"{\footnotesize", r"\setlength{\tabcolsep}{3pt}", r"\renewcommand{\arraystretch}{1.15}", f"\\begin{{longtable}}{{{colspec}}}", - r"\caption{Processing choices of the " + str(len(entries)) + - r" surveyed \dvv\ studies (the literature survey underpinning this paper). " - r"Every study is cited; \texttt{n/r} = not reported in the source. " + r"\caption{Processing choices of the " + + str(len(entries)) + + r" surveyed publications, one row each (the literature survey " + r"underpinning this paper). Every publication is cited; \texttt{n/r} = " + r"not reported in the source; \texttt{n/a} in the estimator column marks " + r"a publication that does not itself measure \dvv\ (theory, kernels, a " + r"companion method) and is kept for the choices it documents. " r"Frequency band, coda window, estimator, reference/stack scheme and " r"uncertainty treatment are the choices Section~\ref{sec:results} shows " r"to control the result.}\label{tab:survey}\\", - r"\toprule", head, r"\midrule", r"\endfirsthead", - r"\multicolumn{" + str(len(COLS) + 1) + - r"}{@{}l}{\footnotesize\itshape Table~\ref{tab:survey} continued}\\", - r"\toprule", head, r"\midrule", r"\endhead", - r"\midrule\multicolumn{" + str(len(COLS) + 1) + - r"}{r@{}}{\footnotesize\itshape continued on next page}\\", r"\endfoot", - r"\bottomrule", r"\endlastfoot", + r"\toprule", + head, + r"\midrule", + r"\endfirsthead", + r"\multicolumn{" + + str(len(COLS) + 1) + + r"}{@{}l}{\footnotesize\itshape Table~\ref{tab:survey} continued}\\", + r"\toprule", + head, + r"\midrule", + r"\endhead", + r"\midrule\multicolumn{" + + str(len(COLS) + 1) + + r"}{r@{}}{\footnotesize\itshape continued on next page}\\", + r"\endfoot", + r"\bottomrule", + r"\endlastfoot", ] for key, row in entries: cells = [f"\\citet{{{key}}}"] @@ -226,21 +335,14 @@ def appendix_table(entries: list[tuple[str, dict]]) -> str: def main() -> None: rows = list(csv.DictReader(CSV.open())) cache = json.loads(CACHE.read_text()) if CACHE.exists() else {} - reuse = existing_keys() + reuse = existing_dois() - entries: list[tuple[str, dict]] = [] # (key, row) in CSV order, for the table - new_bib: list[str] = [] # entries to write to survey.bib - used: dict[str, str] = {} # key -> doi (disambiguation) + entries: list[tuple[str, dict]] = [] # (key, row) in CSV order, for the table + new_bib: list[str] = [] # entries to write to survey.bib - for row in rows: - doi = doi_of(row["doi_url"]) or row["doi_url"] - key = make_key(row["authors_year"], row.get("year", "")) - # Disambiguate identical surname+year that are different papers. - while key in used and used[key] != doi and key not in reuse: - key += "b" if key[-1].isdigit() else chr(ord(key[-1]) + 1) - used[key] = doi + for key, row, reused in assign_keys(rows, reuse): entries.append((key, row)) - if key not in reuse: # reuse references.bib entry if present + if not reused: # reuse references.bib entry if present rec = cache.get(doi_of(row["doi_url"]) or "") new_bib.append(bib_entry(key, row, rec)) @@ -248,10 +350,13 @@ def main() -> None: "% Auto-generated by paper/build_survey.py from the literature survey.\n" "% Keys that also appear in references.bib are intentionally omitted here\n" "% (the narrative entry is reused) so each paper is listed once.\n\n" - + "\n".join(new_bib)) + + "\n".join(new_bib) + ) APPENDIX.write_text(appendix_table(entries)) - reused = len(entries) - len(new_bib) - print(f"wrote {SURVEY_BIB.name} ({len(new_bib)} new entries, {reused} reuse references.bib)") + n_reused = len(entries) - len(new_bib) + print( + f"wrote {SURVEY_BIB.name} ({len(new_bib)} new entries, {n_reused} reuse references.bib)" + ) print(f"wrote {APPENDIX.name} ({len(entries)} studies catalogued + cited)") diff --git a/paper/data/survey_rates.json b/paper/data/survey_rates.json new file mode 100644 index 0000000..93ea628 --- /dev/null +++ b/paper/data/survey_rates.json @@ -0,0 +1,121 @@ +{ + "source": "literature/dvv_processing_parameters.csv", + "rows": 103, + "distinct_dois": 103, + "rows_measuring_dvv": 96, + "rows_not_measuring_dvv": [ + "Köpfli et al., 2024", + "Nakata & Snieder 2011", + "Bertello, Berti, Castellaro, Squarzoni (2018)", + "Tsai, 2011", + "Guillemot et al., 2020", + "Guillemot et al., 2021", + "Snieder, Grêt, Douma & Scales 2002" + ], + "rows_full_text": 82, + "rows_abstract_only": 21, + "rows_full_text_measuring_dvv": 75, + "signal_source": { + "Ambient noise": 81, + "Earthquake coda": 11, + "Both": 11 + }, + "earthquake_coda_only_measuring": 9, + "reported_among_full_text_measuring": { + "frequency band": { + "reported": 68, + "of": 75 + }, + "coda window": { + "reported": 59, + "of": 75 + }, + "estimator": { + "reported": 74, + "of": 75 + } + }, + "uncertainty_coding_among_full_text_measuring": { + "error_estimate": 57, + "qc_threshold_only": 11, + "none": 7, + "of": 75 + }, + "uncertainty_codes": { + "Brenguier et al., 2008": "error", + "Obermann et al., 2013": "error", + "Hotovec-Ellis et al., 2014": "error", + "Hotovec-Ellis et al., 2015": "qc", + "Rivet et al., 2015": "error", + "De Plaen et al., 2016": "error", + "Donaldson et al., 2017": "error", + "Takano et al., 2017": "error", + "Lesage et al., 2018": "error", + "De Plaen et al., 2019": "error", + "Donaldson et al., 2019": "qc", + "Olivier et al., 2019": "error", + "Yates et al., 2019": "error", + "Feng et al., 2020": "error", + "Hotovec-Ellis et al., 2022": "none", + "Yates et al., 2024": "error", + "Yukutake et al., 2025": "error", + "Schaff & Beroza 2004": "error", + "Rubinstein & Beroza 2005": "qc", + "Wegler & Sens-Schönfelder 2007": "error", + "Hadziioannou, Larose, Coutant, Roux, Campillo 2009": "error", + "Wegler, Nakahara, Sens-Schönfelder, Korn, Shiomi 2009": "error", + "Chen, Froment, Liu, Campillo 2010": "none", + "Rivet, Campillo, Shapiro, Cruz-Atienza, Radiguet, Cotte, Kostoglodov 2011": "error", + "Hobiger, Wegler, Shiomi, Nakahara 2012": "qc", + "Minato, Tsuji, Ohmi, Matsuoka 2012": "error", + "Obermann, Planès, Larose, Sens-Schönfelder, Campillo 2013": "error", + "Liu, Huang, Peng, Su 2014": "error", + "Taira, Brenguier, Kong 2015": "error", + "Gassenmeier, Sens-Schönfelder, Eulenfeld, Bartsch, Victor, Tilmann, Korn 2016": "error", + "Hillers, Campillo, Brenguier, Moreau, Agnew, Ben-Zion 2019": "qc", + "Wang, Campillo, Brenguier, Lecointre, Takeda, Hashima 2019": "error", + "Mao, Mordret, Campillo, Fang, van der Hilst 2020": "qc", + "Poli, Marguin, Wang, D'Agostino, Johnson 2020": "qc", + "Boschelli, Zaccarelli, Faenza 2021": "error", + "Sheng, Mordret, Sager, Brenguier, Boué, Rousset, Vernon, Higueret, Ben-Zion 2022": "error", + "Mainsant, Larose, Brönnimann, Jongmans, Michoud, Jaboyedoff (2012)": "error", + "Bontemps, Lacroix, Larose, Jara, Taipe (2020)": "none", + "Le Breton, Bontemps, Guillemot, Baillet, Larose (2021)": "error", + "Whiteley, Chambers, Uhlemann et al. (2026)": "none", + "Sens-Schönfelder & Wegler, 2006": "error", + "Lecocq, Longuevergne, Pedersen, Brenguier & Stammler, 2017": "error", + "Nimiya, Ikeda & Tsuji, 2017": "error", + "Wang, Brenguier, Campillo, Lecointre, Takeda & Aoki, 2017": "qc", + "Clements & Denolle, 2018": "qc", + "Kim & Lekic, 2019": "none", + "Andajani, Tsuji, Snieder & Ikeda, 2020": "error", + "Gaubert-Bastide, Garambois, et al., 2022": "error", + "Illien, Andermann, Sens-Schönfelder, et al., 2022": "none", + "Mao, Lecointre, van der Hilst & Campillo, 2022": "error", + "Clements & Denolle, 2023": "error", + "Delouche, et al., 2023": "error", + "Fokker, Ruigrok, Hawkins & Trampert, 2023": "error", + "Zhang et al., 2023": "error", + "Mao, Ellsworth, Zheng & Beroza, 2025": "error", + "Mordret et al., 2016": "error", + "James et al., 2019": "error", + "Lindner et al., 2021": "error", + "Luo et al., 2023": "error", + "Gassenmeier et al., 2015": "error", + "Obermann et al., 2015": "none", + "Olivier et al., 2017": "error", + "Taira et al., 2018": "error", + "Bensen et al. 2007": "error", + "Clarke, Zaccarelli, Shapiro & Brenguier 2011": "error", + "Hadziioannou, Larose, Baig, Roux & Campillo 2011": "qc", + "Weaver, Hadziioannou, Larose & Campillo 2011": "error", + "Zhan, Tsai & Clayton 2013": "error", + "Lecocq, Caudron & Brenguier 2014": "qc", + "Mikesell, Malcolm, Yang & Haney 2015": "error", + "Stehly, Froment, Campillo, Liu & Chen 2015": "error", + "Daskalakis, Evangelidis, Garnier, Melis, Papanicolaou & Tsogka 2016": "error", + "Obermann, Planès, Hadziioannou & Campillo 2016": "error", + "Wang & Yao 2020": "error", + "Yuan, Bryan & Denolle 2021": "error" + } +} diff --git a/paper/manuscript_marine.qmd b/paper/manuscript_marine.qmd index 43fea18..4839faa 100644 --- a/paper/manuscript_marine.qmd +++ b/paper/manuscript_marine.qmd @@ -160,7 +160,7 @@ without full reporting of parameter choices we cannot interpret a reported \dvv\ as a robust value. Here we make the problem concrete for \dvv\ monitoring. We use purely synthetic correlation time series to test the methods (estimators) and parameter choices that the community -makes to estimate \dvv$\,$which we report over 103 studies in Appendix\ \ref{app:survey}. +makes to estimate \dvv$\,$which we report over 103 publications in Appendix\ \ref{app:survey}. We do not aim to report the "best" pipeline, which is most often the one reported in scientific papers, but instead document the parameter impacts (Section\ \ref{sec:results}). We then propose a new measurement error that incorporates these effects into a data @@ -690,7 +690,7 @@ continuum, from one global reference, through segment references joined pairwise, to the fully coupled inversion, than as a menu of unrelated choices. Against that, the uncumulated day-by-day trailing reference swept above is a -limit case rather than a practice: we found no surveyed study that re-baselines +limit case rather than a practice: we found no surveyed publication that re-baselines continuously and reports the increments without summing them. It is retained here because it isolates what re-baselining costs when the trend is not reconstructed, which is the failure mode the two families above exist to avoid. @@ -791,8 +791,9 @@ RMS an order of magnitude worse even after clearing it. The accepted range for each of these parameters across the published literature is catalogued directly, study by study, in the survey of Appendix\ \ref{app:survey} (Table\ \ref{tab:survey}): the frequency band, coda -window, estimator, and uncertainty treatment actually reported by 103 ambient-noise -\dvv\ studies. +window, estimator, and uncertainty treatment actually reported by the 96 +publications of the survey that measure \dvv\ (of 103 catalogued; 81 from +ambient noise alone, 11 from earthquake coda alone, 11 from both). ## Coda window {#sec:param_window} @@ -808,7 +809,7 @@ $\sim\!3.9\,\%$ (the noise floor, not the signal), while a window hand-adapted to the band (3--12$\,$s) recovers the truth at $\sim\!0.01\,\%$ --- nearly a 400$\times$ difference from this one choice alone. This is itself an instance of a literature-documented tension: band-matched windowing is recommended, but -Table\ \ref{tab:survey} shows many surveyed studies instead reuse one fixed +Table\ \ref{tab:survey} shows many surveyed publications instead reuse one fixed window across bands. Hand-adapting the window per band, as above, requires knowing the band in @@ -1725,27 +1726,55 @@ mean $\bar x$ but obey $\sigma_{\mathrm{SD}}/\sigma_{\mathrm{SE}}=\sqrt{N}$. # Survey of processing choices across the literature {#app:survey} -Table\ \ref{tab:survey} catalogues the processing choices of the ambient-noise -\dvv\ monitoring studies we surveyed: 103 rows for 103 publications, one -per publication (the full machine-readable version and provenance are -distributed with the codameter package). It is the empirical basis -for the paper's claim that no convention is shared: the estimator, the frequency -band, the coda window, the reference scheme and --- most unevenly of all --- the -uncertainty treatment vary study to study, and the last is frequently -unreported. Every study is cited here. +Table\ \ref{tab:survey} catalogues the processing choices of the +coda-wave \dvv\ monitoring publications we surveyed: 103 rows for 103 +publications, one per publication and one DOI each (the machine-readable +table and its provenance are distributed with the codameter package). It is +the empirical basis for the paper's claim that no convention is shared: the +estimator, the frequency band, the coda window, the reference scheme and +--- most unevenly of all --- the uncertainty treatment vary from +publication to publication, and the last is frequently unreported. Every +publication is cited here. + +The survey was assembled in June 2026 by six literature scans of the +published journal literature, one per application group (volcano, +earthquake and fault, landslide, groundwater, cryosphere, geothermal and +reservoir, methodology), each recording a DOI per publication; the scans +returned 117 rows, which were deduplicated by DOI, checked against the +Crossref record of each DOI (four wrong DOIs corrected, one unverifiable +row dropped) and reduced to the 103 publications listed +(\texttt{literature/README.md} records the corrections). The table keeps a +publication when it measures a temporal relative velocity change from the +coda of ambient-noise correlations or of repeating earthquakes, or when it +defines a method or a sensitivity kernel that the measuring publications +rely on. Seven of the 103 do not themselves measure \dvv\ (theory, +depth kernels, deconvolution interferometry, spectral methods) and are marked +``n/a'' in the estimator column; the counts below exclude them. Of the 96 +that measure \dvv, 81 use ambient noise alone, 9 use earthquake coda alone +and the rest both (the machine-readable table records this per row); +the coda-only rows are kept because their processing choices are the same +choices, made on a different wavefield. The four measurement fields (frequency band, coda window, estimator and -uncertainty treatment) were re-checked against the \emph{full text} for the 82 -studies we could read (open access plus institutional access); for those rows a -blank or ``n/r'' means the -value is genuinely not stated in the paper. The remaining rows were populated -from abstracts and search metadata, where ``n/r'' means only that the value was -not found in the abstract, not that the study failed to report it; those cells -are flagged in the machine-readable table (\texttt{measurement\_source}) and -remain to be filled from the paywalled full texts. Because a cell populated from -an abstract can only move from ``n/r'' to reported once the full text is read, -the apparent under-reporting in the table is an upper bound on the true -under-reporting; the verified rate is the one among the 82 full-text rows. +uncertainty treatment) were re-checked against the \emph{full text} for the +82 publications we could read (open access plus institutional access); for +those rows a blank or ``n/r'' means the value is genuinely not stated in +the paper. The remaining 21 rows were populated from abstracts and search +metadata, where ``n/r'' means only that the value was not found in the +abstract, not that the publication failed to report it; those cells are +flagged in the machine-readable table (\texttt{measurement\_source}), +remain to be filled from the paywalled full texts, and are excluded from +the rates below. Among the 75 full-text publications that measure \dvv, +68 report the frequency band, 59 the coda window and 74 the estimator. For +the uncertainty column we distinguish a stated error on \dvv\ (a formula, +a standard deviation or error, a confidence interval, a regression or +bootstrap error, a stated noise or significance level) from a +quality-control rule alone (a coherence or correlation threshold, a +weighting, a rejection criterion, with no error magnitude) and from +nothing: 57 of the 75 state an error, 11 state a quality rule only, and 7 +state neither. The rule, the keyword coding and the eight cells coded by +hand are in \texttt{paper/survey\_rates.py}, which writes these counts to +\texttt{paper/data/survey\_rates.json}. \input{appendix_table.tex} diff --git a/paper/manuscript_marine.tex b/paper/manuscript_marine.tex index 21093a5..1011a82 100644 --- a/paper/manuscript_marine.tex +++ b/paper/manuscript_marine.tex @@ -413,16 +413,16 @@ \section{Introduction}\label{sec:intro} Here we make the problem concrete for \dvv~monitoring. We use purely synthetic correlation time series to test the methods (estimators) and parameter choices that the community makes to estimate \dvv\(\,\)which -we report over 103 studies in Appendix~\ref{app:survey}. We do not aim -to report the ``best'' pipeline, which is most often the one reported in -scientific papers, but instead document the parameter impacts -(Section~\ref{sec:results}). We then propose a new measurement error -that incorporates these effects into a data covariance matrix \(C_d\) -(Section~\ref{sec:bayes}). Throughout this paper, key notation includes: -\(\sigma\) (measurement uncertainty of a recovered \dvv~estimate), \(B\) -(frequency bandwidth), and \(W=[t_1,t_2]\) (coda window); subscripts -distinguish specific contexts (e.g., \(\sigma_k\) for per-component -uncertainty), each defined where first introduced. +we report over 103 publications in Appendix~\ref{app:survey}. We do not +aim to report the ``best'' pipeline, which is most often the one +reported in scientific papers, but instead document the parameter +impacts (Section~\ref{sec:results}). We then propose a new measurement +error that incorporates these effects into a data covariance matrix +\(C_d\) (Section~\ref{sec:bayes}). Throughout this paper, key notation +includes: \(\sigma\) (measurement uncertainty of a recovered +\dvv~estimate), \(B\) (frequency bandwidth), and \(W=[t_1,t_2]\) (coda +window); subscripts distinguish specific contexts (e.g., \(\sigma_k\) +for per-component uncertainty), each defined where first introduced. One example of propagating such error into downstream science is the migration of the surface \dvv~measurement to depth profiles of @@ -983,13 +983,13 @@ \subsection{Reference}\label{sec:param_ref} fully coupled inversion, than as a menu of unrelated choices. Against that, the uncumulated day-by-day trailing reference swept above -is a limit case rather than a practice: we found no surveyed study that -re-baselines continuously and reports the increments without summing -them. It is retained here because it isolates what re-baselining costs -when the trend is not reconstructed, which is the failure mode the two -families above exist to avoid. codameter implements the joint inversion -but neither the cumulated trailing reference nor cluster-based reference -selection. +is a limit case rather than a practice: we found no surveyed publication +that re-baselines continuously and reports the increments without +summing them. It is retained here because it isolates what re-baselining +costs when the trend is not reconstructed, which is the failure mode the +two families above exist to avoid. codameter implements the joint +inversion but neither the cumulated trailing reference nor cluster-based +reference selection. \begin{figure} \centering @@ -1094,7 +1094,9 @@ \subsection{Substacking}\label{sec:param_substack} literature is catalogued directly, study by study, in the survey of Appendix~\ref{app:survey} (Table~\ref{tab:survey}): the frequency band, coda window, estimator, and uncertainty treatment actually reported by -103 ambient-noise \dvv~studies. +the 96 publications of the survey that measure \dvv~(of 103 catalogued; +81 from ambient noise alone, 11 from earthquake coda alone, 11 from +both). \subsection{Coda window}\label{sec:param_window} @@ -1112,8 +1114,8 @@ \subsection{Coda window}\label{sec:param_window} recovers the truth at \(\sim\!0.01\,\%\) --- nearly a 400\(\times\) difference from this one choice alone. This is itself an instance of a literature-documented tension: band-matched windowing is recommended, -but Table~\ref{tab:survey} shows many surveyed studies instead reuse one -fixed window across bands. +but Table~\ref{tab:survey} shows many surveyed publications instead +reuse one fixed window across bands. Hand-adapting the window per band, as above, requires knowing the band in advance and re-tuning per deployment. A more principled alternative @@ -2109,27 +2111,55 @@ \section{Survey of processing choices across the literature}\label{app:survey} Table~\ref{tab:survey} catalogues the processing choices of the -ambient-noise \dvv~monitoring studies we surveyed: 103 rows for 103 -publications, one per publication (the full machine-readable version and -provenance are distributed with the codameter package). It is the -empirical basis for the paper's claim that no convention is shared: the -estimator, the frequency band, the coda window, the reference scheme and ---- most unevenly of all --- the uncertainty treatment vary study to -study, and the last is frequently unreported. Every study is cited here. +coda-wave \dvv~monitoring publications we surveyed: 103 rows for 103 +publications, one per publication and one DOI each (the machine-readable +table and its provenance are distributed with the codameter package). It +is the empirical basis for the paper's claim that no convention is +shared: the estimator, the frequency band, the coda window, the +reference scheme and --- most unevenly of all --- the uncertainty +treatment vary from publication to publication, and the last is +frequently unreported. Every publication is cited here. + +The survey was assembled in June 2026 by six literature scans of the +published journal literature, one per application group (volcano, +earthquake and fault, landslide, groundwater, cryosphere, geothermal and +reservoir, methodology), each recording a DOI per publication; the scans +returned 117 rows, which were deduplicated by DOI, checked against the +Crossref record of each DOI (four wrong DOIs corrected, one unverifiable +row dropped) and reduced to the 103 publications listed +(\texttt{literature/README.md} records the corrections). The table keeps +a publication when it measures a temporal relative velocity change from +the coda of ambient-noise correlations or of repeating earthquakes, or +when it defines a method or a sensitivity kernel that the measuring +publications rely on. Seven of the 103 do not themselves measure +\dvv~(theory, depth kernels, deconvolution interferometry, spectral +methods) and are marked ``n/a'\,' in the estimator column; the counts +below exclude them. Of the 96 that measure \dvv, 81 use ambient noise +alone, 9 use earthquake coda alone and the rest both (the +machine-readable table records this per row); the coda-only rows are +kept because their processing choices are the same choices, made on a +different wavefield. The four measurement fields (frequency band, coda window, estimator and uncertainty treatment) were re-checked against the \emph{full text} for -the 82 studies we could read (open access plus institutional access); -for those rows a blank or -\texttt{n/r\textquotesingle{}\textquotesingle{}\ means\ the\ value\ is\ genuinely\ not\ stated\ in\ the\ paper.\ The\ remaining\ rows\ were\ populated\ from\ abstracts\ and\ search\ metadata,\ where}n/r'\,' +the 82 publications we could read (open access plus institutional +access); for those rows a blank or +\texttt{n/r\textquotesingle{}\textquotesingle{}\ means\ the\ value\ is\ genuinely\ not\ stated\ in\ the\ paper.\ The\ remaining\ 21\ rows\ were\ populated\ from\ abstracts\ and\ search\ metadata,\ where}n/r'\,' means only that the value was not found in the abstract, not that the -study failed to report it; those cells are flagged in the -machine-readable table (\texttt{measurement\_source}) and remain to be -filled from the paywalled full texts. Because a cell populated from an -abstract can only move from ``n/r'\,' to reported once the full text is -read, the apparent under-reporting in the table is an upper bound on the -true under-reporting; the verified rate is the one among the 82 -full-text rows. +publication failed to report it; those cells are flagged in the +machine-readable table (\texttt{measurement\_source}), remain to be +filled from the paywalled full texts, and are excluded from the rates +below. Among the 75 full-text publications that measure \dvv, 68 report +the frequency band, 59 the coda window and 74 the estimator. For the +uncertainty column we distinguish a stated error on \dvv~(a formula, a +standard deviation or error, a confidence interval, a regression or +bootstrap error, a stated noise or significance level) from a +quality-control rule alone (a coherence or correlation threshold, a +weighting, a rejection criterion, with no error magnitude) and from +nothing: 57 of the 75 state an error, 11 state a quality rule only, and +7 state neither. The rule, the keyword coding and the eight cells coded +by hand are in \texttt{paper/survey\_rates.py}, which writes these +counts to \texttt{paper/data/survey\_rates.json}. \input{appendix_table.tex} diff --git a/paper/survey.bib b/paper/survey.bib index 77e12b3..d93cd2f 100644 --- a/paper/survey.bib +++ b/paper/survey.bib @@ -22,6 +22,16 @@ @article{Duputel2009 doi = {10.1016/j.jvolgeores.2008.11.024} } +@article{Obermann2013b, + author = {Obermann, A. and Plan{\`e}s, T. and Larose, E. and Campillo, M.}, + title = {Imaging preeruptive and coeruptive structural and mechanical changes of a volcano with ambient seismic noise}, + journal = {Journal of Geophysical Research: Solid Earth}, + year = {2013}, + volume = {118}, + pages = {6285--6294}, + doi = {10.1002/2013jb010399} +} + @article{HotovecEllis2014, author = {HotovecEllis, A. J. and Gomberg, J. and Vidale, J. E. and Creager, K. C.}, title = {A continuous record of intereruption velocity change at Mount St. Helens from coda wave interferometry}, diff --git a/paper/survey_rates.py b/paper/survey_rates.py new file mode 100644 index 0000000..23029f9 --- /dev/null +++ b/paper/survey_rates.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Denominators and verified reporting rates of the literature survey (Appendix C). + +Reads ``literature/dvv_processing_parameters.csv`` and writes +``paper/data/survey_rates.json`` with the counts the appendix quotes: + +- rows and distinct DOIs (one row per publication); +- rows that measure dv/v (``dvv_method`` not ``n/a``) and the rows that do not + (theory, kernels, deconvolution interferometry, spectral methods); +- rows populated from the full text versus from abstracts; +- among the full-text rows that measure dv/v, the fraction reporting the + frequency band, the coda window and the estimator, and the uncertainty + column coded by the rule below. + +Uncertainty coding rule (applied to the ``uncertainty_treatment`` cell of every +full-text row that measures dv/v): + +``error`` + the paper states an uncertainty on dv/v: a formula, a standard deviation + or standard error, a confidence interval, a regression or bootstrap + error, a stated noise level or significance level; +``qc`` + the paper states only a quality-control rule (a coherence or correlation + threshold, a weighting, a rejection criterion) and no error magnitude; +``none`` + the cell is ``n/r``, ``n/a`` or ``none``. + +The rule is keyword-based with the overrides listed in ``OVERRIDES`` for the +cells the keywords misread; every override is a judgement recorded here so +that it can be disputed. + +Run: python paper/survey_rates.py +""" + +from __future__ import annotations + +import csv +import json +import re +from collections import Counter +from pathlib import Path + +HERE = Path(__file__).resolve().parent +CSV = HERE.parent / "literature" / "dvv_processing_parameters.csv" +OUT = HERE / "data" / "survey_rates.json" + +ERR = re.compile( + r"error|\bsd\b|std|sigma|uncertain|\bci\b|confidence|bootstrap|rms|residual|" + r"regression|formula|weaver|clarke|variance|spread|scatter|misfit|interval|" + r"deviation|jackknife|posterior|bayes|percentile|quantile|propagat|noise level|" + r"\bse\b|\berr\b", + re.I, +) +QC = re.compile( + r"\bcc\b|coheren|threshold|reject|discard|qc|quality|correlation coefficient|" + r">\s*0\.|>=|weight", + re.I, +) +# authors_year prefix -> code, for cells the keyword rule misreads. +OVERRIDES = { + "Lesage et al., 2018": "error", # empirical noise level ~0.05% + "Gassenmeier": "error", # confidence intervals from a Gaussian fit + "Wegler & Sens-Sch": "error", # day-to-day fluctuation ~0.1% stated + "Stehly, Froment": "error", # +-0.1% significance level stated + "Kim & Lekic": "none", # "none (grid-search CC max)" + "Whiteley": "none", + "Illien": "none", + "Obermann et al., 2015": "none", +} + + +def is_blank(v: str) -> bool: + v = (v or "").strip().lower() + return v == "" or v.startswith("n/r") or v.startswith("n/a") + + +def measures_dvv(row: dict) -> bool: + return not row["dvv_method"].strip().lower().startswith("n/a") + + +def code_uncertainty(row: dict) -> str: + for prefix, code in OVERRIDES.items(): + if row["authors_year"].startswith(prefix): + return code + v = row["uncertainty_treatment"] or "" + if is_blank(v): + return "none" + if v.strip().lower().startswith("none"): + # "none (QC only: coh>=0.5)" is a quality rule; "none (data report)" is none. + return "qc" if QC.search(v) else "none" + if ERR.search(v): + return "error" + if QC.search(v): + return "qc" + return "none" + + +def main() -> int: + rows = list(csv.DictReader(CSV.open())) + full = [r for r in rows if r["measurement_source"].lower().startswith("full")] + meas = [r for r in rows if measures_dvv(r)] + meas_full = [r for r in full if measures_dvv(r)] + fields = { + "freq_band_hz": "frequency band", + "coda_window_s": "coda window", + "dvv_method": "estimator", + } + rates = { + name: { + "reported": sum(not is_blank(r[f]) for r in meas_full), + "of": len(meas_full), + } + for f, name in fields.items() + } + unc = Counter(code_uncertainty(r) for r in meas_full) + payload = { + "source": str(CSV.relative_to(HERE.parent)), + "rows": len(rows), + "distinct_dois": len({r["doi_url"] for r in rows}), + "rows_measuring_dvv": len(meas), + "rows_not_measuring_dvv": [ + r["authors_year"] for r in rows if not measures_dvv(r) + ], + "rows_full_text": len(full), + "rows_abstract_only": len(rows) - len(full), + "rows_full_text_measuring_dvv": len(meas_full), + "signal_source": dict(Counter(r["signal_source"] for r in rows)), + "earthquake_coda_only_measuring": sum( + r["signal_source"] == "Earthquake coda" and measures_dvv(r) for r in rows + ), + "reported_among_full_text_measuring": rates, + "uncertainty_coding_among_full_text_measuring": { + "error_estimate": unc["error"], + "qc_threshold_only": unc["qc"], + "none": unc["none"], + "of": len(meas_full), + }, + "uncertainty_codes": { + r["authors_year"]: code_uncertainty(r) for r in meas_full + }, + } + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(json.dumps(payload, indent=1, ensure_ascii=False)) + print( + json.dumps( + {k: v for k, v in payload.items() if k != "uncertainty_codes"}, + indent=1, + ensure_ascii=False, + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_build_survey.py b/tests/test_build_survey.py new file mode 100644 index 0000000..55cd410 --- /dev/null +++ b/tests/test_build_survey.py @@ -0,0 +1,76 @@ +"""Key assignment of the survey bibliography builder (paper/build_survey.py).""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).resolve().parents[1] / "paper" / "build_survey.py" + + +@pytest.fixture(scope="module") +def bs(): + spec = importlib.util.spec_from_file_location("build_survey", SCRIPT) + mod = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(mod) + return mod + + +BIB = """ +@article{Obermann2013, + title = {Depth sensitivity of seismic coda waves}, + journal = {GJI}, year = {2013}, + doi = {10.1093/gji/ggt043} +} +@article{Snieder2002, + title = {Coda wave interferometry}, year = {2002} +} +""" + + +def _row(label, year, doi): + return {"authors_year": label, "year": year, "doi_url": doi} + + +def test_existing_dois_parses_keys_and_dois(bs): + d = bs.existing_dois(BIB) + assert d == {"Obermann2013": "10.1093/gji/ggt043", "Snieder2002": None} + + +def test_same_surname_year_different_doi_is_not_reused(bs): + reuse = bs.existing_dois(BIB) + rows = [ + _row("Obermann et al., 2013", "2013", "https://doi.org/10.1002/2013JB010399"), + _row("Obermann, Planès 2013", "2013", "https://doi.org/10.1093/gji/ggt043"), + ] + out = bs.assign_keys(rows, reuse) + assert [(k, reused) for k, _, reused in out] == [ + ("Obermann2013b", False), + ("Obermann2013", True), + ] + + +def test_matching_doi_or_missing_bib_doi_is_reused(bs): + reuse = bs.existing_dois(BIB) + rows = [ + _row("Obermann, Planès 2013", "2013", "https://doi.org/10.1093/gji/ggt043"), + _row("Snieder et al., 2002", "2002", "https://doi.org/10.1126/science.1070015"), + ] + out = bs.assign_keys(rows, reuse) + assert [(k, reused) for k, _, reused in out] == [ + ("Obermann2013", True), + ("Snieder2002", True), + ] + + +def test_two_new_papers_with_one_key_are_disambiguated(bs): + rows = [ + _row("Wang et al., 2017", "2017", "10.1/a"), + _row("Wang & Li 2017", "2017", "10.1/b"), + _row("Wang et al., 2017", "2017", "10.1/a"), # same paper twice + ] + out = bs.assign_keys(rows, {}) + assert [k for k, _, _ in out] == ["Wang2017", "Wang2017b", "Wang2017"] From 34f03e80c7fc712788b80353704dd52ff4b6ba0b Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Sun, 13 Sep 2026 14:52:10 -0700 Subject: [PATCH 2/7] Log the P4 execution in the iteration-3 plan record (#42) Co-Authored-By: Claude Opus 5 --- review/REVISION_PLAN_ITER3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/review/REVISION_PLAN_ITER3.md b/review/REVISION_PLAN_ITER3.md index 6296028..ba8bca0 100644 --- a/review/REVISION_PLAN_ITER3.md +++ b/review/REVISION_PLAN_ITER3.md @@ -157,3 +157,4 @@ The submission decision should rest on a defensible measurement claim, transpare - 2026-09-11: Rewritten for Marine Denolle's observational priorities, with explicit measurement targets, diagnostic failure responses, provenance requirements, and evidence-based completion criteria. No experiments or implementation tasks are marked complete by this rewrite. - 2026-09-11: GitHub issues opened under milestone "Iteration 3 revision": P0 #38, P1 #39, P2 #40, P3 #41, P4 #42, P5 #43, P6 #44, P7 #45; author inputs #46 (Gate 1 provenance and rights, blocks P3), #47 (Yuan 2021 Table B3, blocks P1 and P4), #48 (survey rows and search rules, blocks P4), #49 (Fig 8 keep, trim or drop, blocks P0), #50 (step-error metric, blocks P1). Status is tracked in the issues; this file changes only by ticking boxes and appending to this record. - 2026-09-13: P0 executed on branch `iter3/P0` (commits 10a9db3, c66b409) for issue #38; every P0 item addressed except the Fig 8 keep/trim/drop decision (#49), where the composite was kept and made legible pending the decision. PDF rebuilt: 83 pages, no `??`, no `,%`, no `<<`; the Acknowledgements placeholder remains for P6. +- 2026-09-13: P4 executed on branch `iter3/P4` (commit 04edd37) for issue #42: DOI-aware key reuse (Obermann2013b), denominators (103 publications, 96 measuring, 82 full text, 75 full text measuring), coded uncertainty rates (57 / 11 / 7), documented provenance. Row verification and the search queries stay with the author in #48; the Yuan regime with #47. From 3880ee66cad51662b50d5eaea684630841a171fd Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Sun, 13 Sep 2026 16:25:46 -0700 Subject: [PATCH 3/7] Address the Copilot review on #53: wavefield split among the measuring rows, the ADS-identified row, and the n/r code-span corruption - The split is now stated among the 96 publications that measure dv/v (77 ambient noise, 9 earthquake coda, 10 both), in the Results sentence and in Appendix C; survey_rates.json records it. - One row (Kristjansdottir et al. 2019, an EGU abstract) has an ADS record and no DOI; the text says so instead of claiming a DOI for every row. - The two-backtick quotes around n/r and n/a in Appendix C formed a markdown code span that rendered as a garbled texttt block (present before this branch); they are now \texttt{n/r} and \texttt{n/a}. Co-Authored-By: Claude Opus 5 --- paper/data/survey_rates.json | 8 +++++ paper/manuscript_marine.qmd | 22 +++++++------ paper/manuscript_marine.tex | 63 ++++++++++++++++++------------------ paper/survey_rates.py | 4 +++ 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/paper/data/survey_rates.json b/paper/data/survey_rates.json index 93ea628..9df04ca 100644 --- a/paper/data/survey_rates.json +++ b/paper/data/survey_rates.json @@ -20,6 +20,14 @@ "Earthquake coda": 11, "Both": 11 }, + "signal_source_measuring_dvv": { + "Ambient noise": 77, + "Earthquake coda": 9, + "Both": 10 + }, + "rows_without_doi": [ + "Kristjánsdóttir et al., 2019" + ], "earthquake_coda_only_measuring": 9, "reported_among_full_text_measuring": { "frequency band": { diff --git a/paper/manuscript_marine.qmd b/paper/manuscript_marine.qmd index 4839faa..c709907 100644 --- a/paper/manuscript_marine.qmd +++ b/paper/manuscript_marine.qmd @@ -792,8 +792,8 @@ The accepted range for each of these parameters across the published literature is catalogued directly, study by study, in the survey of Appendix\ \ref{app:survey} (Table\ \ref{tab:survey}): the frequency band, coda window, estimator, and uncertainty treatment actually reported by the 96 -publications of the survey that measure \dvv\ (of 103 catalogued; 81 from -ambient noise alone, 11 from earthquake coda alone, 11 from both). +publications of the survey that measure \dvv\ (of 103 catalogued; 77 from +ambient noise alone, 9 from earthquake coda alone, 10 from both). ## Coda window {#sec:param_window} @@ -1728,8 +1728,10 @@ mean $\bar x$ but obey $\sigma_{\mathrm{SD}}/\sigma_{\mathrm{SE}}=\sqrt{N}$. Table\ \ref{tab:survey} catalogues the processing choices of the coda-wave \dvv\ monitoring publications we surveyed: 103 rows for 103 -publications, one per publication and one DOI each (the machine-readable -table and its provenance are distributed with the codameter package). It is +publications, one per publication, each identified by its DOI (by its ADS +record for the one conference abstract, @Kristjansdottir2019; the +machine-readable table and its provenance are distributed with the +codameter package). It is the empirical basis for the paper's claim that no convention is shared: the estimator, the frequency band, the coda window, the reference scheme and --- most unevenly of all --- the uncertainty treatment vary from @@ -1749,18 +1751,18 @@ coda of ambient-noise correlations or of repeating earthquakes, or when it defines a method or a sensitivity kernel that the measuring publications rely on. Seven of the 103 do not themselves measure \dvv\ (theory, depth kernels, deconvolution interferometry, spectral methods) and are marked -``n/a'' in the estimator column; the counts below exclude them. Of the 96 -that measure \dvv, 81 use ambient noise alone, 9 use earthquake coda alone -and the rest both (the machine-readable table records this per row); +\texttt{n/a} in the estimator column; the counts below exclude them. Of the +96 that measure \dvv, 77 use ambient noise alone, 9 use earthquake coda +alone and 10 both (the machine-readable table records this per row); the coda-only rows are kept because their processing choices are the same choices, made on a different wavefield. The four measurement fields (frequency band, coda window, estimator and uncertainty treatment) were re-checked against the \emph{full text} for the 82 publications we could read (open access plus institutional access); for -those rows a blank or ``n/r'' means the value is genuinely not stated in -the paper. The remaining 21 rows were populated from abstracts and search -metadata, where ``n/r'' means only that the value was not found in the +those rows a blank or \texttt{n/r} means the value is genuinely not stated +in the paper. The remaining 21 rows were populated from abstracts and search +metadata, where \texttt{n/r} means only that the value was not found in the abstract, not that the publication failed to report it; those cells are flagged in the machine-readable table (\texttt{measurement\_source}), remain to be filled from the paywalled full texts, and are excluded from diff --git a/paper/manuscript_marine.tex b/paper/manuscript_marine.tex index 1011a82..1ffc9bb 100644 --- a/paper/manuscript_marine.tex +++ b/paper/manuscript_marine.tex @@ -1095,7 +1095,7 @@ \subsection{Substacking}\label{sec:param_substack} Appendix~\ref{app:survey} (Table~\ref{tab:survey}): the frequency band, coda window, estimator, and uncertainty treatment actually reported by the 96 publications of the survey that measure \dvv~(of 103 catalogued; -81 from ambient noise alone, 11 from earthquake coda alone, 11 from +77 from ambient noise alone, 9 from earthquake coda alone, 10 from both). \subsection{Coda window}\label{sec:param_window} @@ -2112,13 +2112,14 @@ \section{Survey of processing choices across the Table~\ref{tab:survey} catalogues the processing choices of the coda-wave \dvv~monitoring publications we surveyed: 103 rows for 103 -publications, one per publication and one DOI each (the machine-readable -table and its provenance are distributed with the codameter package). It -is the empirical basis for the paper's claim that no convention is -shared: the estimator, the frequency band, the coda window, the -reference scheme and --- most unevenly of all --- the uncertainty -treatment vary from publication to publication, and the last is -frequently unreported. Every publication is cited here. +publications, one per publication, each identified by its DOI (by its +ADS record for the one conference abstract, \citet{Kristjansdottir2019}; +the machine-readable table and its provenance are distributed with the +codameter package). It is the empirical basis for the paper's claim that +no convention is shared: the estimator, the frequency band, the coda +window, the reference scheme and --- most unevenly of all --- the +uncertainty treatment vary from publication to publication, and the last +is frequently unreported. Every publication is cited here. The survey was assembled in June 2026 by six literature scans of the published journal literature, one per application group (volcano, @@ -2133,33 +2134,33 @@ \section{Survey of processing choices across the when it defines a method or a sensitivity kernel that the measuring publications rely on. Seven of the 103 do not themselves measure \dvv~(theory, depth kernels, deconvolution interferometry, spectral -methods) and are marked ``n/a'\,' in the estimator column; the counts -below exclude them. Of the 96 that measure \dvv, 81 use ambient noise -alone, 9 use earthquake coda alone and the rest both (the -machine-readable table records this per row); the coda-only rows are -kept because their processing choices are the same choices, made on a -different wavefield. +methods) and are marked \texttt{n/a} in the estimator column; the counts +below exclude them. Of the 96 that measure \dvv, 77 use ambient noise +alone, 9 use earthquake coda alone and 10 both (the machine-readable +table records this per row); the coda-only rows are kept because their +processing choices are the same choices, made on a different wavefield. The four measurement fields (frequency band, coda window, estimator and uncertainty treatment) were re-checked against the \emph{full text} for the 82 publications we could read (open access plus institutional -access); for those rows a blank or -\texttt{n/r\textquotesingle{}\textquotesingle{}\ means\ the\ value\ is\ genuinely\ not\ stated\ in\ the\ paper.\ The\ remaining\ 21\ rows\ were\ populated\ from\ abstracts\ and\ search\ metadata,\ where}n/r'\,' -means only that the value was not found in the abstract, not that the -publication failed to report it; those cells are flagged in the -machine-readable table (\texttt{measurement\_source}), remain to be -filled from the paywalled full texts, and are excluded from the rates -below. Among the 75 full-text publications that measure \dvv, 68 report -the frequency band, 59 the coda window and 74 the estimator. For the -uncertainty column we distinguish a stated error on \dvv~(a formula, a -standard deviation or error, a confidence interval, a regression or -bootstrap error, a stated noise or significance level) from a -quality-control rule alone (a coherence or correlation threshold, a -weighting, a rejection criterion, with no error magnitude) and from -nothing: 57 of the 75 state an error, 11 state a quality rule only, and -7 state neither. The rule, the keyword coding and the eight cells coded -by hand are in \texttt{paper/survey\_rates.py}, which writes these -counts to \texttt{paper/data/survey\_rates.json}. +access); for those rows a blank or \texttt{n/r} means the value is +genuinely not stated in the paper. The remaining 21 rows were populated +from abstracts and search metadata, where \texttt{n/r} means only that +the value was not found in the abstract, not that the publication failed +to report it; those cells are flagged in the machine-readable table +(\texttt{measurement\_source}), remain to be filled from the paywalled +full texts, and are excluded from the rates below. Among the 75 +full-text publications that measure \dvv, 68 report the frequency band, +59 the coda window and 74 the estimator. For the uncertainty column we +distinguish a stated error on \dvv~(a formula, a standard deviation or +error, a confidence interval, a regression or bootstrap error, a stated +noise or significance level) from a quality-control rule alone (a +coherence or correlation threshold, a weighting, a rejection criterion, +with no error magnitude) and from nothing: 57 of the 75 state an error, +11 state a quality rule only, and 7 state neither. The rule, the keyword +coding and the eight cells coded by hand are in +\texttt{paper/survey\_rates.py}, which writes these counts to +\texttt{paper/data/survey\_rates.json}. \input{appendix_table.tex} diff --git a/paper/survey_rates.py b/paper/survey_rates.py index 23029f9..d99d422 100644 --- a/paper/survey_rates.py +++ b/paper/survey_rates.py @@ -125,6 +125,10 @@ def main() -> int: "rows_abstract_only": len(rows) - len(full), "rows_full_text_measuring_dvv": len(meas_full), "signal_source": dict(Counter(r["signal_source"] for r in rows)), + "signal_source_measuring_dvv": dict(Counter(r["signal_source"] for r in meas)), + "rows_without_doi": [ + r["authors_year"] for r in rows if "10." not in r["doi_url"] + ], "earthquake_coda_only_measuring": sum( r["signal_source"] == "Earthquake coda" and measures_dvv(r) for r in rows ), From cf5d3e13e02e364a5e08fcb822a136362b101be7 Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Mon, 14 Sep 2026 11:34:26 -0700 Subject: [PATCH 4/7] Remove file names from the Appendix C text (#42) Co-Authored-By: Claude Opus 5 --- paper/manuscript_marine.qmd | 8 ++++---- paper/manuscript_marine.tex | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/paper/manuscript_marine.qmd b/paper/manuscript_marine.qmd index c709907..1b1be40 100644 --- a/paper/manuscript_marine.qmd +++ b/paper/manuscript_marine.qmd @@ -1744,8 +1744,8 @@ earthquake and fault, landslide, groundwater, cryosphere, geothermal and reservoir, methodology), each recording a DOI per publication; the scans returned 117 rows, which were deduplicated by DOI, checked against the Crossref record of each DOI (four wrong DOIs corrected, one unverifiable -row dropped) and reduced to the 103 publications listed -(\texttt{literature/README.md} records the corrections). The table keeps a +row dropped) and reduced to the 103 publications listed (the corrections +are recorded with the machine-readable table). The table keeps a publication when it measures a temporal relative velocity change from the coda of ambient-noise correlations or of repeating earthquakes, or when it defines a method or a sensitivity kernel that the measuring publications @@ -1775,8 +1775,8 @@ quality-control rule alone (a coherence or correlation threshold, a weighting, a rejection criterion, with no error magnitude) and from nothing: 57 of the 75 state an error, 11 state a quality rule only, and 7 state neither. The rule, the keyword coding and the eight cells coded by -hand are in \texttt{paper/survey\_rates.py}, which writes these counts to -\texttt{paper/data/survey\_rates.json}. +hand are recorded with the machine-readable table, together with these +counts. \input{appendix_table.tex} diff --git a/paper/manuscript_marine.tex b/paper/manuscript_marine.tex index 1ffc9bb..6a0cc74 100644 --- a/paper/manuscript_marine.tex +++ b/paper/manuscript_marine.tex @@ -251,7 +251,7 @@ \title{The reproducibility cost of ad-hoc processing choices in ambient-noise seismic velocity-change monitoring} \author{M. A. Denolle} -\date{2026-09-13} +\date{2026-09-14} \begin{document} \maketitle \begin{abstract} @@ -2127,9 +2127,9 @@ \section{Survey of processing choices across the reservoir, methodology), each recording a DOI per publication; the scans returned 117 rows, which were deduplicated by DOI, checked against the Crossref record of each DOI (four wrong DOIs corrected, one unverifiable -row dropped) and reduced to the 103 publications listed -(\texttt{literature/README.md} records the corrections). The table keeps -a publication when it measures a temporal relative velocity change from +row dropped) and reduced to the 103 publications listed (the corrections +are recorded with the machine-readable table). The table keeps a +publication when it measures a temporal relative velocity change from the coda of ambient-noise correlations or of repeating earthquakes, or when it defines a method or a sensitivity kernel that the measuring publications rely on. Seven of the 103 do not themselves measure @@ -2158,9 +2158,8 @@ \section{Survey of processing choices across the coherence or correlation threshold, a weighting, a rejection criterion, with no error magnitude) and from nothing: 57 of the 75 state an error, 11 state a quality rule only, and 7 state neither. The rule, the keyword -coding and the eight cells coded by hand are in -\texttt{paper/survey\_rates.py}, which writes these counts to -\texttt{paper/data/survey\_rates.json}. +coding and the eight cells coded by hand are recorded with the +machine-readable table, together with these counts. \input{appendix_table.tex} From bd17cc2eed84e0c1c883b1504d555ce3ba51124a Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Mon, 14 Sep 2026 15:12:44 -0700 Subject: [PATCH 5/7] Survey rates: count DOIs apart from identifiers; builder output says publications (#42, Copilot review) distinct_identifiers 103, distinct_dois 102 (one row carries an ADS record, listed in rows_without_doi); the generated survey.bib and appendix table are unchanged. Co-Authored-By: Claude Opus 5 --- paper/build_survey.py | 14 +++++++------- paper/data/survey_rates.json | 3 ++- paper/survey_rates.py | 6 ++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/paper/build_survey.py b/paper/build_survey.py index e2502c7..a6de053 100644 --- a/paper/build_survey.py +++ b/paper/build_survey.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 -r"""Generate the paper's 103-study survey bibliography and appendix longtable. +r"""Generate the paper's 103-publication survey bibliography and appendix longtable. Reads the literature survey CSV (``literature/dvv_processing_parameters.csv``) and the cached Crossref metadata (``literature/.crossref_cache.json``) and writes: - - ``paper/survey.bib`` one BibTeX entry per surveyed study, with keys + - ``paper/survey.bib`` one BibTeX entry per surveyed publication, with keys that *reuse* the hand-curated ``references.bib`` - keys when the study is already cited in the + keys when the publication is already cited in the narrative (so every paper appears exactly once in the reference list). - ``paper/appendix_table.tex`` a ``longtable`` cataloguing the processing - choices of every study, with a ``\citet`` to - each — so all 103 studies are *cited*, and the + choices of every publication, with a ``\citet`` to + each — so all 103 publications are *cited*, and the table breaks cleanly across pages (no float overlap with the bibliography). @@ -275,7 +275,7 @@ def bib_entry(key: str, row: dict, rec: dict | None) -> str: def appendix_table(entries: list[tuple[str, dict]]) -> str: - """A longtable cataloguing every study's processing choices (all \\citet).""" + """A longtable cataloguing every publication's processing choices (all \\citet).""" colspec = ( "@{}p{2.1cm} " + " ".join(f">{{\\raggedright\\arraybackslash}}p{{{w}}}" for *_, w in COLS) @@ -357,7 +357,7 @@ def main() -> None: print( f"wrote {SURVEY_BIB.name} ({len(new_bib)} new entries, {n_reused} reuse references.bib)" ) - print(f"wrote {APPENDIX.name} ({len(entries)} studies catalogued + cited)") + print(f"wrote {APPENDIX.name} ({len(entries)} publications catalogued + cited)") if __name__ == "__main__": diff --git a/paper/data/survey_rates.json b/paper/data/survey_rates.json index 9df04ca..66ba128 100644 --- a/paper/data/survey_rates.json +++ b/paper/data/survey_rates.json @@ -1,7 +1,8 @@ { "source": "literature/dvv_processing_parameters.csv", "rows": 103, - "distinct_dois": 103, + "distinct_identifiers": 103, + "distinct_dois": 102, "rows_measuring_dvv": 96, "rows_not_measuring_dvv": [ "Köpfli et al., 2024", diff --git a/paper/survey_rates.py b/paper/survey_rates.py index d99d422..de14c75 100644 --- a/paper/survey_rates.py +++ b/paper/survey_rates.py @@ -4,7 +4,8 @@ Reads ``literature/dvv_processing_parameters.csv`` and writes ``paper/data/survey_rates.json`` with the counts the appendix quotes: -- rows and distinct DOIs (one row per publication); +- rows, distinct identifiers (one row per publication) and, among them, the + distinct DOIs (one row, a conference abstract, carries an ADS record); - rows that measure dv/v (``dvv_method`` not ``n/a``) and the rows that do not (theory, kernels, deconvolution interferometry, spectral methods); - rows populated from the full text versus from abstracts; @@ -116,7 +117,8 @@ def main() -> int: payload = { "source": str(CSV.relative_to(HERE.parent)), "rows": len(rows), - "distinct_dois": len({r["doi_url"] for r in rows}), + "distinct_identifiers": len({r["doi_url"] for r in rows}), + "distinct_dois": len({r["doi_url"] for r in rows if "10." in r["doi_url"]}), "rows_measuring_dvv": len(meas), "rows_not_measuring_dvv": [ r["authors_year"] for r in rows if not measures_dvv(r) From 6ad409634c7f59be16f1b7f9f0e64b2ca216a6da Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Mon, 14 Sep 2026 16:11:27 -0700 Subject: [PATCH 6/7] Survey builder: suffix sequence b, c, d; Publication column header; fixture guard (#42, Copilot review) A third paper sharing a base key now gets Key2013c instead of Key2013bc (test extended); the longtable header says Publication like the caption. Co-Authored-By: Claude Opus 5 --- paper/appendix_table.tex | 4 ++-- paper/build_survey.py | 9 +++++++-- tests/test_build_survey.py | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/paper/appendix_table.tex b/paper/appendix_table.tex index d927522..ed2f5de 100644 --- a/paper/appendix_table.tex +++ b/paper/appendix_table.tex @@ -5,12 +5,12 @@ \begin{longtable}{@{}p{2.1cm} >{\raggedright\arraybackslash}p{1.9cm} >{\raggedright\arraybackslash}p{1.5cm} >{\raggedright\arraybackslash}p{1.3cm} >{\raggedright\arraybackslash}p{1.5cm} >{\raggedright\arraybackslash}p{2.4cm} >{\raggedright\arraybackslash}p{3.0cm}@{}} \caption{Processing choices of the 103 surveyed publications, one row each (the literature survey underpinning this paper). Every publication is cited; \texttt{n/r} = not reported in the source; \texttt{n/a} in the estimator column marks a publication that does not itself measure \dvv\ (theory, kernels, a companion method) and is kept for the choices it documents. Frequency band, coda window, estimator, reference/stack scheme and uncertainty treatment are the choices Section~\ref{sec:results} shows to control the result.}\label{tab:survey}\\ \toprule -\textbf{Study} & \textbf{Site / region} & \textbf{Freq (Hz)} & \textbf{Coda (s)} & \textbf{Method} & \textbf{Reference / stack} & \textbf{Uncertainty treatment} \\ +\textbf{Publication} & \textbf{Site / region} & \textbf{Freq (Hz)} & \textbf{Coda (s)} & \textbf{Method} & \textbf{Reference / stack} & \textbf{Uncertainty treatment} \\ \midrule \endfirsthead \multicolumn{7}{@{}l}{\footnotesize\itshape Table~\ref{tab:survey} continued}\\ \toprule -\textbf{Study} & \textbf{Site / region} & \textbf{Freq (Hz)} & \textbf{Coda (s)} & \textbf{Method} & \textbf{Reference / stack} & \textbf{Uncertainty treatment} \\ +\textbf{Publication} & \textbf{Site / region} & \textbf{Freq (Hz)} & \textbf{Coda (s)} & \textbf{Method} & \textbf{Reference / stack} & \textbf{Uncertainty treatment} \\ \midrule \endhead \midrule\multicolumn{7}{r@{}}{\footnotesize\itshape continued on next page}\\ diff --git a/paper/build_survey.py b/paper/build_survey.py index a6de053..d9cb720 100644 --- a/paper/build_survey.py +++ b/paper/build_survey.py @@ -224,7 +224,12 @@ def _taken(k: str, doi: str) -> bool: doi = doi_of(row["doi_url"]) or row["doi_url"] key = make_key(row["authors_year"], row.get("year", "")) while _taken(key, doi): - key += "b" if key[-1].isdigit() else chr(ord(key[-1]) + 1) + # Key2013 -> Key2013b -> Key2013c: replace a trailing suffix letter + # rather than appending to it. + if key[-1].isdigit(): + key += "b" + else: + key = key[:-1] + chr(ord(key[-1]) + 1) used[key] = doi out.append((key, row, key in reuse)) return out @@ -282,7 +287,7 @@ def appendix_table(entries: list[tuple[str, dict]]) -> str: + "@{}" ) head = ( - "\\textbf{Study} & " + "\\textbf{Publication} & " + " & ".join(f"\\textbf{{{h}}}" for _, h, _ in COLS) + r" \\" ) diff --git a/tests/test_build_survey.py b/tests/test_build_survey.py index 55cd410..d7a4328 100644 --- a/tests/test_build_survey.py +++ b/tests/test_build_survey.py @@ -13,8 +13,8 @@ @pytest.fixture(scope="module") def bs(): spec = importlib.util.spec_from_file_location("build_survey", SCRIPT) + assert spec is not None and spec.loader is not None, SCRIPT mod = importlib.util.module_from_spec(spec) - assert spec.loader is not None spec.loader.exec_module(mod) return mod @@ -71,6 +71,7 @@ def test_two_new_papers_with_one_key_are_disambiguated(bs): _row("Wang et al., 2017", "2017", "10.1/a"), _row("Wang & Li 2017", "2017", "10.1/b"), _row("Wang et al., 2017", "2017", "10.1/a"), # same paper twice + _row("Wang, Chen 2017", "2017", "10.1/c"), # a third distinct paper ] out = bs.assign_keys(rows, {}) - assert [k for k, _, _ in out] == ["Wang2017", "Wang2017b", "Wang2017"] + assert [k for k, _, _ in out] == ["Wang2017", "Wang2017b", "Wang2017", "Wang2017c"] From cc3e2edc47adc0eb5371a3ef37f65549c153a05c Mon Sep 17 00:00:00 2001 From: Marine Denolle Date: Mon, 14 Sep 2026 16:19:25 -0700 Subject: [PATCH 7/7] Survey builder: compute the author string once (#42, Copilot review) Co-Authored-By: Claude Opus 5 --- paper/build_survey.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paper/build_survey.py b/paper/build_survey.py index d9cb720..3e2e9e8 100644 --- a/paper/build_survey.py +++ b/paper/build_survey.py @@ -240,10 +240,11 @@ def bib_entry(key: str, row: dict, rec: dict | None) -> str: doi = doi_of(row["doi_url"]) or "" year = row.get("year", "") if rec and rec.get("title"): + authors = bib_authors(rec) fields = { - "author": bib_authors(rec) - and latexify(bib_authors(rec)) - or authors_from_label(row["authors_year"]), + "author": latexify(authors) + if authors + else authors_from_label(row["authors_year"]), "title": latexify(clean_html(rec["title"].rstrip(". "))), "journal": latexify(clean_html(rec.get("container", ""))), "year": rec.get("year") or year,