Fermion-qubit mappings
In order to simulate the dynamics of physical systems with a quantum computer, it is necessary to map the Hamiltonian of an electron to the qubit counterpart. Hamiltonians for fermionic systems, as typically used in quantum chemistry, are often expressed using anti-commuting creation and annihilation operators: , under second quantization. If we can rewrite the creation and annihilation operators as Pauli operators that can act on qubits, we can represent them on a quantum computer.
Here, , satisfy the anti-commutation relations:
and , denote the label of degree of freedom the operator acts on.
Fermionic wavefunctions exhibit antisymmetry, but when mapping directly from spin orbitals to qubits on a quantum computer, where the presence of an electron in a spin orbital is represented as and the absence as , this antisymmetry is not maintained. This discrepancy arises because electrons are indistinguishable particles, whereas qubits are distinguishable. To correctly emulate the behavior of fermions, several mapping techniques have been developed that preserve the necessary anti-commutation relations.
In this tutorial, we explain how to perform mapping from OpenFermion
's FermionOperator
to QURI Parts Operator
, where we provide 3 types of mapping:
- Jordan-Wigner mapping
- Bravyi-Kitaev mapping
- Symmetry-conserving Bravyi-Kitaev mapping
Prerequisite
QURI Parts modules used in this tutorial: quri-parts-core
and quri-parts-openfermion
. You can install them as follows:
!pip install "quri_parts[openfermion]"
Overview
Here we set up a Fermi-Hubbard hamiltonian for demonstration:
from openfermion import fermi_hubbard
n_site = 2
hamiltonian = fermi_hubbard(x_dimension=n_site, y_dimension=1, tunneling=1, coulomb=2)
print("Fermi-Hubbard Hamiltonian:")
print(hamiltonian)
#output
Fermi-Hubbard Hamiltonian:
2.0 [0^ 0 1^ 1] +
-1.0 [0^ 2] +
-1.0 [1^ 3] +
-1.0 [2^ 0] +
2.0 [2^ 2 3^ 3] +
-1.0 [3^ 1]
where ^ denotes and denotes in [・]. For example, [0^ 2] denotes . Note that this is a Hamiltonian written in the form of second quantization.
In QURI Parts, we provide mapping objects that generates:
-
OpenFermion
operator mapper: a function that mapsopenfermion.ops.FermionOperator
openfermion.ops.InteractionOperator
openfermion.ops.MajoranaOperator
to QURI Parts
Operator
. -
state mapper: A function that maps the occupation number state:
to a
ComputationalBasisState
. -
inverse state mapper: A function that maps a
ComputationalBasisState
to the occupation number state.
We use Jordan-Wigner mapping as an example. The steps of obtaining the mappers in QURI Parts are:
- Create a mapping object.
- Retrieve the mappers by accessing corresponding properties.
We first create a mapping object that performs Jordan-Wigner mapping for a system with 4 spin orbitals.
from quri_parts.openfermion.transforms import jordan_wigner
jw_mapping = jordan_wigner(n_spin_orbitals=2*n_site)
Map a Fermion operator to qubit operator
operator_mapper = jw_mapping.of_operator_mapper
qubit_hamiltonian = operator_mapper(hamiltonian)
print(qubit_hamiltonian)
#output
(-0.5+0j)*Y0 Z1 Y2 + (-0.5+0j)*X0 Z1 X2 + (-0.5+0j)*Y1 Z2 Y3 + (-0.5+0j)*X1 Z2 X3 + (1+0j)*I + (-0.5+0j)*Z1 + (-0.5+0j)*Z0 + (0.5+0j)*Z0 Z1 + (-0.5+0j)*Z3 + (-0.5+0j)*Z2 + (0.5+0j)*Z2 Z3
Map an occupation state to a ComputationalBasisState
Let's look at what the occupation state is mapped to under Jordan-Wigner mapping.
state_mapper = jw_mapping.state_mapper
occ_state = [0, 3]
cb_state = state_mapper(occ_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
print("ComputationalBasisState:", cb_state)
print("State preparation circuit:")
for g in cb_state.circuit.gates:
print(g)
#output
Occupation state: |0, 3>
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1001, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(3,), control_indices=(), controlled_on=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
Map a ComputationalBasisState
to an occupation state
We look at what the computational basis state is mapped to under Jordan-Wigner mapping.
from quri_parts.core.state import quantum_state
inv_state_mapper = jw_mapping.inv_state_mapper
cb_state = quantum_state(n_qubits=2*n_site, bits=0b1011)
occ_state = inv_state_mapper(cb_state)
print("ComputationalBasisState:", cb_state)
print("Occupation state:", "|" + ", ".join(map(str, occ_state)) + ">")
#output
ComputationalBasisState: ComputationalBasisState(qubit_count=4, bits=0b1011, phase=0π/2)
Occupation state: |0, 1, 3>