diff --git a/qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py b/qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py index cde25a0fd..4203f53a6 100644 --- a/qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py +++ b/qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py @@ -20,6 +20,10 @@ from qiskit.circuit import Parameter, QuantumCircuit from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit.primitives import BaseEstimatorV1 +from qiskit.primitives.base import BaseEstimatorV2 +from numpy import array + from ..base.base_estimator_gradient import BaseEstimatorGradient from ..base.estimator_gradient_result import EstimatorGradientResult from ..utils import _make_param_shift_parameter_values @@ -97,13 +101,24 @@ def _run_unique( job_param_values.extend(param_shift_parameter_values) all_n.append(n) - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) + PUBs = [(job_circuits[i], [observable], job_param_values[i]) for i in range(n)] + + if isinstance(self._estimator, BaseEstimatorV1): + # Run the single job with all circuits. + job = self._estimator.run( + job_circuits, + job_observables, + job_param_values, + **options, + ) + results = job.result() + elif isinstance(self._estimator, BaseEstimatorV2): + job = self._estimator.run(PUBs, precision=0.001, **options) + else: + raise AlgorithmError( + f"The accepted estimators are BaseEstimatorV1 (deprecated) and BaseEstimatorV2; got {type(self._estimator)} instead." + ) + try: results = job.result() except Exception as exc: @@ -113,10 +128,14 @@ def _run_unique( gradients = [] partial_sum_n = 0 for n in all_n: - result = results.values[partial_sum_n : partial_sum_n + n] + if isinstance(self._estimator, BaseEstimatorV1): + result = results.values[partial_sum_n : partial_sum_n + n] + opt = self._get_local_options(options) + elif isinstance(self._estimator, BaseEstimatorV2): + result = array([float(result.data.evs[0]) for result in results]) + opt = options gradient_ = (result[: n // 2] - result[n // 2 :]) / 2 gradients.append(gradient_) partial_sum_n += n - opt = self._get_local_options(options) return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py b/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py index 1f9bfa0b2..2b5fbcdfc 100644 --- a/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py +++ b/qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py @@ -23,6 +23,10 @@ from qiskit.providers import Options from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit.primitives import BaseEstimatorV1 +from qiskit.primitives.base import BaseEstimatorV2 +from numpy import array + from ..base.base_estimator_gradient import BaseEstimatorGradient from ..base.estimator_gradient_result import EstimatorGradientResult @@ -102,23 +106,38 @@ def _run( job_param_values.extend(plus + minus) all_n.append(2 * self._batch_size) - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) + PUBs = [ + (job_circuits[i], [observable], job_param_values[i]) for i in range(len(job_circuits)) + ] + + if isinstance(self._estimator, BaseEstimatorV1): + # Run the single job with all circuits. + job = self._estimator.run( + job_circuits, + job_observables, + job_param_values, + **options, + ) + results = job.result() + elif isinstance(self._estimator, BaseEstimatorV2): + job = self._estimator.run(PUBs, precision=0.001, **options) + else: + raise AlgorithmError("Wrong Estimator Type.") + try: results = job.result() except Exception as exc: raise AlgorithmError("Estimator job failed.") from exc - # Compute the gradients. gradients = [] partial_sum_n = 0 for i, n in enumerate(all_n): - result = results.values[partial_sum_n : partial_sum_n + n] + if isinstance(self._estimator, BaseEstimatorV1): + result = results.values[partial_sum_n : partial_sum_n + n] + opt = self._get_local_options(options) + elif isinstance(self._estimator, BaseEstimatorV2): + result = array([float(result.data.evs[0]) for result in results]) + opt = options partial_sum_n += n n = len(result) // 2 diffs = (result[:n] - result[n:]) / (2 * self._epsilon) @@ -129,6 +148,4 @@ def _run( gradient = np.mean(batch_gradients, axis=0) indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] gradients.append(gradient[indices]) - - opt = self._get_local_options(options) return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit_machine_learning/neural_networks/estimator_qnn.py b/qiskit_machine_learning/neural_networks/estimator_qnn.py index f55d82224..705a23408 100644 --- a/qiskit_machine_learning/neural_networks/estimator_qnn.py +++ b/qiskit_machine_learning/neural_networks/estimator_qnn.py @@ -18,6 +18,9 @@ from copy import copy from typing import Sequence +from qiskit.primitives import BaseEstimatorV1 +from qiskit.primitives.base import BaseEstimatorV2 + import numpy as np from qiskit.circuit import Parameter, QuantumCircuit from qiskit.primitives import BaseEstimator, Estimator, EstimatorResult @@ -27,6 +30,7 @@ BaseEstimatorGradient, EstimatorGradientResult, ParamShiftEstimatorGradient, + SPSAEstimatorGradient, ) from ..circuit.library import QNNCircuit @@ -111,6 +115,7 @@ def __init__( weight_params: Sequence[Parameter] | None = None, gradient: BaseEstimatorGradient | None = None, input_gradients: bool = False, + num_qubits: int | None = None, ): r""" Args: @@ -141,6 +146,7 @@ def __init__( Note that this parameter is ``False`` by default, and must be explicitly set to ``True`` for a proper gradient computation when using :class:`~qiskit_machine_learning.connectors.TorchConnector`. + num_qubits: Number of qubits for actual circuit. Raises: QiskitMachineLearningError: Invalid parameter values. @@ -149,8 +155,15 @@ def __init__( estimator = Estimator() self.estimator = estimator self._org_circuit = circuit + if num_qubits is None: + self.num_qubits = circuit.num_qubits + print( + "Warning: No number of qubits provided, using it from provided circuit, if the circuit is transpiled this can be wrong and cause issues." + ) + else: + self.num_qubits = num_qubits if observables is None: - observables = SparsePauliOp.from_list([("Z" * circuit.num_qubits, 1)]) + observables = SparsePauliOp.from_list([("Z" * self.num_qubits, 1)]) if isinstance(observables, BaseOperator): observables = (observables,) self._observables = observables @@ -208,23 +221,33 @@ def input_gradients(self, input_gradients: bool) -> None: def _forward_postprocess(self, num_samples: int, result: EstimatorResult) -> np.ndarray: """Post-processing during forward pass of the network.""" - return np.reshape(result.values, (-1, num_samples)).T + return np.reshape(result, (-1, num_samples)).T def _forward( self, input_data: np.ndarray | None, weights: np.ndarray | None ) -> np.ndarray | None: """Forward pass of the neural network.""" parameter_values_, num_samples = self._preprocess_forward(input_data, weights) - job = self.estimator.run( - [self._circuit] * num_samples * self.output_shape[0], - [op for op in self._observables for _ in range(num_samples)], - np.tile(parameter_values_, (self.output_shape[0], 1)), - ) - try: - results = job.result() - except Exception as exc: - raise QiskitMachineLearningError("Estimator job failed.") from exc + if isinstance(self.estimator, BaseEstimatorV1): + job = self.estimator.run( + [self._circuit] * num_samples * self.output_shape[0], + [op for op in self._observables for _ in range(num_samples)], + np.tile(parameter_values_, (self.output_shape[0], 1)), + ) + results = job.result().values + elif isinstance(self.estimator, BaseEstimatorV2): + PUBs = [] + for _ in range(num_samples): + for observable in self._observables: + PUBs.append((self._circuit, [observable], parameter_values_)) + job = self.estimator.run(PUBs, precision=0.001) + results = job.result() + results = [result.data.evs[0] for result in results] + else: + raise QiskitMachineLearningError( + f"The accepted estimators are BaseEstimatorV1 (deprecated) and BaseEstimatorV2; got {type(self.estimator)} instead." + ) return self._forward_postprocess(num_samples, results) def _backward_postprocess( @@ -270,12 +293,10 @@ def _backward( job = None if self._input_gradients: - job = self.gradient.run(circuits, observables, param_values) # type: ignore[arg-type] + job = self.gradient.run(circuits, observables, param_values) elif len(parameter_values[0]) > self._num_inputs: params = [self._circuit.parameters[self._num_inputs :]] * num_circuits - job = self.gradient.run( - circuits, observables, param_values, parameters=params # type: ignore[arg-type] - ) + job = self.gradient.run(circuits, observables, param_values, parameters=params) if job is not None: try: diff --git a/test/neural_networks/test_estimator_qnn.py b/test/neural_networks/test_estimator_qnn.py index 566329f27..1d07ef940 100644 --- a/test/neural_networks/test_estimator_qnn.py +++ b/test/neural_networks/test_estimator_qnn.py @@ -23,6 +23,9 @@ from qiskit_machine_learning.circuit.library import QNNCircuit from qiskit_machine_learning.neural_networks.estimator_qnn import EstimatorQNN +from qiskit_ibm_runtime import Session, EstimatorV2 +from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager CASE_DATA = { "shape_1_1": { @@ -170,6 +173,373 @@ } +class TestEstimatorQNN_V2(QiskitMachineLearningTestCase): + """EstimatorQNN Tests for estimator_v2. The correct references is obtained from EstimatorQNN""" + + def __init__( + self, + TestCase, + ): + self.backend = GenericBackendV2(num_qubits=8) + self.session = Session(backend=self.backend) + self.estimator = EstimatorV2(mode=self.session) + self.pm = generate_preset_pass_manager(backend=self.backend, optimization_level=1) + super().__init__(TestCase) + + def _test_network_passes( + self, + estimator_qnn, + case_data, + ): + test_data = case_data["test_data"] + weights = case_data["weights"] + correct_forwards = case_data["correct_forwards"] + correct_weight_backwards = case_data["correct_weight_backwards"] + correct_input_backwards = case_data["correct_input_backwards"] + + # test forward pass + with self.subTest("forward pass"): + for i, inputs in enumerate(test_data): + forward = estimator_qnn.forward(inputs, weights) + np.testing.assert_allclose(forward, correct_forwards[i], atol=1e-2) + # test backward pass without input_gradients + with self.subTest("backward pass without input gradients"): + for i, inputs in enumerate(test_data): + input_backward, weight_backward = estimator_qnn.backward(inputs, weights) + if correct_weight_backwards[i] is None: + self.assertIsNone(weight_backward) + else: + np.testing.assert_allclose( + weight_backward, correct_weight_backwards[i], atol=1e-2 + ) + self.assertIsNone(input_backward) + # test backward pass with input_gradients + with self.subTest("backward pass with input gradients"): + estimator_qnn.input_gradients = True + for i, inputs in enumerate(test_data): + input_backward, weight_backward = estimator_qnn.backward(inputs, weights) + if correct_weight_backwards[i] is None: + self.assertIsNone(weight_backward) + else: + np.testing.assert_allclose( + weight_backward, correct_weight_backwards[i], atol=1e-2 + ) + if correct_input_backwards[i] is None: + self.assertIsNone(input_backward) + else: + np.testing.assert_allclose( + input_backward, correct_input_backwards[i], atol=1e-2 + ) + + def test_estimator_qnn_1_1(self): + """Test Estimator QNN with input/output dimension 1/1.""" + params = [Parameter("input1"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + op = SparsePauliOp.from_list([("Z", 1), ("X", 1)]) + isa_qc = self.pm.run(qc) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=[params[0]], + weight_params=[params[1]], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + + self._test_network_passes(estimator_qnn, CASE_DATA["shape_1_1"]) + + def test_estimator_qnn_2_1(self): + """Test Estimator QNN with input/output dimension 2/1.""" + params = [ + Parameter("input1"), + Parameter("input2"), + Parameter("weight1"), + Parameter("weight2"), + ] + qc = QuantumCircuit(2) + qc.h(0) + qc.ry(params[0], 0) + qc.ry(params[1], 1) + qc.rx(params[2], 0) + qc.rx(params[3], 1) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("ZZ", [0, 1], 1), ("XX", [0, 1], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=params[:2], + weight_params=params[2:], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + + self._test_network_passes(estimator_qnn, CASE_DATA["shape_2_1"]) + + def test_estimator_qnn_1_2(self): + """Test Estimator QNN with input/output dimension 1/2.""" + params = [Parameter("input1"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + + isa_qc = self.pm.run(qc) + op1 = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + op2 = SparsePauliOp.from_sparse_list( + [("Z", [0], 2), ("X", [0], 2)], num_qubits=isa_qc.num_qubits + ) + + # construct QNN + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op1, op2], + input_params=[params[0]], + weight_params=[params[1]], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + + self._test_network_passes(estimator_qnn, CASE_DATA["shape_1_2"]) + + def test_estimator_qnn_2_2(self): + """Test Estimator QNN with input/output dimension 2/2.""" + params = [ + Parameter("input1"), + Parameter("input2"), + Parameter("weight1"), + Parameter("weight2"), + ] + qc = QuantumCircuit(2) + qc.h(0) + qc.ry(params[0], 0) + qc.ry(params[1], 1) + qc.rx(params[2], 0) + qc.rx(params[3], 1) + isa_qc = self.pm.run(qc) + op1 = SparsePauliOp.from_sparse_list([("ZZ", [0, 1], 1)], num_qubits=isa_qc.num_qubits) + op2 = SparsePauliOp.from_sparse_list([("XX", [0, 1], 1)], num_qubits=isa_qc.num_qubits) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op1, op2], + input_params=params[:2], + weight_params=params[2:], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + + self._test_network_passes(estimator_qnn, CASE_DATA["shape_2_2"]) + + def test_no_input_parameters(self): + """Test Estimator QNN with no input parameters.""" + params = [Parameter("weight0"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=None, + weight_params=params, + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + self._test_network_passes(estimator_qnn, CASE_DATA["no_input_parameters"]) + + def test_no_weight_parameters(self): + """Test Estimator QNN with no weight parameters.""" + params = [Parameter("input0"), Parameter("input1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=params, + weight_params=None, + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + self._test_network_passes(estimator_qnn, CASE_DATA["no_weight_parameters"]) + + def test_no_parameters(self): + """Test Estimator QNN with no parameters.""" + qc = QuantumCircuit(1) + qc.h(0) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=None, + weight_params=None, + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + self._test_network_passes(estimator_qnn, CASE_DATA["no_parameters"]) + + def test_default_observables(self): + """Test Estimator QNN with default observables.""" + params = [Parameter("input1"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + isa_qc = self.pm.run(qc) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + input_params=[params[0]], + weight_params=[params[1]], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + self._test_network_passes(estimator_qnn, CASE_DATA["default_observables"]) + + def test_single_observable(self): + """Test Estimator QNN with single observable.""" + params = [Parameter("input1"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=op, + input_params=[params[0]], + weight_params=[params[1]], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + self._test_network_passes(estimator_qnn, CASE_DATA["single_observable"]) + + def test_setters_getters(self): + """Test Estimator QNN properties.""" + params = [Parameter("input1"), Parameter("weight1")] + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(params[0], 0) + qc.rx(params[1], 0) + isa_qc = self.pm.run(qc) + op = SparsePauliOp.from_sparse_list( + [("Z", [0], 1), ("X", [0], 1)], num_qubits=isa_qc.num_qubits + ) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=[op], + input_params=[params[0]], + weight_params=[params[1]], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + with self.subTest("Test circuit getter."): + self.assertEqual(estimator_qnn.circuit, isa_qc) + with self.subTest("Test observables getter."): + self.assertEqual(estimator_qnn.observables, [op]) + with self.subTest("Test input_params getter."): + self.assertEqual(estimator_qnn.input_params, [params[0]]) + with self.subTest("Test weight_params getter."): + self.assertEqual(estimator_qnn.weight_params, [params[1]]) + with self.subTest("Test input_gradients setter and getter."): + self.assertFalse(estimator_qnn.input_gradients) + estimator_qnn.input_gradients = True + self.assertTrue(estimator_qnn.input_gradients) + + def test_qnn_qc_circui_construction(self): + """Test Estimator QNN properties and forward/backward pass for QNNCircuit construction""" + num_qubits = 2 + feature_map = ZZFeatureMap(feature_dimension=num_qubits) + ansatz = RealAmplitudes(num_qubits=num_qubits, reps=1) + + qnn_qc = QNNCircuit(num_qubits=num_qubits, feature_map=feature_map, ansatz=ansatz) + qc = QuantumCircuit(num_qubits) + qc.compose(feature_map, inplace=True) + qc.compose(ansatz, inplace=True) + isa_qc = self.pm.run(qc) + isa_qnn_qc = self.pm.run(qc) + estimator_qc = EstimatorQNN( + circuit=isa_qc, + input_params=feature_map.parameters, + weight_params=ansatz.parameters, + input_gradients=True, + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + estimator_qnn_qc = EstimatorQNN( + circuit=isa_qnn_qc, + input_gradients=True, + estimator=self.estimator, + num_qubits=qnn_qc.num_qubits, + ) + + input_data = [1, 2] + weights = [1, 2, 3, 4] + + with self.subTest("Test if Estimator QNN properties are equal."): + self.assertEqual(estimator_qnn_qc.input_params, estimator_qc.input_params) + self.assertEqual(estimator_qnn_qc.weight_params, estimator_qc.weight_params) + self.assertEqual(estimator_qnn_qc.observables, estimator_qc.observables) + + with self.subTest("Test if forward pass yields equal results."): + forward_qc = estimator_qc.forward(input_data=input_data, weights=weights) + forward_qnn_qc = estimator_qnn_qc.forward(input_data=input_data, weights=weights) + np.testing.assert_array_almost_equal(forward_qc, forward_qnn_qc) + + with self.subTest("Test if backward pass yields equal results."): + backward_qc = estimator_qc.backward(input_data=input_data, weights=weights) + backward_qnn_qc = estimator_qnn_qc.backward(input_data=input_data, weights=weights) + # Test if input grad is identical + np.testing.assert_array_almost_equal(backward_qc[0], backward_qnn_qc[0]) + # Test if weights grad is identical + np.testing.assert_array_almost_equal(backward_qc[1], backward_qnn_qc[1]) + + def test_binding_order(self): + """Test parameter binding order gives result as expected""" + qc = ZFeatureMap(feature_dimension=2, reps=1) + input_params = qc.parameters + weight = Parameter("weight") + for i in range(qc.num_qubits): + qc.rx(weight, i) + isa_qc = self.pm.run(qc) + observable1 = SparsePauliOp.from_list([("Z" * isa_qc.num_qubits, 1)]) + estimator_qnn = EstimatorQNN( + circuit=isa_qc, + observables=observable1, + input_params=input_params, + weight_params=[weight], + estimator=self.estimator, + num_qubits=qc.num_qubits, + ) + + estimator_qnn_weights = [3] + estimator_qnn_input = [2, 33] + res = estimator_qnn.forward(estimator_qnn_input, estimator_qnn_weights) + # When parameters were used in circuit order, before being assigned correctly, so inputs + # went to input params, weights to weight params, this gave 0.00613403 + self.assertAlmostEqual(res[0][0], 0.00040017) + + class TestEstimatorQNN(QiskitMachineLearningTestCase): """EstimatorQNN Tests. The correct references is obtained from EstimatorQNN""" @@ -188,7 +558,7 @@ def _test_network_passes( with self.subTest("forward pass"): for i, inputs in enumerate(test_data): forward = estimator_qnn.forward(inputs, weights) - np.testing.assert_allclose(forward, correct_forwards[i], atol=1e-3) + np.testing.assert_allclose(forward, correct_forwards[i], atol=1e-2) # test backward pass without input_gradients with self.subTest("backward pass without input gradients"): for i, inputs in enumerate(test_data): @@ -197,7 +567,7 @@ def _test_network_passes( self.assertIsNone(weight_backward) else: np.testing.assert_allclose( - weight_backward, correct_weight_backwards[i], atol=1e-3 + weight_backward, correct_weight_backwards[i], atol=1e-2 ) self.assertIsNone(input_backward) # test backward pass with input_gradients @@ -209,13 +579,13 @@ def _test_network_passes( self.assertIsNone(weight_backward) else: np.testing.assert_allclose( - weight_backward, correct_weight_backwards[i], atol=1e-3 + weight_backward, correct_weight_backwards[i], atol=1e-2 ) if correct_input_backwards[i] is None: self.assertIsNone(input_backward) else: np.testing.assert_allclose( - input_backward, correct_input_backwards[i], atol=1e-3 + input_backward, correct_input_backwards[i], atol=1e-2 ) def test_estimator_qnn_1_1(self):