Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bugs #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
*.ptx
*.cubin
*.fatbin
ans.txt
*.exe
219 changes: 219 additions & 0 deletions GSI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Imports
from GSI_cpp.isomorphism import create_graph, find_isomorphisms, print_mapping, initializeGPU
import networkx as nx

# Initialize GPU
def initGPU(dev=0,verbose=True):
"""
Initialize the GPU with the specified device number.

Args:
dev (int): The GPU device number to initialize. Default is 0.
"""
initializeGPU(dev,verbose)

# Create graph
def createGraph(node_ids, node_labels, edge_ids, edge_labels, column_oriented):
"""
Create a custom Graph object using the provided node IDs, node labels, edge IDs, and edge labels.

Args:
node_ids (list of int): List of node IDs.
node_labels (list of int): List of node labels corresponding to node IDs.
edge_ids (list of tuple): List of tuples representing edge connections (from, to).
edge_labels (list of int): List of edge labels corresponding to edge IDs.
column_oriented (bool): Flag indicating whether the graph should be column-oriented.

Returns:
Graph: The created custom Graph object.
"""
return create_graph(node_ids, node_labels, edge_ids, edge_labels, column_oriented)

# Find Isomorphisms
def findIsomorphism(query, data):
"""
Find isomorphisms between the query graph and the data graph.

Args:
query (Graph): The query graph.
data (Graph): The data graph.

Returns:
tuple: A tuple containing a boolean indicating if isomorphisms were found and the list of mappings.
"""
mappings = find_isomorphisms(query, data)

if len(mappings) == 0:
return False, mappings
else:
return True, mappings

# Print Mappings
def printMappings(mappings):
"""
Print the mappings of isomorphisms.

Args:
mappings (list of dict): The list of mappings from the query graph to the data graph.
"""
for mapping, n in zip(mappings, range(len(mappings))):
print(f"====== Mapping #{n+1} =====")
print_mapping(mapping)

# NetworkX Graph to Isomorphism Graph
def nxGraph(nx_graph, bidirectional = True, column_oriented = False):
"""
Convert a NetworkX graph to a custom Graph object suitable for isomorphism detection.

Args:
nx_graph (networkx.Graph): The NetworkX graph to convert.
bidirectional (bool): Flag indicating whether edges should be treated as bidirectional. Default is True.
column_oriented (bool): Flag indicating whether the graph should be column-oriented. Default is False.

Returns:
Graph: The created custom Graph object.
"""

# Extract node IDs and labels
node_ids = list(nx_graph.nodes())
node_labels = [nx_graph.nodes[node_id].get('label', 1) for node_id in node_ids] # Default label to 1 if not provided

# Extract edge IDs and labels
edge_ids = list(nx_graph.edges())
edge_labels = [1 for edge_id in edge_ids] # Default label to 1

'''
print(node_ids)
print(edge_ids)
print("OK SO FAR")
'''

# Check if bidirectional flag is set
if bidirectional:
# Create bidirectional edges
bidirectional_edges = []
for from_node, to_node in edge_ids:
bidirectional_edges.append((from_node, to_node))
bidirectional_edges.append((to_node, from_node))
edge_ids = bidirectional_edges
edge_labels = [1 for _ in edge_ids] # Default label to 1 for all edges

'''
print("Node ID's: ", node_ids)
print("Node Label's: ", node_labels)
print("zipped: ", list(zip(node_labels,node_ids)))

print("Edge ID's: ", edge_ids)
print("Edge Label's: ", edge_labels)
#print("OK SO FAR")
print()
'''

# Sort nodes by their label in increasing order
_combined = list(zip(node_labels,node_ids))
_sorted_combined = sorted(_combined)

# Unzip the sorted combined array back into two arrays
A_sorted, B_sorted = zip(*_sorted_combined)

# Convert the tuples back to lists
node_labels = list(A_sorted)
node_ids = list(B_sorted)

index_node_ids = list(range(len(node_ids)))
algo_map = {inid: nid for nid, inid in zip(node_ids, index_node_ids)}

'''
print()
print("Node Label's: ", node_labels)
print("Node id's: ",node_ids)
print("Index node ID's: ", index_node_ids)
print("zipped: ", list(zip(node_labels,index_node_ids)))
print("Index map: ", algo_map)
'''

# Convert node IDs and edge IDs to required format
edge_ids = [(node_ids.index(from_node), node_ids.index(to_node)) for from_node, to_node in edge_ids]

# Create the custom Graph object
return createGraph(index_node_ids, node_labels, edge_ids, edge_labels, column_oriented), algo_map

