# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2020.
#
# 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 trial wavefunction is a Unitary Coupled-Cluster Single and Double excitations
variational form.
For more information, see https://arxiv.org/abs/1805.04340
Also, for more information on the tapering see: https://arxiv.org/abs/1701.08213
And for singlet q-UCCD (full) and pair q-UCCD see: https://arxiv.org/abs/1911.10864
"""
from typing import Optional, Union, List
import logging
import sys
import collections
import copy
import numpy as np
from qiskit.aqua.utils.validation import validate_min, validate_in_set
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.tools import parallel_map
from qiskit.tools.events import TextProgressBar
from qiskit.aqua import aqua_globals
from qiskit.aqua.components.initial_states import InitialState
from qiskit.aqua.operators import WeightedPauliOperator, Z2Symmetries
from qiskit.aqua.components.variational_forms import VariationalForm
from qiskit.chemistry.fermionic_operator import FermionicOperator
logger = logging.getLogger(__name__)
[docs]class UCCSD(VariationalForm):
"""
This trial wavefunction is a Unitary Coupled-Cluster Single and Double excitations
variational form.
For more information, see https://arxiv.org/abs/1805.04340
And for the singlet q-UCCD (full) and pair q-UCCD) see: https://arxiv.org/abs/1911.10864
"""
def __init__(self,
num_orbitals: int,
num_particles: Union[List[int], int],
reps: int = 1,
active_occupied: Optional[List[int]] = None,
active_unoccupied: Optional[List[int]] = None,
initial_state: Optional[InitialState] = None,
qubit_mapping: str = 'parity',
two_qubit_reduction: bool = True,
num_time_slices: int = 1,
shallow_circuit_concat: bool = True,
z2_symmetries: Optional[Z2Symmetries] = None,
method_singles: str = 'both',
method_doubles: str = 'ucc',
excitation_type: str = 'sd',
same_spin_doubles: bool = True,
skip_commute_test: bool = False) -> None:
"""Constructor.
Args:
num_orbitals: number of spin orbitals, has a min. value of 1.
num_particles: number of particles, if it is a list,
the first number is alpha and the second number if beta.
reps: number of repetitions of basic module, has a min. value of 1.
active_occupied: list of occupied orbitals to consider as active space.
active_unoccupied: list of unoccupied orbitals to consider as active space.
initial_state: An initial state object.
qubit_mapping: qubit mapping type.
two_qubit_reduction: two qubit reduction is applied or not.
num_time_slices: parameters for dynamics, has a min. value of 1.
shallow_circuit_concat: indicate whether to use shallow (cheap) mode for
circuit concatenation.
z2_symmetries: represent the Z2 symmetries, including symmetries,
sq_paulis, sq_list, tapering_values, and cliffords.
method_singles: specify the single excitation considered. 'alpha', 'beta',
'both' only alpha or beta spin-orbital single excitations or
both (all of them).
method_doubles: specify the single excitation considered. 'ucc' (conventional
ucc), succ (singlet ucc), succ_full (singlet ucc full),
pucc (pair ucc).
excitation_type: specify the excitation type 'sd', 's', 'd' respectively
for single and double, only single, only double excitations.
same_spin_doubles: enable double excitations of the same spin.
skip_commute_test: when tapering excitation operators we test and exclude any that do
not commute with symmetries. This test can be skipped to include
all tapered excitation operators whether they commute or not.
Raises:
ValueError: Num particles list is not 2 entries
"""
validate_min('num_orbitals', num_orbitals, 1)
if isinstance(num_particles, list) and len(num_particles) != 2:
raise ValueError('Num particles value {}. Number of values allowed is 2'.format(
num_particles))
validate_min('reps', reps, 1)
validate_in_set('qubit_mapping', qubit_mapping,
{'jordan_wigner', 'parity', 'bravyi_kitaev'})
validate_min('num_time_slices', num_time_slices, 1)
validate_in_set('method_singles', method_singles, {'both', 'alpha', 'beta'})
validate_in_set('method_doubles', method_doubles, {'ucc', 'pucc', 'succ', 'succ_full'})
validate_in_set('excitation_type', excitation_type, {'sd', 's', 'd'})
super().__init__()
self._z2_symmetries = Z2Symmetries([], [], [], []) \
if z2_symmetries is None else z2_symmetries
self._num_qubits = num_orbitals if not two_qubit_reduction else num_orbitals - 2
self._num_qubits = self._num_qubits if self._z2_symmetries.is_empty() \
else self._num_qubits - len(self._z2_symmetries.sq_list)
self._reps = reps
self._num_orbitals = num_orbitals
if isinstance(num_particles, list):
self._num_alpha = num_particles[0]
self._num_beta = num_particles[1]
else:
logger.info("We assume that the number of alphas and betas are the same.")
self._num_alpha = num_particles // 2
self._num_beta = num_particles // 2
self._num_particles = [self._num_alpha, self._num_beta]
if sum(self._num_particles) > self._num_orbitals:
raise ValueError('# of particles must be less than or equal to # of orbitals.')
self._initial_state = initial_state
self._qubit_mapping = qubit_mapping
self._two_qubit_reduction = two_qubit_reduction
self._num_time_slices = num_time_slices
self._shallow_circuit_concat = shallow_circuit_concat
# advanced parameters
self._method_singles = method_singles
self._method_doubles = method_doubles
self._excitation_type = excitation_type
self.same_spin_doubles = same_spin_doubles
self._skip_commute_test = skip_commute_test
self._single_excitations, self._double_excitations = \
UCCSD.compute_excitation_lists([self._num_alpha, self._num_beta], self._num_orbitals,
active_occupied, active_unoccupied,
same_spin_doubles=self.same_spin_doubles,
method_singles=self._method_singles,
method_doubles=self._method_doubles,
excitation_type=self._excitation_type,)
self._hopping_ops, self._num_parameters = self._build_hopping_operators()
self._excitation_pool = None
self._bounds = [(-np.pi, np.pi) for _ in range(self._num_parameters)]
self._logging_construct_circuit = True
self._support_parameterized_circuit = True
self.uccd_singlet = False
if self._method_doubles == 'succ_full':
self.uccd_singlet = True
self._single_excitations, self._double_excitations = \
UCCSD.compute_excitation_lists([self._num_alpha, self._num_beta],
self._num_orbitals,
active_occupied, active_unoccupied,
same_spin_doubles=self.same_spin_doubles,
method_singles=self._method_singles,
method_doubles=self._method_doubles,
excitation_type=self._excitation_type,
)
if self.uccd_singlet:
self._hopping_ops, _ = self._build_hopping_operators()
else:
self._hopping_ops, self._num_parameters = self._build_hopping_operators()
self._bounds = [(-np.pi, np.pi) for _ in range(self._num_parameters)]
if self.uccd_singlet:
self._double_excitations_grouped = \
UCCSD.compute_excitation_lists_singlet(self._double_excitations, num_orbitals)
self.num_groups = len(self._double_excitations_grouped)
logging.debug('Grouped double excitations for singlet ucc')
logging.debug(self._double_excitations_grouped)
self._num_parameters = self.num_groups
self._bounds = [(-np.pi, np.pi) for _ in range(self.num_groups)]
# this will order the hopping operators
self.labeled_double_excitations = []
for i in range(len(self._double_excitations)):
self.labeled_double_excitations.append((self._double_excitations[i], i))
order_hopping_op = UCCSD.order_labels_for_hopping_ops(self._double_excitations,
self._double_excitations_grouped)
logging.debug('New order for hopping ops')
logging.debug(order_hopping_op)
self._hopping_ops_doubles_temp = []
self._hopping_ops_doubles = self._hopping_ops[len(self._single_excitations):]
for i in order_hopping_op:
self._hopping_ops_doubles_temp.append(self._hopping_ops_doubles[i])
self._hopping_ops[len(self._single_excitations):] = self._hopping_ops_doubles_temp
self._logging_construct_circuit = True
@property
def single_excitations(self):
"""
Getter of single excitation list
Returns:
list[list[int]]: single excitation list
"""
return self._single_excitations
@property
def double_excitations(self):
"""
Getter of double excitation list
Returns:
list[list[int]]: double excitation list
"""
return self._double_excitations
@property
def excitation_pool(self):
"""
Getter of full list of available excitations (called the pool)
Returns:
list[WeightedPauliOperator]: excitation pool
"""
return self._excitation_pool
def _build_hopping_operators(self):
if logger.isEnabledFor(logging.DEBUG):
TextProgressBar(sys.stderr)
results = parallel_map(UCCSD._build_hopping_operator,
self._single_excitations + self._double_excitations,
task_args=(self._num_orbitals, self._num_particles,
self._qubit_mapping, self._two_qubit_reduction,
self._z2_symmetries,
self._skip_commute_test),
num_processes=aqua_globals.num_processes)
hopping_ops = []
s_e_list = []
d_e_list = []
for op, index in results:
if op is not None and not op.is_empty():
hopping_ops.append(op)
if len(index) == 2: # for double excitation
s_e_list.append(index)
else: # for double excitation
d_e_list.append(index)
self._single_excitations = s_e_list
self._double_excitations = d_e_list
num_parameters = len(hopping_ops) * self._reps
return hopping_ops, num_parameters
@staticmethod
def _build_hopping_operator(index, num_orbitals, num_particles, qubit_mapping,
two_qubit_reduction, z2_symmetries,
skip_commute_test=False):
"""
Builds a hopping operator given the list of indices (index) that is a single or a double
excitation.
Args:
index (list): a single or double excitation (e.g. double excitation [0,1,2,3] for a 4
spin-orbital system)
num_orbitals (int): number of spin-orbitals
num_particles (int): number of electrons
qubit_mapping (str): qubit mapping type
two_qubit_reduction (bool): reduce the number of qubits by 2 if
parity qubit mapping is used
z2_symmetries (Z2Symmetries): class that contains the symmetries
of hamiltonian for tapering
skip_commute_test (bool): when tapering excitation operators we test and exclude any
that do not commute with symmetries. This test can be skipped to
include all tapered excitation operators whether they commute
or not.
Returns:
WeightedPauliOperator: qubit_op
list: index
"""
h_1 = np.zeros((num_orbitals, num_orbitals))
h_2 = np.zeros((num_orbitals, num_orbitals, num_orbitals, num_orbitals))
if len(index) == 2:
i, j = index
h_1[i, j] = 1.0
h_1[j, i] = -1.0
elif len(index) == 4:
i, j, k, m = index
h_2[i, j, k, m] = 1.0
h_2[m, k, j, i] = -1.0
dummpy_fer_op = FermionicOperator(h1=h_1, h2=h_2)
qubit_op = dummpy_fer_op.mapping(qubit_mapping)
if two_qubit_reduction:
qubit_op = Z2Symmetries.two_qubit_reduction(qubit_op, num_particles)
if not z2_symmetries.is_empty():
symm_commuting = True
for symmetry in z2_symmetries.symmetries:
symmetry_op = WeightedPauliOperator(paulis=[[1.0, symmetry]])
symm_commuting = qubit_op.commute_with(symmetry_op)
if not symm_commuting:
break
if not skip_commute_test:
qubit_op = z2_symmetries.taper(qubit_op) if symm_commuting else None
else:
qubit_op = z2_symmetries.taper(qubit_op)
if qubit_op is None:
logger.debug('Excitation (%s) is skipped since it is not commuted '
'with symmetries', ','.join([str(x) for x in index]))
return qubit_op, index
[docs] def manage_hopping_operators(self):
"""
Triggers the adaptive behavior of this UCCSD instance.
This function is used by the Adaptive VQE algorithm. It stores the full list of available
hopping operators in a so called "excitation pool" and clears the previous list to be empty.
Furthermore, the depth is asserted to be 1 which is required by the Adaptive VQE algorithm.
"""
# store full list of excitations as pool
self._excitation_pool = self._hopping_ops.copy()
# check depth parameter
if self._reps != 1:
logger.warning('The reps of the variational form was not 1 but %i which does not work \
in the adaptive VQE algorithm. Thus, it has been reset to 1.')
self._reps = 1
# reset internal excitation list to be empty
self._hopping_ops = []
self._num_parameters = len(self._hopping_ops) * self._reps
self._bounds = [(-np.pi, np.pi) for _ in range(self._num_parameters)]
[docs] def push_hopping_operator(self, excitation):
"""
Pushes a new hopping operator.
Args:
excitation (WeightedPauliOperator): the new hopping operator to be added
"""
self._hopping_ops.append(excitation)
self._num_parameters = len(self._hopping_ops) * self._reps
self._bounds = [(-np.pi, np.pi) for _ in range(self._num_parameters)]
[docs] def pop_hopping_operator(self):
"""
Pops the hopping operator that was added last.
"""
self._hopping_ops.pop()
self._num_parameters = len(self._hopping_ops) * self._reps
self._bounds = [(-np.pi, np.pi) for _ in range(self._num_parameters)]
[docs] def construct_circuit(self, parameters, q=None):
"""
Construct the variational form, given its parameters.
Args:
parameters (Union(numpy.ndarray, list[Parameter], ParameterVector)): circuit parameters
q (QuantumRegister, optional): Quantum Register for the circuit.
Returns:
QuantumCircuit: a quantum circuit with given `parameters`
Raises:
ValueError: the number of parameters is incorrect.
"""
if len(parameters) != self._num_parameters:
raise ValueError('The number of parameters has to be {}'.format(self._num_parameters))
if q is None:
q = QuantumRegister(self._num_qubits, name='q')
if self._initial_state is not None:
circuit = self._initial_state.construct_circuit('circuit', q)
else:
circuit = QuantumCircuit(q)
if logger.isEnabledFor(logging.DEBUG) and self._logging_construct_circuit:
logger.debug("Evolving hopping operators:")
TextProgressBar(sys.stderr)
self._logging_construct_circuit = False
num_excitations = len(self._hopping_ops)
if not self.uccd_singlet:
list_excitation_operators = [
(self._hopping_ops[index % num_excitations], parameters[index])
for index in range(self._reps * num_excitations)]
else:
list_excitation_operators = []
counter = 0
for i in range(int(self._reps * self.num_groups)):
for _ in range(len(self._double_excitations_grouped[i % self.num_groups])):
list_excitation_operators.append((self._hopping_ops[counter],
parameters[i]))
counter += 1
# TODO to uncomment to update for Operator flow:
# from functools import reduce
# ops = [(qubit_op.to_opflow().to_matrix_op() * param).exp_i()
# for (qubit_op, param) in list_excitation_operators]
# circuit += reduce(lambda x, y: x @ y, reversed(ops)).to_circuit()
# return circuit
results = parallel_map(UCCSD._construct_circuit_for_one_excited_operator,
list_excitation_operators,
task_args=(q, self._num_time_slices),
num_processes=aqua_globals.num_processes)
for qc in results:
if self._shallow_circuit_concat:
circuit.data += qc.data
else:
circuit += qc
return circuit
@staticmethod
def _construct_circuit_for_one_excited_operator(qubit_op_and_param, qr, num_time_slices):
qubit_op, param = qubit_op_and_param
# TODO: need to put -1j in the coeff of pauli since the Parameter.
# does not support complex number, but it can be removed if Parameter supports complex
qubit_op = qubit_op * -1j
qc = qubit_op.evolve(state_in=None, evo_time=param,
num_time_slices=num_time_slices,
quantum_registers=qr)
return qc
@property
def preferred_init_points(self):
"""Getter of preferred initial points based on the given initial state."""
if self._initial_state is None:
return None
else:
bitstr = self._initial_state.bitstr
if bitstr is not None:
return np.zeros(self._num_parameters, dtype=np.float)
else:
return None
[docs] @staticmethod
def compute_excitation_lists(num_particles, num_orbitals, active_occ_list=None,
active_unocc_list=None, same_spin_doubles=True,
method_singles='both', method_doubles='ucc',
excitation_type='sd'):
"""
Computes single and double excitation lists.
Args:
num_particles (Union(list, int)): number of particles, if it is a tuple, the first
number is alpha and the second number if beta.
num_orbitals (int): Total number of spin orbitals
active_occ_list (list): List of occupied orbitals to include, indices are
0 to n where n is max(num_alpha, num_beta)
active_unocc_list (list): List of unoccupied orbitals to include, indices are
0 to m where m is num_orbitals // 2 - min(num_alpha, num_beta)
same_spin_doubles (bool): True to include alpha,alpha and beta,beta double excitations
as well as alpha,beta pairings. False includes only alpha,beta
excitation_type (str): choose 'sd', 's', 'd' to compute q-UCCSD, q-UCCS,
q-UCCD excitation lists
method_singles (str): specify type of single excitations, 'alpha', 'beta', 'both' only
alpha or beta spin-orbital single excitations or both (all
single excitations)
method_doubles (str): choose method for double excitations 'ucc' (conventional ucc),
'succ' (singlet ucc), 'succ_full' (singlet ucc full),
'pucc' (pair ucc)
Returns:
list: Single excitation list
list: Double excitation list
Raises:
ValueError: invalid setting of number of particles
ValueError: invalid setting of number of orbitals
"""
if isinstance(num_particles, list):
num_alpha = num_particles[0]
num_beta = num_particles[1]
else:
logger.info("We assume that the number of alphas and betas are the same.")
num_alpha = num_particles // 2
num_beta = num_particles // 2
num_particles = num_alpha + num_beta
if num_particles < 2:
raise ValueError('Invalid number of particles {}'.format(num_particles))
if num_orbitals < 4 or num_orbitals % 2 != 0:
raise ValueError('Invalid number of orbitals {}'.format(num_orbitals))
if num_orbitals <= num_particles:
raise ValueError('No unoccupied orbitals')
# convert the user-defined active space for alpha and beta respectively
active_occ_list_alpha = []
active_occ_list_beta = []
active_unocc_list_alpha = []
active_unocc_list_beta = []
beta_idx = num_orbitals // 2
# making lists of indexes of MOs involved in excitations
if active_occ_list is not None:
active_occ_list = [i if i >= 0 else i + max(num_alpha, num_beta) for i in
active_occ_list]
for i in active_occ_list:
if i < num_alpha:
active_occ_list_alpha.append(i)
else:
raise ValueError(
'Invalid index {} in active active_occ_list {}'.format(i, active_occ_list))
if i < num_beta:
active_occ_list_beta.append(i)
else:
raise ValueError(
'Invalid index {} in active active_occ_list {}'.format(i, active_occ_list))
else:
active_occ_list_alpha = list(range(0, num_alpha))
active_occ_list_beta = [i + beta_idx for i in range(0, num_beta)]
if active_unocc_list is not None:
active_unocc_list = [i + min(num_alpha, num_beta) if i >= 0
else i + num_orbitals // 2 for i in active_unocc_list]
for i in active_unocc_list:
if i >= num_alpha:
active_unocc_list_alpha.append(i)
else:
raise ValueError('Invalid index {} in active active_unocc_list {}'
.format(i, active_unocc_list))
if i >= num_beta:
active_unocc_list_beta.append(i)
else:
raise ValueError('Invalid index {} in active active_unocc_list {}'
.format(i, active_unocc_list))
else:
active_unocc_list_alpha = list(range(num_alpha, num_orbitals // 2))
active_unocc_list_beta = [i + beta_idx for i in range(num_beta, num_orbitals // 2)]
logger.debug('active_occ_list_alpha %s', active_occ_list_alpha)
logger.debug('active_unocc_list_alpha %s', active_unocc_list_alpha)
logger.debug('active_occ_list_beta %s', active_occ_list_beta)
logger.debug('active_unocc_list_beta %s', active_unocc_list_beta)
single_excitations = []
double_excitations = []
# lists of single excitations
if method_singles == 'alpha ':
for occ_alpha in active_occ_list_alpha:
for unocc_alpha in active_unocc_list_alpha:
single_excitations.append([occ_alpha, unocc_alpha])
elif method_singles == 'beta':
for occ_beta in active_occ_list_beta:
for unocc_beta in active_unocc_list_beta:
single_excitations.append([occ_beta, unocc_beta])
else:
for occ_alpha in active_occ_list_alpha:
for unocc_alpha in active_unocc_list_alpha:
single_excitations.append([occ_alpha, unocc_alpha])
for occ_beta in active_occ_list_beta:
for unocc_beta in active_unocc_list_beta:
single_excitations.append([occ_beta, unocc_beta])
logger.info('Singles excitations with alphas and betas orbitals are used.')
# different methods of excitations for double excitations
if method_doubles in ['ucc', 'succ_full']:
for occ_alpha in active_occ_list_alpha:
for unocc_alpha in active_unocc_list_alpha:
for occ_beta in active_occ_list_beta:
for unocc_beta in active_unocc_list_beta:
double_excitations.append(
[occ_alpha, unocc_alpha, occ_beta, unocc_beta])
# pair ucc
elif method_doubles == 'pucc':
for occ_alpha in active_occ_list_alpha:
for unocc_alpha in active_unocc_list_alpha:
for occ_beta in active_occ_list_beta:
for unocc_beta in active_unocc_list_beta:
# makes sure the el. excite from same spatial to same spatial orbitals
if occ_beta - occ_alpha == num_orbitals / 2 \
and unocc_beta - unocc_alpha == num_orbitals / 2:
double_excitations.append(
[occ_alpha, unocc_alpha, occ_beta, unocc_beta])
# singlet ucc
elif method_doubles == 'succ':
for i in active_occ_list_alpha:
for i_prime in active_unocc_list_alpha:
for j in active_occ_list_beta:
for j_prime in active_unocc_list_beta:
if j - beta_idx >= i and j_prime - beta_idx >= i_prime:
double_excitations.append([i, i_prime, j, j_prime])
same_spin_doubles = False
logger.info('Same spin double excitations are forced to be disabled in'
'singlet ucc')
# same spin excitations
if same_spin_doubles and len(active_occ_list_alpha) > 1 and len(
active_unocc_list_alpha) > 1:
for i, occ_alpha in enumerate(active_occ_list_alpha[:-1]):
for j, unocc_alpha in enumerate(active_unocc_list_alpha[:-1]):
for occ_alpha_1 in active_occ_list_alpha[i + 1:]:
for unocc_alpha_1 in active_unocc_list_alpha[j + 1:]:
double_excitations.append([occ_alpha, unocc_alpha,
occ_alpha_1, unocc_alpha_1])
up_active_occ_list = active_occ_list_beta
up_active_unocc_list = active_unocc_list_beta
for i, occ_beta in enumerate(up_active_occ_list[:-1]):
for j, unocc_beta in enumerate(up_active_unocc_list[:-1]):
for occ_beta_1 in up_active_occ_list[i + 1:]:
for unocc_beta_1 in up_active_unocc_list[j + 1:]:
double_excitations.append([occ_beta, unocc_beta,
occ_beta_1, unocc_beta_1])
if excitation_type == 's':
double_excitations = []
elif excitation_type == 'd':
single_excitations = []
else:
logger.info('Singles and Doubles excitations are used.')
logger.debug('single_excitations (%s) %s', len(single_excitations), single_excitations)
logger.debug('double_excitations (%s) %s', len(double_excitations), double_excitations)
return single_excitations, double_excitations
# below are all tool functions that serve to group excitations that are controlled by
# same angle theta in singlet ucc
[docs] @staticmethod
def compute_excitation_lists_singlet(double_exc, num_orbitals):
"""
Outputs the list of lists of grouped excitation. A single list inside is controlled by
the same parameter theta.
Args:
double_exc (list): exc.group. [[0,1,2,3], [...]]
num_orbitals (int): number of molecular orbitals
Returns:
list: de_groups grouped excitations
"""
de_groups = UCCSD.group_excitations_if_same_ao(double_exc, num_orbitals)
return de_groups
[docs] @staticmethod
def same_ao_double_excitation_block_spin(de_1, de_2, num_orbitals):
"""
Regroups the excitations that involve same spatial orbitals
for example, with labeling.
2--- ---5
1--- ---4
0-o- -o-3
excitations [0,1,3,5] and [0,2,3,4] are controlled by the same parameter in the full
singlet UCCSD unlike in usual UCCSD where every excitation is controlled by independent
parameter.
Args:
de_1 (list): double exc in block spin [ from to from to ]
de_2 (list): double exc in block spin [ from to from to ]
num_orbitals (int): number of molecular orbitals
Returns:
int: says if given excitation involves same spatial orbitals 1 = yes, 0 = no.
"""
half_active_space = int(num_orbitals / 2)
de_1_new = copy.copy(de_1)
de_2_new = copy.copy(de_2)
count = -1
for ind in de_1_new:
count += 1
if ind >= half_active_space:
de_1_new[count] = ind % half_active_space
count = -1
for ind in de_2_new:
count += 1
if ind >= half_active_space:
de_2_new[count] = ind % half_active_space
# check if 2 unordered lists are same (involve same AOs)
if collections.Counter(de_1_new) == collections.Counter(de_2_new):
# we check that the permutations of terms i,j and k,l in [[i,j][k,l]] [[a,b][c,d]
# as [i,j] ==? [a,b] or [c,d] and [k,l] ==? ...
# then only return 0, basically criterion for equivalence of 2 mirror excitations
return 1
else:
return 0
[docs] @staticmethod
def group_excitations(list_de, num_orbitals):
"""
Groups the excitations and gives out the remaining ones in the list_de_temp list
because those excitations are controlled by the same parameter in full singlet UCCSD
unlike in usual UCCSD where every excitation has its own parameter.
Args:
list_de (list): list of the double excitations grouped
num_orbitals (int): number of spin-orbitals (qubits)
Returns:
tuple: list_same_ao_group, list_de_temp, the grouped double_exc
(that involve same spatial orbitals)
"""
list_de_temp = copy.copy(list_de)
list_same_ao_group = []
de1 = list_de[0]
counter = 0
for de2 in list_de:
if UCCSD.same_ao_double_excitation_block_spin(de1, de2, num_orbitals) == 1:
counter += 1
if counter == 1:
list_same_ao_group.append(de1)
for i in list_de_temp:
if i == de1:
list_de_temp.remove(de1)
if de1 != de2:
list_same_ao_group.append(de2)
for i in list_de_temp:
if i == de2:
list_de_temp.remove(de2)
return list_same_ao_group, list_de_temp
[docs] @staticmethod
def group_excitations_if_same_ao(list_de, num_orbitals):
"""
Define that, given list of double excitations list_de and number of spin-orbitals
num_orbitals, which excitations involve the same spatial orbitals for full singlet UCCSD.
Args:
list_de (list): list of double exc
num_orbitals (int): number of spin-orbitals
Returns:
list: grouped list of excitations
"""
list_groups = []
list_same_ao_group, list_de_temp = UCCSD.group_excitations(list_de, num_orbitals)
list_groups.append(list_same_ao_group)
while len(list_de_temp) != 0:
list_same_ao_group, list_de_temp = UCCSD.group_excitations(list_de_temp, num_orbitals)
list_groups.append(list_same_ao_group)
return list_groups
[docs] @staticmethod
def order_labels_for_hopping_ops(double_exc, gde):
"""
Orders the hopping operators according to the grouped excitations for the full singlet
UCCSD.
Args:
double_exc (list): list of double excitations
gde (list of lists): list of grouped excitations for full singlet UCCSD
Returns:
list: ordered_labels to order hopping ops
"""
labeled_de = []
for i, _ in enumerate(double_exc):
labeled_de.append((double_exc[i], i))
ordered_labels = []
for group in gde:
for exc in group:
for l_e in labeled_de:
if exc == l_e[0]:
ordered_labels.append(l_e[1])
return ordered_labels