Skip to content

Commit

Permalink
Correct programming logic and add unittests for MERA class. (#21)
Browse files Browse the repository at this point in the history
* initial commit

* remove extensive branching in mera. this closes #17. minor changes to ttn.

* minor syntax change

* correct number of params for simple parameterization

* add more tests cases for ttn. add more tests for mera

* disable some pylint errors, for example, too-many-args (which  most probably should be fixed while working on #10), and duplicate-code in ttn_circuit and mera_circuit fixtures. this duplicate code can eb ignored since it will be quite difficult to reuse the fixture in either of the files.

* add new test for num_qubits = 5. this brings the tests to 100 percent coverage

* add two more tests for complex general and real general for 5 qubits
  • Loading branch information
SaashaJoshi authored Dec 8, 2023
1 parent d937b77 commit 831baac
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 71 deletions.
148 changes: 87 additions & 61 deletions quantum_image_processing/models/tensor_network_circuits/mera.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
"""Multiscale Entanglement Renormalization Ansatz (MERA) Tensor Network"""
from __future__ import annotations
import uuid
from typing import Callable
from typing import Callable, Optional
import math
import numpy as np
from qiskit.circuit import (
QuantumCircuit,
QuantumRegister,
ClassicalRegister,
ParameterVector,
)
from quantum_image_processing.gates.two_qubit_unitary import TwoQubitUnitary
from quantum_image_processing.models.tensor_network_circuits.ttn import TTN


class MERA(TwoQubitUnitary):
class MERA(TTN):
"""
Implements a Multiscale Entanglement Renormalization Ansatz
(MERA) tensor network structure as given by [2].
Expand All @@ -29,14 +28,39 @@ class MERA(TwoQubitUnitary):
doi: https://doi.org/10.1103/physrevlett.101.110501.
"""

def __init__(self, img_dims: tuple[int, int], layer_depth: type(None) = None):
self.img_dims = img_dims
self.num_qubits = int(math.prod(img_dims))
def __init__(
self, img_dims: tuple[int, int], layer_depth: Optional[int] | None = None
):
"""
Initializes the MERA class with given input variables.
Args:
img_dims (int): dimensions of the input image data.
layer_depth (int): number of MERA layers to be built in a circuit.
"""
TTN.__init__(self, img_dims)
self.num_qubits = int(math.prod(self.img_dims))

if not isinstance(layer_depth, int) and layer_depth is not None:
raise TypeError("The input layer_depth must be of the type int or None.")

# Should there be a max cap on the value of layer_depth?
if isinstance(layer_depth, int) and layer_depth < 1:
raise ValueError("The input layer_depth must be at least 1.")

if layer_depth is None:
self.layer_depth = int(np.ceil(np.sqrt(self.num_qubits)))
else:
self.layer_depth = layer_depth

self._circuit = QuantumCircuit(self.num_qubits)
self.mera_qr = self._circuit.qubits

@property
def circuit(self):
"""Returns the MERA circuit."""
return self._circuit

def mera_simple(self, complex_structure: bool = True) -> QuantumCircuit:
"""
Builds a MERA circuit with a simple unitary gate
Expand All @@ -50,13 +74,14 @@ def mera_simple(self, complex_structure: bool = True) -> QuantumCircuit:
circuit (QuantumCircuit): Returns the MERA circuit
generated with the help of the input arguments.
"""
# Check params here.
param_vector = ParameterVector(
f"theta_{str(uuid.uuid4())[:5]}",
int(self.num_qubits / 2 * (self.num_qubits / 2 + 1)) + 3,
10 * self.num_qubits - 1,
)
param_vector_copy = param_vector
return self.mera_backbone(
self.simple_parameterization,
TwoQubitUnitary().simple_parameterization,
param_vector_copy,
complex_structure,
)
Expand Down Expand Up @@ -86,18 +111,18 @@ def mera_general(self, complex_structure: bool = True) -> QuantumCircuit:
)
param_vector_copy = param_vector
return self.mera_backbone(
self.general_parameterization,
TwoQubitUnitary().general_parameterization,
param_vector_copy,
complex_structure,
)

# pylint: disable=too-many-branches
def mera_backbone(
self,
gate_structure: Callable,
param_vector_copy: ParameterVector,
complex_structure: bool = True,
) -> QuantumCircuit:
# pylint: disable=duplicate-code
"""
Lays out the backbone structure of a MERA circuit onto
which the unitary gates are applied.
Expand All @@ -116,72 +141,73 @@ def mera_backbone(
circuit (QuantumCircuit): Returns the MERA circuit
generated with the help of the input arguments.
"""
mera_qr = QuantumRegister(size=self.num_qubits)
mera_cr = ClassicalRegister(size=self.num_qubits)
mera_circ = QuantumCircuit(mera_qr, mera_cr)

# Make recursive layer structure using a staticmethod i.e. convert code to staticmethod.
# This should solve R0912: Too many branches (13/12) (too-many-branches)
qubit_list = []
for layer in range(self.layer_depth):
if layer == 0:
# D unitary blocks
for index in range(1, self.num_qubits, 2):
if index == self.num_qubits - 1:
break

_, param_vector_copy = gate_structure(
circuit=mera_circ,
qubits=[mera_qr[index], mera_qr[index + 1]],
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)

# U unitary blocks
mera_circ.barrier()
for index in range(0, self.num_qubits, 2):
if index == self.num_qubits - 1:
qubit_list.append(mera_qr[index])
else:
qubit_list.append(mera_qr[index + 1])
_, param_vector_copy = gate_structure(
circuit=mera_circ,
qubits=[mera_qr[index], mera_qr[index + 1]],
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)
# Layer = 0
for index in range(1, self.num_qubits, 2):
if index == self.num_qubits - 1:
break

# D unitary blocks
unitary_block, param_vector_copy = gate_structure(
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)
self.circuit.compose(
unitary_block,
qubits=[self.mera_qr[index], self.mera_qr[index + 1]],
inplace=True,
)

# U unitary blocks
for index in range(0, self.num_qubits, 2):
if index == self.num_qubits - 1:
qubit_list.append(self.mera_qr[index])
else:
temp_list = []
qubit_list.append(self.mera_qr[index + 1])
unitary_block, param_vector_copy = gate_structure(
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)
self.circuit.compose(
unitary_block,
qubits=[self.mera_qr[index], self.mera_qr[index + 1]],
inplace=True,
)

# Rest of the layers.
temp_list = qubit_list.copy()
if self.layer_depth > 1:
while len(temp_list) > 1:
# D unitary blocks
mera_circ.barrier()
for index in range(1, len(qubit_list), 2):
if len(qubit_list) == 2 or index == len(qubit_list) - 1:
if len(temp_list) == 2 or index == len(temp_list) - 1:
break

_, param_vector_copy = gate_structure(
circuit=mera_circ,
qubits=[qubit_list[index], qubit_list[index + 1]],
unitary_block, param_vector_copy = gate_structure(
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)
self.circuit.compose(
unitary_block,
qubits=[qubit_list[index], qubit_list[index + 1]],
inplace=True,
)

# U unitary blocks
mera_circ.barrier()
for index in range(0, len(qubit_list) - 1, 2):
_, param_vector_copy = gate_structure(
circuit=mera_circ,
qubits=[qubit_list[index], qubit_list[index + 1]],
unitary_block, param_vector_copy = gate_structure(
parameter_vector=param_vector_copy,
complex_structure=complex_structure,
)
temp_list.append(qubit_list[index + 1])

if len(qubit_list) % 2 != 0:
temp_list.append(qubit_list[-1])
self.circuit.compose(
unitary_block,
qubits=[qubit_list[index], qubit_list[index + 1]],
inplace=True,
)
temp_list.pop(0)
qubit_list = temp_list

if len(qubit_list) == 1:
mera_circ.ry(param_vector_copy[0], mera_qr[-1])
if len(qubit_list) == 1:
self.circuit.ry(param_vector_copy[0], self.mera_qr[-1])

return mera_circ
return self.circuit
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, img_dims: tuple[int, int]):
raise ValueError("Image dimensions cannot be zero or negative.")

self.img_dims = img_dims
self.num_qubits = int(math.prod(img_dims))
self.num_qubits = int(math.prod(self.img_dims))

self._circuit = QuantumCircuit(self.num_qubits)
self.q_reg = self._circuit.qubits
Expand Down Expand Up @@ -94,7 +94,6 @@ def ttn_general(self, complex_structure: bool = True) -> QuantumCircuit:

def ttn_with_aux(self, complex_structure: bool = True):
"""
TODO: Find the implementation procedure for this.
Implements a TTN network with alternative parameterization
that requires an auxiliary qubit, as given in [1].
Expand Down Expand Up @@ -132,6 +131,7 @@ def ttn_backbone(
QuantumCircuit: quantum circuit with unitary gates
represented by general parameterization.
"""
# Layer = 0
qubit_list = []
for index in range(0, self.num_qubits, 2):
if index == self.num_qubits - 1:
Expand All @@ -148,6 +148,7 @@ def ttn_backbone(
inplace=True,
)

# Rest of the layers.
for _ in range(int(np.sqrt(self.num_qubits))):
temp_list = []
for index in range(0, len(qubit_list) - 1, 2):
Expand Down
Loading

0 comments on commit 831baac

Please sign in to comment.