Skip to content

Commit

Permalink
Merge pull request #132 from Blueqat/welltyped-running
Browse files Browse the repository at this point in the history
Well-typed running
  • Loading branch information
gyu-don authored Jun 22, 2021
2 parents 1a8bfb0 + 565e5f2 commit c60a6d2
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 30 deletions.
117 changes: 93 additions & 24 deletions blueqat/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@

import warnings
from functools import partial
from typing import Callable
import typing
from typing import Callable, Tuple

import numpy as np

from . import gate

if typing.TYPE_CHECKING:
from .backends.backendbase import Backend
BackendUnion = typing.Union[None, str, Backend]

GATE_SET = {
# 1 qubit gates (alphabetical)
"h": gate.HGate,
Expand Down Expand Up @@ -92,7 +99,7 @@ def __repr__(self):
return f'Circuit({self.n_qubits}).' + '.'.join(
str(op) for op in self.ops)

def __get_backend(self, backend_name):
def __get_backend(self, backend_name: str) -> 'Backend':
try:
return self._backends[backend_name]
except KeyError:
Expand Down Expand Up @@ -138,10 +145,8 @@ def __iadd__(self, other):
return self

def copy(self,
copy_backends=True,
copy_default_backend=True,
copy_cache=None,
copy_history=None):
copy_backends: bool = True,
copy_default_backend: bool = True) -> 'Circuit':
"""Copy the circuit.
params:
Expand All @@ -153,26 +158,17 @@ def copy(self,
copied._backends = {k: v.copy() for k, v in self._backends.items()}
if copy_default_backend:
copied._default_backend = self._default_backend

# Warn for deprecated options
if copy_cache is not None:
warnings.warn(
"copy_cache is deprecated. Use copy_backends instead.",
DeprecationWarning)
if copy_history is not None:
warnings.warn("copy_history is deprecated.", DeprecationWarning)

return copied

def dagger(self,
ignore_measurement=False,
copy_backends=False,
copy_default_backend=True):
ignore_measurement: bool = False,
copy_backends: bool = False,
copy_default_backend: bool = True) -> 'Circuit':
"""Make Hermitian conjugate of the circuit.
This feature is beta. Interface may be changed.
ignore_measurement (bool, optional):
ignore_measurement (bool, optional):
| If True, ignore the measurement in the circuit.
| Otherwise, if measurement in the circuit, raises ValueError.
"""
Expand Down Expand Up @@ -210,7 +206,7 @@ def run(self, *args, backend=None, **kwargs):
| e.g. "statevector" returns the state vector after run the circuit.
| "shots" returns the counter of measured value.
| token, url (str, optional): The token and URL for cloud resource.
Returns:
Depends on backend.
Expand All @@ -226,7 +222,7 @@ def run(self, *args, backend=None, **kwargs):
backend = self.__get_backend(backend)
return backend.run(self.ops, self.n_qubits, *args, **kwargs)

def make_cache(self, backend=None):
def make_cache(self, backend: 'BackendUnion' = None) -> None:
"""Make a cache to reduce the time of run. Some backends may implemented it.
This is temporary API. It may changed or deprecated."""
Expand All @@ -235,7 +231,9 @@ def make_cache(self, backend=None):
backend = DEFAULT_BACKEND_NAME
else:
backend = self._default_backend
return self.__get_backend(backend).make_cache(self.ops, self.n_qubits)
if isinstance(backend, str):
backend = self.__get_backend(backend)
return backend.make_cache(self.ops, self.n_qubits)

def to_qasm(self, *args, **kwargs):
"""Returns the OpenQASM output of this circuit."""
Expand All @@ -245,7 +243,7 @@ def to_unitary(self, *args, **kwargs):
"""Returns sympy unitary matrix of this circuit."""
return self.run_with_sympy_unitary(*args, **kwargs)

def set_default_backend(self, backend_name):
def set_default_backend(self, backend_name: str) -> None:
"""Set the default backend of this circuit.
This setting is only applied for this circuit.
Expand All @@ -267,14 +265,85 @@ def set_default_backend(self, backend_name):
raise ValueError(f"Unknown backend '{backend_name}'.")
self._default_backend = backend_name

