summrs commited on
Commit
923982b
Β·
verified Β·
1 Parent(s): fad7d95

import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from scipy.sparse.linalg import eigsh
from scipy.sparse import csgraph
import ast

# Helper Functions
def parse_graph_input(graph_input):
"""Parse user input to create an adjacency list."""
try:
# Try interpreting as a dictionary (adjacency list)
graph = ast.literal_eval(graph_input)
if isinstance(graph, dict):
return graph
except:
pass

try:
# Try interpreting as an edge list
edges = ast.literal_eval(graph_input)
if not isinstance(edges, list):
raise ValueError("Invalid graph input. Please use an adjacency list or edge list.")

graph = {}
for u, v in edges:
graph.setdefault(u, []).append(v)
graph.setdefault(v, []).append(u)
return graph
except:
raise ValueError("Invalid graph input. Please use a valid adjacency list or edge list.")

def visualize_graph(graph):
"""Generate a visualization of the graph using a circular layout."""
if len(graph) > 50: # Skip visualization for large graphs
return None

plt.figure()
nodes = list(graph.keys())
edges = [(u, v) for u in graph for v in graph[u]]

pos = nx.circular_layout(nx.Graph(edges))
nx.draw(
nx.Graph(edges),
pos,
with_labels=True,
node_color='lightblue',
edge_color='gray',
node_size=500,
font_size=10
)
return plt.gcf()

def calculate_spectrum(matrix, k=6, which='LM'):
"""Calculate the largest k eigenvalues of a sparse matrix."""
eigenvalues, _ = eigsh(matrix, k=k, which=which)
return sorted(eigenvalues.real)

def spectral_isomorphism_test(graph1, graph2):
"""Perform spectral isomorphism test with step-by-step explanation."""
adj_matrix1 = nx.adjacency_matrix(nx.Graph(graph1))
adj_matrix2 = nx.adjacency_matrix(nx.Graph(graph2))
lap_matrix1 = nx.laplacian_matrix(nx.Graph(graph1))
lap_matrix2 = nx.laplacian_matrix(nx.Graph(graph2))

adj_spectrum1 = calculate_spectrum(adj_matrix1, k=min(6, len(graph1) - 1))
adj_spectrum2 = calculate_spectrum(adj_matrix2, k=min(6, len(graph2) - 1))
lap_spectrum1 = calculate_spectrum(lap_matrix1, k=min(6, len(graph1) - 1), which='SM')
lap_spectrum2 = calculate_spectrum(lap_matrix2, k=min(6, len(graph2) - 1), which='SM')

adj_spectrum1 = [round(float(x), 2) for x in adj_spectrum1]
adj_spectrum2 = [round(float(x), 2) for x in adj_spectrum2]
lap_spectrum1 = [round(float(x), 2) for x in lap_spectrum1]
lap_spectrum2 = [round(float(x), 2) for x in lap_spectrum2]

output = (
f"### **Spectral Isomorphism Test Results**\n\n"

f"#### **Step 1: Node and Edge Counts**\n"
f"- **Graph 1**: Nodes: {len(graph1)}, Edges: {sum(len(neighbors) for neighbors in graph1.values()) // 2}\n"
f"- **Graph 2**: Nodes: {len(graph2)}, Edges: {sum(len(neighbors) for neighbors in graph2.values()) // 2}\n\n"

f"#### **Step 2: Adjacency Spectra**\n"
f"- Graph 1: {adj_spectrum1}\n"
f"- Graph 2: {adj_spectrum2}\n"
f"- Are the adjacency spectra approximately equal? {'βœ… Yes' if np.allclose(adj_spectrum1, adj_spectrum2) else '❌ No'}\n\n"

f"#### **Step 3: Laplacian Spectra**\n"
f"- Graph 1: {lap_spectrum1}\n"
f"- Graph 2: {lap_spectrum2}\n"
f"- Are the Laplacian spectra approximately equal? {'βœ… Yes' if np.allclose(lap_spectrum1, lap_spectrum2) else '❌ No'}\n\n"

f"#### **Final Result**\n"
f"- Outcome: {'βœ… PASS' if np.allclose(adj_spectrum1, adj_spectrum2) and np.allclose(lap_spectrum1, lap_spectrum2) else '❌ FAIL'}\n"
f"- Conclusion: The graphs are {'isomorphic' if np.allclose(adj_spectrum1, adj_spectrum2) and np.allclose(lap_spectrum1, lap_spectrum2) else 'NOT isomorphic'}.\n"
)
return output

