Fermion-qubit mappings#
In this tutorial, we explain how to perform mapping from fermionic operators to qubit operators with QURI Parts, 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-chem
, quri-parts-pyscf
, and quri-parts-openfermion
. You can install them as follows:
[1]:
!pip install "quri_parts[chem]"
!pip install "quri_parts[pyscf]"
!pip install "quri_parts[openfermion]"
Getting the Electron integrals and the Hamiltonian#
We first prepare a fermionic Hamiltonian for later demonstration. For generating the molecular Hamiltonian, please refer to the Hamiltonian generation tutorial.
[2]:
from pyscf import gto, scf
from quri_parts.pyscf.mol import get_spin_mo_integrals_from_mole
from quri_parts.openfermion.mol import get_fermionic_hamiltonian
h2_atom_list = [['H', [0, 0, 0]], ['H', [0, 0, 2]]]
h2_mol = gto.M(atom=h2_atom_list, verbose = 0)
h2_mf = scf.RHF(h2_mol).run()
_, mo_eint_set = get_spin_mo_integrals_from_mole(h2_mol, h2_mf.mo_coeff)
fermionic_hamiltonian = get_fermionic_hamiltonian(mo_eint_set)
Convert Openfermion Operator to Quri-Parts Operator#
Here, we demonstrate how to convert OpenFermion’s operator to QURI Parts Operator
. For example, given the fermionic Hamiltonian we obtained above, we use OpenFermion’s jordan_wigner
function to map it into a QubitOperator
and then convert it into QURI Parts Operator
with the operator_from_openfermion_op
function we provide.
[3]:
from quri_parts.openfermion.operator import operator_from_openfermion_op
from openfermion import jordan_wigner as of_jordan_wigner
qubit_operator = of_jordan_wigner(fermionic_hamiltonian)
qp_operator = operator_from_openfermion_op(qubit_operator)
print(qp_operator)
(-0.5339363487727398+0j)*I + (0.06727930458983411+0j)*Z0 + (0.06727930458983411+0j)*Z1 + (0.006651295687574416+0j)*Z2 + (0.006651295687574416+0j)*Z3 + (0.1273657031065746+0j)*Z0 Z1 + (0.06501569581211997+0j)*Z0 Z2 + (0.12980031453238416+0j)*Z0 Z3 + (0.12980031453238416+0j)*Z1 Z2 + (0.06501569581211997+0j)*Z1 Z3 + (0.13366602988233997+0j)*Z2 Z3 + -0.06478461872026421*X0 X1 Y2 Y3 + 0.06478461872026421*X0 Y1 Y2 X3 + 0.06478461872026421*Y0 X1 X2 Y3 + -0.06478461872026421*Y0 Y1 X2 X3
This method of obtaining the Operator
is a bit cumbersome and we are difficult to obtain mapped states with it. Thus, we introduce several OpenFermionQubitMapping
objects below showing how to map OpenFermion’s operators and occupation states into QURI Parts Operator
and ComputationBasisState
.
Jordan-Wigner Mapping#
QURI Parts provides Jordan-Wigner mapping that can convert OpenFermion’s operators to QURI Parts Operator
and fermionic states into a ComputationalBasisState
.
The jordan_wigner.get_of_operator_mapper
returns a function that maps
openfermion.ops.FermionOperator
openfermion.ops.InteractionOperator
openfermion.ops.MajoranaOperator
to QURI Parts Operator
. Here, we use the hamiltonian we defined above, which is an InteractionOperator
object, to demonstrate how to obtain the Jordan-Wigner Hamiltonian written in terms of QURI Parts Operators
.
[4]:
from quri_parts.openfermion.transforms import jordan_wigner
# Obtaining the operator mapper
operator_mapper = jordan_wigner.get_of_operator_mapper()
# Map the Hamiltonian into quri-parts Operators with Jordan-Wigner mapping
jordan_wigner_hamiltonian = operator_mapper(fermionic_hamiltonian)
for op, coeff in jordan_wigner_hamiltonian.items():
print(coeff.round(10), op)
(-0.5339363488+0j) I
(0.0672793046+0j) Z0
(0.0672793046+0j) Z1
(0.0066512957+0j) Z2
(0.0066512957+0j) Z3
(0.1273657031+0j) Z0 Z1
(0.0650156958+0j) Z0 Z2
(0.1298003145+0j) Z0 Z3
(0.1298003145+0j) Z1 Z2
(0.0650156958+0j) Z1 Z3
(0.1336660299+0j) Z2 Z3
-0.0647846187 X0 X1 Y2 Y3
0.0647846187 X0 Y1 Y2 X3
0.0647846187 Y0 X1 X2 Y3
-0.0647846187 Y0 Y1 X2 X3
State Mapping#
The state vector describing the occupation number can also be mapped to computational basis state using the jordan_wigner
object.
In this case, we may construct a state mapper function from the jordan_wigner
object with the get_state_mapper
method where we need to supply it with the number of spin orbitals:
[5]:
jw_state_mapper = jordan_wigner.get_state_mapper(n_spin_orbitals=4)
With the state mapper at hand, we may pass in the labels of the occupied spin orbitals to obtain a Jordan-Wigner-mapped ComputationalBasisState
object.
For example, to generate the qubit state for
where the indices on the ladder operators are the spin-orbital indices, we can do:
[6]:
occupation_spin_orbitals = [0, 1, 2]
jw_state = jw_state_mapper(occupation_spin_orbitals)
jw_state_preparation_circuit = jw_state.circuit # The circuit that prepares the specified state
print('State:')
print(jw_state, '\n')
print('State preparation circuit')
for gate in jw_state_preparation_circuit.gates:
print(gate)
State:
ComputationalBasisState(qubit_count=4, bits=0b111, phase=0π/2)
State preparation circuit
QuantumGate(name='X', target_indices=(0,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(1,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(2,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
Bravyi-Kitaev mapping#
quri-parts
also provides Bravyi-Kitaev mapping, the interface is the similar tojordan_wigner
’s. However, note that you need to pass in the number of spin orbitals to the .get_of_operator_mapper
method in order to perform the BK mapping:
[7]:
from quri_parts.openfermion.transforms import bravyi_kitaev
# Obtaining the operator mapper
bk_operator_mapper = bravyi_kitaev.get_of_operator_mapper(n_spin_orbitals=4)
# Map the Hamiltonian into quri-parts Operators with Bravyi-Kitaev mapping
bravyi_kitaev_hamiltonian = bk_operator_mapper(fermionic_hamiltonian)
for op, coeff in bravyi_kitaev_hamiltonian.items():
print(coeff.round(10), op)
(0.0672793046+0j) Z0
(-0.5339363488+0j) I
(0.0672793046+0j) Z0 Z1
(0.0066512957+0j) Z2
(0.0066512957+0j) Z1 Z2 Z3
(0.0647846187+0j) Y0 Z1 Y2
(0.0647846187+0j) X0 Z1 X2
(0.0647846187+0j) X0 Z1 X2 Z3
(0.0647846187+0j) Y0 Z1 Y2 Z3
(0.1273657031+0j) Z1
(0.0650156958+0j) Z0 Z2
(0.1298003145+0j) Z0 Z1 Z2
(0.1298003145+0j) Z0 Z1 Z2 Z3
(0.0650156958+0j) Z0 Z2 Z3
(0.1336660299+0j) Z1 Z3
[8]:
bk_state_mapper = bravyi_kitaev.get_state_mapper(n_spin_orbitals=4)
occupation_spin_orbitals = [0, 1, 2]
bk_state_mapper_state = bk_state_mapper(occupation_spin_orbitals)
bk_state_preparation_circuit = bk_state_mapper_state.circuit
print('State:')
print(bk_state_mapper_state, '\n')
print('State preparation circuit:')
for gate in bk_state_preparation_circuit.gates:
print(gate)
State:
ComputationalBasisState(qubit_count=4, bits=0b1101, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(2,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(3,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
Symmetry Conserving Bravyi-Kitaev mapping#
QURI Parts also provides symmetry-conserving Bravyi-Kitaev (SCBK) mapping, the interface is the similar tojordan_wigner
’s. However, note that you need to pass in the number of spin orbitals as well as the number of electrons in order to perform SCBK mapping to an operator. In addition, also note that the number of qubits for SCBK is less than the number of spin orbitals as we show below:
[9]:
import numpy as np
from quri_parts.openfermion.transforms import symmetry_conserving_bravyi_kitaev
# Obtaining the operator mapper
sym_conserving_bk_operator_mapper = symmetry_conserving_bravyi_kitaev.get_of_operator_mapper(n_fermions=2, n_spin_orbitals=4)
print(
f'Number of qubits using SCBK: {symmetry_conserving_bravyi_kitaev.n_qubits_required(n_spin_orbitals=4)}'
)
# Map the Hamiltonian into quri-parts Operators with Jordan-Wigner mapping
sym_conserving_bravyi_kitaev_hamiltonian = sym_conserving_bk_operator_mapper(fermionic_hamiltonian)
for op, coeff in sym_conserving_bravyi_kitaev_hamiltonian.items():
if (c := np.round(coeff, 10)):
print(c, op)
Number of qubits using SCBK: 2
-0.6639677404 I
0.0606280089 Z0
0.0606280089 Z1
0.0014311039 Z0 Z1
0.2591384749 X0 X1
[10]:
scbk_state_mapper = symmetry_conserving_bravyi_kitaev.get_state_mapper(n_fermions=2, n_spin_orbitals=4)
occupation_spin_orbitals = [0, 1]
scbk_state_mapper_state = scbk_state_mapper(occupation_spin_orbitals)
scbk_state_preparation_circuit = scbk_state_mapper_state.circuit
print('State:')
print(scbk_state_mapper_state, '\n')
print('State preparation circuit:')
for gate in scbk_state_preparation_circuit.gates:
print(gate)
State:
ComputationalBasisState(qubit_count=2, bits=0b11, phase=0π/2)
State preparation circuit:
QuantumGate(name='X', target_indices=(0,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())
QuantumGate(name='X', target_indices=(1,), control_indices=(), classical_indices=(), params=(), pauli_ids=(), unitary_matrix=())