Research-Stack/5-Applications/scripts/mathlib_ingestion_pipeline.py
allaun 475f6319ea chore(repo): push local 768-commit branch state onto clean remote baseline
This squashes all local history (768 commits) onto the scrubbed PR #90
baseline. Individual commits were lost during filter-repo corruption;
the working tree content is preserved intact.

Build: N/A (working tree state only)
2026-06-15 22:46:50 -05:00

402 lines
15 KiB
Python

#!/usr/bin/env python3
"""
Mathlib Ingestion Pipeline with Apache 2.0 Attribution
Extracts entire Mathlib corpus with proper attribution and stores in queryable database.
Enables swarm to "SWALLOW" Mathlib for self-improving knowledge absorption.
License: Apache 2.0
Attribution: Mathlib contributors, Lean Community
"""
import os
import json
import sqlite3
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import List, Dict, Optional
from datetime import datetime
import hashlib
from shim.utils.datetime_utils import utc_now
# ═══════════════════════════════════════════════════════════════════════════
# §0 Attribution Metadata (Apache 2.0 Compliance)
# ═══════════════════════════════════════════════════════════════════════════
MATHLIB_LICENSE = {
"name": "Apache License 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0",
"attribution_required": True,
"commercial_use_allowed": True,
"modification_allowed": True
}
MATHLIB_ATTRIBUTION = {
"source": "Mathlib4",
"repository": "https://github.com/leanprover-community/mathlib4",
"license": MATHLIB_LICENSE,
"contributors": "Lean Community, Mathlib Contributors",
"extracted_at": utc_now(),
"extraction_method": "lake build export"
}
@dataclass
class MathlibEntry:
"""Single Mathlib entry with full attribution."""
module_path: str
content: str
docstring: str
theorem_count: int
definition_count: int
authors: List[str]
dependencies: List[str]
hash: str
attribution: Dict[str, str]
def to_dict(self) -> Dict:
return asdict(self)
# ═══════════════════════════════════════════════════════════════════════════
# §1 Mathlib Extraction from Lake Build
# ═══════════════════════════════════════════════════════════════════════════
def extract_lean_file(file_path: Path) -> Optional[MathlibEntry]:
"""Extract content from a single Lean file with attribution."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Extract docstring (first /- ... -/ block)
docstring = ""
if "/-" in content:
start = content.find("/-")
end = content.find("-/", start)
if end != -1:
docstring = content[start+2:end].strip()
# Count theorems and definitions
theorem_count = content.count("theorem ") + content.count("lemma ")
definition_count = content.count("def ") + content.count("structure ")
# Extract imports/dependencies
dependencies = []
for line in content.split('\n'):
if line.strip().startswith("import ") or line.strip().startswith("open "):
dependencies.append(line.strip())
# Generate content hash for deduplication
content_hash = hashlib.sha256(content.encode()).hexdigest()
# Module path relative to mathlib
module_path = str(file_path.relative_to(file_path.parents[len(file_path.parts) - file_path.parts.index("Mathlib") - 1]))
return MathlibEntry(
module_path=module_path,
content=content,
docstring=docstring,
theorem_count=theorem_count,
definition_count=definition_count,
authors=[], # Would need to parse git history
dependencies=dependencies,
hash=content_hash,
attribution=MATHLIB_ATTRIBUTION
)
except Exception as e:
print(f"[ERROR] Failed to extract {file_path}: {e}")
return None
def extract_mathlib_corpus(mathlib_path: Path) -> List[MathlibEntry]:
"""Extract entire Mathlib corpus with attribution."""
entries = []
# Find all .lean files in Mathlib
for lean_file in mathlib_path.rglob("*.lean"):
entry = extract_lean_file(lean_file)
if entry:
entries.append(entry)
print(f"[OK] Extracted: {entry.module_path}")
print(f"[INFO] Extracted {len(entries)} Mathlib entries")
return entries
# ═══════════════════════════════════════════════════════════════════════════
# §2 Database Storage (JSON and SQLite)
# ═══════════════════════════════════════════════════════════════════════════
class MathlibDatabase:
"""Queryable database for Mathlib corpus with attribution."""
def __init__(self, db_path: Path, use_sqlite: bool = True):
self.db_path = db_path
self.use_sqlite = use_sqlite
self.entries: List[MathlibEntry] = []
def add_entry(self, entry: MathlibEntry):
"""Add an entry to the database."""
self.entries.append(entry)
def save_json(self):
"""Save database as JSON."""
json_path = self.db_path.with_suffix('.json')
data = {
"metadata": {
"license": MATHLIB_LICENSE,
"attribution": MATHLIB_ATTRIBUTION,
"entry_count": len(self.entries),
"saved_at": utc_now()
},
"entries": [entry.to_dict() for entry in self.entries]
}
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
print(f"[OK] Saved JSON database: {json_path}")
def save_sqlite(self):
"""Save database as SQLite."""
sqlite_path = self.db_path.with_suffix('.db')
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
# Create tables
cursor.execute('''
CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY,
value TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_path TEXT UNIQUE,
content TEXT,
docstring TEXT,
theorem_count INTEGER,
definition_count INTEGER,
authors TEXT,
dependencies TEXT,
hash TEXT,
attribution TEXT
)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_module_path ON entries(module_path)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_hash ON entries(hash)
''')
# Insert metadata
for key, value in MATHLIB_ATTRIBUTION.items():
cursor.execute('INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)',
(key, json.dumps(value) if isinstance(value, dict) else value))
# Insert entries
for entry in self.entries:
cursor.execute('''
INSERT OR REPLACE INTO entries
(module_path, content, docstring, theorem_count, definition_count, authors, dependencies, hash, attribution)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
entry.module_path,
entry.content,
entry.docstring,
entry.theorem_count,
entry.definition_count,
json.dumps(entry.authors),
json.dumps(entry.dependencies),
entry.hash,
json.dumps(entry.attribution)
))
conn.commit()
conn.close()
print(f"[OK] Saved SQLite database: {sqlite_path}")
def save(self):
"""Save database in chosen format."""
if self.use_sqlite:
self.save_sqlite()
else:
self.save_json()
# ═══════════════════════════════════════════════════════════════════════════
# §3 Query Interface for Swarm
# ═══════════════════════════════════════════════════════════════════════════
class MathlibQuery:
"""Query interface for Mathlib database."""
def __init__(self, db_path: Path, use_sqlite: bool = True):
self.db_path = db_path
self.use_sqlite = use_sqlite
def query_by_keyword(self, keyword: str, limit: int = 10) -> List[Dict]:
"""Query database by keyword."""
if self.use_sqlite:
return self._query_sqlite_keyword(keyword, limit)
else:
return self._query_json_keyword(keyword, limit)
def _query_sqlite_keyword(self, keyword: str, limit: int) -> List[Dict]:
"""Query SQLite database by keyword."""
sqlite_path = self.db_path.with_suffix('.db')
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
cursor.execute('''
SELECT module_path, content, docstring, theorem_count, definition_count, attribution
FROM entries
WHERE content LIKE ? OR docstring LIKE ?
LIMIT ?
''', (f'%{keyword}%', f'%{keyword}%', limit))
results = []
for row in cursor.fetchall():
results.append({
"module_path": row[0],
"content": row[1][:1000], # Limit content
"docstring": row[2],
"theorem_count": row[3],
"definition_count": row[4],
"attribution": json.loads(row[5])
})
conn.close()
return results
def _query_json_keyword(self, keyword: str, limit: int) -> List[Dict]:
"""Query JSON database by keyword."""
json_path = self.db_path.with_suffix('.json')
with open(json_path, 'r') as f:
data = json.load(f)
results = []
keyword_lower = keyword.lower()
for entry in data['entries']:
if (keyword_lower in entry['content'].lower() or
keyword_lower in entry['docstring'].lower()):
results.append({
"module_path": entry['module_path'],
"content": entry['content'][:1000],
"docstring": entry['docstring'],
"theorem_count": entry['theorem_count'],
"definition_count": entry['definition_count'],
"attribution": entry['attribution']
})
if len(results) >= limit:
break
return results
def query_by_module(self, module_path: str) -> Optional[Dict]:
"""Query database by module path."""
if self.use_sqlite:
return self._query_sqlite_module(module_path)
else:
return self._query_json_module(module_path)
def _query_sqlite_module(self, module_path: str) -> Optional[Dict]:
"""Query SQLite database by module path."""
sqlite_path = self.db_path.with_suffix('.db')
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
cursor.execute('''
SELECT module_path, content, docstring, theorem_count, definition_count, attribution
FROM entries
WHERE module_path = ?
''', (module_path,))
row = cursor.fetchone()
conn.close()
if row:
return {
"module_path": row[0],
"content": row[1],
"docstring": row[2],
"theorem_count": row[3],
"definition_count": row[4],
"attribution": json.loads(row[5])
}
return None
def _query_json_module(self, module_path: str) -> Optional[Dict]:
"""Query JSON database by module path."""
json_path = self.db_path.with_suffix('.json')
with open(json_path, 'r') as f:
data = json.load(f)
for entry in data['entries']:
if entry['module_path'] == module_path:
return entry
return None
# ═══════════════════════════════════════════════════════════════════════════
# §4 Main Execution
# ═══════════════════════════════════════════════════════════════════════════
def main():
mathlib_path = Path("0-Core-Formalism/lean/Semantics/.lake/packages/mathlib")
output_path = Path("shared-data/data/mathlib_database")
output_path.mkdir(parents=True, exist_ok=True)
print("[INFO] Starting Mathlib ingestion pipeline with Apache 2.0 attribution...")
print(f"[INFO] Mathlib path: {mathlib_path}")
print(f"[INFO] Output path: {output_path}")
# Check if Mathlib exists
if not mathlib_path.exists():
print(f"[ERROR] Mathlib not found at {mathlib_path}")
print("[INFO] Run 'lake update' to download Mathlib")
return
# Extract Mathlib corpus
entries = extract_mathlib_corpus(mathlib_path)
if not entries:
print("[ERROR] No Mathlib entries extracted")
return
# Save database
db = MathlibDatabase(output_path / "mathlib_corpus", use_sqlite=True)
for entry in entries:
db.add_entry(entry)
db.save()
# Test query
print("\n[INFO] Testing query interface...")
query = MathlibQuery(output_path / "mathlib_corpus", use_sqlite=True)
test_results = query.query_by_keyword("theorem", limit=3)
print(f"[OK] Query 'theorem' returned {len(test_results)} results")
for result in test_results:
print(f" - {result['module_path']}: {result['theorem_count']} theorems")
print("\n[OK] Mathlib ingestion pipeline complete")
print(f"[INFO] Total entries: {len(entries)}")
print(f"[INFO] Database: {output_path / 'mathlib_corpus.db'}")
if __name__ == "__main__":
main()