-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
300 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
qiskit_nature/second_q/circuit/library/ansatzes/puccsd.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# This code is part of a Qiskit project. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
""" | ||
The spin-adapted paired-UCC ansatz. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Sequence, cast | ||
from collections import defaultdict | ||
|
||
from qiskit.circuit import QuantumCircuit | ||
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper | ||
from qiskit_nature.second_q.operators import FermionicOp | ||
|
||
from .ucc import UCC | ||
from .utils.fermionic_excitation_generator import ( | ||
generate_fermionic_excitations, | ||
get_alpha_excitations, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class PUCCSD(UCC): | ||
"""The spin-adapted paired-UCC Ansatz. | ||
This ansatz (by default) contains paired single and double excitations. This ensures that not | ||
only the number of particles but also the spin is preserved. [1] | ||
Note, that this ansatz will produce a generalized operator pool (``generalized=True``). | ||
This is a convenience subclass of the UCC ansatz. For more information refer to :class:`UCC`. | ||
References: | ||
[1] `arXiv:2207.00085 <https://arxiv.org/abs/2207.00085>`_ | ||
""" | ||
|
||
def __init__( | ||
self, | ||
num_spatial_orbitals: int | None = None, | ||
num_particles: tuple[int, int] | None = None, | ||
qubit_mapper: QubitConverter | QubitMapper | None = None, | ||
*, | ||
reps: int = 1, | ||
initial_state: QuantumCircuit | None = None, | ||
) -> None: | ||
# pylint: disable=unused-argument | ||
""" | ||
Args: | ||
num_spatial_orbitals: The number of spatial orbitals. | ||
num_particles: The tuple of the number of alpha- and beta-spin particles. | ||
qubit_mapper: The :class:`~qiskit_nature.second_q.mappers.QubitMapper` or | ||
:class:`~qiskit_nature.second_q.mappers.QubitConverter` instance (use of the latter | ||
is deprecated) which takes care of mapping to a qubit operator. | ||
reps: The number of times to repeat the evolved operators. | ||
initial_state: A ``QuantumCircuit`` object to prepend to the circuit. | ||
""" | ||
self._excitations_dict: dict[ | ||
tuple[tuple[int, ...], tuple[int, ...]], list[tuple[tuple[int, ...], tuple[int, ...]]] | ||
] | None = None | ||
super().__init__( | ||
num_spatial_orbitals=num_spatial_orbitals, | ||
num_particles=num_particles, | ||
excitations=self.generate_excitations, | ||
qubit_mapper=qubit_mapper, | ||
alpha_spin=True, | ||
beta_spin=True, | ||
max_spin_excitation=None, | ||
generalized=True, | ||
include_imaginary=False, | ||
reps=reps, | ||
initial_state=initial_state, | ||
) | ||
|
||
def generate_excitations( | ||
self, num_spatial_orbitals: int, num_particles: tuple[int, int] | ||
) -> list[tuple[tuple[int, ...], tuple[int, ...]]]: | ||
"""Generates the excitations for the PUCCSD Ansatz. | ||
Args: | ||
num_spatial_orbitals: the number of spatial orbitals. | ||
num_particles: the number of alpha and beta electrons. Note, these must be identical for | ||
this class. | ||
Returns: | ||
The list of excitations encoded as tuples of tuples. Each tuple in the list is a pair of | ||
tuples. The first tuple contains the occupied spin orbital indices whereas the second | ||
one contains the indices of the unoccupied spin orbitals. | ||
""" | ||
excitations: list[tuple[tuple[int, ...], tuple[int, ...]]] = [] | ||
excitations.extend( | ||
generate_fermionic_excitations( | ||
1, | ||
num_spatial_orbitals, | ||
num_particles, | ||
alpha_spin=True, | ||
beta_spin=False, | ||
generalized=True, | ||
) | ||
) | ||
|
||
num_electrons = num_particles[0] | ||
beta_index_shift = num_spatial_orbitals | ||
|
||
# generate alpha-spin orbital indices for occupied and unoccupied ones | ||
alpha_excitations = get_alpha_excitations( | ||
num_spatial_orbitals, num_electrons, generalized=True | ||
) | ||
logger.debug("Generated list of single alpha excitations: %s", alpha_excitations) | ||
|
||
for alpha_exc in alpha_excitations: | ||
# create the beta-spin excitation by shifting into the upper block-spin orbital indices | ||
beta_exc = ( | ||
alpha_exc[0] + beta_index_shift, | ||
alpha_exc[1] + beta_index_shift, | ||
) | ||
# add the excitation tuple | ||
occ: tuple[int, ...] | ||
unocc: tuple[int, ...] | ||
occ, unocc = zip(alpha_exc, beta_exc) | ||
exc_tuple = (occ, unocc) | ||
excitations.append(exc_tuple) | ||
logger.debug("Added the excitation: %s", exc_tuple) | ||
|
||
return excitations | ||
|
||
def _build_fermionic_excitation_ops(self, excitations: Sequence) -> list[FermionicOp]: | ||
"""Builds all possible excitation operators with the given number of excitations for the | ||
specified number of particles distributed in the number of orbitals. | ||
Args: | ||
excitations: the list of excitations. | ||
Returns: | ||
The list of excitation operators in the second quantized formalism. | ||
""" | ||
operators: list[FermionicOp] = [] | ||
self._excitations_dict = defaultdict(list) | ||
beta_index_shift = self.num_spatial_orbitals | ||
|
||
# Reform the excitations list to a dictionary. Each items in the dictionary | ||
# corresponds to a parameter. | ||
for exc in excitations: | ||
if len(exc[0]) == 1: | ||
# single excitation | ||
self._excitations_dict[exc].append(exc) | ||
self._excitations_dict[exc].append( | ||
((exc[0][0] + beta_index_shift,), (exc[1][0] + beta_index_shift,)) | ||
) | ||
elif len(exc[0]) == 2: | ||
# double excitation | ||
self._excitations_dict[exc].append(exc) | ||
|
||
for exc_list in self._excitations_dict.values(): | ||
sum_ops = cast(FermionicOp, sum(super()._build_fermionic_excitation_ops(exc_list))) | ||
operators.append(sum_ops) | ||
|
||
return operators |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
features: | ||
- | | ||
Adds a new convenience subclass of the :class:`.UCC` ansatz. Namely, the | ||
spin-symmetry-adapted ansatz, :class:`.PUCCSD`, which includes single and double | ||
excitations while always pairing the excitations such that both, the number of | ||
particles and the total spin, will be preserved. | ||
You can use it like any of the other :class:`.UCC`-style ansätze, for example: | ||
.. code-block:: python | ||
from qiskit_nature.second_q.circuit.library import PUCCSD | ||
from qiskit_nature.second_q.mappers import JordanWignerMapper | ||
ansatz = PUCCSD( | ||
num_spatial_orbitals=4, | ||
num_particles=(2, 2), | ||
qubit_mapper=JordanWignerMapper(), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# This code is part of a Qiskit project. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Test the PUCCSD Ansatz.""" | ||
|
||
import unittest | ||
from test import QiskitNatureTestCase | ||
from test.second_q.circuit.library.ansatzes.test_ucc import assert_ucc_like_ansatz | ||
|
||
from ddt import data, ddt, unpack | ||
|
||
from qiskit_nature.second_q.circuit.library import PUCCSD | ||
from qiskit_nature.second_q.mappers import JordanWignerMapper | ||
from qiskit_nature.second_q.operators import FermionicOp | ||
|
||
|
||
@ddt | ||
class TestPUCCSD(QiskitNatureTestCase): | ||
"""Tests for the PUCCSD Ansatz.""" | ||
|
||
@unpack | ||
@data( | ||
( | ||
2, | ||
(1, 1), | ||
[ | ||
FermionicOp( | ||
{"+_0 -_1": 1j, "+_1 -_0": -1j, "+_2 -_3": 1j, "+_3 -_2": -1j}, | ||
num_spin_orbitals=4, | ||
), | ||
FermionicOp({"+_0 +_2 -_1 -_3": 1j, "+_3 +_1 -_2 -_0": -1j}, num_spin_orbitals=4), | ||
], | ||
), | ||
( | ||
4, | ||
(2, 2), | ||
[ | ||
FermionicOp( | ||
{"+_0 -_1": 1j, "+_1 -_0": -1j, "+_4 -_5": 1j, "+_5 -_4": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp( | ||
{"+_0 -_2": 1j, "+_2 -_0": -1j, "+_4 -_6": 1j, "+_6 -_4": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp( | ||
{"+_0 -_3": 1j, "+_3 -_0": -1j, "+_4 -_7": 1j, "+_7 -_4": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp( | ||
{"+_1 -_2": 1j, "+_2 -_1": -1j, "+_5 -_6": 1j, "+_6 -_5": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp( | ||
{"+_1 -_3": 1j, "+_3 -_1": -1j, "+_5 -_7": 1j, "+_7 -_5": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp( | ||
{"+_2 -_3": 1j, "+_3 -_2": -1j, "+_6 -_7": 1j, "+_7 -_6": -1j}, | ||
num_spin_orbitals=8, | ||
), | ||
FermionicOp({"+_0 +_4 -_1 -_5": 1j, "+_5 +_1 -_4 -_0": -1j}, num_spin_orbitals=8), | ||
FermionicOp({"+_0 +_4 -_2 -_6": 1j, "+_6 +_2 -_4 -_0": -1j}, num_spin_orbitals=8), | ||
FermionicOp({"+_0 +_4 -_3 -_7": 1j, "+_7 +_3 -_4 -_0": -1j}, num_spin_orbitals=8), | ||
FermionicOp({"+_1 +_5 -_2 -_6": 1j, "+_6 +_2 -_5 -_1": -1j}, num_spin_orbitals=8), | ||
FermionicOp({"+_1 +_5 -_3 -_7": 1j, "+_7 +_3 -_5 -_1": -1j}, num_spin_orbitals=8), | ||
FermionicOp({"+_2 +_6 -_3 -_7": 1j, "+_7 +_3 -_6 -_2": -1j}, num_spin_orbitals=8), | ||
], | ||
), | ||
) | ||
def test_puccd_ansatz(self, num_spatial_orbitals, num_particles, expect): | ||
"""Tests the PUCCSD Ansatz.""" | ||
mapper = JordanWignerMapper() | ||
|
||
ansatz = PUCCSD( | ||
qubit_mapper=mapper, | ||
num_particles=num_particles, | ||
num_spatial_orbitals=num_spatial_orbitals, | ||
) | ||
|
||
assert_ucc_like_ansatz(self, ansatz, num_spatial_orbitals, expect) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |