{ "cells": [ { "cell_type": "markdown", "id": "2151396e-0934-4455-989d-a317ceb90e23", "metadata": {}, "source": [ "# Compressing MPS\n", "## Overview\n", "In this notebook we will show how to compress the wavefuntion stored in an MPS via singular value decomposition (SVD)." ] }, { "cell_type": "markdown", "id": "f0a23995-a701-4cf5-be37-5f27866597ed", "metadata": {}, "source": [ "## The model\n", "In this tutorial we will skip the process of building the model from scratch and use one of the pre-built models in Renormalizer that is frequently used for testing.\n", "The model we use is a Frenkel-Holstein model with 3 electronic sites and 2 vibration modes for each electronic site\n", "\n", "$$\n", "\\hat H = \\sum_{i=1}^3\\sum_{j=1}^3 J_{ij} \\hat a^\\dagger_i \\hat a_{i+1} + \\sum_{i=1}^3\\sum_{k=1,2}\\frac{1}{2} (\\hat p_{ik}^2 + \\omega_k^2 \\hat x_{ik}^2) + \\sum_{i=1}^3\\sum_{k=1,2} \\hat a^\\dagger_i \\hat a_i \\hat x_{ik}\n", "$$\n", "\n", "Therefore, the model has 9 sites in total. 3 of them are electronic sites and the rest are vibrational sites." ] }, { "cell_type": "code", "execution_count": 1, "id": "c1172b48-f891-425f-ba30-6e8b2c04cdbc", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:00.943968Z", "iopub.status.busy": "2026-10-14T01:46:00.943764Z", "iopub.status.idle": "2026-10-14T01:46:01.985727Z", "shell.execute_reply": "2026-10-14T01:46:01.984803Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:01,202[INFO] Use numpy as backend\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:01,202[INFO] numpy random seed is 9012\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:01,203[INFO] backend random seed is 2019\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:01,204[INFO] random seed is 1092\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:01,212[INFO] Git Commit Hash: 3507afde83fd6f408c1c2198aad4dfe455d88312\n" ] } ], "source": [ "from renormalizer.tests.parameter import holstein_model" ] }, { "cell_type": "code", "execution_count": 2, "id": "04ae5c85-d8f2-4c14-a2a7-f1d37e2db36a", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:01.988153Z", "iopub.status.busy": "2026-10-14T01:46:01.987873Z", "iopub.status.idle": "2026-10-14T01:46:01.994207Z", "shell.execute_reply": "2026-10-14T01:46:01.993296Z" } }, "outputs": [ { "data": { "text/plain": [ "[BasisSimpleElectron(dof: 0, nbas: 2, qn: [[0], [1]]),\n", " BasisSHO(dof: (0, 0), x0: 0.0, omega: 0.0004852952677877878, nbas: 4),\n", " BasisSHO(dof: (0, 1), x0: 0.0, omega: 0.007087607302669169, nbas: 4),\n", " BasisSimpleElectron(dof: 1, nbas: 2, qn: [[0], [1]]),\n", " BasisSHO(dof: (1, 0), x0: 0.0, omega: 0.0004852952677877878, nbas: 4),\n", " BasisSHO(dof: (1, 1), x0: 0.0, omega: 0.007087607302669169, nbas: 4),\n", " BasisSimpleElectron(dof: 2, nbas: 2, qn: [[0], [1]]),\n", " BasisSHO(dof: (2, 0), x0: 0.0, omega: 0.0004852952677877878, nbas: 4),\n", " BasisSHO(dof: (2, 1), x0: 0.0, omega: 0.007087607302669169, nbas: 4)]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "holstein_model.basis" ] }, { "cell_type": "code", "execution_count": 3, "id": "f9e48f60-8a7e-4c02-8424-11fd9740d0b4", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:01.995767Z", "iopub.status.busy": "2026-10-14T01:46:01.995595Z", "iopub.status.idle": "2026-10-14T01:46:02.000546Z", "shell.execute_reply": "2026-10-14T01:46:01.999561Z" } }, "outputs": [ { "data": { "text/plain": [ "[Op('a^\\\\dagger a', [0, 0], 0.10016074648885956, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [0, 1], -0.0036749322175664446, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [0, 2], -0.007349864435132889, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [1, 0], -0.0036749322175664446, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [1, 1], 0.10016074648885956, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [1, 2], -0.011024796652699332, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [2, 0], -0.007349864435132889, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [2, 1], -0.011024796652699332, [[1], [-1]]),\n", " Op('a^\\\\dagger a', [2, 2], 0.10016074648885956, [[1], [-1]]),\n", " Op('p^2', [(0, 0)], 0.5),\n", " Op('x^2', [(0, 0)], 1.1775574846861032e-07),\n", " Op('p^2', [(0, 1)], 0.5),\n", " Op('x^2', [(0, 1)], 2.5117088638424668e-05),\n", " Op('p^2', [(1, 0)], 0.5),\n", " Op('x^2', [(1, 0)], 1.1775574846861032e-07),\n", " Op('p^2', [(1, 1)], 0.5),\n", " Op('x^2', [(1, 1)], 2.5117088638424668e-05),\n", " Op('p^2', [(2, 0)], 0.5),\n", " Op('x^2', [(2, 0)], 1.1775574846861032e-07),\n", " Op('p^2', [(2, 1)], 0.5),\n", " Op('x^2', [(2, 1)], 2.5117088638424668e-05),\n", " Op('a^\\\\dagger a x', [0, 0, (0, 0)], -7.097609983197019e-06, [[1], [-1], [0]]),\n", " Op('a^\\\\dagger a x', [0, 0, (0, 1)], -0.00044069941383207155, [[1], [-1], [0]]),\n", " Op('a^\\\\dagger a x', [1, 1, (1, 0)], -7.097609983197019e-06, [[1], [-1], [0]]),\n", " Op('a^\\\\dagger a x', [1, 1, (1, 1)], -0.00044069941383207155, [[1], [-1], [0]]),\n", " Op('a^\\\\dagger a x', [2, 2, (2, 0)], -7.097609983197019e-06, [[1], [-1], [0]]),\n", " Op('a^\\\\dagger a x', [2, 2, (2, 1)], -0.00044069941383207155, [[1], [-1], [0]])]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "holstein_model.ham_terms" ] }, { "cell_type": "markdown", "id": "0a280ed3-79f7-4c08-adc3-e28bde549486", "metadata": {}, "source": [ "## Compressing MPS\n", "Firstly construct a random MPS and the Hamiltonian MPO" ] }, { "cell_type": "code", "execution_count": 4, "id": "e42487a2-bc67-4564-b6c7-e1e5c4986c7e", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.002119Z", "iopub.status.busy": "2026-10-14T01:46:02.001969Z", "iopub.status.idle": "2026-10-14T01:46:02.004766Z", "shell.execute_reply": "2026-10-14T01:46:02.003972Z" } }, "outputs": [], "source": [ "from renormalizer import Mps, Mpo" ] }, { "cell_type": "code", "execution_count": 5, "id": "4b2494e0-5998-4ef4-977e-df809b9ba287", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.006538Z", "iopub.status.busy": "2026-10-14T01:46:02.006390Z", "iopub.status.idle": "2026-10-14T01:46:02.014000Z", "shell.execute_reply": "2026-10-14T01:46:02.013142Z" } }, "outputs": [ { "data": { "text/plain": [ "[1, 2, 5, 5, 5, 5, 5, 5, 5, 1]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mps = Mps.random(holstein_model, qntot=1, m_max=5)\n", "mps.bond_dims" ] }, { "cell_type": "code", "execution_count": 6, "id": "34dfbdac-51eb-43de-ba7c-312dfee8a1bf", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.015878Z", "iopub.status.busy": "2026-10-14T01:46:02.015725Z", "iopub.status.idle": "2026-10-14T01:46:02.033488Z", "shell.execute_reply": "2026-10-14T01:46:02.032674Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,017[DEBUG] # of operator terms: 27\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,018[DEBUG] Input operator terms: 27\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,019[DEBUG] After combination of the same terms: 27\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,019[DEBUG] symbolic mpo algorithm: qr\n" ] }, { "data": { "text/plain": [ "[1, 4, 5, 4, 5, 5, 4, 3, 3, 1]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mpo = Mpo(holstein_model)\n", "mpo.bond_dims" ] }, { "cell_type": "markdown", "id": "7895b7c9-3486-4696-b0e5-a7fd3c786eb0", "metadata": {}, "source": [ "After applying the MPO on the MPS, the resulting MPS has a larger bond dimension. More specifically, the bond dimension is the product of the corresponding bond dimension of MPS and MPO" ] }, { "cell_type": "code", "execution_count": 7, "id": "25cd899b-259e-471e-b052-778ef25e82e3", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.035259Z", "iopub.status.busy": "2026-10-14T01:46:02.035109Z", "iopub.status.idle": "2026-10-14T01:46:02.039718Z", "shell.execute_reply": "2026-10-14T01:46:02.038925Z" } }, "outputs": [ { "data": { "text/plain": [ "[1, 8, 25, 20, 25, 25, 20, 15, 15, 1]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mps2 = mpo @ mps\n", "mps2.bond_dims" ] }, { "cell_type": "code", "execution_count": 8, "id": "463bec71-41cc-48ec-9a34-42dbe43d811b", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.041386Z", "iopub.status.busy": "2026-10-14T01:46:02.041235Z", "iopub.status.idle": "2026-10-14T01:46:02.045278Z", "shell.execute_reply": "2026-10-14T01:46:02.044467Z" } }, "outputs": [ { "data": { "text/plain": [ "array([ 1, 8, 25, 20, 25, 25, 20, 15, 15, 1])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "np.array(mps.bond_dims) * np.array(mpo.bond_dims) # the same as the bond dimension of `mps2`" ] }, { "cell_type": "markdown", "id": "48bacff0-1e4b-4aea-a0cc-fedd09f62e3b", "metadata": {}, "source": [ "We then try to compress the MPS. We first make a copy for future reference since the compress is in place" ] }, { "cell_type": "code", "execution_count": 9, "id": "dfc867cf-6362-44f0-833f-ea554db867cb", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.047164Z", "iopub.status.busy": "2026-10-14T01:46:02.047018Z", "iopub.status.idle": "2026-10-14T01:46:02.050135Z", "shell.execute_reply": "2026-10-14T01:46:02.049220Z" } }, "outputs": [], "source": [ "mps3 = mps2.copy()" ] }, { "cell_type": "markdown", "id": "2e572f67-21f0-4256-b527-eb4433ee23a7", "metadata": {}, "source": [ "Then compress `mps2` by SVD truncation to a fixed bond dimension of $M=5$. Note that as the product of an MPO and an MPS, `mps2` is not canonicalised, so it has to be canonicalised before SVD compression.\n", "\n", "After the compression, the size of the matrices in `mps2` is greatly reduced." ] }, { "cell_type": "code", "execution_count": 10, "id": "f251628c-ad3f-4d12-907d-8036df48d65f", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.051852Z", "iopub.status.busy": "2026-10-14T01:46:02.051558Z", "iopub.status.idle": "2026-10-14T01:46:02.064082Z", "shell.execute_reply": "2026-10-14T01:46:02.063231Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,060[DEBUG] size before/after compress: 47.2KiB/4.2KiB, ratio: 11.177777777777777\n" ] }, { "data": { "text/plain": [ "[1, 2, 5, 5, 5, 5, 5, 5, 4, 1]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m_trunc = 5\n", "mps3.canonicalise().compress(m_trunc)\n", "mps3.bond_dims" ] }, { "cell_type": "markdown", "id": "4d0852f0-c31c-4917-903d-3ec01324f837", "metadata": {}, "source": [ "We can see that the information loss, measured by the difference of energy expectations, is relatively small." ] }, { "cell_type": "code", "execution_count": 11, "id": "b4e07a29-a29a-430e-8796-7f1c7f60596b", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.065914Z", "iopub.status.busy": "2026-10-14T01:46:02.065639Z", "iopub.status.idle": "2026-10-14T01:46:02.073680Z", "shell.execute_reply": "2026-10-14T01:46:02.072821Z" } }, "outputs": [ { "data": { "text/plain": [ "(0.0031187687587272113, 0.0031676697428603723, 0.0154375260373589)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "e1 = mps3.expectation(mpo)\n", "e2 = mps2.expectation(mpo)\n", "e1, e2, abs((e1 - e2) / e2)" ] }, { "cell_type": "markdown", "id": "109d0adb-1e37-47e2-9684-2c710a069b4a", "metadata": {}, "source": [ "> As a result of the random generation of the MPS, the compress accuracy is not very impressive. Much higher efficiency can be expected for the real wavefunction of quantum systems." ] }, { "cell_type": "markdown", "id": "9fac7bc2-9585-401c-9fde-095f8df33e6f", "metadata": {}, "source": [ "To estimate the ``truncation error'', firstly obtain all singular values before compression in `s_list` by setting `ret_s=True`." ] }, { "cell_type": "code", "execution_count": 12, "id": "aec6ab93-996e-48e0-af8b-d77eaa026246", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.075292Z", "iopub.status.busy": "2026-10-14T01:46:02.075118Z", "iopub.status.idle": "2026-10-14T01:46:02.086388Z", "shell.execute_reply": "2026-10-14T01:46:02.085536Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,083[DEBUG] size before/after compress: 47.2KiB/4.2KiB, ratio: 11.177777777777777\n" ] } ], "source": [ "mps3 = mps2.copy()\n", "mps3, s_list = mps3.canonicalise().compress(m_trunc, ret_s=True)" ] }, { "cell_type": "markdown", "id": "32fbc873-3d94-4706-9be3-93c76ca364a3", "metadata": {}, "source": [ "Then compute the largest truncation error, which corresponds to the most significant discarded singular value from all tensor slices." ] }, { "cell_type": "code", "execution_count": 13, "id": "cdba9eac-6f3d-450b-bd3b-3c76b087fad0", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.088262Z", "iopub.status.busy": "2026-10-14T01:46:02.088090Z", "iopub.status.idle": "2026-10-14T01:46:02.092727Z", "shell.execute_reply": "2026-10-14T01:46:02.091925Z" } }, "outputs": [ { "data": { "text/plain": [ "np.float64(0.006233732177084583)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "truncation_error = 0.0\n", "\n", "# Iterate over each singular value list in s_list\n", "for s in s_list:\n", " # Sort the singular values in descending order\n", " sorted_s = np.sort(s)[::-1]\n", " \n", " # If there are more than m_trunc singular values, calculate the largest discarded singular value\n", " if len(sorted_s) > m_trunc:\n", " discarded_singular_value = sorted_s[m_trunc]\n", " truncation_error = max(truncation_error, discarded_singular_value)\n", "\n", "truncation_error" ] }, { "cell_type": "markdown", "id": "bf4d3ebe-2300-4bf1-9232-2c42fd2cbacd", "metadata": {}, "source": [ "## Compress Configuration\n", "It is sometimes desirable to perform truncation based on the magnitude of the singular values. Also, in production level calculations, MPSs are frequently compressed according to a fixed strategy.\n", "We can set the `compress_config` attribute for MPS to control the compression behavior" ] }, { "cell_type": "code", "execution_count": 14, "id": "978f8a58-7ed6-4087-953b-26929d485b63", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.094515Z", "iopub.status.busy": "2026-10-14T01:46:02.094335Z", "iopub.status.idle": "2026-10-14T01:46:02.097139Z", "shell.execute_reply": "2026-10-14T01:46:02.096375Z" } }, "outputs": [], "source": [ "from renormalizer.utils import CompressConfig, CompressCriteria" ] }, { "cell_type": "code", "execution_count": 15, "id": "11f4780c-ac99-48b9-a23c-a557baecef0a", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.098933Z", "iopub.status.busy": "2026-10-14T01:46:02.098752Z", "iopub.status.idle": "2026-10-14T01:46:02.101741Z", "shell.execute_reply": "2026-10-14T01:46:02.100925Z" } }, "outputs": [], "source": [ "mps3 = mps2.copy()\n", "mps3.compress_config = CompressConfig(CompressCriteria.threshold, threshold=1e-5)" ] }, { "cell_type": "markdown", "id": "9d968790-43e8-4929-8634-72b520510f00", "metadata": {}, "source": [ "Here the compress criteria is set to the singular value threshold, which is set to 1e-5.\n", "\n", "`CompressCriteria` is an `Enum` class for all possible compression strategies. Currently there're three different strategies." ] }, { "cell_type": "code", "execution_count": 16, "id": "8fd44580-df1a-4eef-b295-197e68e0ddf6", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.103334Z", "iopub.status.busy": "2026-10-14T01:46:02.103183Z", "iopub.status.idle": "2026-10-14T01:46:02.107013Z", "shell.execute_reply": "2026-10-14T01:46:02.106180Z" } }, "outputs": [ { "data": { "text/plain": [ "['both', 'fixed', 'threshold']" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[s for s in dir(CompressCriteria) if not s.startswith(\"__\")]" ] }, { "cell_type": "markdown", "id": "3bd86357-1d07-4905-ba77-9abe688809ff", "metadata": {}, "source": [ "> Using `CompressCriteria` might seem to be a overkill compared to an implementation using simple strings. \n", "However, when it comes to time evolution configurations, since there're a lot of different algorithms and some of them have rather long names,\n", "the `Enum` class can help manage the different algorithms. So here the compression configuration uses the same implementation. This is also for forward compatibility.\n", "\n", "We next compress `mps3` using the compress configuration associated with the `mps3` object. $10^{-5}$ is a relatively tight threshold, so the bond dimension is higher than 5." ] }, { "cell_type": "code", "execution_count": 17, "id": "a1705a7c-c236-4ef0-9b9c-c14b434e563e", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.108631Z", "iopub.status.busy": "2026-10-14T01:46:02.108484Z", "iopub.status.idle": "2026-10-14T01:46:02.120070Z", "shell.execute_reply": "2026-10-14T01:46:02.119221Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2026-10-14 01:46:02,116[DEBUG] size before/after compress: 47.2KiB/26.5KiB, ratio: 1.7784325279905715\n" ] }, { "data": { "text/plain": [ "[1, 2, 8, 15, 17, 18, 11, 8, 4, 1]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mps3.canonicalise().compress()\n", "mps3.bond_dims" ] }, { "cell_type": "markdown", "id": "beaf8b25-65da-4498-b4ef-35c92dfdc720", "metadata": {}, "source": [ "We can expect that the accuracy is also higher" ] }, { "cell_type": "code", "execution_count": 18, "id": "bea28667-b788-40c3-8d02-eaf38102ae45", "metadata": { "execution": { "iopub.execute_input": "2026-10-14T01:46:02.121603Z", "iopub.status.busy": "2026-10-14T01:46:02.121456Z", "iopub.status.idle": "2026-10-14T01:46:02.127412Z", "shell.execute_reply": "2026-10-14T01:46:02.126598Z" } }, "outputs": [ { "data": { "text/plain": [ "(0.0031676697428603628, 0.0031676697428603723, 3.0119866944389965e-15)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "e1 = mps3.expectation(mpo)\n", "e2 = mps2.expectation(mpo)\n", "e1, e2, abs((e1 - e2) / e2)" ] } ], "metadata": { "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.17" } }, "nbformat": 4, "nbformat_minor": 5 }