Skip to content

Commit

Permalink
qcnn class: add sequence method to build a qcnn model
Browse files Browse the repository at this point in the history
  • Loading branch information
SaashaJoshi committed Nov 15, 2023
1 parent 2d21202 commit 024ddff
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 105 deletions.
7 changes: 7 additions & 0 deletions quantum_image_processing/models/neural_networks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
""" Neural Network Structures"""

from .quantum_neural_network import QuantumNeuralNetwork

__all__ = [
"QuantumNeuralNetwork",
]
172 changes: 110 additions & 62 deletions quantum_image_processing/models/neural_networks/convolutional/qcnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

from __future__ import annotations
import numpy as np
from qiskit.circuit import QuantumCircuit, ClassicalRegister
from quantum_image_processing.models.neural_networks.neural_network import NeuralNetwork
from typing import Optional, Callable
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister, IfElseOp
from quantum_image_processing.models.neural_networks.layer import Layer
from quantum_image_processing.models.neural_networks.quantum_neural_network import (
QuantumNeuralNetwork,
)
from quantum_image_processing.models.tensor_network_circuits.mera import MERA


class QuantumConvolutionalLayer(MERA):
class QuantumConvolutionalLayer(Layer, MERA):
"""
Builds a convolutional layer in the neural network
with the help of the MERA tensor network.
Expand All @@ -25,7 +29,15 @@ class QuantumConvolutionalLayer(MERA):
doi: https://doi.org/10.1038/s41567-019-0648-8.
"""

def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list = None, *args, **kwargs):
def __init__(
self,
num_qubits: int,
circuit: QuantumCircuit,
layer_depth: int,
mera_instance: int,
complex_structure: bool = True,
unmeasured_bits: dict = None,
):
"""
Initializes a convolutional layer to a MERA
tensor network structure.
Expand All @@ -38,45 +50,52 @@ def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list = None,
*args and **kwargs (like img_dims and layer_depth): inputs
as inherited from parent class, MERA.
"""
MERA.__init__(self, *args, **kwargs)
Layer.__init__(self, num_qubits)
MERA.__init__(self, num_qubits, layer_depth)

self.circuit = circuit
if unmeasured_qubit_list is None:
self.unmeasured_qubit_list = self.circuit.qubits
self.mera_instance = mera_instance
self.complex_structure = complex_structure

self.unmeasured_bits = {}
if unmeasured_bits is None:
self.unmeasured_bits["qubits"] = self.circuit.qubits
self.unmeasured_bits["clbits"] = self.circuit.clbits
else:
self.unmeasured_qubit_list = unmeasured_qubit_list
self.unmeasured_bits = unmeasured_bits

def convolutional_layer(
self, mera_instance: int, complex_structure: bool
) -> tuple[QuantumCircuit, list]:
def build_layer(self) -> tuple[QuantumCircuit, dict]:
"""
Implements the MERA tensor network with a restriction
on the depth of a convolutional layer, specified by a
hyperparameter, `layer_depth`.
Args:
mera_instance (int): integer to denote a structural choice
mera_type (int): integer to denote a structural choice
for unitary gate parameterization.
For example, [0, 1, 2] == [real, general, aux]
complex_structure (bool)(default=True): boolean marker
for real or complex gate parameterization.
"""
self.circuit.barrier()
instance_mapping = {
0: self.mera_simple,
1: self.mera_general,
2: None,
}
if mera_instance in instance_mapping:
method = instance_mapping[mera_instance]
if self.mera_instance in instance_mapping:
method = instance_mapping[self.mera_instance]
if callable(method):
# self.circuit.compose(method(complex_structure), inplace=True)
self.circuit.append(method(complex_structure), qargs=self.unmeasured_qubit_list)

return self.circuit.decompose(), list(self.circuit.qubits)
return self.circuit.compose(
method(self.complex_structure),
qubits=self.circuit.qubits,
clbits=self.circuit.clbits,
), self.unmeasured_bits


