Analysis shows no significant correlation between conceptual depth and hop distance to letter nodes (r=0.281, p=0.113). Includes Python scripts, visualizations, and LaTeX paper. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
158 lines
5.4 KiB
Python
158 lines
5.4 KiB
Python
"""
|
|
Compute minimum hops from each node to the nearest letter node (a-z) in the slipnet.
|
|
Like Erdos number, but starting from letter nodes.
|
|
Adds 'minPathToLetter' field to each node in the JSON.
|
|
"""
|
|
|
|
import json
|
|
import networkx as nx
|
|
|
|
def load_slipnet(filepath):
|
|
with open(filepath, 'r') as f:
|
|
return json.load(f)
|
|
|
|
def save_slipnet(data, filepath):
|
|
with open(filepath, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
def build_graph(data):
|
|
"""Build an undirected unweighted NetworkX graph from slipnet JSON."""
|
|
G = nx.Graph() # Undirected graph - edges work both ways
|
|
|
|
# Add all nodes
|
|
for node in data['nodes']:
|
|
G.add_node(node['name'])
|
|
|
|
# Add edges (unweighted - each edge counts as 1 hop)
|
|
for link in data['links']:
|
|
G.add_edge(link['source'], link['destination'])
|
|
|
|
return G
|
|
|
|
def get_letter_nodes():
|
|
"""Return set of letter nodes (a-z)."""
|
|
return set(chr(i) for i in range(ord('a'), ord('z') + 1))
|
|
|
|
def compute_min_hops_to_letters(G, letter_nodes):
|
|
"""
|
|
Compute minimum hops from each node to the nearest letter node.
|
|
Like Erdos number but for letters.
|
|
Returns dict: node_name -> {hops, path, nearest_letter}
|
|
"""
|
|
results = {}
|
|
|
|
# For each node, find shortest path (by hop count) to any letter
|
|
for node in G.nodes():
|
|
if node in letter_nodes:
|
|
# Letters have 0 hops to themselves
|
|
results[node] = {
|
|
'hops': 0,
|
|
'path': [node],
|
|
'nearestLetter': node
|
|
}
|
|
else:
|
|
min_hops = float('inf')
|
|
min_path = None
|
|
nearest_letter = None
|
|
|
|
for letter in letter_nodes:
|
|
try:
|
|
# Find shortest path by hop count (no weight parameter)
|
|
path = nx.shortest_path(G, source=node, target=letter)
|
|
hops = len(path) - 1 # Number of edges = nodes - 1
|
|
if hops < min_hops:
|
|
min_hops = hops
|
|
min_path = path
|
|
nearest_letter = letter
|
|
except nx.NetworkXNoPath:
|
|
continue
|
|
|
|
if min_path is not None:
|
|
results[node] = {
|
|
'hops': min_hops,
|
|
'path': min_path,
|
|
'nearestLetter': nearest_letter
|
|
}
|
|
else:
|
|
results[node] = {
|
|
'hops': None,
|
|
'path': None,
|
|
'nearestLetter': None
|
|
}
|
|
|
|
return results
|
|
|
|
def main():
|
|
filepath = r'C:\Users\alexa\copycat\slipnet_analysis\slipnet.json'
|
|
|
|
# Load the slipnet
|
|
data = load_slipnet(filepath)
|
|
print(f"Loaded slipnet with {data['nodeCount']} nodes and {data['linkCount']} links")
|
|
|
|
# Build the graph
|
|
G = build_graph(data)
|
|
print(f"Built graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges")
|
|
|
|
# Get letter nodes
|
|
letter_nodes = get_letter_nodes()
|
|
print(f"Letter nodes: {sorted(letter_nodes)}")
|
|
|
|
# Compute minimum hops to letters
|
|
path_results = compute_min_hops_to_letters(G, letter_nodes)
|
|
|
|
# Find max hops among reachable nodes
|
|
max_hops = max(r['hops'] for r in path_results.values() if r['hops'] is not None)
|
|
unreachable_hops = 2 * max_hops
|
|
print(f"Max hops among reachable nodes: {max_hops}")
|
|
print(f"Assigning unreachable nodes hops = 2 * {max_hops} = {unreachable_hops}")
|
|
|
|
# Assign unreachable nodes hops = 2 * max_hops
|
|
for node_name, result in path_results.items():
|
|
if result['hops'] is None:
|
|
result['hops'] = unreachable_hops
|
|
result['path'] = None # No path exists
|
|
result['nearestLetter'] = None
|
|
|
|
# Add results to each node in the JSON
|
|
for node in data['nodes']:
|
|
node_name = node['name']
|
|
if node_name in path_results:
|
|
result = path_results[node_name]
|
|
node['minPathToLetter'] = {
|
|
'hops': result['hops'],
|
|
'path': result['path'],
|
|
'nearestLetter': result['nearestLetter']
|
|
}
|
|
|
|
# Save the updated JSON
|
|
save_slipnet(data, filepath)
|
|
print(f"\nUpdated slipnet saved to {filepath}")
|
|
|
|
# Print summary sorted by hops
|
|
print("\n=== Summary of minimum hops to letter nodes (Erdos-style) ===")
|
|
reachable_nodes = [(n['name'], n['minPathToLetter']) for n in data['nodes']
|
|
if 'minPathToLetter' in n and n['minPathToLetter']['path'] is not None
|
|
and n['minPathToLetter']['hops'] > 0]
|
|
unreachable_nodes = [(n['name'], n['minPathToLetter']) for n in data['nodes']
|
|
if 'minPathToLetter' in n and n['minPathToLetter']['path'] is None
|
|
and n['minPathToLetter']['hops'] > 0]
|
|
|
|
# Sort by hops, then by name
|
|
reachable_nodes.sort(key=lambda x: (x[1]['hops'], x[0]))
|
|
unreachable_nodes.sort(key=lambda x: x[0])
|
|
|
|
print(f"\n{'Node':<30} {'Hops':<6} {'Nearest':<8} {'Path'}")
|
|
print("-" * 80)
|
|
|
|
for name, info in reachable_nodes:
|
|
path_str = ' -> '.join(info['path'])
|
|
print(f"{name:<30} {info['hops']:<6} {info['nearestLetter']:<8} {path_str}")
|
|
|
|
if unreachable_nodes:
|
|
print(f"\nUnreachable nodes (assigned hops = {unreachable_hops}):")
|
|
for name, info in unreachable_nodes:
|
|
print(f" {name:<30} (depth: {[n['conceptualDepth'] for n in data['nodes'] if n['name'] == name][0]})")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|