# Process chemical graphs
def chemGraphProcess(G, label_map):
# Create a new node property "label"
# Encode each unique symbol / elemental_type to an integer in "label"

# Extract the mode ('symbol' or 'elemental_type')
mode = list(list(G.nodes.data())[0][1].keys())[0]

# Stores the unique elements
label_list = []
# Stores the node -> labels mapping
label_dict = {}

# For every node
for node in G.nodes(mode):
node_index = node[0]
node_symbol = node[1]

label_dict[node_index] = label_map.index(node_symbol)+1

# Set the node attribute
nx.set_node_attributes(G, label_dict, "label")

return G

# Print a graph
def printGraph(G):
return G.print_graph()

# Print networkx graph:
def print_nx_graph(nx_graph):
"""
Prints the details of a NetworkX graph including the number of nodes, nodes (ID, label),
number of edges, and edges ((START NODE, END NODE), label).

Args:
nx_graph (networkx.Graph): The NetworkX graph to print.
"""
# Get nodes and their labels
node_ids = list(nx_graph.nodes())
node_labels = {node: nx_graph.nodes[node].get('symbol', 1) for node in node_ids} # Default label to 1 if not provided

# Get edges and their labels
edge_ids = list(nx_graph.edges())
edge_labels = {(u, v): nx_graph.edges[u, v].get('label', 1) for u, v in edge_ids} # Default label to 1 if not provided

# Print the number of nodes
print(f"Graph contains {len(node_ids)} nodes.")

# Print nodes (ID, label)
print("Nodes (ID, label):")
for node_id in node_ids:
print(f"({node_id}, {node_labels[node_id]})")

# Print the number of edges
print(f"Graph contains {len(edge_ids)} edges.")

# Print edges ((START NODE, END NODE), label)
print("Edges ((START NODE, END NODE), label):")
for (start_node, end_node) in edge_ids:
print(f"(({start_node}, {end_node}), {edge_labels[(start_node, end_node)]})")


# Given the parent graph (Reactant), return a mapping between chemical symbols/labels to a number
# Encode each unique symbol / elemental_type to an integer in "label"
def encodeLabels(G):
# Extract the mode ('symbol' or 'elemental_type')
mode = list(list(G.nodes.data())[0][1].keys())[0]


_tmp = []

# Iterate through
for node in list(G.nodes.data()):
#ID = node[0]
symbol = node[1][mode]
_tmp.append(symbol)
#print("Encoding: ", list(dict.fromkeys(_tmp)))
return list(dict.fromkeys(_tmp))
88 changes: 88 additions & 0 deletions GSI_cpp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Define compilers and options

# CUDA Path
# If CUDA_PATH is not already defined, use the default path
CUDA_PATH = /usr/local/cuda-12.5

# Path to gcc-11
# If GCC_11_PATH is not already defined, use the default path installed by Linuxbrew
GCC_11_PATH = /home/linuxbrew/.linuxbrew/Cellar/gcc@11/11.4.0

# Define the compilers explicitly
CC = $(GCC_11_PATH)/bin/gcc-11
CXX = $(GCC_11_PATH)/bin/g++-11
NVCC = $(CUDA_PATH)/bin/nvcc

# Compilation flags
CFLAGS = -std=c++14 -c -O2 -I$(CUDA_PATH)/include
EXEFLAG = -O2 -shared
LDFLAGS = -L$(CUDA_PATH)/lib64 -lcudart -lcudadevrt #-ldl -lm
INCLUDES = -I$(CUDA_PATH)/include
GPU_ARCHITECTURE = sm_86

# Pybind11 and Python includes
PYBIND11_INCLUDES = $(shell python3 -m pybind11 --includes)
PYTHON_LDFLAGS = $(shell python3-config --ldflags)

# NVCC command
NVCCFLAGS = -arch=$(GPU_ARCHITECTURE) -rdc=true --ptxas-options=-v -Xcompiler -fPIC $(INCLUDES) --compiler-bindir $(GCC_11_PATH)/bin -std=c++14 -c -O2 -I$(CUDA_PATH)/include

# Object directory and files
objdir = ./objs/
objfile = $(objdir)Util.o $(objdir)IO.o $(objdir)Match.o $(objdir)Graph.o $(objdir)isomorphism.o $(objdir)pybind_wrapper.o

# Default target
all: libisomorphism.so isomorphism$(shell python3-config --extension-suffix)

# Link all object files to create the shared object file
libisomorphism.so: $(objfile)
$(NVCC) -arch=sm_86 -shared -o libisomorphism.so $(objfile) $(LDFLAGS)


