Skip to content

Commit

Permalink
Merge branch 'ScQ-Cloud:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
chensgit169 authored Nov 25, 2023
2 parents 7d329de + bc13569 commit 6a6183d
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 42 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.dot
*.egg-info
*.pyc
*.pyd
Expand Down
6 changes: 4 additions & 2 deletions quafu/algorithms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Algorithm module"""

from .hamiltonian import Hamiltonian
from .ansatz import *
from .estimator import *
from .ansatz import QAOAAnsatz, AlterLayeredAnsatz, QuantumNeuralNetwork
from .estimator import Estimator
from .templates.angle import AngleEmbedding
from .templates.basic_entangle import BasicEntangleLayers
31 changes: 29 additions & 2 deletions quafu/algorithms/ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@

"""Ansatz circuits for VQA"""
from abc import ABC, abstractmethod
from typing import List
from typing import Any, List
import numpy as np

from quafu.algorithms.hamiltonian import Hamiltonian
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.synthesis.evolution import ProductFormula
from .hamiltonian import Hamiltonian
from .interface_provider import InterfaceProvider


class Ansatz(QuantumCircuit, ABC):
Expand Down Expand Up @@ -138,3 +139,29 @@ def _build(self):
self.cnot(ctrl, targ)
for qubit in range(self.num):
self.ry(qubit, self._theta[self._layer, qubit])


class QuantumNeuralNetwork(Ansatz):
"""A Wrapper of quantum circuit as QNN"""

# TODO(zhaoyilun): docs
def __init__(self, num_qubits: int, layers: List[Any], interface="torch"):
""""""
# Get transformer according to specified interface
self._transformer = InterfaceProvider.get(interface)
self._layers = layers

# FIXME(zhaoyilun): don't use this default value
self._weights = np.empty((1, 1))
super().__init__(num_qubits)

def _build(self):
"""Essentially initialize weights using transformer"""
for layer in self._layers:
self.add_gates(layer)

self._weights = self._transformer.init_weights((1, self.num_parameters))

@property
def weights(self):
return self._weights
1 change: 1 addition & 0 deletions quafu/algorithms/gradients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
# limitations under the License.

from .param_shift import ParamShift
from .vjp import run_circ, compute_vjp, jacobian
4 changes: 2 additions & 2 deletions quafu/algorithms/gradients/param_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from typing import List
import numpy as np

from quafu.algorithms import Estimator
from quafu.algorithms.hamiltonian import Hamiltonian
from ..estimator import Estimator
from ..hamiltonian import Hamiltonian


class ParamShift:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,13 @@ def compute_vjp(jac: np.ndarray, dy: np.ndarray):
vjp[i] = dy[i, :].T @ jac[i, :, :]

return vjp


# class QNode:
# """Quantum node which essentially wraps the execution of a quantum circuit"""
#
# def __init__(self, circ: QuantumCircuit) -> None:
# self._circ = circ
#
# def __call__(self):
# return execu
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@
import torch
import numpy as np
from quafu import QuantumCircuit
from quafu.algorithms.layers.qnode import compute_vjp, jacobian
from ..gradients import compute_vjp, jacobian, run_circ


class TorchTransformer:
@staticmethod
def init_weights(shape):
"""Return torch gradient tensor with specified shape"""
return torch.randn(*shape, requires_grad=True, dtype=torch.double)


class ExecuteCircuits(torch.autograd.Function):
"""TODO(zhaoyilun): document"""
"""Parameters are input from previous layers"""

@staticmethod
def forward(ctx, parameters, kwargs):
Expand All @@ -47,7 +54,13 @@ def backward(ctx, grad_out):


# TODO(zhaoyilun): doc
def execute(circ: QuantumCircuit, run_fn, grad_fn, parameters: torch.Tensor):
def execute(
circ: QuantumCircuit,
parameters: torch.Tensor,
run_fn=run_circ,
grad_fn=None,
method="internal",
):
"""execute.
Args:
Expand All @@ -58,4 +71,9 @@ def execute(circ: QuantumCircuit, run_fn, grad_fn, parameters: torch.Tensor):

kwargs = {"circ": circ, "run_fn": run_fn, "grad_fn": grad_fn}

