Repository: bfGraph/STGraph (language: python)

Issue:
Benchmarking/base



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 2ed651b..0ab8df6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,8 +19,6 @@ __pycache__/
 build
 dist
 *.egg-info
-*.txt
-test_*.*
 *.csv
 *.prof
 .vscode/
diff --git a/assets/Base Graph Approach Example.png b/assets/Base Graph Approach Example.png
new file mode 100644
index 0000000..f612884
Binary files /dev/null and b/assets/Base Graph Approach Example.png differ
diff --git a/assets/Class Diagram SeastarGraph.png b/assets/Class Diagram SeastarGraph.png
new file mode 100644
index 0000000..f15d4ee
Binary files /dev/null and b/assets/Class Diagram SeastarGraph.png differ
diff --git a/assets/Intermediate graph representation during forward and backward prop.png b/assets/Intermediate graph representation during forward and backward prop.png
new file mode 100644
index 0000000..6677cf4
Binary files /dev/null and b/assets/Intermediate graph representation during forward and backward prop.png differ
diff --git a/assets/Representation of dynamic graph computations.png b/assets/Representation of dynamic graph computations.png
new file mode 100644
index 0000000..97f6c85
Binary files /dev/null and b/assets/Representation of dynamic graph computations.png differ
diff --git a/assets/Seastar Forward and Backward Prop State Stack.png b/assets/Seastar Forward and Backward Prop State Stack.png
new file mode 100644
index 0000000..2ca321c
Binary files /dev/null and b/assets/Seastar Forward and Backward Prop State Stack.png differ
diff --git a/assets/Seastar State Stack Backward Propagation.png b/assets/Seastar State Stack Backward Propagation.png
new file mode 100644
index 0000000..49bff5a
Binary files /dev/null and b/assets/Seastar State Stack Backward Propagation.png differ
diff --git a/assets/Seastar State Stack Forward Propagation.png b/assets/Seastar State Stack Forward Propagation.png
new file mode 100644
index 0000000..80279cc
Binary files /dev/null and b/assets/Seastar State Stack Forward Propagation.png differ
diff --git a/benchmarking/dataset/doorah/doorah.py b/benchmarking/dataset/doorah/doorah.py
index d2c66c7..c35b718 100644
--- a/benchmarking/dataset/doorah/doorah.py
+++ b/benchmarking/dataset/doorah/doorah.py
@@ -112,30 +112,30 @@
 def get_doorah_dataset():
     return edges_list, edges_weight_list, all_features_list, all_targets_list
 
-def preprocess_graph_structure(edges):
-    # inspect(edges)
-    tmp_set = set()
-    for i in range(len(edges)):
-        tmp_set = set()
-        for j in range(len(edges[i][0])):
-            tmp_set.add(edges[i][0][j])
-            tmp_set.add(edges[i][1][j])
-    max_num_nodes = len(tmp_set)
+# def preprocess_graph_structure(edges):
+#     # inspect(edges)
+#     tmp_set = set()
+#     for i in range(len(edges)):
+#         tmp_set = set()
+#         for j in range(len(edges[i][0])):
+#             tmp_set.add(edges[i][0][j])
+#             tmp_set.add(edges[i][1][j])
+#     max_num_nodes = len(tmp_set)
 
-    edge_dict = {}
-    for i in range(len(edges)):
-        edge_set = set()
-        for j in range(len(edges[i][0])):
-            edge_set.add((edges[i][0][j],edges[i][1][j]))
-        edge_dict[str(i)] = edge_set
+#     edge_dict = {}
+#     for i in range(len(edges)):
+#         edge_set = set()
+#         for j in range(len(edges[i][0])):
+#             edge_set.add((edges[i][0][j],edges[i][1][j]))
+#         edge_dict[str(i)] = edge_set
     
-    edge_final_dict = {}
-    edge_final_dict["0"] = {"add": list(edge_dict["0"]),"delete": []}
-    for i in range(1,len(edges)):
-        edge_final_dict[str(i)] = {"add": list(edge_dict[str(i)].difference(edge_dict[str(i-1)])), "delete": list(edge_dict[str(i-1)].difference(edge_dict[str(i)]))}
+#     edge_final_dict = {}
+#     edge_final_dict["0"] = {"add": list(edge_dict["0"]),"delete": []}
+#     for i in range(1,len(edges)):
+#         edge_final_dict[str(i)] = {"add": list(edge_dict[str(i)].difference(edge_dict[str(i-1)])), "delete": list(edge_dict[str(i-1)].difference(edge_dict[str(i)]))}
     
-    return edge_final_dict, max_num_nodes
+#     return edge_final_dict, max_num_nodes
 
-operations, max_num_nodes = preprocess_graph_structure(edges_list)
+# operations, max_num_nodes = preprocess_graph_structure(edges_list)
 # inspect(operations)
 # inspect(max_num_nodes)
\ No newline at end of file
diff --git a/benchmarking/tgcn/pygt-dynamic/train.py b/benchmarking/tgcn/pygt-dynamic/train.py
index 3354e41..bd0f738 100644
--- a/benchmarking/tgcn/pygt-dynamic/train.py
+++ b/benchmarking/tgcn/pygt-dynamic/train.py
@@ -21,7 +21,7 @@
 # install(show_locals=True)
 
 from seastar.dataset.SoorahBase import SoorahBase
-# from seastar.dataset.EnglandCOVID import EnglandCOVID
+from seastar.dataset.EnglandCOVID import EnglandCOVID
 
 
 # GPU | CPU
@@ -67,6 +67,9 @@ def main(args):
     all_features = to_default_device(torch.FloatTensor(np.array(all_features)))
     all_targets = to_default_device(torch.FloatTensor(np.array(all_targets)))
     
+    print("📝📝📝 GPU Memory in just storing features/targets is: {}".format(((nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem) * 1.0)/(1024**2)))
+    print("📝📝📝 CPU Memory in just storing features/targets is: {}".format(((psutil.virtual_memory()[3] - initial_used_cpu_mem) * 1.0)/(1024**2)))
+    print("\n")
     # Hyperparameters
     train_test_split = 0.8
     
@@ -85,6 +88,9 @@ def main(args):
     test_targets = all_targets[int(len(all_targets) * train_test_split):]
 
     model = to_default_device(PyGT_TGCN(8))
+    print("📝📝📝 GPU Memory after storing model is: {}".format(((nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem) * 1.0)/(1024**2)))
+    print("📝📝📝 CPU Memory after storing model is: {}".format(((psutil.virtual_memory()[3] - initial_used_cpu_mem) * 1.0)/(1024**2)))
+    print("\n")
 
     # use optimizer
     optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
@@ -95,6 +101,11 @@ def main(args):
     
     edge_weight_lst = [to_default_device(torch.FloatTensor(edge_weight)) for edge_weight in train_edge_weights_lst]
     train_edges_lst = [to_default_device(torch.from_numpy(np.array(edge_index))) for edge_index in train_edges_lst]
+    
+    print("📝📝📝 GPU Memory after storing edge list/weights: {}".format(((nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem) * 1.0)/(1024**2)))
+    print("📝📝📝 CPU Memory after storing edge list/weights: {}".format(((psutil.virtual_memory()[3] - initial_used_cpu_mem) * 1.0)/(1024**2)))
+    print("\n")
+    
     # train_edges_lst = [to_default_device(torch.from_numpy(np.array(edge_index).T)) for edge_index in train_edges_lst]
 
     # G = GPMAGraph(train_edges_lst)
@@ -139,6 +150,13 @@ def main(args):
             # if index == 1:
             #     quit()
             
+        # print("📝📝📝 GPU Memory after training is: {}".format(((nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem)*1.0))/(1024**2))
+        print("📝📝📝 GPU Memory after training is: {}".format(((nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem) * 1.0)/(1024**2)))
+        print("📝📝📝 CPU Memory after training is: {}".format(((psutil.virtual_memory()[3] - initial_used_cpu_mem) * 1.0)/(1024**2)))
+        print("\n")
+            
+        if index == 3:
+            quit()
     
         cost = cost / (index+1)
         
diff --git a/benchmarking/tgcn/seastar-dynamic/tgcn.py b/benchmarking/tgcn/seastar-dynamic/tgcn.py
index 65ef743..cf7345d 100644
--- a/benchmarking/tgcn/seastar-dynamic/tgcn.py
+++ b/benchmarking/tgcn/seastar-dynamic/tgcn.py
@@ -9,6 +9,9 @@
 import snoop
 from rich import inspect
 
+from rich.traceback import install
+install(show_locals=True)
+
 class SeastarGCNLayer(nn.Module):
     def __init__(self,
                  in_feats,
diff --git a/benchmarking/tgcn/seastar-dynamic/train.py b/benchmarking/tgcn/seastar-dynamic/train.py
index 88493e7..7663c32 100644
--- a/benchmarking/tgcn/seastar-dynamic/train.py
+++ b/benchmarking/tgcn/seastar-dynamic/train.py
@@ -16,6 +16,9 @@
 
 from rich import inspect
 from rich.pretty import pprint
+from rich.console import Console
+from rich.table import Table
+
 # from rich.traceback import install
 # install(show_locals=True)
 
@@ -26,65 +29,79 @@
 from seastar.dataset.SoorahBase import SoorahBase
 from seastar.dataset.wikimaths import WikiMaths
 
+import seastar.compiler.debugging.print_variables as print_var
+
+
 # GPU | CPU
 def get_default_device():
-    
     if torch.cuda.is_available():
-        return torch.device('cuda:0')
+        return torch.device("cuda:0")
     else:
-        return torch.device('cpu')
+        return torch.device("cpu")
+
 
 def to_default_device(data):
-    
-    if isinstance(data,(list,tuple)):
-        return [to_default_device(x,get_default_device()) for x in data]
-    
-    return data.to(get_default_device(),non_blocking = True)
+    if isinstance(data, (list, tuple)):
+        return [to_default_device(x, get_default_device()) for x in data]
+
+    return data.to(get_default_device(), non_blocking=True)
+
 
 def main(args):
+    print_var.is_print_verbose_log = args.verbose
+
+    # preparing the benchmarking table
+    table = Table(title=f"\nBenchmarking T-GCN with {args.type}\n", show_edge=False, style="black bold")
+
+    table.add_column("Epoch", justify="right")
+    table.add_column("Time (s)", justify="left")
+    table.add_column("MSE", justify="left")
+    table.add_column("Max. GPU Memory (mb)", justify="left")
+    table.add_column("Avg. GPU Memory (mb)", justify="left")
+    table.add_column("Max. CPU Memory (mb)", justify="left")
+    table.add_column("Avg. CPU Memory (mb)", justify="left")
 
-    if torch.cuda.is_available():
-        print("🎉 CUDA is available")
-    else:
-        print("😔 CUDA is not available")
-        
     nvidia_smi.nvmlInit()
     handle = nvidia_smi.nvmlDeviceGetHandleByIndex(0)
-    
+
     initial_used_gpu_mem = nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used
-    initial_used_cpu_mem = (psutil.virtual_memory()[3])
-    
-    eng_covid = SoorahBase(args.dataset, verbose=True, for_seastar=True)
-    # eng_covid = EnglandCOVID(verbose=True)
-    
+    initial_used_cpu_mem = psutil.virtual_memory()[3]
+
+    # eng_covid = SoorahBase(args.dataset, verbose=True, lags=args.lags, for_seastar=True)
+    eng_covid = EnglandCOVID(verbose=True)
+
     print("Loaded dataset into the train.py seastar")
-    
+
     edge_list = eng_covid.get_edges()
     edge_weight_list = eng_covid.get_edge_weights()
     all_features = eng_covid.get_all_features()
     all_targets = eng_covid.get_all_targets()
-    
+
     all_features = to_default_device(torch.FloatTensor(np.array(all_features)))
     all_targets = to_default_device(torch.FloatTensor(np.array(all_targets)))
-    
+
     # Hyperparameters
     train_test_split = 0.8
-    
+
     # train_test_split for graph
-    train_edges_lst = edge_list[:int(len(edge_list) * train_test_split)]
-    test_edges_lst = edge_list[int(len(edge_list) * train_test_split):]
-    
-    train_edge_weights_lst = edge_weight_list[:int(len(edge_weight_list) * train_test_split)]
-    test_edge_weights_lst = edge_weight_list[int(len(edge_weight_list) * train_test_split):]
+    train_edges_lst = edge_list[: int(len(edge_list) * train_test_split)]
+    test_edges_lst = edge_list[int(len(edge_list) * train_test_split) :]
+
+    train_edge_weights_lst = edge_weight_list[
+        : int(len(edge_weight_list) * train_test_split)
+    ]
+    test_edge_weights_lst = edge_weight_list[
+        int(len(edge_weight_list) * train_test_split) :
+    ]
 
     # train_test_split for features
-    train_features = all_features[:int(len(all_features) * train_test_split)]
-    train_targets = all_targets[:int(len(all_targets) * train_test_split)]
-    
-    test_features = all_features[int(len(all_features) * train_test_split):]
-    test_targets = all_targets[int(len(all_targets) * train_test_split):]
+    train_features = all_features[: int(len(all_features) * train_test_split)]
+    train_targets = all_targets[: int(len(all_targets) * train_test_split)]
+
+    test_features = all_features[int(len(all_features) * train_test_split) :]
+    test_targets = all_targets[int(len(all_targets) * train_test_split) :]
 
-    model = to_default_device(SeastarTGCN(8))
+    model = to_default_device(SeastarTGCN(args.lags))
 
     # use optimizer
     optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
@@ -103,9 +120,17 @@ def main(args):
         print("Error: Invalid Type")
         quit()
 
+    # inspect(G.graph_updates)
+    # num_edges = 0
+    # for key, val in G.graph_updates.items():
+    #     num_edges += len(val["add"])
+    #     num_edges -= len(val["delete"])
+    #     print("For timestamp={} NUM_ADDITIONS={} NUM_DELETIONS={} | TOTAL_EDGE_COUNT={}".format(key,len(val["add"]),len(val["delete"]),num_edges),flush=True)
+    # quit()
+
     # train
     print("Training...\n")
-    for epoch in range(args.num_epochs):
+    for epoch in tqdm(range(args.num_epochs)):
         model.train()
         if cuda:
             torch.cuda.synchronize()
@@ -114,53 +139,58 @@ def main(args):
         cost = 0
         hidden_state = None
         optimizer.zero_grad()
-        
+
         gpu_mem_arr = []
         cpu_mem_arr = []
 
         # dyn_graph_index is dynamic graph index
-        for index in range(0,len(train_features)): 
-            
+        for index in range(0, len(train_features)):
             # t1 = time.time()
-            
+
             # Getting the graph for a particular timestamp
-            G.get_graph(index) 
+            G.get_graph(index)
 
             # normalization
             degs = torch.from_numpy(G.in_degrees()).type(torch.int32)
             norm = torch.pow(degs, -0.5)
             norm[torch.isinf(norm)] = 0
             norm = to_default_device(norm)
-            G.ndata['norm'] = norm.unsqueeze(1)
-            edge_weight = to_default_device(torch.FloatTensor(train_edge_weights_lst[index]))
-            edge_weight = torch.unsqueeze(edge_weight,1)
+            G.ndata["norm"] = norm.unsqueeze(1)
+            edge_weight = to_default_device(
+                torch.FloatTensor(train_edge_weights_lst[index])
+            )
+            edge_weight = torch.unsqueeze(edge_weight, 1)
 
             # forward propagation
-            y_hat, hidden_state = model(G, train_features[index], edge_weight, hidden_state)
-            
-            cost = cost + torch.mean((y_hat-train_targets[index])**2)
-            
-            used_gpu_mem = nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
+            y_hat, hidden_state = model(
+                G, train_features[index], edge_weight, hidden_state
+            )
+
+            cost = cost + torch.mean((y_hat - train_targets[index]) ** 2)
+
+            used_gpu_mem = (
+                nvidia_smi.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
+            )
             gpu_mem_arr.append(used_gpu_mem)
             used_cpu_mem = (psutil.virtual_memory()[3]) - initial_used_cpu_mem
             cpu_mem_arr.append(used_cpu_mem)
-            
             # run_time_this_timestamp = time.time() - t1
             # print(f"⌛⌛⌛ Takes a total of {run_time_this_timestamp}")
-            
+
             # if index == 1:
             #     quit()
-            
-    
-        cost = cost / (index+1)
-        
+
+        cost = cost / (index + 1)
+
         # t1 = time.time()
+        # print("⚠️⚠️⚠️ Starting Backprop")
         cost.backward()
+        # print("⚠️⚠️⚠️ Backprop Completed")
         # print(f"⌛⌛⌛ Time taken for backprop {time.time() - t1}")
-        
+
         # if epoch == 1:
         #     quit()
-        
+
         optimizer.step()
 
         if cuda:
@@ -171,11 +201,32 @@ def main(args):
         if epoch >= 3:
             dur.append(run_time_this_epoch)
 
-        print('Epoch {:03d} | Time(s) {:.4f} | MSE {:.2f} | Used GPU Memory (Max) {:.3f} mb | Used GPU Memory (Avg) {:.3f} mb | Used CPU Memory (Max) {:.3f} mb | Used CPU Memory (Avg) {:.3f} mb'.format(
-            epoch, run_time_this_epoch, cost, (max(gpu_mem_arr) * 1.0 / (1024**2)), ((sum(gpu_mem_arr) * 1.0) / ((1024**2) * len(gpu_mem_arr))), (max(cpu_mem_arr) * 1.0 / (1024**2)), ((sum(cpu_mem_arr) * 1.0) / ((1024**2) * len(cpu_mem_arr)))
-        ))
-
-    print('Average Time taken: {:6f}'.format(np.mean(dur)))
+        table.add_row(
+            str(epoch),
+            str(round(run_time_this_epoch, 4)),
+            str(round(cost.item(), 4)),
+            str(round((max(gpu_mem_arr) * 1.0 / (1024**2)), 4)),
+            str(round(((sum(gpu_mem_arr) * 1.0) / ((1024**2) * len(gpu_mem_arr))), 4)),
+            str(round((max(cpu_mem_arr) * 1.0 / (1024**2)), 4)),
+            str(round(((sum(cpu_mem_arr) * 1.0) / ((1024**2) * len(cpu_mem_arr))), 4)),
+        )
+
+        # print(
+        #     "Epoch {:03d} | Time(s) {:.4f} | MSE {:.2f} | Used GPU Memory (Max) {:.3f} mb | Used GPU Memory (Avg) {:.3f} mb | Used CPU Memory (Max) {:.3f} mb | Used CPU Memory (Avg) {:.3f} mb".format(
+        #         epoch,
+        #         run_time_this_epoch,
+        #         cost,
+        #         (max(gpu_mem_arr) * 1.0 / (1024**2)),
+        #         ((sum(gpu_mem_arr) * 1.0) / ((1024**2) * len(gpu_mem_arr))),
+        #         (max(cpu_mem_arr) * 1.0 / (1024**2)),
+        #         ((sum(cpu_mem_arr) * 1.0) / ((1024**2) * len(cpu_mem_arr))),
+        #     )
+        # )
+
+    console = Console()
+    console.print(table)
+
+    print("\nAverage Time taken (s): {:4f}".format(np.mean(dur)))
 
     # evaluate
     # print("Evaluating...\n")
@@ -184,7 +235,7 @@ def main(args):
 
     # test_graph_log_dict, test_max_num_nodes = preprocess_graph_structure(test_edges_lst)
     # G = GPMAGraph(test_graph_log_dict,test_max_num_nodes)
-    
+
     # # G = PCSRGraph(test_graph_log_dict,test_max_num_nodes)
 
     # predictions = []
@@ -201,7 +252,7 @@ def main(args):
     #     G.ndata['norm'] = norm.unsqueeze(1)
     #     edge_weight = to_default_device(torch.FloatTensor(test_edge_weights_lst[index]))
     #     edge_weight = torch.unsqueeze(edge_weight,1)
-        
+
     #     # forward propagation
     #     y_hat, hidden_state = model(G, test_features[index], edge_weight, hidden_state)
     #     cost = cost + torch.mean((y_hat-test_targets[index])**2)
@@ -213,23 +264,32 @@ def main(args):
     # print("MSE: {:.4f}".format(cost))
 
 
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='GCN')
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="GCN")
 
     # COMMENT IF SNOOP IS TO BE ENABLED
     snoop.install(enabled=False)
 
-
-    parser.add_argument("--lr", type=float, default=1e-2,
-            help="learning rate")
-    parser.add_argument("--type", type=str, default="naive",
-            help="Seastar Type")
-    parser.add_argument("--num_epochs", type=int, default=1,
-            help="number of training epochs")
-    parser.add_argument("--dataset", type=str, default="soorah_base",
-            help="Name of the Soorah Dataset", metavar="dataset")
+    parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
+    parser.add_argument("--lags", type=int, default=8, help="lags")
+    parser.add_argument("--type", type=str, default="naive", help="Seastar Type")
+    parser.add_argument(
+        "--num_epochs", type=int, default=1, help="number of training epochs"
+    )
+    parser.add_argument(
+        "--dataset",
+        type=str,
+        default="soorah_base",
+        help="Name of the Soorah Dataset",
+        metavar="dataset",
+    )
+    parser.add_argument(
+        "--verbose",
+        type=bool,
+        default=False,
+        help="If set to true, will print out logs while Seastar compiles your model",
+        metavar="verbose",
+    )
     args = parser.parse_args()
-    print(args)
 
-    main(args)
\ No newline at end of file
+    main(args)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..a4bcf39
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,16 @@
+Jinja2==3.1.2
+networkx==3.1
+numpy==1.23.4
+nvidia_ml_py3==7.352.0
+pandas==1.3.5
+prettytable==3.7.0
+psutil==5.9.4
+pynvrtc==9.2
+rich==13.3.5
+snoop==0.4.2
+sphinx_rtd_theme==1.2.0
+sympy==1.11.1
+torch==2.0.0+cu118
+torch_geometric_temporal==0.54.0
+tqdm==4.64.1
+pybind11==2.10.4
\ No newline at end of file
diff --git a/seastar/compiler/autodiff.py b/seastar/compiler/autodiff.py
index b1affaf..3b8e458 100644
--- a/seastar/compiler/autodiff.py
+++ b/seastar/compiler/autodiff.py
@@ -9,6 +9,8 @@
 from .passes.mem_planning import mem_planning
 from .passes import optimize, fuse, visualize
 
