From cbf5810062a515ac5dc8fa1300e4f9e9bb89293f Mon Sep 17 00:00:00 2001 From: Stavros <35475381+stavros11@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:24:27 +0400 Subject: [PATCH 1/4] Add tolerance attributes in config --- src/qibo/backends/abstract.py | 6 ++++++ src/qibo/config.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index 5c5c3a7d4d..3e837338d6 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -15,6 +15,8 @@ def __init__(self): self.available_platforms = [None] self.precision = 'double' + from qibo.config import PRECISION_TOL_DOUBLE + self.precision_tol = PRECISION_TOL_DOUBLE self._dtypes = {'DTYPEINT': 'int64', 'DTYPE': 'float64', 'DTYPECPX': 'complex128'} @@ -56,11 +58,15 @@ def dtypes(self, name): def set_precision(self, dtype): if dtype == 'single': + from qibo.config import PRECISION_TOL_DOUBLE self._dtypes['DTYPE'] = 'float32' self._dtypes['DTYPECPX'] = 'complex64' + self.precision_tol = PRECISION_TOL_DOUBLE elif dtype == 'double': + from qibo.config import PRECISION_TOL_SINGLE self._dtypes['DTYPE'] = 'float64' self._dtypes['DTYPECPX'] = 'complex128' + self.precision_tol = PRECISION_TOL_SINGLE else: raise_error(ValueError, f'dtype {dtype} not supported.') self.precision = dtype diff --git a/src/qibo/config.py b/src/qibo/config.py index 81cbdc9073..6644c55dbd 100644 --- a/src/qibo/config.py +++ b/src/qibo/config.py @@ -21,6 +21,10 @@ # Eigenvalues smaller than this cut-off are ignored in entropy calculation EIGVAL_CUTOFF = 1e-14 +# Tolerance for the probability sum check in the unitary channel +PRECISION_TOL_SINGLE = 1e-8 +PRECISION_TOL_DOUBLE = 1e-12 + # Batch size for sampling shots in measurement frequencies calculation SHOT_BATCH_SIZE = 2 ** 18 From f0cc922cd1b38030017165d0ed4912ef2c73fefe Mon Sep 17 00:00:00 2001 From: Stavros <35475381+stavros11@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:25:42 +0400 Subject: [PATCH 2/4] Use tolerance when checking probability sum --- src/qibo/abstractions/gates.py | 4 ---- src/qibo/core/gates.py | 4 ++++ src/qibo/tests/test_abstract_gates.py | 2 -- src/qibo/tests/test_core_gates.py | 22 ++++++++++++++++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/qibo/abstractions/gates.py b/src/qibo/abstractions/gates.py index caf8033d56..cd7d351209 100644 --- a/src/qibo/abstractions/gates.py +++ b/src/qibo/abstractions/gates.py @@ -1467,10 +1467,6 @@ def __init__(self, p, ops, seed=None): self.name = "UnitaryChannel" self.probs = p self.psum = sum(p) - if self.psum > 1 or self.psum <= 0: - raise_error(ValueError, "UnitaryChannel probability sum should be " - "between 0 and 1 but is {}." - "".format(self.psum)) self.seed = seed self.density_matrix = False self.init_args = [p, self.gates] diff --git a/src/qibo/core/gates.py b/src/qibo/core/gates.py index 05ed72557b..b5b5bb14fc 100644 --- a/src/qibo/core/gates.py +++ b/src/qibo/core/gates.py @@ -927,6 +927,10 @@ def __init__(self, p: List[float], ops: List["Gate"], seed: Optional[int] = None): BackendGate.__init__(self) abstract_gates.UnitaryChannel.__init__(self, p, ops, seed=seed) + if self.psum > 1 + K.precision_tol or self.psum <= 0: + raise_error(ValueError, "UnitaryChannel probability sum should be " + "between 0 and 1 but is {}." + "".format(self.psum)) self.set_seed() def calculate_inverse_gates(self): diff --git a/src/qibo/tests/test_abstract_gates.py b/src/qibo/tests/test_abstract_gates.py index 363aa1a3fe..525ab95c69 100644 --- a/src/qibo/tests/test_abstract_gates.py +++ b/src/qibo/tests/test_abstract_gates.py @@ -247,8 +247,6 @@ def test_unitary_channel_init(): gate = gates.UnitaryChannel(2 * [0.1], ops) with pytest.raises(ValueError): gate = gates.UnitaryChannel(4 * [-0.1], ops) - with pytest.raises(ValueError): - gate = gates.UnitaryChannel(4 * [0.5], ops) def test_pauli_noise_channel_init(): diff --git a/src/qibo/tests/test_core_gates.py b/src/qibo/tests/test_core_gates.py index 6b41c17e45..e2deda159d 100644 --- a/src/qibo/tests/test_core_gates.py +++ b/src/qibo/tests/test_core_gates.py @@ -423,7 +423,7 @@ def test_general_channel(backend): initial_rho = random_density_matrix(2) gate = gates.KrausChannel([((1,), a1), ((0, 1), a2)]) assert gate.target_qubits == (0, 1) - final_rho = gate(np.copy(initial_rho)) + final_rho = gate(K.cast(np.copy(initial_rho))) m1 = np.kron(np.eye(2), K.to_numpy(a1)) m2 = K.to_numpy(a2) target_rho = (m1.dot(initial_rho).dot(m1.conj().T) + @@ -476,6 +476,24 @@ def test_unitary_channel(backend): K.assert_allclose(final_state, target_state) +@pytest.mark.parametrize("precision", ["double", "single"]) +def test_unitary_channel_probability_tolerance(backend, precision): + """Create ``UnitaryChannel`` with probability sum within tolerance (see #562).""" + import qibo + original_precision = qibo.get_precision() + qibo.set_precision(precision) + nqubits = 2 + param = 0.006 + num_terms = 2 ** (2 * nqubits) + max_param = num_terms / (num_terms - 1) + prob_identity = 1 - param / max_param + prob_pauli = param / num_terms + probs = [prob_identity] + [prob_pauli] * (num_terms - 1) + matrices = len(probs) * [((0, 1), np.random.random((4, 4)))] + gate = gates.UnitaryChannel(probs, matrices) + qibo.set_precision(original_precision) + + def test_unitary_channel_errors(): """Check errors raised by ``gates.UnitaryChannel``.""" a1 = np.array([[0, 1], [1, 0]]) @@ -488,7 +506,7 @@ def test_unitary_channel_errors(): # Probability > 1 with pytest.raises(ValueError): gate = gates.UnitaryChannel([1.1, 0.2], matrices) - # Probability sum = 0 + # Probability sum < 0 with pytest.raises(ValueError): gate = gates.UnitaryChannel([0.0, 0.0], matrices) From a50872a82d8e036184bb9c76efe06eb543bd4a4f Mon Sep 17 00:00:00 2001 From: Stavros <35475381+stavros11@users.noreply.github.com> Date: Sun, 27 Mar 2022 12:11:44 +0400 Subject: [PATCH 3/4] Update tests --- src/qibo/tests/test_backends_init.py | 3 +++ src/qibo/tests/test_core_gates.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/qibo/tests/test_backends_init.py b/src/qibo/tests/test_backends_init.py index 7a00673f4b..3e06710e82 100644 --- a/src/qibo/tests/test_backends_init.py +++ b/src/qibo/tests/test_backends_init.py @@ -52,10 +52,13 @@ def test_set_precision(backend, precision): backends.set_precision(precision) if precision == "single": expected_dtype = K.backend.complex64 + expected_tol = 1e-12 else: expected_dtype = K.backend.complex128 + expected_tol = 1e-8 assert backends.get_precision() == precision assert K.dtypes('DTYPECPX') == expected_dtype + assert K.precision_tol == expected_tol # Test that circuits use proper precision circuit = models.Circuit(2) circuit.add([gates.H(0), gates.H(1)]) diff --git a/src/qibo/tests/test_core_gates.py b/src/qibo/tests/test_core_gates.py index e2deda159d..14174f9cc8 100644 --- a/src/qibo/tests/test_core_gates.py +++ b/src/qibo/tests/test_core_gates.py @@ -489,6 +489,10 @@ def test_unitary_channel_probability_tolerance(backend, precision): prob_identity = 1 - param / max_param prob_pauli = param / num_terms probs = [prob_identity] + [prob_pauli] * (num_terms - 1) + if precision == "double": + probs = np.array(probs, dtype="float64") + else: + probs = np.array(probs, dtype="float32") matrices = len(probs) * [((0, 1), np.random.random((4, 4)))] gate = gates.UnitaryChannel(probs, matrices) qibo.set_precision(original_precision) From d939a67aee2b0c66c7f8355804db7d4a7bd30552 Mon Sep 17 00:00:00 2001 From: Stavros <35475381+stavros11@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:42:23 +0400 Subject: [PATCH 4/4] Swap single and double tolerance (Andrea's comment) --- src/qibo/backends/abstract.py | 8 ++++---- src/qibo/tests/test_backends_init.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index 3e837338d6..5c51177c9c 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -58,15 +58,15 @@ def dtypes(self, name): def set_precision(self, dtype): if dtype == 'single': - from qibo.config import PRECISION_TOL_DOUBLE + from qibo.config import PRECISION_TOL_SINGLE self._dtypes['DTYPE'] = 'float32' self._dtypes['DTYPECPX'] = 'complex64' - self.precision_tol = PRECISION_TOL_DOUBLE + self.precision_tol = PRECISION_TOL_SINGLE elif dtype == 'double': - from qibo.config import PRECISION_TOL_SINGLE + from qibo.config import PRECISION_TOL_DOUBLE self._dtypes['DTYPE'] = 'float64' self._dtypes['DTYPECPX'] = 'complex128' - self.precision_tol = PRECISION_TOL_SINGLE + self.precision_tol = PRECISION_TOL_DOUBLE else: raise_error(ValueError, f'dtype {dtype} not supported.') self.precision = dtype diff --git a/src/qibo/tests/test_backends_init.py b/src/qibo/tests/test_backends_init.py index 3e06710e82..6837fd1995 100644 --- a/src/qibo/tests/test_backends_init.py +++ b/src/qibo/tests/test_backends_init.py @@ -52,10 +52,10 @@ def test_set_precision(backend, precision): backends.set_precision(precision) if precision == "single": expected_dtype = K.backend.complex64 - expected_tol = 1e-12 + expected_tol = 1e-8 else: expected_dtype = K.backend.complex128 - expected_tol = 1e-8 + expected_tol = 1e-12 assert backends.get_precision() == precision assert K.dtypes('DTYPECPX') == expected_dtype assert K.precision_tol == expected_tol