RiC-O — In-Memory Demo (NetworkX)
This notebook demonstrates how to model a RiC-O (Records in Contexts — Ontology) graph using cvcdocdb with the NetworkX in-memory backend.
Key concepts:
NetworkXGraphstores everything in memory with optional pickle persistenceSame CVCDocDB entity classes (
Thing,Agent,RecordResource, etc.) as Neo4jSame
load_ric_o_naf()loader downloads real RiC-O data from GitHubget_subdocuments()traverses_propagateedges (BFS)Cypher queries are supported via an in-memory executor
Persistence file is created fresh each run in
/tmp/
Steps:
Create an in-memory NetworkX graph with unique persistence path
Load real RiC-O data from the National Archives of France (NAF) example
Initialize propagation properties
Query subdocuments
Inspect the hierarchy
Summary statistics
Why NetworkX?
NetworkX is useful for:
Prototyping — no external database needed
Testing — fast, deterministic, easy to clean up
Learning — see the graph structure without Neo4j overhead
Persistence — pickle file survives notebook restarts (optional)
[ ]:
import importlib.util
# Check if cvcdocdb is installed
package_to_check = 'cvcdocdb'
spec = importlib.util.find_spec(package_to_check)
if spec is None:
print(f'⚠️ {package_to_check} not installed. Installing...')
%pip install -q --upgrade cvcdocdb
print("✅ Installation complete.")
else:
print(f'✅ {package_to_check} already installed. Skipping.')
[2]:
import os
import sys
import uuid
from pathlib import Path
# Read .env file manually (no python-dotenv dependency)
def read_env(env_path):
"""Read a .env file and return a dict of key=value pairs."""
env_vars = {}
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
env_vars[key.strip()] = value.strip()
return env_vars
# Find .env in repo root
_env_path = Path.cwd() / ".env"
if not _env_path.exists():
for parent in Path.cwd().parents:
candidate = parent / ".env"
if candidate.exists():
_env_path = candidate
break
if _env_path.exists():
_env_vars = read_env(_env_path)
for key, value in _env_vars.items():
os.environ.setdefault(key, value)
print(f"✅ Loaded .env from {_env_path}")
print(f" Found {len(_env_vars)} variables")
else:
print("⚠️ .env file not found. NetworkX backend doesn't need credentials.")
# Add cvcdocdb package to path for development
_repo_root = Path.cwd()
if not (_repo_root / "cvcdocdb").exists():
for parent in Path.cwd().parents:
if (parent / "cvcdocdb").exists():
_repo_root = parent
break
# Add repo root to sys.path so local cvcdocdb loads before PyPI version
if str(_repo_root) not in sys.path:
sys.path.insert(0, str(_repo_root))
print(f"✅ Added {_repo_root} to sys.path")
from cvcdocdb.networkx_graph import NetworkXGraph
from cvcdocdb.rico_entities import (
Thing,
Agent, Person, CorporateBody, AgentName, Appellation,
Date, Place, PlaceName, Event, Activity,
RecordResource, Instantiation, Title,
Group, Name,
)
def section(title):
"""Print a section header."""
print(f"\n{'=' * 60}")
print(f" {title}")
print(f"{'=' * 60}")
def sub_section(title):
"""Print a subsection header."""
print(f"\n--- {title} ---")
print("✅ All imports successful.")
✅ Loaded .env from /Users/oriol/Desenvolupament/cvcdocdb/.env
Found 5 variables
✅ Added /Users/oriol/Desenvolupament/cvcdocdb to sys.path
✅ All imports successful.
Step 1: Create in-memory graph with unique persistence path
The NetworkX backend stores data in memory. A pickle file is used for persistence across notebook restarts. Each run creates a unique file using a UUID, so previous runs never interfere.
[3]:
import os
import tempfile
# Generate a unique persistence file for this run
_run_id = uuid.uuid4().hex[:8]
_persistence_file = os.path.join(
tempfile.gettempdir(),
f"ric_o_networkx_{_run_id}.pkl"
)
print(f"📁 Persistence file: {_persistence_file}")
print(f" (unique per run — safe to delete after)")
# Create the in-memory graph
graph = NetworkXGraph(persistence_path=_persistence_file)
print(f"\n✅ NetworkXGraph created.")
print(f" Nodes in memory: {len(graph.get_node_ids())}")
print(f" Edges in memory: {len(graph.get_edges())}")
print(f" Persistence enabled: {_persistence_file is not None}")
📁 Persistence file: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_5343112b.pkl
(unique per run — safe to delete after)
✅ NetworkXGraph created.
Nodes in memory: 0
Edges in memory: 0
Persistence enabled: True
Step 2: Load real RiC-O data from the National Archives of France
The cvcdocdb include a loader that downloads and parses RiC-O RDF/XML data from the ICA-EGAD/RiC-O repository.
What gets loaded:
RDF Type |
CVCDocDB Entity |
Parent |
|---|---|---|
|
|
— (strong node) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hierarchy example:
Thing (strong)
├── Agent (WeakNode)
│ ├── AgentName (WeakNode of Agent)
│ └── Instantiation (WeakNode of Agent)
└── RecordResource (WeakNode)
└── Instantiation (WeakNode of RecordResource)
The loader downloads files from GitHub, parses the RDF/XML, and inserts entities into the graph with proper parent-child relationships.
[4]:
from cvcdocdb.exemples import load_ric_o_naf
section("Loading RiC-O data from National Archives of France")
# Load real RiC-O data
# limit: total entities to load (soft limit)
# max_agents: number of agent files to load
# max_records: number of record resource files to load
stats = load_ric_o_naf(
graph,
limit=50,
max_agents=10,
max_records=3,
)
print(f"\n📊 Load statistics:")
print(f" Agents loaded: {stats['agents']}")
print(f" Records loaded: {stats['records']}")
print(f" Things created: {stats['things']}")
print(f" Child entities: {stats.get('children', '?')}")
print(f" Total entities: {stats['total']}")
============================================================
Loading RiC-O data from National Archives of France
============================================================
📊 Load statistics:
Agents loaded: 10
Records loaded: 27
Things created: 40
Child entities: 75
Total entities: 152
Step 3: Initialize propagation properties
Scan the graph and initialize _propagate flags on edges between parent and child nodes. This enables cascade delete behavior.
In NetworkX, this is much faster than Neo4j since everything is in memory.
[5]:
# Check state before init (using the public get_node_attrs()/get_node_ids() API)
sub_section("Before init_propagation()")
attrs_before = [graph.get_node_attrs(nid) for nid in graph.get_node_ids()]
total_before = len(attrs_before)
weak_before = sum(1 for a in attrs_before if a.get('is_weak'))
done_before = sum(1 for a in attrs_before if a.get('_weak_init_done'))
print(f" Total nodes: {total_before}")
print(f" Nodes with is_weak: {weak_before}")
print(f" Nodes with _weak_init_done: {done_before}")
# Run initialization
print("\nRunning init_propagation()...")
init_result = graph.init_propagation()
print(f"Result: {init_result}")
# Check state after init
sub_section("After init_propagation()")
attrs_after = [graph.get_node_attrs(nid) for nid in graph.get_node_ids()]
total_after = len(attrs_after)
weak_after = sum(1 for a in attrs_after if a.get('is_weak'))
done_after = sum(1 for a in attrs_after if a.get('_weak_init_done'))
print(f" Total nodes: {total_after}")
print(f" Nodes with is_weak: {weak_after}")
print(f" Nodes with _weak_init_done: {done_after}")
# Count propagation edges
prop_count = sum(
1 for u, v, rel_type in graph.get_edges()
if (graph.get_edge_attrs(u, v, rel_type) or {}).get('_propagate') is True
)
print(f" Edges with _propagate: {prop_count}")
--- Before init_propagation() ---
Total nodes: 853
Nodes with is_weak: 0
Nodes with _weak_init_done: 0
Running init_propagation()...
Result: True
--- After init_propagation() ---
Total nodes: 853
Nodes with is_weak: 0
Nodes with _weak_init_done: 0
Edges with _propagate: 0
Step 4: Inspect the loaded graph structure
Query the graph to see what entities were loaded and how they are organized. We use the public get_node_ids()/get_node_attrs()/get_edges()/get_edge_attrs() accessors for inspection — the same public API used by the Neo4j-backed ric_o_demo.ipynb twin (there via Cypher query()).
[6]:
# Show all Things (strong nodes)
sub_section("Nodes by label")
from collections import Counter
label_counts = Counter(
graph.get_node_attrs(nid).get('main_label', 'Node') for nid in graph.get_node_ids()
)
for label, count in label_counts.most_common():
print(f" {label}: {count}")
# Show relations by type
sub_section("Relations by type")
rel_counts = Counter()
for u, v, rel_type in graph.get_edges():
edge_attrs = graph.get_edge_attrs(u, v, rel_type) or {}
label = 'HAS_*' if edge_attrs.get('_propagate') else rel_type
rel_counts[label] += 1
for rel_type, count in rel_counts.most_common():
print(f" {rel_type}: {count}")
--- Nodes by label ---
Instantiation: 404
RecordResource: 398
AgentName: 48
Record: 3
--- Relations by type ---
IS_INSTANTIATION_OF: 398
HAS_INSTANTIATION: 398
[7]:
# Show all Things (strong nodes)
sub_section("All Things (strong nodes)")
things = []
for nid in graph.get_node_ids():
attrs = graph.get_node_attrs(nid)
if attrs.get('main_label') == 'Thing':
identifier = attrs.get('identifier', '?')
name = attrs.get('title', identifier)
things.append((nid, identifier, name))
things.sort(key=lambda x: x[1])
for nid, identifier, name in things:
print(f" {identifier}: {name}")
--- All Things (strong nodes) ---
Step 5: Get subdocuments
Use get_subdocuments() to retrieve all weak descendants of each Thing. This traverses only edges with _propagate=True using BFS.
[8]:
# Show subdocuments for each Thing
for nid, identifier, name in things:
thing_node = Thing(pk={"identifier": identifier})
subdocs = graph.get_subdocuments(thing_node)
sub_section(f"Subdocuments of Thing({identifier})")
print(f" Found {len(subdocs)} subdocuments:")
# Group by label
by_label = {}
for sd in subdocs:
label = sd['label']
if label not in by_label:
by_label[label] = []
by_label[label].append(sd)
for label in sorted(by_label.keys()):
items = by_label[label]
for item in items[:5]: # Show max 5 per label
name = item['pk'].get('fullName', item['pk'].get('name', item['pk'].get('agentId', '?')))
print(f" - {label}: {name}")
if len(items) > 5:
print(f" ... and {len(items) - 5} more")
--- Subdocuments of Thing(record-000005) ---
Found 13 subdocuments:
- Agent: agent-agent-000005
- AgentName: France. Ministère de la Culture et de la Communication (1959-....)
- AgentName: Ministère de la Culture
- AgentName: Ministère de la Culture et de la Communication
- AgentName: Ministère de la Culture, de la Communication, des Grands Travaux et du Bicentenaire
- AgentName: Ministère de la Culture et de la Communication
... and 7 more
--- Subdocuments of Thing(record-000016) ---
Found 11 subdocuments:
- Agent: agent-agent-000016
- AgentName: France. Ministère de l'Éducation nationale (1828-....)
- AgentName: Ministère de l'Éducation nationale, de la Jeunesse et de la Vie associative
- AgentName: Ministère de l'Éducation nationale
- AgentName: Ministère de l'Éducation nationale, de l'Enseignement supérieur et de la Recherche
- AgentName: Ministère de la Jeunesse, de l'Éducation nationale et de la Recherche
... and 5 more
--- Subdocuments of Thing(record-000051) ---
Found 4 subdocuments:
- Agent: agent-agent-000051
- AgentName: France. Ministère des affaires culturelles. Direction de l'architecture (1959-1978)
- AgentName: France. Ministère de la culture et de l'environnement. Direction de l'architecture
- AgentName: France. Secrétariat d'État à la culture. Direction de l'architecture
--- Subdocuments of Thing(record-003500) ---
Found 0 subdocuments:
--- Subdocuments of Thing(record-003529) ---
Found 8 subdocuments:
- Agent: agent-agent-003529
- AgentName: France. Direction des bibliothèques et de la lecture publique. Bureau de la gestion et du contrôle financiers (1945-1975)
- AgentName: Bureau de la gestion et du contrôle financier
- AgentName: Deuxième bureau (Gestion et contrôle financier, Affaires générales-Documentation-Matériel)
- AgentName: Bureau de la gestion et du contrôle financiers
- AgentName: Bureau de la comptabilité
... and 2 more
--- Subdocuments of Thing(record-003530) ---
Found 4 subdocuments:
- Agent: agent-agent-003530
- AgentName: France. Direction des bibliothèques et de la lecture publique. Division des services administratifs. Bureau des affaires générales (1965-1975)
- AgentName: DB 3
- AgentName: BL 3
--- Subdocuments of Thing(record-003531) ---
Found 7 subdocuments:
- Agent: agent-agent-003531
- AgentName: France. Direction des bibliothèques et de la lecture publique. Bureau du personnel (1945-1975)
- AgentName: Bureau du personnel
- AgentName: Bureau du personnel et des affaires générales
- AgentName: Bureau du personnel
- AgentName: BL 1
... and 1 more
--- Subdocuments of Thing(record-003532) ---
Found 4 subdocuments:
- Agent: agent-agent-003532
- AgentName: France. Direction des bibliothèques et de la lecture publique. Division des affaires administratives (1965-1975)
- AgentName: Services administratifs
- AgentName: Division des affaires administratives
--- Subdocuments of Thing(record-003549) ---
Found 2 subdocuments:
- Agent: agent-agent-003549
- AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section des bibliothèques d'étude et de recherche (1971-1975)
--- Subdocuments of Thing(record-003550) ---
Found 2 subdocuments:
- Agent: agent-agent-003550
- AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section des affaires communes (1971-1975)
--- Subdocuments of Thing(record-003551) ---
Found 3 subdocuments:
- Agent: agent-agent-003551
- AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section de la lecture publique (1968-1975)
- AgentName: Service de la lecture publique
--- Subdocuments of Thing(record-007375) ---
Found 0 subdocuments:
--- Subdocuments of Thing(record-009555) ---
Found 0 subdocuments:
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_1) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_2) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_3) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_4) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-007375-d_1) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-007375-d_2) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_1) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_10) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_11) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_12) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_13) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_14) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_15) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_16) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_17) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_18) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_2) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_3) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_4) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_5) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_6) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_7) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_8) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_9) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-003500) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-007375) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-009555) ---
Found 2 subdocuments:
- Instantiation: ?
- RecordResource: ?
Step 6: Query the hierarchy paths
Visualize the full hierarchy for a sample Thing by walking the graph via the public get_edges()/get_edge_attrs()/get_node_attrs() API.
[9]:
def _walk_hierarchy(graph, start_id, max_depth=6):
"""Walk the graph from start_id and return all reachable nodes."""
all_edges = graph.get_edges()
paths = []
queue = [(start_id, [start_id], [])]
while queue:
current, path_nodes, path_edges = queue.pop(0)
if len(path_nodes) > 1:
paths.append((path_nodes, path_edges))
if len(path_nodes) >= max_depth:
continue
for u, v, rel_type in all_edges:
if u != current:
continue
edge_attrs = graph.get_edge_attrs(u, v, rel_type) or {}
display_rel = 'parent' if edge_attrs.get('_propagate') else rel_type
paths.append((path_nodes + [v], path_edges + [(u, v, display_rel)]))
if len(path_nodes) < max_depth - 1:
queue.append((v, path_nodes + [v], path_edges + [(u, v, display_rel)]))
return paths
# Walk hierarchy for first Thing
if things:
first_nid, first_id, first_name = things[0]
sub_section(f"Hierarchy: Thing({first_id})")
paths = _walk_hierarchy(graph, first_nid)
for path_nodes, path_edges in paths:
parts = []
for node_id in path_nodes:
attrs = graph.get_node_attrs(node_id) or {}
label = attrs.get('main_label', 'Node')
name = attrs.get('fullName', attrs.get('name', attrs.get('agentId', attrs.get('recordResourceId', str(node_id)))))
parts.append(f"{label}({name})")
for src, dst, rel_type in path_edges:
parts.append(f"-[{rel_type}]->")
print(f" {' → '.join(parts)}")
Step 7: Inspect specific entities
Look at the properties of loaded entities to understand the data model.
[10]:
# Show sample Agent entities
sub_section("Sample Agent entities")
agents = []
for nid in graph.get_node_ids():
attrs = graph.get_node_attrs(nid)
if attrs.get('main_label') in ('Agent', 'CorporateBody', 'Person', 'Family'):
agents.append((
nid, attrs.get('agentId', '?'), attrs.get('label', 'No label'),
attrs.get('beginningDate', ''), attrs.get('endDate', ''),
))
agents.sort(key=lambda x: x[1])
for nid, agent_id, label, birth, death in agents[:5]:
print(f" {agent_id}: {label}")
print(f" Born: {birth or 'N/A'}, Died: {death or 'N/A'}")
# Show sample RecordResource entities
sub_section("Sample RecordResource entities")
record_resources = []
for nid in graph.get_node_ids():
attrs = graph.get_node_attrs(nid)
if attrs.get('main_label') == 'RecordResource':
record_resources.append((
nid, attrs.get('recordResourceId', '?'), attrs.get('title', ''),
attrs.get('beginningDate', ''), attrs.get('endDate', ''),
))
record_resources.sort(key=lambda x: x[1])
for nid, rr_id, title, from_date, to_date in record_resources[:5]:
print(f" {rr_id}: {title or 'No title'}")
print(f" Date range: {from_date or 'N/A'} — {to_date or 'N/A'}")
--- Sample Agent entities ---
--- Sample RecordResource entities ---
?: No title
Date range: N/A — N/A
?: No title
Date range: N/A — N/A
?: No title
Date range: N/A — N/A
?: No title
Date range: N/A — N/A
?: No title
Date range: N/A — N/A
Step 8: Summary statistics
Show the final state of the RiC-O graph.
[11]:
section("Final summary")
# Nodes by label
sub_section("Nodes by label")
label_counts = Counter(
graph.get_node_attrs(nid).get('main_label', 'Node') for nid in graph.get_node_ids()
)
for label, count in label_counts.most_common():
print(f" {label}: {count}")
# Propagation edges
sub_section("Propagation edges by type")
prop_rel_counts = Counter()
for u, v, rel_type in graph.get_edges():
edge_attrs = graph.get_edge_attrs(u, v, rel_type) or {}
if edge_attrs.get('_propagate') is True:
prop_rel_counts[rel_type] += 1
for rel_type, count in prop_rel_counts.most_common():
print(f" {rel_type}: {count}")
# All Things
sub_section("All Things")
for nid, identifier, name in things:
print(f" {identifier}: {name}")
# Subdocument counts
sub_section("Subdocuments per Thing")
for nid, identifier, name in things:
thing_node = Thing(pk={"identifier": identifier})
subdocs = graph.get_subdocuments(thing_node)
print(f" {identifier}: {len(subdocs)} subdocuments")
# Persistence file info
sub_section("Persistence file")
print(f" Path: {_persistence_file}")
if os.path.exists(_persistence_file):
size = os.path.getsize(_persistence_file)
print(f" Size: {size:,} bytes ({size / 1024:.1f} KB)")
else:
print(" File not found (data only in memory)")
print("\n" + "=" * 60)
print(" ✅ RiC-O NetworkX demo completed successfully!")
print("=" * 60)
============================================================
Final summary
============================================================
--- Nodes by label ---
Instantiation: 404
RecordResource: 398
AgentName: 48
Record: 3
--- Propagation edges by type ---
--- All Things ---
--- Subdocuments per Thing ---
--- Persistence file ---
Path: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_5343112b.pkl
Size: 132,590 bytes (129.5 KB)
============================================================
✅ RiC-O NetworkX demo completed successfully!
============================================================
Cleanup
Close the graph and optionally delete the persistence file.
[12]:
graph.close()
print("✅ NetworkXGraph closed.")
# Clean up persistence file
if os.path.exists(_persistence_file):
os.remove(_persistence_file)
print(f"✅ Persistence file deleted: {_persistence_file}")
else:
print(f"ℹ️ No persistence file to clean up")
✅ NetworkXGraph closed.
✅ Persistence file deleted: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_e073bd7a.pkl