diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index a8a43dfd..12c6b78f 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v4.7.1
+ uses: actions/setup-python@v5.1.0
with:
python-version: 3.9
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index 85c35645..2b3157eb 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v4.7.1
+ uses: actions/setup-python@v5.1.0
with:
python-version: 3.9
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 49f4f9b0..057c062d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v4.7.1
+ uses: actions/setup-python@v5.1.0
with:
python-version: 3.9
diff --git a/.gitignore b/.gitignore
index ca0dca44..4b891006 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,3 +133,6 @@ dmypy.json
# Pyre type checker
.pyre/
.DS_Store
+
+# Data folders
+data/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3e8f321a..e1cb2566 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,7 +11,7 @@ repos:
- id: trailing-whitespace
- repo: https://github.com/psf/black
- rev: 23.11.0
+ rev: 24.3.0
hooks:
- id: black
entry: black .
@@ -33,13 +33,13 @@ repos:
pass_filenames: false
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.7.1
+ rev: v1.9.0
hooks:
- id: mypy
exclude: '(^examples|^docs)/.*'
- repo: https://github.com/python-poetry/poetry
- rev: 1.7.0
+ rev: 1.8.0
hooks:
- id: poetry-check
- id: poetry-export
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 7c3a951b..a426e9ef 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -6,9 +6,9 @@ build:
python: "3.9"
sphinx:
- configuration: docs/conf.py
+ configuration: docs/source/conf.py
python:
install:
- - requirements: docs/requirements.txt
+ - requirements: docs/source/requirements.txt
- requirements: requirements.txt
diff --git a/LICENSE.md b/LICENSE.md
index 03c9dd0f..387a9345 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022 Aleksandr Berezutskii
+Copyright (c) 2024 Aleksandr Berezutskii
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 30ff8c2f..d92c46cf 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
[![codecov](https://codecov.io/gh/quicophy/mdopt/branch/main/graph/badge.svg?token=4G7VWYX0S2)](https://codecov.io/gh/quicophy/mdopt)
-[![tests](https://github.com/quicophy/mdopt/actions/workflows/tests.yml/badge.svg)](https://github.com/quicophy/mdopt/actions/workflows/tests.yml)
+[![tests](https://github.com/quicophy/mdopt/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/quicophy/mdopt/actions/workflows/tests.yml)
[![Documentation Status](https://readthedocs.org/projects/mdopt/badge/?version=latest)](https://mdopt.readthedocs.io/en/latest/?badge=latest)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/quicophy/mdopt/main.svg)](https://results.pre-commit.ci/latest/github/quicophy/mdopt/main)
[![lint](https://github.com/quicophy/mdopt/actions/workflows/lint.yml/badge.svg)](https://github.com/quicophy/mdopt/actions/workflows/lint.yml)
-[![mypy](https://github.com/quicophy/mdopt/actions/workflows/mypy.yml/badge.svg)](https://github.com/quicophy/mdopt/actions/workflows/mypy.yml)
+[![mypy](https://github.com/quicophy/mdopt/actions/workflows/mypy.yml/badge.svg?branch=main)](https://github.com/quicophy/mdopt/actions/workflows/mypy.yml)
[![Unitary Fund](https://img.shields.io/badge/Supported%20By-Unitary%20Fund-brightgreen.svg?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAACgAAAASCAYAAAApH5ymAAAAt0lEQVRIic2WUQ6AIAiGsXmC7n9Gr1Dzwcb%2BUAjN8b%2B0BNwXApbKRRcF1nGmN5y0Jon7WWO%2B6pgJLhtynzUHKTMNrNo4ZPPldikW10f7qYBEMoTmJ73z2NFHcJkAvbLUpVYmvwIigKeRsjdQEtagZ2%2F0DzsHG2h9iICrRwh2qObbGPIfMDPCMjHNQawpbc71bBZhsrpNYs3qqCFmO%2FgBjHTEqKm7eIdMg9p7PCvma%2Fz%2FwQAMfRHRDTlhQGoOLve1AAAAAElFTkSuQmCC)](http://unitary.fund)
[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)
diff --git a/docs/README.rst b/docs/README.rst
deleted file mode 100644
index 079fa563..00000000
--- a/docs/README.rst
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-.. image:: https://codecov.io/gh/quicophy/mdopt/branch/main/graph/badge.svg?token=4G7VWYX0S2
- :target: https://codecov.io/gh/quicophy/mdopt
- :alt: codecov
-
-
-.. image:: https://github.com/quicophy/mdopt/actions/workflows/tests.yml/badge.svg
- :target: https://github.com/quicophy/mdopt/actions/workflows/tests.yml
- :alt: tests
-
-
-.. image:: https://readthedocs.org/projects/mdopt/badge/?version=latest
- :target: https://mdopt.readthedocs.io/en/latest/?badge=latest
- :alt: Documentation Status
-
-
-.. image:: https://img.shields.io/badge/License-MIT-blue.svg
- :target: https://lbesson.mit-license.org/
- :alt: MIT license
-
-
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
- :target: https://github.com/psf/black
- :alt: Code style: black
-
-
-mdopt
-=====
-
-mdopt is a python package built on top of numpy for discrete optimisation in the tensor-network (specifically, MPS-MPO) language. The code is hosted on github, so please feel free to submit issues and pull requests.
-
-Installation
-------------
-
-Use the package manager `pip `_ to install mdopt.
-
-.. code-block:: bash
-
- pip install mdopt
-
-Usage
------
-
-For usage, see the examples folder.
diff --git a/docs/contractor.rst b/docs/contractor.rst
deleted file mode 100644
index e8181c7e..00000000
--- a/docs/contractor.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Contractor module
-========================
-
-
-The Contractor submodule
-----------------------------------
-
-.. automodule:: mdopt.contractor.contractor
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/examples.rst b/docs/examples.rst
deleted file mode 100644
index 98a957a8..00000000
--- a/docs/examples.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-examples
------------
-
-.. toctree::
- :maxdepth: 4
-
- examples/main_component/main_component.ipynb
- examples/decoding/classical.ipynb
- examples/decoding/quantum.ipynb
diff --git a/docs/examples/__init__.py b/docs/examples/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/examples/decoding/__init__.py b/docs/examples/decoding/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/examples/decoding/classical.ipynb b/docs/examples/decoding/classical.ipynb
deleted file mode 100644
index c9bda623..00000000
--- a/docs/examples/decoding/classical.ipynb
+++ /dev/null
@@ -1,250 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In this experiment, we decode a classical linear error correcting code.\n",
- "First, we build the MPS containing the superposition of all codewords.\n",
- "Then, we demostrate simple decoding of a classical LDPC code using Dephasing DMRG --\n",
- "our own built-in DMRG-like optimisation algorithm to solve the main component problem --\n",
- "the problem of finding a computational basis state cotributing the most to a given state."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "import qecstruct as qec\n",
- "from mdopt.optimiser.utils import (\n",
- " ConstraintString,\n",
- " IDENTITY,\n",
- " SWAP,\n",
- " XOR_BULK,\n",
- " XOR_LEFT,\n",
- " XOR_RIGHT,\n",
- ")\n",
- "from examples.decoding.decoding import (\n",
- " linear_code_constraint_sites,\n",
- " linear_code_prepare_message,\n",
- " linear_code_codewords,\n",
- ")\n",
- "from examples.decoding.decoding import (\n",
- " apply_bias_channel,\n",
- " apply_constraints,\n",
- " decode,\n",
- ")\n",
- "from mdopt.mps.utils import create_simple_product_state, create_custom_product_state\n",
- "from mdopt.utils.utils import mpo_to_matrix"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Fixing a random seed\n",
- "SEED = 123\n",
- "\n",
- "tensors = [XOR_LEFT, XOR_BULK, SWAP, XOR_RIGHT]\n",
- "\n",
- "# Defining the parameters of a classical LDPC code.\n",
- "NUM_BITS, NUM_CHECKS = 10, 6\n",
- "CHECK_DEGREE, BIT_DEGREE = 5, 3\n",
- "if NUM_BITS / NUM_CHECKS != CHECK_DEGREE / BIT_DEGREE:\n",
- " raise ValueError(\"The Tanner graph of the code must be bipartite.\")\n",
- "\n",
- "# Constructing the code as a qecstruct object.\n",
- "example_code = qec.random_regular_code(\n",
- " NUM_BITS, NUM_CHECKS, BIT_DEGREE, CHECK_DEGREE, qec.Rng(SEED)\n",
- ")\n",
- "\n",
- "# Preparing the initial state.\n",
- "state = create_simple_product_state(NUM_BITS, which=\"+\")\n",
- "state_dense = state.dense(flatten=True)\n",
- "\n",
- "# Getting the sites where each string of constraints should be applied.\n",
- "code_constraint_sites = linear_code_constraint_sites(example_code)\n",
- "\n",
- "print(\"\")\n",
- "print(\"Checking the codeword superposition state:\")\n",
- "print(\"\")\n",
- "\n",
- "# Preparing the codeword superposition state by the MPS-MPO evolution.\n",
- "state = apply_constraints(state, code_constraint_sites, tensors)\n",
- "\n",
- "# Preparing the codeword superposition state in the dense form.\n",
- "for j in range(NUM_CHECKS):\n",
- "\n",
- " # Preparing the MPO.\n",
- " constraint_string = ConstraintString(tensors, code_constraint_sites[j])\n",
- " constraint_mpo = constraint_string.mpo()\n",
- "\n",
- " # Finding the starting site of the MPS to build a correct dense-form operator.\n",
- " START_SITE = min(constraint_string.flat())\n",
- "\n",
- " # Preparing the dense-form operator.\n",
- " identities_l = [IDENTITY for _ in range(START_SITE)]\n",
- " identities_r = [\n",
- " IDENTITY for _ in range(NUM_BITS - len(constraint_mpo) - START_SITE)\n",
- " ]\n",
- " full_mpo = identities_l + constraint_mpo + identities_r\n",
- " mpo_dense = mpo_to_matrix(full_mpo, interlace=False, group=True)\n",
- "\n",
- " # Doing the contraction in dense form.\n",
- " state_dense = mpo_dense @ state_dense\n",
- "\n",
- "# Tolerance under which we round tensor elements to zero.\n",
- "TOL = 1e-12\n",
- "mps_dense = state.dense(flatten=True)\n",
- "mps_dense[np.abs(mps_dense) < TOL] = 0\n",
- "\n",
- "# Retreiving codewords.\n",
- "cwords = linear_code_codewords(example_code)\n",
- "cwords_to_compare_mps = np.flatnonzero(mps_dense)\n",
- "cwords_to_compare_dense = np.flatnonzero(state_dense)\n",
- "\n",
- "print()\n",
- "print(\"Codewords from the generator matrix:\")\n",
- "print(cwords)\n",
- "print(\"Codewords from the dense-form simulation:\")\n",
- "print(cwords_to_compare_mps)\n",
- "print(\"Codewords from the MPS-form simulation:\")\n",
- "print(cwords_to_compare_dense)\n",
- "print(\"\")\n",
- "print(\n",
- " \"All lists of codewords match:\",\n",
- " np.logical_and(\n",
- " np.array_equal(cwords, cwords_to_compare_mps),\n",
- " np.array_equal(cwords_to_compare_mps, cwords_to_compare_dense),\n",
- " ),\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(\"\")\n",
- "print(\"Retreiving a perturbed codeword:\")\n",
- "print(\"\")\n",
- "\n",
- "# Defining the parameters of a classical LDPC code.\n",
- "NUM_BITS, NUM_CHECKS = 16, 12\n",
- "CHECK_DEGREE, BIT_DEGREE = 4, 3\n",
- "if NUM_BITS / NUM_CHECKS != CHECK_DEGREE / BIT_DEGREE:\n",
- " raise ValueError(\"The Tanner graph of the code must be bipartite.\")\n",
- "\n",
- "# Defining the bias channel parameter and the error probability.\n",
- "PROB_ERROR = 0.15\n",
- "PROB_CHANNEL = PROB_ERROR\n",
- "\n",
- "# Maximum bond dimension for contractor/DMRG.\n",
- "CHI_MAX_CONTRACTOR = 1e4\n",
- "CHI_MAX_DMRG = 1e4\n",
- "# Number of DMRG sweeps.\n",
- "NUM_RUNS = 1\n",
- "\n",
- "# Constructing the code as a qecstruct object.\n",
- "example_code = qec.random_regular_code(\n",
- " NUM_BITS, NUM_CHECKS, BIT_DEGREE, CHECK_DEGREE, qec.Rng(SEED)\n",
- ")\n",
- "\n",
- "# Getting the sites where each string of constraints should be applied.\n",
- "code_constraint_sites = linear_code_constraint_sites(example_code)\n",
- "\n",
- "# Building an initial and a perturbed codeword.\n",
- "INITIAL_CODEWORD, PERTURBED_CODEWORD = linear_code_prepare_message(\n",
- " example_code, PROB_ERROR, error_model=qec.BinarySymmetricChannel, seed=SEED\n",
- ")\n",
- "print(\"The initial codeword is\", INITIAL_CODEWORD)\n",
- "print(\"The perturbed codeword is\", PERTURBED_CODEWORD)\n",
- "print(\"\")\n",
- "\n",
- "# Building the corresponding matrix product states.\n",
- "initial_codeword_state = create_custom_product_state(\n",
- " INITIAL_CODEWORD, form=\"Right-canonical\"\n",
- ")\n",
- "perturbed_codeword_state = create_custom_product_state(\n",
- " PERTURBED_CODEWORD, form=\"Right-canonical\"\n",
- ")\n",
- "\n",
- "# Passing the perturbed codeword state through the bias channel.\n",
- "perturbed_codeword_state = apply_bias_channel(\n",
- " basis_mps=perturbed_codeword_state,\n",
- " basis_string=PERTURBED_CODEWORD,\n",
- " prob_channel=PROB_CHANNEL,\n",
- ")\n",
- "\n",
- "print(\"Applying constraints:\")\n",
- "print(\"\")\n",
- "# Applying the parity constraints defined by the code.\n",
- "perturbed_codeword_state = apply_constraints(\n",
- " perturbed_codeword_state,\n",
- " code_constraint_sites,\n",
- " tensors,\n",
- " chi_max=CHI_MAX_CONTRACTOR,\n",
- " renormalise=True,\n",
- " strategy=\"naive\",\n",
- " silent=False,\n",
- ")\n",
- "\n",
- "print(\"\")\n",
- "print(\"Decoding:\")\n",
- "print(\"\")\n",
- "# Decoding the perturbed codeword.\n",
- "dmrg_container, success = decode(\n",
- " message=perturbed_codeword_state,\n",
- " codeword=initial_codeword_state,\n",
- " code=example_code,\n",
- " num_runs=NUM_RUNS,\n",
- " chi_max_dmrg=CHI_MAX_DMRG,\n",
- " cut=1e-10,\n",
- " silent=False,\n",
- ")\n",
- "print(\"\")\n",
- "print(\n",
- " \"The overlap of the density MPO main component and the initial codeword state: \",\n",
- " success,\n",
- ")\n",
- "print(\n",
- " \"__________________________________________________________________________________________\"\n",
- ")"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "mdopt-ZdbamFdU-py3.9",
- "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.2"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "cd00668ec6929851fcf19d7aebdf8f5927f35d0f54b527f252ebcdaf64fd8c43"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/docs/examples/decoding/decoding.py b/docs/examples/decoding/decoding.py
deleted file mode 100644
index e31a1108..00000000
--- a/docs/examples/decoding/decoding.py
+++ /dev/null
@@ -1,623 +0,0 @@
-"""
-Below, we define some decoding-specific functions over the MPS/MPO entities
-we encounter during the decoding process as well as the functions we use
-to generate and operate over both classical and quantum error correcting codes.
-"""
-
-from functools import reduce
-from typing import cast, Union, Optional, List, Tuple
-
-import numpy as np
-from tqdm import tqdm
-import qecstruct as qec
-from more_itertools import powerset
-
-from mdopt.mps.explicit import ExplicitMPS
-from mdopt.mps.canonical import CanonicalMPS
-from mdopt.mps.utils import find_orth_centre, inner_product, create_simple_product_state
-from mdopt.contractor.contractor import apply_one_site_operator, mps_mpo_contract
-from mdopt.optimiser.dephasing_dmrg import DephasingDMRG
-from mdopt.optimiser.utils import ConstraintString
-
-
-def bias_channel(p_bias: np.float32 = np.float32(0.5), which: str = "0") -> np.ndarray:
- """
- Here, we define bias channel -- an operator which will bias us towards the initial message
- while decoding by ranking the bitstrings according to Hamming distance from the latter.
- This function returns a one-site bias channel MPO which
- acts on one-qubit computational basis states as follows:
- |0> -> √(1-p)|0> + √p|1>,
- |1> -> √(1-p)|1> + √p|0>,
- Note, that this operation is unitary, which means that it preserves the canonical form.
-
- Parameters
- ----------
- p_bias : np.float32
- Probability of the channel.
- which : str
- "0" or "1", depending on which one-qubit basis state we are acting on.
-
- Returns
- -------
- b_ch : np.ndarray
- The corresponding one-site MPO.
- """
-
- if not 0 <= p_bias <= 1:
- raise ValueError(
- f"The channel parameter `p_bias` should be a probability, "
- f"given {p_bias}."
- )
- if which not in ["0", "1", "+"]:
- raise ValueError("Invalid one-qubit basis state given.")
-
- if which == "0":
- b_channel = np.array(
- [
- [np.sqrt(1 - p_bias), np.sqrt(p_bias)],
- [np.sqrt(p_bias), -np.sqrt(1 - p_bias)],
- ]
- )
- if which == "1":
- b_channel = np.array(
- [
- [-np.sqrt(1 - p_bias), np.sqrt(p_bias)],
- [np.sqrt(p_bias), np.sqrt(1 - p_bias)],
- ]
- )
- if which == "+":
- b_channel = np.array(
- [
- [1.0, 0.0],
- [0.0, 1.0],
- ]
- )
-
- return b_channel
-
-
-def apply_bias_channel(
- basis_mps: Union[ExplicitMPS, CanonicalMPS],
- basis_string: str,
- prob_channel: np.float32 = np.float32(0.5),
-) -> Union[ExplicitMPS, CanonicalMPS]:
- """
- The function which applies a bias channel to a computational-basis-state MPS.
-
- Parameters
- ----------
- basis_mps : Union[ExplicitMPS, CanonicalMPS]
- The computational-basis-state MPS, e.g., ``|010010>``.
- basis_string : str
- The string of "0", "1" and "+" which corresponds to ``basis_mps``.
- prob_channel : np.float32
- The bias channel probability.
-
-
- Returns
- -------
- biased_mps : CanonicalMPS
- The resulting MPS.
- """
-
- if len(basis_mps) != len(basis_string):
- raise ValueError(
- f"The lengths of `basis_mps` and `codeword_string` should be equal, but given the "
- f"MPS of length {len(basis_mps)} and the string of length {len(basis_string)}."
- )
-
- biased_mps_tensors = []
- for i, mps_tensor in enumerate(basis_mps.tensors):
- biased_mps_tensors.append(
- apply_one_site_operator(
- tensor=mps_tensor,
- operator=bias_channel(prob_channel, which=basis_string[i]),
- )
- )
-
- if isinstance(basis_mps, ExplicitMPS):
- return ExplicitMPS(
- tensors=biased_mps_tensors,
- singular_values=basis_mps.singular_values,
- tolerance=basis_mps.tolerance,
- chi_max=basis_mps.chi_max,
- )
-
- if isinstance(basis_mps, CanonicalMPS):
- return CanonicalMPS(
- tensors=biased_mps_tensors,
- orth_centre=basis_mps.orth_centre,
- tolerance=basis_mps.tolerance,
- chi_max=basis_mps.chi_max,
- )
-
-
-# Below, we define some utility functions to operate with data structures from `qecstruct` --
-# an error-correction library we are using in this example.
-
-
-def bin_vec_to_dense(vector: "qec.sparse.BinaryVector") -> np.ndarray:
- """
- Given a vector (1D array) in the ``qecstruct.sparse.BinaryVector`` format
- (native to ``qecstruct``), returns its dense representation.
-
- Parameters
- ----------
- vector : qec.sparse.BinaryVector
- The vector we want to densify.
-
- Returns
- -------
- array : np.ndarray
- The dense representation.
- """
-
- array = np.zeros(vector.len(), dtype=int)
- for pos in vector:
- array[pos] = 1
- return array
-
-
-def linear_code_checks(code: "qec.LinearCode") -> List[List[int]]:
- """
- Given a linear code, returns a list of its checks, where each check
- is represented as a list of indices of the bits touched by it.
-
- Parameters
- ----------
- code : qec.LinearCode
- Linear code object.
-
- Returns
- -------
- checks : List[List[int]]
- List of checks.
- """
-
- parity_matrix = code.par_mat()
- array = np.zeros((parity_matrix.num_rows(), parity_matrix.num_columns()), dtype=int)
- for row, cols in enumerate(parity_matrix.rows()):
- for col in cols:
- array[row, col] = 1
- return [list(np.nonzero(row)[0]) for row in array]
-
-
-def linear_code_constraint_sites(code: "qec.LinearCode") -> List[List[List[int]]]:
- """
- Returns the list of MPS sites where the logical constraints should be applied.
-
- Parameters
- ----------
- code : qec.LinearCode
- Linear code object.
-
- Returns
- -------
- strings : List[List[List[int]]]
- List of MPS sites.
- """
-
- sites_all = linear_code_checks(code)
- check_degree = len(sites_all[0])
- constraints_strings = []
-
- for sites in sites_all:
- # Retreiving the sites indices where we apply the "bulk"/"boundary" XOR tensors.
- xor_left_sites = [sites[0]]
- xor_bulk_sites = [sites[i] for i in range(1, check_degree - 1)]
- xor_right_sites = [sites[-1]]
-
- # Retreiving the sites indices where we apply the SWAP tensors.
- swap_sites = list(range(sites[0] + 1, sites[-1]))
- for k in range(1, check_degree - 1):
- swap_sites.remove(sites[k])
-
- constraints_strings.append(
- [xor_left_sites, xor_bulk_sites, swap_sites, xor_right_sites]
- )
-
- return cast(List[List[List[int]]], constraints_strings)
-
-
-def linear_code_codewords(code: "qec.LinearCode") -> np.ndarray:
- """
- Returns the list of codewords of a linear code. Codewords are returned
- as integers in most-significant-bit-first convention.
-
- Parameters
- ----------
- code : qec.LinearCode
- Linear code object.
-
- Returns
- -------
- codewords : np.ndarray
- The codewords.
- """
-
- codewords = []
-
- gen_mat = code.gen_mat()
- rows_bin = gen_mat.rows()
- rows_dense = [bin_vec_to_dense(row_bin) for row_bin in rows_bin]
- rows_int = [row.dot(1 << np.arange(row.size)[::-1]) for row in rows_dense]
-
- # Append the all-zeros codeword which is always a codeword.
- codewords.append(0)
-
- # Append the rows of the generator matrix.
- for basis_codeword in rows_int:
- codewords.append(basis_codeword)
-
- # Append all linear combinations.
- for generators in powerset(rows_int):
- if len(generators) > 1:
- codewords.append(reduce(np.bitwise_xor, generators))
-
- return np.sort(np.array(codewords))
-
-
-def css_code_checks(code: qec.CssCode) -> Tuple[List[int]]:
- """
- Given a quantum CSS code, returns a list of its checks, where each check
- is represented as a list of indices of the bits adjacent to it.
-
- Parameters
- ----------
- code : qec.CssCode
- The CSS code object.
-
- Returns
- -------
- checks : Tuple[List[List[int]]
- A tuple of two lists, where the first one corresponds to X checks and
- the second one -- to Z checks.
- """
-
- parity_matrix_x = code.x_stabs_binary()
- array_x = np.zeros(
- (parity_matrix_x.num_rows(), parity_matrix_x.num_columns()), dtype=int
- )
- for row, cols in enumerate(parity_matrix_x.rows()):
- for col in cols:
- array_x[row, col] = 1
-
- parity_matrix_z = code.z_stabs_binary()
- array_z = np.zeros(
- (parity_matrix_z.num_rows(), parity_matrix_z.num_columns()), dtype=int
- )
- for row, cols in enumerate(parity_matrix_z.rows()):
- for col in cols:
- array_z[row, col] = 1
-
- checks_x = [
- 2 * np.nonzero(row)[0] + code.num_x_logicals() + code.num_z_logicals()
- for row in array_x
- ]
- checks_x = [list(check_x) for check_x in checks_x]
- checks_z = [
- 2 * np.nonzero(row)[0] + code.num_x_logicals() + code.num_z_logicals() + 1
- for row in array_z
- ]
- checks_z = [list(check_z) for check_z in checks_z]
-
- return checks_x, checks_z
-
-
-def css_code_constraint_sites(code: qec.CssCode) -> Tuple[List[int]]:
- """
- Returns the list of MPS sites where the logical constraints should be applied.
-
- Parameters
- ----------
- code : qec.CssCode
- CSS code object.
-
- Returns
- -------
- strings : Tuple[List[int]]
- List of MPS sites.
- """
-
- sites_x, sites_z = css_code_checks(code)
-
- constraints_strings_x = []
- constraints_strings_z = []
-
- for sites in sites_x:
- xor_left_sites_x = [sites[0]]
- xor_bulk_sites_x = [sites[i] for i in range(1, len(sites) - 1)]
- xor_right_sites_x = [sites[-1]]
-
- swap_sites_x = list(range(sites[0] + 1, sites[-1]))
- for k in range(1, len(sites) - 1):
- swap_sites_x.remove(sites[k])
-
- constraints_strings_x.append(
- [xor_left_sites_x, xor_bulk_sites_x, swap_sites_x, xor_right_sites_x]
- )
-
- for sites in sites_z:
- xor_left_sites_z = [sites[0]]
- xor_bulk_sites_z = [sites[i] for i in range(1, len(sites) - 1)]
- xor_right_sites_z = [sites[-1]]
-
- swap_sites_z = list(range(sites[0] + 1, sites[-1]))
- for k in range(1, len(sites) - 1):
- swap_sites_z.remove(sites[k])
-
- constraints_strings_z.append(
- [xor_left_sites_z, xor_bulk_sites_z, swap_sites_z, xor_right_sites_z]
- )
-
- return constraints_strings_x, constraints_strings_z
-
-
-def css_code_logicals(code: qec.CssCode):
- """
- Returns the list of MPS sites where the logical constraints should be applied.
-
- Parameters
- ----------
- code : qec.CssCode
- The CSS code object.
-
- Returns
- -------
- logicals : Tuple[List[int]]
- List of logical operators, first X, then Z.
- """
-
- log_matrix_x = code.z_logicals_binary()
- array_x = np.zeros((log_matrix_x.num_rows(), log_matrix_x.num_columns()), dtype=int)
- for row, cols in enumerate(log_matrix_x.rows()):
- for col in cols:
- array_x[row, col] = 1
-
- log_matrix_z = code.x_logicals_binary()
- array_z = np.zeros((log_matrix_z.num_rows(), log_matrix_z.num_columns()), dtype=int)
- for row, cols in enumerate(log_matrix_z.rows()):
- for col in cols:
- array_z[row, col] = 1
-
- x_logicals = [
- 2 * np.nonzero(row)[0] + code.num_x_logicals() + code.num_z_logicals() + 1
- for row in array_x
- ]
- x_logicals = [list(x_logical) for x_logical in x_logicals]
- z_logicals = [
- 2 * np.nonzero(row)[0] + code.num_x_logicals() + code.num_z_logicals()
- for row in array_z
- ]
- z_logicals = [list(z_logical) for z_logical in z_logicals]
-
- return z_logicals[0], x_logicals[0]
-
-
-def css_code_logicals_sites(code: qec.CssCode) -> Tuple[List[int]]:
- """
- Returns the list of MPS sites where the logical operators should be applied.
-
- Parameters
- ----------
- code : qec.CssCode
- CSS code object.
-
- Returns
- -------
- strings : Tuple[List[int]]
- List of MPS sites.
- """
-
- sites_x, sites_z = css_code_logicals(code)
-
- copy_site_x = [0]
- copy_site_z = [1]
-
- xor_right_site_x = [sites_x[-1]]
- xor_right_site_z = [sites_z[-1]]
-
- xor_bulk_sites_x = [sites_x[i] for i in range(len(sites_x) - 1)]
- xor_bulk_sites_z = [sites_z[i] for i in range(len(sites_z) - 1)]
-
- swap_sites_x = list(range(copy_site_x[0] + 1, xor_right_site_x[0]))
- swap_sites_x = [site for site in swap_sites_x if site not in xor_bulk_sites_x]
- swap_sites_z = list(range(copy_site_z[0] + 1, xor_right_site_z[0]))
- swap_sites_z = [site for site in swap_sites_z if site not in xor_bulk_sites_z]
-
- string_x = [copy_site_x, xor_bulk_sites_x, swap_sites_x, xor_right_site_x]
- string_z = [copy_site_z, xor_bulk_sites_z, swap_sites_z, xor_right_site_z]
-
- return string_x, string_z
-
-
-def linear_code_prepare_message(
- code: "qec.LinearCode",
- prob_error: np.float32 = np.float32(0.5),
- error_model: "qec.noise_model" = qec.BinarySymmetricChannel,
- seed: Optional[int] = None,
-) -> Tuple[str, str]:
- """
- This function prepares a message in the form of a random codeword
- and its perturbed version after applying an error model.
-
- Parameters
- ----------
- code : qec.LinearCode
- Linear code object.
- prob_error : np.float32
- Error probability of the error model.
- error_model : qec.noise_model
- The error model used to flip bits of a random codeword.
- seed : Optional[int]
- Random seed.
-
- Returns
- -------
- initial_codeword : str
- The bitstring of the initial codeword.
- perturbed_codeword : str
- The bitstring of the perturbed codeword.
- """
-
- num_bits = len(code)
- initial_codeword = code.random_codeword(qec.Rng(seed))
- perturbed_codeword = initial_codeword + error_model(prob_error).sample(
- num_bits, qec.Rng(seed)
- )
- initial_codeword = "".join(str(bit) for bit in bin_vec_to_dense(initial_codeword))
- perturbed_codeword = "".join(
- str(bit) for bit in bin_vec_to_dense(perturbed_codeword)
- )
-
- return initial_codeword, perturbed_codeword
-
-
-# The functions below are used to apply constraints to a codeword MPS and perform actual decoding.
-
-
-def apply_constraints(
- mps: Union[ExplicitMPS, CanonicalMPS],
- strings: List[List[int]],
- logical_tensors: List[np.ndarray],
- chi_max: int = int(1e4),
- renormalise: bool = False,
- strategy: str = "naive",
- silent: bool = False,
-) -> CanonicalMPS:
- """
- This function applies logical constraints to an MPS.
-
- Parameters
- ----------
- mps : Union[ExplicitMPS, CanonicalMPS]
- The MPS to which the logical constraints are being applied.
- strings : List[List[int]]
- The list of arguments for :class:`ConstraintString`.
- logical_tensors : List[np.ndarray]
- List of logical tensors for :class:`ConstraintString`.
- chi_max : int
- Maximum bond dimension to keep in the contractor.
- renormalise : bool
- Whether to renormalise the singular values at each MPS bond involved in contraction.
- strategy : str
- The contractor strategy.
- silent : bool
- Whether to show the progress bar or not.
-
- Returns
- -------
- mps : CanonicalMPS
- The resulting MPS.
- """
-
- if strategy == "naive":
- for string in tqdm(strings, disable=silent):
- # Preparing the MPO.
- string = ConstraintString(logical_tensors, string)
- mpo = string.mpo()
-
- # Finding the starting site for the MPS to perform contraction.
- start_site = min(string.flat())
-
- # Preparing the MPS for contraction.
- if isinstance(mps, ExplicitMPS):
- mps = mps.mixed_canonical(orth_centre=start_site)
-
- if isinstance(mps, CanonicalMPS):
- if mps.orth_centre is None:
- orth_centres, flags_left, flags_right = find_orth_centre(
- mps, return_orth_flags=True
- )
-
- # Managing possible issues with multiple orthogonality centres
- # arising if we do not renormalise while contracting.
- if orth_centres and len(orth_centres) == 1:
- mps.orth_centre = orth_centres[0]
- # Convention.
- if all(flags_left) and all(flags_right):
- mps.orth_centre = 0
- elif flags_left in ([True] + [False] * (mps.num_sites - 1)):
- if flags_right == [not flag for flag in flags_left]:
- mps.orth_centre = mps.num_sites - 1
- elif flags_left in ([True] * (mps.num_sites - 1) + [False]):
- if flags_right == [not flag for flag in flags_left]:
- mps.orth_centre = 0
- elif all(flags_right):
- mps.orth_centre = 0
- elif all(flags_left):
- mps.orth_centre = mps.num_sites - 1
-
- mps = cast(
- Union[ExplicitMPS, CanonicalMPS],
- mps.move_orth_centre(final_pos=start_site),
- )
-
- # Doing the contraction.
- mps = mps_mpo_contract(
- mps,
- mpo,
- start_site,
- renormalise=renormalise,
- chi_max=chi_max,
- inplace=False,
- )
-
- return cast(CanonicalMPS, mps)
-
-
-def decode(
- message: Union[ExplicitMPS, CanonicalMPS],
- codeword: Union[ExplicitMPS, CanonicalMPS],
- code: "qec.LinearCode",
- num_runs: int = int(1),
- chi_max_dmrg: int = int(1e4),
- cut: np.float32 = np.float32(1e-12),
- silent: bool = False,
-) -> Tuple[DephasingDMRG, np.float32]:
- """
- This function performs actual decoding of a message given a code and
- the DMRG truncation parameters.
- Returns the overlap between the decoded message given the initial message.
-
- Parameters
- ----------
- message : Union[ExplicitMPS, CanonicalMPS]
- The message MPS.
- codeword : Union[ExplicitMPS, CanonicalMPS]
- The codeword MPS.
- code : qec.LinearCode
- Linear code object.
- num_runs : int
- Number of DMRG sweeps.
- chi_max_dmrg : int
- Maximum bond dimension to keep in the DMRG algorithm.
- cut : np.float32
- The lower boundary of the spectrum in the DMRG algorithm.
- All the singular values smaller than that will be discarded.
-
- Returns
- -------
- engine : DephasingDMRG
- The container class for the Deohasing DMRG algorithm, see :class:`mdopt.optimiser.DMRG`.
- overlap : np.float32
- The overlap between the decoded message and a given codeword,
- computed as the following inner product ||.
- """
-
- # Creating an all-plus state to start the DMRG with.
- num_bits = len(code)
- mps_dmrg_start = create_simple_product_state(num_bits, which="+")
- engine = DephasingDMRG(
- mps_dmrg_start,
- message,
- chi_max=chi_max_dmrg,
- cut=cut,
- mode="LA",
- silent=silent,
- )
- engine.run(num_runs)
- mps_dmrg_final = engine.mps.right_canonical()
- overlap = abs(inner_product(mps_dmrg_final, codeword))
-
- return engine, overlap
diff --git a/docs/examples/decoding/quantum.ipynb b/docs/examples/decoding/quantum.ipynb
deleted file mode 100644
index a60e1686..00000000
--- a/docs/examples/decoding/quantum.ipynb
+++ /dev/null
@@ -1,410 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In this experiment, we decode Shor's nine-qubit quantum error correcting code.\n",
- "We show decoding of the Shor's nine-qubit code using Dephasing DMRG, which is our own built-in DMRG-like optimisation algorithm to solve the main component problem which in its turn is the problem of finding a computational basis state cotributing the most to a given state."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "import qecstruct as qec\n",
- "from mdopt.mps.utils import marginalise, create_custom_product_state\n",
- "from mdopt.contractor.contractor import mps_mpo_contract\n",
- "from mdopt.optimiser.utils import (\n",
- " SWAP,\n",
- " COPY_LEFT,\n",
- " XOR_BULK,\n",
- " XOR_LEFT,\n",
- " XOR_RIGHT,\n",
- ")\n",
- "from examples.decoding.decoding import (\n",
- " css_code_checks,\n",
- " css_code_logicals,\n",
- " css_code_logicals_sites,\n",
- " css_code_constraint_sites,\n",
- ")\n",
- "from examples.decoding.decoding import (\n",
- " apply_constraints,\n",
- " apply_bias_channel,\n",
- ")"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let us first import the code from `qecstruct` and take a look at it."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "X stabilizers:\n",
- "[0, 1, 2, 3, 4, 5]\n",
- "[3, 4, 5, 6, 7, 8]\n",
- "Z stabilizers:\n",
- "[0, 1]\n",
- "[1, 2]\n",
- "[3, 4]\n",
- "[4, 5]\n",
- "[6, 7]\n",
- "[7, 8]"
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "code = qec.shor_code()\n",
- "code"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "This quantum error correcting code is defined on 9 physical qubits and has 2 logical operators. This means we will need $9*2 + 2 = 20$ sites in our MPS."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [],
- "source": [
- "num_sites = 2 * len(code) + code.num_x_logicals() + code.num_z_logicals()\n",
- "num_logicals = code.num_x_logicals() + code.num_z_logicals()\n",
- "assert num_sites == 20\n",
- "assert num_logicals == 2"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now, let us define the initial state. First of all we will check that no error implies no correction. This means starting from the all-zeros state followed by decoding will return all-zeros state for the logical operators (the final logical operator will be identity operator)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {},
- "outputs": [],
- "source": [
- "error = \"000000000010000111\"\n",
- "string_state = \"++\" + error\n",
- "error_state = create_custom_product_state(string=string_state, form=\"Right-canonical\")"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Here, we get the sites where the checks will be applied. We will need to construct MPOs using this data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "X checks:\n",
- "[2, 4, 6, 8, 10, 12]\n",
- "[8, 10, 12, 14, 16, 18]\n",
- "Z checks:\n",
- "[3, 5]\n",
- "[5, 7]\n",
- "[9, 11]\n",
- "[11, 13]\n",
- "[15, 17]\n",
- "[17, 19]\n"
- ]
- }
- ],
- "source": [
- "checks_x, checks_z = css_code_checks(code)\n",
- "print(\"X checks:\")\n",
- "for check in checks_x:\n",
- " print(check)\n",
- "print(\"Z checks:\")\n",
- "for check in checks_z:\n",
- " print(check)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "These lists mention only the sites where we will apply the XOR constraints. However, the MPOs will also consist of other tensors, such as SWAPs (wire crossings) and boundary XOR constraints. In what follows we define the list of these auxiliary tensors and the corresponding sites where they reside."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Full X-check lists of sites:\n",
- "[[2], [4, 6, 8, 10], [3, 5, 7, 9, 11], [12]]\n",
- "[[8], [10, 12, 14, 16], [9, 11, 13, 15, 17], [18]]\n",
- "Full Z-check lists of sites:\n",
- "[[3], [], [4], [5]]\n",
- "[[5], [], [6], [7]]\n",
- "[[9], [], [10], [11]]\n",
- "[[11], [], [12], [13]]\n",
- "[[15], [], [16], [17]]\n",
- "[[17], [], [18], [19]]\n"
- ]
- }
- ],
- "source": [
- "csscode_constraint_sites = css_code_constraint_sites(code)\n",
- "print(\"Full X-check lists of sites:\")\n",
- "for string in csscode_constraint_sites[0]:\n",
- " print(string)\n",
- "print(\"Full Z-check lists of sites:\")\n",
- "for string in csscode_constraint_sites[1]:\n",
- " print(string)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let us now take a look at the logical operators."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[0, 1, 2]\n",
- "\n",
- "[0, 3, 6]\n",
- "\n"
- ]
- }
- ],
- "source": [
- "print(code.x_logicals_binary())\n",
- "print(code.z_logicals_binary())"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We need to again translate it to our MPO language by changing the indices since we add the logical-operator sites to the end of the MPS."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[2, 4, 6]\n",
- "[3, 9, 15]\n"
- ]
- }
- ],
- "source": [
- "print(css_code_logicals(code)[0])\n",
- "print(css_code_logicals(code)[1])"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now goes the same operation of adding sites where auxiliary tensors go."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[0], [2, 4], [1, 3, 5], [6]]\n",
- "[[1], [3, 9], [2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14], [15]]\n"
- ]
- }
- ],
- "source": [
- "print(css_code_logicals_sites(code)[0])\n",
- "print(css_code_logicals_sites(code)[1])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {},
- "outputs": [],
- "source": [
- "tensors_constraints = [XOR_LEFT, XOR_BULK, SWAP, XOR_RIGHT]\n",
- "tensors_logicals = [COPY_LEFT, XOR_BULK, SWAP, XOR_RIGHT]\n",
- "constraint_sites_logicals = css_code_logicals_sites(code)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now the fun part, contracting the logical MPOs."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {},
- "outputs": [],
- "source": [
- "renormalise = True\n",
- "error_state = apply_bias_channel(\n",
- " basis_mps=error_state, basis_string=string_state, prob_channel=0.1\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "100%|██████████| 2/2 [00:00<00:00, 324.99it/s]\n",
- "100%|██████████| 6/6 [00:00<00:00, 1956.45it/s]\n",
- "100%|██████████| 2/2 [00:00<00:00, 483.58it/s]\n"
- ]
- }
- ],
- "source": [
- "error_state = apply_constraints(\n",
- " error_state,\n",
- " csscode_constraint_sites[0],\n",
- " tensors_constraints,\n",
- " renormalise=renormalise,\n",
- ")\n",
- "error_state = apply_constraints(\n",
- " error_state,\n",
- " csscode_constraint_sites[1],\n",
- " tensors_constraints,\n",
- " renormalise=renormalise,\n",
- ")\n",
- "error_state = apply_constraints(\n",
- " error_state,\n",
- " constraint_sites_logicals,\n",
- " tensors_logicals,\n",
- " renormalise=renormalise,\n",
- ")"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Marginalise over the message bits to get the logical operator MPS."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[0.00110897 0.00278983 0.00142582 0.00358692]\n"
- ]
- }
- ],
- "source": [
- "sites_to_marginalise = list(range(num_logicals, len(error_state)))\n",
- "logical = marginalise(mps=error_state, sites_to_marginalise=sites_to_marginalise)\n",
- "print(logical.dense(flatten=True))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "mdopt-ZdbamFdU-py3.11",
- "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.2"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "64c06a7280c9749d5771a76ca6109d7df6b2615ddb3b9b0828f83fb315c7f8a2"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/docs/examples/ising/__init__.py b/docs/examples/ising/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/examples/ising/ground_state.ipynb b/docs/examples/ising/ground_state.ipynb
deleted file mode 100644
index 1ef973b5..00000000
--- a/docs/examples/ising/ground_state.ipynb
+++ /dev/null
@@ -1,273 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In this experiment we will use our DMRG optimiser to find the ground state\n",
- "of an open-bounded transverse field Ising chain. The Hamiltonian reads:\n",
- "$$\n",
- "H = - \\sum_{i=1}^{N-1} Z_i Z_{i+1} - h * \\sum_{i=1}^{N} X_i.\n",
- "$$\n",
- "Here, the magnetic field is in the units of the nearest-neighbour ZZ-interaction.\n",
- "We find the ground state of this Hamiltonian and compute some observables."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from opt_einsum import contract\n",
- "from tqdm import tqdm\n",
- "from scipy.sparse.linalg import eigsh\n",
- "\n",
- "from ising import IsingExact, IsingMPO\n",
- "from mdopt.mps.utils import create_simple_product_state\n",
- "from mdopt.optimiser.dmrg import DMRG as dmrg"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let us first check we build the right MPO. We do this by virtue of constructing a 3-site MPO and then changing it into the Hamiltonian."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Checking the exact and the MPO Hamiltonians being the same: True\n"
- ]
- }
- ],
- "source": [
- "NUM_SITES = 3\n",
- "H_MAGNETIC = 1.0\n",
- "ising_exact = IsingExact(num_sites=NUM_SITES, h_magnetic=H_MAGNETIC)\n",
- "ising_mpo = IsingMPO(num_sites=NUM_SITES, h_magnetic=H_MAGNETIC)\n",
- "ham_mpo = ising_mpo.hamiltonian_mpo()\n",
- "m = contract(\n",
- " \"zabc, adef, dygh -> begcfh\",\n",
- " ham_mpo[0],\n",
- " ham_mpo[1],\n",
- " ham_mpo[2],\n",
- " optimize=[(0, 1), (0, 1)],\n",
- ").reshape((8, 8))\n",
- "\n",
- "print(\n",
- " \"Checking the exact and the MPO Hamiltonians being the same:\",\n",
- " (ising_exact.hamiltonian_dense() == m).all(),\n",
- ")"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Then, we solve the model by both exact diagonalisation and DMRG. Afterwards, we need to check that the ground states are the same up to a phase."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "DMRG running:\n",
- "\n"
- ]
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "100%|██████████| 10/10 [00:23<00:00, 2.33s/it]\n"
- ]
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "Eigensolver running.\n",
- "The ground states are the same: True\n"
- ]
- }
- ],
- "source": [
- "NUM_SITES = 10\n",
- "H_MAGNETIC = 1.0\n",
- "NUM_DMRG_RUNS = 10\n",
- "CHI_MAX = 128\n",
- "CUT = 1e-12\n",
- "MODE = \"SA\"\n",
- "TOL = 1e-7\n",
- "ising_exact = IsingExact(num_sites=NUM_SITES, h_magnetic=H_MAGNETIC)\n",
- "ising_mpo = IsingMPO(num_sites=NUM_SITES, h_magnetic=H_MAGNETIC)\n",
- "ham_mpo = ising_mpo.hamiltonian_mpo()\n",
- "ham_sparse = ising_exact.hamiltonian_sparse()\n",
- "\n",
- "mps_start = create_simple_product_state(NUM_SITES, which=\"+\")\n",
- "\n",
- "print(\"DMRG running:\")\n",
- "print(\"\")\n",
- "engine = dmrg(mps_start, ham_mpo, chi_max=CHI_MAX, cut=CUT, mode=MODE)\n",
- "engine.run(NUM_DMRG_RUNS)\n",
- "print(\"\")\n",
- "ground_state_mps = engine.mps\n",
- "print(\"Eigensolver running.\")\n",
- "ground_state_exact = eigsh(ham_sparse, k=2, tol=TOL)[1][:, 0]\n",
- "print(\n",
- " \"The ground states are the same:\",\n",
- " np.isclose(abs(ground_state_mps.dense()), abs(ground_state_exact)).all(),\n",
- ")"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now, we would like to compare the magnetisation plots from exact diagonalisation and DMRG. The plots should coincide exactly."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "100%|██████████| 20/20 [08:05<00:00, 24.25s/it]\n"
- ]
- }
- ],
- "source": [
- "transverse_magnetic_field_space = np.linspace(0.2, 2.0, 20)\n",
- "mag_z_exact = []\n",
- "mag_x_exact = []\n",
- "mag_z_dmrg = []\n",
- "mag_x_dmrg = []\n",
- "for magnetic_field in tqdm(transverse_magnetic_field_space):\n",
- " ising_exact = IsingExact(num_sites=NUM_SITES, h_magnetic=magnetic_field)\n",
- " ising_mpo = IsingMPO(num_sites=NUM_SITES, h_magnetic=magnetic_field)\n",
- " ham_mpo = ising_mpo.hamiltonian_mpo()\n",
- " ham_sparse = ising_exact.hamiltonian_sparse()\n",
- " mps_start = create_simple_product_state(num_sites=NUM_SITES, which=\"+\")\n",
- " engine = dmrg(mps_start, ham_mpo, chi_max=CHI_MAX, cut=CUT, mode=MODE, silent=True)\n",
- " engine.run(NUM_DMRG_RUNS)\n",
- " ground_state_mps = engine.mps\n",
- " ground_state_exact = eigsh(ham_sparse, k=2, tol=TOL)[1][:, 0]\n",
- "\n",
- " mag_z_exact.append(ising_exact.average_chain_z_magnetisation(ground_state_exact))\n",
- " mag_x_exact.append(ising_exact.average_chain_x_magnetisation(ground_state_exact))\n",
- "\n",
- " mag_z_dmrg.append(ising_mpo.average_chain_z_magnetisation(ground_state_mps))\n",
- " mag_x_dmrg.append(ising_mpo.average_chain_x_magnetisation(ground_state_mps))"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we can take a look at the plots!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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Blaza3/0zTffSJLOnj2rs2fPqkGDBpZ9999/v1q0aKFFixYpLCxMJSUlat++vQoKCi7m4+1tcz5XIoo8AAAA4CpnMplsfmzySnTgwAGNGzdOixYt0urVqzVkyBB98cUXcnFx0ZkzZ5SWlqZFixapZ8+ekqSvvvrK6viOHTvqvffe09mzZ8uczfPw8FBxce1/d9FWrK4JAAAAoN7Iz89Xenq61ef06dMqLi7WE088obi4OA0bNkxLlizRrl279NZbb0mSAgIC1LhxYy1cuFD79+/Xhg0bFB8fb9X3gAEDFBISon79+unrr7/WwYMH9a9//UvJycmSpJYtW+rQoUNKTU3V6dOnlZ+fX+vXXxUUeQAAAADqjcTERIWGhlp9evToob/85S/65Zdf9I9//EOSFBoaqoULF+rll1/Wzp075eLiolWrVmnbtm1q3769xo0bpzfffNOqbw8PD61bt05BQUG655571KFDB73++utydb34WOnDDz+sPn366I477lDTpk31wQcf1Pr1V4XJMAyjrpO42pjNZvn5+Sk7O1u+vr51nQ4AAACuIhcuXNChQ4cUEREhLy+vuk7nqlDRmNdEbcBMHgAAAAA4EYo8AAAAAHAiFHkAAAAA4EQo8gAAAADAiVDkAQAAAIATocgDAAAAACdCkQcAAAAAToQiDwAAAACcCEUeAAAAADgRijwAAAAAcCIUeQAAAADqhaFDh8pkMslkMsnd3V3BwcG66667tHjxYpWUlFjiWrZsKZPJpFWrVpXq44YbbpDJZNLSpUtLxZtMJvn4+KhDhw567733Sh1rGIYWLVqkrl27ytfXVw0bNtQNN9yg5557Tvv376+Ra7YHRR4AAACAeqNPnz46efKkDh8+rP/+97+644479Nxzz+m+++5TUVGRJS48PFxLliyxOvbbb79Venq6GjRoUKrf6dOn6+TJk9qzZ4+eeOIJjRw5Uv/9738t+w3D0OOPP65nn31W99xzj9atW6cffvhB77//vry8vPTqq6/W3EXbyK2uEwAAAACAqvL09FRISIgkqVmzZrrpppt0yy23qFevXlq6dKlGjBghSRo4cKDmzp2ro0ePKjw8XJK0ePFiDRw4UMuXLy/Vb6NGjSz9Tpw4UbNmzdL69et19913S5JWr16tVatW6d///rceeOABy3HXXHONbrnlFhmGUaPXbQtm8gAA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rjWDca05jG3NYFxrBuNaMxjXmpGdna1rrrlGgYGBDuuzXhV5TZs2VdOmTWuk74iICIWEhCgpKclS1JnNZqWkpFhW6OzatauysrK0bds2de7cWZK0YcMGlZSUKCYmpty+PT095enpWardz8+P3yA1wNfXl3GtAYxrzWBcawbjWnMY25rBuNYMxrVmMK41w8XFcculOO3CK0eOHFFqaqqOHDmi4uJipaamKjU11eqddm3bttWaNWskSSaTSWPHjtWrr76qTz/9VLt379bgwYMVFhamfv36SZKuv/569enTRyNHjtTWrVv19ddfa8yYMXrssceqvLImAAAAANSkejWTZ4uEhAQtW7bMsn3jjTdKkjZu3Kjbb79dkpSWlmZZBEWSJkyYoNzcXI0aNUpZWVnq0aOHEhMT5eXlZYlZsWKFxowZo169esnFxUUPP/yw5s2bVzsXBQAAAACVcNoib+nSpVq6dGmFMX9cwcZkMmn69OmaPn16uccEBgZq5cqV1crN09NTU6dOLfMRTtiPca0ZjGvNYFxrBuNacxjbmsG41gzGtWYwrjWjJsbV6V+GDgAAAABXE6f9Th4AAAAAXI0o8gAAAADAiVDkAQAAAIATocirIfPnz1fLli3l5eWlmJgYbd26tdzYRYsWqWfPngoICFBAQIBiY2MrjL+a2TKul1u1apVMJpPldRiwZuu4ZmVl6emnn1ZoaKg8PT113XXXae3atbWUbf1h67i+/fbbatOmjby9vRUeHq5x48bpwoULtZRt/bBlyxbdf//9CgsLk8lk0ieffFLpMZs2bdJNN90kT09PRUZGVroo19XI1nH9+OOPddddd6lp06by9fVV165d9b///a92kq1H7Pn/9ZKvv/5abm5ulnf34nf2jGt+fr5eeukltWjRQp6enmrZsqUWL15c88nWI/aM64oVK9SpUyf5+PgoNDRUTz75pM6cOVPzydYjM2fO1M0336xGjRopKChI/fr1U1paWqXHffjhh2rbtq28vLzUoUMHm/+eRZFXA1avXq34+HhNnTpV27dvV6dOnRQXF6fMzMwy4zdt2qQBAwZo48aNSk5OVnh4uHr37q3jx4/XcuZXNlvH9ZLDhw9r/Pjx6tmzZy1lWr/YOq4FBQW66667dPjwYX300UdKS0vTokWL1KxZs1rO/Mpm67iuXLlSkyZN0tSpU7Vv3z69//77Wr16tV588cVazvzKlpubq06dOmn+/PlVij906JDuvfde3XHHHUpNTdXYsWM1YsQICpI/sHVct2zZorvuuktr167Vtm3bdMcdd+j+++/Xjh07ajjT+sXWcb0kKytLgwcPVq9evWoos/rNnnF99NFHlZSUpPfff19paWn64IMP1KZNmxrMsv6xdVy//vprDR48WMOHD9fevXv14YcfauvWrRo5cmQNZ1q/bN68WU8//bS+/fZbrV+/XoWFherdu7dyc3PLPeabb77RgAEDNHz4cO3YsUP9+vVTv379tGfPnqqf2IDDdenSxXj66act28XFxUZYWJgxc+bMKh1fVFRkNGrUyFi2bFlNpVgv2TOuRUVFRrdu3Yz33nvPGDJkiNG3b99ayLR+sXVc//73vxutWrUyCgoKaivFesnWcX366aeNO++806otPj7e6N69e43mWZ9JMtasWVNhzIQJE4wbbrjBqq1///5GXFxcDWZWv1VlXMvSrl07Y9q0aY5PyEnYMq79+/c3Xn75ZWPq1KlGp06dajSv+q4q4/rf//7X8PPzM86cOVM7STmBqozrm2++abRq1cqqbd68eUazZs1qMLP6LzMz05BkbN68udyYRx991Lj33nut2mJiYoz/7//7/6p8HmbyHKygoEDbtm1TbGyspc3FxUWxsbFKTk6uUh95eXkqLCxUYGBgTaVZ79g7rtOnT1dQUJCGDx9eG2nWO/aM66effqquXbvq6aefVnBwsNq3b6/XXntNxcXFtZX2Fc+ece3WrZu2bdtmeaTz4MGDWrt2re65555aydlZJScnW/06SFJcXFyV78eompKSEp07d44/txxgyZIlOnjwoKZOnVrXqTiNTz/9VNHR0Zo1a5aaNWum6667TuPHj9f58+frOrV6rWvXrjp69KjWrl0rwzCUkZGhjz76iD+3KpGdnS1JFd4vHfFnl9O+DL2unD59WsXFxQoODrZqDw4O1o8//lilPiZOnKiwsLBSv7hXM3vG9auvvtL777+v1NTUWsiwfrJnXA8ePKgNGzZo4MCBWrt2rfbv368///nPKiws5C8lv7FnXB9//HGdPn1aPXr0kGEYKioq0lNPPcXjmtWUnp5e5q+D2WzW+fPn5e3tXUeZOZfZs2crJydHjz76aF2nUq/9/PPPmjRpkr788ku5ufFXNEc5ePCgvvrqK3l5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",
- "text/plain": [
- "