Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support freezing Hartree-Fock energies at each forging iteration #307

Merged
merged 36 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3cee1bd
Implement freezing hF energy value
caleb-johnson Jul 5, 2023
71af5c2
ruff
caleb-johnson Jul 5, 2023
1a2debe
init commit
caleb-johnson Jul 5, 2023
790ee93
save
caleb-johnson Jul 6, 2023
5ad4a38
tutorial is code for unit test. Ensure groundstate agrees with resear…
caleb-johnson Jul 6, 2023
e096083
ruff
caleb-johnson Jul 6, 2023
d418088
mypy and hf variable names
caleb-johnson Jul 8, 2023
d66cf84
black
caleb-johnson Jul 8, 2023
3d14e04
Add unit test
caleb-johnson Jul 8, 2023
804276b
improve docstring and add data
caleb-johnson Jul 8, 2023
36ce83d
Add section to forging explanatory
caleb-johnson Jul 10, 2023
7af31e9
Add release note and image
caleb-johnson Jul 10, 2023
08f98ca
feature instead of upgrade
caleb-johnson Jul 10, 2023
4250fc3
Update fixed-hartree-fock-447c33a956ffea84.yaml
caleb-johnson Jul 10, 2023
1f78313
Fix data filenames, and update comment
caleb-johnson Jul 10, 2023
35110be
black
caleb-johnson Jul 10, 2023
2eb206a
Update docs
caleb-johnson Jul 10, 2023
ad0b172
maxiter=0
caleb-johnson Jul 10, 2023
c66f135
Update hf_energy docstring. Update explanatory
caleb-johnson Jul 11, 2023
d7ad173
getters/setters coverage
caleb-johnson Jul 11, 2023
17a2f4c
Fix bug in asymmetric_bitstrings + set HF. Add test to cover bug
caleb-johnson Jul 11, 2023
e21d283
Include slow test
caleb-johnson Jul 11, 2023
7b76210
Run slow tests for coverage
caleb-johnson Jul 11, 2023
8e47c07
slow
caleb-johnson Jul 11, 2023
b8a1995
slow usage error
caleb-johnson Jul 11, 2023
21af043
ruff
caleb-johnson Jul 11, 2023
8f6fba9
cleanup
caleb-johnson Jul 12, 2023
36ad956
Fix bug in fixing HF energy with asymmetric bitstrings. Other bugfixes.
caleb-johnson Jul 12, 2023
8ea03b7
Remove unused args
caleb-johnson Jul 12, 2023
2f6b74e
Test to ensure our masks didnt interfere with schmidt matrix
caleb-johnson Jul 12, 2023
61cd6c8
revert run slow change. handle in separate pr
caleb-johnson Jul 12, 2023
924cc23
Add notes to test explaining hard-coded values
caleb-johnson Jul 20, 2023
93aa88c
Merge branch 'main' of github.com:Qiskit-Extensions/circuit-knitting-…
caleb-johnson Aug 14, 2023
a56a4e7
Update circuit_knitting/forging/entanglement_forging_ground_state_sol…
caleb-johnson Aug 14, 2023
2a06708
Update test_entanglement_forging_ground_state_solver.py
caleb-johnson Aug 14, 2023
06d08e7
black
caleb-johnson Aug 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def __init__(
backend_names: str | list[str] | None = None,
options: Options | list[Options] | None = None,
mo_coeff: np.ndarray | None = None,
hf_energy: float | None = None,
):
"""
Assign the necessary class variables and initialize any defaults.
Expand All @@ -143,6 +144,9 @@ def __init__(
backend_names: Backend name or list of backend names to use during parallel computation
options: Options or list of options to be applied to the backends
mo_coeff: Coefficients for converting an input problem to MO basis
hf_energy: The Hartree-Fock energy to use at each iteration. If this is left unset,
the energy will be calculated using quantum experiments. See :ref:`Fixing the Hartree-Fock bitstring`
for more information.

Returns:
None
Expand All @@ -157,6 +161,7 @@ def __init__(
self._orbitals_to_reduce = orbitals_to_reduce
self.backend_names = backend_names # type: ignore
self.options = options
self.hf_energy = hf_energy
self._mo_coeff = mo_coeff
self._optimizer: Optimizer | MINIMIZER = optimizer or SPSA()

Expand Down Expand Up @@ -246,6 +251,16 @@ def mo_coeff(self, mo_coeff: np.ndarray | None) -> None:
"""Set the coefficients for converting integrals to the MO basis."""
self._mo_coeff = mo_coeff

@property
def hf_energy(self) -> float | None:
"""Return the Hartree-Fock energy."""
return self._hf_energy

@hf_energy.setter
def hf_energy(self, hf_energy: float | None) -> None:
"""Set the Hartree-Fock energy."""
self._hf_energy = hf_energy

def solve(
self,
problem: ElectronicStructureProblem,
Expand Down Expand Up @@ -284,18 +299,24 @@ def solve(
hamiltonian_terms = self.get_qubit_operators(problem)
ef_operator = convert_cholesky_operator(hamiltonian_terms, self._ansatz)

# Set the knitter class field
# Shift the hf value so it can be entered into the Schmidt matrix
fixed_hf_value = None
if self.hf_energy is not None:
fixed_hf_value = self.hf_energy - self._energy_shift
if self._service is not None:
backend_names = self._backend_names or ["ibmq_qasm_simulator"]
self._knitter = EntanglementForgingKnitter(
self._ansatz,
fixed_hf_value=fixed_hf_value,
service=self._service,
backend_names=backend_names,
options=self._options,
)
else:
self._knitter = EntanglementForgingKnitter(
self._ansatz, options=self._options
self._ansatz,
fixed_hf_value=fixed_hf_value,
options=self._options,
)
self._history = EntanglementForgingHistory()
self._eval_count = 0
Expand Down
47 changes: 43 additions & 4 deletions circuit_knitting/forging/entanglement_forging_knitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from __future__ import annotations

import copy
from typing import Sequence, Any
from concurrent.futures import ThreadPoolExecutor

Expand All @@ -38,6 +39,7 @@ class EntanglementForgingKnitter:
def __init__(
self,
ansatz: EntanglementForgingAnsatz,
fixed_hf_value: float | None = None,
service: QiskitRuntimeService | None = None,
backend_names: str | list[str] | None = None,
options: Options | list[Options] | None = None,
Expand All @@ -48,6 +50,9 @@ def __init__(
Args:
ansatz: The container for the circuit structure and bitstrings to be used
(and generate the stateprep circuits)
fixed_hf_value: This value will be used instead of calculating the Hartree-Fock energy.
This value should represent the Hartree-Fock energy shifted by the nuclear repulsion energy.
The HF energy needs to be shifted in order to be used in the Schmidt matrix.
service: The service used to spawn Qiskit primitives and runtime jobs
backend_names: Names of the backends to use for calculating expectation values
options: Options to use with the backends
Expand All @@ -59,6 +64,7 @@ def __init__(
self._session_ids: list[str | None] | None = None
self._backend_names: list[str] | None = None
self._options: list[Options] | None = None
self.fixed_hf_value = fixed_hf_value
# Call constructors
self.backend_names = backend_names # type: ignore
self.options = options
Expand Down Expand Up @@ -141,6 +147,16 @@ def service(self, service: QiskitRuntimeService | None) -> None:
"""Change the service class field."""
self._service = service.active_account() if service is not None else service

@property
def fixed_hf_value(self) -> float | None:
"""Return the shifted Hartree-Fock energy."""
return self._fixed_hf_value

@fixed_hf_value.setter
def fixed_hf_value(self, fixed_hf_value: float | None) -> None:
"""Set the shifted Hartree-Fock energy to bet used in the Schmidt matrix."""
self._fixed_hf_value = fixed_hf_value

def __call__(
self,
ansatz_parameters: Sequence[float],
Expand Down Expand Up @@ -191,6 +207,8 @@ def __call__(
tensor_ansatze_u = [
prep_circ.compose(circuit_u) for prep_circ in self._tensor_circuits_u
]
if self.fixed_hf_value is not None:
tensor_ansatze_u = tensor_ansatze_u[1:]
garrison marked this conversation as resolved.
Show resolved Hide resolved
superposition_ansatze_u = [
prep_circ.compose(circuit_u) for prep_circ in self._superposition_circuits_u
]
Expand All @@ -201,6 +219,8 @@ def __call__(
tensor_ansatze_v = [
prep_circ.compose(circuit_u) for prep_circ in self._tensor_circuits_v
]
if self.fixed_hf_value is not None:
tensor_ansatze_v = tensor_ansatze_v[1:]
superposition_ansatze_v = [
prep_circ.compose(circuit_u)
for prep_circ in self._superposition_circuits_v
Expand Down Expand Up @@ -282,10 +302,29 @@ def __call__(
)
self._session_ids[i] = job_id

# Pack NaNs into the expval list in place of the HF bitstring results to
# ensure Schmidt matrix is the correct shape
garrison marked this conversation as resolved.
Show resolved Hide resolved
if self._fixed_hf_value is not None:
num_paulis = len(forged_operator.tensor_paulis)
nans_u = np.empty(num_paulis)
nans_u[:] = np.nan
if not self._ansatz.bitstrings_are_symmetric:
# Should be equal number of expvals for each subsystem
assert len(tensor_expvals) % 2 == 0
num_tensor_terms = int(len(tensor_expvals) / 2)
nans_v = copy.deepcopy(nans_u)
tensor_expvals.insert(num_tensor_terms, nans_v)
tensor_expvals.insert(0, nans_u)

# Compute the Schmidt matrix
h_schmidt = self._compute_h_schmidt(
forged_operator, np.array(tensor_expvals), np.array(superposition_expvals)
)

# Hard-code the shifted Hartree-Fock energy, if desired
if self._fixed_hf_value is not None:
h_schmidt[0, 0] = self._fixed_hf_value

evals, evecs = np.linalg.eigh(h_schmidt)
schmidt_coeffs = evecs[:, 0]
energy = evals[0]
Expand Down Expand Up @@ -569,10 +608,10 @@ def _estimate_expvals(
tensor_paulis: list[Pauli],
superposition_ansatze: list[QuantumCircuit],
superposition_paulis: list[Pauli],
service_args: dict[str, Any] | None = None,
backend_name: str | None = None,
options: Options | None = None,
session_id: str | None = None,
service_args: dict[str, Any] | None,
backend_name: str | None,
options: Options | None,
session_id: str | None,
) -> tuple[list[np.ndarray], list[np.ndarray], str | None]:
"""Run quantum circuits to generate the expectation values.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions docs/entanglement_forging/explanation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,44 @@ that do not participate in electronic excitations (i.e. core orbitals or
those that lie out of symmetry) by removing the bits that correspond to
them.