return ExecuteCircuits.apply(parameters, kwargs)
if method == "external":
return ExecuteCircuits.apply(parameters, kwargs)
elif method == "internal":
return ExecuteCircuits.apply(circ.weights, kwargs)
else:
raise NotImplementedError(f"Unsupported execution method: {method}")
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Pre-built quafu circuit blocks"""
from .interface.torch import TorchTransformer

PROVIDERS = {"torch": TorchTransformer}


class InterfaceProvider:
@classmethod
def get(cls, name: str):
if name not in PROVIDERS:
raise NotImplementedError(f"Unsupported interface: {name}")
return PROVIDERS[name]
1 change: 0 additions & 1 deletion quafu/algorithms/layers/__init__.py

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

ROT = {"X": qeg.RXGate, "Y": qeg.RYGate, "Z": qeg.RZGate}


class AngleEmbedding:
def __init__(self, features, num_qubits: int, rotation="X"):
"""
Expand All @@ -29,13 +30,11 @@ def __init__(self, features, num_qubits: int, rotation="X"):
"""
if rotation not in ROT:
raise ValueError(f"Rotation option {rotation} not recognized.")

shape = np.shape(features)[-1:]
n_features = shape[0]
if n_features != num_qubits:
raise ValueError(
"The length of Features must match num_qubits"
)
raise ValueError("The length of Features must match num_qubits")
self.features = features
self.num_qubits = num_qubits
self.op = ROT[rotation]
Expand All @@ -49,7 +48,7 @@ def _build(self):
gate = self.op(pos=i, paras=self.features[i])
gate_list.append(gate)
return gate_list

def __iter__(self):
return iter(self.gate_list)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

ROT = {"X": qeg.RXGate, "Y": qeg.RYGate, "Z": qeg.RZGate}


class BasicEntangleLayers:
def __init__(self, weights, num_qubits, rotation="X"):
"""
Expand All @@ -28,16 +29,16 @@ def __init__(self, weights, num_qubits, rotation="X"):
rotation(str): one-parameter single-qubit gate to use
"""
weights = np.asarray(weights)
#convert weights to numpy array if weights is list otherwise keep unchanged
# convert weights to numpy array if weights is list otherwise keep unchanged
shape = np.shape(weights)

##TODO(qtzhuang): If weights are batched, i.e. dim>3, additional processing is required
if weights.ndim > 2:
raise ValueError(
f"Weights tensor must be 2-dimensional "
)
raise ValueError(f"Weights tensor must be 2-dimensional ")

if not (len(shape) == 3 or len(shape) == 2): # 3 is when batching, 2 is no batching
if not (
len(shape) == 3 or len(shape) == 2
): # 3 is when batching, 2 is no batching
raise ValueError(
f"Weights tensor must be 2-dimensional "
f"or 3-dimensional if batching; got shape {shape}"
Expand All @@ -51,7 +52,7 @@ def __init__(self, weights, num_qubits, rotation="X"):
self.weights = weights
self.num_qubits = num_qubits
self.op = ROT[rotation]

"""Build the quantum basic_entangle layer and get the gate_list"""
self.gate_list = self._build()

Expand All @@ -62,17 +63,17 @@ def _build(self):
for i in range(self.num_qubits):
gate = self.op(pos=i, paras=self.weights[layer][i])
gate_list.append(gate)

# if num_qubits equals two, it just need to apply CNOT one time
if self.num_qubits == 2:
gate_list.append(qeg.CXGate(0,1))
gate_list.append(qeg.CXGate(0, 1))

elif self.num_qubits > 2:
for i in range(self.num_qubits):
gate_list.append(qeg.CXGate(i, (i+1)%self.num_qubits))
gate_list.append(qeg.CXGate(i, (i + 1) % self.num_qubits))

return gate_list

def __iter__(self):
return iter(self.gate_list)

Expand Down
9 changes: 6 additions & 3 deletions tests/quafu/algorithms/angle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@

from quafu.circuits import QuantumCircuit
import quafu.elements.element_gates as qeg
from quafu.algorithms.embedding.angle import AngleEmbedding
from quafu.algorithms import AngleEmbedding
import numpy as np


class TestAngleEmbedding:
"""Example of angle embedding"""

def test_build(self):
num_qubits = 4
qc = QuantumCircuit(num_qubits)
feature = np.array([[6,-12.5,11.15,7],[8,9.5,-11,-5],[5,0.5,8,-7]])
feature = np.array([[6, -12.5, 11.15, 7], [8, 9.5, -11, -5], [5, 0.5, 8, -7]])
for i in range(4):
qc.add_ins(qeg.HGate(pos=i))
for i in range(len(feature)):
qc.add_gates(AngleEmbedding(features=feature[i], num_qubits=num_qubits,rotation='Y'))
qc.add_gates(
AngleEmbedding(features=feature[i], num_qubits=num_qubits, rotation="Y")
)
qc.draw_circuit(width=num_qubits)
7 changes: 6 additions & 1 deletion tests/quafu/algorithms/ansatz_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""TODO: test of ansatz needs improvement once ansatz has more featuers"""

