From 7e8ca9c2acfbe26709531ae03a9941c88289081c Mon Sep 17 00:00:00 2001 From: gyu-don Date: Mon, 29 Mar 2021 15:25:41 +0900 Subject: [PATCH 1/6] ENH: Add methods statevector(), shots(), oneshot() --- blueqat/circuit.py | 69 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/blueqat/circuit.py b/blueqat/circuit.py index 368dbf8..a9f1914 100644 --- a/blueqat/circuit.py +++ b/blueqat/circuit.py @@ -17,7 +17,10 @@ import warnings from functools import partial -from typing import Callable +import typing +from typing import Callable, Tuple + +import numpy as np from . import gate @@ -210,7 +213,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. @@ -275,6 +278,68 @@ def get_default_backend_name(self): """ return DEFAULT_BACKEND_NAME if self._default_backend is None else self._default_backend + def statevector(self, *args, backend=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 backend.hasattr('statevector'): + return backend.statevector(self.ops, self.n_qubits, *args, + **kwargs) + return backend.run(self.ops, + self.n_qubits, + *args, + returns='statevector', + **kwargs) + + def shots(self, *args, backend=None, **kwargs) -> typing.Counter[str]: + """Run the circuit and get shots 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 backend.hasattr('shots'): + return backend.shots(self.ops, self.n_qubits, *args, **kwargs) + return backend.run(self.ops, + self.n_qubits, + *args, + returns='shots', + **kwargs) + + def oneshot(self, *args, backend=None, **kwargs) -> Tuple[np.ndarray, str]: + """Run the circuit and get shots 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 backend.hasattr('oneshot'): + return backend.shots(self.ops, self.n_qubits, *args, **kwargs) + v, cnt = backend.run(self.ops, + self.n_qubits, + *args, + returns='statevector_and_shots', + **kwargs) + return v, cnt.most_common()[0][0] + class _GateWrapper: def __init__(self, circuit, name, gate): From 47d7f7b432e861ead923fd1d6394d9220fec0e91 Mon Sep 17 00:00:00 2001 From: gyu-don Date: Fri, 11 Jun 2021 22:29:00 +0900 Subject: [PATCH 2/6] API: Remove deprecated options in Circuit.copy() --- blueqat/circuit.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/blueqat/circuit.py b/blueqat/circuit.py index a9f1914..7bf724d 100644 --- a/blueqat/circuit.py +++ b/blueqat/circuit.py @@ -141,10 +141,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: @@ -156,15 +154,6 @@ 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, From e217ccbded5234324c9e4c388d252e493d4aab91 Mon Sep 17 00:00:00 2001 From: gyu-don Date: Fri, 11 Jun 2021 22:30:59 +0900 Subject: [PATCH 3/6] BUG: Resolve AttributeError --- blueqat/circuit.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/blueqat/circuit.py b/blueqat/circuit.py index 7bf724d..111ff72 100644 --- a/blueqat/circuit.py +++ b/blueqat/circuit.py @@ -227,7 +227,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.""" @@ -279,19 +281,21 @@ def statevector(self, *args, backend=None, **kwargs) -> np.ndarray: elif isinstance(backend, str): backend = self.__get_backend(backend) - if backend.hasattr('statevector'): - return backend.statevector(self.ops, self.n_qubits, *args, + if hasattr(backend, 'statevector'): + return backend.statevector(self.ops, self.n_qubits, **kwargs) return backend.run(self.ops, self.n_qubits, - *args, returns='statevector', **kwargs) - def shots(self, *args, backend=None, **kwargs) -> typing.Counter[str]: + 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.statevector has no argument `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) @@ -300,18 +304,23 @@ def shots(self, *args, backend=None, **kwargs) -> typing.Counter[str]: elif isinstance(backend, str): backend = self.__get_backend(backend) - if backend.hasattr('shots'): - return backend.shots(self.ops, self.n_qubits, *args, **kwargs) + if hasattr(backend, 'shots'): + return backend.shots(self.ops, + self.n_qubits, + shots=shots, + **kwargs) return backend.run(self.ops, self.n_qubits, - *args, + shots=shots, returns='shots', **kwargs) - def oneshot(self, *args, backend=None, **kwargs) -> Tuple[np.ndarray, str]: + 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.statevector has no argument `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) @@ -320,11 +329,11 @@ def oneshot(self, *args, backend=None, **kwargs) -> Tuple[np.ndarray, str]: elif isinstance(backend, str): backend = self.__get_backend(backend) - if backend.hasattr('oneshot'): - return backend.shots(self.ops, self.n_qubits, *args, **kwargs) + if hasattr(backend, 'oneshot'): + return backend.oneshot(self.ops, self.n_qubits, **kwargs) v, cnt = backend.run(self.ops, self.n_qubits, - *args, + shots=1, returns='statevector_and_shots', **kwargs) return v, cnt.most_common()[0][0] From 9ce1a7ed75c5bc77116dc7d3afb68c482467715f Mon Sep 17 00:00:00 2001 From: gyu-don Date: Fri, 11 Jun 2021 22:31:28 +0900 Subject: [PATCH 4/6] MAINT: Add type hinting --- blueqat/circuit.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/blueqat/circuit.py b/blueqat/circuit.py index 111ff72..b152dac 100644 --- a/blueqat/circuit.py +++ b/blueqat/circuit.py @@ -24,6 +24,10 @@ 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, @@ -95,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: @@ -157,14 +161,14 @@ def copy(self, 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. """ @@ -218,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.""" @@ -239,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. @@ -261,7 +265,7 @@ 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: @@ -269,7 +273,9 @@ def get_default_backend_name(self): """ return DEFAULT_BACKEND_NAME if self._default_backend is None else self._default_backend - def statevector(self, *args, backend=None, **kwargs) -> np.ndarray: + 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`.') From 48ebc1206366314580a9c496da4f6eb8e4d4a6b2 Mon Sep 17 00:00:00 2001 From: gyu-don Date: Tue, 15 Jun 2021 16:01:19 +0900 Subject: [PATCH 5/6] BUG: Solve NameError --- blueqat/circuit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blueqat/circuit.py b/blueqat/circuit.py index b152dac..fe1109a 100644 --- a/blueqat/circuit.py +++ b/blueqat/circuit.py @@ -222,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: BackendUnion = None) -> 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.""" @@ -274,7 +274,7 @@ def get_default_backend_name(self) -> str: return DEFAULT_BACKEND_NAME if self._default_backend is None else self._default_backend def statevector(self, - backend: BackendUnion = None, + backend: 'BackendUnion' = None, **kwargs) -> np.ndarray: """Run the circuit and get a statevector as a result.""" if kwargs.get('returns'): @@ -297,7 +297,7 @@ def statevector(self, def shots(self, shots: int, - backend: BackendUnion = None, + backend: 'BackendUnion' = None, **kwargs) -> typing.Counter[str]: """Run the circuit and get shots as a result.""" if kwargs.get('returns'): @@ -322,7 +322,7 @@ def shots(self, **kwargs) def oneshot(self, - backend: BackendUnion = None, + backend: 'BackendUnion' = None, **kwargs) -> Tuple[np.ndarray, str]: """Run the circuit and get shots as a result.""" if kwargs.get('returns'): From 565e5f2a09773df267c44643aa5ffdbebf818fc4 Mon Sep 17 00:00:00 2001 From: gyu-don Date: Tue, 22 Jun 2021 15:06:13 +0900 Subject: [PATCH 6/6] TST: Add tests for statevector(), shots(), oneshot() methods --- tests/test_circuit.py | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 5cbd8be..ebef5b6 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -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)) @@ -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, @@ -212,11 +214,13 @@ 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 @@ -224,13 +228,16 @@ def test_globalphase_u(backend): 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): @@ -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): @@ -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] @@ -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): @@ -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)