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

More ansatzes and example scripts #82

Merged
merged 36 commits into from
Jan 8, 2025
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
d63c221
Upload students' work as Python scripts
chmwzc Feb 16, 2024
812e327
Merge branch 'main' into dec2023rap
chmwzc May 16, 2024
6b9862e
Initial outline of paper2 circuit ansatz
chmwzc May 16, 2024
4fd1edf
Merge branch 'main' into dec2023rap
chmwzc Jun 27, 2024
b0d94e4
Finish first draft of symmetry-preserving ansatz
chmwzc Jun 27, 2024
5d2705a
Fix small bugs, should be working now
chmwzc Jun 28, 2024
ac3c236
Fix bugs and start adding tests
chmwzc Jul 1, 2024
1314849
Add more tests for symmetry ansatz
chmwzc Jul 1, 2024
9b60610
Remove old draft file
chmwzc Jul 29, 2024
65fe973
Draft code for remaining circuit ansatz
chmwzc Jul 29, 2024
d5449e7
Finish code for circuit with only one excitation
chmwzc Jul 31, 2024
4c88ffd
Add error handling if excitation input not valid
chmwzc Jul 31, 2024
0f69ccf
Rename file; 'universal.py' is too vague
chmwzc Jul 31, 2024
c181fb1
Move out some utility functions to util.py
chmwzc Jul 31, 2024
2098761
Move utility function tests over to new .py file
chmwzc Jul 31, 2024
f5d3020
Remove utility functions from ucc.py
chmwzc Jul 31, 2024
5b1b725
Add tests for Givens excitation circuit
chmwzc Aug 1, 2024
1038d30
Fix code coverage?
chmwzc Aug 1, 2024
c3847cf
Fill in remaining code coverage
chmwzc Aug 1, 2024
29fcafc
Add convenience function for all excitations
chmwzc Aug 1, 2024
b21cc21
Sort out theta argument between two functions
chmwzc Aug 1, 2024
e55cf97
Simplify ansatz convenience function
chmwzc Aug 1, 2024
17a8cd3
Add tests for ansatz convenience function
chmwzc Aug 1, 2024
9ea125c
Merge branch 'main' into dec2023rap
chmwzc Aug 11, 2024
ac77c14
Update and misc. edits to documentation
chmwzc Aug 11, 2024
f1cf698
Start writing adaptive example in tutorial
chmwzc Aug 17, 2024
5a9acd4
Update code for single excitation
chmwzc Aug 20, 2024
120b63f
Draft tutorial for adaptive methods
chmwzc Aug 21, 2024
32f65ce
Add more to adaptive tutorial
chmwzc Aug 21, 2024
a928d1f
Finish of text of adaptive tutorial
chmwzc Aug 22, 2024
5a6e45a
Remove students' old scripts from examples/
chmwzc Aug 22, 2024
4e81792
Add references and clean up tutorial
chmwzc Sep 13, 2024
6301dd9
Remove unused file
chmwzc Sep 13, 2024
fce3705
Merge branch 'main' into dec2023rap
chmwzc Oct 29, 2024
0c290f9
Merge branch 'main' into dec2023rap
chmwzc Nov 12, 2024
c4ef7d3
Merge branch 'main' into dec2023rap
chmwzc Dec 24, 2024
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ If you use the Qibochem plugin please refer to the documentation for citation in

## Contact

