def test_log_i(self): """ MatrixOp.log_i() test """ op = (-1.052373245772859 * I ^ I) + \ (0.39793742484318045 * I ^ Z) + \ (0.18093119978423156 * X ^ X) + \ (-0.39793742484318045 * Z ^ I) + \ (-0.01128010425623538 * Z ^ Z) * np.pi/2 # Test with CircuitOp log_exp_op = op.to_matrix_op().exp_i().log_i().to_pauli_op() np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) # Test with MatrixOp log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) # Test with PauliOp log_exp_op = op.to_matrix_op().exp_i().to_pauli_op().log_i().to_pauli_op() np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) # Test with EvolvedOp log_exp_op = op.exp_i().to_pauli_op().log_i().to_pauli_op() np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix()) # Test with proper ListOp op = ListOp([(0.39793742484318045 * I ^ Z), (0.18093119978423156 * X ^ X), (-0.39793742484318045 * Z ^ I), (-0.01128010425623538 * Z ^ Z) * np.pi / 2]) log_exp_op = op.to_matrix_op().exp_i().to_matrix_op().log_i().to_pauli_op() np.testing.assert_array_almost_equal(op.to_matrix(), log_exp_op.to_matrix())
def test_not_to_matrix_called(self): """ 45 qubit calculation - literally will not work if to_matrix is somehow called (in addition to massive=False throwing an error)""" qs = 45 states_op = ListOp([Zero ^ qs, One ^ qs, (Zero ^ qs) + (One ^ qs)]) paulis_op = ListOp([Z ^ qs, (I ^ Z ^ I) ^ int(qs / 3)]) converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) np.testing.assert_array_almost_equal(converted_meas.eval(), [[1, -1, 0], [1, -1, 0]])
def test_ibmq_grouped_pauli_expectation(self): """ pauli expect op vector state vector test """ from qiskit import IBMQ p = IBMQ.load_account() backend = p.get_backend('ibmq_qasm_simulator') paulis_op = ListOp([X, Y, Z, I]) states_op = ListOp([One, Zero, Plus, Minus]) valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) sampled = CircuitSampler(backend).convert(converted_meas) np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1)
def test_listop_num_qubits(self): """Test that ListOp.num_qubits checks that all operators have the same number of qubits.""" op = ListOp([X ^ Y, Y ^ Z]) with self.subTest('All operators have the same numbers of qubits'): self.assertEqual(op.num_qubits, 2) op = ListOp([X ^ Y, Y]) with self.subTest('Operators have different numbers of qubits'): with self.assertRaises(ValueError): op.num_qubits # pylint: disable=pointless-statement with self.assertRaises(ValueError): X @ op # pylint: disable=pointless-statement
def test_pauli_expect_op_vector_state_vector(self): """ pauli expect op vector state vector test """ paulis_op = ListOp([X, Y, Z, I]) states_op = ListOp([One, Zero, Plus, Minus]) valids = [[+0, 0, 1, -1], [+0, 0, 0, 0], [-1, 1, 0, -0], [+1, 1, 1, 1]] converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) np.testing.assert_array_almost_equal(converted_meas.eval(), valids, decimal=1) sampled = self.sampler.convert(converted_meas) np.testing.assert_array_almost_equal(sampled.eval(), valids, decimal=1)
def test_summedop_equals(self): """Test SummedOp.equals """ ops = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, -1]]), Zero, Minus] sum_op = sum(ops + [ListOp(ops)]) self.assertEqual(sum_op, sum_op) self.assertEqual(sum_op + sum_op, 2 * sum_op) self.assertEqual(sum_op + sum_op + sum_op, 3 * sum_op) ops2 = [Z, CircuitOp(ZGate()), MatrixOp([[1, 0], [0, 1]]), Zero, Minus] sum_op2 = sum(ops2 + [ListOp(ops)]) self.assertNotEqual(sum_op, sum_op2) self.assertEqual(sum_op2, sum_op2) sum_op3 = sum(ops) self.assertNotEqual(sum_op, sum_op3) self.assertNotEqual(sum_op2, sum_op3) self.assertEqual(sum_op3, sum_op3)
def test_grad_combo_fn_chain_rule_nat_grad(self): """ 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)], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn) grad = NaturalGradient(grad_method='lin_comb', regularization='ridge' ).convert(grad_op, qc.ordered_parameters) value_dict = dict( zip(qc.ordered_parameters, np.random.rand(len(qc.ordered_parameters)))) correct_values = [[0.20777236], [-18.92560338], [-15.89005475], [-10.44002031]] np.testing.assert_array_almost_equal(grad.assign_parameters(value_dict).eval(), correct_values, decimal=3)
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)], combo_fn=combo_fn, grad_combo_fn=grad_combo_fn) grad = Gradient(grad_method=method).convert(grad_op, qc.ordered_parameters) 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 _run(self): """ Run the algorithm to compute up to the requested k number of eigenvalues. Returns: dict: Dictionary of results Raises: AquaError: if no operator has been provided """ if self._operator is None: raise AquaError("Operator was never provided") self._ret = {} self._solve() self._get_ground_state_energy() self._get_energies() logger.debug('NumPyEigensolver _run result:\n%s', pprint.pformat(self._ret, indent=4)) result = EigensolverResult() if 'eigvals' in self._ret: result.eigenvalues = self._ret['eigvals'] if 'eigvecs' in self._ret: result.eigenstates = ListOp( [StateFn(vec) for vec in self._ret['eigvecs']]) if 'aux_ops' in self._ret: result.aux_operator_eigenvalues = self._ret['aux_ops'] logger.debug('EigensolverResult dict:\n%s', pprint.pformat(result.data, indent=4)) return result
def invalid_input(self): """Test invalid input raises an error.""" op = Z with self.subTest('alpha < 0'): with self.assertRaises(ValueError): _ = CVaRMeasurement(op, alpha=-0.2) with self.subTest('alpha > 1'): with self.assertRaises(ValueError): _ = CVaRMeasurement(op, alpha=12.3) with self.subTest('Single pauli operator not diagonal'): op = Y with self.assertRaises(AquaError): _ = CVaRMeasurement(op) with self.subTest('Summed pauli operator not diagonal'): op = X ^ Z + Z ^ I with self.assertRaises(AquaError): _ = CVaRMeasurement(op) with self.subTest('List operator not diagonal'): op = ListOp([X ^ Z, Z ^ I]) with self.assertRaises(AquaError): _ = CVaRMeasurement(op) with self.subTest('Matrix operator not diagonal'): op = MatrixOp([[1, 1], [0, 1]]) with self.assertRaises(AquaError): _ = CVaRMeasurement(op)
def test_list_op_parameters(self): """Test that Parameters are stored correctly in a List Operator""" lam = Parameter('λ') phi = Parameter('φ') omega = Parameter('ω') mat_op = PrimitiveOp([[0, 1], [1, 0]], coeff=omega) qc = QuantumCircuit(1) qc.rx(phi, 0) qc_op = PrimitiveOp(qc) op1 = SummedOp([mat_op, qc_op]) params = [phi, omega] self.assertEqual(op1.parameters, set(params)) # check list nesting case op2 = PrimitiveOp([[1, 0], [0, -1]], coeff=lam) list_op = ListOp([op1, op2]) params.append(lam) self.assertEqual(list_op.parameters, set(params))
def grad_classify(x_list, params, class_labels): qc = deepcopy(circuit) qc_list = [] for x in x_list: parameters = {} for i, p in enumerate(feature_map.ordered_parameters): parameters[p] = x[i] circ_ = qc.assign_parameters(parameters) if sv_sim: raise TypeError( 'For now the gradient implementation only allows for Aer backends.' ) qc_list += [StateFn(circ_)] if not sv_sim: qc_list = ListOp(qc_list) grad_fn = Gradient(method='lin_comb').gradient_wrapper( qc_list, var_form.ordered_parameters, backend=qi) grad = grad_fn(params) probs = [] for grad_vec in grad: prob = [] for i, qc in enumerate(qc_list): counts = VectorStateFn(grad_vec[i]).to_dict_fn().primitive prob += [return_probabilities(counts, class_labels)] probs += [prob] return probs
def test_pauli_expect_op_vector(self): """ pauli expect op vector test """ paulis_op = ListOp([X, Y, Z, I]) converted_meas = self.expect.convert(~StateFn(paulis_op)) plus_mean = (converted_meas @ Plus) sampled_plus = self.sampler.convert(plus_mean) np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) minus_mean = (converted_meas @ Minus) sampled_minus = self.sampler.convert(minus_mean) np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) zero_mean = (converted_meas @ Zero) sampled_zero = self.sampler.convert(zero_mean) # TODO bug with Aer's Y np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 1, 1, 1], decimal=1) sum_zero = (Plus + Minus) * (.5**.5) sum_zero_mean = (converted_meas @ sum_zero) sampled_zero_mean = self.sampler.convert(sum_zero_mean) # !!NOTE!!: Depolarizing channel (Sampling) means interference # does not happen between circuits in sum, so expectation does # not equal expectation for Zero!! np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 2], decimal=1)
def test_multi_representation_ops(self): """ Test observables with mixed representations """ mixed_ops = ListOp([X.to_matrix_op(), H, H + I, X]) converted_meas = self.expect.convert(~StateFn(mixed_ops)) plus_mean = (converted_meas @ Plus) sampled_plus = self.sampler.convert(plus_mean) np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, .5**.5, (1 + .5**.5), 1], decimal=1)
def test_pauli_expect_op_vector(self): """ pauli expect op vector test """ paulis_op = ListOp([X, Y, Z, I]) converted_meas = self.expect.convert(~StateFn(paulis_op)) plus_mean = (converted_meas @ Plus) np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) sampled_plus = self.sampler.convert(plus_mean) np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) minus_mean = (converted_meas @ Minus) np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) sampled_minus = self.sampler.convert(minus_mean) np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) zero_mean = (converted_meas @ Zero) np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) sampled_zero = self.sampler.convert(zero_mean) np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) sum_zero = (Plus + Minus) * (.5**.5) sum_zero_mean = (converted_meas @ sum_zero) np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) sampled_zero_mean = self.sampler.convert(sum_zero_mean) # !!NOTE!!: Depolarizing channel (Sampling) means interference # does not happen between circuits in sum, so expectation does # not equal expectation for Zero!! np.testing.assert_array_almost_equal(sampled_zero_mean.eval(), [0, 0, 0, 2], decimal=1) for i, op in enumerate(paulis_op.oplist): mat_op = op.to_matrix() np.testing.assert_array_almost_equal( zero_mean.eval()[i], Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), decimal=1) np.testing.assert_array_almost_equal( plus_mean.eval()[i], Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), decimal=1) np.testing.assert_array_almost_equal( minus_mean.eval()[i], Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), decimal=1)
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.), ~StateFn(Z) @ CircuitStateFn(primitive=qc, coeff=1.) ], 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.], [-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 convert( self, operator: OperatorBase, params: Optional[Union[ParameterVector, ParameterExpression, List[ParameterExpression]]] = None ) -> OperatorBase: r""" Args: operator: The operator we are taking the gradient of. params: The parameters we are taking the gradient with respect to. Returns: An operator whose evaluation yields the NaturalGradient. Raises: TypeError: If ``operator`` does not represent an expectation value or the quantum state is not ``CircuitStateFn``. ValueError: If ``params`` contains a parameter not present in ``operator``. """ if not isinstance(operator[-1], CircuitStateFn): raise TypeError( 'Please make sure that the operator for which you want to compute ' 'Quantum Fisher Information represents an expectation value or a ' 'loss function and that the quantum state is given as ' 'CircuitStateFn.') if not isinstance(params, Iterable): params = [params] # Instantiate the gradient grad = Gradient(self._grad_method, epsilon=self._epsilon).convert(operator, params) # Instantiate the QFI metric which is used to re-scale the gradient metric = self._qfi_method.convert(operator[-1], params) * 0.25 # Define the function which compute the natural gradient from the gradient and the QFI. def combo_fn(x): c = np.real(x[0]) a = np.real(x[1]) if self.regularization: # If a regularization method is chosen then use a regularized solver to # construct the natural gradient. nat_grad = NaturalGradient._regularized_sle_solver( a, c, regularization=self.regularization) else: try: # Try to solve the system of linear equations Ax = C. nat_grad = np.linalg.solve(a, c) except np.linalg.LinAlgError: # singular matrix nat_grad = np.linalg.lstsq(a, c)[0] return np.real(nat_grad) # Define the ListOp which combines the gradient and the QFI according to the combination # function defined above. return ListOp([grad, metric], combo_fn=combo_fn)
def test_construction(self): """Test the correct operator expression is constructed.""" alpha = 0.5 base_expecation = PauliExpectation() cvar_expecation = CVaRExpectation(alpha=alpha, expectation=base_expecation) with self.subTest('single operator'): op = ~StateFn(Z) @ Plus expected = CVaRMeasurement(Z, alpha) @ Plus cvar = cvar_expecation.convert(op) self.assertEqual(cvar, expected) with self.subTest('list operator'): op = ~StateFn(ListOp([Z ^ Z, I ^ Z])) @ (Plus ^ Plus) expected = ListOp([ CVaRMeasurement((Z ^ Z), alpha) @ (Plus ^ Plus), CVaRMeasurement((I ^ Z), alpha) @ (Plus ^ Plus) ]) cvar = cvar_expecation.convert(op) self.assertEqual(cvar, expected)
def test_pauli_expect_state_vector(self): """ pauli expect state vector test """ states_op = ListOp([One, Zero, Plus, Minus]) paulis_op = X converted_meas = self.expect.convert(~StateFn(paulis_op) @ states_op) sampled = self.sampler.convert(converted_meas) # Small test to see if execution results are accessible for composed_op in sampled: self.assertIn('counts', composed_op[0].execution_results) np.testing.assert_array_almost_equal(sampled.eval(), [0, 0, 1, -1], decimal=1)
class TestListOpComboFn(QiskitAquaTestCase): """Test combo fn is propagated.""" def setUp(self): super().setUp() self.combo_fn = lambda x: [x_i ** 2 for x_i in x] self.listop = ListOp([X], combo_fn=self.combo_fn) def assertComboFnPreserved(self, processed_op): """Assert the quadratic combo_fn is preserved.""" x = [1, 2, 3] self.assertListEqual(processed_op.combo_fn(x), self.combo_fn(x)) def test_at_conversion(self): """Test after conversion the combo_fn is preserved.""" for method in ['to_matrix_op', 'to_pauli_op', 'to_circuit_op']: with self.subTest(method): converted = getattr(self.listop, method)() self.assertComboFnPreserved(converted) def test_after_mul(self): """Test after multiplication the combo_fn is preserved.""" self.assertComboFnPreserved(2 * self.listop) def test_at_traverse(self): """Test after traversing the combo_fn is preserved.""" def traverse_fn(op): return -op traversed = self.listop.traverse(traverse_fn) self.assertComboFnPreserved(traversed) def test_after_adjoint(self): """Test after traversing the combo_fn is preserved.""" self.assertComboFnPreserved(self.listop.adjoint()) def test_after_reduce(self): """Test after reducing the combo_fn is preserved.""" self.assertComboFnPreserved(self.listop.reduce())
def test_pauli_expect_op_vector(self): """ pauli expect op vector test """ paulis_op = ListOp([X, Y, Z, I]) converted_meas = self.expect.convert(~StateFn(paulis_op)) plus_mean = (converted_meas @ Plus) np.testing.assert_array_almost_equal(plus_mean.eval(), [1, 0, 0, 1], decimal=1) sampled_plus = self.sampler.convert(plus_mean) np.testing.assert_array_almost_equal(sampled_plus.eval(), [1, 0, 0, 1], decimal=1) minus_mean = (converted_meas @ Minus) np.testing.assert_array_almost_equal(minus_mean.eval(), [-1, 0, 0, 1], decimal=1) sampled_minus = self.sampler.convert(minus_mean) np.testing.assert_array_almost_equal(sampled_minus.eval(), [-1, 0, 0, 1], decimal=1) zero_mean = (converted_meas @ Zero) np.testing.assert_array_almost_equal(zero_mean.eval(), [0, 0, 1, 1], decimal=1) sampled_zero = self.sampler.convert(zero_mean) np.testing.assert_array_almost_equal(sampled_zero.eval(), [0, 0, 1, 1], decimal=1) sum_zero = (Plus + Minus) * (.5**.5) sum_zero_mean = (converted_meas @ sum_zero) np.testing.assert_array_almost_equal(sum_zero_mean.eval(), [0, 0, 1, 1], decimal=1) sampled_zero = self.sampler.convert(sum_zero) np.testing.assert_array_almost_equal( (converted_meas @ sampled_zero).eval(), [0, 0, 1, 1], decimal=1) for i, op in enumerate(paulis_op.oplist): mat_op = op.to_matrix() np.testing.assert_array_almost_equal( zero_mean.eval()[i], Zero.adjoint().to_matrix() @ mat_op @ Zero.to_matrix(), decimal=1) np.testing.assert_array_almost_equal( plus_mean.eval()[i], Plus.adjoint().to_matrix() @ mat_op @ Plus.to_matrix(), decimal=1) np.testing.assert_array_almost_equal( minus_mean.eval()[i], Minus.adjoint().to_matrix() @ mat_op @ Minus.to_matrix(), decimal=1)
def convert( self, operator: OperatorBase, params: Optional[Union[ParameterExpression, ParameterVector, List[ParameterExpression], Tuple[ParameterExpression, ParameterExpression], List[Tuple[ParameterExpression, ParameterExpression]]]] = None ) -> OperatorBase: """ Args: operator: The operator corresponding to our quantum state we are taking the gradient of: |ψ(ω)〉 params: The parameters we are taking the gradient wrt: ω If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, then the 1st order derivative of the operator is calculated. If a Tuple[ParameterExpression, ParameterExpression] or List[Tuple[ParameterExpression, ParameterExpression]] is given, then the 2nd order derivative of the operator is calculated. Returns: An operator corresponding to the gradient resp. Hessian. The order is in accordance with the order of the given parameters. Raises: AquaError: If the parameters are given in an invalid format. """ if isinstance(params, (ParameterExpression, ParameterVector)): return self._parameter_shift(operator, params) elif isinstance(params, tuple): return self._parameter_shift( self._parameter_shift(operator, params[0]), params[1]) elif isinstance(params, Iterable): if all(isinstance(param, ParameterExpression) for param in params): return self._parameter_shift(operator, params) elif all(isinstance(param, tuple) for param in params): return ListOp([ self._parameter_shift( self._parameter_shift(operator, pair[0]), pair[1]) for pair in params ]) else: raise AquaError( 'The linear combination gradient does only support the computation ' 'of 1st gradients and 2nd order gradients.') else: raise AquaError( 'The linear combination gradient does only support the computation ' 'of 1st gradients and 2nd order gradients.')
def test_indexing(self): """Test indexing and slicing""" coeff = 3 + .2j states_op = ListOp([X, Y, Z, I], coeff=coeff) single_op = states_op[1] self.assertIsInstance(single_op, OperatorBase) self.assertNotIsInstance(single_op, ListOp) list_one_element = states_op[1:2] self.assertIsInstance(list_one_element, ListOp) self.assertEqual(len(list_one_element), 1) self.assertEqual(list_one_element[0], Y) list_two_elements = states_op[::2] self.assertIsInstance(list_two_elements, ListOp) self.assertEqual(len(list_two_elements), 2) self.assertEqual(list_two_elements[0], X) self.assertEqual(list_two_elements[1], Z) states_op = ListOp([X, Y, Z, I], coeff=coeff) self.assertEqual(list_one_element.coeff, coeff) self.assertEqual(list_two_elements.coeff, coeff)
def test_state_hessian_custom_combo_fn(self, method): """Test the state Hessian with on an operator which includes a user-defined combo_fn. Tr(|psi><psi|Z) = sin(a)sin(b) Tr(|psi><psi|X) = cos(a) d^2<H>/da^2 = - 0.5 cos(a) + 1 sin(a)sin(b) d^2<H>/dbda = - 1 cos(a)cos(b) d^2<H>/dbda = - 1 cos(a)cos(b) d^2<H>/db^2 = + 1 sin(a)sin(b) """ ham = 0.5 * X - 1 * Z a = Parameter('a') b = Parameter('b') params = [(a, a), (a, b), (b, b)] q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) qc.rz(a, q[0]) qc.rx(b, q[0]) op = ListOp([~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.)], combo_fn=lambda x: x[0]**3 + 4 * x[0]) state_hess = Hessian(hess_method=method).convert(operator=op, params=params) values_dict = [{ a: np.pi / 4, b: np.pi }, { a: np.pi / 4, b: np.pi / 4 }, { a: np.pi / 2, b: np.pi / 4 }] correct_values = [[-1.28163104, 2.56326208, 1.06066017], [-0.04495626, -2.40716991, 1.8125], [2.82842712, -1.5, 1.76776695]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal( state_hess.assign_parameters(value_dict).eval(), correct_values[i], decimal=1)
def aux_operators(self, aux_operators: Optional[List[Optional[Union[OperatorBase, LegacyBaseOperator]]]]) -> None: """ Set aux operators """ # We need to handle the array entries being Optional i.e. having value None self._aux_op_nones = None if isinstance(aux_operators, list): self._aux_op_nones = [op is None for op in aux_operators] zero_op = I.tensorpower(self.operator.num_qubits) * 0.0 converted = [op.to_opflow() if op else zero_op for op in aux_operators] # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. converted = [zero_op if op == 0 else op for op in converted] aux_operators = ListOp(converted) elif isinstance(aux_operators, LegacyBaseOperator): aux_operators = [aux_operators.to_opflow()] self._aux_operators = aux_operators
def aux_operators(self, aux_operators: Optional[List[Optional[Union[OperatorBase, LegacyBaseOperator]]]]) -> None: """ Set aux operators """ # This is all terrible code to deal with weight 0-qubit None aux_ops. self._aux_op_nones = None if isinstance(aux_operators, list): self._aux_op_nones = [op is None for op in aux_operators] zero_op = I.tensorpower(self.operator.num_qubits) * 0.0 converted = [op.to_opflow() if op else zero_op for op in aux_operators] # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. converted = [zero_op if op == 0 else op for op in converted] aux_operators = ListOp(converted) elif isinstance(aux_operators, LegacyBaseOperator): aux_operators = [aux_operators.to_opflow()] self._aux_operators = aux_operators
def _eval_aux_ops(self, threshold=1e-12): # Create new CircuitSampler to avoid breaking existing one's caches. sampler = CircuitSampler(self.quantum_instance) aux_op_meas = self.expectation.convert(StateFn(ListOp(self.aux_operators), is_measurement=True)) aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.get_optimal_circuit())) values = np.real(sampler.convert(aux_op_expect).eval()) # Discard values below threshold aux_op_results = (values * (np.abs(values) > threshold)) # Deal with the aux_op behavior where there can be Nones or Zero qubit Paulis in the list self._ret['aux_ops'] = [None if is_none else [result] for (is_none, result) in zip(self._aux_op_nones, aux_op_results)] # As this has mixed types, since it can included None, it needs to explicitly pass object # data type to avoid numpy 1.19 warning message about implicit conversion being deprecated self._ret['aux_ops'] = np.array([self._ret['aux_ops']], dtype=object)
def convert( self, operator: OperatorBase, params: Optional[Union[ParameterVector, ParameterExpression, List[ParameterExpression]]] = None ) -> OperatorBase: r""" Args: operator: The operator we are taking the gradient of. params: The parameters we are taking the gradient with respect to. Returns: An operator whose evaluation yields the NaturalGradient. Raises: TypeError: If ``operator`` does not represent an expectation value or the quantum state is not ``CircuitStateFn``. ValueError: If ``params`` contains a parameter not present in ``operator``. """ if not isinstance(operator[-1], CircuitStateFn): raise TypeError( 'Please make sure that the operator for which you want to compute ' 'Quantum Fisher Information represents an expectation value or a ' 'loss function and that the quantum state is given as ' 'CircuitStateFn.') if not isinstance(params, Iterable): params = [params] grad = Gradient(self._grad_method, epsilon=self._epsilon).convert(operator, params) metric = self._qfi_method.convert(operator[-1], params) * 0.25 def combo_fn(x): c = np.real(x[0]) a = np.real(x[1]) if self.regularization: nat_grad = NaturalGradient._regularized_sle_solver( a, c, regularization=self.regularization) else: try: nat_grad = np.linalg.solve(a, c) except np.linalg.LinAlgError: # singular matrix nat_grad = np.linalg.lstsq(a, c)[0] return np.real(nat_grad) return ListOp([grad, metric], combo_fn=combo_fn)
def test_parameter_binding_on_listop(self): """Test passing a ListOp with differing parameters works with the circuit sampler.""" x, y = Parameter('x'), Parameter('y') circuit1 = QuantumCircuit(1) circuit1.p(0.2, 0) circuit2 = QuantumCircuit(1) circuit2.p(x, 0) circuit3 = QuantumCircuit(1) circuit3.p(y, 0) bindings = {x: -0.4, y: 0.4} listop = ListOp( [StateFn(circuit) for circuit in [circuit1, circuit2, circuit3]]) sampler = CircuitSampler(Aer.get_backend('qasm_simulator')) sampled = sampler.convert(listop, params=bindings) self.assertTrue(all(len(op.parameters) == 0 for op in sampled.oplist))
def _hessian_states( self, state_op: StateFn, meas_op: Optional[OperatorBase] = None, target_params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], List[Tuple[ParameterExpression, ParameterExpression]]]] = None ) -> OperatorBase: """Generate the operator states whose evaluation returns the Hessian (items). Args: state_op: The operator representing the quantum state for which we compute the Hessian. meas_op: The operator representing the observable for which we compute the gradient. target_params: The parameters we are computing the Hessian wrt: ω Returns: Operators which give the Hessian. If a parameter appears multiple times, one circuit is created per parameterized gates to compute the product rule. Raises: AquaError: If one of the circuits could not be constructed. TypeError: If ``operator`` is of unsupported type. """ state_qc = deepcopy(state_op.primitive) if isinstance(target_params, list) and isinstance( target_params[0], tuple): tuples_list = deepcopy(target_params) target_params = [] for tuples in tuples_list: if all([ param in state_qc._parameter_table.get_keys() for param in tuples ]): for param in tuples: if param not in target_params: target_params.append(param) elif isinstance(target_params, tuple): tuples_list = deepcopy([target_params]) target_params = [] for tuples in tuples_list: if all([ param in state_qc._parameter_table.get_keys() for param in tuples ]): for param in tuples: if param not in target_params: target_params.append(param) else: raise TypeError( 'Please define in the parameters for which the Hessian is evaluated either ' 'as parameter tuple or a list of parameter tuples') qr_add0 = QuantumRegister(1, 'work_qubit0') work_q0 = qr_add0[0] qr_add1 = QuantumRegister(1, 'work_qubit1') work_q1 = qr_add1[0] # create a copy of the original circuit with an additional working qubit register circuit = state_qc.copy() circuit.add_register(qr_add0, qr_add1) # Get the circuits needed to compute the Hessian hessian_ops = None for param_a, param_b in tuples_list: if param_a not in state_qc._parameter_table.get_keys() or param_b \ not in state_qc._parameter_table.get_keys(): hessian_op = ~Zero @ One else: param_gates_a = state_qc._parameter_table[param_a] param_gates_b = state_qc._parameter_table[param_b] for i, param_occurence_a in enumerate(param_gates_a): coeffs_a, gates_a = self._gate_gradient_dict( param_occurence_a[0])[param_occurence_a[1]] # apply Hadamard on working qubit self.insert_gate(circuit, param_occurence_a[0], HGate(), qubits=[work_q0]) self.insert_gate(circuit, param_occurence_a[0], HGate(), qubits=[work_q1]) for j, gate_to_insert_a in enumerate(gates_a): coeff_a = coeffs_a[j] hessian_circuit_temp = QuantumCircuit(*circuit.qregs) hessian_circuit_temp.data = circuit.data # Fix working qubit 0 phase sign = np.sign(coeff_a) is_complex = np.iscomplex(coeff_a) if sign == -1: if is_complex: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], SdgGate(), qubits=[work_q0]) else: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], ZGate(), qubits=[work_q0]) else: if is_complex: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], SGate(), qubits=[work_q0]) # Insert controlled, intercepting gate - controlled by |1> if isinstance(param_occurence_a[0], UGate): if param_occurence_a[1] == 0: self.insert_gate( hessian_circuit_temp, param_occurence_a[0], RZGate(param_occurence_a[0].params[2])) self.insert_gate(hessian_circuit_temp, param_occurence_a[0], RXGate(np.pi / 2)) self.insert_gate(hessian_circuit_temp, param_occurence_a[0], gate_to_insert_a, additional_qubits=([work_q0], [])) self.insert_gate(hessian_circuit_temp, param_occurence_a[0], RXGate(-np.pi / 2)) self.insert_gate( hessian_circuit_temp, param_occurence_a[0], RZGate(-param_occurence_a[0].params[2])) elif param_occurence_a[1] == 1: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], gate_to_insert_a, after=True, additional_qubits=([work_q0], [])) else: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], gate_to_insert_a, additional_qubits=([work_q0], [])) else: self.insert_gate(hessian_circuit_temp, param_occurence_a[0], gate_to_insert_a, additional_qubits=([work_q0], [])) for m, param_occurence_b in enumerate(param_gates_b): coeffs_b, gates_b = self._gate_gradient_dict( param_occurence_b[0])[param_occurence_b[1]] for n, gate_to_insert_b in enumerate(gates_b): coeff_b = coeffs_b[n] # create a copy of the original circuit with the same registers hessian_circuit = QuantumCircuit( *hessian_circuit_temp.qregs) hessian_circuit.data = hessian_circuit_temp.data # Fix working qubit 1 phase sign = np.sign(coeff_b) is_complex = np.iscomplex(coeff_b) if sign == -1: if is_complex: self.insert_gate(hessian_circuit, param_occurence_b[0], SdgGate(), qubits=[work_q1]) else: self.insert_gate(hessian_circuit, param_occurence_b[0], ZGate(), qubits=[work_q1]) else: if is_complex: self.insert_gate(hessian_circuit, param_occurence_b[0], SGate(), qubits=[work_q1]) # Insert controlled, intercepting gate - controlled by |1> if isinstance(param_occurence_b[0], UGate): if param_occurence_b[1] == 0: self.insert_gate( hessian_circuit, param_occurence_b[0], RZGate(param_occurence_b[0]. params[2])) self.insert_gate( hessian_circuit, param_occurence_b[0], RXGate(np.pi / 2)) self.insert_gate( hessian_circuit, param_occurence_b[0], gate_to_insert_b, additional_qubits=([work_q1], [])) self.insert_gate( hessian_circuit, param_occurence_b[0], RXGate(-np.pi / 2)) self.insert_gate( hessian_circuit, param_occurence_b[0], RZGate(-param_occurence_b[0]. params[2])) elif param_occurence_b[1] == 1: self.insert_gate( hessian_circuit, param_occurence_b[0], gate_to_insert_b, after=True, additional_qubits=([work_q1], [])) else: self.insert_gate( hessian_circuit, param_occurence_b[0], gate_to_insert_b, additional_qubits=([work_q1], [])) else: self.insert_gate( hessian_circuit, param_occurence_b[0], gate_to_insert_b, additional_qubits=([work_q1], [])) hessian_circuit.h(work_q0) hessian_circuit.cz(work_q1, work_q0) hessian_circuit.h(work_q1) term = state_op.coeff * np.sqrt(np.abs(coeff_a) * np.abs(coeff_b)) \ * CircuitStateFn(hessian_circuit) # Chain Rule Parameter Expression gate_param_a = param_occurence_a[0].params[ param_occurence_a[1]] gate_param_b = param_occurence_b[0].params[ param_occurence_b[1]] if meas_op: meas = deepcopy(meas_op) if isinstance(gate_param_a, ParameterExpression): expr_grad = DerivativeBase.parameter_expression_grad( gate_param_a, param_a) meas *= expr_grad if isinstance(gate_param_b, ParameterExpression): expr_grad = DerivativeBase.parameter_expression_grad( gate_param_a, param_a) meas *= expr_grad term = meas @ term else: term = ListOp([term], combo_fn=partial( self._hess_combo_fn, state_op=state_op)) if isinstance(gate_param_a, ParameterExpression): expr_grad = DerivativeBase.parameter_expression_grad( gate_param_a, param_a) term *= expr_grad if isinstance(gate_param_b, ParameterExpression): expr_grad = DerivativeBase.parameter_expression_grad( gate_param_a, param_a) term *= expr_grad if i == 0 and j == 0 and m == 0 and n == 0: hessian_op = term else: # Product Rule hessian_op += term # Create a list of Hessian elements w.r.t. the given parameter tuples if len(tuples_list) == 1: return hessian_op else: if not hessian_ops: hessian_ops = [hessian_op] else: hessian_ops += [hessian_op] return ListOp(hessian_ops)