chemistry
eigenkit.chemistry
Quantum chemistry with the core of the library.
Build a fermionic Hamiltonian (FermionOperator), map it to a qubit
Observable (jordan_wigner), and find its ground-state energy with the
Variational Quantum Eigensolver (VQE), which uses the variational optimisation
tools of the core. The package uses only NumPy and SciPy, needs no PyTorch, and
works with any backend. For small systems, exact_ground_state_energy gives
the classical reference value.
Example
from eigenkit.chemistry import (
fermi_hubbard_dimer, jordan_wigner, VQE, exact_ground_state_energy,
)
hamiltonian, offset = jordan_wigner(fermi_hubbard_dimer(t=1.0, u=2.0))
result = VQE(layers=3, restarts=5).solve(hamiltonian, n_qubits=4, offset=offset)
exact = exact_ground_state_energy(hamiltonian, n_qubits=4, offset=offset)
print(result.energy, exact)
FermionOperator
A complex linear combination of fermionic terms, stored as {term: coeff}.
__init__(terms=None)
Build an operator from a mapping of terms to coefficients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
terms
|
dict[Term, complex] | None
|
Maps each term to its coefficient. A term is a tuple of
|
None
|
annihilation(mode)
classmethod
The annihilation operator a_mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
int
|
The index of the spin-orbital from which an electron is removed. |
required |
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The operator, as a |
creation(mode)
classmethod
The creation operator a†_mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
int
|
The index of the spin-orbital in which an electron is created. |
required |
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The operator, as a |
hermitian_conjugate()
The adjoint of the operator.
The order of each term is reversed, creation and annihilation are swapped, and each coefficient is replaced by its complex conjugate.
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The adjoint, as a |
identity(coefficient=1.0)
classmethod
The identity operator, optionally scaled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coefficient
|
Scalar
|
The factor that multiplies the identity. The default is |
1.0
|
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The scaled identity, as a |
number(mode)
classmethod
The number operator a†_mode a_mode.
It counts the electrons in one spin-orbital.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
int
|
The index of the spin-orbital. |
required |
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The operator, as a |
zero()
classmethod
The zero operator, which has no terms.
Returns:
| Type | Description |
|---|---|
'FermionOperator'
|
The zero |
Molecule
dataclass
A molecular Hamiltonian in second quantisation.
fermion_operator already includes the constant term (the nuclear repulsion),
so jordan_wigner(fermion_operator) returns it as the offset. hf_energy and
fci_energy, when computed, are the reference energies from PySCF, which are
useful targets for the Variational Quantum Eigensolver (VQE).
Attributes:
| Name | Type | Description |
|---|---|---|
fermion_operator |
FermionOperator
|
The Hamiltonian in second quantisation, including the constant term. |
n_qubits |
int
|
The number of qubits needed after the mapping, one per spin-orbital with Jordan-Wigner. |
n_electrons |
int
|
The number of electrons, which sets the Hartree-Fock reference state and the excitations that a unitary coupled-cluster singles and doubles (UCCSD) ansatz can use. |
nuclear_repulsion |
float
|
The constant nuclear term, in Hartree. It is already
included in |
hf_energy |
float
|
The Hartree-Fock energy from PySCF, in Hartree. A VQE run should reach a lower energy. |
fci_energy |
float | None
|
The full configuration interaction (FCI) energy, in Hartree,
which is the exact answer in this basis set. It is |
VQE
Bases: BaseQuantumSolver
Variational Quantum Eigensolver (VQE).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers
|
int
|
The depth of the default hardware-efficient ansatz. The default is
|
2
|
provider
|
ProviderSpec
|
The backend to use. The default is |
'numpy'
|
optimizer
|
Optimizer
|
The optimiser: the name of a |
'L-BFGS-B'
|
restarts
|
int
|
The number of runs from random starting points. The lowest
energy is kept, because variational landscapes have local minima. The
default is |
1
|
random_state
|
int | None
|
The base seed. Restart |
42
|
solve(hamiltonian, n_qubits, *, offset=0.0, ansatz=None)
Minimise the expectation value of the Hamiltonian over the ansatz.
It runs restarts independent optimisations from different random starting
points and keeps the lowest energy, because a variational landscape has local
minima.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hamiltonian
|
Observable
|
The Hamiltonian to minimise, as a qubit |
required |
n_qubits
|
int
|
The number of qubits of the circuit. It must match the Hamiltonian. |
required |
offset
|
float
|
The constant added to the minimised value. This is where the
identity term returned by the mapping goes, which for a molecule
includes the nuclear repulsion, in Hartree. If it is left out, the
energy is wrong by exactly that constant. The default is |
0.0
|
ansatz
|
CircuitSpec | None
|
The circuit to optimise. The default, |
None
|
Returns:
| Type | Description |
|---|---|
VQEResult
|
A |
VQEResult
dataclass
Bases: ValueEquality
Outcome of a run of the Variational Quantum Eigensolver (VQE).
The result is read-only. Assigning to a field raises FrozenInstanceError, and
writing into an array field raises ValueError. This prevents a mistyped =
(instead of ==) or an in-place edit from leaving a result whose scalar fields
describe a different run from the array it holds. To get a writable copy of the
array, use np.array(result.field).
Results compare by value, including their arrays. They are not hashable, because a result is a collection of numbers and not a key, and a correct hash of a float array is expensive to compute.
Attributes:
| Name | Type | Description |
|---|---|---|
energy |
float
|
The lowest energy found, with the offset added back. For a molecule, it is in Hartree. |
optimal_parameters |
ndarray
|
The ansatz weights that produced this energy. |
n_iterations |
int
|
The number of optimiser iterations in the best restart. It is not summed over restarts. |
success |
bool
|
Whether the optimiser of that restart reported convergence. |
__post_init__()
Make the array field read-only.
Freezing the dataclass prevents a field from being reassigned, but it does not
prevent writing into the array that the field holds, and a result that has been
written into still reports the scalar values of the original run. The result
therefore keeps its own copy, so the array it was built from can still be
changed by the caller. To get a writable copy, use
np.array(result.optimal_parameters).
apply_hartree_fock(circuit, n_electrons)
Prepare the Hartree-Fock reference state on an existing builder.
It applies an X gate to each of the lowest n_electrons spin-orbitals, which
marks them as occupied (|1⟩).
The convention is the one of the Jordan-Wigner mapping: mode j is qubit j,
and the spin-orbitals are ordered so that the lowest n_electrons of them are
occupied in the reference state, for example |1…1 0…0⟩.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
circuit
|
CircuitBuilder
|
The builder to add the gates to. |
required |
n_electrons
|
int
|
The number of electrons, which sets the Hartree-Fock reference state. |
required |
Returns:
| Type | Description |
|---|---|
CircuitBuilder
|
The same builder, so that calls can be chained. |
bravyi_kitaev(operator, n_qubits=None)
Map a FermionOperator to qubits with the Bravyi-Kitaev transformation.
The transformation itself is done by OpenFermion (the chemistry extra), and
the result is converted to an eigenkit Observable. The Jordan-Wigner mapping,
in contrast, is implemented in eigenkit. n_qubits defaults to the largest
mode index plus one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
FermionOperator
|
The fermionic operator to map. |
required |
n_qubits
|
int
|
The number of qubits. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
observable |
Observable
|
The operator on qubits, without its identity part. |
offset |
float
|
The coefficient of the identity part, which the
|
dense_hamiltonian(observable, n_qubits, offset=0.0)
The 2**n_qubits × 2**n_qubits matrix of observable + offset * I.
Qubit 0 is the most significant factor of the tensor product, which matches the ordering of basis states in the simulators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observable
|
Observable
|
The qubit operator. |
required |
n_qubits
|
int
|
The number of qubits. |
required |
offset
|
float
|
The constant (identity) term that the mapping returned separately, added back to the energy. For a molecule, it includes the nuclear repulsion, in Hartree. |
0.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The matrix, as a complex NumPy array. |
exact_ground_state_energy(observable, n_qubits, offset=0.0)
The lowest eigenvalue of observable + offset * I, by dense diagonalisation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observable
|
Observable
|
The qubit operator. |
required |
n_qubits
|
int
|
The number of qubits. |
required |
offset
|
float
|
The constant (identity) term that the mapping returned separately, added back to the energy. For a molecule, it includes the nuclear repulsion, in Hartree. |
0.0
|
Returns:
| Type | Description |
|---|---|
float
|
The ground-state energy. |
fermi_hubbard_dimer(t, u)
The two-site Fermi-Hubbard model with spin 1/2.
It has four spin-orbital modes, indexed by mode = 2 * site + spin, with spin
0 for up and 1 for down. Site 0 has modes 0 and 1, and site 1 has
modes 2 and 3. The Hamiltonian is:
The ground-state energy at half filling has the closed form
fermi_hubbard_dimer_ground_energy().
This makes the model an exact check of the fermion-to-qubit mapping and of
VQE, with no external dependency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
The hopping amplitude between the two sites. |
required |
u
|
float
|
The on-site Coulomb repulsion. |
required |
Returns:
| Type | Description |
|---|---|
FermionOperator
|
The Hamiltonian, as a |
fermi_hubbard_dimer_ground_energy(t, u)
The ground-state energy of the Fermi-Hubbard dimer at half filling.
The energy of fermi_hubbard_dimer()
with two electrons in a singlet state is (U - sqrt(U² + 16t²)) / 2. This value
is the ground state over all particle numbers only when U < 3t. For a larger
U, double occupancy costs so much energy that the state with a single
electron, of energy -t, is lower, and the ground state over all particle
numbers is min(-t, (U - sqrt(U² + 16t²)) / 2). An ansatz that conserves the
number of particles, such as UCCSD, targets the half-filling sector directly.
The hardware-efficient ansatz explores all sectors, so use it only where the
half-filling state is the overall ground state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
The hopping amplitude, as in the model. |
required |
u
|
float
|
The on-site repulsion, as in the model. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The ground-state energy at half filling, from the closed form. |
fermion_operator_from_openfermion(operator)
Convert an openfermion.FermionOperator to an eigenkit FermionOperator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
FermionOperator
|
The fermionic operator to convert. |
required |
Returns:
| Type | Description |
|---|---|
FermionOperator
|
The same operator, as an eigenkit |
hardware_efficient_ansatz(n_qubits, layers)
A standard hardware-efficient ansatz.
It starts with a layer of RY rotations, then repeats layers times a linear
chain of CNOT gates followed by an RY and an RZ rotation on each qubit. It
has n_qubits + 2 * layers * n_qubits trainable weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_qubits
|
int
|
The number of qubits. |
required |
layers
|
int
|
The number of repetitions. |
required |
Returns:
| Type | Description |
|---|---|
CircuitSpec
|
The ansatz, as a |
hartree_fock_state(n_qubits, n_electrons)
A circuit that prepares the Hartree-Fock reference state.
The state is |1…1 0…0⟩ on n_qubits qubits, with n_electrons occupied
spin-orbitals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_qubits
|
int
|
The number of qubits. |
required |
n_electrons
|
int
|
The number of electrons, which sets the Hartree-Fock reference state. |
required |
Returns:
| Type | Description |
|---|---|
CircuitSpec
|
The circuit, as a |
hydrogen_molecule(bond_length=0.7414, *, basis='sto-3g', run_fci=False)
The hydrogen molecule H₂ at the given bond length, in angstroms.
In the minimal STO-3G basis set, this is the standard 4-qubit benchmark for VQE.
Its FCI ground-state energy is about -1.137 Hartree at equilibrium.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bond_length
|
float
|
The distance between the nuclei, in angstroms. The
default, |
0.7414
|
basis
|
str
|
The name of the PySCF basis set. |
'sto-3g'
|
run_fci
|
bool
|
Whether to also compute the exact FCI energy, the target against which a VQE run is measured. |
False
|
Returns:
| Type | Description |
|---|---|
Molecule
|
The molecule, with its Hamiltonian and reference energies. |
jordan_wigner(operator, *, atol=1e-10)
Map a FermionOperator to qubits with the Jordan-Wigner transformation.
The operator must be Hermitian, as a Hamiltonian is. If a resulting
coefficient is not real, a ValueError is raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
FermionOperator
|
The fermionic operator to map. |
required |
atol
|
float
|
Terms whose coefficient is smaller than this value in magnitude are dropped. |
1e-10
|
Returns:
| Name | Type | Description |
|---|---|---|
observable |
Observable
|
The operator on qubits, without its identity part. |
offset |
float
|
The coefficient of the identity part, which the
|
molecular_hamiltonian(geometry, *, basis='sto-3g', charge=0, multiplicity=1, run_fci=False)
Build a Molecule from a geometry, with PySCF and OpenFermion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
list[tuple[str, tuple[float, float, float]]]
|
A list of |
required |
basis
|
str
|
The name of the PySCF basis set. The default is the minimal basis
set |
'sto-3g'
|
charge
|
int
|
The total charge of the molecule. |
0
|
multiplicity
|
int
|
The spin multiplicity, |
1
|
run_fci
|
bool
|
Whether to also compute the FCI energy, which is exact in this basis set and a useful target for VQE. It needs an exact diagonalisation, so it is only possible for small systems. |
False
|
Returns:
| Type | Description |
|---|---|
Molecule
|
The |
observable_from_openfermion(qubit_operator, *, atol=1e-10)
Convert an openfermion.QubitOperator to an (Observable, offset) pair.
The identity term becomes the real offset. The coefficients must be real,
because the operator must be Hermitian, and a ValueError is raised otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit_operator
|
QubitOperator
|
The operator to convert. |
required |
atol
|
float
|
Terms whose coefficient is smaller than this value in magnitude are dropped. |
1e-10
|
Returns:
| Name | Type | Description |
|---|---|---|
observable |
Observable
|
The operator, without its identity term. |
offset |
float
|
The coefficient of the identity term. |
to_openfermion_fermion_operator(operator)
Convert an eigenkit FermionOperator to an openfermion.FermionOperator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
FermionOperator
|
The fermionic operator to convert. |
required |
Returns:
| Type | Description |
|---|---|
FermionOperator
|
The same operator, in OpenFermion. |
uccsd_ansatz(n_qubits, n_electrons, reps=1)
The UCCSD ansatz on the Hartree-Fock reference, with a Trotter approximation.
UCCSD is the unitary coupled-cluster ansatz with single and double excitations.
The circuit prepares (∏_k exp(θ_k (T_k − T_k†)))^reps |HF⟩, where |HF⟩ is
the Hartree-Fock state.
Each generator G = T − T† is anti-Hermitian. The Jordan-Wigner mapping turns
H = −iG into a Hermitian qubit operator, and exp(θG) = exp(iθH) is then a
product of pauli_exponential gates. The Pauli terms of one generator commute,
so that product is exact. The product over different generators is a
first-order Trotter approximation. reps repeats the whole sequence with
independent weights, which reduces the Trotter error: more repetitions bring
the circuit closer to the exact UCCSD unitary. The ansatz has
reps * n_excitations weights in total.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_qubits
|
int
|
The number of qubits, one per spin-orbital. |
required |
n_electrons
|
int
|
The number of electrons, which sets the Hartree-Fock reference and the possible excitations. |
required |
reps
|
int
|
The number of Trotter repetitions of the product of
excitations. The default is |
1
|
Returns:
| Type | Description |
|---|---|
CircuitSpec
|
The ansatz as a |
uccsd_excitation_generators(n_qubits, n_electrons)
The excitation generators T - T† of the UCCSD ansatz.
UCCSD is the unitary coupled-cluster ansatz with single and double excitations.
The excitations go from the occupied spin-orbitals (the lowest n_electrons)
to the unoccupied ones. Single and double excitations are taken over all choices
of occupied and unoccupied indices, with no restriction from spin symmetry,
which keeps the construction simple and correct. A variant restricted by spin
symmetry, which would have fewer parameters, is not implemented.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_qubits
|
int
|
The number of qubits. |
required |
n_electrons
|
int
|
The number of electrons, which sets the Hartree-Fock reference state. |
required |
Returns:
| Type | Description |
|---|---|
list[FermionOperator]
|
The generators, as a list of |