Source code for tencirchem.static.uccsd

#  Copyright (c) 2023. The TenCirChem Developers. All Rights Reserved.
#
#  This file is distributed under ACADEMIC PUBLIC LICENSE
#  and WITHOUT ANY WARRANTY. See the LICENSE file for details.


from typing import Tuple, List

import numpy as np
from pyscf.gto.mole import Mole

from tencirchem.static.ucc import UCC
from tencirchem.constants import DISCARD_EPS


[docs] class UCCSD(UCC): """ Run UCCSD calculation. For a comprehensive tutorial see :doc:`/tutorial_jupyter/ucc_functions`. Examples -------- >>> import numpy as np >>> from tencirchem import UCCSD >>> from tencirchem.molecule import h2 >>> uccsd = UCCSD(h2) >>> e_ucc = uccsd.kernel() >>> np.testing.assert_allclose(e_ucc, uccsd.e_fci, atol=1e-10) >>> e_hf = uccsd.energy(np.zeros(uccsd.n_params)) >>> np.testing.assert_allclose(e_hf, uccsd.e_hf, atol=1e-10) """
[docs] def __init__( self, mol: Mole, init_method: str = "mp2", active_space: Tuple[int, int] = None, mo_coeff: np.ndarray = None, pick_ex2: bool = True, epsilon: float = DISCARD_EPS, sort_ex2: bool = True, engine: str = None, run_hf: bool = True, run_mp2: bool = True, run_ccsd: bool = True, run_fci: bool = True, ): r""" Initialize the class with molecular input. Parameters ---------- mol: Mole The molecule as PySCF ``Mole`` object. init_method: str, optional How to determine the initial amplitude guess. Accepts ``"mp2"`` (default), ``"ccsd"``,``"fe"`` and ``"zeros"``. active_space: Tuple[int, int], optional Active space approximation. The first integer is the number of electrons and the second integer is the number or spatial-orbitals. Defaults to None. mo_coeff: np.ndarray, optional Molecule coefficients. If provided then RHF is skipped. Can be used in combination with the ``init_state`` attribute. Defaults to None which means RHF orbitals are used. pick_ex2: bool, optional Whether screen out two body excitations based on the inital guess amplitude. Defaults to True, which means excitations with amplitude less than ``epsilon`` (see below) are discarded. epsilon: float, optional The threshold to discard two body excitations. Defaults to 1e-12. sort_ex2: bool, optional Whether sort two-body excitations in the ansatz based on the initial guess amplitude. Large excitations come first. Defaults to True. Note this could lead to different ansatz for the same molecule at different geometry. engine: str, optional The engine to run the calculation. See :ref:`advanced:Engines` for details. run_hf: bool, optional Whether run HF for molecule orbitals. Defaults to ``True``. run_mp2: bool, optional Whether run MP2 for initial guess and energy reference. Defaults to ``True``. run_ccsd: bool, optional Whether run CCSD for initial guess and energy reference. Defaults to ``True``. run_fci: bool, optional Whether run FCI for energy reference. Defaults to ``True``. See Also -------- tencirchem.KUPCCGSD tencirchem.PUCCD tencirchem.UCC """ super().__init__( mol, init_method, active_space, mo_coeff, engine=engine, run_hf=run_hf, run_mp2=run_mp2, run_ccsd=run_ccsd, run_fci=run_fci, ) self.pick_ex2 = pick_ex2 self.sort_ex2 = sort_ex2 # screen out excitation operators based on t2 amplitude self.t2_discard_eps = epsilon self.ex_ops, self.param_ids, self.init_guess = self.get_ex_ops(self.t1, self.t2)
[docs] def get_ex_ops(self, t1: np.ndarray = None, t2: np.ndarray = None) -> Tuple[List[Tuple], List[int], List[float]]: """ Get one-body and two-body excitation operators for UCCSD ansatz. Pick and sort two-body operators if ``self.pick_ex2`` and ``self.sort_ex2`` are set to ``True``. Parameters ---------- t1: np.ndarray, optional Initial one-body amplitudes based on e.g. CCSD t2: np.ndarray, optional Initial two-body amplitudes based on e.g. MP2 Returns ------- ex_op: List[Tuple] The excitation operators. Each operator is represented by a tuple of ints. param_ids: List[int] The mapping from excitations to parameters. init_guess: List[float] The initial guess for the parameters. See Also -------- get_ex1_ops: Get one-body excitation operators. get_ex2_ops: Get two-body excitation operators. Examples -------- >>> from tencirchem import UCCSD >>> from tencirchem.molecule import h2 >>> uccsd = UCCSD(h2) >>> ex_op, param_ids, init_guess = uccsd.get_ex_ops() >>> ex_op [(3, 2), (1, 0), (1, 3, 2, 0)] >>> param_ids [0, 0, 1] >>> init_guess # doctest:+ELLIPSIS [0.0, ...] """ ex1_ops, ex1_param_ids, ex1_init_guess = self.get_ex1_ops(t1) ex2_ops, ex2_param_ids, ex2_init_guess = self.get_ex2_ops(t2) # screen out symmetrically not allowed excitation ex2_ops, ex2_param_ids, ex2_init_guess = self.pick_and_sort( ex2_ops, ex2_param_ids, ex2_init_guess, self.pick_ex2, self.sort_ex2 ) ex_op = ex1_ops + ex2_ops param_ids = ex1_param_ids + [i + max(ex1_param_ids) + 1 for i in ex2_param_ids] init_guess = ex1_init_guess + ex2_init_guess return ex_op, param_ids, init_guess
[docs] def pick_and_sort(self, ex_ops, param_ids, init_guess, do_pick=True, do_sort=True): # sort operators according to amplitude if do_sort: sorted_ex_ops = sorted(zip(ex_ops, param_ids), key=lambda x: -np.abs(init_guess[x[1]])) else: sorted_ex_ops = list(zip(ex_ops, param_ids)) ret_ex_ops = [] ret_param_ids = [] for ex_op, param_id in sorted_ex_ops: # discard operators with tiny amplitude. # The default eps is so small that the screened out excitations are probably not allowed if do_pick and np.abs(init_guess[param_id]) < self.t2_discard_eps: continue ret_ex_ops.append(ex_op) ret_param_ids.append(param_id) unique_ids = np.unique(ret_param_ids) ret_init_guess = np.array(init_guess)[unique_ids] id_mapping = {old: new for new, old in enumerate(unique_ids)} ret_param_ids = [id_mapping[i] for i in ret_param_ids] return ret_ex_ops, ret_param_ids, list(ret_init_guess)
@property def e_uccsd(self) -> float: """ Returns UCCSD energy """ return self.energy()