{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-01-26T16:03:43.018524Z",
     "start_time": "2024-01-26T16:03:42.688623Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sage.all import *\n",
    "\n",
    "#######################################################################################\n",
    "#######################################################################################\n",
    "# Functions that were used in the edge space project.\n",
    "#  Some may still be useful in this project\n",
    "\n",
    "def S_mat_sage(G):\n",
    "    \"\"\"return matrix S such that ST = C (TS=A)\"\"\"\n",
    "    edge_list = list(G.edges())\n",
    "    m = len(edge_list)\n",
    "    n = len(G.vertices())\n",
    "\n",
    "    S = matrix.zero(2 * m, n)  # np.zeros((2*m,n))\n",
    "    for x in range(n):\n",
    "        # iterate through nodes\n",
    "        for j in range(m):\n",
    "            # iterate through edges\n",
    "            if edge_list[j][1] == x:\n",
    "                # if edge points to x\n",
    "                S[j, x] = 1\n",
    "            if edge_list[j][0] == x:\n",
    "                # if edge comes from x\n",
    "                # well there must be an edge leaving too\n",
    "                S[j + m, x] = 1\n",
    "\n",
    "    return S\n",
    "\n",
    "\n",
    "def T_mat_sage(G):\n",
    "    \"\"\"return matrix T such that ST = C (TS=A)\"\"\"\n",
    "    edge_list = list(G.edges())\n",
    "    m = len(edge_list)\n",
    "    n = len(G.vertices())\n",
    "\n",
    "    T = matrix.zero(n, 2 * m)  # np.zeros((n,2*m))\n",
    "    for x in range(n):\n",
    "        # iterate through nodes\n",
    "        for j in range(m):\n",
    "            # iterate through edges\n",
    "            if edge_list[j][0] == x:\n",
    "                # if edge comes from x\n",
    "                T[x, j] = 1\n",
    "            if edge_list[j][1] == x:\n",
    "                # must be edge from other side\n",
    "                T[x, j + m] = 1\n",
    "\n",
    "    return T\n",
    "\n",
    "\n",
    "def tau_mat_sage(G):\n",
    "    \"\"\"return matrix tau such that ST-tau = B\"\"\"\n",
    "    edge_list = list(G.edges())\n",
    "    m = len(edge_list)\n",
    "    zero_mat = matrix.zero(m)\n",
    "    id_mat = identity_matrix(m)\n",
    "    t = block_matrix([[zero_mat, id_mat], [id_mat, zero_mat]])\n",
    "    return t\n",
    "\n",
    "\n",
    "def B_mat_sage(G):\n",
    "    \"\"\"return matrix B = ST-tau from a sage graph\"\"\"\n",
    "    edge_list = list(G.edges())\n",
    "    m = len(edge_list)\n",
    "    n = len(G.vertices())\n",
    "\n",
    "    # get S mat\n",
    "    S = matrix.zero(2 * m, n)  # np.zeros((2*m,n))\n",
    "    for x in range(n):\n",
    "        # iterate through nodes\n",
    "        for j in range(m):\n",
    "            # iterate through edges\n",
    "            if edge_list[j][1] == x:\n",
    "                # if edge points to x\n",
    "                S[j, x] = 1\n",
    "            if edge_list[j][0] == x:\n",
    "                # if edge comes from x\n",
    "                # well there must be an edge leaving too\n",
    "                S[j + m, x] = 1\n",
    "\n",
    "    T = matrix.zero(n, 2 * m)  # np.zeros((n,2*m))\n",
    "    for x in range(n):\n",
    "        # iterate through nodes\n",
    "        for j in range(m):\n",
    "            # iterate through edges\n",
    "            if edge_list[j][0] == x:\n",
    "                # if edge comes from x\n",
    "                T[x, j] = 1\n",
    "            if edge_list[j][1] == x:\n",
    "                # must be edge from other side\n",
    "                T[x, j + m] = 1\n",
    "\n",
    "    zero_mat = matrix.zero(m)\n",
    "    id_mat = identity_matrix(m)\n",
    "    t = block_matrix([[zero_mat, id_mat], [id_mat, zero_mat]])\n",
    "\n",
    "    return S * T - t\n",
    "\n",
    "\n",
    "def nb_trans_mat_sage(G):\n",
    "    \"\"\"return NBRW edge space transition probability matrix\"\"\"\n",
    "    b = B_mat_sage(G)\n",
    "    the_row_sums = sum(b)\n",
    "    row_sums = [i ^ -1 for i in the_row_sums]\n",
    "    return b * diagonal_matrix(row_sums)  # *b #wasn't stochastic like this...\n",
    "\n",
    "\n",
    "def Dmat_inv(G):\n",
    "    \"\"\"\n",
    "    D^{-1}\n",
    "    \"\"\"\n",
    "    return np.diag([1 / d for d in G.degree()])\n",
    "\n",
    "\n",
    "def trans_mat_sage(G):\n",
    "    \"\"\"vertex space transition matrix of a simple random walk\"\"\"\n",
    "    D = np.diag([i ^ -1 for i in G.degree()])\n",
    "    return D @ G.adjacency_matrix()\n",
    "\n",
    "\n",
    "#######################################################################################\n",
    "#######################################################################################\n",
    "# Functions to calculate the NBRW in the vertex space\n",
    "\n",
    "def nb_hitting_times(G, j):\n",
    "    \"\"\"\n",
    "    return list (np.array()) m_{i, j} where m is\n",
    "    NB hitting time from i to j for all i, with j fixed.\n",
    "\n",
    "    THIS IS ACTUALLY I THINK nb hitting time m_{e, j}. 2m vector of hitting times from\n",
    "    the edge e to the vertex j. (SEE Theorem 4.3 I believe.)\n",
    "\n",
    "    i.e. the columns of M_{ev}\n",
    "    \"\"\"\n",
    "    m = len(G.edges())\n",
    "    n = len(G)\n",
    "    P = nb_trans_mat_sage(G)\n",
    "    Gedges = [(g[0], g[1]) for g in G.edges()]\n",
    "    Gedges.extend([(g[1], g[0]) for g in G.edges()])\n",
    "    entries_to_delete = []\n",
    "\n",
    "    # Find the rows/columns to delete\n",
    "    for i in range(len(Gedges)):\n",
    "        if Gedges[i][0] == j:\n",
    "            entries_to_delete.append(i)\n",
    "    P_new = np.delete(P, entries_to_delete, axis=0)\n",
    "    P_new = np.delete(P_new, entries_to_delete, axis=1)\n",
    "\n",
    "    # Keep a list of what was NOT deleted, to construct the full vector later\n",
    "    remaining_entries = [i for i in range(2 * m)]\n",
    "    for k in entries_to_delete:\n",
    "        remaining_entries.remove(k)\n",
    "\n",
    "    # Solve (I - P)x = 1\n",
    "    ones = np.array([1] * len(P_new))\n",
    "    tau_vec = np.linalg.solve(np.identity(len(P_new)) - P_new, ones)\n",
    "\n",
    "    # recreate full tau_vec, putting the 0's back in where necessary\n",
    "    full_tau = np.array([0.0] * (2 * m))\n",
    "    for i in range(len(remaining_entries)):\n",
    "        full_tau[remaining_entries[i]] = tau_vec[i]\n",
    "\n",
    "    return full_tau\n",
    "\n",
    "\n",
    "def M_mat(G):\n",
    "    \"\"\"\n",
    "    Matrix M as in (4.4) in the Hitting Times Paper\n",
    "    (Basically the start point operator divided by the row sums)\n",
    "    \"\"\"\n",
    "    M = T_mat_sage(G).numpy()\n",
    "    # Divide by row sums\n",
    "    return M / M.sum(axis=1, keepdims=1)\n",
    "\n",
    "\n",
    "def nb_vertex_mfpt(G):\n",
    "    \"\"\"\n",
    "    Get the matrix (np.array probably) where M_{i,j} is the mfpt i -> j.\n",
    "    That is, $M_v^{nb}$\n",
    "\n",
    "    For now, assumes vertices are {0, 1, ..., n-1}\n",
    "    \"\"\"\n",
    "    n = len(G)\n",
    "    MFPT = np.zeros((n, n))\n",
    "\n",
    "    for i in range(n):\n",
    "        MFPT_col_i = M_mat(G) @ nb_hitting_times(G, i)\n",
    "        for j in range(n):\n",
    "            MFPT[j][i] = MFPT_col_i[j]\n",
    "\n",
    "    return MFPT\n",
    "\n",
    "\n",
    "def nb_vertex_kemeny_mfpt(G):\n",
    "    \"\"\"\n",
    "    Use these hitting times to get Kemeny's constant\n",
    "    \"\"\"\n",
    "    volG = 2 * len(G.edges())\n",
    "    steady = np.array([d / volG for d in G.degree()])\n",
    "    return steady @ nb_vertex_mfpt(G) @ steady\n",
    "\n",
    "\n",
    "def M_ev_NBRW(G):\n",
    "    \"\"\"\n",
    "    return the matrix $M_{ev}$, that is, the hitting times (mean first passage times)\n",
    "    of the nonbacktracking random walk\n",
    "    \"\"\"\n",
    "    m = len(G.edges())\n",
    "    n = len(G)\n",
    "    Mev = np.zeros((2 * m, n))\n",
    "    for i in range(n):\n",
    "        Mvec = nb_hitting_times(G, i)\n",
    "        for j in range(2 * m):\n",
    "            Mev[j][i] = Mvec[j]\n",
    "\n",
    "    return Mev\n",
    "\n",
    "\n",
    "#######################################################################################\n",
    "#######################################################################################\n",
    "# Other useful NBRW stuff. Particularly helpful when working with Z_{nb}\n",
    "\n",
    "def P_hat(G):\n",
    "    \"\"\"\n",
    "    (D-I)^{-1}A.\n",
    "    I should probably not take an inverse since its so easy but whatever. change as needed\n",
    "    \"\"\"\n",
    "    return np.diag([1 / (d - 1) for d in G.degree()]) @ G.adjacency_matrix()\n",
    "\n",
    "\n",
    "def P_mat(G):\n",
    "    \"\"\"\n",
    "    D^{-1}A\n",
    "    \"\"\"\n",
    "    return np.diag([1 / d for d in G.degree()]) @ G.adjacency_matrix()\n",
    "\n",
    "\n",
    "def clean_matrix_display(M, tol=1e-10):\n",
    "    \"\"\"\n",
    "    make zeros look like 0\n",
    "    \"\"\"\n",
    "    m = len(M[0][:])\n",
    "    n = len(M[:][0])\n",
    "    for i in range(n):\n",
    "        for j in range(m):\n",
    "            if np.abs(M[i][j]) < tol:\n",
    "                M[i][j] = 0\n",
    "    return M\n",
    "\n",
    "\n",
    "# October 17, 2023: Potential candidate for fundamental matrix of NBRW. The trace of which should be Kemeny + 1\n",
    "def Znb(G):\n",
    "    \"\"\"\n",
    "    A guess at what Znb should be. Seems to work pretty close to what we want numerically. Matches exactly with\n",
    "    edge transitive graphs. Usually close with all other graphs we've tried.\n",
    "    \"\"\"\n",
    "    n = len(G)\n",
    "    m = len(G.edges())\n",
    "    Dinv = np.diag([1 / d for d in G.degree()])\n",
    "    T = T_mat_sage(G)\n",
    "    S = S_mat_sage(G)\n",
    "    Wnb = np.ones((2 * m, 2 * m)) / (2 * m)\n",
    "    Wv = np.outer(np.ones(n), [d / (2 * m) for d in G.degree()])\n",
    "\n",
    "    return np.eye(n) + Dinv @ T @ (np.linalg.inv(np.eye(2 * m) - nb_trans_mat_sage(G) + Wnb)) @ S - Wv\n",
    "\n",
    "\n",
    "def mrt_NBRW_vertex(G):\n",
    "    \"\"\"\n",
    "    Diagonal matrix of mean return times of a NBRW.\n",
    "    See Equations below (4.8) in hitting times paper.\n",
    "\n",
    "    Note: This should be the same Mean Return Times as SRW I believe which might just be 1/\\pi_j\n",
    "          if I remember correctly as I'm typing this note\n",
    "    \"\"\"\n",
    "    Dinv = np.diag([1 / d for d in G.degree()])\n",
    "    T = T_mat_sage(G)\n",
    "    Pnb = nb_trans_mat_sage(G)\n",
    "    Mev = M_ev_NBRW(G)\n",
    "    return np.eye(len(G)) + np.diag(np.diag(Dinv @ T @ Pnb @ Mev))\n",
    "\n",
    "\n",
    "def Pv_NBRW(G, k):\n",
    "    \"\"\"\n",
    "    Return the vertex space transition probability matrix for the nonbacktracking random walk for the kth step\n",
    "    \"\"\"\n",
    "    if k == 0:\n",
    "        return np.eye(len(G))\n",
    "\n",
    "    if k == 1:\n",
    "        return trans_mat_sage(G)\n",
    "\n",
    "    # If k > 1\n",
    "    Dinv = np.diag([1 / d for d in G.degree()])\n",
    "    T = T_mat_sage(G)\n",
    "    S = S_mat_sage(G)\n",
    "    Pnb = nb_trans_mat_sage(G)\n",
    "    return Dinv @ T @ (np.linalg.matrix_power(Pnb, k - 1)) @ S\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
