Research-Stack/scripts/load_complete_graph.py
allaun 6e9f887a02 feat(data): pyrochlore receipt v2 and complete graph loader
- pyrochlore_sidon_receipt_v2.json: current S=1 receipt for pyrochlore
  Sidon bridge.
- scripts/load_complete_graph.py: loader script for complete interaction
  graph data.
2026-06-21 01:04:19 -05:00

147 lines
5.7 KiB
Python

#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "gremlinpython",
# "python-dotenv",
# ]
# ///
"""
load_complete_graph.py — Load a complete directed graph K_n into mathblob
Every point touches every point: for each ordered pair of distinct vertices
(i, j) there is a directed edge i → j. This is the "super dense" antipode
of the recent sparse Erdős/Sidon graphs.
Run with: uv run scripts/load_complete_graph.py [--n N] [--label LABEL]
Requires: .env.gremlin
"""
import os
import argparse
import time
from pathlib import Path
from dotenv import load_dotenv
from gremlin_python.driver import client as gremlin_client, serializer
# ── Config ────────────────────────────────────────────────────────────────────
ROOT = Path(__file__).parent.parent
ENV_FILE = ROOT / ".env.gremlin"
load_dotenv(ENV_FILE)
ENDPOINT = os.environ["GREMLIN_ENDPOINT"]
USERNAME = os.environ["GREMLIN_USERNAME"]
PASSWORD = os.environ["GREMLIN_PASSWORD"]
BATCH_SIZE = 50
# ── Gremlin helpers ───────────────────────────────────────────────────────────
def make_client():
return gremlin_client.Client(
ENDPOINT, "g",
username=USERNAME,
password=PASSWORD,
message_serializer=serializer.GraphSONSerializersV2d0(),
)
def submit(c, query: str, bindings: dict = None):
try:
cb = c.submitAsync(query, bindings or {})
return cb.result().all().result()
except Exception as e:
print(f" ERR: {e!s:.120}")
return None
def _safe_id(raw: str) -> str:
"""Cosmos DB element IDs cannot contain '/', '\', '|', or spaces."""
return raw.replace("/", "_").replace("\\", "_").replace("|", "_").replace(" ", "_")
# ── Vertex / edge upserts ─────────────────────────────────────────────────────
def upsert_vertex(c, vid: str, vlabel: str, graph_label: str):
q = (
f"g.V().has('{vlabel}','id',vid).fold()"
f".coalesce(unfold(),"
f"addV('{vlabel}').property('id',vid).property('pk',vid).property('graph',graph_label))"
f".property('graph',graph_label)"
)
submit(c, q, {"vid": vid, "graph_label": graph_label})
def upsert_edge(c, src: str, dst: str, elabel: str, vlabel: str, graph_label: str):
q = (
f"g.V().has('{vlabel}','id',src).as('a')"
f".V().has('{vlabel}','id',dst).as('b')"
f".coalesce(__.select('a').outE('{elabel}').where(__.inV().as('b')),"
f"__.select('a').addE('{elabel}').to('b').property('graph',graph_label))"
f".property('graph',graph_label)"
)
submit(c, q, {"src": src, "dst": dst, "graph_label": graph_label})
# ── Complete graph generation ─────────────────────────────────────────────────
def generate_complete_graph(n: int, graph_label: str):
vertices = []
for i in range(n):
vid = _safe_id(f"{graph_label}:node:{i}")
vertices.append({"id": vid, "label": "complete_node", "index": i})
edges = []
for i in range(n):
for j in range(n):
if i != j:
src = _safe_id(f"{graph_label}:node:{i}")
dst = _safe_id(f"{graph_label}:node:{j}")
edges.append((src, dst, "touches"))
return vertices, edges
# ── Load ──────────────────────────────────────────────────────────────────────
def load_complete_graph(n: int, graph_label: str):
vertices, edges = generate_complete_graph(n, graph_label)
print(f"K_{n}: {len(vertices)} vertices, {len(edges)} edges")
c = make_client()
try:
print("Upserting vertices...")
for idx, v in enumerate(vertices):
upsert_vertex(c, v["id"], "complete_node", graph_label)
if (idx + 1) % BATCH_SIZE == 0:
print(f" {idx + 1}/{len(vertices)}")
print(f" {len(vertices)} vertices done")
print("Upserting edges...")
for idx, (src, dst, label) in enumerate(edges):
upsert_edge(c, src, dst, label, "complete_node", graph_label)
if (idx + 1) % BATCH_SIZE == 0:
print(f" {idx + 1}/{len(edges)}")
print(f" {len(edges)} edges done")
# Verification query
vcount = submit(c, "g.V().has('graph',graph_label).count()", {"graph_label": graph_label})
ecount = submit(c, "g.E().has('graph',graph_label).count()", {"graph_label": graph_label})
print(f"Loaded: {vcount} vertices, {ecount} edges")
finally:
c.close()
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="Load a complete directed graph K_n into mathblob")
parser.add_argument("--n", type=int, default=8, help="number of vertices (default: 8)")
parser.add_argument("--label", type=str, default="complete_graph_k8", help="graph label/tag")
args = parser.parse_args()
if args.n < 2:
raise ValueError("n must be at least 2")
start = time.time()
load_complete_graph(args.n, args.label)
elapsed = time.time() - start
print(f"Finished in {elapsed:.1f}s")
if __name__ == "__main__":
main()