def setUp(self): super().setUp() # specify "run configuration" backend = Aer.get_backend('statevector_simulator') quantum_instance = QuantumInstance(backend) # specify how to evaluate expected values and gradients expval = PauliExpectation() gradient = Gradient() # construct parametrized circuit params = [Parameter('input1'), Parameter('weight1')] qc = QuantumCircuit(1) qc.h(0) qc.ry(params[0], 0) qc.rx(params[1], 0) qc_sfn = StateFn(qc) # construct cost operator cost_operator = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op = ~cost_operator @ qc_sfn # define QNN self.qnn = OpflowQNN(op, [params[0]], [params[1]], expval, gradient, quantum_instance=quantum_instance)
def _prepare_list_op( quantum_state: Union[Statevector, QuantumCircuit, OperatorBase, ], observables: ListOrDict[OperatorBase], ) -> ListOp: """ Accepts a list or a dictionary of operators and converts them to a ``ListOp``. Args: quantum_state: An unparametrized quantum circuit representing a quantum state that expectation values are computed against. observables: A list or a dictionary of operators. Returns: A ``ListOp`` that includes all provided observables. """ if isinstance(observables, dict): observables = list(observables.values()) if not isinstance(quantum_state, StateFn): quantum_state = StateFn(quantum_state) return ListOp([ StateFn(obs, is_measurement=True).compose(quantum_state) for obs in observables ])
def test_circuit_sampler_caching(self, caching): """Test caching all operators works.""" try: from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest( "Aer doesn't appear to be installed. Error: '{}'".format( str(ex))) return x = Parameter('x') circuit = QuantumCircuit(1) circuit.ry(x, 0) expr1 = ~StateFn(H) @ StateFn(circuit) expr2 = ~StateFn(X) @ StateFn(circuit) sampler = CircuitSampler(Aer.get_backend('statevector_simulator'), caching=caching) res1 = sampler.convert(expr1, params={x: 0}).eval() res2 = sampler.convert(expr2, params={x: 0}).eval() res3 = sampler.convert(expr1, params={x: 0}).eval() res4 = sampler.convert(expr2, params={x: 0}).eval() self.assertEqual(res1, res3) self.assertEqual(res2, res4) if caching == 'last': self.assertEqual(len(sampler._cached_ops.keys()), 1) else: self.assertEqual(len(sampler._cached_ops.keys()), 2)
def measure_aux_ops(self, obs_wfn, pauli, parameters, expectator, sampler): # This function calculates the expectation value of a given operator # Prepare the operator and the parameters wfn = StateFn(obs_wfn) op = StateFn(pauli, is_measurement=True) values_obs = dict(zip(self.obs_params[:], parameters.tolist())) braket = op @ wfn grouped = expectator.convert(braket) sampled_op = sampler.convert(grouped, params=values_obs) #print(sampled_op.eval()) mean_value = sampled_op.eval().real est_err = 0 if (not self.instance.is_statevector): variance = expectator.compute_variance(sampled_op).real est_err = np.sqrt(variance / self.shots) res = [mean_value, est_err] return res
def test_flatten_statefn_composed_with_composed_op(self): """Test that composing a StateFn with a ComposedOp constructs a single ComposedOp""" circuit = QuantumCircuit(1) vector = [1, 0] ex = ~StateFn(I) @ (CircuitOp(circuit) @ StateFn(vector)) self.assertEqual(len(ex), 3) self.assertEqual(ex.eval(), 1)
def test_coefficients_correctly_propagated(self): """Test that the coefficients in SummedOp and states are correctly used.""" try: from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest( "Aer doesn't appear to be installed. Error: '{}'".format( str(ex))) return with self.subTest('zero coeff in SummedOp'): op = 0 * (I + Z) state = Plus self.assertEqual((~StateFn(op) @ state).eval(), 0j) backend = Aer.get_backend('qasm_simulator') q_instance = QuantumInstance(backend, seed_simulator=97, seed_transpiler=97) op = I with self.subTest('zero coeff in summed StateFn and CircuitSampler'): state = 0 * (Plus + Minus) sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) self.assertEqual(sampler.eval(), 0j) with self.subTest( 'coeff gets squared in CircuitSampler shot-based readout'): state = (Plus + Minus) / numpy.sqrt(2) sampler = CircuitSampler(q_instance).convert(~StateFn(op) @ state) self.assertAlmostEqual(sampler.eval(), 1 + 0j)
def test_pauli_two_design(self): """Test standard gradient descent on the Pauli two-design example.""" circuit = PauliTwoDesign(3, reps=3, seed=2) parameters = list(circuit.parameters) obs = Z ^ Z ^ I expr = ~StateFn(obs) @ StateFn(circuit) initial_point = np.array([ 0.1822308, -0.27254251, 0.83684425, 0.86153976, -0.7111668, 0.82766631, 0.97867993, 0.46136964, 2.27079901, 0.13382699, 0.29589915, 0.64883193, ]) def objective(x): return expr.bind_parameters(dict(zip(parameters, x))).eval().real optimizer = GradientDescent(maxiter=100, learning_rate=0.1, perturbation=0.1) result = optimizer.optimize(circuit.num_parameters, objective, initial_point=initial_point) self.assertLess(result[1], -0.95) # final loss self.assertEqual(result[2], 100) # function evaluations
def test_qiskit_result_instantiation(self): """qiskit result instantiation test""" qc = QuantumCircuit(3) # REMEMBER: This is Qubit 2 in Operator land. qc.h(0) sv_res = execute( qc, BasicAer.get_backend("statevector_simulator")).result() sv_vector = sv_res.get_statevector() qc_op = PrimitiveOp(qc) @ Zero qasm_res = execute(qc_op.to_circuit(meas=True), BasicAer.get_backend("qasm_simulator")).result() np.testing.assert_array_almost_equal( StateFn(sv_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0]) np.testing.assert_array_almost_equal( StateFn(sv_vector).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0]) np.testing.assert_array_almost_equal( StateFn(qasm_res).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0], decimal=1) np.testing.assert_array_almost_equal( ((I ^ I ^ H) @ Zero).to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0]) np.testing.assert_array_almost_equal( qc_op.to_matrix(), [0.5**0.5, 0.5**0.5, 0, 0, 0, 0, 0, 0])
def test_grouped_pauli_expectation(self): """grouped pauli expectation test""" two_qubit_H2 = ((-1.052373245772859 * I ^ I) + (0.39793742484318045 * I ^ Z) + (-0.39793742484318045 * Z ^ I) + (-0.01128010425623538 * Z ^ Z) + (0.18093119978423156 * X ^ X)) wf = CX @ (H ^ I) @ Zero expect_op = PauliExpectation(group_paulis=False).convert( ~StateFn(two_qubit_H2) @ wf) self.sampler._extract_circuitstatefns(expect_op) num_circuits_ungrouped = len(self.sampler._circuit_ops_cache) self.assertEqual(num_circuits_ungrouped, 5) expect_op_grouped = PauliExpectation(group_paulis=True).convert( ~StateFn(two_qubit_H2) @ wf) q_instance = QuantumInstance( BasicAer.get_backend("statevector_simulator"), seed_simulator=self.seed, seed_transpiler=self.seed, ) sampler = CircuitSampler(q_instance) sampler._extract_circuitstatefns(expect_op_grouped) num_circuits_grouped = len(sampler._circuit_ops_cache) self.assertEqual(num_circuits_grouped, 2)
def _get_observable_evaluator( ansatz: QuantumCircuit, observables: Union[OperatorBase, List[OperatorBase]], expectation: ExpectationBase, sampler: CircuitSampler, ) -> Callable[[np.ndarray], Union[float, List[float]]]: """Get a callable to evaluate a (list of) observable(s) for given circuit parameters.""" if isinstance(observables, list): observables = ListOp(observables) expectation_value = StateFn(observables, is_measurement=True) @ StateFn(ansatz) converted = expectation.convert(expectation_value) ansatz_parameters = ansatz.parameters def evaluate_observables(theta: np.ndarray) -> Union[float, List[float]]: """Evaluate the observables for the ansatz parameters ``theta``. Args: theta: The ansatz parameters. Returns: The observables evaluated at the ansatz parameters. """ value_dict = dict(zip(ansatz_parameters, theta)) sampled = sampler.convert(converted, params=value_dict) return sampled.eval() return evaluate_observables
def test_add_direct(self): """ add direct test """ wf = StateFn({'101010': .5, '111111': .3}) + (Zero ^ 6) self.assertEqual(wf.primitive, {'101010': 0.5, '111111': 0.3, '000000': 1.0}) wf = (4 * StateFn({'101010': .5, '111111': .3})) + ((3 + .1j) * (Zero ^ 6)) self.assertEqual(wf.primitive, {'000000': (3 + 0.1j), '101010': (2 + 0j), '111111': (1.2 + 0j)})
def test_jax_chain_rule(self, method: str, autograd: bool): """Test the chain rule functionality using Jax d<H>/d<X> = 2<X> d<H>/d<Z> = - sin(<Z>) <Z> = Tr(|psi><psi|Z) = sin(a)sin(b) <X> = Tr(|psi><psi|X) = cos(a) d<H>/da = d<H>/d<X> d<X>/da + d<H>/d<Z> d<Z>/da = - 2 cos(a)sin(a) - sin(sin(a)sin(b)) * cos(a)sin(b) d<H>/db = d<H>/d<X> d<X>/db + d<H>/d<Z> d<Z>/db = - sin(sin(a)sin(b)) * sin(a)cos(b) """ a = Parameter("a") b = Parameter("b") params = [a, b] q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.rz(params[0], q[0]) qc.rx(params[1], q[0]) def combo_fn(x): return jnp.power(x[0], 2) + jnp.cos(x[1]) def grad_combo_fn(x): return np.array([2 * x[0], -np.sin(x[1])]) op = ListOp( [ ~StateFn(X) @ CircuitStateFn(primitive=qc, coeff=1.0), ~StateFn(Z) @ CircuitStateFn(primitive=qc, coeff=1.0), ], combo_fn=combo_fn, grad_combo_fn=None if autograd else grad_combo_fn, ) state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [ { a: np.pi / 4, b: np.pi }, { params[0]: np.pi / 4, params[1]: np.pi / 4 }, { params[0]: np.pi / 2, params[1]: np.pi / 4 }, ] correct_values = [[-1.0, 0.0], [-1.2397, -0.2397], [0, -0.45936]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)
def test_opflow_qnn_2_2(self, q_i): """Test Torch Connector + Opflow QNN with input/output dimension 2/2.""" from torch import Tensor if q_i == "sv": quantum_instance = self._sv_quantum_instance else: quantum_instance = self._qasm_quantum_instance # construct parametrized circuit params_1 = [Parameter("input1"), Parameter("weight1")] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter("input2"), Parameter("weight2")] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN( op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance, input_gradients=True, ) model = TorchConnector(qnn) test_data = [ Tensor([1]), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]), ] # test model self._validate_output_shape(model, test_data) if q_i == "sv": self._validate_backward_pass(model)
def test_statefn_overlaps(self): """state functions overlaps test""" wf = (4 * StateFn({"101010": 0.5, "111111": 0.3})) + ((3 + 0.1j) * (Zero ^ 6)) wf_vec = StateFn(wf.to_matrix()) self.assertAlmostEqual(wf.adjoint().eval(wf), 14.45) self.assertAlmostEqual(wf_vec.adjoint().eval(wf_vec), 14.45) self.assertAlmostEqual(wf_vec.adjoint().eval(wf), 14.45) self.assertAlmostEqual(wf.adjoint().eval(wf_vec), 14.45)
def test_opflow_qnn_2_2(self, config): """Test Opflow QNN with input/output dimension 2/2.""" q_i, input_grad_required = config if q_i == STATEVECTOR: quantum_instance = self.sv_quantum_instance elif q_i == QASM: quantum_instance = self.qasm_quantum_instance else: quantum_instance = None # construct parametrized circuit params_1 = [Parameter("input1"), Parameter("weight1")] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter("input2"), Parameter("weight2")] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([("Z", 1.0), ("X", 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN( op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance, ) qnn.input_gradients = input_grad_required test_data = [np.array([1, 2]), np.array([[1, 2], [3, 4]])] # test model self.validate_output_shape(qnn, test_data) # test the qnn after we set a quantum instance if quantum_instance is None: qnn.quantum_instance = self.qasm_quantum_instance self.validate_output_shape(qnn, test_data)
def test_opflow_qnn_2_1(self, config): """Test Opflow QNN with input/output dimension 2/1.""" q_i, input_grad_required = config # construct QNN if q_i == STATEVECTOR: quantum_instance = self.sv_quantum_instance elif q_i == QASM: quantum_instance = self.qasm_quantum_instance else: quantum_instance = None # specify how to evaluate expected values and gradients expval = PauliExpectation() gradient = Gradient() # construct parametrized circuit 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) qc_sfn = StateFn(qc) # construct cost operator cost_operator = StateFn(PauliSumOp.from_list([("ZZ", 1.0), ("XX", 1.0)])) # combine operator and circuit to objective function op = ~cost_operator @ qc_sfn # define QNN qnn = OpflowQNN( op, params[:2], params[2:], expval, gradient, quantum_instance=quantum_instance, ) qnn.input_gradients = input_grad_required test_data = [np.array([1, 2]), np.array([[1, 2]]), np.array([[1, 2], [3, 4]])] # test model self.validate_output_shape(qnn, test_data) # test the qnn after we set a quantum instance if quantum_instance is None: qnn.quantum_instance = self.qasm_quantum_instance self.validate_output_shape(qnn, test_data)
def test_opflow_qnn_2_2(self, q_i): """ Test Torch Connector + Opflow QNN with input/output dimension 2/2.""" if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # construct parametrized circuit params_1 = [Parameter('input1'), Parameter('weight1')] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter('input2'), Parameter('weight2')] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN(op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance) try: model = TorchConnector(qnn) test_data = [ Tensor(1), Tensor([1, 2]), Tensor([[1], [2]]), Tensor([[1, 2], [3, 4]]) ] # test model self.validate_output_shape(model, test_data) if q_i == 'sv': self.validate_backward_pass(model) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def get_fidelity( circuit: QuantumCircuit, backend: Optional[Union[Backend, QuantumInstance]] = None, expectation: Optional[ExpectationBase] = None, ) -> Callable[[np.ndarray, np.ndarray], float]: r"""Get a function to compute the fidelity of ``circuit`` with itself. Let ``circuit`` be a parameterized quantum circuit performing the operation :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns a function to evaluate .. math:: F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2. The output of this function can be used as input for the ``fidelity`` to the :class:~`qiskit.algorithms.optimizers.QNSPSA` optimizer. Args: circuit: The circuit preparing the parameterized ansatz. backend: A backend of quantum instance to evaluate the circuits. If None, plain matrix multiplication will be used. expectation: An expectation converter to specify how the expected value is computed. If a shot-based readout is used this should be set to ``PauliExpectation``. Returns: A handle to the function :math:`F`. """ params_x = ParameterVector("x", circuit.num_parameters) params_y = ParameterVector("y", circuit.num_parameters) expression = ~StateFn(circuit.assign_parameters(params_x)) @ StateFn( circuit.assign_parameters(params_y)) if expectation is not None: expression = expectation.convert(expression) if backend is None: def fidelity(values_x, values_y): value_dict = dict( zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist())) return np.abs(expression.bind_parameters(value_dict).eval())**2 else: sampler = CircuitSampler(backend) def fidelity(values_x, values_y): value_dict = dict( zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist())) return np.abs( sampler.convert(expression, params=value_dict).eval())**2 return fidelity
def test_gradient_u(self, method): """Test the state gradient for U Tr(|psi><psi|Z) = - 0.5 sin(a)cos(c) Tr(|psi><psi|X) = cos^2(a/2) cos(b+c) - sin^2(a/2) cos(b-c) """ ham = 0.5 * X - 1 * Z a = Parameter("a") b = Parameter("b") c = Parameter("c") q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.u(a, b, c, q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) params = [a, b, c] state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [{ a: np.pi / 4, b: 0, c: 0 }, { a: np.pi / 4, b: np.pi / 4, c: np.pi / 4 }] correct_values = [[0.3536, 0, 0], [0.3232, -0.42678, -0.92678]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1) # Tr(|psi><psi|Z) = - 0.5 sin(a)cos(c) # Tr(|psi><psi|X) = cos^2(a/2) cos(b+c) - sin^2(a/2) cos(b-c) # dTr(|psi><psi|H)/da = 0.5(cos(2a)) + 0.5() q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.u(a, a, a, q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) params = [a] state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [{a: np.pi / 4}, {a: np.pi / 2}] correct_values = [[-1.03033], [-1]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)
def test_expectation_with_coeff(self): """Test AerPauliExpectation with coefficients.""" with self.subTest("integer coefficients"): exp = 3 * ~StateFn(X) @ (2 * Minus) target = self.sampler.convert(self.expect.convert(exp)).eval() self.assertEqual(target, -12) with self.subTest("complex coefficients"): exp = 3j * ~StateFn(X) @ (2j * Minus) target = self.sampler.convert(self.expect.convert(exp)).eval() self.assertEqual(target, -12j)
def test_evaluating_nonunitary_circuit_state(self): """Test evaluating a circuit works even if it contains non-unitary instruction (resets). TODO: allow this for (~StateFn(circuit) @ op @ StateFn(circuit)), but this requires refactoring how the AerPauliExpectation works, since that currently relies on composing with CircuitMeasurements """ circuit = QuantumCircuit(1) circuit.initialize([0, 1], [0]) op = Z res = (~StateFn(op) @ StateFn(circuit)).eval() self.assertAlmostEqual(-1 + 0j, res)
def test_opflow_qnn_2_1(self, q_i): """ Test Opflow QNN with input/output dimension 2/1.""" # construct QNN if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # specify how to evaluate expected values and gradients expval = PauliExpectation() gradient = Gradient() # construct parametrized circuit 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) qc_sfn = StateFn(qc) # construct cost operator cost_operator = StateFn( PauliSumOp.from_list([('ZZ', 1.0), ('XX', 1.0)])) # combine operator and circuit to objective function op = ~cost_operator @ qc_sfn # define QNN qnn = OpflowQNN(op, params[:2], params[2:], expval, gradient, quantum_instance=quantum_instance) test_data = [ np.array([1, 2]), np.array([[1, 2]]), np.array([[1, 2], [3, 4]]) ] # test model self.validate_output_shape(qnn, test_data)
def __init__(self, num_qubits: int, feature_map: QuantumCircuit = None, var_form: QuantumCircuit = None, observable: Union[QuantumCircuit, OperatorBase] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None): r"""Initializes the Two Layer Quantum Neural Network. Args: num_qubits: The number of qubits to represent the network. feature_map: The (parametrized) circuit to be used as feature map. If None is given, the `ZZFeatureMap` is used. var_form: The (parametrized) circuit to be used as variational form. If None is given, the `RealAmplitudes` circuit is used. observable: observable to be measured to determine the output of the network. If None is given, the `Z^{\otimes num_qubits}` observable is used. """ self.num_qubits = num_qubits # TODO: circuits need to have well-defined parameter order! self.feature_map = feature_map if feature_map else ZZFeatureMap( num_qubits) idx = np.argsort([p.name for p in self.feature_map.parameters]) input_params = list(self.feature_map.parameters) input_params = [input_params[i] for i in idx] # TODO: circuits need to have well-defined parameter order! self.var_form = var_form if var_form else RealAmplitudes(num_qubits) idx = np.argsort([p.name for p in self.var_form.parameters]) weight_params = list(self.var_form.parameters) weight_params = [weight_params[i] for i in idx] # construct circuit self.qc = QuantumCircuit(num_qubits) self.qc.append(self.feature_map, range(num_qubits)) self.qc.append(self.var_form, range(num_qubits)) # construct observable self.observable = observable if observable else PauliSumOp.from_list( [('Z' * num_qubits, 1)]) # combine all to operator operator = ~StateFn(self.observable) @ StateFn(self.qc) super().__init__(operator, input_params, weight_params, quantum_instance=quantum_instance)
def test_opflow_qnn_2_2(self, q_i): """ Test Opflow QNN with input/output dimension 2/2.""" if q_i == 'sv': quantum_instance = self.sv_quantum_instance else: quantum_instance = self.qasm_quantum_instance # construct parametrized circuit params_1 = [Parameter('input1'), Parameter('weight1')] qc_1 = QuantumCircuit(1) qc_1.h(0) qc_1.ry(params_1[0], 0) qc_1.rx(params_1[1], 0) qc_sfn_1 = StateFn(qc_1) # construct cost operator h_1 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_1 = ~h_1 @ qc_sfn_1 # construct parametrized circuit params_2 = [Parameter('input2'), Parameter('weight2')] qc_2 = QuantumCircuit(1) qc_2.h(0) qc_2.ry(params_2[0], 0) qc_2.rx(params_2[1], 0) qc_sfn_2 = StateFn(qc_2) # construct cost operator h_2 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)])) # combine operator and circuit to objective function op_2 = ~h_2 @ qc_sfn_2 op = ListOp([op_1, op_2]) qnn = OpflowQNN(op, [params_1[0], params_2[0]], [params_1[1], params_2[1]], quantum_instance=quantum_instance) test_data = [ np.array([1, 2]), np.array([[1, 2], [3, 4]]) ] # test model self.validate_output_shape(qnn, test_data)
def test_state_gradient3(self, method): """Test the state gradient 3 Tr(|psi><psi|Z) = sin(a)sin(c(a)) = sin(a)sin(cos(a)+1) Tr(|psi><psi|X) = cos(a) d<H>/da = - 0.5 sin(a) - 1 cos(a)sin(cos(a)+1) + 1 sin^2(a)cos(cos(a)+1) """ ham = 0.5 * X - 1 * Z a = Parameter("a") # b = Parameter('b') params = a c = np.cos(a) + 1 q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.rz(a, q[0]) qc.rx(c, q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] correct_values = [-1.1220, -0.9093, 0.0403] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)
def test_grad_combo_fn_chain_rule(self, method): """Test the chain rule for a custom gradient combo function.""" np.random.seed(2) def combo_fn(x): amplitudes = x[0].primitive.data pdf = np.multiply(amplitudes, np.conj(amplitudes)) return np.sum(np.log(pdf)) / (-len(amplitudes)) def grad_combo_fn(x): amplitudes = x[0].primitive.data pdf = np.multiply(amplitudes, np.conj(amplitudes)) grad = [] for prob in pdf: grad += [-1 / prob] return grad qc = RealAmplitudes(2, reps=1) grad_op = ListOp([StateFn(qc.decompose())], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn) grad = Gradient(grad_method=method).convert(grad_op) value_dict = dict( zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters)))) correct_values = [ [(-0.16666259133549044 + 0j)], [(-7.244949702732864 + 0j)], [(-2.979791752749964 + 0j)], [(-5.310186078432614 + 0j)], ] np.testing.assert_array_almost_equal( grad.assign_parameters(value_dict).eval(), correct_values)
def test_state_gradient4(self, method): """Test the state gradient 4 Tr(|psi><psi|ZX) = -cos(a) daTr(|psi><psi|ZX) = sin(a) """ ham = X ^ Z a = Parameter("a") params = a q = QuantumRegister(2) qc = QuantumCircuit(q) qc.x(q[0]) qc.h(q[1]) qc.crz(a, q[0], q[1]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] correct_values = [1 / np.sqrt(2), 0, 1] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)
def test_natural_gradient4(self, grad_method, qfi_method, regularization): """Test the natural gradient 4""" # Avoid regularization = lasso intentionally because it does not converge try: ham = 0.5 * X - 1 * Z a = Parameter("a") params = a q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.rz(a, q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) nat_grad = NaturalGradient(grad_method=grad_method, qfi_method=qfi_method, regularization=regularization).convert( operator=op, params=params) values_dict = [{a: np.pi / 4}] correct_values = [[0.0]] if regularization == "ridge" else [[ -1.41421342 ]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=3) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def test_natural_gradient(self, method, regularization): """Test the natural gradient""" try: for params in (ParameterVector("a", 2), [Parameter("a"), Parameter("b")]): ham = 0.5 * X - 1 * Z q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.rz(params[0], q[0]) qc.rx(params[1], q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) nat_grad = NaturalGradient( grad_method=method, regularization=regularization).convert(operator=op) values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] # reference values obtained by classically computing the natural gradients correct_values = [[ -3.26, 1.63 ]] if regularization == "ridge" else [[-4.24, 0]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( nat_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1) except MissingOptionalLibraryError as ex: self.skipTest(str(ex))
def test_gradient_p(self, method): """Test the state gradient for p |psi> = 1/sqrt(2)[[1, exp(ia)]] Tr(|psi><psi|Z) = 0 Tr(|psi><psi|X) = cos(a) d<H>/da = - 0.5 sin(a) """ ham = 0.5 * X - 1 * Z a = Parameter("a") params = a q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.p(a, q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.0) state_grad = Gradient(grad_method=method).convert(operator=op, params=params) values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}] correct_values = [-0.5 / np.sqrt(2), 0, -0.5] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_grad.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)