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

Add functions to find correct sector for qubit tapering #2041

Merged
merged 16 commits into from
Jan 6, 2022

Conversation

obliviateandsurrender
Copy link
Contributor

Context:
This PR adds functions to find the correct sector for which the tapered Hamiltonian will correspond to the ground state of the system.

Description of the Change:

  1. _sector_energy function returns the energy of the Hamiltonian in a given sector.
  2. find_optimal_sector function returns the correct sector containing the ground state. It offers two strategies to do so - (i) by performing a brute force through all the possible permutations (exponential), and (ii) by building the sector using a relation between Pauli-Z operator and occupation number under JW transform (linear).

Benefits:
Possible Drawbacks:
Related GitHub Issues:

@obliviateandsurrender obliviateandsurrender added WIP 🚧 Work-in-progress qchem ⚛️ Related to the QChem package labels Dec 17, 2021
@obliviateandsurrender obliviateandsurrender changed the title Add functions to find correct sector for qubit tapering [WIP] Add functions to find correct sector for qubit tapering Dec 17, 2021
@codecov
Copy link

codecov bot commented Dec 20, 2021

Codecov Report

Merging #2041 (3128fb8) into master (9902cc3) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #2041   +/-   ##
=======================================
  Coverage   99.18%   99.18%           
=======================================
  Files         226      226           
  Lines       17354    17368   +14     
=======================================
+ Hits        17212    17226   +14     
  Misses        142      142           
Impacted Files Coverage Δ
pennylane/hf/__init__.py 100.00% <ø> (ø)
pennylane/hf/tapering.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9902cc3...3128fb8. Read the comment docs.

@obliviateandsurrender obliviateandsurrender changed the title [WIP] Add functions to find correct sector for qubit tapering Add functions to find correct sector for qubit tapering Dec 20, 2021
@obliviateandsurrender obliviateandsurrender removed the WIP 🚧 Work-in-progress label Dec 20, 2021
@obliviateandsurrender obliviateandsurrender marked this pull request as ready for review December 20, 2021 10:37
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
return energy


def find_optimal_sector(qubit_op, generators, paulix_ops, brute_force=True, num_electrons=0):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function should only return the correct sector and not the energy. This means that we should not perform the brute force method here since the user can always call the transform_hamiltonian function with any desired sector. Also, if the user can already obtain the correct sector, then no need to do a brute force at all. Therefore, the args and return might be:

Args: generators, reference HF state
Return: list of eigenvalues

Instead of the reference HF state, we might have mol, core=None, active=None or something similar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Soran 💯 Why use an inefficient brute force method when we know a better algorithm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. My original intention to include brute force was for the sake of completeness. But now that transform_hamiltonian can already allow users to do it already, I also believe that we can skip doing it here.

Comment on lines 493 to 501
To obtain the optimal sector one can brute force through all the permutation or by utilize the following
relation between the Pauli-Z qubit operator and the occupation number under Jordan-Wigner transform.

.. math::

\sigma_z^{i} = a_{i}^{\dagger}a_{i} - 1

This relation allows us to figure out whether the orbital is occupied or unoccupied in a given symmetry sector,
to build the correct eigensector.
Copy link
Contributor

@soranjh soranjh Dec 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The theory can be explained a bit better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. How exactly does knowing if an orbital is occupied or not help us build the correct eigensector?

tests/hf/test_tapering.py Outdated Show resolved Hide resolved
tests/hf/test_tapering.py Outdated Show resolved Hide resolved
Copy link
Contributor

@soranjh soranjh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @obliviateandsurrender. I have two major suggestions:

  1. Focus this PR (and the function) to obtain the sector, no need to compute the energy here which means that we do not need to perform the brute force method.
  2. It will be great if we have a slightly more transparent explanation of how the sector is found..


.. math::

\sigma_z^{i} = a_{i}^{\dagger}a_{i} - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be \sigma_z^{i} = 1 - 2 a_{i}^{\dagger}a_{i} or maybe I am missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number operator is [[0, 0], [0, 1]] so indeed the formula Soran is quoting should be the correct one. Also, I think it's better to express the number operator in terms of the Pauli operators. I suggest changing to