import numpy as np
from quafu.algorithms.ansatz import AlterLayeredAnsatz, QAOAAnsatz
from quafu.algorithms.ansatz import AlterLayeredAnsatz, QAOAAnsatz, QuantumNeuralNetwork
from quafu.algorithms.hamiltonian import Hamiltonian


Expand All @@ -35,3 +35,8 @@ def test_build(self):
circ = AlterLayeredAnsatz(4, 4)
# print("\n ::: testing ::: \n")
# circ.plot_circuit(save_path="TestAlterLayeredAnsatz.svg")


class TestQuantumNeuralNetwork:
def test_build(self):
circ = QuantumNeuralNetwork(2, [])
10 changes: 7 additions & 3 deletions tests/quafu/algorithms/basic_entangle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@

from quafu.circuits import QuantumCircuit
import quafu.elements.element_gates as qeg
from quafu.algorithms.layers.basic_entangle import BasicEntangleLayers
from quafu.algorithms import BasicEntangleLayers
import numpy as np


class TestBasicEntangleLayers:
"""Example of building basic_entangle layer"""

def test_build(self):
num_qubits = 3
qc = QuantumCircuit(num_qubits)
weights = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]])
# weights = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])
# weights = np.array([[0.1, 0.2], [0.3, 0.4]])
qc.add_gates(BasicEntangleLayers(weights=weights, num_qubits=num_qubits,rotation='Y'))
qc.add_gates(
BasicEntangleLayers(weights=weights, num_qubits=num_qubits, rotation="Y")
)
qc.draw_circuit(width=num_qubits)

##TODO(): if weights are as follows, it need additional processing.
# weights = np.array([
# weights = np.array([
# [
# [1, 2, 3, 4],
# [5, 6, 7, 8],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,32 @@
import numpy as np

import torch.nn
from quafu.algorithms.layers import jacobian, compute_vjp
from quafu.algorithms.layers.torch import execute
from quafu.algorithms.layers.qnode import run_circ
from quafu.algorithms.gradients import jacobian, compute_vjp
from quafu.algorithms.interface.torch import execute
from quafu.algorithms.templates.basic_entangle import BasicEntangleLayers
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.algorithms import QuantumNeuralNetwork


class MyModel(torch.nn.Module):
class ModelStandardCircuit(torch.nn.Module):
def __init__(self, circ: QuantumCircuit):
super().__init__()
self.circ = circ
self.linear = torch.nn.Linear(3, 3, dtype=torch.double)

def forward(self, features):
out = self.linear(features)
out = execute(self.circ, run_circ, None, out)
out = execute(self.circ, out, method="external")
return out


class ModelQuantumNeuralNetwork(torch.nn.Module):
def __init__(self, circ: QuantumNeuralNetwork):
super().__init__()
self.circ = circ

def forward(self, features):
out = execute(self.circ, features)
return out


Expand All @@ -49,9 +60,25 @@ def test_compute_vjp(self):
assert len(vjp.shape) == 2
assert vjp.shape[0] == 4

def test_torch_layer(self):
def test_torch_layer_standard_circuit(self):
batch_size = 1
model = ModelStandardCircuit(self.circ)
features = torch.randn(
batch_size, 3, requires_grad=True, dtype=torch.double
) # batch_size=4, num_params=3
outputs = model(features)
targets = torch.randn(batch_size, 2, dtype=torch.double)
criterion = torch.nn.MSELoss()
loss = criterion(outputs, targets)
loss.backward()

def test_torch_layer_qnn(self):
"""Use QuantumNeuralNetwork ansatz"""
weights = np.random.randn(2, 2)
entangle_layer = BasicEntangleLayers(weights, 2)
qnn = QuantumNeuralNetwork(2, [entangle_layer])
batch_size = 1
model = MyModel(self.circ)
model = ModelQuantumNeuralNetwork(qnn)
features = torch.randn(
batch_size, 3, requires_grad=True, dtype=torch.double
) # batch_size=4, num_params=3
Expand Down

0 comments on commit 6a6183d

Please sign in to comment.