.. _Fixing the Hartree-Fock bitstring:

Fixing the Hartree-Fock bitstring
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In some cases, it is possible to increase the accuracy of simulations and speed up
the execution by bypassing the experiments associated with the first bitstring and
replacing those results with the Hartree-Fock energy value.

.. code-block:: python
:caption: Fixing the HF energy at each iteration
from qiskit_nature.second_q.problems import ElectronicStructureProblem
from circuit_knitting.forging import EntanglementForgingGroundStateSolver

problem = ElectronicStructureProblem(...)
hf_energy = ...

solver = EntanglementForgingGroundStateSolver(
ansatz=ansatz,
hf_energy=hf_energy
)

result = solver.solve(problem)

This setting requires an ansatz that leaves the Hartree-Fock (HF) state
unchanged with respect to the optimization parameters. As a rule of thumb,
this can be achieved by restricting entanglement between the qubits representing
occupied orbitals (bits = 1) in the HF state and the qubits representing
unoccupied orbitals (bits = 0) in the HF state.
garrison marked this conversation as resolved.
Show resolved Hide resolved

For example, this figure from [1] shows the A, B, and C qubits entangled with
the hop gates, D & E qubits entangled with hop gates, while the partition between
(A,B,C) and (D,E) are only entangled with a CZ gate.

.. figure:: figs/fixed_hf.png
:width: 250
:alt: Fixing the first bitstring to the HF value

.. _Ansatz design:

Designing the ansatz used in Entanglement Forging
Expand Down
4 changes: 4 additions & 0 deletions releasenotes/notes/fixed-hartree-fock-447c33a956ffea84.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Users may now bypass experiments associated with the Hartree-Fock bitstring and replace their results with a specified Hartree-Fock value using the ``hf_energy`` class field in :class:`~circuit_knitting.forging.EntanglementForgingGroundStateSolver`. Refer to the :ref:`explanatory material <Fixing the Hartree-Fock Bitstring>` for more information.
Binary file added test/forging/test_data/H2O_0.90_one_body.npy
Binary file not shown.
Binary file added test/forging/test_data/H2O_0.90_two_body.npy
Binary file not shown.
Loading