def check_graph_homomorphism(graph1, graph2, mapping):
"""Check if a mapping defines a graph homomorphism."""
result = []
for u, v in graph1.edges():
mapped_u, mapped_v = mapping.get(u), mapping.get(v)
if mapped_u is None or mapped_v is None:
result.append(f"Mapping is incomplete. Missing vertex {u} or {v}.")
continue
if (mapped_u, mapped_v) not in graph2.edges() and (mapped_v, mapped_u) not in graph2.edges():
result.append(f"Edge ({u}, {v}) in Graph 1 maps to ({mapped_u}, {mapped_v}) in Graph 2. Edge does NOT exist in Graph 2.")
else:
result.append(f"Edge ({u}, {v}) in Graph 1 maps to ({mapped_u}, {mapped_v}) in Graph 2. Edge exists in Graph 2.")

is_homomorphism = all(("exists" in line) for line in result)
final_result = (
f"**Final Result:** {'βœ… Mapping IS a Graph Homomorphism.' if is_homomorphism else '❌ Mapping IS NOT a Graph Homomorphism.'}\n"
f"Explanation: A graph homomorphism must preserve all adjacencies. If any edge fails to map correctly, the mapping is invalid."
)
return "\n".join(result) + "\n\n" + final_result

def demonstrate_matrix_representations(graph):
"""Display adjacency matrix, Laplacian matrix, and spectra."""
adj_matrix = nx.adjacency_matrix(nx.Graph(graph)).todense()
laplacian_matrix = nx.laplacian_matrix(nx.Graph(graph)).todense()
degree_matrix = np.diag([len(graph[v]) for v in graph])

adj_spectrum = calculate_spectrum(nx.adjacency_matrix(nx.Graph(graph)), k=min(6, len(graph) - 1))
lap_spectrum = calculate_spectrum(nx.laplacian_matrix(nx.Graph(graph)), k=min(6, len(graph) - 1), which='SM')

algebraic_connectivity = lap_spectrum[1] if len(lap_spectrum) > 1 else 0 # Second smallest eigenvalue

output = (
f"### **Matrix Representations and Spectra**\n\n"

f"#### **Adjacency Matrix**\n"
f"```\n{adj_matrix}\n```\n\n"

f"#### **Laplacian Matrix**\n"
f"```\n{laplacian_matrix}\n```\n\n"

f"#### **Degree Matrix**\n"
f"```\n{degree_matrix}\n```\n\n"

f"#### **Adjacency Spectrum**\n"
f"```{[round(x, 2) for x in adj_spectrum]}```\n\n"

f"#### **Laplacian Spectrum**\n"
f"```{[round(x, 2) for x in lap_spectrum]}```\n\n"

f"#### **Algebraic Connectivity**\n"
f"The second smallest eigenvalue (Algebraic Connectivity): {round(algebraic_connectivity, 2)}\n\n"

f"**Explanation:** These matrices and spectra provide insights into the graph's structure. Algebraic connectivity measures robustness."
)
return output

def process_inputs(graph1_input, graph2_input, question_type, mapping=None):
"""Process user inputs and perform the selected operation."""
# Parse graphs
graph1 = parse_graph_input(graph1_input)
graph2 = parse_graph_input(graph2_input)

# Determine operation based on question type
if question_type == "Spectral Isomorphism Test":
result = spectral_isomorphism_test(graph1, graph2)
elif question_type == "Graph Homomorphism Check":
if mapping is None:
result = "Error: Mapping is required for Graph Homomorphism Check."
else:
result = check_graph_homomorphism(nx.Graph(graph1), nx.Graph(graph2), eval(mapping))
elif question_type == "Matrix Representations and Spectra":
result = demonstrate_matrix_representations(graph1)
else:
result = "Unsupported question type."

# Visualize graphs
graph1_plot = visualize_graph(graph1)
graph2_plot = visualize_graph(graph2)

return graph1_plot, graph2_plot, result

# Gradio Interface
with gr.Blocks(title="Graph Theory Project") as demo:
gr.Markdown("# Graph Theory Project")
gr.Markdown("Analyze graphs using algebraic methods!")

with gr.Row():
graph1_input = gr.Textbox(label="Graph 1 Input (e.g., '{0: [1], 1: [0, 2], 2: [1]}' or edge list)")
graph2_input = gr.Textbox(label="Graph 2 Input (e.g., '{0: [1], 1: [0, 2], 2: [1]}' or edge list)")