def get_default_backend_name(self):
def get_default_backend_name(self) -> str:
"""Get the default backend of this circuit or global setting.
Returns:
str: The name of default backend.
"""
return DEFAULT_BACKEND_NAME if self._default_backend is None else self._default_backend

def statevector(self,
backend: 'BackendUnion' = None,
**kwargs) -> np.ndarray:
"""Run the circuit and get a statevector as a result."""
if kwargs.get('returns'):
raise ValueError('Circuit.statevector has no argument `returns`.')
if backend is None:
if self._default_backend is None:
backend = self.__get_backend(DEFAULT_BACKEND_NAME)
else:
backend = self.__get_backend(self._default_backend)
elif isinstance(backend, str):
backend = self.__get_backend(backend)

if hasattr(backend, 'statevector'):
return backend.statevector(self.ops, self.n_qubits,
**kwargs)
return backend.run(self.ops,
self.n_qubits,
returns='statevector',
**kwargs)

def shots(self,
shots: int,
backend: 'BackendUnion' = None,
**kwargs) -> typing.Counter[str]:
"""Run the circuit and get shots as a result."""
if kwargs.get('returns'):
raise ValueError('Circuit.shots has no argument `returns`.')
if backend is None:
if self._default_backend is None:
backend = self.__get_backend(DEFAULT_BACKEND_NAME)
else:
backend = self.__get_backend(self._default_backend)
elif isinstance(backend, str):
backend = self.__get_backend(backend)

if hasattr(backend, 'shots'):
return backend.shots(self.ops,
self.n_qubits,
shots=shots,
**kwargs)
return backend.run(self.ops,
self.n_qubits,
shots=shots,
returns='shots',
**kwargs)

def oneshot(self,
backend: 'BackendUnion' = None,
**kwargs) -> Tuple[np.ndarray, str]:
"""Run the circuit and get shots as a result."""
if kwargs.get('returns'):
raise ValueError('Circuit.oneshot has no argument `returns`.')
if backend is None:
if self._default_backend is None:
backend = self.__get_backend(DEFAULT_BACKEND_NAME)
else:
backend = self.__get_backend(self._default_backend)
elif isinstance(backend, str):
backend = self.__get_backend(backend)

if hasattr(backend, 'oneshot'):
return backend.oneshot(self.ops, self.n_qubits, **kwargs)
v, cnt = backend.run(self.ops,
self.n_qubits,
shots=1,
returns='statevector_and_shots',
**kwargs)
return v, cnt.most_common()[0][0]


class _GateWrapper:
def __init__(self, circuit, name, gate):
Expand Down
41 changes: 35 additions & 6 deletions tests/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ def test_toffoli_gate(bits, backend):

def test_u_gate(backend):
assert np.allclose(
Circuit().u(1.23, 4.56, -5.43, -0.5 * (4.56 - 5.43))[1].run(backend=backend),
Circuit().u(1.23, 4.56, -5.43,
-0.5 * (4.56 - 5.43))[1].run(backend=backend),
Circuit().rz(-5.43)[1].ry(1.23)[1].rz(4.56)[1].run(backend=backend))


Expand All @@ -183,7 +184,8 @@ def test_rotation1(backend):