n_i=a_{i}^{\dagger}a_{i}=I-2\sigma_z^i

But please check that we are using labels on the Pauli operators consistently. I recall in other PRs we were using \sigma_i^z, though I may be wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Even I'd gotten the same result when I was calculating it by myself. But I wonder why Setia's paper that we are following used this definition (Eq 17).

Copy link
Contributor

@ixfoduap ixfoduap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code performing the main calculation looks good, but the rest of the PR needs additional work.

pennylane/hf/tapering.py Outdated Show resolved Hide resolved
"""Computes energy of the given tapered Hamiltonian.

Args:
sector (pennylane.Hamiltonian): tapered Hamiltonian whose energy has to be calculated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still very confused by what we mean by "sector". I thought it was a list of eigenvalues? But here it is a Hamiltonian?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry for the confusion. I somehow used "sector" here to represent the tapered Hamiltonian. I guess while implementing the function I more or less remembered quite a literal definition of the word sector (as in part of the whole). Didn't realize the conflict.

"""

if len(sector.wires) < 2:
energy = min(sp.linalg.eigvals(qml.utils.sparse_hamiltonian(sector).toarray()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this be very expensive for more complicated Hamiltonians? Thinking long term, if there is ever an experiment attempting to simulate a large system, it may be prohibitive to directly diagonalize the Hamiltonian. Isn't that a problem here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The energy calculation was being done only to support the brute force method. Now that I have removed it, this is not needed either.

return energy


def find_optimal_sector(qubit_op, generators, paulix_ops, brute_force=True, num_electrons=0):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Soran 💯 Why use an inefficient brute force method when we know a better algorithm?

pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
f" got 'orbitals'={num_orbitals} < 'electrons'={num_electrons}."
)

hf_str = np.where(np.arange(num_orbitals) < num_electrons, 1, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever implementation!

pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
@@ -349,10 +303,13 @@ def test_generate_paulis(generators, num_qubits, result):
),
],
)
def test_generate_symmetries(hamiltonian, num_qubits, res_generators, res_pauli_ops):
def test_generate_symmetries(symbols, geometry, num_qubits, res_generators, res_pauli_ops):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change being made?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid hardcoding the input Hamiltonian which could be huge for any molecule other than H2.

def optimal_sector(qubit_op, generators, active_electrons):
r"""Get the optimal sector which contains the ground state.

To obtain the optimal sector, we need to choose the right eigenvalue for the symmetries :math:`\vec{\tau}`. We can do
Copy link
Contributor

@soranjh soranjh Dec 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the optimal eigenvalue sector is obtained by applying each \tau to the referee HF state. Assuming that \taus are composed of only I and Pauli-Z, then finding the eigenvalue is trivial by just looking at the wires of the Pauli-Z ops and the occupation state of the qubits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the confirmation on this! I have updated the docstring to make it more concise.

@@ -464,3 +465,65 @@ def transform_hamiltonian(h, generators, paulix_ops, paulix_sector):
c = qml.math.stack(c)

return _simplify(qml.Hamiltonian(c, o))


def optimal_sector(qubit_op, generators, active_electrons):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use h instead of qubit_op?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just a bit more inclined towards using descriptive variable names.

tests/hf/test_tapering.py Outdated Show resolved Hide resolved
tests/hf/test_tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
pennylane/hf/tapering.py Outdated Show resolved Hide resolved
Copy link
Contributor

@soranjh soranjh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for the changes.

pennylane/hf/tapering.py Show resolved Hide resolved
perm = []
for tau in generators:
symmstr = np.array([1 if wire in tau.ops[0].wires else 0 for wire in qubit_op.wires])
coeff = -1 if numpy.logical_xor.reduce(numpy.logical_and(symmstr, hf_str)) else 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is a 1-liner

@Jaybsoni
Copy link
Contributor

Jaybsoni commented Jan 4, 2022

Looks good to me. Sorry for the delay!

@obliviateandsurrender obliviateandsurrender merged commit 49b94fa into master Jan 6, 2022
@obliviateandsurrender obliviateandsurrender deleted the opt_symmetry_sector branch January 6, 2022 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
qchem ⚛️ Related to the QChem package
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants