Source code for qiskit_nature.second_q.circuit.library.ansatzes.utils.vibration_excitation_generator

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 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.
"""
This method is used by the :class:`~.UVCC` Ansatz in order to construct its excitation operators. It
must be called for each type of excitation (singles, doubles, etc.) that is to be considered in the
Ansatz.
"""

from __future__ import annotations

from typing import Any

import itertools
import logging
import operator

logger = logging.getLogger(__name__)


[docs]def generate_vibration_excitations( num_excitations: int, num_modals: list[int], ) -> list[tuple[tuple[Any, ...], ...]]: """Generates all possible excitations with the given number of excitations for the specified number of particles distributed among the given number of spin orbitals. This method assumes block-ordered spin-orbitals. Args: num_excitations: number of excitations per operator (1 means single excitations, etc.). num_modals: the number of modals per mode. 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. """ partial_sum_modals = list(itertools.accumulate(num_modals, operator.add)) # First, we construct the list of single excitations: single_excitations = [] idx_sum = 0 for accumulated_sum in partial_sum_modals: # the unoccupied modals in each mode are all modals but the lowest one: unoccupied = list(range(idx_sum + 1, accumulated_sum)) # the single excitations for this mode are therefore simply each entry in this list, when # excited into it from the lowest modal of this list: single_excitations.extend([(idx_sum, m) for m in unoccupied]) # and now we update the running index of the lowest modal for the next mode idx_sum = accumulated_sum logger.debug("Generated list of single excitations: %s", single_excitations) # we can find the actual list of excitations by doing the following: # 1. combine the single alpha- and beta-spin excitations # 2. find all possible combinations of length `num_excitations` pool = itertools.combinations(single_excitations, num_excitations) excitations = [] visited_excitations = set() for exc in pool: # validate an excitation by asserting that all indices are unique: # 1. get the frozen set of indices in the excitation exc_set = frozenset(itertools.chain.from_iterable(exc)) # 2. all indices must be unique (size of set equals 2 * num_excitations) # 3. and we also don't want to include permuted variants of identical excitations if len(exc_set) == num_excitations * 2 and exc_set not in visited_excitations: visited_excitations.add(exc_set) exc_tuple = tuple(zip(*exc)) excitations.append(exc_tuple) logger.debug("Added the excitation: %s", exc_tuple) return excitations