def test_crotation(backend):
assert np.allclose(
Circuit().cu(1.23, 4.56, -5.43, -0.5 * (4.56 - 5.43))[3, 1].run(backend=backend),
Circuit().cu(1.23, 4.56, -5.43,
-0.5 * (4.56 - 5.43))[3, 1].run(backend=backend),
Circuit().crz(-5.43)[3,
1].cry(1.23)[3,
1].crz(4.56)[3,
Expand Down Expand Up @@ -212,25 +214,30 @@ def test_globalphase_rz(backend):
c = Circuit().rz(theta)[0]
assert abs(c.run(backend=backend)[0] - cmath.exp(-0.5j * theta)) < 1e-8


def test_globalphase_r(backend):
theta = 1.2
c = Circuit().phase(theta)[0]
assert abs(c.run(backend=backend)[0] - 1) < 1e-8


def test_globalphase_u(backend):
theta = 1.2
phi = 1.6
lambd = 2.3
c = Circuit().u(theta, phi, lambd)[0]
assert abs(c.run(backend=backend)[0] - math.cos(theta / 2)) < 1e-8


def test_globalphase_u_with_gamma(backend):
theta = 1.2
phi = 1.6
lambd = 2.3
gamma = -1.4
c = Circuit().u(theta, phi, lambd, gamma)[0]
assert abs(c.run(backend=backend)[0] - cmath.exp(1j * gamma) * math.cos(theta / 2)) < 1e-8
assert abs(
c.run(backend=backend)[0] -
cmath.exp(1j * gamma) * math.cos(theta / 2)) < 1e-8


def test_controlled_gate_phase_crz(backend):
Expand Down Expand Up @@ -278,7 +285,8 @@ def test_controlled_gate_phase_cu_with_gamma(backend):
v0 = c0.run(backend=backend)
v1 = c1.run(backend=backend)
assert abs(abs(v0[0]) - 1) < 1e-8
assert abs(v1[1] / v0[0] - cmath.exp(1j * gamma) * math.cos(theta / 2)) < 1e-8
assert abs(v1[1] / v0[0] -
cmath.exp(1j * gamma) * math.cos(theta / 2)) < 1e-8


def test_measurement1(backend):
Expand Down Expand Up @@ -443,7 +451,11 @@ def test_complicated_circuit(backend):
c.cx[0, 2].h[0].rx(1.5707963267948966)[2].h[0].ry(-1.5707963267948966)[2]
c.cx[0, 2].cx[2, 3].rz(0.095491506289)[3].cx[2, 3].cx[0, 2].h[0]
c.rx(1.5707963267948966)[2].h[0].ry(-1.5707963267948966)[2].cx[0, 1]
c.cx[1, 2].rz(0.095491506289)[2].cx[1, 2].cx[0, 1].h[0].u(math.pi / 2, 1.58, -0.62)[2]
c.cx[1,
2].rz(0.095491506289)[2].cx[1,
2].cx[0,
1].h[0].u(math.pi / 2, 1.58,
-0.62)[2]
c.h[0].rx(-1.5707963267948966)[2].cx[0, 1].cx[1, 2].cx[2, 3]
c.rz(0.095491506289)[3].cx[2, 3].cx[1, 2].cx[0, 1].h[0]
c.rx(1.5707963267948966)[2].u(0.42, -1.5707963267948966, 1.64)[2].h[2]
Expand Down Expand Up @@ -640,7 +652,8 @@ def test_initial_vec(backend):

c = Circuit().h[0]
v1 = c.run(backend=backend)
assert np.allclose(c.run(backend=backend, initial=v1), Circuit(1).run(backend=backend))
assert np.allclose(c.run(backend=backend, initial=v1),
Circuit(1).run(backend=backend))


def test_initial_vec2(backend):
Expand All @@ -667,3 +680,19 @@ def test_initial_vec3(backend):
v = Circuit(4).h[3].run(backend=backend)
v2 = Circuit(4).run(backend=backend, initial=v)
assert np.allclose(v, v2)


def test_statevector_method(backend):
assert isinstance(
Circuit().h[0].cx[0, 1].m[:].statevector(backend=backend), np.ndarray)


def test_shots_method(backend):
assert isinstance(
Circuit().h[0].cx[0, 1].m[:].shots(200, backend=backend), Counter)


def test_oneshot_method(backend):
vec, meas = Circuit().h[0].cx[0, 1].m[:].oneshot(backend=backend)
assert isinstance(vec, np.ndarray)
assert isinstance(meas, str)

0 comments on commit c60a6d2

Please sign in to comment.