mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-30 18:56:16 +00:00
- 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.
147 lines
5.7 KiB
Python
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()
|