+from seastar.compiler.debugging.seastar_logger import print_log
+
 def diff(vars, grads, forward_units, fprog):
     '''
     For each var we find the statment that computes it, then we use itself as well as its grad
@@ -56,7 +58,9 @@ def diff(vars, grads, forward_units, fprog):
                     if not is_const_scalar(var):
                         processed_count[var] += sum([1 if unit.program.has_stmt(stmt) else 0 for stmt in var.users])
             stopping_var = stopping_var.union(set([ret.id for ret in unit.all_rets()]))
-    print('\n------------Autodiff: Retrive BackwardProg-----------\n')
+    
+    print_log("[cyan bold]AutoDiff[/cyan bold]: Retrieving backward program")
+    
     while q:
         y = q.pop()
         cur_stmt = y.stmt
@@ -65,14 +69,12 @@ def diff(vars, grads, forward_units, fprog):
             if cur_stmt:
                 for arg in cur_stmt.args:
                     if not is_const_scalar(arg) and arg.requires_grad:
-                        print('Handled by DL backend. Skipping to next var:', arg)
                         q.append(arg)
             continue
         if 'gtypecast' in cur_stmt.op_name.lower():
             # Skip gtypecast
             x = cur_stmt.args[0]
             grad_map[y.id]._val_type = x.val_type
-            print('differentiating gtypecast:', cur_stmt, ' using y:', y, 'grad_y:', grad_map[y.id])
             q.append(cur_stmt.args[0])
             continue
         #if y not in grad_map:
@@ -81,7 +83,6 @@ def diff(vars, grads, forward_units, fprog):
         #    print('Create grad', grad_map[y], 'for', y, ',who is produced by', y.stmt)
         grad_y = grad_map[y.id]
         x2stmts = cur_stmt.grad(y, grad_y)
-        print('\ndifferentiating:', cur_stmt, ' using y:', y, 'grad_y:', grad_y)
         x2stmt_list = list(x2stmts.items())
         for x, stmts in x2stmt_list:
             # Compute gradient
@@ -114,17 +115,23 @@ def diff(vars, grads, forward_units, fprog):
                     arg._grad = fprog.find_var_by_id(arg.id)._grad
             for ret in unit.unit_rets():
                 output_var.add(ret)
-    print('\n------------Autodiff: Optimizing BackwardProg-----------\n')
+    
+    print_log("[cyan bold]Autodiff[/cyan bold]: Optimizing backward program")
     optimize(BProg)
     #visualize.plot_exec_units(forward_units)
     #visualize.plot_programs([unit._prog for unit in forward_units] + [BProg])
-    print('\n------------Autodiff: Gradient Driven MemPlanning-----------\n')
+    
+    print_log("[cyan bold]Autodiff[/cyan bold]: Gradient Driven MemPlanning")
     output_grad_map = {k:k._grad for k in need_grad_var}
     bp_prog_list = mem_planning(forward_units, BProg, output_grad_map, grads)
-    print('\n------------Autodiff: Optimizing Programs of Each Gradient-----------\n')
+    
+    print_log("[cyan bold]Autodiff[/cyan bold]: Optimizing programs of each gradient")
     for prog in bp_prog_list:
         optimize(prog)
-    print('\n------------Autodiff: Fusing Programs of Each Gradient-----------\n')
+        
+    print_log("[cyan bold]Autodiff[/cyan bold]: Fusing programs of each gradient")
     backward_exe_units = fuse(bp_prog_list, [v for _, v in output_grad_map.items()])
-    print('\n------------Autodiff: Done-----------\n')
+    
+    print_log("[cyan bold]Autodiff[/cyan bold]: Completed")
+    
     return backward_exe_units
diff --git a/seastar/compiler/code_gen/code_gen.py b/seastar/compiler/code_gen/code_gen.py
index 0723811..da40086 100644
--- a/seastar/compiler/code_gen/code_gen.py
+++ b/seastar/compiler/code_gen/code_gen.py
@@ -106,6 +106,5 @@ def gen_cuda(configs):
         else:
             raise NotImplementedError('{} Template not supported'.format(config['template_name']))
         h += rendered_tpl
-    # UNCOMMENT IF KERNEL IS TO BE PRINTED    
-    # print(h)
+    
     return compile_cuda(h)
diff --git a/seastar/compiler/code_gen/compiler.py b/seastar/compiler/code_gen/compiler.py
index c25aff1..65460e4 100644
--- a/seastar/compiler/code_gen/compiler.py
+++ b/seastar/compiler/code_gen/compiler.py
@@ -1,9 +1,11 @@
 from .cuda_driver import *
 from pynvrtc.compiler import Program, ProgramException
-from .device_info import deviceinfo
+from .device_info import DeviceInfo
 import subprocess
 import ctypes
 import snoop
+from ctypes import c_void_p, c_char_p, byref
+from .cuda_error import ASSERT_DRV
 
 PTX_PATH='./egl_kernel.ptx'
 CU_PATH='./egl_kernel.cu'
@@ -12,19 +14,21 @@
 def compile_with_nvcc(cuda_text):
     with open(CU_PATH, 'w+') as f:
         f.write(cuda_text)
-    device = deviceinfo()
+    device = DeviceInfo()
     nvcc_path = device.nvcc_path
-    cp = str(device.cc_major.value * 10 + device.cc_minor.value)
+    cp = str(device.cc_major * 10 + device.cc_minor)
     extra_flags = ' -lineinfo'
     cmd = nvcc_path + ' ' + CU_PATH + ' -arch=compute_' + cp + ' -ptx ' + extra_flags
-    print('cmd', cmd)
+
+    # Trying to set max register count
+    # cmd = nvcc_path + ' ' + CU_PATH + ' -arch=compute_' + cp + ' -ptx ' + ' -maxrregcount=32 ' + extra_flags
+
     ret = subprocess.check_output(cmd, shell=True)
-    print('Output of nvcc call', ret)
 
 def compile_with_nvrtc(cuda_text):
     c = Program(cuda_text)
-    device = deviceinfo()
-    cp = str(device.cc_major.value * 10 + device.cc_minor.value)
+    device = DeviceInfo()
+    cp = str(device.cc_major * 10 + device.cc_minor)
     ptx = c.compile(['-arch=compute_' + cp])
     with open(PTX_PATH, 'w+') as f:
         f.write(ptx)
@@ -32,11 +36,9 @@ def compile_with_nvrtc(cuda_text):
 def compile_cuda(cuda_text):
     try:
         compile_with_nvcc(cuda_text)
-        cu_module = c_void_p()
-        char_p = c_char_p((PTX_PATH).encode())
-        ret = cuModuleLoad(byref(cu_module), char_p)
-        if ret:
-            raise Exception('cuModuleLoad fails with ret ' + str(ret))
+        char_p = (PTX_PATH).encode()
+        ret, cu_module = cuModuleLoad(char_p)
+        ASSERT_DRV(ret)
         return cu_module
     except Exception as e:
         raise e
diff --git a/seastar/compiler/code_gen/cuda_driver.py b/seastar/compiler/code_gen/cuda_driver.py
index 1320fae..b75075a 100644
--- a/seastar/compiler/code_gen/cuda_driver.py
+++ b/seastar/compiler/code_gen/cuda_driver.py
@@ -1,67 +1,37 @@
-# Copy from: https://github.com/PacktPublishing/Hands-On-GPU-Programming-with-Python-and-CUDA/blob/master/Chapter10/cuda_driver.py
+from cuda import cuda, cudart
 from ctypes import *
-import sys
-
-if 'linux' in sys.platform:
-	cuda = CDLL('/usr/lib/wsl/lib/libcuda.so')
- 	# cuda = CDLL('libcuda.so')
-elif 'win' in sys.platform:
-	cuda = CDLL('nvcuda.dll')
-
-CUDA_ERRORS = {0 : 'CUDA_SUCCESS', 1 : 'CUDA_ERROR_INVALID_VALUE', 200 : 'CUDA_ERROR_INVALID_IMAGE', 201 : 'CUDA_ERROR_INVALID_CONTEXT ', 400 : 'CUDA_ERROR_INVALID_HANDLE' }
 
 cuInit = cuda.cuInit
-cuInit.argtypes = [c_uint]
-cuInit.restype = int
-
 cuDeviceGetCount = cuda.cuDeviceGetCount
-cuDeviceGetCount.argtypes = [POINTER(c_int)]
-cuDeviceGetCount.restype = int
-
 cuCtxGetCurrent = cuda.cuCtxGetCurrent
-cuCtxGetCurrent.argtypes = [c_void_p]
-cuCtxGetCurrent.restype = int
-
 cuDeviceGet = cuda.cuDeviceGet
-cuDeviceGet.argtypes = [POINTER(c_int), c_int]
-cuDeviceGet.restype = int
-
+cuDeviceGetName = cuda.cuDeviceGetName
+cuDeviceGetAttribute = cuda.cuDeviceGetAttribute
+cuMemGetInfo = cuda.cuMemGetInfo
 cuCtxCreate = cuda.cuCtxCreate
-cuCtxCreate.argtypes = [c_void_p, c_uint, c_int]
-cuCtxCreate.restype = int
-
 cuModuleLoad = cuda.cuModuleLoad
-cuModuleLoad.argtypes = [c_void_p, c_char_p]
-cuModuleLoad.restype  = int
-
 cuCtxSynchronize = cuda.cuCtxSynchronize
-cuCtxSynchronize.argtypes = []
-cuCtxSynchronize.restype = int
-
+cuModuleLoadData = cuda.cuModuleLoadData
 cuModuleGetFunction = cuda.cuModuleGetFunction
-cuModuleGetFunction.argtypes = [c_void_p, c_void_p, c_char_p ]
-cuModuleGetFunction.restype = int
-
+cuStreamCreate = cuda.cuStreamCreate
 cuMemAlloc = cuda.cuMemAlloc
-cuMemAlloc.argtypes = [c_void_p, c_size_t]
-cuMemAlloc.restype = int
-
-cuMemcpyHtoD = cuda.cuMemcpyHtoD 
-cuMemcpyHtoD.argtypes = [c_void_p, c_void_p, c_size_t]
-cuMemAlloc.restype = int
-
-cuMemcpyDtoH = cuda.cuMemcpyDtoH 
-cuMemcpyDtoH.argtypes = [c_void_p, c_void_p, c_size_t]
-cuMemcpyDtoH.restype = int
-
+cuMemcpyHtoD = cuda.cuMemcpyHtoD
+cuMemcpyDtoH = cuda.cuMemcpyDtoH
+cuMemcpyHtoDAsync = cuda.cuMemcpyHtoDAsync
+cuMemcpyDtoHAsync = cuda.cuMemcpyDtoHAsync
+cuStreamSynchronize = cuda.cuStreamSynchronize
 cuMemFree = cuda.cuMemFree
-cuMemFree.argtypes = [c_void_p] 
-cuMemFree.restype = int
-
 cuLaunchKernel = cuda.cuLaunchKernel
-cuLaunchKernel.argtypes = [c_void_p, c_uint, c_uint, c_uint, c_uint, c_uint, c_uint, c_uint, c_void_p, c_void_p, c_void_p]
-cuLaunchKernel.restype = int
-
 cuCtxDestroy = cuda.cuCtxDestroy
-cuCtxDestroy.argtypes = [c_void_p]
-cuCtxDestroy.restype = int
\ No newline at end of file
+cuStreamDestroy = cuda.cuStreamDestroy
+cuCtxGetDevice = cuda.cuCtxGetDevice
+
+cudaSetDevice = cudart.cudaSetDevice
+
+# Macros
+COMPUTE_CAPABILITY_MAJOR = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR
+COMPUTE_CAPABILITY_MINOR = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR
+MULTIPROCESSOR_COUNT = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT
+MAX_THREAD_PER_MULTIPROCESSOR = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR
+CLOCK_RATE = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CLOCK_RATE
+MEMORY_CLOCK_RATE = cuda.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE
\ No newline at end of file
diff --git a/seastar/compiler/code_gen/cuda_error.py b/seastar/compiler/code_gen/cuda_error.py
new file mode 100644
index 0000000..d2d5f60
--- /dev/null
+++ b/seastar/compiler/code_gen/cuda_error.py
@@ -0,0 +1,13 @@
+from cuda import cuda, nvrtc
+
+def ASSERT_DRV(err):
+    if isinstance(err, tuple):
+        err = err[0]
+    if isinstance(err, cuda.CUresult):
+        if err != cuda.CUresult.CUDA_SUCCESS:
+            raise RuntimeError("Cuda Error: {}".format(err))
+    elif isinstance(err, nvrtc.nvrtcResult):
+        if err != nvrtc.nvrtcResult.NVRTC_SUCCESS:
+            raise RuntimeError("Nvrtc Error: {}".format(err))
+    else:
+        raise RuntimeError("Unknown error type: {}".format(err))
\ No newline at end of file
diff --git a/seastar/compiler/code_gen/device_info.py b/seastar/compiler/code_gen/device_info.py
index 9d42bc7..e3ece4b 100644
--- a/seastar/compiler/code_gen/device_info.py
+++ b/seastar/compiler/code_gen/device_info.py
@@ -1,82 +1,91 @@
-import sys
-import ctypes
 import os
 
-# Some constants taken from cuda.h
-CUDA_SUCCESS = 0
-CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT = 16
-CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR = 39
-CU_DEVICE_ATTRIBUTE_CLOCK_RATE = 13
-CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE = 36
-
-
-
-
-class deviceinfo():
-
-    def __init__(self):
-        # /usr/lib/wsl/lib/libcuda.so
-        libnames = ('/usr/lib/wsl/lib/libcuda.so', 'libcuda.dylib', 'cuda.dll')
-        for libname in libnames:
-            try:
-                cuda = ctypes.CDLL(libname)
-            except OSError:
-                continue
-            else:
-                break
-        else:
-            raise OSError("could not load any of: " + ' '.join(libnames))
-
-        self.nGpus = ctypes.c_int()
-        self.name = b' ' * 100
-        self.cc_major = ctypes.c_int()
-        self.cc_minor = ctypes.c_int()
-        self.cores = ctypes.c_int()
-        self.threads_per_core = ctypes.c_int()
-        self.clockrate = ctypes.c_int()
-        self.freeMem = ctypes.c_size_t()
-        self.totalMem = ctypes.c_size_t()
-        print(0)
-
-        #nvcc path
+from cuda import cuda
+from prettytable import PrettyTable, HEADER, NONE, SINGLE_BORDER
+from termcolor import colored
+
+from .cuda_driver import *
+from ctypes import *
+
+global_gpu_device = None
+
+class DeviceInfo:
+    def __init__(self, print_log=False):
+        # breakpoint()
+        self.nGpus = 0
+        self.name = ""
+        self.cc_major = 0
+        self.cc_minor = 0
+        self.cores = 0
+        self.thread_per_core = 0
+        self.gpu_clockrate = 0
+        self.memory_clockrate = 0
+        self.freeMem = 0
+        self.totalMem = 0
+
+        # Retrieving the path of nvcc
         self.nvcc_path = os.popen('which nvcc').read()[:-1]
         if not len(self.nvcc_path):
             self.nvcc_path = '/usr/local/cuda/bin/nvcc'
 
+        # Make sure to call cuInit(), otherwise we won't be
+        # able to make any CUDA Driver API calls
+        # cuInit(0)
 
-        device = ctypes.c_int()
+        # Getting the number of compatible GPU devices
+        err, self.nGpus = cuDeviceGetCount()    
 
-        cuda.cuInit(0)
-        print(1)
+        # Going to get certain parameters for only the first GPU
+        # i.e ordinal = 0 for cuDeviceGet
+        ordinal = 0
+        err, self.device = cuDeviceGet(ordinal)
+        # err, self.context = cuCtxCreate(0, self.device)
 
-        cuda.cuDeviceGetCount(ctypes.byref(self.nGpus))
-        print("Found %d device(s)." % self.nGpus.value)
-        #only count first GPU
-        for i in range(1):
-            cuda.cuDeviceGet(ctypes.byref(device), i)
+        err, device_name = cuDeviceGetName(25, self.device)
+        self.name = device_name.decode("utf-8")
 
-            cuda.cuDeviceGetName(ctypes.c_char_p(self.name), len(self.name), device)
-            cuda.cuDeviceComputeCapability(ctypes.byref(self.cc_major), ctypes.byref(self.cc_minor), device)
-            cuda.cuDeviceGetAttribute(ctypes.byref(self.cores), CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, device)
-            cuda.cuDeviceGetAttribute(ctypes.byref(self.threads_per_core), CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR, device)
-            cuda.cuDeviceGetAttribute(ctypes.byref(self.clockrate), CU_DEVICE_ATTRIBUTE_CLOCK_RATE, device)
-            cuda.cuDeviceGetAttribute(ctypes.byref(self.clockrate), CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE, device)
-            cuda.cuMemGetInfo(ctypes.byref(self.freeMem), ctypes.byref(self.totalMem))
-        print(2)
-
-    def log(self):
-        print("Number of device : %d" % (self.nGpus.value))
-        print("Name: %s" % (self.name.split(b'\0', 1)[0].decode()))
-        print("Compute Capability %d.%d" % (self.cc_major.value, self.cc_minor.value))
-        print("Multiprocessor : %d" %(self.cores.value)) 
-        print("Concurrent threads: %d" % (self.cores.value * self.threads_per_core.value))
-        print("GPU clock: %g MHz" % (self.clockrate.value / 1000.))
-        print("Memory clock: %g MHz" % (self.clockrate.value / 1000.))
-        print("Total Memory: %ld MiB" % (self.totalMem.value / 1024**2))
-        print("Free Memory: %ld MiB" % (self.freeMem.value / 1024**2))
+        err, self.cc_major = cuDeviceGetAttribute(COMPUTE_CAPABILITY_MAJOR, self.device)
+        err, self.cc_minor = cuDeviceGetAttribute(COMPUTE_CAPABILITY_MINOR, self.device)
+        err, self.cores = cuDeviceGetAttribute(MULTIPROCESSOR_COUNT, self.device)
+        err, self.thread_per_core = cuDeviceGetAttribute(MAX_THREAD_PER_MULTIPROCESSOR, self.device)
+        err, self.gpu_clockrate = cuDeviceGetAttribute(CLOCK_RATE, self.device)
+        err, self.memory_clockrate = cuDeviceGetAttribute(MEMORY_CLOCK_RATE, self.device)
+        err, self.freeMem, self.totalMem = cuMemGetInfo()
 
+        if print_log:
+            self.log()
 
+    def log(self):
 
-if __name__=="__main__":
-    device = deviceinfo()
-    device.log()
+        log_table = PrettyTable()
+        log_table.field_names = ["Device Property", "Value"]
+        log_table.align["Device Property"] = "r"
+        log_table.align["Value"] = "l"
+        log_table.set_style(SINGLE_BORDER)
+
+        log_table.border = True
+        log_table.hrules = HEADER
+        log_table.vrules = NONE
+
+        log_table.add_row(["Number of Devices", self.nGpus])
+        log_table.add_row(["Name", self.name])
+        log_table.add_row(["Compute Capability", str(self.cc_major) + "." + str(self.cc_minor)])
+        log_table.add_row(["Multiprocessor Count", self.cores])
+        log_table.add_row(["Concurrent Threads", self.cores*self.thread_per_core])
+        log_table.add_row(["GPU Clock", str(self.gpu_clockrate/1000) + colored(" MHz", "dark_grey")])
+        log_table.add_row(["Memory Clock", str(self.memory_clockrate/1000) + colored(" MHz", "dark_grey")])
+        log_table.add_row(["Total Memory", str(self.totalMem/1024**2) + colored(" MiB", "dark_grey")])
+        log_table.add_row(["Free Memory", str(self.freeMem/1024**2) + colored(" MiB", "dark_grey")])
+
+        print("\n")
+        print(log_table)
+        print(colored("\nNote: In case either Total Memory or Free Memory is showing 0\n      it is because no context has been loaded into device", "dark_grey"))
+        print("\n")
+
+# def get_global_gpu_device():
+#     if global_gpu_device == None:
+#         return DeviceInfo(print_log=False)
+#     return global_gpu_device
+
+# if __name__ == "__main__":
+#     device = DeviceInfo(print_log=True)
\ No newline at end of file
diff --git a/seastar/compiler/code_gen/kernel_context.py b/seastar/compiler/code_gen/kernel_context.py
index 791309b..9149ee2 100644
--- a/seastar/compiler/code_gen/kernel_context.py
+++ b/seastar/compiler/code_gen/kernel_context.py
@@ -40,7 +40,6 @@ def init_offset_cache(self):
                     else:
                         self.param_offset_init += init_stmt
                     self.offset_cache[offset_key] = '[' + offset_id + ']'
-        print('offset_cache:', self.offset_cache)
 
     def get_offset_key(self, var):
         return (tuple(var.var_shape), var.val_type)
diff --git a/seastar/compiler/code_gen/templates/fa/tpl_fa_pcsr.jinja b/seastar/compiler/code_gen/templates/fa/tpl_fa_pcsr.jinja
index 9285593..9142b0c 100644
--- a/seastar/compiler/code_gen/templates/fa/tpl_fa_pcsr.jinja
+++ b/seastar/compiler/code_gen/templates/fa/tpl_fa_pcsr.jinja
@@ -29,7 +29,10 @@ extern "C" __global__ void {{kernel_name}}
 
                 // PCSR indexes edges starting from 1
                 // Seastar requires edgs to be indexed from 0
-                {{index_type}} eid = __ldg(eids + e) - 1;
+                // {{index_type}} eid = __ldg(eids + e) - 1;
+
+                // New PCSR
+                {{index_type}} eid = __ldg(eids + e);
                 
                 {{init_inner_offset}}
                 
diff --git a/seastar/compiler/debugging/pretty_printers.py b/seastar/compiler/debugging/pretty_printers.py
deleted file mode 100644
index 19db89e..0000000
--- a/seastar/compiler/debugging/pretty_printers.py
+++ /dev/null
@@ -1,136 +0,0 @@
-from prettytable import PrettyTable
-# from prettytable import SINGLE_BORDER, ALL
-
-# TODO: This is manually taken from the prettytable page since there is an import error
-SINGLE_BORDER = 16
-ALL = 1
-
-from ..utils import ValType
-from ..node import CentralNode
-
-def get_val_type_name(val_type: ValType):
-    if val_type == ValType.S:
-        return "Source"
-    if val_type == ValType.D:
-        return "Destination"
-    if val_type == ValType.E:
-        return "Edge"
-    if val_type == ValType.P:
-        return "Parameter"
-
-def tensor_to_list(tensor):
-    tensor_list = []
-    for index, row in enumerate(tensor):
-        tensor_row = []
-        for _, element in enumerate(row):
-            tensor_row.append(element)
-        tensor_list.append(tensor_row)
-
-    return tensor_list
-
-def pretty_print_GIR(gir, gir_name):
-
-    gir_table = PrettyTable()
-    gir_table.set_style(SINGLE_BORDER)
-    gir_table.align = "l"
-    gir_table.hrules = ALL
-
-    gir_table.field_names = ["op_schema (Schema)", "ret (Var)", "op_name", "args (var)"]
-    gir_content_rows = []
-
-    print(f'\n🦋 Pretty Printing GIR - {gir_name}\n')
-
-    for gir_node in gir:
-        gir_row = []
-
-        gir_row.append(str(gir_node.op_schema)) # adding schema information
-        gir_row.append(str(gir_node.ret))       # adding ret information
-        gir_row.append(str(gir_node.op_name))   # adding operation name
-        gir_row.append(str(gir_node.print_stmt_args()))
-
-        gir_content_rows.append(gir_row)
-
-    gir_table.add_rows(gir_content_rows)
-    print(gir_table)
-
-def pretty_print_Central_Node(central_node: CentralNode, print_tensors=True):
-    print("\n🦋 Pretty Printing Central Node ----------------------------------------------------------\n")
-
-    # print(f'⏺️ Central Node : {central_node.get_address()}\n')
-
-    central_node_properties = list(central_node.__dict__.items())
-    neighbour_nodes_info = central_node_properties[:2]
-    edges_info = central_node_properties[2:4]
-    misc_info = central_node_properties[4:]
-
-    central_node_property_names = central_node.__dict__.keys()
-
-    # printing neighbouring nodes information
-    for info_name, info_value in neighbour_nodes_info:
-        print(f'🔵 {info_name} : {info_value}')
-
-    print("")
-
-    # printing edges information
-    for info_name, info_value in edges_info:
-        print(f'🟣 {info_name} : {info_value}')
-
-    print("")
-
-    # printing misc information
-
-    val_table = PrettyTable()
-    val_table.set_style(SINGLE_BORDER)
-    val_table.align = "l"
-    val_table.hrules = ALL
-
-    val_table.field_names = ["Val Name", "Val Type", "Backend", "Reduced Dimension", "_t", "ID", "_v","Var"]
-    val_table_content_rows = []
-    gir_list = []
-    tensor_info = {}
-
-    for info_name, info_value in misc_info:
-        val_row = []
-
-        tensor_info[info_name] = {}
-        tensor_info[info_name]['_t'] = info_value._t
-        tensor_info[info_name]['_v'] = info_value._v
-
-        val_row.append(info_name)
-        val_row.append(get_val_type_name(info_value._val_type))
-        val_row.append(info_value._Bkey)
-        val_row.append(info_value._reduce_dim)
-
-        val_row.append(info_value._t if print_tensors else "")
-
-        # _t = info_value._t
-        # _v = info_value._v
-        val_row.append(info_value._id)
-        val_row.append(info_value._v if print_tensors else "")
-        val_row.append(info_value.var)
-        gir_list.append((info_name, info_value.fprog))
-
-        val_table_content_rows.append(val_row)
-
-    val_table.add_rows(val_table_content_rows)
-    print(val_table)
-
-    # print(tensor_info)
-
-    # for info_name, tensor_vals in tensor_info.items():
-    #     print(f'🏹 Tensor information for {info_name}\n')
-    #     _t, _v = tensor_vals.get("_t", None), tensor_vals.get("_v", None)
-    #     print(f'_t = {_t}\n')
-    #     print(f'_v = {_v}\n')
-
-    for gir_name, gir in gir_list:
-        pretty_print_GIR(gir, gir_name)
-
-    print("\n------------------------------------------------------------------------------------------\n")
-    # print("In Neighbouring Nodes:\n")
-    # for in_nodes in central_node.innbs:
-    #     print(in_nodes)
-
-    # print("\nOut Neighbouring Nodes:\n")
-    # for out_nodes in central_node.outnbs:
-    #     print(out_nodes)
\ No newline at end of file
diff --git a/seastar/compiler/debugging/print_variables.py b/seastar/compiler/debugging/print_variables.py
new file mode 100644
index 0000000..525399b
--- /dev/null
+++ b/seastar/compiler/debugging/print_variables.py
@@ -0,0 +1,2 @@
+global is_print_verbose_log
+is_print_verbose_log = False
\ No newline at end of file
diff --git a/seastar/compiler/debugging/seastar_logger.py b/seastar/compiler/debugging/seastar_logger.py
new file mode 100644
index 0000000..3f1b0e7
--- /dev/null
+++ b/seastar/compiler/debugging/seastar_logger.py
@@ -0,0 +1,15 @@
+from prettytable import PrettyTable
+# from prettytable import SINGLE_BORDER, ALL
+
+# TODO: This is manually taken from the prettytable page since there is an import error
+SINGLE_BORDER = 16
+ALL = 1
+
+from rich.console import Console
+console = Console()
+
+import seastar.compiler.debugging.print_variables as print_var
+
+def print_log(log_message):
+    if print_var.is_print_verbose_log:
+        console.log(log_message)
\ No newline at end of file
diff --git a/seastar/compiler/execution_unit.py b/seastar/compiler/execution_unit.py
index 77a107d..a451325 100644
--- a/seastar/compiler/execution_unit.py
+++ b/seastar/compiler/execution_unit.py
@@ -3,6 +3,9 @@
 from .code_gen.cuda_driver import *
 from .code_gen.kernel_context import KernelContext, LinearizedKernelContext
 from .utils import is_const_scalar, ParallelMode, MAX_THREAD_PER_BLOCK, MAX_BLOCK 
+from .code_gen.cuda_error import ASSERT_DRV
+
+from seastar.compiler.debugging.seastar_logger import print_log
 
 # TODO: remove
 import numpy as np
@@ -165,7 +168,6 @@ def compute_tile_sizes(self, blockDimx, blockDimy):
             x1, x2 = self.nearest_pow2(math.sqrt(cof*n/N_bw))
             t1 = N_Ar * n/x1 + N_bw * x1
             t2 = N_Ar * n/x2 + N_bw * x2
-            print('option1:',x1, 'sectors:',t1, 'option2:',x2, 'sectors:', t2)
             tile_sizex = x1 if t1 <= t2 else t2
         else:
             tile_sizex = WARP_SIZE if WARP_SIZE < blockDimx else blockDimx
@@ -174,7 +176,6 @@ def compute_tile_sizes(self, blockDimx, blockDimy):
         while tile_sizey > blockDimy:
             tile_sizey = tile_sizey / 2
             tile_sizex = tile_sizex * 2
-        print('In compute tiling', N_Ar, N_Aw, N_br, N_bw, self.kernel_args(), 'tx:', tile_sizex, 'ty', tile_sizey)
         return [int(tile_sizex), int(tile_sizey)]
         
     def nearest_pow2(self, targ):
@@ -256,11 +257,11 @@ def prepare_compiled_kernel(self, graph, compiled_module):
         num_nodes = graph.get_num_nodes()
         if self.use_fa_tmpl():
             launch_config = self.calculate_kernel_params_fa(num_nodes)
-            print('template name:', self._template_name, 'number of nodes:', num_nodes, 'launch_config', launch_config)
+            print_log(f'[yellow bold]Execution Unit[/yellow bold]:  Generating FA Kernel with num_nodes: {str(num_nodes)}, launch_config: {str(launch_config)}')
             self._K = FeatureAdaptiveKernel(num_nodes, row_offsets_ptr, col_indices_ptr, eids_ptr, max_dims, self._kernel_name, compiled_module, launch_config)
         else:
             launch_config, tile_sizes = self.calculate_kernel_params(num_nodes)
-            print('template name:', self._template_name, 'number of nodes:', num_nodes, 'launch_config', launch_config, 'tile_sizes', tile_sizes, 'max_dims', self.max_dims())
+            print_log(f'[yellow bold]Execution Unit[/yellow bold]:  Generating V2 Kernel with num_nodes: {str(num_nodes)}, launch_config: {str(launch_config)}, tile_size: {str(tile_sizes)}, max_dims: {str(max_dims)}')
             self._K = V2Kernel(num_nodes, row_offsets_ptr, col_indices_ptr, eids_ptr, max_dims, self._kernel_name, compiled_module, launch_config, tile_sizes)
 
     def reset_graph_info(self, graph):
@@ -362,22 +363,23 @@ def run(self, tensor_list):
                                  self.launch_config[3],
                                  self.launch_config[4],
                                  self.launch_config[5],
-                                 0, None, params, None)
+                                 0, None, params, 0)
             
-            if ret:
-                raise Exception('cuLaunchKernel', ret)
+            ASSERT_DRV(ret)
         except Exception as e:
             raise e
+        
+        # sync_ret = cudaDeviceSynchronize()
+        # if ret:
+        #     raise Exception('cudaDeviceSynchronize', sync_ret)
 
 class V2Kernel(Kernel):
     def __init__(self, num_nodes, row_offsets_ptr, col_indices_ptr, eids_ptr, max_dims, kernel_name, compiled_module, launch_config, tile_sizes):
         self.scalar_args = [c_int(num_nodes), c_int(max_dims[1]), c_int(max_dims[0]), c_int(tile_sizes[0]), c_int(tile_sizes[1])]
         self.const_kernel_args =  [c_void_p(row_offsets_ptr), c_void_p(eids_ptr), c_void_p(col_indices_ptr)] + self.scalar_args
         self.const_kernel_ptrs = [c_void_p(addressof(v)) for v in self.const_kernel_args]
-        self.K = c_void_p(0)
-        ret = cuModuleGetFunction(byref(self.K), compiled_module, c_char_p(kernel_name.encode()))
-        if ret:
-            raise Exception('cuModuleGetFunction', ret)
+        ret, self.K = cuModuleGetFunction(compiled_module, kernel_name.encode())
+        ASSERT_DRV(ret)
         self.launch_config = launch_config[0],launch_config[1], 1, launch_config[2], launch_config[3],1
 
 class FeatureAdaptiveKernel(Kernel):
@@ -386,8 +388,6 @@ def __init__(self, num_nodes, row_offsets_ptr, col_indices_ptr, eids_ptr, max_di
         self.const_kernel_args =  [c_void_p(row_offsets_ptr), c_void_p(eids_ptr), c_void_p(col_indices_ptr)] + self.scalar_args
         self.const_kernel_ptrs = [c_void_p(addressof(v)) for v in self.const_kernel_args]
 
-        self.K = c_void_p(0)
-        ret = cuModuleGetFunction(byref(self.K), compiled_module, c_char_p(kernel_name.encode()))
-        if ret:
-            raise Exception('cuModuleGetFunction', ret)
+        ret, self.K = cuModuleGetFunction( compiled_module, kernel_name.encode())
+        ASSERT_DRV(ret)
         self.launch_config = launch_config[0],1,1,launch_config[1],1,1
\ No newline at end of file
diff --git a/seastar/compiler/executor.py b/seastar/compiler/executor.py
index ea76e7a..2b5e70f 100644
--- a/seastar/compiler/executor.py
+++ b/seastar/compiler/executor.py
@@ -2,6 +2,7 @@
 import snoop
 from collections import deque
 from ..graph.dynamic.DynamicGraph import DynamicGraph
+from seastar.compiler.debugging.seastar_logger import print_log
 
 class Stack:
     def __init__(self, val=None):
@@ -186,12 +187,11 @@ def construct_backward_mappping(self, funits, bunits):
                         for bu in bunits:
                             if arg._grad in bu.unit_rets():
                                 ret[arg] = bu
-        for k, v in ret.items():
-            print('k', k, 'v', v.kernel_name)
         return ret
     
     def merge_units(self, exec_units):
-        print('start merging', len(exec_units))
+        print_log("[green bold]Executor[/green bold]: Start merging units")
+        
         assert len(exec_units) > 0, 'Error: empty exec units'
         grouped_unit = [MergedUnit([exec_units[0]])]
         for i in range(1, len(exec_units)):
@@ -199,7 +199,8 @@ def merge_units(self, exec_units):
                 grouped_unit[-1].append(exec_units[i])
             else:
                 grouped_unit.append(MergedUnit([exec_units[i]]))
-        print('merged units', len(grouped_unit), grouped_unit)
+        
+        print_log("[green bold]Executor[/green bold]: Units merging completed")
         return grouped_unit
   
     def restart(self, input_map, graph=None):
diff --git a/seastar/compiler/op.py b/seastar/compiler/op.py
index 8f9abcd..f57aa72 100644
--- a/seastar/compiler/op.py
+++ b/seastar/compiler/op.py
@@ -26,7 +26,6 @@ def __call__(self, *args, **kargs):
         try:
             ret = self._op(*tuple(arg.v for arg in args), **kargs)
         except Exception as e:
-            print("Exception:", str(self), 'e', e, 'args:', args, 'kargs', kargs)
             raise e
         if isinstance(ret, tuple) or isinstance(ret, list):
             raise NotImplementedError("Ops that return multiple tensors are not supported op:", str(self), 'ret:', ret)
@@ -57,7 +56,6 @@ def to_schema(self):
 class TorchOp(Op):
     def to_schema(self):
         if 'module' in str(type(self._op)):
-            print('here', type(self._op).__name__)
             return Schema(type(self._op).__name__.split('.')[-1], **{key: val for key, val in self._op.__dict__.items() if not key.startswith('_')})
         elif 'builtin_function_or_method' in str(type(self._op)):
             return Schema(str(self._op).split()[2])
diff --git a/seastar/compiler/passes/cf.py b/seastar/compiler/passes/cf.py
index 5eb9a34..ec4d9ff 100644
--- a/seastar/compiler/passes/cf.py
+++ b/seastar/compiler/passes/cf.py
@@ -26,7 +26,5 @@ def CF(prog):
                 if arg == 1:
                     has_one = i
             if has_one != -1:
-                print('CF: remove', cur_stmt)
                 cur_stmt.ret.replace_all_uses_with(cur_stmt.args[1-i], propogate_shape=False)
-                cur_stmt.remove_cur()
-    print('after cf program becomes:', prog)
\ No newline at end of file
+                cur_stmt.remove_cur()
\ No newline at end of file
diff --git a/seastar/compiler/passes/cse.py b/seastar/compiler/passes/cse.py
index bb7cc03..43a35ce 100644
--- a/seastar/compiler/passes/cse.py
+++ b/seastar/compiler/passes/cse.py
@@ -9,9 +9,7 @@ def CSE(prog):
         if info in s_map:
             ret_var = s_map[info]
             if stmt.ret:
-                print("CSE: replacing" ,stmt.ret, 'with', ret_var, 'in', info, stmt)
                 stmt.ret.replace_all_uses_with(ret_var, propogate_shape=False)
             stmt.remove_cur()
         else:
-            s_map[info] = stmt.ret
-    print('After apply CSE', prog)
\ No newline at end of file
+            s_map[info] = stmt.ret
\ No newline at end of file
diff --git a/seastar/compiler/passes/dce.py b/seastar/compiler/passes/dce.py
index 1a4b498..33a509f 100644
--- a/seastar/compiler/passes/dce.py
+++ b/seastar/compiler/passes/dce.py
@@ -6,5 +6,4 @@ def DCE(prog, output_vars):
     '''
     for s in reversed(prog): 
         if len(s.ret.users) == 0 and s.ret not in output_vars:
-            s.remove_cur()
-    print('After DCE programs becomes:', prog)
\ No newline at end of file
+            s.remove_cur()
\ No newline at end of file
diff --git a/seastar/compiler/passes/dependency_analysis.py b/seastar/compiler/passes/dependency_analysis.py
index 07d6e33..0c81254 100644
--- a/seastar/compiler/passes/dependency_analysis.py
+++ b/seastar/compiler/passes/dependency_analysis.py
@@ -3,7 +3,6 @@
 from ..utils import is_const_scalar
 
 def dep_program(target_var, stopping_vars):
-    print('dep_program', stopping_vars)
     '''
         Step1: Analyze the part of the program that computes a "target_var".
         Recursively 1. find the stmt that creates the current var 2. find the
@@ -33,6 +32,5 @@ def dep_program(target_var, stopping_vars):
     for stmt in prog:
         for var in stmt.args:
             if var in stopping_vars:
-                print('setting ', var, 'stmt to be None')
                 var.stmt = None # Stopping further propogation
     return prog
\ No newline at end of file
diff --git a/seastar/compiler/passes/fusion.py b/seastar/compiler/passes/fusion.py
index d2c9a26..769c7cc 100644
--- a/seastar/compiler/passes/fusion.py
+++ b/seastar/compiler/passes/fusion.py
@@ -8,6 +8,8 @@
 from .dependency_analysis import dep_program
 from datetime import datetime
 
+from seastar.compiler.debugging.seastar_logger import print_log
+
 class FusionStateMachine():
     state_trans = {
         0 :  { 'a2d' : 1, 'a2s' : 1, 'd' : 3, 's' : 5 , 'e' : 1},
@@ -24,7 +26,6 @@ def __init__(self, init_stmt=None):
     
     def accept(self, stmt):
         trans = FusionStateMachine.stmt_to_trans(stmt)
-        print('cur', self.cur, 'trans', trans, stmt, 'transitionable?', trans in FusionStateMachine.state_trans[self.cur])
         return trans in FusionStateMachine.state_trans[self.cur]
 
     def advance(self, stmt):
@@ -71,7 +72,6 @@ def merge_program(prog_list):
             if mergable(prog_list[i], prog_list[j]):
                 share_input_map[i].add(j)
 
-    print('merge_program', share_input_map)
     merged_set = set()
     ret_list = []    
     for i, shared_set in share_input_map.items():
@@ -121,12 +121,10 @@ def merge_stmt(cur_stmt, p_stmt, stmt2fused_prog, stmt2state_machine, stmt_stack
         stmt_stack.append(p_stmt)
         return
     if fusable(cur_stmt, p_stmt, stmt2state_machine, new_fsm):
-        print('fusable', cur_stmt, 'current state:', stmt2state_machine[cur_stmt].cur, 'with p_stmt:', p_stmt, 'new_fsm', new_fsm)
         if p_stmt in stmt2fused_prog:
             if cur_stmt in stmt2fused_prog:
                 cur_prog = stmt2fused_prog[cur_stmt]
                 if stmt2fused_prog[p_stmt] == cur_prog:
-                    print('cur_stmt is already in prog')
                     return
                 stmt2fused_prog[p_stmt].copy_append_prog(cur_prog)
                 for stmt in cur_prog:
@@ -147,7 +145,6 @@ def merge_stmt(cur_stmt, p_stmt, stmt2fused_prog, stmt2state_machine, stmt_stack
                 prog_list.append(var_prog)
             stmt_stack.append(p_stmt)
     else:
-        print('not fusable', cur_stmt, 'current state:', stmt2state_machine[cur_stmt].cur, 'with p_stmt:', p_stmt, 'new_fsm', new_fsm)
         if cur_stmt not in stmt2fused_prog:
             var_prog = Program()
             var_prog.copy_append_stmt(cur_stmt)
@@ -172,14 +169,12 @@ def merge_independent(exec_units):
     # Merge candidates
     merged_units = []
     merged_set = set()
-    print('merge independent', candidates)
     for tar_id, src_id_list in candidates.items():
         if tar_id in merged_set:
             continue
         tar_unit = exec_units[tar_id]
         for sid in src_id_list:
             src_unit = exec_units[sid]
-            print('src-dst program:', src_unit, tar_unit)
             tar_unit.merge_with_independent_unit(src_unit)
             merged_set.add(sid)
         merged_units.append(tar_unit)
@@ -194,10 +189,11 @@ def fuse(progs, outputs):
         return progs
     
     if len(progs) > 1:
-        print('-----Program Fusion------')
+        print_log("[orange1 bold]Fusion[/orange1 bold]: Program fusion started")
         progs = merge_program(progs)
+        print_log("[orange1 bold]Fusion[/orange1 bold]: Program fusion completed")
 
-    print('-----Operator Fusion------')
+    print_log("[orange1 bold]Fusion[/orange1 bold]: Operator fusion started")
     # Starting from each output var, fuse as many operators as possible according to dependenies.
     # Use DFS-manner to allow maximal locality of statements
     stmt2fused_prog = {}
@@ -210,7 +206,7 @@ def fuse(progs, outputs):
             var_list.append(ret)
     var_list.sort(key=lambda var: var.int_id)
     var_stack = deque(var_list)
-    print('sorted var_list', var_list, 'var_stack', var_stack)
+    
     while var_stack:
         var = var_stack.pop()
         stmt_stack = deque([var.stmt])
@@ -229,7 +225,6 @@ def fuse(progs, outputs):
                 continue
             if cur_stmt not in stmt2state_machine:
                 stmt2state_machine[cur_stmt] = FusionStateMachine(cur_stmt)
-            print('cur_stmt', cur_stmt, 'current state', stmt2state_machine[cur_stmt].cur, 'dep_sttms', dep_stmts)
             if len(dep_stmts) == 1:
                 p_stmt = dep_stmts[0]
                 merge_stmt(cur_stmt, p_stmt, stmt2fused_prog, stmt2state_machine, stmt_stack, var_stack, prog_list, new_fsm=False)
@@ -245,16 +240,19 @@ def fuse(progs, outputs):
                     merge_stmt(cur_stmt, l_stmt, stmt2fused_prog, stmt2state_machine, stmt_stack, var_stack, prog_list, new_fsm=True)
             else:
                 raise NotImplementedError('Currenty we assume num of oprands of all operators is no larger than 2')
+            
+    print_log("[orange1 bold]Fusion[/orange1 bold]: Operator fusion completed")
+
     prog_l = []
     for p in prog_list:
         prog = Program()
         prog.copy_append_stmts(sorted(p, key=lambda x : x.ret.int_id))
         prog_l.append(prog)
-        print(prog)
-
+    
     prog_blks = [prog for prog in reversed(prog_l) if len(prog) > 0] 
 
-    print('------Construct Execution Unit------')
+    print_log("[orange1 bold]Fusion[/orange1 bold]: Constructing execution unit")
+
     exe_units = []
     for prog in prog_blks:
         args = set()
@@ -302,4 +300,7 @@ def fuse(progs, outputs):
     # Correctness remains quesationable
     exe_units.sort(key=lambda x:x.max_ret_id())
     exe_units = merge_independent(exe_units)
+    
+    print_log("[orange1 bold]Fusion[/orange1 bold]: Constructing execution unit completed")
+    
     return exe_units
diff --git a/seastar/compiler/passes/mem_planning.py b/seastar/compiler/passes/mem_planning.py
index 2754bf1..2ff6391 100644
--- a/seastar/compiler/passes/mem_planning.py
+++ b/seastar/compiler/passes/mem_planning.py
@@ -3,6 +3,8 @@
 from ..program import Program
 from ..utils import is_const_scalar
 
+from seastar.compiler.debugging.seastar_logger import print_log
+
 def mem_planning(funits, BProg, grad_map, grads):
     ''' 
         Conduct memory planning by considering both FProg and BProg
@@ -11,20 +13,18 @@ def mem_planning(funits, BProg, grad_map, grads):
         Add cost-model to choose a different materialized vars
     '''
 
-    print('Vars that need gradient and their gradients:', grad_map)
     materialized_vars = set(grads)
     for unit in funits:
         materialized_vars = materialized_vars.union(unit.materilized_vars())
-    print('Materialized vars in forward units', materialized_vars)
-
 
-    print('\n------------Autodiff: Peephole Optimization-----------\n')
+    print_log("[red bold]Peephole[/red bold]: Starting Peephole optimization")
 
     bp_list = []
     for var, grad in grad_map.items():
         bp_list.append(dep_program(grad, stopping_vars=materialized_vars))
-        print('backward program for', var, bp_list[-1])
 
     for bp in bp_list:
         PH(bp, materialized_vars, set([grad_map[key] for key in grad_map]))
+        
+    print_log("[red bold]Peephole[/red bold]: Peephole optimization completed")
     return bp_list
\ No newline at end of file
diff --git a/seastar/compiler/passes/peephole.py b/seastar/compiler/passes/peephole.py
index 0f40101..da18e01 100644
--- a/seastar/compiler/passes/peephole.py
+++ b/seastar/compiler/passes/peephole.py
@@ -8,6 +8,8 @@
 from ..program import Stmt, Var
 from ..schema import Schema
 
+from seastar.compiler.debugging.seastar_logger import print_log
+
 def execute_sym_program(prog, sym_table, rmv_list):
     for s in prog:
         if 'mul' in s.op_name.lower():
@@ -27,11 +29,10 @@ def execute_sym_program(prog, sym_table, rmv_list):
             l = sym_table[s.args[0].id] if not is_const_scalar(s.args[0]) else s.args[0] 
             sym_table[s.ret.id] = l
         else:
-            print('early stopping due to encounter', s)
+            print_log(f'[red bold]Peephole[/red bold]: Early stopping due to enouncter {str(s)}')
             break
 
 def generate_stmts_from_expr(expr, var_table):
-    print('var_table', var_table)
 
     preceding_neg = False
     if expr[0] == '-':
@@ -44,7 +45,6 @@ def generate_stmts_from_expr(expr, var_table):
         arg0 = var_table[tok_q.popleft()]
         op = tok_q.popleft()
         arg1 = var_table[tok_q.popleft()]
-        print('to create stmt for', arg0, op, arg1)
         if op == '*':
             op_name = 'Mul'
         elif op == '/':
@@ -52,7 +52,6 @@ def generate_stmts_from_expr(expr, var_table):
         else:
             raise NotImplementedError('op', op, 'is not supprted for PH optimization')
         stmt_list.append(Stmt.create_binary_bcast_stmt(Schema(op_name), args=[arg0, arg1]))
-        print('after create binary_bcast stmt', arg1)
         ret = stmt_list[-1].ret
         var_table[ret.id] = ret
         tok_q.appendleft(ret.id)
@@ -77,9 +76,7 @@ def sum_propogation(sum_stmt):
             old_arg = stmt.args[i]
             if old_arg == ret:
                 # Replace sum ret with sum arg
-                print(old_arg, 'is replaced with', arg, 'in stmt', stmt)
                 stmt.args[i] = arg
-                print('after replacement', stmt)
                 shape_propogation(stmt)
 
 def PH(BProg, known_vars, output_vars):
@@ -89,7 +86,6 @@ def PH(BProg, known_vars, output_vars):
         by applying various mathmatically equivelent tansformations.
     '''
     # Find candidate chain breaker;
-    print('Before PH', BProg)
     candidate_vars = set()
     var_table = {}
     for s in BProg:
@@ -112,7 +108,6 @@ def PH(BProg, known_vars, output_vars):
         dep_prog = dep_program(var, known_vars)
         sum_map[var.id] = []
         execute_sym_program(dep_prog, sym_table, sum_map[var.id])
-    print('candidate_vars', candidate_vars)
 
     simplifiable_expression = []
     for cv in candidate_vars: 
@@ -122,24 +117,18 @@ def PH(BProg, known_vars, output_vars):
                 var2 = sym_table[kv.id]
                 var3 = var1/var2
                 if 'mul' in str(type(var3)).lower() and str(var3).count(var_prefix) < str(var1).count(var_prefix) - 1:
-                    print(var1, '/', var2, '=', var3)
                     simplifiable_expression.append((cv.id, str(var3) +'*'+kv.id, sum_map[cv.id]))
-    print('simplifiable expr:', simplifiable_expression)
     for tu in simplifiable_expression:
         target_var = var_table[tu[0]]
         stmts = generate_stmts_from_expr(tu[1], var_table)
         BProg.insert_stmts_before(target_var.stmt, stmts)
         # Replace last op
         target_var.replace_all_uses_with(stmts[-1].ret, propogate_shape=False)
-        print('new prog after inserting generated stmts', BProg)
         DCE(BProg, output_vars)
         for s in tu[2]:
             for st in BProg:
                 if st == s:
-                    print('trying to remove', st)
                     # Omit the last stmt as it's already be removed
-                    print('s.ret.users', st.ret.users)
                     sum_propogation(st)
                     st.remove_cur()
         BProg.resort_vars()
-        print('Final bprog:', BProg)
diff --git a/seastar/compiler/program.py b/seastar/compiler/program.py
index 3a0fa33..d1a7ec2 100644
--- a/seastar/compiler/program.py
+++ b/seastar/compiler/program.py
@@ -456,7 +456,6 @@ def copy_prepend_prog(self, other_prog):
 
     def insert_stmts_before(self, stmt, stmts_list):
         for s in reversed(stmts_list):
-            print('current s', s, 'current stmt', stmt)
             stmt.insert_before(s)
             stmt = s
 
diff --git a/seastar/compiler/registry.py b/seastar/compiler/registry.py
index 93a2ed2..2537cc5 100644
--- a/seastar/compiler/registry.py
+++ b/seastar/compiler/registry.py
@@ -4,6 +4,8 @@
 from .utils import ValType,is_const_scalar, WriteType, WriteLocation, infer_val_type
 from .schema import Schema
 
+from seastar.compiler.debugging.seastar_logger import print_log
+
 GradInfo = namedtuple('GradInfo', ['targ', 'args', 'grad_x', 'op_schema'])
 
 impl_registry = {}
@@ -28,7 +30,7 @@ def look_up_registry(stmt):
         # We don't necessarily generate ops for Node-wise op as they can be 
         # supported by backends
         if stmt.is_edgewise() and op_name not in cb_registry:
-            print('Warnning: EdgeType op' + op_name + ' is not registered in any registry!')
+            print_log(f'[magenta bold]Registry[/magenta bold]: EdgeType op {op_name} is not registered in any registry!')
     else:
         op_impl = impl_registry[op_name]
     return op_impl
@@ -144,7 +146,6 @@ def multiply_grad(self, dzdy, dydx, x):
                                                                                        val_type=infer_val_type([dzdy, dydx]),
                                                                                        device=dzdy.device))]
         if x.var_shape != max_dim:
-            print("Inconsistent shape between x and its gradient")
             if len(x.var_shape) != len(max_dim):
                 raise NotImplementedError('Multiply grad has not supported input and gradient that have different dimension')
             diff_dim = -1
@@ -408,7 +409,7 @@ def register_ops():
     for name, obj in inspect.getmembers(sys.modules[__name__]):
         if inspect.isclass(obj) and name.endswith('Op'):
             key = name.split('Op')[0].lower()
-            print('Registering', name, 'with key', key)
+            print_log(f'[magenta bold]Registry[/magenta bold]: Registering {name} with key {key}')
             impl_registry[key] = obj
 
 register_ops()
diff --git a/seastar/compiler/seastar.py b/seastar/compiler/seastar.py
index 702ea3b..34a9986 100644
--- a/seastar/compiler/seastar.py
+++ b/seastar/compiler/seastar.py
@@ -12,7 +12,7 @@
 from .code_gen import code_gen 
 from .executor import Executor
 from .utils import var_prefix, cen_attr_postfix, inb_attr_postfix
-from .debugging.pretty_printers import pretty_print_GIR, pretty_print_Central_Node
+
 import snoop
 
 
@@ -195,7 +195,6 @@ def compile(self, nspace, hetero_graph=False):
         def wrapper(func):
             if not func.__name__ in self._ctx_map:
                 if not hetero_graph:
-                    print('create context:', self._ctx_map)
                     self._ctx_map[func.__name__] = Context(func, nspace, self._run_cb)
                 else:
                     raise NotImplementedError('Heterogeneous graph is not supported yet')
diff --git a/seastar/compiler/val.py b/seastar/compiler/val.py
index c9c8001..d2b996c 100644
--- a/seastar/compiler/val.py
+++ b/seastar/compiler/val.py
@@ -131,7 +131,6 @@ def __init__(self, backend, tensor, val_type, id, fprog, reduce_dim):
             self._v = self._t.clone().detach().requires_grad_(False).mean(dim=0)
         else:
             self._v = self._t.clone().detach().requires_grad_(False)
-        print('in torch val constructor:', tensor.requires_grad, 'vid', id, ' requires_grad?', self._v.requires_grad)
         self.var = Var.create_var(self.size, self.dtype, self.val_type, var_id = self._id, device=self._t.device, requires_grad=self._t.requires_grad) 
         self.fprog = fprog
 
diff --git a/seastar/dataset/EnglandCOVID.py b/seastar/dataset/EnglandCOVID.py
index baa1993..6ca8013 100644
--- a/seastar/dataset/EnglandCOVID.py
+++ b/seastar/dataset/EnglandCOVID.py
@@ -43,6 +43,10 @@ def _load_dataset(self) -> None:
         if self._verbose:
             console.log(f"Downloading [cyan]{self.name}[/cyan] dataset")
         self._dataset = json.loads(urllib.request.urlopen(self._url_path).read())
+        
+        # THIS NEEDS TO BE EDITED
+        # with open('../../dataset/eng_covid/eng_covid.json', 'w') as f:
+        #     json.dump(self._dataset,f)
 
     def _get_edge_info(self):
         # getting the edge_list and edge_weights
diff --git a/seastar/dataset/SoorahBase.py b/seastar/dataset/SoorahBase.py
index ce14557..73dd06d 100644
--- a/seastar/dataset/SoorahBase.py
+++ b/seastar/dataset/SoorahBase.py
@@ -12,8 +12,8 @@
 
 console = Console()
 
-from rich.traceback import install
-install(show_locals=True)
+# from rich.traceback import install
+# install(show_locals=True)
 
 class SoorahBase:
     def __init__(self, dataset_name ,verbose: bool = False, lags: int = 8, split=0.75, for_seastar= False) -> None:
@@ -26,7 +26,7 @@ def __init__(self, dataset_name ,verbose: bool = False, lags: int = 8, split=0.7
         self._max_num_nodes = 0
 
         self._local_path = f'../../dataset/{dataset_name}/{dataset_name}.json'
-        self._url_path = None
+        self._url_path = "https://raw.githubusercontent.com/benedekrozemberczki/pytorch_geometric_temporal/master/dataset/england_covid.json"
         self._verbose = verbose
 
         self._load_dataset()
@@ -44,15 +44,23 @@ def get_graph_data(self):
 
     def _load_dataset(self) -> None:
         # loading the dataset by downloading them online
-        if self._verbose:
-            console.log(f"Downloading [cyan]{self.name}[/cyan] dataset")
+        # if self._verbose:
+        #     console.log(f"Downloading [cyan]{self.name}[/cyan] dataset")
         
         # for online download
-        # self._dataset = json.loads(urllib.request.urlopen(self._url_path).read())
+        if os.path.exists(self._local_path):
+            dataset_file = open(self._local_path)
+            self._dataset = json.load(dataset_file)
+            if self._verbose:
+                console.log(f"Loading [cyan]{self.name}[/cyan] dataset from dataset/")
+        else:
+            if self._verbose:
+                console.log("Downloading [cyan]England Covid[/cyan] dataset")
+            self._dataset = json.loads(urllib.request.urlopen(self._url_path).read())
         
         # for local
-        dataset_file = open(self._local_path)
-        self._dataset = json.load(dataset_file)
+        # dataset_file = open(self._local_path)
+        # self._dataset = json.load(dataset_file)
 
     def _get_edge_info_seastar(self):
         # getting the edge_list and edge_weights
@@ -136,6 +144,9 @@ def _presort_edge_weights(self):
 
         self._edge_list = final_edges_lst
         self._edge_weights = final_edge_weights_lst
+        
+        print("🍎🍎🍎 Edge Weights")
+        # print(self._edge_weights)
 
     def get_edges(self):
         return self._edge_list
diff --git a/seastar/graph/README.md b/seastar/graph/README.md
new file mode 100644
index 0000000..4fce11f
--- /dev/null
+++ b/seastar/graph/README.md
@@ -0,0 +1,17 @@
+# Seastar Graph Abstraction
+
+## Building the Graphs
+
+The graphs' logic is implemented in a CUDA file, which requires compilation using nvcc to generate a shared object (.so) file. This file is then utilized by the Python-based graph abstraction. To compile these files, execute the provided shell scripts.
+
+### Dynamic Graphs
+
+1. Grant execution permissions to the script by executing `chmod u+x build_dynamic.sh`. This step should be performed whenever changes are made to the shell script.
+
+2. Run the script as shown : `./build_dynamic.sh [graph_names]`. For instance: `./build_dynamic.sh gpma pcsr`
+
+### Static Graphs
+
+1. Grant execution permissions to the script by executing `chmod u+x build_static.sh`. This step should be performed whenever changes are made to the shell script.
+
+2. Run the script as shown : `./build_static.sh`.
\ No newline at end of file
diff --git a/seastar/graph/build_dynamic.sh b/seastar/graph/build_dynamic.sh
new file mode 100644
index 0000000..54cea85
--- /dev/null
+++ b/seastar/graph/build_dynamic.sh
@@ -0,0 +1,12 @@
+echo "🏗️ Building Seastar Dynamic Graphs"
+echo " "
+
+for graph in "$@"
+do
+    echo "🔨 Building $graph"
+    cd dynamic/$graph/
+    /usr/local/cuda-11.7/bin/nvcc $(python3 -m pybind11 --includes) -shared -rdc=true --compiler-options '-fPIC' -D__CDPRT_SUPPRESS_SYNC_DEPRECATION_WARNING -o $graph.so $graph.cu
+    echo "✅ $graph build completed"
+    echo ""
+    cd ../..
+done
\ No newline at end of file
diff --git a/seastar/graph/build_static.sh b/seastar/graph/build_static.sh
new file mode 100644
index 0000000..4976a9f
--- /dev/null
+++ b/seastar/graph/build_static.sh
@@ -0,0 +1,6 @@
+echo "🔨 Building csr"
+cd static/
+/usr/local/cuda-11.7/bin/nvcc $(python3 -m pybind11 --includes) -shared -rdc=true --compiler-options '-fPIC' -D__CDPRT_SUPPRESS_SYNC_DEPRECATION_WARNING -o csr.so csr.cu
+echo "✅ csr build completed"
+cd ..
+echo ""
\ No newline at end of file
diff --git a/seastar/graph/dynamic/DynamicGraph.py b/seastar/graph/dynamic/DynamicGraph.py
index d127b0e..3f82ed8 100644
--- a/seastar/graph/dynamic/DynamicGraph.py
+++ b/seastar/graph/dynamic/DynamicGraph.py
@@ -41,13 +41,21 @@ def _preprocess_graph_structure(self, edge_list):
         
         graph_attr = self._get_graph_attr(edge_list)
         
-        tmp_set = set()
+        max_num_nodes = 0
         for i in range(len(edge_list)):
-            tmp_set = set()
             for j in range(len(edge_list[i])):
-                tmp_set.add(edge_list[i][j][0])
-                tmp_set.add(edge_list[i][j][1])
-        self.max_num_nodes = len(tmp_set)
+                max_num_nodes = max(max_num_nodes,edge_list[i][j][0],edge_list[i][j][1])
+        self.max_num_nodes = max_num_nodes + 1
+        
+        # SINCE THE CONCEPT OF NODE IDS HASNT BEEN IMPLEMENTED
+        # WE CANT USE THIS (AS in consider the case where nodes are labelled 1,2,3)
+        # What hapens to 0, Our system right now cant map each node to an ID
+        # tmp_set = set()
+        # for i in range(len(edge_list)):
+        #     for j in range(len(edge_list[i])):
+        #         tmp_set.add(edge_list[i][j][0])
+        #         tmp_set.add(edge_list[i][j][1])
+        # self.max_num_nodes = len(tmp_set)
 
         edge_dict = {}
         for i in range(len(edge_list)):
@@ -72,7 +80,7 @@ def _preprocess_graph_structure(self, edge_list):
             }
         
     def get_graph(self, timestamp: int):
-
+        # print("💄💄💄 Get_graph (forward) called",flush=True)
         self._is_backprop_state = False
         
         if timestamp < self.current_timestamp:
@@ -83,14 +91,17 @@ def get_graph(self, timestamp: int):
             self.current_timestamp += 1
 
     def get_backward_graph(self, timestamp: int):
+        # print("📝📝📝 Get_backward_graph (backward) called",flush=True)
         if not self._is_backprop_state:
-            self._init_reverse_graph()
+            # print("💄💄💄 Calling backward init")
             self._is_backprop_state = True
+            self._init_reverse_graph()
         
         if timestamp > self.current_timestamp:
             raise Exception("⏰ Invalid timestamp during SeastarGraph.update_graph_backward()")
         
         while self.current_timestamp > timestamp:
+            # print("🎒🎒🎒 Calling update backward")
             self._update_graph_backward()
             self.current_timestamp -= 1
     
diff --git a/seastar/graph/dynamic/naive/NaiveGraph.py b/seastar/graph/dynamic/naive/NaiveGraph.py
index bbbf850..f87bf9b 100644
--- a/seastar/graph/dynamic/naive/NaiveGraph.py
+++ b/seastar/graph/dynamic/naive/NaiveGraph.py
@@ -3,21 +3,24 @@
 from rich import inspect
 
 from seastar.graph.dynamic.DynamicGraph import DynamicGraph
-from seastar.graph.static.csr import CSR
+from seastar.graph.static.csr import CSR, print_dev_array
 from collections import deque
 import time
 
 class NaiveGraph(DynamicGraph):
     def __init__(self, edge_list):
         super().__init__(edge_list)
-        
+        # inspect(edge_list)
         self._prepare_edge_lst_fwd(edge_list)
         self._prepare_edge_lst_bwd(self.fwd_edge_list)  
-        self._forward_graph = [CSR(self.fwd_edge_list[i], self.graph_updates["0"]["num_nodes"], is_edge_reverse=True) for i in range(len(self.fwd_edge_list))]
-        self._backward_graph = [CSR(self.bwd_edge_list[i], self.graph_updates["0"]["num_nodes"], is_edge_reverse=True) for i in range(len(self.bwd_edge_list))]
+        self._forward_graph = [CSR(self.fwd_edge_list[i], self.graph_updates[str(i)]["num_nodes"], is_edge_reverse=True) for i in range(len(self.fwd_edge_list))]
+        self._backward_graph = [CSR(self.bwd_edge_list[i], self.graph_updates[str(i)]["num_nodes"]) for i in range(len(self.bwd_edge_list))]
+        # self._forward_graph = [CSR(self.fwd_edge_list[0], self.graph_updates[str(0)]["num_nodes"], is_edge_reverse=True)]
+        # self._backward_graph = [CSR(self.bwd_edge_list[0], self.graph_updates[str(0)]["num_nodes"])]
+
         self._get_graph_csr_ptrs(0)
         
-    def _prepare_edge_lst_fwd(self, edge_list):    
+    def _prepare_edge_lst_fwd(self, edge_list):   
         self.fwd_edge_list = []
         for i in range(len(edge_list)):
             edge_list_for_t = edge_list[i]
@@ -28,18 +31,18 @@ def _prepare_edge_lst_fwd(self, edge_list):
     def _prepare_edge_lst_bwd(self, edge_list):    
         self.bwd_edge_list = []
         for i in range(len(edge_list)):
-            edge_list_for_t = edge_list[i]
+            edge_list_for_t = copy.deepcopy(edge_list[i])
             edge_list_for_t.sort()
             self.bwd_edge_list.append(edge_list_for_t)
         
-    def _graph_stack_push(self, elem):
-        self.graph_stack.append(elem)
+    # def _graph_stack_push(self, elem):
+    #     self.graph_stack.append(elem)
     
-    def _graph_stack_pop(self):
-        self.graph_stack.pop()
+    # def _graph_stack_pop(self):
+    #     self.graph_stack.pop()
         
-    def _graph_stack_top(self):
-        return self.graph_stack[-1]
+    # def _graph_stack_top(self):
+    #     return self.graph_stack[-1]
         
     def graph_type(self):
         return "csr"
@@ -51,15 +54,23 @@ def out_degrees(self):
         return np.array(self._forward_graph[self.current_timestamp].in_degrees, dtype='int32')
     
     def _get_graph_csr_ptrs(self, timestamp):