# Compile source files into object files
$(objdir)Util.o: util/Util.cpp util/Util.h | $(objdir)
$(CC) -fPIC $(CFLAGS) util/Util.cpp -o $(objdir)Util.o

$(objdir)Graph.o: graph/Graph.cpp graph/Graph.h | $(objdir)
$(CC) -fPIC $(CFLAGS) graph/Graph.cpp -o $(objdir)Graph.o

$(objdir)IO.o: io/IO.cpp io/IO.h | $(objdir)
$(CC) -fPIC $(CFLAGS) io/IO.cpp -o $(objdir)IO.o

$(objdir)Match.o: match/Match.cu match/Match.h | $(objdir)
$(NVCC) $(NVCCFLAGS) match/Match.cu -o $(objdir)Match.o

$(objdir)isomorphism.o: main/isomorphism.cu main/isomorphism.h | $(objdir)
$(NVCC) $(NVCCFLAGS) main/isomorphism.cu -o $(objdir)isomorphism.o

# Ensure the object directory exists
$(objdir):
mkdir -p $(objdir)

# Pybind11 module
$(objdir)pybind_wrapper.o: pybind_wrapper.cu main/isomorphism.h graph/Graph.h match/Match.h io/IO.h util/Util.h | $(objdir)
$(NVCC) $(NVCCFLAGS) -Xcompiler -fPIC $(CFLAGS) $(PYBIND11_INCLUDES) pybind_wrapper.cu -o $(objdir)pybind_wrapper.o

isomorphism$(shell python3-config --extension-suffix): $(objdir)pybind_wrapper.o $(objdir)Util.o $(objdir)IO.o $(objdir)Match.o $(objdir)Graph.o $(objdir)isomorphism.o
$(NVCC) -arch=$(GPU_ARCHITECTURE) -rdc=true --ptxas-options=-v -Xcompiler -fPIC $(INCLUDES) --compiler-bindir $(GCC_11_PATH)/bin -std=c++14 -O2 -I$(CUDA_PATH)/include $(EXEFLAG) -o isomorphism$(shell python3-config --extension-suffix) $(objdir)pybind_wrapper.o $(objdir)Util.o $(objdir)IO.o $(objdir)Match.o $(objdir)Graph.o $(objdir)isomorphism.o $(PYTHON_LDFLAGS) $(LDFLAGS)


# Phony targets
.PHONY: clean dist tarball test sumlines doc

clean:
rm -f $(objdir)*

dist: clean
rm -f *.txt *.exe *.so *.cpython-*.so
rm -f *.g
rm -f cuda-memcheck.*

tarball:
tar -czvf gsi.tar.gz main util match io graph Makefile README.md objs

sumlines:
bash script/sumline.sh

doc:
doxygen
14 changes: 14 additions & 0 deletions GSI_cpp/data/d.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
t # 0
5 6 3 2
v 0 1
v 1 2
v 2 3
v 3 1
v 4 2
e 0 1 1
e 0 2 2
e 1 3 1
e 2 3 2
e 3 4 1
e 2 4 1
t # -1
12 changes: 12 additions & 0 deletions GSI_cpp/data/data.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
t # 0
3 6 1 1
v 0 1
v 1 1
v 2 1
e 0 1 1
e 1 2 1
e 2 0 1
e 1 0 1
e 2 1 1
e 0 2 1
t # -1
17 changes: 17 additions & 0 deletions GSI_cpp/data/data_pair.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
t # 0
4 10 1 1
v 0 1
v 1 1
v 2 1
v 3 1
e 0 1 1
e 1 2 1
e 2 0 1
e 0 3 1
e 2 3 1
e 1 0 1
e 2 1 1
e 0 2 1
e 3 0 1
e 3 2 1
t # -1
9 changes: 9 additions & 0 deletions GSI_cpp/data/q.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
t # 0
3 3 3 2
v 0 1
v 1 2
v 2 3
e 0 1 1
e 0 2 2
e 1 2 1
t # -1
6 changes: 4 additions & 2 deletions data/query.g → GSI_cpp/data/query.g
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
t # 0
3 2 1 1
3 4 1 1
v 0 1
v 1 1
v 2 1
e 0 1 1
e 1 0 1
e 1 2 1
t # -1
e 2 1 1
t # -1
10 changes: 10 additions & 0 deletions GSI_cpp/data/query_pair.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
t # 0
3 4 1 1
v 0 1
v 1 1
v 2 1
e 0 1 1
e 1 0 1
e 0 2 1
e 2 0 1
t # -1
File renamed without changes.
Loading