{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Steenrod barcode of a filtration of $\\mathbb RP^2$\n", "\n", "In this notebook we use ``steenroder`` to computing the Steenrod barcode of a small filtration.\n", "We will assume familiarity with the content of the notebook *barcode* where it is explained how the barcode of persistent relative cohomology and persistent cocycle representatives are computed by ``steenroder``.\n", "\n", "The filtered complex $X$ that we consider is the following model for the real projective plane\n", "\n", "" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:46:36.040092Z", "iopub.status.busy": "2022-05-29T19:46:36.039776Z", "iopub.status.idle": "2022-05-29T19:46:36.050224Z", "shell.execute_reply": "2022-05-29T19:46:36.049597Z" } }, "outputs": [], "source": [ "rp2 = (\n", " (0,),\n", " (1,), (0,1),\n", " (2,), (0,2), (1,2), (0,1,2),\n", " (3,), (0,3), (1,3), (0,1,3), (2,3),\n", " (4,), (0,4), (1,4), (2,4), (1,2,4), (3,4), (0,3,4), (2,3,4),\n", " (5,), (0,5), (1,5), (2,5), (0,2,5), (3,5), (1,3,5), (2,3,5), (4,5), (0,4,5), (1,4,5)\n", " )\n", "\n", "filtration = rp2\n", "maxdim = max(map(len, filtration)) - 1\n", "m = len(filtration) - 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What will be computed\n", "\n", "### Relative cohomology\n", "\n", "We will consider the persistent relative cohomology of $X$\n", "\n", "$$\n", "H^\\bullet(X) \\leftarrow H^\\bullet(X, X_{0}) \\leftarrow H^\\bullet(X, X_{1}) \\leftarrow \\cdots \\leftarrow H^\\bullet(X, X_{m})\n", "$$\n", "as a persistence module.\n", "\n", "### Regular barcode\n", "\n", "For $i < j \\in \\{-1,\\dots,m\\}$ let $X_{ij}$ be the unique composition $H^\\bullet(X, X_{i}) \\leftarrow H^\\bullet(X, X_{j})$ with the convention $X_{-1} = \\emptyset$.\n", "The barcode of this persistence module is the multiset\n", "\n", "$$\n", "Bar_X =\n", "\\big\\{ [p,q] \\mid -1 \\le p < q \\le m \\big\\}\n", "$$\n", "\n", "defined by the property\n", "\n", "$$\n", "\\mathrm{rank} \\, X_{ij} =\n", "\\mathrm{card} \\big\\{[p,q] \\in Bar_X \\mid p \\le i < j \\le q \\big\\}.\n", "$$\n", "\n", "### Steenrod barcode\n", "\n", "Since the cohomology operation $Sq^k$ is natural, we obtain an endomorphism of persistent relative cohomology\n", "\n", "\\begin{align*}\n", "&H^\\bullet(X) \\leftarrow \\cdots \\leftarrow H^\\bullet(X, X_{m}) \\\\\n", "& {\\tiny Sq^k} \\uparrow \\kern 2.5cm {\\tiny Sq^k} \\uparrow\\\\\n", "&H^\\bullet(X) \\leftarrow \\cdots \\leftarrow H^\\bullet(X, X_{m}).\n", "\\end{align*}\n", "\n", "The $Sq^k$-barcode of $X$ is the barcode of the image persistent module of this endomorphism.\n", "\n", "Let us now compute these invariants using `steenroder`.\n", "We remark that since there is a compilation step required, the first time this function is run takes a bit long." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:46:36.053295Z", "iopub.status.busy": "2022-05-29T19:46:36.052929Z", "iopub.status.idle": "2022-05-29T19:47:12.166750Z", "shell.execute_reply": "2022-05-29T19:47:12.165368Z" } }, "outputs": [], "source": [ "from steenroder import barcodes\n", "\n", "k = 1\n", "barcode, steenrod_barcode = barcodes(k, filtration)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For each dimension ``d`` each of these is a 2D int array of shape ``(n_bars, 2)`` containing the bars of persistent relative cohomology and of the image of $Sq^k$ in degree ``d``.\n", "Let us inspect this output." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:12.170251Z", "iopub.status.busy": "2022-05-29T19:47:12.169735Z", "iopub.status.idle": "2022-05-29T19:47:12.175182Z", "shell.execute_reply": "2022-05-29T19:47:12.174636Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Persistent relative cohomology:\n", "Regular barcode:\n", "dim 0:[[-1, 0]]\n", "dim 1:[[20, 21], [12, 13], [-1, 11], [7, 8], [3, 4], [1, 2]]\n", "dim 2:[[-1, 30], [28, 29], [22, 27], [25, 26], [23, 24], [14, 19], [17, 18], [15, 16], [9, 10], [5, 6]]\n", "Sq^1-barcode:\n", "dim 0:[]\n", "dim 1:[]\n", "dim 2:[[-1 11]]\n" ] } ], "source": [ "print('Persistent relative cohomology:')\n", "\n", "print('Regular barcode:')\n", "for d in range(maxdim + 1):\n", " print(f'dim {d}:{list(map(list, barcode[d]))}')\n", " \n", "print(f'Sq^{k}-barcode:')\n", "for d in range(maxdim + 1):\n", " print(f'dim {d}:{steenrod_barcode[d]}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are three infinite bars in the regular barcode\n", "[-1,0] in deg 0,\n", "[-1,11] in deg 1,\n", "[-1,30] in deg 2.\n", "Additionally, there is one $Sq^1$-bar\n", "[-1,11] in deg 2.\n", "This $Sq^1$-bar witnesses in the persistent context the non-trivial relationship $Sq^1 [\\alpha_1] = [\\alpha_2]$ between the degree 1 and 2 generators of the cohomology of $\\mathbb R P^2$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How this is computed\n", "\n", "We will now describe an effective computation of the $Sq^k$-barcode of persistent relative cohomology bulding on the computation of regular barcodes and persistent cocycle representatives.\n", "We refer to the notebook *barcode* where more details about this computation are given.\n", "Recall that the $Sq^k$-barcode is by definition the barcode of the image persistent module of the endomorphism $Sq^k$.\n", "\n", "### The $R = D^\\perp V$ decomposition\n", "\n", "Let $D$ be the boundary matrix of the filtration with respect to the ordered basis of simplices and $D^\\perp$ its antitransposed:\n", "\n", "$$\n", "D^\\perp_{p,\\, q} = D_{\\overline q,\\ \\overline p}\n", "$$\n", "where $\\overline j = m-j$ for $j \\in \\{0,\\dots,m\\}$.\n", "\n", "Let us consider the unique decomposition $R = D^\\perp V$ where $V$ is an invertible upper triangular matrix and $R$ is reduced, i.e., no two columns have the same pivot row.\n", "\n", "### The regular barcode\n", "\n", "Denoting the $i$-th column of $R$ by $R_{i}$ where $i \\in \\{0,\\dots,m\\}$, let\n", "\n", "$$\n", "P = \\{ i \\ |\\ R_i = 0\\}, \\qquad N = \\{ i \\ |\\ R_i \\neq 0\\}, \\qquad E = P \\setminus \\{\\text{pivots of } R\\}.\n", "$$\n", "\n", "There exists a canonical bijection between the union of $N$ and $E$ and the persistence relative cohomology barcode of the filtration given by\n", "\n", "\\begin{align*}\n", "N \\ni j &\\mapsto \\Big[\\, \\overline j, \\overline{\\mathrm{pivot}\\,R_j}\\, \\Big] \\in Bar^{\\dim(j)+1}_X \\\\\n", "E \\ni j &\\mapsto \\big[\\! -1, \\overline j \\,\\big] \\in Bar^{\\dim(j)}_X\n", "\\end{align*}\n", "\n", "### Representatives\n", "\n", "Additionally, a persistent cocycle representative for each bar is given by\n", "\\begin{equation*}\n", "[i,j] \\mapsto\n", "\\begin{cases}\n", "V_{\\overline j}, & i = -1, \\\\\n", "R_{\\overline i}, & i \\neq -1.\n", "\\end{cases}\n", "\\end{equation*}\n", "More specifically, a basis for $H^\\bullet(X, X_{\\overline j-1})$ thought of as a subspace in the direct sum\n", "\n", "$$\n", "\\ker \\delta = \\mathrm{img}\\, \\delta \\oplus H^\\bullet(X, X_{\\overline j-1}; \\Bbbk),\n", "$$\n", "\n", "is given by the set of cochains corresponding to the vectors in the union of\n", "\n", "$$\n", "\\big\\{R_k \\mid k \\in N,\\, j < \\mathrm{pivot}(R_k)\\big\\}\n", "\\quad \\text{and} \\quad\n", "\\{V_i \\mid i \\in E,\\, i \\leq j\\},\n", "$$\n", "\n", "and a basis for $\\mathrm{img} \\delta$ is given by\n", "$$\n", "\\{R_i \\mid i \\in N,\\, i \\leq j\\}.\n", "$$\n", "\n", "and a basis of coboundaries in $C^\\bullet(X, X_{\\overline j})$ corresponds to non-zero vectors in\n", "\n", "\\begin{equation*}\n", "\\{R_i\\ |\\ i \\in N,\\, i \\leq j\\}.\n", "\\end{equation*}\n", "\n", "### Steenrod representatives\n", "\n", "Given vector $v$ corresponding to a cocycle $\\alpha$, let $\\mathtt{sq^k}(v)$ be the vector correponding to cocycle representative of $Sq^k \\big( [\\alpha] \\big)$.\n", "For example, the one obtained using the following pseudo-code, where $A$ is the support of $\\alpha$.\n", "\n", "\n", "\n", "### Steenrod matrix\n", "\n", "Identifying a vector with the support of its associated cochain, define $Q^k$ to be the matrix whose columns are given by\n", "\n", "\\begin{equation*}\n", "Q^k_i = \\begin{cases}\n", "\\mathtt{sq^k}(V_i), & i \\in E, \\\\\n", "\\mathtt{sq^k}(R_j), & i = pivot(R_j), \\\\\n", "0, & otherwise.\n", "\\end{cases}\n", "\\end{equation*}\n", "\n", "### Reduction\n", "\n", "Given $R$ and $Q^k$, we denote by $R_{\\le j}$ and $Q^k_{\\le j}$ the submatrices containing all columns with indices less than or equal to $j$, and $R_{\\le j} \\mid Q^k_{\\le j}$ the matrix obtained by concatenating their columns.\n", "With this notation the following pseudo-code computes the $k$-Steenrod barcode of the filtration.\n", "\n", "\n", "\n", "Intuitively, the step from $j-1$ to $j$ either adds a new non-zero coboundary $R_j$ (which implies $Q^k_j = 0$) or the image $Q^k_j$ of a persistent cocycle generator (which implies $R_j=0$).\n", "In either case, we need to reduce with respect to the subspace of coboundaries, generated by $R_{\\leq j}$, the image of $Sq^k$, which is generated by $Q^k_{\\leq j}$.\n", "This process is done keeping track of when columns in $Q^k$ become zero and extracting from this information the $Sq^k$-barcode of the filtration." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using ``steenroder``\n", "\n", "Let us start by using the following functions explained in more detail in the notebook *barcode*." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:12.177843Z", "iopub.status.busy": "2022-05-29T19:47:12.177396Z", "iopub.status.idle": "2022-05-29T19:47:13.670342Z", "shell.execute_reply": "2022-05-29T19:47:13.669521Z" } }, "outputs": [], "source": [ "from steenroder import sort_filtration_by_dim, compute_reduced_triangular, compute_barcode_and_coho_reps\n", "\n", "filtration_by_dim = sort_filtration_by_dim(filtration)\n", "spx2idx, idxs, reduced, triangular = compute_reduced_triangular(filtration_by_dim)\n", "barcode, coho_reps = compute_barcode_and_coho_reps(idxs, reduced, triangular)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Algorithm 1\n", "\n", "Using an implementation of Algorithm 1, ``steenroder`` constructs the Steenrod matrix using the method ``compute_steenrod_matrix``.\n", "Its output is a list of ``numba.typed.List``, one list per simplex dimension.\n", "Explicitly, ``steenrod_matrix[d][j]`` entry is the result of computing the Steenrod square of ``coho_reps[d][j]``.\n", "\n", "Since we are studying $Sq^k\\big([\\alpha]\\big)$ for $k=1$ we concentarte on classes $[\\alpha]$ of degree $1$ since $Sq^1\\big([\\alpha]\\big) = 0$ otherwise." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.673978Z", "iopub.status.busy": "2022-05-29T19:47:13.673545Z", "iopub.status.idle": "2022-05-29T19:47:13.791616Z", "shell.execute_reply": "2022-05-29T19:47:13.790702Z" }, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bar : Cocycle --> Sq^1-cocycle:\n", "[20 21] : {(1, 5), (4, 5), (0, 5), (2, 5), (3, 5)} --> []\n", "[12 13] : {(2, 4), (0, 4), (3, 4), (1, 4), (4, 5)} --> [(0, 4, 5), (1, 4, 5)]\n", "[-1 11] : {(2, 4), (1, 5), (1, 4), (2, 3), (3, 5)} --> [(2, 3, 5)]\n", "[7 8] : {(3, 4), (0, 3), (2, 3), (1, 3), (3, 5)} --> [(0, 3, 4), (2, 3, 4), (1, 3, 5), (2, 3, 5)]\n", "[3 4] : {(2, 4), (1, 2), (2, 3), (0, 2), (2, 5)} --> [(1, 2, 4), (0, 2, 5)]\n", "[1 2] : {(0, 1), (1, 2), (1, 5), (1, 4), (1, 3)} --> [(0, 1, 2), (0, 1, 3)]\n" ] } ], "source": [ "from steenroder import compute_steenrod_matrix\n", "\n", "k = 1\n", "steenrod_matrix = compute_steenrod_matrix(k, coho_reps, filtration_by_dim, spx2idx)\n", "\n", "d = 1\n", "print(f'Bar : Cocycle --> Sq^{k}-cocycle:')\n", "for bar, coho_rep, st_coho_rep in zip(barcode[d], coho_reps[d], steenrod_matrix[d+1]):\n", " cocycle = []\n", " for p in coho_rep:\n", " cocycle.append(filtration[idxs[d][p]])\n", " st_cocycle = []\n", " for p in st_coho_rep:\n", " st_cocycle.append(filtration[idxs[d+1][p]])\n", " print(f'{str(bar): <7} : {set(cocycle)} --> {st_cocycle}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will compare these representatives with those produced by a more explicit implementation of Algorithm 1." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.794969Z", "iopub.status.busy": "2022-05-29T19:47:13.794524Z", "iopub.status.idle": "2022-05-29T19:47:13.802934Z", "shell.execute_reply": "2022-05-29T19:47:13.802191Z" } }, "outputs": [], "source": [ "from itertools import combinations\n", "\n", "def sq(k, cocycle, filtration):\n", " '''Return a cocycle representative of the image of Sq^k applied to the class represented by the given cocycle'''\n", " st_cocycle = set()\n", " for pair in combinations(cocycle, 2):\n", " a, b = set(pair[0]), set(pair[1])\n", " if (len(a.union(b)) == len(a) + k and\n", " tuple(sorted(a.union(b))) in filtration):\n", " a_bar, b_bar = a.difference(b), b.difference(a)\n", " index = dict()\n", " for v in a_bar.union(b_bar):\n", " pos = sorted(a.union(b)).index(v)\n", " pos_bar = sorted(a_bar.union(b_bar)).index(v)\n", " index[v] = (pos + pos_bar) % 2\n", " index_a = {index[v] for v in a_bar}\n", " index_b = {index[w] for w in b_bar}\n", " if (index_a == {0} and index_b == {1}\n", " or index_a == {1} and index_b == {0}):\n", " u = sorted(a.union(b))\n", " st_cocycle ^= {tuple(u)}\n", " return st_cocycle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us not compute cocycle representatives generating the image of $Sq^k$." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.806073Z", "iopub.status.busy": "2022-05-29T19:47:13.805388Z", "iopub.status.idle": "2022-05-29T19:47:13.812346Z", "shell.execute_reply": "2022-05-29T19:47:13.811514Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cocycle --> Sq^1-cocycle:\n", "{(1, 5), (4, 5), (0, 5), (2, 5), (3, 5)} --> set()\n", "{(2, 4), (0, 4), (3, 4), (1, 4), (4, 5)} --> {(1, 4, 5), (0, 4, 5)}\n", "{(2, 4), (1, 5), (1, 4), (2, 3), (3, 5)} --> {(2, 3, 5)}\n", "{(3, 4), (0, 3), (2, 3), (1, 3), (3, 5)} --> {(0, 3, 4), (2, 3, 4), (2, 3, 5), (1, 3, 5)}\n", "{(2, 4), (1, 2), (2, 3), (0, 2), (2, 5)} --> {(1, 2, 4), (0, 2, 5)}\n", "{(0, 1), (1, 2), (1, 5), (1, 4), (1, 3)} --> {(0, 1, 2), (0, 1, 3)}\n" ] } ], "source": [ "d = 1\n", "print(f'Cocycle --> Sq^{k}-cocycle:')\n", "for coho_rep in coho_reps[d]:\n", " cocycle = []\n", " for p in coho_rep:\n", " cocycle.append(filtration[idxs[d][p]])\n", " st_cocycle = sq(k, cocycle, filtration)\n", " print(f'{set(cocycle)} --> {st_cocycle}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Algorithm 2\n", "\n", "It is carried through in `compute_steenrod_barcode`, whose output is the $Sq^k$-barcode of the filtration." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.817376Z", "iopub.status.busy": "2022-05-29T19:47:13.816980Z", "iopub.status.idle": "2022-05-29T19:47:13.827410Z", "shell.execute_reply": "2022-05-29T19:47:13.826860Z" }, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "array([[-1, 11]])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from steenroder import compute_steenrod_barcode\n", "\n", "dd = d + k\n", "steenrod_barcode = compute_steenrod_barcode(k, steenrod_matrix, idxs, reduced, barcode)\n", "steenrod_barcode[dd]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will compare this output to an explicit construction obtained by implementing the above algorithms.\n", "Let us start by representing the matrix $Q$ and $R$ as instances of `np.array`.\n", "Recall that `steenroder` indexes everything with respect to the order of the original filtration, so we need to apply the transformation $(p,q) \\mapsto (m-p, m-q)$ to the entries of the (sparse) matrices it produces." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.830115Z", "iopub.status.busy": "2022-05-29T19:47:13.829708Z", "iopub.status.idle": "2022-05-29T19:47:13.837446Z", "shell.execute_reply": "2022-05-29T19:47:13.836792Z" } }, "outputs": [], "source": [ "import numpy as np\n", "\n", "antiQ = np.zeros((m+1,m+1), dtype=int)\n", "for d in range(maxdim-k+1):\n", " for bar, col in zip(barcode[d], steenrod_matrix[d+k]):\n", " for p in col:\n", " antiQ[idxs[d+k][p], bar[1]] = 1\n", "Q = np.flip(antiQ, [0,1])\n", "\n", "antiR = np.zeros((m+1,m+1), dtype=int)\n", "for d in range(maxdim+1):\n", " for i, col in enumerate(reduced[d]):\n", " for j in col:\n", " antiR[idxs[d+1][j], idxs[d][i]] = 1\n", "R = np.flip(antiR, [0,1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before introducing an implementation of Algorithm 2 we need some auxiliary functions:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.840730Z", "iopub.status.busy": "2022-05-29T19:47:13.840094Z", "iopub.status.idle": "2022-05-29T19:47:13.847906Z", "shell.execute_reply": "2022-05-29T19:47:13.847266Z" } }, "outputs": [], "source": [ "def pivot(column):\n", " \"\"\"Returns the index of the largest non-zero entry of the column or None if all entries are 0\"\"\"\n", " try:\n", " return max(column.nonzero()[0])\n", " except ValueError:\n", " return None\n", "\n", "def reduce_vector(reduced, column):\n", " \"\"\"Reduces a column with respect to a reduced matrix with the same number of rows\"\"\"\n", " num_col = reduced.shape[1]\n", " i = -1\n", " while i >= -num_col:\n", " if not np.any(column):\n", " break\n", " else:\n", " piv_v = pivot(column)\n", " piv_i = pivot(reduced[:, i])\n", "\n", " if piv_i == piv_v:\n", " column[:, 0] = np.logical_xor(reduced[:, i], column[:, 0])\n", " i = 0\n", " i -= 1\n", "\n", "def reduce_matrix(reduced, matrix):\n", " \"\"\"Reduces a matrix with respect to a reduced matrix with the same number of rows\"\"\"\n", " num_vector = matrix.shape[1]\n", " reducing = reduced.copy()\n", "\n", " for i in range(num_vector):\n", " reduce_vector(reducing, matrix[:, i:i+1])\n", " reducing = np.concatenate([reducing, matrix[:, i:i+1]], axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now define a function implementing Algorithm 2.\n", "Using it we obtain the same Steenrod barcode as the one produced by `steenroder`." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2022-05-29T19:47:13.850975Z", "iopub.status.busy": "2022-05-29T19:47:13.850515Z", "iopub.status.idle": "2022-05-29T19:47:13.880240Z", "shell.execute_reply": "2022-05-29T19:47:13.879585Z" } }, "outputs": [ { "data": { "text/plain": [ "[[-1, 11]]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def get_steenrod_barcode(R, Q):\n", " \"\"\"Returns the Steenrod barcodes implementing Algorithm 2\"\"\"\n", " m = R.shape[1]-1\n", " alive = {i: True for i in range(m)}\n", " steenrod_barcode = []\n", " for j in range(m):\n", " reduce_matrix(R[:,:j+1], Q[:,:j+1])\n", " for i in range(j+1):\n", " if alive[i] and not np.any(Q[:,i]):\n", " alive[i] = False\n", " if j > i:\n", " steenrod_barcode.append([m-j, m-i])\n", " steenrod_barcode += [[-1,m-i] for i in alive if alive[i]]\n", " return steenrod_barcode\n", "\n", "get_steenrod_barcode(R, Q)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### To-Do\n", "\n", "duality and truncations" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.12" } }, "nbformat": 4, "nbformat_minor": 2 }