{ "cells": [ { "cell_type": "markdown", "id": "766915ff-1eba-4d01-a843-1964c59778f7", "metadata": {}, "source": [ "Tensor Network Random Unitary Evolution\n", "======================\n", "\n", "This example demonstrates some features of ``TensorNetwork`` manipulation as well as the use of ``MatrixProductState.gate``, based on 'evolving' an intitial MPS with many random nearest neighbour unitaries." ] }, { "cell_type": "code", "execution_count": 1, "id": "0576ebd1-f7f9-4ffa-8ef2-ab4e3f4464d5", "metadata": {}, "outputs": [], "source": [ "%config InlineBackend.figure_formats = ['svg']" ] }, { "cell_type": "code", "execution_count": 2, "id": "ff7c2578-05bf-4549-b437-eac3a368b63a", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from quimb import *\n", "from quimb.tensor import *" ] }, { "cell_type": "markdown", "id": "581066be-4456-473a-ba9c-c9c46d192440", "metadata": {}, "source": [ "First we specify how sites we want, how many gates to apply, and some other parameters:" ] }, { "cell_type": "code", "execution_count": 3, "id": "aebfa330-2686-4bcd-92f5-20b7a19d6549", "metadata": {}, "outputs": [], "source": [ "# the initial state\n", "n = 50\n", "cyclic = False\n", "chi = 4 # intial bond dimension\n", "psi = MPS_rand_state(n, chi, cyclic=cyclic, tags=\"KET\", dtype=\"complex128\")\n", "\n", "# the gates\n", "n_gates = 5 * n\n", "gates = [rand_uni(4) for _ in range(n_gates)]\n", "u_tags = [f\"U{i}\" for i in range(n_gates)]" ] }, { "cell_type": "markdown", "id": "ccb6c729-3b16-4f5f-9cf7-6340bb0c52e7", "metadata": {}, "source": [ "We generate a unique tag for each gate we will apply, which we can also use to address all the gates only.\n", "\n", "Then we apply each gate to the MPS inplace:" ] }, { "cell_type": "code", "execution_count": 4, "id": "23aa5be6-586f-4d19-8958-501a6a871aca", "metadata": {}, "outputs": [], "source": [ "for U, t in zip(gates, u_tags):\n", " # generate a random coordinate\n", " i = np.random.randint(0, n - int(not cyclic))\n", "\n", " # apply the next gate to the coordinate\n", " # propagate_tags='sites' (the default in fact) specifies that the\n", " # new gate tensor should inherit the site tags from tensors it acts on\n", " psi.gate_(U, where=[i, i + 1], tags=t, propagate_tags=\"sites\")" ] }, { "cell_type": "code", "execution_count": 5, "id": "4d40704b-a27a-48b9-a231-03d351bee29e", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "psi.draw(color=[\"KET\"])" ] }, { "cell_type": "markdown", "id": "187d108d-b727-42f1-8570-3783b5f59d29", "metadata": {}, "source": [ "To make the graph a bit neater we can supply some fixed positions:" ] }, { "cell_type": "code", "execution_count": 6, "id": "9775c77a-4e48-4511-8b3a-d9a371b91e6c", "metadata": {}, "outputs": [], "source": [ "fix = {\n", " # [key - tags that uniquely locate a tensor]: [val - (x, y) coord]\n", " **{(\"KET\", f\"I{i}\"): (i, +10) for i in range(n)},\n", " # can also use a external index, 'k0' etc, as a key to fix it\n", " **{f\"k{i}\": (i, -10) for i in range(n)},\n", "}" ] }, { "cell_type": "markdown", "id": "baee88f8-bbbd-41ea-8b6a-df7cba954946", "metadata": {}, "source": [ "When fixing graphs, it might also be necessary to play with the spring parameter ``k``:" ] }, { "cell_type": "code", "execution_count": 7, "id": "f79815cc-66fd-403d-8392-02c7f0735cd6", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "psi.draw(fix=fix, k=0.001, color=[\"I5\", \"I15\", \"I25\", \"I35\", \"I45\"])" ] }, { "cell_type": "markdown", "id": "2b03b5bd-4970-441b-9773-53264d9e8617", "metadata": {}, "source": [ "We can see the 'lightcone' effect of adding ``propagate_tags='sites``.\n", "\n", "Next let's form the norm overlap, and add one tag to all the gates, and another to all the non-gate tensors:" ] }, { "cell_type": "code", "execution_count": 8, "id": "96d99b9e-0368-424d-b370-55761fc5a18f", "metadata": {}, "outputs": [], "source": [ "psiH = psi.H\n", "psiH.retag_({\"KET\": \"BRA\"}) # specify this to distinguish\n", "\n", "norm = psiH | psi\n", "norm.add_tag(\"UGs\", where=u_tags, which=\"any\")\n", "norm.add_tag(\"VEC0\", where=u_tags, which=\"!any\")" ] }, { "cell_type": "code", "execution_count": 9, "id": "cb7a8ad4-6735-44c0-809b-80a56d76dc50", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "norm.draw(color=[\"VEC0\", \"UGs\"])" ] }, { "cell_type": "markdown", "id": "41e8fc46-f71b-4565-9cb8-00561ccd408e", "metadata": {}, "source": [ "Again, it's a bit messy so we can specify some positions for some tensors:" ] }, { "cell_type": "code", "execution_count": 10, "id": "9e7037a3-b038-40d6-9b82-28cfe9ff9560", "metadata": {}, "outputs": [], "source": [ "fix = {\n", " **{(f\"I{i}\", \"KET\", \"VEC0\"): (i, -20) for i in range(n)},\n", " **{(f\"I{i}\", \"BRA\", \"VEC0\"): (i, +20) for i in range(n)},\n", "}" ] }, { "cell_type": "markdown", "id": "cee14830-ae7d-42f7-a6eb-a507f23ff228", "metadata": {}, "source": [ "``iterations`` can also be increased if the graph is not relaxing well." ] }, { "cell_type": "code", "execution_count": 11, "id": "fcf1e088-fbf3-49aa-8bd1-0bd208246e09", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "(psiH | psi).draw(\n", " color=[\"VEC0\", \"UGs\", \"I5\", \"I15\", \"I25\", \"I35\", \"I45\"],\n", " node_size=30,\n", " iterations=500,\n", " fix=fix,\n", " k=0.0001,\n", ")" ] }, { "cell_type": "markdown", "id": "3b3f5d4d-87b3-42fd-bb89-a65540483422", "metadata": {}, "source": [ "Later ``color`` tags take precedence over earlier ones.\n", "\n", "Since this circuit is still relatively low depth, we can fully contract it as well:" ] }, { "cell_type": "code", "execution_count": 12, "id": "4e42e233-11a1-4ffa-bf7d-3aae8a68c742", "metadata": {}, "outputs": [], "source": [ "# this calculates an opimized path for the contraction, which is cached\n", "# the path can also be inspected with `print(expr)`\n", "expr = (psi.H | psi).contract(all, get=\"path-info\")" ] }, { "cell_type": "code", "execution_count": 13, "id": "1ba19750-1fc9-4bcd-87cc-d46e5263d3e2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 182 ms, sys: 8.08 ms, total: 190 ms\n", "Wall time: 116 ms\n" ] }, { "data": { "text/plain": [ "(0.9999999999999871+5.204170427930421e-18j)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%time\n", "(psi.H | psi) ^ all" ] }, { "cell_type": "markdown", "id": "4bcc8438-6313-4cc9-ba0f-da4bf4d5b027", "metadata": {}, "source": [ "Manually perform partial trace\n", "----------------------------------" ] }, { "cell_type": "markdown", "id": "fb9bbc1c-e976-45ff-b371-76c036ddb5e3", "metadata": {}, "source": [ "Here, to perform the partial trace we need to do two things. (i) Make a copy of the vector to be the 'bra' with different indices, (ii) match up the subsystem indices we want to trace out in the 'ket' and 'bra':" ] }, { "cell_type": "code", "execution_count": 14, "id": "2a707423-0b87-469c-8534-7b6c5402ede4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# make a 'bra' vector copy with 'upper' indices\n", "psiH = psi.H\n", "psiH.retag_({\"KET\": \"BRA\"})\n", "# this automatically reindexes the TN\n", "psiH.site_ind_id = \"b{}\"\n", "\n", "# define two subsystems\n", "sysa = range(15, 35)\n", "sysb = [i for i in range(n) if i not in sysa]\n", "\n", "# join indices for sysb only\n", "psi.reindex_sites(\"dummy_ptr{}\", sysb, inplace=True)\n", "psiH.reindex_sites(\"dummy_ptr{}\", sysb, inplace=True)\n", "\n", "rho_ab = psiH | psi\n", "rho_ab" ] }, { "cell_type": "code", "execution_count": 15, "id": "0ce92fc9-30ed-4518-9f43-c162e127cf49", "metadata": {}, "outputs": [], "source": [ "fix = {\n", " **{f\"k{i}\": (i, -10) for i in range(n)},\n", " **{(f\"I{i}\", \"KET\", \"VEC0\"): (i, 0) for i in range(n)},\n", " **{(f\"I{i}\", \"BRA\", \"VEC0\"): (i, 10) for i in range(n)},\n", " **{f\"b{i}\": (i, 20) for i in range(n)},\n", "}" ] }, { "cell_type": "markdown", "id": "3a570cb4-77fa-4eb5-98d8-6a7d1e9b0e5b", "metadata": {}, "source": [ "Again we can graph this:" ] }, { "cell_type": "code", "execution_count": 16, "id": "07f41b4d-28ff-4fac-b5c8-d2ea9cbc1b72", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "rho_ab.draw(\n", " color=[\"VEC0\"] + [f\"I{i}\" for i in sysa], iterations=500, fix=fix, k=0.001\n", ")" ] }, { "cell_type": "markdown", "id": "67fc85cc-bb7c-4647-9ebc-43e747d9d8e6", "metadata": {}, "source": [ "Estimate Subsystem Entropy\n", "------------------------------\n", "\n", "We can treat this whole reduced density matrix as an effective linear operator, $A$, then calculate for example its entropy as a spectral sum function, $-\\text{Tr}(A \\log_2 A)$. First we set the left and right indices, and turn it into a ``scipy.sparse.linalg.LinearOperator``:" ] }, { "cell_type": "code", "execution_count": 17, "id": "872cf19d-56a2-4c12-b7fd-cbb6484447fb", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<1048576x1048576 TNLinearOperator with dtype=complex128>" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "right_ix = [f\"b{i}\" for i in sysa]\n", "left_ix = [f\"k{i}\" for i in sysa]\n", "\n", "rho_ab_lo = rho_ab.aslinearoperator(left_ix, right_ix, backend=\"cupy\")\n", "rho_ab_lo" ] }, { "cell_type": "markdown", "id": "8bd4cef4-50c2-4652-acb2-651fde0a993c", "metadata": {}, "source": [ "This can be quite slow, so wise to check progress:" ] }, { "cell_type": "code", "execution_count": 18, "id": "eeac1e2a-5f54-4615-91ad-f94e0b5aa798", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LANCZOS f(A) CALC: tol=0.01, tau=0.0001, R=10, bsz=1\n", "k=57: Returning estimate -5.480053227733483.\n", "Repeat 1: estimate is -5.480053227733483\n", "k=48: Returning estimate -4.987340728705768.\n", "Repeat 2: estimate is -4.987340728705768\n", "k=42: Returning estimate -6.551967475397945.\n", "Repeat 3: estimate is -6.551967475397945\n", "Total estimate = -5.673120477279066 ± 0.3771149062292873\n", "k=48: Returning estimate -6.368435763357799.\n", "Repeat 4: estimate is -6.368435763357799\n", "Total estimate = -5.846949298798749 ± 0.3204038840660703\n", "k=44: Returning estimate -6.689604577557199.\n", "Repeat 5: estimate is -6.689604577557199\n", "Total estimate = -6.015480354550439 ± 0.2973612427906209\n", "k=57: Returning estimate -4.993761544626728.\n", "Repeat 6: estimate is -4.993761544626728\n", "Total estimate = -5.845193886229819 ± 0.2925233683278253\n", "k=51: Returning estimate -5.3493674304000605.\n", "Repeat 7: estimate is -5.3493674304000605\n", "Total estimate = -5.774361535396997 ± 0.2591682330256407\n", "k=48: Returning estimate -7.03548223683227.\n", "Repeat 8: estimate is -7.03548223683227\n", "Total estimate = -5.932001623076406 ± 0.2704990389051788\n", "k=59: Returning estimate -4.799243182135965.\n", "Repeat 9: estimate is -4.799243182135965\n", "Total estimate = -5.806139574083024 ± 0.2681310051615072\n", "k=51: Returning estimate -5.372669211113707.\n", "Repeat 10: estimate is -5.372669211113707\n", "Total estimate = -5.762792537786092 ± 0.24479665051340085\n", "ESTIMATE is -5.762792537786092 ± 0.24479665051340085\n" ] } ], "source": [ "S_a = -approx_spectral_function(rho_ab_lo, f=xlogx, verbosity=1, R=10)" ] }, { "cell_type": "markdown", "id": "4dd03393-516d-4804-8d2f-b53a48549a44", "metadata": {}, "source": [ "Which yields the final entropy (in bits) of the central 20 qubits as:" ] }, { "cell_type": "code", "execution_count": 19, "id": "b8e6b530-f27c-41d7-94c2-0505fe50fd1f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.762792537786092" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "S_a" ] }, { "cell_type": "markdown", "id": "400b73ec-14ae-4a22-b076-43cbc40c72d6", "metadata": {}, "source": [ "Since ``TNLinearOperator`` repeatedly calls the same effective matrix-vector tensor contraction and does not require high precision this kind of computation is also ideally suited to being compiled into a GPU expression using ``tensorflow`` for example." ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 4 }