## `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

```python
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 (mode, action) pairs, where action is 1 for creation and 0 for annihilation, applied from left to right. The empty tuple is the identity. The coefficients of repeated terms are added together. Leave it out for the zero operator. The default is None. |

#### `annihilation(mode)`

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 FermionOperator. |

#### `creation(mode)`

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 FermionOperator. |

#### `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 FermionOperator. |

#### `identity(coefficient=1.0)`

The identity operator, optionally scaled.

Parameters:

| Name          | Type     | Description                                                  | Default |
| ------------- | -------- | ------------------------------------------------------------ | ------- |
| `coefficient` | `Scalar` | The factor that multiplies the identity. The default is 1.0. | `1.0`   |

Returns:

| Type                | Description                                |
| ------------------- | ------------------------------------------ |
| `'FermionOperator'` | The scaled identity, as a FermionOperator. |

#### `number(mode)`

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 FermionOperator. |

#### `zero()`

The zero operator, which has no terms.

Returns:

| Type                | Description               |
| ------------------- | ------------------------- |
| `'FermionOperator'` | The zero FermionOperator. |

### `Molecule`

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 fermion_operator, and it is kept separately so that it can be reported.                            |
| `hf_energy`         | `float`           | The Hartree-Fock energy from PySCF, in Hartree. A VQE run should reach a lower energy.                                                                              |
| `fci_energy`        | \`float           | None\`                                                                                                                                                              |

### `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.                                                                            | `2`                                                                                                        |
| `provider`     | `ProviderSpec` | The backend to use. The default is "numpy", the built-in reference simulator.                                                                    | `'numpy'`                                                                                                  |
| `optimizer`    | `Optimizer`    | The optimiser: the name of a scipy.optimize.minimize method, "spsa", or a custom callable (see minimize_expectation). The default is "L-BFGS-B". | `'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.   | `1`                                                                                                        |
| `random_state` | \`int          | None\`                                                                                                                                           | The base seed. Restart r uses random_state + r, and None leaves every restart unseeded. The default is 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 Observable, usually the one returned by jordan_wigner.                                                                                                                                                                 | *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. | `0.0`                                                                                                       |
| `ansatz`      | \`CircuitSpec | None\`                                                                                                                                                                                                                                                         | The circuit to optimise. The default, None, uses the built-in hardware-efficient ansatz with layers layers. |

Returns:

| Type        | Description                                                                           |
| ----------- | ------------------------------------------------------------------------------------- |
| `VQEResult` | A VQEResult with the best energy, its parameters, and whether that restart converged. |

### `VQEResult`

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 Observable does not contain. |

### `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:

```text
H = -t Σ_σ (a†_{0σ} a_{1σ} + a†_{1σ} a_{0σ})
    + U (n_{0↑} n_{0↓} + n_{1↑} n_{1↓})
```

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 FermionOperator. |

### `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 FermionOperator. |

### `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 CircuitSpec. |

### `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 CircuitSpec with no trainable weights. |

### `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, is close to equilibrium.  | `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 Observable does not contain. |

### `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 (element, (x, y, z)) entries, with the coordinates in angstroms.                                                                                                         | *required* |
| `basis`        | `str`                                          | The name of the PySCF basis set. The default is the minimal basis set "sto-3g".                                                                                                    | `'sto-3g'` |
| `charge`       | `int`                                          | The total charge of the molecule.                                                                                                                                                  | `0`        |
| `multiplicity` | `int`                                          | The spin multiplicity, 2S + 1.                                                                                                                                                     | `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 Molecule, with its Hamiltonian and reference energies. |

### `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.           | `1`        |

Returns:

| Type          | Description                                                                             |
| ------------- | --------------------------------------------------------------------------------------- |
| `CircuitSpec` | The ansatz as a CircuitSpec, with one trainable angle per generator and per repetition. |

### `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 FermionOperator objects. |
