Skip to content

Commit

Permalink
feat: use and new api in algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhaoyilunnn committed Jun 7, 2024
1 parent fcf3168 commit 77f2774
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 60 deletions.
12 changes: 9 additions & 3 deletions quafu/algorithms/ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import numpy as np
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.elements import Parameter
from quafu.synthesis.evolution import ProductFormula

from .hamiltonian import Hamiltonian
Expand Down Expand Up @@ -52,8 +53,10 @@ def __init__(self, hamiltonian: Hamiltonian, num_qubits: int, num_layers: int =
self._evol = ProductFormula()

# Initialize parameters
self._beta = np.zeros(num_layers)
self._gamma = np.zeros(num_layers)
self._beta = np.array([Parameter(f"beta_{i}", 0.0) for i in range(num_layers)])
self._gamma = np.array(
[Parameter(f"gamma_{i}", 0.0) for i in range(num_layers)]
)

# Build circuit structure
super().__init__(num_qubits)
Expand Down Expand Up @@ -122,7 +125,10 @@ def __init__(self, num_qubits: int, layer: int):
layer: Number of layers.
"""
self._layer = layer
self._theta = np.zeros((layer + 1, num_qubits))
self._theta = np.array(
[Parameter(f"theta_{i}", 0.0) for i in range((layer + 1) * num_qubits)]
)
self._theta = np.reshape(self._theta, (layer + 1, num_qubits))
super().__init__(num_qubits)

def _build(self):
Expand Down
8 changes: 5 additions & 3 deletions quafu/algorithms/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
# limitations under the License.
"""Pre-build wrapper to calculate expectation value"""
from typing import List, Optional

from ..circuits.quantum_circuit import QuantumCircuit
from ..simulators import simulate
from ..tasks.tasks import Task
from .hamiltonian import Hamiltonian
from ..simulators import simulate


def execute_circuit(circ: QuantumCircuit, observables: Hamiltonian):
"""Execute circuit on quafu simulator"""
sim_res = simulate(circ, hamiltonian= observables)
sim_res = simulate(circ, hamiltonian=observables)
expectations = sim_res["pauli_expects"]
return sum(expectations)

Expand All @@ -44,6 +45,7 @@ def __init__(
task_options: options to config a task instance
"""
self._circ = circ
self._circ.get_parameter_grads() # parameter shift currently requires calling this for initialization
self._backend = backend
self._task = None
if backend != "sim":
Expand Down Expand Up @@ -85,7 +87,7 @@ def run(self, observables: Hamiltonian, params: List[float]):
Expectation value
"""
if params is not None:
self._circ.update_params(params)
self._circ._update_params(params)

if self._backend == "sim":
return self._run_simulation(observables)
Expand Down
1 change: 1 addition & 0 deletions quafu/algorithms/gradients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from .gradiant import grad_adjoint, grad_finit_diff, grad_para_shift
from .param_shift import ParamShift
from .vjp import compute_vjp, jacobian, run_circ
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
from ..circuits.quantum_circuit import QuantumCircuit
from ..simulators.simulator import SVSimulator
from ..elements import Parameter, ParameterExpression
import numpy as np
from ..exceptions import CircuitError
from ..elements.matrices import XMatrix, YMatrix, ZMatrix
from ..elements import QuantumGate, ControlledGate
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.elements import ControlledGate, Parameter, ParameterExpression, QuantumGate
from quafu.elements.matrices import XMatrix, YMatrix, ZMatrix
from quafu.exceptions import CircuitError
from quafu.simulators.simulator import SVSimulator


def assemble_grads(para_grads, gate_grads):
grads = []
for var in para_grads:
grad_p = para_grads[var]
fullgrad = 0.
fullgrad = 0.0
for pos_g in grad_p:
pos, gp = pos_g
pos, gp = pos_g
gg = gate_grads[pos[0]][pos[1]]
fullgrad += gg * gp
grads.append(fullgrad)

return grads

def grad_para_shift(qc:QuantumCircuit, hamiltonian, backend=SVSimulator()):

def grad_para_shift(qc: QuantumCircuit, hamiltonian, backend=SVSimulator()):
"""
Parameter shift gradients. Each gate must have one parameter
"""
para_grads = qc._calc_parameter_grads()
gate_grads= [[] for _ in qc.gates]
gate_grads = [[] for _ in qc.gates]

for i, op in enumerate(qc.gates):
if len(op.paras) > 0:
if isinstance(op.paras[0], Parameter) or isinstance(op.paras[0],ParameterExpression):
if isinstance(op.paras[0], Parameter) or isinstance(
op.paras[0], ParameterExpression
):
if op.name not in ["RX", "RY", "RZ"]:
raise CircuitError("It seems the circuit can not apply parameter-shift rule to calculate gradient.You may need compile the circuit first")
op.paras[0] = op.paras[0] + np.pi/2
raise CircuitError(
"It seems the circuit can not apply parameter-shift rule to calculate gradient. You may need compile the circuit first"
)
op.paras[0] = op.paras[0] + np.pi / 2
res1 = sum(backend.run(qc, hamiltonian=hamiltonian)["pauli_expects"])
op.paras[0] = op.paras[0] - np.pi
res2 = sum(backend.run(qc, hamiltonian=hamiltonian)["pauli_expects"])
op.paras[0]._undo(2)
gate_grads[i].append((res1 - res2) / 2.)
gate_grads[i].append((res1 - res2) / 2.0)

return assemble_grads(para_grads, gate_grads)


def grad_finit_diff(qc, hamiltonian, backend=SVSimulator()):
variables = qc.variables
grads = []
Expand All @@ -50,7 +56,7 @@ def grad_finit_diff(qc, hamiltonian, backend=SVSimulator()):
res2 = sum(backend.run(qc, hamiltonian=hamiltonian)["pauli_expects"])
v.value += 1e-10
grads.append((res1 - res2) / (2 * 1e-10))

return grads


Expand All @@ -60,37 +66,37 @@ def grad_gate(op):
"""
if isinstance(op, ControlledGate):
if op._targ_name == "RX":
circ = QuantumCircuit(max(op.pos)+1)
circ = QuantumCircuit(max(op.pos) + 1)
deriv_mat = -0.5j * XMatrix @ op._get_targ_matrix()
circ << QuantumGate("dRX", op.targs, [], deriv_mat)
cdim = 1 << (len(op.ctrls))
proj_mat = np.zeros((cdim, cdim))
proj_mat[cdim-1, cdim-1] = 1.
proj_mat[cdim - 1, cdim - 1] = 1.0
circ << QuantumGate("projCtrl", op.ctrls, [], proj_mat)
return circ.wrap()

elif op._targ_name == "RY":
circ = QuantumCircuit(max(op.pos)+1)
circ = QuantumCircuit(max(op.pos) + 1)
deriv_mat = -0.5j * YMatrix @ op._get_targ_matrix()
circ << QuantumGate("dRY", op.targs, [], deriv_mat)
cdim = 1 << (len(op.ctrls))
proj_mat = np.zeros((cdim, cdim))
proj_mat[cdim-1, cdim-1] = 1.
proj_mat[cdim - 1, cdim - 1] = 1.0
circ << QuantumGate("projCtrl", op.ctrls, [], proj_mat)
return circ.wrap()

elif op._targ_name == "RZ":
circ = QuantumCircuit(max(op.pos)+1)
circ = QuantumCircuit(max(op.pos) + 1)
deriv_mat = -0.5j * ZMatrix @ op._get_targ_matrix()
circ << QuantumGate("dRZ", op.targs, [], deriv_mat)
cdim = 1 << (len(op.ctrls))
proj_mat = np.zeros((cdim, cdim))
proj_mat[cdim-1, cdim-1] = 1.
proj_mat[cdim - 1, cdim - 1] = 1.0
circ << QuantumGate("projCtrl", op.ctrls, [], proj_mat)
return circ.wrap()
else:
raise NotImplementedError

else:
if op.name == "RX":
deriv_mat = -0.5j * XMatrix @ op.matrix
Expand All @@ -103,30 +109,37 @@ def grad_gate(op):
return QuantumGate("dRZ", op.pos, [], deriv_mat)
else:
raise NotImplementedError



def grad_adjoint(qc, hamiltonian, psi_in=np.array([], dtype=complex)):
"""
Reverse mode gradient: arXiv:2009.02823
"""
para_grads = qc._calc_parameter_grads()
backend = SVSimulator()
lam = backend.run(qc, psi = psi_in)["statevector"]
lam = backend.run(qc, psi=psi_in)["statevector"]
phi = np.copy(lam)
lam = backend._apply_hamil(hamiltonian, lam)
begin = 0
end = len(qc.gates)
gate_grads= [[] for _ in range(end)]
gate_grads = [[] for _ in range(end)]
for i, op in enumerate(qc.gates):
if len(op.paras) > 0 and (isinstance(op.paras[0], Parameter) or isinstance(op.paras[0],ParameterExpression)):
if len(op.paras) > 0 and (
isinstance(op.paras[0], Parameter)
or isinstance(op.paras[0], ParameterExpression)
):
begin = i
break

for i in range(begin, end)[::-1]:
op = qc.gates[i]
phi = backend._apply_op(op.dagger(), phi)
if len(op.paras) > 0 and (isinstance(op.paras[0], Parameter) or isinstance(op.paras[0],ParameterExpression)):
mu = np.copy(phi)
mu = backend._apply_op(grad_gate(op), mu)
gate_grads[i].append(np.real(2. * np.inner(lam.conj(), mu)))
if len(op.paras) > 0 and (
isinstance(op.paras[0], Parameter)
or isinstance(op.paras[0], ParameterExpression)
):
mu = np.copy(phi)
mu = backend._apply_op(grad_gate(op), mu)
gate_grads[i].append(np.real(2.0 * np.inner(lam.conj(), mu)))
lam = backend._apply_op(op.dagger(), lam)
return assemble_grads(para_grads, gate_grads)
return assemble_grads(para_grads, gate_grads)
13 changes: 12 additions & 1 deletion quafu/algorithms/gradients/param_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from ..estimator import Estimator
from ..hamiltonian import Hamiltonian
from .gradiant import grad_para_shift


class ParamShift:
Expand All @@ -34,7 +35,7 @@ def __call__(self, obs: Hamiltonian, params: List[float]):
estimator (Estimator): estimator to calculate expectation values
params (List[float]): params to optimize
"""
return self.grad(obs, params)
return self.new_grad(obs, params)

def _gen_param_shift_vals(self, params):
"""Given a param list with n values, replicate to 2*n param list"""
Expand All @@ -45,6 +46,7 @@ def _gen_param_shift_vals(self, params):
minus_params = params - offsets * np.pi / 2
return plus_params.tolist() + minus_params.tolist()

# TODO: delete after 0.4.1
def grad(self, obs: Hamiltonian, params: List[float]):
"""grad.
Expand All @@ -61,3 +63,12 @@ def grad(self, obs: Hamiltonian, params: List[float]):
num_shift_params = len(res)
grads = (res[: num_shift_params // 2] - res[num_shift_params // 2 :]) / 2
return grads

def new_grad(self, obs: Hamiltonian, params: List[float]):
"""Calculate the gradients of given the circuit based on the parameter shift rule
Args:
obs (Hamiltonian): observables for measurement.
params (List[float]): parameters to apply to the circuit.
"""
self._est._circ._update_params(params)
return grad_para_shift(self._est._circ, obs)
1 change: 1 addition & 0 deletions quafu/circuits/quantum_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ def _update_params(self, values, order=[]):
val = values[order[i]] if order else values[i]
self._variables[i].value = val

# TODO: delete after 0.4.1
def update_params(self, paras_list: List[Any]):
"""Update parameters of parameterized gates
Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
from quafu.circuits.quantum_circuit import QuantumCircuit


class TestMergeCircuits:
"""Example of merging circuits"""
class TestConstructQLayers:
"""Test stacking multiple different quantum layers"""

def test_merge_circuits(self):
def test_construct_qlayers(self):
state = np.array([7, 2, 3, 4])
encoding_layer = AmplitudeEmbedding(state=state, num_qubits=2, normalize=True)

Expand Down
38 changes: 28 additions & 10 deletions tests/quafu/algorithms/gradient_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,47 @@

import sys

import numpy as np
import pytest
from quafu.algorithms.estimator import Estimator
from quafu.algorithms.gradients import ParamShift
from quafu.algorithms.gradients import ParamShift, grad_para_shift
from quafu.algorithms.hamiltonian import Hamiltonian
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.elements import Parameter


# TODO: remove this test after releasing 0.4.1 as it is not necessary
class TestParamShift:
@pytest.mark.skipif(
sys.platform == "darwin", reason="Avoid error on MacOS arm arch."
)
def test_call(self):
"""
This test simply ensures that the legacy implementation of parameter shift produces
the same results with the new implementation
"""
theta_0 = Parameter("theta_0", 0.2)
theta_1 = Parameter("theta_1", 0.6)
ham = Hamiltonian.from_pauli_list([("Z0 Z1", 1), ("X1", 1)])
circ = QuantumCircuit(2)
# circ.h(0)
# circ.h(1)
circ.rx(0, 0.5)
circ.cnot(0, 1)
circ.ry(1, 0.5)

circ_0 = QuantumCircuit(2)
circ_0.rx(0, theta_0)
circ_0.cnot(0, 1)
circ_0.ry(1, theta_1)

params = [0.2, 0.6]
estimator = Estimator(circ)
estimator = Estimator(circ_0)
grad = ParamShift(estimator)

grads = grad(ham, params)
print(grads)
grads_0 = grad(ham, params)
print(grads_0)

circ_1 = QuantumCircuit(2)
circ_1.rx(0, theta_0)
circ_1.cnot(0, 1)
circ_1.ry(1, theta_1)
circ_1.get_parameter_grads()
grads_1 = grad_para_shift(circ_1, ham)
print(grads_1)

assert np.allclose(grads_0, grads_1, atol=1e-6)
8 changes: 5 additions & 3 deletions tests/quafu/algorithms/qnn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from quafu.algorithms.interface.torch import TorchTransformer
from quafu.algorithms.templates.basic_entangle import BasicEntangleLayers
from quafu.circuits.quantum_circuit import QuantumCircuit
from quafu.elements import Parameter
from torch import nn
from torch.utils.data import DataLoader, TensorDataset

Expand Down Expand Up @@ -112,10 +113,11 @@ def forward(self, features):

class TestLayers:
circ = QuantumCircuit(2)
theta = [Parameter(f"theta_{i}", 0.1) for i in range(3)]
circ.x(0)
circ.rx(0, 0.1)
circ.ry(1, 0.5)
circ.ry(0, 0.1)
circ.rx(0, theta[0])
circ.ry(1, theta[1])
circ.ry(0, theta[2])

def _model_grad(self, model, batch_size):
"""Test one forward pass and gradient calculation of a model"""
Expand Down
2 changes: 1 addition & 1 deletion tests/quafu/algorithms/varational_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np
import scipy.sparse as sp
from quafu.algorithms.gradient import grad_adjoint, grad_finit_diff, grad_para_shift
from quafu.algorithms.gradients import grad_adjoint, grad_finit_diff, grad_para_shift
from quafu.algorithms.hamiltonian import Hamiltonian, PauliMats, PauliOp
from quafu.elements import Parameter
from quafu.elements.element_gates import CRYGate, CXGate, HGate, RXGate, RYGate, RZGate
Expand Down

0 comments on commit 77f2774

Please sign in to comment.