From a281f6945e58fc826b7583d5263a97342319695d Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Dec 2019 11:07:44 +0100 Subject: [PATCH 01/40] add sine test and circuit test --- test/aqua/test_amplitude_estimation.py | 141 +++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 9 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 70c5e0198a..d39e14136e 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -18,8 +18,9 @@ from test.aqua.common import QiskitAquaTestCase import numpy as np from parameterized import parameterized -from qiskit import QuantumRegister, QuantumCircuit, BasicAer, execute +from qiskit import QuantumRegister, QuantumCircuit, BasicAer, execute, transpile from qiskit.aqua import QuantumInstance +from qiskit.aqua.components.iqfts import Standard from qiskit.aqua.components.uncertainty_models import GaussianConditionalIndependenceModel as GCI from qiskit.aqua.components.uncertainty_problems import \ UnivariatePiecewiseLinearObjective as PwlObjective @@ -82,6 +83,27 @@ def build_controlled_power(self, qc, q, q_control, power, qc.cry(2 * power * theta_p, q_control, q[i_state]) +class SineIntegralAFactory(UncertaintyProblem): + def __init__(self, num_qubits): + super().__init__(num_qubits + 1) + self._i_objective = num_qubits + + def build(self, qc, q, q_ancillas=None): + n = self.num_target_qubits - 1 + q_state = [q[i] for i in range(self.num_target_qubits) if i != self._i_objective] + q_objective = q[self._i_objective] + + # prepare 1/sqrt{2^n} sum_x |x>_n + for q_i in q_state: + qc.h(q_i) + + # apply the sine/cosine term + qc.ry(2 * 1 / 2 / 2**n, q_objective) + + for i, q_i in enumerate(q_state): + qc.cry(2 * 2**i / 2**n, q_i, q_objective) + + class TestBernoulli(QiskitAquaTestCase): """ Test Bernoulli """ @@ -112,13 +134,13 @@ def qasm(shots=100): [0.82, IterativeAmplitudeEstimation(0.00001, 0.05), {'estimation': 0.82}], [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {'estimation': 0.49}] ]) - def test_statevector(self, prob, a_e, expect): + def test_statevector(self, prob, ae, expect): """ statevector test """ # construct factories for A and Q - a_e.a_factory = BernoulliAFactory(prob) - a_e.q_factory = BernoulliQFactory(a_e.a_factory) + ae.a_factory = BernoulliAFactory(prob) + ae.q_factory = BernoulliQFactory(ae.a_factory) - result = a_e.run(self._statevector) + result = ae.run(self._statevector) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, @@ -135,13 +157,114 @@ def test_statevector(self, prob, a_e, expect): [0.4, 1000, IterativeAmplitudeEstimation(0.001, 0.05), {'estimation': 0.400071}], [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {'estimation': 0.811711}] ]) - def test_qasm(self, prob, shots, a_e, expect): + def test_qasm(self, prob, shots, ae, expect): + """ qasm test """ + # construct factories for A and Q + ae.a_factory = BernoulliAFactory(prob) + ae.q_factory = BernoulliQFactory(ae.a_factory) + + result = ae.run(self._qasm(shots)) + + for key, value in expect.items(): + self.assertAlmostEqual(value, result[key], places=3, + msg="estimate `{}` failed".format(key)) + + @parameterized.expand([ + [True], [False] + ]) + def test_ae_circuit(self, efficient_circuit): + print(efficient_circuit) + prob = 0.5 + basis_gates = ['u1', 'u2', 'u3', 'cx'] + + for m in range(2, 7): + print('m =', m) + + ae = AmplitudeEstimation(m, a_factory=BernoulliAFactory(prob)) + angle = 2 * np.arcsin(np.sqrt(prob)) + + # manually set up the inefficient AE circuit + q_ancilla = QuantumRegister(m, 'a') + q_objective = QuantumRegister(1, 'q') + circuit = QuantumCircuit(q_ancilla, q_objective) + + # initial hadamards + for i in range(m): + circuit.h(q_ancilla[i]) + + # A operator + circuit.ry(angle, q_objective) + + if efficient_circuit: + ae.q_factory = BernoulliQFactory(ae.a_factory) + for power in range(m): + circuit.cry(2 ** power * angle, q_ancilla[power], q_objective[0]) + + else: + q_factory = QFactory(ae.a_factory, i_objective=0) + for power in range(m): + for _ in range(2**power): + q_factory.build_controlled(circuit, q_objective, q_ancilla[power]) + + # fourier transform + iqft = Standard(m) + circuit = iqft.construct_circuit(qubits=q_ancilla, circuit=circuit, do_swaps=False) + expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + + actual_circuit = ae.construct_circuit(measurement=False) + actual_ops = transpile(actual_circuit, basis_gates=basis_gates).count_ops() + + for key in expected_ops.keys(): + self.assertEqual(expected_ops[key], actual_ops[key]) + + +class TestSineIntegral(QiskitAquaTestCase): + def setUp(self): + super().setUp() + + self._statevector = QuantumInstance(backend=BasicAer.get_backend('statevector_simulator'), + seed_simulator=123, + seed_transpiler=41) + + def qasm(shots=100): + return QuantumInstance(backend=BasicAer.get_backend('qasm_simulator'), shots=shots, + seed_simulator=7192, seed_transpiler=90000) + + self._qasm = qasm + + @parameterized.expand([ + [4, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.272675}], + [4, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.272675}], + [3, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272675}], + [4, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], + [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272675}], + [5, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], + ]) + def test_statevector(self, n, ae, expect): + """ statevector test """ + # construct factories for A and Q + ae.a_factory = SineIntegralAFactory(n) + + result = ae.run(self._statevector) + + for key, value in expect.items(): + self.assertAlmostEqual(value, result[key], places=3, + msg="estimate `{}` failed".format(key)) + + @parameterized.expand([ + [4, 10, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.272675}], + [4, 1000, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.272675}], + [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272675}], + [4, 1000, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], + [3, 10, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272675}], + [5, 1000, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], + ]) + def test_qasm(self, n, shots, ae, expect): """ qasm test """ # construct factories for A and Q - a_e.a_factory = BernoulliAFactory(prob) - a_e.q_factory = BernoulliQFactory(a_e.a_factory) + ae.a_factory = SineIntegralAFactory(n) - result = a_e.run(self._qasm(shots)) + result = ae.run(self._qasm(shots)) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, From cc1734bf17ded84df3abcf07703fb79c73fea4ab Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Dec 2019 11:14:55 +0100 Subject: [PATCH 02/40] add docstring --- test/aqua/test_amplitude_estimation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index d39e14136e..a6cacd6041 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -173,6 +173,7 @@ def test_qasm(self, prob, shots, ae, expect): [True], [False] ]) def test_ae_circuit(self, efficient_circuit): + """ Test circuits resulting from canonical amplitude estimation """ print(efficient_circuit) prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] From 222b851dabf468b601fb662f6335ca8c42530884 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Dec 2019 11:15:36 +0100 Subject: [PATCH 03/40] add docstring --- test/aqua/test_amplitude_estimation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index a6cacd6041..b13e3fe987 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -220,6 +220,8 @@ def test_ae_circuit(self, efficient_circuit): class TestSineIntegral(QiskitAquaTestCase): + """ Integrate the sine squared using amplitude estimation """ + def setUp(self): super().setUp() From cb460ec015d2c1fe8e751758a7a3e2500dd4db26 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Dec 2019 11:42:10 +0100 Subject: [PATCH 04/40] add setter for i_objective, tests for setting of A/Q/i_obj --- .../amplitude_estimation/ae_algorithm.py | 14 ++++++ test/aqua/test_amplitude_estimation.py | 46 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae_algorithm.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae_algorithm.py index 505e7d38ee..ba419490fe 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae_algorithm.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae_algorithm.py @@ -141,3 +141,17 @@ def i_objective(self): return self.a_factory.num_target_qubits - 1 return None + + @i_objective.setter + def i_objective(self, i_objective): + """ + Set the index of the objective qubit, i.e. the qubit deciding between 'good/bad' states. + + Args: + i_objective (int): the index + + Note: + No checks about the validity of the index are performed, since i_objective could also + be set before the A/Q operators and in that case checks cannot be done. + """ + self._i_objective = i_objective diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index b13e3fe987..3d5a2ab61d 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -219,6 +219,52 @@ def test_ae_circuit(self, efficient_circuit): self.assertEqual(expected_ops[key], actual_ops[key]) +class TestInternals(QiskitAquaTestCase): + def setUp(self): + super().setUp() + self.a_factory = BernoulliAFactory(0) + self.q_factory = BernoulliQFactory(self.a_factory) + self.i_objective = 0 + + @parameterized.expand([ + [AmplitudeEstimation(2)], + [IterativeAmplitudeEstimation(0.1, 0.001)], + [MaximumLikelihoodAmplitudeEstimation(3)], + ]) + def test_operators(self, ae): + """ Test if A/Q operator + i_objective set correctly """ + self.assertIsNone(ae.a_factory) + self.assertIsNone(ae.q_factory) + self.assertIsNone(ae.i_objective) + self.assertIsNone(ae._a_factory) + self.assertIsNone(ae._q_factory) + self.assertIsNone(ae._i_objective) + + ae.a_factory = self.a_factory + self.assertIsNotNone(ae.a_factory) + self.assertIsNotNone(ae.q_factory) + self.assertIsNotNone(ae.i_objective) + self.assertIsNotNone(ae._a_factory) + self.assertIsNone(ae._q_factory) + self.assertIsNone(ae._i_objective) + + ae.q_factory = self.q_factory + self.assertIsNotNone(ae.a_factory) + self.assertIsNotNone(ae.q_factory) + self.assertIsNotNone(ae.i_objective) + self.assertIsNotNone(ae._a_factory) + self.assertIsNotNone(ae._q_factory) + self.assertIsNone(ae._i_objective) + + ae.i_objective = self.i_objective + self.assertIsNotNone(ae.a_factory) + self.assertIsNotNone(ae.q_factory) + self.assertIsNotNone(ae.i_objective) + self.assertIsNotNone(ae._a_factory) + self.assertIsNotNone(ae._q_factory) + self.assertIsNotNone(ae._i_objective) + + class TestSineIntegral(QiskitAquaTestCase): """ Integrate the sine squared using amplitude estimation """ From 0f32b75102b3342f5b20e561cfac989f8bf54eb9 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 18 Dec 2019 12:06:02 +0100 Subject: [PATCH 05/40] add test for updating A operator, test for IQAE circuit --- test/aqua/test_amplitude_estimation.py | 86 +++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 3d5a2ab61d..1c64d4bf69 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -174,13 +174,10 @@ def test_qasm(self, prob, shots, ae, expect): ]) def test_ae_circuit(self, efficient_circuit): """ Test circuits resulting from canonical amplitude estimation """ - print(efficient_circuit) prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] for m in range(2, 7): - print('m =', m) - ae = AmplitudeEstimation(m, a_factory=BernoulliAFactory(prob)) angle = 2 * np.arcsin(np.sqrt(prob)) @@ -218,13 +215,55 @@ def test_ae_circuit(self, efficient_circuit): for key in expected_ops.keys(): self.assertEqual(expected_ops[key], actual_ops[key]) + @parameterized.expand([ + [True], [False] + ]) + def test_iqae_circuit(self, efficient_circuit): + prob = 0.5 + basis_gates = ['u1', 'u2', 'u3', 'cx'] + + for k in range(2, 7): + ae = IterativeAmplitudeEstimation(0.01, 0.05, a_factory=BernoulliAFactory(prob)) + angle = 2 * np.arcsin(np.sqrt(prob)) + + # manually set up the inefficient AE circuit + q_objective = QuantumRegister(1, 'q') + circuit = QuantumCircuit(q_objective) + + # A operator + circuit.ry(angle, q_objective) + + if efficient_circuit: + ae.q_factory = BernoulliQFactory(ae.a_factory) + for power in range(k): + circuit.ry(2 ** power * angle, q_objective[0]) + + else: + q_factory = QFactory(ae.a_factory, i_objective=0) + for power in range(k): + for _ in range(2**power): + q_factory.build(circuit, q_objective) + + expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + + actual_circuit = ae.construct_circuit(k, measurement=False) + actual_ops = transpile(actual_circuit, basis_gates=basis_gates).count_ops() + + for key in expected_ops.keys(): + self.assertEqual(expected_ops[key], actual_ops[key]) + class TestInternals(QiskitAquaTestCase): def setUp(self): super().setUp() - self.a_factory = BernoulliAFactory(0) - self.q_factory = BernoulliQFactory(self.a_factory) - self.i_objective = 0 + self.a_bernoulli = BernoulliAFactory(0) + self.q_bernoulli = BernoulliQFactory(self.a_bernoulli) + self.i_bernoulli = 0 + + num_qubits = 5 + self.a_integral = SineIntegralAFactory(num_qubits) + self.q_intergal = QFactory(self.a_integral, num_qubits) + self.i_intergal = num_qubits @parameterized.expand([ [AmplitudeEstimation(2)], @@ -240,7 +279,7 @@ def test_operators(self, ae): self.assertIsNone(ae._q_factory) self.assertIsNone(ae._i_objective) - ae.a_factory = self.a_factory + ae.a_factory = self.a_bernoulli self.assertIsNotNone(ae.a_factory) self.assertIsNotNone(ae.q_factory) self.assertIsNotNone(ae.i_objective) @@ -248,7 +287,7 @@ def test_operators(self, ae): self.assertIsNone(ae._q_factory) self.assertIsNone(ae._i_objective) - ae.q_factory = self.q_factory + ae.q_factory = self.q_bernoulli self.assertIsNotNone(ae.a_factory) self.assertIsNotNone(ae.q_factory) self.assertIsNotNone(ae.i_objective) @@ -256,7 +295,7 @@ def test_operators(self, ae): self.assertIsNotNone(ae._q_factory) self.assertIsNone(ae._i_objective) - ae.i_objective = self.i_objective + ae.i_objective = self.i_bernoulli self.assertIsNotNone(ae.a_factory) self.assertIsNotNone(ae.q_factory) self.assertIsNotNone(ae.i_objective) @@ -264,6 +303,35 @@ def test_operators(self, ae): self.assertIsNotNone(ae._q_factory) self.assertIsNotNone(ae._i_objective) + @parameterized.expand([ + [AmplitudeEstimation(2)], + [IterativeAmplitudeEstimation(0.1, 0.001)], + [MaximumLikelihoodAmplitudeEstimation(3)], + ]) + def test_a_factory_update(self, ae): + """ Test if the Q factory is updated if the a_factory changes -- except set manually """ + # Case 1: Set to BernoulliAFactory with default Q operator + ae.a_factory = self.a_bernoulli + self.assertIsInstance(ae.q_factory.a_factory, BernoulliAFactory) + self.assertEqual(ae.i_objective, self.i_bernoulli) + + # Case 2: Change to SineIntegralAFactory with default Q operator + ae.a_factory = self.a_integral + self.assertIsInstance(ae.q_factory.a_factory, SineIntegralAFactory) + self.assertEqual(ae.i_objective, self.i_intergal) + + # Case 3: Set to BernoulliAFactory with special Q operator + ae.a_factory = self.a_bernoulli + ae.q_factory = self.q_bernoulli + self.assertIsInstance(ae.q_factory, BernoulliQFactory) + self.assertEqual(ae.i_objective, self.i_bernoulli) + + # Case 4: Set to SineIntegralAFactory, and do not set Q. Then the old Q operator + # should remain + ae.a_factory = self.a_integral + self.assertIsInstance(ae.q_factory, BernoulliQFactory) + self.assertEqual(ae.i_objective, self.i_bernoulli) + class TestSineIntegral(QiskitAquaTestCase): """ Integrate the sine squared using amplitude estimation """ From af9e1a7ff75c9ab38346eb199a723cd2e8674e8b Mon Sep 17 00:00:00 2001 From: Cryoris Date: Thu, 19 Dec 2019 16:56:14 +0100 Subject: [PATCH 06/40] fix safe_max/min --- .../single_sample/amplitude_estimation/mlae.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 413af427c3..dfc2162abc 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -222,7 +222,7 @@ def _safe_min(self, array, default=0): Returns: float: default if array is empty, otherwise numpy.max(array) """ - if not array: + if len(array) == 0: return default return np.min(array) @@ -231,7 +231,7 @@ def _safe_max(self, array, default=(np.pi / 2)): Returns: float: default if array is empty, otherwise numpy.max(array) """ - if not array: + if len(array) == 0: return default return np.max(array) @@ -319,7 +319,7 @@ def _fisher_ci(self, alpha=0.05, observed=False): mapped_ci = [self.a_factory.value_to_estimation(bound) for bound in ci] return mapped_ci - def _likelihood_ratio_ci(self, alpha=0.05, nevals=10000): + def _likelihood_ratio_ci(self, alpha=0.05, nevals=None): """ Compute the likelihood-ratio confidence interval. @@ -332,6 +332,9 @@ def _likelihood_ratio_ci(self, alpha=0.05, nevals=10000): float: The alpha-likelihood-ratio confidence interval. """ + if nevals is None: + nevals = self._likelihood_evals + def loglikelihood(theta, one_counts, all_counts): logL = 0 for i, k in enumerate(self._evaluation_schedule): @@ -352,6 +355,7 @@ def loglikelihood(theta, one_counts, all_counts): # the (outer) LR confidence interval above_thres = thetas[values >= thres] + print('above_thres', above_thres) # it might happen that the `above_thres` array is empty, # to still provide a valid result use safe_min/max which @@ -360,7 +364,7 @@ def loglikelihood(theta, one_counts, all_counts): self._safe_max(above_thres, default=(np.pi / 2))] mapped_ci = [self.a_factory.value_to_estimation(np.sin(bound)**2) for bound in ci] - return mapped_ci + return thetas, values, thres, mapped_ci def confidence_interval(self, alpha, kind='fisher'): """ From d4b3182559c2af5e527b20b7cf5acef080b5bf6f Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 10:01:17 +0100 Subject: [PATCH 07/40] only apply Q if the power is not 0 --- .../single_sample/amplitude_estimation/iqae.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index aa6bfe4076..e0b0f11f01 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -31,8 +31,6 @@ class IterativeAmplitudeEstimation(AmplitudeEstimationAlgorithm): """ - The Iterative Amplitude Estimation Algorithm. - This class implements the Iterative Quantum Amplitude Estimation (QAE) algorithm, proposed in https://arxiv.org/abs/1912.05559. The output of the algorithm is an estimate that, with at least probability 1 - alpha, differs by epsilon to the target value, where @@ -81,6 +79,8 @@ class IterativeAmplitudeEstimation(AmplitudeEstimationAlgorithm): def __init__(self, epsilon, alpha, ci_method='beta', min_ratio=2, a_factory=None, q_factory=None, i_objective=None): """ + Initializer. + The output of the algorithm is an estimate for the amplitude `a`, that with at least probability 1 - alpha has an error of epsilon. The number of A operator calls scales linearly in 1/epsilon (up to a logarithmic factor). @@ -188,8 +188,8 @@ def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): return int(k), upper_half_circle def construct_circuit(self, k, measurement=False): - r""" - Construct the circuit Q^k A \|0>, with the A operator specifying the QAE problem and + """ + Construct the circuit Q^k A |0>, with the A operator specifying the QAE problem and the Grover operator Q. Args: @@ -198,7 +198,7 @@ def construct_circuit(self, k, measurement=False): circuits Returns: - QuantumCircuit: the circuit Q^k A \|0> + QuantumCircuit: the circuit Q^k A |0> """ # set up circuit q = QuantumRegister(self.a_factory.num_target_qubits, 'q') @@ -223,7 +223,8 @@ def construct_circuit(self, k, measurement=False): self.a_factory.build(circuit, q, q_aux) # add Q^k - self.q_factory.build_power(circuit, q, k, q_aux) + if k != 0: + self.q_factory.build_power(circuit, q, k, q_aux) # add optional measurement if measurement: From 04eaa5a6b06358bc979acee92fc88270de9f64b2 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 10:05:41 +0100 Subject: [PATCH 08/40] only apply Q if the power is not 0 --- .../algorithms/single_sample/amplitude_estimation/mlae.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index dfc2162abc..6db2cf24fb 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -168,7 +168,9 @@ def construct_circuits(self, measurement=False): self._circuits = [] for k in self._evaluation_schedule: qc_k = qc_a.copy(name='qc_a_q_%s' % k) - self.q_factory.build_power(qc_k, q, k, q_aux) + + if k != 0: + self.q_factory.build_power(qc_k, q, k, q_aux) if measurement: qc_k.measure(q[self.i_objective], c[0]) @@ -355,7 +357,6 @@ def loglikelihood(theta, one_counts, all_counts): # the (outer) LR confidence interval above_thres = thetas[values >= thres] - print('above_thres', above_thres) # it might happen that the `above_thres` array is empty, # to still provide a valid result use safe_min/max which @@ -364,7 +365,7 @@ def loglikelihood(theta, one_counts, all_counts): self._safe_max(above_thres, default=(np.pi / 2))] mapped_ci = [self.a_factory.value_to_estimation(np.sin(bound)**2) for bound in ci] - return thetas, values, thres, mapped_ci + return mapped_ci def confidence_interval(self, alpha, kind='fisher'): """ From 62c6d703c982d0680ba0fb7f6ff8bcdfc1364e6a Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 12:21:41 +0100 Subject: [PATCH 09/40] evaluation schedule must include Q^0 --- .../aqua/algorithms/single_sample/amplitude_estimation/mlae.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 6db2cf24fb..658e701fef 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -83,7 +83,7 @@ def __init__(self, log_max_evals, a_factory=None, i_objective=None, # get parameters self._log_max_evals = log_max_evals - self._evaluation_schedule = [2**j for j in range(log_max_evals)] + self._evaluation_schedule = [0] + [2**j for j in range(log_max_evals)] self._likelihood_evals = likelihood_evals # default number of evaluations is max(10^5, 10^5 * 2^(log_max_evals - 5)) From ba71eb34d22eb524b137e534816339d80cc5179a Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 15:02:05 +0100 Subject: [PATCH 10/40] add test for MLAE circuits --- test/aqua/test_amplitude_estimation.py | 73 +++++++++++++++++++++----- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 1c64d4bf69..1505d55032 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -218,7 +218,7 @@ def test_ae_circuit(self, efficient_circuit): @parameterized.expand([ [True], [False] ]) - def test_iqae_circuit(self, efficient_circuit): + def test_iqae_circuits(self, efficient_circuit): prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] @@ -235,14 +235,14 @@ def test_iqae_circuit(self, efficient_circuit): if efficient_circuit: ae.q_factory = BernoulliQFactory(ae.a_factory) - for power in range(k): - circuit.ry(2 ** power * angle, q_objective[0]) + # for power in range(k): + # circuit.ry(2 ** power * angle, q_objective[0]) + circuit.ry(2 * 2**k * angle, q_objective[0]) else: q_factory = QFactory(ae.a_factory, i_objective=0) - for power in range(k): - for _ in range(2**power): - q_factory.build(circuit, q_objective) + for _ in range(2**k): + q_factory.build(circuit, q_objective) expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() @@ -252,6 +252,53 @@ def test_iqae_circuit(self, efficient_circuit): for key in expected_ops.keys(): self.assertEqual(expected_ops[key], actual_ops[key]) + @parameterized.expand([ + [True], [False] + ]) + def test_mlae_circuits(self, efficient_circuit): + prob = 0.5 + basis_gates = ['u1', 'u2', 'u3', 'cx'] + + for k in range(1, 7): + ae = MaximumLikelihoodAmplitudeEstimation(k, a_factory=BernoulliAFactory(prob)) + angle = 2 * np.arcsin(np.sqrt(prob)) + + # compute all the circuits used for MLAE + circuits = [] + + # 0th power + q_objective = QuantumRegister(1, 'q') + circuit = QuantumCircuit(q_objective) + circuit.ry(angle, q_objective) + circuits += [circuit] + + # powers of 2 + for power in range(k): + q_objective = QuantumRegister(1, 'q') + circuit = QuantumCircuit(q_objective) + + # A operator + circuit.ry(angle, q_objective) + + # Q^(2^j) operator + if efficient_circuit: + ae.q_factory = BernoulliQFactory(ae.a_factory) + circuit.ry(2 * 2 ** power * angle, q_objective[0]) + + else: + q_factory = QFactory(ae.a_factory, i_objective=0) + for _ in range(2**power): + q_factory.build(circuit, q_objective) + + actual_circuits = ae.construct_circuits(measurement=False) + + for actual, expected in zip(actual_circuits, circuits): + actual_ops = transpile(actual, basis_gates=basis_gates).count_ops() + expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + + for key in expected_ops.keys(): + self.assertEqual(expected_ops[key], actual_ops[key]) + class TestInternals(QiskitAquaTestCase): def setUp(self): @@ -352,9 +399,9 @@ def qasm(shots=100): @parameterized.expand([ [4, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.272675}], [4, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.272675}], - [3, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272675}], + [3, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272074}], [4, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], - [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272675}], + [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272082}], [5, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], ]) def test_statevector(self, n, ae, expect): @@ -369,11 +416,11 @@ def test_statevector(self, n, ae, expect): msg="estimate `{}` failed".format(key)) @parameterized.expand([ - [4, 10, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.272675}], - [4, 1000, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.272675}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272675}], - [4, 1000, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], - [3, 10, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272675}], + [4, 10, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.333333}], + [4, 1000, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.274335}], + [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.256878}], + [4, 1000, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.271866}], + [3, 10, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.255756}], [5, 1000, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], ]) def test_qasm(self, n, shots, ae, expect): From 08e7974256f3bd7977873af580c2b35ea7935230 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:06:06 +0100 Subject: [PATCH 11/40] CI is based on 'mle' not 'estimation' --- .../aqua/algorithms/single_sample/amplitude_estimation/ae.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 36605259d5..9c1c48cd32 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -285,7 +285,7 @@ def confidence_interval(self, alpha, kind='likelihood_ratio'): # if statevector simulator the estimate is exact if self._quantum_instance.is_statevector: - return 2 * [self._ret['estimation']] + return 2 * [self._ret['mle']] if kind in ['likelihood_ratio', 'lr']: return self._likelihood_ratio_ci(alpha) @@ -331,7 +331,7 @@ def loglikelihood(a): right_of_qae = np.sin(np.pi * (y + 1) / M)**2 bubbles = [qae, right_of_qae] - elif y == int(M / 2): + elif y == int(M / 2): # remember, M = 2^m is a power of 2 left_of_qae = np.sin(np.pi * (y - 1) / M)**2 bubbles = [left_of_qae, qae] From e36a20c836cafe1c38914afcba79e2761e79de2c Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:32:08 +0100 Subject: [PATCH 12/40] correct definition of bubbles / add comments --- .../single_sample/amplitude_estimation/ae.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 9c1c48cd32..a92d13b6ec 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -217,11 +217,21 @@ def _likelihood_ratio_ci(self, alpha): # the likelihood ratio: the two bubbles next to the QAE estimate M = 2**self._m qae = self._ret['value'] + + bubbles = None y = M * np.arcsin(np.sqrt(qae)) / np.pi - left_of_qae = np.sin(np.pi * (y - 1) / M)**2 - right_of_qae = np.sin(np.pi * (y + 1) / M)**2 + if y == 0: + right_of_qae = np.sin(np.pi * (y + 1) / M)**2 + bubbles = [qae, right_of_qae] - bubbles = [left_of_qae, qae, right_of_qae] + elif y == int(M / 2): # remember, M = 2^m is a power of 2 + left_of_qae = np.sin(np.pi * (y - 1) / M)**2 + bubbles = [left_of_qae, qae] + + else: + left_of_qae = np.sin(np.pi * (y - 1) / M)**2 + right_of_qae = np.sin(np.pi * (y + 1) / M)**2 + bubbles = [left_of_qae, qae, right_of_qae] # likelihood function ai = np.asarray(self._ret['values']) @@ -241,6 +251,9 @@ def cut(x): return loglikelihood(x) - thres # Store the boundaries of the confidence interval + # It's valid to start off with the zero-width confidence interval, since the maximum + # of the likelihood function is guaranteed to be over the threshold, and if alpha = 0 + # that's the valid interval lower = upper = self._ret['ml_value'] # Check the two intervals/bubbles: check if they surpass the From 8ad95d085ff5b2f25a48c49af303e01346dbf106 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:34:45 +0100 Subject: [PATCH 13/40] remove unnecessary definition, y should be int --- .../aqua/algorithms/single_sample/amplitude_estimation/ae.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index a92d13b6ec..70fe317a97 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -187,6 +187,7 @@ def _compute_fisher_information(self, observed=False): fisher_information = None mlv = self._ret['ml_value'] # MLE in [0,1] m = self._m + if observed: ai = np.asarray(self._ret['values']) pi = np.asarray(self._ret['probabilities']) @@ -218,8 +219,7 @@ def _likelihood_ratio_ci(self, alpha): M = 2**self._m qae = self._ret['value'] - bubbles = None - y = M * np.arcsin(np.sqrt(qae)) / np.pi + y = int(np.round(M * np.arcsin(np.sqrt(qae)) / np.pi)) if y == 0: right_of_qae = np.sin(np.pi * (y + 1) / M)**2 bubbles = [qae, right_of_qae] @@ -339,7 +339,6 @@ def loglikelihood(a): # Compute the two intervals in which are candidates for containing # the maximum of the log-likelihood function: the two bubbles next to # the QAE estimate - bubbles = None if y == 0: right_of_qae = np.sin(np.pi * (y + 1) / M)**2 bubbles = [qae, right_of_qae] From 8256230739009d4ffd74ddd07826c8023c490ea7 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:39:11 +0100 Subject: [PATCH 14/40] consistent order: A op, Q op, i_obj --- .../algorithms/single_sample/amplitude_estimation/ae.py | 7 ++++--- .../algorithms/single_sample/amplitude_estimation/mlae.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 70fe317a97..2e4d621ff8 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -72,17 +72,18 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): ], } - def __init__(self, num_eval_qubits, a_factory=None, - i_objective=None, q_factory=None, iqft=None): + def __init__(self, num_eval_qubits, a_factory=None, q_factory=None, i_objective=None, + iqft=None): """ + Initializer. Args: num_eval_qubits (int): number of evaluation qubits a_factory (CircuitFactory): the CircuitFactory subclass object representing the problem unitary - i_objective (int): i objective q_factory (CircuitFactory): the CircuitFactory subclass object representing an amplitude estimation sample (based on a_factory) + i_objective (int): i objective iqft (IQFT): the Inverse Quantum Fourier Transform pluggable component, defaults to using a standard iqft when None """ diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 658e701fef..d4864dae74 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -63,8 +63,8 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): ], } - def __init__(self, log_max_evals, a_factory=None, i_objective=None, - q_factory=None, likelihood_evals=None): + def __init__(self, log_max_evals, a_factory=None, q_objective=None, i_objective=None, + likelihood_evals=None): """ Args: @@ -72,9 +72,9 @@ def __init__(self, log_max_evals, a_factory=None, i_objective=None, resulting evaluation schedule will be [Q^2^0, ..., Q^2^{max_evals_log-1}] a_factory (CircuitFactory): the CircuitFactory subclass object representing the problem unitary - i_objective (int): index of qubit representing the objective in the uncertainty problem q_factory (CircuitFactory): the CircuitFactory subclass object representing an amplitude estimation sample (based on a_factory) + i_objective (int): index of qubit representing the objective in the uncertainty problem likelihood_evals (int): The number of gridpoints for the maximum search of the likelihood function """ From 49da38f314e820427bbbe6c1ff0f4e07324315d0 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:39:49 +0100 Subject: [PATCH 15/40] fix typo --- .../aqua/algorithms/single_sample/amplitude_estimation/mlae.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index d4864dae74..8837fa316c 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -63,7 +63,7 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): ], } - def __init__(self, log_max_evals, a_factory=None, q_objective=None, i_objective=None, + def __init__(self, log_max_evals, a_factory=None, q_factory=None, i_objective=None, likelihood_evals=None): """ From 7578226fe699b111a59f93a23b58eb7f3f8218b4 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:46:53 +0100 Subject: [PATCH 16/40] fix bug: MLE not in CI --- .../algorithms/single_sample/amplitude_estimation/mlae.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 8837fa316c..52466a636f 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -346,7 +346,8 @@ def loglikelihood(theta, one_counts, all_counts): one_counts, all_counts = self._get_hits() - thetas = np.linspace(np.pi / nevals / 2, np.pi / 2, nevals) + eps = 1e-15 # to avoid invalid value in log + thetas = np.linspace(0 + eps, np.pi / 2 - eps, nevals) values = np.zeros(len(thetas)) for i, t in enumerate(thetas): values[i] = loglikelihood(t, one_counts, all_counts) @@ -399,7 +400,7 @@ def _compute_mle_safe(self): one_hits, all_hits = self._get_hits() # search range - eps = 1e-15 # to avoid division by 0 + eps = 1e-15 # to avoid invalid value in log search_range = [0 + eps, np.pi / 2 - eps] def loglikelihood(theta): From 8f4ecf0facf2b3b77b88c272985766c8acbd5f7f Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 20 Dec 2019 18:50:27 +0100 Subject: [PATCH 17/40] update default num. of evaluations --- .../algorithms/single_sample/amplitude_estimation/mlae.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 52466a636f..e8baa29840 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -86,10 +86,10 @@ def __init__(self, log_max_evals, a_factory=None, q_factory=None, i_objective=No self._evaluation_schedule = [0] + [2**j for j in range(log_max_evals)] self._likelihood_evals = likelihood_evals - # default number of evaluations is max(10^5, 10^5 * 2^(log_max_evals - 5)) + # default number of evaluations is max(10^5, pi/2 * 10^3 * 2^(log_max_evals)) if likelihood_evals is None: default = 10000 - self._likelihood_evals = default * np.maximum(1, pow(2, log_max_evals - 5)) + self._likelihood_evals = np.maximum(default, int(np.pi / 2 * 1000 * 2 ** log_max_evals)) self._circuits = [] self._ret = {} @@ -333,7 +333,6 @@ def _likelihood_ratio_ci(self, alpha=0.05, nevals=None): Returns: float: The alpha-likelihood-ratio confidence interval. """ - if nevals is None: nevals = self._likelihood_evals From ec0accf211b870b78c54403ef25265f5ef5bd948 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 12:01:47 +0100 Subject: [PATCH 18/40] update comments --- .../amplitude_estimation/iqae.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index e0b0f11f01..89b03509b2 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -138,15 +138,16 @@ def precision(self, epsilon): def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): """ - Find the largest integer k, such that the scaled interval (4k + 2)*theta_interval + Find the largest integer k_next, such that the interval (4 * k_next + 2)*theta_interval lies completely in [0, pi] or [pi, 2pi], for theta_interval = (theta_lower, theta_upper). Args: k (int): current power of the Q operator - upper_half_circle (bool): boolean flag of whether theta lies in upper half-circle or not + upper_half_circle (bool): boolean flag of whether theta_interval lies in the + upper half-circle [0, pi] or in the lower one [pi, 2pi] theta_interval (tuple(float, float)): current confidence interval for the angle theta, i.e. (theta_lower, theta_upper) - min_ratio (float): minimal ratio K/K_i allowed in the algorithm + min_ratio (float): minimal ratio K/K_next allowed in the algorithm Returns: tuple(int, bool): next power k, and boolean flag for the extrapolated interval @@ -155,30 +156,29 @@ def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): AquaError: if min_ratio is smaller or equal to 1 """ if min_ratio <= 1: - raise AquaError('min_ratio must be larger than 1: ' - 'the next k should not be smaller than the previous one') + raise AquaError('min_ratio must be larger than 1 to ensure convergence') # initialize variables theta_l, theta_u = theta_interval - old_scaling = 4 * k + 2 # current K_i factor + old_scaling = 4 * k + 2 # current scaling factor, called K := (4k + 2) - # the largest feasible K cannot be larger than K_max, which is bounded by the length of - # the current confidence interval + # the largest feasible scaling factor K cannot be larger than K_max, + # which is bounded by the length of the current confidence interval max_scaling = int(1 / (2 * (theta_u - theta_l))) - scaling = max_scaling - (max_scaling - 2) % 4 + scaling = max_scaling - (max_scaling - 2) % 4 # bring into the form 4 * k_max + 2 - # find next feasible K = 4k+2 + # find the largest feasible scaling factor K_next, and thus k_next while scaling >= min_ratio * old_scaling: theta_min = scaling * theta_l - int(scaling * theta_l) theta_max = scaling * theta_u - int(scaling * theta_u) if theta_min <= theta_max <= 0.5 and theta_min <= 0.5: - # if extrapolated theta is in upper half-circle + # the extrapolated theta interval is in the upper half-circle upper_half_circle = True return int((scaling - 2) / 4), upper_half_circle elif theta_max >= 0.5 and theta_max >= theta_min >= 0.5: - # if extrapolated theta is in lower half-circle + # the extrapolated theta interval is in the upper half-circle upper_half_circle = False return int((scaling - 2) / 4), upper_half_circle From d79f6061454c639e569f08decd003f6140151f21 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 12:49:48 +0100 Subject: [PATCH 19/40] add docstrings, rename ci -> confint throughout --- .../single_sample/amplitude_estimation/ae.py | 77 +++++++++++++++++-- .../amplitude_estimation/iqae.py | 12 +-- .../amplitude_estimation/mlae.py | 76 ++++++++++++------ 3 files changed, 129 insertions(+), 36 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 2e4d621ff8..4332af62c1 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -35,8 +35,17 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): - """ - The Amplitude Estimation algorithm. + r""" + This class implements the Quantum Amplitude Estimation (QAE) algorithm, introduced by + https://arxiv.org/abs/quant-ph/0005055. This (original) version uses quantum phase + estimation along with a set of m ancilla qubits to find an estimate, that is restricted + to the grid + + \{sin^2(\pi y / 2^m) : y = 0, ..., 2^{m-1}\}. + + Using a maximum likelihood postprocessing, this grid constraint can be circumvented. + This improved estimator is implemented as well, see https://arxiv.org/abs/1912.05559 (App. A) + for more detail. """ CONFIGURATION = { @@ -138,6 +147,12 @@ def init_params(cls, params, algo_input): @property def _num_qubits(self): + """ + Return the number of qubits needed in the circuit. + + Returns: + int: the total number of qubits + """ if self.a_factory is None: # if A factory is not set, no qubits are specified return 0 @@ -167,6 +182,19 @@ def construct_circuit(self, measurement=False): return self._circuit def _evaluate_statevector_results(self, probabilities): + """ + Given the probabilities from statevector simulation of the QAE circuit, compute the + probabilities that the measurements y/gridpoints a are the best estimate. + + Args: + probabilities (list[float] | numpy.array): the probabilities obtained from the + statevector simulation, i.e. real(statevector * statevector.conj())[0] + + Returns: + tuple[OrderedDict, OrderedDict]: dictionaries containind the a gridpoints with + respective probabilities and y measurements with respective probabilities, + in this order + """ # map measured results to estimates y_probabilities = OrderedDict() for i, probability in enumerate(probabilities): @@ -178,6 +206,7 @@ def _evaluate_statevector_results(self, probabilities): for y, probability in y_probabilities.items(): if y >= int(self._M / 2): y = self._M - y + # due to the finite accuracy of the sine, we round the result to 7 decimals a = np.round(np.power(np.sin(y * np.pi / 2 ** self._m), 2), decimals=7) a_probabilities[a] = a_probabilities.get(a, 0) + probability @@ -185,6 +214,16 @@ def _evaluate_statevector_results(self, probabilities): return a_probabilities, y_probabilities def _compute_fisher_information(self, observed=False): + """ + Computes the Fisher information for the output of the previous run. + + Args: + observed (bool): if true, the observed Fisher information is returned, otherwise + the expected Fisher information + + Returns: + float: the Fisher information + """ fisher_information = None mlv = self._ret['ml_value'] # MLE in [0,1] m = self._m @@ -205,16 +244,40 @@ def integrand(x): return fisher_information - def _fisher_ci(self, alpha, observed=False): + def _fisher_confint(self, alpha, observed=False): + """ + Compute the confidence interval for the MLE of the previous run based on the Fisher + information. + + Args: + alpha (float): specify the (1 - alpha) confidence level (0 < alpha < 1) + observed (bool): if true, the observed Fisher information is used to construct the + confidence interval, otherwise the expected Fisher information + + Returns: + list[float]: the confidence interval + """ shots = self._ret['shots'] mle = self._ret['ml_value'] + # approximate the standard deviation of the MLE and construct the confidence interval std = np.sqrt(shots * self._compute_fisher_information(observed)) ci = mle + norm.ppf(1 - alpha / 2) / std * np.array([-1, 1]) + # transform the confidence interval from [0, 1] to the target interval return [self.a_factory.value_to_estimation(bound) for bound in ci] - def _likelihood_ratio_ci(self, alpha): + def _likelihood_ratio_confint(self, alpha): + """ + Compute the confidence interval for the MLE of the previous run based on the likelihood + ratio. + + Args: + alpha (float): specify the (1 - alpha) confidence level (0 < alpha < 1) + + Returns: + list[float]: the confidence interval + """ # Compute the two intervals in which we the look for values above # the likelihood ratio: the two bubbles next to the QAE estimate M = 2**self._m @@ -302,13 +365,13 @@ def confidence_interval(self, alpha, kind='likelihood_ratio'): return 2 * [self._ret['mle']] if kind in ['likelihood_ratio', 'lr']: - return self._likelihood_ratio_ci(alpha) + return self._likelihood_ratio_confint(alpha) if kind in ['fisher', 'fi']: - return self._fisher_ci(alpha, observed=False) + return self._fisher_confint(alpha, observed=False) if kind in ['observed_fisher', 'observed_information', 'oi']: - return self._fisher_ci(alpha, observed=True) + return self._fisher_confint(alpha, observed=True) raise NotImplementedError('CI `{}` is not implemented.'.format(kind)) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 89b03509b2..5916245cd3 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -76,7 +76,7 @@ class IterativeAmplitudeEstimation(AmplitudeEstimationAlgorithm): ], } - def __init__(self, epsilon, alpha, ci_method='beta', min_ratio=2, a_factory=None, + def __init__(self, epsilon, alpha, confint_method='beta', min_ratio=2, a_factory=None, q_factory=None, i_objective=None): """ Initializer. @@ -88,8 +88,8 @@ def __init__(self, epsilon, alpha, ci_method='beta', min_ratio=2, a_factory=None Args: epsilon (float): target precision for estimation target `a` alpha (float): confidence level, the target probability is 1 - alpha - ci_method (str): statistical method used to estimate the confidence intervals in each - iteration, can be 'chernoff' for the Chernoff intervals or 'beta' for the + confint_method (str): statistical method used to estimate the confidence intervals in + each iteration, can be 'chernoff' for the Chernoff intervals or 'beta' for the Clopper-Pearson intervals (default) min_ratio (float): minimal q-ratio (K_{i+1} / K_i) for FindNextK a_factory (CircuitFactory): the A operator, specifying the QAE problem @@ -108,8 +108,8 @@ def __init__(self, epsilon, alpha, ci_method='beta', min_ratio=2, a_factory=None self._alpha = alpha self._min_ratio = min_ratio - if ci_method in ['chernoff', 'beta']: - self._ci_method = ci_method + if confint_method in ['chernoff', 'beta']: + self._confint_method = confint_method else: raise AquaError('invalid method to compute confidence intervals') @@ -376,7 +376,7 @@ def _run(self): round_one_counts += num_one_shots[-j] # compute a_min_i, a_max_i - if self._ci_method == 'chernoff': + if self._confint_method == 'chernoff': a_i_min, a_i_max = self._chernoff_confint(prob, round_shots, max_rounds, self._alpha) else: # 'beta' diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index e8baa29840..046708a524 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -The Amplitude Estimation Algorithm. +The Maximum Likelihood Amplitude Estimation algorithm. """ import logging @@ -33,7 +33,12 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): """ - The Amplitude Estimation without QPE algorithm. + This class implements the an quantum amplitude estimation (QAE) algorithm without phase + estimation, according to https://arxiv.org/abs/1904.10246. In comparison to the original + QAE algorithm (https://arxiv.org/abs/quant-ph/0005055), this implementation relies solely + on different powers of the Grover algorithm and does not require ancilla qubits. + Finally, the estimate is determined via a maximum likelihood estimation, which is why this + class in named MaximumLikelihoodAmplitudeEstimation. """ CONFIGURATION = { @@ -66,10 +71,11 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): def __init__(self, log_max_evals, a_factory=None, q_factory=None, i_objective=None, likelihood_evals=None): """ + Initializer. Args: log_max_evals (int): base-2-logarithm of maximal number of evaluations - - resulting evaluation schedule will be [Q^2^0, ..., Q^2^{max_evals_log-1}] + resulting evaluation schedule will be [A, Q^2^0, ..., Q^2^{max_evals_log-1}] a_factory (CircuitFactory): the CircuitFactory subclass object representing the problem unitary q_factory (CircuitFactory): the CircuitFactory subclass object representing @@ -97,7 +103,8 @@ def __init__(self, log_max_evals, a_factory=None, q_factory=None, i_objective=No @classmethod def init_params(cls, params, algo_input): """ - Initialize via parameters dictionary and algorithm input instance + Initialize via parameters dictionary and algorithm input instance. + Args: params (dict): parameters dictionary algo_input (AlgorithmInput): Input instance @@ -124,10 +131,16 @@ def init_params(cls, params, algo_input): @property def _num_qubits(self): + """ + Return the number of qubits needed in the circuit. + + Returns: + int: the total number of qubits + """ if self.a_factory is None: # if A factory is not set, no qubits are specified return 0 - num_ancillas = self.q_factory.required_ancillas_controlled() + num_ancillas = self.q_factory.required_ancillas() num_qubits = self.a_factory.num_target_qubits + num_ancillas return num_qubits @@ -179,9 +192,18 @@ def construct_circuits(self, measurement=False): return self._circuits - def _evaluate_statevectors(self, state_vectors): + def _evaluate_statevectors(self, statevectors): + """ + For each statevector, compute the probability that |1> is measured in the objective qubit. + + Args: + statevectors (Union(list[list[complex]], list[numpy.array]): a list of statvectors + + Returns: + list[float]: the corresponding probabilities + """ probabilities = [] - for sv in state_vectors: + for sv in statevectors: p_k = 0 for i, a in enumerate(sv): p = np.abs(a)**2 @@ -200,7 +222,7 @@ def _get_hits(self): tuple(list, list): a pair of two lists, ([1-counts per experiment], [shots per experiment]) Raises: - AquaError: Call run() first + AquaError: if self.run() has not been called yet """ one_hits = [] # h_k: how often 1 has been measured, for a power Q^(m_k) all_hits = [] # N_k: how often has been measured at a power Q^(m_k) @@ -277,6 +299,7 @@ def _compute_fisher_information(self, a=None, num_sum_terms=None, observed=False # Compute the Fisher information fisher_information = None if observed: + # Note, that the observed Fisher information is very unreliable in this algorithm! d_logL = 0 for Nk, hk, mk in zip(all_hits, one_hits, evaluation_schedule): tan = np.tan((2 * mk + 1) * theta_a) @@ -292,7 +315,7 @@ def _compute_fisher_information(self, a=None, num_sum_terms=None, observed=False return fisher_information - def _fisher_ci(self, alpha=0.05, observed=False): + def _fisher_confint(self, alpha=0.05, observed=False): """ Compute the alpha confidence interval based on the Fisher information @@ -316,12 +339,12 @@ def _fisher_ci(self, alpha=0.05, observed=False): fisher_information = self._compute_fisher_information(observed=True) normal_quantile = norm.ppf(1 - alpha / 2) - ci = np.real(self._ret['value']) + \ + confint = np.real(self._ret['value']) + \ normal_quantile / np.sqrt(fisher_information) * np.array([-1, 1]) - mapped_ci = [self.a_factory.value_to_estimation(bound) for bound in ci] - return mapped_ci + mapped_confint = [self.a_factory.value_to_estimation(bound) for bound in confint] + return mapped_confint - def _likelihood_ratio_ci(self, alpha=0.05, nevals=None): + def _likelihood_ratio_confint(self, alpha=0.05, nevals=None): """ Compute the likelihood-ratio confidence interval. @@ -361,11 +384,11 @@ def loglikelihood(theta, one_counts, all_counts): # it might happen that the `above_thres` array is empty, # to still provide a valid result use safe_min/max which # then yield [0, pi/2] - ci = [self._safe_min(above_thres, default=0), - self._safe_max(above_thres, default=(np.pi / 2))] - mapped_ci = [self.a_factory.value_to_estimation(np.sin(bound)**2) for bound in ci] + confint = [self._safe_min(above_thres, default=0), + self._safe_max(above_thres, default=(np.pi / 2))] + mapped_confint = [self.a_factory.value_to_estimation(np.sin(bound)**2) for bound in confint] - return mapped_ci + return mapped_confint def confidence_interval(self, alpha, kind='fisher'): """ @@ -381,13 +404,13 @@ def confidence_interval(self, alpha, kind='fisher'): return 2 * [self._ret['estimation']] if kind in ['likelihood_ratio', 'lr']: - return self._likelihood_ratio_ci(alpha) + return self._likelihood_ratio_confint(alpha) if kind in ['fisher', 'fi']: - return self._fisher_ci(alpha, observed=False) + return self._fisher_confint(alpha, observed=False) if kind in ['observed_fisher', 'observed_information', 'oi']: - return self._fisher_ci(alpha, observed=True) + return self._fisher_confint(alpha, observed=True) raise NotImplementedError('CI `{}` is not implemented.'.format(kind)) @@ -414,6 +437,13 @@ def loglikelihood(theta): return est_theta def _run_mle(self): + """ + Compute the maximum likelihood estimator (MLE) for the angle theta, based on which the + final result of this algorithm is computed. + + Returns: + float: the MLE for the angle theta, related to the amplitude a via a = sin^2(theta) + """ # TODO implement a **reliable**, fast method to find the maximum of the likelihood function return self._compute_mle_safe() @@ -429,8 +459,8 @@ def _run(self): ret = self._quantum_instance.execute(self._circuits) # get statevectors and construct MLE input - state_vectors = [np.asarray(ret.get_statevector(circuit)) for circuit in self._circuits] - self._ret['statevectors'] = state_vectors + statevectors = [np.asarray(ret.get_statevector(circuit)) for circuit in self._circuits] + self._ret['statevectors'] = statevectors else: # run circuit on QASM simulator @@ -446,7 +476,7 @@ def _run(self): self._ret['estimation'] = self.a_factory.value_to_estimation(self._ret['value']) self._ret['fisher_information'] = self._compute_fisher_information() - confidence_interval = self._fisher_ci(alpha=0.05) + confidence_interval = self._fisher_confint(alpha=0.05) self._ret['95%_confidence_interval'] = confidence_interval return self._ret From 8530532fc3fd8efa6896f0afd57677fc781fa3d3 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 13:01:43 +0100 Subject: [PATCH 20/40] add "ae" to good-names, well-known, precise variable name for Amplitude Estimation --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 6040fbfed6..392d0c1102 100644 --- a/.pylintrc +++ b/.pylintrc @@ -118,7 +118,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # pi = the PI constant # op = operation iterator # b = basis iterator -good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ar,br, +good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ae,ar,br, __unittest # Bad variable names which should always be refused, separated by a comma From 6a987f4f66165c9507f13213e21db286b55b5271 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 14:19:44 +0100 Subject: [PATCH 21/40] remove too complex cases, add docstrings --- test/aqua/test_amplitude_estimation.py | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 1505d55032..6b7e6a9949 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -84,11 +84,19 @@ def build_controlled_power(self, qc, q, q_control, power, class SineIntegralAFactory(UncertaintyProblem): + r""" + Construct the A operator to approximate the integral + + \int_0^1 \sin^2(x) dx + + with a specified number of qubits. + """ + def __init__(self, num_qubits): super().__init__(num_qubits + 1) self._i_objective = num_qubits - def build(self, qc, q, q_ancillas=None): + def build(self, qc, q, q_ancillas=None, params=None): n = self.num_target_qubits - 1 q_state = [q[i] for i in range(self.num_target_qubits) if i != self._i_objective] q_objective = q[self._i_objective] @@ -219,6 +227,7 @@ def test_ae_circuit(self, efficient_circuit): [True], [False] ]) def test_iqae_circuits(self, efficient_circuit): + """ Test the circuits constructed for IQAE """ prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] @@ -256,6 +265,7 @@ def test_iqae_circuits(self, efficient_circuit): [True], [False] ]) def test_mlae_circuits(self, efficient_circuit): + """ Test the circuits constructed for MLAE """ prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] @@ -294,13 +304,15 @@ def test_mlae_circuits(self, efficient_circuit): for actual, expected in zip(actual_circuits, circuits): actual_ops = transpile(actual, basis_gates=basis_gates).count_ops() - expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + expected_ops = transpile(expected, basis_gates=basis_gates).count_ops() for key in expected_ops.keys(): self.assertEqual(expected_ops[key], actual_ops[key]) -class TestInternals(QiskitAquaTestCase): +class TestProblemSetting(QiskitAquaTestCase): + """ Test the setting and getting of the A and Q operator and the objective qubit index """ + def setUp(self): super().setUp() self.a_bernoulli = BernoulliAFactory(0) @@ -397,12 +409,9 @@ def qasm(shots=100): self._qasm = qasm @parameterized.expand([ - [4, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.272675}], - [4, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.272675}], - [3, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.272074}], + [2, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.270290}], [4, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272082}], - [5, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], ]) def test_statevector(self, n, ae, expect): """ statevector test """ @@ -417,11 +426,8 @@ def test_statevector(self, n, ae, expect): @parameterized.expand([ [4, 10, AmplitudeEstimation(2), {'estimation': 0.5, 'mle': 0.333333}], - [4, 1000, AmplitudeEstimation(4), {'estimation': 0.30866, 'mle': 0.274335}], [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.256878}], - [4, 1000, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.271866}], - [3, 10, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.255756}], - [5, 1000, IterativeAmplitudeEstimation(0.00001, 0.01), {'estimation': 0.272675}], + [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {'estimation': 0.271790}], ]) def test_qasm(self, n, shots, ae, expect): """ qasm test """ @@ -434,6 +440,9 @@ def test_qasm(self, n, shots, ae, expect): self.assertAlmostEqual(value, result[key], places=3, msg="estimate `{}` failed".format(key)) + # def test_confidence_intervals(self, n, ae, expect): + # ae.a_factory = SineIntegralAFactory(n) + class TestCreditRiskAnalysis(QiskitAquaTestCase): """ Test Credit Risk Analysis """ From eef5a2a6b68fac77bbaeffbc5460c5db8d996036 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 14:20:11 +0100 Subject: [PATCH 22/40] fix expected value after MLAE fix --- test/finance/test_amplitude_estimation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/finance/test_amplitude_estimation.py b/test/finance/test_amplitude_estimation.py index cdd47acd2a..c41d5d4e13 100644 --- a/test/finance/test_amplitude_estimation.py +++ b/test/finance/test_amplitude_estimation.py @@ -25,13 +25,14 @@ from qiskit.finance.components.uncertainty_problems import \ (EuropeanCallDelta, FixedIncomeExpectedValue) from qiskit.aqua.components.uncertainty_problems import \ - UnivariatePiecewiseLinearObjective as PwlObjective + UnivariatePiecewiseLinearObjective as PwlObjective from qiskit.aqua.components.uncertainty_problems import UnivariateProblem from qiskit.aqua.algorithms import AmplitudeEstimation, MaximumLikelihoodAmplitudeEstimation class TestEuropeanCallOption(QiskitFinanceTestCase): """ Test European Call Option """ + def setUp(self): super().setUp() @@ -110,7 +111,7 @@ def setUp(self): ['statevector', MaximumLikelihoodAmplitudeEstimation(5), {'estimation': 0.16330976193204114}], ['qasm', MaximumLikelihoodAmplitudeEstimation(3), - {'estimation': 0.1027255930905642}], + {'estimation': 0.09808188840384949}], ]) def test_expected_value(self, simulator, a_e, expect): """ expected value test """ @@ -151,6 +152,7 @@ def test_delta(self, simulator, a_e, expect): class TestFixedIncomeAssets(QiskitFinanceTestCase): """ Test Fixed Income Assets """ + def setUp(self): super().setUp() @@ -170,7 +172,7 @@ def setUp(self): ['statevector', MaximumLikelihoodAmplitudeEstimation(5), {'estimation': 2.340361798381051}], ['qasm', MaximumLikelihoodAmplitudeEstimation(5), - {'estimation': 2.317921060790118}] + {'estimation': 2.3175136132411103}] ]) def test_expected_value(self, simulator, a_e, expect): """ expected value test """ From a3277e6e17c4e1d72b58f65f7b0cbe9fba8b191e Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 14:59:59 +0100 Subject: [PATCH 23/40] add confidence interval tests --- test/aqua/test_amplitude_estimation.py | 71 +++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 6b7e6a9949..fdaf7e8386 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -113,7 +113,11 @@ def build(self, qc, q, q_ancillas=None, params=None): class TestBernoulli(QiskitAquaTestCase): - """ Test Bernoulli """ + """ + Tests based on the Bernoulli A operator. This class tests + * the estimation result + * the constructed circuits + """ def setUp(self): super().setUp() @@ -393,7 +397,11 @@ def test_a_factory_update(self, ae): class TestSineIntegral(QiskitAquaTestCase): - """ Integrate the sine squared using amplitude estimation """ + """ + Tests based on the A operator to integrate sin^2(x). This class tests + * the estimation result + * the confidence intervals + """ def setUp(self): super().setUp() @@ -414,7 +422,7 @@ def qasm(shots=100): [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272082}], ]) def test_statevector(self, n, ae, expect): - """ statevector test """ + """ Statevector end-to-end test """ # construct factories for A and Q ae.a_factory = SineIntegralAFactory(n) @@ -430,7 +438,7 @@ def test_statevector(self, n, ae, expect): [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {'estimation': 0.271790}], ]) def test_qasm(self, n, shots, ae, expect): - """ qasm test """ + """ QASM end-to-end test """ # construct factories for A and Q ae.a_factory = SineIntegralAFactory(n) @@ -440,8 +448,59 @@ def test_qasm(self, n, shots, ae, expect): self.assertAlmostEqual(value, result[key], places=3, msg="estimate `{}` failed".format(key)) - # def test_confidence_intervals(self, n, ae, expect): - # ae.a_factory = SineIntegralAFactory(n) + @parameterized.expand([ + [AmplitudeEstimation(3), 'mle', + {'likelihood_ratio': [0.24947346406470136, 0.3003771197734433], + 'fisher': [0.24861769995820207, 0.2999286066724035], + 'observed_fisher': [0.24845622030041542, 0.30009008633019013]}], + [MaximumLikelihoodAmplitudeEstimation(3), 'estimation', + {'likelihood_ratio': [0.25987941798909114, 0.27985361366769945], + 'fisher': [0.2584889015125656, 0.2797018754936686], + 'observed_fisher': [0.2659279996107888, 0.2722627773954454]}], + ]) + def test_confidence_intervals(self, ae, key, expect): + """ End-to-end test for all confidence intervals """ + n = 3 + ae.a_factory = SineIntegralAFactory(n) + + # statevector + result = ae.run(self._statevector) + methods = ['lr', 'fi', 'oi'] # short for likelihood_ratio, fisher, observed_fisher + alphas = [0.1, 0.00001, 0.9] # alpha shouldn't matter in statevector + for alpha, method in zip(alphas, methods): + confint = ae.confidence_interval(alpha, method) + # confidence interval based on statevector should be empty, as we are sure of the result + self.assertAlmostEqual(confint[1] - confint[0], 0.0) + self.assertAlmostEqual(confint[0], result[key]) + + # qasm + shots = 100 + alpha = 0.01 + result = ae.run(self._qasm(shots)) + for method, expected_confint in expect.items(): + confint = ae.confidence_interval(alpha, method) + self.assertEqual(confint, expected_confint) + self.assertTrue(confint[0] <= result[key] <= confint[1]) + + def test_iqae_confidence_intervals(self): + """ End-to-end test for the IQAE confidence interval """ + n = 3 + ae = IterativeAmplitudeEstimation(0.1, 0.01, a_factory=SineIntegralAFactory(n)) + expected_confint = [0.19840508760087738, 0.35110155403424115] + + # statevector + result = ae.run(self._statevector) + confint = result['confidence_interval'] + # confidence interval based on statevector should be empty, as we are sure of the result + self.assertAlmostEqual(confint[1] - confint[0], 0.0) + self.assertAlmostEqual(confint[0], result['estimation']) + + # qasm + shots = 100 + result = ae.run(self._qasm(shots)) + confint = result['confidence_interval'] + self.assertEqual(confint, expected_confint) + self.assertTrue(confint[0] <= result['estimation'] <= confint[1]) class TestCreditRiskAnalysis(QiskitAquaTestCase): From 8ababcb156398d80e9072195517e9adafd80713c Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 15:08:47 +0100 Subject: [PATCH 24/40] fix spell/lint/style --- test/aqua/test_amplitude_estimation.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index fdaf7e8386..05aaa719b2 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -87,7 +87,7 @@ class SineIntegralAFactory(UncertaintyProblem): r""" Construct the A operator to approximate the integral - \int_0^1 \sin^2(x) dx + \int_0^1 \sin^2(x) d x with a specified number of qubits. """ @@ -198,7 +198,7 @@ def test_ae_circuit(self, efficient_circuit): q_objective = QuantumRegister(1, 'q') circuit = QuantumCircuit(q_ancilla, q_objective) - # initial hadamards + # initial Hadamard gates for i in range(m): circuit.h(q_ancilla[i]) @@ -450,13 +450,14 @@ def test_qasm(self, n, shots, ae, expect): @parameterized.expand([ [AmplitudeEstimation(3), 'mle', - {'likelihood_ratio': [0.24947346406470136, 0.3003771197734433], - 'fisher': [0.24861769995820207, 0.2999286066724035], - 'observed_fisher': [0.24845622030041542, 0.30009008633019013]}], + {'likelihood_ratio': [0.24947346406470136, 0.3003771197734433], + 'fisher': [0.24861769995820207, 0.2999286066724035], + 'observed_fisher': [0.24845622030041542, 0.30009008633019013]} + ], [MaximumLikelihoodAmplitudeEstimation(3), 'estimation', - {'likelihood_ratio': [0.25987941798909114, 0.27985361366769945], - 'fisher': [0.2584889015125656, 0.2797018754936686], - 'observed_fisher': [0.2659279996107888, 0.2722627773954454]}], + {'likelihood_ratio': [0.25987941798909114, 0.27985361366769945], + 'fisher': [0.2584889015125656, 0.2797018754936686], + 'observed_fisher': [0.2659279996107888, 0.2722627773954454]}], ]) def test_confidence_intervals(self, ae, key, expect): """ End-to-end test for all confidence intervals """ From bdfb98d67509d0578e72662ff4ba29ed07f69c10 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 15:08:58 +0100 Subject: [PATCH 25/40] fix spell/lint/style --- .../algorithms/single_sample/amplitude_estimation/ae.py | 9 ++++----- .../single_sample/amplitude_estimation/mlae.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 4332af62c1..9a72f430d6 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -43,8 +43,8 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): \{sin^2(\pi y / 2^m) : y = 0, ..., 2^{m-1}\}. - Using a maximum likelihood postprocessing, this grid constraint can be circumvented. - This improved estimator is implemented as well, see https://arxiv.org/abs/1912.05559 (App. A) + Using a maximum likelihood post processing, this grid constraint can be circumvented. + This improved estimator is implemented as well, see https://arxiv.org/abs/1912.05559 Appendix A for more detail. """ @@ -191,9 +191,8 @@ def _evaluate_statevector_results(self, probabilities): statevector simulation, i.e. real(statevector * statevector.conj())[0] Returns: - tuple[OrderedDict, OrderedDict]: dictionaries containind the a gridpoints with - respective probabilities and y measurements with respective probabilities, - in this order + tuple[dict, dict]: dictionaries containing the a gridpoints with respective + probabilities and y measurements with respective probabilities, in this order """ # map measured results to estimates y_probabilities = OrderedDict() diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 046708a524..e7f0fdba1f 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -197,7 +197,7 @@ def _evaluate_statevectors(self, statevectors): For each statevector, compute the probability that |1> is measured in the objective qubit. Args: - statevectors (Union(list[list[complex]], list[numpy.array]): a list of statvectors + statevectors (Union(list[list[complex]], list[numpy.array]): a list of statevectors Returns: list[float]: the corresponding probabilities From 067f18f29de7ad2384665cec3dae662162f9c206 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 22 Dec 2019 15:43:28 +0100 Subject: [PATCH 26/40] fix test --- test/finance/test_amplitude_estimation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/finance/test_amplitude_estimation.py b/test/finance/test_amplitude_estimation.py index c41d5d4e13..7c9bd6aa50 100644 --- a/test/finance/test_amplitude_estimation.py +++ b/test/finance/test_amplitude_estimation.py @@ -111,7 +111,7 @@ def setUp(self): ['statevector', MaximumLikelihoodAmplitudeEstimation(5), {'estimation': 0.16330976193204114}], ['qasm', MaximumLikelihoodAmplitudeEstimation(3), - {'estimation': 0.09808188840384949}], + {'estimation': 0.09784548904622023}], ]) def test_expected_value(self, simulator, a_e, expect): """ expected value test """ @@ -170,9 +170,9 @@ def setUp(self): ['qasm', AmplitudeEstimation(5), {'estimation': 2.4600, 'mle': 2.3632087675061726}], ['statevector', MaximumLikelihoodAmplitudeEstimation(5), - {'estimation': 2.340361798381051}], + {'estimation': 2.340228883624973}], ['qasm', MaximumLikelihoodAmplitudeEstimation(5), - {'estimation': 2.3175136132411103}] + {'estimation': 2.3174630932734077}] ]) def test_expected_value(self, simulator, a_e, expect): """ expected value test """ From 36e17ac18cd805b2915c908cd4f1b76f76c7abd1 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Jan 2020 18:36:46 +0100 Subject: [PATCH 27/40] rename log_max_evals to m this is consistent with the naming in amplitude estimation also remove self._m since never used --- .../single_sample/amplitude_estimation/mlae.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index a72237d172..b330d1f9fc 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -42,7 +42,7 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): class in named MaximumLikelihoodAmplitudeEstimation. """ - def __init__(self, log_max_evals: int, + def __init__(self, m: int, a_factory: Optional[CircuitFactory] = None, q_factory: Optional[CircuitFactory] = None, i_objective: Optional[int] = None, @@ -51,8 +51,8 @@ def __init__(self, log_max_evals: int, Initializer. Args: - log_max_evals: base-2-logarithm of the maximal number of evaluations. The resulting - evaluation schedule will be [0, Q^2^0, ..., Q^2^{max_evals_log-1}]. + m: base-2-logarithm of the maximal number of evaluations. The resulting + evaluation schedule will be [0, Q^2^0, ..., Q^2^{m-1}]. Has a minimum value of 1. a_factory: the CircuitFactory subclass object representing the problem unitary q_factory: the CircuitFactory subclass object representing @@ -62,18 +62,17 @@ def __init__(self, log_max_evals: int, likelihood_evals: the number of gridpoints for the maximum search of the likelihood function """ - validate_min('log_max_evals', log_max_evals, 1) + validate_min('m', m, 1) super().__init__(a_factory, q_factory, i_objective) # get parameters - self._log_max_evals = log_max_evals - self._evaluation_schedule = [0] + [2**j for j in range(log_max_evals)] + self._evaluation_schedule = [0] + [2**j for j in range(m)] self._likelihood_evals = likelihood_evals - # default number of evaluations is max(10^5, pi/2 * 10^3 * 2^(log_max_evals)) + # default number of evaluations is max(10^5, pi/2 * 10^3 * 2^(m)) if likelihood_evals is None: default = 10000 - self._likelihood_evals = np.maximum(default, int(np.pi / 2 * 1000 * 2 ** log_max_evals)) + self._likelihood_evals = np.maximum(default, int(np.pi / 2 * 1000 * 2 ** m)) self._circuits = [] self._ret = {} From 03900bc140ce4fc75fbb23f7a9cfb3af69eec5f6 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Jan 2020 18:57:12 +0100 Subject: [PATCH 28/40] add num_oracle_queries for all AE variants --- .../single_sample/amplitude_estimation/ae.py | 6 +++++- .../amplitude_estimation/iqae.py | 2 +- .../amplitude_estimation/mlae.py | 21 +++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 18996db47b..3512127102 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -401,7 +401,8 @@ def _run(self): # construct probabilities y_probabilities = {} a_probabilities = {} - shots = sum(ret.get_counts().values()) + shots = self._quantum_instance._run_config.shots + for state, counts in ret.get_counts().items(): y = int(state.replace(' ', '')[:self._m][::-1], 2) p = counts / shots @@ -443,6 +444,9 @@ def _run(self): self._ret['estimation'] = est self._ret['value'] = val + # count the number of Q-oracle calls + self._ret['num_oracle_queries'] = self._M - 1 + # get MLE self._run_mle() diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 27f5ad330c..41adb9a755 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -302,7 +302,7 @@ def _run(self): theta_i_interval = [np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval] theta_intervals.append(theta_i_interval) - num_oracle_queries = 1 + num_oracle_queries = 0 # no Q-oracle call, only a single one to A else: num_iterations = 0 # keep track of the number of iterations diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index b330d1f9fc..530a4096e8 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -104,10 +104,12 @@ def construct_circuits(self, measurement=False): Returns: list: a list with the QuantumCircuit objects for the algorithm """ + # keep track of the Q-oracle queries + self._ret['num_oracle_queries'] = 0 + # construct first part of circuit q = QuantumRegister(self.a_factory.num_target_qubits, 'q') - - qc_a = QuantumCircuit(q, name='qc_a') + qc_0 = QuantumCircuit(q, name='qc_a') # 0 applications of Q, only a single A operator # get number of ancillas num_ancillas = np.maximum(self.a_factory.required_ancillas(), @@ -117,18 +119,18 @@ def construct_circuits(self, measurement=False): # pylint: disable=comparison-with-callable if num_ancillas > 0: q_aux = QuantumRegister(num_ancillas, 'aux') - qc_a.add_register(q_aux) + qc_0.add_register(q_aux) # add classical register if needed if measurement: c = ClassicalRegister(1) - qc_a.add_register(c) + qc_0.add_register(c) - self.a_factory.build(qc_a, q, q_aux) + self.a_factory.build(qc_0, q, q_aux) self._circuits = [] for k in self._evaluation_schedule: - qc_k = qc_a.copy(name='qc_a_q_%s' % k) + qc_k = qc_0.copy(name='qc_a_q_%s' % k) if k != 0: self.q_factory.build_power(qc_k, q, k, q_aux) @@ -410,6 +412,9 @@ def _run(self): statevectors = [np.asarray(ret.get_statevector(circuit)) for circuit in self._circuits] self._ret['statevectors'] = statevectors + # to count the number of Q-oracle calls (don't count shots) + shots = 1 + else: # run circuit on QASM simulator self.construct_circuits(measurement=True) @@ -418,11 +423,15 @@ def _run(self): # get counts and construct MLE input self._ret['counts'] = [ret.get_counts(circuit) for circuit in self._circuits] + # to count the number of Q-oracle calls + shots = self._quantum_instance._run_config.shots + # run maximum likelihood estimation and construct results self._ret['theta'] = self._run_mle() self._ret['value'] = np.sin(self._ret['theta'])**2 self._ret['estimation'] = self.a_factory.value_to_estimation(self._ret['value']) self._ret['fisher_information'] = self._compute_fisher_information() + self._ret['num_oracle_queries'] = shots * sum(k for k in self._evaluation_schedule) confidence_interval = self._fisher_confint(alpha=0.05) self._ret['95%_confidence_interval'] = confidence_interval From 66b77ea3305dc771bf7750f7633fc52ba86648bf Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Jan 2020 19:01:44 +0100 Subject: [PATCH 29/40] remove QAES -- not part of this PR --- .../amplitude_estimation/qaes.py | 245 ------------------ 1 file changed, 245 deletions(-) delete mode 100644 qiskit/aqua/algorithms/single_sample/amplitude_estimation/qaes.py diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/qaes.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/qaes.py deleted file mode 100644 index db9e83ee8a..0000000000 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/qaes.py +++ /dev/null @@ -1,245 +0,0 @@ - -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -The Iterative Quantum Amplitude Estimation Algorithm. -""" - - -import logging -import numpy as np - -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.aqua import AquaError - -from .ae_algorithm import AmplitudeEstimationAlgorithm - -logger = logging.getLogger(__name__) - - -class SimplifiedAmplitudeEstimation(AmplitudeEstimationAlgorithm): - r""" - Aar: (1 - eps) sqrt(a) < sqrt(\hat a) < (1 + eps) sqrt(a) - => (1 - 2eps + eps^2) a < \hat a < (1 + 2eps + eps^2) a - => (1 - 2eps - eps^2) a < \hat a < (1 + 2eps + eps^2) a - - Want: (1 - e) a < \hat a < (1 + e) a - - Thus, e = 2eps + eps^2 = eps(2 + eps), or eps = ... - """ - - def __init__(self, epsilon, delta, a_factory=None, q_factory=None, i_objective=None): - """ - todo - - Args: - epsilon (float): target precision for estimation target `a` - delta (float): confidence level, the target probability is 1 - alpha - a_factory (CircuitFactory): the A operator, specifying the QAE problem - q_factory (CircuitFactory): the Q operator (Grover operator), constructed from the - A operator - i_objective (int): index of the objective qubit, that marks the 'good/bad' states - """ - # self.validate(locals()) - super().__init__(a_factory, q_factory, i_objective) - - # store parameters - self._transformed_epsilon = epsilon # transform to aaronsons eps - self._delta = delta - - # results dictionary - self._ret = {} - - @property - def precision(self): - """ - Returns the target precision `epsilon` of the algorithm - - Returns: - float: target precision - """ - return self._epsilon - - @precision.setter - def precision(self, epsilon): - """ - Set the target precision of the algorithm. - - Args: - epsilon (float): target precision for estimation target a - """ - self._epsilon = epsilon - self._transformed_epsilon = epsilon # TODO transform correctly - - def _find_next_r(self, k, upper_half_circle, theta_interval, min_ratio=2): - """ - todo - """ - pass - - def construct_circuit(self, k, measurement=False): - r""" - Construct the circuit Q^k A \|0>, with the A operator specifying the QAE problem and - the Grover operator Q. - - Args: - k (int): the power of Q operator - measurement (bool): boolean flag to indicate if measurements should be included in the - circuits - - Returns: - QuantumCircuit: the circuit Q^k A \|0> - """ - # set up circuit - q = QuantumRegister(self.a_factory.num_target_qubits, 'q') - circuit = QuantumCircuit(q, name='circuit') - - # get number of ancillas and add register if needed - num_ancillas = np.maximum(self.a_factory.required_ancillas(), - self.q_factory.required_ancillas()) - - q_aux = None - # pylint: disable=comparison-with-callable - if num_ancillas > 0: - q_aux = QuantumRegister(num_ancillas, 'aux') - circuit.add_register(q_aux) - - # add classical register if needed - if measurement: - c = ClassicalRegister(1) - circuit.add_register(c) - - # add A operator - self.a_factory.build(circuit, q, q_aux) - - # add Q^k - self.q_factory.build_power(circuit, q, k, q_aux) - - # add optional measurement - if measurement: - circuit.measure(q[self.i_objective], c[0]) - - return circuit - - def _run(self): - # check if A factory has been set - if self.a_factory is None: - raise AquaError("a_factory must be set!") - - # for statevector we can directly return the probability to measure 1 - # note, that no iterations here are necessary - if self._quantum_instance.is_statevector: - # simulate circuit - circuit = self.construct_circuit(k=0, measurement=False) - ret = self._quantum_instance.execute(circuit) - num_oracle_queries = 1 - - # get statevector - statevector = ret.get_statevector(circuit) - - # calculate the probability of measuring '1' - num_qubits = self.a_factory.num_target_qubits - - # sum over all amplitudes where the objective qubit is 1 - prob = 0 - for i, amplitude in enumerate(statevector): - if ('{:0%db}' % num_qubits).format(i)[-(1 + self.i_objective)] == '1': - prob = prob + np.abs(amplitude)**2 - - # get the estimate and confidence intervals - value = prob - theta_min, theta_max = 2 * [np.arcsin(np.sqrt(np.sqrt(value) / 1000))] - confint = 2 * [value] - - else: - num_oracle_queries = 0 - - # step 1 - num_samples = int(np.ceil(1e5 * np.log(120 / self._delta))) - self._ret['num_samples_step1'] = num_samples - self._ret['r_step1'] = [] - - t = 0 - good_counts = 0 - while good_counts < num_samples / 3: - # find the nearest odd integer - r = np.ceil((12 / 11)**t) - if r % 2 == 0: - r -= 1 - self._ret['r_step1'] += [r] - - # execute the circuit - circuit = self.construct_circuit((r - 1) / 2, measurement=True) - ret = self._quantum_instance.execute(circuit, shots=num_samples) - num_oracle_queries += num_samples * np.maximum(r, 1) - - # get the counts - counts = ret.get_counts(circuit) - good_counts = counts['1'] - - # increase t - t += 1 - - # step 2 - theta_min = 5 / 8 * (11 / 12)**(t + 1) - theta_max = 5 / 8 * (11 / 12)**(t - 1) - - self._ret['num_samples_step2'] = [] - self._ret['r_step2'] = [] - - t = 0 - while theta_max > (1 + self._transformed_epsilon / 5) * theta_min: - num_samples = int( - np.ceil(1000 * np.log(100 / self._delta / self._transformed_epsilon * 0.9**t))) - self._ret['num_samples_step2'] += [num_samples] - - # find next r - r = self._find_next_r(theta_min, theta_max) # constant time - self._ret['r_step2'] += [r] - - # execute the circuit - circuit = self.construct_circuit((r - 1) / 2, measurement=True) - ret = self._quantum_instance.execute(circuit, shots=num_samples) - num_oracle_queries += num_samples * np.maximum(r, 1) - - # get the counts - counts = ret.get_counts(circuit) - good_counts = counts['1'] - - # update bounds - theta_ratio = theta_max / theta_min - 1 # called gamma in Aaronson's paper - if good_counts >= num_samples / 2: - theta_min = theta_max / (1 + 0.9 * theta_ratio) - else: - theta_max = (1 + 0.9 * theta_ratio) * theta_min - - # increase t - t += 1 - - # get the estimate and confidence intervals - value = (1000 * np.sin(theta_max))**2 - confint = [(1 - self._epsilon) * value, (1 + self._epsilon) * value] - - estimation = self.a_factory.value_to_estimation(value) - mapped_confint = [self.a_factory.value_to_estimation(bound) for bound in confint] - - # update results dictionary - self._ret['value'] = value - self._ret['estimation'] = estimation - self._ret['num_oracle_queries'] = num_oracle_queries - self._ret['theta_interval'] = [theta_min, theta_max] - self._ret['alpha'] = self._delta - self._ret['confidence_interval'] = mapped_confint - - return self._ret From b53f231197fadb0868d784579d9a6464149ee214 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Jan 2020 19:20:35 +0100 Subject: [PATCH 30/40] fix lints, docstrings begin first line --- .../amplitude_estimation/mlae.py | 2 +- test/aqua/test_amplitude_estimation.py | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 530a4096e8..e3c7238624 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -59,7 +59,7 @@ def __init__(self, m: int, an amplitude estimation sample (based on a_factory) i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions with the state |1> and 'bad' solutions with the state |0> - likelihood_evals: the number of gridpoints for the maximum search of the likelihood + likelihood_evals: the number of gridpoints for the maximum search of the likelihood function """ validate_min('m', m, 1) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 87fe4ce600..4ef0a49ad6 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test Amplitude Estimation """ +"""Test the quantum amplitude estimation algorithm.""" import unittest from test.aqua import QiskitAquaTestCase @@ -33,9 +33,9 @@ class BernoulliAFactory(UncertaintyProblem): - """ - Circuit Factory representing the operator A. - A is used to initialize the state as well as to construct Q. + r"""Circuit Factory representing the operator A in a Bernoulli problem. + + Given a probability $p$, the operator A prepares the state $\sqrt{1 - p}|0> + \sqrt{p}|1>$. """ def __init__(self, probability=0.5): @@ -54,12 +54,11 @@ def value_to_estimation(self, value): class BernoulliQFactory(QFactory): - """ - Circuit Factory representing the operator Q. - This implementation exploits the fact that powers of Q - can be implemented efficiently by just multiplying the angle. - (amplitude estimation only requires controlled powers of Q, - thus, only this method is overridden.) + """Circuit Factory representing the operator Q in a Bernoulli problem. + + This implementation exploits the fact that powers of Q can be implemented efficiently by just + multiplying the angle. Note, that since amplitude estimation only requires controlled powers of + Q only that method is overridden. """ def __init__(self, bernoulli_expected_value): @@ -84,8 +83,7 @@ def build_controlled_power(self, qc, q, q_control, power, class SineIntegralAFactory(UncertaintyProblem): - r""" - Construct the A operator to approximate the integral + r"""Construct the A operator to approximate the integral \int_0^1 \sin^2(x) d x @@ -113,8 +111,9 @@ def build(self, qc, q, q_ancillas=None, params=None): class TestBernoulli(QiskitAquaTestCase): - """ - Tests based on the Bernoulli A operator. This class tests + """Tests based on the Bernoulli A operator. + + This class tests * the estimation result * the constructed circuits """ @@ -315,7 +314,7 @@ def test_mlae_circuits(self, efficient_circuit): class TestProblemSetting(QiskitAquaTestCase): - """ Test the setting and getting of the A and Q operator and the objective qubit index """ + """Test the setting and getting of the A and Q operator and the objective qubit index.""" def setUp(self): super().setUp() @@ -372,7 +371,7 @@ def test_operators(self, ae): [MaximumLikelihoodAmplitudeEstimation(3)], ]) def test_a_factory_update(self, ae): - """ Test if the Q factory is updated if the a_factory changes -- except set manually """ + """Test if the Q factory is updated if the a_factory changes -- except set manually.""" # Case 1: Set to BernoulliAFactory with default Q operator ae.a_factory = self.a_bernoulli self.assertIsInstance(ae.q_factory.a_factory, BernoulliAFactory) @@ -397,8 +396,9 @@ def test_a_factory_update(self, ae): class TestSineIntegral(QiskitAquaTestCase): - """ - Tests based on the A operator to integrate sin^2(x). This class tests + """Tests based on the A operator to integrate sin^2(x). + + This class tests * the estimation result * the confidence intervals """ @@ -438,7 +438,7 @@ def test_statevector(self, n, ae, expect): [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {'estimation': 0.271790}], ]) def test_qasm(self, n, shots, ae, expect): - """ QASM end-to-end test """ + """QASM simulator end-to-end test.""" # construct factories for A and Q ae.a_factory = SineIntegralAFactory(n) @@ -460,11 +460,11 @@ def test_qasm(self, n, shots, ae, expect): 'observed_fisher': [0.2659279996107888, 0.2722627773954454]}], ]) def test_confidence_intervals(self, ae, key, expect): - """ End-to-end test for all confidence intervals """ + """End-to-end test for all confidence intervals.""" n = 3 ae.a_factory = SineIntegralAFactory(n) - # statevector + # statevector simulator result = ae.run(self._statevector) methods = ['lr', 'fi', 'oi'] # short for likelihood_ratio, fisher, observed_fisher alphas = [0.1, 0.00001, 0.9] # alpha shouldn't matter in statevector @@ -474,7 +474,7 @@ def test_confidence_intervals(self, ae, key, expect): self.assertAlmostEqual(confint[1] - confint[0], 0.0) self.assertAlmostEqual(confint[0], result[key]) - # qasm + # qasm simulator shots = 100 alpha = 0.01 result = ae.run(self._qasm(shots)) @@ -484,19 +484,19 @@ def test_confidence_intervals(self, ae, key, expect): self.assertTrue(confint[0] <= result[key] <= confint[1]) def test_iqae_confidence_intervals(self): - """ End-to-end test for the IQAE confidence interval """ + """End-to-end test for the IQAE confidence interval.""" n = 3 ae = IterativeAmplitudeEstimation(0.1, 0.01, a_factory=SineIntegralAFactory(n)) expected_confint = [0.19840508760087738, 0.35110155403424115] - # statevector + # statevector simulator result = ae.run(self._statevector) confint = result['confidence_interval'] # confidence interval based on statevector should be empty, as we are sure of the result self.assertAlmostEqual(confint[1] - confint[0], 0.0) self.assertAlmostEqual(confint[0], result['estimation']) - # qasm + # qasm simulator shots = 100 result = ae.run(self._qasm(shots)) confint = result['confidence_interval'] @@ -505,7 +505,8 @@ def test_iqae_confidence_intervals(self): class TestCreditRiskAnalysis(QiskitAquaTestCase): - """ Test Credit Risk Analysis """ + """Test a more difficult example, motived from Credit Risk Analysis.""" + @parameterized.expand([ 'statevector_simulator' ]) @@ -549,7 +550,6 @@ def test_conditional_value_at_risk(self, simulator): agg.num_sum_qubits, 0, 2 ** agg.num_sum_qubits - 1, # max value that can be reached by the qubit register - # (will not always be reached) breakpoints, slopes, offsets, From 871d908291460431d72bd60d44c8615d8138f0f1 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 24 Jan 2020 14:08:30 -0500 Subject: [PATCH 31/40] fix lint --- test/aqua/test_amplitude_estimation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 4ef0a49ad6..030812618f 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -56,8 +56,8 @@ def value_to_estimation(self, value): class BernoulliQFactory(QFactory): """Circuit Factory representing the operator Q in a Bernoulli problem. - This implementation exploits the fact that powers of Q can be implemented efficiently by just - multiplying the angle. Note, that since amplitude estimation only requires controlled powers of + This implementation exploits the fact that powers of Q can be implemented efficiently by just + multiplying the angle. Note, that since amplitude estimation only requires controlled powers of Q only that method is overridden. """ @@ -111,7 +111,7 @@ def build(self, qc, q, q_ancillas=None, params=None): class TestBernoulli(QiskitAquaTestCase): - """Tests based on the Bernoulli A operator. + """Tests based on the Bernoulli A operator. This class tests * the estimation result @@ -396,7 +396,7 @@ def test_a_factory_update(self, ae): class TestSineIntegral(QiskitAquaTestCase): - """Tests based on the A operator to integrate sin^2(x). + """Tests based on the A operator to integrate sin^2(x). This class tests * the estimation result From fc5ec8f75a932bc3531b52c4c0552a6ccc43946f Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 24 Jan 2020 14:52:53 -0500 Subject: [PATCH 32/40] fix docstring --- .../aqua/algorithms/single_sample/amplitude_estimation/ae.py | 2 +- .../algorithms/single_sample/amplitude_estimation/iqae.py | 4 ++-- .../algorithms/single_sample/amplitude_estimation/mlae.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 3512127102..b5efe0788e 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -61,7 +61,7 @@ def __init__(self, num_eval_qubits: int, num_eval_qubits: number of evaluation qubits, has a min. value of 1. a_factory: the CircuitFactory subclass object representing the problem unitary i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions - with the state |1> and 'bad' solutions with the state |0> + with the state \|1> and 'bad' solutions with the state \|0> q_factory: the CircuitFactory subclass object representing an amplitude estimation sample (based on a_factory) iqft: the Inverse Quantum Fourier Transform component, defaults to using a standard IQFT diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 41adb9a755..2dc1a4894b 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -160,7 +160,7 @@ def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): def construct_circuit(self, k, measurement=False): """ - Construct the circuit Q^k A |0>, with the A operator specifying the QAE problem and + Construct the circuit Q^k A \|0>, with the A operator specifying the QAE problem and the Grover operator Q. Args: @@ -169,7 +169,7 @@ def construct_circuit(self, k, measurement=False): circuits Returns: - QuantumCircuit: the circuit Q^k A |0> + QuantumCircuit: the circuit Q^k A \|0> """ # set up circuit q = QuantumRegister(self.a_factory.num_target_qubits, 'q') diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index e3c7238624..e4888fe616 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -58,7 +58,7 @@ def __init__(self, m: int, q_factory: the CircuitFactory subclass object representing an amplitude estimation sample (based on a_factory) i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions - with the state |1> and 'bad' solutions with the state |0> + with the state \|1> and 'bad' solutions with the state \|0> likelihood_evals: the number of gridpoints for the maximum search of the likelihood function """ From 3ff59586d6dc4d5b4a74d54d3c484a6381f0468e Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 24 Jan 2020 15:01:56 -0500 Subject: [PATCH 33/40] fix docstring --- qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py | 2 +- .../aqua/algorithms/single_sample/amplitude_estimation/iqae.py | 2 +- .../aqua/algorithms/single_sample/amplitude_estimation/mlae.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index b5efe0788e..82b7b4c752 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -54,7 +54,7 @@ def __init__(self, num_eval_qubits: int, i_objective: Optional[int] = None, q_factory: Optional[CircuitFactory] = None, iqft: Optional[IQFT] = None) -> None: - """ + r""" Initializer. Args: diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 2dc1a4894b..9d2aa6b8a1 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -159,7 +159,7 @@ def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): return int(k), upper_half_circle def construct_circuit(self, k, measurement=False): - """ + r""" Construct the circuit Q^k A \|0>, with the A operator specifying the QAE problem and the Grover operator Q. diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index e4888fe616..b109bb6fee 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -47,7 +47,7 @@ def __init__(self, m: int, q_factory: Optional[CircuitFactory] = None, i_objective: Optional[int] = None, likelihood_evals: Optional[int] = None) -> None: - """ + r""" Initializer. Args: From 497fc9df4b319e8be4306fc0f6dc1b6095b3f944 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Fri, 24 Jan 2020 21:18:32 +0100 Subject: [PATCH 34/40] add type hints, fix args order in ae.py --- .../single_sample/amplitude_estimation/ae.py | 114 ++++++------- .../amplitude_estimation/iqae.py | 111 +++++++------ .../amplitude_estimation/mlae.py | 153 ++++++++++-------- 3 files changed, 199 insertions(+), 179 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 3512127102..9134252515 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -11,17 +11,16 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -The Amplitude Estimation Algorithm. -""" +"""The Amplitude Estimation Algorithm.""" -from typing import Optional +from typing import Optional, Union, List, Tuple import logging from collections import OrderedDict import numpy as np from scipy.stats import chi2, norm from scipy.optimize import bisect +from qiskit import QuantumCircuit from qiskit.aqua import AquaError from qiskit.aqua.utils import CircuitFactory from qiskit.aqua.circuits import PhaseEstimationCircuit @@ -36,7 +35,8 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): - r""" + r"""The Amplitude Estimation algorithm. + This class implements the Quantum Amplitude Estimation (QAE) algorithm, introduced by https://arxiv.org/abs/quant-ph/0005055. This (original) version uses quantum phase estimation along with a set of m ancilla qubits to find an estimate, that is restricted @@ -51,19 +51,18 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): def __init__(self, num_eval_qubits: int, a_factory: Optional[CircuitFactory] = None, - i_objective: Optional[int] = None, q_factory: Optional[CircuitFactory] = None, + i_objective: Optional[int] = None, iqft: Optional[IQFT] = None) -> None: - """ - Initializer. + """Initializer. Args: num_eval_qubits: number of evaluation qubits, has a min. value of 1. a_factory: the CircuitFactory subclass object representing the problem unitary - i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions - with the state |1> and 'bad' solutions with the state |0> q_factory: the CircuitFactory subclass object representing an amplitude estimation sample (based on a_factory) + i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions + with the state |1> and 'bad' solutions with the state |0> iqft: the Inverse Quantum Fourier Transform component, defaults to using a standard IQFT when None """ @@ -82,12 +81,11 @@ def __init__(self, num_eval_qubits: int, self._ret = {} @property - def _num_qubits(self): - """ - Return the number of qubits needed in the circuit. + def _num_qubits(self) -> int: + """Return the number of qubits needed in the circuit. Returns: - int: the total number of qubits + The total number of qubits. """ if self.a_factory is None: # if A factory is not set, no qubits are specified return 0 @@ -97,16 +95,14 @@ def _num_qubits(self): return num_qubits - def construct_circuit(self, measurement=False): - """ - Construct the Amplitude Estimation quantum circuit. + def construct_circuit(self, measurement: bool = False) -> QuantumCircuit: + """Construct the Amplitude Estimation quantum circuit. Args: - measurement (bool): Boolean flag to indicate if measurement - should be included in the circuit. + measurement: Boolean flag to indicate if measurements should be included in the circuit. Returns: - QuantumCircuit: the QuantumCircuit object for the constructed circuit + The QuantumCircuit object for the constructed circuit. """ pec = PhaseEstimationCircuit( iqft=self._iqft, num_ancillae=self._m, @@ -117,18 +113,20 @@ def construct_circuit(self, measurement=False): self._circuit = pec.construct_circuit(measurement=measurement) return self._circuit - def _evaluate_statevector_results(self, probabilities): - """ + def _evaluate_statevector_results(self, probabilities: Union[List[float], np.ndarray] + ) -> Tuple[OrderedDict, OrderedDict]: + """Evaluate the results from statevector simulation. + Given the probabilities from statevector simulation of the QAE circuit, compute the probabilities that the measurements y/gridpoints a are the best estimate. Args: - probabilities (list[float] | numpy.array): the probabilities obtained from the - statevector simulation, i.e. real(statevector * statevector.conj())[0] + probabilities: The probabilities obtained from the statevector simulation, + i.e. real(statevector * statevector.conj())[0] Returns: - tuple[dict, dict]: dictionaries containing the a gridpoints with respective - probabilities and y measurements with respective probabilities, in this order + Dictionaries containing the a gridpoints with respective probabilities and + y measurements with respective probabilities, in this order. """ # map measured results to estimates y_probabilities = OrderedDict() @@ -148,16 +146,15 @@ def _evaluate_statevector_results(self, probabilities): return a_probabilities, y_probabilities - def _compute_fisher_information(self, observed=False): - """ - Computes the Fisher information for the output of the previous run. + def _compute_fisher_information(self, observed: bool = False) -> float: + """Computes the Fisher information for the output of the previous run. Args: - observed (bool): if true, the observed Fisher information is returned, otherwise - the expected Fisher information + observed: If True, the observed Fisher information is returned, otherwise + the expected Fisher information. Returns: - float: the Fisher information + The Fisher information. """ fisher_information = None mlv = self._ret['ml_value'] # MLE in [0,1] @@ -179,18 +176,16 @@ def integrand(x): return fisher_information - def _fisher_confint(self, alpha, observed=False): - """ - Compute the confidence interval for the MLE of the previous run based on the Fisher - information. + def _fisher_confint(self, alpha: float, observed: bool = False) -> List[float]: + """Compute the Fisher information confidence interval for the MLE of the previous run. Args: - alpha (float): specify the (1 - alpha) confidence level (0 < alpha < 1) - observed (bool): if true, the observed Fisher information is used to construct the - confidence interval, otherwise the expected Fisher information + alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). + observed: If True, the observed Fisher information is used to construct the + confidence interval, otherwise the expected Fisher information. Returns: - list[float]: the confidence interval + The Fisher information confidence interval. """ shots = self._ret['shots'] mle = self._ret['ml_value'] @@ -202,16 +197,14 @@ def _fisher_confint(self, alpha, observed=False): # transform the confidence interval from [0, 1] to the target interval return [self.a_factory.value_to_estimation(bound) for bound in ci] - def _likelihood_ratio_confint(self, alpha): - """ - Compute the confidence interval for the MLE of the previous run based on the likelihood - ratio. + def _likelihood_ratio_confint(self, alpha: float) -> List[float]: + """Compute the likelihood ratio confidence interval for the MLE of the previous run. Args: - alpha (float): specify the (1 - alpha) confidence level (0 < alpha < 1) + alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). Returns: - list[float]: the confidence interval + The likelihood ratio confidence interval. """ # Compute the two intervals in which we the look for values above # the likelihood ratio: the two bubbles next to the QAE estimate @@ -275,21 +268,20 @@ def cut(x): ci = [lower, upper] return [self.a_factory.value_to_estimation(bound) for bound in ci] - def confidence_interval(self, alpha, kind='likelihood_ratio'): - """ - Compute the (1 - alpha) confidence interval + def confidence_interval(self, alpha: float, kind: str = 'likelihood_ratio') -> List[float]: + """Compute the (1 - alpha) confidence interval. Args: - alpha (float): confidence level: compute the (1 - alpha) confidence interval - kind (str): the method to compute the confidence interval, can be 'fisher', - 'observed_fisher' or 'likelihood_ratio' (default) + alpha: Confidence level: compute the (1 - alpha) confidence interval. + kind: The method to compute the confidence interval, can be 'fisher', 'observed_fisher' + or 'likelihood_ratio' (default) Returns: - list[float]: the (1 - alpha) confidence interval + The (1 - alpha) confidence interval of the specified kind. Raises: - AquaError: if 'mle' is not in self._ret.keys() (i.e. `run` was not called yet) - NotImplementedError: if the confidence interval method `kind` is not implemented + AquaError: If 'mle' is not in self._ret.keys() (i.e. `run` was not called yet). + NotImplementedError: If the confidence interval method `kind` is not implemented. """ # check if AE did run already if 'mle' not in self._ret.keys(): @@ -310,14 +302,14 @@ def confidence_interval(self, alpha, kind='likelihood_ratio'): raise NotImplementedError('CI `{}` is not implemented.'.format(kind)) - def _run_mle(self): - """ - Compute the Maximum Likelihood Estimator (MLE) + def _run_mle(self) -> None: + """Compute the Maximum Likelihood Estimator (MLE). Returns: - The MLE for the previous AE run + The MLE for the previous AE run. - Note: Before calling this method, call the method `run` of the AmplitudeEstimation instance + Note: + Before calling this method, call the method `run` of the AmplitudeEstimation instance. """ M = self._M qae = self._ret['value'] @@ -367,7 +359,7 @@ def loglikelihood(a): self._ret['ml_value'] = a_opt self._ret['mle'] = val_opt - def _run(self): + def _run(self) -> dict: # check if A factory has been set if self.a_factory is None: raise AquaError("a_factory must be set!") diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 41adb9a755..a4b94ba654 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -12,11 +12,9 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -The Iterative Quantum Amplitude Estimation Algorithm. -""" +"""The Iterative Quantum Amplitude Estimation Algorithm.""" -from typing import Optional +from typing import Optional, Union, List, Tuple import logging import numpy as np from scipy.stats import beta @@ -32,7 +30,8 @@ class IterativeAmplitudeEstimation(AmplitudeEstimationAlgorithm): - """ + """The Iterative Amplitude Estimation algorithm. + This class implements the Iterative Quantum Amplitude Estimation (QAE) algorithm, proposed in https://arxiv.org/abs/1912.05559. The output of the algorithm is an estimate that, with at least probability 1 - alpha, differs by epsilon to the target value, where @@ -49,8 +48,7 @@ def __init__(self, epsilon: float, alpha: float, a_factory: Optional[CircuitFactory] = None, q_factory: Optional[CircuitFactory] = None, i_objective: Optional[int] = None) -> None: - """ - Initializer. + """Initializer. The output of the algorithm is an estimate for the amplitude `a`, that with at least probability 1 - alpha has an error of epsilon. The number of A operator calls scales @@ -88,40 +86,38 @@ def __init__(self, epsilon: float, alpha: float, self._ret = {} @property - def precision(self): - """ - Returns the target precision `epsilon` of the algorithm + def precision(self) -> float: + """Returns the target precision `epsilon` of the algorithm. Returns: - float: target precision + The target precision (which is half the width of the confidence interval). """ return self._epsilon @precision.setter - def precision(self, epsilon): - """ - Set the target precision of the algorithm. + def precision(self, epsilon: float) -> None: + """Set the target precision of the algorithm. Args: - epsilon (float): target precision for estimation target a + epsilon: Target precision for estimation target `a`. """ self._epsilon = epsilon - def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): - """ - Find the largest integer k_next, such that the interval (4 * k_next + 2)*theta_interval + def _find_next_k(self, k: int, upper_half_circle: bool, theta_interval: Tuple[float, float], + min_ratio: int = 2) -> Tuple[int, bool]: + """Find the largest integer k_next, such that the interval (4 * k_next + 2)*theta_interval lies completely in [0, pi] or [pi, 2pi], for theta_interval = (theta_lower, theta_upper). Args: - k (int): current power of the Q operator - upper_half_circle (bool): boolean flag of whether theta_interval lies in the - upper half-circle [0, pi] or in the lower one [pi, 2pi] - theta_interval (tuple(float, float)): current confidence interval for the angle - theta, i.e. (theta_lower, theta_upper) - min_ratio (float): minimal ratio K/K_next allowed in the algorithm + k: The current power of the Q operator. + upper_half_circle: Boolean flag of whether theta_interval lies in the + upper half-circle [0, pi] or in the lower one [pi, 2pi]. + theta_interval: The current confidence interval for the angle theta, + i.e. (theta_lower, theta_upper). + min_ratio: Minimal ratio K/K_next allowed in the algorithm. Returns: - tuple(int, bool): next power k, and boolean flag for the extrapolated interval + The next power k, and boolean flag for the extrapolated interval. Raises: AquaError: if min_ratio is smaller or equal to 1 @@ -158,18 +154,18 @@ def _find_next_k(self, k, upper_half_circle, theta_interval, min_ratio=2): # if we do not find a feasible k, return the old one return int(k), upper_half_circle - def construct_circuit(self, k, measurement=False): - """ - Construct the circuit Q^k A |0>, with the A operator specifying the QAE problem and - the Grover operator Q. + def construct_circuit(self, k: int, measurement: bool = False) -> QuantumCircuit: + """Construct the circuit Q^k A |0>. + + The A operator specifies the QAE problem and Q is the Grover operator. Args: - k (int): the power of Q operator - measurement (bool): boolean flag to indicate if measurements should be included in the - circuits + k: The power of the Q operator. + measurement: Boolean flag to indicate if measurements should be included in the + circuits. Returns: - QuantumCircuit: the circuit Q^k A |0> + The circuit Q^k A |0>. """ # set up circuit q = QuantumRegister(self.a_factory.num_target_qubits, 'q') @@ -203,18 +199,18 @@ def construct_circuit(self, k, measurement=False): return circuit - def _probability_to_measure_one(self, counts_or_statevector): - """ - Convenient function to get the probability to measure '1' in the last qubit + def _probability_to_measure_one(self, + counts_or_statevector: Union[dict, List[complex], np.ndarray] + ) -> Union[Tuple[int, float], float]: + """Get the probability to measure '1' in the last qubit. Args: - counts_or_statevector (Union(dict, numpy.array, list)): either the counts dictionary - returned from the qasm_simulator (with one measured qubit only!) or the statevector - returned from the statevector_simulator + counts_or_statevector: Either a counts-dictionary (with one measured qubit only!) or + the statevector returned from the statevector_simulator. Returns: - Union(tuple(int, float), float): if a dict is given, it returns - (#one-counts, #one-counts/#all-counts), otherwise Pr(measure '1' in the last qubit) + If a dict is given, return (#one-counts, #one-counts/#all-counts), + otherwise Pr(measure '1' in the last qubit). """ if isinstance(counts_or_statevector, dict): one_counts = counts_or_statevector.get('1', 0) @@ -231,29 +227,42 @@ def _probability_to_measure_one(self, counts_or_statevector): return prob - def _chernoff_confint(self, value, shots, max_rounds, alpha): - """ - Compute the Chernoff confidence interval for i.i.d. Bernoulli trials with `shots` samples: + def _chernoff_confint(self, value: float, shots: int, max_rounds: int, alpha: float + ) -> Tuple[float, float]: + """Compute the Chernoff confidence interval for iid. Bernoulli trials with `shots` samples. + + The confidence interval is [value - eps, value + eps], where eps = sqrt(3 * log(2 * max_rounds/ alpha) / shots) but at most [0, 1]. Args: - value (float): the current estimate - shots (int): the number of shots - max_rounds (int): the maximum number of rounds, used to compute epsilon_a - alpha (float): the confidence level, used to compute epsilon_a + value: The current estimate. + shots: The number of shots. + max_rounds: The maximum number of rounds, used to compute epsilon_a. + alpha: The confidence level, used to compute epsilon_a. Returns: - tuple(float, float): the Chernoff confidence interval + The Chernoff confidence interval. """ eps = np.sqrt(3 * np.log(2 * max_rounds / alpha) / shots) lower = np.maximum(0, value - eps) upper = np.minimum(1, value + eps) return lower, upper - def _clopper_pearson_confint(self, counts, shots, alpha): + def _clopper_pearson_confint(self, counts: int, shots: int, alpha: float + ) -> Tuple[float, float]: + """Compute the Clopper-Pearson confidence interval for iid. Bernoulli trials. + + Args: + counts: The number of positive counts. + shots: The number of shots. + alpha: The confidence level for the confidence interval. + + Returns: + The Clopper-Pearson confidence interval. + """ lower, upper = 0, 1 # if counts == 0, the beta quantile returns nan @@ -266,7 +275,7 @@ def _clopper_pearson_confint(self, counts, shots, alpha): return lower, upper - def _run(self): + def _run(self) -> dict: # check if A factory has been set if self.a_factory is None: raise AquaError("a_factory must be set!") diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index e3c7238624..e9b7ed6b76 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -11,11 +11,9 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -The Maximum Likelihood Amplitude Estimation algorithm. -""" +"""The Maximum Likelihood Amplitude Estimation algorithm.""" -from typing import Optional +from typing import Optional, List, Union, Tuple import logging import numpy as np from scipy.optimize import brute @@ -33,7 +31,8 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): - """ + """The Maximum Likelihood Amplitude Estimation algorithm. + This class implements the an quantum amplitude estimation (QAE) algorithm without phase estimation, according to https://arxiv.org/abs/1904.10246. In comparison to the original QAE algorithm (https://arxiv.org/abs/quant-ph/0005055), this implementation relies solely @@ -47,8 +46,7 @@ def __init__(self, m: int, q_factory: Optional[CircuitFactory] = None, i_objective: Optional[int] = None, likelihood_evals: Optional[int] = None) -> None: - """ - Initializer. + """Initializer. Args: m: base-2-logarithm of the maximal number of evaluations. The resulting @@ -78,12 +76,11 @@ def __init__(self, m: int, self._ret = {} @property - def _num_qubits(self): - """ - Return the number of qubits needed in the circuit. + def _num_qubits(self) -> int: + """Return the number of qubits needed in the circuit. Returns: - int: the total number of qubits + The total number of qubits. """ if self.a_factory is None: # if A factory is not set, no qubits are specified return 0 @@ -93,16 +90,14 @@ def _num_qubits(self): return num_qubits - def construct_circuits(self, measurement=False): - """ - Construct the Amplitude Estimation w/o QPE quantum circuits. + def construct_circuits(self, measurement: bool = False) -> List[QuantumCircuit]: + """Construct the Amplitude Estimation w/o QPE quantum circuits. Args: - measurement (bool): Boolean flag to indicate if measurement - should be included in the circuits. + measurement: Boolean flag to indicate if measurement should be included in the circuits. Returns: - list: a list with the QuantumCircuit objects for the algorithm + A list with the QuantumCircuit objects for the algorithm. """ # keep track of the Q-oracle queries self._ret['num_oracle_queries'] = 0 @@ -142,15 +137,16 @@ def construct_circuits(self, measurement=False): return self._circuits - def _evaluate_statevectors(self, statevectors): - """ - For each statevector, compute the probability that |1> is measured in the objective qubit. + def _evaluate_statevectors(self, + statevectors: Union[List[List[complex]], List[np.ndarray]] + ) -> List[float]: + """For each statevector compute the probability that |1> is measured in the objective qubit. Args: - statevectors (Union(list[list[complex]], list[numpy.array]): a list of statevectors + statevectors: A list of statevectors. Returns: - list[float]: the corresponding probabilities + The corresponding probabilities. """ probabilities = [] for sv in statevectors: @@ -164,15 +160,14 @@ def _evaluate_statevectors(self, statevectors): return probabilities - def _get_hits(self): - """ - Get the good and total counts + def _get_hits(self) -> Tuple[List[int], List[int]]: + """Get the good and total counts. Returns: - tuple(list, list): a pair of two lists, - ([1-counts per experiment], [shots per experiment]) + A pair of two lists, ([1-counts per experiment], [shots per experiment]). + Raises: - AquaError: if self.run() has not been called yet + AquaError: If self.run() has not been called yet. """ one_hits = [] # h_k: how often 1 has been measured, for a power Q^(m_k) all_hits = [] # N_k: how often has been measured at a power Q^(m_k) @@ -192,36 +187,49 @@ def _get_hits(self): return one_hits, all_hits def _safe_min(self, array, default=0): - """ + """Find the minimal element, but if the array is empty return `default`. + + Args: + array: The input array. + default: The default value. + Returns: - float: default if array is empty, otherwise numpy.max(array) + `default` if `array` is empty, otherwise `numpy.min(array)`. """ if len(array) == 0: return default return np.min(array) def _safe_max(self, array, default=(np.pi / 2)): - """ + """Find the maximal element, but if the array is empty return `default`. + + Args: + array: The input array. + default: The default value. + Returns: - float: default if array is empty, otherwise numpy.max(array) + `default` if `array` is empty, otherwise `numpy.max(array)`. """ if len(array) == 0: return default return np.max(array) - def _compute_fisher_information(self, a=None, num_sum_terms=None, observed=False): - """ - Compute the Fisher information. + def _compute_fisher_information(self, a: Optional[float] = None, + num_sum_terms: Optional[int] = None, + observed: bool = False) -> float: + """Compute the Fisher information. Args: - a (float): a - num_sum_terms (int): num sum terms - observed (bool): If True, compute the observed Fisher information, - otherwise the theoretical one + a: The amplitude `a`. Can be omitted if `run` was called already, then the estimate + of the algorithm is used. + num_sum_terms: The number of sum terms to be included in the calculation of the + Fisher information. By default all values are included. + observed: If True, compute the observed Fisher information, otherwise the theoretical + one. Returns: - float: The computed Fisher information, or np.inf if statevector - simulation was used. + The computed Fisher information, or np.inf if statevector simulation was used. + Raises: KeyError: Call run() first! """ @@ -230,7 +238,7 @@ def _compute_fisher_information(self, a=None, num_sum_terms=None, observed=False try: a = self._ret['value'] except KeyError: - raise KeyError("Call run() first!") + raise KeyError('Call run() first!') # Corresponding angle to the value a (only use real part of 'a') theta_a = np.arcsin(np.sqrt(np.real(a))) @@ -265,13 +273,12 @@ def _compute_fisher_information(self, a=None, num_sum_terms=None, observed=False return fisher_information - def _fisher_confint(self, alpha=0.05, observed=False): - """ - Compute the alpha confidence interval based on the Fisher information + def _fisher_confint(self, alpha: float = 0.05, observed: bool = False) -> List[float]: + """Compute the `alpha` confidence interval based on the Fisher information. Args: - alpha (float): The level of the confidence interval (< 0.5) - observed (bool): If True, use observed Fisher information + alpha: The level of the confidence interval (must be <= 0.5), default to 0.05. + observed: If True, use observed Fisher information. Returns: float: The alpha confidence interval based on the Fisher information @@ -294,17 +301,17 @@ def _fisher_confint(self, alpha=0.05, observed=False): mapped_confint = [self.a_factory.value_to_estimation(bound) for bound in confint] return mapped_confint - def _likelihood_ratio_confint(self, alpha=0.05, nevals=None): - """ - Compute the likelihood-ratio confidence interval. + def _likelihood_ratio_confint(self, alpha: float = 0.05, + nevals: Optional[int] = None) -> List[float]: + """Compute the likelihood-ratio confidence interval. Args: - alpha (float): the level of the confidence interval (< 0.5) - nevals (int): the number of evaluations to find the - intersection with the loglikelihood function + alpha: The level of the confidence interval (< 0.5), defaults to 0.05. + nevals: The number of evaluations to find the intersection with the loglikelihood + function. Defaults to an adaptive value based on the maximal power of Q. Returns: - float: The alpha-likelihood-ratio confidence interval. + The alpha-likelihood-ratio confidence interval. """ if nevals is None: nevals = self._likelihood_evals @@ -340,10 +347,24 @@ def loglikelihood(theta, one_counts, all_counts): return mapped_confint - def confidence_interval(self, alpha, kind='fisher'): - """ - Proxy calling the correct method to compute the confidence interval, - according to the value of `kind` + def confidence_interval(self, alpha: float, kind: str = 'fisher') -> List[float]: + """Compute the `alpha` confidence interval using the method `kind`. + + The confidence level is (1 - `alpha`) and supported kinds are 'fisher', + 'likelihood_ratio' and 'observed_fisher' with shorthand notations 'fi', 'lr' and 'oi', + respectively. + + Args: + alpha: The confidence level. + kind: The method to compute the confidence interval. Defaults to 'fisher', which + computes the theoretical Fisher information. + + Returns: + The specified confidence interval. + + Raises: + AquaError: If `run()` hasn't been called yet. + NotImplementedError: If the method `kind` is not supported. """ # check if AE did run already if 'estimation' not in self._ret.keys(): @@ -365,9 +386,9 @@ def confidence_interval(self, alpha, kind='fisher'): raise NotImplementedError('CI `{}` is not implemented.'.format(kind)) def _compute_mle_safe(self): - """ - Compute the MLE via a grid-search. This is a stable approach if - sufficient gridpoints are used (usually > 10'000). + """Compute the MLE via a grid-search. + + This is a stable approach if sufficient gridpoints are used. """ one_hits, all_hits = self._get_hits() @@ -386,18 +407,16 @@ def loglikelihood(theta): est_theta = brute(loglikelihood, [search_range], Ns=self._likelihood_evals)[0] return est_theta - def _run_mle(self): - """ - Compute the maximum likelihood estimator (MLE) for the angle theta, based on which the - final result of this algorithm is computed. + def _run_mle(self) -> float: + """Compute the maximum likelihood estimator (MLE) for the angle theta. Returns: - float: the MLE for the angle theta, related to the amplitude a via a = sin^2(theta) + The MLE for the angle theta, related to the amplitude a via a = sin^2(theta) """ # TODO implement a **reliable**, fast method to find the maximum of the likelihood function return self._compute_mle_safe() - def _run(self): + def _run(self) -> dict: # check if A factory has been set if self.a_factory is None: raise AquaError("a_factory must be set!") From 86f977b9ebf430a3e388fc44e7baff57f29a8183 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Jan 2020 17:16:14 +0100 Subject: [PATCH 35/40] avoid using `ae` (use `qae` instead) --- .pylintrc | 2 +- test/aqua/test_amplitude_estimation.py | 187 +++++++++++++------------ 2 files changed, 95 insertions(+), 94 deletions(-) diff --git a/.pylintrc b/.pylintrc index 392d0c1102..6040fbfed6 100644 --- a/.pylintrc +++ b/.pylintrc @@ -118,7 +118,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # pi = the PI constant # op = operation iterator # b = basis iterator -good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ae,ar,br, +good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ar,br, __unittest # Bad variable names which should always be refused, separated by a comma diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index 4ef0a49ad6..d77a2156d2 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -145,13 +145,13 @@ def qasm(shots=100): [0.82, IterativeAmplitudeEstimation(0.00001, 0.05), {'estimation': 0.82}], [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {'estimation': 0.49}] ]) - def test_statevector(self, prob, ae, expect): + def test_statevector(self, prob, qae, expect): """ statevector test """ # construct factories for A and Q - ae.a_factory = BernoulliAFactory(prob) - ae.q_factory = BernoulliQFactory(ae.a_factory) + qae.a_factory = BernoulliAFactory(prob) + qae.q_factory = BernoulliQFactory(qae.a_factory) - result = ae.run(self._statevector) + result = qae.run(self._statevector) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, @@ -168,13 +168,13 @@ def test_statevector(self, prob, ae, expect): [0.4, 1000, IterativeAmplitudeEstimation(0.001, 0.05), {'estimation': 0.400071}], [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {'estimation': 0.811711}] ]) - def test_qasm(self, prob, shots, ae, expect): + def test_qasm(self, prob, shots, qae, expect): """ qasm test """ # construct factories for A and Q - ae.a_factory = BernoulliAFactory(prob) - ae.q_factory = BernoulliQFactory(ae.a_factory) + qae.a_factory = BernoulliAFactory(prob) + qae.q_factory = BernoulliQFactory(qae.a_factory) - result = ae.run(self._qasm(shots)) + result = qae.run(self._qasm(shots)) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, @@ -183,13 +183,13 @@ def test_qasm(self, prob, shots, ae, expect): @parameterized.expand([ [True], [False] ]) - def test_ae_circuit(self, efficient_circuit): + def test_qae_circuit(self, efficient_circuit): """ Test circuits resulting from canonical amplitude estimation """ prob = 0.5 basis_gates = ['u1', 'u2', 'u3', 'cx'] for m in range(2, 7): - ae = AmplitudeEstimation(m, a_factory=BernoulliAFactory(prob)) + qae = AmplitudeEstimation(m, a_factory=BernoulliAFactory(prob)) angle = 2 * np.arcsin(np.sqrt(prob)) # manually set up the inefficient AE circuit @@ -205,12 +205,12 @@ def test_ae_circuit(self, efficient_circuit): circuit.ry(angle, q_objective) if efficient_circuit: - ae.q_factory = BernoulliQFactory(ae.a_factory) + qae.q_factory = BernoulliQFactory(qae.a_factory) for power in range(m): circuit.cry(2 ** power * angle, q_ancilla[power], q_objective[0]) else: - q_factory = QFactory(ae.a_factory, i_objective=0) + q_factory = QFactory(qae.a_factory, i_objective=0) for power in range(m): for _ in range(2**power): q_factory.build_controlled(circuit, q_objective, q_ancilla[power]) @@ -218,13 +218,12 @@ def test_ae_circuit(self, efficient_circuit): # fourier transform iqft = Standard(m) circuit = iqft.construct_circuit(qubits=q_ancilla, circuit=circuit, do_swaps=False) - expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + expected_transpiled = transpile(circuit, basis_gates=basis_gates) - actual_circuit = ae.construct_circuit(measurement=False) - actual_ops = transpile(actual_circuit, basis_gates=basis_gates).count_ops() + actual_circuit = qae.construct_circuit(measurement=False) + actual_transpiled = transpile(actual_circuit, basis_gates=basis_gates) - for key in expected_ops.keys(): - self.assertEqual(expected_ops[key], actual_ops[key]) + self.assertEqual(expected_transpiled, actual_transpiled) @parameterized.expand([ [True], [False] @@ -235,7 +234,7 @@ def test_iqae_circuits(self, efficient_circuit): basis_gates = ['u1', 'u2', 'u3', 'cx'] for k in range(2, 7): - ae = IterativeAmplitudeEstimation(0.01, 0.05, a_factory=BernoulliAFactory(prob)) + qae = IterativeAmplitudeEstimation(0.01, 0.05, a_factory=BernoulliAFactory(prob)) angle = 2 * np.arcsin(np.sqrt(prob)) # manually set up the inefficient AE circuit @@ -246,23 +245,25 @@ def test_iqae_circuits(self, efficient_circuit): circuit.ry(angle, q_objective) if efficient_circuit: - ae.q_factory = BernoulliQFactory(ae.a_factory) + qae.q_factory = BernoulliQFactory(qae.a_factory) # for power in range(k): # circuit.ry(2 ** power * angle, q_objective[0]) circuit.ry(2 * 2**k * angle, q_objective[0]) else: - q_factory = QFactory(ae.a_factory, i_objective=0) + q_factory = QFactory(qae.a_factory, i_objective=0) for _ in range(2**k): q_factory.build(circuit, q_objective) - expected_ops = transpile(circuit, basis_gates=basis_gates).count_ops() + expected_transpiled = transpile(circuit, basis_gates=basis_gates) - actual_circuit = ae.construct_circuit(k, measurement=False) - actual_ops = transpile(actual_circuit, basis_gates=basis_gates).count_ops() + actual_circuit = qae.construct_circuit(k, measurement=False) + actual_transpiled = transpile(actual_circuit, basis_gates=basis_gates) - for key in expected_ops.keys(): - self.assertEqual(expected_ops[key], actual_ops[key]) + print(actual_transpiled) + print(expected_transpiled) + + self.assertEqual(expected_transpiled, actual_transpiled) @parameterized.expand([ [True], [False] @@ -273,7 +274,7 @@ def test_mlae_circuits(self, efficient_circuit): basis_gates = ['u1', 'u2', 'u3', 'cx'] for k in range(1, 7): - ae = MaximumLikelihoodAmplitudeEstimation(k, a_factory=BernoulliAFactory(prob)) + qae = MaximumLikelihoodAmplitudeEstimation(k, a_factory=BernoulliAFactory(prob)) angle = 2 * np.arcsin(np.sqrt(prob)) # compute all the circuits used for MLAE @@ -295,22 +296,22 @@ def test_mlae_circuits(self, efficient_circuit): # Q^(2^j) operator if efficient_circuit: - ae.q_factory = BernoulliQFactory(ae.a_factory) + qae.q_factory = BernoulliQFactory(qae.a_factory) circuit.ry(2 * 2 ** power * angle, q_objective[0]) else: - q_factory = QFactory(ae.a_factory, i_objective=0) + q_factory = QFactory(qae.a_factory, i_objective=0) for _ in range(2**power): q_factory.build(circuit, q_objective) - actual_circuits = ae.construct_circuits(measurement=False) + actual_circuits = qae.construct_circuits(measurement=False) for actual, expected in zip(actual_circuits, circuits): - actual_ops = transpile(actual, basis_gates=basis_gates).count_ops() - expected_ops = transpile(expected, basis_gates=basis_gates).count_ops() + self.assertEqual(actual, expected) + expected_transpiled = transpile(expected, basis_gates=basis_gates) + actual_transpiled = transpile(actual, basis_gates=basis_gates) - for key in expected_ops.keys(): - self.assertEqual(expected_ops[key], actual_ops[key]) + self.assertEqual(expected_transpiled, actual_transpiled) class TestProblemSetting(QiskitAquaTestCase): @@ -332,67 +333,67 @@ def setUp(self): [IterativeAmplitudeEstimation(0.1, 0.001)], [MaximumLikelihoodAmplitudeEstimation(3)], ]) - def test_operators(self, ae): + def test_operators(self, qae): """ Test if A/Q operator + i_objective set correctly """ - self.assertIsNone(ae.a_factory) - self.assertIsNone(ae.q_factory) - self.assertIsNone(ae.i_objective) - self.assertIsNone(ae._a_factory) - self.assertIsNone(ae._q_factory) - self.assertIsNone(ae._i_objective) - - ae.a_factory = self.a_bernoulli - self.assertIsNotNone(ae.a_factory) - self.assertIsNotNone(ae.q_factory) - self.assertIsNotNone(ae.i_objective) - self.assertIsNotNone(ae._a_factory) - self.assertIsNone(ae._q_factory) - self.assertIsNone(ae._i_objective) - - ae.q_factory = self.q_bernoulli - self.assertIsNotNone(ae.a_factory) - self.assertIsNotNone(ae.q_factory) - self.assertIsNotNone(ae.i_objective) - self.assertIsNotNone(ae._a_factory) - self.assertIsNotNone(ae._q_factory) - self.assertIsNone(ae._i_objective) - - ae.i_objective = self.i_bernoulli - self.assertIsNotNone(ae.a_factory) - self.assertIsNotNone(ae.q_factory) - self.assertIsNotNone(ae.i_objective) - self.assertIsNotNone(ae._a_factory) - self.assertIsNotNone(ae._q_factory) - self.assertIsNotNone(ae._i_objective) + self.assertIsNone(qae.a_factory) + self.assertIsNone(qae.q_factory) + self.assertIsNone(qae.i_objective) + self.assertIsNone(qae._a_factory) + self.assertIsNone(qae._q_factory) + self.assertIsNone(qae._i_objective) + + qae.a_factory = self.a_bernoulli + self.assertIsNotNone(qae.a_factory) + self.assertIsNotNone(qae.q_factory) + self.assertIsNotNone(qae.i_objective) + self.assertIsNotNone(qae._a_factory) + self.assertIsNone(qae._q_factory) + self.assertIsNone(qae._i_objective) + + qae.q_factory = self.q_bernoulli + self.assertIsNotNone(qae.a_factory) + self.assertIsNotNone(qae.q_factory) + self.assertIsNotNone(qae.i_objective) + self.assertIsNotNone(qae._a_factory) + self.assertIsNotNone(qae._q_factory) + self.assertIsNone(qae._i_objective) + + qae.i_objective = self.i_bernoulli + self.assertIsNotNone(qae.a_factory) + self.assertIsNotNone(qae.q_factory) + self.assertIsNotNone(qae.i_objective) + self.assertIsNotNone(qae._a_factory) + self.assertIsNotNone(qae._q_factory) + self.assertIsNotNone(qae._i_objective) @parameterized.expand([ [AmplitudeEstimation(2)], [IterativeAmplitudeEstimation(0.1, 0.001)], [MaximumLikelihoodAmplitudeEstimation(3)], ]) - def test_a_factory_update(self, ae): + def test_a_factory_update(self, qae): """Test if the Q factory is updated if the a_factory changes -- except set manually.""" # Case 1: Set to BernoulliAFactory with default Q operator - ae.a_factory = self.a_bernoulli - self.assertIsInstance(ae.q_factory.a_factory, BernoulliAFactory) - self.assertEqual(ae.i_objective, self.i_bernoulli) + qae.a_factory = self.a_bernoulli + self.assertIsInstance(qae.q_factory.a_factory, BernoulliAFactory) + self.assertEqual(qae.i_objective, self.i_bernoulli) # Case 2: Change to SineIntegralAFactory with default Q operator - ae.a_factory = self.a_integral - self.assertIsInstance(ae.q_factory.a_factory, SineIntegralAFactory) - self.assertEqual(ae.i_objective, self.i_intergal) + qae.a_factory = self.a_integral + self.assertIsInstance(qae.q_factory.a_factory, SineIntegralAFactory) + self.assertEqual(qae.i_objective, self.i_intergal) # Case 3: Set to BernoulliAFactory with special Q operator - ae.a_factory = self.a_bernoulli - ae.q_factory = self.q_bernoulli - self.assertIsInstance(ae.q_factory, BernoulliQFactory) - self.assertEqual(ae.i_objective, self.i_bernoulli) + qae.a_factory = self.a_bernoulli + qae.q_factory = self.q_bernoulli + self.assertIsInstance(qae.q_factory, BernoulliQFactory) + self.assertEqual(qae.i_objective, self.i_bernoulli) # Case 4: Set to SineIntegralAFactory, and do not set Q. Then the old Q operator # should remain - ae.a_factory = self.a_integral - self.assertIsInstance(ae.q_factory, BernoulliQFactory) - self.assertEqual(ae.i_objective, self.i_bernoulli) + qae.a_factory = self.a_integral + self.assertIsInstance(qae.q_factory, BernoulliQFactory) + self.assertEqual(qae.i_objective, self.i_bernoulli) class TestSineIntegral(QiskitAquaTestCase): @@ -421,12 +422,12 @@ def qasm(shots=100): [4, MaximumLikelihoodAmplitudeEstimation(4), {'estimation': 0.272675}], [3, IterativeAmplitudeEstimation(0.1, 0.1), {'estimation': 0.272082}], ]) - def test_statevector(self, n, ae, expect): + def test_statevector(self, n, qae, expect): """ Statevector end-to-end test """ # construct factories for A and Q - ae.a_factory = SineIntegralAFactory(n) + qae.a_factory = SineIntegralAFactory(n) - result = ae.run(self._statevector) + result = qae.run(self._statevector) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, @@ -437,12 +438,12 @@ def test_statevector(self, n, ae, expect): [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {'estimation': 0.256878}], [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {'estimation': 0.271790}], ]) - def test_qasm(self, n, shots, ae, expect): + def test_qasm(self, n, shots, qae, expect): """QASM simulator end-to-end test.""" # construct factories for A and Q - ae.a_factory = SineIntegralAFactory(n) + qae.a_factory = SineIntegralAFactory(n) - result = ae.run(self._qasm(shots)) + result = qae.run(self._qasm(shots)) for key, value in expect.items(): self.assertAlmostEqual(value, result[key], places=3, @@ -459,17 +460,17 @@ def test_qasm(self, n, shots, ae, expect): 'fisher': [0.2584889015125656, 0.2797018754936686], 'observed_fisher': [0.2659279996107888, 0.2722627773954454]}], ]) - def test_confidence_intervals(self, ae, key, expect): + def test_confidence_intervals(self, qae, key, expect): """End-to-end test for all confidence intervals.""" n = 3 - ae.a_factory = SineIntegralAFactory(n) + qae.a_factory = SineIntegralAFactory(n) # statevector simulator - result = ae.run(self._statevector) + result = qae.run(self._statevector) methods = ['lr', 'fi', 'oi'] # short for likelihood_ratio, fisher, observed_fisher alphas = [0.1, 0.00001, 0.9] # alpha shouldn't matter in statevector for alpha, method in zip(alphas, methods): - confint = ae.confidence_interval(alpha, method) + confint = qae.confidence_interval(alpha, method) # confidence interval based on statevector should be empty, as we are sure of the result self.assertAlmostEqual(confint[1] - confint[0], 0.0) self.assertAlmostEqual(confint[0], result[key]) @@ -477,20 +478,20 @@ def test_confidence_intervals(self, ae, key, expect): # qasm simulator shots = 100 alpha = 0.01 - result = ae.run(self._qasm(shots)) + result = qae.run(self._qasm(shots)) for method, expected_confint in expect.items(): - confint = ae.confidence_interval(alpha, method) + confint = qae.confidence_interval(alpha, method) self.assertEqual(confint, expected_confint) self.assertTrue(confint[0] <= result[key] <= confint[1]) def test_iqae_confidence_intervals(self): """End-to-end test for the IQAE confidence interval.""" n = 3 - ae = IterativeAmplitudeEstimation(0.1, 0.01, a_factory=SineIntegralAFactory(n)) + qae = IterativeAmplitudeEstimation(0.1, 0.01, a_factory=SineIntegralAFactory(n)) expected_confint = [0.19840508760087738, 0.35110155403424115] # statevector simulator - result = ae.run(self._statevector) + result = qae.run(self._statevector) confint = result['confidence_interval'] # confidence interval based on statevector should be empty, as we are sure of the result self.assertAlmostEqual(confint[1] - confint[0], 0.0) @@ -498,7 +499,7 @@ def test_iqae_confidence_intervals(self): # qasm simulator shots = 100 - result = ae.run(self._qasm(shots)) + result = qae.run(self._qasm(shots)) confint = result['confidence_interval'] self.assertEqual(confint, expected_confint) self.assertTrue(confint[0] <= result['estimation'] <= confint[1]) From 34420c7fb19abd175f7c0a363793a461f9c14d64 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Jan 2020 17:31:30 +0100 Subject: [PATCH 36/40] rename `m` -> `num_oracle_circuit` --- .../amplitude_estimation/mlae.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index e9b7ed6b76..665e4e89e1 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -41,7 +41,7 @@ class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimationAlgorithm): class in named MaximumLikelihoodAmplitudeEstimation. """ - def __init__(self, m: int, + def __init__(self, num_oracle_circuits: int, a_factory: Optional[CircuitFactory] = None, q_factory: Optional[CircuitFactory] = None, i_objective: Optional[int] = None, @@ -49,28 +49,31 @@ def __init__(self, m: int, """Initializer. Args: - m: base-2-logarithm of the maximal number of evaluations. The resulting - evaluation schedule will be [0, Q^2^0, ..., Q^2^{m-1}]. + num_oracle_circuits: The number of circuits applying different powers of the Grover + oracle Q. The (`num_oracle_circuits` + 1) executed circuits will be + `[id, Q^2^0, ..., Q^2^{num_oracle_circuits-1}] A |0>`, where A is the problem + unitary encoded in the argument `a_factory`. Has a minimum value of 1. - a_factory: the CircuitFactory subclass object representing the problem unitary - q_factory: the CircuitFactory subclass object representing + a_factory: The CircuitFactory subclass object representing the problem unitary. + q_factory: The CircuitFactory subclass object representing. an amplitude estimation sample (based on a_factory) - i_objective: the index of the objective qubit, i.e. the qubit marking 'good' solutions - with the state |1> and 'bad' solutions with the state |0> - likelihood_evals: the number of gridpoints for the maximum search of the likelihood - function + i_objective: The index of the objective qubit, i.e. the qubit marking 'good' solutions + with the state |1> and 'bad' solutions with the state |0>. + likelihood_evals: The number of gridpoints for the maximum search of the likelihood + function. """ - validate_min('m', m, 1) + validate_min('num_oracle_circuits', num_oracle_circuits, 1) super().__init__(a_factory, q_factory, i_objective) # get parameters - self._evaluation_schedule = [0] + [2**j for j in range(m)] + self._evaluation_schedule = [0] + [2**j for j in range(num_oracle_circuits)] self._likelihood_evals = likelihood_evals # default number of evaluations is max(10^5, pi/2 * 10^3 * 2^(m)) if likelihood_evals is None: default = 10000 - self._likelihood_evals = np.maximum(default, int(np.pi / 2 * 1000 * 2 ** m)) + self._likelihood_evals = np.maximum(default, + int(np.pi / 2 * 1000 * 2 ** num_oracle_circuits)) self._circuits = [] self._ret = {} From 9688c656f9725c98907112d3ae59850c4a5c103e Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Jan 2020 18:41:42 +0100 Subject: [PATCH 37/40] fix spell & style --- .../algorithms/single_sample/amplitude_estimation/ae.py | 8 ++++---- .../single_sample/amplitude_estimation/iqae.py | 4 ++-- .../single_sample/amplitude_estimation/mlae.py | 9 +++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 9134252515..a53b0f8ae2 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -121,11 +121,11 @@ def _evaluate_statevector_results(self, probabilities: Union[List[float], np.nda probabilities that the measurements y/gridpoints a are the best estimate. Args: - probabilities: The probabilities obtained from the statevector simulation, + probabilities: The probabilities obtained from the statevector simulation, i.e. real(statevector * statevector.conj())[0] Returns: - Dictionaries containing the a gridpoints with respective probabilities and + Dictionaries containing the a gridpoints with respective probabilities and y measurements with respective probabilities, in this order. """ # map measured results to estimates @@ -273,7 +273,7 @@ def confidence_interval(self, alpha: float, kind: str = 'likelihood_ratio') -> L Args: alpha: Confidence level: compute the (1 - alpha) confidence interval. - kind: The method to compute the confidence interval, can be 'fisher', 'observed_fisher' + kind: The method to compute the confidence interval, can be 'fisher', 'observed_fisher' or 'likelihood_ratio' (default) Returns: @@ -308,7 +308,7 @@ def _run_mle(self) -> None: Returns: The MLE for the previous AE run. - Note: + Note: Before calling this method, call the method `run` of the AmplitudeEstimation instance. """ M = self._M diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index a4b94ba654..af7db2b58f 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -229,7 +229,7 @@ def _probability_to_measure_one(self, def _chernoff_confint(self, value: float, shots: int, max_rounds: int, alpha: float ) -> Tuple[float, float]: - """Compute the Chernoff confidence interval for iid. Bernoulli trials with `shots` samples. + """Compute the Chernoff confidence interval for `shots` i.i.d. Bernoulli trials. The confidence interval is @@ -253,7 +253,7 @@ def _chernoff_confint(self, value: float, shots: int, max_rounds: int, alpha: fl def _clopper_pearson_confint(self, counts: int, shots: int, alpha: float ) -> Tuple[float, float]: - """Compute the Clopper-Pearson confidence interval for iid. Bernoulli trials. + """Compute the Clopper-Pearson confidence interval for `shots` i.i.d. Bernoulli trials. Args: counts: The number of positive counts. diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 665e4e89e1..3257839872 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -50,8 +50,8 @@ def __init__(self, num_oracle_circuits: int, Args: num_oracle_circuits: The number of circuits applying different powers of the Grover - oracle Q. The (`num_oracle_circuits` + 1) executed circuits will be - `[id, Q^2^0, ..., Q^2^{num_oracle_circuits-1}] A |0>`, where A is the problem + oracle Q. The (`num_oracle_circuits` + 1) executed circuits will be + `[id, Q^2^0, ..., Q^2^{num_oracle_circuits-1}] A |0>`, where A is the problem unitary encoded in the argument `a_factory`. Has a minimum value of 1. a_factory: The CircuitFactory subclass object representing the problem unitary. @@ -351,11 +351,12 @@ def loglikelihood(theta, one_counts, all_counts): return mapped_confint def confidence_interval(self, alpha: float, kind: str = 'fisher') -> List[float]: + # pylint: disable=wrong-spelling-in-docstring """Compute the `alpha` confidence interval using the method `kind`. The confidence level is (1 - `alpha`) and supported kinds are 'fisher', - 'likelihood_ratio' and 'observed_fisher' with shorthand notations 'fi', 'lr' and 'oi', - respectively. + 'likelihood_ratio' and 'observed_fisher' with shorthand + notations 'fi', 'lr' and 'oi', respectively. Args: alpha: The confidence level. From 2579ba272272fa31e3c7d2139bbe91dc32b9dc8a Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Jan 2020 20:12:40 +0100 Subject: [PATCH 38/40] test circuit unitaries instead of gate counts --- test/aqua/test_amplitude_estimation.py | 59 ++++++++++++++------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index d77a2156d2..fba3f133d2 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -56,8 +56,8 @@ def value_to_estimation(self, value): class BernoulliQFactory(QFactory): """Circuit Factory representing the operator Q in a Bernoulli problem. - This implementation exploits the fact that powers of Q can be implemented efficiently by just - multiplying the angle. Note, that since amplitude estimation only requires controlled powers of + This implementation exploits the fact that powers of Q can be implemented efficiently by just + multiplying the angle. Note, that since amplitude estimation only requires controlled powers of Q only that method is overridden. """ @@ -111,7 +111,7 @@ def build(self, qc, q, q_ancillas=None, params=None): class TestBernoulli(QiskitAquaTestCase): - """Tests based on the Bernoulli A operator. + """Tests based on the Bernoulli A operator. This class tests * the estimation result @@ -122,8 +122,10 @@ def setUp(self): super().setUp() self._statevector = QuantumInstance(backend=BasicAer.get_backend('statevector_simulator'), - seed_simulator=2, - seed_transpiler=2) + seed_simulator=2, seed_transpiler=2) + + self._unitary = QuantumInstance(backend=BasicAer.get_backend('unitary_simulator'), shots=1, + seed_simulator=42, seed_transpiler=91) def qasm(shots=100): return QuantumInstance(backend=BasicAer.get_backend('qasm_simulator'), shots=shots, @@ -184,9 +186,11 @@ def test_qasm(self, prob, shots, qae, expect): [True], [False] ]) def test_qae_circuit(self, efficient_circuit): - """ Test circuits resulting from canonical amplitude estimation """ + """Test circuits resulting from canonical amplitude estimation. + + Build the circuit manually and from the algorithm and compare the resulting unitaries. + """ prob = 0.5 - basis_gates = ['u1', 'u2', 'u3', 'cx'] for m in range(2, 7): qae = AmplitudeEstimation(m, a_factory=BernoulliAFactory(prob)) @@ -207,7 +211,7 @@ def test_qae_circuit(self, efficient_circuit): if efficient_circuit: qae.q_factory = BernoulliQFactory(qae.a_factory) for power in range(m): - circuit.cry(2 ** power * angle, q_ancilla[power], q_objective[0]) + circuit.cry(2 * 2 ** power * angle, q_ancilla[power], q_objective[0]) else: q_factory = QFactory(qae.a_factory, i_objective=0) @@ -218,20 +222,23 @@ def test_qae_circuit(self, efficient_circuit): # fourier transform iqft = Standard(m) circuit = iqft.construct_circuit(qubits=q_ancilla, circuit=circuit, do_swaps=False) - expected_transpiled = transpile(circuit, basis_gates=basis_gates) + expected_unitary = self._unitary.execute(circuit).get_unitary() actual_circuit = qae.construct_circuit(measurement=False) - actual_transpiled = transpile(actual_circuit, basis_gates=basis_gates) + actual_unitary = self._unitary.execute(actual_circuit).get_unitary() - self.assertEqual(expected_transpiled, actual_transpiled) + diff = np.sum(np.abs(actual_unitary - expected_unitary)) + self.assertAlmostEqual(diff, 0) @parameterized.expand([ [True], [False] ]) def test_iqae_circuits(self, efficient_circuit): - """ Test the circuits constructed for IQAE """ + """Test circuits resulting from iterative amplitude estimation. + + Build the circuit manually and from the algorithm and compare the resulting unitaries. + """ prob = 0.5 - basis_gates = ['u1', 'u2', 'u3', 'cx'] for k in range(2, 7): qae = IterativeAmplitudeEstimation(0.01, 0.05, a_factory=BernoulliAFactory(prob)) @@ -248,22 +255,20 @@ def test_iqae_circuits(self, efficient_circuit): qae.q_factory = BernoulliQFactory(qae.a_factory) # for power in range(k): # circuit.ry(2 ** power * angle, q_objective[0]) - circuit.ry(2 * 2**k * angle, q_objective[0]) + circuit.ry(2 * k * angle, q_objective[0]) else: q_factory = QFactory(qae.a_factory, i_objective=0) - for _ in range(2**k): + for _ in range(k): q_factory.build(circuit, q_objective) - expected_transpiled = transpile(circuit, basis_gates=basis_gates) + expected_unitary = self._unitary.execute(circuit).get_unitary() actual_circuit = qae.construct_circuit(k, measurement=False) - actual_transpiled = transpile(actual_circuit, basis_gates=basis_gates) + actual_unitary = self._unitary.execute(actual_circuit).get_unitary() - print(actual_transpiled) - print(expected_transpiled) - - self.assertEqual(expected_transpiled, actual_transpiled) + diff = np.sum(np.abs(actual_unitary - expected_unitary)) + self.assertAlmostEqual(diff, 0) @parameterized.expand([ [True], [False] @@ -271,7 +276,6 @@ def test_iqae_circuits(self, efficient_circuit): def test_mlae_circuits(self, efficient_circuit): """ Test the circuits constructed for MLAE """ prob = 0.5 - basis_gates = ['u1', 'u2', 'u3', 'cx'] for k in range(1, 7): qae = MaximumLikelihoodAmplitudeEstimation(k, a_factory=BernoulliAFactory(prob)) @@ -307,11 +311,10 @@ def test_mlae_circuits(self, efficient_circuit): actual_circuits = qae.construct_circuits(measurement=False) for actual, expected in zip(actual_circuits, circuits): - self.assertEqual(actual, expected) - expected_transpiled = transpile(expected, basis_gates=basis_gates) - actual_transpiled = transpile(actual, basis_gates=basis_gates) - - self.assertEqual(expected_transpiled, actual_transpiled) + expected_unitary = self._unitary.execute(expected).get_unitary() + actual_unitary = self._unitary.execute(actual).get_unitary() + diff = np.sum(np.abs(actual_unitary - expected_unitary)) + self.assertAlmostEqual(diff, 0) class TestProblemSetting(QiskitAquaTestCase): @@ -397,7 +400,7 @@ def test_a_factory_update(self, qae): class TestSineIntegral(QiskitAquaTestCase): - """Tests based on the A operator to integrate sin^2(x). + """Tests based on the A operator to integrate sin^2(x). This class tests * the estimation result From 5618cb1229e50c12e9555ceb7bf48e5ab4fbfe90 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sun, 26 Jan 2020 22:02:11 +0100 Subject: [PATCH 39/40] fix lint --- .../single_sample/amplitude_estimation/iqae.py | 2 +- .../single_sample/amplitude_estimation/mlae.py | 18 ------------------ test/aqua/test_amplitude_estimation.py | 2 +- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py index 81abc5e8cc..98e9920200 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/iqae.py @@ -154,7 +154,7 @@ def _find_next_k(self, k: int, upper_half_circle: bool, theta_interval: Tuple[fl # if we do not find a feasible k, return the old one return int(k), upper_half_circle - def construct_circuit(self, k, measurement=False): + def construct_circuit(self, k: int, measurement: bool = False) -> QuantumCircuit: r"""Construct the circuit Q^k A \|0>. The A operator is the unitary specifying the QAE problem and Q the associated Grover diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py index 4456f55658..32feec55d6 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/mlae.py @@ -190,29 +190,11 @@ def _get_hits(self) -> Tuple[List[int], List[int]]: return one_hits, all_hits def _safe_min(self, array, default=0): - """Find the minimal element, but if the array is empty return `default`. - - Args: - array: The input array. - default: The default value. - - Returns: - `default` if `array` is empty, otherwise `numpy.min(array)`. - """ if len(array) == 0: return default return np.min(array) def _safe_max(self, array, default=(np.pi / 2)): - """Find the maximal element, but if the array is empty return `default`. - - Args: - array: The input array. - default: The default value. - - Returns: - `default` if `array` is empty, otherwise `numpy.max(array)`. - """ if len(array) == 0: return default return np.max(array) diff --git a/test/aqua/test_amplitude_estimation.py b/test/aqua/test_amplitude_estimation.py index fba3f133d2..1d43fc65da 100644 --- a/test/aqua/test_amplitude_estimation.py +++ b/test/aqua/test_amplitude_estimation.py @@ -18,7 +18,7 @@ from test.aqua import QiskitAquaTestCase import numpy as np from parameterized import parameterized -from qiskit import QuantumRegister, QuantumCircuit, BasicAer, execute, transpile +from qiskit import QuantumRegister, QuantumCircuit, BasicAer, execute from qiskit.aqua import QuantumInstance from qiskit.aqua.components.iqfts import Standard from qiskit.aqua.components.uncertainty_models import GaussianConditionalIndependenceModel as GCI From 3fd0891b159b17adf24899cde8bf9b40e080058d Mon Sep 17 00:00:00 2001 From: Cryoris Date: Wed, 29 Jan 2020 21:52:38 +0100 Subject: [PATCH 40/40] update docstring --- .../algorithms/single_sample/amplitude_estimation/ae.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py index 6832675ff3..1a832bc446 100644 --- a/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py +++ b/qiskit/aqua/algorithms/single_sample/amplitude_estimation/ae.py @@ -11,7 +11,7 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The Amplitude Estimation Algorithm.""" +"""The Quantum Phase Estimation-based Amplitude Estimation algorithm.""" from typing import Optional, Union, List, Tuple import logging @@ -35,9 +35,9 @@ class AmplitudeEstimation(AmplitudeEstimationAlgorithm): - r"""The Amplitude Estimation algorithm. + r"""The Quantum Phase Estimation-based Amplitude Estimation algorithm. - This class implements the Quantum Amplitude Estimation (QAE) algorithm, introduced by + This class implements the original Quantum Amplitude Estimation (QAE) algorithm, introduced by https://arxiv.org/abs/quant-ph/0005055. This (original) version uses quantum phase estimation along with a set of m ancilla qubits to find an estimate, that is restricted to the grid