commit c3e37f642a7665c8b7815ca7f213a79f251270f2 Author: zhoujie <929834232@qq.com> Date: Mon Jul 20 03:58:38 2026 -1000 init diff --git a/.claude/skills/literature-search-verify/SKILL.md b/.claude/skills/literature-search-verify/SKILL.md new file mode 100644 index 0000000..c9f9d2d --- /dev/null +++ b/.claude/skills/literature-search-verify/SKILL.md @@ -0,0 +1,94 @@ +--- +name: literature-search-verify +description: Search academic literature across arXiv, Semantic Scholar, Crossref, and other connected paper-search MCP tools, and independently verify every candidate citation before it is treated as real. Use this whenever the user asks to find papers, search literature on a topic, build a reading list, compile related-work references, check whether a citation or bibliography entry actually exists, or prepare references to import into Zotero or a .bib file — especially in academic writing contexts where a fabricated citation would be a real problem. Also covers guiding the user to the Zotero Connector browser extension for Chinese-language sources (CNKI/知网, Wanfang/万方, VIP/维普) that have no public API and cannot be reached by search tools. +--- + +# 文献检索 + 反幻觉引用核查 + +## 为什么需要这个技能 + +大语言模型在编造论文引用这件事上非常擅长——生成的标题、作者、期刊名读起来都很像真的,但可能根本不存在,或者张冠李戴(把A论文的结论安在B论文头上)。这在正式学术写作里是不可接受的:一篇论文只要有一条编造的引用被发现,审稿人对全篇的信任都会崩塌。 + +所以这个技能的核心不是"搜索",而是"搜索之后不轻信"——每一条打算真正拿去引用的文献,都必须经过独立交叉验证,验证不通过的必须明确标出来,而不是悄悄丢弃或者悄悄当作真的用。 + +## 工作流程 + +### 第一步:明确检索范围 + +在开始搜索前,搞清楚(不确定就直接问,一句话就够): +- 核心关键词/研究方向(可以中英文混合,比如"UAV磁补偿 Tolles-Lawson"这类) +- 大致的时间范围(比如"近5年"还是不限) +- 是否需要限定顶会/顶刊,还是什么来源都要 + +### 第二步:检索——直接调用脚本,不要自己现编API调用 + +`scripts/` 目录下已经写好了能直接跑的检索脚本,不依赖任何第三方Python包,也不需要装MCP工具: + +```bash +# 一次性搞定:检索 arXiv + Semantic Scholar + Crossref,自动去重、逐条验证, +# 并把通过验证的条目写成BibTeX文件——这是应该默认调用的入口 +python3 scripts/literature_search.py "UAV magnetic compensation Tolles-Lawson" \ + --max-per-source 8 --bib-out refs.bib +``` + +正常情况下**只需要跑这一条命令**,它内部会依次调用 `search_arxiv.py`、`search_semantic_scholar.py`、`search_crossref.py` 做检索,再对每条合并后的候选文献跑 `verify_citation.py` 做交叉验证,输出一份JSON报告(每条候选都带`verdict`字段)。如果只是想单独查一个来源,或者针对某一条文献单独复核,再分别调用对应的单个脚本(用法见每个脚本文件开头的docstring)。 + +如果这些脚本因为网络原因跑不动(比如内网/代理限制导致连不上 arxiv.org、semanticscholar.org、crossref.org),`literature_search.py` 会把每个来源的报错单独记在`search_errors`里而不是直接崩溃——这时候老实告诉用户"检索脚本连不上网络,以下是报错信息",不要退回去凭记忆编文献。如果用户这边确实连不上这几个学术API域名,才退回到 web_search 工具,并在结果里明确标注"来自通用网络搜索的补充结果,未经过脚本的交叉验证流程,置信度较低"。 + +如果用户已经连了 paper-search-mcp / scholar_mcp_server 这类MCP工具,可以补充用来扩大覆盖面(比如它们能覆盖PubMed、能直接下载PDF),但**不能替代**`verify_citation.py`的交叉验证这一步——MCP搜到的候选一样要过一遍验证,不能因为是工具搜出来的就默认可信。 + +### 第三步:理解验证结果——这是最关键的一步 + +`literature_search.py`(或单独调用`verify_citation.py`)对每条候选文献做的核查是: + +1. **arXiv ID 独立核实**:如果有 arXiv ID,反查一次 arXiv API,确认这个ID真的存在且标题对得上——一个编造的ID在这一步会直接暴露。 +2. **DOI 独立核实**:如果有 DOI,反查一次 Crossref,确认这个 DOI 真的能解析出对应文献。 +3. **跨源标题复核**:不管有没有ID,单独拿标题去 Semantic Scholar 搜一次,要求返回的标题跟候选标题高度相似(相似度≥0.9)——这一步专门用来抓"标题作者读起来很像真的,但其实是编出来的"这种情况。 + +每条候选最后会带一个`verdict`: +- **verified**:至少一项独立核查通过,而且没有任何一项核查明确失败 +- **suspect**:至少一项核查明确失败(比如DOI查不到、跨源标题对不上)——**这种情况下不要用这条文献,即使标题看起来很合适** +- **unverified**:所有核查项都因为网络等原因被跳过(`skipped`),不代表验证通过,只代表"没能验证"——**同样不能当成已核实的文献直接使用**,要跟用户说清楚原因 + +呈现给用户时按这三档分组说明,`suspect`和`unverified`都要明确标出来,不要因为报告里有个"看起来还行"的标题就含糊地当真的用。 + +**原则**:找不到真实存在的相关文献时,直接说"没找到符合条件的文献",不要为了凑数编一条出来。这条原则没有例外。 + +### 第四步:输出 + +按 verified / suspect / unverified 分组呈现结果,每条包含标题、作者年份、venue、标识符、一句话相关性说明。 + +`literature_search.py` 传了 `--bib-out` 参数时,会自动把所有 `verified` 的条目写成BibTeX文件,citation key 用"姓氏+年份"约定,可以直接导入 Zotero(配合 Better BibTeX 插件)。这些 key 也是后续`paper-writing-grounded`技能里`\cite{}`要用到的,两个技能之间通过这些key保持一致,不需要额外对照。 + +### 第五步:提醒中文文献的检索缺口 + +MCP 检索工具覆盖的是 arXiv/Semantic Scholar/Crossref 这类有公开 API 的英文为主的库,**知网、万方、维普这类中文数据库没有公开 API,搜不到很正常,不是技能出错**。遇到用户明显需要中文文献的场景,主动提醒:装好 Zotero Connector 浏览器插件,在浏览器里正常登录学校账号搜索、打开文献页面,点一下 Connector 图标就能把元数据和 PDF 存进 Zotero——这部分需要用户手动完成,不要尝试用检索工具"模拟"或"猜测"中文文献的存在。 + +### 第六步:归档 + +`output/` 目录只是脚本运行时的草稿区——里面混着每一轮探索性检索的原始JSON(包括被过滤掉的噪声,比如"Tolles""Lawson"被当成人名匹配出的无关文献),不适合作为最终交付物,而且随着会话增多会越堆越乱、也不方便下次会话或用户直接翻阅。 + +所以每次整理出一份**稳定可信的参考文献列表**(不管是第一轮检索还是后续多轮补充检索合并后的结果)之后,调用归档脚本把它固化到项目级目录,而不是留在技能自己的`output/`里: + +```bash +python3 scripts/archive_references.py "UAV aeromagnetic compensation" \ + --bib output/uav_aeromagnetic_compensation_final.bib \ + --project-root . \ + --pdfs-dir output/pdfs \ + --suspect "某条可疑文献标题|不建议引用的具体原因" \ + --notes "检索覆盖了哪些方向、哪些方向搜了但没结果、中文文献缺口提醒等" +``` + +这会在 `/references/<按主题自动生成的slug>/` 下生成: +- `references.bib` —— 传入的bib文件原样拷贝过去 +- `pdfs/`(如果传了`--pdfs-dir`且里面有PDF)—— 一并拷贝过去 +- `README.md` —— 自动从bib里解析出条目列表(标题/年份/venue/DOI/note)生成索引,`--suspect`和`--notes`里的内容会分别整理进"不要引用"和"检索覆盖说明"两个小节 + +几个要点: +- `--bib` 传的必须是**已经过滤掉无关噪声、只保留verified条目**的干净bib文件,不要把`literature_search.py`直接吐出来的、可能夹杂噪声的原始bib不加甄别地拿去归档。 +- 同一个`topic`名字多次调用会往同一个归档目录里覆盖更新(bib和README会被覆盖,pdfs按文件名去重合并),所以后续检索到更多文献后可以直接对同一个topic重新跑一遍归档脚本来更新,不需要手动合并。 +- 这一步做完之后可以明确告诉用户归档目录的路径,方便他们后续在`paper-writing-grounded`阶段直接引用。 + +## 和 paper-writing-grounded 技能的配合 + +这个技能负责把"真实存在、经过核实的文献"整理好并生成 BibTeX;写作阶段的 paper-writing-grounded 技能会直接消费这里产出的 citation key,正文引用只能来自这里核实过的条目,不会凭空生成新的引用。两个技能配合使用时,建议先跑完这个技能、拿到稳定的参考文献列表,再进入写作。 diff --git a/.claude/skills/literature-search-verify/scripts/archive_references.py b/.claude/skills/literature-search-verify/scripts/archive_references.py new file mode 100644 index 0000000..bc5b8d0 --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/archive_references.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +""" +Archive a finished literature-search-verify session into a permanent, +project-level folder instead of leaving results sitting in the skill's +own scratch output/ directory (which is easy to lose track of across +sessions and isn't meant to be a durable deliverable location). + +Bundles the verified BibTeX file -- and, if given, any downloaded PDFs -- +into /references//, and writes a README.md +index (entry list, suspect/unverified entries flagged separately, free- +text coverage notes) so a future session or a human can find and trust +what's there without re-reading the conversation that produced it. + +No third-party dependencies; uses only the standard library. + +CLI usage: + python3 archive_references.py "UAV aeromagnetic compensation" \\ + --bib output/uav_aeromagnetic_compensation_final.bib \\ + --project-root . \\ + --pdfs-dir output/pdfs \\ + --suspect "Some fabricated-looking title|DOI resolves but venue is topically unrelated" \\ + --notes "Kalman-filter and GA/PSO angles searched, no on-topic hits found." + +Output: prints the path of the archive directory that was created/updated. +""" +import argparse +import os +import re +import shutil +import sys +from datetime import date + + +def slugify(text): + text = text.strip().lower() + text = re.sub(r"[^a-z0-9]+", "_", text) + return text.strip("_")[:60] or "references" + + +def parse_bib_entries(bib_path): + """Minimal BibTeX parser -- just enough to pull key/title/year/venue/doi/note + (plus the raw entry text, for reordering) for the README index. Not a + general-purpose BibTeX parser.""" + with open(bib_path, encoding="utf-8") as f: + content = f.read() + + entries = [] + for m in re.finditer(r"@(\w+)\{([^,\n]+),(.*?)\n\}", content, re.S): + entry_type, key, body = m.groups() + fields = {} + for fm in re.finditer(r"(\w+)\s*=\s*\{(.*?)\}\s*,?\s*(?=\n\s*\w+\s*=|\n\Z|\Z)", body, re.S): + fields[fm.group(1).lower()] = re.sub(r"\s+", " ", fm.group(2)).strip() + entries.append({"type": entry_type, "key": key.strip(), "raw": m.group(0).strip(), **fields}) + return entries + + +def year_sort_key(entry): + """Chronological order, oldest first; entries with no parseable year sort last.""" + year_str = re.sub(r"[^0-9]", "", entry.get("year", "") or "") + year = int(year_str) if year_str else 9999 + return (year, entry.get("key", "")) + + +def build_readme(topic, entries, pdf_count, suspect, notes): + lines = [] + lines.append(f"# {topic} — literature archive") + lines.append("") + lines.append(f"Archived: {date.today().isoformat()}") + lines.append(f"Verified entries: {len(entries)}") + lines.append(f"PDFs bundled: {pdf_count}") + lines.append("") + lines.append( + "Every entry in `references.bib` passed independent verification " + "(arXiv ID / DOI resolution and/or cross-source title match, " + "similarity >= 0.9) via the literature-search-verify skill before " + "being archived here. Citation keys follow the surname+year " + "convention and are stable -- the paper-writing-grounded skill's " + "`\\cite{}` calls should match these keys directly." + ) + lines.append("") + lines.append("## Entries (chronological, oldest first)") + lines.append("") + for e in entries: + title = e.get("title", "?") + year = e.get("year", "?") + venue = e.get("journal") or e.get("booktitle") or e.get("school") or "" + doi = e.get("doi", "") + note = e.get("note", "") + line = f"- **{e['key']}** ({year}) — {title}" + if venue: + line += f". *{venue}*" + if doi: + line += f". DOI: {doi}" + lines.append(line) + if note: + lines.append(f" - Note: {note}") + + if suspect: + lines.append("") + lines.append("## Flagged during search — NOT included above, do not cite") + lines.append("") + for s in suspect: + parts = s.split("|", 1) + title = parts[0].strip() + reason = parts[1].strip() if len(parts) > 1 else "" + lines.append(f"- {title}" + (f" — {reason}" if reason else "")) + + if notes: + lines.append("") + lines.append("## Search coverage notes") + lines.append("") + lines.append(notes) + + return "\n".join(lines) + "\n" + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("topic", help="Human-readable topic name, e.g. \"UAV aeromagnetic compensation\"") + ap.add_argument("--bib", required=True, help="path to the curated/verified .bib file to archive") + ap.add_argument("--project-root", default=".", help="project root; archive is written under /references//") + ap.add_argument("--pdfs-dir", default=None, help="optional folder of open-access PDFs to copy alongside the bib") + ap.add_argument("--suspect", action="append", default=[], help="title|reason of a suspect/unverified entry to log; repeatable") + ap.add_argument("--notes", default=None, help="free-text notes on search coverage/gaps for the README") + args = ap.parse_args() + + if not os.path.isfile(args.bib): + print(f"error: bib file not found: {args.bib}", file=sys.stderr) + sys.exit(1) + + slug = slugify(args.topic) + archive_dir = os.path.join(args.project_root, "references", slug) + os.makedirs(archive_dir, exist_ok=True) + + bib_dest = os.path.join(archive_dir, "references.bib") + shutil.copyfile(args.bib, bib_dest) + entries = parse_bib_entries(bib_dest) + entries.sort(key=year_sort_key) + + # Rewrite the archived .bib in chronological order (oldest first) so the + # file itself, not just the README, reads as a timeline. + header = f"% {args.topic} -- verified references, chronological order\n% Archived {date.today().isoformat()}\n\n" + with open(bib_dest, "w", encoding="utf-8") as f: + f.write(header) + f.write("\n\n".join(e["raw"] for e in entries)) + f.write("\n") + + pdf_count = 0 + if args.pdfs_dir and os.path.isdir(args.pdfs_dir): + pdf_dest_dir = os.path.join(archive_dir, "pdfs") + os.makedirs(pdf_dest_dir, exist_ok=True) + for fn in sorted(os.listdir(args.pdfs_dir)): + if fn.lower().endswith(".pdf"): + shutil.copyfile(os.path.join(args.pdfs_dir, fn), os.path.join(pdf_dest_dir, fn)) + pdf_count += 1 + + readme = build_readme(args.topic, entries, pdf_count, args.suspect, args.notes) + with open(os.path.join(archive_dir, "README.md"), "w", encoding="utf-8") as f: + f.write(readme) + + print(archive_dir) + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/literature-search-verify/scripts/literature_search.py b/.claude/skills/literature-search-verify/scripts/literature_search.py new file mode 100644 index 0000000..20dec6d --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/literature_search.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +End-to-end literature search: query arXiv + Semantic Scholar + Crossref, +merge/dedupe candidates, independently verify each one, and emit both a +human-readable report and BibTeX for the entries that passed verification. + +This is the one script Claude should actually call for a normal literature +search -- the individual search_*.py / verify_citation.py scripts exist +mainly as building blocks it can reuse for one-off / follow-up lookups. + +CLI usage: + python3 literature_search.py "UAV magnetic compensation Tolles-Lawson" \\ + --max-per-source 8 --bib-out refs.bib + +Output: prints a JSON report to stdout (one entry per merged candidate, +with its verdict), and if --bib-out is given, writes BibTeX for every +"verified" entry to that file (never for "suspect" or "unverified" ones). +""" +import sys +import os +import json +import argparse +import difflib +import re + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from search_arxiv import search_arxiv +from search_semantic_scholar import search_s2 +from search_crossref import search_crossref +from verify_citation import verify + + +def _similar(a, b, threshold=0.88): + if not a or not b: + return False + return difflib.SequenceMatcher(None, a.lower().strip(), b.lower().strip()).ratio() >= threshold + + +def merge_candidates(all_results): + """Dedupe candidates that are the same paper found via multiple sources, + merging their metadata (preferring whichever source has an identifier).""" + merged = [] + for item in all_results: + placed = False + for m in merged: + if _similar(item.get("title"), m.get("title")): + # merge: fill in any missing fields, keep track of all sources + for key in ("doi", "arxiv_id", "abstract", "venue", "year", "citation_count", "pdf_url"): + if not m.get(key) and item.get(key): + m[key] = item[key] + m["sources"] = sorted(set(m.get("sources", [m.get("source")]) + [item.get("source")])) + placed = True + break + if not placed: + item = dict(item) + item["sources"] = [item.get("source")] + merged.append(item) + return merged + + +def make_bibtex_key(candidate, used_keys): + authors = candidate.get("authors") or [] + surname = "unknown" + if authors: + first_author = authors[0] + surname = first_author.strip().split()[-1].lower() + surname = re.sub(r"[^a-z]", "", surname) or "unknown" + year = str(candidate.get("year") or "nd") + base = f"{surname}{year}" + key = base + suffix = ord("a") + while key in used_keys: + key = f"{base}{chr(suffix)}" + suffix += 1 + used_keys.add(key) + return key + + +def to_bibtex(candidate, key): + authors = candidate.get("authors") or [] + author_str = " and ".join(authors) if authors else "Unknown" + title = candidate.get("title") or "" + year = candidate.get("year") or "" + venue = candidate.get("venue") or "" + doi = candidate.get("doi") or "" + arxiv_id = candidate.get("arxiv_id") or "" + + if arxiv_id and not venue: + entry_type = "misc" + fields = [ + ("author", author_str), + ("title", title), + ("year", str(year)), + ("eprint", arxiv_id), + ("archivePrefix", "arXiv"), + ] + else: + entry_type = "article" + fields = [ + ("author", author_str), + ("title", title), + ("journal", venue), + ("year", str(year)), + ] + if doi: + fields.append(("doi", doi)) + + lines = [f"@{entry_type}{{{key},"] + for k, v in fields: + if v: + lines.append(f" {k} = {{{v}}},") + lines.append("}") + return "\n".join(lines) + + +def run(query, max_per_source=8): + all_results = [] + errors = {} + for name, fn in (("arxiv", search_arxiv), ("semantic_scholar", search_s2), ("crossref", search_crossref)): + try: + all_results.extend(fn(query, max_per_source)) + except Exception as e: + errors[name] = str(e) + + merged = merge_candidates(all_results) + + used_keys = set() + for cand in merged: + result = verify(title=cand.get("title"), arxiv_id=cand.get("arxiv_id"), doi=cand.get("doi")) + cand["verdict"] = result["verdict"] + cand["verification_checks"] = result["checks"] + if result["verdict"] == "verified": + cand["bibtex_key"] = make_bibtex_key(cand, used_keys) + + return {"query": query, "search_errors": errors, "candidates": merged} + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("query") + ap.add_argument("--max-per-source", type=int, default=8) + ap.add_argument("--bib-out", default=None, help="path to write BibTeX for verified entries") + args = ap.parse_args() + + try: + report = run(args.query, args.max_per_source) + except Exception as e: + print(json.dumps({"error": str(e)}, ensure_ascii=False)) + sys.exit(1) + + print(json.dumps(report, ensure_ascii=False, indent=2)) + + if args.bib_out: + verified = [c for c in report["candidates"] if c["verdict"] == "verified"] + with open(args.bib_out, "w", encoding="utf-8") as f: + for cand in verified: + f.write(to_bibtex(cand, cand["bibtex_key"])) + f.write("\n\n") + sys.stderr.write(f"Wrote {len(verified)} verified BibTeX entries to {args.bib_out}\n") diff --git a/.claude/skills/literature-search-verify/scripts/search_arxiv.py b/.claude/skills/literature-search-verify/scripts/search_arxiv.py new file mode 100644 index 0000000..8367290 --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/search_arxiv.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Search arXiv via its public Atom API. No API key required. + +CLI usage: + python3 search_arxiv.py "UAV magnetic compensation" --max 10 + +Importable: + from search_arxiv import search_arxiv +""" +import sys +import json +import argparse +import urllib.request +import urllib.parse +import xml.etree.ElementTree as ET + +ARXIV_API = "http://export.arxiv.org/api/query" +NS = {"atom": "http://www.w3.org/2005/Atom"} + + +def search_arxiv(query, max_results=10, timeout=20): + params = { + "search_query": f"all:{query}", + "start": 0, + "max_results": max_results, + "sortBy": "relevance", + "sortOrder": "descending", + } + url = f"{ARXIV_API}?{urllib.parse.urlencode(params)}" + with urllib.request.urlopen(url, timeout=timeout) as resp: + data = resp.read() + root = ET.fromstring(data) + results = [] + for entry in root.findall("atom:entry", NS): + id_el = entry.find("atom:id", NS) + title_el = entry.find("atom:title", NS) + summary_el = entry.find("atom:summary", NS) + published_el = entry.find("atom:published", NS) + if id_el is None or title_el is None: + continue + arxiv_id_full = id_el.text.strip() + arxiv_id = arxiv_id_full.rsplit("/", 1)[-1] + title = " ".join(title_el.text.split()) + summary = " ".join(summary_el.text.split()) if summary_el is not None else "" + authors = [ + a.find("atom:name", NS).text + for a in entry.findall("atom:author", NS) + if a.find("atom:name", NS) is not None + ] + published = published_el.text[:10] if published_el is not None else None + pdf_url = None + for link in entry.findall("atom:link", NS): + if link.attrib.get("title") == "pdf": + pdf_url = link.attrib.get("href") + results.append({ + "source": "arxiv", + "arxiv_id": arxiv_id, + "title": title, + "authors": authors, + "year": published[:4] if published else None, + "published": published, + "abstract": summary, + "pdf_url": pdf_url, + "doi": None, + }) + return results + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("query") + ap.add_argument("--max", type=int, default=10) + args = ap.parse_args() + try: + out = search_arxiv(args.query, args.max) + print(json.dumps(out, ensure_ascii=False, indent=2)) + except Exception as e: + print(json.dumps({"error": str(e)}, ensure_ascii=False)) + sys.exit(1) diff --git a/.claude/skills/literature-search-verify/scripts/search_crossref.py b/.claude/skills/literature-search-verify/scripts/search_crossref.py new file mode 100644 index 0000000..1f76745 --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/search_crossref.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Search the Crossref works API. No API key required. +Good for journal articles / DOIs that arXiv and Semantic Scholar might miss. + +CLI usage: + python3 search_crossref.py "UAV magnetic compensation" --max 10 + +Importable: + from search_crossref import search_crossref +""" +import sys +import json +import argparse +import urllib.request +import urllib.parse + +CROSSREF_API = "https://api.crossref.org/works" +UA = "literature-search-verify-skill/1.0 (mailto:research-assistant@example.com)" + + +def search_crossref(query, max_results=10, timeout=20): + params = {"query": query, "rows": max_results} + url = f"{CROSSREF_API}?{urllib.parse.urlencode(params)}" + req = urllib.request.Request(url, headers={"User-Agent": UA}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + data = json.loads(resp.read()) + results = [] + for item in data.get("message", {}).get("items", []) or []: + titles = item.get("title") or [] + title = titles[0] if titles else "" + authors = [] + for a in item.get("author", []) or []: + name = " ".join(filter(None, [a.get("given"), a.get("family")])) + if name: + authors.append(name) + year = None + date_parts = (item.get("issued", {}) or {}).get("date-parts") + if date_parts and date_parts[0]: + year = date_parts[0][0] + containers = item.get("container-title") or [] + results.append({ + "source": "crossref", + "title": title, + "authors": authors, + "year": year, + "venue": containers[0] if containers else None, + "doi": item.get("DOI"), + "arxiv_id": None, + "abstract": None, + }) + return results + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("query") + ap.add_argument("--max", type=int, default=10) + args = ap.parse_args() + try: + out = search_crossref(args.query, args.max) + print(json.dumps(out, ensure_ascii=False, indent=2)) + except Exception as e: + print(json.dumps({"error": str(e)}, ensure_ascii=False)) + sys.exit(1) diff --git a/.claude/skills/literature-search-verify/scripts/search_semantic_scholar.py b/.claude/skills/literature-search-verify/scripts/search_semantic_scholar.py new file mode 100644 index 0000000..dd6cc6b --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/search_semantic_scholar.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Search the Semantic Scholar Graph API. No API key required for light use; +set the S2_API_KEY environment variable for higher rate limits. + +CLI usage: + python3 search_semantic_scholar.py "UAV magnetic compensation" --max 10 + +Importable: + from search_semantic_scholar import search_s2 +""" +import sys +import os +import json +import argparse +import urllib.request +import urllib.parse + +S2_API = "https://api.semanticscholar.org/graph/v1/paper/search" +FIELDS = "title,authors,year,venue,externalIds,abstract,citationCount" + + +def search_s2(query, max_results=10, timeout=20): + params = {"query": query, "limit": max_results, "fields": FIELDS} + url = f"{S2_API}?{urllib.parse.urlencode(params)}" + req = urllib.request.Request(url) + api_key = os.environ.get("S2_API_KEY") + if api_key: + req.add_header("x-api-key", api_key) + with urllib.request.urlopen(req, timeout=timeout) as resp: + data = json.loads(resp.read()) + results = [] + for p in data.get("data", []) or []: + ext = p.get("externalIds") or {} + results.append({ + "source": "semantic_scholar", + "title": p.get("title"), + "authors": [a.get("name") for a in (p.get("authors") or [])], + "year": p.get("year"), + "venue": p.get("venue"), + "doi": ext.get("DOI"), + "arxiv_id": ext.get("ArXiv"), + "abstract": p.get("abstract"), + "citation_count": p.get("citationCount"), + }) + return results + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("query") + ap.add_argument("--max", type=int, default=10) + args = ap.parse_args() + try: + out = search_s2(args.query, args.max) + print(json.dumps(out, ensure_ascii=False, indent=2)) + except Exception as e: + print(json.dumps({"error": str(e)}, ensure_ascii=False)) + sys.exit(1) diff --git a/.claude/skills/literature-search-verify/scripts/verify_citation.py b/.claude/skills/literature-search-verify/scripts/verify_citation.py new file mode 100644 index 0000000..b7f4a24 --- /dev/null +++ b/.claude/skills/literature-search-verify/scripts/verify_citation.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Independently cross-verify a single candidate citation. This is the anti- +hallucination check: it never trusts a single source. If a check cannot be +run at all (e.g. no network), that check is reported as "skipped" -- never +silently counted as a pass. + +CLI usage: + python3 verify_citation.py --title "Compensation of magnetic ..." \\ + --arxiv-id 2401.12345 --doi 10.1109/TGRS.2024.1234567 + +Importable: + from verify_citation import verify +""" +import sys +import json +import argparse +import difflib +import urllib.request +import urllib.parse +import urllib.error +import xml.etree.ElementTree as ET + +ATOM_NS = {"atom": "http://www.w3.org/2005/Atom"} + + +def title_similarity(a, b): + if not a or not b: + return 0.0 + return difflib.SequenceMatcher(None, a.lower().strip(), b.lower().strip()).ratio() + + +def check_arxiv_id(arxiv_id, timeout=20): + """Confirm an arXiv ID actually resolves to a real paper.""" + try: + url = f"http://export.arxiv.org/api/query?id_list={urllib.parse.quote(arxiv_id)}" + with urllib.request.urlopen(url, timeout=timeout) as resp: + data = resp.read() + root = ET.fromstring(data) + entry = root.find("atom:entry", ATOM_NS) + if entry is None: + return {"status": "fail", "reason": "arXiv ID not found"} + title_el = entry.find("atom:title", ATOM_NS) + title = " ".join(title_el.text.split()) if title_el is not None else None + return {"status": "pass", "canonical_title": title} + except Exception as e: + return {"status": "skipped", "reason": str(e)} + + +def check_doi(doi, timeout=20): + """Confirm a DOI actually resolves via Crossref.""" + try: + url = f"https://api.crossref.org/works/{urllib.parse.quote(doi)}" + req = urllib.request.Request( + url, headers={"User-Agent": "literature-search-verify-skill/1.0"} + ) + with urllib.request.urlopen(req, timeout=timeout) as resp: + data = json.loads(resp.read()) + titles = data.get("message", {}).get("title") or [] + return {"status": "pass", "canonical_title": titles[0] if titles else None} + except urllib.error.HTTPError as e: + if e.code == 404: + return {"status": "fail", "reason": "DOI not found in Crossref"} + return {"status": "skipped", "reason": f"HTTP {e.code}"} + except Exception as e: + return {"status": "skipped", "reason": str(e)} + + +def check_title_cross_source(title, timeout=20): + """Independently re-search by title on a different source (Semantic + Scholar) and require a near-exact title match. This is what catches a + plausible-sounding but entirely invented title/author combination.""" + try: + params = {"query": title, "limit": 3, "fields": "title"} + url = f"https://api.semanticscholar.org/graph/v1/paper/search?{urllib.parse.urlencode(params)}" + with urllib.request.urlopen(url, timeout=timeout) as resp: + data = json.loads(resp.read()) + candidates = data.get("data", []) or [] + if not candidates: + return {"status": "fail", "reason": "no matching title found on Semantic Scholar"} + best = max(candidates, key=lambda p: title_similarity(title, p.get("title", ""))) + sim = title_similarity(title, best.get("title", "")) + if sim >= 0.9: + return {"status": "pass", "similarity": round(sim, 3), "matched_title": best.get("title")} + return {"status": "fail", "similarity": round(sim, 3), "matched_title": best.get("title")} + except Exception as e: + return {"status": "skipped", "reason": str(e)} + + +def verify(title=None, arxiv_id=None, doi=None): + checks = {} + if arxiv_id: + checks["arxiv_id_check"] = check_arxiv_id(arxiv_id) + if doi: + checks["doi_check"] = check_doi(doi) + if title: + checks["title_cross_source_check"] = check_title_cross_source(title) + + passed = [c for c in checks.values() if c["status"] == "pass"] + failed = [c for c in checks.values() if c["status"] == "fail"] + + if failed: + verdict = "suspect" # something actively contradicted it + elif passed: + verdict = "verified" # at least one independent check passed + else: + verdict = "unverified" # everything skipped (e.g. no network) -- NOT the same as verified + + return {"title": title, "arxiv_id": arxiv_id, "doi": doi, "verdict": verdict, "checks": checks} + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--title", default=None) + ap.add_argument("--arxiv-id", default=None) + ap.add_argument("--doi", default=None) + args = ap.parse_args() + if not any([args.title, args.arxiv_id, args.doi]): + print(json.dumps({"error": "provide at least one of --title/--arxiv-id/--doi"})) + sys.exit(1) + result = verify(args.title, args.arxiv_id, args.doi) + print(json.dumps(result, ensure_ascii=False, indent=2)) diff --git a/.claude/skills/paper-wiriting-grounded/SKILL.md b/.claude/skills/paper-wiriting-grounded/SKILL.md new file mode 100644 index 0000000..72529d2 --- /dev/null +++ b/.claude/skills/paper-wiriting-grounded/SKILL.md @@ -0,0 +1,50 @@ +--- +name: paper-writing-grounded +description: Draft, revise, or polish academic paper and thesis sections (abstract, introduction, methods, experiments, related work, conclusion) for LaTeX conference/journal templates or Word/WPS-ready Chinese theses, while strictly preventing fabricated numbers, invented experimental results, or unsupported quantitative claims. Use this whenever the user asks to write, draft, outline, or polish any section of a paper or dissertation, wants raw results turned into prose, needs a LaTeX draft matching a conference template, wants Word/WPS text for a Chinese-language thesis, or wants AI-sounding writing "humanized" — anywhere the draft could end up stating a number, percentage, or comparison that didn't actually come from real data. +--- + +# 论文写作(强制数据溯源版) + +## 为什么需要这个技能 + +LLM写论文时最大的隐患不是文笔差,而是**在没有真实数据支撑的地方,顺手编一个"看起来合理"的数字**——比如"补偿后RMSE降低了23%"这种话,读起来完全正常,但如果这个23%不是从真实实验里来的,那就是数据捏造,一旦被发现是学术诚信问题,不是文笔问题。这个技能的核心规则就一条:**任何关于"我们的方法/我们的结果"的具体数字、百分比、对比、最高级描述,必须能追溯到用户提供的真实结果,追溯不到就必须明确标记出来,绝不能编一个数填上去。** + +## 工作流程 + +### 第一步:建立"真实结果登记表" + +在动笔写任何会出现具体数字的段落之前(尤其是实验/结果部分),先确认这一节要用到的真实数据来源——可以是用户粘贴的数字、一份CSV/JSON格式的指标文件、SimPEG/Harmonica处理流程跑出来的输出、图表标题里的数值等等。如果对话里还没有提供,直接问用户要,而不是先写着占位数字"等下再改"——占位数字很容易被忘记改掉,最后混进定稿。 + +把这一节会用到的每个具体数字记下来,连同它的出处(来自哪个文件/哪次实验/哪张图),这就是这一节写作时唯一可信的"真实结果登记表"。 + +### 第二步:分节起草 + +根据目标产出选结构: +- **英文期刊/会议投稿(LaTeX)**:Abstract → Introduction → Related Work → Methods → Experiments → Conclusion,先问清楚目标模板(NeurIPS/ICLR/ICML,或者你们学校的LaTeX模板),没问清楚就先按通用IMRAD结构起草,后面再套模板。 +- **中文学位论文(Word/WPS)**:按论文各章节结构(通常是绪论/文献综述/方法/实验与结果/结论)起草,产出用docx技能生成格式化的Word文档。 + +### 第三步:强制溯源规则(核心) + +起草时,每写到一个具体数字、百分比、显著性描述("显著优于"、"最优"这类)时,问自己:这个数字能不能在第一步的"真实结果登记表"里找到出处,或者能不能在literature-search-verify技能核实过的文献里找到支持? + +- 能追溯到 → 正常写,行文里可以顺带标注来源(比如"如表2所示") +- 追溯不到 → **不要编数字填上去**,改成明确的占位标记:`[需要数据:补偿后与补偿前的RMSE对比数值]`,让用户知道这里缺什么、需要补什么,而不是假装写完了 + +这条规则同样适用于"我们首次提出"、"计算效率更高"这类没有具体数字但仍是实质性主张的表述——同样需要能落到某个真实依据上,落不到就标记出来,不要含糊带过。 + +### 第四步:自查 + +起草完一节后,回头把这一节里所有的数字、比较、最高级表述再过一遍,确认每一条都能对应到登记表或已核实的引用。在回复末尾列出这次自查发现的、还没解决的`[需要数据]`标记,方便用户一次性补齐,而不是散落在长文里被忽略。 + +### 第五步:去AI味润色 + +润色不等于压缩。除非用户明确要求精简,不要为了让句子读起来"更自然"而删掉具体的研究对象、数据口径、方法条件、指标定义这些内容——这些恰恰是审稿人会重点核对的地方。去掉的应该是空洞的模板化表达(比如"综上所述,本研究具有重要意义"这类没有信息量的套话)和明显的AI腔调用词,而不是数据本身的精度和限定条件。 + +### 第六步:产出格式 + +- LaTeX:正文里的`\cite{}`只使用literature-search-verify技能核实过、生成过BibTeX的citation key,不自己编新的引用键。 +- Word/WPS:用docx技能产出格式化文档;提醒用户Zotero的Word插件可以配合插入引用,但WPS对该插件兼容性一般,可能需要先用Zotero导出RTF/纯文本引用再手动整理进WPS。 + +## 和 literature-search-verify 技能的配合 + +正文里任何"related work"或背景介绍部分引用的文献,只能来自literature-search-verify技能已核实的条目;这个技能不负责验证引用真实性,只负责确保"我们自己的实验结果"这部分不被编造数据污染。两者结合,才是"文献不编、数据不编"的完整闭环。 diff --git a/.claude/skills/paper-wiriting-grounded/paper-writing-grounded.skill b/.claude/skills/paper-wiriting-grounded/paper-writing-grounded.skill new file mode 100644 index 0000000..59c2cc1 Binary files /dev/null and b/.claude/skills/paper-wiriting-grounded/paper-writing-grounded.skill differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab778b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +output +*.pyc \ No newline at end of file diff --git a/references/uav_aeromagnetic_compensation/README.md b/references/uav_aeromagnetic_compensation/README.md new file mode 100644 index 0000000..fdc93e6 --- /dev/null +++ b/references/uav_aeromagnetic_compensation/README.md @@ -0,0 +1,56 @@ +# UAV aeromagnetic compensation — literature archive + +Archived: 2026-07-20 +Verified entries: 30 +PDFs bundled: 3 + +Every entry in `references.bib` passed independent verification (arXiv ID / DOI resolution and/or cross-source title match, similarity >= 0.9) via the literature-search-verify skill before being archived here. Citation keys follow the surname+year convention and are stable -- the paper-writing-grounded skill's `\cite{}` calls should match these keys directly. + +## Entries (chronological, oldest first) + +- **leach1980** (1980) — Aeromagnetic Compensation as a Linear Regression Problem. *Information Linkage Between Applied Mathematics and Industry*. DOI: 10.1016/b978-0-12-628750-9.50017-6 + - Note: Early foundational formulation of aeromagnetic compensation as a linear regression / least-squares problem +- **williams1993** (1993) — Aeromagnetic compensation using neural networks. *Neural Computing \& Applications*. DOI: 10.1007/bf01414949 +- **leblanc2001** (2001) — Denoising of aeromagnetic data via the wavelet transform. *Geophysics*. DOI: 10.1190/1.1487121 +- **fedi2006** (2006) — On ``Wavelet denoising of aeromagnetic data'' (George E. Leblanc and William A. Morris, 2001, Geophysics, 71, 1793--1804). *Geophysics*. DOI: 10.1190/1.2233897 + - Note: Discussion/comment on Leblanc \& Morris 2001 +- **zhang2011** (2011) — A simplified aeromagnetic compensation model for low magnetism UAV platform. *2011 IEEE International Geoscience and Remote Sensing Symposium (IGARSS)*. DOI: 10.1109/igarss.2011.6049950 +- **metge2013** (2013) — Dynamic magnetic field compensation for micro UAV attitude estimation. *2013 International Conference on Unmanned Aircraft Systems (ICUAS)*. DOI: 10.1109/icuas.2013.6564754 + - Note: Magnetic compensation for onboard attitude estimation, not for the aeromagnetic survey signal itself +- **zhang2016** (2016) — Aeromagnetic compensation with partial least square regression. *ASEG Extended Abstracts*. DOI: 10.1071/aseg2016ab300 +- **zhao2016** (2016) — A Novel Aeromagnetic Compensation Method Based on the Improved Recursive Least-Squares. *Smart Innovation, Systems and Technologies*. DOI: 10.1007/978-3-319-50212-0_21 +- **ma2017** (2017) — A dual estimate method for aeromagnetic compensation. *Measurement Science and Technology*. DOI: 10.1088/1361-6501/aa883b +- **wu2017** (2017) — Aeromagnetic gradient compensation method for helicopter based on \ensuremath{\epsilon}-support vector regression algorithm. *Journal of Applied Remote Sensing*. DOI: 10.1117/1.jrs.11.025012 +- **li2018** (2018) — Aeromagnetic compensation of Rotor UAV Based on Least Squares. *2018 37th Chinese Control Conference (CCC)*. DOI: 10.23919/chicc.2018.8483068 +- **melo2018** (2018) — 2D discrete wavelet transform for denoising aeromagnetic data. *SEG Technical Program Expanded Abstracts 2018*. DOI: 10.1190/segam2018-2998295.1 +- **hang2019** (2019) — A Simulation Method of Generating the Output of Magnetometer for Aeromagnetic Compensation. *IGARSS 2019 - 2019 IEEE International Geoscience and Remote Sensing Symposium*. DOI: 10.1109/igarss.2019.8897903 +- **tuck2019** (2019) — Characterization and compensation of magnetic interference resulting from unmanned aircraft systems. *Carleton University*. DOI: 10.22215/etd/2019-13546 +- **walter2019** (2019) — Spectral Analysis of Magnetometer Swing in High-Resolution UAV-borne Aeromagnetic Surveys. *2019 IEEE Systems and Technologies for Remote Sensing Applications Through Unmanned Aerial Systems (STRATUS)*. DOI: 10.1109/stratus.2019.8713313 +- **wang2019** (2019) — An Automatic Method to Estimate the Calibration Quality of the Aeromagnetic Compensation. *IGARSS 2019 - 2019 IEEE International Geoscience and Remote Sensing Symposium*. DOI: 10.1109/igarss.2019.8898533 +- **zhao2020** (2020) — An Aeromagnetic Compensation Algorithm Based on Neural Network. *82nd EAGE Annual Conference \& Exhibition*. DOI: 10.3997/2214-4609.202010906 +- **gnadt2022** (2022) — Derivation and Extensions of the Tolles-Lawson Model for Aeromagnetic Compensation. *arXiv preprint arXiv:2212.09899* +- **nerrise2024** (2024) — Physics-Informed Calibration of Aeromagnetic Compensation in Magnetic Navigation Systems using Liquid Time-Constant Networks. *arXiv preprint arXiv:2401.09631* +- **yuan2024** (2024) — Application study of UAV aeromagnetic measurement based on rubidium optical pump magnetometer. *International Workshop on Gravity, Electrical \& Magnetic Methods and Their Applications, Shenzhen, China, May 19--22, 2024*. DOI: 10.1190/gem2024-021.1 +- **dai2025** (2025) — Aeromagnetic Compensation for UAVs Using Transformer Neural Networks. *Sensors*. DOI: 10.3390/s25226852 +- **fang2025** (2025) — An aeromagnetic compensation method based on the extended Tolles Lawson model. *Journal of Physics: Conference Series*. DOI: 10.1088/1742-6596/3169/1/012043 +- **hu2025** (2025) — Influence of Attitude Changes on Magnetic Measurement Accuracy in UAV Magnetic Anomaly Detection. *2025 5th International Conference on Sensors and Information Technology (ICSI)*. DOI: 10.1109/icsi64877.2025.11009300 +- **qiao2025** (2025) — Dual-Channel Aeromagnetic Compensation Method for Continuous and Intermittent OBE Interference. *IEEE Transactions on Instrumentation and Measurement*. DOI: 10.1109/tim.2025.3599271 +- **wang2025** (2025) — An Aeromagnetic Compensation Algorithm Based on a Temporal Convolutional Network. *Applied Sciences*. DOI: 10.3390/app15063105 +- **wang2025maneuver** (2025) — Magnetometer Compensation for Magnetic Interference in Aircraft Maneuvers by Using INS. *Advances in Guidance, Navigation and Control*. DOI: 10.1007/978-981-96-2240-5_29 +- **song2026** (2026) — An Enhanced Tolles--Lawson Model With Temperature Compensation for Aeromagnetic Compensation of Triaxial Magnetometers. *IEEE Transactions on Instrumentation and Measurement*. DOI: 10.1109/tim.2026.3697092 +- **sun2026** (2026) — Physics-Informed Tolles--Lawson and Neural Network Hybrid Modeling for Magnetic Compensation in Uncrewed Ground Vehicles. *IEEE Sensors Journal*. DOI: 10.1109/jsen.2026.3684910 + - Note: Ground-vehicle (not airborne UAV) application of the Tolles--Lawson + NN hybrid compensation approach +- **xie2026** (2026) — Aeromagnetic Nonlinear Interference Compensation Method Based on Hybrid LSTM and BP Neural Network Architecture. *Computer Science and Application*. DOI: 10.12677/csa.2026.161011 +- **you2026** (2026) — Electromagnetic interference compensation for aeromagnetic data using adaptive wavelet denoising and partial least squares regression. *Measurement Science and Technology*. DOI: 10.1088/1361-6501/ae8616 + +## Flagged during search — NOT included above, do not cite + +- An Aeromagnetic Compensation Algorithm based on Complete Ensemble Empirical Mode Decomposition with Adaptive Noise and a Physics-Guided Neural Network (DOI 10.52710/fcb.145) — DOI resolves and title matches via Crossref, but the venue is "Fuel Cells Bulletin", topically unrelated to aeromagnetics -- likely a hijacked/predatory journal or metadata error. Do not cite. + +## Search coverage notes + +Direction covered: UAV/airborne aeromagnetic compensation (Tolles-Lawson family) -- theory, least-squares/ridge/PLS regression variants, wavelet denoising, neural-network (BP/LSTM/TCN/Transformer/physics-informed) approaches, and UAV-specific magnetic-interference characterization. +Queried via arXiv + Crossref + Semantic Scholar (S2 was rate-limited (HTTP 429) for much of the session, so most entries only got single-channel verification -- arXiv ID or DOI resolution -- rather than the additional cross-source title check; this is noted per-entry as "unverified"/"skipped" in the raw JSON reports, not silently upgraded to double-verified). +Searched but found no on-topic hits: Kalman-filter-based aeromagnetic compensation; genetic-algorithm/PSO-based aeromagnetic compensation. +Not covered at all: CNKI/Wanfang/VIP (Chinese databases, no public API) -- use the Zotero Connector browser extension logged into a university account for these. +Raw per-query JSON search/verification reports (including filtered-out noise from ambiguous keyword matches like "Tolles"/"Lawson" as surnames) are kept in .claude/skills/literature-search-verify/output/ for audit purposes and are not part of this archive. diff --git a/references/uav_aeromagnetic_compensation/pdfs/fang2025_tlgf.pdf b/references/uav_aeromagnetic_compensation/pdfs/fang2025_tlgf.pdf new file mode 100644 index 0000000..91bf564 Binary files /dev/null and b/references/uav_aeromagnetic_compensation/pdfs/fang2025_tlgf.pdf differ diff --git a/references/uav_aeromagnetic_compensation/pdfs/gnadt2022_tolles_lawson.pdf b/references/uav_aeromagnetic_compensation/pdfs/gnadt2022_tolles_lawson.pdf new file mode 100644 index 0000000..af1baaa Binary files /dev/null and b/references/uav_aeromagnetic_compensation/pdfs/gnadt2022_tolles_lawson.pdf differ diff --git a/references/uav_aeromagnetic_compensation/pdfs/nerrise2024_ltc_magnav.pdf b/references/uav_aeromagnetic_compensation/pdfs/nerrise2024_ltc_magnav.pdf new file mode 100644 index 0000000..ab8e213 Binary files /dev/null and b/references/uav_aeromagnetic_compensation/pdfs/nerrise2024_ltc_magnav.pdf differ diff --git a/references/uav_aeromagnetic_compensation/references.bib b/references/uav_aeromagnetic_compensation/references.bib new file mode 100644 index 0000000..82d4209 --- /dev/null +++ b/references/uav_aeromagnetic_compensation/references.bib @@ -0,0 +1,251 @@ +% UAV aeromagnetic compensation -- verified references, chronological order +% Archived 2026-07-20 + +@article{leach1980, + title = {Aeromagnetic Compensation as a Linear Regression Problem}, + author = {Leach, Barrie W.}, + year = {1980}, + journal = {Information Linkage Between Applied Mathematics and Industry}, + doi = {10.1016/b978-0-12-628750-9.50017-6}, + note = {Early foundational formulation of aeromagnetic compensation as a linear regression / least-squares problem} +} + +@article{williams1993, + title = {Aeromagnetic compensation using neural networks}, + author = {Williams, Peter M.}, + year = {1993}, + journal = {Neural Computing \& Applications}, + doi = {10.1007/bf01414949} +} + +@article{leblanc2001, + title = {Denoising of aeromagnetic data via the wavelet transform}, + author = {Leblanc, George E. and Morris, William A.}, + year = {2001}, + journal = {Geophysics}, + doi = {10.1190/1.1487121} +} + +@article{fedi2006, + title = {On ``Wavelet denoising of aeromagnetic data'' (George E. Leblanc and William A. Morris, 2001, Geophysics, 71, 1793--1804)}, + author = {Fedi, M. and Quarta, T.}, + year = {2006}, + journal = {Geophysics}, + doi = {10.1190/1.2233897}, + note = {Discussion/comment on Leblanc \& Morris 2001} +} + +@inproceedings{zhang2011, + title = {A simplified aeromagnetic compensation model for low magnetism UAV platform}, + author = {Zhang, Baogang and Guo, Ziqi and Qiao, Yanchao}, + year = {2011}, + booktitle = {2011 IEEE International Geoscience and Remote Sensing Symposium (IGARSS)}, + doi = {10.1109/igarss.2011.6049950} +} + +@inproceedings{metge2013, + title = {Dynamic magnetic field compensation for micro UAV attitude estimation}, + author = {Metge, J. and Megret, R. and Giremus, A. and Berthoumieu, Y. and Mazel, C.}, + year = {2013}, + booktitle = {2013 International Conference on Unmanned Aircraft Systems (ICUAS)}, + doi = {10.1109/icuas.2013.6564754}, + note = {Magnetic compensation for onboard attitude estimation, not for the aeromagnetic survey signal itself} +} + +@article{zhang2016, + title = {Aeromagnetic compensation with partial least square regression}, + author = {Zhang, Dailei and Huang, Danian and Lu, Junwei and Zhu, Boyuan}, + year = {2016}, + journal = {ASEG Extended Abstracts}, + doi = {10.1071/aseg2016ab300} +} + +@incollection{zhao2016, + title = {A Novel Aeromagnetic Compensation Method Based on the Improved Recursive Least-Squares}, + author = {Zhao, Guanyi and Shao, Yuqing and Han, Qi and Tong, Xiaojun}, + year = {2016}, + booktitle = {Smart Innovation, Systems and Technologies}, + doi = {10.1007/978-3-319-50212-0_21} +} + +@article{ma2017, + title = {A dual estimate method for aeromagnetic compensation}, + author = {Ma, Ming and Zhou, Zhijian and Cheng, Defu}, + year = {2017}, + journal = {Measurement Science and Technology}, + doi = {10.1088/1361-6501/aa883b} +} + +@article{wu2017, + title = {Aeromagnetic gradient compensation method for helicopter based on \ensuremath{\epsilon}-support vector regression algorithm}, + author = {Wu, Peilin and Zhang, Qunying and Fei, Chunjiao and Fang, Guangyou}, + year = {2017}, + journal = {Journal of Applied Remote Sensing}, + doi = {10.1117/1.jrs.11.025012} +} + +@inproceedings{li2018, + title = {Aeromagnetic compensation of Rotor UAV Based on Least Squares}, + author = {Li, Han and Ge, Jian and Dong, Haobin and Qiu, Xiangyu and Luo, Wang and Liu, Huan and Yuan, Zhiwen and Zhu, Jun and Zhang, Haiyang}, + year = {2018}, + booktitle = {2018 37th Chinese Control Conference (CCC)}, + doi = {10.23919/chicc.2018.8483068} +} + +@inproceedings{melo2018, + title = {2D discrete wavelet transform for denoising aeromagnetic data}, + author = {Melo, Felipe F. and Barbosa, Val{\'e}ria C. F. and Jim{\'e}nez-Teja, Yolanda}, + year = {2018}, + booktitle = {SEG Technical Program Expanded Abstracts 2018}, + doi = {10.1190/segam2018-2998295.1} +} + +@inproceedings{hang2019, + title = {A Simulation Method of Generating the Output of Magnetometer for Aeromagnetic Compensation}, + author = {Hang, Zhiyuan and He, Futong and Wang, Zhifang and Han, Qi}, + year = {2019}, + booktitle = {IGARSS 2019 - 2019 IEEE International Geoscience and Remote Sensing Symposium}, + doi = {10.1109/igarss.2019.8897903} +} + +@phdthesis{tuck2019, + title = {Characterization and compensation of magnetic interference resulting from unmanned aircraft systems}, + author = {Tuck, Loughlin}, + year = {2019}, + school = {Carleton University}, + doi = {10.22215/etd/2019-13546} +} + +@inproceedings{walter2019, + title = {Spectral Analysis of Magnetometer Swing in High-Resolution UAV-borne Aeromagnetic Surveys}, + author = {Walter, Callum and Braun, Alexander and Fotopoulos, Georgia}, + year = {2019}, + booktitle = {2019 IEEE Systems and Technologies for Remote Sensing Applications Through Unmanned Aerial Systems (STRATUS)}, + doi = {10.1109/stratus.2019.8713313} +} + +@inproceedings{wang2019, + title = {An Automatic Method to Estimate the Calibration Quality of the Aeromagnetic Compensation}, + author = {Wang, Yizhen and Han, Qi and Hu, Kai and Zhan, Dechen}, + year = {2019}, + booktitle = {IGARSS 2019 - 2019 IEEE International Geoscience and Remote Sensing Symposium}, + doi = {10.1109/igarss.2019.8898533} +} + +@inproceedings{zhao2020, + title = {An Aeromagnetic Compensation Algorithm Based on Neural Network}, + author = {Zhao, X. and Yu, P. and Jiao, J.}, + year = {2020}, + booktitle = {82nd EAGE Annual Conference \& Exhibition}, + doi = {10.3997/2214-4609.202010906} +} + +@article{gnadt2022, + title = {Derivation and Extensions of the Tolles-Lawson Model for Aeromagnetic Compensation}, + author = {Gnadt, Albert R. and Wollaber, Allan B. and Nielsen, Aaron P.}, + year = {2022}, + journal = {arXiv preprint arXiv:2212.09899}, + eprint = {2212.09899}, + archivePrefix = {arXiv}, + url = {https://arxiv.org/abs/2212.09899} +} + +@article{nerrise2024, + title = {Physics-Informed Calibration of Aeromagnetic Compensation in Magnetic Navigation Systems using Liquid Time-Constant Networks}, + author = {Nerrise, Favour and Sosanya, Andrew Sosa and Neary, Patrick}, + year = {2024}, + journal = {arXiv preprint arXiv:2401.09631}, + eprint = {2401.09631}, + archivePrefix = {arXiv}, + url = {https://arxiv.org/abs/2401.09631} +} + +@inproceedings{yuan2024, + title = {Application study of UAV aeromagnetic measurement based on rubidium optical pump magnetometer}, + author = {Yuan, Peng and Qiao, Zhong-kun}, + year = {2024}, + booktitle = {International Workshop on Gravity, Electrical \& Magnetic Methods and Their Applications, Shenzhen, China, May 19--22, 2024}, + doi = {10.1190/gem2024-021.1} +} + +@article{dai2025, + title = {Aeromagnetic Compensation for UAVs Using Transformer Neural Networks}, + author = {Dai, Weiming and Yang, Changcheng and Zhou, Shuai}, + year = {2025}, + journal = {Sensors}, + doi = {10.3390/s25226852} +} + +@article{fang2025, + title = {An aeromagnetic compensation method based on the extended Tolles Lawson model}, + author = {Fang, Yuanxing and Zhang, Chao and Zheng, Yaoxin and Liu, Weiqiang and Zha, Songyuan}, + year = {2025}, + journal = {Journal of Physics: Conference Series}, + doi = {10.1088/1742-6596/3169/1/012043} +} + +@inproceedings{hu2025, + title = {Influence of Attitude Changes on Magnetic Measurement Accuracy in UAV Magnetic Anomaly Detection}, + author = {Hu, Xinyue}, + year = {2025}, + booktitle = {2025 5th International Conference on Sensors and Information Technology (ICSI)}, + doi = {10.1109/icsi64877.2025.11009300} +} + +@article{qiao2025, + title = {Dual-Channel Aeromagnetic Compensation Method for Continuous and Intermittent OBE Interference}, + author = {Qiao, Zhi and Li, You and Meng, Zhaohai and Han, Qi}, + year = {2025}, + journal = {IEEE Transactions on Instrumentation and Measurement}, + doi = {10.1109/tim.2025.3599271} +} + +@article{wang2025, + title = {An Aeromagnetic Compensation Algorithm Based on a Temporal Convolutional Network}, + author = {Wang, Han and Zuo, Boxin}, + year = {2025}, + journal = {Applied Sciences}, + doi = {10.3390/app15063105} +} + +@incollection{wang2025maneuver, + title = {Magnetometer Compensation for Magnetic Interference in Aircraft Maneuvers by Using INS}, + author = {Wang, Guanjie and Yue, Yazhou and Dong, Jiahang and Zhou, Qi and Wang, Haoming and Wang, Jingjiang and Jiang, Haofeng}, + year = {2025}, + booktitle = {Advances in Guidance, Navigation and Control}, + series = {Lecture Notes in Electrical Engineering}, + doi = {10.1007/978-981-96-2240-5_29} +} + +@article{song2026, + title = {An Enhanced Tolles--Lawson Model With Temperature Compensation for Aeromagnetic Compensation of Triaxial Magnetometers}, + author = {Song, Wenhua and Yang, Zhicheng and Ma, Yan and Li, Bin and Xie, Songyun and Chen, Chen and Qi, Kankan}, + year = {2026}, + journal = {IEEE Transactions on Instrumentation and Measurement}, + doi = {10.1109/tim.2026.3697092} +} + +@article{sun2026, + title = {Physics-Informed Tolles--Lawson and Neural Network Hybrid Modeling for Magnetic Compensation in Uncrewed Ground Vehicles}, + author = {Sun, Zhaolong and Zhang, Yiwen and Deng, Shengyao and Xiao, Liang and Liu, Xin and Zhang, Yang}, + year = {2026}, + journal = {IEEE Sensors Journal}, + doi = {10.1109/jsen.2026.3684910}, + note = {Ground-vehicle (not airborne UAV) application of the Tolles--Lawson + NN hybrid compensation approach} +} + +@article{xie2026, + title = {Aeromagnetic Nonlinear Interference Compensation Method Based on Hybrid LSTM and BP Neural Network Architecture}, + author = {Xie, Jihong}, + year = {2026}, + journal = {Computer Science and Application}, + doi = {10.12677/csa.2026.161011} +} + +@article{you2026, + title = {Electromagnetic interference compensation for aeromagnetic data using adaptive wavelet denoising and partial least squares regression}, + author = {You, Gaoyun and Li, Xinsan and Li, Ting and Li, Can and Shen, Qiang}, + year = {2026}, + journal = {Measurement Science and Technology}, + doi = {10.1088/1361-6501/ae8616} +}