def test_wrong_shape(self, hamiltonian): """Tests that an exception is raised if the Hamiltonian does not have the correct shape""" with pytest.raises( ValueError, match="The Hamiltonian should have shape", ): pu.decompose_hamiltonian(hamiltonian)
def test_decomposition(self, hamiltonian): """Tests that decompose_hamiltonian successfully decomposes Hamiltonians into a linear combination of Pauli matrices""" decomposed_coeff, decomposed_obs = pu.decompose_hamiltonian(hamiltonian) linear_comb = sum([decomposed_coeff[i] * o.matrix for i, o in enumerate(decomposed_obs)]) assert np.allclose(hamiltonian, linear_comb)
def test_observable_types(self, hamiltonian, hide_identity): """Tests that the Hamiltonian decomposes into a linear combination of tensors, the identity matrix, and Pauli matrices.""" allowed_obs = (Tensor, Identity, PauliX, PauliY, PauliZ) decomposed_coeff, decomposed_obs = pu.decompose_hamiltonian(hamiltonian, hide_identity) assert all([isinstance(o, allowed_obs) for o in decomposed_obs])
def test_result_length(self, hamiltonian): """Tests that tensors are composed of a number of terms equal to the number of qubits.""" decomposed_coeff, decomposed_obs = pu.decompose_hamiltonian(hamiltonian) n = int(np.log2(len(hamiltonian))) tensors = filter(lambda obs: isinstance(obs, Tensor), decomposed_obs) assert all(len(tensor.obs) == n for tensor in tensors)
def test_hide_identity_true(self): """Tests that there are no Identity observables in the tensor products when hide_identity=True""" H = np.array(np.diag([0, 0, 0, 1])) coeff, obs_list = pu.decompose_hamiltonian(H, hide_identity=True) tensors = filter(lambda obs: isinstance(obs, Tensor), obs_list) for tensor in tensors: all_identities = all(isinstance(o, Identity) for o in tensor.obs) no_identities = not any(isinstance(o, Identity) for o in tensor.obs) assert all_identities or no_identities
def test_decomp(self, H): """Tests that decompose_hamiltonian successfully decomposes Hamiltonians into Pauli matrices""" N = int(np.log2(len(H))) coeff, combs = pu.decompose_hamiltonian(H) result = np.zeros((2**N, 2**N)) matrices = [] for term in combs: base = [qml.Identity] * N if term.num_wires == N: matrices.append(term.matrix) else: base[term.wires[0]] = term.__class__ matrices.append( functools.reduce(operator.matmul, [t(i) for i, t in enumerate(base)]).matrix) for term in zip(coeff, matrices): result = result + term[0] * term[1] assert (H == result).all
def test_not_hermitian(self): """Tests that an exception is raised if the Hamiltonian is not Hermitian, i.e. equal to its own conjugate transpose""" with pytest.raises(ValueError, match="The Hamiltonian is not Hermitian"): pu.decompose_hamiltonian(np.array([[1, 2], [3, 4]]))
def find_excited_states(H): """ Fill in the missing parts between the # QHACK # markers below. Implement a variational method that can find the three lowest energies of the provided Hamiltonian. Args: H (qml.Hamiltonian): The input Hamiltonian Returns: The lowest three eigenenergies of the Hamiltonian as a comma-separated string, sorted from smallest to largest. """ energies = np.zeros(3) # QHACK # w1 = range(len(H.wires)) w2 = range(len(H.wires)) dev = qml.device('default.qubit', wires=H.wires) dev1 = qml.device('default.qubit', wires=w1) dev2 = qml.device('default.qubit', wires=w2) def variational_ansatz0(params, wires): params = params[1:] n_qubits = len(wires) n_rotations = len(params) if n_rotations > 1: n_layers = n_rotations // n_qubits n_extra_rots = n_rotations - n_layers * n_qubits for layer_idx in range(n_layers): layer_params = params[layer_idx * n_qubits : layer_idx * n_qubits + n_qubits, :] qml.broadcast(qml.Rot, wires, pattern="single", parameters=layer_params) qml.broadcast(qml.CNOT, wires, pattern="ring") extra_params = params[-n_extra_rots:, :] extra_wires = wires[: n_qubits - 1 - n_extra_rots : -1] qml.broadcast(qml.Rot, extra_wires, pattern="single", parameters=extra_params) else: qml.Rot(*params[0], wires=wires[0]) def variational_ansatz1(params, wires): rot_params = params[0] params = params[1:] n_qubits = len(wires) n_rotations = len(params) qml.PauliX(wires=0) qml.RY(rot_params[1], wires=2) if n_rotations > 1: n_layers = n_rotations // n_qubits n_extra_rots = n_rotations - n_layers * n_qubits for layer_idx in range(n_layers): layer_params = params[layer_idx * n_qubits : layer_idx * n_qubits + n_qubits, :] qml.broadcast(qml.Rot, wires, pattern="single", parameters=layer_params) qml.broadcast(qml.CNOT, wires, pattern="ring") extra_params = params[-n_extra_rots:, :] extra_wires = wires[: n_qubits - 1 - n_extra_rots : -1] qml.broadcast(qml.Rot, extra_wires, pattern="single", parameters=extra_params) else: qml.Rot(*params[0], wires=wires[0]) @qml.qnode(dev1) def get_state(params, wires=w2): n_qubits = len(wires) n_rotations = len(params) if n_rotations > 1: n_layers = n_rotations // n_qubits n_extra_rots = n_rotations - n_layers * n_qubits for layer_idx in range(n_layers): layer_params = params[layer_idx * n_qubits : layer_idx * n_qubits + n_qubits, :] qml.broadcast(qml.Rot, wires, pattern="single", parameters=layer_params) qml.broadcast(qml.CNOT, wires, pattern="ring") extra_params = params[-n_extra_rots:, :] extra_wires = wires[: n_qubits - 1 - n_extra_rots : -1] qml.broadcast(qml.Rot, extra_wires, pattern="single", parameters=extra_params) else: qml.Rot(*params[0], wires=wires[0]) return qml.state() num_qubits = len(H.wires) num_param_sets = (2 ** num_qubits) - 1 energy = 0 cost_fn = qml.ExpvalCost(variational_ansatz0, H, dev) opt = qml.AdamOptimizer(stepsize=0.1) params = np.random.normal(0, np.pi, (num_param_sets+1, 3)) conv_tol = 1e-08 max_iterations = 400 for n in range(max_iterations): params, prev_energy = opt.step_and_cost(cost_fn, params) energy = cost_fn(params) conv = np.abs(energy - prev_energy) if conv <= conv_tol: break energies[0] = energy state_1 = get_state(params) print(state_1) additional_term = np.outer(state_1, state_1) * 3 coeffs, ops = decompose_hamiltonian(additional_term) new_coeffs = (H.coeffs) for c in coeffs: new_coeffs.append(c) new_ops = (H.ops) for o in ops: new_ops.append(o) H1 = qml.Hamiltonian(new_coeffs, new_ops) energy1 = 0 cost_fn_1 = qml.ExpvalCost(variational_ansatz, H1, dev) params1 = np.random.normal(0, np.pi, (num_param_sets, 3)) for n in range(max_iterations): params1, prev_energy = opt.step_and_cost(cost_fn_1, params1) energy1 = cost_fn_1(params1) conv = np.abs(energy1 - prev_energy) if conv <= conv_tol: break energies[1] = energy1 # QHACK # return ",".join([str(E) for E in energies])
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)