For questions, comments and suggestions please contact us at [https://matrix.to/#/#qibo:matrix.org](url)
To get in touch with the community and the developers, consider joining the Qibo workspace on Matrix:

[![Matrix](https://img.shields.io/matrix/qibo%3Amatrix.org?logo=matrix)](https://matrix.to/#/#qibo:matrix.org)

If you have a question about the project, contact us at [📫](mailto:[email protected]).

## Contributing

Expand Down
12 changes: 12 additions & 0 deletions doc/source/api-reference/ansatz.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ Basis rotation
--------------

.. autofunction:: qibochem.ansatz.basis_rotation.basis_rotation_gates

Givens Excitation
-----------------

.. autofunction:: qibochem.ansatz.givens_excitation.givens_excitation_circuit

.. autofunction:: qibochem.ansatz.givens_excitation.givens_excitation_ansatz

Symmetry-Preserving
-------------------

.. autofunction:: qibochem.ansatz.symmetry.symm_preserving_circuit
40 changes: 19 additions & 21 deletions doc/source/appendix/citing-qibochem.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
Publications
============

If Qibochem has been significant in your research, and you would like to acknowledge
the project in your academic publication, we suggest citing the following documents:

Software References in Zenodo
-----------------------------

Wong Zicheng, Adrian Mak, Tan Le, Stefano Carrazza, Alessandro Candido, & Ye Jun. (2024). qiboteam/qibochem: Qibochem 0.0.1 (v0.0.1). Zenodo. https://doi.org/10.5281/zenodo.10473173

Authorship Guideline
--------------------

In order to appear as an author of a Qibochem publication (paper, proceedings, etc)
each author must fullfil the following requirements:

* Participate to the official meetings.

* Contribute to the code with documented commits.

Publications
============

If Qibochem has been significant in your research, and you would like to acknowledge the project in your academic publication, we suggest citing the following reference:

Software References in Zenodo
-----------------------------

Wong Zi Cheng, Adrian Matthew Mak, Tan Le, Stefano Carrazza, Alessandro Candido & Ye Jun. (2024). qiboteam/qibochem: Qibochem 0.0.1 (v0.0.1). Zenodo. https://doi.org/10.5281/zenodo.10473173

Authorship Guideline
--------------------

In order to appear as an author of a Qibochem publication (paper, proceedings, etc), one must fullfil the following requirements:

* Participate to the official meetings.

* Contribute to the code with documented commits.

* Contribute to the manuscript elaboration.
366 changes: 366 additions & 0 deletions doc/source/tutorials/adaptive.rst

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions doc/source/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ This section provides examples of how to use Qibochem.
hamiltonian
ansatz
measurement
adaptive
5 changes: 4 additions & 1 deletion doc/source/tutorials/measurement.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ In practice, the expectation value for each of the individual Pauli terms have t
This process of obtaining the electronic energy (Hamiltonian expectation value) is still reasonable for a small system.
However, the number of Pauli terms in a molecular Hamiltonian scales on the order of :math:`O(N^4)`, where N is the number of qubits.

.. warning::

The code block below might take a few minutes to run!

.. code-block:: python

# Warning: This code block might take a few minutes to run
from qibochem.driver import Molecule

# Build the N2 molecule and get the molecular Hamiltonian
Expand Down
16 changes: 7 additions & 9 deletions src/qibochem/ansatz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from qibochem.ansatz.basis_rotation import basis_rotation_gates
from qibochem.ansatz.givens_excitation import (
givens_excitation_ansatz,
givens_excitation_circuit,
)
from qibochem.ansatz.hardware_efficient import he_circuit
from qibochem.ansatz.hf_reference import hf_circuit
from qibochem.ansatz.qeb import qeb_circuit
from qibochem.ansatz.ucc import (
generate_excitations,
mp2_amplitude,
sort_excitations,
ucc_ansatz,
ucc_circuit,
)

# TODO: Probably can move some of the functions, e.g. generate_excitations/sort_excitations to a new 'util.py'
from qibochem.ansatz.symmetry import symm_preserving_circuit
from qibochem.ansatz.ucc import ucc_ansatz, ucc_circuit
from qibochem.ansatz.util import generate_excitations, mp2_amplitude, sort_excitations
155 changes: 155 additions & 0 deletions src/qibochem/ansatz/givens_excitation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""
Circuit ansatz for representing a fermionic excitation as a Givens rotation by Arrazola et al.
Reference: https://doi.org/10.22331/q-2022-06-20-742
"""

from qibo import Circuit, gates

from qibochem.ansatz.hf_reference import hf_circuit
from qibochem.ansatz.util import generate_excitations, mp2_amplitude, sort_excitations

# Helper functions


def single_excitation_gate(sorted_orbitals, theta):
"""
Decomposition of a Givens single excitation gate into single qubit rotations and CNOTs. In principle, should be
identical to gates.GIVENS(qubit0, qubit1, theta)

Args:
sorted_orbitals (list): Sorted list of orbitals involved in the excitation
theta (float): Rotation angle

Returns:
(list): List of gates representing the decomposition of the Givens' single excitation gate
"""
result = []
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[1]))
result.append(gates.RY(sorted_orbitals[0], 0.5 * theta))
result.append(gates.CNOT(sorted_orbitals[1], sorted_orbitals[0]))
result.append(gates.RY(sorted_orbitals[0], -0.5 * theta))
result.append(gates.CNOT(sorted_orbitals[1], sorted_orbitals[0]))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[1]))
return result


def double_excitation_gate(sorted_orbitals, theta):
"""
Decomposition of a Givens double excitation gate into single qubit rotations and CNOTs

Args:
sorted_orbitals (list): Sorted list of orbitals involved in the excitation
theta (float): Rotation angle

Returns:
(list): List of gates representing the decomposition of the Givens' double excitation gate
"""
result = []
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[3]))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[2]))
result.append(gates.H(sorted_orbitals[0]))
result.append(gates.H(sorted_orbitals[3]))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[1]))
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[3]))
result.append(gates.RY(sorted_orbitals[0], -0.125 * theta))
result.append(gates.RY(sorted_orbitals[1], 0.125 * theta))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[3]))
result.append(gates.H(sorted_orbitals[3]))
result.append(gates.CNOT(sorted_orbitals[3], sorted_orbitals[1]))
result.append(gates.RY(sorted_orbitals[0], -0.125 * theta))
result.append(gates.RY(sorted_orbitals[1], 0.125 * theta))
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[1]))
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[0]))
result.append(gates.RY(sorted_orbitals[0], 0.125 * theta))
result.append(gates.RY(sorted_orbitals[1], -0.125 * theta))
result.append(gates.CNOT(sorted_orbitals[3], sorted_orbitals[1]))
result.append(gates.H(sorted_orbitals[3]))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[3]))
result.append(gates.RY(sorted_orbitals[0], 0.125 * theta))
result.append(gates.RY(sorted_orbitals[1], -0.125 * theta))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[1]))
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[0]))
result.append(gates.H(sorted_orbitals[0]))
result.append(gates.H(sorted_orbitals[3]))
result.append(gates.CNOT(sorted_orbitals[0], sorted_orbitals[2]))
result.append(gates.CNOT(sorted_orbitals[2], sorted_orbitals[3]))
return result


# Main function
def givens_excitation_circuit(n_qubits, excitation, theta=None):
"""
Circuit ansatz corresponding to the Givens rotation excitation from Arrazola et al.

Args:
n_qubits: Number of qubits in the circuit
excitation: Iterable of orbitals involved in the excitation; must have an even number of elements
E.g. ``[0, 1, 2, 3]`` represents the excitation of electrons in orbitals ``(0, 1)`` to ``(2, 3)``
theta (float): Rotation angle. Default: 0.0

Returns:
Qibo ``Circuit``: Circuit ansatz for a single Givens excitation
"""
sorted_orbitals = sorted(excitation)
# Check size of orbitals input
assert len(sorted_orbitals) % 2 == 0, f"{excitation} must have an even number of items"

if theta is None:
theta = 0.0

circuit = Circuit(n_qubits)
if len(excitation) == 2:
circuit.add(single_excitation_gate(sorted_orbitals, theta))
elif len(excitation) == 4:
circuit.add(double_excitation_gate(sorted_orbitals, theta))
else:
raise NotImplementedError("Can only handle single and double excitations!")
return circuit


def givens_excitation_ansatz(
molecule,
excitations=None,
include_hf=True,
use_mp2_guess=True,
):
"""
Convenience function for buildng a circuit corresponding to the Givens excitation ansatz with multiple excitations
for a given ``Molecule``. If no excitations are given, it defaults to including all possible spin-allowed
excitations, up to doubles.

Args:
molecule: The ``Molecule`` of interest.
excitations: List of excitations (e.g. ``[[0, 1, 2, 3], [0, 1, 4, 5]]``) used to build the
UCC circuit. Overrides the ``excitation_level`` argument
include_hf: Whether or not to start the circuit with a Hartree-Fock circuit. Default: ``True``
use_mp2_guess: Whether to use MP2 amplitudes or a numpy zero array as the initial guess parameter. Default: ``True``;
use the MP2 amplitudes as the default guess parameters

Returns:
Qibo ``Circuit``: Circuit corresponding to a Givens excitation circuit ansatz
"""
# TODO: Consolidate/Meld this function with the ucc_ansatz function; both are largely identical

# Get the number of electrons and spin-orbitals from the molecule argument
n_elec = molecule.nelec if molecule.n_active_e is None else molecule.n_active_e
n_orbs = molecule.nso if molecule.n_active_orbs is None else molecule.n_active_orbs

# If no excitations given, defaults to all possible double and single excitations
if excitations is None:
excitations = []
for order in range(2, 0, -1): # Reversed to get double excitations first, then singles
excitations += sort_excitations(generate_excitations(order, range(0, n_elec), range(n_elec, n_orbs)))
else:
# Some checks to ensure the given excitations are valid
assert all(len(_ex) in (2, 4) for _ex in excitations), "Only single and double excitations allowed!"

# Build the circuit
if include_hf:
circuit = hf_circuit(n_orbs, n_elec) # Only works with (default) JW mapping
else:
circuit = Circuit(n_orbs)
for excitation in excitations:
theta = mp2_amplitude(excitation, molecule.eps, molecule.tei) if use_mp2_guess else None
circuit += givens_excitation_circuit(n_orbs, excitation, theta)
return circuit
105 changes: 105 additions & 0 deletions src/qibochem/ansatz/symmetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Symmetry-Preserving circuit ansatz from Gard et al. Reference: https://doi.org/10.1038/s41534-019-0240-1
"""

from math import factorial

import numpy as np
from qibo import Circuit, gates

# Helper functions


def a_gate(qubit1, qubit2, theta=None, phi=None):
"""
Decomposition of the 'A' gate as defined in the paper, acting on qubit1 and qubit2. 'A' corresponds to the following
unitary matrix:

A(\\theta, \\phi) =
\\begin{pmatrix}
1 & 0 & 0 & 0 \\\\
0 & \\cos \\theta & e^{i \\phi} \\sin \\theta & 0 \\\\
0 & e^{-i \\phi} \\sin \\theta & -\\cos \\theta & 0 \\\\
0 & 0 & 0 & 1
\\end{pmatrix}

Args:
qubit1 (int): Index of the first qubit
qubit2 (int): Index of the second qubit
theta (float): First rotation angle. Default: 0.0
phi (float): Second rotation angle. Default: 0.0

Returns:
(list): List of gates representing the decomposition of the 'A' gate
"""
if theta is None:
theta = 0.0
if phi is None:
phi = 0.0

# R(theta, phi) = R_z (phi + pi) R_y (theta + 0.5*pi)
r_gate = [gates.RY(qubit2, theta + 0.5 * np.pi), gates.RZ(qubit2, phi + np.pi)]
r_gate_dagger = [_gate.dagger() for _gate in r_gate][::-1]
return (
[gates.CNOT(qubit2, qubit1)]
+ r_gate_dagger
+ [gates.CNOT(qubit1, qubit2)]
+ r_gate
+ [gates.CNOT(qubit2, qubit1)]
)


def x_gate_indices(n_qubits, n_electrons):
"""Obtain the qubit indices for X gates to be added to the circuit"""
indices = [2 * _i for _i in range(0, min(n_electrons, n_qubits // 2))]
if n_electrons > n_qubits // 2:
indices += [2 * _i + 1 for _i in range(n_electrons - (n_qubits // 2))]
return sorted(indices)


def a_gate_indices(n_qubits, n_electrons, x_gates):
"""
Obtain the qubit indices for a single layer of the primitive pattern of 'A' gates in the circuit ansatz
"""
assert len(x_gates) == n_electrons, f"n_electrons ({n_electrons}) != Number of X gates given! ({x_gates})"
# 2. Apply 'first layer' of gates on all adjacent pairs of qubits on which either X*I or I*X has been applied.
first_layer = [(_i, _i + 1) for _i in x_gates if _i + 1 < n_qubits and _i + 1 not in x_gates]
first_layer += [(_i - 1, _i) for _i in x_gates if _i - 1 >= 0 and _i - 1 not in x_gates]
# 3a. Apply 'second layer' of gates on adjacent pairs of qubits. Each pair includes 1 qubit acted on in the previous
# step and a qubit free of gates. Continue placing gates on adjacent qubits until all neighboring qubits are connected
second_layer = [(_i, _i + 1) for _i in range(max(pair[1] for pair in first_layer), n_qubits - 1)]
second_layer += [(_i - 1, _i) for _i in range(min(pair[0] for pair in first_layer), 0, -1)]
# 3b. The first and second layers define a primitive pattern:
primitive_pattern = first_layer + second_layer
# Need to add any missing connections between neighbouring qubits
primitive_pattern += [pair for _i in range(n_qubits - 1) if (pair := (_i, _i + 1)) not in primitive_pattern]
# 4. Repeat the primitive pattern until (n_qubits choose n_electrons) A gates are placed
n_gates_per_layer = len(primitive_pattern)
n_a_gates = factorial(n_qubits) // (factorial(n_qubits - n_electrons) * factorial(n_electrons))
assert (
n_a_gates % n_gates_per_layer == 0
), f"n_a_gates ({n_a_gates}) is not a multiple of n_gates_per_layer ({n_gates_per_layer})!"
return (n_a_gates // n_gates_per_layer) * primitive_pattern


# Main function
def symm_preserving_circuit(n_qubits, n_electrons):
"""
Symmetry-preserving circuit ansatz from Gard et al. (https://doi.org/10.1038/s41534-019-0240-1)

Args:
n_qubits: Number of qubits in the quantum circuit
n_electrons: Number of electrons in the molecular system

Returns:
Qibo ``Circuit``: Circuit corresponding to the symmetry-preserving ansatz
"""
circuit = Circuit(n_qubits)
x_gates = x_gate_indices(n_qubits, n_electrons)
circuit.add(gates.X(_i) for _i in x_gates)
# Generate the qubit pair indices for adding A gates
a_gate_qubits = a_gate_indices(n_qubits, n_electrons, x_gates)
a_gates = [a_gate(qubit1, qubit2) for qubit1, qubit2 in a_gate_qubits]
# Each a_gate is a list of elementary gates, so a_gates is a nested list; need to unpack it
circuit.add(_gates for _a_gate in a_gates for _gates in _a_gate)
return circuit
Loading
Loading