# pylint: disable=too-few-public-methods
class QuantumPoolingLayer:
class QuantumPoolingLayer(Layer):
"""
Builds a pooling layer in the neural network
with the help of controlled phase flip gates.
Expand All @@ -88,7 +107,12 @@ class QuantumPoolingLayer:
doi: https://doi.org/10.1038/s41567-019-0648-8.
"""

def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list):
def __init__(
self,
num_qubits: int,
circuit: QuantumCircuit,
unmeasured_bits: dict = None,
):
"""
Initializes a pooling layer with the preceding
convolutional or pooling layer.
Expand All @@ -98,35 +122,42 @@ def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list):
existing convolutional or pooling layer as an input,
and applies an/additional pooling layer over it.
"""
Layer.__init__(self, num_qubits)
self.circuit = circuit
self.unmeasured_qubit_list = unmeasured_qubit_list
self.unmeasured_bits = unmeasured_bits

def pooling_layer(self) -> tuple[QuantumCircuit, list]:
def build_layer(self) -> tuple[QuantumCircuit, dict]:
"""
Implements a pooling layer with alternating phase flips on
qubits when their adjacent qubits result in X = -1, when
measured in X-basis.
"""
classical_reg = ClassicalRegister(np.ceil(len(self.circuit.qubits) / 2))
self.circuit.add_register(classical_reg)

unmeasured_bits = {"qubits": [], "clbits": []}
self.circuit.barrier()
unmeasured_qubit_list = []
for qubit in range(len(self.circuit.qubits)):
if qubit % 2 == 0 and qubit != len(self.circuit.qubits) - 1:
# Measurement in X-basis.
self.circuit.h(qubit + 1)
self.circuit.measure(qubit + 1, classical_reg[int(qubit / 2)])
unmeasured_qubit_list.append(self.circuit.qubits[qubit])
# Dynamic circuit
with self.circuit.if_test((classical_reg[int(qubit / 2)], 1)):
self.circuit.z(qubit)
for index in range(0, len(self.unmeasured_bits["qubits"][:-1]), 2):
unmeasured_bits["qubits"].append(self.unmeasured_bits["qubits"][index])
unmeasured_bits["clbits"].append(self.unmeasured_bits["clbits"][index])

# Measurement in X-basis.
self.circuit.h(self.unmeasured_bits["qubits"][index + 1].index)
self.circuit.measure(
self.unmeasured_bits["qubits"][index + 1].index,
self.unmeasured_bits["clbits"][index + 1].index,
)
# Dynamic circuit - cannot be composed if using context manager form (e.g. with)
with self.circuit.if_test((self.unmeasured_bits["clbits"][index + 1].index, 1)):
self.circuit.z(self.unmeasured_bits["qubits"][index].index)

return self.circuit, unmeasured_qubit_list
return self.circuit, unmeasured_bits


class FullyConnectedLayer:
def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list):
class FullyConnectedLayer(Layer):
def __init__(
self,
num_qubits: int,
circuit: QuantumCircuit,
unmeasured_bits: dict
):
"""
Initializes a fully connected layer with the preceding
convolutional or polling layers.
Expand All @@ -136,54 +167,71 @@ def __init__(self, circuit: QuantumCircuit, unmeasured_qubit_list: list):
existing convolutional or pooling layer as an input,
and applies a fully connected layer to it.
unmeasured_qubit_list (list): Takes into consideration
unmeasured_bits (dict): Takes into consideration
the unmeasured qubits in the preceding circuit. Only these
qubits are used to create the FC layer.
"""
Layer.__init__(self, num_qubits)
self.circuit = circuit
self.unmeasured_qubit_list = unmeasured_qubit_list
self.unmeasured_bits = unmeasured_bits

def fully_connected_layer(self) -> QuantumCircuit:
def build_layer(self) -> tuple[QuantumCircuit, dict]:
"""
Implements a fully connected layer with controlled phase
gates on adjacent qubits followed by a measurement in X-basis.
"""
self.circuit.barrier()
for index in range(len(self.unmeasured_qubit_list) - 1):
unmeasured_bits = {"qubits": [], "clbits": []}
for index in range(len(self.unmeasured_bits["qubits"]) - 1):
self.circuit.cz(
self.unmeasured_qubit_list[index], self.unmeasured_qubit_list[index + 1]
self.unmeasured_bits["qubits"][index],
self.unmeasured_bits["qubits"][index + 1],
)
self.final_measurement()
return self.circuit
return self.circuit, unmeasured_bits

def final_measurement(self) -> QuantumCircuit:
"""
Implements a measurement in X-basis on the remaining qubits.
"""
cr = ClassicalRegister(len(self.unmeasured_qubit_list))
self.circuit.add_register(cr)
self.circuit.barrier()
# Measurement in X-basis
self.circuit.h(self.unmeasured_qubit_list)
self.circuit.measure(self.unmeasured_qubit_list, cr)
self.circuit.h(self.unmeasured_bits["qubits"])
self.circuit.measure(self.unmeasured_bits["qubits"], self.unmeasured_bits["clbits"])
return self.circuit


class QCNN(NeuralNetwork):
def __init__(self, circuit: QuantumCircuit):
self.circuit = circuit

def qcnn_structure(self):
pass
class QCNN(QuantumNeuralNetwork):
def __init__(self, num_qubits: int):
"""
Initializes a Quantum Neural Network circuit with the given
number of qubits.
def compose(self, circuit: QuantumCircuit):
self.circuit.compose(circuit, inplace=True)
Args:
num_qubits (int): builds a quantum circuit with the
given number of qubits.
def forward_pass(self):
pass
unmeasured_qubits (list): list of Qubit objects that
remain unmeasured in a circuit.
"""
QuantumNeuralNetwork.__init__(self, num_qubits)
self.qreg = QuantumRegister(self.num_qubits)
self.creg = ClassicalRegister(self.num_qubits)
self.circuit = QuantumCircuit(self.qreg, self.creg)

def backward_pass(self):
pass
def sequence(self, operations: list[tuple[Callable, dict]]):
"""
Builds a Quantum Neural Network circuit by composing
the circuit with given list of operations.
"""
unmeasured_bits = {"qubits": self.circuit.qubits, "clbits": self.circuit.clbits}
for layer, params in operations:
layer = layer(
circuit=self.circuit,
num_qubits=self.num_qubits,
unmeasured_bits=unmeasured_bits,
**params
)
self.circuit, unmeasured_bits = layer.build_layer()

def qcnn_backbone(self):
pass
return self.circuit
14 changes: 14 additions & 0 deletions quantum_image_processing/models/neural_networks/layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Abstract Base Class for QNN Layers"""

from __future__ import annotations
from abc import ABC, abstractmethod
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister


class Layer(ABC):
def __init__(self, num_qubits: int, **kwargs):
self.num_qubits = num_qubits

@abstractmethod
def build_layer(self):
return NotImplementedError
28 changes: 0 additions & 28 deletions quantum_image_processing/models/neural_networks/neural_network.py

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Neural Network Abstract Base Class"""

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Callable
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister


class QuantumNeuralNetwork(ABC):
"""
Abstract base class for all quantum neural network structures.
These structures consist of data encoding/embedding,
model layers (consisting of layers such as convolutional,
pooling, etc.), and a measurement stage.
"""

def __init__(self, num_qubits: int):
"""
Initializes a Quantum Neural Network circuit with the given
number of qubits.
"""
self.num_qubits = num_qubits
# self.qr = QuantumRegister(self.num_qubits)
# self.cr = ClassicalRegister(self.num_qubits)
# self.circuit = QuantumCircuit(self.qr, self.cr)

@abstractmethod
def sequence(self, operations: Callable):
"""
Composes circuits with given list of operations.
"""
return NotImplementedError
Loading

0 comments on commit 024ddff

Please sign in to comment.