Repository: bfGraph/STGraph (language: python)

Issue:
📝 Quick Changelog Doc Fixes
Changes the release date for v1.1.0 in the Changelogs Doc Page


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml
new file mode 100644
index 00000000..41ab56d7
--- /dev/null
+++ b/.github/workflows/ruff.yaml
@@ -0,0 +1,34 @@
+name: Ruff Linting
+
+on: [push]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.8"]
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install ruff
+    - name: Analysing the code with ruff
+      run: |
+        cd stgraph/dataset/
+        ruff check .
+        cd ../../
+        cd stgraph/graph
+        ruff check .
+        cd ../../
+        cd stgraph/utils
+        ruff check .
+        cd ../../
+        cd stgraph/nn/pytorch/static
+        ruff check gcn_conv.py
+        cd ../../../../
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d5835c5c..c809e018 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,10 @@ benchmarking/results/
 *.egg-info/
 stgraph/build/
 stgraph/dist/
+dev-stgraph/
 docs/_build
+docs_old/
+docs_v2/
 __pycache__/
 .DS_Store
 build
@@ -25,4 +28,9 @@ dist
 *.svg
 *.json
 *.npy
-dev-stgraph/
\ No newline at end of file
+.coverage
+dev-stgraph/
+htmlconv/
+*.txt
+egl_kernel.cu
+egl_kernel.ptx
\ No newline at end of file
diff --git a/.idea/STGraph.iml b/.idea/STGraph.iml
new file mode 100644
index 00000000..8b8c3954
--- /dev/null
+++ b/.idea/STGraph.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="format" value="PLAIN" />
+    <option name="myDocStringFormat" value="Plain" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..1fbfa1d3
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/STGraph.iml" filepath="$PROJECT_DIR$/.idea/STGraph.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 00000000..3cf40740
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="4baa1f61-eecf-4877-8886-d2920b410d7b" name="Changes" comment="" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="MarkdownSettingsMigration">
+    <option name="stateVersion" value="1" />
+  </component>
+  <component name="ProjectId" id="2WZeYaJ8I0KbBr5dn5McjvPVF7M" />
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
+  </component>
+  <component name="PropertiesComponent"><![CDATA[{
+  "keyToString": {
+    "RunOnceActivity.OpenProjectViewOnStart": "true",
+    "RunOnceActivity.ShowReadmeOnStart": "true",
+    "last_opened_file_path": "C:/Nithin stuff/Final Year Project/Organisation Repos/STGraph"
+  }
+}]]></component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="4baa1f61-eecf-4877-8886-d2920b410d7b" name="Changes" comment="" />
+      <created>1696944579134</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1696944579134</updated>
+    </task>
+    <servers />
+  </component>
+  <component name="Vcs.Log.Tabs.Properties">
+    <option name="TAB_STATES">
+      <map>
+        <entry key="MAIN">
+          <value>
+            <State />
+          </value>
+        </entry>
+      </map>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..f2f3fdbd
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,15 @@
+[FORMAT]
+
+# Set the maximum line length to a value that suits your project
+max-line-length = 120
+
+[MESSAGES CONTROL]
+
+# Disable specific messages by adding them to the "disable" option
+disable=
+    line-too-long,
+    too-many-instance-attributes,
+    too-many-arguments,
+    import-error,
+    too-few-public-methods,
+    # Add more disabled messages here if needed
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 2cbe6845..edf4b0ac 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -13,13 +13,13 @@ build:
 
 # Build documentation in the "docs/" directory with Sphinx
 sphinx:
-   configuration: docs/conf.py
+   configuration: docs/source/conf.py
 
 # Optional but recommended, declare the Python requirements required
 # to build your documentation
 # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
 python:
    install:
-   - requirements: requirements.txt
+   - requirements: docs/requirements.txt
    - method: pip
      path: .
diff --git a/README.md b/README.md
index a93ff1e0..2fac5fea 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
 
 [![Documentation Status](https://readthedocs.org/projects/stgraph/badge/?version=latest)](https://stgraph.readthedocs.io/en/latest/?badge=latest)
 [![TGL Workshop - @ NeurIPS'23](https://img.shields.io/badge/TGL_Workshop-%40_NeurIPS'23-6d4a8f)](https://neurips.cc/virtual/2023/76335)
+[![GrAPL - @IPDPS'24](https://img.shields.io/badge/GrAPL-%40IPDPS'24-282792)](https://hpc.pnl.gov/grapl/index.html)
 [![PyPI - 1.0.0](https://img.shields.io/static/v1?label=PyPI&message=1.0.0&color=%23ffdf76&logo=Python)](https://pypi.org/project/stgraph/)
 
 <div align="center">
@@ -104,68 +105,7 @@ With this you have successfully installed STGraph locally to make development ch
 
 ## Running your first STGraph Program
 
-In this is quick mini tutorial, we will show you how to train a simple GCN model on the Cora dataset. After installing STGraph and entering the STGraph directory, enter the following commands to reach the GCN `benchmarking` folder
-
-```
-cd benchmarking/gcn/stgraph
-```
-
-Run the `train.py`, with 100 epochs and specify the dataset name. For this example, we shall use Cora
-
-```
-python3 train.py --num_epochs 100 --dataset cora
-```
-
-You should get an output like this. The initial prints are truncated.
-
-```
-.
-.
-.
-Epoch 00090 | Time(s) 0.0048 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00091 | Time(s) 0.0024 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00092 | Time(s) 0.0029 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00093 | Time(s) 0.0029 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00094 | Time(s) 0.0027 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00095 | Time(s) 0.0030 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00096 | Time(s) 0.0024 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00097 | Time(s) 0.0022 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00098 | Time(s) 0.0022 | train_acc 0.303791 | Used_Memory 32.975098 mb
-Epoch 00099 | Time(s) 0.0036 | train_acc 0.303791 | Used_Memory 32.975098 mb
-
-^^^0.032202^^^0.003098
-```
-
-If you don't get this output and have followed every single step in the setting up and installation section, please raise an issue we will look into it.
-
-## How to build STGraph
-
-This is for users who want to make changes to the STGraph codebase and get it build each time. Follow the steps mentioned to properly build STGraph.
-
-### Compiling the CUDA code
-
-The following steps need to be done if you made any changes to any CUDA files within the `stgraph/graph` directory for each graph representation.
-
-STGraph supports training dynamic and static graphs. To handle all the graph representations logic, it is written as a PyBind11 module over a CUDA file. As of now the following CUDA code for different graph representations exists
-
-1. `csr.cu`
-2. `pcsr.cu`
-3. `gpma.cu`
-
-To compile the `[name].cu` file, run the following command
-
-```
-/usr/local/cuda-11.7/bin/nvcc $(python3 -m pybind11 --includes) -shared -rdc=true --compiler-options '-fPIC' -D__CDPRT_SUPPRESS_SYNC_DEPRECATION_WARNING -o [name].so [name].cu
-```
-This would generate the [name].so shared object file, that is used in the STGraph module. 
-
-### Building STGraph
-
-Make sure to go back to the root directory and run the following to build and install STGraph
-
-```
- python3 -m build && pip uninstall stgraph -y && pip install dist/stgraph-1.0.0-py3-none-any.whl
-```
+Please have a look inside the `tutorials/` directory to write and train your own GNNs using STGraph
 
 ## Contributing
 
diff --git a/benchmarking/dynamic-temporal-tgcn/pygt/train.py b/benchmarking/dynamic-temporal-tgcn/pygt/train.py
index 4a67e104..8cc093a6 100644
--- a/benchmarking/dynamic-temporal-tgcn/pygt/train.py
+++ b/benchmarking/dynamic-temporal-tgcn/pygt/train.py
@@ -9,7 +9,7 @@
 import os
 from model import PyGT_TGCN
 from stgraph.dataset.LinkPredDataLoader import LinkPredDataLoader
-from stgraph.benchmark_tools.table import BenchmarkTable
+from stgraph.utils import DataTable
 from utils import to_default_device, get_default_device
 
 def main(args):
@@ -71,7 +71,7 @@ def main(args):
     # metrics
     dur = []
     max_gpu = []
-    table = BenchmarkTable(f"(PyGT Dynamic-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"])
+    table = DataTable(f"(PyGT Dynamic-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"])
 
     try:
         # train
diff --git a/benchmarking/dynamic-temporal-tgcn/seastar/train.py b/benchmarking/dynamic-temporal-tgcn/seastar/train.py
index 6afd2b8e..de46f81a 100644
--- a/benchmarking/dynamic-temporal-tgcn/seastar/train.py
+++ b/benchmarking/dynamic-temporal-tgcn/seastar/train.py
@@ -8,13 +8,15 @@
 import sys
 import os
 from stgraph.dataset.LinkPredDataLoader import LinkPredDataLoader
-from stgraph.benchmark_tools.table import BenchmarkTable
-from stgraph.graph.dynamic.gpma.GPMAGraph import GPMAGraph
-from stgraph.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
-from stgraph.graph.dynamic.naive.NaiveGraph import NaiveGraph
+from stgraph.benchmark_tools.table import DataTable
+from stgraph.utils import DataTable
+from stgraph.graph.dynamic.gpma.gpma_graph import GPMAGraph
+from stgraph.graph.dynamic.pcsr.pcsr_graph import PCSRGraph
+from stgraph.graph.dynamic.naive.naive_graph import NaiveGraph
 from model import STGraphTGCN
 from utils import to_default_device, get_default_device
 
+
 def main(args):
 
     if torch.cuda.is_available():
@@ -22,42 +24,102 @@ def main(args):
     else:
         print("😔 CUDA is not available")
         quit()
-    
+
     # dummy object to account for initial CUDA context object
     Graph = None
     if args.type == "naive":
-        Graph = NaiveGraph([[(0,0)]],1)
+        Graph = NaiveGraph([[(0, 0)]], 1)
     elif args.type == "pcsr":
-        Graph = PCSRGraph([[(0,1)]],2) # PCSRGraph([[(0,1)]],2)
+        Graph = PCSRGraph([[(0, 1)]], 2)  # PCSRGraph([[(0,1)]],2)
     elif args.type == "gpma":
-        Graph = GPMAGraph([[(0,0)]],1)
-    
+        Graph = GPMAGraph([[(0, 0)]], 1)
+
     if args.dataset == "math":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'sx-mathoverflow-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"sx-mathoverflow-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "wikitalk":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'wiki-talk-temporal-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"wiki-talk-temporal-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "askubuntu":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'sx-askubuntu-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"sx-askubuntu-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "superuser":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'sx-superuser-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"sx-superuser-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "stackoverflow":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'sx-stackoverflow-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"sx-stackoverflow-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "reddit_title":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'reddit-title-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"reddit-title-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "reddit_body":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'reddit-body-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"reddit-body-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "email":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'email-eu-core-temporal-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"email-eu-core-temporal-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "bitcoin_otc":
-        dataloader = LinkPredDataLoader('dynamic-temporal', f'bitcoin-otc-data-{args.slide_size}', args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = LinkPredDataLoader(
+            "dynamic-temporal",
+            f"bitcoin-otc-data-{args.slide_size}",
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     else:
         print("😔 Unrecognized dataset")
         quit()
 
     edge_lists = dataloader.get_edges()
     pos_neg_edges_lists, pos_neg_targets_lists = dataloader.get_pos_neg_edges()
-    pos_neg_edges_lists = [to_default_device(torch.from_numpy(pos_neg_edges)) for pos_neg_edges in pos_neg_edges_lists]
-    pos_neg_targets_lists = [to_default_device(torch.from_numpy(pos_neg_targets).type(torch.float32)) for pos_neg_targets in pos_neg_targets_lists]
+    pos_neg_edges_lists = [
+        to_default_device(torch.from_numpy(pos_neg_edges))
+        for pos_neg_edges in pos_neg_edges_lists
+    ]
+    pos_neg_targets_lists = [
+        to_default_device(torch.from_numpy(pos_neg_targets).type(torch.float32))
+        for pos_neg_targets in pos_neg_targets_lists
+    ]
 
     pynvml.nvmlInit()
     handle = pynvml.nvmlDeviceGetHandleByIndex(0)
@@ -89,16 +151,27 @@ def main(args):
     backprop_every = args.backprop_every
     if backprop_every == 0:
         backprop_every = total_timestamps
-    
+
     if total_timestamps % backprop_every == 0:
-        num_iter = int(total_timestamps/backprop_every)
+        num_iter = int(total_timestamps / backprop_every)
     else:
-        num_iter = int(total_timestamps/backprop_every) + 1
+        num_iter = int(total_timestamps / backprop_every) + 1
 
     # metrics
     dur = []
     max_gpu = []
-    table = BenchmarkTable(f"(STGraph Dynamic-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)", "Build FWD Graph Time(s)", "Build BWD Graph Time(s)", "Move to GPU Time(s)"])
+    table = DataTable(
+        f"(STGraph Dynamic-Temporal) TGCN on {dataloader.name} dataset",
+        [
+            "Epoch",
+            "Time(s)",
+            "MSE",
+            "Used GPU Memory (Max MB)",
+            "Build FWD Graph Time(s)",
+            "Build BWD Graph Time(s)",
+            "Move to GPU Time(s)",
+        ],
+    )
 
     try:
         # train
@@ -107,7 +180,7 @@ def main(args):
             torch.cuda.synchronize()
             torch.cuda.reset_peak_memory_stats(0)
             model.train()
-            
+
             t0 = time.time()
             gpu_mem_arr = []
             cost_arr = []
@@ -117,7 +190,10 @@ def main(args):
                 optimizer.zero_grad()
                 cost = 0
                 hidden_state = None
-                y_hat = torch.randn((dataloader.max_num_nodes, args.feat_size), device=get_default_device())
+                y_hat = torch.randn(
+                    (dataloader.max_num_nodes, args.feat_size),
+                    device=get_default_device(),
+                )
                 G.get_graph(index * backprop_every)
                 for k in range(backprop_every):
                     t = index * backprop_every + k
@@ -128,7 +204,10 @@ def main(args):
 
                     initial_used_gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used
                     G.get_graph(t)
-                    graph_mem_delta = pynvml.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
+                    graph_mem_delta = (
+                        pynvml.nvmlDeviceGetMemoryInfo(handle).used
+                        - initial_used_gpu_mem
+                    )
                     graph_mem = graph_mem + graph_mem_delta
 
                     if G.get_ndata("norm") is None:
@@ -141,11 +220,11 @@ def main(args):
                     y_hat, hidden_state = model(G, y_hat, None, hidden_state)
                     out = model.decode(y_hat, pos_neg_edges_lists[t]).view(-1)
                     cost = cost + criterion(out, pos_neg_targets_lists[t])
-                
+
                 if cost == 0:
                     break
-        
-                cost = cost / (backprop_every+1)
+
+                cost = cost / (backprop_every + 1)
                 cost.backward()
                 optimizer.step()
                 torch.cuda.synchronize()
@@ -159,61 +238,87 @@ def main(args):
                 dur.append(run_time_this_epoch)
                 max_gpu.append(max(gpu_mem_arr))
 
-            table.add_row([epoch, "{:.5f}".format(run_time_this_epoch), "{:.4f}".format(sum(cost_arr)/len(cost_arr)), "{:.4f}".format((max(gpu_mem_arr) * 1.0 / (1024**2))), "{:.5f}".format(G.get_fwd_graph_time), "{:.5f}".format(G.get_bwd_graph_time), "{:.5f}".format(G.move_to_gpu_time)])
+            table.add_row(
+                [
+                    epoch,
+                    "{:.5f}".format(run_time_this_epoch),
+                    "{:.4f}".format(sum(cost_arr) / len(cost_arr)),
+                    "{:.4f}".format((max(gpu_mem_arr) * 1.0 / (1024**2))),
+                    "{:.5f}".format(G.get_fwd_graph_time),
+                    "{:.5f}".format(G.get_bwd_graph_time),
+                    "{:.5f}".format(G.move_to_gpu_time),
+                ]
+            )
 
         table.display()
-        print('Average Time taken: {:6f}'.format(np.mean(dur)))
+        print("Average Time taken: {:6f}".format(np.mean(dur)))
         return np.mean(dur), (max(max_gpu) * 1.0 / (1024**2))
 
     except RuntimeError as e:
-        if 'out of memory' in str(e):
-            table.add_row(["OOM", "OOM", "OOM", "OOM",  "OOM", "OOM", "OOM"])
+        if "out of memory" in str(e):
+            table.add_row(["OOM", "OOM", "OOM", "OOM", "OOM", "OOM", "OOM"])
             table.display()
         else:
             print("😔 Something went wrong")
         return "OOM", "OOM"
 
+
 def write_results(args, time_taken, max_gpu):
     cutoff = "whole"
     if args.cutoff_time < sys.maxsize:
         cutoff = str(args.cutoff_time)
     file_name = f"stgraph_{args.type}_{args.dataset}_T{cutoff}_S{args.slide_size}_B{args.backprop_every}_H{args.num_hidden}_F{args.feat_size}"
-    df_data = pd.DataFrame([{'Filename': file_name, 'Time Taken (s)': time_taken, 'Max GPU Usage (MB)': max_gpu}])
-    
-    if os.path.exists('../../results/dynamic-temporal.csv'):
-        df = pd.read_csv('../../results/dynamic-temporal.csv')
+    df_data = pd.DataFrame(
+        [
+            {
+                "Filename": file_name,
+                "Time Taken (s)": time_taken,
+                "Max GPU Usage (MB)": max_gpu,
+            }
+        ]
+    )
+
+    if os.path.exists("../../results/dynamic-temporal.csv"):
+        df = pd.read_csv("../../results/dynamic-temporal.csv")
         df = pd.concat([df, df_data])
     else:
         df = df_data
-    
-    df.to_csv('../../results/dynamic-temporal.csv', sep=',', index=False, encoding='utf-8')
 
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='STGraph Static TGCN')
+    df.to_csv(
+        "../../results/dynamic-temporal.csv", sep=",", index=False, encoding="utf-8"
+    )
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="STGraph Static TGCN")
     snoop.install(enabled=False)
 
-    parser.add_argument("--dataset", type=str, default="math",
-            help="Name of the Dataset (math, wikitalk, askubuntu, superuser, stackoverflow, email, bitcoin_otc, reddit_title, reddit_body)")
-    parser.add_argument("--slide-size", type=str, default="1.0",
-            help="Slide Size")
-    parser.add_argument("--type", type=str, default="naive", 
-            help="STGraph Type")
-    parser.add_argument("--backprop-every", type=int, default=0,
-            help="Feature size of nodes")
-    parser.add_argument("--feat-size", type=int, default=8,
-            help="Feature size of nodes")
-    parser.add_argument("--num-hidden", type=int, default=100,
-            help="Number of hidden units")
-    parser.add_argument("--lr", type=float, default=1e-2,
-            help="learning rate")
-    parser.add_argument("--cutoff-time", type=int, default=sys.maxsize,
-        help="cutoff time")
-    parser.add_argument("--num-epochs", type=int, default=1,
-            help="number of training epochs")
+    parser.add_argument(
+        "--dataset",
+        type=str,
+        default="math",
+        help="Name of the Dataset (math, wikitalk, askubuntu, superuser, stackoverflow, email, bitcoin_otc, reddit_title, reddit_body)",
+    )
+    parser.add_argument("--slide-size", type=str, default="1.0", help="Slide Size")
+    parser.add_argument("--type", type=str, default="naive", help="STGraph Type")
+    parser.add_argument(
+        "--backprop-every", type=int, default=0, help="Feature size of nodes"
+    )
+    parser.add_argument(
+        "--feat-size", type=int, default=8, help="Feature size of nodes"
+    )
+    parser.add_argument(
+        "--num-hidden", type=int, default=100, help="Number of hidden units"
+    )
+    parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
+    parser.add_argument(
+        "--cutoff-time", type=int, default=sys.maxsize, help="cutoff time"
+    )
+    parser.add_argument(
+        "--num-epochs", type=int, default=1, help="number of training epochs"
+    )
     args = parser.parse_args()
-    
+
     print(args)
     time_taken, max_gpu = main(args)
     write_results(args, time_taken, max_gpu)
-
-    
\ No newline at end of file
diff --git a/benchmarking/gat/seastar/model.py b/benchmarking/gat/seastar/model.py
index 75826088..3434d316 100644
--- a/benchmarking/gat/seastar/model.py
+++ b/benchmarking/gat/seastar/model.py
@@ -1,5 +1,5 @@
 import torch.nn as nn
-from stgraph.nn.pytorch.gat_conv import GATConv
+from stgraph.nn.pytorch.static.gat_conv import GATConv
 
 class GAT(nn.Module):
     def __init__(self,
diff --git a/benchmarking/gat/seastar/train.py b/benchmarking/gat/seastar/train.py
index aaa52fab..bd98c88d 100644
--- a/benchmarking/gat/seastar/train.py
+++ b/benchmarking/gat/seastar/train.py
@@ -13,7 +13,7 @@
 import torch
 import torch.nn.functional as F
 import pynvml
-from stgraph.graph.static.StaticGraph import StaticGraph
+from stgraph.graph.static.static_graph import StaticGraph
 from stgraph.dataset.CoraDataLoader import CoraDataLoader
 from utils import EarlyStopping, accuracy
 import snoop
@@ -25,8 +25,8 @@ def train(args):
     cora = CoraDataLoader(verbose=True)
 
     # To account for the initial CUDA Context object for pynvml
-    tmp = StaticGraph([(0,0)], [1], 1)
-    
+    tmp = StaticGraph([(0, 0)], [1], 1)
+
     features = torch.FloatTensor(cora.get_all_features())
     labels = torch.LongTensor(cora.get_all_targets())
     train_mask = cora.get_train_mask()
@@ -49,15 +49,15 @@ def train(args):
 
     assert train_mask.shape[0] == num_nodes
 
-    print('dataset {}'.format("Cora"))
-    print('# of edges : {}'.format(num_edges))
-    print('# of nodes : {}'.format(num_nodes))
-    print('# of features : {}'.format(num_feats))
+    print("dataset {}".format("Cora"))
+    print("# of edges : {}".format(num_edges))
+    print("# of nodes : {}".format(num_nodes))
+    print("# of features : {}".format(num_feats))
 
     features = torch.FloatTensor(features)
     labels = torch.LongTensor(labels)
 
-    if hasattr(torch, 'BoolTensor'):
+    if hasattr(torch, "BoolTensor"):
         train_mask = torch.BoolTensor(train_mask)
 
     else:
@@ -74,17 +74,19 @@ def train(args):
 
     # create model
     heads = ([args.num_heads] * args.num_layers) + [args.num_out_heads]
-    model = GAT(g,
-                args.num_layers,
-                num_feats,
-                args.num_hidden,
-                n_classes,
-                heads,
-                F.elu,
-                args.in_drop,
-                args.attn_drop,
-                args.negative_slope,
-                args.residual)
+    model = GAT(
+        g,
+        args.num_layers,
+        num_feats,
+        args.num_hidden,
+        n_classes,
+        heads,
+        F.elu,
+        args.in_drop,
+        args.attn_drop,
+        args.negative_slope,
+        args.residual,
+    )
     print(model)
     if args.early_stop:
         stopper = EarlyStopping(patience=100)
@@ -94,7 +96,8 @@ def train(args):
 
     # use optimizer
     optimizer = torch.optim.Adam(
-        model.parameters(), lr=args.lr, weight_decay=args.weight_decay)
+        model.parameters(), lr=args.lr, weight_decay=args.weight_decay
+    )
 
     # initialize graph
     dur = []
@@ -103,8 +106,8 @@ def train(args):
     Used_memory = 0
 
     for epoch in range(args.num_epochs):
-        #print('epoch = ', epoch) 
-        #print('mem0 = {}'.format(mem0))
+        # print('epoch = ', epoch)
+        # print('mem0 = {}'.format(mem0))
         torch.cuda.synchronize()
         tf = time.time()
         model.train()
@@ -120,7 +123,7 @@ def train(args):
         torch.cuda.synchronize()
         loss.backward()
         optimizer.step()
-        t2 =time.time()
+        t2 = time.time()
         run_time_this_epoch = t2 - tf
 
         if epoch >= 3:
@@ -131,56 +134,77 @@ def train(args):
 
         train_acc = accuracy(logits[train_mask], labels[train_mask])
 
-        #log for each step
-        print('Epoch {:05d} | Time(s) {:.4f} | train_acc {:.6f} | Used_Memory {:.6f} mb'.format(
-            epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))
-        ))
+        # log for each step
+        print(
+            "Epoch {:05d} | Time(s) {:.4f} | train_acc {:.6f} | Used_Memory {:.6f} mb".format(
+                epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))
+            )
+        )
 
     if args.early_stop:
-        model.load_state_dict(torch.load('es_checkpoint.pt'))
+        model.load_state_dict(torch.load("es_checkpoint.pt"))
 
-    #OUTPUT we need
-    avg_run_time = avg_run_time *1. / record_time
-    Used_memory /= (1024**3)
-    print('^^^{:6f}^^^{:6f}'.format(Used_memory, avg_run_time))
+    # OUTPUT we need
+    avg_run_time = avg_run_time * 1.0 / record_time
+    Used_memory /= 1024**3
+    print("^^^{:6f}^^^{:6f}".format(Used_memory, avg_run_time))
 
-if __name__ == '__main__':
 
-    parser = argparse.ArgumentParser(description='GAT')
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="GAT")
 
     # COMMENT IF SNOOP IS TO BE ENABLED
     snoop.install(enabled=False)
 
-    parser.add_argument("--gpu", type=int, default=0,
-                        help="which GPU to use. Set -1 to use CPU.")
-    parser.add_argument("--num_epochs", type=int, default=200,
-                        help="number of training epochs")
-    parser.add_argument("--num_heads", type=int, default=8,
-                        help="number of hidden attention heads")
-    parser.add_argument("--num_out_heads", type=int, default=1,
-                        help="number of output attention heads")
-    parser.add_argument("--num_layers", type=int, default=1,
-                        help="number of hidden layers")
-    parser.add_argument("--num_hidden", type=int, default=32,
-                        help="number of hidden units")
-    parser.add_argument("--residual", action="store_true", default=False,
-                        help="use residual connection")
-    parser.add_argument("--in_drop", type=float, default=.6,
-                        help="input feature dropout")
-    parser.add_argument("--attn_drop", type=float, default=.6,
-                        help="attention dropout")
-    parser.add_argument("--lr", type=float, default=0.005,
-                        help="learning rate")
-    parser.add_argument('--weight_decay', type=float, default=5e-4,
-                        help="weight decay")
-    parser.add_argument('--negative_slope', type=float, default=0.2,
-                        help="the negative slope of leaky relu")
-    parser.add_argument('--early_stop', action='store_true', default=False,
-                        help="indicates whether to use early stop or not")
-    parser.add_argument('--fastmode', action="store_true", default=False,
-                        help="skip re-evaluate the validation set")
+    parser.add_argument(
+        "--gpu", type=int, default=0, help="which GPU to use. Set -1 to use CPU."
+    )
+    parser.add_argument(
+        "--num_epochs", type=int, default=200, help="number of training epochs"
+    )
+    parser.add_argument(
+        "--num_heads", type=int, default=8, help="number of hidden attention heads"
+    )
+    parser.add_argument(
+        "--num_out_heads", type=int, default=1, help="number of output attention heads"
+    )
+    parser.add_argument(
+        "--num_layers", type=int, default=1, help="number of hidden layers"
+    )
+    parser.add_argument(
+        "--num_hidden", type=int, default=32, help="number of hidden units"
+    )
+    parser.add_argument(
+        "--residual", action="store_true", default=False, help="use residual connection"
+    )
+    parser.add_argument(
+        "--in_drop", type=float, default=0.6, help="input feature dropout"
+    )
+    parser.add_argument(
+        "--attn_drop", type=float, default=0.6, help="attention dropout"
+    )
+    parser.add_argument("--lr", type=float, default=0.005, help="learning rate")
+    parser.add_argument("--weight_decay", type=float, default=5e-4, help="weight decay")
+    parser.add_argument(
+        "--negative_slope",
+        type=float,
+        default=0.2,
+        help="the negative slope of leaky relu",
+    )
+    parser.add_argument(
+        "--early_stop",
+        action="store_true",
+        default=False,
+        help="indicates whether to use early stop or not",
+    )
+    parser.add_argument(
+        "--fastmode",
+        action="store_true",
+        default=False,
+        help="skip re-evaluate the validation set",
+    )
     args = parser.parse_args()
 
     print(args)
-        
+
     train(args)
diff --git a/benchmarking/gcn/seastar/model.py b/benchmarking/gcn/seastar/model.py
index f9554d9d..231e78c8 100644
--- a/benchmarking/gcn/seastar/model.py
+++ b/benchmarking/gcn/seastar/model.py
@@ -1,5 +1,5 @@
 import torch.nn as nn
-from stgraph.nn.pytorch.graph_conv import GraphConv
+from stgraph.nn.pytorch.static.gcn_conv import GCNConv
 
 class GCN(nn.Module):
     def __init__(self,
@@ -13,12 +13,12 @@ def __init__(self,
         self.g = g
         self.layers = nn.ModuleList()
         # input layer
-        self.layers.append(GraphConv(in_feats, n_hidden, activation))
+        self.layers.append(GCNConv(in_feats, n_hidden, activation))
         # hidden layers
         for i in range(n_layers - 1):
-            self.layers.append(GraphConv(n_hidden, n_hidden, activation))
+            self.layers.append(GCNConv(n_hidden, n_hidden, activation))
         # output layer
-        self.layers.append(GraphConv(n_hidden, n_classes, None))
+        self.layers.append(GCNConv(n_hidden, n_classes, None))
 
     def forward(self, g, features):
         h = features
diff --git a/benchmarking/gcn/seastar/train.py b/benchmarking/gcn/seastar/train.py
index b426d747..8c82203c 100644
--- a/benchmarking/gcn/seastar/train.py
+++ b/benchmarking/gcn/seastar/train.py
@@ -5,23 +5,23 @@
 import pynvml
 import torch.nn as nn
 import torch.nn.functional as F
-from stgraph.graph.static.StaticGraph import StaticGraph
-from stgraph.dataset.CoraDataLoader import CoraDataLoader
-from utils import to_default_device, accuracy
+from stgraph.graph.static.static_graph import StaticGraph
+from stgraph.dataset import CoraDataLoader
+from utils import to_default_device, accuracy, generate_test_mask, generate_train_mask
 from model import GCN
 
-def main(args):
 
+def main(args):
     cora = CoraDataLoader(verbose=True)
 
     # To account for the initial CUDA Context object for pynvml
-    tmp = StaticGraph([(0,0)], [1], 1)
-    
+    tmp = StaticGraph([(0, 0)], [1], 1)
+
     features = torch.FloatTensor(cora.get_all_features())
     labels = torch.LongTensor(cora.get_all_targets())
-    
-    train_mask = cora.get_train_mask()
-    test_mask = cora.get_test_mask()
+
+    train_mask = generate_train_mask(len(features), 0.6)
+    test_mask = generate_test_mask(len(features), 0.6)
 
     train_mask = torch.BoolTensor(train_mask)
     test_mask = torch.BoolTensor(test_mask)
@@ -47,7 +47,7 @@ def main(args):
 
     # A simple sanity check
     print("Measuerd Graph Size (pynvml): ", graph_mem, " B", flush=True)
-    print("Measuerd Graph Size (pynvml): ", (graph_mem)/(1024**2), " MB", flush=True)
+    print("Measuerd Graph Size (pynvml): ", (graph_mem) / (1024**2), " MB", flush=True)
 
     # normalization
     degs = torch.from_numpy(g.weighted_in_degrees()).type(torch.int32)
@@ -58,23 +58,18 @@ def main(args):
 
     num_feats = features.shape[1]
     n_classes = int(max(labels) - min(labels) + 1)
-    print("Num Classes: ",n_classes)
-
-    model = GCN(g,
-                num_feats,
-                args.num_hidden,
-                n_classes,
-                args.num_layers,
-                F.relu)
-    
+    print("Num Classes: ", n_classes)
+
+    model = GCN(g, num_feats, args.num_hidden, n_classes, args.num_layers, F.relu)
+
     if cuda:
         model.cuda()
     loss_fcn = torch.nn.CrossEntropyLoss()
 
     # use optimizer
-    optimizer = torch.optim.Adam(model.parameters(),
-                                 lr=args.lr,
-                                 weight_decay=args.weight_decay)
+    optimizer = torch.optim.Adam(
+        model.parameters(), lr=args.lr, weight_decay=args.weight_decay
+    )
 
     # initialize graph
     dur = []
@@ -106,40 +101,45 @@ def main(args):
             dur.append(run_time_this_epoch)
 
         train_acc = accuracy(logits[train_mask], labels[train_mask])
-        print('Epoch {:05d} | Time(s) {:.4f} | train_acc {:.6f} | Used_Memory {:.6f} mb '.format(
-            epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))
-        ))
+        print(
+            "Epoch {:05d} | Time(s) {:.4f} | train_acc {:.6f} | Used_Memory {:.6f} mb ".format(
+                epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))
+            )
+        )
 
-    Used_memory /= (1024**3)
-    print('^^^{:6f}^^^{:6f}'.format(Used_memory, np.mean(dur)))
+    Used_memory /= 1024**3
+    print("^^^{:6f}^^^{:6f}".format(Used_memory, np.mean(dur)))
 
 
-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("--dropout", type=float, default=0.5,
-            help="dropout probability")
-    parser.add_argument("--dataset", type=str,
-            help="Datset to train your model")
-    parser.add_argument("--gpu", type=int, default=0,
-            help="gpu")
-    parser.add_argument("--lr", type=float, default=1e-2,
-            help="learning rate")
-    parser.add_argument("--num_epochs", type=int, default=200,
-            help="number of training epochs")
-    parser.add_argument("--num_hidden", type=int, default=16,
-            help="number of hidden gcn units")
-    parser.add_argument("--num_layers", type=int, default=1,
-            help="number of hidden gcn layers")
-    parser.add_argument("--weight-decay", type=float, default=5e-4,
-            help="Weight for L2 loss")
-    parser.add_argument("--self-loop", action='store_true',
-            help="graph self-loop (default=False)")
+    parser.add_argument(
+        "--dropout", type=float, default=0.5, help="dropout probability"
+    )
+    parser.add_argument("--dataset", type=str, help="Datset to train your model")
+    parser.add_argument("--gpu", type=int, default=0, help="gpu")
+    parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
+    parser.add_argument(
+        "--num_epochs", type=int, default=200, help="number of training epochs"
+    )
+    parser.add_argument(
+        "--num_hidden", type=int, default=16, help="number of hidden gcn units"
+    )
+    parser.add_argument(
+        "--num_layers", type=int, default=1, help="number of hidden gcn layers"
+    )
+    parser.add_argument(
+        "--weight-decay", type=float, default=5e-4, help="Weight for L2 loss"
+    )
+    parser.add_argument(
+        "--self-loop", action="store_true", help="graph self-loop (default=False)"
+    )
     parser.set_defaults(self_loop=False)
     args = parser.parse_args()
     print(args)
 
-    main(args)
\ No newline at end of file
+    main(args)
diff --git a/benchmarking/gcn/seastar/utils.py b/benchmarking/gcn/seastar/utils.py
index dfae36be..0f2d52c9 100644
--- a/benchmarking/gcn/seastar/utils.py
+++ b/benchmarking/gcn/seastar/utils.py
@@ -1,21 +1,32 @@
 import torch
 
+
 def accuracy(logits, labels):
     _, indices = torch.max(logits, dim=1)
     correct = torch.sum(indices == labels)
     return correct.item() * 1.0 / len(labels)
 
+
 # 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)
\ No newline at end of file
+    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 generate_train_mask(size: int, train_test_split: int) -> list:
+    cutoff = size * train_test_split
+    return [1 if i < cutoff else 0 for i in range(size)]
+
+
+def generate_test_mask(size: int, train_test_split: int) -> list:
+    cutoff = size * train_test_split
+    return [0 if i < cutoff else 1 for i in range(size)]
diff --git a/benchmarking/results/result_generator_static.py b/benchmarking/results/result_generator_static.py
index fed35888..c6fcb4e1 100644
--- a/benchmarking/results/result_generator_static.py
+++ b/benchmarking/results/result_generator_static.py
@@ -1,7 +1,7 @@
 import csv
 from rich import inspect
 
-from stgraph.benchmark_tools.table import BenchmarkTable
+from stgraph.utils import DataTable
 
 all_results = []
 
@@ -43,7 +43,7 @@ def get_dataset_name(parameters):
     
             
     # forming the Table 1: Time measurements for varying feature sizes 
-    table_1 = BenchmarkTable(f"Time measurements (s) for varying feature sizes - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
+    table_1 = DataTable(f"Time measurements (s) for varying feature sizes - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
     pygt_results_table_1 = {}
     seastar_results_table_1 = {}
 
@@ -72,7 +72,7 @@ def get_dataset_name(parameters):
         table_1.display()
 
     # forming the Table 2: Memory measurements for varying feature sizes 
-    table_2 = BenchmarkTable(f"Memory taken (MB) for varying feature sizes - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
+    table_2 = DataTable(f"Memory taken (MB) for varying feature sizes - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
     pygt_results_table_2 = {}
     seastar_results_table_2 = {}
 
@@ -103,7 +103,7 @@ def get_dataset_name(parameters):
 for dataset in dataset_names:
         
     # forming the Table 1: Time measurements for varying sequence lengths
-    table_1 = BenchmarkTable(f"Time measurements (s) for varying sequence lengths - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
+    table_1 = DataTable(f"Time measurements (s) for varying sequence lengths - {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
     pygt_results_table_1 = {}
     seastar_results_table_1 = {}
 
@@ -132,7 +132,7 @@ def get_dataset_name(parameters):
         table_1.display()
 
     # forming the Table 2: Memory measurements for varying sequence lengths
-    table_2 = BenchmarkTable(f"Memory taken (MB) for varying sequence lengths- {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
+    table_2 = DataTable(f"Memory taken (MB) for varying sequence lengths- {dataset}", ["Hidden Dimension", "PyG-T", "STGraph"])
     pygt_results_table_2 = {}
     seastar_results_table_2 = {}
 
diff --git a/benchmarking/static-temporal-tgcn/pygt/train.py b/benchmarking/static-temporal-tgcn/pygt/train.py
index 16f35de7..b268fb5e 100644
--- a/benchmarking/static-temporal-tgcn/pygt/train.py
+++ b/benchmarking/static-temporal-tgcn/pygt/train.py
@@ -15,7 +15,7 @@
 from stgraph.dataset.METRLADataLoader import METRLADataLoader
 from stgraph.dataset.MontevideoBusDataLoader import MontevideoBusDataLoader
 
-from stgraph.benchmark_tools.table import BenchmarkTable
+from stgraph.utils import DataTable
 from utils import to_default_device, get_default_device
 
 def main(args):
@@ -74,7 +74,7 @@ def main(args):
     # metrics
     dur = []
     max_gpu = []
-    table = BenchmarkTable(f"(PyGT Static-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"])
+    table = DataTable(f"(PyGT Static-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"])
 
     try:
         # train
diff --git a/benchmarking/static-temporal-tgcn/seastar/train.py b/benchmarking/static-temporal-tgcn/seastar/train.py
index 1328b742..f991b486 100644
--- a/benchmarking/static-temporal-tgcn/seastar/train.py
+++ b/benchmarking/static-temporal-tgcn/seastar/train.py
@@ -9,7 +9,7 @@
 import os
 
 from model import STGraphTGCN
-from stgraph.graph.static.StaticGraph import StaticGraph
+from stgraph.graph.static.static_graph import StaticGraph
 
 from stgraph.dataset.WindmillOutputDataLoader import WindmillOutputDataLoader
 from stgraph.dataset.WikiMathDataLoader import WikiMathDataLoader
@@ -18,11 +18,12 @@
 from stgraph.dataset.METRLADataLoader import METRLADataLoader
 from stgraph.dataset.MontevideoBusDataLoader import MontevideoBusDataLoader
 
-from stgraph.benchmark_tools.table import BenchmarkTable
+from stgraph.utils import DataTable
 from utils import to_default_device, get_default_device
 
 from rich import inspect
 
+
 def main(args):
 
     if torch.cuda.is_available():
@@ -32,20 +33,63 @@ def main(args):
         quit()
 
     # Dummy object to account for CUDA context object
-    Graph = StaticGraph([(0,0)], [1], 1)
-    
+    Graph = StaticGraph([(0, 0)], [1], 1)
+
     if args.dataset == "wiki":
-        dataloader = WikiMathDataLoader('static-temporal', 'wikivital_mathematics', args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = WikiMathDataLoader(
+            "static-temporal",
+            "wikivital_mathematics",
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "windmill":
-        dataloader = WindmillOutputDataLoader('static-temporal', 'windmill_output', args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = WindmillOutputDataLoader(
+            "static-temporal",
+            "windmill_output",
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "hungarycp":
-        dataloader = HungaryCPDataLoader('static-temporal', 'HungaryCP', args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = HungaryCPDataLoader(
+            "static-temporal",
+            "HungaryCP",
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "pedalme":
-        dataloader = PedalMeDataLoader('static-temporal', 'pedalme', args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = PedalMeDataLoader(
+            "static-temporal",
+            "pedalme",
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "metrla":
-        dataloader = METRLADataLoader('static-temporal', 'METRLA', args.feat_size, args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = METRLADataLoader(
+            "static-temporal",
+            "METRLA",
+            args.feat_size,
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     elif args.dataset == "monte":
-        dataloader = MontevideoBusDataLoader('static-temporal', 'montevideobus', args.feat_size, args.cutoff_time, verbose=True, for_stgraph=True)
+        dataloader = MontevideoBusDataLoader(
+            "static-temporal",
+            "montevideobus",
+            args.feat_size,
+            args.cutoff_time,
+            verbose=True,
+            for_stgraph=True,
+        )
     else:
         print("😔 Unrecognized dataset")
         quit()
@@ -53,19 +97,23 @@ def main(args):
     edge_list = dataloader.get_edges()
     edge_weight_list = dataloader.get_edge_weights()
     targets = dataloader.get_all_targets()
-    
+
     pynvml.nvmlInit()
     handle = pynvml.nvmlDeviceGetHandleByIndex(0)
     initial_used_gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used
     G = StaticGraph(edge_list, edge_weight_list, dataloader.num_nodes)
     graph_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
 
-    edge_weight = to_default_device(torch.unsqueeze(torch.FloatTensor(edge_weight_list), 1))
+    edge_weight = to_default_device(
+        torch.unsqueeze(torch.FloatTensor(edge_weight_list), 1)
+    )
     targets = to_default_device(torch.FloatTensor(np.array(targets)))
 
     num_hidden_units = args.num_hidden
     num_outputs = 1
-    model = to_default_device(STGraphTGCN(args.feat_size, num_hidden_units, num_outputs))
+    model = to_default_device(
+        STGraphTGCN(args.feat_size, num_hidden_units, num_outputs)
+    )
     optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
 
     # Logging Output
@@ -78,23 +126,26 @@ def main(args):
     backprop_every = args.backprop_every
     if backprop_every == 0:
         backprop_every = total_timestamps
-    
+
     if total_timestamps % backprop_every == 0:
-        num_iter = int(total_timestamps/backprop_every)
+        num_iter = int(total_timestamps / backprop_every)
     else:
-        num_iter = int(total_timestamps/backprop_every) + 1
+        num_iter = int(total_timestamps / backprop_every) + 1
 
     # metrics
     dur = []
     max_gpu = []
-    table = BenchmarkTable(f"(STGraph Static-Temporal) TGCN on {dataloader.name} dataset", ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"])
-    
+    table = DataTable(
+        f"(STGraph Static-Temporal) TGCN on {dataloader.name} dataset",
+        ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"],
+    )
+
     # 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.set_ndata('norm', norm.unsqueeze(1))
+    G.set_ndata("norm", norm.unsqueeze(1))
 
     # train
     print("Training...\n")
@@ -103,7 +154,7 @@ def main(args):
             torch.cuda.synchronize()
             torch.cuda.reset_peak_memory_stats(0)
             model.train()
-            
+
             t0 = time.time()
             gpu_mem_arr = []
             cost_arr = []
@@ -112,20 +163,24 @@ def main(args):
                 optimizer.zero_grad()
                 cost = 0
                 hidden_state = None
-                y_hat = torch.randn((dataloader.num_nodes, args.feat_size), device=get_default_device())
+                y_hat = torch.randn(
+                    (dataloader.num_nodes, args.feat_size), device=get_default_device()
+                )
                 for k in range(backprop_every):
                     t = index * backprop_every + k
 
                     if t >= total_timestamps:
                         break
 
-                    y_out, y_hat, hidden_state = model(G, y_hat, edge_weight, hidden_state)
-                    cost = cost + torch.mean((y_out-targets[t])**2)
-                
+                    y_out, y_hat, hidden_state = model(
+                        G, y_hat, edge_weight, hidden_state
+                    )
+                    cost = cost + torch.mean((y_out - targets[t]) ** 2)
+
                 if cost == 0:
                     break
-        
-                cost = cost / (backprop_every+1)
+
+                cost = cost / (backprop_every + 1)
                 cost.backward()
                 optimizer.step()
                 torch.cuda.synchronize()
@@ -140,56 +195,82 @@ def main(args):
                 dur.append(run_time_this_epoch)
                 max_gpu.append(max(gpu_mem_arr))
 
-            table.add_row([epoch, "{:.5f}".format(run_time_this_epoch), "{:.4f}".format(sum(cost_arr)/len(cost_arr)), "{:.4f}".format((max(gpu_mem_arr) * 1.0 / (1024**2)))])
+            table.add_row(
+                [
+                    epoch,
+                    "{:.5f}".format(run_time_this_epoch),
+                    "{:.4f}".format(sum(cost_arr) / len(cost_arr)),
+                    "{:.4f}".format((max(gpu_mem_arr) * 1.0 / (1024**2))),
+                ]
+            )
 
         table.display()
-        print('Average Time taken: {:6f}'.format(np.mean(dur)))
+        print("Average Time taken: {:6f}".format(np.mean(dur)))
         return np.mean(dur), (max(max_gpu) * 1.0 / (1024**2))
-    
+
     except RuntimeError as e:
-        if 'out of memory' in str(e):
+        if "out of memory" in str(e):
             table.add_row(["OOM", "OOM", "OOM", "OOM"])
             table.display()
         else:
             print("😔 Something went wrong")
         return "OOM", "OOM"
 
+
 def write_results(args, time_taken, max_gpu):
     cutoff = "whole"
     if args.cutoff_time < sys.maxsize:
         cutoff = str(args.cutoff_time)
     file_name = f"stgraph_{args.dataset}_T{cutoff}_B{args.backprop_every}_H{args.num_hidden}_F{args.feat_size}"
-    df_data = pd.DataFrame([{'Filename': file_name, 'Time Taken (s)': time_taken, 'Max GPU Usage (MB)': max_gpu}])
-    
-    if os.path.exists('../../results/static-temporal.csv'):
-        df = pd.read_csv('../../results/static-temporal.csv')
+    df_data = pd.DataFrame(
+        [
+            {
+                "Filename": file_name,
+                "Time Taken (s)": time_taken,
+                "Max GPU Usage (MB)": max_gpu,
+            }
+        ]
+    )
+
+    if os.path.exists("../../results/static-temporal.csv"):
+        df = pd.read_csv("../../results/static-temporal.csv")
         df = pd.concat([df, df_data])
     else:
         df = df_data
-    
-    df.to_csv('../../results/static-temporal.csv', sep=',', index=False, encoding='utf-8')
+
+    df.to_csv(
+        "../../results/static-temporal.csv", sep=",", index=False, encoding="utf-8"
+    )
 
 
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='STGraph Static TGCN')
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="STGraph Static TGCN")
     snoop.install(enabled=False)
 
-    parser.add_argument("--dataset", type=str, default="wiki",
-            help="Name of the Dataset (wiki, windmill, hungary_cp, pedalme, metrla, monte)")
-    parser.add_argument("--backprop-every", type=int, default=0,
-            help="Feature size of nodes")
-    parser.add_argument("--feat-size", type=int, default=8,
-            help="Feature size of nodes")
-    parser.add_argument("--num-hidden", type=int, default=100,
-            help="Number of hidden units")
-    parser.add_argument("--lr", type=float, default=1e-2,
-            help="learning rate")
-    parser.add_argument("--cutoff-time", type=int, default=sys.maxsize,
-            help="learning rate")
-    parser.add_argument("--num-epochs", type=int, default=1,
-            help="number of training epochs")
+    parser.add_argument(
+        "--dataset",
+        type=str,
+        default="wiki",
+        help="Name of the Dataset (wiki, windmill, hungary_cp, pedalme, metrla, monte)",
+    )
+    parser.add_argument(
+        "--backprop-every", type=int, default=0, help="Feature size of nodes"
+    )
+    parser.add_argument(
+        "--feat-size", type=int, default=8, help="Feature size of nodes"
+    )
+    parser.add_argument(
+        "--num-hidden", type=int, default=100, help="Number of hidden units"
+    )
+    parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
+    parser.add_argument(
+        "--cutoff-time", type=int, default=sys.maxsize, help="learning rate"
+    )
+    parser.add_argument(
+        "--num-epochs", type=int, default=1, help="number of training epochs"
+    )
     args = parser.parse_args()
-    
+
     print(args)
     time_taken, max_gpu = main(args)
-    write_results(args, time_taken, max_gpu)
\ No newline at end of file
+    write_results(args, time_taken, max_gpu)
diff --git a/docs/Makefile b/docs/Makefile
index d4bb2cbb..d0c3cbf1 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -5,8 +5,8 @@
 # from the environment for the first two.
 SPHINXOPTS    ?=
 SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = .
-BUILDDIR      = _build
+SOURCEDIR     = source
+BUILDDIR      = build
 
 # Put it first so that "make" without argument is like "make help".
 help:
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..b264bcf3
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,15 @@
+# STGraph Documentation
+
+Incase there are any major changes in the stgraph module or new modules are added, make sure to run the following beforehand
+
+```shell
+sphinx-apidoc -o docs/source stgraph
+```
+
+Then run the following to generate the docs
+
+```shell
+cd docs
+make clean
+make html
+```
\ No newline at end of file
diff --git a/docs/_static/Seastar_docs_logo.png b/docs/_static/Seastar_docs_logo.png
deleted file mode 100644
index 3a43b18e..00000000
Binary files a/docs/_static/Seastar_docs_logo.png and /dev/null differ
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
deleted file mode 100644
index a94f84a4..00000000
--- a/docs/_static/custom.css
+++ /dev/null
@@ -1,16 +0,0 @@
-
-.wy-side-nav-search {
-    background-color: #0e103d;
-}
-
-.wy-menu-vertical header, .wy-menu-vertical p.caption {
-	color: #0e103d;
-}
-
-.wy-menu-vertical a {
-	color: #0e103d;
-}
-
-.wy-nav-side {
-	background: #efefef;
-}
\ No newline at end of file
diff --git a/docs/api_references/seastar_datasets.rst b/docs/api_references/seastar_datasets.rst
deleted file mode 100644
index ce002b40..00000000
--- a/docs/api_references/seastar_datasets.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-STGraph Datasets
-################
\ No newline at end of file
diff --git a/docs/api_references/seastar_gnn.rst b/docs/api_references/seastar_gnn.rst
deleted file mode 100644
index e2ecbe82..00000000
--- a/docs/api_references/seastar_gnn.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-STGraph GNN
-###########
\ No newline at end of file
diff --git a/docs/api_references/seastar_temporal_gnn.rst b/docs/api_references/seastar_temporal_gnn.rst
deleted file mode 100644
index b0a59600..00000000
--- a/docs/api_references/seastar_temporal_gnn.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-STGraph Temporal GNN
-####################
\ No newline at end of file
diff --git a/docs/developers_guide/developer_manual/developer_manual.rst b/docs/developers_guide/developer_manual/developer_manual.rst
deleted file mode 100644
index 5836a0fb..00000000
--- a/docs/developers_guide/developer_manual/developer_manual.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-Developer Manual
-################
-
-.. toctree::
-   :maxdepth: 2
-   :caption: Table of Contents
-
-   map_of_stgraph
\ No newline at end of file
diff --git a/docs/developers_guide/developer_manual/map_of_seastar.rst b/docs/developers_guide/developer_manual/map_of_seastar.rst
deleted file mode 100644
index e62d79ed..00000000
--- a/docs/developers_guide/developer_manual/map_of_seastar.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Map of STGraph Repository
-#########################
\ No newline at end of file
diff --git a/docs/developers_guide/extending_seastar.rst b/docs/developers_guide/extending_seastar.rst
deleted file mode 100644
index 3731b654..00000000
--- a/docs/developers_guide/extending_seastar.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Extending STGraph
-#################
\ No newline at end of file
diff --git a/docs/developers_guide/glossary.rst b/docs/developers_guide/glossary.rst
deleted file mode 100644
index bed97f44..00000000
--- a/docs/developers_guide/glossary.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Glossary
-########
\ No newline at end of file
diff --git a/docs/developers_guide/release_notes.rst b/docs/developers_guide/release_notes.rst
deleted file mode 100644
index 8f00ff77..00000000
--- a/docs/developers_guide/release_notes.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Release Notes
-#############
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index d58caa61..00000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-.. STGraph documentation master file, created by
-   sphinx-quickstart on Thu Apr 13 11:45:42 2023.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
-STGraph Tutorial and Documentation
-##################################
-
-Using vertex-centric programming to build GNNs
-
-Getting Started
-===============
-
-.. toctree::
-   :maxdepth: 2
-   :caption: User Guide
-
-   user_guide/installation
-   user_guide/stgraph tutorial
-
-.. toctree::
-   :maxdepth: 2
-   :caption: API References
-
-   api_references/stgraph_gnn
-   api_references/stgraph_temporal_gnn
-   api_references/stgraph_datasets
-
-.. toctree::
-   :maxdepth: 2
-   :caption: Developers Guide
-
-   developers_guide/extending_stgraph
-   developers_guide/developer_manual/developer_manual
-   developers_guide/glossary
-   developers_guide/release_notes
-
-..
-   * :ref:`genindex`
-   * :ref:`modindex`
-   * :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
index 954237b9..dc1312ab 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -7,8 +7,8 @@ REM Command file for Sphinx documentation
 if "%SPHINXBUILD%" == "" (
 	set SPHINXBUILD=sphinx-build
 )
-set SOURCEDIR=.
-set BUILDDIR=_build
+set SOURCEDIR=source
+set BUILDDIR=build
 
 %SPHINXBUILD% >NUL 2>NUL
 if errorlevel 9009 (
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..8f3ab60d
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,4 @@
+torch
+stgraph
+sphinx
+sphinx_rtd_theme
\ No newline at end of file
diff --git a/docs/source/_static/Images/tutorials/CoraBalloons.png b/docs/source/_static/Images/tutorials/CoraBalloons.png
new file mode 100644
index 00000000..90306f1d
Binary files /dev/null and b/docs/source/_static/Images/tutorials/CoraBalloons.png differ
diff --git a/docs/source/_static/STGraph_docs_logo.png b/docs/source/_static/STGraph_docs_logo.png
new file mode 100644
index 00000000..edbf150c
Binary files /dev/null and b/docs/source/_static/STGraph_docs_logo.png differ
diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css
new file mode 100644
index 00000000..26b8da26
--- /dev/null
+++ b/docs/source/_static/custom.css
@@ -0,0 +1,4 @@
+.wy-table-responsive table td {
+    word-wrap: break-word;
+    white-space: normal;
+}
\ No newline at end of file
diff --git a/docs/source/_static/stgraph_docs_banner.png b/docs/source/_static/stgraph_docs_banner.png
new file mode 100644
index 00000000..d453056d
Binary files /dev/null and b/docs/source/_static/stgraph_docs_banner.png differ
diff --git a/docs/source/_templates/class.rst b/docs/source/_templates/class.rst
new file mode 100644
index 00000000..f1973ace
--- /dev/null
+++ b/docs/source/_templates/class.rst
@@ -0,0 +1,10 @@
+.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: {{ module }}
+
+
+{{ name | underline}}
+
+.. autoclass:: {{ name }}
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/changelogs/1_0_0.rst b/docs/source/changelogs/1_0_0.rst
new file mode 100644
index 00000000..de6f653b
--- /dev/null
+++ b/docs/source/changelogs/1_0_0.rst
@@ -0,0 +1,26 @@
+v1.0.0
+======
+
+**Release Date**: 4th Nov, 2023
+
+Announcing the official v1 release of STGraph, built on top of Seastar. STGraph is streamlined to have more 
+reliable dependencies and supports execution out of the box. Additionally, we introduce support for TGNNs 
+by introducing integrations with dynamic graph data structures.
+
+Noting the immense amount of support provided by `@unnikrishnan-c <https://github.com/unnikrishnan-c>`_ and 
+`@KevinJudeConcessao <https://github.com/KevinJudeConcessao>`_ in making these changes.
+
+**New features**
+
+* 🔥 Removed DGL Hack dependency
+* 🤖 Making Seastar Backend Agnostic
+* ➕ Added TGCN support for static-temporal graphs
+* ➕ Added TGCN support for DTDGs
+* ✨ Integrated PCSR and GPMA with STGraph for DTDG support
+* ⚡ Optimized PCSR and GPMA integrations in STGraph
+* 🕐 Benchmarked STGraph on 5 static-temporal and 5 DTDGs
+
+**Contributors** : `@unnikrishnan-c <https://github.com/unnikrishnan-c>`_, `@KevinJudeConcessao <https://github.com/KevinJudeConcessao>`_, 
+`@nithinmanoj10 <https://github.com/nithinmanoj10>`_, `@JoelMathewC <https://github.com/JoelMathewC>`_
+
+View the `GitHub Changelog <https://github.com/bfGraph/STGraph/releases/tag/v1.0.0>`_
\ No newline at end of file
diff --git a/docs/source/changelogs/1_1_0.rst b/docs/source/changelogs/1_1_0.rst
new file mode 100644
index 00000000..c2a3c4d8
--- /dev/null
+++ b/docs/source/changelogs/1_1_0.rst
@@ -0,0 +1,17 @@
+v1.1.0
+======
+
+**Release Date**: 9th Sept, 2024
+
+We’re excited to introduce version 1.1.0 of STGraph, featuring significant improvements in dataset loading, testing, and documentation. Below are the highlights
+
+**Key Features & Updates**
+
+* 📦 Dataset loaders now categorized into submodules (dynamic, static, temporal)
+* ⚡ Standardized graph data handling with caching for efficient processing
+* 🧪 Comprehensive PyTest coverage for all dataset loaders
+* 🧪 Added scalable test scripts for GNN and TGNN layers across datasets
+* 📝 Created a brand new Read-the-dos documentation page for STGraph
+* 🧹 Ruff linter integrated; modules checked: `stgraph.dataset`, `stgraph.utils`
+
+View the `GitHub Changelog <https://github.com/bfGraph/STGraph/discussions/143>`_
diff --git a/docs/source/changelogs/index.rst b/docs/source/changelogs/index.rst
new file mode 100644
index 00000000..6cefca58
--- /dev/null
+++ b/docs/source/changelogs/index.rst
@@ -0,0 +1,35 @@
+STGraph Changelog
+=================
+
+Welcome to the STGraph Changelog!
+
+Here, you'll find a detailed record of the evolution of STGraph. The changelog provides a chronological 
+overview of updates, enhancements, bug fixes, and new features introduced with each release.
+
+What's new in v1.1.0?
+---------------------
+
+**Release Date**: 9th Sept, 2024
+
+We’re excited to introduce version 1.1.0 of STGraph, featuring significant improvements in dataset loading, testing, and documentation. Below are the highlights
+
+**Key Features & Updates**
+
+* 📦 Dataset loaders now categorized into submodules (dynamic, static, temporal)
+* ⚡ Standardized graph data handling with caching for efficient processing
+* 🧪 Comprehensive PyTest coverage for all dataset loaders
+* 🧪 Added scalable test scripts for GNN and TGNN layers across datasets
+* 📝 Created a brand new Read-the-dos documentation page for STGraph
+* 🧹 Ruff linter integrated; modules checked: `stgraph.dataset`, `stgraph.utils`
+
+View the `GitHub Changelog <https://github.com/bfGraph/STGraph/discussions/143>`_
+
+All Releases
+------------
+
+.. toctree::
+   :maxdepth: 1
+   :glob:
+
+   1_1_0
+   1_0_0
\ No newline at end of file
diff --git a/docs/conf.py b/docs/source/conf.py
similarity index 61%
rename from docs/conf.py
rename to docs/source/conf.py
index 795adab7..171ba688 100644
--- a/docs/conf.py
+++ b/docs/source/conf.py
@@ -11,36 +11,40 @@
 import sys
 
 # Temporary fix to ensure that the stgraph codebase can be accessed
-sys.path.insert(0,os.path.abspath('../python'))
+sys.path.insert(0, os.path.abspath("../../stgraph"))
 
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
-html_theme = 'sphinx_rtd_theme'
-html_static_path = ['_static']
+html_theme = "sphinx_rtd_theme"
+html_static_path = ["_static"]
 html_logo = "_static/STGraph_docs_logo.png"
 html_theme_options = {
-    'logo_only': True,
-    'display_version': False,
+    "logo_only": True,
+    "display_version": False,
 }
 
 html_css_files = ["custom.css"]
 
-project = 'STGraph'
-copyright = '2023, Joel Mathew Cherian, Nithin Puthalath Manoj'
-author = 'Joel Mathew Cherian, Nithin Puthalath Manoj'
-release = '0.2.0'
+project = "STGraph"
+copyright = "2024, Joel Mathew Cherian, Nithin Puthalath Manoj"
+author = "Joel Mathew Cherian, Nithin Puthalath Manoj"
+release = "1.1.0"
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 
-extensions = ['sphinx.ext.autodoc']
-
-templates_path = ['_templates']
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx.ext.viewcode",
+    'sphinx.ext.intersphinx',
+    "sphinx.ext.autosummary",
+    "sphinx.ext.napoleon"
+]
 
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 
 # -- Options for HTML output -------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-
diff --git a/docs/source/generated/modules.rst b/docs/source/generated/modules.rst
new file mode 100644
index 00000000..a42e13f3
--- /dev/null
+++ b/docs/source/generated/modules.rst
@@ -0,0 +1,7 @@
+stgraph
+=======
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph
diff --git a/docs/source/generated/stgraph.compiler.backend.mxnet.rst b/docs/source/generated/stgraph.compiler.backend.mxnet.rst
new file mode 100644
index 00000000..832e1e78
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.backend.mxnet.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.backend.mxnet package
+======================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.backend.mxnet
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.backend.pytorch.rst b/docs/source/generated/stgraph.compiler.backend.pytorch.rst
new file mode 100644
index 00000000..f9194bf4
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.backend.pytorch.rst
@@ -0,0 +1,29 @@
+stgraph.compiler.backend.pytorch package
+========================================
+
+Submodules
+----------
+
+stgraph.compiler.backend.pytorch.torch\_callback module
+-------------------------------------------------------
+
+.. automodule:: stgraph.compiler.backend.pytorch.torch_callback
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.backend.pytorch.torch\_kernel\_wrapper module
+--------------------------------------------------------------
+
+.. automodule:: stgraph.compiler.backend.pytorch.torch_kernel_wrapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.backend.pytorch
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.backend.rst b/docs/source/generated/stgraph.compiler.backend.rst
new file mode 100644
index 00000000..095c29ec
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.backend.rst
@@ -0,0 +1,39 @@
+stgraph.compiler.backend package
+================================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.backend.mxnet
+   stgraph.compiler.backend.pytorch
+   stgraph.compiler.backend.tensorflow
+
+Submodules
+----------
+
+stgraph.compiler.backend.callback module
+----------------------------------------
+
+.. automodule:: stgraph.compiler.backend.callback
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.backend.kernel\_wrapper module
+-----------------------------------------------
+
+.. automodule:: stgraph.compiler.backend.kernel_wrapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.backend
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.backend.tensorflow.rst b/docs/source/generated/stgraph.compiler.backend.tensorflow.rst
new file mode 100644
index 00000000..63396614
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.backend.tensorflow.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.backend.tensorflow package
+===========================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.backend.tensorflow
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.code_gen.rst b/docs/source/generated/stgraph.compiler.code_gen.rst
new file mode 100644
index 00000000..7187d744
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.code_gen.rst
@@ -0,0 +1,77 @@
+stgraph.compiler.code\_gen package
+==================================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.code_gen.templates
+
+Submodules
+----------
+
+stgraph.compiler.code\_gen.code\_gen module
+-------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.code_gen
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.compiler module
+------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.compiler
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.cuda\_check module
+---------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.cuda_check
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.cuda\_driver module
+----------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.cuda_driver
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.cuda\_error module
+---------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.cuda_error
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.device\_info module
+----------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.device_info
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.code\_gen.kernel\_context module
+-------------------------------------------------
+
+.. automodule:: stgraph.compiler.code_gen.kernel_context
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.code_gen
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.code_gen.templates.fa.rst b/docs/source/generated/stgraph.compiler.code_gen.templates.fa.rst
new file mode 100644
index 00000000..571660c3
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.code_gen.templates.fa.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.code\_gen.templates.fa package
+===============================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.code_gen.templates.fa
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.code_gen.templates.rst b/docs/source/generated/stgraph.compiler.code_gen.templates.rst
new file mode 100644
index 00000000..21ba80d6
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.code_gen.templates.rst
@@ -0,0 +1,19 @@
+stgraph.compiler.code\_gen.templates package
+============================================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.code_gen.templates.fa
+   stgraph.compiler.code_gen.templates.v2
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.code_gen.templates
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.code_gen.templates.v2.rst b/docs/source/generated/stgraph.compiler.code_gen.templates.v2.rst
new file mode 100644
index 00000000..d2016b68
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.code_gen.templates.v2.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.code\_gen.templates.v2 package
+===============================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.code_gen.templates.v2
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.debugging.rst b/docs/source/generated/stgraph.compiler.debugging.rst
new file mode 100644
index 00000000..68516555
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.debugging.rst
@@ -0,0 +1,29 @@
+stgraph.compiler.debugging package
+==================================
+
+Submodules
+----------
+
+stgraph.compiler.debugging.print\_variables module
+--------------------------------------------------
+
+.. automodule:: stgraph.compiler.debugging.print_variables
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.debugging.stgraph\_logger module
+-------------------------------------------------
+
+.. automodule:: stgraph.compiler.debugging.stgraph_logger
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.debugging
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.execution_unit.V2Kernel.rst b/docs/source/generated/stgraph.compiler.execution_unit.V2Kernel.rst
new file mode 100644
index 00000000..f8346c32
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.execution_unit.V2Kernel.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.compiler.execution_unit
+
+
+V2Kernel
+========
+
+.. autoclass:: V2Kernel
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.compiler.op.agg.rst b/docs/source/generated/stgraph.compiler.op.agg.rst
new file mode 100644
index 00000000..e106425d
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.op.agg.rst
@@ -0,0 +1,21 @@
+stgraph.compiler.op.agg package
+===============================
+
+Submodules
+----------
+
+stgraph.compiler.op.agg.agg\_op module
+--------------------------------------
+
+.. automodule:: stgraph.compiler.op.agg.agg_op
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.op.agg
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.op.mxnet.rst b/docs/source/generated/stgraph.compiler.op.mxnet.rst
new file mode 100644
index 00000000..e0fac953
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.op.mxnet.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.op.mxnet package
+=================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.op.mxnet
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.op.pytorch.rst b/docs/source/generated/stgraph.compiler.op.pytorch.rst
new file mode 100644
index 00000000..d7fdd921
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.op.pytorch.rst
@@ -0,0 +1,21 @@
+stgraph.compiler.op.pytorch package
+===================================
+
+Submodules
+----------
+
+stgraph.compiler.op.pytorch.torch\_op module
+--------------------------------------------
+
+.. automodule:: stgraph.compiler.op.pytorch.torch_op
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.op.pytorch
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.op.rst b/docs/source/generated/stgraph.compiler.op.rst
new file mode 100644
index 00000000..cb65d239
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.op.rst
@@ -0,0 +1,40 @@
+stgraph.compiler.op package
+===========================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.op.agg
+   stgraph.compiler.op.mxnet
+   stgraph.compiler.op.pytorch
+   stgraph.compiler.op.tensorflow
+
+Submodules
+----------
+
+stgraph.compiler.op.op module
+-----------------------------
+
+.. automodule:: stgraph.compiler.op.op
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.op.op\_factory module
+--------------------------------------
+
+.. automodule:: stgraph.compiler.op.op_factory
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.op
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.op.tensorflow.rst b/docs/source/generated/stgraph.compiler.op.tensorflow.rst
new file mode 100644
index 00000000..d4d0adb1
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.op.tensorflow.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.op.tensorflow package
+======================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.op.tensorflow
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.passes.rst b/docs/source/generated/stgraph.compiler.passes.rst
new file mode 100644
index 00000000..0770597a
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.passes.rst
@@ -0,0 +1,77 @@
+stgraph.compiler.passes package
+===============================
+
+Submodules
+----------
+
+stgraph.compiler.passes.cf module
+---------------------------------
+
+.. automodule:: stgraph.compiler.passes.cf
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.cse module
+----------------------------------
+
+.. automodule:: stgraph.compiler.passes.cse
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.dce module
+----------------------------------
+
+.. automodule:: stgraph.compiler.passes.dce
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.dependency\_analysis module
+---------------------------------------------------
+
+.. automodule:: stgraph.compiler.passes.dependency_analysis
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.fusion module
+-------------------------------------
+
+.. automodule:: stgraph.compiler.passes.fusion
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.mem\_planning module
+--------------------------------------------
+
+.. automodule:: stgraph.compiler.passes.mem_planning
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.peephole module
+---------------------------------------
+
+.. automodule:: stgraph.compiler.passes.peephole
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.passes.visualize module
+----------------------------------------
+
+.. automodule:: stgraph.compiler.passes.visualize
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.passes
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.rst b/docs/source/generated/stgraph.compiler.rst
new file mode 100644
index 00000000..9c120ec3
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.rst
@@ -0,0 +1,98 @@
+stgraph.compiler package
+========================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.backend
+   stgraph.compiler.code_gen
+   stgraph.compiler.debugging
+   stgraph.compiler.op
+   stgraph.compiler.passes
+   stgraph.compiler.val
+
+Submodules
+----------
+
+stgraph.compiler.autodiff module
+--------------------------------
+
+.. automodule:: stgraph.compiler.autodiff
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.execution\_unit module
+---------------------------------------
+
+.. automodule:: stgraph.compiler.execution_unit
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.executor module
+--------------------------------
+
+.. automodule:: stgraph.compiler.executor
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.node module
+----------------------------
+
+.. automodule:: stgraph.compiler.node
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.program module
+-------------------------------
+
+.. automodule:: stgraph.compiler.program
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.registry module
+--------------------------------
+
+.. automodule:: stgraph.compiler.registry
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.schema module
+------------------------------
+
+.. automodule:: stgraph.compiler.schema
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.stgraph module
+-------------------------------
+
+.. automodule:: stgraph.compiler.stgraph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.utils module
+-----------------------------
+
+.. automodule:: stgraph.compiler.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.val.mxnet.rst b/docs/source/generated/stgraph.compiler.val.mxnet.rst
new file mode 100644
index 00000000..182c26ae
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.val.mxnet.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.val.mxnet package
+==================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.val.mxnet
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.val.pytorch.rst b/docs/source/generated/stgraph.compiler.val.pytorch.rst
new file mode 100644
index 00000000..68f01f58
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.val.pytorch.rst
@@ -0,0 +1,21 @@
+stgraph.compiler.val.pytorch package
+====================================
+
+Submodules
+----------
+
+stgraph.compiler.val.pytorch.torch\_val module
+----------------------------------------------
+
+.. automodule:: stgraph.compiler.val.pytorch.torch_val
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.val.pytorch
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.val.rst b/docs/source/generated/stgraph.compiler.val.rst
new file mode 100644
index 00000000..963f15e3
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.val.rst
@@ -0,0 +1,39 @@
+stgraph.compiler.val package
+============================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler.val.mxnet
+   stgraph.compiler.val.pytorch
+   stgraph.compiler.val.tensorflow
+
+Submodules
+----------
+
+stgraph.compiler.val.val module
+-------------------------------
+
+.. automodule:: stgraph.compiler.val.val
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.compiler.val.val\_factory module
+----------------------------------------
+
+.. automodule:: stgraph.compiler.val.val_factory
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.val
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.compiler.val.tensorflow.rst b/docs/source/generated/stgraph.compiler.val.tensorflow.rst
new file mode 100644
index 00000000..9d0aea71
--- /dev/null
+++ b/docs/source/generated/stgraph.compiler.val.tensorflow.rst
@@ -0,0 +1,10 @@
+stgraph.compiler.val.tensorflow package
+=======================================
+
+Module contents
+---------------
+
+.. automodule:: stgraph.compiler.val.tensorflow
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.dataset.CoraDataLoader.rst b/docs/source/generated/stgraph.dataset.CoraDataLoader.rst
new file mode 100644
index 00000000..82df04ef
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.CoraDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+CoraDataLoader
+==============
+
+.. autoclass:: CoraDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.EnglandCovidDataLoader.rst b/docs/source/generated/stgraph.dataset.EnglandCovidDataLoader.rst
new file mode 100644
index 00000000..7f088e4c
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.EnglandCovidDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+EnglandCovidDataLoader
+======================
+
+.. autoclass:: EnglandCovidDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.HungaryCPDataLoader.rst b/docs/source/generated/stgraph.dataset.HungaryCPDataLoader.rst
new file mode 100644
index 00000000..a4d7460b
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.HungaryCPDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+HungaryCPDataLoader
+===================
+
+.. autoclass:: HungaryCPDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.METRLADataLoader.rst b/docs/source/generated/stgraph.dataset.METRLADataLoader.rst
new file mode 100644
index 00000000..8fc8965b
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.METRLADataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+METRLADataLoader
+================
+
+.. autoclass:: METRLADataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.MontevideoBusDataLoader.rst b/docs/source/generated/stgraph.dataset.MontevideoBusDataLoader.rst
new file mode 100644
index 00000000..b129b3f0
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.MontevideoBusDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+MontevideoBusDataLoader
+=======================
+
+.. autoclass:: MontevideoBusDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.PedalMeDataLoader.rst b/docs/source/generated/stgraph.dataset.PedalMeDataLoader.rst
new file mode 100644
index 00000000..91141c1f
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.PedalMeDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+PedalMeDataLoader
+=================
+
+.. autoclass:: PedalMeDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.STGraphDataset.rst b/docs/source/generated/stgraph.dataset.STGraphDataset.rst
new file mode 100644
index 00000000..be60e691
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.STGraphDataset.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+STGraphDataset
+==============
+
+.. autoclass:: STGraphDataset
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.STGraphDynamicDataset.rst b/docs/source/generated/stgraph.dataset.STGraphDynamicDataset.rst
new file mode 100644
index 00000000..7e93a86b
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.STGraphDynamicDataset.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+STGraphDynamicDataset
+=====================
+
+.. autoclass:: STGraphDynamicDataset
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.STGraphStaticDataset.rst b/docs/source/generated/stgraph.dataset.STGraphStaticDataset.rst
new file mode 100644
index 00000000..67a8e34d
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.STGraphStaticDataset.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+STGraphStaticDataset
+====================
+
+.. autoclass:: STGraphStaticDataset
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.STGraphTemporalDataset.rst b/docs/source/generated/stgraph.dataset.STGraphTemporalDataset.rst
new file mode 100644
index 00000000..586a0eb1
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.STGraphTemporalDataset.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+STGraphTemporalDataset
+======================
+
+.. autoclass:: STGraphTemporalDataset
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.WikiMathDataLoader.rst b/docs/source/generated/stgraph.dataset.WikiMathDataLoader.rst
new file mode 100644
index 00000000..94f0ffab
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.WikiMathDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+WikiMathDataLoader
+==================
+
+.. autoclass:: WikiMathDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.WindmillOutputDataLoader.rst b/docs/source/generated/stgraph.dataset.WindmillOutputDataLoader.rst
new file mode 100644
index 00000000..7ecb72f7
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.WindmillOutputDataLoader.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.dataset
+
+
+WindmillOutputDataLoader
+========================
+
+.. autoclass:: WindmillOutputDataLoader
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.dataset.dynamic.rst b/docs/source/generated/stgraph.dataset.dynamic.rst
new file mode 100644
index 00000000..9ea08a78
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.dynamic.rst
@@ -0,0 +1,29 @@
+stgraph.dataset.dynamic package
+===============================
+
+Submodules
+----------
+
+stgraph.dataset.dynamic.england\_covid\_dataloader module
+---------------------------------------------------------
+
+.. automodule:: stgraph.dataset.dynamic.england_covid_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.dynamic.stgraph\_dynamic\_dataset module
+--------------------------------------------------------
+
+.. automodule:: stgraph.dataset.dynamic.stgraph_dynamic_dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.dataset.dynamic
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.dataset.rst b/docs/source/generated/stgraph.dataset.rst
new file mode 100644
index 00000000..b26db646
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.rst
@@ -0,0 +1,31 @@
+stgraph.dataset package
+=======================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.dataset.dynamic
+   stgraph.dataset.static
+   stgraph.dataset.temporal
+
+Submodules
+----------
+
+stgraph.dataset.stgraph\_dataset module
+---------------------------------------
+
+.. automodule:: stgraph.dataset.stgraph_dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.dataset.static.rst b/docs/source/generated/stgraph.dataset.static.rst
new file mode 100644
index 00000000..40d916b7
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.static.rst
@@ -0,0 +1,29 @@
+stgraph.dataset.static package
+==============================
+
+Submodules
+----------
+
+stgraph.dataset.static.cora\_dataloader module
+----------------------------------------------
+
+.. automodule:: stgraph.dataset.static.cora_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.static.stgraph\_static\_dataset module
+------------------------------------------------------
+
+.. automodule:: stgraph.dataset.static.stgraph_static_dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.dataset.static
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.dataset.temporal.rst b/docs/source/generated/stgraph.dataset.temporal.rst
new file mode 100644
index 00000000..930201d6
--- /dev/null
+++ b/docs/source/generated/stgraph.dataset.temporal.rst
@@ -0,0 +1,69 @@
+stgraph.dataset.temporal package
+================================
+
+Submodules
+----------
+
+stgraph.dataset.temporal.hungarycp\_dataloader module
+-----------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.hungarycp_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.metrla\_dataloader module
+--------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.metrla_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.montevideobus\_dataloader module
+---------------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.montevideobus_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.pedalme\_dataloader module
+---------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.pedalme_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.stgraph\_temporal\_dataset module
+----------------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.stgraph_temporal_dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.wikimath\_dataloader module
+----------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.wikimath_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.dataset.temporal.windmilloutput\_dataloader module
+----------------------------------------------------------
+
+.. automodule:: stgraph.dataset.temporal.windmilloutput_dataloader
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.dataset.temporal
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.DynamicGraph.rst b/docs/source/generated/stgraph.graph.DynamicGraph.rst
new file mode 100644
index 00000000..8c6dfb59
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.DynamicGraph.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.graph
+
+
+DynamicGraph
+============
+
+.. autoclass:: DynamicGraph
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.graph.STGraphBase.rst b/docs/source/generated/stgraph.graph.STGraphBase.rst
new file mode 100644
index 00000000..c2b600d2
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.STGraphBase.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.graph
+
+
+STGraphBase
+===========
+
+.. autoclass:: STGraphBase
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.graph.StaticGraph.rst b/docs/source/generated/stgraph.graph.StaticGraph.rst
new file mode 100644
index 00000000..6df364b0
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.StaticGraph.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.graph
+
+
+StaticGraph
+===========
+
+.. autoclass:: StaticGraph
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.graph.dynamic.gpma.rst b/docs/source/generated/stgraph.graph.dynamic.gpma.rst
new file mode 100644
index 00000000..44dcae02
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.dynamic.gpma.rst
@@ -0,0 +1,29 @@
+stgraph.graph.dynamic.gpma package
+==================================
+
+Submodules
+----------
+
+stgraph.graph.dynamic.gpma.gpma module
+--------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.gpma.gpma
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.graph.dynamic.gpma.gpma\_graph module
+---------------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.gpma.gpma_graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph.dynamic.gpma
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.dynamic.naive.rst b/docs/source/generated/stgraph.graph.dynamic.naive.rst
new file mode 100644
index 00000000..6a42edce
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.dynamic.naive.rst
@@ -0,0 +1,21 @@
+stgraph.graph.dynamic.naive package
+===================================
+
+Submodules
+----------
+
+stgraph.graph.dynamic.naive.naive\_graph module
+-----------------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.naive.naive_graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph.dynamic.naive
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.dynamic.pcsr.rst b/docs/source/generated/stgraph.graph.dynamic.pcsr.rst
new file mode 100644
index 00000000..a0eb9356
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.dynamic.pcsr.rst
@@ -0,0 +1,29 @@
+stgraph.graph.dynamic.pcsr package
+==================================
+
+Submodules
+----------
+
+stgraph.graph.dynamic.pcsr.pcsr module
+--------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.pcsr.pcsr
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.graph.dynamic.pcsr.pcsr\_graph module
+---------------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.pcsr.pcsr_graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph.dynamic.pcsr
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.dynamic.rst b/docs/source/generated/stgraph.graph.dynamic.rst
new file mode 100644
index 00000000..67a88f58
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.dynamic.rst
@@ -0,0 +1,31 @@
+stgraph.graph.dynamic package
+=============================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.graph.dynamic.gpma
+   stgraph.graph.dynamic.naive
+   stgraph.graph.dynamic.pcsr
+
+Submodules
+----------
+
+stgraph.graph.dynamic.dynamic\_graph module
+-------------------------------------------
+
+.. automodule:: stgraph.graph.dynamic.dynamic_graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph.dynamic
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.rst b/docs/source/generated/stgraph.graph.rst
new file mode 100644
index 00000000..6b6c2294
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.rst
@@ -0,0 +1,30 @@
+stgraph.graph package
+=====================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.graph.dynamic
+   stgraph.graph.static
+
+Submodules
+----------
+
+stgraph.graph.stgraph\_base module
+----------------------------------
+
+.. automodule:: stgraph.graph.stgraph_base
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.graph.static.rst b/docs/source/generated/stgraph.graph.static.rst
new file mode 100644
index 00000000..b748e9f2
--- /dev/null
+++ b/docs/source/generated/stgraph.graph.static.rst
@@ -0,0 +1,29 @@
+stgraph.graph.static package
+============================
+
+Submodules
+----------
+
+stgraph.graph.static.csr module
+-------------------------------
+
+.. automodule:: stgraph.graph.static.csr
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.graph.static.static\_graph module
+-----------------------------------------
+
+.. automodule:: stgraph.graph.static.static_graph
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.graph.static
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.nn.GCNConv.rst b/docs/source/generated/stgraph.nn.GCNConv.rst
new file mode 100644
index 00000000..4c24f952
--- /dev/null
+++ b/docs/source/generated/stgraph.nn.GCNConv.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.nn
+
+
+GCNConv
+=======
+
+.. autoclass:: GCNConv
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.nn.pytorch.rst b/docs/source/generated/stgraph.nn.pytorch.rst
new file mode 100644
index 00000000..ca26df79
--- /dev/null
+++ b/docs/source/generated/stgraph.nn.pytorch.rst
@@ -0,0 +1,19 @@
+stgraph.nn.pytorch package
+==========================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.nn.pytorch.static
+   stgraph.nn.pytorch.temporal
+
+Module contents
+---------------
+
+.. automodule:: stgraph.nn.pytorch
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.nn.pytorch.static.rst b/docs/source/generated/stgraph.nn.pytorch.static.rst
new file mode 100644
index 00000000..a8646e12
--- /dev/null
+++ b/docs/source/generated/stgraph.nn.pytorch.static.rst
@@ -0,0 +1,29 @@
+stgraph.nn.pytorch.static package
+=================================
+
+Submodules
+----------
+
+stgraph.nn.pytorch.static.gat\_conv module
+------------------------------------------
+
+.. automodule:: stgraph.nn.pytorch.static.gat_conv
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.nn.pytorch.static.gcn\_conv module
+------------------------------------------
+
+.. automodule:: stgraph.nn.pytorch.static.gcn_conv
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.nn.pytorch.static
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.nn.pytorch.temporal.rst b/docs/source/generated/stgraph.nn.pytorch.temporal.rst
new file mode 100644
index 00000000..a2c2d445
--- /dev/null
+++ b/docs/source/generated/stgraph.nn.pytorch.temporal.rst
@@ -0,0 +1,21 @@
+stgraph.nn.pytorch.temporal package
+===================================
+
+Submodules
+----------
+
+stgraph.nn.pytorch.temporal.tgcn module
+---------------------------------------
+
+.. automodule:: stgraph.nn.pytorch.temporal.tgcn
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.nn.pytorch.temporal
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.nn.rst b/docs/source/generated/stgraph.nn.rst
new file mode 100644
index 00000000..885737f5
--- /dev/null
+++ b/docs/source/generated/stgraph.nn.rst
@@ -0,0 +1,18 @@
+stgraph.nn package
+==================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.nn.pytorch
+
+Module contents
+---------------
+
+.. automodule:: stgraph.nn
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.rst b/docs/source/generated/stgraph.rst
new file mode 100644
index 00000000..ef83e5bc
--- /dev/null
+++ b/docs/source/generated/stgraph.rst
@@ -0,0 +1,22 @@
+stgraph package
+===============
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   stgraph.compiler
+   stgraph.dataset
+   stgraph.graph
+   stgraph.nn
+   stgraph.utils
+
+Module contents
+---------------
+
+.. automodule:: stgraph
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/stgraph.utils.DataTable.rst b/docs/source/generated/stgraph.utils.DataTable.rst
new file mode 100644
index 00000000..53564bc0
--- /dev/null
+++ b/docs/source/generated/stgraph.utils.DataTable.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.utils
+
+
+DataTable
+=========
+
+.. autoclass:: DataTable
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.utils.SizeConstants.rst b/docs/source/generated/stgraph.utils.SizeConstants.rst
new file mode 100644
index 00000000..74eacb02
--- /dev/null
+++ b/docs/source/generated/stgraph.utils.SizeConstants.rst
@@ -0,0 +1,11 @@
+﻿.. role:: hidden
+    :class: hidden-section
+.. currentmodule:: stgraph.utils
+
+
+SizeConstants
+=============
+
+.. autoclass:: SizeConstants
+    :show-inheritance:
+    :members:
\ No newline at end of file
diff --git a/docs/source/generated/stgraph.utils.rst b/docs/source/generated/stgraph.utils.rst
new file mode 100644
index 00000000..5598e54c
--- /dev/null
+++ b/docs/source/generated/stgraph.utils.rst
@@ -0,0 +1,29 @@
+stgraph.utils package
+=====================
+
+Submodules
+----------
+
+stgraph.utils.constants module
+------------------------------
+
+.. automodule:: stgraph.utils.constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+stgraph.utils.data\_table module
+--------------------------------
+
+.. automodule:: stgraph.utils.data_table
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: stgraph.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 00000000..6c3f86ec
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,54 @@
+.. STGraph documentation master file, created by
+   sphinx-quickstart on Tue Oct 17 14:51:34 2023.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+.. image:: _static/stgraph_docs_banner.png
+   :alt: STGraph Docs Banner
+
+🌟 STGraph
+==========
+
+STGraph is a cutting-edge deep learning framework, built on top of Seastar, designed
+to easily write and train GNNs and TGNNs on real life graph datasets.
+
+With a focus on TGNNs to capture both inter-node interactions and node-specific interactions in dynamic graphs,
+STGraph extends the powerful Seastar vertex-centric programming model to support TGNNs, enabling efficient training on GPUs.
+Unlike existing solutions that store dynamic temporal graphs as separate snapshots, leading to high memory overhead, 
+STGraph dynamically constructs snapshots on demand during training. This innovative approach leverages dynamic graph 
+data structures, efficiently adapting to temporal updates. 
+
+Explore the STGraph documentation and tutorials to get started with writing and training your on GNNs and TGNNs.
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Install STGraph
+   :glob:
+
+   install/index
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Tutorials
+   :glob:
+
+   tutorials/gcn_cora
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Package Reference
+   :glob:
+
+   package_reference/stgraph.dataset
+   package_reference/stgraph.compiler
+   package_reference/stgraph.graph
+   package_reference/stgraph.utils
+   package_reference/stgraph.nn
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Changelog
+   :glob:
+
+   changelogs/index
+
diff --git a/docs/source/install/index.rst b/docs/source/install/index.rst
new file mode 100644
index 00000000..00cdb907
--- /dev/null
+++ b/docs/source/install/index.rst
@@ -0,0 +1,78 @@
+Installation Guide
+##################
+
+Welcome to the STGraph installation guide that will lead you through the installation process ensuring a smooth setup
+so you can start training TGNN models in your local machine.
+
+**Currently STGraph is available for 🐍 Python 3.8 only**
+
+.. note::
+
+    It is always recommended to install STGraph and it's dependencies inside a dedicated python
+    virtual environment with Python version set to 3.8
+
+Installation for STGraph Package Users
+======================================
+
+This guide is tailored for users of the STGraph package, designed for constructing GNN and TGNN models. 
+We recommend creating a new virtual environment with Python version 3.8 and installing stgraph inside that 
+dedicated environment.
+
+**Installing STGraph from PyPI**
+
+.. code-block:: bash
+
+    pip install stgraph
+
+**Installing PyTorch**
+
+In addition, STGraph relies on PyTorch. Ensure it is installed in your virtual environment with the following command
+
+.. code-block:: bash
+
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
+
+Upon completion of the above steps, you have successfully installed STGraph. Proceed to write and 
+train your first GNN and TGNN models.
+
+Installation for STGraph Package Developers
+===========================================
+
+This guide is intended for those interested in developing and contributing to STGraph.
+
+**Download source files from GitHub**
+
+.. code-block:: bash
+
+    git clone https://github.com/bfGraph/STGraph.git
+    cd STGraph
+
+**Create a dedicated virtual environment**
+
+Inside the STGraph directory create and activate a dedicated virtual environment named dev-stgraph 
+with Python version 3.8
+
+.. code-block:: bash
+
+    python3.8 -m venv dev-stgraph
+    source dev-stgraph/bin/activate
+
+**Install STGraph in editable mode**
+
+Make sure to install the STGraph package in editable mode to ease your development process.
+
+.. code-block:: bash
+
+    pip install -e .[dev]
+    pip list
+
+**Install PyTorch**
+
+Ensure to install PyTorch as well for development
+
+.. code-block:: bash
+
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
+
+With this you have successfully installed STGraph locally to make development changes and contribute to the project. 
+Head out to our `Pull Requests <https://github.com/bfGraph/STGraph/pulls>`_ page and get started with your first contribution. 
\ No newline at end of file
diff --git a/docs/source/package_reference/index.rst b/docs/source/package_reference/index.rst
new file mode 100644
index 00000000..dc709ddd
--- /dev/null
+++ b/docs/source/package_reference/index.rst
@@ -0,0 +1,10 @@
+Package Reference
+#################
+
+.. toctree::
+    :maxdepth: 2
+
+    stgraph.dataset
+    stgraph.compiler
+    stgraph.graph
+    stgraph.utils
\ No newline at end of file
diff --git a/docs/source/package_reference/stgraph.compiler.rst b/docs/source/package_reference/stgraph.compiler.rst
new file mode 100644
index 00000000..7e9136b8
--- /dev/null
+++ b/docs/source/package_reference/stgraph.compiler.rst
@@ -0,0 +1,20 @@
+stgraph.compiler
+################
+
+.. note::
+
+    Please note that the documentation for stgraph.compiler module is still work in progress.
+    Please do check-in for further documentation updates in upcoming releases.
+
+.. currentmodule:: stgraph.compiler
+.. automodule:: stgraph.compiler
+
+Execution Units
+===============
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    execution_unit.V2Kernel
\ No newline at end of file
diff --git a/docs/source/package_reference/stgraph.dataset.rst b/docs/source/package_reference/stgraph.dataset.rst
new file mode 100644
index 00000000..4b40df5c
--- /dev/null
+++ b/docs/source/package_reference/stgraph.dataset.rst
@@ -0,0 +1,53 @@
+stgraph.dataset
+###############
+
+.. currentmodule:: stgraph.dataset
+.. automodule:: stgraph.dataset
+
+Base Dataset Classes
+=====================
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    STGraphDataset
+    STGraphStaticDataset
+    STGraphTemporalDataset
+    STGraphDynamicDataset
+
+Static Graph Datasets
+=====================
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    CoraDataLoader
+
+Temporal Graph Datasets
+==============================
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    HungaryCPDataLoader
+    METRLADataLoader
+    MontevideoBusDataLoader
+    PedalMeDataLoader
+    WikiMathDataLoader
+    WindmillOutputDataLoader
+
+Dynamic Graph Datasets
+======================
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    EnglandCovidDataLoader
\ No newline at end of file
diff --git a/docs/source/package_reference/stgraph.graph.rst b/docs/source/package_reference/stgraph.graph.rst
new file mode 100644
index 00000000..758ba860
--- /dev/null
+++ b/docs/source/package_reference/stgraph.graph.rst
@@ -0,0 +1,37 @@
+stgraph.graph
+#############
+
+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``.
+
+.. currentmodule:: stgraph.graph
+.. automodule:: stgraph.graph
+
+Base Graph Class
+================
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    STGraphBase
+    StaticGraph
+    DynamicGraph
\ No newline at end of file
diff --git a/docs/source/package_reference/stgraph.nn.rst b/docs/source/package_reference/stgraph.nn.rst
new file mode 100644
index 00000000..19b356d4
--- /dev/null
+++ b/docs/source/package_reference/stgraph.nn.rst
@@ -0,0 +1,17 @@
+stgraph.nn
+##########
+
+.. currentmodule:: stgraph.nn
+.. automodule:: stgraph.nn
+
+PyTorch
+=======
+
+GNN layer implementation for PyTorch specific backend
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    GCNConv
\ No newline at end of file
diff --git a/docs/source/package_reference/stgraph.utils.rst b/docs/source/package_reference/stgraph.utils.rst
new file mode 100644
index 00000000..2351478f
--- /dev/null
+++ b/docs/source/package_reference/stgraph.utils.rst
@@ -0,0 +1,22 @@
+stgraph.utils
+#############
+
+.. currentmodule:: stgraph.utils
+.. automodule:: stgraph.utils
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    DataTable
+
+Constants
+---------
+
+.. autosummary::
+    :toctree: ../generated/
+    :nosignatures:
+    :template: class.rst
+
+    SizeConstants
\ No newline at end of file
diff --git a/docs/source/tutorials/gcn_cora.rst b/docs/source/tutorials/gcn_cora.rst
new file mode 100644
index 00000000..80365265
--- /dev/null
+++ b/docs/source/tutorials/gcn_cora.rst
@@ -0,0 +1,484 @@
+Cora Publication Prediction using Graph Convolutional Networks (GCN)
+====================================================================
+
+Graph Neural Networks (GNNs) are specially designed to understand and learn from data organized in graphs, 
+making them incredibly versatile and powerful. Graph Convolutional Networks (GCNs) is a widely adopted
+model which makes use of both node features and local connections.
+
+In this introductory tutorial, you will be able to 
+
+1. Build a GCN model using STGraph's neural network layers.
+2. Load the Cora dataset provided by STGraph.
+3. Train and evaluate the GCN model for node classification task on the GPU.
+
+You can find the entire source code for this tutorial under the ``tutorials`` directory in our GitHub `repo <https://github.com/bfGraph/STGraph/tree/main/tutorials>`_
+
+The Task At Hand
+----------------
+
+The Cora dataset is a widely used citation network for benchmarking graph-based machine learning algorithms.
+It comprises and captures the relationship between 2708 scientific publications classified into one of seven classes, 
+where nodes represent individual papers, and edges denote citation links between them. The network comprises of 
+5,429 connections. Each publication in the dataset is characterized by a binary word vector (0 or 1), 
+signifying the non-existence or existence of the respective word from a dictionary of 1,433 unique words.
+
+Our task is to train a GCN model on the Cora dataset and predict the topic of a publication (node) by considering 
+the neighboring node information and the overall graph structure. Or in other words, Node Classification.
+
+.. figure:: ../_static/Images/tutorials/CoraBalloons.png
+   :alt: CoraBalloons
+   :align: center
+   :width: 400
+
+   Cora Dataset Visualized [1]
+
+.. note::
+
+    This tutorial does not cover the detailed mechanics of how or why a GCN layer works.We
+    will only focus on using the GCN layer provided by STGraph to create a trainable multi-layer GCN model for node classification
+    on the Cora dataset. To learn more about GCN layers, refer to the following resources:
+
+    1. `Semi-Supervised Classification with Graph Convolutional Networks <https://arxiv.org/abs/1609.02907>`_
+    2. `Graph Convolutional Networks (GCNs) made simple <https://youtu.be/2KRAOZIULzw?si=Ryc74igSJ-zVMhjf>`_
+
+
+Code File Structure
+-------------------
+
+We will structure our tutorial with the following 4 files:
+
+.. code-block:: python
+
+    ├── main.py
+    ├── model.py
+    ├── train.py
+    └── utils.py
+
+
+Writing the GCN model
+---------------------
+
+Let's start by building our GCN model within a file named ``model.py``. First, import all the required modules. We will use PyTorch as our backend framework,
+along with the :class:`GCNConv <stgraph.nn.pytorch.static.gcn_conv.GCNConv>` layer from STGraph, which is designed for the PyTorch backend.
+
+.. code-block:: python
+
+    # model.py
+
+    import torch.nn as nn
+    import torch.nn.functional as F
+
+    from stgraph.nn.pytorch.static.gcn_conv import GCNConv
+
+Our main component is the GCN class, which represents the Graph Convolutional Network we will train. Here’s the code to initialize the GCN object
+
+.. code-block:: python
+
+    # model.py
+
+    class GCN(nn.Module):
+        def __init__(
+            self,
+            graph,
+            in_feats: int,
+            n_hidden: int,
+            n_classes: int,
+            n_hidden_layers: int,
+        ) -> None:
+            super(GCN, self).__init__()
+
+            self._graph = graph
+            self._layers = nn.ModuleList()
+
+            # input layer
+            self._layers.append(GCNConv(in_feats, n_hidden, F.relu, bias=True))
+
+            # hidden layers
+            for i in range(n_hidden_layers):
+                self._layers.append(GCNConv(n_hidden, n_hidden, F.relu, bias=True))
+
+            # output layer
+            self._layers.append(GCNConv(n_hidden, n_classes, None, bias=True))
+
+
+First, let's review all the arguments passed to the initialization method
+
+1. **graph**: This should be an STGraph graph object representing our graph dataset. For our tutorial, the Cora dataset will be of type :class:`StaticGraph <stgraph.graph.static.static_graph.StaticGraph>`.
+2. **in_feats**: The size of node features, which would equal the number of neurons in the input layer of our GCN architecture.
+3. **n_hidden**: The number of neurons in each hidden layer. We assume all hidden layers have the same number of neurons.
+4. **n_classes**: The number of classes each node in the Cora dataset can be classified into. It also corresponds to the number of neurons in the output layer of our GCN architecture.
+5. **n_hidden_layers**: The number of hidden layers present in the GCN architecture.
+
+We will initialize a list to hold all the layers of our GCN model. Using ``nn.ModuleList()`` allows for easier management of these layers. To this list,
+we will append ``GraphConv`` layers for the input layer, all the hidden layers, and then the output layer. The in_channel for the input layer equals to the
+size of a single node feature list and the out_channel for the output layer equals to the number of classes we are trying to classify the nodes into.
+Note that we use an element-wise ReLU activation function only for the input and hidden layers.
+
+By setting the bias argument to true, we are associating a learnable bias parameter with the input, hidden and output layers.
+
+Next up we can add the ``forward`` method inside the GCN class. When given the node feature as input to the network, it returns the corresponding output activations
+by following the feedforward mechanism described for a GCN layer.
+
+.. code-block:: python
+
+    # model.py
+
+    def forward(self, features):
+        h = features
+        for layer in self._layers:
+            h = layer.forward(self._graph, h)
+        return h
+
+Preparing the Training Script
+-----------------------------
+
+Now that we have defined our GCN model, we can now prepare the training script to train our model on the Cora dataset. You can go ahead and import all the
+necessary modules first.
+
+.. code-block:: python
+
+    # train.py
+
+    import traceback
+
+    import torch
+    import torch.nn.functional as F
+
+    from stgraph.utils import DataTable
+    from stgraph.dataset import CoraDataLoader
+    from stgraph.graph.static.static_graph import StaticGraph
+    from model import GCN
+    from utils import (
+        accuracy,
+        generate_test_mask,
+        generate_train_mask,
+        row_normalize_feature,
+        get_node_norms,
+    )
+
+You would notice that we haven't defined any of the imported methods from ``utils``. We will write down the logic for each one of them as we progress through writing the training script.
+
+Loading the Cora Graph Data
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Let's define our ``train`` method first
+
+.. code-block:: python
+
+    # train.py
+
+    def train(lr, num_epochs, num_hidden, num_hidden_layers, weight_decay):
+        if not torch.cuda.is_available():
+            print("CUDA is not available")
+            exit(1)
+
+We are passing the following hyperparameters as arguments to ``train``
+
+1. **lr**: The learning rate for the model.
+2. **num_epochs**: Number of epochs to train the model for.
+3. **num_hidden**: Number of neurons in each hidden layer.
+4. **num_hidden_layers**: Count of hidden layers.
+5. **weight_decay**: Weight decay value for L2 regularization to avoid overfitting
+
+As soon as we enter the ``train`` function, we are checking whether CUDA is available on the system. If it is not available, then we exit from the program.
+STGraph requires CUDA to be present for it to train any model.
+
+Next up we load our Cora dataset and all the necessary features, labels and weights. Once loaded into CPU, they are finally moved into the GPU using the ``.cuda()`` method.
+
+.. code-block:: python
+
+    # train.py
+
+        cora = CoraDataLoader()
+
+        node_features = row_normalize_feature(
+            torch.FloatTensor(cora.get_all_features())
+        )
+        node_labels = torch.LongTensor(cora.get_all_targets())
+        edge_weights = [1 for _ in range(cora.gdata["num_edges"])]
+
+        train_mask = torch.BoolTensor(
+            generate_train_mask(cora.gdata["num_nodes"], 0.7)
+        )
+        test_mask = torch.BoolTensor(
+            generate_test_mask(cora.gdata["num_nodes"], 0.7)
+        )
+
+        torch.cuda.set_device(0)
+        node_features = node_features.cuda()
+        node_labels = node_labels.cuda()
+        train_mask = train_mask.cuda()
+        test_mask = test_mask.cuda()
+
+The node features are row-normalised as shown below
+
+.. code-block:: python
+
+    # utils.py
+
+    def row_normalize_feature(features):
+        row_sum = features.sum(dim=1, keepdim=True)
+        r_inv = torch.where(row_sum != 0, 1.0 / row_sum, torch.zeros_like(row_sum))
+        norm_features = features * r_inv
+
+        return norm_features
+
+We are considering that the edge-weight is 1 for all edges. The ``train_mask`` and ``test_mask`` can be generated using the following two helper functions. We are taking the test-train
+split to be 0.7, but you can experiment with different values.
+
+.. code-block:: python
+
+    # utils.py
+
+    def generate_train_mask(size, train_test_split):
+        cutoff = size * train_test_split
+        return [1 if i < cutoff else 0 for i in range(size)]
+
+
+    def generate_test_mask(size, train_test_split):
+        cutoff = size * train_test_split
+        return [0 if i < cutoff else 1 for i in range(size)]
+
+Creating STGraph Graph Object and GCN Model
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+We need to create a :class:`StaticGraph <stgraph.graph.static.static_graph.StaticGraph>` object representing our Cora dataset, which can then be passed to our GCN model.
+
+.. code-block:: python
+
+    # train.py
+
+    cora_graph = StaticGraph(
+        edge_list=cora.get_edges(),
+        edge_weights=edge_weights,
+        num_nodes=cora.gdata["num_nodes"]
+    )
+
+    cora_graph.set_ndata("norm", get_node_norms(cora_graph))
+
+The node-wise normalization ``norm`` is set as node meta-data. This is internally used by the :class:`GCNConv <stgraph.nn.pytorch.static.gcn_conv.GCNConv>` layer while aggregating the
+features of a nodes neighbours. We calculate the node-wise normalization as follows
+
+.. code-block:: python
+
+    # utils.py
+
+    def get_node_norms(graph: StaticGraph):
+        degrees = torch.from_numpy(graph.weighted_in_degrees()).type(torch.int32)
+        norm = torch.pow(degrees, -0.5)
+        norm[torch.isinf(norm)] = 0
+        return to_default_device(norm).unsqueeze(1)
+
+We can go ahead and now load up the GCN model we created earlier into the GPU using ``.cuda()``. Follow it up by using Cross Entropy Loss and Adam as the loss function and optimizer respectively.
+
+.. code-block:: python
+
+    # train.py
+
+        model = GCN(
+            graph=cora_graph,
+            in_feats=cora.gdata["num_feats"],
+            n_hidden=num_hidden,
+            n_classes=cora.gdata["num_classes"],
+            n_hidden_layers=num_hidden_layers
+        ).cuda()
+
+        loss_function = F.cross_entropy
+        optimizer = torch.optim.Adam(
+            model.parameters(), lr=lr, weight_decay=weight_decay
+        )
+
+Training the GCN Model
+^^^^^^^^^^^^^^^^^^^^^^
+
+To help visualize various metrics such as accuracy, loss, etc. during training, we can use the :class:`BenchmarkTable <stgraph.benchmark_tools.table.BenchmarkTable>` present in the STGraph utility package.
+
+.. code-block:: python
+
+    # train.py
+
+    table = DataTable(
+        f"STGraph GCN on CORA dataset",
+        ["Epoch", "Train Accuracy %", "Loss"],
+    )
+
+Here is the entire training block
+
+.. code-block:: python
+
+    # train.py
+
+        try:
+            print("Started Training")
+            for epoch in range(num_epochs):
+                model.train()
+                torch.cuda.synchronize()
+
+                logits = model.forward(node_features)
+                loss = loss_function(logits[train_mask], node_labels[train_mask])
+                optimizer.zero_grad()
+                loss.backward()
+                optimizer.step()
+
+                torch.cuda.synchronize()
+
+                train_acc = accuracy(logits[train_mask], node_labels[train_mask])
+
+                table.add_row(
+                    [epoch, float(f"{train_acc * 100:.2f}"), float(f"{loss.item():.5f}")]
+                )
+            print("Training Ended")
+            table.display()
+
+            print("Evaluating trained GCN model on the Test Set")
+
+            model.eval()
+            logits_test = model(node_features)
+            loss_test = loss_function(logits_test[train_mask], node_labels[train_mask])
+            test_acc = accuracy(logits_test[test_mask], node_labels[test_mask])
+
+            print(f"Loss for Test: {loss_test}")
+            print(f"Accuracy for Test: {float(test_acc) * 100} %")
+
+        except Exception as e:
+            print("------------- Error -------------")
+            print(e)
+            traceback.print_exc()
+
+For each epoch, we are doing the following
+
+1. Running a single forward pass with ``node_features`` as input and ``logits`` as output.
+2. Calculating the loss using the Cross Entropy Loss function.
+3. Reset the gradients of all the parameters that the optimizer is managing using ``optimizer.zero_grad()``.
+4. Perform backpropagation using ``loss.backward()``.
+5. Update the parameters with ``optimizer.step()``.
+6. Calculate the training accuracy.
+7. Add necessary information to be displayed in the table.
+
+Training accuracy is calculated as follows
+
+.. code-block:: python
+
+    # utils.py
+
+    def accuracy(logits, labels):
+        _, indices = torch.max(logits, dim=1)
+        correct = torch.sum(indices == labels)
+        return correct.item() * 1.0 / len(labels)
+
+Finally we evaluate the model on the test set and report the accuracy and loss.
+
+The main.py File
+^^^^^^^^^^^^^^^^
+
+Let's prepare a ``main.py`` which accepts the hyperparameters as command-line arguments and invokes the ``train`` method.
+
+.. code-block:: python
+
+    # main.py
+
+    import argparse
+
+    from train import train
+
+
+    def main(args) -> None:
+        train(
+            lr=args.learning_rate,
+            num_epochs=args.epochs,
+            num_hidden=args.num_hidden,
+            num_hidden_layers=args.num_hidden_layers,
+            weight_decay=args.weight_decay,
+        )
+
+
+    if __name__ == "__main__":
+        parser = argparse.ArgumentParser(description="Training GCN on CORA Dataset")
+
+        parser.add_argument(
+            "-lr",
+            "--learning-rate",
+            type=float,
+            default=0.01,
+            help="Learning Rate for the GCN Model",
+        )
+
+        parser.add_argument(
+            "-e",
+            "--epochs",
+            type=int,
+            default=200,
+            help="Number of Epochs to Train the GCN Model",
+        )
+
+        parser.add_argument(
+            "-n",
+            "--num-hidden",
+            type=int,
+            default=16,
+            help="Number of Neurons in Hidden Layers",
+        )
+
+        parser.add_argument(
+            "-l", "--num-hidden-layers", type=int, default=1, help="Number of Hidden Layers"
+        )
+
+        parser.add_argument(
+            "-w", "--weight-decay", type=float, default=5e-4, help="Weight Decay"
+        )
+
+        args = parser.parse_args()
+        main(args=args)
+
+Let's go ahead and train our GCN model! Run this command to train a GCN model with our default hyperparameters
+
+1. Learning rate set to 0.01
+2. 200 Epochs
+3. 16 neurons in the hidden layers
+4. 1 hidden layer
+5. Weight decay of 0.0005
+
+.. code-block:: bash
+
+    $ python3 main.py
+
+Here is a truncated output
+
+.. code-block:: bash
+
+    Started Training
+    Training Ended
+
+        STGraph GCN on CORA dataset
+
+     Epoch ┃ Train Accuracy % ┃ Loss
+    ━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━
+     0     │ 14.98            │ 1.94579
+     1     │ 27.74            │ 1.93584
+     2     │ 27.74            │ 1.92458
+     3     │ 27.74            │ 1.91228
+     4     │ 27.74            │ 1.89956
+     5     │ 27.74            │ 1.88697
+     .
+     .
+     .
+     195   │ 76.27            │ 0.6078
+     196   │ 76.16            │ 0.60734
+     197   │ 76.37            │ 0.60676
+     198   │ 76.16            │ 0.60579
+     199   │ 76.32            │ 0.60465
+
+    Evaluating trained GCN model on the Test Set
+    Loss for Test: 0.6035217642784119
+    Accuracy for Test: 75.1231527093596 %
+
+We are achieving a training accuracy of around 76% and testing accuracy of 75%. This is pretty good for our first attempt.
+
+Exercises
+---------
+
+STGraph users need not stop here and can try out the following exercises to try to make the model learn better
+
+1. In the tutorial we are splitting the dataset only into a training set and testing set. Try creating a validation set as well to tune and optimize the hyperparameters.
+2. Try changing the number of hidden layers and number of hidden layer neurons. Maybe use no hidden layer at all. Do you notice any form of improvement? Or does it make the model worse?
+3. We did not use any activation function in the output layer. Try finding some common activation functions that can be used in the output layer for classification tasks and modify the GCN model.
\ No newline at end of file
diff --git a/docs/user_guide/installation.rst b/docs/user_guide/installation.rst
deleted file mode 100644
index c021bc22..00000000
--- a/docs/user_guide/installation.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Installation
-############
\ No newline at end of file
diff --git a/docs/user_guide/seastar tutorial.rst b/docs/user_guide/seastar tutorial.rst
deleted file mode 100644
index 9d292bbd..00000000
--- a/docs/user_guide/seastar tutorial.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-STGraph Tutorial
-################
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 04765eb5..00000000
--- a/examples/README.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# Your first STGraph program
-
-NOTE: This tutorial is still in progress
-
-In this beginner friendly tutorial, you will be writing your first GNN and TGNN models and training them on real life graph datasets. Make sure to have `stgraph` installed before continuing with the tutorial.
-
-## Writing a GCN Model
-
-Open up your favourite text editor or Python IDE and create a file named `model.py` with the following code which defines a GCN layer with PyTorch as the backend.
-
-**model.py**
-```python
-import torch.nn as nn
-from stgraph.nn.pytorch.graph_conv import GraphConv
-
-
-class GCN(nn.Module):
-    def __init__(self, g, in_feats, n_hidden, n_classes, n_layers, activation):
-        super(GCN, self).__init__()
-
-        self.g = g
-        self.layers = nn.ModuleList()
-        self.layers.append(GraphConv(in_feats, n_hidden, activation))
-
-        for i in range(n_layers - 1):
-            self.layers.append(GraphConv(n_hidden, n_hidden, activation))
-        
-        self.layers.append(GraphConv(n_hidden, n_classes, None))
-
-    def forward(self, g, features):
-        h = features
-        for layer in self.layers:
-            h = layer(g, h)
-        return h
-```
-
-**utils.py**
-
-```python
-import torch
-
-
-def accuracy(logits, labels):
-    _, indices = torch.max(logits, dim=1)
-    correct = torch.sum(indices == labels)
-    return correct.item() * 1.0 / len(labels)
-
-
-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)
-```
-
-The following `train.py` utilizes the GCN layer we defined earlier using STGraph to train on the CORA dataset for a node level classification task
-
-**train.py**
-```python
-import argparse, time
-import numpy as np
-import torch
-import pynvml
-import torch.nn as nn
-import torch.nn.functional as F
-from stgraph.graph.static.StaticGraph import StaticGraph
-from stgraph.dataset.CoraDataLoader import CoraDataLoader
-from utils import to_default_device, accuracy
-from model import GCN
-
-
-def main(args):
-    cora = CoraDataLoader(verbose=True)
-
-    # To account for the initial CUDA Context object for pynvml
-    tmp = StaticGraph([(0, 0)], [1], 1)
-
-    features = torch.FloatTensor(cora.get_all_features())
-    labels = torch.LongTensor(cora.get_all_targets())
-
-    train_mask = cora.get_train_mask()
-    test_mask = cora.get_test_mask()
-
-    train_mask = torch.BoolTensor(train_mask)
-    test_mask = torch.BoolTensor(test_mask)
-
-    if args.gpu < 0:
-        cuda = False
-    else:
-        cuda = True
-        torch.cuda.set_device(args.gpu)
-        features = features.cuda()
-        labels = labels.cuda()
-        train_mask = train_mask.cuda()
-        test_mask = test_mask.cuda()
-
-    print("Features Shape: ", features.shape, flush=True)
-    edge_weight = [1 for _ in range(len(cora.get_edges()))]
-
-    pynvml.nvmlInit()
-    handle = pynvml.nvmlDeviceGetHandleByIndex(0)
-    initial_used_gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used
-    g = StaticGraph(cora.get_edges(), edge_weight, features.shape[0])
-    graph_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
-
-    # A simple sanity check
-    print("Measuerd Graph Size (pynvml): ", graph_mem, " B", flush=True)
-    print(
-        "Measuerd Graph Size (pynvml): ", (graph_mem) / (1024**2), " MB", flush=True
-    )
-
-    # normalization
-    degs = torch.from_numpy(g.weighted_in_degrees()).type(torch.int32)
-    norm = torch.pow(degs, -0.5)
-    norm[torch.isinf(norm)] = 0
-    norm = to_default_device(norm)
-    g.set_ndata("norm", norm.unsqueeze(1))
-
-    num_feats = features.shape[1]
-    n_classes = int(max(labels) - min(labels) + 1)
-    print("Num Classes: ", n_classes)
-
-    model = GCN(g, num_feats, args.num_hidden, n_classes, args.num_layers, F.relu)
-
-    if cuda:
-        model.cuda()
-    loss_fcn = torch.nn.CrossEntropyLoss()
-
-    # use optimizer
-    optimizer = torch.optim.Adam(
-        model.parameters(), lr=args.lr, weight_decay=args.weight_decay
-    )
-
-    # initialize graph
-    dur = []
-    Used_memory = 0
-
-    for epoch in range(args.num_epochs):
-        torch.cuda.reset_peak_memory_stats(0)
-        model.train()
-        if cuda:
-            torch.cuda.synchronize()
-        t0 = time.time()
-
-        # forward
-        logits = model(g, features)
-        loss = loss_fcn(logits[train_mask], labels[train_mask])
-        optimizer.zero_grad()
-        loss.backward()
-        optimizer.step()
-
-        now_mem = torch.cuda.max_memory_allocated(0) + graph_mem
-        Used_memory = max(now_mem, Used_memory)
-
-        if cuda:
-            torch.cuda.synchronize()
-
-        run_time_this_epoch = time.time() - t0
-
-        if epoch >= 3:
-            dur.append(run_time_this_epoch)
-
-        train_acc = accuracy(logits[train_mask], labels[train_mask])
-        print(
-            "Epoch {:05d} | Time(s) {:.4f} | train_acc {:.6f} | Used_Memory {:.6f} mb ".format(
-                epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))
-            )
-        )
-
-    Used_memory /= 1024**3
-    print("^^^{:6f}^^^{:6f}".format(Used_memory, np.mean(dur)))
-
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(description="GCN")
-
-    parser.add_argument(
-        "--dropout", type=float, default=0.5, help="dropout probability"
-    )
-    parser.add_argument("--dataset", type=str, help="Datset to train your model")
-    parser.add_argument("--gpu", type=int, default=0, help="gpu")
-    parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
-    parser.add_argument(
-        "--num_epochs", type=int, default=200, help="number of training epochs"
-    )
-    parser.add_argument(
-        "--num_hidden", type=int, default=16, help="number of hidden gcn units"
-    )
-    parser.add_argument(
-        "--num_layers", type=int, default=1, help="number of hidden gcn layers"
-    )
-    parser.add_argument(
-        "--weight-decay", type=float, default=5e-4, help="Weight for L2 loss"
-    )
-    parser.add_argument(
-        "--self-loop", action="store_true", help="graph self-loop (default=False)"
-    )
-    parser.set_defaults(self_loop=False)
-    args = parser.parse_args()
-    print(args)
-
-    main(args)
-
-```
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index d91a7af9..6af6ae73 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@ find = {}
 
 [project]
 name = "stgraph"
-version = "1.0.0"
+version = "1.1.0"
 description = "🌟 Vertex Centric approach for building GNN/TGNNs"
 readme = "README.md"
 authors = [
@@ -26,7 +26,7 @@ classifiers = [
 keywords = ["GNN", "TGNN", "GPU Programming"]
 requires-python = ">=3.8"
 dependencies = [
-    'Jinja2 >= 2',
+    'Jinja2 >= 3.1.3',
     'pynvrtc >= 9.2',
     'pydot',
     'networkx >= 3.1',
@@ -44,8 +44,9 @@ dependencies = [
 [project.optional-dependencies]
 dev = [
     "black",
-    "pytest",
-    "tqdm >= 4.64.1",
+    "pytest >= 7.4.3",
+    "pytest-cov >= 4.1.0",
+    "tqdm >= 4.66.3",
     "build >= 0.10.0",
     "gdown >= 4.6.6",
     "pynvml >= 11.5.0",
diff --git a/requirements.txt b/requirements.txt
index b9d140cf..b86b5e5a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 cuda_python==12.1.0
-Jinja2==3.1.2
+Jinja2==3.1.3
 networkx==3.1
 numpy==1.23.4
 pandas==1.3.5
@@ -10,7 +10,7 @@ snoop==0.4.2
 sphinx_rtd_theme==1.2.0
 sympy==1.11.1
 termcolor==2.3.0
-tqdm==4.64.1
+tqdm==4.66.3
 pybind11==2.10.4
 build==0.10.0 
 pynvml==11.5.0
diff --git a/ruff.toml b/ruff.toml
new file mode 100644
index 00000000..53099a0a
--- /dev/null
+++ b/ruff.toml
@@ -0,0 +1,32 @@
+[lint]
+select = ["ALL"]
+
+ignore = [
+    "FBT002",
+    "FBT001",
+    "PLR0913",
+    "TRY003",
+    "EM101",
+    "ERA001",
+    "S607",
+    "S602",
+    "PTH111",
+    "PTH110",
+    "PTH107",
+    "PTH123",
+    "S605",
+    "S310",
+    "FIX002",
+    "D203",
+    "D211",
+    "D212",
+    "D213",
+    "PIE790",
+    "E501",
+    "TD002",
+    "TD003",
+    "TD004"
+]
+
+[lint.per-file-ignores]
+"__init__.py" = ["F401"]
diff --git a/stgraph/benchmark_tools/table.py b/stgraph/benchmark_tools/table.py
deleted file mode 100644
index 7ef01167..00000000
--- a/stgraph/benchmark_tools/table.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from rich.console import Console
-from rich.table import Table
-
-console = Console()
-
-class BenchmarkTable:
-    def __init__(self, title: str, col_name_list: list[str]):
-        self.title = '\n' + title + '\n'
-        self.col_name_list = col_name_list
-        self._table = Table(title=self.title, show_edge=False, style="black bold")
-        self._num_cols = len(col_name_list)
-        self._num_rows = 0
-        
-        self._table_add_columns()
-        
-    def _table_add_columns(self):
-        for col_name in self.col_name_list:
-            self._table.add_column(col_name, justify="left")
-            
-    def add_row(self, values: list):
-        values_str = tuple([str(val) for val in values])
-        self._table.add_row(*values_str)
-    
-    def display(self):
-        console.print(self._table)
\ No newline at end of file
diff --git a/stgraph/compiler/__init__.py b/stgraph/compiler/__init__.py
index 58d726b4..c186208f 100644
--- a/stgraph/compiler/__init__.py
+++ b/stgraph/compiler/__init__.py
@@ -1,3 +1,5 @@
+"""The backbone of STGraph"""
+
 from .stgraph import *
 from .program import *
-from .schema import *
\ No newline at end of file
+from .schema import *
diff --git a/stgraph/compiler/autodiff.py b/stgraph/compiler/autodiff.py
index fe71cb46..ef0040f5 100644
--- a/stgraph/compiler/autodiff.py
+++ b/stgraph/compiler/autodiff.py
@@ -1,3 +1,5 @@
+"""The auto-differentiation module"""
+
 from collections import deque, defaultdict
 import copy
 
@@ -12,7 +14,8 @@
 from stgraph.compiler.debugging.stgraph_logger import print_log
 
 def diff(vars, grads, forward_units, fprog):
-    '''
+    """The forward graph differentiator
+    
     For each var we find the statment that computes it, then we use itself as well as its grad
     to get the statements to calculate or accumulate the gradient for each of its imputs. 
 
@@ -23,15 +26,17 @@ def diff(vars, grads, forward_units, fprog):
     variable. If the var is in stopping var, there is no need to propogate further as the task will
     be delegated to backend system.
     
-    args:
-        vars : the var that has gradient to propogate back. Determined by zoomOut
-        grads : the coresponding gradient for each var
-        forward_units : forward execution units
-
-    return:
-        BProg : differentiated program to compute the gradients
-        grad_map : the gradient map of vars
-    '''
+    :param vars: The var that has gradient to propogate back, determined by zoomOut
+    :type vars: Var
+    
+    :param grads: The coresponding gradient for each var
+    
+    :param forward_units: Forward execution units
+    
+    :return: The differentiated program to compute the gradients
+    :rtype: BProg
+    """
+    
     assert len(vars) == len(grads), 'Each var must have a corresponding grad'
     BProg = Program()
     q = deque() 
diff --git a/stgraph/compiler/execution_unit.py b/stgraph/compiler/execution_unit.py
index 4a3588d9..4540ac20 100644
--- a/stgraph/compiler/execution_unit.py
+++ b/stgraph/compiler/execution_unit.py
@@ -1,3 +1,5 @@
+"""The fundamental execution unit of STGraph"""
+
 import math
 import snoop
 from .code_gen.cuda_driver import *
@@ -372,6 +374,28 @@ def run(self, tensor_list):
             raise e
 
 class V2Kernel(Kernel):
+    r"""The Version 2 Kernel
+    
+    This class contains the parameters for the second version of the kernel
+    written for STGraph
+    
+    Parameters
+    ----------
+    
+    num_nodes : int
+        Number of nodes present in the graph
+    row_offsets_ptr : c_type
+        Pointer to the row offset array
+    col_indices_ptr : c_type
+        Pointer to the column indicies array
+    
+    Attributes
+    ----------
+    scalar_args : list[c_types]
+        List of the scalar arguments passed to the kernel
+    launch_config : list[int]
+        List of the kernel launch configurations
+    """
     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
diff --git a/stgraph/compiler/executor.py b/stgraph/compiler/executor.py
index 799fd600..eab80c26 100644
--- a/stgraph/compiler/executor.py
+++ b/stgraph/compiler/executor.py
@@ -1,36 +1,37 @@
 from .utils import is_const_scalar, ParallelMode
 import snoop
 from collections import deque
-from ..graph.dynamic.DynamicGraph import DynamicGraph
+from ..graph.dynamic.dynamic_graph import DynamicGraph
 from stgraph.compiler.debugging.stgraph_logger import print_log
 import torch
 
+
 class Stack:
     def __init__(self, val=None):
         self.content = deque()
         if val is not None:
             self.content.append(val)
-    
+
     def push(self, val):
         self.content.append(val)
-    
+
     def pop(self):
         self.content.pop()
-    
+
     def top(self):
         return self.content[-1]
-    
+
     def print(self):
         for elem in self.content:
             print(elem)
-        
+
 
 class ExeState(object):
     def __init__(self):
 
         # contains tensors for all previous execution of nb_compute
         self.tensor_map_stack = Stack()
-        
+
         # contains timestamps of graphs that were forward propagated
         self.graph_timestamp_stack = Stack()
 
@@ -43,11 +44,11 @@ def __init__(self):
 
         self.dep_map = {}
         self.executed_bunit = set()
-    
+
     def reset(self, input_map, f_merged_units, bunits):
         # print("ENTERING RESET")
         self.dep_map = {}
-        #for mu in f_merged_units:
+        # for mu in f_merged_units:
         #    if mu.compiled():
         #        for ret in mu.union_of_rets():
         #            self.dep_map[ret.id] = 0
@@ -71,28 +72,29 @@ def reset(self, input_map, f_merged_units, bunits):
                     self.bwd_common_tensor_list.append(arg.id)
         # print("End of initializing bwd_common_tensor_list\n")
 
-        #print('dependency map', self.dep_map)
+        # print('dependency map', self.dep_map)
         self.num_bunits = len(bunits)
         # deletes all tensors that were previously stored here (verified)
-        self.current_tensor_map = {key: val for key,val in input_map.items()}
+        self.current_tensor_map = {key: val for key, val in input_map.items()}
         self.executed_bunit.clear()
-    
+
     def track_executed_bu(self, bu):
         self.executed_bunit.add(bu)
-    
+
     def is_executed_bu(self, bu):
         return bu in self.executed_bunit
-    
+
     def all_bu_executed(self):
         return len(self.executed_bunit) == self.num_bunits
-    
+
     def track_tensor(self, key, val):
         self.current_tensor_map[key] = val
         if key in self.bunit_arg_ids:
             self.bwd_common_tensor_list.append(key)
-    
+
     def clear_current_tensor_state(self):
         self.current_tensor_map = {}
+
     # def clear_cache(self):
     #     rmv_list = []
     #     for k in self.tensor_map:
@@ -102,25 +104,26 @@ def clear_current_tensor_state(self):
     #     for k in rmv_list:
     #         self.tensor_map.pop(k)
 
+
 class MergedUnit(object):
     def __init__(self, units):
         self.units = units
         self._joint_inputs = None
         self._joint_args = None
-        self._joint_rets = None 
+        self._joint_rets = None
         self._kernel_args = None
         self._union_of_rets = None
-    
+
     def append(self, unit):
         self.units.append(unit)
         return self
-    
+
     def last(self):
         return self.units[-1]
-    
+
     def compiled(self):
         return self.units[-1].compiled
-    
+
     def joint_inputs(self):
         if not self._joint_inputs:
             var_set = set()
@@ -140,15 +143,15 @@ def joint_rets(self):
                 var_set = var_set - u._args
             self._joint_rets = [var for var in var_set]
         return self._joint_rets
-    
+
     def joint_args(self):
         if not self._joint_args:
             var_set = set()
             for u in self.units:
                 var_set = var_set.union(u._args)
-            self._joint_args= [var for var in var_set] + self.joint_rets()
+            self._joint_args = [var for var in var_set] + self.joint_rets()
         return self._joint_args
-    
+
     def union_of_rets(self):
         if not self._union_of_rets:
             var_set = set()
@@ -156,7 +159,7 @@ def union_of_rets(self):
                 var_set = var_set.union(u._rets)
             self._union_of_rets = var_set
         return self._union_of_rets
-    
+
     def kernel_arg_list(self):
         if not self._kernel_args:
             args = self.joint_args()
@@ -169,22 +172,27 @@ def kernel_arg_list(self):
                             kernel_arg.append(i)
                 self._kernel_args.append(kernel_arg)
         return self._kernel_args
-    
+
     def __str__(self):
         return str(self.units)
-    
+
     def __repr__(self):
         return self.__str__()
-    
-    def  __iter__(self):
+
+    def __iter__(self):
         for unit in self.units:
             yield unit
 
+
 class Executor(object):
-    def __init__(self, graph, forward_exec_units, backward_exec_units, compiled_module, rets):
+    def __init__(
+        self, graph, forward_exec_units, backward_exec_units, compiled_module, rets
+    ):
         self.forward_exec_units = self.merge_units(forward_exec_units)
         self.bulist = backward_exec_units
-        self.var2bu = self.construct_backward_mappping(self.forward_exec_units,backward_exec_units)
+        self.var2bu = self.construct_backward_mappping(
+            self.forward_exec_units, backward_exec_units
+        )
         self._rets = rets
         self.ts = ExeState()
         self.new_zeros = None
@@ -199,7 +207,7 @@ def __init__(self, graph, forward_exec_units, backward_exec_units, compiled_modu
         for u in self.bulist:
             if u.compiled:
                 u.prepare_compiled_kernel(graph, compiled_module)
-    
+
     def construct_backward_mappping(self, funits, bunits):
         ret = {}
         for mu in funits:
@@ -210,68 +218,66 @@ def construct_backward_mappping(self, funits, bunits):
                             if arg._grad in bu.unit_rets():
                                 ret[arg] = bu
         return ret
-    
+
     def merge_units(self, exec_units):
         print_log("[green bold]Executor[/green bold]: Start merging units")
-        
-        assert len(exec_units) > 0, 'Error: empty exec units'
+
+        assert len(exec_units) > 0, "Error: empty exec units"
         grouped_unit = [MergedUnit([exec_units[0]])]
         for i in range(1, len(exec_units)):
             if exec_units[i].compiled == grouped_unit[-1].last().compiled:
                 grouped_unit[-1].append(exec_units[i])
             else:
                 grouped_unit.append(MergedUnit([exec_units[i]]))
-        
+
         print_log("[green bold]Executor[/green bold]: Units merging completed")
         return grouped_unit
-  
+
     def restart(self, input_map, graph=None):
         # print("ENTERING RESTART")
         self.ts.reset(input_map, self.forward_exec_units, self.bulist)
         if graph != None:
-            
+
             # TODO: REMOVE
             # TODO: getting graph of current timestamp, probably better to move
             # this outside the compiler
-            # current_timestamp = self.ts.tensor_map_stack.len() 
+            # current_timestamp = self.ts.tensor_map_stack.len()
             # self.graph.get_forward_graph_for_timestamp(current_timestamp)
-            
+
             for mu in self.forward_exec_units:
                 for u in mu:
                     if u.compiled:
                         # TODO: (Joel) Feel like this is going to be problematic for dynamic graphs
                         u.reset_graph_info(graph)
-            
+
             # NOTE: COMMENTED OUT NOW SINCE THIS IS HANDLED IN BACKWARD_CB
             # for u in self.bulist:
             #     if u.compiled:
             #         u.reset_graph_info(graph)
-            
+
             self.num_nodes = graph.get_num_nodes()
             self.num_edges = graph.get_num_edges()
 
-    
     def set_raw_ptr_cb(self, cb):
         self.raw_ptr = cb
 
     def set_new_zeros_cb(self, cb):
         self.new_zeros = cb
-    
+
     def execute(self, FuncWrapper):
-        ''' Execute forward pass'''
-        for i,unit in enumerate(self.forward_exec_units):
+        """Execute forward pass"""
+        for i, unit in enumerate(self.forward_exec_units):
             if unit.last().compiled:
                 self.execute_compiled(i, FuncWrapper)
             else:
                 self.execute_prog(unit)
         ret = tuple([self.ts.current_tensor_map[ret.id] for ret in self._rets])
-        
+
         # TODO: Will need to uncomment this one line
         # self.ts.clear_cache()
 
-
         # bytes_list = [v.numel() *4 for k,v in self.ts.tensor_map.items()]
-        #print('after forward', self.ts.tensor_map.keys(), ' bytes ', bytes_list, sum(bytes_list))
+        # print('after forward', self.ts.tensor_map.keys(), ' bytes ', bytes_list, sum(bytes_list))
 
         # Old position
         # self.ts.tensor_map_stack.push(self.ts.current_tensor_map)
@@ -282,22 +288,36 @@ def execute(self, FuncWrapper):
         #     print("Index: {}".format(index))
         #     print(self.ts.tensor_map_stack.content[index])
 
-        return  ret
-    
+        return ret
+
     def create_tensor_for_vars(self, var_list):
-        ret_tensors = {var.id : self.new_zeros(size=[self.num_edges if var.is_edgevar() else self.num_nodes] + list(var.var_shape),
-                                               dtype=var.var_dtype,
-                                               device=var.device,
-                                               requires_grad=False) for var in var_list if var.id not in self.ts.current_tensor_map}
-        
+        ret_tensors = {
+            var.id: self.new_zeros(
+                size=[self.num_edges if var.is_edgevar() else self.num_nodes]
+                + list(var.var_shape),
+                dtype=var.var_dtype,
+                device=var.device,
+                requires_grad=False,
+            )
+            for var in var_list
+            if var.id not in self.ts.current_tensor_map
+        }
+
         for key, val in ret_tensors.items():
-            self.ts.track_tensor(key,val)
+            self.ts.track_tensor(key, val)
 
     def create_tensor_for_grad_vars(self, var_list, tensor_map):
-        ret_tensors = {var.id : self.new_zeros(size=[self.num_edges if var.is_edgevar() else self.num_nodes] + list(var.var_shape),
-                                               dtype=var.var_dtype,
-                                               device=var.device,
-                                               requires_grad=False) for var in var_list if var.id not in tensor_map}
+        ret_tensors = {
+            var.id: self.new_zeros(
+                size=[self.num_edges if var.is_edgevar() else self.num_nodes]
+                + list(var.var_shape),
+                dtype=var.var_dtype,
+                device=var.device,
+                requires_grad=False,
+            )
+            for var in var_list
+            if var.id not in tensor_map
+        }
         tensor_map = {**tensor_map, **ret_tensors}
         return tensor_map
 
@@ -308,86 +328,118 @@ def execute_unit(self, unit, tensor_list):
     def execute_compiled(self, uid, FuncWrapper):
         units = self.forward_exec_units[uid]
         args = units.joint_args()
-        rets =  units.joint_rets()
+        rets = units.joint_rets()
         for unit in units:
             self.create_tensor_for_vars(unit.unit_rets())
-            
+
         kernel_arg_list = units.kernel_arg_list()
-        ret_tensors = FuncWrapper.apply(self, uid, kernel_arg_list, rets, *[self.ts.current_tensor_map[var.id] for var in args])
+        ret_tensors = FuncWrapper.apply(
+            self,
+            uid,
+            kernel_arg_list,
+            rets,
+            *[self.ts.current_tensor_map[var.id] for var in args],
+        )
         # Only the return values returned by the function will have grad_fn set properly.
         # Therefore we need to replace the tensors in self.tensor_map with the return values
-        for i,ret in enumerate(rets):
+        for i, ret in enumerate(rets):
             self.ts.track_tensor(ret.id, ret_tensors[i])
-    
+
     def forward_cb(self, uid, kernel_args, rets, tensor_list):
-        '''FuncWrapper will call this function in forward pass'''
+        """FuncWrapper will call this function in forward pass"""
         units = self.forward_exec_units[uid]
-        for i,unit in enumerate(units):
+        for i, unit in enumerate(units):
             self.execute_unit(unit, [tensor_list[tidx] for tidx in kernel_args[i]])
-        
-        self.ts.tensor_map_stack.push({key: self.ts.current_tensor_map[key] for key in self.ts.bwd_common_tensor_list})
+
+        self.ts.tensor_map_stack.push(
+            {
+                key: self.ts.current_tensor_map[key]
+                for key in self.ts.bwd_common_tensor_list
+            }
+        )
         # self.ts.tensor_map_stack.push(self.ts.current_tensor_map)
-        
-        if isinstance(self.graph,DynamicGraph):
+
+        if isinstance(self.graph, DynamicGraph):
             self.ts.graph_timestamp_stack.push(self.graph.current_timestamp)
-        
+
         ret = tuple([self.ts.current_tensor_map[ret.id] for ret in rets])
         self.ts.clear_current_tensor_state()
         return ret
 
     def backward_cb(self, kid, grad_list):
-        '''FuncWrapper will call this function in backward pass'''
+        """FuncWrapper will call this function in backward pass"""
 
         # print("BACKWARD CALLED")
 
-        # which backward kernel to call? un-executed kernel that has all dependency satisfied. 
+        # which backward kernel to call? un-executed kernel that has all dependency satisfied.
         # We need to get the grad_map in order to properly set the variables in compiled kernels.
         funits = self.forward_exec_units[kid]
         args = funits.joint_args()
         rets = funits.joint_rets()
         inputs = funits.joint_inputs()
-        ret_grads = [ret._grad for ret in rets] # ret_grads corresponds vars in grad_list
+        ret_grads = [
+            ret._grad for ret in rets
+        ]  # ret_grads corresponds vars in grad_list
         tensor_map = self.ts.tensor_map_stack.top()
-        
-        if isinstance(self.graph,DynamicGraph):
+
+        if isinstance(self.graph, DynamicGraph):
             current_timestamp = self.ts.graph_timestamp_stack.top()
             self.graph.get_backward_graph(current_timestamp)
 
-        for i,grad in enumerate(ret_grads):
+        for i, grad in enumerate(ret_grads):
             # We track the ret_grads as its value is fixed to grad_list
             tensor_map[grad.id] = grad_list[i]
-        arg_grads = [arg._grad if arg in inputs and arg.requires_grad else None for arg in args] # arg_grads corresponds to the grads of funit.unit_args
+        arg_grads = [
+            arg._grad if arg in inputs and arg.requires_grad else None for arg in args
+        ]  # arg_grads corresponds to the grads of funit.unit_args
         for bu in self.bulist:
 
             if bu.compiled:
                 # if self.ts.is_executed_bu(bu):
                 #     continue
-                
+
                 # NOTE: Added to fix gpma
                 bu.reset_graph_info(self.graph)
-                
-                tensor_map = self.create_tensor_for_grad_vars(bu.unit_rets(),tensor_map)
+
+                tensor_map = self.create_tensor_for_grad_vars(
+                    bu.unit_rets(), tensor_map
+                )
 
                 self.execute_unit(bu, [tensor_map[arg.id] for arg in bu.kernel_args()])
-                
+
                 # self.ts.track_executed_bu(bu)
             else:
                 # The backward pass of some forward unit may be splitted into compiled and uncompiled parts
                 self.execute_prog([bu])
 
-        ret = tuple([tensor_map[grad.id] if grad != None else None for grad in arg_grads] + [None for grad in ret_grads])
-        
+        ret = tuple(
+            [tensor_map[grad.id] if grad != None else None for grad in arg_grads]
+            + [None for grad in ret_grads]
+        )
+
         del tensor_map
         self.ts.tensor_map_stack.pop()
-        
-        if isinstance(self.graph,DynamicGraph):
+
+        if isinstance(self.graph, DynamicGraph):
             self.ts.graph_timestamp_stack.pop()
-        
+
         return ret
 
     def execute_prog(self, units):
         current_tensor_map = self.ts.current_tensor_map
         self.ts.clear_current_tensor_state()
-        for unit in  units:
+        for unit in units:
             for stmt in unit.program:
-                self.ts.track_tensor(stmt.ret.id, stmt.execute([current_tensor_map[arg.id] if not is_const_scalar(arg) else arg for arg in stmt.args]))
\ No newline at end of file
+                self.ts.track_tensor(
+                    stmt.ret.id,
+                    stmt.execute(
+                        [
+                            (
+                                current_tensor_map[arg.id]
+                                if not is_const_scalar(arg)
+                                else arg
+                            )
+                            for arg in stmt.args
+                        ]
+                    ),
+                )
diff --git a/stgraph/dataset/CoraDataLoader.py b/stgraph/dataset/CoraDataLoader.py
deleted file mode 100644
index b9dbe00f..00000000
--- a/stgraph/dataset/CoraDataLoader.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import os
-import json
-import urllib.request
-import time
-import random
-
-import numpy as np
-
-from rich import inspect
-from rich.pretty import pprint
-from rich.progress import track
-from rich.console import Console
-
-console = Console()
-
-class CoraDataLoader:
-    def __init__(self, verbose:bool = False, split=0.75) -> None:
-        self.name = "Cora"
-        self.num_nodes = 0
-        self.num_edges = 0
-        
-        self._train_split = split
-        self._test_split = 1-split
-
-        self._local_file_path = f"../../dataset/cora/cora.json"
-        self._url_path = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/cora.json"
-        self._verbose = verbose
-
-        self._load_dataset()
-        self._get_edge_info()
-        self._get_targets_and_features()
-        self._get_graph_attributes()
-        
-        self._train_mask = [0] * self.num_nodes
-        self._test_mask = [0] * self.num_nodes
-        
-        self._get_mask_info()
-
-    def _load_dataset(self) -> None:
-        
-        if self._is_local_exists():
-            # loading the dataset from the local folder
-            if self._verbose:
-                console.log(f'Loading [cyan]{self.name}[/cyan] dataset locally')
-            with open(self._local_file_path) as dataset_json:
-                self._dataset = json.load(dataset_json)
-        else:
-            # loading the dataset by downloading them online
-            if self._verbose:
-                console.log(f'Downloading [cyan]{self.name}[/cyan] dataset')
-            self._dataset = json.loads(urllib.request.urlopen(self._url_path).read())
-
-    def _get_edge_info(self):
-        edges = np.array(self._dataset["edges"])
-        edge_list = []
-        for i in range(len(edges)):
-            edge = edges[i]
-            edge_list.append((edge[0], edge[1]))
-            
-        self._edge_list = edge_list
-
-    def _get_targets_and_features(self):
-        self._all_features = np.array(self._dataset["features"])
-        self._all_targets = np.array(self._dataset["labels"]).T
-
-    def _get_mask_info(self):
-        train_len = int(self.num_nodes * self._train_split)
-        
-        for i in range(0, train_len):
-            self._train_mask[i] = 1
-            
-        random.shuffle(self._train_mask)
-            
-        for i in range(len(self._train_mask)):
-            if self._train_mask[i] == 0:
-                self._test_mask[i] = 1
-            
-        self._train_mask = np.array(self._train_mask)
-        self._test_mask = np.array(self._test_mask)
-
-    def get_edges(self) -> np.ndarray:
-        return self._edge_list
-
-    def get_all_features(self) -> np.ndarray:
-        return self._all_features
-
-    def get_all_targets(self) -> np.ndarray:
-        return self._all_targets
-
-    def get_train_mask(self):
-        return self._train_mask
-    
-    def get_test_mask(self):
-        return self._test_mask
-
-    def get_train_features(self) -> np.ndarray:
-        train_range = int(len(self._all_features) * self.split)
-        return self._all_features[:train_range]
-
-    def get_train_targets(self) -> np.ndarray:
-        train_range = int(len(self._all_targets) * self.split)
-        return self._all_targets[:train_range]
-
-    def get_test_features(self) -> np.ndarray:
-        test_range = int(len(self._all_features) * self.split)
-        return self._all_features[test_range:]
-
-    def get_test_targets(self) -> np.ndarray:
-        test_range = int(len(self._all_targets) * self.split)
-        return self._all_targets[test_range:]
-
-    def _get_graph_attributes(self):
-        node_set = set()
-        for edge in self._edge_list:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            
-        self.num_nodes = len(node_set)
-        self.num_edges = len(self._edge_list)
-        
-    def _is_local_exists(self) -> bool:
-        return os.path.exists(self._local_file_path)
\ No newline at end of file
diff --git a/stgraph/dataset/EnglandCovidDataLoader.py b/stgraph/dataset/EnglandCovidDataLoader.py
deleted file mode 100644
index 5586b895..00000000
--- a/stgraph/dataset/EnglandCovidDataLoader.py
+++ /dev/null
@@ -1,152 +0,0 @@
-import os
-import json
-import urllib.request
-import time
-
-import numpy as np
-
-from rich import inspect
-from rich.pretty import pprint
-from rich.progress import track
-from rich.console import Console
-
-console = Console()
-
-from rich.traceback import install
-
-install(show_locals=True)
-
-
-class EnglandCovidDataLoader:
-    def __init__(
-        self, verbose: bool = False, lags: int = 8, split=0.75, for_stgraph=False
-    ) -> None:
-        self.name = "EnglandCOVID"
-        self.lags = lags
-        self.split = split
-
-        self._graph_attr = {}
-        self._graph_updates = {}
-        self._max_num_nodes = 0
-
-        self._local_path = "england_covid.json"
-        self._url_path = "https://raw.githubusercontent.com/benedekrozemberczki/pytorch_geometric_temporal/master/dataset/england_covid.json"
-        self._verbose = verbose
-
-        self._load_dataset()
-        self.total_timestamps = self._dataset["time_periods"]
-        self._get_targets_and_features()
-
-        if for_stgraph:
-            self._get_edge_info_stgraph()
-            self._presort_edge_weights()
-        else:
-            self._get_edge_info_pygt()
-
-    def get_graph_data(self):
-        return self._graph_updates, self._max_num_nodes
-
-    def _load_dataset(self) -> None:
-        # loading the dataset by downloading them online
-        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_stgraph(self):
-        # getting the edge_list and edge_weights
-        self._edge_list = []
-        self._edge_weights = []
-
-        for time in range(self._dataset["time_periods"]):
-            time_edge_list = []
-            time_edge_weights = []
-
-            for edge in self._dataset["edge_mapping"]["edge_index"][str(time)]:
-                time_edge_list.append((edge[0], edge[1]))
-
-            for weight in self._dataset["edge_mapping"]["edge_weight"][str(time)]:
-                time_edge_weights.append(weight)
-
-            self._edge_list.append(time_edge_list)
-            self._edge_weights.append(time_edge_weights)
-
-    def _get_edge_info_pygt(self):
-        self._edge_list = []
-        self._edge_weights = []
-
-        for time in range(self._dataset["time_periods"]):
-            self._edge_list.append(
-                np.array(self._dataset["edge_mapping"]["edge_index"][str(time)]).T
-            )
-            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._all_features = [
-            standardized_target[i : i + self.lags, :].T
-            for i in range(self._dataset["time_periods"] - self.lags)
-        ]
-        self._all_targets = [
-            standardized_target[i + self.lags, :].T
-            for i in range(self._dataset["time_periods"] - self.lags)
-        ]
-
-    def _presort_edge_weights(self):
-        """
-        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(self._edge_list)):
-            src_list = [edge[0] for edge in self._edge_list[i]]
-            dst_list = [edge[1] for edge in self._edge_list[i]]
-            weights = self._edge_weights[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)
-
-            # 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])
-            )
-
-            time_edge = []
-
-            for edge in sorted_edge_info_list:
-                time_edge.append((edge[0], edge[1]))
-                sorted_edge_weights_lst.append(edge[2])
-
-            final_edges_lst.append(time_edge)
-            final_edge_weights_lst.append(np.array(sorted_edge_weights_lst))
-
-        self._edge_list = final_edges_lst
-        self._edge_weights = final_edge_weights_lst
-
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_features(self):
-        return self._all_features
-
-    def get_all_targets(self):
-        return self._all_targets
diff --git a/stgraph/dataset/HungaryCPDataLoader.py b/stgraph/dataset/HungaryCPDataLoader.py
deleted file mode 100644
index e57da723..00000000
--- a/stgraph/dataset/HungaryCPDataLoader.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-
-class HungaryCPDataLoader:
-    def __init__(self, folder_name, dataset_name, lags, cutoff_time, verbose: bool = False, for_stgraph = False) -> None:
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        self.lags = lags
-        
-        self._load_dataset()
-        self.total_timestamps = min(len(self._dataset["FX"]), cutoff_time)
-        
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets_and_features()
-        
-    def _load_dataset(self):
-        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/{self.name}.json')
-        else:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-            
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-        
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-        
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T
-            
-    def _get_edge_weights(self):
-        self._edge_weights = np.ones(self.num_edges)
-        
-    def _get_targets_and_features(self):
-        stacked_target = np.array(self._dataset["FX"])
-        self._all_targets = np.array([
-            stacked_target[i, :].T
-            for i in range(stacked_target.shape[0])
-        ])
-        
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_targets(self):
-        return self._all_targets
\ No newline at end of file
diff --git a/stgraph/dataset/LinkPredDataLoader.py b/stgraph/dataset/LinkPredDataLoader.py
deleted file mode 100644
index 0961713b..00000000
--- a/stgraph/dataset/LinkPredDataLoader.py
+++ /dev/null
@@ -1,79 +0,0 @@
-import os
-import json
-import numpy as np
-from rich.console import Console
-
-console = Console()
-
-class LinkPredDataLoader:
-    def __init__(self, folder_name, dataset_name, cutoff_time, verbose: bool = False, for_stgraph= False):
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-
-        self._load_dataset()
-        self._get_max_num_nodes()
-        self.total_timestamps = min(self._dataset["time_periods"], cutoff_time)
-        self._get_edge_info()
-        self._preprocess_pos_neg_edges()
-
-    def _load_dataset(self) -> None:
-        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:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-    
-    def _get_max_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-
-        for i in range(len(self._dataset["edge_mapping"]["edge_index"])):
-            for edge in self._dataset["edge_mapping"]["edge_index"][str(i)]["add"]:
-                node_set.add(edge[0])
-                node_set.add(edge[1])
-                max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.max_num_nodes = len(node_set)
-
-    def _get_edge_info(self):
-        # getting the edge_list and edge_weights
-        edge_list = []
-        updates = self._dataset["edge_mapping"]["edge_index"]
-
-        working_set = set([(edge[0], edge[1]) for edge in updates["0"]["add"]])
-        edge_list.append(list(working_set))
-        for time in range(1, self.total_timestamps):
-            working_set = working_set.union(set([(edge[0], edge[1]) for edge in updates[str(time)]["add"]])).difference(set([(edge[0], edge[1]) for edge in updates[str(time)]["delete"]]))
-            edge_list.append(list(working_set))
-        
-        if self.for_stgraph:
-            self._edge_list = edge_list
-        else:
-            self._edge_list = [np.array(edge_lst_t).T for edge_lst_t in edge_list]
-    
-    def _preprocess_pos_neg_edges(self):
-        updates = self._dataset["edge_mapping"]["edge_index"]
-
-        pos_neg_edge_list = []
-        pos_neg_edge_label_list = []
-
-        for i in range(1, self.total_timestamps):
-            pos_edges_tup = list(updates[str(i)]["add"])
-            neg_edges_tup = list(updates[str(i)]["neg"])
-            pos_neg_edge_list.append(pos_edges_tup + neg_edges_tup)
-            pos_neg_edge_label_list.append([(edge[0], edge[1], 1) for edge in pos_edges_tup] + [(edge[0], edge[1], 0) for edge in neg_edges_tup])
-
-        self._pos_neg_edge_list = [np.array(edge_list).T for edge_list in pos_neg_edge_list]
-        self._pos_neg_edge_label_list = [np.array([edge[2] for edge in edge_list]) for edge_list in pos_neg_edge_label_list]
-
-    def get_edges(self):
-        return self._edge_list
-    
-    def get_pos_neg_edges(self):
-        return self._pos_neg_edge_list, self._pos_neg_edge_label_list
\ No newline at end of file
diff --git a/stgraph/dataset/METRLADataLoader.py b/stgraph/dataset/METRLADataLoader.py
deleted file mode 100644
index 7f88a007..00000000
--- a/stgraph/dataset/METRLADataLoader.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-import torch
-
-from rich import inspect
-
-class METRLADataLoader:
-    def __init__(self , folder_name, dataset_name, num_timesteps_in, num_timesteps_out, cutoff_time, verbose: bool = False, for_stgraph: bool = False):
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        
-        self.num_timesteps_in = num_timesteps_in
-        self.num_timesteps_out = num_timesteps_out
-        
-        self._load_dataset()
-        self.total_timestamps = min(self._dataset["time_periods"], cutoff_time)
-
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets_and_features()
-        
-    def _load_dataset(self):
-        # loading the dataset locally
-        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:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()   
-            
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-            
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-            
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T     
-            
-    # TODO: We are sorting the edge weights accordingly, but are we doing
-    # the same for edges in the edge list
-    def _get_edge_weights(self):
-        if self.for_stgraph:
-            edges = self._dataset["edges"]
-            edge_weights = self._dataset["weights"]
-            comb_edge_list = [(edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))]
-            comb_edge_list.sort(key=lambda x: (x[1], x[0]))
-            self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
-        else:
-            self._edge_weights = np.array(self._dataset["weights"])   
-       
-    def _get_targets_and_features(self):
-        X = []
-        
-        for timestamp in range(self._dataset['time_periods']):
-            if timestamp < self.total_timestamps:
-                X.append(self._dataset[str(timestamp)])
-            
-        X = np.array(X)
-        X = X.transpose(
-            (1, 2, 0)
-        )
-        X = X.astype(np.float32)
-
-        # Normalise as in DCRNN paper (via Z-Score Method)
-        means = np.mean(X, axis=(0, 2))
-        X = X - means.reshape(1, -1, 1)
-        stds = np.std(X, axis=(0, 2))
-        X = X / stds.reshape(1, -1, 1)
-
-        X = torch.from_numpy(X)
-            
-        inspect(X)
-            
-        indices = [
-            (i, i + (self.num_timesteps_in + self.num_timesteps_out))
-            for i in range(X.shape[2] - (self.num_timesteps_in + self.num_timesteps_out) + 1)
-        ]
-
-        # Generate observations
-        features, target = [], []
-        for i, j in indices:
-            features.append((X[:, :, i : i + self.num_timesteps_in]).numpy())
-            target.append((X[:, 0, i + self.num_timesteps_in : j]).numpy())
-
-        # inspect(indices)
-        # inspect(features)
-        # inspect(target)
-        # quit()
-
-        self._all_features = np.array(features)
-        self._all_targets = np.array(target)
-        
-    def get_edges(self):
-        return self._edge_list
-    
-    def get_edge_weights(self):
-        return self._edge_weights
-    
-    def get_all_targets(self):
-        return self._all_targets
-    
-    def get_all_features(self):
-        return self._all_features
\ No newline at end of file
diff --git a/stgraph/dataset/MontevideoBusDataLoader.py b/stgraph/dataset/MontevideoBusDataLoader.py
deleted file mode 100644
index 68282db4..00000000
--- a/stgraph/dataset/MontevideoBusDataLoader.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-
-class MontevideoBusDataLoader:
-    def __init__(self, folder_name, dataset_name, lags, cutoff_time, verbose: bool = False, for_stgraph = False) -> None:
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        self.lags = lags
-        
-        self._load_dataset()
-        self.total_timestamps = min(len(self._dataset["nodes"][0]['y']), cutoff_time)
-        
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets()
-        
-    def _load_dataset(self):
-        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/{self.name}.json')
-        else:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-            
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-        
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-        
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T
-            
-    def _get_edge_weights(self):
-        if self.for_stgraph:
-            edges = self._dataset["edges"]
-            edge_weights = self._dataset["weights"]
-            comb_edge_list = [(edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))]
-            comb_edge_list.sort(key=lambda x: (x[1], x[0]))
-            self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
-        else:
-            self._edge_weights = np.array(self._dataset["weights"])
-        
-    def _get_targets(self, target_var: str = "y"):
-        targets = []
-        for node in self._dataset["nodes"]:
-            y = node.get(target_var)
-            targets.append(np.array(y))
-        stacked_targets = np.stack(targets).T
-        standardized_targets = (
-            stacked_targets - np.mean(stacked_targets, axis=0)
-        ) / np.std(stacked_targets, axis=0)
-        self._all_targets = np.array([
-            standardized_targets[i, :].T
-            for i in range(len(standardized_targets))
-        ])
-        
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_targets(self):
-        return self._all_targets
\ No newline at end of file
diff --git a/stgraph/dataset/PedalMeDataLoader.py b/stgraph/dataset/PedalMeDataLoader.py
deleted file mode 100644
index f9fec7d5..00000000
--- a/stgraph/dataset/PedalMeDataLoader.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-
-from rich import inspect
-
-class PedalMeDataLoader:
-    def __init__(self, folder_name, dataset_name, lags, cutoff_time, verbose: bool = False, for_stgraph: bool = False):
-        self.name = dataset_name
-        self._local_path =  f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        self.lags = lags
-    
-        self._load_dataset()
-        self.total_timestamps = min(self._dataset["time_periods"], cutoff_time)
-    
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets_and_features()
-    
-    def _load_dataset(self):
-        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/{self.name}.json')
-        else:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-            
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-            
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-        
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T
-            
-    def _get_edge_weights(self):
-        if self.for_stgraph:
-            edges = self._dataset["edges"]
-            edge_weights = self._dataset["weights"]
-            comb_edge_list = [(edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))]
-            comb_edge_list.sort(key=lambda x: (x[1], x[0]))
-            self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
-        else:
-            self._edge_weights = np.array(self._dataset["weights"])
-            
-    def _get_targets_and_features(self):
-        targets = []
-        for time in range(self.total_timestamps):
-            targets.append(np.array(self._dataset[str(time)]))
-        
-        stacked_target = np.stack(targets)
-
-        self._all_targets = np.array(
-            [
-                stacked_target[i, :].T
-                for i in range(stacked_target.shape[0])
-            ]
-        )
-                
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_targets(self):
-        return self._all_targets
\ No newline at end of file
diff --git a/stgraph/dataset/WikiMathDataLoader.py b/stgraph/dataset/WikiMathDataLoader.py
deleted file mode 100644
index c71d2d2a..00000000
--- a/stgraph/dataset/WikiMathDataLoader.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-
-class WikiMathDataLoader:
-    def __init__(self, folder_name, dataset_name, lags, cutoff_time, verbose: bool = False, for_stgraph= False) -> None:
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        self.lags = lags
-
-        self._load_dataset()
-        self.total_timestamps = min(self._dataset["time_periods"], cutoff_time)
-        
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets_and_features()
-
-    def _load_dataset(self):
-        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:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-    
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-    
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-    
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T
-
-    def _get_edge_weights(self):
-        if self.for_stgraph:
-            edges = self._dataset["edges"]
-            edge_weights = self._dataset["weights"]
-            comb_edge_list = [(edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))]
-            comb_edge_list.sort(key=lambda x: (x[1], x[0]))
-            self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
-        else:
-            self._edge_weights = np.array(self._dataset["weights"])
-
-    def _get_targets_and_features(self):
-        targets = []
-        for time in range(self.total_timestamps):
-            targets.append(np.array(self._dataset[str(time)]["y"]))
-        stacked_target = np.stack(targets)
-        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
-            np.std(stacked_target, axis=0) + 10 ** -10
-        )
-        self._all_targets = np.array(
-            [
-                standardized_target[i, :].T
-                for i in range(len(targets))
-            ]
-        )
-
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_targets(self):
-        return self._all_targets
diff --git a/stgraph/dataset/WindmillOutputDataLoader.py b/stgraph/dataset/WindmillOutputDataLoader.py
deleted file mode 100644
index 58674c40..00000000
--- a/stgraph/dataset/WindmillOutputDataLoader.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import os
-import json
-from rich.console import Console
-import numpy as np
-console = Console()
-
-class WindmillOutputDataLoader:
-    def __init__(self, folder_name, dataset_name, lags, cutoff_time, verbose: bool = False, for_stgraph= False) -> None:
-        self.name = dataset_name
-        self._local_path = f'../../dataset/{folder_name}/{dataset_name}.json'
-        self._verbose = verbose
-        self.for_stgraph = for_stgraph
-        self.lags = lags
-
-        self._load_dataset()
-        self.total_timestamps = min(self._dataset["time_periods"], cutoff_time)
-
-        self._get_num_nodes()
-        self._get_num_edges()
-        self._get_edges()
-        self._get_edge_weights()
-        self._get_targets_and_features()
-
-    def _load_dataset(self):
-        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:
-            console.log(f'Failed to find [cyan]{self.name}[/cyan] dataset from dataset')
-            quit()
-    
-    def _get_num_nodes(self):
-        node_set = set()
-        max_node_id = 0
-        for edge in self._dataset["edges"]:
-            node_set.add(edge[0])
-            node_set.add(edge[1])
-            max_node_id = max(max_node_id, edge[0], edge[1])
-        
-        assert max_node_id == len(node_set) - 1, "Node ID labelling is not continuous"
-        self.num_nodes = len(node_set)
-    
-    def _get_num_edges(self):
-        self.num_edges = len(self._dataset["edges"])
-    
-    def _get_edges(self):
-        if self.for_stgraph:
-            self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
-        else:
-            self._edge_list = np.array(self._dataset["edges"]).T
-
-    def _get_edge_weights(self):
-        if self.for_stgraph:
-            edges = self._dataset["edges"]
-            edge_weights = self._dataset["weights"]
-            comb_edge_list = [(edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))]
-            comb_edge_list.sort(key=lambda x: (x[1], x[0]))
-            self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
-        else:
-            self._edge_weights = np.array(self._dataset["weights"])
-
-    def _get_targets_and_features(self):
-        stacked_target = np.stack(self._dataset["block"])
-        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
-            np.std(stacked_target, axis=0) + 10 ** -10
-        )
-        self._all_targets = [
-            standardized_target[i, :].T
-            for i in range(self.total_timestamps)
-        ]
-
-    def get_edges(self):
-        return self._edge_list
-
-    def get_edge_weights(self):
-        return self._edge_weights
-
-    def get_all_targets(self):
-        return self._all_targets
diff --git a/stgraph/dataset/__init__.py b/stgraph/dataset/__init__.py
index 5d49c59a..d6419c84 100644
--- a/stgraph/dataset/__init__.py
+++ b/stgraph/dataset/__init__.py
@@ -1,1 +1,14 @@
-'''Dataset loader provided by STGraph'''
\ No newline at end of file
+"""Dataset loader provided by STGraph."""
+
+from stgraph.dataset.dynamic.england_covid_dataloader import EnglandCovidDataLoader
+from stgraph.dataset.dynamic.stgraph_dynamic_dataset import STGraphDynamicDataset
+from stgraph.dataset.static.cora_dataloader import CoraDataLoader
+from stgraph.dataset.static.stgraph_static_dataset import STGraphStaticDataset
+from stgraph.dataset.stgraph_dataset import STGraphDataset
+from stgraph.dataset.temporal.hungarycp_dataloader import HungaryCPDataLoader
+from stgraph.dataset.temporal.metrla_dataloader import METRLADataLoader
+from stgraph.dataset.temporal.montevideobus_dataloader import MontevideoBusDataLoader
+from stgraph.dataset.temporal.pedalme_dataloader import PedalMeDataLoader
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+from stgraph.dataset.temporal.wikimath_dataloader import WikiMathDataLoader
+from stgraph.dataset.temporal.windmilloutput_dataloader import WindmillOutputDataLoader
diff --git a/stgraph/dataset/dynamic/__init__.py b/stgraph/dataset/dynamic/__init__.py
new file mode 100644
index 00000000..4c0ddc36
--- /dev/null
+++ b/stgraph/dataset/dynamic/__init__.py
@@ -0,0 +1,1 @@
+"""Dataset loaders for real-world dynamic datasets."""
diff --git a/stgraph/dataset/dynamic/england_covid_dataloader.py b/stgraph/dataset/dynamic/england_covid_dataloader.py
new file mode 100644
index 00000000..d142cf78
--- /dev/null
+++ b/stgraph/dataset/dynamic/england_covid_dataloader.py
@@ -0,0 +1,214 @@
+"""COVID-19 cases in England's NUTS3 regions."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.dynamic.stgraph_dynamic_dataset import STGraphDynamicDataset
+
+
+class EnglandCovidDataLoader(STGraphDynamicDataset):
+    r"""COVID-19 cases in England's NUTS3 regions.
+
+    This dataset captures the interplay between COVID-19 cases and mobility
+    in England's NUTS3 regions from March 3rd to May 12th. It is a directed
+    and weighted graph that offers daily case count and movement of people
+    between each region through node and edge features respectively.
+
+    This class provides functionality for loading, processing, and accessing
+    the England Covid dataset for use in deep learning tasks such as predicting
+    the COVID cases in a region.
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import EnglandCovidDataLoader
+
+        eng_covid = EnglandCovidDataLoader(verbose=True)
+        num_nodes_dict = eng_covid.gdata["num_nodes"]
+        num_edges_dict = eng_covid.gdata["num_edges"]
+        total_timestamps = eng_covid.gdata["total_timestamps"]
+
+        edge_list = eng_covid.get_edges()
+        edge_weights = eng_covid.get_edge_weights()
+        feats = eng_covid.get_all_features()
+        targets = eng_covid.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 8)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: EnglandCovidDataLoader,
+        verbose: bool = False,
+        lags: int = 8,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        """COVID-19 cases in England's NUTS3 regions."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+
+        self.name = "England_COVID"
+        self._url = "https://raw.githubusercontent.com/benedekrozemberczki/pytorch_geometric_temporal/master/dataset/england_covid.json"
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+        self._all_features = None
+        self._all_targets = None
+        self._edge_list = None
+        self._edge_weights = None
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: EnglandCovidDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_targets_and_features()
+        self._set_edge_info()
+        self._presort_edge_weights()
+
+    def _set_total_timestamps(self: EnglandCovidDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time:
+            self.gdata["total_timestamps"] = min(
+                self._dataset["time_periods"],
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = self._dataset["time_periods"]
+
+    def _set_targets_and_features(self: EnglandCovidDataLoader) -> None:
+        r"""Calculate and set the target and feature attributes."""
+        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._all_features = [
+            standardized_target[i : i + self._lags, :].T
+            for i in range(self.gdata["total_timestamps"] - self._lags)
+        ]
+        self._all_targets = [
+            standardized_target[i + self._lags, :].T
+            for i in range(self.gdata["total_timestamps"] - self._lags)
+        ]
+
+    def _set_edge_info(self: EnglandCovidDataLoader) -> None:
+        r"""Set edge info such as edge list and edge weights."""
+        self._edge_list = []
+        self._edge_weights = []
+
+        for time in range(self.gdata["total_timestamps"]):
+            time_edge_weights = []
+
+            time_edge_list = [
+                (edge[0], edge[1])
+                for edge in self._dataset["edge_mapping"]["edge_index"][str(time)]
+            ]
+
+            time_edge_weights = list(
+                self._dataset["edge_mapping"]["edge_weight"][str(time)],
+            )
+
+            self._edge_list.append(time_edge_list)
+            self._edge_weights.append(time_edge_weights)
+            self.gdata["num_edges"][str(time)] = len(time_edge_list)
+            self.gdata["num_nodes"][str(time)] = len(
+                {node for edge in time_edge_list for node in edge},
+            )
+
+    def _presort_edge_weights(self: EnglandCovidDataLoader) -> None:
+        r"""Presorting the edges.
+
+        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(self._edge_list)):
+            src_list = [edge[0] for edge in self._edge_list[i]]
+            dst_list = [edge[1] for edge in self._edge_list[i]]
+            weights = self._edge_weights[i]
+
+            edge_info_list = []
+            sorted_edge_weights_lst = []
+
+            for src, dst, weight in zip(src_list, dst_list, weights):
+                edge_info_list.append((src, dst, weight))
+
+            # 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]),
+            )
+
+            time_edge = []
+
+            for edge in sorted_edge_info_list:
+                time_edge.append((edge[0], edge[1]))
+                sorted_edge_weights_lst.append(edge[2])
+
+            final_edges_lst.append(time_edge)
+            final_edge_weights_lst.append(np.array(sorted_edge_weights_lst))
+
+        self._edge_list = final_edges_lst
+        self._edge_weights = final_edge_weights_lst
+
+    def get_edges(self: EnglandCovidDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: EnglandCovidDataLoader) -> list:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_features(self: EnglandCovidDataLoader) -> list:
+        r"""Return the features for each timestamp."""
+        return self._all_features
+
+    def get_all_targets(self: EnglandCovidDataLoader) -> list:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
diff --git a/stgraph/dataset/dynamic/stgraph_dynamic_dataset.py b/stgraph/dataset/dynamic/stgraph_dynamic_dataset.py
new file mode 100644
index 00000000..a50442e4
--- /dev/null
+++ b/stgraph/dataset/dynamic/stgraph_dynamic_dataset.py
@@ -0,0 +1,27 @@
+"""Base class for all STGraph dynamic graph datasets."""
+
+from __future__ import annotations
+
+from stgraph.dataset.stgraph_dataset import STGraphDataset
+
+
+class STGraphDynamicDataset(STGraphDataset):
+    r"""Base class for dynamic graph datasets."""
+
+    def __init__(self: STGraphDynamicDataset) -> None:
+        r"""Provide the base structure for handling dynamic graph datasets."""
+        super().__init__()
+
+        self._init_graph_data()
+
+    def _init_graph_data(self: STGraphDynamicDataset) -> dict:
+        r"""Initialize graph meta data for a dynamic dataset.
+
+        The ``num_nodes``, ``num_edges``, ``total_timestamps`` keys are set to value 0
+        """
+        self.gdata["num_nodes"] = {}
+        self.gdata["num_edges"] = {}
+        self.gdata["total_timestamps"] = 0
+
+        self._lags = 0
+        self._cutoff_time = None
diff --git a/stgraph/dataset/static/__init__.py b/stgraph/dataset/static/__init__.py
new file mode 100644
index 00000000..ff774c01
--- /dev/null
+++ b/stgraph/dataset/static/__init__.py
@@ -0,0 +1,1 @@
+"""Collection of dataset loaders for Static real-world datasets."""
diff --git a/stgraph/dataset/static/cora_dataloader.py b/stgraph/dataset/static/cora_dataloader.py
new file mode 100644
index 00000000..7f85b48c
--- /dev/null
+++ b/stgraph/dataset/static/cora_dataloader.py
@@ -0,0 +1,142 @@
+"""Citation network consisting of scientific publications."""
+
+
+from __future__ import annotations
+
+import numpy as np
+from rich.console import Console
+
+from stgraph.dataset.static.stgraph_static_dataset import STGraphStaticDataset
+
+console = Console()
+
+
+class CoraDataLoader(STGraphStaticDataset):
+    r"""Citation network consisting of scientific publications.
+
+    The Cora dataset consists of 2708 scientific publications classified into
+    one of seven classes. The citation network consists of 5429 links. Each
+    publication in the dataset is described by a 0/1-valued word vector
+    indicating the absence/presence of the corresponding word from the dictionary.
+    The dictionary consists of 1433 unique words.
+
+    This class provides functionality for loading, processing, and accessing the
+    Cora dataset for use in deep learning tasks such as graph-based
+    node classification.
+
+    .. list-table:: gdata
+        :widths: 25 25 25 25
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - num_feats
+          - num_classes
+        * - 2708
+          - 10556
+          - 1433
+          - 7
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import CoraDataLoader
+
+        cora = CoraDataLoader()
+        num_nodes = cora.gdata["num_nodes"]
+        edge_list = cora.get_edges()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: CoraDataLoader,
+        verbose: bool = False,
+        redownload: bool = False,
+    ) -> None:
+        """Citation network consisting of scientific publications."""
+        super().__init__()
+
+        self.name = "Cora"
+        self._url = (
+            "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/cora.json"
+        )
+        self._verbose = verbose
+        self._edge_list = None
+        self._all_features = None
+        self._all_targets = None
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: CoraDataLoader) -> None:
+        r"""Process the Cora dataset.
+
+        Calls private methods to extract edge list, node features, target classes
+        and the train/test binary mask array.
+        """
+        self._set_edge_info()
+        self._set_targets_and_features()
+        self._set_graph_attributes()
+
+    def _set_edge_info(self: CoraDataLoader) -> None:
+        r"""Extract edge information from the dataset."""
+        edges = np.array(self._dataset["edges"])
+        edge_list = []
+
+        for src, dst in edges:
+            edge_list.append((src, dst))
+
+        self._edge_list = edge_list
+
+    def _set_targets_and_features(self: CoraDataLoader) -> None:
+        r"""Extract targets and features from the dataset."""
+        self._all_features = np.array(self._dataset["features"])
+        self._all_targets = np.array(self._dataset["labels"]).T
+
+    def _set_graph_attributes(self: CoraDataLoader) -> None:
+        r"""Calculate and stores graph meta data inside ``gdata``."""
+        node_set = set()
+        for edge in self._edge_list:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+
+        self.gdata["num_nodes"] = len(node_set)
+        self.gdata["num_edges"] = len(self._edge_list)
+        self.gdata["num_feats"] = len(self._all_features[0])
+        self.gdata["num_classes"] = len(set(self._all_targets))
+
+    def get_edges(self: CoraDataLoader) -> list:
+        r"""Get the edge list."""
+        return self._edge_list
+
+    def get_all_features(self: CoraDataLoader) -> np.ndarray:
+        r"""Get all features."""
+        return self._all_features
+
+    def get_all_targets(self: CoraDataLoader) -> np.ndarray:
+        r"""Get all targets."""
+        return self._all_targets
diff --git a/stgraph/dataset/static/stgraph_static_dataset.py b/stgraph/dataset/static/stgraph_static_dataset.py
new file mode 100644
index 00000000..d4225700
--- /dev/null
+++ b/stgraph/dataset/static/stgraph_static_dataset.py
@@ -0,0 +1,23 @@
+"""Base class for all STGraph static graph datasets."""
+
+from __future__ import annotations
+
+from stgraph.dataset.stgraph_dataset import STGraphDataset
+
+
+class STGraphStaticDataset(STGraphDataset):
+    r"""Base class for static graph datasets."""
+
+    def __init__(self: STGraphStaticDataset) -> None:
+        r"""Provide the base structure for handling static graph datasets."""
+        super().__init__()
+
+        self._init_graph_data()
+
+    def _init_graph_data(self: STGraphStaticDataset) -> dict:
+        r"""Initialize graph meta data for a static dataset.
+
+        The ``num_nodes`` and ``num_edges`` keys are set to value 0
+        """
+        self.gdata["num_nodes"] = 0
+        self.gdata["num_edges"] = 0
diff --git a/stgraph/dataset/stgraph_dataset.py b/stgraph/dataset/stgraph_dataset.py
new file mode 100644
index 00000000..bef40db3
--- /dev/null
+++ b/stgraph/dataset/stgraph_dataset.py
@@ -0,0 +1,222 @@
+"""Base class for all STGraph dataset loaders."""
+
+from __future__ import annotations
+
+import json
+import os
+import ssl
+import urllib.request
+from abc import ABC, abstractmethod
+
+from rich.console import Console
+
+console = Console()
+
+
+class STGraphDataset(ABC):
+    r"""Abstract base class for graph dataset loaders."""
+
+    def __init__(self: STGraphDataset) -> None:
+        r"""Abstract base class for graph dataset loaders.
+
+        The dataset handling is done as follows
+
+        1. Checks whether the dataset is present in cache.
+        2. If not present in the cache, it downloads it from the URL.
+        3. It then saves the downloaded file inside the cache.
+        4. Incase it is present inside the cache, it directly loads it from there
+        5. Dataset specific graph processing is then done
+
+        Attributes
+        ----------
+        name : str
+            The name of the dataset
+        gdata : dict
+            Meta data associated with the dataset
+
+        _dataset : dict
+            The loaded graph dataset
+        _url : str
+            The URL from where the dataset is downloaded online
+        _verbose : bool
+            Flag to control whether to display verbose info
+        _cache_folder : str
+            Folder inside ~/.stgraph where the dataset cache is stored
+        _cache_file_type : str
+            The file type used for storing the cached dataset
+
+        Methods
+        -------
+        _has_dataset_cache()
+            Checks if the dataset is stored in cache
+
+        _get_cache_file_path()
+            Returns the absolute path of the cached dataset file
+
+        _init_graph_data()
+            Initialises the ``gdata`` attribute with all necessary meta data
+
+        _process_dataset()
+            Processes the dataset to be used by STGraph
+
+        _download_dataset()
+            Downloads the dataset using the URL
+
+        _save_dataset()
+            Saves the dataset to cache
+
+        _load_dataset()
+        Loads the dataset from cache
+
+        """
+        self.name = ""
+        self.gdata = {}
+
+        self._dataset = {}
+        self._url = ""
+        self._verbose = False
+        self._cache_folder = "/dataset_cache/"
+        self._cache_file_type = "json"
+
+    def _has_dataset_cache(self: STGraphDataset) -> bool:
+        r"""Check if the dataset is stored in cache.
+
+        This private method checks whether the graph dataset cache file exists
+        in the dataset cache folder. The cache .json file is found in the following
+        directory ``~/.stgraph/dataset_cache/.
+
+        Returns:
+        -------
+        bool
+            ``True`` if the cache file exists, else ``False``
+
+        Notes:
+        -----
+        The cache file is usually stored as a json file named as
+        ``dataset_name.json`` and is stored inside the ``~/.stgraph/dataset_cache/``.
+        Incase the directory does not exists, it is created by this method.
+
+        This private method is intended for internal use within the class and
+        should not be called directly from outside the class.
+
+        Example:
+        -------
+        .. code-block:: python
+
+            if self._has_dataset_cache():
+                # The dataset is cached, continue cached operations
+            else:
+                # The dataset is not cached, continue load and save operations
+
+        """
+        user_home_dir = os.path.expanduser("~")
+        stgraph_dir = user_home_dir + "/.stgraph"
+        cache_dir = stgraph_dir + self._cache_folder
+
+        if os.path.exists(stgraph_dir) is False:
+            os.system("mkdir " + stgraph_dir)
+
+        if os.path.exists(cache_dir) is False:
+            os.system("mkdir " + cache_dir)
+
+        cache_file_name = self.name + "." + self._cache_file_type
+
+        return os.path.exists(cache_dir + cache_file_name)
+
+    def _get_cache_file_path(self: STGraphDataset) -> str:
+        r"""Return the absolute path of the cached dataset file.
+
+        Returns
+        -------
+        str
+            The absolute path of the cached dataset file
+
+        """
+        user_home_dir = os.path.expanduser("~")
+        stgraph_dir = user_home_dir + "/.stgraph"
+        cache_dir = stgraph_dir + self._cache_folder
+        cache_file_name = self.name + "." + self._cache_file_type
+
+        return cache_dir + cache_file_name
+
+    def _delete_cached_dataset(self: STGraphDataset) -> None:
+        r"""Delete the cached dataset file."""
+        os.remove(self._get_cache_file_path())
+
+    @abstractmethod
+    def _init_graph_data(self: STGraphDataset) -> None:
+        r"""Initialise the ``gdata`` attribute with all necessary meta data.
+
+        This is an abstract method that is implemented by ``STGraphStaticDataset``.
+        The meta data is initialised based on the type of the graph dataset. The
+        values are calculated as key-value pairs by the respective dataloaders
+        when they are initialised.
+        """
+
+    @abstractmethod
+    def _process_dataset(self: STGraphDataset) -> None:
+        r"""Process the dataset to be used by STGraph.
+
+        This is an abstract method that is to be implemented by each dataset loader.
+        The implementation in specific to the nature of the dataset itself.
+        The dataset is processed in such a way that it can be smoothly used
+        within STGraph.
+        """
+
+    def _download_dataset(self: STGraphDataset) -> None:
+        r"""Download the dataset using the URL.
+
+        Downloads the dataset files from the URL set by default for each data loader
+        or by one provided by the user. If verbose mode is enabled, it displays
+        download status.
+        """
+        if self._verbose:
+            console.log(
+                f"[cyan bold]{self.name}[/cyan bold] not present in cache."
+                "Downloading right now.",
+            )
+
+        if not self._url.startswith(("http:", "https:")):
+            raise ValueError("URL must start with 'http:' or 'https:'")
+
+        context = ssl.create_default_context()
+        context.check_hostname = False
+        context.verify_mode = ssl.CERT_NONE
+
+        self._dataset = json.loads(
+            urllib.request.urlopen(self._url, context=context).read(),
+        )
+
+        if self._verbose:
+            console.log(f"[cyan bold]{self.name}[/cyan bold] download complete.")
+
+    def _save_dataset(self: STGraphDataset) -> None:
+        r"""Save the dataset to cache.
+
+        Saves the downloaded dataset file to the cache folder. If verbose mode
+        is enabled, it displays the save information.
+        """
+        with open(self._get_cache_file_path(), "w") as cache_file:
+            json.dump(self._dataset, cache_file)
+
+            if self._verbose:
+                console.log(
+                    f"[cyan bold]{self.name}[/cyan bold] dataset saved to cache",
+                )
+
+    def _load_dataset(self: STGraphDataset) -> None:
+        r"""Load the dataset from cache.
+
+        Loads the caches dataset json file as a python dictionary. If verbose mode
+        is enabled, it displays the loading status.
+        """
+        if self._verbose:
+            console.log(f"Loading [cyan bold]{self.name}[/cyan bold] from cache")
+
+        with open(self._get_cache_file_path()) as cache_file:
+            self._dataset = json.load(cache_file)
+
+        if self._verbose:
+            console.log(
+                f"Successfully loaded [cyan bold]{self.name}[/cyan bold] from cache",
+            )
diff --git a/stgraph/dataset/temporal/__init__.py b/stgraph/dataset/temporal/__init__.py
new file mode 100644
index 00000000..2d8ab191
--- /dev/null
+++ b/stgraph/dataset/temporal/__init__.py
@@ -0,0 +1,1 @@
+"""Collection of dataset loaders for Temporal real-world datasets."""
diff --git a/stgraph/dataset/temporal/hungarycp_dataloader.py b/stgraph/dataset/temporal/hungarycp_dataloader.py
new file mode 100644
index 00000000..8bbf5ada
--- /dev/null
+++ b/stgraph/dataset/temporal/hungarycp_dataloader.py
@@ -0,0 +1,173 @@
+"""County level chicken pox cases in Hungary."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class HungaryCPDataLoader(STGraphTemporalDataset):
+    r"""County level chicken pox cases in Hungary.
+
+    This dataset comprises information on weekly occurrences of chickenpox
+    in Hungary from 2005 to 2015. The graph structure is static with nodes
+    representing the counties and edges are neighbourhoods between them.
+    Vertex features are lagged weekly counts of the chickenpox cases.
+
+    This class provides functionality for loading, processing, and accessing
+    the Hungary Chickenpox dataset for use in deep learning tasks such as
+    County level case count prediction.
+
+    .. list-table:: gdata
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 20
+          - 102
+          - 521
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import HungaryCPDataLoader
+
+        hungary = HungaryCPDataLoader(verbose=True)
+        num_nodes = hungary.gdata["num_nodes"]
+        edge_list = hungary.get_edges()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 4)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: HungaryCPDataLoader,
+        verbose: bool = False,
+        lags: int = 4,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        """County level chicken pox cases in Hungary."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+
+        self.name = "Hungary_Chickenpox"
+        self._url = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/HungaryCP.json"
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+        self._edge_list = None
+        self._edge_weights = None
+        self._all_targets = None
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: HungaryCPDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_targets_and_features()
+
+    def _set_total_timestamps(self: HungaryCPDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                len(self._dataset["FX"]),
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = len(self._dataset["FX"])
+
+    def _set_num_nodes(self: HungaryCPDataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise RuntimeError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: HungaryCPDataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: HungaryCPDataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: HungaryCPDataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        self._edge_weights = np.ones(self.gdata["num_edges"])
+
+    def _set_targets_and_features(self: HungaryCPDataLoader) -> None:
+        r"""Calculate and set the target and feature attributes."""
+        stacked_target = np.array(self._dataset["FX"])
+
+        self._all_targets = [
+            stacked_target[i + self._lags, :].T
+            for i in range(self.gdata["total_timestamps"] - self._lags)
+        ]
+
+    def get_edges(self: HungaryCPDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: HungaryCPDataLoader) -> np.ndarray:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_targets(self: HungaryCPDataLoader) -> np.ndarray:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
diff --git a/stgraph/dataset/temporal/metrla_dataloader.py b/stgraph/dataset/temporal/metrla_dataloader.py
new file mode 100644
index 00000000..d72fef5b
--- /dev/null
+++ b/stgraph/dataset/temporal/metrla_dataloader.py
@@ -0,0 +1,226 @@
+"""Traffic forecasting based on Los Angeles city."""
+
+from __future__ import annotations
+
+import numpy as np
+import torch
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class METRLADataLoader(STGraphTemporalDataset):
+    r"""Traffic forecasting dataset based on the Los Angeles city.
+
+    A dataset for predicting traffic patterns in the Los Angeles Metropolitan area,
+    comprising traffic data obtained from 207 loop detectors on highways in Los
+    Angeles County. The dataset includes aggregated 5-minute interval readings
+    spanning a four-month period from March 2012 to June 2012.
+
+    This class provides functionality for loading, processing, and accessing
+    the METRLA dataset for use in deep learning tasks such as traffic
+    forecasting.
+
+    .. list-table:: gdata
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 207
+          - 1722
+          - 100
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import METRLADataLoader
+
+        metrla = METRLADataLoader(verbose=True)
+        num_nodes = metrla.gdata["num_nodes"]
+        num_edges = metrla.gdata["num_edges"]
+        total_timestamps = metrla.gdata["total_timestamps"]
+
+        edge_list = metrla.get_edges()
+        edge_weights = metrla.get_edge_weights()
+        feats = metrla.get_all_features()
+        targets = metrla.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    num_timesteps_in : int, optional
+        The number of timesteps the sequence model sees (default is 12)
+    num_timesteps_out : int, optional
+        The number of timesteps the sequence model has to predict (default is 12)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: METRLADataLoader,
+        verbose: bool = False,
+        num_timesteps_in: int = 12,
+        num_timesteps_out: int = 12,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        """Traffic forecasting based on the Los Angeles city."""
+        super().__init__()
+
+        if not isinstance(num_timesteps_in, int):
+            raise TypeError("num_timesteps_in must be of type int")
+        if num_timesteps_in < 0:
+            raise ValueError("num_timesteps_in must be a positive integer")
+
+        if not isinstance(num_timesteps_out, int):
+            raise TypeError("num_timesteps_out must be of type int")
+        if num_timesteps_out < 0:
+            raise ValueError("num_timesteps_out must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+
+        self.name = "METRLA"
+        self._url = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/METRLA.json"
+        self._verbose = verbose
+        self._num_timesteps_in = num_timesteps_in
+        self._num_timesteps_out = num_timesteps_out
+        self._cutoff_time = cutoff_time
+        self._edge_list = None
+        self._edge_weights = None
+        self._all_features = None
+        self._all_targets = None
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: METRLADataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_targets_and_features()
+
+    def _set_total_timestamps(self: METRLADataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                self._dataset["time_periods"],
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = self._dataset["time_periods"]
+
+    def _set_num_nodes(self: METRLADataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise RuntimeError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: METRLADataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: METRLADataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: METRLADataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        edges = self._dataset["edges"]
+        edge_weights = self._dataset["weights"]
+        comb_edge_list = [
+            (edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))
+        ]
+        comb_edge_list.sort(key=lambda x: (x[1], x[0]))
+        self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
+
+    def _set_targets_and_features(self: METRLADataLoader) -> None:
+        r"""Calculate and set the target and feature attributes."""
+        x = [
+            self._dataset[str(timestamp)]
+            for timestamp in range(self.gdata["total_timestamps"])
+        ]
+
+        x = np.array(x).transpose(1, 2, 0).astype(np.float32)
+        # x = x.transpose((1, 2, 0))
+        # x = x.astype(np.float32)
+
+        # Normalise as in DCRNN paper (via Z-Score Method)
+        means = np.mean(x, axis=(0, 2))
+        x = x - means.reshape(1, -1, 1)
+        stds = np.std(x, axis=(0, 2))
+        x = x / stds.reshape(1, -1, 1)
+
+        x = torch.from_numpy(x)
+
+        indices = [
+            (i, i + (self._num_timesteps_in + self._num_timesteps_out))
+            for i in range(
+                x.shape[2] - (self._num_timesteps_in + self._num_timesteps_out) + 1,
+            )
+        ]
+
+        # Generate observations
+        features, target = [], []
+        for i, j in indices:
+            features.append((x[:, :, i : i + self._num_timesteps_in]).numpy())
+            target.append((x[:, 0, i + self._num_timesteps_in : j]).numpy())
+
+        self._all_features = np.array(features)
+        self._all_targets = np.array(target)
+
+    def get_edges(self: METRLADataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: METRLADataLoader) -> np.ndarray:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_targets(self: METRLADataLoader) -> np.ndarray:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
+
+    def get_all_features(self: METRLADataLoader) -> np.ndarray:
+        r"""Return the features for each timestamp."""
+        return self._all_features
diff --git a/stgraph/dataset/temporal/montevideobus_dataloader.py b/stgraph/dataset/temporal/montevideobus_dataloader.py
new file mode 100644
index 00000000..e51a0a8c
--- /dev/null
+++ b/stgraph/dataset/temporal/montevideobus_dataloader.py
@@ -0,0 +1,221 @@
+"""Passenger inflow at bus stops in Montevideo city."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class MontevideoBusDataLoader(STGraphTemporalDataset):
+    r"""Passenger inflow at bus stops in Montevideo city.
+
+    This dataset compiles hourly passenger inflow data for 11 key bus lines
+    in Montevideo, Uruguay, during October 2020. Focused on routes to the city
+    center, it encompasses bus stop vertices, interlinked by edges representing
+    connections with weights indicating road distances. The target variable
+    is passenger inflow, sourced from diverse data outlets within Montevideo's
+    Metropolitan Transportation System (STM).
+
+    This class provides functionality for loading, processing, and accessing the
+    Montevideo Bus dataset for use in deep learning tasks such as
+    passenger inflow prediction.
+
+    .. list-table:: gdata
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 675
+          - 690
+          - 744
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import MontevideoBusDataLoader
+
+        monte = MontevideoBusDataLoader(verbose=True)
+        num_nodes = monte.gdata["num_nodes"]
+        num_edges = monte.gdata["num_edges"]
+        total_timestamps = monte.gdata["total_timestamps"]
+
+        edge_list = monte.get_edges()
+        edge_weights = monte.get_edge_weights()
+        feats = monte.get_all_features()
+        targets = monte.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 4)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: MontevideoBusDataLoader,
+        verbose: bool = False,
+        lags: int = 4,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        r"""Passenger inflow at bus stops in Montevideo city."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+        if cutoff_time is not None and cutoff_time <= lags:
+            raise ValueError("cutoff_time must be greater than lags")
+
+        self.name = "Montevideo_Bus"
+        self._url = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/montevideobus.json"
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: MontevideoBusDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_features()
+        self._set_targets()
+
+    def _set_total_timestamps(self: MontevideoBusDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                len(self._dataset["nodes"][0]["y"]),
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = len(self._dataset["nodes"][0]["y"])
+
+    def _set_num_nodes(self: MontevideoBusDataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise ValueError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: MontevideoBusDataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: MontevideoBusDataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: MontevideoBusDataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        edges = self._dataset["edges"]
+        edge_weights = self._dataset["weights"]
+        comb_edge_list = [
+            (edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))
+        ]
+        comb_edge_list.sort(key=lambda x: (x[1], x[0]))
+        self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
+
+    def _set_features(self: MontevideoBusDataLoader) -> None:
+        r"""Calculate and set the feature attributes."""
+        features = [
+            np.array(node.get("X").get(feature_var)[: self.gdata["total_timestamps"]])
+            for node in self._dataset["nodes"]
+            for feature_var in ["y"]
+        ]
+
+        stacked_features = np.stack(features).T
+        standardized_features = (
+            stacked_features - np.mean(stacked_features, axis=0)
+        ) / np.std(stacked_features, axis=0)
+
+        self._all_features = np.array(
+            [
+                standardized_features[i : i + self._lags, :].T
+                for i in range(len(standardized_features) - self._lags)
+            ],
+        )
+
+    def _set_targets(self: MontevideoBusDataLoader) -> None:
+        r"""Calculate and set the target attributes."""
+        targets = []
+        for node in self._dataset["nodes"]:
+            y = node.get("y")[: self.gdata["total_timestamps"]]
+            targets.append(np.array(y))
+
+        stacked_targets = np.stack(targets).T
+        standardized_targets = (
+            stacked_targets - np.mean(stacked_targets, axis=0)
+        ) / np.std(stacked_targets, axis=0)
+
+        self._all_targets = np.array(
+            [
+                standardized_targets[i + self._lags, :].T
+                for i in range(len(standardized_targets) - self._lags)
+            ],
+        )
+
+    def get_edges(self: MontevideoBusDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: MontevideoBusDataLoader) -> np.ndarray:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_targets(self: MontevideoBusDataLoader) -> np.ndarray:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
+
+    def get_all_features(self: MontevideoBusDataLoader) -> np.ndarray:
+        r"""Return the features for each timestamp."""
+        return self._all_features
diff --git a/stgraph/dataset/temporal/pedalme_dataloader.py b/stgraph/dataset/temporal/pedalme_dataloader.py
new file mode 100644
index 00000000..d3371f43
--- /dev/null
+++ b/stgraph/dataset/temporal/pedalme_dataloader.py
@@ -0,0 +1,189 @@
+"""PedalMe Bicycle deliver orders in London."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class PedalMeDataLoader(STGraphTemporalDataset):
+    r"""PedalMe Bicycle deliver orders in London.
+
+    This class provides functionality for loading, processing, and
+    accessing the PedalMe dataset for use in deep learning tasks
+    such as node classification.
+
+    .. list-table:: gdata
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 15
+          - 225
+          - 36
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import PedalMeDataLoader
+
+        pedal = PedalMeDataLoader(verbose=True)
+        num_nodes = pedal.gdata["num_nodes"]
+        num_edges = pedal.gdata["num_edges"]
+        total_timestamps = pedal.gdata["total_timestamps"]
+
+        edge_list = pedal.get_edges()
+        edge_weights = pedal.get_edge_weights()
+        targets = pedal.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 4)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: PedalMeDataLoader,
+        verbose: bool = False,
+        lags: int = 4,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        """PedalMe Bicycle deliver orders in London."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+        if cutoff_time is not None and cutoff_time <= lags:
+            raise ValueError("cutoff_time must be greater than lags")
+
+        self.name = "PedalMe"
+        self._url = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/pedalme.json"
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: PedalMeDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_targets()
+        self._set_features()
+
+    def _set_total_timestamps(self: PedalMeDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                self._dataset["time_periods"],
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = self._dataset["time_periods"]
+
+    def _set_num_nodes(self: PedalMeDataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise ValueError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: PedalMeDataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: PedalMeDataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: PedalMeDataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        edges = self._dataset["edges"]
+        edge_weights = self._dataset["weights"]
+        comb_edge_list = [
+            (edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))
+        ]
+        comb_edge_list.sort(key=lambda x: (x[1], x[0]))
+        self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
+
+    def _set_targets(self: PedalMeDataLoader) -> None:
+        r"""Calculate and set the target attributes."""
+        targets = [
+            np.array(self._dataset[str(time)])
+            for time in range(self.gdata["total_timestamps"])
+        ]
+
+        stacked_target = np.stack(targets)
+
+        self._all_targets = np.array(
+            [
+                stacked_target[i + self._lags, :].T
+                for i in range(stacked_target.shape[0] - self._lags)
+            ],
+        )
+
+    def _set_features(self: PedalMeDataLoader) -> None:
+        pass
+
+    def get_edges(self: PedalMeDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: PedalMeDataLoader) -> np.ndarray:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_targets(self: PedalMeDataLoader) -> np.ndarray:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
diff --git a/stgraph/dataset/temporal/stgraph_temporal_dataset.py b/stgraph/dataset/temporal/stgraph_temporal_dataset.py
new file mode 100644
index 00000000..e7712461
--- /dev/null
+++ b/stgraph/dataset/temporal/stgraph_temporal_dataset.py
@@ -0,0 +1,35 @@
+"""Base class for all STGraph temporal graph datasets."""
+
+from __future__ import annotations
+
+from rich.console import Console
+
+from stgraph.dataset.stgraph_dataset import STGraphDataset
+
+console = Console()
+
+
+class STGraphTemporalDataset(STGraphDataset):
+    r"""Base class for temporal graph datasets.
+
+    This class is a subclass of ``STGraphDataset`` and provides the base structure for
+    handling temporal graph datasets.
+    """
+
+    def __init__(self: STGraphTemporalDataset) -> None:
+        r"""Provide the base structure for handling temporal graph datasets."""
+        super().__init__()
+
+        self._init_graph_data()
+
+    def _init_graph_data(self: STGraphTemporalDataset) -> dict:
+        r"""Initialize graph meta data for a temporal dataset.
+
+        The ``num_nodes``, ``num_edges``, ``total_timestamps`` keys are set to value 0
+        """
+        self.gdata["num_nodes"] = 0
+        self.gdata["num_edges"] = 0
+        self.gdata["total_timestamps"] = 0
+
+        self._lags = 0
+        self._cutoff_time = None
diff --git a/stgraph/dataset/temporal/wikimath_dataloader.py b/stgraph/dataset/temporal/wikimath_dataloader.py
new file mode 100644
index 00000000..3776b454
--- /dev/null
+++ b/stgraph/dataset/temporal/wikimath_dataloader.py
@@ -0,0 +1,193 @@
+r"""Vital mathematical articles sourced from Wikipedia."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class WikiMathDataLoader(STGraphTemporalDataset):
+    r"""Vital mathematical articles sourced from Wikipedia.
+
+    The graph dataset is static, with vertices representing Wikipedia pages and
+    edges representing links. The graph is both directed and weighted, where
+    the weights indicate the number of links originating from the source page
+    connecting to the target page. The target is the daily user visits to the
+    Wikipedia pages between March 16th 2019 and March 15th 2021 which results
+    in 731 periods.
+
+    This class provides functionality for loading, processing, and accessing
+    the Hungary Chickenpox dataset for use in deep learning tasks such as County
+    level case count prediction.
+
+    .. list-table:: gdata
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 1068
+          - 27079
+          - 731
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import WikiMathDataLoader
+
+        wiki = WikiMathDataLoader(verbose=True)
+        num_nodes = wiki.gdata["num_nodes"]
+        num_edges = wiki.gdata["num_edges"]
+        total_timestamps = wiki.gdata["total_timestamps"]
+
+        edge_list = wiki.get_edges()
+        edge_weights = wiki.get_edge_weights()
+        targets = wiki.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 8)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: WikiMathDataLoader,
+        verbose: bool = False,
+        lags: int = 8,
+        cutoff_time: int | None = None,
+        redownload: bool = False,
+    ) -> None:
+        r"""Vital mathematical articles sourced from Wikipedia."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+
+        self.name = "WikiMath"
+        self._url = "https://raw.githubusercontent.com/bfGraph/STGraph-Datasets/main/wikivital_mathematics.json"
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: WikiMathDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_targets()
+        self._set_features()
+
+    def _set_total_timestamps(self: WikiMathDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                self._dataset["time_periods"],
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = self._dataset["time_periods"]
+
+    def _set_num_nodes(self: WikiMathDataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise ValueError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: WikiMathDataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: WikiMathDataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: WikiMathDataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        edges = self._dataset["edges"]
+        edge_weights = self._dataset["weights"]
+        comb_edge_list = [
+            (edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))
+        ]
+        comb_edge_list.sort(key=lambda x: (x[1], x[0]))
+        self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
+
+    def _set_targets(self: WikiMathDataLoader) -> None:
+        r"""Calculate and set the target attributes."""
+        targets = [
+            np.array(self._dataset[str(time)]["y"])
+            for time in range(self.gdata["total_timestamps"])
+        ]
+
+        stacked_target = np.stack(targets)
+        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
+            np.std(stacked_target, axis=0) + 10**-10
+        )
+        self._all_targets = np.array(
+            [standardized_target[i, :].T for i in range(len(targets))],
+        )
+
+    def _set_features(self: WikiMathDataLoader) -> None:
+        pass
+
+    def get_edges(self: WikiMathDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: WikiMathDataLoader) -> np.ndarray:
+        r"""Return the edge weights."""
+        return self._edge_weights
+
+    def get_all_targets(self: WikiMathDataLoader) -> np.ndarray:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
diff --git a/stgraph/dataset/temporal/windmilloutput_dataloader.py b/stgraph/dataset/temporal/windmilloutput_dataloader.py
new file mode 100644
index 00000000..5221f306
--- /dev/null
+++ b/stgraph/dataset/temporal/windmilloutput_dataloader.py
@@ -0,0 +1,221 @@
+r"""Hourly energy output of windmills."""
+
+from __future__ import annotations
+
+import numpy as np
+
+from stgraph.dataset.temporal.stgraph_temporal_dataset import STGraphTemporalDataset
+
+
+class WindmillOutputDataLoader(STGraphTemporalDataset):
+    r"""Hourly energy output of windmills.
+
+    This class provides functionality for loading, processing, and accessing
+    the Windmill output dataset for use in deep learning such as
+    regression tasks.
+
+    .. list-table:: gdata for Windmill Output Small
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 11
+          - 121
+          - 17472
+
+    .. list-table:: gdata for Windmill Output Medium
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 26
+          - 676
+          - 17472
+
+    .. list-table:: gdata for Windmill Output Large
+        :widths: 33 33 33
+        :header-rows: 1
+
+        * - num_nodes
+          - num_edges
+          - total_timestamps
+        * - 319
+          - 101761
+          - 17472
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.dataset import WindmillOutputDataLoader
+
+        wind_small = WindmillOutputDataLoader(verbose=True, size="small")
+        num_nodes = wind_small.gdata["num_nodes"]
+        num_edges = wind_small.gdata["num_edges"]
+        total_timestamps = wind_small.gdata["total_timestamps"]
+
+        edge_list = wind_small.get_edges()
+        edge_weights = wind_small.get_edge_weights()
+        targets = wind_small.get_all_targets()
+
+    Parameters
+    ----------
+    verbose : bool, optional
+        Flag to control whether to display verbose info (default is False)
+    lags : int, optional
+        The number of time lags (default is 8)
+    cutoff_time : int, optional
+        The cutoff timestamp for the temporal dataset (default is None)
+    size : str, optional
+        The dataset size among large, medium and small (default is large)
+    redownload : bool, optional (default is False)
+        Redownload the dataset online and save to cache
+
+    Attributes
+    ----------
+    name : str
+        The name of the dataset.
+    gdata : dict
+        Graph meta data.
+
+    """
+
+    def __init__(
+        self: WindmillOutputDataLoader,
+        verbose: bool = False,
+        lags: int = 8,
+        cutoff_time: int | None = None,
+        size: str = "large",
+        redownload: bool = False,
+    ) -> None:
+        r"""Hourly energy output of windmills."""
+        super().__init__()
+
+        if not isinstance(lags, int):
+            raise TypeError("lags must be of type int")
+        if lags < 0:
+            raise ValueError("lags must be a positive integer")
+
+        if cutoff_time is not None and not isinstance(cutoff_time, int):
+            raise TypeError("cutoff_time must be of type int")
+        if cutoff_time is not None and cutoff_time < 0:
+            raise ValueError("cutoff_time must be a positive integer")
+
+        if not isinstance(size, str):
+            raise TypeError("size must be of type string")
+        if size not in ["large", "medium", "small"]:
+            raise ValueError(
+                "size must take either of the following values : "
+                "large, medium or small",
+            )
+
+        self.name = "WindMill_" + size
+        self._verbose = verbose
+        self._lags = lags
+        self._cutoff_time = cutoff_time
+        self._size = size
+
+        size_urls = {
+            "large": "https://graphmining.ai/temporal_datasets/windmill_output.json",
+            "medium": "https://graphmining.ai/temporal_datasets/windmill_output_medium.json",
+            "small": "https://graphmining.ai/temporal_datasets/windmill_output_small.json",
+        }
+
+        self._url = size_urls[self._size]
+
+        if redownload and self._has_dataset_cache():
+            self._delete_cached_dataset()
+
+        if self._has_dataset_cache():
+            self._load_dataset()
+        else:
+            self._download_dataset()
+            self._save_dataset()
+
+        self._process_dataset()
+
+    def _process_dataset(self: WindmillOutputDataLoader) -> None:
+        self._set_total_timestamps()
+        self._set_num_nodes()
+        self._set_num_edges()
+        self._set_edges()
+        self._set_edge_weights()
+        self._set_targets()
+
+    def _set_total_timestamps(self: WindmillOutputDataLoader) -> None:
+        r"""Set the total timestamps present in the dataset.
+
+        It sets the total timestamps present in the dataset into the
+        gdata attribute dictionary. It is the minimum of the cutoff time
+        choosen by the user and the total time periods present in the
+        original dataset.
+        """
+        if self._cutoff_time is not None:
+            self.gdata["total_timestamps"] = min(
+                self._dataset["time_periods"],
+                self._cutoff_time,
+            )
+        else:
+            self.gdata["total_timestamps"] = self._dataset["time_periods"]
+
+    def _set_num_nodes(self: WindmillOutputDataLoader) -> None:
+        r"""Set the total number of nodes present in the dataset."""
+        node_set = set()
+        max_node_id = 0
+        for edge in self._dataset["edges"]:
+            node_set.add(edge[0])
+            node_set.add(edge[1])
+            max_node_id = max(max_node_id, edge[0], edge[1])
+
+        if max_node_id != len(node_set) - 1:
+            raise ValueError("Node ID labelling is not continuous")
+
+        self.gdata["num_nodes"] = len(node_set)
+
+    def _set_num_edges(self: WindmillOutputDataLoader) -> None:
+        r"""Set the total number of edges present in the dataset."""
+        self.gdata["num_edges"] = len(self._dataset["edges"])
+
+    def _set_edges(self: WindmillOutputDataLoader) -> None:
+        r"""Set the edge list of the dataset."""
+        self._edge_list = [(edge[0], edge[1]) for edge in self._dataset["edges"]]
+
+    def _set_edge_weights(self: WindmillOutputDataLoader) -> None:
+        r"""Set the edge weights of the dataset."""
+        edges = self._dataset["edges"]
+        edge_weights = self._dataset["weights"]
+        comb_edge_list = [
+            (edges[i][0], edges[i][1], edge_weights[i]) for i in range(len(edges))
+        ]
+        comb_edge_list.sort(key=lambda x: (x[1], x[0]))
+        self._edge_weights = np.array([edge_det[2] for edge_det in comb_edge_list])
+
+    def _set_targets(self: WindmillOutputDataLoader) -> None:
+        r"""Calculate and sets the target attributes."""
+        stacked_target = np.stack(self._dataset["block"])
+        standardized_target = (stacked_target - np.mean(stacked_target, axis=0)) / (
+            np.std(stacked_target, axis=0) + 10**-10
+        )
+        self._all_targets = [
+            standardized_target[i, :].T for i in range(self.gdata["total_timestamps"])
+        ]
+
+    def _set_features(self: WindmillOutputDataLoader) -> None:
+        pass
+
+    def get_edges(self: WindmillOutputDataLoader) -> list:
+        r"""Return the edge list."""
+        return self._edge_list
+
+    def get_edge_weights(self: WindmillOutputDataLoader) -> np.ndarray:
+        r"""Return the edge weight."""
+        return self._edge_weights
+
+    def get_all_targets(self: WindmillOutputDataLoader) -> list:
+        r"""Return the targets for each timestamp."""
+        return self._all_targets
diff --git a/stgraph/graph/STGraphBase.py b/stgraph/graph/STGraphBase.py
deleted file mode 100644
index 3ad967f6..00000000
--- a/stgraph/graph/STGraphBase.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from abc import ABC, abstractmethod
-
-class STGraphBase(ABC):
-    def __init__(self):
-        self._ndata = {}
-        
-        self._forward_graph = None
-        self._backward_graph = None
-        
-        self.fwd_row_offset_ptr = None
-        self.fwd_column_indices_ptr = None
-        self.fwd_eids_ptr = None
-        self.fwd_node_ids_ptr = None
-        
-        self.bwd_row_offset_ptr = None
-        self.bwd_column_indices_ptr = None
-        self.bwd_eids_ptr = None
-        self.bwd_node_ids_ptr = None
-    
-    @abstractmethod
-    def _get_graph_csr_ptrs(self):
-        pass
-    
-    @abstractmethod
-    def get_num_nodes(self):
-        pass
-    
-    @abstractmethod
-    def get_num_edges(self):
-        pass
-
-    @abstractmethod
-    def get_ndata(self, field):
-        pass
-
-    @abstractmethod
-    def set_ndata(self, field, val):
-        pass
-    
-    @property
-    @abstractmethod
-    def graph_type(self):
-        pass
\ No newline at end of file
diff --git a/stgraph/graph/__init__.py b/stgraph/graph/__init__.py
index a5aa46c6..1b4b46c5 100644
--- a/stgraph/graph/__init__.py
+++ b/stgraph/graph/__init__.py
@@ -1,1 +1,8 @@
-'''Graph Abstraction provided by STGraph'''
\ No newline at end of file
+"""Graph representation modules provided by STGraph."""
+
+from stgraph.graph.dynamic.dynamic_graph import DynamicGraph
+from stgraph.graph.dynamic.gpma.gpma_graph import GPMAGraph
+from stgraph.graph.dynamic.naive.naive_graph import NaiveGraph
+from stgraph.graph.dynamic.pcsr.pcsr_graph import PCSRGraph
+from stgraph.graph.static.static_graph import StaticGraph
+from stgraph.graph.stgraph_base import STGraphBase
diff --git a/stgraph/graph/dynamic/__init__.py b/stgraph/graph/dynamic/__init__.py
index e69de29b..fff87265 100644
--- a/stgraph/graph/dynamic/__init__.py
+++ b/stgraph/graph/dynamic/__init__.py
@@ -0,0 +1,1 @@
+"""Dynamic Graph respresentation modules provided by STGraph."""
diff --git a/stgraph/graph/dynamic/DynamicGraph.py b/stgraph/graph/dynamic/dynamic_graph.py
similarity index 57%
rename from stgraph/graph/dynamic/DynamicGraph.py
rename to stgraph/graph/dynamic/dynamic_graph.py
index 030d4c8c..6130a53a 100644
--- a/stgraph/graph/dynamic/DynamicGraph.py
+++ b/stgraph/graph/dynamic/dynamic_graph.py
@@ -1,13 +1,46 @@
-from stgraph.graph.STGraphBase import STGraphBase
-from abc import abstractmethod
+"""Represent Dynamic Graphs in STGraph."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    import numpy as np
+
 import time
+from abc import abstractmethod
+
+from stgraph.graph.stgraph_base import STGraphBase
+
 
 class DynamicGraph(STGraphBase):
-    def __init__(self, edge_list, max_num_nodes):
+    r"""Represent Dynamic Graphs in STGraph.
+
+    This abstract class outlines the interface for defining a dynamic graph
+    used in STGraph. As of now the dynamic graph is implemented using the
+    following graph representation format:
+
+    1. Compressed Sparse Row (CSR)
+    2. Packed Compressed Sparse Row (PCSR)
+    3. GPMA
+
+    Please note that this documentation is still work in progress.
+
+    """
+
+    def __init__(
+        self: DynamicGraph,
+        edge_list: list,
+        max_num_nodes: int,
+    ) -> None:
+        r"""Represent Dynamic Graphs in STGraph."""
         super().__init__()
         self.graph_updates = {}
         self.max_num_nodes = max_num_nodes
-        self.graph_attr = {str(t): (self.max_num_nodes, len(set(edge_list[t]))) for t in range(len(edge_list))}
+        self.graph_attr = {
+            str(t): (self.max_num_nodes, len(set(edge_list[t])))
+            for t in range(len(edge_list))
+        }
 
         # Indicates whether the graph is currently undergoing backprop
         self._is_backprop_state = False
@@ -20,7 +53,8 @@ def __init__(self, edge_list, max_num_nodes):
 
         self._preprocess_graph_structure(edge_list)
 
-    def _preprocess_graph_structure(self, edge_list):
+    def _preprocess_graph_structure(self: DynamicGraph, edge_list: list) -> None:
+        r"""TODO:."""
         edge_dict = {}
         for i in range(len(edge_list)):
             edge_set = set()
@@ -43,8 +77,9 @@ def _preprocess_graph_structure(self, edge_list):
                 "add": additions,
                 "delete": deletions,
             }
-    
-    def reset_graph(self):
+
+    def reset_graph(self: DynamicGraph) -> None:
+        r"""TODO:."""
         self._get_cached_graph("base")
         self.current_timestamp = 0
 
@@ -52,26 +87,28 @@ def reset_graph(self):
         self.get_bwd_graph_time = 0
         self.move_to_gpu_time = 0
 
-    def get_graph(self, timestamp: int):
+    def get_graph(self: DynamicGraph, timestamp: int) -> None:
+        r"""TODO:."""
         t0 = time.time()
 
         self._is_backprop_state = False
 
         if timestamp < self.current_timestamp:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()",
             )
-        
+
         if self._get_cached_graph(timestamp - 1):
             self.current_timestamp = timestamp - 1
 
         while self.current_timestamp < timestamp:
             self._update_graph_forward()
             self.current_timestamp += 1
-        
+
         self.get_fwd_graph_time += time.time() - t0
 
-    def get_backward_graph(self, timestamp: int):
+    def get_backward_graph(self: DynamicGraph, timestamp: int) -> None:
+        r"""TODO:."""
         t0 = time.time()
 
         if not self._is_backprop_state:
@@ -80,58 +117,72 @@ def get_backward_graph(self, timestamp: int):
             self._init_reverse_graph()
 
         if timestamp > self.current_timestamp:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()",
             )
 
         while self.current_timestamp > timestamp:
             self._update_graph_backward()
             self.current_timestamp -= 1
-        
+
         self.get_bwd_graph_time += time.time() - t0
 
-    def get_num_nodes(self):
+    def get_num_nodes(self: DynamicGraph) -> int:
+        r"""TODO:."""
         return self.graph_attr[str(self.current_timestamp)][0]
 
-    def get_num_edges(self):
+    def get_num_edges(self: DynamicGraph) -> int:
+        r"""TODO:."""
         return self.graph_attr[str(self.current_timestamp)][1]
-    
-    def get_ndata(self, field):
-        if str(self.current_timestamp) in self._ndata and field in self._ndata[str(self.current_timestamp)]:
+
+    def get_ndata(self: DynamicGraph, field: str) -> any:
+        r"""TODO:."""
+        if (
+            str(self.current_timestamp) in self._ndata
+            and field in self._ndata[str(self.current_timestamp)]
+        ):
             return self._ndata[str(self.current_timestamp)][field]
-        else:
-            return None
 
-    def set_ndata(self, field, val):
+        return None
+
+    def set_ndata(self: DynamicGraph, field: str, val: any) -> None:
+        r"""TODO:."""
         if str(self.current_timestamp) in self._ndata:
             self._ndata[str(self.current_timestamp)][field] = val
         else:
             self._ndata[str(self.current_timestamp)] = {field: val}
 
     @abstractmethod
-    def in_degrees(self):
+    def in_degrees(self: DynamicGraph) -> np.ndarray:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def out_degrees(self):
+    def out_degrees(self: DynamicGraph) -> np.ndarray:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def _cache_graph(self):
+    def _cache_graph(self: DynamicGraph) -> None:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def _get_cached_graph(self, timestamp):
+    def _get_cached_graph(self: DynamicGraph, timestamp: str | int) -> bool:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def _update_graph_forward(self):
+    def _update_graph_forward(self: DynamicGraph) -> None:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def _init_reverse_graph(self):
+    def _init_reverse_graph(self: DynamicGraph) -> None:
+        r"""TODO:."""
         pass
 
     @abstractmethod
-    def _update_graph_backward(self):
+    def _update_graph_backward(self: DynamicGraph) -> None:
+        r"""TODO:."""
         pass
diff --git a/stgraph/graph/dynamic/gpma/__init__.py b/stgraph/graph/dynamic/gpma/__init__.py
index 741218bf..f23e4442 100644
--- a/stgraph/graph/dynamic/gpma/__init__.py
+++ b/stgraph/graph/dynamic/gpma/__init__.py
@@ -1,1 +1,1 @@
-'''GPMA data structure to represent Dynamic Graphs in GPU'''
\ No newline at end of file
+"""Represent Dynamic Graphs using GPMA in STGraph."""
diff --git a/stgraph/graph/dynamic/gpma/GPMAGraph.py b/stgraph/graph/dynamic/gpma/gpma_graph.py
similarity index 54%
rename from stgraph/graph/dynamic/gpma/GPMAGraph.py
rename to stgraph/graph/dynamic/gpma/gpma_graph.py
index 69ee4c7e..abaaa7fc 100644
--- a/stgraph/graph/dynamic/gpma/GPMAGraph.py
+++ b/stgraph/graph/dynamic/gpma/gpma_graph.py
@@ -1,26 +1,60 @@
+"""Represent Dynamic Graphs using GPMA in STGraph."""
+
+from __future__ import annotations
+
 import copy
-import time
 
 import numpy as np
-from rich import inspect
 
-from stgraph.graph.dynamic.DynamicGraph import DynamicGraph
+from stgraph.graph.dynamic.dynamic_graph import DynamicGraph
 from stgraph.graph.dynamic.gpma.gpma import (
     GPMA,
-    init_gpma,
-    init_graph_updates,
-    edge_update_t,
-    label_edges,
     build_backward_csr,
+    edge_update_t,
     free_backward_csr,
     get_csr_ptrs,
+    get_in_degrees,
     get_out_degrees,
-    get_in_degrees
+    init_gpma,
+    init_graph_updates,
+    label_edges,
 )
 
 
 class GPMAGraph(DynamicGraph):
-    def __init__(self, edge_list, max_num_nodes):
+    r"""Represent Dynamic Graphs using GPMA in STGraph.
+
+    TODO: Add a paragraph explaining about GPMA in brief.
+
+    Example:
+    --------
+    .. code-block:: python
+
+        from stgraph.graph import GPMAGraph
+        from stgraph.dataset import EnglandCovidDataLoader
+
+        eng_covid = EnglandCovidDataLoader()
+
+        G = GPMAGraph(
+            edge_list = eng_covid.get_edges(),
+            max_num_nodes = max(eng_covid.gdata["num_nodes"]),
+        )
+
+    Parameters
+    ----------
+    edge_list : list
+        Edge list of the graph across all timestamps
+    max_num_nodes : int
+        Maximum number of nodes present in the graph across all timestamps
+
+    Attributes
+    ----------
+    TODO:.
+
+    """
+
+    def __init__(self: GPMAGraph, edge_list: list, max_num_nodes: int) -> None:
+        r"""Represent Dynamic Graphs using GPMA in STGraph."""
         super().__init__(edge_list, max_num_nodes)
 
         # forward graph for GPMA
@@ -37,34 +71,41 @@ def __init__(self, edge_list, max_num_nodes):
         self.graph_cache = {}
         self.graph_cache["base"] = copy.deepcopy(self._forward_graph)
 
-    def graph_type(self):
+    def graph_type(self: GPMAGraph) -> str:
+        r"""Return the graph type."""
         return "gpma"
 
-    def _cache_graph(self):
-        self.graph_cache[str(self.current_timestamp)] = copy.deepcopy(self._forward_graph)
+    def _cache_graph(self: GPMAGraph) -> None:
+        r"""TODO:."""
+        self.graph_cache[str(self.current_timestamp)] = copy.deepcopy(
+            self._forward_graph,
+        )
 
-    def _get_cached_graph(self, timestamp):
+    def _get_cached_graph(self: GPMAGraph, timestamp: int | str) -> bool:
+        r"""TODO:."""
         if timestamp == "base":
             self._forward_graph = copy.deepcopy(self.graph_cache["base"])
             self._get_graph_csr_ptrs()
             return True
-        else:
-            if str(timestamp) in self.graph_cache:
-                self._forward_graph = self.graph_cache[str(timestamp)]
-                del self.graph_cache[str(timestamp)]
-                self._get_graph_csr_ptrs()
-                return True
-            else:
-                return False
-    
-    def in_degrees(self):
+
+        if str(timestamp) in self.graph_cache:
+            self._forward_graph = self.graph_cache[str(timestamp)]
+            del self.graph_cache[str(timestamp)]
+            self._get_graph_csr_ptrs()
+            return True
+
+        return False
+
+    def in_degrees(self: GPMAGraph) -> np.ndarray:
+        r"""TODO:."""
         return np.array(get_out_degrees(self._forward_graph), dtype="int32")
-    
-    def out_degrees(self):
-        return np.array(get_in_degrees(self._forward_graph), dtype="int32")
 
-    def _get_graph_csr_ptrs(self):
+    def out_degrees(self: GPMAGraph) -> np.ndarray:
+        r"""TODO:."""
+        return np.array(get_in_degrees(self._forward_graph), dtype="int32")
 
+    def _get_graph_csr_ptrs(self: GPMAGraph) -> None:
+        r"""TODO:."""
         forward_csr_ptrs = get_csr_ptrs(self._forward_graph)
         self.fwd_row_offset_ptr = forward_csr_ptrs[0]
         self.fwd_column_indices_ptr = forward_csr_ptrs[1]
@@ -78,34 +119,34 @@ def _get_graph_csr_ptrs(self):
             self.bwd_eids_ptr = backward_csr_ptrs[2]
             self.bwd_node_ids_ptr = backward_csr_ptrs[3]
 
-    def _update_graph_forward(self):
+    def _update_graph_forward(self: GPMAGraph) -> None:
+        r"""TODO:."""
         # if we went through the entire time-stamps
         if str(self.current_timestamp + 1) not in self.graph_updates:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()",
             )
 
         edge_update_t(self._forward_graph, self.current_timestamp + 1)
         label_edges(self._forward_graph)
         self._get_graph_csr_ptrs()
 
-    def _init_reverse_graph(self):
-        """Generates the reverse of the base graph"""
+    def _init_reverse_graph(self: GPMAGraph) -> None:
+        r"""Generate the reverse of the base graph."""
         free_backward_csr(self._forward_graph)
         build_backward_csr(self._forward_graph)
         self._get_graph_csr_ptrs()
 
-    def _update_graph_backward(self):
+    def _update_graph_backward(self: GPMAGraph) -> None:
+        r"""TODO:."""
         if self.current_timestamp < 0:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()",
             )
 
         # Freeing resources from previous CSR
         free_backward_csr(self._forward_graph)
-        edge_update_t(
-            self._forward_graph, self.current_timestamp, revert_update=True
-        )
+        edge_update_t(self._forward_graph, self.current_timestamp, revert_update=True)
         label_edges(self._forward_graph)
         build_backward_csr(self._forward_graph)
         self._get_graph_csr_ptrs()
diff --git a/stgraph/graph/dynamic/naive/__init__.py b/stgraph/graph/dynamic/naive/__init__.py
index e69de29b..ceeb7cd2 100644
--- a/stgraph/graph/dynamic/naive/__init__.py
+++ b/stgraph/graph/dynamic/naive/__init__.py
@@ -0,0 +1,1 @@
+"""Represent Dynamic Graphs using CSR in STGraph."""
diff --git a/stgraph/graph/dynamic/naive/NaiveGraph.py b/stgraph/graph/dynamic/naive/naive_graph.py
similarity index 55%
rename from stgraph/graph/dynamic/naive/NaiveGraph.py
rename to stgraph/graph/dynamic/naive/naive_graph.py
index 2190515b..7fe1cdd0 100644
--- a/stgraph/graph/dynamic/naive/NaiveGraph.py
+++ b/stgraph/graph/dynamic/naive/naive_graph.py
@@ -1,17 +1,51 @@
+"""Represent Dynamic Graphs using CSR in STGraph."""
+
+from __future__ import annotations
+
 import copy
+
 import numpy as np
-from rich import inspect
 
-from stgraph.graph.dynamic.DynamicGraph import DynamicGraph
+from stgraph.graph.dynamic.dynamic_graph import DynamicGraph
 from stgraph.graph.static.csr import CSR
-from collections import deque
-import time
 
 
 class NaiveGraph(DynamicGraph):
-    def __init__(self, edge_list, max_num_nodes):
+    r"""Represent Dynamic Graphs using CSR in STGraph.
+
+    TODO: Add a paragraph explaining about GPMA in brief.
+
+    Example:
+    --------
+    .. code-block:: python
+
+        from stgraph.graph import NaiveGraph
+        from stgraph.dataset import EnglandCovidDataLoader
+
+        eng_covid = EnglandCovidDataLoader()
+
+        G = NaiveGraph(
+            edge_list = eng_covid.get_edges(),
+            max_num_nodes = max(eng_covid.gdata["num_nodes"]),
+        )
+
+    Parameters
+    ----------
+    edge_list : list
+        Edge list of the graph across all timestamps
+    max_num_nodes : int
+        Maximum number of nodes present in the graph across all timestamps
+
+    Attributes
+    ----------
+    TODO:.
+
+    """
+
+    def __init__(self: NaiveGraph, edge_list: list, max_num_nodes: int) -> None:
+        r"""Represent Dynamic Graphs using CSR in STGraph."""
         super().__init__(edge_list, max_num_nodes)
-        # inspect(edge_list)
+
         self._prepare_edge_lst_fwd(edge_list)
         self._prepare_edge_lst_bwd(self.fwd_edge_list)
 
@@ -39,7 +73,8 @@ def __init__(self, edge_list, max_num_nodes):
 
         self._get_graph_csr_ptrs(0)
 
-    def _prepare_edge_lst_fwd(self, edge_list):
+    def _prepare_edge_lst_fwd(self: NaiveGraph, edge_list: list) -> None:
+        r"""TODO:."""
         self.fwd_edge_list = []
         for i in range(len(edge_list)):
             edge_list_for_t = edge_list[i]
@@ -50,32 +85,38 @@ def _prepare_edge_lst_fwd(self, edge_list):
             ]
             self.fwd_edge_list.append(edge_list_for_t)
 
-    def _prepare_edge_lst_bwd(self, edge_list):
+    def _prepare_edge_lst_bwd(self: NaiveGraph, edge_list: list) -> None:
+        r"""TODO:."""
         self.bwd_edge_list = []
         for i in range(len(edge_list)):
             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_type(self):
+    def graph_type(self: NaiveGraph) -> str:
+        r"""Return the graph type."""
         return "csr"
-    
-    def _cache_graph(self):
+
+    def _cache_graph(self: NaiveGraph) -> None:
         pass
 
-    def _get_cached_graph(self, timestamp):
+    def _get_cached_graph(self: NaiveGraph) -> None:
         return False
 
-    def in_degrees(self):
-        return np.array(self._forward_graph[self.current_timestamp].out_degrees, dtype="int32")
-    
-    def out_degrees(self):
-        return np.array(self._forward_graph[self.current_timestamp].in_degrees, dtype="int32")
-    
-    # def weighted_in_degrees(self):
-    #     return np.array(self._forward_graph[self.current_timestamp].weighted_out_degrees, dtype="float32")
+    def in_degrees(self: NaiveGraph) -> np.ndarray:
+        r"""TODO:."""
+        return np.array(
+            self._forward_graph[self.current_timestamp].out_degrees, dtype="int32",
+        )
+
+    def out_degrees(self: NaiveGraph) -> np.ndarray:
+        r"""TODO:."""
+        return np.array(
+            self._forward_graph[self.current_timestamp].in_degrees, dtype="int32",
+        )
 
-    def _get_graph_csr_ptrs(self, timestamp):
+    def _get_graph_csr_ptrs(self: NaiveGraph, timestamp: int) -> None:
+        r"""TODO:."""
         if self._is_backprop_state:
             bwd_csr_ptrs = self._backward_graph[timestamp]
             self.bwd_row_offset_ptr = bwd_csr_ptrs.row_offset_ptr
@@ -89,21 +130,22 @@ def _get_graph_csr_ptrs(self, timestamp):
             self.fwd_eids_ptr = fwd_csr_ptrs.eids_ptr
             self.fwd_node_ids_ptr = fwd_csr_ptrs.node_ids_ptr
 
-    def _update_graph_forward(self):
-        """Updates the current base graph to the next timestamp"""
+    def _update_graph_forward(self: NaiveGraph) -> None:
+        """Update the current base graph to the next timestamp."""
         if str(self.current_timestamp + 1) not in self.graph_updates:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_forward()",
             )
         self._get_graph_csr_ptrs(self.current_timestamp + 1)
 
-    def _init_reverse_graph(self):
-        """Generates the reverse of the base graph"""
+    def _init_reverse_graph(self: NaiveGraph) -> None:
+        """Generate the reverse of the base graph."""
         self._get_graph_csr_ptrs(self.current_timestamp)
 
-    def _update_graph_backward(self):
+    def _update_graph_backward(self: NaiveGraph) -> None:
+        r"""TODO:."""
         if self.current_timestamp < 0:
-            raise Exception(
-                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()"
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()",
             )
         self._get_graph_csr_ptrs(self.current_timestamp - 1)
diff --git a/stgraph/graph/dynamic/pcsr/PCSRGraph.py b/stgraph/graph/dynamic/pcsr/PCSRGraph.py
deleted file mode 100644
index 7c39aa73..00000000
--- a/stgraph/graph/dynamic/pcsr/PCSRGraph.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import copy
-import numpy as np
-from rich import inspect
-import time
-
-from stgraph.graph.dynamic.DynamicGraph import DynamicGraph
-from stgraph.graph.dynamic.pcsr.pcsr import PCSR
-
-class PCSRGraph(DynamicGraph):
-    def __init__(self, edge_list, max_num_nodes):
-        super().__init__(edge_list, max_num_nodes)
-        
-        # Get the maximum number of edges
-        self._get_max_num_edges()
-        
-        self._forward_graph = PCSR(self.max_num_nodes, self.max_num_edges)
-        self._forward_graph.edge_update_list(self.graph_updates["0"]["add"], is_reverse_edge=True)
-        self._forward_graph.label_edges()   
-        self._forward_graph.build_csr()
-        self._get_graph_csr_ptrs()
-
-        self.graph_cache = {}
-        self.graph_cache["base"] = copy.deepcopy(self._forward_graph)
-    
-    def _get_max_num_edges(self):
-        updates = self.graph_updates
-        edge_set = set()
-        for i in range(len(updates)):
-          for j in range(len(updates[str(i)]["add"])):
-            edge_set.add(updates[str(i)]["add"][j])
-        self.max_num_edges = len(edge_set)
-                
-    def graph_type(self):
-        return "pcsr"
-    
-    def _cache_graph(self):
-        self.graph_cache[str(self.current_timestamp)] = copy.deepcopy(self._forward_graph)
-
-    def _get_cached_graph(self, timestamp):
-        if timestamp == "base":
-            self._forward_graph = copy.deepcopy(self.graph_cache["base"])
-            self._get_graph_csr_ptrs()
-            return True
-        else:
-            if str(timestamp) in self.graph_cache:
-                self._forward_graph = self.graph_cache[str(timestamp)]
-                del self.graph_cache[str(timestamp)]
-                self._get_graph_csr_ptrs()
-                return True
-            else:
-                return False
-        
-    def in_degrees(self):
-        return np.array(self._forward_graph.out_degrees, dtype='int32')
-    
-    def out_degrees(self):
-        return np.array(self._forward_graph.in_degrees, dtype='int32')
-    
-    def _get_graph_csr_ptrs(self): 
-        csr_ptrs = self._forward_graph.get_csr_ptrs()
-        if self._is_backprop_state:
-            self.bwd_row_offset_ptr = csr_ptrs[0]
-            self.bwd_column_indices_ptr = csr_ptrs[1]
-            self.bwd_eids_ptr = csr_ptrs[2]
-            self.bwd_node_ids_ptr = csr_ptrs[3]
-        else:
-            self.fwd_row_offset_ptr = csr_ptrs[0]
-            self.fwd_column_indices_ptr = csr_ptrs[1]
-            self.fwd_eids_ptr = csr_ptrs[2]
-            self.fwd_node_ids_ptr = csr_ptrs[3]
-    
-    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 STGraphBase.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"]
-
-        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()   
-        move_to_gpu_time = self._forward_graph.build_csr()
-        self.move_to_gpu_time += move_to_gpu_time
-        self._get_graph_csr_ptrs()
-        
-    def _init_reverse_graph(self):
-        ''' Generates the reverse of the base graph'''
-        move_to_gpu_time = self._forward_graph.build_reverse_csr()
-        self.move_to_gpu_time += move_to_gpu_time
-        self._get_graph_csr_ptrs()
-
-    def _update_graph_backward(self):
-        if self.current_timestamp < 0:
-            raise Exception("⏰ Invalid timestamp during STGraphBase.update_graph_backward()")
-        
-        graph_additions = self.graph_updates[str(self.current_timestamp)]["delete"]
-        graph_deletions = self.graph_updates[str(self.current_timestamp)]["add"]
-
-        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()
-        move_to_gpu_time = self._forward_graph.build_reverse_csr()
-        self.move_to_gpu_time += move_to_gpu_time
-        self._get_graph_csr_ptrs()
-        
\ No newline at end of file
diff --git a/stgraph/graph/dynamic/pcsr/__init__.py b/stgraph/graph/dynamic/pcsr/__init__.py
index e69de29b..3aa80822 100644
--- a/stgraph/graph/dynamic/pcsr/__init__.py
+++ b/stgraph/graph/dynamic/pcsr/__init__.py
@@ -0,0 +1,1 @@
+"""Represent Dynamic Graphs using PCSR in STGraph."""
diff --git a/stgraph/graph/dynamic/pcsr/pcsr_graph.py b/stgraph/graph/dynamic/pcsr/pcsr_graph.py
new file mode 100644
index 00000000..3f6facff
--- /dev/null
+++ b/stgraph/graph/dynamic/pcsr/pcsr_graph.py
@@ -0,0 +1,166 @@
+"""Represent Dynamic Graphs using PCSR in STGraph."""
+
+from __future__ import annotations
+
+import copy
+
+import numpy as np
+
+from stgraph.graph.dynamic.dynamic_graph import DynamicGraph
+from stgraph.graph.dynamic.pcsr.pcsr import PCSR
+
+
+class PCSRGraph(DynamicGraph):
+    r"""Represent Dynamic Graphs using PCSR in STGraph.
+
+    TODO: Add a paragraph explaining about PCSR in brief.
+
+    Example:
+    --------
+    .. code-block:: python
+
+        from stgraph.graph import PCSRGraph
+        from stgraph.dataset import EnglandCovidDataLoader
+
+        eng_covid = EnglandCovidDataLoader()
+
+        G = PCSRGraph(
+            edge_list = eng_covid.get_edges(),
+            max_num_nodes = max(eng_covid.gdata["num_nodes"]),
+        )
+
+    Parameters
+    ----------
+    edge_list : list
+        Edge list of the graph across all timestamps
+    max_num_nodes : int
+        Maximum number of nodes present in the graph across all timestamps
+
+    Attributes
+    ----------
+    TODO:.
+
+    """
+
+    def __init__(self: PCSRGraph, edge_list: list, max_num_nodes: int) -> None:
+        r"""Represent Dynamic Graphs using PCSR in STGraph."""
+        super().__init__(edge_list, max_num_nodes)
+
+        # Get the maximum number of edges
+        self._get_max_num_edges()
+
+        self._forward_graph = PCSR(self.max_num_nodes, self.max_num_edges)
+        self._forward_graph.edge_update_list(
+            self.graph_updates["0"]["add"],
+            is_reverse_edge=True,
+        )
+        self._forward_graph.label_edges()
+        self._forward_graph.build_csr()
+        self._get_graph_csr_ptrs()
+
+        self.graph_cache = {}
+        self.graph_cache["base"] = copy.deepcopy(self._forward_graph)
+
+    def _get_max_num_edges(self: PCSRGraph) -> None:
+        r"""TODO:."""
+        updates = self.graph_updates
+        edge_set = set()
+        for i in range(len(updates)):
+            for j in range(len(updates[str(i)]["add"])):
+                edge_set.add(updates[str(i)]["add"][j])
+        self.max_num_edges = len(edge_set)
+
+    def graph_type(self: PCSRGraph) -> str:
+        r"""Return the graph type."""
+        return "pcsr"
+
+    def _cache_graph(self: PCSRGraph) -> None:
+        r"""TODO:."""
+        self.graph_cache[str(self.current_timestamp)] = copy.deepcopy(
+            self._forward_graph,
+        )
+
+    def _get_cached_graph(self: PCSRGraph, timestamp: int | str) -> bool:
+        r"""TODO:."""
+        if timestamp == "base":
+            self._forward_graph = copy.deepcopy(self.graph_cache["base"])
+            self._get_graph_csr_ptrs()
+            return True
+
+        if str(timestamp) in self.graph_cache:
+            self._forward_graph = self.graph_cache[str(timestamp)]
+            del self.graph_cache[str(timestamp)]
+            self._get_graph_csr_ptrs()
+            return True
+
+        return False
+
+    def in_degrees(self: PCSRGraph) -> np.ndarray:
+        r"""TODO:."""
+        return np.array(self._forward_graph.out_degrees, dtype="int32")
+
+    def out_degrees(self: PCSRGraph) -> np.ndarray:
+        r"""TODO:."""
+        return np.array(self._forward_graph.in_degrees, dtype="int32")
+
+    def _get_graph_csr_ptrs(self: PCSRGraph) -> None:
+        r"""TODO:."""
+        csr_ptrs = self._forward_graph.get_csr_ptrs()
+        if self._is_backprop_state:
+            self.bwd_row_offset_ptr = csr_ptrs[0]
+            self.bwd_column_indices_ptr = csr_ptrs[1]
+            self.bwd_eids_ptr = csr_ptrs[2]
+            self.bwd_node_ids_ptr = csr_ptrs[3]
+        else:
+            self.fwd_row_offset_ptr = csr_ptrs[0]
+            self.fwd_column_indices_ptr = csr_ptrs[1]
+            self.fwd_eids_ptr = csr_ptrs[2]
+            self.fwd_node_ids_ptr = csr_ptrs[3]
+
+    def _update_graph_forward(self: PCSRGraph) -> None:
+        r"""Update the current base graph to the next timestamp."""
+        if str(self.current_timestamp + 1) not in self.graph_updates:
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.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"]
+
+        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()
+        move_to_gpu_time = self._forward_graph.build_csr()
+        self.move_to_gpu_time += move_to_gpu_time
+        self._get_graph_csr_ptrs()
+
+    def _init_reverse_graph(self: PCSRGraph) -> None:
+        """Generate the reverse of the base graph."""
+        move_to_gpu_time = self._forward_graph.build_reverse_csr()
+        self.move_to_gpu_time += move_to_gpu_time
+        self._get_graph_csr_ptrs()
+
+    def _update_graph_backward(self: PCSRGraph) -> None:
+        r"""TODO:."""
+        if self.current_timestamp < 0:
+            raise RuntimeError(
+                "⏰ Invalid timestamp during STGraphBase.update_graph_backward()",
+            )
+
+        graph_additions = self.graph_updates[str(self.current_timestamp)]["delete"]
+        graph_deletions = self.graph_updates[str(self.current_timestamp)]["add"]
+
+        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()
+        move_to_gpu_time = self._forward_graph.build_reverse_csr()
+        self.move_to_gpu_time += move_to_gpu_time
+        self._get_graph_csr_ptrs()
diff --git a/stgraph/graph/static/StaticGraph.py b/stgraph/graph/static/StaticGraph.py
deleted file mode 100644
index 74ed7348..00000000
--- a/stgraph/graph/static/StaticGraph.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from abc import ABC, abstractmethod
-import copy
-
-import numpy as np
-
-from rich.console import Console
-
-console = Console()
-
-from stgraph.graph.STGraphBase import STGraphBase
-
-
-from stgraph.graph.static.csr import CSR
-
-class StaticGraph(STGraphBase):
-    def __init__(self, edge_list, edge_weights, num_nodes):    
-        super().__init__()
-        self._num_nodes = num_nodes
-        self._num_edges = len(set(edge_list))
-        
-        # console.log("Building forward edge list")
-        self._prepare_edge_lst_fwd(edge_list)
-        # console.log("Creating forward graph")
-        self._forward_graph = CSR(self.fwd_edge_list, edge_weights, self._num_nodes, is_edge_reverse=True)
-        
-        # console.log("Building backward edge list")
-        self._prepare_edge_lst_bwd(self.fwd_edge_list)
-        # console.log("Creating backward graph")
-        self._backward_graph = CSR(self.bwd_edge_list, edge_weights, self._num_nodes)
-        
-        # console.log("Getting CSR ptrs")
-        self._get_graph_csr_ptrs()
-    
-    def _prepare_edge_lst_fwd(self, edge_list):    
-        edge_list_for_t = edge_list
-        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))]
-        self.fwd_edge_list = edge_list_for_t
-    
-    def _prepare_edge_lst_bwd(self, edge_list):    
-        edge_list_for_t = copy.deepcopy(edge_list)
-        edge_list_for_t.sort()
-        self.bwd_edge_list = edge_list_for_t
-        
-    def _get_graph_csr_ptrs(self):
-        self.fwd_row_offset_ptr = self._forward_graph.row_offset_ptr
-        self.fwd_column_indices_ptr = self._forward_graph.column_indices_ptr
-        self.fwd_eids_ptr = self._forward_graph.eids_ptr
-        self.fwd_node_ids_ptr = self._forward_graph.node_ids_ptr
-
-        self.bwd_row_offset_ptr = self._backward_graph.row_offset_ptr
-        self.bwd_column_indices_ptr = self._backward_graph.column_indices_ptr
-        self.bwd_eids_ptr = self._backward_graph.eids_ptr
-        self.bwd_node_ids_ptr = self._backward_graph.node_ids_ptr
-            
-    def get_num_nodes(self):
-        return self._num_nodes
-    
-    def get_num_edges(self):
-        return self._num_edges
-    
-    def get_ndata(self, field):
-        if field in self._ndata:
-            return self._ndata[field]
-        else:
-            return None
-
-    def set_ndata(self, field, val):
-        self._ndata[field] = val
-        
-    def graph_type(self):
-        # return "csr"
-        return "csr_unsorted"
-    
-    def in_degrees(self):
-        return np.array(self._forward_graph.out_degrees, dtype='int32')
-    
-    def out_degrees(self):
-        return np.array(self._forward_graph.in_degrees, dtype='int32')
-    
-    def weighted_in_degrees(self):
-        return np.array(self._forward_graph.weighted_out_degrees, dtype='int32')
\ No newline at end of file
diff --git a/stgraph/graph/static/__init__.py b/stgraph/graph/static/__init__.py
index e69de29b..923160b9 100644
--- a/stgraph/graph/static/__init__.py
+++ b/stgraph/graph/static/__init__.py
@@ -0,0 +1,1 @@
+"""Static Graph respresentation modules provided by STGraph."""
diff --git a/stgraph/graph/static/static_graph.py b/stgraph/graph/static/static_graph.py
new file mode 100644
index 00000000..901c9b90
--- /dev/null
+++ b/stgraph/graph/static/static_graph.py
@@ -0,0 +1,126 @@
+"""Represent Static graphs in STGraph."""
+
+from __future__ import annotations
+
+import copy
+
+import numpy as np
+from rich.console import Console
+
+from stgraph.graph.static.csr import CSR
+from stgraph.graph.stgraph_base import STGraphBase
+
+console = Console()
+
+
+class StaticGraph(STGraphBase):
+    r"""Represent Static graphs in STGraph.
+
+    This abstract class outlines the interface for defining a static graphs
+    used in STGraph. As of now the static graph is implemented using the
+    Compressed Sparse Row (CSR) format.
+
+    Example:
+    -------
+    .. code-block:: python
+
+        from stgraph.graph import StaticGraph
+        from stgraph.dataset import HungaryCPDataLoader
+
+        hungary = HungaryCPDataLoader()
+
+        graph = StaticGraph(
+            edge_list = hungary.get_edges(),
+            edge_weights = hungary.get_edge_weights(),
+            num_nodes = hungary.gdata["num_nodes"]
+        )
+
+    """
+
+    def __init__(
+        self: StaticGraph,
+        edge_list: list,
+        edge_weights: list,
+        num_nodes: int,
+    ) -> None:
+        r"""Represent Static graphs in STGraph."""
+        super().__init__()
+        self._num_nodes = num_nodes
+        self._num_edges = len(set(edge_list))
+
+        self._prepare_edge_lst_fwd(edge_list)
+        self._forward_graph = CSR(
+            self.fwd_edge_list,
+            edge_weights,
+            self._num_nodes,
+            is_edge_reverse=True,
+        )
+
+        self._prepare_edge_lst_bwd(self.fwd_edge_list)
+        self._backward_graph = CSR(self.bwd_edge_list, edge_weights, self._num_nodes)
+
+        self._get_graph_csr_ptrs()
+
+    # TODO-DOCS:
+    def _prepare_edge_lst_fwd(self: STGraphBase, edge_list: list) -> None:
+        edge_list_for_t = edge_list
+        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))
+        ]
+        self.fwd_edge_list = edge_list_for_t
+
+    # TODO-DOCS @nithin:
+    def _prepare_edge_lst_bwd(self: STGraphBase, edge_list: list) -> None:
+        edge_list_for_t = copy.deepcopy(edge_list)
+        edge_list_for_t.sort()
+        self.bwd_edge_list = edge_list_for_t
+
+    # TODO-DOCS @nithin:
+    def _get_graph_csr_ptrs(self: STGraphBase) -> None:
+        self.fwd_row_offset_ptr = self._forward_graph.row_offset_ptr
+        self.fwd_column_indices_ptr = self._forward_graph.column_indices_ptr
+        self.fwd_eids_ptr = self._forward_graph.eids_ptr
+        self.fwd_node_ids_ptr = self._forward_graph.node_ids_ptr
+
+        self.bwd_row_offset_ptr = self._backward_graph.row_offset_ptr
+        self.bwd_column_indices_ptr = self._backward_graph.column_indices_ptr
+        self.bwd_eids_ptr = self._backward_graph.eids_ptr
+        self.bwd_node_ids_ptr = self._backward_graph.node_ids_ptr
+
+    def get_num_nodes(self: STGraphBase) -> int:
+        r"""Return the number of nodes in the static graph."""
+        return self._num_nodes
+
+    def get_num_edges(self: STGraphBase) -> int:
+        r"""Return the number of edges in the static graph."""
+        return self._num_edges
+
+    def get_ndata(self: STGraphBase, field: any) -> any:
+        r"""Return the graph metadata."""
+        if field in self._ndata:
+            return self._ndata[field]
+
+        return None
+
+    def set_ndata(self: STGraphBase, field: str, val: any) -> None:
+        r"""Set the graph metadata."""
+        self._ndata[field] = val
+
+    def graph_type(self: STGraphBase) -> str:
+        r"""Return the graph type."""
+        return "csr_unsorted"
+
+    def in_degrees(self: STGraphBase) -> np.ndarray:
+        r"""Return the graph inwards node degree array."""
+        return np.array(self._forward_graph.out_degrees, dtype="int32")
+
+    def out_degrees(self: STGraphBase) -> np.ndarray:
+        r"""Return the graph outwards node degree array."""
+        return np.array(self._forward_graph.in_degrees, dtype="int32")
+
+    # TODO-DOCS @nithin:
+    def weighted_in_degrees(self: STGraphBase) -> np.ndarray:
+        r"""weighted_in_degrees."""
+        return np.array(self._forward_graph.weighted_out_degrees, dtype="int32")
diff --git a/stgraph/graph/stgraph_base.py b/stgraph/graph/stgraph_base.py
new file mode 100644
index 00000000..37b07ae2
--- /dev/null
+++ b/stgraph/graph/stgraph_base.py
@@ -0,0 +1,90 @@
+"""Represent graphs in STGraph using this abstract base class."""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+
+
+class STGraphBase(ABC):
+    r"""Represent graphs in STGraph using this abstract base class."""
+
+    def __init__(self: STGraphBase) -> None:
+        r"""Represent graphs in STGraph using this abstract base class.
+
+        This abstract class outlines the interface for defining different
+        types of graphs used in STGraph. It provides the basic structure
+        and methods for graph classes. Subclasses should implement the
+        abstract methods to provide specific graph functionality.
+
+        Attributes
+        ----------
+        fwd_row_offset_ptr
+            Pointer to the forward graphs row offset array
+
+        fwd_column_indices_ptr
+            Pointer to the forward graphs column indices array
+
+        fwd_eids_ptr
+            Pointer to the forward graphs edge ID array
+
+        fwd_node_ids_ptr
+            Pointer to the forward graphs node ID array
+
+        bwd_row_offset_ptr
+            Pointer to the backward graphs row offset array
+
+        bwd_column_indices_ptr
+            Pointer to the backward graphs column indices array
+
+        bwd_eids_ptr
+            Pointer to the backward graphs edge ID array
+
+        bwd_node_ids_ptr
+            Pointer to the backward graphs node ID array
+
+        """
+        self._ndata = {}
+
+        self._forward_graph = None
+        self._backward_graph = None
+
+        self.fwd_row_offset_ptr = None
+        self.fwd_column_indices_ptr = None
+        self.fwd_eids_ptr = None
+        self.fwd_node_ids_ptr = None
+
+        self.bwd_row_offset_ptr = None
+        self.bwd_column_indices_ptr = None
+        self.bwd_eids_ptr = None
+        self.bwd_node_ids_ptr = None
+
+    @abstractmethod
+    def _get_graph_csr_ptrs(self: STGraphBase) -> None:
+        r"""TODO:."""
+        pass
+
+    @abstractmethod
+    def get_num_nodes(self: STGraphBase) -> int:
+        r"""Return the number of nodes in the graph."""
+        pass
+
+    @abstractmethod
+    def get_num_edges(self: STGraphBase) -> int:
+        r"""Return the number of edges in the graph."""
+        pass
+
+    @abstractmethod
+    def get_ndata(self: STGraphBase, field: str) -> any:
+        r"""Return the graph metadata."""
+        pass
+
+    @abstractmethod
+    def set_ndata(self: STGraphBase, field: str, val: any) -> None:
+        r"""Set the graph metadata."""
+        pass
+
+    @property
+    @abstractmethod
+    def graph_type(self: STGraphBase) -> str:
+        r"""Return the graph type."""
+        pass
diff --git a/stgraph/nn/__init__.py b/stgraph/nn/__init__.py
index 0514cbd6..98f055d6 100644
--- a/stgraph/nn/__init__.py
+++ b/stgraph/nn/__init__.py
@@ -1,1 +1,3 @@
-'''State of the art Graph Neural Networks written using STGraph'''
\ No newline at end of file
+"""Vertex-centric implementation for state-of-the-art Graph Neural Networks."""
+
+from stgraph.nn.pytorch.static.gcn_conv import GCNConv
diff --git a/stgraph/nn/pytorch/__init__.py b/stgraph/nn/pytorch/__init__.py
index e69de29b..2b0c5a28 100644
--- a/stgraph/nn/pytorch/__init__.py
+++ b/stgraph/nn/pytorch/__init__.py
@@ -0,0 +1,1 @@
+"""Vertex-centric implementation for state-of-the-art Graph Neural Networks for PyTorch specific backend."""
\ No newline at end of file
diff --git a/stgraph/nn/pytorch/graph_conv.py b/stgraph/nn/pytorch/graph_conv.py
deleted file mode 100644
index c62f4ead..00000000
--- a/stgraph/nn/pytorch/graph_conv.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import torch
-import torch.nn as nn
-from stgraph.compiler import STGraph
-from stgraph.compiler.backend.pytorch.torch_callback import STGraphBackendTorch
-
-class GraphConv(nn.Module):
-    def __init__(self,
-                 in_feats,
-                 out_feats,
-                 activation,
-                 bias=True):
-        super(GraphConv, self).__init__()
-        self.weight = nn.Parameter(torch.Tensor(in_feats, out_feats))
-        if bias:
-            self.bias = nn.Parameter(torch.Tensor(out_feats))
-        else:
-            self.bias = None
-        self.activation = activation
-        self.stgraph = STGraph(STGraphBackendTorch())
-        self.reset_parameters()
-
-    def reset_parameters(self):
-        nn.init.xavier_uniform_(self.weight)
-        if self.bias is not None:
-            nn.init.zeros_(self.bias)
-
-    def forward(self, g, h, edge_weight=None):
-        h = torch.mm(h, self.weight)
-        
-        if edge_weight is None:
-            @self.stgraph.compile(gnn_module=self)
-            def nb_compute(v):
-                h = sum([nb.h*nb.norm for nb in v.innbs])
-                h = h * v.norm
-                return h
-            h = nb_compute(g=g, n_feats={'norm': g.get_ndata("norm"), 'h' : h})
-        else:
-            @self.stgraph.compile(gnn_module=self)
-            def nb_compute(v):
-                h = sum([nb_edge.src.norm * nb_edge.src.h * nb_edge.edge_weight for nb_edge in v.inedges])
-                h = h * v.norm
-                return h
-            h = nb_compute(g=g, n_feats={'norm': g.get_ndata("norm"), 'h' : h}, e_feats={'edge_weight':edge_weight})
-
-        # bias
-        if self.bias is not None:
-            h = h + self.bias
-        if self.activation:
-            h = self.activation(h)
-        return h
diff --git a/stgraph/nn/pytorch/static/__init__.py b/stgraph/nn/pytorch/static/__init__.py
new file mode 100644
index 00000000..38c8aed9
--- /dev/null
+++ b/stgraph/nn/pytorch/static/__init__.py
@@ -0,0 +1,1 @@
+"""State-of-the-art Static Graph Neural Networks written for PyTorch backend."""
diff --git a/stgraph/nn/pytorch/gat_conv.py b/stgraph/nn/pytorch/static/gat_conv.py
similarity index 100%
rename from stgraph/nn/pytorch/gat_conv.py
rename to stgraph/nn/pytorch/static/gat_conv.py
diff --git a/stgraph/nn/pytorch/static/gcn_conv.py b/stgraph/nn/pytorch/static/gcn_conv.py
new file mode 100644
index 00000000..25a004df
--- /dev/null
+++ b/stgraph/nn/pytorch/static/gcn_conv.py
@@ -0,0 +1,189 @@
+"""Graph Convolutional Network Layer."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable
+
+if TYPE_CHECKING:
+    from stgraph.compiler.node import CentralNode
+    from stgraph.graph import StaticGraph
+
+import torch
+from torch import Tensor, nn
+
+from stgraph.compiler import STGraph
+from stgraph.compiler.backend.pytorch.torch_callback import STGraphBackendTorch
+from stgraph.utils.constants import SizeConstants
+
+
+class GCNConv(nn.Module):
+    r"""Graph Convolutional Network Layer.
+
+    Vertex-centric implementation for Graph Convolutional Network (GCN)
+    layer as described in `Semi-supervised Classification with Graph
+    Convolutional Networks <https://arxiv.org/abs/1609.02907>`_.
+
+    A multi-layer GCN model has the following layer-wise propagation rule
+
+    .. math::
+
+        H^{(l+1)} = \sigma \left( \tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2} H^{(l)} W^{(l)} \right)
+
+    - :math:`H^{(l)}`: Matrix of activations in the :math:`l`-th layer; :math:`H^{(0)} = X` is the input feature matrix.
+    - :math:`\sigma`: Activation function (e.g., ReLU).
+    - :math:`\tilde{A} = A + I_N`: Adjacency matrix of the graph with added self-connections.
+    - :math:`I_N`: Identity matrix.
+    - :math:`\tilde{D}_{ii} = \sum_j \tilde{A}_{ij}`: Degree matrix of :math:`\tilde{A}`.
+    - :math:`W^{(l)}`: Trainable weight matrix for the :math:`l`-th layer.
+
+    **Vertex-Centric Formula**
+
+    The vertex-centric implementation can be achieved by aggregating all the
+    features of the neighbouring nodes of the central node
+
+    .. math::
+
+        h^{(l+1)} = \left( \sum_{\text{nb} \in \text{innbs}(v)} \text{nb}_{h^{(l)}} \cdot \text{nb}_{\text{norm}} \cdot \text{weight}_{\text{nb,v}} \right) \cdot v_{\text{norm}}
+
+    - :math:`h^{(l)}`: Activations of central-node in the :math:`l`-th layer.
+    - :math:`\text{innbs}(v)`: In-neighbours of central-node :math:`v`.
+    - :math:`\text{weight}_{\text{nb,v}}`: Weight of edge from :math:`nb` to :math:`v`. In case no edge weights are present, it is set to 1
+    - :math:`norm`: Node wise normalization factor, :math:`v_{\text{norm}} = \text{in_degrees(v)}^{-0.5}`.
+
+    **Node Data**
+
+    The following node data needs to be set using :class:`StaticGraph.set_ndata <stgraph.graph.static.static_graph.StaticGraph>` before calling
+    the :func:`~stgraph.nn.pytorch.static.gcn_conv.GCNConv.forward` method.
+
+    +---------------+--------------------------------+---------------------------------------------------------------------------------------------------+
+    | Node Property | Description                    | Type                                                                                              |
+    +===============+================================+===================================================================================================+
+    | norm          | Node-wise normalization factor | A PyTorch Tensor of shape (num_nodes, 1), where dim=1 contains the node-wise normalization factor |
+    +---------------+--------------------------------+---------------------------------------------------------------------------------------------------+
+
+
+    Parameters
+    ----------
+    in_channels : int
+        Size of input sample passed into the layer
+    out_channels : int
+        Size of output sample outputted by the layer
+    activation : optional
+        Non-linear activation function provided by `PyTorch <https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity>`_
+    bias : bool, optional
+        If set to *True*, learnable bias parameters are added to the layer
+
+    """
+
+    def __init__(
+        self: GCNConv,
+        in_channels: int,
+        out_channels: int,
+        activation: Callable[..., torch.Tensor] | None = None,
+        bias: bool = True,
+    ) -> None:
+        """Graph Convolutional Network Layer."""
+        super().__init__()
+        self.weight = nn.Parameter(torch.Tensor(in_channels, out_channels))
+        if bias:
+            self.bias = nn.Parameter(torch.Tensor(out_channels))
+        else:
+            self.bias = None
+        self.activation = activation
+        self.stgraph = STGraph(STGraphBackendTorch())
+        self.reset_parameters()
+
+    def reset_parameters(self: GCNConv) -> None:
+        r"""Reset the learnable weight and bias parameters.
+
+        The weight parameter is initialized using a Xavier Uniform distribution.
+        The bias parameter is initialized by setting all values to zero.
+        """
+        nn.init.xavier_uniform_(self.weight)
+        if self.bias is not None:
+            nn.init.zeros_(self.bias)
+
+    def forward(
+        self: GCNConv,
+        graph: StaticGraph,
+        h: Tensor,
+        edge_weight: Tensor | None = None,
+    ) -> Tensor:
+        r"""Execute a single forward pass for the GCN layer.
+
+        Runs a single forward pass using the vertex-centric implementation of the GCN layer.
+
+        Parameters
+        ----------
+        graph : StaticGraph
+            A StaticGraph graph object
+        h : Tensor
+            Input for the GCN forward pass
+        edge_weight : Tensor, optional
+            Edge weights for each edge in the graph
+
+        Returns
+        -------
+        Tensor
+            The output after executing the GCN forward pass
+
+        Raises
+        ------
+        KeyError
+            If ``norm`` n_data is not present for the graph
+        ValueError
+            If ``norm`` n_data passed is not of the shape (num_nodes, 1)
+
+        Example
+        -------
+
+        Example usage::
+
+            # Defining a method to run forward pass with multiple GCN layers
+
+            def forward(input: Tensor, layers: List[GCNConv], graph: StaticGraph):
+                h = input
+                for layer in layers:
+                    h = layer.forward(graph, h)
+                return h
+
+        """
+        if graph.get_ndata("norm") is None:
+            raise KeyError("StaticGraph passed to GCNConv forward pass does not contain 'norm' node data")
+        if (len(graph.get_ndata("norm").shape) != SizeConstants.NODE_NORM_SIZE.value or
+                graph.get_ndata("norm").shape[1] != 1 or
+                graph.get_ndata("norm").shape[0] != graph.get_num_nodes()):
+            raise ValueError("Node data 'norm' passed to GCNConv should be of shape (num_nodes, 1)")
+
+        h = torch.mm(h, self.weight)
+
+        if edge_weight is None:
+
+            @self.stgraph.compile(gnn_module=self)
+            def nb_compute(v: CentralNode) -> Tensor:
+                return sum([nb.h * nb.norm for nb in v.innbs]) * v.norm
+
+            h = nb_compute(g=graph, n_feats={"norm": graph.get_ndata("norm"), "h": h})
+        else:
+
+            @self.stgraph.compile(gnn_module=self)
+            def nb_compute(v: CentralNode) -> Tensor:
+                return sum(
+                    [
+                        nb_edge.src.norm * nb_edge.src.h * nb_edge.edge_weight
+                        for nb_edge in v.inedges
+                    ],
+                ) * v.norm
+
+            h = nb_compute(
+                g=graph,
+                n_feats={"norm": graph.get_ndata("norm"), "h": h},
+                e_feats={"edge_weight": edge_weight},
+            )
+
+        # bias
+        if self.bias is not None:
+            h = h + self.bias
+        if self.activation:
+            h = self.activation(h)
+        return h
diff --git a/stgraph/nn/pytorch/temporal/tgcn.py b/stgraph/nn/pytorch/temporal/tgcn.py
index 94685ead..421bfdb3 100644
--- a/stgraph/nn/pytorch/temporal/tgcn.py
+++ b/stgraph/nn/pytorch/temporal/tgcn.py
@@ -1,35 +1,16 @@
 import torch
-from stgraph.nn.pytorch.graph_conv import GraphConv
-
+from stgraph.nn.pytorch.static.gcn_conv import GCNConv
 
 class TGCN(torch.nn.Module):
-
-    def __init__(
-        self,
-        in_channels: int,
-        out_channels: int,
-    ):
+    def __init__(self, in_channels, out_channels):
         super(TGCN, self).__init__()
-
         self.in_channels = in_channels
         self.out_channels = out_channels
-
-        # Update GCN Layer
-        self.conv_z = GraphConv(self.in_channels, self.out_channels, activation=None)
-       
-        # Update linear layer
+        self.conv_z = GCNConv(self.in_channels, self.out_channels, activation=None)
         self.linear_z = torch.nn.Linear(2 * self.out_channels, self.out_channels)
-
-        # Reset GCN layer
-        self.conv_r = GraphConv(self.in_channels, self.out_channels, activation=None)
-
-        # Reset linear layer
+        self.conv_r = GCNConv(self.in_channels, self.out_channels, activation=None)
         self.linear_r = torch.nn.Linear(2 * self.out_channels, self.out_channels)
-
-        # Candidate (Current Memory Content) GCN layer
-        self.conv_h = GraphConv(self.in_channels, self.out_channels, activation=None)
-
-        # Candidate linear layer
+        self.conv_h = GCNConv(self.in_channels, self.out_channels, activation=None)
         self.linear_h = torch.nn.Linear(2 * self.out_channels, self.out_channels)
 
     def _set_hidden_state(self, X, H):
@@ -39,6 +20,7 @@ def _set_hidden_state(self, X, H):
 
     def _calculate_update_gate(self, g, X, edge_weight, H):
         h = self.conv_z(g, X, edge_weight=edge_weight)
+        h = torch.clamp(h, min=-1e6, max=1e6) # Clamp to avoid extreme values
         Z = torch.cat((h, H), dim=1)
         Z = self.linear_z(Z)
         Z = torch.sigmoid(Z)
@@ -46,6 +28,7 @@ def _calculate_update_gate(self, g, X, edge_weight, H):
 
     def _calculate_reset_gate(self, g, X, edge_weight, H):
         h = self.conv_r(g, X, edge_weight=edge_weight)
+        h = torch.clamp(h, min=-1e6, max=1e6) # Clamp to avoid extreme values
         R = torch.cat((h, H), dim=1)
         R = self.linear_r(R)
         R = torch.sigmoid(R)
@@ -53,6 +36,7 @@ def _calculate_reset_gate(self, g, X, edge_weight, H):
 
     def _calculate_candidate_state(self, g, X, edge_weight, H, R):
         h = self.conv_h(g, X, edge_weight=edge_weight)
+        h = torch.clamp(h, min=-1e6, max=1e6) # Clamp to avoid extreme values
         H_tilde = torch.cat((h, H * R), dim=1)
         H_tilde = self.linear_h(H_tilde)
         H_tilde = torch.tanh(H_tilde)
@@ -63,7 +47,6 @@ def _calculate_hidden_state(self, Z, H, H_tilde):
         return H
 
     def forward(self, g, X, edge_weight=None, H=None):
-
         H = self._set_hidden_state(X, H)
         Z = self._calculate_update_gate(g, X, edge_weight, H)
         R = self._calculate_reset_gate(g, X, edge_weight, H)
diff --git a/stgraph/utils/__init__.py b/stgraph/utils/__init__.py
new file mode 100644
index 00000000..6362328c
--- /dev/null
+++ b/stgraph/utils/__init__.py
@@ -0,0 +1,4 @@
+"""Utility package for STGraph."""
+
+from stgraph.utils.constants import SizeConstants
+from stgraph.utils.data_table import DataTable
diff --git a/stgraph/utils/constants.py b/stgraph/utils/constants.py
new file mode 100644
index 00000000..58ccac73
--- /dev/null
+++ b/stgraph/utils/constants.py
@@ -0,0 +1,17 @@
+r"""Constants to be used across the project."""
+
+from enum import Enum
+
+
+class SizeConstants(Enum):
+    r"""Data Size Related Constants.
+
+    +----------------+-------+----------------------------------------------+
+    | Constant       | Value | Description                                  |
+    +================+=======+==============================================+
+    | NODE_NORM_SIZE | 2     | Length of the node-wise normalization tensor |
+    +----------------+-------+----------------------------------------------+
+
+    """
+
+    NODE_NORM_SIZE = 2
diff --git a/stgraph/utils/data_table.py b/stgraph/utils/data_table.py
new file mode 100644
index 00000000..4bcd452c
--- /dev/null
+++ b/stgraph/utils/data_table.py
@@ -0,0 +1,87 @@
+"""Table that can display benchmarking data and other info."""
+
+from __future__ import annotations
+
+from typing import IO
+
+from rich.console import Console
+from rich.table import Table
+
+
+class DataTable:
+    r"""Table that can display benchmarking data and other info.
+
+    This class provides functionality to create and display tables for
+    benchmarking data along with other relevant information.
+
+    Example
+    -------
+
+    .. code-block:: python
+
+        from stgraph.utils import DataTable
+
+        table = DataTable(
+            title = "GCN Benchmark Data",
+            col_name_list = ["Model", "Time", "MSE"]
+        )
+
+        table.add_row("GCN", 12.56, 45.89)
+        table.add_row("GCN", 23.34, 44.32)
+
+        table.display()
+
+    Parameters
+    ----------
+    title : str
+        The title of the table
+    col_name_list : list[str]
+        A list of the table column names
+
+    Attributes
+    ----------
+    title : str
+        The title of the table
+    col_name_list : list[str]
+        A list of the table column names
+
+    """
+
+    def __init__(self: DataTable, title: str, col_name_list: list[str]) -> None:
+        r"""Table that can display benchmarking data and other info."""
+        self.title = "\n" + title + "\n"
+        self.col_name_list = col_name_list
+        self._table = Table(title=self.title, show_edge=False, style="black bold")
+        self._num_cols = len(col_name_list)
+        self._num_rows = 0
+
+        self._table_add_columns()
+
+    def _table_add_columns(self: DataTable) -> None:
+        r"""Prepare the table by adding all the columns."""
+        for col_name in self.col_name_list:
+            self._table.add_column(col_name, justify="left")
+
+    def add_row(self: DataTable, values: list) -> None:
+        r"""Add a row of data to the table.
+
+        Parameters
+        ----------
+        values : list
+            A list of values for each column in the row.
+
+        """
+        values_str = tuple([str(val) for val in values])
+        self._table.add_row(*values_str)
+
+    def display(self: DataTable, output_file: IO[str] | None = None) -> None:
+        r"""Display entire table with data.
+
+        Parameters
+        ----------
+        output_file : Optional[IO[str]], optional
+            File object to write the table to.
+
+        """
+        console = Console() if not output_file else Console(file=output_file)
+        console.print(self._table)
diff --git a/tutorials/README.md b/tutorials/README.md
new file mode 100644
index 00000000..82aebb0a
--- /dev/null
+++ b/tutorials/README.md
@@ -0,0 +1,8 @@
+# STGraph Tutorials
+
+Within this directory you can find the source code for all the tutorials. As the project expands and we introduce more Graph Neural Network layers and data loaders, additional tutorials and source code will be added over time.
+
+| Tutorial Name                                                                    | Task                | NN Layer | Dataset | Graph Type | Backend |
+|----------------------------------------------------------------------------------|---------------------|----------|---------|------------|---------|
+| [Cora Publication Prediction using Graph Convolutional Networks (GCN)](gcn/cora) | Node Classification | GCNConv  | Cora    | Static     | PyTorch |
+
diff --git a/tutorials/gcn/cora/README.md b/tutorials/gcn/cora/README.md
new file mode 100644
index 00000000..41ab0f52
--- /dev/null
+++ b/tutorials/gcn/cora/README.md
@@ -0,0 +1,3 @@
+# Cora Publication Prediction using Graph Convolutional Networks (GCN)
+
+You can find the detailed tutorial in the documentation page.
\ No newline at end of file
diff --git a/tutorials/gcn/cora/__init__.py b/tutorials/gcn/cora/__init__.py
new file mode 100644
index 00000000..6dfcfdc9
--- /dev/null
+++ b/tutorials/gcn/cora/__init__.py
@@ -0,0 +1,1 @@
+"""Cora Publication Prediction using Graph Convolutional Networks (GCN)"""
diff --git a/tutorials/gcn/cora/main.py b/tutorials/gcn/cora/main.py
new file mode 100644
index 00000000..d8f7c4b6
--- /dev/null
+++ b/tutorials/gcn/cora/main.py
@@ -0,0 +1,52 @@
+import argparse
+
+from train import train
+
+
+def main(args) -> None:
+    train(
+        lr=args.learning_rate,
+        num_epochs=args.epochs,
+        num_hidden=args.num_hidden,
+        num_hidden_layers=args.num_hidden_layers,
+        weight_decay=args.weight_decay,
+    )
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Training GCN on CORA Dataset")
+
+    parser.add_argument(
+        "-lr",
+        "--learning-rate",
+        type=float,
+        default=0.01,
+        help="Learning Rate for the GCN Model",
+    )
+
+    parser.add_argument(
+        "-e",
+        "--epochs",
+        type=int,
+        default=200,
+        help="Number of Epochs to Train the GCN Model",
+    )
+
+    parser.add_argument(
+        "-n",
+        "--num-hidden",
+        type=int,
+        default=16,
+        help="Number of Neurons in Hidden Layers",
+    )
+
+    parser.add_argument(
+        "-l", "--num-hidden-layers", type=int, default=1, help="Number of Hidden Layers"
+    )
+
+    parser.add_argument(
+        "-w", "--weight-decay", type=float, default=5e-4, help="Weight Decay"
+    )
+
+    args = parser.parse_args()
+    main(args=args)
diff --git a/tutorials/gcn/cora/model.py b/tutorials/gcn/cora/model.py
new file mode 100644
index 00000000..d5a73140
--- /dev/null
+++ b/tutorials/gcn/cora/model.py
@@ -0,0 +1,77 @@
+"""Graph Convolutional Network Model."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from torch import Tensor, nn
+from torch.nn.functional import relu
+
+from stgraph.nn.pytorch.static.gcn_conv import GCNConv
+
+if TYPE_CHECKING:
+    from stgraph.graph import StaticGraph
+
+
+class GCN(nn.Module):
+    r"""Graph Convolutional Network Model.
+
+    A multi-layer Graph Convolutional Network Model for node classification task.
+
+    Parameters
+    ----------
+    graph : StaticGraph
+        The input static graph the GCN model operates.
+    in_feats : int
+        Number of input features.
+    n_hidden : int
+        Number of hidden units in a hidden layer.
+    n_classes : int
+        Number of output classes.
+    n_hidden_layers : int
+        Number of hidden layers.
+
+    """
+
+    def __init__(
+        self: GCN,
+        graph: StaticGraph,
+        in_feats: int,
+        n_hidden: int,
+        n_classes: int,
+        n_hidden_layers: int,
+    ) -> None:
+        r"""Graph Convolutional Network Model."""
+        super().__init__()
+
+        self._graph = graph
+        self._layers = nn.ModuleList()
+
+        # input layer
+        self._layers.append(GCNConv(in_feats, n_hidden, relu, bias=True))
+
+        # hidden layers
+        for _ in range(n_hidden_layers):
+            self._layers.append(GCNConv(n_hidden, n_hidden, relu, bias=True))
+
+        # output layer
+        self._layers.append(GCNConv(n_hidden, n_classes, None, bias=True))
+
+    def forward(self: GCN, features: Tensor) -> Tensor:
+        r"""Forward pass of the GCN model.
+
+        Parameters
+        ----------
+        features : Tensor
+            Input features for each node in the graph.
+
+        Returns
+        -------
+        Tensor :
+            The output features after applying all the GCN layers.
+
+        """
+        h = features
+        for layer in self._layers:
+            h = layer.forward(self._graph, h)
+        return h
diff --git a/tutorials/gcn/cora/train.py b/tutorials/gcn/cora/train.py
new file mode 100644
index 00000000..2724ac31
--- /dev/null
+++ b/tutorials/gcn/cora/train.py
@@ -0,0 +1,131 @@
+r"""Script to train GCN on Cora dataset."""
+
+import sys
+import traceback
+
+import torch
+from model import GCN
+from torch.nn.functional import cross_entropy
+from utils import (
+    accuracy,
+    generate_test_mask,
+    generate_train_mask,
+    get_node_norms,
+    row_normalize_feature,
+)
+
+from stgraph.utils import DataTable
+from stgraph.dataset import CoraDataLoader
+from stgraph.graph.static.static_graph import StaticGraph
+
+
+def train(
+    lr: float,
+    num_epochs: int,
+    num_hidden: int,
+    num_hidden_layers: int,
+    weight_decay: float,
+) -> None:
+    r"""Script to train GCN on Cora dataset.
+
+    Parameters
+    ----------
+    lr : float
+        Learning Rate.
+    num_epochs : int
+        Number of Epochs.
+    num_hidden : int
+        Number of hidden units in hidden layer.
+    num_hidden_layers : int
+        Number of hidden layers.
+    weight_decay : float
+        Weight decay value for L2 regularization.
+
+    """
+    if not torch.cuda.is_available():
+        print("CUDA is not available")
+        sys.exit(1)
+
+    cora = CoraDataLoader()
+
+    node_features = row_normalize_feature(
+        torch.FloatTensor(cora.get_all_features()),
+    )
+    node_labels = torch.LongTensor(cora.get_all_targets())
+    edge_weights = [1 for _ in range(cora.gdata["num_edges"])]
+
+    train_mask = torch.BoolTensor(
+        generate_train_mask(cora.gdata["num_nodes"], 0.7),
+    )
+    test_mask = torch.BoolTensor(
+        generate_test_mask(cora.gdata["num_nodes"], 0.7),
+    )
+
+    torch.cuda.set_device(0)
+    node_features = node_features.cuda()
+    node_labels = node_labels.cuda()
+    train_mask = train_mask.cuda()
+    test_mask = test_mask.cuda()
+
+    cora_graph = StaticGraph(
+        edge_list=cora.get_edges(),
+        edge_weights=edge_weights,
+        num_nodes=cora.gdata["num_nodes"],
+    )
+
+    cora_graph.set_ndata("norm", get_node_norms(cora_graph))
+
+    model = GCN(
+        graph=cora_graph,
+        in_feats=cora.gdata["num_feats"],
+        n_hidden=num_hidden,
+        n_classes=cora.gdata["num_classes"],
+        n_hidden_layers=num_hidden_layers,
+    ).cuda()
+
+    loss_function = cross_entropy
+    optimizer = torch.optim.Adam(
+        model.parameters(), lr=lr, weight_decay=weight_decay,
+    )
+
+    table = DataTable(
+        "STGraph GCN on CORA dataset",
+        ["Epoch", "Train Accuracy %", "Loss"],
+    )
+
+    try:
+        print("Started Training")
+        for epoch in range(num_epochs):
+            model.train()
+            torch.cuda.synchronize()
+
+            logits = model.forward(node_features)
+            loss = loss_function(logits[train_mask], node_labels[train_mask])
+            optimizer.zero_grad()
+            loss.backward()
+            optimizer.step()
+
+            torch.cuda.synchronize()
+
+            train_acc = accuracy(logits[train_mask], node_labels[train_mask])
+
+            table.add_row(
+                [epoch, float(f"{train_acc * 100:.2f}"), float(f"{loss.item():.5f}")],
+            )
+        print("Training Ended")
+        table.display()
+
+        print("Evaluating trained GCN model on the Test Set")
+
+        model.eval()
+        logits_test = model(node_features)
+        loss_test = loss_function(logits_test[train_mask], node_labels[train_mask])
+        test_acc = accuracy(logits_test[test_mask], node_labels[test_mask])
+
+        print(f"Loss for Test: {loss_test}")
+        print(f"Accuracy for Test: {float(test_acc) * 100} %")
+
+    except Exception as e:
+        print("------------- Error -------------")
+        print(e)
+        traceback.print_exc()
diff --git a/tutorials/gcn/cora/utils.py b/tutorials/gcn/cora/utils.py
new file mode 100644
index 00000000..736e898f
--- /dev/null
+++ b/tutorials/gcn/cora/utils.py
@@ -0,0 +1,170 @@
+"""Utility methods for GCN training on Cora dataset."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import torch
+from torch import Tensor
+
+if TYPE_CHECKING:
+    from stgraph.graph import StaticGraph
+
+
+def accuracy(logits: Tensor, labels: Tensor) -> float:
+    r"""Compute the accuracy of the predictions.
+
+    Parameters
+    ----------
+    logits : Tensor
+        The predicted output from the model, of shape (num_samples, num_classes).
+    labels : Tensor
+        The ground truth labels, of shape (num_samples,).
+
+    Returns
+    -------
+    float :
+        The accuracy of the predictions, calculated as the proportion of
+        correct predictions out of the total number of samples.
+
+    """
+    _, indices = torch.max(logits, dim=1)
+    correct = torch.sum(indices == labels)
+    return correct.item() * 1.0 / len(labels)
+
+
+# GPU | CPU
+def get_default_device() -> torch.device:
+    r"""Return the default device to be used for tensor operations.
+
+    Checks if CUDA is available and returns the first GPU device if it is;
+    otherwise, returns the CPU device.
+
+    Returns
+    -------
+    torch.device :
+        The default device ("cuda:0" if available, otherwise "cpu").
+
+    """
+    if torch.cuda.is_available():
+        return torch.device("cuda:0")
+
+    return torch.device("cpu")
+
+
+def to_default_device(data: Tensor | list | tuple) -> Tensor | list | tuple:
+    r"""Move the given data to the default device.
+
+    If the data is a list or tuple, recursively moves each element to the default device.
+    Otherwise, moves the data directly to the default device.
+
+    Parameters
+    ----------
+    data : Tensor | list | tuple
+        The data to be moved to the default device
+
+    Returns
+    -------
+    Tensor | list | tuple :
+        The data that is moved to the default device
+
+    """
+    if isinstance(data, (list, tuple)):
+        return [to_default_device(x) for x in data]
+
+    return data.to(get_default_device(), non_blocking=True)
+
+
+def generate_train_mask(size: int, train_test_split: float) -> list:
+    r"""Generate a mask for training data.
+
+    Creates a binary mask where the first portion, determined by ``train_test_split``, is set to 1
+    (indicating training samples) and the rest is set to 0 (indicating non-training samples).
+
+    Parameters
+    ----------
+    size : int
+        The total number of samples.
+    train_test_split : float
+        Fraction of samples used for training.
+
+    Returns
+    -------
+    list :
+        A binary mask where 1 represents the training sample.
+
+    """
+    cutoff = size * train_test_split
+    return [1 if i < cutoff else 0 for i in range(size)]
+
+
+def generate_test_mask(size: int, train_test_split: float) -> list:
+    r"""Generate a mask for testing data.
+
+    Creates a binary mask where the first portion, determined by ``train_test_split``, is set to 0
+    (indicating non-testing samples) and the rest is set to 1 (indicating testing samples).
+
+    Parameters
+    ----------
+    size : int
+        The total number of samples.
+    train_test_split : float
+        Fraction of samples used for training.
+
+    Returns
+    -------
+    list :
+        A binary mask where 1 represents the testing sample.
+
+    """
+    cutoff = size * train_test_split
+    return [0 if i < cutoff else 1 for i in range(size)]
+
+
+def row_normalize_feature(features: Tensor) -> Tensor:
+    """Row-normalizes the node features.
+
+    Scales each node features such that the sum of the elements for each node feature is 1.0.
+    If the sum of a row is zero, the row is normalized to zero.
+
+    Parameters
+    ----------
+    features : Tensor
+        The node feature tensor of shape (num_nodes, feat_size).
+
+    Returns
+    -------
+    Tensor :
+        The row-normalized node features.
+
+    """
+    # Compute the sum of each row
+    row_sum = features.sum(dim=1, keepdim=True)
+
+    # Compute the inverse of the row sums, handling division by zero
+    r_inv = torch.where(row_sum != 0, 1.0 / row_sum, torch.zeros_like(row_sum))
+
+    return features * r_inv
+
+
+def get_node_norms(graph: StaticGraph) -> Tensor:
+    r"""Compute node normalization factors for a graph.
+
+    The normalization factor for each node is calculated as the inverse square root of its degree.
+    Nodes with an infinite normalization factor (due to zero degree) are set to zero.
+
+    Parameters
+    ----------
+    graph : StaticGraph
+        The static graph object.
+
+    Returns
+    -------
+    Tensor :
+        A tensor of shape (num_nodes, 1) containing the normalization factors for each node.
+
+    """
+    degrees = torch.from_numpy(graph.weighted_in_degrees()).type(torch.int32)
+    norm = torch.pow(degrees, -0.5)
+    norm[torch.isinf(norm)] = 0
+    return to_default_device(norm).unsqueeze(1)


Test patch:
diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml
new file mode 100644
index 00000000..80a88a16
--- /dev/null
+++ b/.github/workflows/pytest.yaml
@@ -0,0 +1,28 @@
+name: Run Unit Test via Pytest  
+  
+on: [push]  
+  
+jobs:  
+  build:  
+    runs-on: ubuntu-latest  
+    strategy:  
+      matrix:  
+        python-version: ["3.8"]  
+  
+    steps:  
+      - uses: actions/checkout@v3  
+      - name: Set up Python ${{ matrix.python-version }}  
+        uses: actions/setup-python@v4  
+        with:  
+          python-version: ${{ matrix.python-version }}  
+      - name: Install dependencies  
+        run: |  
+          python -m pip install --upgrade pip  
+          pip install -e .[dev]
+          pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
+      - name: Test with pytest  
+        run: |  
+          coverage run -m pytest  -v -s -p no:warnings
+      - name: Generate Coverage Report  
+        run: |  
+          coverage report -m
diff --git a/tests/dataset/dynamic/test_EnglandCovidDataLoader.py b/tests/dataset/dynamic/test_EnglandCovidDataLoader.py
new file mode 100644
index 00000000..b9ce7865
--- /dev/null
+++ b/tests/dataset/dynamic/test_EnglandCovidDataLoader.py
@@ -0,0 +1,75 @@
+import pytest
+
+from stgraph.dataset import EnglandCovidDataLoader
+
+
+def EnglandCovidDataCheck(eng_covid: EnglandCovidDataLoader):
+    assert eng_covid.gdata["total_timestamps"] == (
+        61 if not eng_covid._cutoff_time else eng_covid._cutoff_time
+    )
+    assert (
+        len(list(eng_covid.gdata["num_nodes"].values()))
+        == eng_covid.gdata["total_timestamps"]
+    )
+
+    for time, num_node in eng_covid.gdata["num_nodes"].items():
+        assert num_node == 129
+
+    assert (
+        len(list(eng_covid.gdata["num_edges"].values()))
+        == eng_covid.gdata["total_timestamps"]
+    )
+
+    edge_list = eng_covid.get_edges()
+
+    assert len(edge_list) == eng_covid.gdata["total_timestamps"]
+    assert len(edge_list[0][0]) == 2
+
+    edge_weights = eng_covid.get_edge_weights()
+
+    assert len(edge_weights) == eng_covid.gdata["total_timestamps"]
+
+    for i in range(len(edge_list)):
+        assert len(edge_list[i]) == len(edge_weights[i])
+
+    all_features = eng_covid.get_all_features()
+    all_targets = eng_covid.get_all_targets()
+
+    assert len(all_features) == eng_covid.gdata["total_timestamps"] - eng_covid._lags
+
+    assert all_features[0].shape == (
+        eng_covid.gdata["num_nodes"]["0"],
+        eng_covid._lags,
+    )
+
+    assert len(all_targets) == eng_covid.gdata["total_timestamps"] - eng_covid._lags
+
+    assert all_targets[0].shape == (eng_covid.gdata["num_nodes"]["0"],)
+
+
+def test_EnglandCovidDataLoader():
+    eng_covid = EnglandCovidDataLoader(verbose=True)
+    eng_covid_1 = EnglandCovidDataLoader(cutoff_time=30)
+    eng_covid_3 = EnglandCovidDataLoader(lags=12)
+    eng_covid_4 = EnglandCovidDataLoader(redownload=True)
+
+    EnglandCovidDataCheck(eng_covid)
+    EnglandCovidDataCheck(eng_covid_1)
+    EnglandCovidDataCheck(eng_covid_3)
+    EnglandCovidDataCheck(eng_covid_4)
+
+    with pytest.raises(TypeError) as exec:
+        EnglandCovidDataLoader(lags="lags")
+    assert str(exec.value) == "lags must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        EnglandCovidDataLoader(lags=-1)
+    assert str(exec.value) == "lags must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        EnglandCovidDataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        EnglandCovidDataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
diff --git a/tests/dataset/static/test_CoraDataLoader.py b/tests/dataset/static/test_CoraDataLoader.py
new file mode 100644
index 00000000..97c644bf
--- /dev/null
+++ b/tests/dataset/static/test_CoraDataLoader.py
@@ -0,0 +1,26 @@
+from stgraph.dataset import CoraDataLoader
+
+
+def CoraDataCheck(cora: CoraDataLoader):
+    assert len(cora._edge_list) == 10556
+    assert cora._all_features.shape == (2708, 1433)
+    assert cora._all_targets.shape == (2708,)
+
+    assert cora.gdata["num_nodes"] == 2708
+    assert cora.gdata["num_edges"] == 10556
+    assert cora.gdata["num_feats"] == 1433
+    assert cora.gdata["num_classes"] == 7
+
+    edge_list = cora.get_edges()
+
+    assert len(edge_list) == 10556 and len(edge_list[0]) == 2
+    assert cora.get_all_features().shape == (2708, 1433)
+    assert cora.get_all_targets().shape == (2708,)
+
+
+def test_CoraDataLoader():
+    cora = CoraDataLoader(verbose=True)
+    cora_1 = CoraDataLoader(redownload=True)
+
+    CoraDataCheck(cora)
+    CoraDataCheck(cora_1)
diff --git a/tests/dataset/temporal/test_HungaryCPDataLoader.py b/tests/dataset/temporal/test_HungaryCPDataLoader.py
new file mode 100644
index 00000000..5dfd7bf7
--- /dev/null
+++ b/tests/dataset/temporal/test_HungaryCPDataLoader.py
@@ -0,0 +1,52 @@
+import pytest
+
+from stgraph.dataset import HungaryCPDataLoader
+
+
+def HungaryCPDataChecker(hungary: HungaryCPDataLoader):
+    assert hungary.gdata["total_timestamps"] == (
+        521 if not hungary._cutoff_time else hungary._cutoff_time
+    )
+
+    assert hungary.gdata["num_nodes"] == 20
+    assert hungary.gdata["num_edges"] == 102
+
+    edges = hungary.get_edges()
+    edge_weights = hungary.get_edge_weights()
+    all_targets = hungary.get_all_targets()
+
+    assert len(edges) == 102
+    assert len(edges[0]) == 2
+
+    assert len(edge_weights) == 102
+
+    assert len(all_targets) == hungary.gdata["total_timestamps"] - hungary._lags
+    assert all_targets[0].shape == (hungary.gdata["num_nodes"],)
+
+
+def test_HungaryCPDataLoader():
+    hungary_1 = HungaryCPDataLoader(verbose=True)
+    hungary_2 = HungaryCPDataLoader(lags=6)
+    hungary_3 = HungaryCPDataLoader(cutoff_time=100)
+    hungary_4 = HungaryCPDataLoader(redownload=True)
+
+    HungaryCPDataChecker(hungary_1)
+    HungaryCPDataChecker(hungary_2)
+    HungaryCPDataChecker(hungary_3)
+    HungaryCPDataChecker(hungary_4)
+
+    with pytest.raises(TypeError) as exec:
+        HungaryCPDataLoader(lags="lags")
+    assert str(exec.value) == "lags must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        HungaryCPDataLoader(lags=-1)
+    assert str(exec.value) == "lags must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        HungaryCPDataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        HungaryCPDataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
diff --git a/tests/dataset/temporal/test_METRLADataLoader.py b/tests/dataset/temporal/test_METRLADataLoader.py
new file mode 100644
index 00000000..00963745
--- /dev/null
+++ b/tests/dataset/temporal/test_METRLADataLoader.py
@@ -0,0 +1,59 @@
+import pytest
+from stgraph.dataset import METRLADataLoader
+
+
+def METRLADataCheck(metrla: METRLADataLoader):
+    assert metrla.gdata["total_timestamps"] == (
+        100 if not metrla._cutoff_time else metrla._cutoff_time
+    )
+
+    assert metrla.gdata["num_nodes"] == 207
+    assert metrla.gdata["num_edges"] == 1722
+
+    edges = metrla.get_edges()
+    edge_weights = metrla.get_edge_weights()
+    # all_targets = metrla.get_all_targets()
+    # all_features = metrla.get_all_features()
+
+    assert len(edges) == 1722
+    assert len(edges[0]) == 2
+
+    assert len(edge_weights) == 1722
+
+    # TODO: Check targets and features list
+
+
+def test_METRLADataLoader():
+    metrla_1 = METRLADataLoader(verbose=True)
+    metrla_2 = METRLADataLoader(redownload=True)
+    metrla_3 = METRLADataLoader(num_timesteps_in=8, num_timesteps_out=8)
+    metrla_4 = METRLADataLoader(cutoff_time=50)
+
+    METRLADataCheck(metrla_1)
+    METRLADataCheck(metrla_2)
+    METRLADataCheck(metrla_3)
+    METRLADataCheck(metrla_4)
+
+    with pytest.raises(TypeError) as exec:
+        METRLADataLoader(num_timesteps_in="num_timesteps_in")
+    assert str(exec.value) == "num_timesteps_in must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        METRLADataLoader(num_timesteps_in=-1)
+    assert str(exec.value) == "num_timesteps_in must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        METRLADataLoader(num_timesteps_out="num_timesteps_out")
+    assert str(exec.value) == "num_timesteps_out must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        METRLADataLoader(num_timesteps_out=-1)
+    assert str(exec.value) == "num_timesteps_out must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        METRLADataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        METRLADataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
diff --git a/tests/dataset/temporal/test_MontevideoBusDataLoader.py b/tests/dataset/temporal/test_MontevideoBusDataLoader.py
new file mode 100644
index 00000000..3562b69e
--- /dev/null
+++ b/tests/dataset/temporal/test_MontevideoBusDataLoader.py
@@ -0,0 +1,66 @@
+import pytest
+from stgraph.dataset import MontevideoBusDataLoader
+
+
+def MontevideoBusDataCheck(monte: MontevideoBusDataLoader):
+    assert monte.gdata["total_timestamps"] == (
+        744 if not monte._cutoff_time else monte._cutoff_time
+    )
+
+    assert monte.gdata["num_nodes"] == 675
+    assert monte.gdata["num_edges"] == 690
+
+    edges = monte.get_edges()
+    edge_weights = monte.get_edge_weights()
+    all_targets = monte.get_all_targets()
+    all_features = monte.get_all_features()
+
+    assert len(edges) == 690
+
+    for edge in edges:
+        assert len(edge) == 2
+
+    assert len(edge_weights) == 690
+
+    assert all_features.shape == (
+        monte.gdata["total_timestamps"] - monte._lags,
+        monte.gdata["num_nodes"],
+        monte._lags,
+    )
+
+    assert all_targets.shape == (
+        monte.gdata["total_timestamps"] - monte._lags,
+        monte.gdata["num_nodes"],
+    )
+
+
+def test_MontevideoBusDataLoader():
+    monte_1 = MontevideoBusDataLoader(verbose=True)
+    monte_2 = MontevideoBusDataLoader(redownload=True)
+    monte_3 = MontevideoBusDataLoader(lags=6)
+    monte_4 = MontevideoBusDataLoader(cutoff_time=50)
+
+    MontevideoBusDataCheck(monte_1)
+    MontevideoBusDataCheck(monte_2)
+    MontevideoBusDataCheck(monte_3)
+    MontevideoBusDataCheck(monte_4)
+
+    with pytest.raises(TypeError) as exec:
+        MontevideoBusDataLoader(lags="lags")
+    assert str(exec.value) == "lags must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        MontevideoBusDataLoader(lags=-1)
+    assert str(exec.value) == "lags must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        MontevideoBusDataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        MontevideoBusDataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
+
+    with pytest.raises(ValueError) as exec:
+        MontevideoBusDataLoader(cutoff_time=4)
+    assert str(exec.value) == "cutoff_time must be greater than lags"
diff --git a/tests/dataset/temporal/test_PedalMeDataLoader.py b/tests/dataset/temporal/test_PedalMeDataLoader.py
new file mode 100644
index 00000000..930060b7
--- /dev/null
+++ b/tests/dataset/temporal/test_PedalMeDataLoader.py
@@ -0,0 +1,59 @@
+import pytest
+from stgraph.dataset import PedalMeDataLoader
+
+
+def PedalMeDataCheck(pedal: PedalMeDataLoader):
+    assert pedal.gdata["total_timestamps"] == (
+        36 if not pedal._cutoff_time else pedal._cutoff_time
+    )
+
+    assert pedal.gdata["num_nodes"] == 15
+    assert pedal.gdata["num_edges"] == 225
+
+    edges = pedal.get_edges()
+    edge_weights = pedal.get_edge_weights()
+    all_targets = pedal.get_all_targets()
+
+    assert len(edges) == 225
+
+    for edge in edges:
+        assert len(edge) == 2
+
+    assert len(edge_weights) == 225
+
+    assert all_targets.shape == (
+        pedal.gdata["total_timestamps"] - pedal._lags,
+        pedal.gdata["num_nodes"],
+    )
+
+
+def test_PedalMeDataLoader():
+    pedal_1 = PedalMeDataLoader(verbose=True)
+    pedal_2 = PedalMeDataLoader(redownload=True)
+    pedal_3 = PedalMeDataLoader(lags=6)
+    pedal_4 = PedalMeDataLoader(cutoff_time=20)
+
+    PedalMeDataCheck(pedal_1)
+    PedalMeDataCheck(pedal_2)
+    PedalMeDataCheck(pedal_3)
+    PedalMeDataCheck(pedal_4)
+
+    with pytest.raises(TypeError) as exec:
+        PedalMeDataLoader(lags="lags")
+    assert str(exec.value) == "lags must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        PedalMeDataLoader(lags=-1)
+    assert str(exec.value) == "lags must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        PedalMeDataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        PedalMeDataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
+
+    with pytest.raises(ValueError) as exec:
+        PedalMeDataLoader(cutoff_time=4)
+    assert str(exec.value) == "cutoff_time must be greater than lags"
diff --git a/tests/dataset/temporal/test_WikiMathDataLoader.py b/tests/dataset/temporal/test_WikiMathDataLoader.py
new file mode 100644
index 00000000..ef002837
--- /dev/null
+++ b/tests/dataset/temporal/test_WikiMathDataLoader.py
@@ -0,0 +1,52 @@
+import pytest
+from stgraph.dataset import WikiMathDataLoader
+
+
+def WikiMathDataCheck(wiki: WikiMathDataLoader):
+    assert wiki.gdata["total_timestamps"] == (
+        731 if not wiki._cutoff_time else wiki._cutoff_time
+    )
+
+    assert wiki.gdata["num_nodes"] == 1068
+    assert wiki.gdata["num_edges"] == 27079
+
+    edges = wiki.get_edges()
+    edge_weights = wiki.get_edge_weights()
+    all_targets = wiki.get_all_targets()
+
+    assert len(edges) == 27079
+
+    for edge in edges:
+        assert len(edge) == 2
+
+    assert len(edge_weights) == 27079
+
+    # TODO: Add tests for features and targets arrays
+
+
+def test_WikiMathDataLoader():
+    wiki_1 = WikiMathDataLoader(verbose=True)
+    wiki_2 = WikiMathDataLoader(redownload=True)
+    wiki_3 = WikiMathDataLoader(lags=4)
+    wiki_4 = WikiMathDataLoader(cutoff_time=500)
+
+    WikiMathDataCheck(wiki_1)
+    WikiMathDataCheck(wiki_2)
+    WikiMathDataCheck(wiki_3)
+    WikiMathDataCheck(wiki_4)
+
+    with pytest.raises(TypeError) as exec:
+        WikiMathDataLoader(lags="lags")
+    assert str(exec.value) == "lags must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        WikiMathDataLoader(lags=-1)
+    assert str(exec.value) == "lags must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        WikiMathDataLoader(cutoff_time="time")
+    assert str(exec.value) == "cutoff_time must be of type int"
+
+    with pytest.raises(ValueError) as exec:
+        WikiMathDataLoader(cutoff_time=-1)
+    assert str(exec.value) == "cutoff_time must be a positive integer"
diff --git a/tests/dataset/temporal/test_WindmillOutputDataLoader.py b/tests/dataset/temporal/test_WindmillOutputDataLoader.py
new file mode 100644
index 00000000..368935fc
--- /dev/null
+++ b/tests/dataset/temporal/test_WindmillOutputDataLoader.py
@@ -0,0 +1,77 @@
+import pytest
+from stgraph.dataset import WindmillOutputDataLoader
+
+
+def WindmillOutputDataCheck(wind: WindmillOutputDataLoader):
+    assert wind.gdata["total_timestamps"] == (
+        17472 if not wind._cutoff_time else wind._cutoff_time
+    )
+
+    if wind._size == "large":
+        assert wind.gdata["num_nodes"] == 319
+        assert wind.gdata["num_edges"] == 101761
+    elif wind._size == "medium":
+        assert wind.gdata["num_nodes"] == 26
+        assert wind.gdata["num_edges"] == 676
+    elif wind._size == "small":
+        assert wind.gdata["num_nodes"] == 11
+        assert wind.gdata["num_edges"] == 121
+
+    edges = wind.get_edges()
+    edge_weights = wind.get_edge_weights()
+    all_targets = wind.get_all_targets()
+
+    if wind._size == "large":
+        assert len(edges) == 101761
+        assert len(edge_weights) == 101761
+    elif wind._size == "medium":
+        assert len(edges) == 676
+        assert len(edge_weights) == 676
+    elif wind._size == "small":
+        assert len(edges) == 121
+        assert len(edge_weights) == 121
+
+    for edge in edges:
+        len(edge) == 2
+
+    # TODO: Test for targets and features
+
+
+def test_WindmillOutputDataLoader():
+    for size in ["large", "medium", "small"]:
+        wind_1 = WindmillOutputDataLoader(verbose=True, size=size)
+        wind_2 = WindmillOutputDataLoader(redownload=True, size=size)
+        wind_3 = WindmillOutputDataLoader(lags=4, size=size)
+        wind_4 = WindmillOutputDataLoader(cutoff_time=100, size=size)
+
+        WindmillOutputDataCheck(wind_1)
+        WindmillOutputDataCheck(wind_2)
+        WindmillOutputDataCheck(wind_3)
+        WindmillOutputDataCheck(wind_4)
+
+        with pytest.raises(TypeError) as exec:
+            WindmillOutputDataLoader(lags="lags", size=size)
+        assert str(exec.value) == "lags must be of type int"
+
+        with pytest.raises(ValueError) as exec:
+            WindmillOutputDataLoader(lags=-1, size=size)
+        assert str(exec.value) == "lags must be a positive integer"
+
+        with pytest.raises(TypeError) as exec:
+            WindmillOutputDataLoader(cutoff_time="time", size=size)
+        assert str(exec.value) == "cutoff_time must be of type int"
+
+        with pytest.raises(ValueError) as exec:
+            WindmillOutputDataLoader(cutoff_time=-1, size=size)
+        assert str(exec.value) == "cutoff_time must be a positive integer"
+
+    with pytest.raises(TypeError) as exec:
+        WindmillOutputDataLoader(size=1)
+    assert str(exec.value) == "size must be of type string"
+
+    with pytest.raises(ValueError) as exec:
+        WindmillOutputDataLoader(size="big")
+    assert (
+        str(exec.value) == "size must take either of the following values : "
+        "large, medium or small"
+    )
diff --git a/tests/graph/dynamic/test_DynamicGraphPCSR.py b/tests/graph/dynamic/test_DynamicGraphPCSR.py
deleted file mode 100644
index e0a824d5..00000000
--- a/tests/graph/dynamic/test_DynamicGraphPCSR.py
+++ /dev/null
@@ -1,278 +0,0 @@
-import json
-from rich import inspect
-
-from stgraph.graph.dynamic.pcsr.PCSRGraph import PCSRGraph
-from stgraph.graph.static.csr import get_dev_array
-
-class TestDynamicGraphPCSR:
-    # 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]],
-    ]
-
-    # sorted based on second-element, then first-element
-    sorted_edge_index = [
-        [[0, 1], [1, 2], [2, 3]],
-        [[3, 1], [1, 2], [2, 3]],
-        [[3, 0], [3, 1], [1, 2], [2, 3]],
-        [[3, 0], [0, 1], [3, 1], [2, 3]],
-    ]
-
-    # 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_graph_attr(self):
-        pcsr_graph = PCSRGraph(edge_list=self.sorted_edge_index, max_num_nodes=4)
-        graph_attr = pcsr_graph._get_graph_attr(edge_list=self.sorted_edge_index)
-
-        # checking if the size of graph_attr = 4, which is the
-        # total number of timestamps present
-        assert len(graph_attr) == 4
-
-        # checking the (num_nodes, num_edges) pair for each timestamp
-        assert graph_attr["0"] == (4, 3)
-        assert graph_attr["1"] == (4, 3)
-        assert graph_attr["2"] == (4, 4)
-        assert graph_attr["3"] == (4, 4)
-
-    def test_preprocess_graph_structure(self):
-        pcsr_graph = PCSRGraph(edge_list=self.sorted_edge_index, max_num_nodes=4)
-
-        # checking graph_updates for t = 0
-        graph_updates_0 = pcsr_graph.graph_updates["0"]
-        assert len(graph_updates_0["add"]) == 3
-        for edge in [(0, 1), (1, 2), (2, 3)]:
-            assert edge in graph_updates_0["add"]
-
-        assert len(graph_updates_0["delete"]) == 0
-
-        assert graph_updates_0["num_nodes"] == 4
-        assert graph_updates_0["num_edges"] == 3
-
-        # checking graph_updates for t = 1
-        graph_updates_1 = pcsr_graph.graph_updates["1"]
-        assert len(graph_updates_1["add"]) == 1
-        assert (3, 1) in graph_updates_1["add"]
-
-        assert len(graph_updates_1["delete"]) == 1
-        assert (0, 1) in graph_updates_1["delete"]
-
-        assert graph_updates_1["num_nodes"] == 4
-        assert graph_updates_1["num_edges"] == 3
-
-        # checking graph_updates for t = 2
-        graph_updates_2 = pcsr_graph.graph_updates["2"]
-
-        assert len(graph_updates_2["add"]) == 1
-        assert (3, 0) in graph_updates_2["add"]
-
-        assert len(graph_updates_2["delete"]) == 0
-
-        assert graph_updates_2["num_nodes"] == 4
-        assert graph_updates_2["num_edges"] == 4
-
-        # checking graph_updates for t = 3
-        graph_updates_3 = pcsr_graph.graph_updates["3"]
-
-        assert len(graph_updates_3["add"]) == 1
-        assert (0, 1) in graph_updates_3["add"]
-
-        assert len(graph_updates_3["delete"]) == 1
-        assert (1, 2) in graph_updates_3["delete"]
-
-        assert graph_updates_3["num_nodes"] == 4
-        assert graph_updates_3["num_edges"] == 4
-
-    def test_get_graph(self):
-        pcsr_graph = PCSRGraph(edge_list=self.sorted_edge_index, max_num_nodes=4)
-
-        # for time = 0
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.fwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.fwd_column_indices_ptr, 3),
-            get_dev_array(pcsr_graph.fwd_eids_ptr, 3),
-        )
-
-        assert row_offset == [0, 0, 1, 2, 3]
-        assert column_indices == [0, 1, 2]
-        assert eids == [0, 1, 2]
-
-        # for time = 1
-        pcsr_graph.get_graph(1)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.fwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.fwd_column_indices_ptr, 3),
-            get_dev_array(pcsr_graph.fwd_eids_ptr, 3),
-        )
-
-        assert row_offset == [0, 0, 1, 2, 3]
-        assert column_indices == [3, 1, 2]
-        assert eids == [0, 1, 2]
-
-        # for time = 2
-        pcsr_graph.get_graph(2)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.fwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.fwd_column_indices_ptr, 4),
-            get_dev_array(pcsr_graph.fwd_eids_ptr, 4),
-        )
-
-        assert row_offset == [0, 1, 2, 3, 4]
-        assert column_indices == [3, 3, 1, 2]
-        assert eids == [0, 1, 2, 3]
-
-        # for time = 3
-        pcsr_graph.get_graph(3)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.fwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.fwd_column_indices_ptr, 4),
-            get_dev_array(pcsr_graph.fwd_eids_ptr, 4),
-        )
-
-        assert row_offset == [0, 1, 3, 3, 4]
-        assert column_indices == [3, 0, 3, 2]
-        assert eids == [0, 1, 2, 3]
-
-    def test_get_backward_graph(self):
-        
-        pcsr_graph = PCSRGraph(edge_list=self.sorted_edge_index, max_num_nodes=4)
-        pcsr_graph.get_graph(3)
-
-        # for time = 3
-        pcsr_graph.get_backward_graph(3)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.bwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.bwd_column_indices_ptr, 4),
-            get_dev_array(pcsr_graph.bwd_eids_ptr, 4),
-        )
-        
-        assert row_offset == [0,1,1,2,4]
-        assert column_indices == [1,3,0,1]
-        assert eids == [1,3,0,2]
-        
-        # for time = 2
-        pcsr_graph.get_backward_graph(2)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.bwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.bwd_column_indices_ptr, 4),
-            get_dev_array(pcsr_graph.bwd_eids_ptr, 4),
-        )
-        
-        assert row_offset == [0,0,1,2,4]
-        assert column_indices == [2,3,0,1]
-        assert eids == [2,3,0,1]
-        
-        # for time = 1
-        pcsr_graph.get_backward_graph(1)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.bwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.bwd_column_indices_ptr, 3),
-            get_dev_array(pcsr_graph.bwd_eids_ptr, 3),
-        )
-        
-        assert row_offset == [0,0,1,2,3]
-        assert column_indices == [2,3,1]
-        assert eids == [1,2,0]
-        
-        # for time = 0
-        pcsr_graph.get_backward_graph(0)
-        
-        row_offset, column_indices, eids = (
-            get_dev_array(pcsr_graph.bwd_row_offset_ptr, 5),
-            get_dev_array(pcsr_graph.bwd_column_indices_ptr, 3),
-            get_dev_array(pcsr_graph.bwd_eids_ptr, 3),
-        )
-        
-        assert row_offset == [0,1,2,3,3]
-        assert column_indices == [1,2,3]
-        assert eids == [0,1,2]
-
-    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
-        pcsr_graph = PCSRGraph(edge_list=self.edge_index, max_num_nodes=4)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # graph: t = 1
-        pcsr_graph.get_graph(1)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # graph: t = 2
-        pcsr_graph.get_graph(2)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # graph: t = 3
-        pcsr_graph.get_graph(3)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # Now moving the graph in the backward direction
-        # graph: t = 2
-        pcsr_graph.get_backward_graph(2)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # graph: t = 1
-        pcsr_graph.get_backward_graph(1)
-        assert pcsr_graph.get_num_nodes() == 4
-
-        # graph: t = 1
-        pcsr_graph.get_backward_graph(0)
-        assert pcsr_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
-        pcsr_graph = PCSRGraph(edge_list=self.edge_index, max_num_nodes=4)
-        assert pcsr_graph.get_num_edges() == 3
-
-        # graph: t = 1
-        pcsr_graph.get_graph(1)
-        assert pcsr_graph.get_num_edges() == 3
-
-        # graph: t = 2
-        pcsr_graph.get_graph(2)
-        assert pcsr_graph.get_num_edges() == 4
-
-        # graph: t = 3
-        pcsr_graph.get_graph(3)
-        assert pcsr_graph.get_num_edges() == 4
-
-        # Now moving the graph in the backward direction
-        # graph: t = 2
-        pcsr_graph.get_backward_graph(2)
-        assert pcsr_graph.get_num_edges() == 4
-
-        # graph: t = 1
-        pcsr_graph.get_backward_graph(1)
-        assert pcsr_graph.get_num_edges() == 3
-
-        # graph: t = 1
-        pcsr_graph.get_backward_graph(0)
-        assert pcsr_graph.get_num_edges() == 3
\ No newline at end of file
diff --git a/tests/scripts/README.md b/tests/scripts/README.md
new file mode 100644
index 00000000..13e0258a
--- /dev/null
+++ b/tests/scripts/README.md
@@ -0,0 +1,78 @@
+# STGraph Script Testing
+
+Within this directory are scripts intended for manual execution by users or developers outside of the automated testing suite for STGraph. These scripts are primarily designed to assess the functionality of the following modules, whose correctness cannot be directly unit tested using PyTest. A table containing such modules can be found in the [List of Modules Tested](#list-of-modules-tested) section.
+
+Additional scripts may be added as the project evolves.
+
+## Usage
+
+To execute the script tests, utilize the following command:
+
+```
+python3 stgraph_script.py [-h] [-v | --version VERSION] [-t | --testpack-names TESTPACK_NAMES]
+```
+
+For instance, to evaluate the GCN Dataloaders for STGraph version v1.1.0, execute:
+
+```
+python3 stgraph_script.py -v 1_1_0 -t gcn_dataloaders
+```
+
+Please ensure that the exact version of STGraph is installed within your virtual environment prior to conducting these tests.
+
+### Command Line Arguments
+
+| Argument             | Description                                            | Possible Values                                |
+| -------------------- | ------------------------------------------------------ | ---------------------------------------------- |
+| -h, --help           | Obtain a brief description of the command              | -                                              |
+| -v, --version        | Specify the version of the STGraph testpack to execute | `1_1_0`                                        |
+| -t, --testpack-names | Provide a list of testpack names                       | `gcn_dataloaders`, `temporal_tgcn_dataloaders` |
+
+## A Note to the Developers
+
+The rest of the document outlines essential procedures that developers, contributors, and maintainers must follow for the STGraph project. As the project progresses, it is imperative to keep the testing script updated with new functionalities to prevent any potential issues for end-users.
+
+### Post-Release Protocol
+
+Following the release of a new version of STGraph, code owners are tasked with maintaining the integrity of the testpack folders corresponding to each version. These folders are located within the directory `tests/scripts`.
+
+For instance, upon the release of STGraph version `v1.1.0`, and with the subsequent planning of version `v1.2.0`, the following steps are to be taken:
+
+1. **Creation of New Test Pack:** A copy of the current version's testpack folder `v1_1_0` should be created and renamed to reflect the upcoming version `v1_2_0`.
+2. **Development Phase Updates:** Any further enhancements or additions to the test scripts during the development phase of `v1.2.0` must be implemented within the designated folder. 
+
+```
+tests/
+└── scripts
+    ├── v1_1_0
+    └── v1_2_0
+```
+
+By adhering to this protocol, the project maintains a structured and reliable testing framework, ensuring correctness and stability across successive releases.
+
+### Test Script Creation Protocol
+
+When preparing to write test scripts for newly implemented functionalities, ensure adherence to the following protocol
+
+1. **Choose a Descriptive Name:** Select a meaningful and self-documenting name for your testpack, following the lower snake-case convention.
+2. **Create Testpack Folder**: Within the current development version of STGraph, create a folder bearing the chosen testpack name.
+3. **Script Creation**: Craft a Python script named `<testpack_name>.py`, containing the necessary testing logic.
+4. **Supplementary Files**: Include any additional folders and files deemed necessary for testing within the testpack folder.
+5. **Status Reporting**: Ensure that the Python script incorporates a mechanism to display the status of test cases effectively on the screen.
+6. **Integration with STGraph Script**: Incorporate your newly created testpack as a selectable option for the `--testpack-names` command-line argument within `stgraph_script.py`
+
+To get a better idea on how to develop your scripts, it is advised to refer to previously maintained scripts. This practice ensures uniformity and easier maintenance across all scripts.
+
+## List of Modules Tested
+
+| Module                   | Test Pack Name            | Initial Version Release |
+| ------------------------ | ------------------------- | ----------------------- |
+| GraphConv                | gcn_dataloaders           | `v1.1.0`                |
+| CoraDataLoader           | gcn_dataloaders           | `v1.1.0`                |
+| TGCN                     | temporal_tgcn_dataloaders | `v1.1.0`                |
+| HungaryCPDataLoader      | temporal_tgcn_dataloaders | `v1.1.0`                |
+| METRLADataLoader         | temporal_tgcn_dataloaders | `v1.1.0`                |
+| MontevideoBusDataLoader  | temporal_tgcn_dataloaders | `v1.1.0`                |
+| PedalMeDataLoader        | temporal_tgcn_dataloaders | `v1.1.0`                |
+| WikiMathDataLoader       | temporal_tgcn_dataloaders | `v1.1.0`                |
+| WindmillOutputDataLoader | temporal_tgcn_dataloaders | `v1.1.0`                |
\ No newline at end of file
diff --git a/tests/scripts/stgraph_script.py b/tests/scripts/stgraph_script.py
new file mode 100644
index 00000000..b1236fa0
--- /dev/null
+++ b/tests/scripts/stgraph_script.py
@@ -0,0 +1,50 @@
+import argparse
+import os
+import subprocess
+
+
+def main(args):
+    version_number = args.version
+    testpack_names = args.testpack_names
+
+    for testpack in testpack_names:
+        script_path = "v" + version_number + "/" + testpack + "/" + testpack + ".py"
+        output_folder_path = "v" + version_number + "/" + testpack + "/outputs"
+
+        # create the script outputs folder if it doesn't exist
+        if not os.path.exists(output_folder_path):
+            try:
+                os.makedirs(output_folder_path)
+            except OSError as e:
+                print(f"Failed to create the script outputs folder at {output_folder_path}: {e}")
+
+        if os.path.exists(script_path):
+            subprocess.run(["python3", script_path, "-o", output_folder_path])
+        else:
+            print(f"Script {script_path} doesn't exists")
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="STGraph Test Scripts")
+
+    parser.add_argument(
+        "-v",
+        "--version",
+        type=str,
+        default="1_1_0",
+        choices=["1_1_0"],
+        help="Version of STGraph",
+    )
+
+    parser.add_argument(
+        "-t",
+        "--testpack-names",
+        nargs="*",
+        default=["temporal_tgcn_dataloaders", "gcn_dataloaders"],
+        choices=["temporal_tgcn_dataloaders", "gcn_dataloaders"],
+        help="Names of the testpacks to be executed",
+    )
+
+    args = parser.parse_args()
+
+    main(args=args)
diff --git a/stgraph/benchmark_tools/__init__.py b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/__init__.py
similarity index 100%
rename from stgraph/benchmark_tools/__init__.py
rename to tests/scripts/v1_1_0/gcn_dataloaders/gcn/__init__.py
diff --git a/tests/scripts/v1_1_0/gcn_dataloaders/gcn/model.py b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/model.py
new file mode 100644
index 00000000..231e78c8
--- /dev/null
+++ b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/model.py
@@ -0,0 +1,27 @@
+import torch.nn as nn
+from stgraph.nn.pytorch.static.gcn_conv import GCNConv
+
+class GCN(nn.Module):
+    def __init__(self,
+                 g,
+                 in_feats,
+                 n_hidden,
+                 n_classes,
+                 n_layers,
+                 activation):
+        super(GCN, self).__init__()
+        self.g = g
+        self.layers = nn.ModuleList()
+        # input layer
+        self.layers.append(GCNConv(in_feats, n_hidden, activation))
+        # hidden layers
+        for i in range(n_layers - 1):
+            self.layers.append(GCNConv(n_hidden, n_hidden, activation))
+        # output layer
+        self.layers.append(GCNConv(n_hidden, n_classes, None))
+
+    def forward(self, g, features):
+        h = features
+        for layer in self.layers:
+            h = layer(g, h)
+        return h
\ No newline at end of file
diff --git a/tests/scripts/v1_1_0/gcn_dataloaders/gcn/train.py b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/train.py
new file mode 100644
index 00000000..9424aa12
--- /dev/null
+++ b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/train.py
@@ -0,0 +1,144 @@
+import time
+import traceback
+
+import numpy as np
+import pynvml
+import torch
+import torch.nn.functional as F
+from rich.progress import Progress
+
+from stgraph.utils import DataTable
+from stgraph.dataset import CoraDataLoader
+from stgraph.graph.static.static_graph import StaticGraph
+from .model import GCN
+from .utils import accuracy, generate_test_mask, generate_train_mask, \
+    to_default_device
+
+
+def train(
+    dataset: str,
+    lr: float,
+    num_epochs: int,
+    num_hidden: int,
+    num_layers: int,
+    weight_decay: float,
+    self_loops: bool,
+    output_file_path: str,
+) -> int:
+    with open(output_file_path, "w") as f:
+        if torch.cuda.is_available():
+            print("🎉 CUDA is available", file=f)
+        else:
+            print("😔 CUDA is not available", file=f)
+            return 1
+
+        tmp = StaticGraph([(0, 0)], [1], 1)
+
+        if dataset == "Cora":
+            dataloader = CoraDataLoader()
+        else:
+            print("😔 Unrecognized dataset", file=f)
+            return 1
+
+        features = torch.FloatTensor(dataloader.get_all_features())
+        labels = torch.LongTensor(dataloader.get_all_targets())
+
+        train_mask = generate_train_mask(len(features), 0.6)
+        test_mask = generate_test_mask(len(features), 0.6)
+
+        train_mask = torch.BoolTensor(train_mask)
+        test_mask = torch.BoolTensor(test_mask)
+
+        cuda = True
+        torch.cuda.set_device(0)
+        features = features.cuda()
+        labels = labels.cuda()
+        train_mask = train_mask.cuda()
+        test_mask = test_mask.cuda()
+        edge_weight = [1 for _ in range(len(dataloader.get_edges()))]
+
+        pynvml.nvmlInit()
+        handle = pynvml.nvmlDeviceGetHandleByIndex(0)
+        initial_used_gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used
+        g = StaticGraph(dataloader.get_edges(), edge_weight, features.shape[0])
+        graph_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used - initial_used_gpu_mem
+
+        degs = torch.from_numpy(g.weighted_in_degrees()).type(torch.int32)
+        norm = torch.pow(degs, -0.5)
+        norm[torch.isinf(norm)] = 0
+        norm = to_default_device(norm)
+        g.set_ndata("norm", norm.unsqueeze(1))
+
+        num_feats = features.shape[1]
+        n_classes = int(max(labels) - min(labels) + 1)
+
+        model = GCN(g, num_feats, num_hidden, n_classes, num_layers, F.relu)
+        model.cuda()
+
+        loss_fcn = torch.nn.CrossEntropyLoss()
+        optimizer = torch.optim.Adam(
+            model.parameters(), lr=lr, weight_decay=weight_decay
+        )
+
+        dur = []
+        Used_memory = 0
+        table = DataTable(
+            f"STGraph GCN on {dataloader.name} dataset",
+            ["Epoch", "Time(s)", "Train Accuracy", "Used GPU Memory (Max MB)"],
+        )
+
+        try:
+            with Progress() as progress:
+                epoch_progress = progress.add_task(
+                    f"{dataset}",
+                    total=num_epochs
+                )
+                for epoch in range(num_epochs):
+                    torch.cuda.reset_peak_memory_stats(0)
+                    model.train()
+                    if cuda:
+                        torch.cuda.synchronize()
+                    t0 = time.time()
+
+                    # forward
+                    logits = model(g, features)
+                    loss = loss_fcn(logits[train_mask], labels[train_mask])
+                    optimizer.zero_grad()
+                    loss.backward()
+                    optimizer.step()
+
+                    now_mem = torch.cuda.max_memory_allocated(0) + graph_mem
+                    Used_memory = max(now_mem, Used_memory)
+
+                    if cuda:
+                        torch.cuda.synchronize()
+
+                    run_time_this_epoch = time.time() - t0
+
+                    if epoch >= 3:
+                        dur.append(run_time_this_epoch)
+
+                    train_acc = accuracy(logits[train_mask], labels[train_mask])
+                    table.add_row(
+                        [epoch, run_time_this_epoch, train_acc, (now_mem * 1.0 / (1024**2))]
+                    )
+
+                    progress.update(epoch_progress, advance=1)
+
+            table.display(output_file=f)
+            print("Average Time taken: {:6f}".format(np.mean(dur)), file=f)
+            return 0
+
+        except Exception as e:
+            print("---------------- Error ----------------\n", file=f)
+            print(e, file=f)
+            print("\n", file=f)
+
+            traceback.print_exc(file=f)
+            print("\n", file=f)
+
+            if "out of memory" in str(e):
+                table.add_row(["OOM", "OOM", "OOM", "OOM"])
+                table.display(output_file=f)
+
+            return 1
diff --git a/tests/scripts/v1_1_0/gcn_dataloaders/gcn/utils.py b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/utils.py
new file mode 100644
index 00000000..61d71078
--- /dev/null
+++ b/tests/scripts/v1_1_0/gcn_dataloaders/gcn/utils.py
@@ -0,0 +1,32 @@
+import torch
+
+
+def accuracy(logits, labels):
+    _, indices = torch.max(logits, dim=1)
+    correct = torch.sum(indices == labels)
+    return correct.item() * 1.0 / len(labels)
+
+
+# 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 generate_train_mask(size: int, train_test_split: float) -> list:
+    cutoff = size * train_test_split
+    return [1 if i < cutoff else 0 for i in range(size)]
+
+
+def generate_test_mask(size: int, train_test_split: float) -> list:
+    cutoff = size * train_test_split
+    return [0 if i < cutoff else 1 for i in range(size)]
diff --git a/tests/scripts/v1_1_0/gcn_dataloaders/gcn_dataloaders.py b/tests/scripts/v1_1_0/gcn_dataloaders/gcn_dataloaders.py
new file mode 100644
index 00000000..f19ddb14
--- /dev/null
+++ b/tests/scripts/v1_1_0/gcn_dataloaders/gcn_dataloaders.py
@@ -0,0 +1,84 @@
+import os
+import subprocess
+import argparse
+
+from rich.console import Console
+from rich.table import Table
+from gcn.train import train
+
+console = Console()
+
+
+def main(args):
+    output_folder_path = args.output
+
+    testpack_properties = {
+        "Name": "GCN",
+        "Description": "Testing the GCN model on static datasets",
+    }
+
+    console.rule(
+        f"[bold yellow]{testpack_properties['Name']}: {testpack_properties['Description']}"
+    )
+
+    # if the value is set to "Y", then the tests are executed for the given
+    # dataset. Else if set to "N", then it is ignored.
+    gcn_datasets = {
+        "Cora": "Y",
+    }
+
+    dataset_results = {}
+
+    for dataset_name, execute_choice in gcn_datasets.items():
+        if execute_choice == "Y":
+
+            output_file_path = output_folder_path + "/" + dataset_name + ".txt"
+            if os.path.exists(output_file_path):
+                os.remove(output_file_path)
+
+            result = train(
+                dataset=dataset_name,
+                num_hidden=16,
+                lr=0.01,
+                num_epochs=200,
+                num_layers=1,
+                weight_decay=5e-4,
+                self_loops=False,
+                output_file_path=output_file_path,
+            )
+
+            dataset_results[dataset_name] = result
+
+
+    table = Table(title="GCN Results")
+
+    table.add_column("Dataset", justify="right")
+    table.add_column("Status", justify="left")
+
+    for dataset_name, result in dataset_results.items():
+
+        if result == 0:
+            table.add_row(dataset_name, "✅")
+        else:
+            table.add_row(dataset_name, "❌")
+
+    print("")
+    console.print(table)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="STGraph Test Script for gcn_dataloaders"
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output",
+        type=str,
+        default="outputs",
+        help="Path to the outputs folder",
+    )
+
+    args = parser.parse_args()
+
+    main(args=args)
diff --git a/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/temporal_tgcn_dataloaders.py b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/temporal_tgcn_dataloaders.py
new file mode 100644
index 00000000..47961b8e
--- /dev/null
+++ b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/temporal_tgcn_dataloaders.py
@@ -0,0 +1,91 @@
+import os
+import subprocess
+import argparse
+
+from rich.console import Console
+from rich.table import Table
+from tgcn.train import train
+
+console = Console()
+
+
+def main(args):
+    output_folder_path = args.output
+
+    testpack_properties = {
+        "Name": "Temporal TGCN",
+        "Description": "Testing the TGCN model on temporal datasets",
+    }
+
+    console.rule(
+        f"[bold yellow]{testpack_properties['Name']}: {testpack_properties['Description']}"
+    )
+
+    # for prop_name, prop_value in testpack_properties.items():
+    #     console.print(f"[cyan bold]{prop_name}[/cyan bold] : {prop_value}")
+
+    # if the value is set to "Y", then the tests are executed for the given
+    # dataset. Else if set to "N", then it is ignored.
+    temporal_datasets = {
+        "Hungary_Chickenpox": "Y",
+        "METRLA": "Y",
+        "Montevideo_Bus": "Y",
+        "PedalMe": "Y",
+        "WikiMath": "Y",
+        "WindMill_large": "Y",
+    }
+
+    dataset_results = {}
+
+    for dataset_name, execute_choice in temporal_datasets.items():
+        if execute_choice == "Y":
+
+            output_file_path = output_folder_path + "/" + dataset_name + ".txt"
+            if os.path.exists(output_file_path):
+                os.remove(output_file_path)
+
+            result = train(
+                dataset=dataset_name,
+                num_hidden=16,
+                feat_size=8,
+                lr=1e-4,
+                backprop_every=0,
+                num_epochs=15,
+                output_file_path=output_file_path,
+            )
+
+            dataset_results[dataset_name] = result
+
+    # printing the summary of the run
+    table = Table(title="Temporal-TGCN Results")
+
+    table.add_column("Dataset", justify="right")
+    table.add_column("Status", justify="left")
+
+    for dataset_name, result in dataset_results.items():
+
+        if result == 0:
+            table.add_row(dataset_name, "✅")
+        else:
+            table.add_row(dataset_name, "❌")
+
+    print("")
+    console.print(table)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="STGraph Test Script for temporal_tgcn_dataloaders"
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output",
+        type=str,
+        default="outputs",
+        help="Path to the outputs folder",
+    )
+
+    args = parser.parse_args()
+
+    main(args=args)
diff --git a/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/__init__.py b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/model.py b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/model.py
new file mode 100644
index 00000000..22e199c0
--- /dev/null
+++ b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/model.py
@@ -0,0 +1,18 @@
+import torch
+import torch.nn.functional as F
+from stgraph.nn.pytorch.temporal.tgcn import TGCN
+
+
+class STGraphTGCN(torch.nn.Module):
+    def __init__(self, node_features, num_hidden_units, out_features):
+        super(STGraphTGCN, self).__init__()
+        self.temporal = TGCN(node_features, num_hidden_units)
+        self.linear = torch.nn.Linear(num_hidden_units, node_features)
+        self.linear2 = torch.nn.Linear(node_features, out_features)
+
+    def forward(self, g, node_feat, edge_weight, hidden_state):
+        h = self.temporal(g, node_feat, edge_weight, hidden_state)
+        y = F.relu(h)
+        y = self.linear(y)
+        y_out = self.linear2(y)
+        return y_out, y, h
diff --git a/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/train.py b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/train.py
new file mode 100644
index 00000000..107e335a
--- /dev/null
+++ b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/train.py
@@ -0,0 +1,213 @@
+import time
+import traceback
+
+import numpy as np
+import pynvml
+import torch
+from rich.progress import Progress
+
+from stgraph.utils import DataTable
+from stgraph.dataset import HungaryCPDataLoader
+from stgraph.dataset import METRLADataLoader
+from stgraph.dataset import MontevideoBusDataLoader
+from stgraph.dataset import PedalMeDataLoader
+from stgraph.dataset import WikiMathDataLoader
+from stgraph.dataset import WindmillOutputDataLoader
+from stgraph.graph.static.static_graph import StaticGraph
+from .model import STGraphTGCN
+from .utils import to_default_device, get_default_device, init_weights
+
+
+def train(
+        dataset: str,
+        num_hidden: int,
+        feat_size: int,
+        lr: float,
+        backprop_every: int,
+        num_epochs: int,
+        output_file_path: str,
+) -> int:
+    with open(output_file_path, "w") as f:
+        if torch.cuda.is_available():
+            print("🎉 CUDA is available", file=f)
+        else:
+            print("😔 CUDA is not available", file=f)
+            return 1
+
+        Graph = StaticGraph([(0, 0)], [1], 1)
+
+        if dataset == "WikiMath":
+            dataloader = WikiMathDataLoader(cutoff_time=100)
+        elif dataset == "WindMill_large":
+            dataloader = WindmillOutputDataLoader(cutoff_time=100)
+        elif dataset == "Hungary_Chickenpox":
+            dataloader = HungaryCPDataLoader()
+        elif dataset == "PedalMe":
+            dataloader = PedalMeDataLoader()
+        elif dataset == "METRLA":
+            dataloader = METRLADataLoader()
+        elif dataset == "Montevideo_Bus":
+            dataloader = MontevideoBusDataLoader()
+        else:
+            print("😔 Unrecognized dataset", file=f)
+            return 1
+
+        edge_list = dataloader.get_edges()
+        edge_weight_list = dataloader.get_edge_weights()
+        targets = dataloader.get_all_targets()
+        assert not np.isnan(targets).any(), "Targets contain NaN values"
+
+        pynvml.nvmlInit()
+        handle = pynvml.nvmlDeviceGetHandleByIndex(0)
+        initial_used_gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).used
+        G = StaticGraph(
+            edge_list,
+            edge_weight_list,
+            dataloader.gdata["num_nodes"]
+        )
+        graph_mem = pynvml.nvmlDeviceGetMemoryInfo(
+            handle).used - initial_used_gpu_mem
+
+        edge_weight = to_default_device(
+            torch.unsqueeze(torch.FloatTensor(edge_weight_list), 1)
+        )
+
+        # Clamp edge weights to have min. value of 1e-6
+        edge_weight = torch.clamp(edge_weight, min=1e-6)
+
+        targets = to_default_device(torch.FloatTensor(np.array(targets)))
+
+        num_hidden_units = num_hidden
+        num_outputs = 1
+        model = to_default_device(
+            STGraphTGCN(feat_size, num_hidden_units, num_outputs))
+
+        # Apply custom weight initialization
+        model.apply(init_weights)
+
+        optimizer = torch.optim.Adam(model.parameters(), lr=lr)
+
+        # Logging Output
+        total_timestamps = dataloader.gdata["total_timestamps"]
+
+        if backprop_every == 0:
+            backprop_every = total_timestamps - dataloader._lags
+
+        if total_timestamps % backprop_every == 0:
+            num_iter = int(total_timestamps / backprop_every)
+        else:
+            num_iter = int(total_timestamps / backprop_every) + 1
+
+        # metrics
+        dur = []
+        max_gpu = []
+        table = DataTable(
+            f"(STGraph Static-Temporal) TGCN on {dataloader.name} dataset",
+            ["Epoch", "Time(s)", "MSE", "Used GPU Memory (Max MB)"],
+        )
+
+        # 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.set_ndata("norm", norm.unsqueeze(1))
+
+        print("Starting training...\n", file=f)
+        try:
+            with Progress() as progress:
+                epoch_progress = progress.add_task(
+                    f"{dataset}",
+                    total=num_epochs
+                )
+
+                while not progress.finished:
+                    for epoch in range(num_epochs):
+                        torch.cuda.synchronize()
+                        torch.cuda.reset_peak_memory_stats(0)
+                        model.train()
+
+                        t0 = time.time()
+                        gpu_mem_arr = []
+                        cost_arr = []
+
+                        for index in range(num_iter):
+                            optimizer.zero_grad()
+                            cost = 0
+                            hidden_state = None
+                            y_hat = torch.randn(
+                                (dataloader.gdata["num_nodes"], feat_size),
+                                device=get_default_device(),
+                            )
+                            for k in range(backprop_every):
+                                t = index * backprop_every + k
+
+                                if t >= total_timestamps - dataloader._lags:
+                                    break
+
+                                if dataset == "METRLA" and t >= total_timestamps - (
+                                        dataloader._num_timesteps_out + dataloader._num_timesteps_in):
+                                    break
+
+                                y_out, y_hat, hidden_state = model(
+                                    G, y_hat, edge_weight, hidden_state
+                                )
+
+                                cost = cost + torch.mean(
+                                    (y_out - targets[t]) ** 2)
+
+                            if cost == 0:
+                                break
+
+                            cost = cost / (backprop_every + 1)
+                            cost.backward()
+
+                            torch.nn.utils.clip_grad_norm_(
+                                model.parameters(),
+                                1.0
+                            )
+
+                            optimizer.step()
+                            torch.cuda.synchronize()
+                            cost_arr.append(cost.item())
+
+                        used_gpu_mem = torch.cuda.max_memory_allocated(
+                            0) + graph_mem
+                        gpu_mem_arr.append(used_gpu_mem)
+
+                        run_time_this_epoch = time.time() - t0
+
+                        if epoch >= 3:
+                            dur.append(run_time_this_epoch)
+                            max_gpu.append(max(gpu_mem_arr))
+
+                        table.add_row(
+                            [
+                                epoch,
+                                "{:.5f}".format(run_time_this_epoch),
+                                "{:.4f}".format(sum(cost_arr) / len(cost_arr)),
+                                "{:.4f}".format(
+                                    (max(gpu_mem_arr) * 1.0 / (1024 ** 2))),
+                            ]
+                        )
+
+                        progress.update(epoch_progress, advance=1)
+
+            table.display(output_file=f)
+            print("Average Time taken: {:6f}".format(np.mean(dur)), file=f)
+
+            return 0
+
+        except Exception as e:
+            print("---------------- Error ----------------\n", file=f)
+            print(e, file=f)
+            print("\n", file=f)
+
+            traceback.print_exc(file=f)
+            print("\n", file=f)
+
+            if "out of memory" in str(e):
+                table.add_row(["OOM", "OOM", "OOM", "OOM"])
+                table.display(output_file=f)
+
+            return 1
diff --git a/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/utils.py b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/utils.py
new file mode 100644
index 00000000..1c4a703d
--- /dev/null
+++ b/tests/scripts/v1_1_0/temporal_tgcn_dataloaders/tgcn/utils.py
@@ -0,0 +1,20 @@
+import torch
+
+
+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) for x in data]
+    return data.to(get_default_device(), non_blocking=True)
+
+
+def init_weights(layer):
+    if isinstance(layer, torch.nn.Linear):
+        torch.nn.init.xavier_uniform_(layer.weight)
+        layer.bias.data.fill_(0.01)
\ No newline at end of file