def expectation_value(state_circuit, operator, backend, n_shots): if backend.supports_expectation: compiled_circuit = state_circuit.copy() backend.compile_circuit(compiled_circuit) return backend.get_operator_expectation_value( compiled_circuit, QubitPauliOperator.from_OpenFermion(operator)) elif backend.supports_shots: syn_res_index = state_circuit.bit_readout[syn_res] pauli_circuits, coeffs, energy = gen_pauli_measurement_circuits( state_circuit, compiler_pass(backend), operator) handles = backend.process_circuits(pauli_circuits, n_shots=n_shots) for handle, coeff in zip(handles, coeffs): res = backend.get_result(handle) filtered = filter_shots(res, syn_res) energy += coeff * expectation_from_shots(filtered) backend.pop_result(handle) return energy else: raise NotImplementedError( "Implementation for state and counts to be written")
def test_statevector_expectation() -> None: hamiltonian = QubitOperator() hamiltonian.terms = { (): 0.08406444459465776, ((0, "Z"), ): 0.17218393261915543, ((1, "Z"), ): 0.17218393261915546, ((0, "Z"), (1, "Z")): 0.16892753870087912, ((0, "Y"), (1, "X"), (2, "Y")): 0.04523279994605785, ((0, "X"), (1, "X"), (2, "X")): (0.04523279994605785), ((0, "Y"), (1, "Y"), (2, "X")): -0.04523279994605785, ((0, "X"), (1, "Y"), (2, "Y")): (0.04523279994605785), ((2, "Z"), ): -0.45150698444804915, ((0, "Z"), (2, "Z")): 0.2870580651815905, ((1, "Z"), (2, "Z")): 0.2870580651815905, } target = eigenspectrum(hamiltonian)[0] circ = h2_3q_circ(PARAM) for b in backends: b.compile_circuit(circ) energy = b.get_operator_expectation_value( circ, QubitPauliOperator.from_OpenFermion(hamiltonian)) assert np.isclose(energy, target)
0.12062523483390425 * QubitOperator("Z0 Z2") + 0.16592785033770352 * QubitOperator("Z0 Z3") + 0.17141282644776884 * QubitOperator("Z1") + 0.16592785033770352 * QubitOperator("Z1 Z2") + 0.12062523483390425 * QubitOperator("Z1 Z3") + -0.22343153690813597 * QubitOperator("Z2") + 0.17441287612261608 * QubitOperator("Z2 Z3") + -0.22343153690813597 * QubitOperator("Z3")) # We can simulate this exactly using a statevector simulator like ProjectQ. This has a built-in method for fast calculations of expectation values that works well for small examples like this. from pytket.extensions.projectq import ProjectQBackend backend = ProjectQBackend() ideal_energy = backend.get_operator_expectation_value( ansatz, QubitPauliOperator.from_OpenFermion(hamiltonian)) print(ideal_energy) # Ideally the state generated by this ansatz will only span the computational basis states with exactly two of the four qubits in state $\lvert 1 \rangle$. This is because these basis states correspond to two electrons being present in the molecule. # # This ansatz is a hardware-efficient model that is designed to explore a large portion of the Hilbert space with relatively few entangling gates. Unfortunately, with this much freedom, it will regularly generate states that have no physical interpretation such as states spanning multiple basis states corresponding to different numbers of electrons in the system (which we assume is fixed and conserved). # # We can mitigate this by using a syndrome qubit that calculates the parity of the other qubits. Post-selecting this syndrome with $\langle 0 \rvert$ will project the remaining state onto the subspace of basis states with even parity, increasing the likelihood the observed state will be a physically admissible state. # # Even if the ansatz parameters are tuned to give a physical state, real devices have noise and imperfect gates, so in practice we may also measure bad states with a small probability. If this syndrome qubit is measured as 1, it means an error has definitely occurred, so we should discard the shot. syn = Qubit("synq", 0) syn_res = Bit("synres", 0) ansatz.add_qubit(syn) ansatz.add_bit(syn_res) for qb in qubits:
def ucc(params): ansatz = Circuit(4) ansatz.X(1).X(3) add_excitation(ansatz, singles_a, params[0]) add_excitation(ansatz, singles_b, params[1]) add_excitation(ansatz, doubles, params[2]) DecomposeBoxes().apply(ansatz) return ansatz # The objective function can also be simplified using a utility method for constructing the measurement circuits and processing for expectation value calculations. from pytket.utils.operators import QubitPauliOperator from pytket.utils import get_operator_expectation_value hamiltonian_op = QubitPauliOperator.from_OpenFermion(hamiltonian) # Simplified objective function using utilities: def objective(params): circ = ucc(params) return (get_operator_expectation_value( circ, hamiltonian_op, backend, n_shots=4000) + nuclear_repulsion_energy) arg_values = [-3.79002933e-05, 2.42964799e-05, 4.63447157e-01] energy = objective(arg_values) print(energy)