Source code for mindquantum.algorithm.nisq.chem.quccsd

# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================

# pylint: disable=duplicate-code

"""Generate qUCCSD operators."""

import itertools
import warnings

import numpy

from mindquantum.core.operators import QubitExcitationOperator, hermitian_conjugated
from mindquantum.core.parameterresolver import ParameterResolver


def _check_int_list(input_list, name):
    if not isinstance(input_list, list):
        raise ValueError(f"The input {str(name)} should be a list, but get {type(input_list)}.")
    for i in input_list:
        if not isinstance(i, int):
            raise ValueError(f"The indices of {str(name)} should be integer, but get {type(i)}.")


# pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements
[docs]def quccsd_generator( n_qubits=None, n_electrons=None, anti_hermitian=True, occ_orb=None, vir_orb=None, generalized=False ): r""" Generate qubit-UCCSD (qUCCSD) ansatz using qubit-excitation operators. Note: Currently, unrestricted version is implemented, i.e., excitations from the same spatial-orbital but with different spins will use distinct variational parameters. Args: n_qubits(int): Number of qubits (spin-orbitals). Default: ``None``. n_electrons(int): Number of electrons (occupied spin-orbitals). Default: ``None``. anti_hermitian(bool): Whether to subtract the hermitian conjugate to form anti-Hermitian operators. Default: ``True``. occ_orb(list): Indices of manually assigned occupied spatial orbitals. Default: ``None``. vir_orb(list): Indices of manually assigned virtual spatial orbitals. Default: ``None``. generalized(bool): Whether to use generalized excitations which do not distinguish occupied or virtual orbitals (qUCCGSD). Default: ``False``. Returns: QubitExcitationOperator, Generator of the qUCCSD operators. Examples: >>> from mindquantum.algorithm.nisq import quccsd_generator >>> quccsd_generator() 0 >>> quccsd_generator(4, 2) -1.0*q_s_0 [Q0^ Q2] + -1.0*q_s_2 [Q0^ Q3] + -1.0*q_d_0 [Q1^ Q0^ Q3 Q2] + -1.0*q_s_1 [Q1^ Q2] + -1.0*q_s_3 [Q1^ Q3] + 1.0*q_s_0 [Q2^ Q0] + 1.0*q_s_1 [Q2^ Q1] + 1.0*q_s_2 [Q3^ Q0] + 1.0*q_s_3 [Q3^ Q1] + 1.0*q_d_0 [Q3^ Q2^ Q1 Q0] >>> q_op = quccsd_generator(occ_orb=[0], vir_orb=[1], generalized=True) >>> q_qubit_op = q_op.to_qubit_operator() >>> print(str(q_qubit_op)[:315]) 0.125*I*q_d_4 + 0.125*I*q_d_7 + 0.125*I*q_d_9 [X0 X1 X2 Y3] + 0.125*I*q_d_4 - 0.125*I*q_d_7 - 0.125*I*q_d_9 [X0 X1 Y2 X3] + 0.25*I*q_d_12 + 0.25*I*q_d_5 + 0.5*I*q_s_0 - 0.5*I*q_s_3 [X0 Y1] + -0.125*I*q_d_4 + 0.125*I*q_d_7 - 0.125*I*q_d_9 [X0 Y1 X2 X3] + 0.125*I*q_d_4 + 0.125*I*q_d_7 - 0.125*I*q_d_9 [X0 Y1 Y2 Y3] + """ if n_qubits is not None and not isinstance(n_qubits, int): raise ValueError(f"The number of qubits should be integer, but get {type(n_qubits)}.") if n_electrons is not None and not isinstance(n_electrons, int): raise ValueError(f"The number of electrons should be integer, but get {type(n_electrons)}.") if isinstance(n_electrons, int) and n_electrons > n_qubits: raise ValueError( "The number of electrons must be smaller than the number of qubits (spin-orbitals) in the ansatz!" ) if not isinstance(anti_hermitian, bool): raise ValueError(f"The parameter anti_hermitian should be bool, but get {type(anti_hermitian)}.") if occ_orb is not None: _check_int_list(occ_orb, "occupied orbitals") if vir_orb is not None: _check_int_list(vir_orb, "virtual orbitals") if not isinstance(generalized, bool): raise ValueError(f"The parameter generalized should be bool, but get {type(generalized)}.") occ_indices = [] vir_indices = [] n_orb = 0 n_orb_occ = 0 n_orb_vir = 0 if n_qubits is not None: if n_qubits % 2 != 0: raise ValueError('The total number of qubits (spin-orbitals) should be even.') n_orb = n_qubits // 2 if n_electrons is not None: n_orb_occ = int(numpy.ceil(n_electrons / 2)) n_orb_vir = n_orb - n_orb_occ occ_indices = list(range(n_orb_occ)) vir_indices = [i + n_orb_occ for i in range(n_orb_vir)] warn_flag = False if occ_orb is not None: if len(set(occ_orb)) != len(occ_orb): raise ValueError("Indices for occupied orbitals should be unique!") warn_flag = True n_orb_occ = len(occ_orb) occ_indices = occ_orb if vir_orb is not None: if len(set(vir_orb)) != len(vir_orb): raise ValueError("Indices for virtual orbitals should be unique!") warn_flag = True n_orb_vir = len(vir_orb) vir_indices = vir_orb if set(occ_indices).intersection(vir_indices): raise ValueError("Occupied and virtual orbitals should be different!") indices_tot = occ_indices + vir_indices max_idx = 0 if set(indices_tot): max_idx = max(set(indices_tot)) n_orb = max(n_orb, max_idx) if warn_flag: warnings.warn( "[Note] Override n_qubits and n_electrons with manually set occ_orb and vir_orb. Handle with caution!" ) if generalized: occ_indices = indices_tot vir_indices = indices_tot n_occ = len(occ_indices) if n_occ == 0: warnings.warn("The number of occupied orbitals is zero. Ansatz may contain no parameters.") n_vir = len(vir_indices) if n_vir == 0: warnings.warn("The number of virtual orbitals is zero. Ansatz may contain no parameters.") # Convert spatial-orbital indices to spin-orbital indices occ_indices_spin = [] vir_indices_spin = [] for i in occ_indices: occ_indices_spin.append(i * 2) occ_indices_spin.append(i * 2 + 1) for i in vir_indices: vir_indices_spin.append(i * 2) vir_indices_spin.append(i * 2 + 1) indices_spin_tot = list(set(occ_indices_spin + vir_indices_spin)) if generalized: occ_indices_spin = indices_spin_tot vir_indices_spin = indices_spin_tot n_occ_spin = len(occ_indices_spin) n_vir_spin = len(vir_indices_spin) generator_quccsd_singles = QubitExcitationOperator() generator_quccsd_doubles = QubitExcitationOperator() singles_counter = 0 # pylint: disable=invalid-name for (p, q) in itertools.product(vir_indices_spin, occ_indices_spin): coeff_s = ParameterResolver({f'q_s_{singles_counter}': 1}) q_pq = QubitExcitationOperator(((p, 1), (q, 0)), 1.0) if anti_hermitian: q_pq = q_pq - hermitian_conjugated(q_pq) q_pq = q_pq.normal_ordered() if list(q_pq.terms): generator_quccsd_singles += q_pq * coeff_s singles_counter += 1 doubles_counter = 0 for pq_counter, (p_idx, q_idx) in enumerate(itertools.product(range(n_vir_spin), range(n_vir_spin))): # Only take half of the loop to avoid repeated excitations if q_idx > p_idx: continue p = vir_indices_spin[p_idx] q = vir_indices_spin[q_idx] for rs_counter, (r_idx, s_idx) in enumerate(itertools.product(range(n_occ_spin), range(n_occ_spin))): # Only take half of the loop to avoid repeated excitations if s_idx > r_idx: continue r = occ_indices_spin[r_idx] s = occ_indices_spin[s_idx] if generalized and pq_counter > rs_counter: continue coeff_d = ParameterResolver({f'q_d_{doubles_counter}': 1}) q_pqrs = QubitExcitationOperator(((p, 1), (q, 1), (r, 0), (s, 0)), 1.0) if anti_hermitian: q_pqrs = q_pqrs - hermitian_conjugated(q_pqrs) q_pqrs = q_pqrs.normal_ordered() if list(q_pqrs.terms): generator_quccsd_doubles += q_pqrs * coeff_d doubles_counter += 1 return generator_quccsd_singles + generator_quccsd_doubles