Skip to content

Commit

Permalink
feat: support real machine gradient
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhaoyilunnn committed Jan 20, 2024
1 parent 862cb63 commit 55cba74
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*.so
*.svg
*temp.*
temp/
.DS_Store
.idea
.vscode
Expand Down
13 changes: 12 additions & 1 deletion quafu/algorithms/ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,27 @@ class QuantumNeuralNetwork(Ansatz):
"""A Wrapper of quantum circuit as QNN"""

# TODO(zhaoyilun): docs
def __init__(self, num_qubits: int, layers: List[Any], interface="torch"):
def __init__(
self, num_qubits: int, layers: List[Any], interface="torch", backend="sim"
):
""""""
# 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))

self._backend = backend
super().__init__(num_qubits)

def __call__(self, features):
"""Compute outputs of QNN given input features"""
from .estimator import Estimator

estimator = Estimator(self, backend=self._backend)
return self._transformer.execute(self, features, estimator=estimator)

def _build(self):
"""Essentially initialize weights using transformer"""
for layer in self._layers:
Expand Down
22 changes: 18 additions & 4 deletions quafu/algorithms/gradients/vjp.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,36 @@ def _generate_expval_z(num_qubits: int):


# TODO(zhaoyilun): support more measurement types
def run_circ(circ: QuantumCircuit, params: Optional[List[float]] = None):
# FIXME(zhaoyilun): remove backend
def run_circ(
circ: QuantumCircuit,
params: Optional[List[float]] = None,
backend: str = "sim",
estimator: Optional[Estimator] = None,
):
"""Execute a circuit
Args:
circ (QuantumCircuit): circ
params (Optional[List[float]]): params
backend (str): backend
estimator (Optional[Estimator]): estimator
"""
obs_list = _generate_expval_z(circ.num)
estimator = Estimator(circ)
if estimator is None:
estimator = Estimator(circ, backend=backend)
if params is None:
params = [g.paras for g in circ.parameterized_gates]
output = [estimator.run(obs, params) for obs in obs_list]
return np.array(output)


# TODO(zhaoyilun): support more gradient methods
def jacobian(circ: QuantumCircuit, params_input: np.ndarray):
def jacobian(
circ: QuantumCircuit,
params_input: np.ndarray,
estimator: Optional[Estimator] = None,
):
"""Calculate Jacobian matrix
Args:
Expand All @@ -57,7 +70,8 @@ def jacobian(circ: QuantumCircuit, params_input: np.ndarray):
batch_size, num_params = params_input.shape
obs_list = _generate_expval_z(circ.num)
num_outputs = len(obs_list)
estimator = Estimator(circ)
if estimator is None:
estimator = Estimator(circ)
calc_grad = ParamShift(estimator)
output = np.zeros((batch_size, num_outputs, num_params))
for i in range(batch_size):
Expand Down
21 changes: 17 additions & 4 deletions quafu/algorithms/interface/torch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
# limitations under the License.
"""quafu PyTorch quantum layer"""

from typing import Optional

import numpy as np
import torch
from quafu.algorithms.estimator import Estimator

from quafu import QuantumCircuit

Expand All @@ -28,14 +31,15 @@ def init_weights(shape):
"""Return torch gradient tensor with specified shape"""
return torch.randn(*shape, requires_grad=True, dtype=torch.double)

# TODO(zhaoyilun): doc
# TODO(zhaoyilun): docstrings
@staticmethod
def execute(
circ: QuantumCircuit,
parameters: torch.Tensor,
run_fn=run_circ,
grad_fn=None,
method="internal",
estimator: Optional[Estimator] = None,
):
"""execute.
Expand All @@ -45,11 +49,19 @@ def execute(
grad_fn:
"""

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

if method == "external":
return ExecuteCircuits.apply(parameters, kwargs)
if method == "internal":
from ..ansatz import QuantumNeuralNetwork

assert isinstance(circ, QuantumNeuralNetwork)
return ExecuteCircuits.apply(circ.weights, kwargs)
raise NotImplementedError(f"Unsupported execution method: {method}")

Expand All @@ -61,11 +73,12 @@ class ExecuteCircuits(torch.autograd.Function):
def forward(ctx, parameters, kwargs):
ctx.run_fn = kwargs["run_fn"]
ctx.circ = kwargs["circ"]
ctx.estimator = kwargs["estimator"]
ctx.save_for_backward(parameters)
parameters = parameters.numpy().tolist()
outputs = []
for para in parameters:
out = ctx.run_fn(ctx.circ, para)
out = ctx.run_fn(ctx.circ, para, estimator=ctx.estimator)
outputs.append(out)
outputs = np.stack(outputs)
outputs = torch.from_numpy(outputs)
Expand All @@ -74,7 +87,7 @@ def forward(ctx, parameters, kwargs):
@staticmethod
def backward(ctx, grad_out):
(parameters,) = ctx.saved_tensors
jac = jacobian(ctx.circ, parameters.numpy())
jac = jacobian(ctx.circ, parameters.numpy(), estimator=ctx.estimator)
vjp = compute_vjp(jac, grad_out.numpy())
vjp = torch.from_numpy(vjp)
return vjp, None
54 changes: 46 additions & 8 deletions tests/quafu/algorithms/qnn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import numpy as np
import pytest
import torch.nn
from quafu.algorithms.ansatz import QuantumNeuralNetwork
from quafu.algorithms.gradients import compute_vjp, jacobian
Expand Down Expand Up @@ -42,13 +43,38 @@ def forward(self, features):
return out


class ModelQuantumNeuralNetworkNative(torch.nn.Module):
"""Test execution of qnn()"""

def __init__(self, qnn: QuantumNeuralNetwork):
super().__init__()
self.qnn = qnn

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


class TestLayers:
circ = QuantumCircuit(2)
circ.x(0)
circ.rx(0, 0.1)
circ.ry(1, 0.5)
circ.ry(0, 0.1)

def _model_grad(self, model, batch_size):
"""Test one forward pass and gradient calculation of a model"""

# TODO(zhaoyilun): Make out dimension configurable
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_compute_vjp(self):
params_input = np.random.randn(4, 3)
jac = jacobian(self.circ, params_input)
Expand Down Expand Up @@ -77,12 +103,24 @@ def test_torch_layer_qnn(self):
entangle_layer = BasicEntangleLayers(weights, 2)
qnn = QuantumNeuralNetwork(2, [entangle_layer])
batch_size = 1

# Legacy invokation style
model = ModelQuantumNeuralNetwork(qnn)
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()
self._model_grad(model, batch_size)

# New invokation style
model = ModelQuantumNeuralNetworkNative(qnn)
self._model_grad(model, batch_size)

@pytest.mark.skip(reason="github env doesn't have token")
def test_torch_layer_qnn_real_machine(self):
"""Use QuantumNeuralNetwork ansatz"""
weights = np.random.randn(2, 2)
entangle_layer = BasicEntangleLayers(weights, 2)
qnn = QuantumNeuralNetwork(2, [entangle_layer], backend="ScQ-P10")
qnn.measure([0, 1], [0, 1])
batch_size = 1

# New invokation style
model = ModelQuantumNeuralNetworkNative(qnn)
self._model_grad(model, batch_size)

0 comments on commit 55cba74

Please sign in to comment.