-        fwd_csr_ptrs = self._forward_graph[timestamp]
-        self.fwd_row_offset_ptr = fwd_csr_ptrs.row_offset_ptr
-        self.fwd_column_indices_ptr = fwd_csr_ptrs.column_indices_ptr
-        self.fwd_eids_ptr = fwd_csr_ptrs.eids_ptr
-        
-        bwd_csr_ptrs = self._backward_graph[timestamp]
-        self.bwd_row_offset_ptr = bwd_csr_ptrs.row_offset_ptr
-        self.bwd_column_indices_ptr = bwd_csr_ptrs.column_indices_ptr
-        self.bwd_eids_ptr = bwd_csr_ptrs.eids_ptr
+        if self._is_backprop_state:
+            bwd_csr_ptrs = self._backward_graph[timestamp]
+            # bwd_csr_ptrs.print_csr_arrays()
+            self.bwd_row_offset_ptr = bwd_csr_ptrs.row_offset_ptr
+            self.bwd_column_indices_ptr = bwd_csr_ptrs.column_indices_ptr
+            self.bwd_eids_ptr = bwd_csr_ptrs.eids_ptr
+        else:
+            fwd_csr_ptrs = self._forward_graph[timestamp]
+            # fwd_csr_ptrs.print_csr_arrays()
+            self.fwd_row_offset_ptr = fwd_csr_ptrs.row_offset_ptr
+            self.fwd_column_indices_ptr = fwd_csr_ptrs.column_indices_ptr
+            self.fwd_eids_ptr = fwd_csr_ptrs.eids_ptr
+
+            # print(f"(PYTHON) RECEIVED ROW OFFSET PTR: {fwd_csr_ptrs.row_offset_ptr}")
+            # print_dev_array(fwd_csr_ptrs.row_offset_ptr,129)
+            # print("QUITTING")
+            # quit()
     
     def _update_graph_forward(self):
         ''' Updates the current base graph to the next timestamp
diff --git a/seastar/graph/dynamic/pcsr/PCSRGraph.py b/seastar/graph/dynamic/pcsr/PCSRGraph.py
index c6396c9..cc46ef7 100644
--- a/seastar/graph/dynamic/pcsr/PCSRGraph.py
+++ b/seastar/graph/dynamic/pcsr/PCSRGraph.py
@@ -1,21 +1,41 @@
 import copy
 import numpy as np
 from rich import inspect
+import time
 
 from seastar.graph.dynamic.DynamicGraph import DynamicGraph
-from seastar.graph.dynamic.pcsr.pcsr import PCSR, copy_label_edges, build_reverse_pcsr
+from seastar.graph.dynamic.pcsr.pcsr import PCSR, build_reverse_pcsr
 
 class PCSRGraph(DynamicGraph):
     def __init__(self, edge_list):
         super().__init__(edge_list)
         
+        # preprocessing the edge ids for forward and backward pass
+        # so that it doesn't have to be calculated each time
+        self._prepare_eid_bwd(edge_list) 
+        
         self._forward_graph = PCSR(self.max_num_nodes)
         self._backward_graph = PCSR(self.max_num_nodes)
         self._forward_graph.edge_update_list(self.graph_updates["0"]["add"],is_reverse_edge=True)
-
-        self._forward_graph.label_edges()
-        self._get_graph_csr_ptrs()
         
+        self._update_graph_cache()
+        self._get_graph_csr_ptrs(eids=list())
+    
+    def _prepare_eid_bwd(self, edge_list):   
+        fwd_edge_list = []
+        for i in range(len(edge_list)):
+            edge_list_for_t = edge_list[i]
+            edge_list_for_t.sort(key = lambda x: (x[1],x[0]))
+            edge_list_for_t = [(edge_list_for_t[j][0],edge_list_for_t[j][1],j) for j in range(len(edge_list_for_t))]
+            fwd_edge_list.append(edge_list_for_t)
+        
+        self.bwd_eid_list = []
+        for i in range(len(fwd_edge_list)):
+            edge_list_for_t = fwd_edge_list[i]
+            edge_list_for_t.sort()
+            eid_t = [edge_list_for_t[i][2] for i in range(len(edge_list_for_t))]
+            self.bwd_eid_list.append(eid_t)
+                
     def graph_type(self):
         return "pcsr"
         
@@ -25,67 +45,66 @@ def in_degrees(self):
     def out_degrees(self):
         return np.array([node.in_degree for node in self._forward_graph.nodes], dtype='int32')
     
-    def _update_reverse_graph_cache(self):
-        # saving reverse base graph in cache
-        self.graph_cache['reverse'] = copy.deepcopy(self._backward_graph)
+    def _update_graph_cache(self, is_bwd=False):
+        if is_bwd:
+            # saving reverse base graph in cache
+            self.graph_cache['bwd'] = copy.deepcopy(self._backward_graph)
+        else:
+            self.graph_cache['fwd'] = copy.deepcopy(self._forward_graph)
             
-    def _get_reverse_cached_graph(self):
-        return copy.deepcopy(self.graph_cache['reverse'])
+    def _get_cached_graph(self, is_bwd=False):
+        if is_bwd:
+            return copy.deepcopy(self.graph_cache['bwd'])
+        else:
+            return copy.deepcopy(self.graph_cache['fwd'])
     
-    def _get_graph_csr_ptrs(self):
-        forward_csr_ptrs = self._forward_graph.get_csr_ptrs()
-        backward_csr_ptrs = self._backward_graph.get_csr_ptrs()
-        
-        self.fwd_row_offset_ptr = forward_csr_ptrs[0]
-        self.fwd_column_indices_ptr = forward_csr_ptrs[1]
-        self.fwd_eids_ptr = forward_csr_ptrs[2]
-        
-        self.bwd_row_offset_ptr = backward_csr_ptrs[0]
-        self.bwd_column_indices_ptr = backward_csr_ptrs[1]
-        self.bwd_eids_ptr = backward_csr_ptrs[2]
+    def _get_graph_csr_ptrs(self, eids): 
 
-    # def _get_graph_attributes(self):
-    #     if not self._is_backprop_state:
-    #         graph_attr = self._forward_graph.get_graph_attr()
-    #     else:
-    #         graph_attr = self._backward_graph.get_graph_attr()
-        
-    #     self.num_nodes = graph_attr[0]
-    #     self.num_edges = graph_attr[1]
+        if self._is_backprop_state:
+            backward_csr_ptrs = self._backward_graph.get_csr_ptrs(eids=eids)
+            self.bwd_row_offset_ptr = backward_csr_ptrs[0]
+            self.bwd_column_indices_ptr = backward_csr_ptrs[1]
+            self.bwd_eids_ptr = backward_csr_ptrs[2]
+        else:
+            forward_csr_ptrs = self._forward_graph.get_csr_ptrs(eids=eids)
+            self.fwd_row_offset_ptr = forward_csr_ptrs[0]
+            self.fwd_column_indices_ptr = forward_csr_ptrs[1]
+            self.fwd_eids_ptr = forward_csr_ptrs[2]
     
     def _update_graph_forward(self):
         ''' Updates the current base graph to the next timestamp
         '''
-
         if str(self.current_timestamp + 1) not in self.graph_updates:
             raise Exception("⏰ Invalid timestamp during SeastarGraph.update_graph_forward()")
         
         graph_additions = self.graph_updates[str(self.current_timestamp + 1)]["add"]
         graph_deletions = self.graph_updates[str(self.current_timestamp + 1)]["delete"]
+        
+        # print(f"(FORWARD) ➕➕➕ Adding a total of {len(graph_additions)} elements", flush=True)
+        # print(f"(FORWARD) ➖➖➖ Removing a total of {len(graph_deletions)} elements", flush=True)
 
         self._forward_graph.edge_update_list(graph_additions,is_reverse_edge=True)
         self._forward_graph.edge_update_list(graph_deletions,is_delete=True,is_reverse_edge=True)
-
-        self._forward_graph.label_edges()
-        self._get_graph_csr_ptrs()
-        # self._get_graph_attributes()
+        self._get_graph_csr_ptrs(eids=list())
         
     def _init_reverse_graph(self):
         ''' Generates the reverse of the base graph'''
-
+        
         # checking if the reverse base graph exists in the cache
         # we can load it from there instead of building it each time
-        if 'reverse' in self.graph_cache:
-            self._backward_graph = self._get_reverse_cached_graph()
+        if 'bwd' in self.graph_cache:
+            self._backward_graph = self._get_cached_graph(is_bwd=True)
         else:
             build_reverse_pcsr(self._backward_graph, self._forward_graph)
 
             # storing the reverse base graph in cache after building
             # it for the first time
-            self._update_reverse_graph_cache()
-
-        self._get_graph_csr_ptrs()
-        # self._get_graph_attributes()
+            self._update_graph_cache(is_bwd=True)
+            
+        self._get_graph_csr_ptrs(eids=self.bwd_eid_list[self.current_timestamp])
+        
+        # Resetting the forward graph so that it can be used later
+        self._forward_graph = self._get_cached_graph()
         
     def _update_graph_backward(self):
         if self.current_timestamp < 0:
@@ -93,14 +112,10 @@ def _update_graph_backward(self):
         
         graph_additions = self.graph_updates[str(self.current_timestamp)]["delete"]
         graph_deletions = self.graph_updates[str(self.current_timestamp)]["add"]
-
-        self._backward_graph.edge_update_list(graph_additions)
-        self._forward_graph.edge_update_list(graph_additions,is_reverse_edge=True)
         
+        # print(f"(BACKWARD) ➕➕➕ Adding a total of {len(graph_additions)} elements",flush=True)
+        # print(f"(BACKWARD) ➖➖➖ Removing a total of {len(graph_deletions)} elements",flush=True)
+        
+        self._backward_graph.edge_update_list(graph_additions)   
         self._backward_graph.edge_update_list(graph_deletions,is_delete=True)
-        self._forward_graph.edge_update_list(graph_deletions, is_delete=True, is_reverse_edge=True)
-
-        self._forward_graph.label_edges()
-        copy_label_edges(self._backward_graph, self._forward_graph)
-        self._get_graph_csr_ptrs()
-        # self._get_graph_attributes()
\ No newline at end of file
+        self._get_graph_csr_ptrs(eids=self.bwd_eid_list[self.current_timestamp - 1])
\ No newline at end of file
diff --git a/seastar/graph/dynamic/pcsr/pcsr.cu b/seastar/graph/dynamic/pcsr/pcsr.cu
index 1f7e0c0..fd1bfb1 100644
--- a/seastar/graph/dynamic/pcsr/pcsr.cu
+++ b/seastar/graph/dynamic/pcsr/pcsr.cu
@@ -261,7 +261,11 @@ public:
     // data members
     std::vector<node_t> nodes;
     edge_list_t edges;
-    uint32_t edge_count = 0;
+    uint32_t edge_count;
+
+    DEV_VEC row_offset_device;
+    DEV_VEC column_indices_device;
+    DEV_VEC eids_device;
 
     // member functions
     PCSR(uint32_t init_n);
@@ -273,13 +277,13 @@ public:
     void edge_update_list(std::vector<std::tuple<uint32_t, uint32_t>> edge_list, bool is_delete, bool is_reverse_edge);
     void delete_edge(uint32_t src, uint32_t dest);
     uint64_t get_n();
-    vector<tuple<uint32_t, uint32_t, uint32_t>> get_edges();
+    // vector<tuple<uint32_t, uint32_t, uint32_t>> get_edges();
     void print_graph();
     void print_array();
-    std::tuple<std::size_t, std::size_t, std::size_t> get_csr_ptrs();
-    void label_edges();
-    uint32_t find_edge_id(uint32_t src, uint32_t dest);
-    std::tuple<uint32_t, uint32_t> get_graph_attr();
+    std::tuple<std::uintptr_t, std::uintptr_t, std::uintptr_t> get_csr_ptrs(std::vector<int> eids);
+    // void label_edges();
+    // uint32_t find_edge_id(uint32_t src, uint32_t dest);
+    // std::tuple<uint32_t, uint32_t> get_graph_attr();
 
     uint32_t insert(uint32_t index, edge_t elem, uint32_t src);
     void double_list();
@@ -300,10 +304,13 @@ PCSR::PCSR(uint32_t init_n)
         edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
         edges.H = bsr_word(edges.N / edges.logN);
 
+        edges.items.resize(edges.N);
+        edge_count = 0;
+
         for (int i = 0; i < edges.N; i++)
         {
             edge_t new_edge(0, 0);
-            edges.items.push_back(new_edge);
+            edges.items[i] = new_edge;
         }
 
         for (int i = 0; i < init_n; i++)
@@ -332,11 +339,13 @@ void PCSR::init_graph(std::string graph_file_path)
     edges.N = 2 << bsr_word(node_size);
     edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
     edges.H = bsr_word(edges.N / edges.logN);
+    edge_count = 0;
 
+    edges.items.resize(edges.N);
     for (int i = 0; i < edges.N; i++)
     {
         edge_t new_edge(0, 0);
-        edges.items.push_back(new_edge);
+        edges.items[i] = new_edge;
     }
 
     for (int i = 0; i < node_size; i++)
@@ -472,10 +481,11 @@ void PCSR::double_list()
     edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
     edges.H = bsr_word(edges.N / edges.logN);
 
+    edges.items.resize(edges.N);
     for (int i = edges.N / 2; i < edges.N; i++)
     {
         edge_t new_edge(0, 0);
-        edges.items.push_back(new_edge);
+        edges.items[i] = new_edge;
     }
 
     redistribute(0, edges.N);
@@ -634,6 +644,7 @@ void PCSR::redistribute(int index, int len)
 
 void PCSR::add_edge(uint32_t src, uint32_t dest, uint32_t value)
 {
+    // cout << "Adding edge (" << src << "," << dest << ")\n";
     if (value != 0)
     {
         node_t node = nodes[src];
@@ -677,6 +688,7 @@ void PCSR::add_edge_update(uint32_t src, uint32_t dest, uint32_t value)
 
 void PCSR::delete_edge(uint32_t src, uint32_t dest)
 {
+    // cout << "Deleting edge (" << src << "," << dest << ")\n";
     edge_t e;
     e.value = 0;
     e.dest = dest;
@@ -687,8 +699,8 @@ void PCSR::delete_edge(uint32_t src, uint32_t dest)
         edges.items[loc].value = 0;
         nodes[src].num_neighbors -= 1;
         nodes[dest].in_degree -= 1;
+        --edge_count;
     }
-    --edge_count;
 }
 
 uint64_t PCSR::get_n()
@@ -696,26 +708,29 @@ uint64_t PCSR::get_n()
     return nodes.size();
 }
 
-vector<tuple<uint32_t, uint32_t, uint32_t>> PCSR::get_edges()
-{
-    uint64_t n = get_n();
-    vector<tuple<uint32_t, uint32_t, uint32_t>> output;
-
-    for (int i = 0; i < n; i++)
-    {
-        uint32_t start = nodes[i].beginning;
-        uint32_t end = nodes[i].end;
-        for (int j = start + 1; j < end; j++)
-        {
-            if (!is_null(edges.items[j]))
-            {
-                output.push_back(
-                    make_tuple(i, edges.items[j].dest, edges.items[j].value));
-            }
-        }
-    }
-    return output;
-}
+// vector<tuple<uint32_t, uint32_t, uint32_t>> PCSR::get_edges()
+// {
+//     uint64_t n = get_n();
+//     vector<tuple<uint32_t, uint32_t, uint32_t>> output;
+
+//     output.resize(edge_count);
+//     int iter = 0;
+//     for (int i = 0; i < n; i++)
+//     {
+//         uint32_t start = nodes[i].beginning;
+//         uint32_t end = nodes[i].end;
+//         for (int j = start + 1; j < end; j++)
+//         {
+//             if (!is_null(edges.items[j]))
+//             {
+//                 output[iter] =
+//                     make_tuple(i, edges.items[j].dest, edges.items[j].value);
+//                 iter += 1;
+//             }
+//         }
+//     }
+//     return output;
+// }
 
 void PCSR::print_graph()
 {
@@ -783,102 +798,116 @@ void PCSR::print_array()
     printf("\n\n");
 }
 
-std::tuple<std::size_t, std::size_t, std::size_t> PCSR::get_csr_ptrs()
+std::tuple<std::uintptr_t, std::uintptr_t, std::uintptr_t> PCSR::get_csr_ptrs(std::vector<int> eids)
 {
 
+    // if (edge_count != eids.size())
+    // {
+    //     cout << "🍎🍎🍎 The eids(" << eids.size() << ") and edge_count(" << edge_count << ") [col_indices.size() = " << edges.items.size() << "] DONT match\n"
+    //          << flush;
+    // }
+
     // we are building a compressed CSR arrays without
     // the -1 values which indicates an empty edge
-
     thrust::host_vector<int> row_offset;
     thrust::host_vector<int> column_indices;
-    thrust::host_vector<int> eids;
 
     int row_offset_size = nodes.size() + 1;
     int column_indices_size = edges.items.size();
 
     // first element of row offset is always zero
-    row_offset.push_back(0);
+    row_offset.resize(row_offset_size);
+    column_indices.resize(edge_count);
+    row_offset[0] = 0;
 
     for (int i = 0; i < row_offset_size - 1; ++i)
     {
-        row_offset.push_back(nodes[i].num_neighbors + row_offset[i]);
-    }
-
-    for (int i = 0; i < column_indices_size; ++i)
-    {
-        if (!is_sentinel(edges.items[i]) && !is_null(edges.items[i]))
-        {
-            column_indices.push_back(edges.items[i].dest);
-            eids.push_back(edges.items[i].value);
-        }
+        row_offset[i + 1] = nodes[i].num_neighbors + row_offset[i];
     }
 
-    DEV_VEC row_offset_device = row_offset;
-    DEV_VEC column_indices_device = column_indices;
-    DEV_VEC eids_device = eids;
-
-    std::tuple<std::size_t, std::size_t, std::size_t> t;
-    std::get<0>(t) = (std::size_t)RAW_PTR(row_offset_device);
-    std::get<1>(t) = (std::size_t)RAW_PTR(column_indices_device);
-    std::get<2>(t) = (std::size_t)RAW_PTR(eids_device);
-    return t;
-}
-
-void PCSR::label_edges()
-{
-
-    uint32_t column_indices_size = edges.items.size();
-    uint32_t counter = 1;
+    int iter = 0;
     for (int i = 0; i < column_indices_size; ++i)
     {
         if (!is_sentinel(edges.items[i]) && !is_null(edges.items[i]))
         {
-            edges.items[i].value = counter;
-            ++counter;
+            column_indices[iter] = edges.items[i].dest;
+            iter += 1;
         }
     }
-}
 
-uint32_t PCSR::find_edge_id(uint32_t src, uint32_t dest)
-{
-    edge_t e;
-    e.dest = dest;
-    // setting a random number
-    e.value = 0;
+    row_offset_device = row_offset;
+    column_indices_device = column_indices;
+    eids_device.resize(edge_count);
 
-    uint32_t loc = binary_search(&edges, &e, nodes[src].beginning + 1, nodes[src].end);
-    return edges.items[loc].value;
-}
+    if (eids.size() == 0)
+        thrust::sequence(eids_device.begin(), eids_device.end());
+    else
+        thrust::copy(eids.begin(), eids.end(), eids_device.begin());
 
-std::tuple<uint32_t, uint32_t> PCSR::get_graph_attr()
-{
-    std::tuple<uint32_t, uint32_t> t;
-    std::get<0>(t) = nodes.size();
-    std::get<1>(t) = edge_count;
+    std::tuple<std::uintptr_t, std::uintptr_t, std::uintptr_t> t;
+    std::get<0>(t) = (std::uintptr_t)RAW_PTR(row_offset_device);
+    std::get<1>(t) = (std::uintptr_t)RAW_PTR(column_indices_device);
+    std::get<2>(t) = (std::uintptr_t)RAW_PTR(eids_device);
     return t;
 }
 
-void copy_label_edges(PCSR &pcsr, PCSR &ref_pcsr)
-{
-    uint64_t n = pcsr.get_n();
-
-    for (int i = 0; i < n; i++)
-    {
-        uint32_t start = pcsr.nodes[i].beginning;
-        uint32_t end = pcsr.nodes[i].end;
-        for (int j = start + 1; j < end; j++)
-        {
-            if (!is_null(pcsr.edges.items[j]))
-            {
-                // Searching for the edge_id of reverse edge from reference_pcsr
-                pcsr.edges.items[j].value = ref_pcsr.find_edge_id(pcsr.edges.items[j].dest, i);
-            }
-        }
-    }
-}
+// void PCSR::label_edges()
+// {
+
+//     uint32_t column_indices_size = edges.items.size();
+//     uint32_t counter = 1;
+//     for (int i = 0; i < column_indices_size; ++i)
+//     {
+//         if (!is_sentinel(edges.items[i]) && !is_null(edges.items[i]))
+//         {
+//             edges.items[i].value = counter;
+//             ++counter;
+//         }
+//     }
+// }
+
+// uint32_t PCSR::find_edge_id(uint32_t src, uint32_t dest)
+// {
+//     edge_t e;
+//     e.dest = dest;
+//     // setting a random number
+//     e.value = 0;
+
+//     uint32_t loc = binary_search(&edges, &e, nodes[src].beginning + 1, nodes[src].end);
+//     return edges.items[loc].value;
+// }
+
+// std::tuple<uint32_t, uint32_t> PCSR::get_graph_attr()
+// {
+//     std::tuple<uint32_t, uint32_t> t;
+//     std::get<0>(t) = nodes.size();
+//     std::get<1>(t) = edge_count;
+//     return t;
+// }
+
+// void copy_label_edges(PCSR &pcsr, PCSR &ref_pcsr)
+// {
+//     uint64_t n = pcsr.get_n();
+
+//     for (int i = 0; i < n; i++)
+//     {
+//         uint32_t start = pcsr.nodes[i].beginning;
+//         uint32_t end = pcsr.nodes[i].end;
+//         for (int j = start + 1; j < end; j++)
+//         {
+//             if (!is_null(pcsr.edges.items[j]))
+//             {
+//                 // Searching for the edge_id of reverse edge from reference_pcsr
+//                 pcsr.edges.items[j].value = ref_pcsr.find_edge_id(pcsr.edges.items[j].dest, i);
+//             }
+//         }
+//     }
+// }
 
 void PCSR::edge_update_list(std::vector<std::tuple<uint32_t, uint32_t>> edge_list, bool is_delete = false, bool is_reverse_edge = false)
 {
+    // cout << "📦📦📦 Edge Update list" << endl
+    //      << flush;
     for (auto &edge : edge_list)
     {
         uint32_t src = (is_reverse_edge == true) ? std::get<1>(edge) : std::get<0>(edge);
@@ -893,14 +922,20 @@ void PCSR::edge_update_list(std::vector<std::tuple<uint32_t, uint32_t>> edge_lis
 
 void build_reverse_pcsr(PCSR &pcsr, PCSR &ref_pcsr)
 {
-    vector<tuple<uint32_t, uint32_t, uint32_t>> edge_list = ref_pcsr.get_edges();
-    for (auto &edge : edge_list)
+    // cout << "💄💄💄 Building Reverse PCSR" << endl
+    //      << flush;
+    uint64_t n = ref_pcsr.get_n();
+    for (int i = 0; i < n; i++)
     {
-        // since reverse graph the src and dst are swapped
-        uint32_t src = std::get<1>(edge);
-        uint32_t dst = std::get<0>(edge);
-        uint32_t val = std::get<2>(edge);
-        pcsr.add_edge(src, dst, val);
+        uint32_t start = ref_pcsr.nodes[i].beginning;
+        uint32_t end = ref_pcsr.nodes[i].end;
+        for (int j = start + 1; j < end; j++)
+        {
+            if (!is_sentinel(ref_pcsr.edges.items[j]) && !is_null(ref_pcsr.edges.items[j]))
+            {
+                pcsr.add_edge(ref_pcsr.edges.items[j].dest, i, ref_pcsr.edges.items[j].value);
+            }
+        }
     }
 }
 
@@ -911,7 +946,7 @@ void build_reverse_pcsr(PCSR &pcsr, PCSR &ref_pcsr)
 PYBIND11_MODULE(pcsr, m)
 {
     m.doc() = "PCSR Dynamic Graph Representation";
-    m.def("copy_label_edges", &copy_label_edges, "Label edges of a PCSR based on another PCSR");
+    // m.def("copy_label_edges", &copy_label_edges, "Label edges of a PCSR based on another PCSR");
     m.def("build_reverse_pcsr", &build_reverse_pcsr, "Builds the reverse PCSR based on another PCSR");
 
     py::class_<node_t>(m, "Node")
@@ -957,16 +992,17 @@ PYBIND11_MODULE(pcsr, m)
         .def("add_edge", &PCSR::add_edge)
         .def("add_edge_update", &PCSR::add_edge_update)
         .def("edge_update_list", &PCSR::edge_update_list, py::arg("edge_list"), py::arg("is_delete") = false, py::arg("is_reverse_edge") = false)
-        .def("label_edges", &PCSR::label_edges, "Creates edge labels for the current GPMA")
+        // .def("label_edges", &PCSR::label_edges, "Creates edge labels for the current GPMA")
         .def("delete_edge", &PCSR::delete_edge)
         .def("get_n", &PCSR::get_n)
-        .def("get_edges", &PCSR::get_edges)
+        // .def("get_edges", &PCSR::get_edges)
         .def("print_array", &PCSR::print_array)
-        .def("get_csr_ptrs", &PCSR::get_csr_ptrs)
-        .def("get_graph_attr", &PCSR::get_graph_attr)
-        .def("find_edge_id", &PCSR::find_edge_id)
+        .def("get_csr_ptrs", &PCSR::get_csr_ptrs, py::arg("eids"))
+        // .def("get_graph_attr", &PCSR::get_graph_attr)
+        // .def("find_edge_id", &PCSR::find_edge_id)
         .def_readwrite("nodes", &PCSR::nodes)
         .def_readwrite("edges", &PCSR::edges)
+        .def_readwrite("edge_count", &PCSR::edge_count)
         .def("__copy__", [](const PCSR &self)
              { return PCSR(self); })
         .def(
diff --git a/seastar/graph/static/csr.cu b/seastar/graph/static/csr.cu
index dd49256..6cf5bfb 100644
--- a/seastar/graph/static/csr.cu
+++ b/seastar/graph/static/csr.cu
@@ -2,6 +2,7 @@
 #include <vector>
 #include <tuple>
 #include <chrono>
+#include <cstdint>
 
 #include <thrust/device_vector.h>
 #include <thrust/host_vector.h>
@@ -25,19 +26,24 @@ public:
     thrust::host_vector<int> column_indices;
     thrust::host_vector<int> eids;
 
+    DEV_VEC row_offset_device;
+    DEV_VEC column_indices_device;
+    DEV_VEC eids_device;
+
     std::vector<int> in_degrees;
     std::vector<int> out_degrees;
 
-    std::size_t row_offset_ptr;
-    std::size_t column_indices_ptr;
-    std::size_t eids_ptr;
+    std::uintptr_t row_offset_ptr;
+    std::uintptr_t column_indices_ptr;
+    std::uintptr_t eids_ptr;
 
     CSR(std::vector<std::tuple<int, int, int>> edge_list, int num_nodes, bool is_edge_reverse);
-    // std::tuple<std::size_t, std::size_t, std::size_t> get_csr_ptrs();
+    // std::tuple<std::uintptr_t, std::uintptr_t, std::uintptr_t> get_csr_ptrs();
     void get_csr_ptrs();
     // void label_edges();
     // int find_edge_id(int src, int dst);
     // void copy_label_edges(CSR new_csr);
+    void print_row_offset();
     void print_csr_arrays();
     void print_graph();
 };
@@ -117,13 +123,28 @@ CSR::CSR(std::vector<std::tuple<int, int, int>> edge_list, int num_nodes, bool i
 
 void CSR::get_csr_ptrs()
 {
-    DEV_VEC row_offset_device = row_offset;
-    DEV_VEC column_indices_device = column_indices;
-    DEV_VEC eids_device = eids;
+    row_offset_device = row_offset;
+    column_indices_device = column_indices;
+    eids_device = eids;
 
-    row_offset_ptr = (std::size_t)RAW_PTR(row_offset_device);
-    column_indices_ptr = (std::size_t)RAW_PTR(column_indices_device);
-    eids_ptr = (std::size_t)RAW_PTR(eids_device);
+    row_offset_ptr = reinterpret_cast<std::uintptr_t>(RAW_PTR(row_offset_device));
+    column_indices_ptr = reinterpret_cast<std::uintptr_t>(RAW_PTR(column_indices_device));
+    eids_ptr = reinterpret_cast<std::uintptr_t>(RAW_PTR(eids_device));
+}
+
+void CSR::print_row_offset()
+{
+    int *dev_ptr = reinterpret_cast<int *>(row_offset_ptr);
+    int *host_ptr = (int *)malloc(row_offset.size() * sizeof(int));
+    cudaMemcpy(host_ptr, dev_ptr, row_offset.size() * sizeof(int), cudaMemcpyDeviceToHost);
+
+    std::cout << "PRINTING DEV AFTER RECAST AS SEP FUNC\n";
+    for (int i = 0; i < row_offset.size(); ++i)
+    {
+        std::cout << host_ptr[i] << " ";
+    }
+
+    std::cout << "\n=============================\n";
 }
 
 void CSR::print_csr_arrays()
@@ -199,9 +220,25 @@ void CSR::print_graph()
     }
 }
 
+void print_dev_array(std::uintptr_t ptr, int size)
+{
+    std::cout << "RECEIVED ROW OFFSET PTR: " << ptr << "\n";
+    int *dev_ptr = reinterpret_cast<int *>(ptr);
+    int *host_ptr = (int *)malloc(size * sizeof(int));
+    // thrust::device_ptr<int> dev_ptr = thrust::device_pointer_cast(raw_ptr);
+    cudaMemcpy(host_ptr, dev_ptr, size * sizeof(int), cudaMemcpyDeviceToHost);
+    std::cout << "\nPRINTING ARRAY\n";
+    for (int i = 0; i < size; ++i)
+    {
+        std::cout << host_ptr[i] << " ";
+    }
+    std::cout << "\n";
+}
+
 PYBIND11_MODULE(csr, m)
 {
     m.doc() = "CPython module for CSR"; // optional module docstring
+    m.def("print_dev_array", &print_dev_array, "Print arrays on the device");
 
     py::class_<CSR>(m, "CSR")
         // .def(py::init<std::vector<std::tuple<int, int>>, int, bool>(), py::arg("edge_list"), py::arg("num_nodes"), py::arg("is_edge_reverse") = false)
@@ -211,6 +248,7 @@ PYBIND11_MODULE(csr, m)
         .def("get_csr_ptrs", &CSR::get_csr_ptrs)
         .def("print_csr_arrays", &CSR::print_csr_arrays)
         .def("print_graph", &CSR::print_graph)
+        .def("print_row_offset", &CSR::print_row_offset)
         .def_readwrite("row_offset", &CSR::row_offset)
         .def_readwrite("column_indices", &CSR::column_indices)
         .def_readwrite("eids", &CSR::eids)


Test patch:
diff --git a/benchmarking/tgcn/seastar-dynamic/test_tgcn.py b/benchmarking/tgcn/seastar-dynamic/test_tgcn.py
new file mode 100644
index 0000000..68b9869
--- /dev/null
+++ b/benchmarking/tgcn/seastar-dynamic/test_tgcn.py
@@ -0,0 +1,295 @@
+import argparse, time
+import numpy as np
+import networkx as nx
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import json
+import urllib
+from tqdm import tqdm
+import snoop
+import os
+
+from rich import inspect
+from rich.pretty import pprint
+from rich.traceback import install
+install(show_locals=True)
+
+from seastar.graph.dynamic.gpma.GPMAGraph import GPMAGraph
+from seastar.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
+import pandas as pd
+
+class EnglandCovidDatasetLoader(object):
+
+    def __init__(self):
+        self._read_web_data()
+
+    def _read_web_data(self):
+
+        if os.path.exists("../../dataset/england_covid/data.json"):
+            with open('../../dataset/england_covid/data.json', 'r') as openfile:
+                self._dataset = json.load(openfile)
+        else:
+            url = "https://raw.githubusercontent.com/benedekrozemberczki/pytorch_geometric_temporal/master/dataset/england_covid.json"
+            self._dataset = json.loads(urllib.request.urlopen(url).read())
+            self._save_dataset()
+
+        
+
+    def _save_dataset(self):
+        with open("../../dataset/england_covid/data.json", "w") as outfile:
+            json.dump(self._dataset,outfile)
+
+    def _get_edges(self):
+        self._edges = []
+        for time in range(self._dataset["time_periods"] - self.lags):
+            self._edges.append(
+                np.array(self._dataset["edge_mapping"]["edge_index"][str(time)]).T
+            )
+
+    def _get_edge_weights(self):
+        self._edge_weights = []
+        for time in range(self._dataset["time_periods"] - self.lags):
+            self._edge_weights.append(
+                np.array(self._dataset["edge_mapping"]["edge_weight"][str(time)])
+            )
+
+    def _get_targets_and_features(self):
+        stacked_target = np.array(self._dataset["y"])
+        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
+            np.std(stacked_target, axis=0) + 10 ** -10
+        )
+        self.features = [
+            standardized_target[i : i + self.lags, :].T
+            for i in range(self._dataset["time_periods"] - self.lags)
+        ]
+        self.targets = [
+            standardized_target[i + self.lags, :].T
+            for i in range(self._dataset["time_periods"] - self.lags)
+        ]
+
+    def get_dataset(self, lags: int = 8):
+        self.lags = lags
+        self._get_edges()
+        self._get_edge_weights()
+        self._get_targets_and_features()
+        return self._edges, self._edge_weights, self.features, self.targets
+
+
+class BitcoinOtcDataloader():
+    def __init__(self):
+        self._dataset = pd.read_csv('../../dataset/bitcoin/soc-sign-bitcoinotc.csv',header=0, names=['source', 'target', 'rating','time'])
+        self._dataset.time = self._dataset.time / 10000000
+        self._dataset.time = self._dataset.time.astype(int)
+
+        lst = np.append(self._dataset.source.astype(int).unique(),self._dataset.target.astype(int).unique())
+        lst = np.sort(np.unique(lst))
+        
+        id_dict = {}
+        for i in range(len(lst)):
+            id_dict[lst[i]] = i
+        self.node_id_dict = id_dict
+            
+        self._dataset.source = self._dataset.source.apply(lambda x: self.node_id_dict[x])
+        self._dataset.target = self._dataset.target.apply(lambda x: self.node_id_dict[x])
+    
+    def _get_edges(self):
+        self._edges = []
+        time_lst = self._dataset.time.unique()
+        time_lst.sort()
+        for time in time_lst:
+            filtered_df = self._dataset.where(self._dataset.time == time).dropna()
+            self._edges.append(
+                np.array([filtered_df.source.astype(int).tolist(),filtered_df.target.astype(int).tolist()])
+            )
+
+    def _get_edge_weights(self):
+        self._edge_weights = []
+        for time in self._dataset.time.unique():
+            filtered_df = self._dataset.where(self._dataset.time == time).dropna()
+            self._edge_weights.append(
+                np.array(filtered_df.rating.tolist())
+            )
+
+    def _get_targets_and_features(self):
+        pass
+
+    def get_dataset(self):
+        self._get_edges()
+        self._get_edge_weights()
+        self._get_targets_and_features()
+        return self._edges, self._edge_weights, None, None
+
+def presort_edge_weights(edges_lst, edge_weights_lst):
+    '''
+        Presorting edges according to (dest,src) since that is how eids are formed
+        allowing forward and backward kernel to access edge weights
+    '''
+    final_edges_lst = []
+    final_edge_weights_lst = []
+
+    for i in range(len(edges_lst)):
+        src_list = edges_lst[i][0]
+        dst_list = edges_lst[i][1]
+        weights = edge_weights_lst[i]
+
+        edge_info_list = []
+        sorted_edges_lst = []
+        sorted_edge_weights_lst = []
+
+        for j in range(len(weights)):
+            edge_info = (src_list[j], dst_list[j], weights[j])
+            edge_info_list.append(edge_info)
+
+        # sorted_edge_info_list = sorted(edge_info_list, key=lambda element: (element[0], element[1]))
+
+        # since it has to be sorted according to the reverse order
+        sorted_edge_info_list = sorted(edge_info_list, key=lambda element: (element[1], element[0]))
+
+        temp_src = []
+        temp_dst = []
+
+        for edge in sorted_edge_info_list:
+            temp_src.append(edge[0])
+            temp_dst.append(edge[1])
+            sorted_edge_weights_lst.append(edge[2])
+
+        sorted_edges_lst.append(temp_src)
+        sorted_edges_lst.append(temp_dst)
+        sorted_edges_lst = np.array(sorted_edges_lst)
+
+        final_edges_lst.append(sorted_edges_lst)
+        final_edge_weights_lst.append(np.array(sorted_edge_weights_lst))
+
+def preprocess_graph_structure(edges):
+    # inspect(edges)
+    tmp_set = set()
+    for i in range(len(edges)):
+        for j in range(len(edges[i][0])):
+            tmp_set.add(edges[i][0][j])
+            tmp_set.add(edges[i][1][j])
+    max_num_nodes = len(tmp_set)
+
+    edge_dict = {}
+    for i in range(len(edges)):
+        edge_set = set()
+        for j in range(len(edges[i][0])):
+            edge_set.add((edges[i][0][j],edges[i][1][j]))
+        edge_dict[str(i)] = edge_set
+    
+    edge_final_dict = {}
+    edge_final_dict["0"] = {"add": list(edge_dict["0"]),"delete": []}
+    for i in range(1,len(edges)):
+        edge_final_dict[str(i)] = {"add": list(edge_dict[str(i)].difference(edge_dict[str(i-1)])), "delete": list(edge_dict[str(i-1)].difference(edge_dict[str(i)]))}
+    
+    return edge_final_dict, max_num_nodes
+
+# GPU | CPU
+def get_default_device():
+    
+    if torch.cuda.is_available():
+        return torch.device('cuda:0')
+    else:
+        return torch.device('cpu')
+
+def to_default_device(data):
+    
+    if isinstance(data,(list,tuple)):
+        return [to_default_device(x,get_default_device()) for x in data]
+    
+    return data.to(get_default_device(),non_blocking = True)
+
+def gpma_version(args):
+
+    print("😍 GPMA Version")
+    
+    # Data
+    # dataset = EnglandCovidDatasetLoader()
+    dataset = BitcoinOtcDataloader()
+
+    edges_lst, edge_weights_lst, all_features, all_targets = dataset.get_dataset()
+    presort_edge_weights(edges_lst, edge_weights_lst)
+    train_graph_log_dict, train_max_num_nodes = preprocess_graph_structure(edges_lst)
+    
+    # prev = train_graph_log_dict["0"]
+    # curr = train_graph_log_dict["1"]
+    
+    # inspect(edge_weights_lst)
+    
+    # for key, val in curr["delete"]:
+    #     found = False
+    #     for k1, v1 in prev["add"]:
+    #         if k1 == key and v1 == val:
+    #             found = True
+    #     if not found:
+    #         print("Error ({},{}) not found".format(key,val))
+    
+    t0 = time.time()
+    G = GPMAGraph(train_graph_log_dict,train_max_num_nodes)
+    t1 = time.time()
+    print("Timestamp = Base   |   Time Taken = {:.5f}".format(t1-t0))
+    
+    a = time.time()
+    for i in range(1,18):
+        t0 = time.time()
+        G.get_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+    
+    print(f'\n⏱️ Time taken for forward propagation: {b-a}\n')
+    
+    print("\n### Reverse Graph\n")
+    
+    a = time.time()
+    for i in reversed(range(1,18)):
+        t0 = time.time()
+        G.get_backward_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+
+    print(f'\n⏱️ Time taken for backward propagation: {b-a}\n')
+    
+def pcsr_version(args):
+    print("🎉 PCSR Version")
+    
+    # Data
+    # dataset = EnglandCovidDatasetLoader()
+    dataset = BitcoinOtcDataloader()
+
+    edges_lst, edge_weights_lst, all_features, all_targets = dataset.get_dataset()
+    presort_edge_weights(edges_lst, edge_weights_lst)
+    train_graph_log_dict, train_max_num_nodes = preprocess_graph_structure(edges_lst)
+    
+    t0 = time.time()
+    G = PCSRGraph(train_graph_log_dict,train_max_num_nodes)
+    t1 = time.time()
+    print("Timestamp = Base   |   Time Taken = {:.5f}".format(t1-t0))
+    
+    
+    a = time.time()
+    for i in range(1,18):
+        t0 = time.time()
+        G.get_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+    
+    print(f'\n⏱️ Time taken for forward propagation: {b-a}\n')
+    
+    print("\n### Reverse Graph\n")
+    
+    a = time.time()
+    for i in reversed(range(1,18)):
+        t0 = time.time()
+        G.get_backward_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+
+    print(f'\n⏱️ Time taken for backward propagation: {b-a}\n')
+
+gpma_version(None)
+print("\n----------------------------------------------------------\n")
+pcsr_version(None)
\ No newline at end of file
diff --git a/benchmarking/tgcn/seastar-static/test_tgcn.py b/benchmarking/tgcn/seastar-static/test_tgcn.py
new file mode 100644
index 0000000..68b9869
--- /dev/null
+++ b/benchmarking/tgcn/seastar-static/test_tgcn.py
@@ -0,0 +1,295 @@
+import argparse, time
+import numpy as np
+import networkx as nx
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import json
+import urllib
+from tqdm import tqdm
+import snoop
+import os
+
+from rich import inspect
+from rich.pretty import pprint
+from rich.traceback import install
+install(show_locals=True)
+
+from seastar.graph.dynamic.gpma.GPMAGraph import GPMAGraph
+from seastar.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
+import pandas as pd
+
+class EnglandCovidDatasetLoader(object):
+
+    def __init__(self):
+        self._read_web_data()
+
+    def _read_web_data(self):
+
+        if os.path.exists("../../dataset/england_covid/data.json"):
+            with open('../../dataset/england_covid/data.json', 'r') as openfile:
+                self._dataset = json.load(openfile)
+        else:
+            url = "https://raw.githubusercontent.com/benedekrozemberczki/pytorch_geometric_temporal/master/dataset/england_covid.json"
+            self._dataset = json.loads(urllib.request.urlopen(url).read())
+            self._save_dataset()
+
+        
+
+    def _save_dataset(self):
+        with open("../../dataset/england_covid/data.json", "w") as outfile:
+            json.dump(self._dataset,outfile)
+
+    def _get_edges(self):
+        self._edges = []
+        for time in range(self._dataset["time_periods"] - self.lags):
+            self._edges.append(
+                np.array(self._dataset["edge_mapping"]["edge_index"][str(time)]).T
+            )
+
+    def _get_edge_weights(self):
+        self._edge_weights = []
+        for time in range(self._dataset["time_periods"] - self.lags):
+            self._edge_weights.append(
+                np.array(self._dataset["edge_mapping"]["edge_weight"][str(time)])
+            )
+
+    def _get_targets_and_features(self):
+        stacked_target = np.array(self._dataset["y"])
+        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
+            np.std(stacked_target, axis=0) + 10 ** -10
+        )
+        self.features = [
+            standardized_target[i : i + self.lags, :].T
+            for i in range(self._dataset["time_periods"] - self.lags)
+        ]
+        self.targets = [
+            standardized_target[i + self.lags, :].T
+            for i in range(self._dataset["time_periods"] - self.lags)
+        ]
+
+    def get_dataset(self, lags: int = 8):
+        self.lags = lags
+        self._get_edges()
+        self._get_edge_weights()
+        self._get_targets_and_features()
+        return self._edges, self._edge_weights, self.features, self.targets
+
+
+class BitcoinOtcDataloader():
+    def __init__(self):
+        self._dataset = pd.read_csv('../../dataset/bitcoin/soc-sign-bitcoinotc.csv',header=0, names=['source', 'target', 'rating','time'])
+        self._dataset.time = self._dataset.time / 10000000
+        self._dataset.time = self._dataset.time.astype(int)
+
+        lst = np.append(self._dataset.source.astype(int).unique(),self._dataset.target.astype(int).unique())
+        lst = np.sort(np.unique(lst))
+        
+        id_dict = {}
+        for i in range(len(lst)):
+            id_dict[lst[i]] = i
+        self.node_id_dict = id_dict
+            
+        self._dataset.source = self._dataset.source.apply(lambda x: self.node_id_dict[x])
+        self._dataset.target = self._dataset.target.apply(lambda x: self.node_id_dict[x])
+    
+    def _get_edges(self):
+        self._edges = []
+        time_lst = self._dataset.time.unique()
+        time_lst.sort()
+        for time in time_lst:
+            filtered_df = self._dataset.where(self._dataset.time == time).dropna()
+            self._edges.append(
+                np.array([filtered_df.source.astype(int).tolist(),filtered_df.target.astype(int).tolist()])
+            )
+
+    def _get_edge_weights(self):
+        self._edge_weights = []
+        for time in self._dataset.time.unique():
+            filtered_df = self._dataset.where(self._dataset.time == time).dropna()
+            self._edge_weights.append(
+                np.array(filtered_df.rating.tolist())
+            )
+
+    def _get_targets_and_features(self):
+        pass
+
+    def get_dataset(self):
+        self._get_edges()
+        self._get_edge_weights()
+        self._get_targets_and_features()
+        return self._edges, self._edge_weights, None, None
+
+def presort_edge_weights(edges_lst, edge_weights_lst):
+    '''
+        Presorting edges according to (dest,src) since that is how eids are formed
+        allowing forward and backward kernel to access edge weights
+    '''
+    final_edges_lst = []
+    final_edge_weights_lst = []
+
+    for i in range(len(edges_lst)):
+        src_list = edges_lst[i][0]
+        dst_list = edges_lst[i][1]
+        weights = edge_weights_lst[i]
+
+        edge_info_list = []
+        sorted_edges_lst = []
+        sorted_edge_weights_lst = []
+
+        for j in range(len(weights)):
+            edge_info = (src_list[j], dst_list[j], weights[j])
+            edge_info_list.append(edge_info)
+
+        # sorted_edge_info_list = sorted(edge_info_list, key=lambda element: (element[0], element[1]))
+
+        # since it has to be sorted according to the reverse order
+        sorted_edge_info_list = sorted(edge_info_list, key=lambda element: (element[1], element[0]))
+
+        temp_src = []
+        temp_dst = []
+
+        for edge in sorted_edge_info_list:
+            temp_src.append(edge[0])
+            temp_dst.append(edge[1])
+            sorted_edge_weights_lst.append(edge[2])
+
+        sorted_edges_lst.append(temp_src)
+        sorted_edges_lst.append(temp_dst)
+        sorted_edges_lst = np.array(sorted_edges_lst)
+
+        final_edges_lst.append(sorted_edges_lst)
+        final_edge_weights_lst.append(np.array(sorted_edge_weights_lst))
+
+def preprocess_graph_structure(edges):
+    # inspect(edges)
+    tmp_set = set()
+    for i in range(len(edges)):
+        for j in range(len(edges[i][0])):
+            tmp_set.add(edges[i][0][j])
+            tmp_set.add(edges[i][1][j])
+    max_num_nodes = len(tmp_set)
+
+    edge_dict = {}
+    for i in range(len(edges)):
+        edge_set = set()
+        for j in range(len(edges[i][0])):
+            edge_set.add((edges[i][0][j],edges[i][1][j]))
+        edge_dict[str(i)] = edge_set
+    
+    edge_final_dict = {}
+    edge_final_dict["0"] = {"add": list(edge_dict["0"]),"delete": []}
+    for i in range(1,len(edges)):
+        edge_final_dict[str(i)] = {"add": list(edge_dict[str(i)].difference(edge_dict[str(i-1)])), "delete": list(edge_dict[str(i-1)].difference(edge_dict[str(i)]))}
+    
+    return edge_final_dict, max_num_nodes
+
+# GPU | CPU
+def get_default_device():
+    
+    if torch.cuda.is_available():
+        return torch.device('cuda:0')
+    else:
+        return torch.device('cpu')
+
+def to_default_device(data):
+    
+    if isinstance(data,(list,tuple)):
+        return [to_default_device(x,get_default_device()) for x in data]
+    
+    return data.to(get_default_device(),non_blocking = True)
+
+def gpma_version(args):
+
+    print("😍 GPMA Version")
+    
+    # Data
+    # dataset = EnglandCovidDatasetLoader()
+    dataset = BitcoinOtcDataloader()
+
+    edges_lst, edge_weights_lst, all_features, all_targets = dataset.get_dataset()
+    presort_edge_weights(edges_lst, edge_weights_lst)
+    train_graph_log_dict, train_max_num_nodes = preprocess_graph_structure(edges_lst)
+    
+    # prev = train_graph_log_dict["0"]
+    # curr = train_graph_log_dict["1"]
+    
+    # inspect(edge_weights_lst)
+    
+    # for key, val in curr["delete"]:
+    #     found = False
+    #     for k1, v1 in prev["add"]:
+    #         if k1 == key and v1 == val:
+    #             found = True
+    #     if not found:
+    #         print("Error ({},{}) not found".format(key,val))
+    
+    t0 = time.time()
+    G = GPMAGraph(train_graph_log_dict,train_max_num_nodes)
+    t1 = time.time()
+    print("Timestamp = Base   |   Time Taken = {:.5f}".format(t1-t0))
+    
+    a = time.time()
+    for i in range(1,18):
+        t0 = time.time()
+        G.get_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+    
+    print(f'\n⏱️ Time taken for forward propagation: {b-a}\n')
+    
+    print("\n### Reverse Graph\n")
+    
+    a = time.time()
+    for i in reversed(range(1,18)):
+        t0 = time.time()
+        G.get_backward_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+
+    print(f'\n⏱️ Time taken for backward propagation: {b-a}\n')
+    
+def pcsr_version(args):
+    print("🎉 PCSR Version")
+    
+    # Data
+    # dataset = EnglandCovidDatasetLoader()
+    dataset = BitcoinOtcDataloader()
+
+    edges_lst, edge_weights_lst, all_features, all_targets = dataset.get_dataset()
+    presort_edge_weights(edges_lst, edge_weights_lst)
+    train_graph_log_dict, train_max_num_nodes = preprocess_graph_structure(edges_lst)
+    
+    t0 = time.time()
+    G = PCSRGraph(train_graph_log_dict,train_max_num_nodes)
+    t1 = time.time()
+    print("Timestamp = Base   |   Time Taken = {:.5f}".format(t1-t0))
+    
+    
+    a = time.time()
+    for i in range(1,18):
+        t0 = time.time()
+        G.get_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+    
+    print(f'\n⏱️ Time taken for forward propagation: {b-a}\n')
+    
+    print("\n### Reverse Graph\n")
+    
+    a = time.time()
+    for i in reversed(range(1,18)):
+        t0 = time.time()
+        G.get_backward_graph(i)
+        t1 = time.time()
+        print("Timestamp = {}   |   Time Taken = {:.5f}".format(i,t1-t0))
+    b = time.time()
+
+    print(f'\n⏱️ Time taken for backward propagation: {b-a}\n')
+
+gpma_version(None)
+print("\n----------------------------------------------------------\n")
+pcsr_version(None)
\ No newline at end of file
diff --git a/profiler/test_add.cu b/profiler/test_add.cu
new file mode 100644
index 0000000..7f6a03e
--- /dev/null
+++ b/profiler/test_add.cu
@@ -0,0 +1,41 @@
+#include <thrust/device_vector.h>
+#include <thrust/host_vector.h>
+#include <thrust/remove.h>
+#include <thrust/sort.h>
+#include <cub/cub.cuh>
+
+int main()
+{
+    thrust::host_vector<int> vec_1(500, 10);
+
+    cudaEvent_t start, stop;
+    cudaEventCreate(&start);
+    cudaEventCreate(&stop);
+
+    cudaEventRecord(start);
+    thrust::device_vector<int> vec_2 = vec_1;
+    cudaEventRecord(stop);
+
+    cudaEventSynchronize(stop);
+    float milliseconds = 0;
+    cudaEventElapsedTime(&milliseconds, start, stop);
+
+    printf("\n⏱️ Time taken: %.3f\n", milliseconds);
+
+    thrust::host_vector<int> vec_3(500, 10);
+    thrust::device_vector<int> d_vec_3(500);
+    cudaEventCreate(&start);
+    cudaEventCreate(&stop);
+
+    cudaEventRecord(start);
+    for (int i = 0; i < 500; ++i)
+    {
+        d_vec_3[i] = vec_3[i];
+    }
+    cudaEventRecord(stop);
+
+    cudaEventSynchronize(stop);
+    cudaEventElapsedTime(&milliseconds, start, stop);
+
+    printf("\n⏱️ Time taken: %.3f\n", milliseconds);
+}
\ No newline at end of file
diff --git a/profiler/test_profiler.py b/profiler/test_profiler.py
new file mode 100644
index 0000000..1f65b84
--- /dev/null
+++ b/profiler/test_profiler.py
@@ -0,0 +1,8 @@
+import cProfile
+
+def factorial(n):
+    if n == 1:
+        return 1
+    return n * factorial(n-1)
+
+cProfile.run('factorial(500)')
\ No newline at end of file
diff --git a/profiler/test_vec_add.cu b/profiler/test_vec_add.cu
new file mode 100644
index 0000000..e068f23
--- /dev/null
+++ b/profiler/test_vec_add.cu
@@ -0,0 +1,131 @@
+/*
+ * A simplified example of vector addition in CUDA to illustrate the
+ * data decomposition pattern using blocks of threads.
+ *
+ * To compile:
+ *   nvcc -o va-GPU-simple VA-GPU-simple.cu
+ */
+
+#include <stdio.h>
+
+// In this example we use a very small number of blocks
+// and threads in those blocks for illustration
+// on a very small array
+#define N 100
+#define numThread 2 // 2 threads in a block
+#define numBlock 4  // 4 blocks
+
+/*
+ * 1.
+ *  The 'kernel' function that will be executed on the GPU device hardware.
+ */
+__global__ void add(int *a, int *b, int *c)
+{
+
+    // the initial index that this thread will work on
+    int tid = blockDim.x * blockIdx.x + threadIdx.x;
+
+    // In this above example code, we assume a linear set of blocks of threads in the 'x' dimension,
+    // which is declared in main below when we run this function.
+
+    // The actual computation is being done by individual threads
+    // in each of the blocks.
+    // e.g. we use 4 blocks and 2 threads per block (8 threads will run in parallel)
+    //      and our total array size N is 8
+    //      the thread whose threadIdx.x is 0 within block 0 will compute c[0],
+    //          because tid = (2 * 0)  + 0
+    //      the thread whose threadIdx.x is 0 within block 1 will compute c[2],
+    //          because tid = (2 * 1) + 0
+    //      the thread whose threadIdx.x is 1 within block 1 will compute c[3],
+    //          because tid = (2 * 1) + 1
+    //
+    //     The following while loop will execute once for this simple example:
+    //          c[0] through c[7] will be computed concurrently
+    //
+    while (tid < N)
+    {
+        c[tid] = a[tid] + b[tid]; // The actual computation done by the thread
+        tid += blockDim.x;        // Increment this thread's index by the number of threads per block:
+                                  // in this small case, each thread would then have a tid > N
+    }
+}
+
+/*
+ * The main program that directs the execution of vector add on the GPU
+ */
+int main(void)
+{
+    int *a, *b, *c;             // The arrays on the host CPU machine
+    int *dev_a, *dev_b, *dev_c; // The arrays for the GPU device
+
+    // 2.a allocate the memory on the CPU
+    a = (int *)malloc(N * sizeof(int));
+    b = (int *)malloc(N * sizeof(int));
+    c = (int *)malloc(N * sizeof(int));
+
+    // 2.b. fill the arrays 'a' and 'b' on the CPU with dummy values
+    for (int i = 0; i < N; i++)
+    {
+        a[i] = i;
+        b[i] = i;
+    }
+
+    // 2.c. allocate the memory on the GPU
+    cudaMalloc((void **)&dev_a, N * sizeof(int));
+    cudaMalloc((void **)&dev_b, N * sizeof(int));
+    cudaMalloc((void **)&dev_c, N * sizeof(int));
+
+    // 2.d. copy the arrays 'a' and 'b' to the GPU
+    cudaMemcpy(dev_a, a, N * sizeof(int),
+               cudaMemcpyHostToDevice);
+    cudaMemcpy(dev_b, b, N * sizeof(int),
+               cudaMemcpyHostToDevice);
+
+    cudaEvent_t start, stop;
+    cudaEventCreate(&start);
+    cudaEventCreate(&stop);
+
+    // 3. Execute the vector addition 'kernel function' on th GPU device,
+    // declaring how many blocks and how many threads per block to use.
+    cudaEventRecord(start);
+    add<<<numBlock, numThread>>>(dev_a, dev_b, dev_c);
+    cudaEventRecord(stop);
+
+    // 4. copy the array 'c' back from the GPU to the CPU
+    cudaMemcpy(c, dev_c, N * sizeof(int),
+               cudaMemcpyDeviceToHost);
+
+    cudaEventSynchronize(stop);
+    float milliseconds = 0;
+    cudaEventElapsedTime(&milliseconds, start, stop);
+
+    printf("\n⏱️ Time taken: %f\n", milliseconds);
+
+    // verify that the GPU did the work we requested
+    bool success = true;
+    int total = 0;
+    printf("Checking %d values in the array.\n", N);
+    for (int i = 0; i < N; i++)
+    {
+        if ((a[i] + b[i]) != c[i])
+        {
+            printf("Error:  %d + %d != %d\n", a[i], b[i], c[i]);
+            success = false;
+        }
+        total += 1;
+    }
+    if (success)
+        printf("We did it, %d values correct!\n", total);
+
+    // free the memory we allocated on the CPU
+    free(a);
+    free(b);
+    free(c);
+
+    // free the memory we allocated on the GPU
+    cudaFree(dev_a);
+    cudaFree(dev_b);
+    cudaFree(dev_c);
+
+    return 0;
+}
\ No newline at end of file
diff --git a/seastar/graph/dynamic/gpma/test_gpmagraph.py b/seastar/graph/dynamic/gpma/test_gpmagraph.py
new file mode 100644
index 0000000..951ad7f
--- /dev/null
+++ b/seastar/graph/dynamic/gpma/test_gpmagraph.py
@@ -0,0 +1,73 @@
+from seastar.graph.dynamic.gpma.GPMAGraph import GPMAGraph
+from seastar.graph.dynamic.gpma.gpma import GPMA, init_gpma, print_gpma_info, edge_update_list, label_edges, copy_label_edges, build_reverse_gpma, load_graph
+from rich import inspect
+# g = GPMA()
+# load_graph(g, "facebook.txt")
+
+# print_gpma_info(g, 0)
+
+graph_updates = {
+    "0": {
+        "add": [(2,1),(2,5),(2,3)],
+        "delete": []
+    },
+    "1": {
+        "add": [(1,5),(5,4),(3,4)],
+        "delete": [(2,5)]
+    },
+    "2": {
+        "add": [(3,1)],
+        "delete": [(2,1),(1,5),(5,4)]
+    }
+}
+
+g = GPMAGraph(graph_updates=graph_updates,max_num_nodes=6)
+
+for i in range(6):
+    print_gpma_info(g.forward_graph,i)
+
+g.get_forward_graph_for_timestamp(1)
+
+print("#####\n")
+
+for i in range(6):
+    print_gpma_info(g.forward_graph,i)
+
+g.get_forward_graph_for_timestamp(2)
+
+print("#####\n")
+
+for i in range(6):
+    print_gpma_info(g.forward_graph,i)
+
+print("#####\n")
+
+g.get_backward_graph_for_timestamp(2)
+
+for i in range(6):
+    print_gpma_info(g.backward_graph,i)
+
+print("#####\n")
+
+g.get_backward_graph_for_timestamp(1)
+
+for i in range(6):
+    print_gpma_info(g.backward_graph,i)
+
+print("#####\n")
+
+g.get_backward_graph_for_timestamp(0)
+
+for i in range(6):
+    print_gpma_info(g.backward_graph,i)
+
+print("#####\n")
+
+inspect(g)
+
+print(g.row_offset_ptr)
+print(g.column_indices_ptr)
+print(g.eids_ptr)
+print(g.num_nodes)
+print(g.num_edges)
+
diff --git a/seastar/graph/dynamic/gpma/test_main.py b/seastar/graph/dynamic/gpma/test_main.py
new file mode 100644
index 0000000..0b01069
--- /dev/null
+++ b/seastar/graph/dynamic/gpma/test_main.py
@@ -0,0 +1,64 @@
+from seastar.graph.dynamic.gpma.gpma import GPMA, init_gpma, load_graph, print_gpma_info, edge_update_list, label_edges, copy_label_edges, get_csr_ptrs
+from rich import inspect
+
+# initialsing the GPMA graph 
+# g = GPMA()
+# init_gpma(g, 10)   # num of nodes
+
+# edge_update_list(g, [
+#     (0,1),
+#     (0,2),
+#     (0,3),
+#     (1,2),
+#     (2,4),
+#     (3,6),
+#     (6,9),
+#     (6,8),
+#     (7,9)
+# ], is_delete=False, is_reverse_edge=True)
+
+# label_edges(g)
+
+# for i in range(10):
+#     print_gpma_info(g, i)
+
+#########################################
+
+# initialsing the GPMA graph 
+g = GPMA()
+init_gpma(g, 10)   # num of nodes
+
+g1 = GPMA()
+init_gpma(g1, 10)   # num of nodes
+
+edge_update_list(g, [
+    (1,2),
+    (3,2),
+    (4,3),
+    (4,5),
+    (5,1)
+])
+
+label_edges(g)
+
+edge_update_list(g1, [
+    (2,1),
+    (2,3),
+    (3,4),
+    (5,4),
+    (1,5)
+])
+
+copy_label_edges(g1,g)
+
+# for i in range(6):
+#     print_gpma_info(g, i)
+
+print("###################")
+
+# for i in range(6):
+#     print_gpma_info(g1, i)
+
+
+print(g1.row_offset_ptr)
+print("\nHOLAAA")
\ No newline at end of file
diff --git a/seastar/graph/dynamic/pcsr/test_pcsr.cu b/seastar/graph/dynamic/pcsr/test_pcsr.cu
new file mode 100644
index 0000000..d2ced36
--- /dev/null
+++ b/seastar/graph/dynamic/pcsr/test_pcsr.cu
@@ -0,0 +1,1030 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include <algorithm>
+#include <queue>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <tuple>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <iostream>
+
+#include <thrust/device_vector.h>
+#include <thrust/host_vector.h>
+#include <thrust/remove.h>
+#include <thrust/sort.h>
+#include <cub/cub.cuh>
+
+namespace py = pybind11;
+using namespace pybind11::literals;
+using namespace std;
+
+typedef thrust::device_vector<int> DEV_VEC;
+#define RAW_PTR(x) thrust::raw_pointer_cast((x).data())
+
+////////////////////////////////////////////////////////////////////////////
+
+typedef struct _node
+{
+    // beginning and end of the associated region in the edge list
+    uint32_t beginning;     // deleted = max int
+    uint32_t end;           // end pointer is exclusive
+    uint32_t num_neighbors; // number of edges with this node as source
+    uint32_t in_degree;     // in-degree of a node -  number of edges going into the node
+
+    _node(int beg = 0, int _end = 0, int num_neigh = 0, int in_deg = 0)
+    {
+        beginning = beg;
+        end = _end;
+        num_neighbors = num_neigh;
+        in_degree = in_deg;
+    }
+} node_t;
+
+typedef struct _edge
+{
+    uint32_t dest;  // destination of this edge in the graph, MAX_INT if this is a sentinel
+    uint32_t value; // edge value of zero means it a null since we don't store 0 edges
+
+    _edge(int _dest = 0, int _value = 0)
+    {
+        dest = _dest;
+        value = _value;
+    }
+} edge_t;
+
+typedef struct edge_list
+{
+    int N;
+    int H;
+    int logN;
+    vector<edge_t> items;
+
+    edge_list()
+    {
+        N = 0;
+        H = 0;
+        logN = 0;
+
+        vector<edge_t> temp(0, 0);
+        items = temp;
+    }
+} edge_list_t;
+
+typedef struct _pair_int
+{
+    int x; // length in array
+    int y; // depth
+
+    _pair_int(int _x = 0, int _y = 0)
+    {
+        x = _x;
+        y = _y;
+    }
+} pair_int;
+
+typedef struct _pair_double
+{
+    double x;
+    double y;
+
+    _pair_double(double _x = 0, double _y = 0)
+    {
+        x = _x;
+        y = _y;
+    }
+} pair_double;
+
+////////////////////////////////////////////////////////////////////////////
+
+static inline int bsf_word(int word)
+{
+    int result;
+    __asm__ volatile("bsf %1, %0"
+                     : "=r"(result)
+                     : "r"(word));
+    return result;
+}
+
+static inline int bsr_word(int word)
+{
+    int result;
+    __asm__ volatile("bsr %1, %0"
+                     : "=r"(result)
+                     : "r"(word));
+    return result;
+}
+
+// given index, return the starting index of the leaf it is in
+int find_leaf(edge_list_t *list, int index)
+{
+    return (index / list->logN) * list->logN;
+}
+
+bool is_null(edge_t e) { return e.value == 0; }
+
+bool is_sentinel(edge_t e)
+{
+    return e.dest == UINT32_MAX || e.value == UINT32_MAX;
+}
+
+uint32_t binary_search(edge_list_t *list, edge_t *elem, uint32_t start,
+                       uint32_t end)
+{
+    while (start + 1 < end)
+    {
+        uint32_t mid = (start + end) / 2;
+
+        edge_t item = list->items[mid];
+        uint32_t change = 1;
+        uint32_t check = mid;
+
+        bool flag = true;
+        while (is_null(item) && flag)
+        {
+            flag = false;
+            check = mid + change;
+            if (check < end)
+            {
+                flag = true;
+                if (check <= end)
+                {
+                    item = list->items[check];
+                    if (!is_null(item))
+                    {
+                        break;
+                    }
+                    else if (check == end)
+                    {
+                        break;
+                    }
+                }
+            }
+            check = mid - change;
+            if (check >= start)
+            {
+                flag = true;
+                item = list->items[check];
+            }
+            change++;
+        }
+
+        if (is_null(item) || start == check || end == check)
+        {
+            if (!is_null(item) && start == check && elem->dest <= item.dest)
+            {
+                return check;
+            }
+            return mid;
+        }
+
+        // if we found it, return
+        if (elem->dest == item.dest)
+        {
+            return check;
+        }
+        else if (elem->dest < item.dest)
+        {
+            end =
+                check; // if the searched for item is less than current item, set end
+        }
+        else
+        {
+            start = check;
+            // otherwise, searched for item is more than current and we set start
+        }
+    }
+    if (end < start)
+    {
+        start = end;
+    }
+    // handling the case where there is one element left
+    // if you are leq, return start (index where elt is)
+    // otherwise, return end (no element greater than you in the range)
+    // printf("start = %d, end = %d, n = %d\n", start,end, list->N);
+    if (elem->dest <= list->items[start].dest && !is_null(list->items[start]))
+    {
+        return start;
+    }
+    return end;
+}
+
+// get density of a node
+double get_density(edge_list_t *list, int index, int len)
+{
+    int full = 0;
+    for (int i = index; i < index + len; i++)
+    {
+        full += (!is_null(list->items[i]));
+    }
+    double full_d = (double)full;
+    return full_d / len;
+}
+
+int find_node(int index, int len) { return (index / len) * len; }
+
+pair_double density_bound(edge_list_t *list, int depth)
+{
+    pair_double pair;
+
+    // between 1/4 and 1/2
+    // pair.x = 1.0/2.0 - (( .25*depth)/list->H);
+    // between 1/8 and 1/4
+    pair.x = 1.0 / 4.0 - ((.125 * depth) / list->H);
+    pair.y = 3.0 / 4.0 + ((.25 * depth) / list->H);
+    return pair;
+}
+
+bool edge_equals(edge_t e1, edge_t e2)
+{
+    return e1.dest == e2.dest && e1.value == e2.value;
+}
+
+uint32_t find_elem_pointer(edge_list_t *list, uint32_t index, edge_t elem)
+{
+    edge_t item = list->items[index];
+    while (!edge_equals(item, elem))
+    {
+        item = list->items[++index];
+    }
+    return index;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+class PCSR
+{
+public:
+    // data members
+    std::vector<node_t> nodes;
+    edge_list_t edges;
+    uint32_t edge_count;
+
+    // member functions
+    PCSR(uint32_t init_n);
+    void init_graph(std::string graph_file_path);
+
+    void add_node();
+    void add_edge(uint32_t src, uint32_t dest, uint32_t value);
+    void add_edge_update(uint32_t src, uint32_t dest, uint32_t value);
+    void edge_update_list(std::vector<std::tuple<uint32_t, uint32_t>> edge_list, bool is_delete, bool is_reverse_edge);
+    void delete_edge(uint32_t src, uint32_t dest);
+    uint64_t get_n();
+    vector<tuple<uint32_t, uint32_t, uint32_t>> get_edges();
+    void print_graph();
+    void print_array();
+    std::tuple<std::size_t, std::size_t, std::size_t> get_csr_ptrs(std::vector<int> eids);
+    void label_edges();
+    uint32_t find_edge_id(uint32_t src, uint32_t dest);
+    std::tuple<uint32_t, uint32_t> get_graph_attr();
+
+    uint32_t insert(uint32_t index, edge_t elem, uint32_t src);
+    void double_list();
+    int slide_right(int index);
+    void slide_left(int index);
+    void fix_sentinel(int32_t node_index, int in);
+    void redistribute(int index, int len);
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+// removed default argument of 0
+PCSR::PCSR(uint32_t init_n)
+{
+    if (init_n != 0)
+    {
+        edges.N = 2 << bsr_word(init_n);
+        edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
+        edges.H = bsr_word(edges.N / edges.logN);
+        edge_count = 0;
+
+        for (int i = 0; i < edges.N; i++)
+        {
+            edge_t new_edge(0, 0);
+            edges.items.push_back(new_edge);
+        }
+
+        for (int i = 0; i < init_n; i++)
+        {
+            add_node();
+        }
+    }
+}
+
+void PCSR::init_graph(std::string graph_file_path)
+{
+
+    FILE *fp;
+    fp = fopen(graph_file_path.c_str(), "r");
+    if (!fp)
+    {
+        printf("Open file failed.\n");
+        exit(-1);
+    }
+
+    // loading the number of nodes and edges present in
+    // the graph into the variables node_size and edge_size respectively
+    int node_size, edge_size;
+    fscanf(fp, "%d %d", &node_size, &edge_size);
+
+    edges.N = 2 << bsr_word(node_size);
+    edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
+    edges.H = bsr_word(edges.N / edges.logN);
+
+    for (int i = 0; i < edges.N; i++)
+    {
+        edge_t new_edge(0, 0);
+        edges.items.push_back(new_edge);
+    }
+
+    for (int i = 0; i < node_size; i++)
+    {
+        add_node();
+    }
+
+    for (int i = 0; i < edge_size; i++)
+    {
+        int source, dest, edge_value;
+        fscanf(fp, "%d %d %d", &source, &dest, &edge_value);
+        add_edge(source, dest, edge_value);
+    }
+}
+
+// add a node to the graph
+void PCSR::add_node()
+{
+    node_t node;
+    int len = nodes.size();
+
+    edge_t sentinel;
+    sentinel.dest = UINT32_MAX; // placeholder
+    sentinel.value = len;       // back pointer
+
+    if (len > 0)
+    {
+        node.beginning = nodes[len - 1].end;
+        node.end = node.beginning + 1;
+    }
+    else
+    {
+        node.beginning = 0;
+        node.end = 1;
+        sentinel.value = UINT32_MAX;
+    }
+    node.num_neighbors = 0;
+
+    nodes.push_back(node);
+    insert(node.beginning, sentinel, nodes.size() - 1);
+}
+
+uint32_t PCSR::insert(uint32_t index, edge_t elem, uint32_t src)
+{
+    int node_index = find_leaf(&edges, index);
+    int level = edges.H;
+    int len = edges.logN;
+
+    // always deposit on the left
+    if (is_null(edges.items[index]))
+    {
+        edges.items[index].value = elem.value;
+        edges.items[index].dest = elem.dest;
+    }
+    else
+    {
+        // if the edge already exists in the graph, update its value
+        // do not make another edge
+        // return index of the edge that already exists
+        if (!is_sentinel(elem) && edges.items[index].dest == elem.dest)
+        {
+            edges.items[index].value = elem.value;
+            return index;
+        }
+        if (index == edges.N - 1)
+        {
+            // when adding to the end double then add edge
+            double_list();
+            node_t node = nodes[src];
+            uint32_t loc_to_add =
+                binary_search(&edges, &elem, node.beginning + 1, node.end);
+            return insert(loc_to_add, elem, src);
+        }
+        else
+        {
+            if (slide_right(index) == -1)
+            {
+                index -= 1;
+                slide_left(index);
+            }
+        }
+        edges.items[index].value = elem.value;
+        edges.items[index].dest = elem.dest;
+    }
+
+    double density = get_density(&edges, node_index, len);
+
+    // spill over into next level up, node is completely full.
+    if (density == 1)
+    {
+        node_index = find_node(node_index, len * 2);
+        redistribute(node_index, len * 2);
+    }
+    else
+    {
+        // makes the last slot in a section empty so you can always slide right
+        redistribute(node_index, len);
+    }
+
+    // get density of the leaf you are in
+    pair_double density_b = density_bound(&edges, level);
+    density = get_density(&edges, node_index, len);
+
+    // while density too high, go up the implicit tree
+    // go up to the biggest node above the density bound
+    while (density >= density_b.y)
+    {
+        len *= 2;
+        if (len <= edges.N)
+        {
+            level--;
+            node_index = find_node(node_index, len);
+            density_b = density_bound(&edges, level);
+            density = get_density(&edges, node_index, len);
+        }
+        else
+        {
+            // if you reach the root, double the list
+            double_list();
+
+            // search from the beginning because list was doubled
+            return find_elem_pointer(&edges, 0, elem);
+        }
+    }
+    redistribute(node_index, len);
+
+    return find_elem_pointer(&edges, node_index, elem);
+}
+
+void PCSR::double_list()
+{
+    edges.N *= 2;
+    edges.logN = (1 << bsr_word(bsr_word(edges.N) + 1));
+    edges.H = bsr_word(edges.N / edges.logN);
+
+    for (int i = edges.N / 2; i < edges.N; i++)
+    {
+        edge_t new_edge(0, 0);
+        edges.items.push_back(new_edge);
+    }
+
+    redistribute(0, edges.N);
+}
+
+int PCSR::slide_right(int index)
+{
+    int rval = 0;
+    edge_t el = edges.items[index];
+    edges.items[index].dest = 0;
+    edges.items[index].value = 0;
+    index++;
+    while (index < edges.N && !is_null(edges.items[index]))
+    {
+        edge_t temp = edges.items[index];
+        edges.items[index] = el;
+        if (!is_null(el) && is_sentinel(el))
+        {
+            // fixing pointer of node that goes to this sentinel
+            uint32_t node_index = el.value;
+            if (node_index == UINT32_MAX)
+            {
+                node_index = 0;
+            }
+            fix_sentinel(node_index, index);
+        }
+        el = temp;
+        index++;
+    }
+    if (!is_null(el) && is_sentinel(el))
+    {
+        // fixing pointer of node that goes to this sentinel
+        uint32_t node_index = el.value;
+        if (node_index == UINT32_MAX)
+        {
+            node_index = 0;
+        }
+        fix_sentinel(node_index, index);
+    }
+    // TODO There might be an issue with this going of the end sometimes
+    if (index == edges.N)
+    {
+        index--;
+        slide_left(index);
+        rval = -1;
+        printf("slide off the end on the right, should be rare\n");
+    }
+    edges.items[index] = el;
+    return rval;
+}
+
+void PCSR::slide_left(int index)
+{
+    edge_t el = edges.items[index];
+    edges.items[index].dest = 0;
+    edges.items[index].value = 0;
+
+    index--;
+    while (index >= 0 && !is_null(edges.items[index]))
+    {
+        edge_t temp = edges.items[index];
+        edges.items[index] = el;
+        if (!is_null(el) && is_sentinel(el))
+        {
+            // fixing pointer of node that goes to this sentinel
+            uint32_t node_index = el.value;
+            if (node_index == UINT32_MAX)
+            {
+                node_index = 0;
+            }
+
+            fix_sentinel(node_index, index);
+        }
+        el = temp;
+        index--;
+    }
+
+    if (index == -1)
+    {
+        double_list();
+
+        slide_right(0);
+        index = 0;
+    }
+    if (!is_null(el) && is_sentinel(el))
+    {
+        // fixing pointer of node that goes to this sentinel
+        uint32_t node_index = el.value;
+        if (node_index == UINT32_MAX)
+        {
+            node_index = 0;
+        }
+        fix_sentinel(node_index, index);
+    }
+
+    edges.items[index] = el;
+}
+
+void PCSR::fix_sentinel(int32_t node_index, int in)
+{
+    nodes[node_index].beginning = in;
+    if (node_index > 0)
+    {
+        nodes[node_index - 1].end = in;
+    }
+    if (node_index == nodes.size() - 1)
+    {
+        nodes[node_index].end = edges.N - 1;
+    }
+}
+
+void PCSR::redistribute(int index, int len)
+{
+    // printf("REDISTRIBUTE: \n");
+    // print_array();
+    // std::vector<edge_t> space(len); //
+    edge_t new_edge;
+    vector<edge_t> space(len, new_edge);
+
+    int j = 0;
+
+    // move all items in ofm in the range into
+    // a temp array
+    for (int i = index; i < index + len; i++)
+    {
+        space[j] = edges.items[i];
+        // counting non-null edges
+        j += (!is_null(edges.items[i]));
+        // setting section to null
+        edges.items[i].value = 0;
+        edges.items[i].dest = 0;
+    }
+
+    // evenly redistribute for a uniform density
+    double index_d = index;
+    double step = ((double)len) / j;
+    for (int i = 0; i < j; i++)
+    {
+        int in = index_d;
+
+        edges.items[in] = space[i];
+        if (is_sentinel(space[i]))
+        {
+            // fixing pointer of node that goes to this sentinel
+            uint32_t node_index = space[i].value;
+            if (node_index == UINT32_MAX)
+            {
+                node_index = 0;
+            }
+            fix_sentinel(node_index, in);
+        }
+        index_d += step;
+    }
+    //   free(space);
+}
+
+void PCSR::add_edge(uint32_t src, uint32_t dest, uint32_t value)
+{
+    if (value != 0)
+    {
+        node_t node = nodes[src];
+        nodes[src].num_neighbors++;
+        nodes[dest].in_degree++;
+
+        edge_t e;
+        e.dest = dest;
+        e.value = value;
+
+        uint32_t loc_to_add =
+            binary_search(&edges, &e, node.beginning + 1, node.end);
+        insert(loc_to_add, e, src);
+        ++edge_count;
+    }
+}
+
+void PCSR::add_edge_update(uint32_t src, uint32_t dest, uint32_t value)
+{
+    if (value != 0)
+    {
+        node_t node = nodes[src];
+
+        edge_t e;
+        e.dest = dest;
+        e.value = value;
+
+        uint32_t loc_to_add =
+            binary_search(&edges, &e, node.beginning + 1, node.end);
+        if (edges.items[loc_to_add].dest == dest)
+        {
+            edges.items[loc_to_add].value = value;
+            return;
+        }
+        nodes[src].num_neighbors++;
+        nodes[dest].in_degree++;
+        insert(loc_to_add, e, src);
+        ++edge_count;
+    }
+}
+
+void PCSR::delete_edge(uint32_t src, uint32_t dest)
+{
+    edge_t e;
+    e.value = 0;
+    e.dest = dest;
+    uint32_t loc = binary_search(&edges, &e, nodes[src].beginning + 1, nodes[src].end);
+
+    if (!is_null(edges.items[loc]) && edges.items[loc].dest == dest)
+    {
+        edges.items[loc].value = 0;
+        nodes[src].num_neighbors -= 1;
+        nodes[dest].in_degree -= 1;
+    }
+    --edge_count;
+}
+
+uint64_t PCSR::get_n()
+{
+    return nodes.size();
+}
+
+vector<tuple<uint32_t, uint32_t, uint32_t>> PCSR::get_edges()
+{
+    uint64_t n = get_n();
+    vector<tuple<uint32_t, uint32_t, uint32_t>> output(edge_count);
+
+    cout << "Starting of Edges" << std::flush;
+
+    int iter = 0;
+    for (int i = 0; i < n; i++)
+    {
+        uint32_t start = nodes[i].beginning;
+        uint32_t end = nodes[i].end;
+        for (int j = start + 1; j < end; j++)
+        {
+            if (!is_null(edges.items[j]))
+            {
+                output[iter] =
+                    make_tuple(i, edges.items[j].dest, edges.items[j].value);
+                ++iter;
+            }
+        }
+    }
+    cout << "Ending of Edges" << std::flush;
+    return output;
+}
+
+void PCSR::print_graph()
+{
+    int num_vertices = nodes.size();
+
+    // printing the graph matrix column indices
+    for (int i = 0; i < num_vertices; ++i)
+        printf("   %d", i);
+
+    printf("\n");
+
+    for (int i = 0; i < num_vertices; i++)
+    {
+        // +1 to avoid sentinel
+
+        // printing the graph matrix row indices
+        printf("%d ", i);
+        int matrix_index = 0;
+
+        for (uint32_t j = nodes[i].beginning + 1; j < nodes[i].end; j++)
+        {
+            if (!is_null(edges.items[j]))
+            {
+                while (matrix_index < edges.items[j].dest)
+                {
+                    printf("    ");
+                    matrix_index++;
+                }
+                // printf("%03d ", edges.items[j].value);
+                printf(" %d  ", edges.items[j].value);
+                matrix_index++;
+            }
+        }
+        for (uint32_t j = matrix_index; j < num_vertices; j++)
+        {
+            printf("    ");
+        }
+        printf("\n");
+    }
+}
+
+void PCSR::print_array()
+{
+    for (int i = 0; i < edges.N; i++)
+    {
+        if (is_null(edges.items[i]))
+        {
+            printf("%d-x ", i);
+        }
+        else if (is_sentinel(edges.items[i]))
+        {
+            uint32_t value = edges.items[i].value;
+            if (value == UINT32_MAX)
+            {
+                value = 0;
+            }
+            printf("\n%d-s(%u):(%d, %d) ", i, value, nodes[value].beginning,
+                   nodes[value].end);
+        }
+        else
+        {
+            printf("%d-(%d, %u) ", i, edges.items[i].dest, edges.items[i].value);
+        }
+    }
+    printf("\n\n");
+}
+
+std::tuple<std::size_t, std::size_t, std::size_t> PCSR::get_csr_ptrs(std::vector<int> eids)
+{
+
+    // we are building a compressed CSR arrays without
+    // the -1 values which indicates an empty edge
+
+    thrust::host_vector<int> row_offset;
+    thrust::host_vector<int> column_indices(edge_count);
+    thrust::host_vector<int> eids_host(edge_count);
+
+    cout << "Starting th get pcsr on nodes " << nodes.size() << " and col_indices " << edges.items.size() << std::endl
+         << std::flush;
+
+    int row_offset_size = nodes.size() + 1;
+    int column_indices_size = edges.items.size();
+
+    // first element of row offset is always zero
+    row_offset.resize(row_offset_size);
+    // column_indices.resize(edge_count);
+
+    cout << "1" << std::endl
+         << std::flush;
+
+    row_offset[0] = 0;
+
+    for (int i = 0; i < row_offset_size - 1; ++i)
+    {
+        row_offset[i + 1] = nodes[i].num_neighbors + row_offset[i];
+    }
+
+    cout << "2" << std::endl
+         << std::flush;
+
+    int edge_iter = 0;
+    for (int i = 0; i < column_indices_size; ++i)
+    {
+        if (!is_sentinel(edges.items[i]) && !is_null(edges.items[i]))
+        {
+            column_indices[edge_iter] = edges.items[i].dest;
+            ++edge_iter;
+        }
+    }
+
+    cout << "3" << std::endl
+         << std::flush;
+
+    cout << "Edge Count: " << edge_count << "  Edge iter: " << edge_iter << std::endl
+         << std::flush;
+
+    DEV_VEC row_offset_device = row_offset;
+
+    cout << "3.25" << std::endl
+         << std::flush;
+
+    DEV_VEC column_indices_device = column_indices;
+
+    cout << "3.5" << std::endl
+         << std::flush;
+
+    DEV_VEC eids_device;
+
+    cout << "4" << std::endl
+         << std::flush;
+
+    if (eids.size() == 0)
+    {
+        thrust::sequence(eids_host.begin(), eids_host.end());
+    }
+    else
+    {
+        // copying eids
+        std::cout << "edge count: " << edge_count << ", eid.size(): " << eids.size() << std::endl
+                  << std::flush;
+        thrust::copy(eids.begin(), eids.end(), eids_host.begin());
+    }
+    std::cout << "End Processing\n"
+              << std::flush;
+
+    eids_device = eids_host;
+
+    std::tuple<std::size_t, std::size_t, std::size_t> t;
+    std::get<0>(t) = (std::size_t)RAW_PTR(row_offset_device);
+    std::get<1>(t) = (std::size_t)RAW_PTR(column_indices_device);
+    std::get<2>(t) = (std::size_t)RAW_PTR(eids_device);
+    return t;
+}
+
+void PCSR::label_edges()
+{
+
+    uint32_t column_indices_size = edges.items.size();
+    uint32_t counter = 1;
+    for (int i = 0; i < column_indices_size; ++i)
+    {
+        if (!is_sentinel(edges.items[i]) && !is_null(edges.items[i]))
+        {
+            edges.items[i].value = counter;
+            ++counter;
+        }
+    }
+}
+
+uint32_t PCSR::find_edge_id(uint32_t src, uint32_t dest)
+{
+    edge_t e;
+    e.dest = dest;
+    // setting a random number
+    e.value = 0;
+
+    uint32_t loc = binary_search(&edges, &e, nodes[src].beginning + 1, nodes[src].end);
+    return edges.items[loc].value;
+}
+
+std::tuple<uint32_t, uint32_t> PCSR::get_graph_attr()
+{
+    std::tuple<uint32_t, uint32_t> t;
+    std::get<0>(t) = nodes.size();
+    std::get<1>(t) = edge_count;
+    return t;
+}
+
+void copy_label_edges(PCSR &pcsr, PCSR &ref_pcsr)
+{
+    uint64_t n = pcsr.get_n();
+
+    for (int i = 0; i < n; i++)
+    {
+        uint32_t start = pcsr.nodes[i].beginning;
+        uint32_t end = pcsr.nodes[i].end;
+        for (int j = start + 1; j < end; j++)
+        {
+            if (!is_null(pcsr.edges.items[j]))
+            {
+                // Searching for the edge_id of reverse edge from reference_pcsr
+                pcsr.edges.items[j].value = ref_pcsr.find_edge_id(pcsr.edges.items[j].dest, i);
+            }
+        }
+    }
+}
+
+void PCSR::edge_update_list(std::vector<std::tuple<uint32_t, uint32_t>> edge_list, bool is_delete = false, bool is_reverse_edge = false)
+{
+    for (auto &edge : edge_list)
+    {
+        uint32_t src = (is_reverse_edge == true) ? std::get<1>(edge) : std::get<0>(edge);
+        uint32_t dst = (is_reverse_edge == true) ? std::get<0>(edge) : std::get<1>(edge);
+
+        if (is_delete)
+            delete_edge(src, dst);
+        else
+            add_edge(src, dst, 1);
+    }
+}
+
+void build_reverse_pcsr(PCSR &pcsr, PCSR &ref_pcsr)
+{
+    vector<tuple<uint32_t, uint32_t, uint32_t>> edge_list = ref_pcsr.get_edges();
+    for (auto &edge : edge_list)
+    {
+        // since reverse graph the src and dst are swapped
+        uint32_t src = std::get<1>(edge);
+        uint32_t dst = std::get<0>(edge);
+        uint32_t val = std::get<2>(edge);
+        pcsr.add_edge(src, dst, val);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// PCSR Python Module
+
+PYBIND11_MODULE(pcsr, m)
+{
+    m.doc() = "PCSR Dynamic Graph Representation";
+    m.def("copy_label_edges", &copy_label_edges, "Label edges of a PCSR based on another PCSR");
+    m.def("build_reverse_pcsr", &build_reverse_pcsr, "Builds the reverse PCSR based on another PCSR");
+
+    py::class_<node_t>(m, "Node")
+        .def(py::init<int, int, int>(), py::arg("beg") = 0, py::arg("_end") = 0, py::arg("num_neigh") = 0)
+        .def_readwrite("beginning", &_node::beginning)
+        .def_readwrite("end", &_node::end)
+        .def_readwrite("num_neighbors", &_node::num_neighbors)
+        .def_readwrite("in_degree", &_node::in_degree);
+
+    py::class_<edge_t>(m, "Edge")
+        .def(py::init<int, int>(), py::arg("_dest") = 0, py::arg("_value") = 0)
+        .def_readwrite("dest", &edge_t::dest)
+        .def_readwrite("value", &edge_t::value);
+
+    py::class_<edge_list_t>(m, "EdgeList")
+        .def(py::init<>())
+        .def_readwrite("N", &edge_list_t::N)
+        .def_readwrite("H", &edge_list_t::H)
+        .def_readwrite("logN", &edge_list_t::logN)
+        .def_readwrite("items", &edge_list_t::items);
+
+    py::class_<pair_int>(m, "PairInt")
+        .def(py::init<int, int>())
+        .def_readwrite("x", &pair_int::x)
+        .def_readwrite("y", &pair_int::y);
+
+    py::class_<pair_double>(m, "PairDouble")
+        .def(py::init<double, double>())
+        .def_readwrite("x", &pair_double::x)
+        .def_readwrite("y", &pair_double::y);
+
+    py::class_<PCSR>(m, "PCSR")
+        .def(py::init<int>(), py::arg("init_n") = 0)
+        .def("init_graph", &PCSR::init_graph)
+        .def("add_node", &PCSR::add_node)
+        .def("insert", &PCSR::insert)
+        .def("double_list", &PCSR::double_list)
+        .def("slide_right", &PCSR::slide_right)
+        .def("slide_left", &PCSR::slide_left)
+        .def("fix_sentinel", &PCSR::fix_sentinel)
+        .def("redistribute", &PCSR::redistribute)
+        .def("print_graph", &PCSR::print_graph)
+        .def("add_edge", &PCSR::add_edge)
+        .def("add_edge_update", &PCSR::add_edge_update)
+        .def("edge_update_list", &PCSR::edge_update_list, py::arg("edge_list"), py::arg("is_delete") = false, py::arg("is_reverse_edge") = false)
+        .def("label_edges", &PCSR::label_edges, "Creates edge labels for the current GPMA")
+        .def("delete_edge", &PCSR::delete_edge)
+        .def("get_n", &PCSR::get_n)
+        .def("get_edges", &PCSR::get_edges)
+        .def("print_array", &PCSR::print_array)
+        .def("get_csr_ptrs", &PCSR::get_csr_ptrs, py::arg("eids"))
+        .def("get_graph_attr", &PCSR::get_graph_attr)
+        .def("find_edge_id", &PCSR::find_edge_id)
+        .def_readwrite("nodes", &PCSR::nodes)
+        .def_readwrite("edges", &PCSR::edges)
+        .def("__copy__", [](const PCSR &self)
+             { return PCSR(self); })
+        .def(
+            "__deepcopy__", [](const PCSR &self, py::dict)
+            { return PCSR(self); },
+            "memo"_a);
+}
+
+// /usr/local/cuda-11.7/bin/nvcc $(python3 -m pybind11 --includes) -shared -rdc=true --compiler-options '-fPIC'  -D__CDPRT_SUPPRESS_SYNC_DEPRECATION_WARNING -o pcsr.so pcsr.cu
\ No newline at end of file
diff --git a/seastar/graph/dynamic/pcsr/test_pcsrgraph.py b/seastar/graph/dynamic/pcsr/test_pcsrgraph.py
new file mode 100644
index 0000000..9ab49b0
--- /dev/null
+++ b/seastar/graph/dynamic/pcsr/test_pcsrgraph.py
@@ -0,0 +1,111 @@
+# from seastar.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
+from pcsr import PCSR, build_reverse_pcsr
+# from seastar.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
+import copy
+
+graph_updates = {
+    "0": {
+        "add": [(2,1),(2,5),(2,3)],
+        "delete": []
+    },
+    "1": {
+        "add": [(1,5),(5,4),(3,4)],
+        "delete": [(2,5)]
+    },
+    "2": {
+        "add": [(3,1)],
+        "delete": [(2,1),(1,5),(5,4)]
+    }
+}
+
+# edge_lst = [[(2,1),(2,5),(2,3)],[(1,5),(5,4),(3,4),(2,1),(2,3)],[(3,4),(2,3),(3,1)]]
+
+# g = PCSRGraph(edge_list=edge_lst)
+
+# print("Graph at time=0  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# g.forward_graph.print_graph()
+
+# g._update_graph_forward()
+# print("Graph at time=1  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# g.forward_graph.print_graph()
+
+# g.get_forward_graph_for_timestamp(2)
+# print("Graph at time=2  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# g.forward_graph.print_graph()
+
+# g.get_backward_graph_for_timestamp(2)
+# print("Graph at time=2  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# print("Forward")
+# g.forward_graph.print_graph()
+# print("Backward")
+# g.backward_graph.print_graph()
+
+# g.get_backward_graph_for_timestamp(1)
+# print("Graph at time=1  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# print("Forward")
+# g.forward_graph.print_graph()
+# print("Backward")
+# g.backward_graph.print_graph()
+
+# g.get_backward_graph_for_timestamp(0)
+# print("Graph at time=0  num_nodes={}    num_edges={}".format(g.num_nodes,g.num_edges))
+# print("Forward")
+# g.forward_graph.print_graph()
+# print("Backward")
+# g.backward_graph.print_graph()
+
+# g = PCSR(6)
+
+# g.edge_update_list(graph_updates["0"]["add"])
+# print(f"After first add, num_edges={g.edge_count}")
+
+# g.print_array()
+
+# g1 = copy.deepcopy(g)
+
+# g.edge_update_list(graph_updates["1"]["add"])
+# print(f"After second add, num_edges={g.edge_count}")
+
+# g.edge_update_list(graph_updates["1"]["delete"],is_delete=True)
+# print(f"After second delete, num_edges={g.edge_count}")
+
+# g.edge_update_list(graph_updates["2"]["delete"],is_delete=True)
+# print(f"After third delete, num_edges={g.edge_count}")
+
+# g.edge_update_list(graph_updates["2"]["add"])
+# print(f"After third add, num_edges={g.edge_count}")
+
+# g1.print_array()
+
+edge_lst = [[(2,1),(2,5),(2,3)],[(1,5),(5,4),(3,4),(2,1),(2,3)],[(3,4),(2,3),(3,1)]]
+print("HEREE",flush=True)
+# g = PCSRGraph(edge_list=edge_lst)
+# g._update_graph_forward()
+# g.current_timestamp += 1
+# g._update_graph_forward()
+# g.current_timestamp += 1
+# g._init_reverse_graph()
+# g._is_backprop_state = True
+# g._update_graph_backward()
+# g.current_timestamp -= 1
+# g._update_graph_backward()
+# g.current_timestamp -= 1
+# print(g._backward_graph.edge_count)
+
+# g.get_graph(0)
+# g.get_graph(1)
+# g.get_graph(2)
+# g.get_backward_graph(2)
+# g.get_backward_graph(1)
+# g.get_backward_graph(0)
+# print(g._forward_graph.edge_count)
+
+fwd = PCSR(6)
+bwd = PCSR(6)
+
+fwd.edge_update_list(edge_lst[0], is_reverse_edge=True)
+fwd.edge_update_list([(1,5),(5,4),(3,4)], is_reverse_edge=True)
+build_reverse_pcsr(bwd,fwd)
+print(bwd.edge_count)
+
+
diff --git a/seastar/graph/dynamic/test_DynamicGraph.py b/seastar/graph/dynamic/test_DynamicGraph.py
new file mode 100644
index 0000000..142cb20
--- /dev/null
+++ b/seastar/graph/dynamic/test_DynamicGraph.py
@@ -0,0 +1,133 @@
+# import copy
+# import time
+
+
+# from seastar.graph.SeastarGraph import SeastarGraph
+# from rich import inspect
+
+
+# from abc import ABC, abstractmethod
+
+# class DynamicGraph(SeastarGraph):
+#     def __init__(self, edge_list):
+#         super().__init__()
+#         self.graph_updates = {}
+#         self.max_num_nodes = 0
+#         self.graph_cache = {}
+        
+#         # Indicates whether the graph is currently undergoing backprop
+#         self._is_backprop_state = False
+#         self.current_timestamp = 0
+        
+#         self._preprocess_graph_structure(edge_list)
+        
+#     def _get_graph_attr(self, edge_list):
+#         graph_attr = {}
+#         for time in range(len(edge_list)):
+#             node_set = set()
+#             edge_count = 0
+#             for edge in edge_list[time]:
+#                 src = edge[0]
+#                 dst = edge[1]
+#                 edge_count += 1
+#                 node_set.add(src)
+#                 node_set.add(dst)
+#             node_count = len(node_set)
+#             graph_attr[str(time)] = (node_count, edge_count)
+        
+#         return graph_attr
+        
+#     def _preprocess_graph_structure(self, edge_list):
+        
+#         graph_attr = self._get_graph_attr(edge_list)
+        
+#         tmp_set = set()
+#         for i in range(len(edge_list)):
+#             tmp_set = set()
+#             for j in range(len(edge_list[i])):
+#                 tmp_set.add(edge_list[i][j][0])
+#                 tmp_set.add(edge_list[i][j][1])
+#         self.max_num_nodes = len(tmp_set)
+
+#         edge_dict = {}
+#         for i in range(len(edge_list)):
+#             edge_set = set()
+#             for j in range(len(edge_list[i])):
+#                 edge_set.add((edge_list[i][j][0], edge_list[i][j][1]))
+#             edge_dict[str(i)] = edge_set
+
+#         self.graph_updates = {}
+#         self.graph_updates["0"] = {
+#             "add": list(edge_dict["0"]),
+#             "delete": [],
+#             "num_nodes": graph_attr["0"][0],
+#             "num_edges": graph_attr["0"][1],
+#         }
+#         for i in range(1, len(edge_list)):
+#             self.graph_updates[str(i)] = {
+#                 "add": list(edge_dict[str(i)].difference(edge_dict[str(i - 1)])),
+#                 "delete": list(edge_dict[str(i - 1)].difference(edge_dict[str(i)])),
+#                 "num_nodes": graph_attr[str(i)][0],
+#                 "num_edges": graph_attr[str(i)][1],
+#             }
+        
+#     def get_graph(self, timestamp: int):
+
+#         if self._is_backprop_state:
+#             self._revert_to_base_graph()
+#             self.current_timestamp = 0
+#             self._is_backprop_state = False
+        
+#         if timestamp < self.current_timestamp:
+#             raise Exception("⏰ Invalid timestamp during SeastarGraph.update_graph_forward()")
+
+#         while self.current_timestamp < timestamp:
+#             self._update_graph_forward()
+#             self.current_timestamp += 1
+
+#     def get_backward_graph(self, timestamp: int):
+#         print(f"🔖🔖🔖Timestamp: {timestamp}  CURRENT: {self.current_timestamp}")
+#         if not self._is_backprop_state:
+#             self._init_reverse_graph()
+#             self._is_backprop_state = True
+        
+#         if timestamp > self.current_timestamp:
+#             raise Exception("⏰ Invalid timestamp during SeastarGraph.update_graph_backward()")
+        
+#         while self.current_timestamp > timestamp:
+#             self._update_graph_backward()
+#             self.current_timestamp -= 1
+    
+#     def get_num_nodes(self):
+#         return self.graph_updates[str(self.current_timestamp)]["num_nodes"]
+    
+#     def get_num_edges(self):
+#         return self.graph_updates[str(self.current_timestamp)]["num_edges"]
+    
+#     @abstractmethod
+#     def in_degrees(self):
+#         pass
+    
+#     @abstractmethod
+#     def out_degrees(self):
+#         pass
+    
+    
+#     @abstractmethod
+#     def _update_graph_forward(self):
+#         pass
+    
+#     @abstractmethod
+#     def _init_reverse_graph(self):
+#         pass
+    
+#     @abstractmethod
+#     def _revert_to_base_graph(self):
+#         pass
+    
+#     @abstractmethod
+#     def _update_graph_backward(self):
+#         pass
+    
+
+
diff --git a/seastar/graph/static/test_csr.py b/seastar/graph/static/test_csr.py
new file mode 100644
index 0000000..fe224bf
--- /dev/null
+++ b/seastar/graph/static/test_csr.py
@@ -0,0 +1,22 @@
+from csr import CSR
+
+edge_list = [
+    (7,2),
+    (2,6),
+    (3,1),
+    (3,6),
+    (2,8),
+    (3,7),
+    (2,1),
+    (8,4),
+    (7,4),
+]
+
+c = CSR(edge_list, 9)
+c.label_edges()
+c.print_graph()
+
+r = CSR(edge_list, 9, is_edge_reverse = True)
+r.copy_label_edges(c)
+r.print_graph()
+r.print_csr_arrays()
\ No newline at end of file
diff --git a/tests/graph/dynamic/pcsr/test_PCSRGraph.py b/tests/graph/dynamic/pcsr/test_PCSRGraph.py
new file mode 100644
index 0000000..7be4d37
--- /dev/null
+++ b/tests/graph/dynamic/pcsr/test_PCSRGraph.py
@@ -0,0 +1,5 @@
+"""
+    Unit tests for seastar.graph.dynamic.pcsr.PCSRGraph module
+"""
+
+from seastar.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
\ No newline at end of file
diff --git a/tests/graph/dynamic/pcsr/test_pcsr.py b/tests/graph/dynamic/pcsr/test_pcsr.py
new file mode 100644
index 0000000..58c0409
--- /dev/null
+++ b/tests/graph/dynamic/pcsr/test_pcsr.py
@@ -0,0 +1,10 @@
+"""
+    Unit tests for seastar.graph.dynamic.pcsr.pcsr.PCSR class
+"""
+
+from seastar.graph.dynamic.pcsr.pcsr import PCSR
+
+class TestPCSR:
+    
+    def test_init(self):
+        pcsr = PCSR()
\ No newline at end of file
diff --git a/tests/graph/dynamic/test_DynamicGraph.py b/tests/graph/dynamic/test_DynamicGraph.py
new file mode 100644
index 0000000..fa69bbf
--- /dev/null
+++ b/tests/graph/dynamic/test_DynamicGraph.py
@@ -0,0 +1,102 @@
+import json
+from rich import inspect
+from seastar.graph.dynamic.DynamicGraph import DynamicGraph
+from seastar.graph.dynamic.naive.NaiveGraph import NaiveGraph
+
+
+def get_edge_list(gra):
+    pass
+
+
+class TestDynamicGraph:
+    # Stores the edge-list for each timestamp
+    # edge_index[t] has the edge list for timestamp = t
+    edge_index = [
+        [[0, 1], [1, 2], [2, 3]],
+        [[1, 2], [2, 3], [3, 1]],
+        [[1, 2], [2, 3], [3, 1], [3, 0]],
+        [[2, 3], [3, 1], [3, 0], [0, 1]],
+    ]
+
+    # stores the edge-weights for each timestamp
+    # edge_weight[t] has the edge weight for timestamp = t
+    # corresponding to the edge in edge_index
+    edge_weight = [[4, 7, 9], [2, 11, 13], [3, 8, 1, 5], [15, 6, 10, 12]]
+
+    # total timestamps for this dynamic graph dataset
+    time_periods = 4
+
+    # node features for each timestamp
+    y = [[2, 11, 15, 8], [9, 5, 7, 10], [1, 3, 17, 19], [4, 6, 12, 13]]
+
+    def test_get_num_nodes(self):
+        """
+            Assert the number of nodes in the graph, then repeat
+            this assert step after calling DynamicGraph.get_graph()
+            and DynamicGraph.get_backward_graph() in sequential order
+        """
+
+        # base graph: t = 0
+        naive_graph = NaiveGraph(edge_list=self.edge_index)
+        assert naive_graph.get_num_nodes() == 4
+
+        # graph: t = 1
+        naive_graph.get_graph(1)
+        assert naive_graph.get_num_nodes() == 3
+
+        # graph: t = 2
+        naive_graph.get_graph(2)
+        assert naive_graph.get_num_nodes() == 4
+
+        # graph: t = 3
+        naive_graph.get_graph(3)
+        assert naive_graph.get_num_nodes() == 4
+
+        # Now moving the graph in the backward direction
+        # graph: t = 2
+        naive_graph.get_backward_graph(2)
+        assert naive_graph.get_num_nodes() == 4
+        
+        # graph: t = 1
+        naive_graph.get_backward_graph(1)
+        assert naive_graph.get_num_nodes() == 3
+        
+        # graph: t = 1
+        naive_graph.get_backward_graph(0)
+        assert naive_graph.get_num_nodes() == 4
+        
+    def test_get_num_edges(self):
+        """
+            Assert the number of edges in the graph, then repeat
+            this assert step after calling DynamicGraph.get_graph()
+            and DynamicGraph.get_backward_graph() in sequential order
+        """
+        
+        # base graph: t = 0
+        naive_graph = NaiveGraph(edge_list=self.edge_index)
+        assert naive_graph.get_num_edges() == 3
+
+        # graph: t = 1
+        naive_graph.get_graph(1)
+        assert naive_graph.get_num_edges() == 3
+
+        # graph: t = 2
+        naive_graph.get_graph(2)
+        assert naive_graph.get_num_edges() == 4
+
+        # graph: t = 3
+        naive_graph.get_graph(3)
+        assert naive_graph.get_num_edges() == 4
+
+        # Now moving the graph in the backward direction
+        # graph: t = 2
+        naive_graph.get_backward_graph(2)
+        assert naive_graph.get_num_edges() == 4
+        
+        # graph: t = 1
+        naive_graph.get_backward_graph(1)
+        assert naive_graph.get_num_edges() == 3
+        
+        # graph: t = 1
+        naive_graph.get_backward_graph(0)
+        assert naive_graph.get_num_edges() == 3
\ No newline at end of file