Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alternating layered ansatz implementation and a sample VQE #88

Merged
merged 4 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Lint Commit Messages
on: [push, pull_request]

jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: wagoid/commitlint-github-action@v5
33 changes: 17 additions & 16 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
*.egg-info
*.pyc
*.pyd
*.so
*.svg
*temp.*
.DS_Store
src/*.yml
src/*.egg-info
src/site
src/docs
build
dist
wheelhouse
test
_skbuild
cmake
*.egg-info
.idea
.vscode
thirdparty
*.so
MANIFEST.in
*.pyd
_skbuild
build
cmake
dev_*.py
*temp.*
.idea
dist
parsetab.py
src/*.egg-info
src/*.yml
src/docs
src/site
test
thirdparty
wheelhouse
61 changes: 59 additions & 2 deletions quafu/algorithms/ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

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

Expand All @@ -21,7 +22,24 @@
from quafu.synthesis.evolution import ProductFormula


class QAOACircuit(QuantumCircuit):
class Ansatz(QuantumCircuit, ABC):
"""Ansatz interface"""

def __init__(self, num: int, *args, **kwargs):
super().__init__(num, *args, **kwargs)
self._build()

@property
def num_parameters(self):
"""Get the number of parameters"""
return len(super().parameterized_gates)

@abstractmethod
def _build(self):
pass


class QAOAAnsatz(Ansatz):
"""QAOA circuit"""

def __init__(self, hamiltonian: Hamiltonian, num_layers: int = 1):
Expand All @@ -38,7 +56,10 @@ def __init__(self, hamiltonian: Hamiltonian, num_layers: int = 1):
# Build circuit structure
num_qubits = len(self._pauli_list[0])
super().__init__(num_qubits)
self._build()

@property
def num_parameters(self):
return len(self._beta) + len(self._gamma)

@property
def parameters(self):
Expand Down Expand Up @@ -80,3 +101,39 @@ def update_params(self, beta: List[float], gamma: List[float]):
assert num_para_gates % self._num_layers == 0
self._beta, self._gamma = beta, gamma
super().update_params(self.parameters)


class AlterLayeredAnsatz(Ansatz):
"""A type of quantum circuit template that
are problem-independent and hardware efficient

Reference:
*Alternating layered ansatz*
- http://arxiv.org/abs/2101.08448
- http://arxiv.org/abs/1905.10876
"""

def __init__(self, num_qubits: int, layer: int):
"""
Args:
num_qubits: Number of qubits.
layer: Number of layers.
"""
self._layer = layer
self._theta = np.zeros((layer + 1, num_qubits))
super().__init__(num_qubits)

def _build(self):
"""Construct circuit.

Apply `self._layer` blocks, each block consists of a rotation gates on each qubit
and an entanglement layer.
"""
cnot_pairs = [(i, (i + 1) % self.num) for i in range(self.num)]
for layer in range(self._layer):
for qubit in range(self.num):
self.ry(qubit, self._theta[layer, qubit])
for ctrl, targ in cnot_pairs:
self.cnot(ctrl, targ)
for qubit in range(self.num):
self.ry(qubit, self._theta[self._layer, qubit])
3 changes: 3 additions & 0 deletions quafu/algorithms/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def _run_real_machine(self, observables: Hamiltonian):
"""Submit to quafu service"""
if not isinstance(self._task, Task):
raise ValueError("task not set")
# TODO(zhaoyilun): replace old `submit` API in the future,
# investigate the best implementation for calculating
# expectation on real devices.
obs = observables.to_legacy_quafu_pauli_list()
_, obsexp = self._task.submit(self._circ, obs)
return sum(obsexp)
Expand Down
12 changes: 10 additions & 2 deletions tests/quafu/algorithms/ansatz_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,27 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""TODO: test of ansatz needs improvement once ansatz has more featuers"""

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


class TestQAOACircuit:
TEST_HAM = Hamiltonian(["IIZZ", "ZZII", "IZZI", "ZIIZ"], np.array([1, 1, 1, 1]))

def test_build(self):
qaoa = QAOACircuit(self.TEST_HAM)
qaoa = QAOAAnsatz(self.TEST_HAM)
print("\n ::: testing ::: \n")
qaoa.draw_circuit()

def test_update_params(self):
pass


class TestAlterLayeredAnsatz:
def test_build(self):
circ = AlterLayeredAnsatz(4, 4)
# print("\n ::: testing ::: \n")
# circ.plot_circuit(save_path="TestAlterLayeredAnsatz.svg")
Binary file added tests/quafu/algorithms/data/vqe_hamiltonian.npy
Binary file not shown.
39 changes: 35 additions & 4 deletions tests/quafu/algorithms/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
from typing import List
import numpy as np
import pytest
from quafu.algorithms import Hamiltonian, QAOACircuit, Estimator
from quafu.algorithms import Hamiltonian, QAOAAnsatz, Estimator
from quafu import simulate
from scipy.optimize import minimize
import heapq

from quafu.algorithms.ansatz import AlterLayeredAnsatz


class TestQAOA:
"""Example of a QAOA algorithm to solve maxcut problem"""
Expand Down Expand Up @@ -61,9 +63,9 @@ def test_run(self):
hamlitonian = Hamiltonian.from_pauli_list(
[("IIIZZ", 1), ("IIZIZ", 1), ("IZIIZ", 1), ("ZIIIZ", 1)]
)
ref_mat = np.load("tests/quafu/algorithms/test_hamiltonian.npy")
ref_mat = np.load("tests/quafu/algorithms/data/qaoa_hamiltonian.npy")
assert np.array_equal(ref_mat, hamlitonian.get_matrix())
ansatz = QAOACircuit(hamlitonian, num_layers=num_layers)
ansatz = QAOAAnsatz(hamlitonian, num_layers=num_layers)
ansatz.draw_circuit()

def cost_func(params, ham, estimator: Estimator):
Expand All @@ -73,7 +75,8 @@ def cost_func(params, ham, estimator: Estimator):
return cost

est = Estimator(ansatz)
params = 2 * np.pi * np.random.rand(num_layers * 2)
# params = 2 * np.pi * np.random.rand(num_layers * 2)
params = 2 * np.pi * np.random.rand(ansatz.num_parameters)
res = minimize(cost_func, params, args=(hamlitonian, est), method="COBYLA")
print(res)
ansatz.measure(list(range(5)))
Expand All @@ -82,3 +85,31 @@ def cost_func(params, ham, estimator: Estimator):
print(probs)
self.check_solution(list(probs), ["10000", "01111"])
print("::: PASSED ::: Find Correct Answers:: 01111 and 10000")


class TestVQE:
@pytest.mark.skipif(
sys.platform == "darwin", reason="Avoid error on MacOS arm arch."
)
def test_run(self):
"""A sample VQE algorithm"""

num_layers = 4
num_qubits = 2
hamlitonian = Hamiltonian.from_pauli_list(
[("YZ", 0.3980), ("ZI", -0.3980), ("ZZ", -0.0113), ("XX", 0.1810)]
)
ref_mat = np.load("tests/quafu/algorithms/data/vqe_hamiltonian.npy")
assert np.array_equal(ref_mat, hamlitonian.get_matrix())
ansatz = AlterLayeredAnsatz(num_qubits, num_layers)

def cost_func(params, ham, estimator: Estimator):
cost = estimator.run(ham, params)
return cost

est = Estimator(ansatz)
num_params = ansatz.num_parameters
assert num_params == 10
params = 2 * np.pi * np.random.rand(num_params)
res = minimize(cost_func, params, args=(hamlitonian, est), method="COBYLA")
print(res)
Loading