question_type = gr.Dropdown(
choices=["Spectral Isomorphism Test", "Graph Homomorphism Check", "Matrix Representations and Spectra"],
label="Select Question Type"
)

mapping_input = gr.Textbox(label="Mapping (for Graph Homomorphism Check, e.g., '{0: 0, 1: 1, 2: 2}')", visible=False)

def toggle_mapping_visibility(question_type):
return {"visible": question_type == "Graph Homomorphism Check"}

question_type.change(toggle_mapping_visibility, inputs=question_type, outputs=mapping_input)

with gr.Row():
graph1_output = gr.Plot(label="Graph 1 Visualization")
graph2_output = gr.Plot(label="Graph 2 Visualization")

result_output = gr.Textbox(label="Results", lines=20)

submit_button = gr.Button("Run")
submit_button.click(
lambda g1, g2, qt, m: process_inputs(g1, g2, qt, m),
inputs=[graph1_input, graph2_input, question_type, mapping_input],
outputs=[graph1_output, graph2_output, result_output]
)

# Launch the app
demo.launch()

Files changed (1) hide show
  1. app.py +216 -0
app.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import networkx as nx
5
+ from scipy.sparse.linalg import eigsh
6
+ from scipy.sparse import csgraph
7
+ import ast
8
+
9
+ # Helper Functions
10
+ def parse_graph_input(graph_input):
11
+ """Parse user input to create an adjacency list."""
12
+ try:
13
+ # Try interpreting as a dictionary (adjacency list)
14
+ graph = ast.literal_eval(graph_input)
15
+ if isinstance(graph, dict):
16
+ return graph
17
+ except:
18
+ pass
19
+
20
+ try:
21
+ # Try interpreting as an edge list
22
+ edges = ast.literal_eval(graph_input)
23
+ if not isinstance(edges, list):
24
+ raise ValueError("Invalid graph input. Please use an adjacency list or edge list.")
25
+
26
+ graph = {}
27
+ for u, v in edges:
28
+ graph.setdefault(u, []).append(v)
29
+ graph.setdefault(v, []).append(u)
30
+ return graph
31
+ except:
32
+ raise ValueError("Invalid graph input. Please use a valid adjacency list or edge list.")
33
+
34
+ def visualize_graph(graph):
35
+ """Generate a visualization of the graph using a circular layout."""
36
+ if len(graph) > 50: # Skip visualization for large graphs
37
+ return None
38
+
39
+ plt.figure()
40
+ nodes = list(graph.keys())
41
+ edges = [(u, v) for u in graph for v in graph[u]]
42
+
43
+ pos = nx.circular_layout(nx.Graph(edges))
44
+ nx.draw(
45
+ nx.Graph(edges),
46
+ pos,
47
+ with_labels=True,
48
+ node_color='lightblue',
49
+ edge_color='gray',
50
+ node_size=500,
51
+ font_size=10
52
+ )
53
+ return plt.gcf()
54
+
55
+ def calculate_spectrum(matrix, k=6, which='LM'):
56
+ """Calculate the largest k eigenvalues of a sparse matrix."""
57
+ eigenvalues, _ = eigsh(matrix, k=k, which=which)
58
+ return sorted(eigenvalues.real)
59
+
60
+ def spectral_isomorphism_test(graph1, graph2):
61
+ """Perform spectral isomorphism test with step-by-step explanation."""
62
+ adj_matrix1 = nx.adjacency_matrix(nx.Graph(graph1))
63
+ adj_matrix2 = nx.adjacency_matrix(nx.Graph(graph2))
64
+ lap_matrix1 = nx.laplacian_matrix(nx.Graph(graph1))
65
+ lap_matrix2 = nx.laplacian_matrix(nx.Graph(graph2))
66
+
67
+ adj_spectrum1 = calculate_spectrum(adj_matrix1, k=min(6, len(graph1) - 1))
68
+ adj_spectrum2 = calculate_spectrum(adj_matrix2, k=min(6, len(graph2) - 1))
69
+ lap_spectrum1 = calculate_spectrum(lap_matrix1, k=min(6, len(graph1) - 1), which='SM')
70
+ lap_spectrum2 = calculate_spectrum(lap_matrix2, k=min(6, len(graph2) - 1), which='SM')
71
+
72
+ adj_spectrum1 = [round(float(x), 2) for x in adj_spectrum1]
73
+ adj_spectrum2 = [round(float(x), 2) for x in adj_spectrum2]
74
+ lap_spectrum1 = [round(float(x), 2) for x in lap_spectrum1]
75
+ lap_spectrum2 = [round(float(x), 2) for x in lap_spectrum2]
76
+
77
+ output = (
78
+ f"### **Spectral Isomorphism Test Results**\n\n"
79
+
80
+ f"#### **Step 1: Node and Edge Counts**\n"
81
+ f"- **Graph 1**: Nodes: {len(graph1)}, Edges: {sum(len(neighbors) for neighbors in graph1.values()) // 2}\n"
82
+ f"- **Graph 2**: Nodes: {len(graph2)}, Edges: {sum(len(neighbors) for neighbors in graph2.values()) // 2}\n\n"
83
+
84
+ f"#### **Step 2: Adjacency Spectra**\n"
85
+ f"- Graph 1: {adj_spectrum1}\n"
86
+ f"- Graph 2: {adj_spectrum2}\n"
87
+ f"- Are the adjacency spectra approximately equal? {'βœ… Yes' if np.allclose(adj_spectrum1, adj_spectrum2) else '❌ No'}\n\n"
88
+
89
+ f"#### **Step 3: Laplacian Spectra**\n"
90
+ f"- Graph 1: {lap_spectrum1}\n"
91
+ f"- Graph 2: {lap_spectrum2}\n"
92
+ f"- Are the Laplacian spectra approximately equal? {'βœ… Yes' if np.allclose(lap_spectrum1, lap_spectrum2) else '❌ No'}\n\n"
93
+
94
+ f"#### **Final Result**\n"
95
+ f"- Outcome: {'βœ… PASS' if np.allclose(adj_spectrum1, adj_spectrum2) and np.allclose(lap_spectrum1, lap_spectrum2) else '❌ FAIL'}\n"
96
+ f"- Conclusion: The graphs are {'isomorphic' if np.allclose(adj_spectrum1, adj_spectrum2) and np.allclose(lap_spectrum1, lap_spectrum2) else 'NOT isomorphic'}.\n"
97
+ )
98
+ return output
99
+
100
+ def check_graph_homomorphism(graph1, graph2, mapping):
101
+ """Check if a mapping defines a graph homomorphism."""
102
+ result = []
103
+ for u, v in graph1.edges():
104
+ mapped_u, mapped_v = mapping.get(u), mapping.get(v)
105
+ if mapped_u is None or mapped_v is None:
106
+ result.append(f"Mapping is incomplete. Missing vertex {u} or {v}.")
107
+ continue
108
+ if (mapped_u, mapped_v) not in graph2.edges() and (mapped_v, mapped_u) not in graph2.edges():
109
+ result.append(f"Edge ({u}, {v}) in Graph 1 maps to ({mapped_u}, {mapped_v}) in Graph 2. Edge does NOT exist in Graph 2.")
110
+ else:
111
+ result.append(f"Edge ({u}, {v}) in Graph 1 maps to ({mapped_u}, {mapped_v}) in Graph 2. Edge exists in Graph 2.")
112
+
113
+ is_homomorphism = all(("exists" in line) for line in result)
114
+ final_result = (
115
+ f"**Final Result:** {'βœ… Mapping IS a Graph Homomorphism.' if is_homomorphism else '❌ Mapping IS NOT a Graph Homomorphism.'}\n"
116
+ f"Explanation: A graph homomorphism must preserve all adjacencies. If any edge fails to map correctly, the mapping is invalid."
117
+ )
118
+ return "\n".join(result) + "\n\n" + final_result
119
+
120
+ def demonstrate_matrix_representations(graph):
121
+ """Display adjacency matrix, Laplacian matrix, and spectra."""
122
+ adj_matrix = nx.adjacency_matrix(nx.Graph(graph)).todense()
123
+ laplacian_matrix = nx.laplacian_matrix(nx.Graph(graph)).todense()
124
+ degree_matrix = np.diag([len(graph[v]) for v in graph])
125
+
126
+ adj_spectrum = calculate_spectrum(nx.adjacency_matrix(nx.Graph(graph)), k=min(6, len(graph) - 1))
127
+ lap_spectrum = calculate_spectrum(nx.laplacian_matrix(nx.Graph(graph)), k=min(6, len(graph) - 1), which='SM')
128
+
129
+ algebraic_connectivity = lap_spectrum[1] if len(lap_spectrum) > 1 else 0 # Second smallest eigenvalue
130
+
131
+ output = (
132
+ f"### **Matrix Representations and Spectra**\n\n"
133
+
134
+ f"#### **Adjacency Matrix**\n"
135
+ f"```\n{adj_matrix}\n```\n\n"
136
+
137
+ f"#### **Laplacian Matrix**\n"
138
+ f"```\n{laplacian_matrix}\n```\n\n"
139
+
140
+ f"#### **Degree Matrix**\n"
141
+ f"```\n{degree_matrix}\n```\n\n"
142
+
143
+ f"#### **Adjacency Spectrum**\n"
144
+ f"```{[round(x, 2) for x in adj_spectrum]}```\n\n"
145
+
146
+ f"#### **Laplacian Spectrum**\n"
147
+ f"```{[round(x, 2) for x in lap_spectrum]}```\n\n"
148
+
149
+ f"#### **Algebraic Connectivity**\n"
150
+ f"The second smallest eigenvalue (Algebraic Connectivity): {round(algebraic_connectivity, 2)}\n\n"
151
+
152
+ f"**Explanation:** These matrices and spectra provide insights into the graph's structure. Algebraic connectivity measures robustness."
153
+ )
154
+ return output
155
+
156
+ def process_inputs(graph1_input, graph2_input, question_type, mapping=None):
157
+ """Process user inputs and perform the selected operation."""
158
+ # Parse graphs
159
+ graph1 = parse_graph_input(graph1_input)
160
+ graph2 = parse_graph_input(graph2_input)
161
+
162
+ # Determine operation based on question type
163
+ if question_type == "Spectral Isomorphism Test":
164
+ result = spectral_isomorphism_test(graph1, graph2)
165
+ elif question_type == "Graph Homomorphism Check":
166
+ if mapping is None:
167
+ result = "Error: Mapping is required for Graph Homomorphism Check."
168
+ else:
169
+ result = check_graph_homomorphism(nx.Graph(graph1), nx.Graph(graph2), eval(mapping))
170
+ elif question_type == "Matrix Representations and Spectra":
171
+ result = demonstrate_matrix_representations(graph1)
172
+ else:
173
+ result = "Unsupported question type."
174
+
175
+ # Visualize graphs
176
+ graph1_plot = visualize_graph(graph1)
177
+ graph2_plot = visualize_graph(graph2)
178
+
179
+ return graph1_plot, graph2_plot, result
180
+
181
+ # Gradio Interface
182
+ with gr.Blocks(title="Graph Theory Project") as demo:
183
+ gr.Markdown("# Graph Theory Project")
184
+ gr.Markdown("Analyze graphs using algebraic methods!")
185
+
186
+ with gr.Row():
187
+ graph1_input = gr.Textbox(label="Graph 1 Input (e.g., '{0: [1], 1: [0, 2], 2: [1]}' or edge list)")
188
+ graph2_input = gr.Textbox(label="Graph 2 Input (e.g., '{0: [1], 1: [0, 2], 2: [1]}' or edge list)")
189
+
190
+ question_type = gr.Dropdown(
191
+ choices=["Spectral Isomorphism Test", "Graph Homomorphism Check", "Matrix Representations and Spectra"],
192
+ label="Select Question Type"
193
+ )
194
+
195
+ mapping_input = gr.Textbox(label="Mapping (for Graph Homomorphism Check, e.g., '{0: 0, 1: 1, 2: 2}')", visible=False)
196
+
197
+ def toggle_mapping_visibility(question_type):
198
+ return {"visible": question_type == "Graph Homomorphism Check"}
199
+
200
+ question_type.change(toggle_mapping_visibility, inputs=question_type, outputs=mapping_input)
201
+
202
+ with gr.Row():
203
+ graph1_output = gr.Plot(label="Graph 1 Visualization")
204
+ graph2_output = gr.Plot(label="Graph 2 Visualization")
205
+
206
+ result_output = gr.Textbox(label="Results", lines=20)
207
+
208
+ submit_button = gr.Button("Run")
209
+ submit_button.click(
210
+ lambda g1, g2, qt, m: process_inputs(g1, g2, qt, m),
211
+ inputs=[graph1_input, graph2_input, question_type, mapping_input],
212
+ outputs=[graph1_output, graph2_output, result_output]
213
+ )
214
+
215
+ # Launch the app
216
+ demo.launch()