{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import bibliotek"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch as th\n",
    "import torch_geometric as pyg\n",
    "import matplotlib.pyplot as plt\n",
    "import networkx as nx\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "import sys\n",
    "sys.path.append('..')\n",
    "import utils.vis_utils as vis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "images_path = Path(\"images\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Przykład prostych grafów"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Porażka testu WL\n",
    "\n",
    "Poniższy przykład prezentuje przypadek, gdy test WL nie jest w stanie odróżnić od siebie wierzchołków, pomimo, że grafy nie są identyczne."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 22108 characters]",
      "text/plain": [
       "<Figure size 472.441x393.701 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "g1_wl = nx.Graph()\n",
    "g1_wl.add_edges_from((\n",
    "    # Pierwszy pierścień\n",
    "    ('a', 'c'),\n",
    "    ('c', 'e'),\n",
    "    ('e', 'f'),\n",
    "    ('f', 'd'),\n",
    "    ('d', 'b'),\n",
    "    ('b', 'a'),\n",
    "    # Drugi pierścień\n",
    "    ('g', 'i'),\n",
    "    ('i', 'j'),\n",
    "    ('j', 'h'),\n",
    "    ('h', 'f'),\n",
    "    \n",
    "    ('e', 'g'),\n",
    "    ('f', 'h'),\n",
    "))\n",
    "nx.set_node_attributes(g1_wl, 1, 'f')\n",
    "\n",
    "g2_wl = nx.Graph()\n",
    "g2_wl.add_edges_from((\n",
    "    # Pierwszy pierścień\n",
    "    ('a', 'c'),\n",
    "    ('c', 'e'),\n",
    "    ('e', 'd'),\n",
    "    ('d', 'b'),\n",
    "    ('b', 'a'),\n",
    "    # Drugi pierścień\n",
    "    ('f', 'g'),\n",
    "    ('g', 'i'),\n",
    "    ('i', 'j'),\n",
    "    ('j', 'h'),\n",
    "    ('h', 'f'),\n",
    "    \n",
    "    ('e', 'f')\n",
    "))\n",
    "nx.set_node_attributes(g2_wl, 1, 'f')\n",
    "\n",
    "\n",
    "pos_g1 = nx.spring_layout(g1_wl, seed=3)\n",
    "fig, axs = plt.subplots(1, 2, figsize=vis.image_size_in_cm(12, 10))\n",
    "nx.draw(g1_wl, pos=pos_g1, with_labels=True, ax=axs[0], node_color='0.85', edge_color='0.65')\n",
    "axs[0].set_title(\"Graf 1\")\n",
    "nx.draw(g2_wl, pos=pos_g1, with_labels=True, ax=axs[1], node_color='0.85', edge_color='0.65')\n",
    "axs[1].set_title(\"Graf 2\")\n",
    "fig.savefig(images_path / \"rys_5.26.tiff\", format='tiff', pil_kwargs={'compression': 'tiff_lzw'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Wartości funkcji hashującej dla wierzchołków w obu grafach są identyczne, co oznacza, że test WL nie jest w stanie odróżnić wierzchołków."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/filip/projects/grafowe_sieci_neuronowe/.venv/lib/python3.12/site-packages/networkx/algorithms/graph_hashing.py:211: UserWarning: The hashes produced for graphs without node or edge attributes changed in v3.5 due to a bugfix (see documentation).\n",
      "  node_labels = _init_node_labels(G, edge_attr, node_attr)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'3d6de103498f39f386c74c41e62ae363'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nx.weisfeiler_lehman_graph_hash(g1_wl, iterations=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3d6de103498f39f386c74c41e62ae363'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nx.weisfeiler_lehman_graph_hash(g2_wl, iterations=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Porażka sieci GIN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "g1 = pyg.data.Data()\n",
    "g1.edge_index = th.tensor([\n",
    "    [0, 1, 2],\n",
    "    [1, 2, 0]\n",
    "])\n",
    "g1.x = th.ones((3, 1))\n",
    "g1.y = th.tensor([1, 0, 0])\n",
    "g1 = pyg.transforms.ToUndirected()(g1)\n",
    "g1_nx = pyg.utils.to_networkx(g1)\n",
    "\n",
    "\n",
    "g2 = pyg.data.Data()\n",
    "g2.edge_index = th.tensor([\n",
    "    [0, 1, 2, 3],\n",
    "    [1, 2, 3, 0]\n",
    "])\n",
    "g2.x = th.ones((4, 1))\n",
    "g2.y = th.tensor([1, 0, 0, 0])\n",
    "g2 = pyg.transforms.ToUndirected()(g2)\n",
    "g2_nx = pyg.utils.to_networkx(g2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 23512 characters]",
      "text/plain": [
       "<Figure size 472.441x393.701 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pos_g1 = nx.spring_layout(g1_nx, seed=123)\n",
    "pos_g2 = nx.spring_layout(g2_nx, seed=123)\n",
    "fig2, axs2 = plt.subplots(1, 2, figsize=vis.image_size_in_cm(12, 10))\n",
    "nx.draw(g1_nx, pos=pos_g1, with_labels=True, ax=axs2[0], node_color='0.85', edge_color='0.65')\n",
    "axs2[0].set_title(\"Graf 1\")\n",
    "nx.draw(g2_nx, pos=pos_g2, with_labels=True, ax=axs2[1], node_color='0.85', edge_color='0.65')\n",
    "axs2[1].set_title(\"Graf 2\")\n",
    "fig2.savefig(images_path / \"rys_5.27.tiff\", format='tiff', pil_kwargs={'compression': 'tiff_lzw'})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Wartości funkcji hashującej dla wierzchołków w obu grafach są różne dla metody testu WL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f6ac5505791a3da0eb4ee2bb6b1eb399\n",
      "df42dc6cbacb3d110583a9bf12318979\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/filip/projects/grafowe_sieci_neuronowe/.venv/lib/python3.12/site-packages/networkx/utils/backends.py:551: UserWarning: The hashes produced for directed graphs changed in version v3.5 due to a bugfix to track in and out edges separately (see documentation).\n",
      "  return self.orig_func(*args, **kwargs)\n"
     ]
    }
   ],
   "source": [
    "print(nx.weisfeiler_lehman_graph_hash(g1_nx, iterations=5))\n",
    "print(nx.weisfeiler_lehman_graph_hash(g2_nx, iterations=5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "pyg.seed_everything(123)\n",
    "net = pyg.nn.GraphSAGE(in_channels=1, hidden_channels=3, out_channels=1, num_layers=2, project=True, aggr='mean')\n",
    "\n",
    "net = pyg.nn.GIN(in_channels=1, hidden_channels=3, out_channels=1, num_layers=3, aggr='mean')\n",
    "\n",
    "with th.no_grad():\n",
    "    yhat_g1_before_train = net(g1.x, g1.edge_index)\n",
    "    yhat_g2_before_train = net(g2.x, g2.edge_index)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([[-0.3419],\n",
       "         [-0.3419],\n",
       "         [-0.3419]]),\n",
       " tensor([[-0.3419],\n",
       "         [-0.3419],\n",
       "         [-0.3419],\n",
       "         [-0.3419]]))"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "yhat_g1_before_train, yhat_g2_before_train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0 loss 0.9685438275337219\n",
      "Epoch 1 loss 0.9303528666496277\n",
      "Epoch 2 loss 0.8943023085594177\n",
      "Epoch 3 loss 0.8605101704597473\n",
      "Epoch 4 loss 0.8290813565254211\n",
      "Epoch 5 loss 0.8001032471656799\n",
      "Epoch 6 loss 0.773642361164093\n",
      "Epoch 7 loss 0.7497398853302002\n",
      "Epoch 8 loss 0.7284088134765625\n",
      "Epoch 9 loss 0.7096309661865234\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21800 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pyg.seed_everything(123)\n",
    "net = pyg.nn.GIN(in_channels=1, hidden_channels=3, out_channels=1, num_layers=3, aggr='mean')\n",
    "optimizer = th.optim.Adam(net.parameters(), lr=0.1)\n",
    "criterion = th.nn.BCEWithLogitsLoss()\n",
    "epochs = 10\n",
    "epoch_losses = []\n",
    "for epoch in range(epochs):\n",
    "    optimizer.zero_grad()\n",
    "    yhat = net(g1.x, g1.edge_index)\n",
    "    loss = criterion(yhat, g1.y.float().view(-1, 1))\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "    print(f\"Epoch {epoch} loss {loss.item()}\")\n",
    "    epoch_losses.append(loss.item())\n",
    "plt.plot(epoch_losses);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "with th.no_grad():\n",
    "    yhat_g1_after_train = net(g1.x, g1.edge_index)\n",
    "    yhat_g2_after_train = net(g2.x, g2.edge_index)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Wartości reprezentacji numerycznych wierzchołków (osadzenia - ang. *embeddings*) w obu grafach są takie same - co oznacza, że sieć GIN nie jest w stanie odróżnić wierzchołków."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([[0.0012],\n",
       "         [0.0012],\n",
       "         [0.0012]]),\n",
       " tensor([[0.0012],\n",
       "         [0.0012],\n",
       "         [0.0012],\n",
       "         [0.0012]]))"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "yhat_g1_after_train, yhat_g2_after_train"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
