def test_integration(self, C): """Test expected serialization for a random circuit""" with qml.tape.QuantumTape() as tape: qml.RX(0.4, wires=0) qml.RY(0.6, wires=1).inv().inv() qml.CNOT(wires=[0, 1]) qml.QubitUnitary(np.eye(4), wires=[0, 1]) qml.templates.QFT(wires=[0, 1, 2]).inv() qml.DoubleExcitation(0.555, wires=[3, 2, 1, 0]) qml.DoubleExcitationMinus(0.555, wires=[0, 1, 2, 3]) qml.DoubleExcitationPlus(0.555, wires=[0, 1, 2, 3]) s = _serialize_ops(tape, self.wires_dict) dtype = np.complex64 if C else np.complex128 s_expected = ( ( [ "RX", "RY", "CNOT", "QubitUnitary", "QFT", "DoubleExcitation", "DoubleExcitationMinus", "DoubleExcitationPlus", ], [[0.4], [0.6], [], [], [], [0.555], [0.555], [0.555]], [[0], [1], [0, 1], [0, 1], [0, 1, 2], [3, 2, 1, 0], [0, 1, 2, 3], [0, 1, 2, 3]], [False, False, False, False, False, False, False, False], [ [], [], [], qml.matrix(qml.QubitUnitary(np.eye(4, dtype=dtype), wires=[0, 1])), qml.matrix(qml.templates.QFT(wires=[0, 1, 2]).inv()), [], [], [], ], ), False, ) assert s[0][0] == s_expected[0][0] assert s[0][1] == s_expected[0][1] assert s[0][2] == s_expected[0][2] assert s[0][3] == s_expected[0][3] assert s[1] == s_expected[1] assert all(np.allclose(s1, s2) for s1, s2 in zip(s[0][4], s_expected[0][4]))
def _serialize_ops( tape: QuantumTape, wires_map: dict ) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]: """Serializes the operations of an input tape. The state preparation operations are not included. Args: tape (QuantumTape): the input quantum tape wires_map (dict): a dictionary mapping input wires to the device's backend wires Returns: Tuple[list, list, list, list, list]: A serialization of the operations, containing a list of operation names, a list of operation parameters, a list of observable wires, a list of inverses, and a list of matrices for the operations that do not have a dedicated kernel. """ names = [] params = [] wires = [] inverses = [] mats = [] uses_stateprep = False for o in tape.operations: if isinstance(o, (BasisState, QubitStateVector)): uses_stateprep = True continue elif isinstance(o, Rot): op_list = o.expand().operations else: op_list = [o] for single_op in op_list: is_inverse = single_op.inverse name = single_op.name if not is_inverse else single_op.name[:-4] names.append(name) if not hasattr(StateVectorC128, name): params.append([]) mats.append(qml.matrix(single_op)) if is_inverse: is_inverse = False else: params.append(single_op.parameters) mats.append([]) wires_list = single_op.wires.tolist() wires.append([wires_map[w] for w in wires_list]) inverses.append(is_inverse) return (names, params, wires, inverses, mats), uses_stateprep
def apply_lightning(self, state, operations): """Apply a list of operations to the state tensor. Args: state (array[complex]): the input state tensor operations (list[~pennylane.operation.Operation]): operations to apply dtype (type): Type of numpy ``complex`` to be used. Can be important to specify for large systems for memory allocation purposes. Returns: array[complex]: the output state tensor """ state_vector = np.ravel(state) if self.use_csingle: # use_csingle sim = StateVectorC64(state_vector) else: # self.C_DTYPE is np.complex128 by default sim = StateVectorC128(state_vector) # Skip over identity operations instead of performing # matrix multiplication with the identity. skipped_ops = ["Identity"] for o in operations: if o.base_name in skipped_ops: continue name = o.name.split(".")[ 0] # The split is because inverse gates have .inv appended method = getattr(sim, name, None) wires = self.wires.indices(o.wires) if method is None: # Inverse can be set to False since qml.matrix(o) is already in inverted form method = getattr(sim, "applyMatrix") try: method(qml.matrix(o), wires, False) except AttributeError: # pragma: no cover # To support older versions of PL method(o.matrix, wires, False) else: inv = o.inverse param = o.parameters method(wires, inv, param) return np.reshape(state_vector, state.shape)
def _assert_decomposition(pl_op, params=None): num_wires = pl_op.num_wires dimension = 2**num_wires num_indices = 2 * num_wires wires = list(range(num_wires)) contraction_parameters = [ np.reshape(np.eye(dimension), [2] * num_indices), list(range(num_indices)), ] index_substitutions = {i: i + num_wires for i in range(num_wires)} next_index = num_indices # get decomposed gates decomposed_op = (pl_op.compute_decomposition(*params, wires=wires) if params else pl_op.compute_decomposition(wires=wires)) # Heterogeneous matrix chain multiplication using tensor contraction for gate in reversed(decomposed_op): gate_wires = gate.wires.tolist() # Upper indices, which will be traced out contravariant = [index_substitutions[i] for i in gate_wires] # Lower indices, which will replace the contracted indices in the matrix covariant = list(range(next_index, next_index + len(gate_wires))) indices = contravariant + covariant # `qml.matrix(gate)` as type-(len(contravariant), len(covariant)) tensor gate_tensor = np.reshape(qml.matrix(gate), [2] * len(indices)) contraction_parameters += [gate_tensor, indices] next_index += len(gate_wires) index_substitutions.update( {gate_wires[i]: covariant[i] for i in range(len(gate_wires))}) # Ensure matrix is in the correct order new_indices = wires + [index_substitutions[i] for i in range(num_wires)] contraction_parameters.append(new_indices) actual_matrix = np.reshape(np.einsum(*contraction_parameters), [dimension, dimension]) pl_op_matrix = pl_op.compute_matrix( *params) if params else pl_op.compute_matrix() assert np.allclose(actual_matrix, pl_op_matrix)
def _serialize_named_hermitian_ob(o, wires_map: dict, use_csingle: bool): """Serializes an observable (Named or Hermitian)""" assert not isinstance(o, Tensor) if use_csingle: ctype = np.complex64 named_obs = NamedObsC64 hermitian_obs = HermitianObsC64 else: ctype = np.complex128 named_obs = NamedObsC128 hermitian_obs = HermitianObsC128 wires_list = o.wires.tolist() wires = [wires_map[w] for w in wires_list] if _obs_has_kernel(o): return named_obs(o.name, wires) return hermitian_obs(qml.matrix(o).ravel().astype(ctype), wires)
def test_gate_unitary_correct(op, op_name): """Test if lightning.qubit correctly applies gates by reconstructing the unitary matrix and comparing to the expected version""" if op_name in ("BasisState", "QubitStateVector"): pytest.skip("Skipping operation because it is a state preparation") if op_name in ( "ControlledQubitUnitary", "QubitUnitary", "MultiControlledX", "DiagonalQubitUnitary", ): pytest.skip("Skipping operation.") # These are tested in the device test-suite if op == None: pytest.skip("Skipping operation.") wires = op.num_wires if wires == -1: # This occurs for operations that do not have a predefined number of wires wires = 4 dev = qml.device("lightning.qubit", wires=wires) num_params = op.num_params p = [0.1] * num_params op = getattr(qml, op_name) @qml.qnode(dev) def output(input): qml.BasisState(input, wires=range(wires)) op(*p, wires=range(wires)) return qml.state() unitary = np.zeros((2**wires, 2**wires), dtype=np.complex128) for i, input in enumerate(itertools.product([0, 1], repeat=wires)): out = output(np.array(input)) unitary[:, i] = out unitary_expected = qml.matrix(op(*p, wires=range(wires))) assert np.allclose(unitary, unitary_expected)
def test_gate_generator(pl_op, braket_gate, angle): op = pl_op(angle, wires=[0, 1]) if op.name != "PSWAP": assert np.allclose( qml.matrix(op), scipy.linalg.expm(1j * angle * qml.matrix(op.generator())))
def test_gate_adjoint_non_parametrized(pl_op, braket_gate): op = pl_op(wires=[0, 1]) assert np.allclose( np.dot(pl_op.compute_matrix(), qml.matrix(pl_op.adjoint(op))), np.identity(4))
def _(h: qml.Hermitian): return observables.Hermitian(qml.matrix(h))
def qubit_operator_string(self, observable): """Serializes a PennyLane observable to a string compatible with the openfermion.QubitOperator class. This method decomposes an observable into a sum of Pauli terms and identities, if needed. **Example** >>> dev = QeQiskitDevice(wires=2) >>> obs = qml.PauliZ(0) >>> op_str = dev.qubit_operator_string(obs) >>> print(op_str) 1 [Z0] >>> obs = qml.Hadamard(0) >>> op_str = dev.qubit_operator_string(obs) >>> print(op_str) 0.7071067811865475 [X0] + 0.7071067811865475 [Z0] Args: observable (pennylane.operation.Observable): the observable to serialize Returns: str: the ``openfermion.QubitOperator`` string representation """ accepted_obs = {"PauliX", "PauliY", "PauliZ", "Identity"} if isinstance(observable, Tensor): need_decomposition = any(o.name not in accepted_obs for o in observable.obs) else: need_decomposition = observable.name not in accepted_obs if need_decomposition: original_observable = observable # Decompose the matrix of the observable # This removes information about the wire labels used and # consecutive integer wires are used coeffs, obs_list = decompose_hamiltonian( matrix(original_observable)) for idx in range(len(obs_list)): obs = obs_list[idx] if not isinstance(obs, Tensor): # Convert terms to Tensor such that _terms_to_qubit_operator # can be used obs_list[idx] = Tensor(obs) # Need to use the custom wire labels of the original observable original_wires = original_observable.wires.tolist() for o, mapped_w in zip(obs_list[idx].obs, original_wires): o._wires = Wires(mapped_w) else: if not isinstance(observable, Tensor): # If decomposition is not needed and is not a Tensor, we need # to convert the single observable observable = Tensor(observable) coeffs = [1] obs_list = [observable] # Use consecutive integers as default wire_map wire_map = {v: idx for idx, v in enumerate(self.wires)} return _terms_to_qubit_operator_string(coeffs, obs_list, wires=wire_map)
# Rotations need to be included (qml.PauliY(2), "[Z2]"), (qml.PauliX(0) @ qml.PauliY(2), "[Z0 Z2]"), (qml.PauliY(0) @ qml.PauliX(1) @ qml.PauliY(2), "[Z0 Z1 Z2]"), ] # More advanced examples for testing the correct wires obs_decomposed_wires_check = [ (qml.Hadamard(0), "0.7071067811865475 [X0] + 0.7071067811865475 [Z0]"), (qml.Hadamard(2), "0.7071067811865475 [X2] + 0.7071067811865475 [Z2]"), ( qml.Hadamard(2) @ qml.Hadamard(1), "0.4999999999999999 [X2 X1] + 0.4999999999999999 [X2 Z1] + 0.4999999999999999 [Z2 X1] + 0.4999999999999999 [Z2 Z1]", ), ( qml.Hermitian(qml.matrix( qml.PauliY(1) @ qml.PauliX(0) @ qml.PauliZ(2)), wires=[1, 0, 2]), "1.0 [Y1 X0 Z2]", ), (qml.PauliY(2) @ qml.Hermitian(qml.matrix(qml.PauliX(1)), wires=[1]), "1.0 [Y2 X1]"), ] class TestSerializeOperator: """Test the serialize_operator function""" @pytest.mark.parametrize("wires, expected", [([2], "[Z2]"), ([0, 2], "[Z0 Z2]"), ([0, 1, 2], "[Z0 Z1 Z2]")]) def test_pauliz_operator_string(self, wires, expected): """Test that an operator is serialized correctly on a device with