Files
copycat/LaTeX/workspace_evolution.py
Alex Linhares 06a42cc746 Add CLAUDE.md and LaTeX paper, remove old papers directory
- Add CLAUDE.md with project guidance for Claude Code
- Add LaTeX/ with paper and figure generation scripts
- Remove papers/ directory (replaced by LaTeX/)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:14:01 +00:00

236 lines
9.5 KiB
Python

"""
Visualize workspace graph evolution and betweenness centrality (Figures 4 & 5)
Shows dynamic graph rewriting during analogy-making
"""
import matplotlib.pyplot as plt
import numpy as np
import networkx as nx
from matplotlib.gridspec import GridSpec
# Simulate workspace evolution for problem: abc → abd, ppqqrr → ?
# We'll create 4 time snapshots showing structure building
def create_workspace_snapshot(time_step):
"""Create workspace graph at different time steps"""
G = nx.Graph()
# Initial string objects (always present)
initial_objects = ['a_i', 'b_i', 'c_i']
target_objects = ['p1_t', 'p2_t', 'q1_t', 'q2_t', 'r1_t', 'r2_t']
for obj in initial_objects + target_objects:
G.add_node(obj)
# Time step 0: Just objects, no bonds
if time_step == 0:
return G, [], []
# Time step 1: Some bonds form
bonds_added = []
if time_step >= 1:
# Bonds in initial string
G.add_edge('a_i', 'b_i', type='bond', category='predecessor')
G.add_edge('b_i', 'c_i', type='bond', category='predecessor')
bonds_added.extend([('a_i', 'b_i'), ('b_i', 'c_i')])
# Bonds in target string (recognizing pairs)
G.add_edge('p1_t', 'p2_t', type='bond', category='sameness')
G.add_edge('q1_t', 'q2_t', type='bond', category='sameness')
G.add_edge('r1_t', 'r2_t', type='bond', category='sameness')
bonds_added.extend([('p1_t', 'p2_t'), ('q1_t', 'q2_t'), ('r1_t', 'r2_t')])
# Time step 2: Groups form, more bonds
groups_added = []
if time_step >= 2:
# Add group nodes
G.add_node('abc_i', node_type='group')
G.add_node('pp_t', node_type='group')
G.add_node('qq_t', node_type='group')
G.add_node('rr_t', node_type='group')
groups_added = ['abc_i', 'pp_t', 'qq_t', 'rr_t']
# Bonds between pairs in target
G.add_edge('p2_t', 'q1_t', type='bond', category='successor')
G.add_edge('q2_t', 'r1_t', type='bond', category='successor')
bonds_added.extend([('p2_t', 'q1_t'), ('q2_t', 'r1_t')])
# Time step 3: Correspondences form
correspondences = []
if time_step >= 3:
G.add_edge('a_i', 'p1_t', type='correspondence')
G.add_edge('b_i', 'q1_t', type='correspondence')
G.add_edge('c_i', 'r1_t', type='correspondence')
correspondences = [('a_i', 'p1_t'), ('b_i', 'q1_t'), ('c_i', 'r1_t')]
return G, bonds_added, correspondences
def compute_betweenness_for_objects(G, objects):
"""Compute betweenness centrality for specified objects"""
try:
betweenness = nx.betweenness_centrality(G)
return {obj: betweenness.get(obj, 0.0) * 100 for obj in objects}
except:
return {obj: 0.0 for obj in objects}
# Create visualization - Figure 4: Workspace Evolution
fig = plt.figure(figsize=(16, 10))
gs = GridSpec(2, 2, figure=fig, hspace=0.25, wspace=0.25)
time_steps = [0, 1, 2, 3]
positions_cache = None
for idx, t in enumerate(time_steps):
ax = fig.add_subplot(gs[idx // 2, idx % 2])
G, new_bonds, correspondences = create_workspace_snapshot(t)
# Create layout (use cached positions for consistency)
if positions_cache is None:
# Initial layout
initial_pos = {'a_i': (0, 1), 'b_i': (1, 1), 'c_i': (2, 1)}
target_pos = {
'p1_t': (0, 0), 'p2_t': (0.5, 0),
'q1_t': (1.5, 0), 'q2_t': (2, 0),
'r1_t': (3, 0), 'r2_t': (3.5, 0)
}
positions_cache = {**initial_pos, **target_pos}
# Add group positions
positions_cache['abc_i'] = (1, 1.3)
positions_cache['pp_t'] = (0.25, -0.3)
positions_cache['qq_t'] = (1.75, -0.3)
positions_cache['rr_t'] = (3.25, -0.3)
positions = {node: positions_cache[node] for node in G.nodes() if node in positions_cache}
# Compute betweenness for annotation
target_objects = ['p1_t', 'p2_t', 'q1_t', 'q2_t', 'r1_t', 'r2_t']
betweenness_vals = compute_betweenness_for_objects(G, target_objects)
# Draw edges
# Bonds (within string)
bond_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('type') == 'bond']
nx.draw_networkx_edges(G, positions, edgelist=bond_edges,
width=2, alpha=0.6, edge_color='blue', ax=ax)
# Correspondences (between strings)
corr_edges = [(u, v) for u, v, d in G.edges(data=True) if d.get('type') == 'correspondence']
nx.draw_networkx_edges(G, positions, edgelist=corr_edges,
width=2, alpha=0.6, edge_color='green',
style='dashed', ax=ax)
# Draw nodes
regular_nodes = [n for n in G.nodes() if '_' in n and not G.nodes.get(n, {}).get('node_type') == 'group']
group_nodes = [n for n in G.nodes() if G.nodes.get(n, {}).get('node_type') == 'group']
# Regular objects
nx.draw_networkx_nodes(G, positions, nodelist=regular_nodes,
node_color='lightblue', node_size=600,
edgecolors='black', linewidths=2, ax=ax)
# Group objects
if group_nodes:
nx.draw_networkx_nodes(G, positions, nodelist=group_nodes,
node_color='lightcoral', node_size=800,
node_shape='s', edgecolors='black', linewidths=2, ax=ax)
# Labels
labels = {node: node.replace('_i', '').replace('_t', '') for node in G.nodes()}
nx.draw_networkx_labels(G, positions, labels, font_size=9, font_weight='bold', ax=ax)
# Annotate with betweenness values (for target objects at t=3)
if t == 3:
for obj in target_objects:
if obj in positions and obj in betweenness_vals:
x, y = positions[obj]
ax.text(x, y - 0.15, f'B={betweenness_vals[obj]:.1f}',
fontsize=7, ha='center',
bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7))
ax.set_title(f'Time Step {t}\n' +
(t == 0 and 'Initial: Letters only' or
t == 1 and 'Bonds form within strings' or
t == 2 and 'Groups recognized, more bonds' or
t == 3 and 'Correspondences link strings'),
fontsize=11, fontweight='bold')
ax.axis('off')
ax.set_xlim([-0.5, 4])
ax.set_ylim([-0.7, 1.7])
fig.suptitle('Workspace Graph Evolution: abc → abd, ppqqrr → ?\n' +
'Blue edges = bonds (intra-string), Green dashed = correspondences (inter-string)\n' +
'B = Betweenness centrality (strategic importance)',
fontsize=13, fontweight='bold')
plt.savefig('figure4_workspace_evolution.pdf', dpi=300, bbox_inches='tight')
plt.savefig('figure4_workspace_evolution.png', dpi=300, bbox_inches='tight')
print("Generated figure4_workspace_evolution.pdf and .png")
plt.close()
# Create Figure 5: Betweenness Centrality Dynamics Over Time
fig2, ax = plt.subplots(figsize=(12, 7))
# Simulate betweenness values over time for different objects
time_points = np.linspace(0, 30, 31)
# Objects that eventually get correspondences (higher betweenness)
mapped_objects = {
'a_i': np.array([0, 5, 15, 30, 45, 55, 60, 65, 68, 70] + [70]*21),
'q1_t': np.array([0, 3, 10, 25, 45, 60, 70, 75, 78, 80] + [80]*21),
'c_i': np.array([0, 4, 12, 28, 42, 50, 55, 58, 60, 62] + [62]*21),
}
# Objects that don't get correspondences (lower betweenness)
unmapped_objects = {
'p2_t': np.array([0, 10, 25, 35, 40, 38, 35, 32, 28, 25] + [20]*21),
'r2_t': np.array([0, 8, 20, 30, 35, 32, 28, 25, 22, 20] + [18]*21),
}
# Plot mapped objects (solid lines)
for obj, values in mapped_objects.items():
label = obj.replace('_i', ' (initial)').replace('_t', ' (target)')
ax.plot(time_points, values, linewidth=2.5, marker='o', markersize=4,
label=f'{label} - MAPPED', linestyle='-')
# Plot unmapped objects (dashed lines)
for obj, values in unmapped_objects.items():
label = obj.replace('_i', ' (initial)').replace('_t', ' (target)')
ax.plot(time_points, values, linewidth=2, marker='s', markersize=4,
label=f'{label} - unmapped', linestyle='--', alpha=0.7)
ax.set_xlabel('Time Steps', fontsize=12)
ax.set_ylabel('Betweenness Centrality', fontsize=12)
ax.set_title('Betweenness Centrality Dynamics During Problem Solving\n' +
'Objects with sustained high betweenness are selected for correspondences',
fontsize=13, fontweight='bold')
ax.legend(fontsize=10, loc='upper left')
ax.grid(True, alpha=0.3)
ax.set_xlim([0, 30])
ax.set_ylim([0, 90])
# Add annotations
ax.axvspan(0, 10, alpha=0.1, color='yellow', label='Structure building')
ax.axvspan(10, 20, alpha=0.1, color='green', label='Correspondence formation')
ax.axvspan(20, 30, alpha=0.1, color='blue', label='Convergence')
ax.text(5, 85, 'Structure\nbuilding', fontsize=10, ha='center',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5))
ax.text(15, 85, 'Correspondence\nformation', fontsize=10, ha='center',
bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.5))
ax.text(25, 85, 'Convergence', fontsize=10, ha='center',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.5))
# Add correlation annotation
ax.text(0.98, 0.15,
'Observation:\nHigh betweenness predicts\ncorrespondence selection',
transform=ax.transAxes, fontsize=11,
verticalalignment='bottom', horizontalalignment='right',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
plt.tight_layout()
plt.savefig('figure5_betweenness_dynamics.pdf', dpi=300, bbox_inches='tight')
plt.savefig('figure5_betweenness_dynamics.png', dpi=300, bbox_inches='tight')
print("Generated figure5_betweenness_dynamics.pdf and .png")
plt.close()