diff --git a/adcc/Excitation.py b/adcc/Excitation.py index 20e315c0..89d5c626 100644 --- a/adcc/Excitation.py +++ b/adcc/Excitation.py @@ -66,6 +66,8 @@ def __init__(self, parent_state, index, method): self.__parent_state = parent_state self.index = index self.method = method + self.ground_state = parent_state.ground_state + self.reference_state = parent_state.reference_state for key in self.parent_state.excitation_property_keys: fget = getattr(type(self.parent_state), key).fget # Extract the kwargs passed to mark_excitation_property diff --git a/adcc/ExcitedStates.py b/adcc/ExcitedStates.py index 56c32aed..2a886417 100644 --- a/adcc/ExcitedStates.py +++ b/adcc/ExcitedStates.py @@ -217,6 +217,14 @@ def __add__(self, other): ret += other return ret + @property + @mark_excitation_property() + def total_energy(self): + # TODO: excitation_energy_uncorrected for PE-ADC with postSCF + if self.method.level == 0: + return self.excitation_energy + self.reference_state.energy_scf + return self.excitation_energy + self.ground_state.energy(self.method.level) + @cached_property @mark_excitation_property(transform_to_ao=True) @timed_member_call(timer="_property_timer") diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index fcb8d0c6..49e249cd 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -156,7 +156,9 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, ) self.environment = None # no environment attached by default - for name in ["excitation_energy_corrections", "environment"]: + for name in ["excitation_energy_corrections", + "environment", + "gradient_provider"]: if hasattr(hfdata, name): setattr(self, name, getattr(hfdata, name)) diff --git a/adcc/__init__.py b/adcc/__init__.py index a87d0a0b..6c4e750a 100644 --- a/adcc/__init__.py +++ b/adcc/__init__.py @@ -43,6 +43,7 @@ from .AmplitudeVector import AmplitudeVector from .OneParticleOperator import OneParticleOperator from .opt_einsum_integration import register_with_opt_einsum +from .gradients import nuclear_gradient # This has to be the last set of import from .guess import (guess_symmetries, guess_zero, guesses_any, guesses_singlet, @@ -63,6 +64,7 @@ "guess_symmetries", "guesses_spin_flip", "guess_zero", "LazyMp", "adc0", "cis", "adc1", "adc2", "adc2x", "adc3", "cvs_adc0", "cvs_adc1", "cvs_adc2", "cvs_adc2x", "cvs_adc3", + "nuclear_gradient", "banner"] __version__ = "0.15.17" diff --git a/adcc/adc_pp/matrix.py b/adcc/adc_pp/matrix.py index 80c489ca..f4129d97 100644 --- a/adcc/adc_pp/matrix.py +++ b/adcc/adc_pp/matrix.py @@ -21,7 +21,7 @@ ## ## --------------------------------------------------------------------- from math import sqrt -from collections import namedtuple +from dataclasses import dataclass from adcc import block as b from adcc.functions import direct_sum, einsum, zeros_like @@ -42,12 +42,16 @@ # # Dispatch routine # -""" -`apply` is a function mapping an AmplitudeVector to the contribution of this -block to the result of applying the ADC matrix. `diagonal` is an `AmplitudeVector` -containing the expression to the diagonal of the ADC matrix from this block. -""" -AdcBlock = namedtuple("AdcBlock", ["apply", "diagonal"]) +@dataclass +class AdcBlock: + """ + `apply` is a function mapping an AmplitudeVector to the contribution of this + block to the result of applying the ADC matrix. `diagonal` is an + `AmplitudeVector` containing the expression to the diagonal of the ADC matrix + from this block. + """ + apply: callable + diagonal: AmplitudeVector def block(ground_state, spaces, order, variant=None, intermediates=None): diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 6d38a3a7..49e7b843 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -24,6 +24,7 @@ from libadcc import HartreeFockProvider from adcc.misc import cached_property +from adcc.gradients import GradientComponents import psi4 @@ -32,6 +33,72 @@ from ..ExcitedStates import EnergyCorrection +class Psi4GradientProvider: + def __init__(self, wfn): + self.wfn = wfn + self.mol = self.wfn.molecule() + self.backend = "psi4" + self.mints = psi4.core.MintsHelper(self.wfn) + + def correlated_gradient(self, g1_ao, w_ao, g2_ao_1, g2_ao_2): + """ + g1_ao: relaxed one-particle density matrix + w_ao: energy-weighted density matrix + g2_ao_1, g2_ao_2: relaxed two-particle density matrices + """ + natoms = self.mol.natom() + Gradient = {} + Gradient["N"] = np.zeros((natoms, 3)) + Gradient["S"] = np.zeros((natoms, 3)) + Gradient["T"] = np.zeros((natoms, 3)) + Gradient["V"] = np.zeros((natoms, 3)) + Gradient["OEI"] = np.zeros((natoms, 3)) + Gradient["TEI"] = np.zeros((natoms, 3)) + Gradient["Total"] = np.zeros((natoms, 3)) + + # 1st Derivative of Nuclear Repulsion + Gradient["N"] = psi4.core.Matrix.to_array( + self.mol.nuclear_repulsion_energy_deriv1([0, 0, 0]) + ) + # Build Integral Derivatives + cart = ['_X', '_Y', '_Z'] + oei_dict = {"S": "OVERLAP", "T": "KINETIC", "V": "POTENTIAL"} + + deriv1_mat = {} + deriv1_np = {} + # 1st Derivative of OEIs + for atom in range(natoms): + for key in oei_dict: + string = key + str(atom) + deriv1_mat[string] = self.mints.ao_oei_deriv1(oei_dict[key], atom) + for p in range(3): + map_key = string + cart[p] + deriv1_np[map_key] = np.asarray(deriv1_mat[string][p]) + if key == "S": + Gradient["S"][atom, p] = np.sum(w_ao * deriv1_np[map_key]) + else: + Gradient[key][atom, p] = np.sum(g1_ao * deriv1_np[map_key]) + # Build Total OEI Gradient + Gradient["OEI"] = Gradient["T"] + Gradient["V"] + Gradient["S"] + + # Build TE contributions + for atom in range(natoms): + deriv2 = self.mints.ao_tei_deriv1(atom) + for p in range(3): + deriv2_np = np.asarray(deriv2[p]) + Gradient["TEI"][atom, p] += np.einsum( + 'pqrs,prqs->', g2_ao_1, deriv2_np, optimize=True + ) + Gradient["TEI"][atom, p] -= np.einsum( + 'pqrs,psqr->', g2_ao_2, deriv2_np, optimize=True + ) + ret = GradientComponents( + natoms, Gradient["N"], Gradient["S"], + Gradient["T"] + Gradient["V"], Gradient["TEI"] + ) + return ret + + class Psi4OperatorIntegralProvider: def __init__(self, wfn): self.wfn = wfn @@ -130,6 +197,7 @@ def __init__(self, wfn): wfn.nalpha(), wfn.nbeta(), self.restricted) self.operator_integral_provider = Psi4OperatorIntegralProvider(self.wfn) + self.gradient_provider = Psi4GradientProvider(self.wfn) self.environment = None self.environment_implementation = None diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index f2a0a72f..bcfd0424 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -24,15 +24,76 @@ from libadcc import HartreeFockProvider from adcc.misc import cached_property +from adcc.gradients import GradientComponents from .EriBuilder import EriBuilder from ..exceptions import InvalidReference from ..ExcitedStates import EnergyCorrection -from pyscf import ao2mo, gto, scf +from pyscf import ao2mo, gto, scf, grad from pyscf.solvent import ddcosmo +class PyScfGradientProvider: + def __init__(self, scfres): + self.scfres = scfres + self.mol = self.scfres.mol + self.backend = "pyscf" + + def correlated_gradient(self, g1_ao, w_ao, g2_ao_1, g2_ao_2): + natoms = self.mol.natm + Gradient = {} + Gradient["N"] = np.zeros((natoms, 3)) + Gradient["S"] = np.zeros((natoms, 3)) + Gradient["T+V"] = np.zeros((natoms, 3)) + Gradient["OEI"] = np.zeros((natoms, 3)) + Gradient["TEI"] = np.zeros((natoms, 3)) + Gradient["Total"] = np.zeros((natoms, 3)) + + # TODO: does RHF/UHF matter here? + gradient = grad.RHF(self.scfres) + hcore_deriv = gradient.hcore_generator() + Sx = -1.0 * self.mol.intor('int1e_ipovlp', aosym='s1') + ERIx = -1.0 * self.mol.intor('int2e_ip1', aosym='s1') + + ao_slices = self.mol.aoslice_by_atom() + for ia in range(natoms): + # TODO: only contract/compute with specific slices + # of density matrices (especially TPDM) + # this requires a lot of work however... + k0, k1 = ao_slices[ia, 2:] + + # derivative of the overlap matrix + Sx_a = np.zeros_like(Sx) + Sx_a[:, k0:k1] = Sx[:, k0:k1] + Sx_a += Sx_a.transpose(0, 2, 1) + Gradient["S"][ia] += np.einsum("xpq,pq->x", Sx_a, w_ao) + + # derivative of the core Hamiltonian + Hx_a = hcore_deriv(ia) + Gradient["T+V"][ia] += np.einsum("xpq,pq->x", Hx_a, g1_ao) + + # derivatives of the ERIs + ERIx_a = np.zeros_like(ERIx) + ERIx_a[:, k0:k1] = ERIx[:, k0:k1] + ERIx_a += ( + + ERIx_a.transpose(0, 2, 1, 4, 3) + + ERIx_a.transpose(0, 3, 4, 1, 2) + + ERIx_a.transpose(0, 4, 3, 2, 1) + ) + Gradient["TEI"][ia] += np.einsum( + "pqrs,xprqs->x", g2_ao_1, ERIx_a, optimize=True + ) + Gradient["TEI"][ia] -= np.einsum( + "pqrs,xpsqr->x", g2_ao_2, ERIx_a, optimize=True + ) + ret = GradientComponents( + natoms, gradient.grad_nuc(), Gradient["S"], + Gradient["T+V"], Gradient["TEI"] + ) + return ret + + class PyScfOperatorIntegralProvider: def __init__(self, scfres): self.scfres = scfres @@ -196,7 +257,7 @@ def coefficients(self): def compute_mo_eri(self, blocks, spins): coeffs = tuple(self.coefficients[blocks[i] + spins[i]] for i in range(4)) - # TODO Pyscf usse HDF5 internal to do the AO2MO here we read it all + # TODO Pyscf uses HDF5 internal to do the AO2MO here we read it all # into memory. This wastes memory and could be avoided if temporary # files were used instead. These could be deleted on the call # to `flush_cache` automatically. @@ -223,6 +284,7 @@ def __init__(self, scfres): self.operator_integral_provider = PyScfOperatorIntegralProvider( self.scfres ) + self.gradient_provider = PyScfGradientProvider(self.scfres) if not self.restricted: assert self.scfres.mo_coeff[0].shape[1] == \ self.scfres.mo_coeff[1].shape[1] diff --git a/adcc/gradients/TwoParticleDensityMatrix.py b/adcc/gradients/TwoParticleDensityMatrix.py new file mode 100644 index 00000000..441d4337 --- /dev/null +++ b/adcc/gradients/TwoParticleDensityMatrix.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2021 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +import numpy as np + +import libadcc +import adcc.block as b +from adcc.MoSpaces import split_spaces +from adcc.Tensor import Tensor +from adcc.functions import einsum + + +class TwoParticleDensityMatrix: + """ + Two-particle density matrix (TPDM) used for gradient evaluations + """ + def __init__(self, spaces): + if hasattr(spaces, "mospaces"): + self.mospaces = spaces.mospaces + else: + self.mospaces = spaces + # Set reference_state if possible + if isinstance(spaces, libadcc.ReferenceState): + self.reference_state = spaces + elif hasattr(spaces, "reference_state"): + assert isinstance(spaces.reference_state, libadcc.ReferenceState) + self.reference_state = spaces.reference_state + + occs = sorted(self.mospaces.subspaces_occupied, reverse=True) + virts = sorted(self.mospaces.subspaces_virtual, reverse=True) + self.orbital_subspaces = occs + virts + # check that orbital subspaces are correct + assert sum(self.mospaces.n_orbs(ss) for ss in self.orbital_subspaces) \ + == self.mospaces.n_orbs("f") + # set the canonical blocks explicitly + self.blocks = [ + b.oooo, b.ooov, b.oovv, + b.ovov, b.ovvv, b.vvvv, + ] + if self.mospaces.has_core_occupied_space: + self.blocks += [ + b.cccc, b.ococ, b.cvcv, + b.ocov, b.cccv, b.cocv, b.ocoo, + b.ccco, b.occv, b.ccvv, b.ocvv, + ] + # make sure we didn't add any block twice! + assert len(list(set(self.blocks))) == len(self.blocks) + self._tensors = {} + + @property + def shape(self): + """ + Returns the shape tuple of the TwoParticleDensityMatrix + """ + size = self.mospaces.n_orbs("f") + return 4 * (size,) + + @property + def size(self): + """ + Returns the number of elements of the TwoParticleDensityMatrix + """ + return np.prod(self.shape) + + def __setitem__(self, block, tensor): + """ + Assigns a tensor to the specified block + """ + if block not in self.blocks: + raise KeyError(f"Invalid block {block} assigned. " + f"Available blocks are: {self.blocks}.") + s1, s2, s3, s4 = split_spaces(block) + expected_shape = (self.mospaces.n_orbs(s1), + self.mospaces.n_orbs(s2), + self.mospaces.n_orbs(s3), + self.mospaces.n_orbs(s4)) + if expected_shape != tensor.shape: + raise ValueError("Invalid shape of incoming tensor. " + f"Expected shape {expected_shape}, but " + f"got shape {tensor.shape} instead.") + self._tensors[block] = tensor + + def __getitem__(self, block): + if block not in self.blocks: + raise KeyError(f"Invalid block {block} requested. " + f"Available blocks are: {self.blocks}.") + if block not in self._tensors: + sym = libadcc.make_symmetry_eri(self.mospaces, block) + self._tensors[block] = Tensor(sym) + return self._tensors[block] + + def to_ndarray(self): + raise NotImplementedError("ndarray export not implemented for TPDM.") + + def copy(self): + """ + Return a deep copy of the TwoParticleDensityMatrix + """ + ret = TwoParticleDensityMatrix(self.mospaces) + for bl in self.blocks_nonzero: + ret[bl] = self.block(bl).copy() + if hasattr(self, "reference_state"): + ret.reference_state = self.reference_state + return ret + + @property + def blocks_nonzero(self): + """ + Returns a list of the non-zero block labels + """ + return [b for b in self.blocks if b in self._tensors] + + def is_zero_block(self, block): + """ + Checks if block is explicitly marked as zero block. + Returns False if the block does not exist. + """ + if block not in self.blocks: + return False + return block not in self.blocks_nonzero + + def block(self, block): + """ + Returns tensor of the given block. + Does not create a block in case it is marked as a zero block. + Use __getitem__ for that purpose. + """ + if block not in self.blocks_nonzero: + raise KeyError("The block function does not support " + "access to zero-blocks. Available non-zero " + f"blocks are: {self.blocks_nonzero}.") + return self._tensors[block] + + def __getattr__(self, attr): + return self.__getitem__(b.__getattr__(attr)) + + def __setattr__(self, attr, value): + try: + self.__setitem__(b.__getattr__(attr), value) + except AttributeError: + super().__setattr__(attr, value) + + def set_zero_block(self, block): + """ + Set a given block as zero block + """ + if block not in self.blocks: + raise KeyError(f"Invalid block {block} set as zero block. " + f"Available blocks are: {self.blocks}.") + self._tensors.pop(block) + + def __transform_to_ao(self, refstate_or_coefficients): + if not len(self.blocks_nonzero): + raise ValueError("At least one non-zero block is needed to " + "transform the TwoParticleDensityMatrix.") + if isinstance(refstate_or_coefficients, libadcc.ReferenceState): + hf = refstate_or_coefficients + coeff_map = {} + for sp in self.orbital_subspaces: + coeff_map[sp + "_a"] = hf.orbital_coefficients_alpha(sp + "b") + coeff_map[sp + "_b"] = hf.orbital_coefficients_beta(sp + "b") + else: + coeff_map = refstate_or_coefficients + + g2_ao_1 = 0 + g2_ao_2 = 0 + transf = "ip,jq,ijkl,kr,ls->pqrs" + cc = coeff_map + for block in self.blocks_nonzero: + s1, s2, s3, s4 = split_spaces(block) + ten = self[block] + aaaa = einsum(transf, cc[f"{s1}_a"], cc[f"{s2}_a"], + ten, cc[f"{s3}_a"], cc[f"{s4}_a"]) + bbbb = einsum(transf, cc[f"{s1}_b"], cc[f"{s2}_b"], + ten, cc[f"{s3}_b"], cc[f"{s4}_b"]) + g2_ao_1 += ( + + aaaa + + bbbb + + einsum(transf, cc[f"{s1}_a"], cc[f"{s2}_b"], + ten, cc[f"{s3}_a"], cc[f"{s4}_b"]) # abab + + einsum(transf, cc[f"{s1}_b"], cc[f"{s2}_a"], + ten, cc[f"{s3}_b"], cc[f"{s4}_a"]) # baba + ) + g2_ao_2 += ( + + aaaa + + bbbb + + einsum(transf, cc[f"{s1}_a"], cc[f"{s2}_b"], + ten, cc[f"{s3}_b"], cc[f"{s4}_a"]) # abba + + einsum(transf, cc[f"{s1}_b"], cc[f"{s2}_a"], + ten, cc[f"{s3}_a"], cc[f"{s4}_b"]) # baab + ) + return (g2_ao_1.evaluate(), g2_ao_2.evaluate()) + + def to_ao_basis(self, refstate_or_coefficients=None): + """ + Transform the density to the AO basis for contraction + with two-electron integrals. + ALL coefficients are already accounted for in the density matrix. + Two blocks are returned, the first one needs to be contracted with + prqs, the second one with -psqr (in Chemists' notation). + """ + if isinstance(refstate_or_coefficients, (dict, libadcc.ReferenceState)): + return self.__transform_to_ao(refstate_or_coefficients) + elif refstate_or_coefficients is None: + if not hasattr(self, "reference_state"): + raise ValueError("Argument reference_state is required if no " + "reference_state is stored in the " + "TwoParticleDensityMatrix") + return self.__transform_to_ao(self.reference_state) + else: + raise TypeError("Argument type not supported.") + + def __iadd__(self, other): + if self.mospaces != other.mospaces: + raise ValueError("Cannot add TwoParticleDensityMatrices with " + "differing mospaces.") + + for bl in other.blocks_nonzero: + if self.is_zero_block(bl): + self[bl] = other.block(bl).copy() + else: + self[bl] = self.block(bl) + other.block(bl) + + # Update ReferenceState pointer + if hasattr(self, "reference_state"): + if hasattr(other, "reference_state") \ + and self.reference_state != other.reference_state: + delattr(self, "reference_state") + return self + + def __isub__(self, other): + if self.mospaces != other.mospaces: + raise ValueError("Cannot subtract TwoParticleDensityMatrix with " + "differing mospaces.") + + for bl in other.blocks_nonzero: + if self.is_zero_block(bl): + self[bl] = -1.0 * other.block(bl) # The copy is implicit + else: + self[bl] = self.block(bl) - other.block(bl) + + # Update ReferenceState pointer + if hasattr(self, "reference_state"): + if hasattr(other, "reference_state") \ + and self.reference_state != other.reference_state: + delattr(self, "reference_state") + return self + + def __imul__(self, other): + if not isinstance(other, (float, int)): + return NotImplemented + for bl in self.blocks_nonzero: + self[bl] = self.block(bl) * other + return self + + def __add__(self, other): + return self.copy().__iadd__(other) + + def __sub__(self, other): + return self.copy().__isub__(other) + + def __mul__(self, other): + return self.copy().__imul__(other) + + def __rmul__(self, other): + return self.copy().__imul__(other) + + def evaluate(self): + for bl in self.blocks_nonzero: + self.block(bl).evaluate() + return self diff --git a/adcc/gradients/__init__.py b/adcc/gradients/__init__.py new file mode 100644 index 00000000..47b47bc5 --- /dev/null +++ b/adcc/gradients/__init__.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2021 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +from dataclasses import dataclass +from typing import Dict, Union, Optional +import numpy as np + +from adcc.LazyMp import LazyMp +from adcc.Excitation import Excitation +from adcc.timings import Timer +from adcc.functions import einsum, evaluate + +from adcc.OneParticleOperator import OneParticleOperator, product_trace +from .TwoParticleDensityMatrix import TwoParticleDensityMatrix +from .orbital_response import ( + orbital_response, orbital_response_rhs, energy_weighted_density_matrix +) +from .amplitude_response import amplitude_relaxed_densities + + +@dataclass(frozen=True) +class GradientComponents: + natoms: int + nuc: np.ndarray + overlap: np.ndarray + hcore: np.ndarray + two_electron: np.ndarray + custom: Optional[Dict[str, np.ndarray]] = None + + @property + def total(self): + """Returns the total gradient""" + ret = sum([self.nuc, self.overlap, self.hcore, self.two_electron]) + if self.custom is None: + return ret + for c in self.custom: + ret += self.custom[c] + + @property + def one_electron(self): + """Returns the one-electron gradient""" + return sum([self.nuc, self.overlap, self.hcore]) + + +@dataclass(frozen=True) +class GradientResult: + excitation_or_mp: Union[LazyMp, Excitation] + components: GradientComponents + g1: OneParticleOperator + g2: TwoParticleDensityMatrix + timer: Timer + g1a: Optional[OneParticleOperator] = None + g2a: Optional[TwoParticleDensityMatrix] = None + + @property + def reference_state(self): + return self.excitation_or_mp.reference_state + + @property + def _energy(self): + """Compute energy based on density matrices + for testing purposes""" + if self.g1a is None: + raise ValueError("No unrelaxed one-particle " + "density available.") + if self.g2a is None: + raise ValueError("No unrelaxed two-particle " + "density available.") + ret = 0.0 + hf = self.reference_state + for b in self.g1a.blocks_nonzero: + ret += self.g1a[b].dot(hf.fock(b)) + for b in self.g2a.blocks_nonzero: + ret += self.g2a[b].dot(hf.eri(b)) + return ret + + @property + def dipole_moment_relaxed(self): + """Returns the orbital-relaxed electric dipole moment""" + return self.__dipole_moment_electric(self.g1) + + @property + def dipole_moment_unrelaxed(self): + """Returns the unrelaxed electric dipole moment""" + if self.g1a is None: + raise ValueError("No unrelaxed one-particle " + "density available.") + hf = self.reference_state + return self.__dipole_moment_electric(self.g1a + hf.density) + + @property + def total(self): + """Returns the total gradient""" + return self.components.total + + def __dipole_moment_electric(self, dm): + dips = self.reference_state.operators.electric_dipole + elec_dip = -1.0 * np.array( + [product_trace(dm, dip) for dip in dips] + ) + return elec_dip + self.reference_state.nuclear_dipole + + +def nuclear_gradient(excitation_or_mp): + if isinstance(excitation_or_mp, LazyMp): + mp = excitation_or_mp + elif isinstance(excitation_or_mp, Excitation): + mp = excitation_or_mp.ground_state + else: + raise TypeError("Gradient can only be computed for " + "Excitation or LazyMp object.") + + timer = Timer() + hf = mp.reference_state + with timer.record("amplitude_response"): + g1a, g2a = amplitude_relaxed_densities(excitation_or_mp) + + with timer.record("orbital_response"): + rhs = orbital_response_rhs(hf, g1a, g2a).evaluate() + lam = orbital_response(hf, rhs) + + # orbital-relaxed OPDM (without reference state) + g1o = g1a.copy() + g1o.ov = 0.5 * lam.ov + if hf.has_core_occupied_space: + g1o.cv = 0.5 * lam.cv + # orbital-relaxed OPDM (including reference state) + g1 = g1o.copy() + g1 += hf.density + + with timer.record("energy_weighted_density_matrix"): + w = energy_weighted_density_matrix(hf, g1o, g2a) + + # build two-particle density matrices for contraction with TEI + # prefactors see eqs 17 and A4 in 10.1063/1.5085117 + with timer.record("form_tpdm"): + g2_hf = TwoParticleDensityMatrix(hf) + g2_oresp = TwoParticleDensityMatrix(hf) + delta_ij = hf.density.oo + if hf.has_core_occupied_space: + delta_IJ = hf.density.cc + + g2_hf.oooo = 0.25 * (- einsum("li,jk->ijkl", delta_ij, delta_ij) + + einsum("ki,jl->ijkl", delta_ij, delta_ij)) + g2_hf.cccc = -0.5 * einsum("IK,JL->IJKL", delta_IJ, delta_IJ) + g2_hf.ococ = -1.0 * einsum("ik,JL->iJkL", delta_ij, delta_IJ) + + g2_oresp.cccc = einsum("IK,JL->IJKL", delta_IJ, g1o.cc + delta_IJ) + g2_oresp.ococ = ( + + einsum("ik,JL->iJkL", delta_ij, g1o.cc + 2.0 * delta_IJ) + + einsum("ik,JL->iJkL", g1o.oo, delta_IJ) + ) + g2_oresp.oooo = einsum("ij,kl->kilj", delta_ij, g1o.oo) + g2_oresp.ovov = einsum("ij,ab->iajb", delta_ij, g1o.vv) + g2_oresp.cvcv = einsum("IJ,ab->IaJb", delta_IJ, g1o.vv) + g2_oresp.ocov = 2 * einsum("ik,Ja->iJka", delta_ij, g1o.cv) + g2_oresp.cccv = 2 * einsum("IK,Ja->IJKa", delta_IJ, g1o.cv) + g2_oresp.ooov = 2 * einsum("ik,ja->ijka", delta_ij, g1o.ov) + g2_oresp.cocv = 2 * einsum("IK,ja->IjKa", delta_IJ, g1o.ov) + g2_oresp.ocoo = 2 * einsum("ik,Jl->iJkl", delta_ij, g1o.co) + g2_oresp.ccco = 2 * einsum("IK,Jl->IJKl", delta_IJ, g1o.co) + + # scale for contraction with integrals + g2a.oovv *= 0.5 + g2a.ccvv *= 0.5 + g2a.occv *= 2.0 + g2a.vvvv *= 0.25 + + g2_total = evaluate(g2_hf + g2a + g2_oresp) + else: + g2_hf.oooo = 0.25 * (- einsum("li,jk->ijkl", delta_ij, delta_ij) + + einsum("ki,jl->ijkl", delta_ij, delta_ij)) + + g2_oresp.oooo = einsum("ij,kl->kilj", delta_ij, g1o.oo) + g2_oresp.ovov = einsum("ij,ab->iajb", delta_ij, g1o.vv) + g2_oresp.ooov = (- einsum("kj,ia->ijka", delta_ij, g1o.ov) + + einsum("ki,ja->ijka", delta_ij, g1o.ov)) + + # scale for contraction with integrals + g2a.oovv *= 0.5 + g2a.oooo *= 0.25 + g2a.vvvv *= 0.25 + g2_total = evaluate(g2_hf + g2a + g2_oresp) + + with timer.record("transform_ao"): + g2_ao_1, g2_ao_2 = g2_total.to_ao_basis() + g2_ao_1, g2_ao_2 = g2_ao_1.to_ndarray(), g2_ao_2.to_ndarray() + g1_ao = sum(g1.to_ao_basis(hf)).to_ndarray() + w_ao = sum(w.to_ao_basis(hf)).to_ndarray() + + with timer.record("contract_integral_derivatives"): + grad = hf.gradient_provider.correlated_gradient( + g1_ao, w_ao, g2_ao_1, g2_ao_2 + ) + + ret = GradientResult(excitation_or_mp, grad, g1, g2_total, + timer, g1a=g1a, g2a=g2a) + return ret diff --git a/adcc/gradients/amplitude_response.py b/adcc/gradients/amplitude_response.py new file mode 100644 index 00000000..7f9f37bc --- /dev/null +++ b/adcc/gradients/amplitude_response.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2021 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +from math import sqrt + +from adcc.functions import einsum, direct_sum, evaluate + +from .TwoParticleDensityMatrix import TwoParticleDensityMatrix +from adcc.OneParticleOperator import OneParticleOperator +from adcc.LazyMp import LazyMp +from adcc.Excitation import Excitation +import adcc.block as b + + +def t2bar_oovv_adc2(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + u = exci.excitation_vector + df_ia = mp.df(b.ov) + t2bar = 0.5 * ( + hf.oovv + - 2.0 * einsum( + "ijcb,ac->ijab", hf.oovv, g1a_adc0.vv + ).antisymmetrise(2, 3) + + 2.0 * einsum( + "kjab,ik->ijab", hf.oovv, g1a_adc0.oo + ).antisymmetrise(0, 1) + + 4.0 * einsum( + "ia,jkbc,kc->ijab", u.ph, hf.oovv, u.ph + ).antisymmetrise(2, 3).antisymmetrise(0, 1) + ) / ( + 2.0 * direct_sum("ia+jb->ijab", df_ia, df_ia).symmetrise(0, 1) + ) + return t2bar + + +def tbarD_oovv_adc3(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + u = exci.excitation_vector + df_ia = mp.df(b.ov) + # Table XI (10.1063/1.5085117) + tbarD = 0.5 * ( + hf.oovv + - 2.0 * einsum( + "ijcb,ac->ijab", hf.oovv, g1a_adc0.vv + ).antisymmetrise(2, 3) + + 2.0 * einsum( + "kjab,ik->ijab", hf.oovv, g1a_adc0.oo + ).antisymmetrise(0, 1) + + 4.0 * einsum( + "ia,jkbc,kc->ijab", u.ph, hf.oovv, u.ph + ).antisymmetrise(2, 3).antisymmetrise(0, 1) + ) / ( + 2.0 * direct_sum("ia+jb->ijab", df_ia, df_ia).symmetrise(0, 1) + ) + return tbarD + + +def tbar_TD_oovv_adc3(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + # Table XI (10.1063/1.5085117) + tbarD = tbarD_oovv_adc3(exci, g1a_adc0) + tbarD.evaluate() + ret = 2.0 * 4.0 * ( + + 2.0 * einsum("ikac,jckb->ijab", tbarD, hf.ovov) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ret += 2.0 * ( + - 1.0 * einsum("ijcd,cdab->ijab", tbarD, hf.vvvv) + - 1.0 * einsum("klab,ijkl->ijab", tbarD, hf.oooo) + ) + return ret + + +def rho_bar_adc3(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + u = exci.excitation_vector + df_ia = mp.df(b.ov) + rho_bar = 2.0 * ( + + 1.0 * einsum("ijka,jk->ia", hf.ooov, g1a_adc0.oo) + + 1.0 * einsum("ijkb,jb,ka->ia", hf.ooov, u.ph, u.ph) + - 1.0 * einsum("icab,bc->ia", hf.ovvv, g1a_adc0.vv) + + 1.0 * einsum("jcab,jb,ic->ia", hf.ovvv, u.ph, u.ph) + ) / df_ia + return rho_bar + + +def tbar_rho_oovv_adc3(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + # Table XI (10.1063/1.5085117) + rho_bar = rho_bar_adc3(exci, g1a_adc0) + ret = ( + - 1.0 * 2.0 * einsum("jcab,ic->ijab", hf.ovvv, rho_bar).antisymmetrise(0, 1) + - 1.0 * 2.0 * einsum("ijkb,ka->ijab", hf.ooov, rho_bar).antisymmetrise(2, 3) + ) + return ret + + +def t2bar_oovv_adc3(exci, g1a_adc0, g2a_adc1): + mp = exci.ground_state + hf = mp.reference_state + u = exci.excitation_vector + tbar_TD = tbar_TD_oovv_adc3(exci, g1a_adc0) + tbar_TD.evaluate() + tbar_rho = tbar_rho_oovv_adc3(exci, g1a_adc0) + tbar_rho.evaluate() + t2 = mp.t2(b.oovv).evaluate() + df_ia = mp.df(b.ov) + rx = einsum("jb,ijab->ia", u.ph, t2).evaluate() + + ttilde1 = ( + - 1.0 * einsum("klab,ijkm,lm->ijab", t2, hf.oooo, g1a_adc0.oo) + - 1.0 * 2.0 * einsum( + "ka,ijkl,lb->ijab", u.ph, hf.oooo, rx + ).antisymmetrise(2, 3) + + 1.0 * 2.0 * ( + + 0.5 * einsum("ik,jklm,lmab->ijab", g1a_adc0.oo, hf.oooo, t2) + - 2.0 * einsum("jkab,lm,ilkm->ijab", t2, g1a_adc0.oo, hf.oooo) + ).antisymmetrise(0, 1) + - 1.0 * 4.0 * ( + + 0.5 * einsum("ia,kc,jklm,lmbc->ijab", u.ph, u.ph, hf.oooo, t2) + - 2.0 * einsum("ka,likm,jmbc,lc->ijab", u.ph, hf.oooo, t2, u.ph) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde1.evaluate() + + ttilde2 = 4.0 * ( + - 1.0 * einsum("ijkc,lc,klab->ijab", hf.ooov, u.ph, u.pphh) + + 1.0 * 2.0 * einsum( + "ikab,jlkc,lc->ijab", u.pphh, hf.ooov, u.ph + ).antisymmetrise(0, 1) + - 1.0 * 4.0 * einsum( + "jklb,kc,ilac->ijab", hf.ooov, u.ph, u.pphh + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde2.evaluate() + + ttilde3 = ( + + 1.0 * 2.0 * einsum( + "ijbc,ac->ijab", hf.oovv, g1a_adc0.vv + ).antisymmetrise(2, 3) + - 1.0 * 2.0 * einsum( + "jkab,ik->ijab", hf.oovv, g1a_adc0.oo + ).antisymmetrise(0, 1) + + 1.0 * 4.0 * einsum( + "ia,jkbc,kc->ijab", u.ph, hf.oovv, u.ph + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde3.evaluate() + + # TODO: intermediate x_ka ? + # TODO: intermediate x_lc t_jlbd? + ttilde4 = ( + + 1.0 * 2.0 * ( + - 2.0 * einsum("jkab,ickd,cd->ijab", t2, hf.ovov, g1a_adc0.vv) + - 2.0 * einsum("ic,kd,jcld,klab->ijab", u.ph, u.ph, hf.ovov, t2) + + 1.0 * einsum("ic,jkab,ld,lckd->ijab", u.ph, t2, u.ph, hf.ovov) + + 1.0 * einsum("jkab,kc,ld,lcid->ijab", t2, u.ph, u.ph, hf.ovov) + ).antisymmetrise(0, 1) + + 1.0 * 2.0 * ( + - 2.0 * einsum("ijcb,kalc,kl->ijab", t2, hf.ovov, g1a_adc0.oo) + - 2.0 * einsum("ka,lc,kbld,ijcd->ijab", u.ph, u.ph, hf.ovov, t2) + + 1.0 * einsum("ka,ijbc,ld,kdlc->ijab", u.ph, t2, u.ph, hf.ovov) + + 1.0 * einsum("ijbc,kc,ld,kdla->ijab", t2, u.ph, u.ph, hf.ovov) + ).antisymmetrise(2, 3) + + 1.0 * 4.0 * ( + + 1.0 * einsum("ac,jdkc,ikbd->ijab", g1a_adc0.vv, hf.ovov, t2) + - 1.0 * einsum("jckb,ikad,cd->ijab", hf.ovov, t2, g1a_adc0.vv) + - 1.0 * einsum("ik,kclb,jlac->ijab", g1a_adc0.oo, hf.ovov, t2) + + 1.0 * einsum("jckb,ilac,lk->ijab", hf.ovov, t2, g1a_adc0.oo) + - 1.0 * einsum("ic,jckb,ka->ijab", u.ph, hf.ovov, rx) + - 1.0 * einsum("jb,kc,lcid,klad->ijab", u.ph, u.ph, hf.ovov, t2) + - 1.0 * einsum("ka,jckb,ic->ijab", u.ph, hf.ovov, rx) + - 1.0 * einsum("jb,kc,lakd,ilcd->ijab", u.ph, u.ph, hf.ovov, t2) + + 2.0 * einsum("ka,ickd,ld,jlbc->ijab", u.ph, hf.ovov, u.ph, t2) + + 2.0 * einsum("ic,kcla,kd,jlbd->ijab", u.ph, hf.ovov, u.ph, t2) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde4.evaluate() + + ttilde5 = - 4.0 * ( + + 1.0 * einsum("kcab,kd,ijcd->ijab", hf.ovvv, u.ph, u.pphh) + + 1.0 * 2.0 * einsum( + "ijbc,kcad,kd->ijab", u.pphh, hf.ovvv, u.ph + ).antisymmetrise(2, 3) + + 1.0 * 4.0 * einsum( + "jcbd,kd,ikac->ijab", hf.ovvv, u.ph, u.pphh + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde5.evaluate() + + ttilde6 = ( + - 1.0 * einsum("ijcd,abde,ce->ijab", t2, hf.vvvv, g1a_adc0.vv) + - 1.0 * 2.0 * einsum( + "ic,abcd,jkde,ke->ijab", u.ph, hf.vvvv, t2, u.ph + ).antisymmetrise(0, 1) + - 1.0 * 2.0 * ( + + 0.5 * einsum("ac,bcde,ijde->ijab", g1a_adc0.vv, hf.vvvv, t2) + - 2.0 * einsum("ijbc,de,adce->ijab", t2, g1a_adc0.vv, hf.vvvv) + ).antisymmetrise(2, 3) + - 1.0 * 4.0 * ( + + 0.5 * einsum("ia,kc,bcde,jkde->ijab", u.ph, u.ph, hf.vvvv, t2) + - 2.0 * einsum("ic,adce,jkeb,kd->ijab", u.ph, hf.vvvv, t2, u.ph) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + ttilde6.evaluate() + + ret = 0.5 * ( + hf.oovv + + ttilde1 + ttilde2 + ttilde3 + ttilde4 + ttilde5 + ttilde6 + + tbar_TD + tbar_rho + ) / (2.0 * direct_sum("ia+jb->ijab", df_ia, df_ia).symmetrise(0, 1)) + return ret + + +def t2bar_oovv_cvs_adc2(exci, g1a_adc0): + mp = exci.ground_state + hf = mp.reference_state + df_ia = mp.df(b.ov) + t2bar = 0.5 * ( + - einsum("ijcb,ac->ijab", hf.oovv, g1a_adc0.vv).antisymmetrise(2, 3) + ) / direct_sum("ia+jb->ijab", df_ia, df_ia).symmetrise(0, 1) + return t2bar + + +def ampl_relaxed_dms_mp2(mp): + hf = mp.reference_state + t2 = mp.t2(b.oovv) + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + g1a.oo = -0.5 * einsum('ikab,jkab->ij', t2, t2) + g1a.vv = 0.5 * einsum('ijac,ijbc->ab', t2, t2) + g2a.oovv = -1.0 * mp.t2(b.oovv) + return g1a, g2a + + +def ampl_relaxed_dms_adc0(exci): + hf = exci.reference_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + # g2a is not required for the adc0 gradient, + # but expected by amplitude_relaxed_densities + g1a.oo = - 1.0 * einsum("ia,ja->ij", u.ph, u.ph) + g1a.vv = + 1.0 * einsum("ia,ib->ab", u.ph, u.ph) + return g1a, g2a + + +def ampl_relaxed_dms_adc1(exci): + hf = exci.reference_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + g1a.oo = - 1.0 * einsum("ia,ja->ij", u.ph, u.ph) + g1a.vv = + 1.0 * einsum("ia,ib->ab", u.ph, u.ph) + g2a.ovov = - 1.0 * einsum("ja,ib->iajb", u.ph, u.ph) + return g1a, g2a + + +def ampl_relaxed_dms_adc2(exci): + u = exci.excitation_vector + mp = exci.ground_state + g1a_adc1, g2a_adc1 = ampl_relaxed_dms_adc1(exci) + t2 = mp.t2(b.oovv) + t2bar = t2bar_oovv_adc2(exci, g1a_adc1).evaluate() + + g1a = g1a_adc1.copy() + g1a.oo += ( + - 2.0 * einsum('jkab,ikab->ij', u.pphh, u.pphh) + - 2.0 * einsum('jkab,ikab->ij', t2bar, t2).symmetrise((0, 1)) + ) + g1a.vv += ( + + 2.0 * einsum("ijac,ijbc->ab", u.pphh, u.pphh) + + 2.0 * einsum("ijac,ijbc->ab", t2bar, t2).symmetrise((0, 1)) + ) + + g2a = g2a_adc1.copy() + ru_ov = einsum("ijab,jb->ia", t2, u.ph) + g2a.oovv = ( + 0.5 * ( + - 1.0 * t2 + + 2.0 * einsum("ijcb,ca->ijab", t2, g1a_adc1.vv).antisymmetrise(2, 3) + - 2.0 * einsum("kjab,ki->ijab", t2, g1a_adc1.oo).antisymmetrise(0, 1) + - 4.0 * einsum( + "ia,jb->ijab", u.ph, ru_ov + ).antisymmetrise((0, 1)).antisymmetrise((2, 3)) + ) + - 2.0 * t2bar + ) + g2a.ooov = -2.0 * einsum("kb,ijab->ijka", u.ph, u.pphh) + g2a.ovvv = -2.0 * einsum("ja,ijbc->iabc", u.ph, u.pphh) + return g1a, g2a + + +def ampl_relaxed_dms_adc2x(exci): + u = exci.excitation_vector + g1a, g2a = ampl_relaxed_dms_adc2(exci) + + g2a.ovov += -4.0 * einsum("ikbc,jkac->iajb", u.pphh, u.pphh) + g2a.oooo = 2.0 * einsum('ijab,klab->ijkl', u.pphh, u.pphh) + g2a.vvvv = 2.0 * einsum('ijcd,ijab->abcd', u.pphh, u.pphh) + + return g1a, g2a + + +def ampl_relaxed_dms_adc3(exci): + u = exci.excitation_vector + mp = exci.ground_state + hf = mp.reference_state + g1a_adc1, g2a_adc1 = ampl_relaxed_dms_adc1(exci) + t2bar = t2bar_oovv_adc3(exci, g1a_adc1, g2a_adc1).evaluate() + tbarD = tbarD_oovv_adc3(exci, g1a_adc1).evaluate() + rho_bar = rho_bar_adc3(exci, g1a_adc1).evaluate() + t2 = mp.t2(b.oovv) + tD = mp.td2(b.oovv) + rho = mp.mp2_diffdm + + # Table IX (10.1063/1.5085117) + g1a = g1a_adc1.copy() + g1a.oo += ( + - 2.0 * einsum("jkab,ikab->ij", u.pphh, u.pphh) + - 1.0 * 2.0 * ( + + 1.0 * einsum("ikab,jkab->ij", t2, t2bar) + + 1.0 * einsum("ikab,jkab->ij", tD, tbarD) + + 0.5 * einsum("ia,ja->ij", rho_bar, rho.ov) + ).symmetrise(0, 1) + ) + g1a.vv += ( + + 2.0 * einsum("ijac,ijbc->ab", u.pphh, u.pphh) + + 1.0 * 2.0 * ( + + 1.0 * einsum("ijac,ijbc->ab", t2bar, t2) + + 1.0 * einsum("ijac,ijbc->ab", tbarD, tD) + + 0.5 * einsum("ia,ib->ab", rho_bar, rho.ov) + ).symmetrise(0, 1) + ) + + tsq_ovov = einsum("ikac,jkbc->iajb", t2, t2).evaluate() + tsq_vvvv = einsum("klab,klcd->abcd", t2, t2).evaluate() + tsq_oooo = einsum("ijcd,klcd->ijkl", t2, t2).evaluate() + rx = einsum("ijab,jb->ia", t2, u.ph) + + g2a = TwoParticleDensityMatrix(hf) + g2a.oooo = ( + + 2.0 * einsum("ijab,klab->ijkl", u.pphh, u.pphh) + + 0.5 * 2.0 * ( + + 2.0 * einsum("ijab,klab->ijkl", tbarD, t2) + + 0.5 * 2.0 * einsum( + "jm,imab,klab->ijkl", g1a_adc1.oo, t2, t2 + ).antisymmetrise(0, 1) + + 1.0 * 2.0 * einsum( + "kc,ijbc,ma,lmab->ijkl", u.ph, t2, u.ph, t2 + ).antisymmetrise(2, 3) + + 1.0 * 4.0 * ( + + 1.0 * einsum("ik,jl->ijkl", rho.oo, g1a_adc1.oo) + - 1.0 * einsum("lajb,ia,kb->ijkl", tsq_ovov, u.ph, u.ph) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ).symmetrise([(0, 2), (1, 3)]) + ) + g2a.ooov = ( + - 2.0 * einsum("kb,ijab->ijka", u.ph, u.pphh) + + 1.0 * einsum("la,ijbc,klbc->ijka", u.ph, t2, u.pphh) + + 1.0 * 2.0 * ( + + 2.0 * einsum("ic,jlab,klbc->ijka", u.ph, t2, u.pphh) + - 1.0 * einsum("ja,ilbc,klbc->ijka", u.ph, t2, u.pphh) + + 1.0 * einsum("ja,ik->ijka", rho.ov, g1a_adc1.oo) + + 1.0 * einsum("ia,jb,kb->ijka", u.ph, rho.ov, u.ph) + ).antisymmetrise(0, 1) + - 0.5 * einsum("ijab,kb->ijka", t2, rho_bar) + ) + g2a.oovv = 0.5 * ( + - 1.0 * t2 - 1.0 * tD - 4.0 * t2bar + - 1.0 * 2.0 * ( + + 1.0 * einsum("ijbc,ac->ijab", tD + t2, g1a_adc1.vv) + ).antisymmetrise(2, 3) + + 1.0 * 2.0 * ( + + 1.0 * einsum("jkab,ik->ijab", tD + t2, g1a_adc1.oo) + ).antisymmetrise(0, 1) + - 1.0 * 4.0 * ( + + 1.0 * einsum("ia,jb->ijab", u.ph, + einsum("jkbc,kc->jb", tD, u.ph) + rx) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ) + g2a.ovov = ( + + 1.0 * g2a_adc1.ovov + - 4.0 * einsum("ikbc,jkac->iajb", u.pphh, u.pphh) + + 1.0 * einsum("ij,ab->iajb", rho.oo, g1a_adc1.vv) + + 1.0 * einsum("ab,ij->iajb", rho.vv, g1a_adc1.oo) + + 0.5 * 2.0 * ( + - 4.0 * einsum("ikbc,jkac->iajb", t2, tbarD) + + 1.0 * einsum("ibjc,ac->iajb", tsq_ovov, g1a_adc1.vv) + - 1.0 * einsum("ibka,jk->iajb", tsq_ovov, g1a_adc1.oo) + + 1.0 * einsum("ka,ikbc,jc->iajb", u.ph, t2, rx) + + 1.0 * einsum("jc,ikbc,ka->iajb", u.ph, t2, rx) + + 1.0 * einsum("ja,cb,ic->iajb", u.ph, rho.vv, u.ph) + - 1.0 * einsum("ib,kj,ka->iajb", u.ph, rho.oo, u.ph) + - 2.0 * einsum("jckb,ic,ka->iajb", tsq_ovov, u.ph, u.ph) + + 0.5 * einsum("acbd,ic,jd->iajb", tsq_vvvv, u.ph, u.ph) + + 0.5 * einsum("ikjl,ka,lb->iajb", tsq_oooo, u.ph, u.ph) + ).symmetrise([(0, 2), (1, 3)]) + ) + g2a.ovvv = ( + - 2.0 * einsum("ja,ijbc->iabc", u.ph, u.pphh) + + 1.0 * einsum("id,jkbc,jkad->iabc", u.ph, t2, u.pphh) + + 1.0 * 2.0 * ( + - 2.0 * einsum("jb,ikcd,jkad->iabc", u.ph, t2, u.pphh) + + 1.0 * einsum("ib,jkcd,jkad->iabc", u.ph, t2, u.pphh) + - 1.0 * einsum("ic,ab->iabc", rho.ov, g1a_adc1.vv) + + 1.0 * einsum("ib,jc,ja->iabc", u.ph, rho.ov, u.ph) + ).antisymmetrise(2, 3) + - 0.5 * einsum("ijbc,ja->iabc", t2, rho_bar) + ) + g2a.vvvv = ( + + 2.0 * einsum("ijcd,ijab->abcd", u.pphh, u.pphh) + + 0.5 * 2.0 * ( + + 2.0 * einsum("ijab,ijcd->abcd", tbarD, t2) + + 0.5 * 2.0 * ( + - 1.0 * einsum("be,aecd->abcd", g1a_adc1.vv, tsq_vvvv) + ).antisymmetrise(0, 1) + + 1.0 * 2.0 * ( + + 1.0 * einsum("ia,ijcd,jkbe,ke->abcd", u.ph, t2, t2, u.ph) + ).antisymmetrise(0, 1) + + 1.0 * 4.0 * ( + + 1.0 * einsum("bd,ac->abcd", rho.vv, g1a_adc1.vv) + - 1.0 * einsum("idjb,ia,jc->abcd", tsq_ovov, u.ph, u.ph) + ).antisymmetrise(0, 1).antisymmetrise(2, 3) + ).symmetrise([(0, 2), (1, 3)]) + ) + return g1a, g2a + + +### CVS ### + +def ampl_relaxed_dms_cvs_adc0(exci): + hf = exci.reference_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + # g2a is not required for cvs-adc0 gradient, + # but expected by amplitude_relaxed_densities + g1a.cc = - 1.0 * einsum("Ia,Ja->IJ", u.ph, u.ph) + g1a.vv = + 1.0 * einsum("Ia,Ib->ab", u.ph, u.ph) + return g1a, g2a + + +def ampl_relaxed_dms_cvs_adc1(exci): + hf = exci.reference_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + g2a.cvcv = - 1.0 * einsum("Ja,Ib->IaJb", u.ph, u.ph) + g1a.cc = - 1.0 * einsum("Ia,Ja->IJ", u.ph, u.ph) + g1a.vv = + 1.0 * einsum("Ia,Ib->ab", u.ph, u.ph) + + # Prerequisites for the OC block of the + # orbital response Lagrange multipliers: + fc = hf.fock(b.cc).diagonal() + fo = hf.fock(b.oo).diagonal() + fco = direct_sum("-j+I->jI", fc, fo).evaluate() + g1a.co = - 1.0 * einsum('JbKc,ibKc->Ji', g2a.cvcv, hf.ovcv) / fco + return g1a, g2a + + +def ampl_relaxed_dms_cvs_adc2(exci): + hf = exci.reference_state + mp = exci.ground_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + + # Determine the t-amplitudes and multipliers: + t2oovv = mp.t2(b.oovv) + t2ccvv = mp.t2(b.ccvv) + t2ocvv = mp.t2(b.ocvv) + g1a_cvs0, g2a_cvs0 = ampl_relaxed_dms_cvs_adc0(exci) + t2bar = t2bar_oovv_cvs_adc2(exci, g1a_cvs0).evaluate() + + g1a.cc = ( + - 1.0 * einsum("Ia,Ja->IJ", u.ph, u.ph) + - 1.0 * einsum("kJba,kIba->IJ", u.pphh, u.pphh) + - 0.5 * einsum('IKab,JKab->IJ', t2ccvv, t2ccvv) + - 0.5 * einsum('kIab,kJab->IJ', t2ocvv, t2ocvv) + ) + + g1a.oo = ( + - 1.0 * einsum("jKba,iKba->ij", u.pphh, u.pphh) + - 2.0 * einsum("ikab,jkab->ij", t2bar, t2oovv).symmetrise((0, 1)) + - 0.5 * einsum('iKab,jKab->ij', t2ocvv, t2ocvv) + - 0.5 * einsum('ikab,jkab->ij', t2oovv, t2oovv) + ) + + # Pre-requisites for the OC block of the + # orbital response Lagrange multipliers: + fc = hf.fock(b.cc).diagonal() + fo = hf.fock(b.oo).diagonal() + fco = direct_sum("-j+I->jI", fc, fo).evaluate() + + g1a.vv = ( + + 1.0 * einsum("Ia,Ib->ab", u.ph, u.ph) + + 2.0 * einsum('jIcb,jIca->ab', u.pphh, u.pphh) + + 2.0 * einsum('ijac,ijbc->ab', t2bar, t2oovv).symmetrise((0, 1)) + + 0.5 * einsum('IJac,IJbc->ab', t2ccvv, t2ccvv) + + 0.5 * einsum('ijac,ijbc->ab', t2oovv, t2oovv) + + 1.0 * einsum('iJac,iJbc->ab', t2ocvv, t2ocvv) + ) + + g2a.cvcv = ( + - einsum("Ja,Ib->IaJb", u.ph, u.ph) + ) + + # The factor 1/sqrt(2) is needed because of the scaling used in adcc + # for the ph-pphh blocks. + g2a.occv = (1 / sqrt(2)) * ( + 2.0 * einsum('Ib,kJba->kJIa', u.ph, u.pphh) + ) + + g2a.oovv = ( + + 1.0 * einsum('ijcb,ca->ijab', t2oovv, g1a_cvs0.vv).antisymmetrise((2, 3)) + - 1.0 * t2oovv + - 2.0 * t2bar + ) + + # The factor 2/sqrt(2) is necessary because of the way + # that the ph-pphh is scaled. + g2a.ovvv = (2 / sqrt(2)) * ( + einsum('Ja,iJcb->iabc', u.ph, u.pphh) + ) + + g2a.ccvv = - 1.0 * t2ccvv + g2a.ocvv = - 1.0 * t2ocvv + + # This is the OC block of the orbital response + # Lagrange multipliers (lambda): + g1a.co = ( + - 1.0 * einsum('JbKc,ibKc->Ji', g2a.cvcv, hf.ovcv) + - 0.5 * einsum('JKab,iKab->Ji', g2a.ccvv, hf.ocvv) + + 1.0 * einsum('kJLa,ikLa->Ji', g2a.occv, hf.oocv) + + 0.5 * einsum('kJab,ikab->Ji', g2a.ocvv, hf.oovv) + - 1.0 * einsum('kLJa,kLia->Ji', g2a.occv, hf.ocov) + + 1.0 * einsum('iKLa,JKLa->Ji', g2a.occv, hf.cccv) + + 0.5 * einsum('iKab,JKab->Ji', g2a.ocvv, hf.ccvv) + - 0.5 * einsum('ikab,kJab->Ji', g2a.oovv, hf.ocvv) + + 0.5 * einsum('iabc,Jabc->Ji', g2a.ovvv, hf.cvvv) + ) / fco + + return g1a, g2a + + +def ampl_relaxed_dms_cvs_adc2x(exci): + hf = exci.reference_state + mp = exci.ground_state + u = exci.excitation_vector + g1a = OneParticleOperator(hf) + g2a = TwoParticleDensityMatrix(hf) + + # Determine the t-amplitudes and multipliers: + t2oovv = mp.t2(b.oovv) + t2ccvv = mp.t2(b.ccvv) + t2ocvv = mp.t2(b.ocvv) + g1a_cvs0, _ = ampl_relaxed_dms_cvs_adc0(exci) + t2bar = t2bar_oovv_cvs_adc2(exci, g1a_cvs0).evaluate() + + g1a.cc = ( + - 1.0 * einsum("Ia,Ja->IJ", u.ph, u.ph) + - 1.0 * einsum("kJba,kIba->IJ", u.pphh, u.pphh) + - 0.5 * einsum('IKab,JKab->IJ', t2ccvv, t2ccvv) + - 0.5 * einsum('kIab,kJab->IJ', t2ocvv, t2ocvv) + ) + + g1a.oo = ( + - 1.0 * einsum("jKba,iKba->ij", u.pphh, u.pphh) + - 2.0 * einsum("ikab,jkab->ij", t2bar, t2oovv).symmetrise((0, 1)) + - 0.5 * einsum('iKab,jKab->ij', t2ocvv, t2ocvv) + - 0.5 * einsum('ikab,jkab->ij', t2oovv, t2oovv) + ) + + # Pre-requisites for the OC block of the + # orbital response Lagrange multipliers: + fc = hf.fock(b.cc).diagonal() + fo = hf.fock(b.oo).diagonal() + fco = direct_sum("-j+I->jI", fc, fo).evaluate() + + g1a.vv = ( + + 1.0 * einsum("Ia,Ib->ab", u.ph, u.ph) + + 2.0 * einsum('jIcb,jIca->ab', u.pphh, u.pphh) + + 2.0 * einsum('ijac,ijbc->ab', t2bar, t2oovv).symmetrise((0, 1)) + + 0.5 * einsum('IJac,IJbc->ab', t2ccvv, t2ccvv) + + 0.5 * einsum('ijac,ijbc->ab', t2oovv, t2oovv) + + 1.0 * einsum('iJac,iJbc->ab', t2ocvv, t2ocvv) + ) + + g2a.cvcv = ( + - 1.0 * einsum("Ja,Ib->IaJb", u.ph, u.ph) + - 1.0 * einsum('kIbc,kJac->IaJb', u.pphh, u.pphh) + + 1.0 * einsum('kIcb,kJac->IaJb', u.pphh, u.pphh) + ) + + # The factor 1/sqrt(2) is needed because of the scaling used in adcc + # for the ph-pphh blocks. + g2a.occv = (1 / sqrt(2)) * ( + 2.0 * einsum('Ib,kJba->kJIa', u.ph, u.pphh) + ) + + g2a.oovv = ( + + 1.0 * einsum('ijcb,ca->ijab', t2oovv, g1a_cvs0.vv).antisymmetrise((2, 3)) + - 1.0 * t2oovv + - 2.0 * t2bar + ) + + # The factor 2/sqrt(2) is necessary because of + # the way that the ph-pphh is scaled + g2a.ovvv = (2 / sqrt(2)) * ( + einsum('Ja,iJcb->iabc', u.ph, u.pphh) + ) + + g2a.ovov = 1.0 * ( + - einsum("iKbc,jKac->iajb", u.pphh, u.pphh) + + einsum("iKcb,jKac->iajb", u.pphh, u.pphh) + ) + + g2a.ccvv = - 1.0 * t2ccvv + g2a.ocvv = - 1.0 * t2ocvv + g2a.ococ = 1.0 * einsum("iJab,kLab->iJkL", u.pphh, u.pphh) + g2a.vvvv = 2.0 * einsum("iJcd,iJab->abcd", u.pphh, u.pphh) + + g1a.co = ( + - 1.0 * einsum('JbKc,ibKc->Ji', g2a.cvcv, hf.ovcv) + - 0.5 * einsum('JKab,iKab->Ji', g2a.ccvv, hf.ocvv) + + 1.0 * einsum('kJLa,ikLa->Ji', g2a.occv, hf.oocv) + + 0.5 * einsum('kJab,ikab->Ji', g2a.ocvv, hf.oovv) + - 1.0 * einsum('kLJa,kLia->Ji', g2a.occv, hf.ocov) + + 1.0 * einsum('iKLa,JKLa->Ji', g2a.occv, hf.cccv) + + 0.5 * einsum('iKab,JKab->Ji', g2a.ocvv, hf.ccvv) + - 0.5 * einsum('ikab,kJab->Ji', g2a.oovv, hf.ocvv) + + 0.5 * einsum('iabc,Jabc->Ji', g2a.ovvv, hf.cvvv) + + 1.0 * einsum('kJmL,ikmL->Ji', g2a.ococ, hf.oooc) + - 1.0 * einsum('iKlM,JKMl->Ji', g2a.ococ, hf.ccco) + + 1.0 * einsum('iakb,kbJa->Ji', g2a.ovov, hf.ovcv) + ) / fco + + return g1a, g2a + + +DISPATCH = { + "mp2": ampl_relaxed_dms_mp2, + "adc0": ampl_relaxed_dms_adc0, + "adc1": ampl_relaxed_dms_adc1, + "adc2": ampl_relaxed_dms_adc2, + "adc2x": ampl_relaxed_dms_adc2x, + "adc3": ampl_relaxed_dms_adc3, + "cvs-adc0": ampl_relaxed_dms_cvs_adc0, + "cvs-adc1": ampl_relaxed_dms_cvs_adc1, + "cvs-adc2": ampl_relaxed_dms_cvs_adc2, + "cvs-adc2x": ampl_relaxed_dms_cvs_adc2x, +} + + +def amplitude_relaxed_densities(excitation_or_mp): + """Computation of amplitude-relaxed one- and two-particle density matrices + + Parameters + ---------- + excitation_or_mp : LazyMp, Excitation + Data for which the densities are requested, either LazyMp for ground + state densities or Excitation for excited state densities + + Returns + ------- + (OneParticleOperator, TwoParticleDensityMatrix) + Tuple of amplitude-relaxed one- and two-particle density matrices + + Raises + ------ + NotImplementedError + if density matrices are not implemented for a given method + """ + if isinstance(excitation_or_mp, LazyMp): + method_name = "mp2" + elif isinstance(excitation_or_mp, Excitation): + method_name = excitation_or_mp.method.name + if method_name not in DISPATCH: + raise NotImplementedError("Amplitude response is not " + f"implemented for {method_name}.") + g1a, g2a = DISPATCH[method_name](excitation_or_mp) + return evaluate(g1a), evaluate(g2a) diff --git a/adcc/gradients/orbital_response.py b/adcc/gradients/orbital_response.py new file mode 100644 index 00000000..37573d99 --- /dev/null +++ b/adcc/gradients/orbital_response.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2021 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +import adcc.block as b +from adcc.OneParticleOperator import OneParticleOperator +from adcc.functions import direct_sum, einsum, evaluate +from adcc.AmplitudeVector import AmplitudeVector +from adcc.solver.conjugate_gradient import conjugate_gradient, default_print + + +def orbital_response_rhs(hf, g1a, g2a): + """ + Build the right-hand side for solving the orbital + response given amplitude-relaxed density matrices (method-specific) + """ + # TODO: only add non-zero blocks to equations! + + if hf.has_core_occupied_space: + ret_ov = -1.0 * ( + + 2.0 * einsum("JiKa,JK->ia", hf.cocv, g1a.cc) + + 2.0 * einsum("icab,bc->ia", hf.ovvv, g1a.vv) + + 2.0 * einsum('kija,jk->ia', hf.ooov, g1a.oo) + + 2.0 * einsum("kiJa,Jk->ia", hf.oocv, g1a.co) + - 2.0 * einsum("iJka,Jk->ia", hf.ocov, g1a.co) + + 2.0 * einsum("iJKb,JaKb->ia", hf.occv, g2a.cvcv) # 2PDMs start + + 2.0 * einsum('lKJa,lKiJ->ia', g2a.occv, hf.ococ) + + 1.0 * einsum('jabc,ijbc->ia', g2a.ovvv, hf.oovv) + - 1.0 * einsum('JKab,JKib->ia', g2a.ccvv, hf.ccov) + - 2.0 * einsum('jKab,jKib->ia', g2a.ocvv, hf.ocov) + - 1.0 * einsum('jkab,jkib->ia', g2a.oovv, hf.ooov) + - 2.0 * einsum('jcab,ibjc->ia', g2a.ovvv, hf.ovov) + - 2.0 * einsum('iJKb,JaKb->ia', g2a.occv, hf.cvcv) + - 1.0 * einsum('iJbc,Jabc->ia', g2a.ocvv, hf.cvvv) + - 1.0 * einsum('ijbc,jabc->ia', g2a.oovv, hf.ovvv) + + 1.0 * einsum('ibcd,abcd->ia', g2a.ovvv, hf.vvvv) + - 1.0 * einsum('abcd,ibcd->ia', g2a.vvvv, hf.ovvv) # cvs-adc2x + + 2.0 * einsum('jakb,ijkb->ia', g2a.ovov, hf.ooov) # cvs-adc2x + + 2.0 * einsum('ibjc,jcab->ia', g2a.ovov, hf.ovvv) # cvs-adc2x + - 2.0 * einsum('iJkL,kLJa->ia', g2a.ococ, hf.occv) # cvs-adc2x + ) + + ret_cv = -1.0 * ( + + 2.0 * einsum("JIKa,JK->Ia", hf.cccv, g1a.cc) + + 2.0 * einsum("Icab,bc->Ia", hf.cvvv, g1a.vv) + + 2.0 * einsum('kIja,jk->Ia', hf.ocov, g1a.oo) + + 2.0 * einsum("kIJa,Jk->Ia", hf.occv, g1a.co) + + 2.0 * einsum("JIka,Jk->Ia", hf.ccov, g1a.co) + + 2.0 * einsum("IJKb,JaKb->Ia", hf.cccv, g2a.cvcv) # 2PDMs start + + 2.0 * einsum("Jcab,IbJc->Ia", hf.cvvv, g2a.cvcv) + + 2.0 * einsum('lKJa,lKIJ->Ia', g2a.occv, hf.occc) + - 1.0 * einsum('jabc,jIbc->Ia', g2a.ovvv, hf.ocvv) + - 1.0 * einsum('JKab,JKIb->Ia', g2a.ccvv, hf.cccv) + - 2.0 * einsum('jKab,jKIb->Ia', g2a.ocvv, hf.occv) + - 1.0 * einsum('jkab,jkIb->Ia', g2a.oovv, hf.oocv) + - 2.0 * einsum('jcab,jcIb->Ia', g2a.ovvv, hf.ovcv) + - 1.0 * einsum('IJbc,Jabc->Ia', g2a.ccvv, hf.cvvv) + + 2.0 * einsum('jIKb,jaKb->Ia', g2a.occv, hf.ovcv) + + 1.0 * einsum('jIbc,jabc->Ia', g2a.ocvv, hf.ovvv) + + 2.0 * einsum('kJIb,kJab->Ia', g2a.occv, hf.ocvv) + - 1.0 * einsum('abcd,Ibcd->Ia', g2a.vvvv, hf.cvvv) # cvs-adc2x + - 2.0 * einsum('jakb,jIkb->Ia', g2a.ovov, hf.ocov) # cvs-adc2x + + 2.0 * einsum('jIlK,lKja->Ia', g2a.ococ, hf.ocov) # cvs-adc2x + ) + ret = AmplitudeVector(cv=ret_cv, ov=ret_ov) + else: + # equal to the ov block of the energy-weighted density + # matrix when lambda_ov multipliers are zero + w_ov = 0.5 * ( + + 1.0 * einsum("ijkl,klja->ia", hf.oooo, g2a.ooov) # not in cvs-adc + - 1.0 * einsum("ibcd,abcd->ia", hf.ovvv, g2a.vvvv) + - 1.0 * einsum("jkib,jkab->ia", hf.ooov, g2a.oovv) + + 2.0 * einsum("ijkb,jakb->ia", hf.ooov, g2a.ovov) + + 1.0 * einsum("ijbc,jabc->ia", hf.oovv, g2a.ovvv) + - 2.0 * einsum("ibjc,jcab->ia", hf.ovov, g2a.ovvv) + ) + w_ov = w_ov.evaluate() + + ret_ov = -1.0 * ( + 2.0 * w_ov + - 1.0 * einsum("klja,ijkl->ia", hf.ooov, g2a.oooo) + + 1.0 * einsum("abcd,ibcd->ia", hf.vvvv, g2a.ovvv) + - 2.0 * einsum("jakb,ijkb->ia", hf.ovov, g2a.ooov) # not in cvs-adc + + 1.0 * einsum("jkab,jkib->ia", hf.oovv, g2a.ooov) # not in cvs-adc + + 2.0 * einsum("jcab,ibjc->ia", hf.ovvv, g2a.ovov) + - 1.0 * einsum("jabc,ijbc->ia", hf.ovvv, g2a.oovv) + + 2.0 * einsum("jika,jk->ia", hf.ooov, g1a.oo) + + 2.0 * einsum("icab,bc->ia", hf.ovvv, g1a.vv) + ) + ret = AmplitudeVector(ov=ret_ov) + + return ret + + +def energy_weighted_density_matrix(hf, g1o, g2a): + if hf.has_core_occupied_space: + w = OneParticleOperator(hf) + w.cc = - 0.5 * ( + + einsum("JKab,IKab->IJ", g2a.ccvv, hf.ccvv) + + einsum("kJab,kIab->IJ", g2a.ocvv, hf.ocvv) + ) + w.cc += ( + - hf.fcc + - einsum("IK,JK->IJ", g1o.cc, hf.fcc) + - einsum("ab,IaJb->IJ", g1o.vv, hf.cvcv) + - einsum("KL,IKJL->IJ", g1o.cc, hf.cccc) + - einsum("kl,kIlJ->IJ", g1o.oo, hf.ococ) + + einsum("ka,kJIa->IJ", g1o.ov, hf.occv) + + einsum("ka,kIJa->IJ", g1o.ov, hf.occv) + + einsum("Ka,KJIa->IJ", g1o.cv, hf.cccv) + + einsum("Ka,KIJa->IJ", g1o.cv, hf.cccv) + - einsum("Lk,kILJ->IJ", g1o.co, hf.occc) + - einsum("Lk,kJLI->IJ", g1o.co, hf.occc) + - einsum("JbKc,IbKc->IJ", g2a.cvcv, hf.cvcv) + - einsum("kJLa,kILa->IJ", g2a.occv, hf.occv) + - einsum("kLJa,kLIa->IJ", g2a.occv, hf.occv) + - einsum("kJmL,kImL->IJ", g2a.ococ, hf.ococ) # cvs-adc2x + ) + w.oo = - 0.5 * ( + + einsum("jKab,iKab->ij", g2a.ocvv, hf.ocvv) + + einsum("jkab,ikab->ij", g2a.oovv, hf.oovv) + + einsum("jabc,iabc->ij", g2a.ovvv, hf.ovvv) + ) + w.oo += ( + - hf.foo + - einsum("ij,ii->ij", g1o.oo, hf.foo) + - einsum("KL,iKjL->ij", g1o.cc, hf.ococ) + - einsum("ab,iajb->ij", g1o.vv, hf.ovov) + - einsum("kl,ikjl->ij", g1o.oo, hf.oooo) + - einsum("ka,ikja->ij", g1o.ov, hf.ooov) + - einsum("ka,jkia->ij", g1o.ov, hf.ooov) + - einsum("Ka,iKja->ij", g1o.cv, hf.ocov) + - einsum("Ka,jKia->ij", g1o.cv, hf.ocov) + + einsum("Lk,kijL->ij", g1o.co, hf.oooc) + + einsum("Lk,kjiL->ij", g1o.co, hf.oooc) + - einsum("jKLa,iKLa->ij", g2a.occv, hf.occv) + - einsum("jKlM,iKlM->ij", g2a.ococ, hf.ococ) # cvs-adc2x + - einsum("jakb,iakb->ij", g2a.ovov, hf.ovov) # cvs-adc2x + ) + w.vv = - 0.5 * ( + + einsum("ibcd,iacd->ab", g2a.ovvv, hf.ovvv) + + einsum("IJbc,IJac->ab", g2a.ccvv, hf.ccvv) + + einsum("ijbc,ijac->ab", g2a.oovv, hf.oovv) + + einsum("bcde,acde->ab", g2a.vvvv, hf.vvvv) # cvs-adc2x + ) + w.vv += ( + - einsum("ac,cb->ab", g1o.vv, hf.fvv) + - einsum('JbKc,JaKc->ab', g2a.cvcv, hf.cvcv) + - einsum("jKIb,jKIa->ab", g2a.occv, hf.occv) + - einsum("idbc,idac->ab", g2a.ovvv, hf.ovvv) + - einsum("iJbc,iJac->ab", g2a.ocvv, hf.ocvv) + - einsum("ibjc,iajc->ab", g2a.ovov, hf.ovov) # cvs-adc2x + ) + w.co = 0.5 * ( + - einsum("JKab,iKab->Ji", g2a.ccvv, hf.ocvv) + + einsum("kJab,ikab->Ji", g2a.ocvv, hf.oovv) + ) + w.co += ( + + einsum("KL,iKLJ->Ji", g1o.cc, hf.occc) + - einsum("ab,iaJb->Ji", g1o.vv, hf.ovcv) + - einsum("kl,kilJ->Ji", g1o.oo, hf.oooc) + - einsum("Ka,iKJa->Ji", g1o.cv, hf.occv) + - einsum("Ka,JKia->Ji", g1o.cv, hf.ccov) + - einsum("ka,ikJa->Ji", g1o.ov, hf.oocv) + - einsum("ka,Jkia->Ji", g1o.ov, hf.coov) + - einsum("Lk,kiLJ->Ji", g1o.co, hf.oocc) + + einsum("Lk,iLkJ->Ji", g1o.co, hf.ococ) + - einsum("Ik,jk->Ij", g1o.co, hf.foo) + - einsum("JbKc,ibKc->Ji", g2a.cvcv, hf.ovcv) + + einsum("kJLa,ikLa->Ji", g2a.occv, hf.oocv) + - einsum("kLJa,kLia->Ji", g2a.occv, hf.ocov) + + einsum("kJmL,ikmL->Ji", g2a.ococ, hf.oooc) # cvs-adc2x + ) + w.ov = 0.5 * ( + + einsum("jabc,ijbc->ia", g2a.ovvv, hf.oovv) + - einsum("JKab,JKib->ia", g2a.ccvv, hf.ccov) + - einsum("jkab,jkib->ia", g2a.oovv, hf.ooov) + - einsum("abcd,ibcd->ia", g2a.vvvv, hf.ovvv) # cvs-adc2x + ) + w.ov += ( + - einsum("ij,ja->ia", hf.foo, g1o.ov) + + einsum("JaKc,iJKc->ia", g2a.cvcv, hf.occv) + + einsum("kLJa,iJkL->ia", g2a.occv, hf.ococ) + - einsum("jKab,jKib->ia", g2a.ocvv, hf.ocov) + - einsum("jcab,ibjc->ia", g2a.ovvv, hf.ovov) + + einsum("jakb,ijkb->ia", g2a.ovov, hf.ooov) # cvs-adc2x + ) + w.cv = - 0.5 * ( + + einsum("jabc,jIbc->Ia", g2a.ovvv, hf.ocvv) + + einsum("JKab,JKIb->Ia", g2a.ccvv, hf.cccv) + + einsum("jkab,jkIb->Ia", g2a.oovv, hf.oocv) + + einsum("abcd,Ibcd->Ia", g2a.vvvv, hf.cvvv) # cvs-adc2x + ) + w.cv += ( + - einsum("IJ,Ja->Ia", hf.fcc, g1o.cv) + + einsum("JaKc,IJKc->Ia", g2a.cvcv, hf.cccv) + + einsum("lKJa,lKIJ->Ia", g2a.occv, hf.occc) + - einsum("kJab,kJIb->Ia", g2a.ocvv, hf.occv) + - einsum("jcab,jcIb->Ia", g2a.ovvv, hf.ovcv) + - einsum("jakb,jIkb->Ia", g2a.ovov, hf.ocov) # cvs-adc2x + ) + else: + gi_oo = -0.5 * ( + + 1.0 * einsum("jklm,iklm->ij", hf.oooo, g2a.oooo) + + 1.0 * einsum("jabc,iabc->ij", hf.ovvv, g2a.ovvv) + + 1.0 * einsum("klja,klia->ij", hf.ooov, g2a.ooov) + + 2.0 * einsum("jkla,ikla->ij", hf.ooov, g2a.ooov) + + 1.0 * einsum("jkab,ikab->ij", hf.oovv, g2a.oovv) + + 2.0 * einsum("jakb,iakb->ij", hf.ovov, g2a.ovov) + ) + gi_vv = -0.5 * ( + + 1.0 * einsum("kjib,kjia->ab", hf.ooov, g2a.ooov) + + einsum("bcde,acde->ab", hf.vvvv, g2a.vvvv) + + 1.0 * einsum("ijcb,ijca->ab", hf.oovv, g2a.oovv) + + 2.0 * einsum("jcib,jcia->ab", hf.ovov, g2a.ovov) + + 1.0 * einsum("ibcd,iacd->ab", hf.ovvv, g2a.ovvv) + + 2.0 * einsum("idcb,idca->ab", hf.ovvv, g2a.ovvv) + ) + gi_oo = gi_oo.evaluate() + gi_vv = gi_vv.evaluate() + w = OneParticleOperator(hf) + w.ov = 0.5 * ( + - 2.0 * einsum("ij,ja->ia", hf.foo, g1o.ov) + + 1.0 * einsum("ijkl,klja->ia", hf.oooo, g2a.ooov) + - 1.0 * einsum("ibcd,abcd->ia", hf.ovvv, g2a.vvvv) + - 1.0 * einsum("jkib,jkab->ia", hf.ooov, g2a.oovv) + + 2.0 * einsum("ijkb,jakb->ia", hf.ooov, g2a.ovov) + + 1.0 * einsum("ijbc,jabc->ia", hf.oovv, g2a.ovvv) + - 2.0 * einsum("ibjc,jcab->ia", hf.ovov, g2a.ovvv) + ) + w.oo = ( + + gi_oo - hf.foo + - einsum("ik,jk->ij", g1o.oo, hf.foo) + - einsum("ikjl,kl->ij", hf.oooo, g1o.oo) + - einsum("ikja,ka->ij", hf.ooov, g1o.ov) + - einsum("jkia,ka->ij", hf.ooov, g1o.ov) + - einsum("jaib,ab->ij", hf.ovov, g1o.vv) + ) + w.vv = gi_vv - einsum("ac,cb->ab", g1o.vv, hf.fvv) + return evaluate(w) + + +class OrbitalResponseMatrix: + def __init__(self, hf): + self.hf = hf + + @property + def shape(self): + no1 = self.hf.mospaces.n_orbs(b.o) + if self.hf.has_core_occupied_space: + no1 += self.hf.mospaces.n_orbs(b.c) + nv1 = self.hf.mospaces.n_orbs(b.v) + size = no1 * nv1 + return (size, size) + + def __matmul__(self, lam): + ret_ov = ( + + einsum("ab,ib->ia", self.hf.fvv, lam.ov) + - einsum("ij,ja->ia", self.hf.foo, lam.ov) + + einsum("ijab,jb->ia", self.hf.oovv, lam.ov) + - einsum("ibja,jb->ia", self.hf.ovov, lam.ov) + ) + if self.hf.has_core_occupied_space: + ret_ov += ( + + einsum("iJab,Jb->ia", self.hf.ocvv, lam.cv) + - einsum("ibJa,Jb->ia", self.hf.ovcv, lam.cv) + ) + ret_cv = ( + + einsum("ab,Ib->Ia", self.hf.fvv, lam.cv) + - einsum("IJ,Ja->Ia", self.hf.fcc, lam.cv) + + einsum("IJab,Jb->Ia", self.hf.ccvv, lam.cv) + - einsum("IbJa,Jb->Ia", self.hf.cvcv, lam.cv) + + einsum("Ijab,jb->Ia", self.hf.covv, lam.ov) + - einsum("Ibja,jb->Ia", self.hf.cvov, lam.ov) + ) + ret = AmplitudeVector(cv=ret_cv, ov=ret_ov) + else: + ret = AmplitudeVector(ov=ret_ov) + return evaluate(ret) + + +class OrbitalResponsePinv: + def __init__(self, hf): + self.hf = hf + # Terms common to adc and cvs-adc + fo = hf.fock(b.oo).diagonal() + fv = hf.fock(b.vv).diagonal() + fov = direct_sum("-i+a->ia", fo, fv) + + if hf.has_core_occupied_space: + fc = hf.fock(b.cc).diagonal() + fcv = direct_sum("-I+a->Ia", fc, fv) + self.df = AmplitudeVector(cv=fcv, ov=fov) + else: + self.df = AmplitudeVector(ov=fov) + self.df.evaluate() + + @property + def shape(self): + no1 = self.hf.mospaces.n_orbs(b.o) + if self.hf.has_core_occupied_space: + no1 += self.hf.mospaces.n_orbs(b.c) + nv1 = self.hf.mospaces.n_orbs(b.v) + size = no1 * nv1 + return (size, size) + + def __matmul__(self, invec): + return invec / self.df + + +def orbital_response(hf, rhs): + """ + Solves the orbital response equations + for a given reference state and right-hand side + """ + # TODO: pass solver arguments + A = OrbitalResponseMatrix(hf) + Pinv = OrbitalResponsePinv(hf) + x0 = (Pinv @ rhs).evaluate() + lam = conjugate_gradient(A, rhs=rhs, x0=x0, Pinv=Pinv, + explicit_symmetrisation=None, + callback=default_print) + return lam.solution diff --git a/adcc/solver/conjugate_gradient.py b/adcc/solver/conjugate_gradient.py index 77982cc3..86bbc4eb 100644 --- a/adcc/solver/conjugate_gradient.py +++ b/adcc/solver/conjugate_gradient.py @@ -143,7 +143,7 @@ def is_converged(state): state.solution = x0 state.residual = evaluate(rhs - matrix @ state.solution) state.n_applies += 1 - state.residual_norm = np.sqrt(state.residual @ state.residual) + state.residual_norm = np.sqrt(state.residual.dot(state.residual)) pk = zk = Pinv @ state.residual if explicit_symmetrisation: @@ -166,7 +166,7 @@ def is_converged(state): residual_old = state.residual state.residual = evaluate(residual_old - ak * Apk) - state.residual_norm = np.sqrt(state.residual @ state.residual) + state.residual_norm = np.sqrt(state.residual.dot(state.residual)) callback(state, "next_iter") if is_converged(state): diff --git a/adcc/tests/ExcitedStates_test.py b/adcc/tests/ExcitedStates_test.py index 7e28ab6d..1be10a0a 100644 --- a/adcc/tests/ExcitedStates_test.py +++ b/adcc/tests/ExcitedStates_test.py @@ -43,7 +43,8 @@ def test_excitation_view(self): if key.startswith("_"): continue blacklist = ["__", "index", "_ao", "excitation_vector", - "method", "parent_state"] + "method", "parent_state", "ground_state", + "reference_state"] if any(b in key for b in blacklist): continue try: diff --git a/adcc/tests/backends/testing.py b/adcc/tests/backends/testing.py index 0d3e483c..fcc1c4aa 100644 --- a/adcc/tests/backends/testing.py +++ b/adcc/tests/backends/testing.py @@ -191,6 +191,7 @@ def cached_backend_hf(backend: str, system: str, conv_tol=1e-12, pe_options=None and return the result. """ import adcc.backends + print(system) from .. import testcases diff --git a/adcc/tests/data/gradient_data.json b/adcc/tests/data/gradient_data.json new file mode 100644 index 00000000..a74f95e5 --- /dev/null +++ b/adcc/tests/data/gradient_data.json @@ -0,0 +1 @@ +{"basissets": ["sto3g", "ccpvdz"], "methods": ["mp2", "adc0", "adc1", "adc2", "adc2x", "adc3", "cvs-adc0", "cvs-adc1", "cvs-adc2", "cvs-adc2x"], "molecules": ["h2o"], "h2o": {"sto3g": {"mp2": {"energy": -74.99357808910258, "gradient": [[0.09648089353868272, -7.275957614183426e-12, 0.06886449053126853], [-0.026183461559412535, 0.0, -0.0659738619287964], [-0.07029743172461167, -1.4551915228366852e-11, -0.002890627962187864]], "config": {"conv_tol": 1e-09, "core_orbitals": null}}, "adc0": {"energy": [-73.96883482604426, -73.91481187944933, -73.8057558947831, -73.75173294818818, -73.72721279699914], "gradient": [[[0.21500520807603607, -1.0186340659856796e-10, 0.15122539465664886], [0.074898492108332, 0.0, -0.33320423345139716], [-0.2899037002425757, -2.1827872842550278e-11, 0.18197883959510364]], [[0.15869260571344057, -7.275957614183426e-12, 0.11135792801360367], [0.12612935141078196, -8.731149137020111e-11, -0.3458573067910038], [-0.28482195712422254, -7.275957614183426e-12, 0.23449937959230738]], [[0.4883978163416032, -2.1827872842550278e-11, 0.34780997568304883], [-0.09521430340100778, 7.275957614183426e-12, -0.38596352970489534], [-0.393183512838732, -2.9103830456733704e-11, 0.03815355485130567]], [[0.432085214095423, 2.1827872842550278e-11, 0.3079425090982113], [-0.04398344415676547, -8.731149137020111e-11, -0.39861660305177793], [-0.3881017696039635, -7.275957614183426e-12, 0.09067409489944112]], [[0.4312687490892131, -1.4551915228366852e-11, 0.30399765190668404], [0.0012444719613995403, -2.1827872842550278e-11, -0.45833798522653524], [-0.4325132208832656, -7.275957614183426e-12, 0.15434033421479398]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc1": {"energy": [-74.47588319597416, -74.38511885048102, -74.3571822920938, -74.24904200360893, -74.13327522706388], "gradient": [[[0.2519565781985875, -2.1827872842550278e-11, 0.17643765273533063], [0.043837964338308666, 1.4551915228366852e-11, -0.32756816069741035], [-0.29579454263148364, 2.1827872842550278e-11, 0.15113050883519463]], [[0.44204020009055967, 0.0, 0.31597312643862097], [-0.08205663987610023, 0.0, -0.35633136297838064], [-0.3599835601708037, -2.1827872842550278e-11, 0.040358237114560325]], [[0.102462732247659, 2.1827872842550278e-11, 0.07140888640424237], [0.11964530191471567, 2.1827872842550278e-11, -0.2768455860496033], [-0.22210803416237468, -2.1827872842550278e-11, 0.20543670036568074]], [[0.33387671443779254, 2.1827872842550278e-11, 0.2387534842637251], [-2.251235127914697e-05, -1.4551915228366852e-11, -0.35684544259129325], [-0.33385420200647786, 1.4551915228366852e-11, 0.1180919590042322]], [[0.4270152217577561, 1.4551915228366852e-11, 0.3020655645959778], [-0.0510407698166091, 1.4551915228366852e-11, -0.38096642302843975], [-0.37597445177379996, -2.1827872842550278e-11, 0.0789008592182654]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc2": {"energy": [-74.5230649475857, -74.42102314285407, -74.399904734204, -74.2806092729357, -74.15388076828465], "gradient": [[[0.2729663057762082, -1.2369127944111824e-10, 0.1915359973354498], [0.043751713426900096, -7.275957614183426e-12, -0.3499776703029056], [-0.3167180191812804, 1.4551915228366852e-11, 0.1584416737605352]], [[0.4661114461196121, 3.637978807091713e-11, 0.3327183597430121], [-0.08526563976920443, 1.8917489796876907e-10, -0.3770551475681714], [-0.38084580657596234, 2.1827872842550278e-11, 0.044336788858345244]], [[0.1245402125568944, -1.4551915228366852e-11, 0.08709251005348051], [0.11524763699708274, 2.1827872842550278e-11, -0.2941215308092069], [-0.23978784963401267, -1.4551915228366852e-11, 0.20702902170887683]], [[0.34492495327140205, -1.4551915228366852e-11, 0.24646419990313007], [-0.0014078249514568597, -2.9103830456733704e-11, -0.36650604708120227], [-0.3435171281089424, -7.275957614183426e-12, 0.12004184816760244]], [[0.4314259997627232, -1.4551915228366852e-11, 0.30517971346125705], [-0.05028844868502347, 1.4551915228366852e-11, -0.386704910924891], [-0.3811375508448691, -7.275957614183426e-12, 0.08152519801660674]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc2x": {"energy": [-74.5484662688597, -74.44218300989021, -74.42210922368841, -74.30300532559038, -74.16527252522135], "gradient": [[[0.28895747921342263, 7.275957614183426e-12, 0.2026268877380062], [0.045065266407618765, -1.4551915228366852e-11, -0.3685835983196739], [-0.33402274580294034, -5.093170329928398e-11, 0.16595671139657497]], [[0.4846122063099756, 1.4551915228366852e-11, 0.3459748487730394], [-0.08685884039005032, 0.0, -0.39460422133561224], [-0.39775336616730783, -2.9103830456733704e-11, 0.04862937352299923]], [[0.13661488301295321, 1.4551915228366852e-10, 0.09541062240168685], [0.11530230956850573, -1.4551915228366852e-11, -0.3067889074445702], [-0.25191719264694257, 7.275957614183426e-12, 0.21137828581413487]], [[0.36053903902939055, 7.275957614183426e-12, 0.25766010332881706], [-0.0009222038715961389, 8.003553375601768e-11, -0.3839128288964275], [-0.3596168350704829, -2.1827872842550278e-11, 0.12625272644800134]], [[0.43669924540881766, 7.275957614183426e-12, 0.308943429197825], [-0.05085965209582355, 2.9103830456733704e-11, -0.3915265553150675], [-0.38583959305105964, -7.275957614183426e-12, 0.08258312672114698]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc3": {"energy": [-74.55090439658746, -74.44714801129354, -74.42426273712188, -74.30620148905741, -74.16694386620925], "gradient": [[[0.2908625282943831, 2.1827872842550278e-11, 0.20394485128781525], [0.04500433992507169, -1.4551915228366852e-11, -0.37048940415115794], [-0.33586686859780457, -2.9103830456733704e-11, 0.16654455379466526]], [[0.4865842864601291, -2.1827872842550278e-11, 0.3473889870510902], [-0.08571265485807089, 2.1827872842550278e-11, -0.3983368489789427], [-0.4008716317039216, -1.4551915228366852e-11, 0.05094786284462316]], [[0.13570507883559912, -2.9103830456733704e-11, 0.09476317676308099], [0.11679784433363238, 2.1827872842550278e-11, -0.3079343576464453], [-0.2525029233220266, 2.9103830456733704e-11, 0.21317118174920324]], [[0.3612387817411218, 7.275957614183426e-12, 0.2581562034683884], [0.00045972187217557803, -1.4551915228366852e-11, -0.38661062382743694], [-0.3616985033659148, -7.275957614183426e-12, 0.12845442127581919]], [[0.4404495687704184, 2.9103830456733704e-11, 0.31157616753625916], [-0.05225038512435276, 2.9103830456733704e-11, -0.39351955433085095], [-0.3881991834496148, -2.9103830456733704e-11, 0.08194338764587883]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "cvs-adc0": {"energy": [-54.123082474404995, -53.960003543143834], "gradient": [[[0.13670423482835758, 0.0, 0.09585740332113346], [0.09903088248393033, -1.4551915228366852e-11, -0.2842584849859122], [-0.2357351180180558, 7.275957614183426e-12, 0.188401082654309]], [[0.4100968432685477, 2.1827872842550278e-11, 0.2924419842674979], [-0.07108191314910073, -1.4551915228366852e-11, -0.33701778129761806], [-0.3390149306069361, -7.275957614183426e-12, 0.04457579784502741]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc1": {"energy": [-54.85354413664511, -54.79419426319812], "gradient": [[[0.2044143344683107, 2.1827872842550278e-11, 0.1419889454045915], [0.033883336967846844, -1.4551915228366852e-11, -0.262222796693095], [-0.2382976719745784, 0.0, 0.12023385223437799]], [[0.3112917308098986, 7.275957614183426e-12, 0.22435430129553424], [-0.03036278657236835, 7.275957614183426e-12, -0.29155276880192105], [-0.2809289447250194, 2.1827872842550278e-11, 0.06719846829219023]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc2": {"energy": [-54.98903589084722, -54.90586009813991, -53.16685681552232, -53.101104786907726, -53.00377788426118], "gradient": [[[0.23872009933984373, -2.9103830456733704e-11, 0.1671009868659894], [0.03348872094647959, 4.3655745685100555e-11, -0.29891402232897235], [-0.27220882097026333, 1.4551915228366852e-11, 0.1318130366198602]], [[0.36193002599611646, 2.1827872842550278e-11, 0.2592484456472448], [-0.04305156866030302, 0.0, -0.3264197397438693], [-0.3188784580779611, -7.275957614183426e-12, 0.06717129518801812]], [[0.305568811170815, 7.275957614183426e-12, 0.21378264181839768], [0.19212377251824364, 2.9103830456733704e-11, -0.5935662706178846], [-0.49769258473679656, 1.0913936421275139e-10, 0.3797836300363997]], [[0.23468779257382266, -7.275957614183426e-11, 0.16384246382222045], [0.24505627318285406, 1.964508555829525e-10, -0.5933986320596887], [-0.47974406644061673, -2.1827872842550278e-11, 0.4295561693725176]], [[0.5789614193781745, 2.9103830456733704e-11, 0.4103672228375217], [0.022010977067111526, 3.637978807091713e-11, -0.6463255668786587], [-0.6009723973911605, 1.0186340659856796e-10, 0.23595834522711812]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc2x": {"energy": [-55.08829803906612, -54.9945312916704, -54.266903715981805, -54.258055435885886, -54.20500159732517], "gradient": [[[0.27884997078217566, -1.673470251262188e-10, 0.1953710336747463], [0.03098650036554318, 7.275957614183426e-12, -0.33784350618952885], [-0.3098364717588993, 1.3096723705530167e-10, 0.14247247358434834]], [[0.41212538530817255, -1.4551915228366852e-11, 0.29478439913509646], [-0.05303481598821236, -1.2369127944111824e-10, -0.36559760699310573], [-0.359090569872933, 0.0, 0.07081320873840014]], [[0.6789990236284211, -1.7462298274040222e-10, 0.4736056833571638], [-0.019374826908460818, 2.9103830456733704e-11, -0.6864346827715053], [-0.6596241974912118, 6.548361852765083e-11, 0.21282900056394283]], [[0.6081797262158943, 1.3096723705530167e-10, 0.4384273026807932], [0.01058207180176396, 2.9103830456733704e-11, -0.6685617413022555], [-0.6187617984833196, -5.093170329928398e-11, 0.23013443964737235]], [[0.46743476379924687, 1.4551915228366852e-11, 0.33145697056897916], [0.022216292971279472, 0.0, -0.528248111018911], [-0.489651057854644, 1.4551915228366852e-11, 0.19679114157042932]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}}, "ccpvdz": {"mp2": {"energy": -76.22940338787562, "gradient": [[0.024395466447458602, -2.9103830456733704e-11, 0.01773811209568521], [-0.010762456666270737, -2.9103830456733704e-11, -0.011150370693940204], [-0.013632937501824927, -1.4551915228366852e-11, -0.006587735537323169]], "config": {"conv_tol": 1e-09, "core_orbitals": null}}, "adc0": {"energy": [-75.34687593720773, -75.28099897309124, -75.27790714495661, -75.21203018084012, -75.1263800402113], "gradient": [[[0.05781977490551071, 7.275957614183426e-12, 0.04124122217763215], [-0.0010635846047080122, -1.4551915228366852e-11, -0.06019881294196239], [-0.05674794071819633, 0.0, 0.018958910419314634]], [[0.013119906550855376, -2.1827872842550278e-11, 0.009577973985869903], [0.0517213462953805, 2.1827872842550278e-11, -0.08735985546081793], [-0.06483396996191004, 2.9103830456733704e-11, 0.0777820943549159]], [[0.035872519212716725, 1.4551915228366852e-11, 0.02590936847263947], [0.007818394005880691, 7.275957614183426e-12, -0.04966096833959455], [-0.04368333833554061, 2.9103830456733704e-11, 0.023752558598062024]], [[-0.008827349083730951, -1.4551915228366852e-11, -0.0057538798355381005], [0.060603324898693245, -7.275957614183426e-12, -0.07682201085845008], [-0.051769367564702407, -1.4551915228366852e-11, 0.08257574253366329]], [[0.2704858468277962, 1.4551915228366852e-11, 0.19159464181575458], [-0.07004997117473977, 0.0, -0.18824054856668226], [-0.2004280803448637, -1.4551915228366852e-11, -0.0033529280190123245]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc1": {"energy": [-75.68543998430039, -75.61931016170234, -75.59868950264112, -75.532752986795, -75.45558187581646], "gradient": [[[0.09274209536670242, -7.275957614183426e-12, 0.06557503178919433], [-0.0026306448489776812, 2.1827872842550278e-11, -0.09467139691696502], [-0.0901040762255434, -1.4551915228366852e-11, 0.029097417085722554]], [[0.1093199013848789, 2.1827872842550278e-11, 0.0780909138629795], [-0.006661084160441533, 2.9103830456733704e-11, -0.10735268513963092], [-0.10265199275454506, -2.1827872842550278e-11, 0.029262722055136692]], [[0.011673270331812091, -7.275957614183426e-12, 0.00844753841374768], [0.056844679136702325, 0.0, -0.09296221228578361], [-0.06851130315772025, 2.1827872842550278e-11, 0.08451495548069943]], [[0.02629122353391722, 0.0, 0.019252847356256098], [0.057597583705501165, -1.4551915228366852e-11, -0.11000348474772181], [-0.08388262351218145, -2.9103830456733704e-11, 0.09075075774308061]], [[0.2821038434340153, 1.4551915228366852e-11, 0.19977067352738231], [-0.07840616344037699, 0.0, -0.18870985075773206], [-0.20369109181774547, 2.1827872842550278e-11, -0.011060022770834621]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc2": {"energy": [-75.92967540818942, -75.85499790112056, -75.84309170418786, -75.76675240926447, -75.66953798976122], "gradient": [[[0.11448709326941753, -1.4551915228366852e-11, 0.08113363845041022], [-0.008901050896383822, 2.1827872842550278e-11, -0.1090564420010196], [-0.10557860966218868, 7.275957614183426e-12, 0.027923848014324903]], [[0.1131836650747573, -7.275957614183426e-12, 0.08065145519503858], [-0.004472936961974483, -1.4551915228366852e-11, -0.11437404136813711], [-0.10870395785605069, 2.9103830456733704e-11, 0.03372336812026333]], [[0.022058329042920377, -2.1827872842550278e-11, 0.015899113190243952], [0.050983897534024436, -2.9103830456733704e-11, -0.09580092146643437], [-0.07303541761211818, 7.275957614183426e-12, 0.0799023346407921]], [[0.020308474187913816, 1.4551915228366852e-11, 0.014956989012716804], [0.058050304411153775, 1.4551915228366852e-11, -0.1042313243233366], [-0.07835257506667404, 1.4551915228366852e-11, 0.08927455494267633]], [[0.26928795556159457, -2.9103830456733704e-11, 0.19073455897159874], [-0.07746179534296971, 7.275957614183426e-12, -0.1764748514542589], [-0.1918197939376114, 2.1827872842550278e-11, -0.01425892054248834]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc2x": {"energy": [-75.94713027647231, -75.87072120069274, -75.86069046652048, -75.78212932285793, -75.68339216526915], "gradient": [[[0.12108681791869458, 7.275957614183426e-12, 0.08575116403517313], [-0.008930603667977266, -2.9103830456733704e-11, -0.11596714374172734], [-0.11214869188552257, 1.4551915228366852e-11, 0.030217022183933295]], [[0.12104355383053189, -7.275957614183426e-12, 0.08621679777570534], [-0.005538905352295842, 2.9103830456733704e-11, -0.1212127921753563], [-0.11549783187365392, 1.4551915228366852e-11, 0.034996749353013]], [[0.02765446234116098, 2.1827872842550278e-11, 0.01980982987151947], [0.05061737170035485, -7.275957614183426e-12, -0.10117332988738781], [-0.07826491810556035, 2.9103830456733704e-11, 0.08136406357516535]], [[0.027274335945548955, 2.1827872842550278e-11, 0.019888993163476698], [0.05681757771526463, 0.0, -0.10988471429300262], [-0.08408563704870176, -1.4551915228366852e-11, 0.08999595484056044]], [[0.2716475580527913, -7.275957614183426e-12, 0.19238663020951208], [-0.07715970138815464, -2.9103830456733704e-11, -0.17938891498488374], [-0.1944814575035707, -7.275957614183426e-12, -0.012996937817661092]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "adc3": {"energy": [-75.92857834998667, -75.85463334402809, -75.84167671842562, -75.76664279719428, -75.67470300439263], "gradient": [[[0.11627493508422049, 0.0, 0.08229840418789536], [-0.006295996819972061, 1.4551915228366852e-11, -0.11453756176342722], [-0.10997124934510794, 2.1827872842550278e-11, 0.032240211170574185]], [[0.12384516876772977, 0.0, 0.08824648732843343], [-0.006875947656226344, -7.275957614183426e-12, -0.12234318283299217], [-0.11696223153558094, -1.382431946694851e-10, 0.03409750875289319]], [[0.025811466584855225, 2.9103830456733704e-11, 0.018465745204593986], [0.0533070154779125, 2.9103830456733704e-11, -0.10298046591924503], [-0.07911143552337307, 7.275957614183426e-12, 0.08451522156246938]], [[0.033787787426263094, 1.1641532182693481e-10, 0.024522948973753955], [0.056045876095595304, -2.1827872842550278e-11, -0.11573187115573091], [-0.08982723313965835, -2.1827872842550278e-11, 0.0912091305362992]], [[0.2793064223806141, 3.637978807091713e-11, 0.19778368141851388], [-0.07754907244088827, 7.275957614183426e-12, -0.1869451557067805], [-0.2017506956035504, 2.1827872842550278e-11, -0.010837729831109755]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": null}}, "cvs-adc0": {"energy": [-55.29234315848254, -55.22337436623141, -54.6671662062745, -54.64254226419146, -54.31941455257717], "gradient": [[[-0.01363594663416734, 1.4551915228366852e-11, -0.009354182511742692], [0.005705504816432949, -5.093170329928398e-11, 0.0060958039975957945], [0.007961477167555131, 1.4551915228366852e-11, 0.0032610131020192057]], [[-0.035583202239649836, 1.4551915228366852e-11, -0.02468603632587474], [0.014587483354262076, -2.9103830456733704e-11, 0.016633648599963635], [0.021026079608418513, -2.1827872842550278e-11, 0.008054661513597239]], [[0.295615929106134, -2.9103830456733704e-11, 0.20218788867350668], [-0.11422500915068667, 1.1641532182693481e-10, -0.14526737584674265], [-0.18136249485542066, 0.0, -0.05691822549124481]], [[-0.04258258258050773, -5.820766091346741e-11, -0.021944308828096837], [0.16518283695040736, 8.003553375601768e-11, -0.19658326423086692], [-0.12257139149005525, 5.820766091346741e-11, 0.2185302046345896]], [[-0.0943416317095398, -1.4551915228366852e-11, -0.06655934288573917], [0.01681761433428619, -1.4551915228366852e-11, 0.07614312684745528], [0.07755017422459787, -7.275957614183426e-12, -0.009581486701790709]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc1": {"energy": [-55.7623580120961, -55.74423117760095, -55.23885575120384, -55.20048469034408, -55.178979221895716], "gradient": [[[0.11376989239943214, 3.637978807091713e-11, 0.07504696383693954], [-0.006233317595615517, -2.9103830456733704e-11, -0.10649701902730158], [-0.107507510263531, 1.0186340659856796e-10, 0.03145194291573716]], [[0.1367624182748841, 1.1641532182693481e-10, 0.10286059680947801], [-0.010134714473679196, -8.003553375601768e-11, -0.13692940795590403], [-0.12659825186710805, 7.275957614183426e-12, 0.03407134330336703]], [[-0.035857140050211456, -7.275957614183426e-12, -0.025069641618756577], [0.02807263379509095, -1.382431946694851e-10, -0.0019539316344889812], [0.00781276056659408, 0.0, 0.027025939685699996]], [[0.0637307473589317, -8.003553375601768e-11, 0.046041117268032394], [-0.04479707258724375, -7.275957614183426e-12, -0.005256572927464731], [-0.018904139367805328, 7.275957614183426e-12, -0.04078219792427262]], [[-0.05261707480531186, 0.0, -0.03676470001664711], [0.005314122914569452, 1.2369127944111824e-10, 0.04785384914430324], [0.047328871696663555, 2.9103830456733704e-11, -0.011087133760156576]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc2": {"energy": [-56.463324400674544, -56.39198974445309, -55.93190966426827, -55.91365902930488, -55.634891014248154], "gradient": [[[0.06426175875822082, 1.4551915228366852e-11, 0.04550280176772503], [-0.0016873756248969585, 2.9103830456733704e-11, -0.06586654879356502], [-0.06254429007094586, 1.7462298274040222e-10, 0.020366252538224217]], [[0.059006211573432665, 1.964508555829525e-10, 0.04234478738362668], [0.005969680009002332, -2.1100277081131935e-10, -0.07167619411484338], [-0.06494627146457788, -1.382431946694851e-10, 0.02933372787811095]], [[0.26678762744995765, -1.964508555829525e-10, 0.18208895675343229], [-0.08748003323125886, -3.346940502524376e-10, -0.15278823697735788], [-0.17927927291748347, 7.275957614183426e-11, -0.029298384521098342]], [[0.019884901426848955, -1.4551915228366852e-11, 0.022045515754143707], [0.1168202597182244, -5.093170329928398e-11, -0.1942855826273444], [-0.13667679524223786, 3.637978807091713e-11, 0.17224251533480128]], [[-0.08170067649916746, -3.4924596548080444e-10, -0.05746503695991123], [0.049807636183686554, 1.4551915228366852e-10, 0.01592683904164005], [0.031919313929392956, 4.5838532969355583e-10, 0.04154028196353465]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}, "cvs-adc2x": {"energy": [-56.527690992922494, -56.455320049295366, -56.00756567594842, -55.992414483676754, -55.7258963632124], "gradient": [[[0.08823665066302055, 3.2014213502407074e-10, 0.06228063011803897], [-0.0035475476979627274, 7.275957614183426e-12, -0.08849635638762265], [-0.08465851147047943, 3.2014213502407074e-10, 0.026218218845315278]], [[0.09283125843649032, 8.949427865445614e-10, 0.06637479383789469], [-0.0013974821922602132, -2.255546860396862e-10, -0.09725589540903457], [-0.09140373219997855, -1.8189894035458565e-10, 0.030883435916621238]], [[0.26091981134959497, 7.785274647176266e-10, 0.17749230707704555], [-0.08874684962211177, 2.0372681319713593e-10, -0.14432455416681478], [-0.1721437370069907, -4.147295840084553e-10, -0.03316539854131406]], [[0.0209724435408134, 5.529727786779404e-10, 0.02326315642130794], [0.11284109613188775, 4.874891601502895e-10, -0.19026156545442063], [-0.13378430448210565, -4.0745362639427185e-10, 0.1670009150839178]], [[-0.005838602111907676, 1.1423253454267979e-09, -0.003816529810137581], [0.0007158659500419162, 2.1827872842550278e-11, 0.004858776730543468], [0.005147792071511503, -6.548361852765083e-11, -0.0010405088687548414]]], "config": {"conv_tol": 1e-09, "n_singlets": 5, "core_orbitals": 1}}}, "xyz": "\n O 0 0 0\n H 0 0 1.795239827225189\n H 1.693194615993441 0 -0.599043184453037\n "}} \ No newline at end of file diff --git a/adcc/tests/functionality_gradients_test.py b/adcc/tests/functionality_gradients_test.py new file mode 100644 index 00000000..2df68607 --- /dev/null +++ b/adcc/tests/functionality_gradients_test.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2020 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +import unittest +import itertools + +import adcc +import adcc.backends + +from numpy.testing import assert_allclose + +import pytest + +from .backends.testing import cached_backend_hf +from .testdata_cache import gradient_data + + +backends = [b for b in adcc.backends.available() + if b not in ["molsturm", "veloxchem"]] + +molecules = gradient_data["molecules"] +basissets = gradient_data["basissets"] +methods = gradient_data["methods"] + +@pytest.mark.skipif(len(backends) == 0, reason="No backend found.") +@pytest.mark.parametrize("backend", backends) +@pytest.mark.parametrize("method", methods) +@pytest.mark.parametrize("basis", basissets) +@pytest.mark.parametrize("molecule", molecules) +def test_nuclear_gradient(molecule, basis, method, backend): + grad_ref = gradient_data[molecule][basis][method] + + energy_ref = grad_ref["energy"] + grad_fdiff = grad_ref["gradient"] + kwargs = grad_ref["config"] + conv_tol = kwargs["conv_tol"] + + scfres = cached_backend_hf(backend, f"{molecule}_{basis}", conv_tol=1e-11) + print(kwargs) + + if "adc" in method: + state = adcc.run_adc(scfres, method=method, **kwargs) + for ee in state.excitations: + grad = adcc.nuclear_gradient(ee) + assert_allclose(energy_ref[ee.index], ee.total_energy, + atol=conv_tol) + # check energy computed with unrelaxed densities + gs_corr = 0.0 + if ee.method.level > 0: + # compute the ground state contribution + # to the correlation energy + gs_energy = ee.ground_state.energy(ee.method.level) + gs_corr = gs_energy - ee.reference_state.energy_scf + assert_allclose( + gs_corr + ee.excitation_energy, + grad._energy, atol=1e-10 + ) + assert_allclose( + grad_fdiff[ee.index], grad.total, atol=5e-8, rtol=0, + err_msg=f'Gradient for state {ee.index} wrong.' + ) + else: + # MP2 gradients + refstate = adcc.ReferenceState(scfres) + mp = adcc.LazyMp(refstate) + grad = adcc.nuclear_gradient(mp) + assert_allclose(energy_ref, mp.energy(2), atol=1e-8) + # check energy computed with unrelaxed densities + assert_allclose( + mp.energy_correction(2), grad._energy, atol=5e-8, rtol=0 + ) + assert_allclose( + grad_fdiff, grad.total, atol=1e-8, rtol=0 + ) diff --git a/adcc/tests/generators/dump_fdiff_gradient.py b/adcc/tests/generators/dump_fdiff_gradient.py new file mode 100644 index 00000000..1003e2d4 --- /dev/null +++ b/adcc/tests/generators/dump_fdiff_gradient.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab +## --------------------------------------------------------------------- +## +## Copyright (C) 2021 by the adcc authors +## +## This file is part of adcc. +## +## adcc is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## adcc is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with adcc. If not, see . +## +## --------------------------------------------------------------------- +from dataclasses import dataclass +from pathlib import Path +import itertools +import adcc +import numpy as np +import json +from tqdm import tqdm + +from pyscf import gto, lib + +from adcc.tests.testcases import _xyz + +adcc.set_n_threads(8) +lib.num_threads(8) + +prefactors_5p = np.array([1.0, -8.0, 8.0, -1.0]) / 12.0 +multipliers_5p = [-2, -1, 1, 2] +# prefactors_9p = [1. / 280., -4. / 105., 1. / 5., -4. / 5., +# 4. / 5., -1. / 5., 4. / 105., -1. / 280.] +# multipliers_9p = [-4., -3., - 2., -1., 1., 2., 3., 4.] + + +@dataclass +class Molecule: + name: str + charge: int = 0 + multiplicity: int = 1 + core_orbitals: int = 1 + + @property + def xyz(self): + return _xyz[self.name][0] + + +molecules = [ + Molecule("h2o", 0, 1), + Molecule("ch2nh2", charge=0, multiplicity=2), +] + + +def _molstring(elems, coords): + s = "" + for kk, (i, c) in enumerate(zip(elems, coords)): + s += f"{i} {c[0]} {c[1]} {c[2]}" + if kk != len(elems) - 1: + s += "\n" + return s + + +def adc_energy(scfres, method, **kwargs): + state = adcc.run_adc(method=method, data_or_matrix=scfres, + output=None, + **kwargs) + return state.total_energy + + +def mp_energy(scfres, method, **kwargs): + level = int(method[-1]) + refstate = adcc.ReferenceState(scfres) + return adcc.LazyMp(refstate).energy(level) + + +def fdiff_gradient(molecule, method, basis, step=1e-4, **kwargs): + m = gto.M(atom=molecule.xyz, unit='Bohr', basis=basis, + spin=molecule.multiplicity - 1, charge=molecule.charge) + coords = m.atom_coords().copy() + elements = m.elements.copy() + + conv_tol = kwargs.get("conv_tol", 1e-10) + print('conv tol', conv_tol) + + # run unperturbed system + scfres = adcc.backends.run_hf( + 'pyscf', molecule.xyz, basis, conv_tol=conv_tol, conv_tol_grad=conv_tol * 10, + charge=molecule.charge, multiplicity=molecule.multiplicity + ) + if "adc" in method: + en = adc_energy(scfres, method, **kwargs) + ngrad = len(en) + else: + en = mp_energy(scfres, method, **kwargs) + ngrad = 1 + + natoms = len(elements) + grad = np.zeros((ngrad, natoms, 3)) + at_c = list(itertools.product(range(natoms), range(3))) + for i, c in tqdm(at_c): + for f, p in zip(multipliers_5p, prefactors_5p): + coords_p = coords.copy() + coords_p[i, c] += f * step + geom_p = _molstring(elements, coords_p) + scfres = adcc.backends.run_hf( + 'pyscf', geom_p, basis, conv_tol=conv_tol, + conv_tol_grad=conv_tol * 10, + charge=molecule.charge, multiplicity=molecule.multiplicity, + max_iter=500, + ) + if "adc" in method: + en_pert = adc_energy(scfres, method, **kwargs) + else: + en_pert = mp_energy(scfres, method, **kwargs) + grad[:, i, c] += p * en_pert / step + return en, grad + + +def main(): + basissets = [ + "sto3g", + "ccpvdz", + ] + methods = [ + "mp2", + "adc0", + "adc1", + "adc2", + "adc2x", + "adc3", + "cvs-adc0", + "cvs-adc1", + "cvs-adc2", + "cvs-adc2x", + ] + molnames = [ + "h2o", + # "ch2nh2", + ] + mols = [m for m in molecules if m.name in molnames] + ret = { + "basissets": basissets, + "methods": methods, + "molecules": molnames, + } + config_excited = {"n_singlets": 5} + for molecule in mols: + molname = molecule.name + ret[molname] = {} + for basis in basissets: + ret[molname][basis] = {} + for method in methods: + kwargs = { + # "conv_tol": 1e-10, + # "conv_tol": 1e-9, + "conv_tol": 1e-8, + } + if "adc" in method: + kwargs.update(config_excited) + basename = f"{molname}_{basis}_{method}" + print(f"Evaluating finite difference gradient for {basename}.") + + core_orbitals = None + if "cvs" in method: + core_orbitals = molecule.core_orbitals + kwargs["core_orbitals"] = core_orbitals + en, grad = fdiff_gradient(molecule, method, basis, **kwargs) + if isinstance(en, np.ndarray): + en = en.tolist() + cont = { + "energy": en, + "gradient": np.squeeze(grad).tolist(), + "config": kwargs, + } + ret[molname][basis][method] = cont + ret[molname]["xyz"] = molecule.xyz + + dump_file = Path(__file__).parent.parent / "data" / "gradient_data.json" + with open(dump_file, "w") as fout: + json.dump(ret, fout) + + +if __name__ == "__main__": + main() diff --git a/adcc/tests/testdata_cache.py b/adcc/tests/testdata_cache.py index 602d9dfe..3c5f71af 100644 --- a/adcc/tests/testdata_cache.py +++ b/adcc/tests/testdata_cache.py @@ -299,3 +299,4 @@ def _import_hook(data: dict): psi4_data = read_json_data("psi4_data.json") pyscf_data = read_json_data("pyscf_data.json") tmole_data = read_json_data("tmole_data.json") +gradient_data = read_json_data("gradient_data.json") diff --git a/adcc/tests/workflow_test.py b/adcc/tests/workflow_test.py index 6dbc8e6d..5cda081c 100644 --- a/adcc/tests/workflow_test.py +++ b/adcc/tests/workflow_test.py @@ -272,5 +272,5 @@ def test_obtain_guesses_by_inspection(self): with pytest.raises(InputError): obtain_guesses_by_inspection(matrix1, n_guesses=4, kind="any", n_guesses_doubles=2) - with pytest.raises(InputError): - obtain_guesses_by_inspection(matrix1, n_guesses=40, kind="any") + # with pytest.raises(InputError): + # obtain_guesses_by_inspection(matrix1, n_guesses=40, kind="any") diff --git a/adcc/workflow.py b/adcc/workflow.py index 6c79fb4c..42fe7051 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -359,7 +359,7 @@ def diagonalise_adcmatrix(matrix, n_states, kind, eigensolver="davidson", if conv_tol is None: conv_tol = max(10 * reference_state.conv_tol, 1e-6) if reference_state.conv_tol > conv_tol: - raise InputError( + warnings.warn( "Convergence tolerance of SCF results " f"(== {reference_state.conv_tol}) needs to be lower than ADC " f"convergence tolerance parameter conv_tol (== {conv_tol})." @@ -412,6 +412,13 @@ def diagonalise_adcmatrix(matrix, n_states, kind, eigensolver="davidson", warnings.warn("Ignoring n_guesses_doubles parameter, since guesses " "are explicitly provided.") + nguess_found = len(guesses) + if n_states > nguess_found: + warnings.warn(f"More states requested ({n_states}) than " + f"guesses available ({nguess_found}). " + "Reducing number of eigenstates to number of guesses.") + n_states = nguess_found + solverargs.setdefault("which", "SA") return run_eigensolver(matrix, guesses, n_ep=n_states, conv_tol=conv_tol, callback=callback, @@ -491,8 +498,8 @@ def obtain_guesses_by_inspection(matrix, n_guesses, kind, n_guesses_doubles=None total_guesses = singles_guesses + doubles_guesses if len(total_guesses) < n_guesses: - raise InputError("Less guesses found than requested: {} found, " - "{} requested".format(len(total_guesses), n_guesses)) + warnings.warn("Less guesses found than requested: " + f"{len(total_guesses)} found, {n_guesses} requested.") return total_guesses