From 096d5d146042622d578e298ff88de61f9974039c Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 29 Sep 2022 10:22:16 +0200 Subject: [PATCH 01/42] Integrale und Reference state properties: masses, charges, n_atoms --- adcc/OperatorIntegrals.py | 73 +++++++++- adcc/adc_pp/modified_transition_moments.py | 16 ++- adcc/backends/pyscf.py | 126 +++++++++++++++++- libadcc/HartreeFockSolution_i.hh | 11 ++ libadcc/ReferenceState.cc | 25 ++++ libadcc/ReferenceState.hh | 12 ++ libadcc/pyiface/export_HartreeFockProvider.cc | 55 ++++++++ libadcc/pyiface/export_ReferenceState.cc | 17 ++- 8 files changed, 325 insertions(+), 10 deletions(-) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 2c811578..ab38c667 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -78,7 +78,6 @@ def replicate_ao_block(mospaces, tensor, is_symmetric=True): ]), 1e-14) return result - class OperatorIntegrals: def __init__(self, provider, mospaces, coefficients, conv_tol): self.__provider_ao = provider @@ -102,6 +101,11 @@ def available(self): "electric_dipole", "magnetic_dipole", "nabla", + "r_quadr", + "r_r", + "diag_mag", + 'electric_quadrupole', + "electric_quadrupole_traceless", "pe_induction_elec", "pcm_potential_elec" ) @@ -125,6 +129,24 @@ def import_dipole_like_operator(self, integral, is_symmetric=True): dipoles.append(dip_ff) return dipoles + + def import_scalar(self, integral, is_symmetric=True): + if integral not in self.available: + raise NotImplementedError(f"{integral.replace('_', ' ')} operator " + "not implemented " + f"in {self.provider_ao.backend} backend.") + + scalar = [] + scalar_backend = getattr(self.provider_ao, integral) + scalar_bb = replicate_ao_block(self.mospaces, scalar_backend, + is_symmetric=is_symmetric) + scalar_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric) + transform_operator_ao2mo(scalar_bb, scalar_ff, self.__coefficients, + self.__conv_tol) + scalar.append(scalar_ff) + return scalar + + @property @timed_member_call("_import_timer") def electric_dipole(self): @@ -148,6 +170,55 @@ def nabla(self): """ return self.import_dipole_like_operator("nabla", is_symmetric=False) + def import_quadrupole_like_operator(self, integral, is_symmetric=True): + if integral not in self.available: + raise NotImplementedError(f"{integral.replace('_', ' ')} operator " + "not implemented " + f"in {self.provider_ao.backend} backend.") + op_integrals = getattr(self.provider_ao, integral) + quad = [] + quadrupoles = [] + for i, component in enumerate(["xx", "xy", "xz" , 'yx', 'yy', 'yz', 'zx', 'zy', 'zz']): + quad_backend = getattr(self.provider_ao, integral)[i] + quad_bb = replicate_ao_block(self.mospaces, quad_backend, + is_symmetric=is_symmetric) + quad_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric) + transform_operator_ao2mo(quad_bb, quad_ff, self.__coefficients, self.__conv_tol) + quad.append(quad_ff) + quadrupoles.append(quad[:3]) + quadrupoles.append(quad[3:6]) + quadrupoles.append(quad[6:]) + return quadrupoles + + @property + @timed_member_call("_import_timer") + def r_r(self): + return self.import_quadrupole_like_operator("r_r", + is_symmetric=True) + + @property + @timed_member_call("_import_timer") + def electric_quadrupole_traceless(self): + return self.import_quadrupole_like_operator("electric_quadrupole_traceless", + is_symmetric=True) + + @property + @timed_member_call("_import_timer") + def electric_quadrupole(self): + return self.import_quadrupole_like_operator("electric_quadrupole", + is_symmetric=True) + + @property + @timed_member_call("_import_timer") + def diag_mag(self): + return self.import_quadrupole_like_operator("diag_mag", + is_symmetric=True) + + @property + @timed_member_call("_import_timer") + def r_quadr(self): + return self.import_scalar("r_quadr", is_symmetric=True) + def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. The returned function imports the operator and transforms it to the diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index 72a2e987..dddcaad1 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -28,7 +28,7 @@ from adcc.functions import einsum, evaluate from adcc.Intermediates import Intermediates from adcc.AmplitudeVector import AmplitudeVector - +import numpy as np def mtm_adc0(mp, dipop, intermediates): return AmplitudeVector(ph=dipop.ov) @@ -118,12 +118,20 @@ def modified_transition_moments(method, ground_state, dipole_operator=None, elif not isinstance(dipole_operator, list): unpack = True dipole_operator = [dipole_operator] + dipole_op = np.array(dipole_operator) #test dimensionen of operator if method.name not in DISPATCH: raise NotImplementedError("modified_transition_moments is not " f"implemented for {method.name}.") - - ret = [DISPATCH[method.name](ground_state, dipop, intermediates) - for dipop in dipole_operator] + if dipole_op.ndim == 1: + ret = [DISPATCH[method.name](ground_state, dipop, intermediates) + for dipop in dipole_operator] + elif dipole_op.ndim == 2: #allow quadrupol like operators + ret = [0, 0, 0] #don't know how to this more cleanly + for i in range(3): + ret[i] = [DISPATCH[method.name](ground_state, dipop, intermediates) + for dipop in dipole_operator[i]] + else: + raise NotImplementedError(f"modified_transition_moments are not implemented for {dipole_op.ndim}-dimensional operators. Only 1 and 2-dimensional operators are available.") if unpack: assert len(ret) == 1 ret = ret[0] diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 7ef12650..bccf09d1 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -37,15 +37,32 @@ class PyScfOperatorIntegralProvider: def __init__(self, scfres): self.scfres = scfres self.backend = "pyscf" - + @cached_property def electric_dipole(self): return list(self.scfres.mol.intor_symmetric('int1e_r', comp=3)) + # center of mass: + # coords = self.scfres.mol.atom_coords() + # mass = self.scfres.mol.atom_mass_list() + # mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + # origin = mass_center + + + + @cached_property def magnetic_dipole(self): # TODO: Gauge origin? - with self.scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + charges = self.scfres.mol.atom_charges() + charge_center = np.einsum ('i, ij -> j ', charges, coords)/charges.sum() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + #origin = charge_center + origin = mass_center + #origin = [0.0, 0.0, 0.0] + with self.scfres.mol.with_common_orig(origin): #gauge origin! return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) @@ -57,6 +74,74 @@ def nabla(self): -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) + @cached_property + def r_quadr(self): + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + return r_quadr + + @cached_property + def r_r(self): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) + return list(r_r) + + @cached_property + def diag_mag(self): + #define center of mass: + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + origin = mass_center + #origin = [0.0, 0.0, 0.0] + with self.scfres.mol.with_common_orig(origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) + r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) + for i in range(3): + r_quadr_matrix[i][i] = r_quadr + term = r_quadr_matrix - r_r + term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) + return list(-1/4 * term) + + @cached_property + def electric_quadrupole_traceless(self): + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + origin = mass_center + #origin = [0.0,0.0,0.0] + with self.scfres.mol.with_common_orig(origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) + r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) + r_quadr_matrix[0][0] = r_quadr + r_quadr_matrix[1][1] = r_quadr + r_quadr_matrix[2][2] = r_quadr + term = 1/2 * (3 * r_r - r_quadr_matrix) + term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) + return list(term) + + @cached_property + def electric_quadrupole(self): + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + origin = mass_center + #origin = [0.0,0.0,0.0] + with self.scfres.mol.with_common_orig(origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) + #r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) + #r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + #r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + #r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) + #r_quadr_matrix[0][0] = r_quadr + #r_quadr_matrix[1][1] = r_quadr + #r_quadr_matrix[2][2] = r_quadr + #term =1/2*(3 * r_r - r_quadr_matrix) + #term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) + return list(r_r) + @property def pe_induction_elec(self): try: @@ -126,7 +211,7 @@ class PyScfHFProvider(HartreeFockProvider): This implementation is only valid if no orbital reordering is required. """ - def __init__(self, scfres): + def __init__(self, scfres ): # Do not forget the next line, # otherwise weird errors result super().__init__() @@ -210,6 +295,23 @@ def get_restricted(self): "not determine restricted / unrestricted.") return restricted + def get_n_atoms(self): + n_atoms = self.scfres.mol.natm + return n_atoms + + def get_nuclearcharges(self): + charges = self.scfres.mol.atom_charges() + return charges + + def get_nuclear_masses(self): + masses = self.scfres.mol.atom_mass_list() + return masses + + def get_coordinates(self): + coords = self.scfres.mol.atom_coords() + coords = np.reshape(coords, (3 * coords.shape[0])) + return coords + def get_energy_scf(self): return float(self.scfres.e_tot) @@ -235,8 +337,24 @@ def get_nuclear_multipole(self, order): elif order == 1: coords = self.scfres.mol.atom_coords() return np.einsum('i,ix->x', charges, coords) + elif order == 2: + # electric quadrupole Q_jk = sum_i (q_i * r_ij *r_ik ) + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + coords = coords - mass_center + r_r = np.einsum('ij, ik -> ijk' ,coords, coords) + #r_quadr_matrix = np.zeros((len(mass), 3,3)) + #for i in range(len(mass)): + # for j in range(3): + # for k in range(3): + # if j ==k: + # r_quadr_matrix[i][j][k] = np.trace(r_r[i]) + res = np.einsum('i, ijk -> jk', charges, r_r) + res = np.array([res[0][0], res[0][1], res[0][2], res[1][1], res[1][2], res[2][2]]) #matrix is symmetrical, only the six different values. + return res else: - raise NotImplementedError("get_nuclear_multipole with order > 1") + raise NotImplementedError("get_nuclear_multipole with order > 2") def fill_occupation_f(self, out): if self.restricted: diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index f21d36d7..d7f25703 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -69,6 +69,9 @@ class HartreeFockSolution_i { * */ virtual size_t n_bas() const = 0; ///@} + // + //returns number of atoms + virtual size_t n_atoms() const = 0; /** \name Access to HF SCF results */ ///@{ @@ -76,6 +79,14 @@ class HartreeFockSolution_i { * SCF program used to provide the data */ virtual std::string backend() const = 0; + //nuclear charges + virtual void nuclear_charges(scalar_type* buffer, size_t size) const = 0; + + //nuclear masses + virtual void nuclear_masses(scalar_type* buffer, size_t size) const = 0; + //coordinates + virtual void coordinates(scalar_type* buffer, size_t size) const = 0; + /** SCF convergence threshold */ virtual real_type conv_tol() const = 0; diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index 90764589..185d9004 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -350,12 +350,37 @@ std::string ReferenceState::irreducible_representation() const { throw not_implemented_error("Only C1 is implemented."); } +size_t ReferenceState::n_atoms() const{ + return m_hfsoln_ptr -> n_atoms(); +} + std::vector ReferenceState::nuclear_multipole(size_t order) const { std::vector ret((order + 2) * (order + 1) / 2); m_hfsoln_ptr->nuclear_multipole(order, ret.data(), ret.size()); return ret; } +std::vector ReferenceState::nuclear_charges() const { + size_t n_atoms = ReferenceState::n_atoms(); + std::vector ret(n_atoms); + m_hfsoln_ptr->nuclear_charges(ret.data(), ret.size()); + return ret; +} + +std::vector ReferenceState::nuclear_masses() const { + size_t n_atoms = ReferenceState::n_atoms(); + std::vector ret(n_atoms); + m_hfsoln_ptr->nuclear_masses(ret.data(), ret.size()); + return ret; +} + +std::vector ReferenceState::coordinates() const{ + size_t n_atoms = ReferenceState::n_atoms(); + std::vector ret(3 * n_atoms); + m_hfsoln_ptr->coordinates(ret.data(), ret.size()); + return ret; + } + // // Tensor data // diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index 59e8a1dd..4283415f 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -90,10 +90,22 @@ class ReferenceState { /** Return the number of beta electrons */ size_t n_beta() const { return m_n_beta; } + size_t n_atoms() const; + /** Return the nuclear contribution to the cartesian multipole moment * (in standard ordering, i.e. xx, xy, xz, yy, yz, zz) of the given order. */ std::vector nuclear_multipole(size_t order) const; + //nuclear charges + std::vector nuclear_charges() const; + + //coordinates + std::vector coordinates() const; + + //nuclear masses + std::vector nuclear_masses() const; + + /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 61ce16c2..db4f3ec5 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -39,10 +39,38 @@ class HartreeFockProvider : public HartreeFockSolution_i { std::string backend() const override { return get_backend(); } size_t n_orbs_alpha() const override { return get_n_orbs_alpha(); } size_t n_bas() const override { return get_n_bas(); } + size_t n_atoms() const override { return get_n_atoms(); } real_type conv_tol() const override { return get_conv_tol(); } bool restricted() const override { return get_restricted(); } size_t spin_multiplicity() const override { return get_spin_multiplicity(); } real_type energy_scf() const override { return get_energy_scf(); } + void nuclear_masses(scalar_type* buffer, size_t size) const override{ + py::array_t ret = get_nuclear_masses(); + if (static_cast(size) != ret.size()) { + throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + + ") does not agree with buffer size (" + + std::to_string(size) + ")."); + } + std::copy(ret.data(), ret.data()+ size, buffer); + } + void nuclear_charges(scalar_type* buffer, size_t size) const override{ + py::array_t ret = get_nuclearcharges(); + if (static_cast(size) != ret.size()) { + throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + + ") does not agree with buffer size (" + + std::to_string(size) + ")."); + } + std::copy(ret.data(), ret.data()+ size, buffer); + } + void coordinates(scalar_type* buffer, size_t size) const override{ + py::array_t ret = get_coordinates(); + if (static_cast(size) != ret.size()) { + throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + + ") does not agree with buffer size (" + + std::to_string(size) + ")."); + } + std::copy(ret.data(), ret.data()+ size, buffer); + } // // Translate C++-like interface to python-like interface @@ -248,12 +276,16 @@ class HartreeFockProvider : public HartreeFockSolution_i { // virtual size_t get_n_orbs_alpha() const = 0; virtual size_t get_n_bas() const = 0; + virtual size_t get_n_atoms() const = 0; virtual py::array_t get_nuclear_multipole(size_t order) const = 0; virtual real_type get_conv_tol() const = 0; virtual bool get_restricted() const = 0; virtual size_t get_spin_multiplicity() const = 0; virtual real_type get_energy_scf() const = 0; virtual std::string get_backend() const = 0; + virtual py::array_t get_nuclearcharges() const = 0; + virtual py::array_t get_coordinates() const = 0; + virtual py::array_t get_nuclear_masses() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -276,10 +308,25 @@ class PyHartreeFockProvider : public HartreeFockProvider { size_t get_n_bas() const override { PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_bas, ); } + size_t get_n_atoms() const override { + PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_atoms, ); + } py::array_t get_nuclear_multipole(size_t order) const override { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order); } + py::array_t get_nuclearcharges() const override{ + PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, + get_nuclearcharges, ); + } + py::array_t get_nuclear_masses() const override{ + PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, + get_nuclear_masses, ); + } + py::array_t get_coordinates() const override{ + PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, + get_coordinates, ); + } real_type get_conv_tol() const override { PYBIND11_OVERLOAD_PURE(real_type, HartreeFockProvider, get_conv_tol, ); } @@ -433,10 +480,18 @@ void export_HartreeFockProvider(py::module& m) { .def("get_n_bas", &HartreeFockProvider::get_n_bas, "Returns the number of *spatial* one-electron basis functions. This value " "is abbreviated by `nb` in the documentation.") + .def("get_n_atoms", &HartreeFockProvider::get_n_atoms, + "Returns the number of atoms.") .def("get_nuclear_multipole", &HartreeFockProvider::get_nuclear_multipole, "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " "dipole moment as an array of size 3.") + .def("get_nuclearcharges", &HartreeFockProvider::get_nuclearcharges, + "Returns nuclear charges of SCF reference, array size 3") + .def("get_coordinates", &HartreeFockProvider::get_coordinates, + "Returns coordinates of SCF REfernece State in Bohr") + .def("get_nuclear_masses", &HartreeFockProvider::get_nuclear_masses, + "Returns the nuclear masses") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 0f76c63e..7fb0f659 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -111,13 +111,28 @@ void export_ReferenceState(py::module& m) { .def_property_readonly( "nuclear_total_charge", [](const ReferenceState& ref) { return ref.nuclear_multipole(0)[0]; }) - .def_property_readonly("nuclear_dipole", + .def_property_readonly("nuclear_quadrupole", + [](const ReferenceState& ref) { + py::array_t ret(std::vector{9}); + auto res = ref.nuclear_multipole(2); + std::copy(res.begin(), res.end(), ret.mutable_data()); + return res; + }) + .def_property_readonly("nuclear_dipole", [](const ReferenceState& ref) { py::array_t ret(std::vector{3}); auto res = ref.nuclear_multipole(1); std::copy(res.begin(), res.end(), ret.mutable_data()); return res; }) + .def_property_readonly("nuclear_charges", &ReferenceState::nuclear_charges, + "Nuclear chages") + .def_property_readonly("nuclear_masses", &ReferenceState::nuclear_masses, + "Nuclear masses") + .def_property_readonly("coordinates", &ReferenceState::coordinates, + "Returns coordinates of the SCF Reference as list e.g. [x1, y1, z1, x2, y2, z2,...]") + .def_property_readonly("n_atoms", &ReferenceState::n_atoms, + "Returns the number of atoms") .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From 028a92bb9f2ccbd21cfb38fd853e3086216b79b1 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 29 Sep 2022 10:26:38 +0200 Subject: [PATCH 02/42] test --- adcc/workflow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/adcc/workflow.py b/adcc/workflow.py index a290032e..1412fc98 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -40,8 +40,7 @@ IndexSymmetrisation) __all__ = ["run_adc"] - - +blabla def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, eigensolver=None, guesses=None, n_guesses=None, n_guesses_doubles=None, output=sys.stdout, core_orbitals=None, From f80568ff3725ea95509fc6a711d2ea09cc80bf9e Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 11 Jan 2023 12:58:46 +0100 Subject: [PATCH 03/42] Adapt mtms, transition density (antisymmetric ops) --- adcc/adc_pp/modified_transition_moments.py | 71 ++++++++++++++-------- adcc/adc_pp/transition_dm.py | 2 +- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index dddcaad1..612b7e98 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -28,34 +28,62 @@ from adcc.functions import einsum, evaluate from adcc.Intermediates import Intermediates from adcc.AmplitudeVector import AmplitudeVector -import numpy as np + def mtm_adc0(mp, dipop, intermediates): - return AmplitudeVector(ph=dipop.ov) + #return AmplitudeVector(ph=dipop.ov) + if dipop.is_symmetric: + dipop_vo = dipop.ov.transpose((1, 0)) + else: + dipop_vo = dipop.vo.copy() + return AmplitudeVector(ph=dipop_vo.transpose((1, 0))) def mtm_adc1(mp, dipop, intermediates): - f1 = dipop.ov - einsum("ijab,jb->ia", mp.t2(b.oovv), dipop.ov) + if dipop.is_symmetric: + dipop_vo = dipop.ov.transpose((1, 0)) + else: + dipop_vo = dipop.vo.copy() + f1 = dipop_vo.transpose((1, 0)) + einsum("rkjs,rs->kj", mp.t2(b.oovv), dipop.ov) return AmplitudeVector(ph=f1) def mtm_adc2(mp, dipop, intermediates): t2 = mp.t2(b.oovv) p0 = mp.mp2_diffdm - + hf = mp.reference_state + if dipop.is_symmetric: + dipop_vo = dipop.ov.transpose((1, 0)) + else: + dipop_vo = dipop.vo.copy() f1 = ( - + dipop.ov - - einsum("ijab,jb->ia", t2, - + dipop.ov - 0.5 * einsum("jkbc,kc->jb", t2, dipop.ov)) - + 0.5 * einsum("ij,ja->ia", p0.oo, dipop.ov) - - 0.5 * einsum("ib,ab->ia", dipop.ov, p0.vv) - + einsum("ib,ab->ia", p0.ov, dipop.vv) - - einsum("ij,ja->ia", dipop.oo, p0.ov) + dipop_vo.transpose((1, 0)) + + einsum("rkjs,rs->kj", mp.t2(b.oovv), dipop.ov) + + # antonia 1: + #- einsum("krjs,rs->kj", t2, dipop.ov) + -0.25 * einsum("nvjr,nvsr,sk->kj", t2, t2, dipop_vo) + -0.25 * einsum("knrs,vnrs,jv->kj", t2, t2, dipop_vo) + +0.5 * einsum("knjr,vnsr,sv->kj", t2, t2, dipop_vo) + -1.0 * einsum("vk,vj->kj", dipop.oo, p0.ov) + +1.0 * einsum("jr,kr->kj", dipop.vv, p0.ov) - einsum("ijab,jb->ia", mp.td2(b.oovv), dipop.ov) + # adcc: + # - einsum("ijab,jb->ia", t2, + # + dipop.ov - 0.5 * einsum("jkbc,kc->jb", t2, dipop.ov)) + # + 0.5 * einsum("ij,ja->ia", p0.oo, dipop.ov) + # - 0.5 * einsum("ib,ab->ia", dipop.ov, p0.vv) + # + einsum("ib,ab->ia", p0.ov, dipop.vv) + # - einsum("ij,ja->ia", dipop.oo, p0.ov) + # - einsum("ijab,jb->ia", mp.td2(b.oovv), dipop.ov) ) - f2 = ( - + einsum("ijac,bc->ijab", t2, dipop.vv).antisymmetrise(2, 3) - - einsum("ik,kjab->ijab", dipop.oo, t2).antisymmetrise(0, 1) + f2 = 0.5*( + - 2*einsum("vlij,vk->klij", t2, dipop.oo).antisymmetrise(0, 1) + # + einsum("vkij,vl->klij", t2, dipop.oo) + + 2*einsum("klsj,is->klij", t2, dipop.vv).antisymmetrise(2, 3) + # - einsum("klsi,js->klij", t2, dipop.vv) + # + einsum("ijac,bc->ijab", t2, dipop.vv).antisymmetrise(2, 3) + # - einsum("ik,kjab->ijab", dipop.oo, t2).antisymmetrise(0, 1) ) return AmplitudeVector(ph=f1, pphh=f2) @@ -78,6 +106,7 @@ def mtm_cvs_adc2(mp, dipop, intermediates): "adc0": mtm_adc0, "adc1": mtm_adc1, "adc2": mtm_adc2, + "adc2x": mtm_adc2, # Identical to ADC(2) "cvs-adc0": mtm_cvs_adc0, "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0) "cvs-adc2": mtm_cvs_adc2, @@ -118,20 +147,12 @@ def modified_transition_moments(method, ground_state, dipole_operator=None, elif not isinstance(dipole_operator, list): unpack = True dipole_operator = [dipole_operator] - dipole_op = np.array(dipole_operator) #test dimensionen of operator if method.name not in DISPATCH: raise NotImplementedError("modified_transition_moments is not " f"implemented for {method.name}.") - if dipole_op.ndim == 1: - ret = [DISPATCH[method.name](ground_state, dipop, intermediates) - for dipop in dipole_operator] - elif dipole_op.ndim == 2: #allow quadrupol like operators - ret = [0, 0, 0] #don't know how to this more cleanly - for i in range(3): - ret[i] = [DISPATCH[method.name](ground_state, dipop, intermediates) - for dipop in dipole_operator[i]] - else: - raise NotImplementedError(f"modified_transition_moments are not implemented for {dipole_op.ndim}-dimensional operators. Only 1 and 2-dimensional operators are available.") + + ret = [DISPATCH[method.name](ground_state, dipop, intermediates) + for dipop in dipole_operator] if unpack: assert len(ret) == 1 ret = ret[0] diff --git a/adcc/adc_pp/transition_dm.py b/adcc/adc_pp/transition_dm.py index 111a1f89..1b706f92 100644 --- a/adcc/adc_pp/transition_dm.py +++ b/adcc/adc_pp/transition_dm.py @@ -86,7 +86,7 @@ def tdm_adc2(mp, amplitude, intermediates): # Compute ADC(2) tdm dm.oo = ( # adc2_dp0_oo - einsum("ia,ja->ij", p0.ov, u1) - - einsum("ikab,jkab->ij", u2, t2) + - einsum("ikab,jkab->ji", u2, t2) ) dm.vv = ( # adc2_dp0_vv + einsum("ia,ib->ab", u1, p0.ov) From 09839e9cbbabbae307a5f4ae0740db88cc228042 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 11 Jan 2023 13:26:09 +0100 Subject: [PATCH 04/42] Integrals (PySCF) and warnings (others) --- adcc/backends/__init__.py | 4 +- adcc/backends/molsturm.py | 3 +- adcc/backends/psi4.py | 3 + adcc/backends/pyscf.py | 150 +++++++++++++++---------------------- adcc/backends/veloxchem.py | 2 + 5 files changed, 69 insertions(+), 93 deletions(-) diff --git a/adcc/backends/__init__.py b/adcc/backends/__init__.py index 8abc3424..a8a1567b 100644 --- a/adcc/backends/__init__.py +++ b/adcc/backends/__init__.py @@ -52,7 +52,7 @@ def have_backend(backend): return backend in available() -def import_scf_results(res): +def import_scf_results(res, gauge_origin): """ Import an scf_result from an SCF program. Tries to be smart and guess what the host program was and how to import it. @@ -62,7 +62,7 @@ def import_scf_results(res): from pyscf import scf if isinstance(res, scf.hf.SCF): - return backend_pyscf.import_scf(res) + return backend_pyscf.import_scf(res, gauge_origin) if have_backend("molsturm"): from . import molsturm as backend_molsturm diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index d589b6f7..cceec924 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -21,7 +21,7 @@ ## ## --------------------------------------------------------------------- import numpy as np - +import warnings from molsturm.State import State from adcc.DataHfProvider import DataHfProvider @@ -29,6 +29,7 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") + warnings.warn("Gauge origin is only available in the PyScf backend. For molsturm gauge origin is selcetd as [0.0, 0.0, 0.0]") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index c588fa83..9c0f95d1 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -27,6 +27,8 @@ import psi4 +import warnings + from .EriBuilder import EriBuilder from ..exceptions import InvalidReference from ..ExcitedStates import EnergyCorrection @@ -37,6 +39,7 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) + warnings.warn(f"Gauge origin selection only available in PySCF not in {self.backend}. The gauge origin is selected as [0, 0, 0]") @cached_property def electric_dipole(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index bccf09d1..3442519d 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -21,6 +21,7 @@ ## ## --------------------------------------------------------------------- import numpy as np +import warnings from libadcc import HartreeFockProvider from adcc.misc import cached_property @@ -34,113 +35,77 @@ class PyScfOperatorIntegralProvider: - def __init__(self, scfres): + def __init__(self, scfres, gauge_origin): self.scfres = scfres self.backend = "pyscf" + coords = scfres.mol.atom_coords() + mass = scfres.mol.atom_mass_list() + charges = scfres.mol.atom_charges() + if gauge_origin == "mass_center": + self.gauge_origin = np.einsum("i,ij->j", mass, coords)/mass.sum() + elif gauge_origin == "charge_center": + self.gauge_origin = np.einsum ("i,ij->j", charges, coords)/charges.sum() + elif gauge_origin =="origin": + self.gauge_origin = [0.0, 0.0, 0.0] + elif type(gauge_origin) == list: + self.gauge_origin = gauge_origin + else: + raise NotImplementedError("Gauge origin has to be defined either by using the keywords mass_center, charge_center or origin. Or as a list eg. [x, y, z]") + if isinstance(gauge_origin, str): + print(f"gauge origin is selected as: {gauge_origin}", self.gauge_origin) + else: + print(f"gauge origin is selected as: {gauge_origin}") + warnings.warn("Gauge origin selection is not tested") @cached_property def electric_dipole(self): return list(self.scfres.mol.intor_symmetric('int1e_r', comp=3)) - # center of mass: - # coords = self.scfres.mol.atom_coords() - # mass = self.scfres.mol.atom_mass_list() - # mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - # origin = mass_center - - - - @cached_property def magnetic_dipole(self): - # TODO: Gauge origin? - coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() - charges = self.scfres.mol.atom_charges() - charge_center = np.einsum ('i, ij -> j ', charges, coords)/charges.sum() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - #origin = charge_center - origin = mass_center - #origin = [0.0, 0.0, 0.0] - with self.scfres.mol.with_common_orig(origin): #gauge origin! + with self.scfres.mol.with_common_orig(self.gauge_origin): #gauge origin! return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) @cached_property def nabla(self): - with self.scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + with self.scfres.mol.with_common_orig(self.gauge_origin): return list( -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) - @cached_property - def r_quadr(self): - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) - return r_quadr - - @cached_property - def r_r(self): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) - return list(r_r) - @cached_property def diag_mag(self): - #define center of mass: - coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - origin = mass_center - #origin = [0.0, 0.0, 0.0] - with self.scfres.mol.with_common_orig(origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) - r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) - r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) + with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) for i in range(3): r_quadr_matrix[i][i] = r_quadr - term = r_quadr_matrix - r_r - term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) - return list(-1/4 * term) + term = -0.25 * (r_quadr_matrix - r_r) + term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + return list(term) @cached_property def electric_quadrupole_traceless(self): - coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - origin = mass_center - #origin = [0.0,0.0,0.0] - with self.scfres.mol.with_common_orig(origin): + with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) - r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) + r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) - r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) - r_quadr_matrix[0][0] = r_quadr - r_quadr_matrix[1][1] = r_quadr - r_quadr_matrix[2][2] = r_quadr - term = 1/2 * (3 * r_r - r_quadr_matrix) - term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) - return list(term) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) + for i in range(3): + r_quadr_matrix[i][i] = r_quadr + term = 0.5 * (3 * r_r - r_quadr_matrix) + term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + return list(term) @cached_property def electric_quadrupole(self): - coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - origin = mass_center - #origin = [0.0,0.0,0.0] - with self.scfres.mol.with_common_orig(origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) - #r_r = np.reshape(r_r, (3,3,r_r.shape[1],r_r.shape[1])) - #r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) - #r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) - #r_quadr_matrix = np.zeros((3,3,r_quadr.shape[0], r_quadr.shape[0])) - #r_quadr_matrix[0][0] = r_quadr - #r_quadr_matrix[1][1] = r_quadr - #r_quadr_matrix[2][2] = r_quadr - #term =1/2*(3 * r_r - r_quadr_matrix) - #term = np.reshape(term, (9, r_quadr.shape[0],r_quadr.shape[0])) - return list(r_r) + with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + return list(r_r) @property def pe_induction_elec(self): @@ -211,17 +176,18 @@ class PyScfHFProvider(HartreeFockProvider): This implementation is only valid if no orbital reordering is required. """ - def __init__(self, scfres ): + def __init__(self, scfres, gauge_origin): # Do not forget the next line, # otherwise weird errors result super().__init__() self.scfres = scfres + self.gauge_origin = gauge_origin n_alpha, n_beta = scfres.mol.nelec self.eri_builder = PyScfEriBuilder(self.scfres, self.n_orbs, self.n_orbs_alpha, n_alpha, n_beta, self.restricted) self.operator_integral_provider = PyScfOperatorIntegralProvider( - self.scfres + self.scfres, gauge_origin ) if not self.restricted: assert self.scfres.mo_coeff[0].shape[1] == \ @@ -341,16 +307,20 @@ def get_nuclear_multipole(self, order): # electric quadrupole Q_jk = sum_i (q_i * r_ij *r_ik ) coords = self.scfres.mol.atom_coords() mass = self.scfres.mol.atom_mass_list() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() - coords = coords - mass_center - r_r = np.einsum('ij, ik -> ijk' ,coords, coords) - #r_quadr_matrix = np.zeros((len(mass), 3,3)) - #for i in range(len(mass)): - # for j in range(3): - # for k in range(3): - # if j ==k: - # r_quadr_matrix[i][j][k] = np.trace(r_r[i]) - res = np.einsum('i, ijk -> jk', charges, r_r) + if self.gauge_origin == 'mass_center': + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + coords = coords - mass_center + elif self.gauge_origin == 'charge_center': + charge_center = np.einsum ('i,ij->j', charges, coords)/charges.sum() + coords = coords - charge_center + elif self.gauge_origin == 'origin': + coords = coords + elif type(self.gauge_origin) == list: + coords = coords - self.gauge_origin + else: + raise NotImplementedError() + r_r = np.einsum('ij,ik->ijk' ,coords, coords) + res = np.einsum('i,ijk->jk', charges, r_r) res = np.array([res[0][0], res[0][1], res[0][2], res[1][1], res[1][2], res[2][2]]) #matrix is symmetrical, only the six different values. return res else: @@ -400,7 +370,7 @@ def flush_cache(self): self.eri_builder.flush_cache() -def import_scf(scfres): +def import_scf(scfres, gauge_origin): # TODO The error messages here could be a bit more verbose if not isinstance(scfres, scf.hf.SCF): @@ -415,7 +385,7 @@ def import_scf(scfres): # TODO Check for point-group symmetry, # check for density-fitting or choleski - return PyScfHFProvider(scfres) + return PyScfHFProvider(scfres, gauge_origin) def run_hf(xyz, basis, charge=0, multiplicity=1, conv_tol=1e-11, diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 0f432ff6..e0936ad8 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -23,6 +23,7 @@ import os import tempfile import numpy as np +import warnings from mpi4py import MPI from libadcc import HartreeFockProvider @@ -43,6 +44,7 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" + warnings.warn("Gauge origin selection is only available in the PyScf backend. For {self.backend} gauge origin is selected as [0.0, 0.0, 0.0]") @cached_property def electric_dipole(self): From 350add01d995f8982167bc0f68f07115b163b426 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 11 Jan 2023 13:51:14 +0100 Subject: [PATCH 05/42] SCF backends integrals --- adcc/backends/molsturm.py | 4 +++- adcc/backends/psi4.py | 4 +++- adcc/backends/pyscf.py | 8 ++++++-- adcc/backends/veloxchem.py | 4 +++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index cceec924..d5dd6799 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -29,7 +29,9 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn("Gauge origin is only available in the PyScf backend. For molsturm gauge origin is selcetd as [0.0, 0.0, 0.0]") + warnings.warn( "Gauge origin selection only available in PySCF" + f"not in Molsturm." + "The gauge origin is selected as [0, 0, 0]") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 9c0f95d1..41ca6a3e 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -39,7 +39,9 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) - warnings.warn(f"Gauge origin selection only available in PySCF not in {self.backend}. The gauge origin is selected as [0, 0, 0]") + warnings.warn( "Gauge origin selection only available in PySCF" + f"not in {self.backend}." + "The gauge origin is selected as [0, 0, 0]") @cached_property def electric_dipole(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 3442519d..7fd4c911 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -50,7 +50,10 @@ def __init__(self, scfres, gauge_origin): elif type(gauge_origin) == list: self.gauge_origin = gauge_origin else: - raise NotImplementedError("Gauge origin has to be defined either by using the keywords mass_center, charge_center or origin. Or as a list eg. [x, y, z]") + raise NotImplementedError("Gauge origin has to be defined either by" + "using one of the keywords" + "mass_center, charge_center or origin." + "Or by declaring a list eg. [x, y, z]") if isinstance(gauge_origin, str): print(f"gauge origin is selected as: {gauge_origin}", self.gauge_origin) else: @@ -321,7 +324,8 @@ def get_nuclear_multipole(self, order): raise NotImplementedError() r_r = np.einsum('ij,ik->ijk' ,coords, coords) res = np.einsum('i,ijk->jk', charges, r_r) - res = np.array([res[0][0], res[0][1], res[0][2], res[1][1], res[1][2], res[2][2]]) #matrix is symmetrical, only the six different values. + res = np.array([res[0][0], res[0][1], res[0][2], + res[1][1], res[1][2], res[2][2]]) return res else: raise NotImplementedError("get_nuclear_multipole with order > 2") diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index e0936ad8..ad9b3177 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -44,7 +44,9 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" - warnings.warn("Gauge origin selection is only available in the PyScf backend. For {self.backend} gauge origin is selected as [0.0, 0.0, 0.0]") + warnings.warn( "Gauge origin selection only available in PySCF" + f"not in {self.backend}." + "The gauge origin is selected as [0, 0, 0]") @cached_property def electric_dipole(self): From aa9772e2c9f3feb1e1fe21e47634486d2b6cd307 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 11 Jan 2023 13:51:42 +0100 Subject: [PATCH 06/42] gauge origin selection --- adcc/OperatorIntegrals.py | 67 ++++++++++++------------------- adcc/ReferenceState.py | 14 ++++++- adcc/solver/conjugate_gradient.py | 6 ++- adcc/workflow.py | 15 ++++--- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index ab38c667..ecd6a8cf 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -101,11 +101,9 @@ def available(self): "electric_dipole", "magnetic_dipole", "nabla", - "r_quadr", - "r_r", - "diag_mag", - 'electric_quadrupole', "electric_quadrupole_traceless", + "electric_quadrupole", + "diag_mag", "pe_induction_elec", "pcm_potential_elec" ) @@ -129,24 +127,6 @@ def import_dipole_like_operator(self, integral, is_symmetric=True): dipoles.append(dip_ff) return dipoles - - def import_scalar(self, integral, is_symmetric=True): - if integral not in self.available: - raise NotImplementedError(f"{integral.replace('_', ' ')} operator " - "not implemented " - f"in {self.provider_ao.backend} backend.") - - scalar = [] - scalar_backend = getattr(self.provider_ao, integral) - scalar_bb = replicate_ao_block(self.mospaces, scalar_backend, - is_symmetric=is_symmetric) - scalar_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric) - transform_operator_ao2mo(scalar_bb, scalar_ff, self.__coefficients, - self.__conv_tol) - scalar.append(scalar_ff) - return scalar - - @property @timed_member_call("_import_timer") def electric_dipole(self): @@ -175,49 +155,54 @@ def import_quadrupole_like_operator(self, integral, is_symmetric=True): raise NotImplementedError(f"{integral.replace('_', ' ')} operator " "not implemented " f"in {self.provider_ao.backend} backend.") - op_integrals = getattr(self.provider_ao, integral) quad = [] quadrupoles = [] - for i, component in enumerate(["xx", "xy", "xz" , 'yx', 'yy', 'yz', 'zx', 'zy', 'zz']): + for i, component in enumerate(["xx", "xy", "xz", + "yx", "yy", "yz", + "zx", "zy", "zz"]): quad_backend = getattr(self.provider_ao, integral)[i] quad_bb = replicate_ao_block(self.mospaces, quad_backend, - is_symmetric=is_symmetric) - quad_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric) - transform_operator_ao2mo(quad_bb, quad_ff, self.__coefficients, self.__conv_tol) + is_symmetric=is_symmetric) + quad_ff = OneParticleOperator(self.mospaces, + is_symmetric=is_symmetric) + transform_operator_ao2mo(quad_bb, quad_ff, + self.__coefficients, + self.__conv_tol) quad.append(quad_ff) quadrupoles.append(quad[:3]) quadrupoles.append(quad[3:6]) quadrupoles.append(quad[6:]) return quadrupoles - @property - @timed_member_call("_import_timer") - def r_r(self): - return self.import_quadrupole_like_operator("r_r", - is_symmetric=True) - @property @timed_member_call("_import_timer") def electric_quadrupole_traceless(self): + """ + Return the traceless electric quadrupole integrals + in the molecular orbital basis. + """ return self.import_quadrupole_like_operator("electric_quadrupole_traceless", - is_symmetric=True) + is_symmetric=True) @property @timed_member_call("_import_timer") def electric_quadrupole(self): + """ + Return the traced electric quadrupole integrals + in the molecular orbital basis. + """ return self.import_quadrupole_like_operator("electric_quadrupole", - is_symmetric=True) + is_symmetric=True) @property @timed_member_call("_import_timer") def diag_mag(self): + """ + Return the diamagnetic magnetizability integrals + in the molecular orbital basis. + """ return self.import_quadrupole_like_operator("diag_mag", - is_symmetric=True) - - @property - @timed_member_call("_import_timer") - def r_quadr(self): - return self.import_scalar("r_quadr", is_symmetric=True) + is_symmetric=True) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index a3274c61..5008f7d3 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -37,7 +37,7 @@ class ReferenceState(libadcc.ReferenceState): def __init__(self, hfdata, core_orbitals=None, frozen_core=None, frozen_virtual=None, symmetry_check_on_import=False, - import_all_below_n_orbs=10): + import_all_below_n_orbs=10, gauge_origin='mass_center'): """Construct a ReferenceState holding information about the employed SCF reference. @@ -139,7 +139,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, beta orbital into the core space. """ if not isinstance(hfdata, libadcc.HartreeFockSolution_i): - hfdata = import_scf_results(hfdata) + hfdata = import_scf_results(hfdata, gauge_origin) self._mospaces = MoSpaces(hfdata, frozen_core=frozen_core, frozen_virtual=frozen_virtual, @@ -154,6 +154,8 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, hfdata.operator_integral_provider, self._mospaces, self.orbital_coefficients, self.conv_tol ) + + self._gauge_origin = gauge_origin self.environment = None # no environment attached by default for name in ["excitation_energy_corrections", "environment"]: @@ -225,4 +227,12 @@ def dipole_moment(self): return self.nuclear_dipole - np.array([product_trace(comp, self.density) for comp in dipole_integrals]) + @cached_property + def gauge_origin(self): + """ + Return the selected gauge origin used for operators integrals. + Until now only available for the PySCF backend + """ + return self._gauge_origin + # TODO some nice describe method diff --git a/adcc/solver/conjugate_gradient.py b/adcc/solver/conjugate_gradient.py index 77982cc3..cc2dd222 100644 --- a/adcc/solver/conjugate_gradient.py +++ b/adcc/solver/conjugate_gradient.py @@ -161,7 +161,11 @@ def is_converged(state): Apk = matrix @ pk state.n_applies += 1 res_dot_zk = dot(state.residual, zk) - ak = float(res_dot_zk / dot(pk, Apk)) + x = dot(pk,Apk) + if dot(pk,Apk) == 0: + x = 1e-5 + #ak = float(res_dot_zk / dot(pk, Apk)) + ak = float(res_dot_zk / x) state.solution = evaluate(state.solution + ak * pk) residual_old = state.residual diff --git a/adcc/workflow.py b/adcc/workflow.py index 1412fc98..aef9b229 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -40,13 +40,12 @@ IndexSymmetrisation) __all__ = ["run_adc"] -blabla def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, eigensolver=None, guesses=None, n_guesses=None, n_guesses_doubles=None, output=sys.stdout, core_orbitals=None, frozen_core=None, frozen_virtual=None, method=None, n_singlets=None, n_triplets=None, n_spin_flip=None, - environment=None, **solverargs): + environment=None, gauge_origin ="mass_center", **solverargs): """Run an ADC calculation. Main entry point to run an ADC calculation. The reference to build the ADC @@ -132,6 +131,11 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, The keywords to specify how coupling to an environment model, e.g. PE, is treated. For details see :ref:`environment`. + gauge_origin: list or str, optional + Define the gauge origin, either by specifying a list directly ([x,y,z]) + or by using one of the keywords ('mass_center', 'charge_center', 'origin'). + Default: 'mass_center' + Other parameters ---------------- max_subspace : int, optional @@ -177,7 +181,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, """ matrix = construct_adcmatrix( data_or_matrix, core_orbitals=core_orbitals, frozen_core=frozen_core, - frozen_virtual=frozen_virtual, method=method) + frozen_virtual=frozen_virtual, method=method, gauge_origin=gauge_origin) n_states, kind = validate_state_parameters( matrix.reference_state, n_states=n_states, n_singlets=n_singlets, @@ -218,7 +222,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, # Individual steps # def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, - frozen_virtual=None, method=None): + frozen_virtual=None, method=None, gauge_origin='mass_center'): """ Use the provided data or AdcMatrix object to check consistency of the other passed parameters and construct the AdcMatrix object representing @@ -244,7 +248,8 @@ def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, refstate = adcc_ReferenceState(data_or_matrix, core_orbitals=core_orbitals, frozen_core=frozen_core, - frozen_virtual=frozen_virtual) + frozen_virtual=frozen_virtual, + gauge_origin=gauge_origin) except ValueError as e: raise InputError(str(e)) # In case of an issue with the spaces data_or_matrix = refstate From 2d8f470acd22edd40a6e175d99092438933b08c7 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 2 May 2023 09:56:27 +0200 Subject: [PATCH 07/42] Generate new testdata, make sure old tests still working --- adcc/DataHfProvider.py | 56 +++-- adcc/test_properties.py | 12 +- adcc/testdata/cache.py | 14 +- adcc/testdata/dump_pyscf.py | 266 +++++++++++++++++++++ adcc/testdata/dump_reference_adcc.py | 2 +- adcc/testdata/generate_hfdata_h2o_sto3g.py | 7 +- adcc/testdata/generate_reference.py | 24 +- 7 files changed, 346 insertions(+), 35 deletions(-) create mode 100644 adcc/testdata/dump_pyscf.py diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index f6c431d3..690a52d3 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -69,7 +69,7 @@ def __init__(self, backend="data"): class DataHfProvider(HartreeFockProvider): - def __init__(self, data): + def __init__(self, data, gauge_origin = 'origin'): """ Initialise the DataHfProvider class with the `data` being a supported data container (currently python dictionary or HDF5 file). @@ -204,20 +204,48 @@ def __init__(self, data): + str(mmp["elec_1"].shape)) opprov.electric_dipole = np.asarray(mmp["elec_1"]) magm = data.get("magnetic_moments", {}) - if "mag_1" in magm: - if magm["mag_1"].shape != (3, nb, nb): - raise ValueError("magnetic_moments/mag_1 is expected to have " - "shape " + str((3, nb, nb)) + " not " - + str(magm["mag_1"].shape)) - opprov.magnetic_dipole = np.asarray(magm["mag_1"]) derivs = data.get("derivatives", {}) - if "nabla" in derivs: - if derivs["nabla"].shape != (3, nb, nb): - raise ValueError("derivatives/nabla is expected to " - "have shape " - + str((3, nb, nb)) + " not " - + str(derivs["nabla"].shape)) - opprov.nabla = np.asarray(derivs["nabla"]) + if gauge_origin == 'origin': + if "elec_2" in mmp: + if mmp["elec_2"].shape != (9, nb, nb): + raise ValueError("multipoles/elec_2 is expected to have " + "shape " + str((9, nb, nb)) + " not " + + str(mmp["elec_2"].shape)) + opprov.electric_quadrupole = np.asarray(mmp["elec_2"]) + if "mag_1" in magm: + if magm["mag_1"].shape != (3, nb, nb): + raise ValueError("magnetic_moments/mag_1 is expected to have " + "shape " + str((3, nb, nb)) + " not " + + str(magm["mag_1"].shape)) + opprov.magnetic_dipole = np.asarray(magm["mag_1"]) + if "nabla" in derivs: + print("Helau") + if derivs["nabla"].shape != (3, nb, nb): + raise ValueError("derivatives/nabla is expected to " + "have shape " + + str((3, nb, nb)) + " not " + + str(derivs["nabla"].shape)) + opprov.nabla = np.asarray(derivs["nabla"]) + else: + if f"elec_2_{gauge_origin}" in mmp: + if mmp["elec_2"].shape != (9, nb, nb): + raise ValueError(f"multipoles/elec_2_{gauge_origin} is expected to have " + "shape " + str((9, nb, nb)) + " not " + + str(mmp[f"elec_2_{gauge_origin}"].shape)) + opprov.electric_quadrupole = np.asarray(mmp["elec_2"]) + if f"mag_1_{gauge_origin}" in magm: + if magm[f"mag_1_{gauge_origin}"].shape != (3, nb, nb): + raise ValueError(f"magnetic_moments/mag_1_{gauge_origin} is expected to have " + "shape " + str((3, nb, nb)) + " not " + + str(magm[f"mag_1_{gauge_origin}"].shape)) + opprov.magnetic_dipole = np.asarray(magm[f"mag_1_{gauge_origin}"]) + if f"nabla_mass_center_{gauge_origin}" in derivs: + if derivs[f"nabla_{gauge_origin}"].shape != (3, nb, nb): + raise ValueError(f"derivatives/nabla_{gauge_origin} is expected to " + "have shape " + + str((3, nb, nb)) + " not " + + str(derivs[f"nabla_{gauge_origin}"].shape)) + opprov.nabla = np.asarray(derivs[f"nabla_{gauge_origin}"]) self.operator_integral_provider = opprov # diff --git a/adcc/test_properties.py b/adcc/test_properties.py index 89e6630e..f43fc2d8 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -22,6 +22,7 @@ ## --------------------------------------------------------------------- import unittest import numpy as np +import itertools from numpy.testing import assert_allclose @@ -125,11 +126,11 @@ def base_test(self, system, method, kind): basemethods = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = [m for bm in basemethods for m in [bm, "cvs_" + bm]] +gauge_origins = ["origin", "mass_center", "charge_center"] - -@expand_test_templates(methods) +@expand_test_templates(list(itertools.product(methods, gauge_origins))) class TestMagneticTransitionDipoleMoments(unittest.TestCase): - def template_linear_molecule(self, method): + def template_linear_molecule(self, method, gauge_origin): method = method.replace("_", "-") backend = "" xyz = """ @@ -140,11 +141,12 @@ def template_linear_molecule(self, method): scfres = run_hf(backend, xyz, basis) if "cvs" in method: - state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) + state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2, gauge_origin=gauge_origin) else: - state = run_adc(scfres, method=method, n_singlets=10) + state = run_adc(scfres, method=method, n_singlets=10, gauge_origin=gauge_origin) tdms = state.transition_magnetic_dipole_moment # For linear molecules lying on the z-axis, the z-component must be zero for tdm in tdms: assert tdm[2] < 1e-10 + diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index 6810dca5..25679df3 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -155,18 +155,22 @@ def hfimport(self): ret[k] = hdf5io.load(datafile) return ret - def read_reference_data(self, refname): + def read_reference_data(self, refname, read_gauge = False): prefixes = ["", "cvs", "fc", "fv", "fc_cvs", "fv_cvs", "fc_fv", "fc_fv_cvs"] raws = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = raws + ["_".join([p, r]) for p in prefixes for r in raws if p != ""] - + gauge_origin = ["origin", "mass_center", "charge_center"] ret = {} for k in self.testcases: fulldict = {} for m in methods: - datafile = fullfile(k + "_" + refname + "_" + m + ".hdf5") + if read_gauge: + for g in gauge_origin: + datafile = fullfile(k + "_" + refname + "_" + m + g + ".hdf5") + else: + datafile = fullfile(k + "_" + refname + "_" + m + ".hdf5") if datafile is None or not os.path.isfile(datafile): continue fulldict.update(hdf5io.load(datafile)) @@ -179,8 +183,8 @@ def reference_data(self): return self.read_reference_data("reference") @cached_property - def adcc_reference_data(self): - return self.read_reference_data("adcc_reference") + def adcc_reference_data(self, read_gauge = False): + return self.read_reference_data("adcc_reference", read_gauge) def construct_adc_states(self, refdata): """ diff --git a/adcc/testdata/dump_pyscf.py b/adcc/testdata/dump_pyscf.py new file mode 100644 index 00000000..2e569e9c --- /dev/null +++ b/adcc/testdata/dump_pyscf.py @@ -0,0 +1,266 @@ +## Taken from adcc-testdata +## +## --------------------------------------------------------------------- +## +## Copyright (C) 2019 by Michael F. Herbst +## +## This file is part of adcc-testdata. +## +## adcc-testdata 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-testdata 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-testdata. If not, see . +## +## --------------------------------------------------------------------- +import numpy as np + +from pyscf import ao2mo, scf + +import h5py + + +def dump_pyscf(scfres, out): + """ + Convert pyscf SCF result to HDF5 file in adcc format + """ + if not isinstance(scfres, scf.hf.SCF): + raise TypeError("Unsupported type for dump_pyscf.") + + if not scfres.converged: + raise ValueError( + "Cannot dump a pyscf calculation, " + "which is not yet converged. Did you forget to run " + "the kernel() or the scf() function of the pyscf scf " + "object?" + ) + + if isinstance(out, h5py.File): + data = out + elif isinstance(out, str): + data = h5py.File(out, "w") + else: + raise TypeError("Unknown type for out, only HDF5 file and str supported.") + + # Try to determine whether we are restricted + if isinstance(scfres.mo_occ, list): + restricted = len(scfres.mo_occ) < 2 + elif isinstance(scfres.mo_occ, np.ndarray): + restricted = scfres.mo_occ.ndim < 2 + else: + raise ValueError( + "Unusual pyscf SCF class encountered. Could not " + "determine restricted / unrestricted." + ) + + mo_occ = scfres.mo_occ + mo_energy = scfres.mo_energy + mo_coeff = scfres.mo_coeff + fock_bb = scfres.get_fock() + + # pyscf only keeps occupation and mo energies once if restriced, + # so we unfold it here in order to unify the treatment in the rest + # of the code + if restricted: + mo_occ = np.asarray((mo_occ / 2, mo_occ / 2)) + mo_energy = (mo_energy, mo_energy) + mo_coeff = (mo_coeff, mo_coeff) + fock_bb = (fock_bb, fock_bb) + + # Transform fock matrix to MOs + fock = tuple(mo_coeff[i].transpose().conj() @ fock_bb[i] @ mo_coeff[i] + for i in range(2)) + del fock_bb + + # Determine number of orbitals + n_orbs_alpha = mo_coeff[0].shape[1] + n_orbs_beta = mo_coeff[1].shape[1] + n_orbs = n_orbs_alpha + n_orbs_beta + if n_orbs_alpha != n_orbs_beta: + raise ValueError( + "adcc cannot deal with different number of alpha and " + "beta orbitals like in a restricted " + "open-shell reference at the moment." + ) + + # Determine number of electrons + n_alpha = np.sum(mo_occ[0] > 0) + n_beta = np.sum(mo_occ[1] > 0) + if n_alpha != np.sum(mo_occ[0]) or n_beta != np.sum(mo_occ[1]): + raise ValueError("Fractional occupation numbers are not supported " + "in adcc.") + + # conv_tol is energy convergence, conv_tol_grad is gradient convergence + if scfres.conv_tol_grad is None: + conv_tol_grad = np.sqrt(scfres.conv_tol) + else: + conv_tol_grad = scfres.conv_tol_grad + threshold = max(10 * scfres.conv_tol, conv_tol_grad) + + # + # Put basic data into HDF5 file + # + data.create_dataset("n_orbs_alpha", shape=(), data=int(n_orbs_alpha)) + data.create_dataset("energy_scf", shape=(), data=float(scfres.e_tot)) + data.create_dataset("restricted", shape=(), data=restricted) + data.create_dataset("conv_tol", shape=(), data=float(threshold)) + + if restricted: + # Note: In the pyscf world spin is 2S, so the multiplicity + # is spin + 1 + data.create_dataset( + "spin_multiplicity", shape=(), data=int(scfres.mol.spin) + 1 + ) + else: + data.create_dataset("spin_multiplicity", shape=(), data=0) + + # + # Orbital reordering + # + # TODO This should not be needed any more + # adcc assumes that the occupied orbitals are specified first, + # followed by the virtual orbitals. Pyscf does this by means of the + # mo_occ numpy arrays, so we need to reorder in order to agree + # with what is expected in adcc. + # + # First build a structured numpy array with the negative occupation + # in the primary field and the energy in the secondary + # for each alpha and beta + order_array = ( + np.array(list(zip(-mo_occ[0], mo_energy[0])), + dtype=np.dtype("float,float")), + np.array(list(zip(-mo_occ[1], mo_energy[1])), + dtype=np.dtype("float,float")), + ) + sort_indices = tuple(np.argsort(ary) for ary in order_array) + + # Use the indices which sort order_array (== stort_indices) to reorder + mo_occ = tuple(mo_occ[i][sort_indices[i]] for i in range(2)) + mo_energy = tuple(mo_energy[i][sort_indices[i]] for i in range(2)) + mo_coeff = tuple(mo_coeff[i][:, sort_indices[i]] for i in range(2)) + fock = tuple(fock[i][sort_indices[i]][:, sort_indices[i]] for i in range(2)) + + # + # SCF orbitals and SCF results + # + data.create_dataset("occupation_f", data=np.hstack((mo_occ[0], mo_occ[1]))) + data.create_dataset("orben_f", data=np.hstack((mo_energy[0], mo_energy[1]))) + fullfock_ff = np.zeros((n_orbs, n_orbs)) + fullfock_ff[:n_orbs_alpha, :n_orbs_alpha] = fock[0] + fullfock_ff[n_orbs_alpha:, n_orbs_alpha:] = fock[1] + data.create_dataset("fock_ff", data=fullfock_ff, compression=8) + + non_canonical = np.max(np.abs(data["fock_ff"] - np.diag(data["orben_f"]))) + if non_canonical > data["conv_tol"][()]: + raise ValueError("Running adcc on top of a non-canonical fock " + "matrix is not implemented.") + + cf_bf = np.hstack((mo_coeff[0], mo_coeff[1])) + data.create_dataset("orbcoeff_fb", data=cf_bf.transpose(), compression=8) + + # + # ERI AO to MO transformation + # + if hasattr(scfres, "_eri") and scfres._eri is not None: + # eri is stored ... use it directly + eri_ao = scfres._eri + else: + # eri is not stored ... generate it now. + eri_ao = scfres.mol.intor("int2e", aosym="s8") + + aro = slice(0, n_alpha) + bro = slice(n_orbs_alpha, n_orbs_alpha + n_beta) + arv = slice(n_alpha, n_orbs_alpha) + brv = slice(n_orbs_alpha + n_beta, n_orbs) + + # compute full ERI tensor (with really everything) + eri = ao2mo.general(eri_ao, (cf_bf, cf_bf, cf_bf, cf_bf), compact=False) + eri = eri.reshape(n_orbs, n_orbs, n_orbs, n_orbs) + del eri_ao + + # Adjust spin-forbidden blocks to be exactly zero + eri[aro, bro, :, :] = 0 + eri[aro, brv, :, :] = 0 + eri[arv, bro, :, :] = 0 + eri[arv, brv, :, :] = 0 + + eri[bro, aro, :, :] = 0 + eri[bro, arv, :, :] = 0 + eri[brv, aro, :, :] = 0 + eri[brv, arv, :, :] = 0 + + eri[:, :, aro, bro] = 0 + eri[:, :, aro, brv] = 0 + eri[:, :, arv, bro] = 0 + eri[:, :, arv, brv] = 0 + + eri[:, :, bro, aro] = 0 + eri[:, :, bro, arv] = 0 + eri[:, :, brv, aro] = 0 + eri[:, :, brv, arv] = 0 + data.create_dataset("eri_ffff", data=eri, compression=8) + + # Calculate mass center and charge center + mass = scfres.mol.atom_mass_list() + charges = scfres.mol.atom_charges() + coords = scfres.mol.atom_coords() + mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + charge_center = np.einsum ('i,ij->j', charges, coords)/charges.sum() + + # Compute electric and nuclear multipole moments + mmp = data.create_group("multipoles") + mmp.create_dataset("nuclear_0", shape=(), data=int(np.sum(charges))) + mmp.create_dataset("nuclear_1", data=np.einsum("i,ix->x", charges, coords)) + mmp.create_dataset("elec_0", shape=(), data=-int(n_alpha + n_beta)) + mmp.create_dataset("elec_1", + data=scfres.mol.intor_symmetric("int1e_r", comp=3)) + with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + mmp.create_dataset("elec_2", + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + with scfres.mol.with_common_orig(mass_center): + mmp.create_dataset("elec_2_mass_center", + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + with scfres.mol.with_common_orig(charge_center): + mmp.create_dataset("elec_2_charge_center", + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + + magm = data.create_group("magnetic_moments") + derivs = data.create_group("derivatives") + with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + magm.create_dataset( + "mag_1", + data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) + ) + derivs.create_dataset( + "nabla", + data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) + with scfres.mol.with_common_orig(mass_center): + magm.create_dataset( + "mag_1_mass_center", + data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) + ) + derivs.create_dataset( + "nabla_mass_center", + data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) + with scfres.mol.with_common_orig(charge_center): + magm.create_dataset( + "mag_1_charge_center", + data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) + ) + derivs.create_dataset( + "nabla_charge_center", + data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) + + + data.attrs["backend"] = "pyscf" + return data diff --git a/adcc/testdata/dump_reference_adcc.py b/adcc/testdata/dump_reference_adcc.py index e9f241f6..bba189fa 100644 --- a/adcc/testdata/dump_reference_adcc.py +++ b/adcc/testdata/dump_reference_adcc.py @@ -54,7 +54,7 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", if not len(states): raise ValueError("No excited states obtained.") - + print("hello") # # MP # diff --git a/adcc/testdata/generate_hfdata_h2o_sto3g.py b/adcc/testdata/generate_hfdata_h2o_sto3g.py index 82b32cc3..c0d1fd8c 100755 --- a/adcc/testdata/generate_hfdata_h2o_sto3g.py +++ b/adcc/testdata/generate_hfdata_h2o_sto3g.py @@ -25,10 +25,11 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join +from adcc.testdata.dump_pyscf import dump_pyscf -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +#import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -43,7 +44,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = atd.dump_pyscf(mf, "h2o_sto3g_hfdata.hdf5") +h5f = dump_pyscf(mf, "h2o_sto3g_hfdata.hdf5") # Store configuration parameters for interesting cases to generate # reference data for. The data is stored as a stringified dict. diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index cd0b5f85..ba32a6da 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -31,9 +31,9 @@ import h5py -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +#import adcctestdata as atd # noqa: E402 def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): @@ -43,8 +43,9 @@ def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): dump_method(case, method, kw, spec, generator=generator) -def dump_method(case, method, kwargs, spec, generator="adcc"): +def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin = 'origin'): h5file = case + "_hfdata.hdf5" + #h5file = case + "_hfdata_old.hdf5" if not os.path.isfile(h5file): raise ValueError("HfData not found: " + h5file) @@ -53,8 +54,7 @@ def dump_method(case, method, kwargs, spec, generator="adcc"): hfdata = atd.HdfProvider(h5file) else: dumpfunction = dump_reference_adcc - hfdata = adcc.DataHfProvider(h5py.File(h5file, "r")) - + hfdata = adcc.DataHfProvider(h5py.File(h5file, "r"), gauge_origin = dump_gauge_origin) # Get dictionary of parameters for the reference cases. refcases = ast.literal_eval(hfdata.data["reference_cases"][()].decode()) kwargs = dict(kwargs) @@ -77,7 +77,9 @@ def dump_method(case, method, kwargs, spec, generator="adcc"): dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) else: dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) - if not os.path.isfile(dumpfile): + print("hola") + # if not os.path.isfile(dumpfile): + if os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -181,4 +183,12 @@ def main(): if __name__ == "__main__": - main() + #main() + + dump_method( + "h2o_sto3g", + "adc1", + kwargs = {"n_singlets": 10, "n_triplets": 10}, + spec="gen", + generator = "adcc" + ) From bc6ecaf97aa56a3a347e747c23d08ba4fb69b726 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 4 May 2023 10:49:54 +0200 Subject: [PATCH 08/42] New consistency tests for electric quadrupole operator. Consistency checks for gauge origin dependent operators. Change the default selection of gauge origin for operator integrals to origin. --- adcc/DataHfProvider.py | 9 ++- adcc/ElectronicTransition.py | 16 ++++++ adcc/ReferenceState.py | 8 ++- adcc/backends/__init__.py | 6 +- adcc/test_properties_consistency.py | 85 ++++++++++++++++++---------- adcc/testdata/cache.py | 80 +++++++++++++++++++------- adcc/testdata/dump_pyscf.py | 1 + adcc/testdata/dump_reference_adcc.py | 2 + adcc/testdata/generate_reference.py | 52 +++++++++-------- adcc/workflow.py | 11 ++-- 10 files changed, 187 insertions(+), 83 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 690a52d3..2c6ab592 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -158,6 +158,11 @@ def __init__(self, data, gauge_origin = 'origin'): data : dict or h5py.File Dictionary containing the HartreeFock data to use. For the required keys see details above. + + gauge_origin : str + Selection of the gauge origin for opertaor integrals. + Choose from the keywords: mass_center, charge_center, origin + default: origin """ # Do not forget the next line, otherwise weird errors result @@ -196,6 +201,7 @@ def __init__(self, data, gauge_origin = 'origin'): # Setup integral data opprov = DataOperatorIntegralProvider(self.__backend) + #print(gauge_origin) mmp = data.get("multipoles", {}) if "elec_1" in mmp: if mmp["elec_1"].shape != (3, nb, nb): @@ -219,7 +225,6 @@ def __init__(self, data, gauge_origin = 'origin'): + str(magm["mag_1"].shape)) opprov.magnetic_dipole = np.asarray(magm["mag_1"]) if "nabla" in derivs: - print("Helau") if derivs["nabla"].shape != (3, nb, nb): raise ValueError("derivatives/nabla is expected to " "have shape " @@ -239,7 +244,7 @@ def __init__(self, data, gauge_origin = 'origin'): "shape " + str((3, nb, nb)) + " not " + str(magm[f"mag_1_{gauge_origin}"].shape)) opprov.magnetic_dipole = np.asarray(magm[f"mag_1_{gauge_origin}"]) - if f"nabla_mass_center_{gauge_origin}" in derivs: + if f"nabla_{gauge_origin}" in derivs: if derivs[f"nabla_{gauge_origin}"].shape != (3, nb, nb): raise ValueError(f"derivatives/nabla_{gauge_origin} is expected to " "have shape " diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index c2f7e4a0..b9834680 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -194,6 +194,22 @@ def transition_magnetic_dipole_moment(self): for tdm in self.transition_dm ]) + @cached_property + @mark_excitation_property() + @timed_member_call(timer="_property_timer") + def transition_quadrupole_moment(self): + """List of transition quadrupole moments of all computed states""" + if self.property_method.level == 0: + warnings.warn("ADC(0) transition dipole moments are known to be " + "faulty in some cases.") + operator_integrals = np.array(self.operators.electric_quadrupole) + return np.array([ + [[product_trace(quad1, tdm) + for quad1 in quad] for quad in operator_integrals] + for tdm in self.transition_dm + ]) + + @cached_property @mark_excitation_property() def oscillator_strength(self): diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 5008f7d3..9c21093d 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -37,7 +37,7 @@ class ReferenceState(libadcc.ReferenceState): def __init__(self, hfdata, core_orbitals=None, frozen_core=None, frozen_virtual=None, symmetry_check_on_import=False, - import_all_below_n_orbs=10, gauge_origin='mass_center'): + import_all_below_n_orbs=10, gauge_origin='origin'): """Construct a ReferenceState holding information about the employed SCF reference. @@ -101,6 +101,12 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, parameter, the class will thus automatically import all ERI tensor and Fock matrix blocks. + gauge_origin : str or list, optional + Select the gauge origin for operator integrals. + Either by specifying a list [x,y,z] or by choosing one of the + keywords (mass_center, charge_center, origin) + default: origin + Examples -------- To start a calculation with the 2 lowest alpha and beta orbitals diff --git a/adcc/backends/__init__.py b/adcc/backends/__init__.py index a8a1567b..289d49a3 100644 --- a/adcc/backends/__init__.py +++ b/adcc/backends/__init__.py @@ -52,7 +52,7 @@ def have_backend(backend): return backend in available() -def import_scf_results(res, gauge_origin): +def import_scf_results(res, gauge_origin = 'origin'): """ Import an scf_result from an SCF program. Tries to be smart and guess what the host program was and how to import it. @@ -92,12 +92,12 @@ def import_scf_results(res, gauge_origin): if isinstance(res, (dict, h5py.File)): from adcc.DataHfProvider import DataHfProvider - return DataHfProvider(res) + return DataHfProvider(res, gauge_origin) if isinstance(res, str): if os.path.isfile(res) and (res.endswith(".h5") or res.endswith(".hdf5")): - return import_scf_results(h5py.File(res, "r")) + return import_scf_results(h5py.File(res, "r"), gauge_origin) else: raise ValueError("Unrecognised path or file extension: {}" "".format(res)) diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 62629d19..04952fc1 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -21,61 +21,88 @@ ## ## --------------------------------------------------------------------- import unittest +import itertools import numpy as np from numpy.testing import assert_allclose from adcc.testdata.cache import cache +from .misc import expand_test_templates from .test_state_densities import Runners from pytest import approx +gauge_origins = [ + 'mass_center', + 'charge_center' + ] class TestMagneticTransitionDipoleMoments(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") + systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind + for system in systems_gauge_origin: + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - - res_dms = state.transition_magnetic_dipole_moment - ref = refdata[method][kind] - n_ref = len(state.excitation_vector) - assert_allclose( - res_dms, ref["transition_magnetic_dipole_moments"][:n_ref], atol=1e-4 - ) + res_dms = state.transition_magnetic_dipole_moment + ref = refdata[method][kind] + n_ref = len(state.excitation_vector) + assert_allclose( + res_dms, ref["transition_magnetic_dipole_moments"][:n_ref], atol=1e-4 + ) class TestTransitionDipoleMomentsVelocity(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") + systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] + kind = "any" if kind == "state" else kind + for system in systems_gauge_origin: + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] + + res_dms = state.transition_dipole_moment_velocity + ref = refdata[method][kind] + n_ref = len(state.excitation_vector) + assert_allclose( + res_dms, ref["transition_dipole_moments_velocity"][:n_ref], atol=1e-4 + ) + + +class TestTransitionQuadrupoleMoments(unittest.TestCase, Runners): + def base_test(self, system, method, kind): + method = method.replace("_", "-") + systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind + for system in systems_gauge_origin: + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] + res_dms = state.transition_quadrupole_moment + ref = refdata[method][kind] + n_ref = len(state.excitation_vector) + assert_allclose( + res_dms, ref["transition_quadrupole_moments"][:n_ref], atol=1e-4 + ) - res_dms = state.transition_dipole_moment_velocity - ref = refdata[method][kind] - n_ref = len(state.excitation_vector) - assert_allclose( - res_dms, ref["transition_dipole_moments_velocity"][:n_ref], atol=1e-4 - ) class TestRotatoryStrengths(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") + systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind + for system in systems_gauge_origin: + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - - res_rots = state.rotatory_strength - ref_tmdm = refdata[method][kind]["transition_magnetic_dipole_moments"] - ref_tdmvel = refdata[method][kind]["transition_dipole_moments_velocity"] - refevals = refdata[method][kind]["eigenvalues"] - n_ref = len(state.excitation_vector) - for i in range(n_ref): - assert state.excitation_energy[i] == refevals[i] - ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) - assert res_rots[i] == approx(ref_dot / refevals[i]) + res_rots = state.rotatory_strength + ref_tmdm = refdata[method][kind]["transition_magnetic_dipole_moments"] + ref_tdmvel = refdata[method][kind]["transition_dipole_moments_velocity"] + refevals = refdata[method][kind]["eigenvalues"] + n_ref = len(state.excitation_vector) + for i in range(n_ref): + assert state.excitation_energy[i] == refevals[i] + ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) + assert res_rots[i] == approx(ref_dot / refevals[i]) diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index 25679df3..e31cda5c 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -89,6 +89,11 @@ def fullfile(fn): class TestdataCache(): cases = ["h2o_sto3g", "cn_sto3g", "hf3_631g", "h2s_sto3g", "ch2nh2_sto3g", "methox_sto3g"] + gauge_origins = [ + #"origin", + "mass_center", + "charge_center" + ] mode_full = False @staticmethod @@ -104,6 +109,16 @@ def testcases(self): """ return [k for k in TestdataCache.cases if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] + + + @property + def testcases_gauge_origin(self): + """ + The definition of the test cases: Data generator and reference file + """ + + return [k+"_"+g for k in TestdataCache.cases for g in TestdataCache.gauge_origins] + #if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] @cached_property def hfdata(self): @@ -111,8 +126,9 @@ def hfdata(self): The HF data a testcase is based upon """ ret = {} - for k in self.testcases: - datafile = fullfile(k + "_hfdata.hdf5") + for k in self.testcases + self.testcases_gauge_origin: + hf_k = k.replace('_mass_center', "").replace("_charge_center", "") + datafile = fullfile(hf_k + "_hfdata.hdf5") # TODO This could be made a plain HDF5.File ret[k] = hdf5io.load(datafile) return ret @@ -122,21 +138,43 @@ def refstate(self): def cache_eri(refstate): refstate.import_all() return refstate - return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) - for k in self.testcases} + ret = {} + for k in self.testcases + self.testcases_gauge_origin: + if k in self.testcases: + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='origin')) + else: + if k.endswith("mass_center"): + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='mass_center')) + else: + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='charge_center')) + return ret + + # return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) + # for k in self.testcases + self.testcases_gauge_origin } @cached_property def refstate_cvs(self): ret = {} - for case in self.testcases: + for case in self.testcases + self.testcases_gauge_origin: # TODO once hfdata is an HDF5 file # refcases = ast.literal_eval( # self.hfdata[case]["reference_cases"][()]) refcases = ast.literal_eval(self.hfdata[case]["reference_cases"]) if "cvs" not in refcases: continue - ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"]) + if case in self.testcases: + ret[case] = adcc.ReferenceState(self.hfdata[case], + **refcases["cvs"], + gauge_origin = "origin") + else: + if case.endswith("mass_center"): + ret[case] = adcc.ReferenceState(self.hfdata[case], + **refcases["cvs"], + gauge_origin = "mass_center") + else: + ret[case] = adcc.ReferenceState(self.hfdata[case], + **refcases["cvs"], + gauge_origin = "charge_center") ret[case].import_all() return ret @@ -144,7 +182,17 @@ def refstate_nocache(self, case, spec): # TODO once hfdata is an HDF5 file # refcases = ast.literal_eval(self.hfdata[case]["reference_cases"][()]) refcases = ast.literal_eval(self.hfdata[case]["reference_cases"]) - return adcc.ReferenceState(self.hfdata[case], **refcases[spec]) + if case in self.testcases: + return adcc.ReferenceState(self.hfdata[case], **refcases[spec], gauge_origin = "origin") + else: + if case.endswith("mass_center"): + return adcc.ReferenceState(self.hfdata[case], + **refcases[spec], + gauge_origin = "mass_center") + else: + return adcc.ReferenceState(self.hfdata[case], + **refcases[spec], + gauge_origin = "charge_center") @cached_property def hfimport(self): @@ -161,16 +209,11 @@ def read_reference_data(self, refname, read_gauge = False): raws = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = raws + ["_".join([p, r]) for p in prefixes for r in raws if p != ""] - gauge_origin = ["origin", "mass_center", "charge_center"] ret = {} - for k in self.testcases: + for k in self.testcases + self.testcases_gauge_origin: fulldict = {} for m in methods: - if read_gauge: - for g in gauge_origin: - datafile = fullfile(k + "_" + refname + "_" + m + g + ".hdf5") - else: - datafile = fullfile(k + "_" + refname + "_" + m + ".hdf5") + datafile = fullfile(k + "_" + refname + "_" + m + ".hdf5") if datafile is None or not os.path.isfile(datafile): continue fulldict.update(hdf5io.load(datafile)) @@ -183,8 +226,8 @@ def reference_data(self): return self.read_reference_data("reference") @cached_property - def adcc_reference_data(self, read_gauge = False): - return self.read_reference_data("adcc_reference", read_gauge) + def adcc_reference_data(self): + return self.read_reference_data("adcc_reference") def construct_adc_states(self, refdata): """ @@ -192,7 +235,7 @@ def construct_adc_states(self, refdata): for all test cases, all methods and all kinds (singlet, triplet) """ res = {} - for case in self.testcases: + for case in self.testcases + self.testcases_gauge_origin: if case not in refdata: continue available_kinds = refdata[case]["available_kinds"] @@ -249,7 +292,6 @@ def adc_states(self): def adcc_states(self): return self.construct_adc_states(self.adcc_reference_data) - # Setup cache object cache = TestdataCache() diff --git a/adcc/testdata/dump_pyscf.py b/adcc/testdata/dump_pyscf.py index 2e569e9c..3759fa07 100644 --- a/adcc/testdata/dump_pyscf.py +++ b/adcc/testdata/dump_pyscf.py @@ -212,6 +212,7 @@ def dump_pyscf(scfres, out): charges = scfres.mol.atom_charges() coords = scfres.mol.atom_coords() mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + print(mass_center) charge_center = np.einsum ('i,ij->j', charges, coords)/charges.sum() # Compute electric and nuclear multipole moments diff --git a/adcc/testdata/dump_reference_adcc.py b/adcc/testdata/dump_reference_adcc.py index bba189fa..4620c7e0 100644 --- a/adcc/testdata/dump_reference_adcc.py +++ b/adcc/testdata/dump_reference_adcc.py @@ -190,6 +190,8 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", state.transition_dipole_moment_velocity adc[kind + "/transition_magnetic_dipole_moments"] = \ state.transition_magnetic_dipole_moment + adc[kind + "/transition_quadrupole_moments"] = \ + state.transition_quadrupole_moment adc[kind + "/eigenvalues"] = state.excitation_energy adc[kind + "/eigenvectors_singles"] = np.asarray( eigenvectors_singles) diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index ba32a6da..3c6045d2 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -39,8 +39,9 @@ def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): assert spec in ["gen", "cvs"] for method in ["adc0", "adc1", "adc2", "adc2x", "adc3"]: - kw = kwargs_overwrite.get(method, kwargs) - dump_method(case, method, kw, spec, generator=generator) + for gauge_origin in ["origin", "mass_center", "charge_center"]: + kw = kwargs_overwrite.get(method, kwargs) + dump_method(case, method, kw, spec, generator=generator, dump_gauge_origin = gauge_origin) def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin = 'origin'): @@ -75,9 +76,10 @@ def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin if generator == "atd": dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) - else: + elif generator == "adcc" and dump_gauge_origin == 'origin': dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) - print("hola") + else: + dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, prefix, method) # if not os.path.isfile(dumpfile): if os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, @@ -104,10 +106,11 @@ def dump_h2o_sto3g(): # H2O restricted case = "h2o_sto3g" # Just ADC(2) and ADC(2)-x kwargs = {"n_singlets": 3, "n_triplets": 3} - dump_method(case, "adc2", kwargs, spec="fc") - dump_method(case, "adc2", kwargs, spec="fc-fv") - dump_method(case, "adc2x", kwargs, spec="fv") - dump_method(case, "adc2x", kwargs, spec="fv-cvs") + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2", kwargs, spec="fc", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc-fv", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fv", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fv-cvs", dump_gauge_origin = gauge_origin) def dump_h2o_def2tzvp(): # H2O restricted @@ -173,22 +176,23 @@ def dump_methox_sto3g(): # (R)-2-methyloxirane def main(): dump_h2o_sto3g() - dump_h2o_def2tzvp() - dump_cn_sto3g() - dump_cn_ccpvdz() - dump_hf3_631g() - dump_h2s_sto3g() - dump_h2s_6311g() - dump_methox_sto3g() + #dump_h2o_def2tzvp() + #dump_cn_sto3g() + #dump_cn_ccpvdz() + #dump_hf3_631g() + #dump_h2s_sto3g() + #dump_h2s_6311g() + #dump_methox_sto3g() if __name__ == "__main__": - #main() - - dump_method( - "h2o_sto3g", - "adc1", - kwargs = {"n_singlets": 10, "n_triplets": 10}, - spec="gen", - generator = "adcc" - ) + main() + + #dump_method( + # "h2o_sto3g", + # "adc1", + # kwargs = {"n_singlets": 10, "n_triplets": 10}, + # spec="gen", + # generator = "adcc", + # dump_gauge_origin = 'charge_center' + #) diff --git a/adcc/workflow.py b/adcc/workflow.py index aef9b229..c80fe07a 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -45,7 +45,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, n_guesses_doubles=None, output=sys.stdout, core_orbitals=None, frozen_core=None, frozen_virtual=None, method=None, n_singlets=None, n_triplets=None, n_spin_flip=None, - environment=None, gauge_origin ="mass_center", **solverargs): + environment=None, gauge_origin ="origin", **solverargs): """Run an ADC calculation. Main entry point to run an ADC calculation. The reference to build the ADC @@ -132,9 +132,10 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, e.g. PE, is treated. For details see :ref:`environment`. gauge_origin: list or str, optional - Define the gauge origin, either by specifying a list directly ([x,y,z]) - or by using one of the keywords ('mass_center', 'charge_center', 'origin'). - Default: 'mass_center' + Define the gauge origin for operator integrals. + Either by specifying a list directly ([x,y,z]) + or by using one of the keywords (mass_center, charge_center, origin). + Default: origin Other parameters ---------------- @@ -222,7 +223,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, # Individual steps # def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, - frozen_virtual=None, method=None, gauge_origin='mass_center'): + frozen_virtual=None, method=None, gauge_origin='origin'): """ Use the provided data or AdcMatrix object to check consistency of the other passed parameters and construct the AdcMatrix object representing From e432d0af4b94adb4675ae56b7b817004423b6013 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 4 May 2023 15:56:05 +0200 Subject: [PATCH 09/42] Code Cleanup --- adcc/DataHfProvider.py | 17 +++---- adcc/ElectronicTransition.py | 1 - adcc/OperatorIntegrals.py | 25 +++++----- adcc/ReferenceState.py | 6 +-- adcc/adc_pp/modified_transition_moments.py | 3 +- adcc/backends/__init__.py | 2 +- adcc/backends/molsturm.py | 6 +-- adcc/backends/psi4.py | 4 +- adcc/backends/pyscf.py | 48 ++++++++++--------- adcc/backends/veloxchem.py | 4 +- adcc/test_properties.py | 10 ++-- adcc/test_properties_consistency.py | 21 ++++---- adcc/testdata/cache.py | 45 ++++++++--------- adcc/testdata/dump_pyscf.py | 13 +++-- adcc/testdata/generate_hfdata_h2o_sto3g.py | 4 +- adcc/testdata/generate_reference.py | 47 +++++++++--------- adcc/workflow.py | 4 +- libadcc/pyiface/export_HartreeFockProvider.cc | 10 ++-- 18 files changed, 137 insertions(+), 133 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 2c6ab592..23d92153 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -69,7 +69,7 @@ def __init__(self, backend="data"): class DataHfProvider(HartreeFockProvider): - def __init__(self, data, gauge_origin = 'origin'): + def __init__(self, data, gauge_origin='origin'): """ Initialise the DataHfProvider class with the `data` being a supported data container (currently python dictionary or HDF5 file). @@ -201,7 +201,6 @@ def __init__(self, data, gauge_origin = 'origin'): # Setup integral data opprov = DataOperatorIntegralProvider(self.__backend) - #print(gauge_origin) mmp = data.get("multipoles", {}) if "elec_1" in mmp: if mmp["elec_1"].shape != (3, nb, nb): @@ -234,20 +233,22 @@ def __init__(self, data, gauge_origin = 'origin'): else: if f"elec_2_{gauge_origin}" in mmp: if mmp["elec_2"].shape != (9, nb, nb): - raise ValueError(f"multipoles/elec_2_{gauge_origin} is expected to have " - "shape " + str((9, nb, nb)) + " not " + raise ValueError(f"multipoles/elec_2_{gauge_origin}" + " is expected to have shape " + + str((9, nb, nb)) + " not " + str(mmp[f"elec_2_{gauge_origin}"].shape)) opprov.electric_quadrupole = np.asarray(mmp["elec_2"]) if f"mag_1_{gauge_origin}" in magm: if magm[f"mag_1_{gauge_origin}"].shape != (3, nb, nb): - raise ValueError(f"magnetic_moments/mag_1_{gauge_origin} is expected to have " - "shape " + str((3, nb, nb)) + " not " + raise ValueError(f"magnetic_moments/mag_1_{gauge_origin}" + " is expected to have shape " + + str((3, nb, nb)) + " not " + str(magm[f"mag_1_{gauge_origin}"].shape)) opprov.magnetic_dipole = np.asarray(magm[f"mag_1_{gauge_origin}"]) if f"nabla_{gauge_origin}" in derivs: if derivs[f"nabla_{gauge_origin}"].shape != (3, nb, nb): - raise ValueError(f"derivatives/nabla_{gauge_origin} is expected to " - "have shape " + raise ValueError(f"derivatives/nabla_{gauge_origin}" + " is expected to have shape " + str((3, nb, nb)) + " not " + str(derivs[f"nabla_{gauge_origin}"].shape)) opprov.nabla = np.asarray(derivs[f"nabla_{gauge_origin}"]) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index b9834680..c9878614 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -209,7 +209,6 @@ def transition_quadrupole_moment(self): for tdm in self.transition_dm ]) - @cached_property @mark_excitation_property() def oscillator_strength(self): diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index ecd6a8cf..4c81ef95 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -78,6 +78,7 @@ def replicate_ao_block(mospaces, tensor, is_symmetric=True): ]), 1e-14) return result + class OperatorIntegrals: def __init__(self, provider, mospaces, coefficients, conv_tol): self.__provider_ao = provider @@ -155,18 +156,18 @@ def import_quadrupole_like_operator(self, integral, is_symmetric=True): raise NotImplementedError(f"{integral.replace('_', ' ')} operator " "not implemented " f"in {self.provider_ao.backend} backend.") - quad = [] + quad = [] quadrupoles = [] - for i, component in enumerate(["xx", "xy", "xz", - "yx", "yy", "yz", + for i, component in enumerate(["xx", "xy", "xz", + "yx", "yy", "yz", "zx", "zy", "zz"]): quad_backend = getattr(self.provider_ao, integral)[i] quad_bb = replicate_ao_block(self.mospaces, quad_backend, is_symmetric=is_symmetric) - quad_ff = OneParticleOperator(self.mospaces, + quad_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric) - transform_operator_ao2mo(quad_bb, quad_ff, - self.__coefficients, + transform_operator_ao2mo(quad_bb, quad_ff, + self.__coefficients, self.__conv_tol) quad.append(quad_ff) quadrupoles.append(quad[:3]) @@ -178,31 +179,31 @@ def import_quadrupole_like_operator(self, integral, is_symmetric=True): @timed_member_call("_import_timer") def electric_quadrupole_traceless(self): """ - Return the traceless electric quadrupole integrals + Return the traceless electric quadrupole integrals in the molecular orbital basis. """ return self.import_quadrupole_like_operator("electric_quadrupole_traceless", - is_symmetric=True) + is_symmetric=True) @property @timed_member_call("_import_timer") def electric_quadrupole(self): """ - Return the traced electric quadrupole integrals + Return the traced electric quadrupole integrals in the molecular orbital basis. """ return self.import_quadrupole_like_operator("electric_quadrupole", - is_symmetric=True) + is_symmetric=True) @property @timed_member_call("_import_timer") def diag_mag(self): """ - Return the diamagnetic magnetizability integrals + Return the diamagnetic magnetizability integrals in the molecular orbital basis. """ return self.import_quadrupole_like_operator("diag_mag", - is_symmetric=True) + is_symmetric=True) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 9c21093d..76453ad5 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -103,7 +103,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, gauge_origin : str or list, optional Select the gauge origin for operator integrals. - Either by specifying a list [x,y,z] or by choosing one of the + Either by specifying a list [x,y,z] or by choosing one of the keywords (mass_center, charge_center, origin) default: origin @@ -160,7 +160,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, hfdata.operator_integral_provider, self._mospaces, self.orbital_coefficients, self.conv_tol ) - + self._gauge_origin = gauge_origin self.environment = None # no environment attached by default @@ -236,7 +236,7 @@ def dipole_moment(self): @cached_property def gauge_origin(self): """ - Return the selected gauge origin used for operators integrals. + Return the selected gauge origin used for operators integrals. Until now only available for the PySCF backend """ return self._gauge_origin diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index fceeee38..3c2facf4 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -29,6 +29,7 @@ from adcc.Intermediates import Intermediates from adcc.AmplitudeVector import AmplitudeVector + def mtm_adc0(mp, op, intermediates): f1 = op.ov if op.is_symmetric else op.vo.transpose() return AmplitudeVector(ph=f1) @@ -84,7 +85,7 @@ def mtm_cvs_adc2(mp, op, intermediates): "adc0": mtm_adc0, "adc1": mtm_adc1, "adc2": mtm_adc2, - "adc2x": mtm_adc2, # Identical to ADC(2) + "adc2x": mtm_adc2, # Identical to ADC(2) "cvs-adc0": mtm_cvs_adc0, "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0) "cvs-adc2": mtm_cvs_adc2, diff --git a/adcc/backends/__init__.py b/adcc/backends/__init__.py index 289d49a3..938894b2 100644 --- a/adcc/backends/__init__.py +++ b/adcc/backends/__init__.py @@ -52,7 +52,7 @@ def have_backend(backend): return backend in available() -def import_scf_results(res, gauge_origin = 'origin'): +def import_scf_results(res, gauge_origin='origin'): """ Import an scf_result from an SCF program. Tries to be smart and guess what the host program was and how to import it. diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index d5dd6799..04e12e5b 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -29,9 +29,9 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn( "Gauge origin selection only available in PySCF" - f"not in Molsturm." - "The gauge origin is selected as [0, 0, 0]") + warnings.warn("Gauge origin selection only available in PySCF" + "not in Molsturm." + "The gauge origin is selected as [0, 0, 0]") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 41ca6a3e..43203336 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -39,9 +39,9 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) - warnings.warn( "Gauge origin selection only available in PySCF" + warnings.warn("Gauge origin selection only available in PySCF" f"not in {self.backend}." - "The gauge origin is selected as [0, 0, 0]") + "The gauge origin is selected as [0, 0, 0]") @cached_property def electric_dipole(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 7fd4c911..cb392bb6 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -42,31 +42,34 @@ def __init__(self, scfres, gauge_origin): mass = scfres.mol.atom_mass_list() charges = scfres.mol.atom_charges() if gauge_origin == "mass_center": - self.gauge_origin = np.einsum("i,ij->j", mass, coords)/mass.sum() + self.gauge_origin = np.einsum("i,ij->j", mass, coords) / mass.sum() elif gauge_origin == "charge_center": - self.gauge_origin = np.einsum ("i,ij->j", charges, coords)/charges.sum() - elif gauge_origin =="origin": + self.gauge_origin = np.einsum("i,ij->j", charges, coords) / \ + charges.sum() + elif gauge_origin == "origin": self.gauge_origin = [0.0, 0.0, 0.0] elif type(gauge_origin) == list: self.gauge_origin = gauge_origin else: - raise NotImplementedError("Gauge origin has to be defined either by" - "using one of the keywords" + raise NotImplementedError("Gauge origin has to be defined either by" + "using one of the keywords" "mass_center, charge_center or origin." "Or by declaring a list eg. [x, y, z]") if isinstance(gauge_origin, str): - print(f"gauge origin is selected as: {gauge_origin}", self.gauge_origin) + print(f"Gauge origin for operator integrals is selected as: \ +{gauge_origin}:", self.gauge_origin) else: - print(f"gauge origin is selected as: {gauge_origin}") + print(f"Gauge origin for operator integrals is selected as: \ + {gauge_origin}") warnings.warn("Gauge origin selection is not tested") - + @cached_property def electric_dipole(self): return list(self.scfres.mol.intor_symmetric('int1e_r', comp=3)) @cached_property def magnetic_dipole(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): #gauge origin! + with self.scfres.mol.with_common_orig(self.gauge_origin): return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) @@ -80,7 +83,7 @@ def nabla(self): @cached_property def diag_mag(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): + with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) @@ -93,10 +96,10 @@ def diag_mag(self): @cached_property def electric_quadrupole_traceless(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp =9) + with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp = 1) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) for i in range(3): r_quadr_matrix[i][i] = r_quadr @@ -106,7 +109,7 @@ def electric_quadrupole_traceless(self): @cached_property def electric_quadrupole(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): + with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) return list(r_r) @@ -268,7 +271,7 @@ def get_n_atoms(self): n_atoms = self.scfres.mol.natm return n_atoms - def get_nuclearcharges(self): + def get_nuclear_charges(self): charges = self.scfres.mol.atom_charges() return charges @@ -311,10 +314,11 @@ def get_nuclear_multipole(self, order): coords = self.scfres.mol.atom_coords() mass = self.scfres.mol.atom_mass_list() if self.gauge_origin == 'mass_center': - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + mass_center = np.einsum('i,ij->j', mass, coords) / mass.sum() coords = coords - mass_center elif self.gauge_origin == 'charge_center': - charge_center = np.einsum ('i,ij->j', charges, coords)/charges.sum() + charge_center = np.einsum('i,ij->j', charges, coords) / \ + charges.sum() coords = coords - charge_center elif self.gauge_origin == 'origin': coords = coords @@ -322,11 +326,11 @@ def get_nuclear_multipole(self, order): coords = coords - self.gauge_origin else: raise NotImplementedError() - r_r = np.einsum('ij,ik->ijk' ,coords, coords) - res = np.einsum('i,ijk->jk', charges, r_r) - res = np.array([res[0][0], res[0][1], res[0][2], - res[1][1], res[1][2], res[2][2]]) - return res + r_r = np.einsum('ij,ik->ijk', coords, coords) + res = np.einsum('i,ijk->jk', charges, r_r) + res = np.array([res[0][0], res[0][1], res[0][2], + res[1][1], res[1][2], res[2][2]]) + return res else: raise NotImplementedError("get_nuclear_multipole with order > 2") diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index ad9b3177..1bcc0b0b 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -44,9 +44,9 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" - warnings.warn( "Gauge origin selection only available in PySCF" + warnings.warn("Gauge origin selection only available in PySCF" f"not in {self.backend}." - "The gauge origin is selected as [0, 0, 0]") + "The gauge origin is selected as [0, 0, 0]") @cached_property def electric_dipole(self): diff --git a/adcc/test_properties.py b/adcc/test_properties.py index f43fc2d8..c7562ce5 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -128,6 +128,7 @@ def base_test(self, system, method, kind): methods = [m for bm in basemethods for m in [bm, "cvs_" + bm]] gauge_origins = ["origin", "mass_center", "charge_center"] + @expand_test_templates(list(itertools.product(methods, gauge_origins))) class TestMagneticTransitionDipoleMoments(unittest.TestCase): def template_linear_molecule(self, method, gauge_origin): @@ -139,14 +140,15 @@ def template_linear_molecule(self, method, gauge_origin): """ basis = "sto-3g" scfres = run_hf(backend, xyz, basis) - + if "cvs" in method: - state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2, gauge_origin=gauge_origin) + state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2, + gauge_origin=gauge_origin) else: - state = run_adc(scfres, method=method, n_singlets=10, gauge_origin=gauge_origin) + state = run_adc(scfres, method=method, n_singlets=10, + gauge_origin=gauge_origin) tdms = state.transition_magnetic_dipole_moment # For linear molecules lying on the z-axis, the z-component must be zero for tdm in tdms: assert tdm[2] < 1e-10 - diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 04952fc1..0d69e448 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -21,27 +21,23 @@ ## ## --------------------------------------------------------------------- import unittest -import itertools import numpy as np from numpy.testing import assert_allclose from adcc.testdata.cache import cache -from .misc import expand_test_templates from .test_state_densities import Runners from pytest import approx -gauge_origins = [ - 'mass_center', - 'charge_center' - ] +gauge_origins = ['mass_center', 'charge_center'] + class TestMagneticTransitionDipoleMoments(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: + for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] @@ -49,7 +45,8 @@ def base_test(self, system, method, kind): ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( - res_dms, ref["transition_magnetic_dipole_moments"][:n_ref], atol=1e-4 + res_dms, ref["transition_magnetic_dipole_moments"][:n_ref], + atol=1e-4 ) @@ -58,7 +55,7 @@ def base_test(self, system, method, kind): method = method.replace("_", "-") systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: + for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] @@ -66,7 +63,8 @@ def base_test(self, system, method, kind): ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( - res_dms, ref["transition_dipole_moments_velocity"][:n_ref], atol=1e-4 + res_dms, ref["transition_dipole_moments_velocity"][:n_ref], + atol=1e-4 ) @@ -87,13 +85,12 @@ def base_test(self, system, method, kind): ) - class TestRotatoryStrengths(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: + for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index e31cda5c..670458f7 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -89,11 +89,7 @@ def fullfile(fn): class TestdataCache(): cases = ["h2o_sto3g", "cn_sto3g", "hf3_631g", "h2s_sto3g", "ch2nh2_sto3g", "methox_sto3g"] - gauge_origins = [ - #"origin", - "mass_center", - "charge_center" - ] + gauge_origins = ["mass_center", "charge_center"] mode_full = False @staticmethod @@ -109,7 +105,6 @@ def testcases(self): """ return [k for k in TestdataCache.cases if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] - @property def testcases_gauge_origin(self): @@ -117,8 +112,9 @@ def testcases_gauge_origin(self): The definition of the test cases: Data generator and reference file """ - return [k+"_"+g for k in TestdataCache.cases for g in TestdataCache.gauge_origins] - #if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] + return [k + "_" + g for k in TestdataCache.cases + for g in TestdataCache.gauge_origins + if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] @cached_property def hfdata(self): @@ -141,12 +137,15 @@ def cache_eri(refstate): ret = {} for k in self.testcases + self.testcases_gauge_origin: if k in self.testcases: - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='origin')) + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], + gauge_origin='origin')) else: if k.endswith("mass_center"): - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='mass_center')) + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], + gauge_origin='mass_center')) else: - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], gauge_origin='charge_center')) + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], + gauge_origin='charge_center')) return ret # return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) @@ -165,16 +164,16 @@ def refstate_cvs(self): if case in self.testcases: ret[case] = adcc.ReferenceState(self.hfdata[case], **refcases["cvs"], - gauge_origin = "origin") + gauge_origin="origin") else: if case.endswith("mass_center"): ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"], - gauge_origin = "mass_center") + **refcases["cvs"], + gauge_origin="mass_center") else: ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"], - gauge_origin = "charge_center") + **refcases["cvs"], + gauge_origin="charge_center") ret[case].import_all() return ret @@ -183,16 +182,17 @@ def refstate_nocache(self, case, spec): # refcases = ast.literal_eval(self.hfdata[case]["reference_cases"][()]) refcases = ast.literal_eval(self.hfdata[case]["reference_cases"]) if case in self.testcases: - return adcc.ReferenceState(self.hfdata[case], **refcases[spec], gauge_origin = "origin") + return adcc.ReferenceState(self.hfdata[case], **refcases[spec], + gauge_origin="origin") else: if case.endswith("mass_center"): return adcc.ReferenceState(self.hfdata[case], - **refcases[spec], - gauge_origin = "mass_center") + **refcases[spec], + gauge_origin="mass_center") else: return adcc.ReferenceState(self.hfdata[case], - **refcases[spec], - gauge_origin = "charge_center") + **refcases[spec], + gauge_origin="charge_center") @cached_property def hfimport(self): @@ -203,7 +203,7 @@ def hfimport(self): ret[k] = hdf5io.load(datafile) return ret - def read_reference_data(self, refname, read_gauge = False): + def read_reference_data(self, refname, read_gauge=False): prefixes = ["", "cvs", "fc", "fv", "fc_cvs", "fv_cvs", "fc_fv", "fc_fv_cvs"] raws = ["adc0", "adc1", "adc2", "adc2x", "adc3"] @@ -292,6 +292,7 @@ def adc_states(self): def adcc_states(self): return self.construct_adc_states(self.adcc_reference_data) + # Setup cache object cache = TestdataCache() diff --git a/adcc/testdata/dump_pyscf.py b/adcc/testdata/dump_pyscf.py index 3759fa07..967a0ced 100644 --- a/adcc/testdata/dump_pyscf.py +++ b/adcc/testdata/dump_pyscf.py @@ -211,9 +211,9 @@ def dump_pyscf(scfres, out): mass = scfres.mol.atom_mass_list() charges = scfres.mol.atom_charges() coords = scfres.mol.atom_coords() - mass_center = np.einsum('i,ij->j', mass, coords)/mass.sum() + mass_center = np.einsum('i,ij->j', mass, coords) / mass.sum() print(mass_center) - charge_center = np.einsum ('i,ij->j', charges, coords)/charges.sum() + charge_center = np.einsum('i,ij->j', charges, coords) / charges.sum() # Compute electric and nuclear multipole moments mmp = data.create_group("multipoles") @@ -224,14 +224,14 @@ def dump_pyscf(scfres, out): data=scfres.mol.intor_symmetric("int1e_r", comp=3)) with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): mmp.create_dataset("elec_2", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) with scfres.mol.with_common_orig(mass_center): mmp.create_dataset("elec_2_mass_center", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) with scfres.mol.with_common_orig(charge_center): mmp.create_dataset("elec_2_charge_center", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) - + data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) + magm = data.create_group("magnetic_moments") derivs = data.create_group("derivatives") with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): @@ -262,6 +262,5 @@ def dump_pyscf(scfres, out): data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) - data.attrs["backend"] = "pyscf" return data diff --git a/adcc/testdata/generate_hfdata_h2o_sto3g.py b/adcc/testdata/generate_hfdata_h2o_sto3g.py index c0d1fd8c..38876713 100755 --- a/adcc/testdata/generate_hfdata_h2o_sto3g.py +++ b/adcc/testdata/generate_hfdata_h2o_sto3g.py @@ -27,9 +27,9 @@ from os.path import dirname, join from adcc.testdata.dump_pyscf import dump_pyscf -#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -#import adcctestdata as atd # noqa: E402 +# import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 3c6045d2..4358125e 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -41,12 +41,13 @@ def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): for method in ["adc0", "adc1", "adc2", "adc2x", "adc3"]: for gauge_origin in ["origin", "mass_center", "charge_center"]: kw = kwargs_overwrite.get(method, kwargs) - dump_method(case, method, kw, spec, generator=generator, dump_gauge_origin = gauge_origin) + dump_method(case, method, kw, spec, generator=generator, + dump_gauge_origin=gauge_origin) -def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin = 'origin'): +def dump_method(case, method, kwargs, spec, generator="adcc", + dump_gauge_origin="origin"): h5file = case + "_hfdata.hdf5" - #h5file = case + "_hfdata_old.hdf5" if not os.path.isfile(h5file): raise ValueError("HfData not found: " + h5file) @@ -55,7 +56,8 @@ def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin hfdata = atd.HdfProvider(h5file) else: dumpfunction = dump_reference_adcc - hfdata = adcc.DataHfProvider(h5py.File(h5file, "r"), gauge_origin = dump_gauge_origin) + hfdata = adcc.DataHfProvider(h5py.File(h5file, "r"), + gauge_origin=dump_gauge_origin) # Get dictionary of parameters for the reference cases. refcases = ast.literal_eval(hfdata.data["reference_cases"][()].decode()) kwargs = dict(kwargs) @@ -79,7 +81,8 @@ def dump_method(case, method, kwargs, spec, generator="adcc", dump_gauge_origin elif generator == "adcc" and dump_gauge_origin == 'origin': dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) else: - dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, prefix, method) + dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, + prefix, method) # if not os.path.isfile(dumpfile): if os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, @@ -107,10 +110,13 @@ def dump_h2o_sto3g(): # H2O restricted case = "h2o_sto3g" # Just ADC(2) and ADC(2)-x kwargs = {"n_singlets": 3, "n_triplets": 3} for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec="fc", dump_gauge_origin = gauge_origin) - dump_method(case, "adc2", kwargs, spec="fc-fv", dump_gauge_origin = gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fv", dump_gauge_origin = gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fv-cvs", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc", dump_gauge_origin=gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc-fv", + dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fv", + dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fv-cvs", + dump_gauge_origin=gauge_origin) def dump_h2o_def2tzvp(): # H2O restricted @@ -176,23 +182,14 @@ def dump_methox_sto3g(): # (R)-2-methyloxirane def main(): dump_h2o_sto3g() - #dump_h2o_def2tzvp() - #dump_cn_sto3g() - #dump_cn_ccpvdz() - #dump_hf3_631g() - #dump_h2s_sto3g() - #dump_h2s_6311g() - #dump_methox_sto3g() + # dump_h2o_def2tzvp() + # dump_cn_sto3g() + # dump_cn_ccpvdz() + # dump_hf3_631g() + # dump_h2s_sto3g() + # dump_h2s_6311g() + # dump_methox_sto3g() if __name__ == "__main__": main() - - #dump_method( - # "h2o_sto3g", - # "adc1", - # kwargs = {"n_singlets": 10, "n_triplets": 10}, - # spec="gen", - # generator = "adcc", - # dump_gauge_origin = 'charge_center' - #) diff --git a/adcc/workflow.py b/adcc/workflow.py index c80fe07a..2db2a155 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -40,12 +40,14 @@ IndexSymmetrisation) __all__ = ["run_adc"] + + def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, eigensolver=None, guesses=None, n_guesses=None, n_guesses_doubles=None, output=sys.stdout, core_orbitals=None, frozen_core=None, frozen_virtual=None, method=None, n_singlets=None, n_triplets=None, n_spin_flip=None, - environment=None, gauge_origin ="origin", **solverargs): + environment=None, gauge_origin="origin", **solverargs): """Run an ADC calculation. Main entry point to run an ADC calculation. The reference to build the ADC diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index db4f3ec5..76c7cf47 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -54,7 +54,7 @@ class HartreeFockProvider : public HartreeFockSolution_i { std::copy(ret.data(), ret.data()+ size, buffer); } void nuclear_charges(scalar_type* buffer, size_t size) const override{ - py::array_t ret = get_nuclearcharges(); + py::array_t ret = get_nuclear_charges(); if (static_cast(size) != ret.size()) { throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + ") does not agree with buffer size (" + @@ -283,7 +283,7 @@ class HartreeFockProvider : public HartreeFockSolution_i { virtual size_t get_spin_multiplicity() const = 0; virtual real_type get_energy_scf() const = 0; virtual std::string get_backend() const = 0; - virtual py::array_t get_nuclearcharges() const = 0; + virtual py::array_t get_nuclear_charges() const = 0; virtual py::array_t get_coordinates() const = 0; virtual py::array_t get_nuclear_masses() const = 0; @@ -315,9 +315,9 @@ class PyHartreeFockProvider : public HartreeFockProvider { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order); } - py::array_t get_nuclearcharges() const override{ + py::array_t get_nuclear_charges() const override{ PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclearcharges, ); + get_nuclear_charges, ); } py::array_t get_nuclear_masses() const override{ PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, @@ -486,7 +486,7 @@ void export_HartreeFockProvider(py::module& m) { "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " "dipole moment as an array of size 3.") - .def("get_nuclearcharges", &HartreeFockProvider::get_nuclearcharges, + .def("get_nuclear_charges", &HartreeFockProvider::get_nuclear_charges, "Returns nuclear charges of SCF reference, array size 3") .def("get_coordinates", &HartreeFockProvider::get_coordinates, "Returns coordinates of SCF REfernece State in Bohr") From 5b745cbf066040072449125b681628bd3640a616 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 5 May 2023 12:06:38 +0200 Subject: [PATCH 10/42] clean up conjugate gradient, clean up code, generate reference data --- adcc/backends/molsturm.py | 4 +- adcc/backends/psi4.py | 4 +- adcc/backends/veloxchem.py | 4 +- adcc/solver/conjugate_gradient.py | 18 ++-- adcc/test_properties.py | 10 ++- adcc/test_state_densities.py | 84 +++++++++---------- adcc/testdata/dump_reference_adcc.py | 1 - adcc/testdata/generate_hfdata_cn_ccpvdz.py | 7 +- adcc/testdata/generate_hfdata_cn_sto3g.py | 8 +- adcc/testdata/generate_hfdata_h2o_def2tzvp.py | 7 +- adcc/testdata/generate_reference.py | 6 +- 11 files changed, 84 insertions(+), 69 deletions(-) diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index 04e12e5b..9e3f1c4d 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -29,8 +29,8 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn("Gauge origin selection only available in PySCF" - "not in Molsturm." + warnings.warn("Gauge origin selection only available in PySCF " + "not in Molsturm. " "The gauge origin is selected as [0, 0, 0]") n_alpha = scfres["n_alpha"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 43203336..7c99d1d2 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -39,8 +39,8 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) - warnings.warn("Gauge origin selection only available in PySCF" - f"not in {self.backend}." + warnings.warn("Gauge origin selection only available in PySCF " + f"not in {self.backend}. " "The gauge origin is selected as [0, 0, 0]") @cached_property diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 1bcc0b0b..b2183b05 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -44,8 +44,8 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" - warnings.warn("Gauge origin selection only available in PySCF" - f"not in {self.backend}." + warnings.warn("Gauge origin selection only available in PySCF " + f"not in {self.backend}. " "The gauge origin is selected as [0, 0, 0]") @cached_property diff --git a/adcc/solver/conjugate_gradient.py b/adcc/solver/conjugate_gradient.py index cc2dd222..e2b8094e 100644 --- a/adcc/solver/conjugate_gradient.py +++ b/adcc/solver/conjugate_gradient.py @@ -78,6 +78,8 @@ def default_print(state, identifier, file=sys.stdout): elif identifier == "is_converged": print("=== Converged ===", file=file) print(" Number of matrix applies: ", state.n_applies) + elif identifier == "is_zero": + print("=== Is Zero, Converged ===", file=file) def conjugate_gradient(matrix, rhs, x0=None, conv_tol=1e-9, max_iter=100, @@ -161,11 +163,17 @@ def is_converged(state): Apk = matrix @ pk state.n_applies += 1 res_dot_zk = dot(state.residual, zk) - x = dot(pk,Apk) - if dot(pk,Apk) == 0: - x = 1e-5 - #ak = float(res_dot_zk / dot(pk, Apk)) - ak = float(res_dot_zk / x) + + # if rhs is zero (for example, mtms of z components + # magnetic dipole moments of linear molecules) + # no iterations are needed. + if res_dot_zk == 0: + if is_converged(state): + state.converged = True + callback(state, "is_zero") + return state + + ak = float(res_dot_zk / dot(pk, Apk)) state.solution = evaluate(state.solution + ak * pk) residual_old = state.residual diff --git a/adcc/test_properties.py b/adcc/test_properties.py index c7562ce5..be2160e6 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -28,7 +28,7 @@ from adcc.State2States import State2States from adcc.testdata.cache import cache -from adcc.backends import run_hf +from adcc.backends import run_hf, available from adcc import run_adc from .misc import assert_allclose_signfix, expand_test_templates @@ -133,14 +133,18 @@ def base_test(self, system, method, kind): class TestMagneticTransitionDipoleMoments(unittest.TestCase): def template_linear_molecule(self, method, gauge_origin): method = method.replace("_", "-") - backend = "" + backend = available()[0] xyz = """ C 0 0 0 O 0 0 2.7023 """ basis = "sto-3g" + + if backend != 'pyscf' and gauge_origin != 'origin': + skip("Gauge origin selection is only implemented " + "for pyscf backend") scfres = run_hf(backend, xyz, basis) - + if "cvs" in method: state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2, gauge_origin=gauge_origin) diff --git a/adcc/test_state_densities.py b/adcc/test_state_densities.py index ca2bebf6..46a67885 100644 --- a/adcc/test_state_densities.py +++ b/adcc/test_state_densities.py @@ -51,14 +51,14 @@ def template_h2o_sto3g_triplet(self, method): def template_h2o_def2tzvp_triplet(self, method): self.base_test("h2o_def2tzvp", method, "triplet") - def template_cn_sto3g(self, method): - self.base_test("cn_sto3g", method, "state") - - def template_cn_ccpvdz(self, method): - self.base_test("cn_ccpvdz", method, "state") - - def template_hf3_631g_spin_flip(self, method): - self.base_test("hf3_631g", method, "spin_flip") +# def template_cn_sto3g(self, method): +# self.base_test("cn_sto3g", method, "state") +# +# def template_cn_ccpvdz(self, method): +# self.base_test("cn_ccpvdz", method, "state") +# +# def template_hf3_631g_spin_flip(self, method): +# self.base_test("hf3_631g", method, "spin_flip") # # Other runners (to test that FC and FV work as they should) @@ -69,40 +69,40 @@ def test_h2o_sto3g_fc_adc2_singlets(self): def test_h2o_sto3g_fv_adc2x_singlets(self): self.base_test("h2o_sto3g", "fv_adc2x", "singlet") - def test_cn_sto3g_fc_adc2_states(self): - self.base_test("cn_sto3g", "fc_adc2", "state") - - def test_cn_sto3g_fv_adc2x_states(self): - self.base_test("cn_sto3g", "fv_adc2x", "state") - - def test_cn_sto3g_fv_cvs_adc2x_states(self): - self.base_test("cn_sto3g", "fv_cvs_adc2x", "state") - - def test_h2s_sto3g_fc_cvs_adc2_singlets(self): - self.base_test("h2s_sto3g", "fc_cvs_adc2", "singlet") - - def test_h2s_6311g_fc_adc2_singlets(self): - self.base_test("h2s_6311g", "fc_adc2", "singlet") - - def test_h2s_6311g_fv_adc2_singlets(self): - self.base_test("h2s_6311g", "fv_adc2", "singlet") - - def test_h2s_6311g_fc_cvs_adc2x_singlets(self): - self.base_test("h2s_6311g", "fc_cvs_adc2x", "singlet") - - def test_h2s_6311g_fv_cvs_adc2x_singlets(self): - self.base_test("h2s_6311g", "fv_cvs_adc2x", "singlet") - - -# Return combinations not tested so far: -# The rationale is that cvs-spin-flip as a method do not make -# that much sense and probably the routines are anyway covered -# by the other testing we do. -delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc0") -delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc1") -delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2") -delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2x") -delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc3") +# def test_cn_sto3g_fc_adc2_states(self): +# self.base_test("cn_sto3g", "fc_adc2", "state") +# +# def test_cn_sto3g_fv_adc2x_states(self): +# self.base_test("cn_sto3g", "fv_adc2x", "state") +# +# def test_cn_sto3g_fv_cvs_adc2x_states(self): +# self.base_test("cn_sto3g", "fv_cvs_adc2x", "state") +# +# def test_h2s_sto3g_fc_cvs_adc2_singlets(self): +# self.base_test("h2s_sto3g", "fc_cvs_adc2", "singlet") +# +# def test_h2s_6311g_fc_adc2_singlets(self): +# self.base_test("h2s_6311g", "fc_adc2", "singlet") +# +# def test_h2s_6311g_fv_adc2_singlets(self): +# self.base_test("h2s_6311g", "fv_adc2", "singlet") +# +# def test_h2s_6311g_fc_cvs_adc2x_singlets(self): +# self.base_test("h2s_6311g", "fc_cvs_adc2x", "singlet") +# +# def test_h2s_6311g_fv_cvs_adc2x_singlets(self): +# self.base_test("h2s_6311g", "fv_cvs_adc2x", "singlet") +# +# +## Return combinations not tested so far: +## The rationale is that cvs-spin-flip as a method do not make +## that much sense and probably the routines are anyway covered +## by the other testing we do. +#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc0") +#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc1") +#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2") +#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2x") +#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc3") class TestStateDiffDm(unittest.TestCase, Runners): diff --git a/adcc/testdata/dump_reference_adcc.py b/adcc/testdata/dump_reference_adcc.py index 4620c7e0..8803921e 100644 --- a/adcc/testdata/dump_reference_adcc.py +++ b/adcc/testdata/dump_reference_adcc.py @@ -54,7 +54,6 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", if not len(states): raise ValueError("No excited states obtained.") - print("hello") # # MP # diff --git a/adcc/testdata/generate_hfdata_cn_ccpvdz.py b/adcc/testdata/generate_hfdata_cn_ccpvdz.py index b0dd1283..ef4fec5d 100755 --- a/adcc/testdata/generate_hfdata_cn_ccpvdz.py +++ b/adcc/testdata/generate_hfdata_cn_ccpvdz.py @@ -26,10 +26,11 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join +from adcc.testdata.dump_pyscf import dump_pyscf -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +#import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -47,7 +48,7 @@ mf.max_cycle = 600 mf = scf.addons.frac_occ(mf) mf.kernel() -h5f = atd.dump_pyscf(mf, "cn_ccpvdz_hfdata.hdf5") +h5f = dump_pyscf(mf, "cn_ccpvdz_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfdata_cn_sto3g.py b/adcc/testdata/generate_hfdata_cn_sto3g.py index 23d86411..57826f3e 100755 --- a/adcc/testdata/generate_hfdata_cn_sto3g.py +++ b/adcc/testdata/generate_hfdata_cn_sto3g.py @@ -26,10 +26,12 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join +from adcc.testdata.dump_pyscf import dump_pyscf -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) + +# import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -47,7 +49,7 @@ mf.max_cycle = 500 mf = scf.addons.frac_occ(mf) mf.kernel() -h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") +h5f = dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfdata_h2o_def2tzvp.py b/adcc/testdata/generate_hfdata_h2o_def2tzvp.py index f5f47b6b..f99bca71 100755 --- a/adcc/testdata/generate_hfdata_h2o_def2tzvp.py +++ b/adcc/testdata/generate_hfdata_h2o_def2tzvp.py @@ -25,10 +25,11 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join +from adcc.testdata.dump_pyscf import dump_pyscf -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +# import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -43,7 +44,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = atd.dump_pyscf(mf, "h2o_def2tzvp_hfdata.hdf5") +h5f = dump_pyscf(mf, "h2o_def2tzvp_hfdata.hdf5") # Store configuration parameters for interesting cases to generate # reference data for. The data is stored as a stringified dict. diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 4358125e..22a5e21d 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -84,8 +84,8 @@ def dump_method(case, method, kwargs, spec, generator="adcc", dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, prefix, method) # if not os.path.isfile(dumpfile): - if os.path.isfile(dumpfile): - dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, + # if os.path.isfile(dumpfile): + dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -181,7 +181,7 @@ def dump_methox_sto3g(): # (R)-2-methyloxirane def main(): - dump_h2o_sto3g() + # dump_h2o_sto3g() # dump_h2o_def2tzvp() # dump_cn_sto3g() # dump_cn_ccpvdz() From 6a2faf12949ce474122afe49bc4e326a1bc75579 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 5 May 2023 14:59:00 +0200 Subject: [PATCH 11/42] generate testdata --- adcc/test_state_densities.py | 84 +++++++++++----------- adcc/testdata/generate_hfdata_h2s_6311g.py | 7 +- adcc/testdata/generate_hfdata_h2s_sto3g.py | 7 +- adcc/testdata/generate_hfdata_hf3_631g.py | 8 +-- adcc/testdata/generate_reference.py | 30 ++++---- 5 files changed, 72 insertions(+), 64 deletions(-) diff --git a/adcc/test_state_densities.py b/adcc/test_state_densities.py index 46a67885..ca2bebf6 100644 --- a/adcc/test_state_densities.py +++ b/adcc/test_state_densities.py @@ -51,14 +51,14 @@ def template_h2o_sto3g_triplet(self, method): def template_h2o_def2tzvp_triplet(self, method): self.base_test("h2o_def2tzvp", method, "triplet") -# def template_cn_sto3g(self, method): -# self.base_test("cn_sto3g", method, "state") -# -# def template_cn_ccpvdz(self, method): -# self.base_test("cn_ccpvdz", method, "state") -# -# def template_hf3_631g_spin_flip(self, method): -# self.base_test("hf3_631g", method, "spin_flip") + def template_cn_sto3g(self, method): + self.base_test("cn_sto3g", method, "state") + + def template_cn_ccpvdz(self, method): + self.base_test("cn_ccpvdz", method, "state") + + def template_hf3_631g_spin_flip(self, method): + self.base_test("hf3_631g", method, "spin_flip") # # Other runners (to test that FC and FV work as they should) @@ -69,40 +69,40 @@ def test_h2o_sto3g_fc_adc2_singlets(self): def test_h2o_sto3g_fv_adc2x_singlets(self): self.base_test("h2o_sto3g", "fv_adc2x", "singlet") -# def test_cn_sto3g_fc_adc2_states(self): -# self.base_test("cn_sto3g", "fc_adc2", "state") -# -# def test_cn_sto3g_fv_adc2x_states(self): -# self.base_test("cn_sto3g", "fv_adc2x", "state") -# -# def test_cn_sto3g_fv_cvs_adc2x_states(self): -# self.base_test("cn_sto3g", "fv_cvs_adc2x", "state") -# -# def test_h2s_sto3g_fc_cvs_adc2_singlets(self): -# self.base_test("h2s_sto3g", "fc_cvs_adc2", "singlet") -# -# def test_h2s_6311g_fc_adc2_singlets(self): -# self.base_test("h2s_6311g", "fc_adc2", "singlet") -# -# def test_h2s_6311g_fv_adc2_singlets(self): -# self.base_test("h2s_6311g", "fv_adc2", "singlet") -# -# def test_h2s_6311g_fc_cvs_adc2x_singlets(self): -# self.base_test("h2s_6311g", "fc_cvs_adc2x", "singlet") -# -# def test_h2s_6311g_fv_cvs_adc2x_singlets(self): -# self.base_test("h2s_6311g", "fv_cvs_adc2x", "singlet") -# -# -## Return combinations not tested so far: -## The rationale is that cvs-spin-flip as a method do not make -## that much sense and probably the routines are anyway covered -## by the other testing we do. -#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc0") -#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc1") -#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2") -#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2x") -#delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc3") + def test_cn_sto3g_fc_adc2_states(self): + self.base_test("cn_sto3g", "fc_adc2", "state") + + def test_cn_sto3g_fv_adc2x_states(self): + self.base_test("cn_sto3g", "fv_adc2x", "state") + + def test_cn_sto3g_fv_cvs_adc2x_states(self): + self.base_test("cn_sto3g", "fv_cvs_adc2x", "state") + + def test_h2s_sto3g_fc_cvs_adc2_singlets(self): + self.base_test("h2s_sto3g", "fc_cvs_adc2", "singlet") + + def test_h2s_6311g_fc_adc2_singlets(self): + self.base_test("h2s_6311g", "fc_adc2", "singlet") + + def test_h2s_6311g_fv_adc2_singlets(self): + self.base_test("h2s_6311g", "fv_adc2", "singlet") + + def test_h2s_6311g_fc_cvs_adc2x_singlets(self): + self.base_test("h2s_6311g", "fc_cvs_adc2x", "singlet") + + def test_h2s_6311g_fv_cvs_adc2x_singlets(self): + self.base_test("h2s_6311g", "fv_cvs_adc2x", "singlet") + + +# Return combinations not tested so far: +# The rationale is that cvs-spin-flip as a method do not make +# that much sense and probably the routines are anyway covered +# by the other testing we do. +delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc0") +delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc1") +delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2") +delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc2x") +delattr(Runners, "test_hf3_631g_spin_flip_cvs_adc3") class TestStateDiffDm(unittest.TestCase, Runners): diff --git a/adcc/testdata/generate_hfdata_h2s_6311g.py b/adcc/testdata/generate_hfdata_h2s_6311g.py index de4317f8..a8261230 100755 --- a/adcc/testdata/generate_hfdata_h2s_6311g.py +++ b/adcc/testdata/generate_hfdata_h2s_6311g.py @@ -26,9 +26,10 @@ from static_data import xyz from os.path import dirname, join -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +#import adcctestdata as atd # noqa: E402 +from adcc.testdata.dump_pyscf import dump_pyscf # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -44,7 +45,7 @@ mf.diis_space = 3 mf.max_cycle = 100 mf.kernel() -h5f = atd.dump_pyscf(mf, "h2s_6311g_hfdata.hdf5") +h5f = dump_pyscf(mf, "h2s_6311g_hfdata.hdf5") core = "core_orbitals" fc = "frozen_core" diff --git a/adcc/testdata/generate_hfdata_h2s_sto3g.py b/adcc/testdata/generate_hfdata_h2s_sto3g.py index 2e44b21b..4e3d6c77 100755 --- a/adcc/testdata/generate_hfdata_h2s_sto3g.py +++ b/adcc/testdata/generate_hfdata_h2s_sto3g.py @@ -27,9 +27,10 @@ from static_data import xyz -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +#import adcctestdata as atd # noqa: E402 +from adcc.testdata.dump_pyscf import dump_pyscf # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -45,7 +46,7 @@ mf.diis_space = 3 mf.max_cycle = 100 mf.kernel() -h5f = atd.dump_pyscf(mf, "h2s_sto3g_hfdata.hdf5") +h5f = dump_pyscf(mf, "h2s_sto3g_hfdata.hdf5") core = "core_orbitals" fc = "frozen_core" diff --git a/adcc/testdata/generate_hfdata_hf3_631g.py b/adcc/testdata/generate_hfdata_hf3_631g.py index f7f85866..c746b47f 100755 --- a/adcc/testdata/generate_hfdata_hf3_631g.py +++ b/adcc/testdata/generate_hfdata_hf3_631g.py @@ -26,10 +26,10 @@ from os.path import dirname, join from static_data import xyz +from adcc.testdata.dump_pyscf import dump_pyscf +# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) - -import adcctestdata as atd # noqa: E402 +# import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -46,7 +46,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = atd.dump_pyscf(mf, "hf3_631g_hfdata.hdf5") +h5f = dump_pyscf(mf, "hf3_631g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 22a5e21d..818f1cfc 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -132,12 +132,14 @@ def dump_cn_sto3g(): # CN unrestricted # Just ADC(2) and ADC(2)-x for the other methods case = "cn_sto3g" - dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 12, - "max_subspace": 30}, spec="fc") - dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 14, - "max_subspace": 30}, spec="fc-fv") - dump_method(case, "adc2x", {"n_states": 4, "n_guess_singles": 8}, spec="fv") - dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs") + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 12, + "max_subspace": 30}, spec="fc", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 14, + "max_subspace": 30}, spec="fc-fv", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", {"n_states": 4, "n_guess_singles": 8}, spec="fv", + dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs", dump_gauge_origin = gauge_origin) def dump_cn_ccpvdz(): # CN unrestricted @@ -155,23 +157,27 @@ def dump_h2s_sto3g(): case = "h2s_sto3g" kwargs = {"n_singlets": 3, "n_triplets": 3} - dump_method(case, "adc2", kwargs, spec="fc-cvs") - dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs") + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2", kwargs, spec="fc-cvs", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs", dump_gauge_origin = gauge_origin) def dump_h2s_6311g(): case = "h2s_6311g" kwargs = {"n_singlets": 3, "n_triplets": 3} for spec in ["gen", "fc", "fv", "fc-fv"]: - dump_method(case, "adc2", kwargs, spec=spec) + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2", kwargs, spec=spec, dump_gauge_origin = gauge_origin) kwargs = {"n_singlets": 3, "n_triplets": 3, "n_guess_singles": 6, "max_subspace": 60} for spec in ["fv-cvs", "fc-cvs", "fc-fv-cvs"]: - dump_method(case, "adc2x", kwargs, spec=spec) + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2x", kwargs, spec=spec, dump_gauge_origin = gauge_origin) kwargs["n_guess_singles"] = 8 - dump_method(case, "adc2x", kwargs, spec="cvs") + for gauge_origin in ["origin", "mass_center", "charge_center"]: + dump_method(case, "adc2x", kwargs, spec="cvs", dump_gauge_origin = gauge_origin) def dump_methox_sto3g(): # (R)-2-methyloxirane @@ -187,7 +193,7 @@ def main(): # dump_cn_ccpvdz() # dump_hf3_631g() # dump_h2s_sto3g() - # dump_h2s_6311g() + dump_h2s_6311g() # dump_methox_sto3g() From ce1fac0b00ffdf9545b91081816e542d1ee67aae Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Mon, 8 May 2023 09:53:52 +0200 Subject: [PATCH 12/42] Undo changes in generate_hfdata files --- adcc/testdata/generate_hfdata_cn_ccpvdz.py | 7 +++---- adcc/testdata/generate_hfdata_cn_sto3g.py | 8 +++----- adcc/testdata/generate_hfdata_h2o_def2tzvp.py | 7 +++---- adcc/testdata/generate_hfdata_h2o_sto3g.py | 7 +++---- adcc/testdata/generate_hfdata_h2s_6311g.py | 7 +++---- adcc/testdata/generate_hfdata_h2s_sto3g.py | 7 +++---- adcc/testdata/generate_hfdata_hf3_631g.py | 8 ++++---- 7 files changed, 22 insertions(+), 29 deletions(-) diff --git a/adcc/testdata/generate_hfdata_cn_ccpvdz.py b/adcc/testdata/generate_hfdata_cn_ccpvdz.py index ef4fec5d..b0dd1283 100755 --- a/adcc/testdata/generate_hfdata_cn_ccpvdz.py +++ b/adcc/testdata/generate_hfdata_cn_ccpvdz.py @@ -26,11 +26,10 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join -from adcc.testdata.dump_pyscf import dump_pyscf -#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -#import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -48,7 +47,7 @@ mf.max_cycle = 600 mf = scf.addons.frac_occ(mf) mf.kernel() -h5f = dump_pyscf(mf, "cn_ccpvdz_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "cn_ccpvdz_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfdata_cn_sto3g.py b/adcc/testdata/generate_hfdata_cn_sto3g.py index 57826f3e..23d86411 100755 --- a/adcc/testdata/generate_hfdata_cn_sto3g.py +++ b/adcc/testdata/generate_hfdata_cn_sto3g.py @@ -26,12 +26,10 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join -from adcc.testdata.dump_pyscf import dump_pyscf +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) - -# import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -49,7 +47,7 @@ mf.max_cycle = 500 mf = scf.addons.frac_occ(mf) mf.kernel() -h5f = dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfdata_h2o_def2tzvp.py b/adcc/testdata/generate_hfdata_h2o_def2tzvp.py index f99bca71..f5f47b6b 100755 --- a/adcc/testdata/generate_hfdata_h2o_def2tzvp.py +++ b/adcc/testdata/generate_hfdata_h2o_def2tzvp.py @@ -25,11 +25,10 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join -from adcc.testdata.dump_pyscf import dump_pyscf -# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -44,7 +43,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = dump_pyscf(mf, "h2o_def2tzvp_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "h2o_def2tzvp_hfdata.hdf5") # Store configuration parameters for interesting cases to generate # reference data for. The data is stored as a stringified dict. diff --git a/adcc/testdata/generate_hfdata_h2o_sto3g.py b/adcc/testdata/generate_hfdata_h2o_sto3g.py index 38876713..82b32cc3 100755 --- a/adcc/testdata/generate_hfdata_h2o_sto3g.py +++ b/adcc/testdata/generate_hfdata_h2o_sto3g.py @@ -25,11 +25,10 @@ from pyscf import gto, scf from static_data import xyz from os.path import dirname, join -from adcc.testdata.dump_pyscf import dump_pyscf -# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -44,7 +43,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = dump_pyscf(mf, "h2o_sto3g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "h2o_sto3g_hfdata.hdf5") # Store configuration parameters for interesting cases to generate # reference data for. The data is stored as a stringified dict. diff --git a/adcc/testdata/generate_hfdata_h2s_6311g.py b/adcc/testdata/generate_hfdata_h2s_6311g.py index a8261230..de4317f8 100755 --- a/adcc/testdata/generate_hfdata_h2s_6311g.py +++ b/adcc/testdata/generate_hfdata_h2s_6311g.py @@ -26,10 +26,9 @@ from static_data import xyz from os.path import dirname, join -#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -#import adcctestdata as atd # noqa: E402 -from adcc.testdata.dump_pyscf import dump_pyscf +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -45,7 +44,7 @@ mf.diis_space = 3 mf.max_cycle = 100 mf.kernel() -h5f = dump_pyscf(mf, "h2s_6311g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "h2s_6311g_hfdata.hdf5") core = "core_orbitals" fc = "frozen_core" diff --git a/adcc/testdata/generate_hfdata_h2s_sto3g.py b/adcc/testdata/generate_hfdata_h2s_sto3g.py index 4e3d6c77..2e44b21b 100755 --- a/adcc/testdata/generate_hfdata_h2s_sto3g.py +++ b/adcc/testdata/generate_hfdata_h2s_sto3g.py @@ -27,10 +27,9 @@ from static_data import xyz -#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -#import adcctestdata as atd # noqa: E402 -from adcc.testdata.dump_pyscf import dump_pyscf +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -46,7 +45,7 @@ mf.diis_space = 3 mf.max_cycle = 100 mf.kernel() -h5f = dump_pyscf(mf, "h2s_sto3g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "h2s_sto3g_hfdata.hdf5") core = "core_orbitals" fc = "frozen_core" diff --git a/adcc/testdata/generate_hfdata_hf3_631g.py b/adcc/testdata/generate_hfdata_hf3_631g.py index c746b47f..f7f85866 100755 --- a/adcc/testdata/generate_hfdata_hf3_631g.py +++ b/adcc/testdata/generate_hfdata_hf3_631g.py @@ -26,10 +26,10 @@ from os.path import dirname, join from static_data import xyz -from adcc.testdata.dump_pyscf import dump_pyscf -# sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# import adcctestdata as atd # noqa: E402 +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) + +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -46,7 +46,7 @@ mf.diis_space = 3 mf.max_cycle = 500 mf.kernel() -h5f = dump_pyscf(mf, "hf3_631g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "hf3_631g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, From 6c7eff97f88ef03c47384c992c50958495d36348 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 10 May 2023 11:32:05 +0200 Subject: [PATCH 13/42] Code Cleanup. Better Descriptions etc. --- adcc/DataHfProvider.py | 4 +- adcc/ReferenceState.py | 10 +- adcc/backends/__init__.py | 2 +- adcc/backends/pyscf.py | 27 +- adcc/solver/conjugate_gradient.py | 6 +- adcc/test_properties.py | 2 +- adcc/testdata/cache.py | 11 +- adcc/testdata/dump_pyscf.py | 266 ------------------ adcc/testdata/generate_reference.py | 69 +++-- adcc/workflow.py | 4 +- libadcc/pyiface/export_HartreeFockProvider.cc | 13 +- libadcc/pyiface/export_ReferenceState.cc | 11 +- 12 files changed, 90 insertions(+), 335 deletions(-) delete mode 100644 adcc/testdata/dump_pyscf.py diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 23d92153..8340d6fd 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -69,7 +69,7 @@ def __init__(self, backend="data"): class DataHfProvider(HartreeFockProvider): - def __init__(self, data, gauge_origin='origin'): + def __init__(self, data, gauge_origin="origin"): """ Initialise the DataHfProvider class with the `data` being a supported data container (currently python dictionary or HDF5 file). @@ -210,7 +210,7 @@ def __init__(self, data, gauge_origin='origin'): opprov.electric_dipole = np.asarray(mmp["elec_1"]) magm = data.get("magnetic_moments", {}) derivs = data.get("derivatives", {}) - if gauge_origin == 'origin': + if gauge_origin == "origin": if "elec_2" in mmp: if mmp["elec_2"].shape != (9, nb, nb): raise ValueError("multipoles/elec_2 is expected to have " diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 76453ad5..7b852394 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -37,7 +37,7 @@ class ReferenceState(libadcc.ReferenceState): def __init__(self, hfdata, core_orbitals=None, frozen_core=None, frozen_virtual=None, symmetry_check_on_import=False, - import_all_below_n_orbs=10, gauge_origin='origin'): + import_all_below_n_orbs=10, gauge_origin="origin"): """Construct a ReferenceState holding information about the employed SCF reference. @@ -103,8 +103,8 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, gauge_origin : str or list, optional Select the gauge origin for operator integrals. - Either by specifying a list [x,y,z] or by choosing one of the - keywords (mass_center, charge_center, origin) + Either by specifying a list in atomics units [x,y,z] or by choosing + one of the keywords (mass_center, charge_center, origin) default: origin Examples @@ -236,8 +236,8 @@ def dipole_moment(self): @cached_property def gauge_origin(self): """ - Return the selected gauge origin used for operators integrals. - Until now only available for the PySCF backend + Return the selected gauge origin used for operators integrals in + atomic units. Until now only available for the PySCF backend. """ return self._gauge_origin diff --git a/adcc/backends/__init__.py b/adcc/backends/__init__.py index 938894b2..74ff739e 100644 --- a/adcc/backends/__init__.py +++ b/adcc/backends/__init__.py @@ -52,7 +52,7 @@ def have_backend(backend): return backend in available() -def import_scf_results(res, gauge_origin='origin'): +def import_scf_results(res, gauge_origin="origin"): """ Import an scf_result from an SCF program. Tries to be smart and guess what the host program was and how to import it. diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index cb392bb6..b1806b7a 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -52,12 +52,13 @@ def __init__(self, scfres, gauge_origin): self.gauge_origin = gauge_origin else: raise NotImplementedError("Gauge origin has to be defined either by" - "using one of the keywords" - "mass_center, charge_center or origin." - "Or by declaring a list eg. [x, y, z]") + " using one of the keywords" + " mass_center, charge_center or origin." + " Or by declaring a list eg. [x, y, z]" + " in atomic units.") if isinstance(gauge_origin, str): - print(f"Gauge origin for operator integrals is selected as: \ -{gauge_origin}:", self.gauge_origin) + print("Gauge origin for operator integrals is selected as:" + f" {self.gauge_origin}, ({gauge_origin.replace('_',' ')})") else: print(f"Gauge origin for operator integrals is selected as: \ {gauge_origin}") @@ -308,26 +309,26 @@ def get_nuclear_multipole(self, order): return np.array([np.sum(charges)]) elif order == 1: coords = self.scfres.mol.atom_coords() - return np.einsum('i,ix->x', charges, coords) + return np.einsum("i,ix->x", charges, coords) elif order == 2: # electric quadrupole Q_jk = sum_i (q_i * r_ij *r_ik ) coords = self.scfres.mol.atom_coords() mass = self.scfres.mol.atom_mass_list() - if self.gauge_origin == 'mass_center': - mass_center = np.einsum('i,ij->j', mass, coords) / mass.sum() + if self.gauge_origin == "mass_center": + mass_center = np.einsum("i,ij->j", mass, coords) / mass.sum() coords = coords - mass_center - elif self.gauge_origin == 'charge_center': - charge_center = np.einsum('i,ij->j', charges, coords) / \ + elif self.gauge_origin == "charge_center": + charge_center = np.einsum("i,ij->j", charges, coords) / \ charges.sum() coords = coords - charge_center - elif self.gauge_origin == 'origin': + elif self.gauge_origin == "origin": coords = coords elif type(self.gauge_origin) == list: coords = coords - self.gauge_origin else: raise NotImplementedError() - r_r = np.einsum('ij,ik->ijk', coords, coords) - res = np.einsum('i,ijk->jk', charges, r_r) + r_r = np.einsum("ij,ik->ijk", coords, coords) + res = np.einsum("i,ijk->jk", charges, r_r) res = np.array([res[0][0], res[0][1], res[0][2], res[1][1], res[1][2], res[2][2]]) return res diff --git a/adcc/solver/conjugate_gradient.py b/adcc/solver/conjugate_gradient.py index e2b8094e..b6ff31e1 100644 --- a/adcc/solver/conjugate_gradient.py +++ b/adcc/solver/conjugate_gradient.py @@ -164,10 +164,10 @@ def is_converged(state): state.n_applies += 1 res_dot_zk = dot(state.residual, zk) - # if rhs is zero (for example, mtms of z components + # if rhs is zero (for example, mtms of z components # magnetic dipole moments of linear molecules) - # no iterations are needed. - if res_dot_zk == 0: + # no iterations are needed. + if res_dot_zk == 0: if is_converged(state): state.converged = True callback(state, "is_zero") diff --git a/adcc/test_properties.py b/adcc/test_properties.py index be2160e6..19e13777 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -140,7 +140,7 @@ def template_linear_molecule(self, method, gauge_origin): """ basis = "sto-3g" - if backend != 'pyscf' and gauge_origin != 'origin': + if backend != "pyscf" and gauge_origin != "origin": skip("Gauge origin selection is only implemented " "for pyscf backend") scfres = run_hf(backend, xyz, basis) diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index 670458f7..edd38095 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -109,7 +109,8 @@ def testcases(self): @property def testcases_gauge_origin(self): """ - The definition of the test cases: Data generator and reference file + The definition of additional gauge origin dependent + test cases: Data generator and reference file """ return [k + "_" + g for k in TestdataCache.cases @@ -123,7 +124,7 @@ def hfdata(self): """ ret = {} for k in self.testcases + self.testcases_gauge_origin: - hf_k = k.replace('_mass_center', "").replace("_charge_center", "") + hf_k = k.replace("_mass_center", "").replace("_charge_center", "") datafile = fullfile(hf_k + "_hfdata.hdf5") # TODO This could be made a plain HDF5.File ret[k] = hdf5io.load(datafile) @@ -138,14 +139,14 @@ def cache_eri(refstate): for k in self.testcases + self.testcases_gauge_origin: if k in self.testcases: ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin='origin')) + gauge_origin="origin")) else: if k.endswith("mass_center"): ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin='mass_center')) + gauge_origin="mass_center")) else: ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin='charge_center')) + gauge_origin="charge_center")) return ret # return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) diff --git a/adcc/testdata/dump_pyscf.py b/adcc/testdata/dump_pyscf.py deleted file mode 100644 index 967a0ced..00000000 --- a/adcc/testdata/dump_pyscf.py +++ /dev/null @@ -1,266 +0,0 @@ -## Taken from adcc-testdata -## -## --------------------------------------------------------------------- -## -## Copyright (C) 2019 by Michael F. Herbst -## -## This file is part of adcc-testdata. -## -## adcc-testdata 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-testdata 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-testdata. If not, see . -## -## --------------------------------------------------------------------- -import numpy as np - -from pyscf import ao2mo, scf - -import h5py - - -def dump_pyscf(scfres, out): - """ - Convert pyscf SCF result to HDF5 file in adcc format - """ - if not isinstance(scfres, scf.hf.SCF): - raise TypeError("Unsupported type for dump_pyscf.") - - if not scfres.converged: - raise ValueError( - "Cannot dump a pyscf calculation, " - "which is not yet converged. Did you forget to run " - "the kernel() or the scf() function of the pyscf scf " - "object?" - ) - - if isinstance(out, h5py.File): - data = out - elif isinstance(out, str): - data = h5py.File(out, "w") - else: - raise TypeError("Unknown type for out, only HDF5 file and str supported.") - - # Try to determine whether we are restricted - if isinstance(scfres.mo_occ, list): - restricted = len(scfres.mo_occ) < 2 - elif isinstance(scfres.mo_occ, np.ndarray): - restricted = scfres.mo_occ.ndim < 2 - else: - raise ValueError( - "Unusual pyscf SCF class encountered. Could not " - "determine restricted / unrestricted." - ) - - mo_occ = scfres.mo_occ - mo_energy = scfres.mo_energy - mo_coeff = scfres.mo_coeff - fock_bb = scfres.get_fock() - - # pyscf only keeps occupation and mo energies once if restriced, - # so we unfold it here in order to unify the treatment in the rest - # of the code - if restricted: - mo_occ = np.asarray((mo_occ / 2, mo_occ / 2)) - mo_energy = (mo_energy, mo_energy) - mo_coeff = (mo_coeff, mo_coeff) - fock_bb = (fock_bb, fock_bb) - - # Transform fock matrix to MOs - fock = tuple(mo_coeff[i].transpose().conj() @ fock_bb[i] @ mo_coeff[i] - for i in range(2)) - del fock_bb - - # Determine number of orbitals - n_orbs_alpha = mo_coeff[0].shape[1] - n_orbs_beta = mo_coeff[1].shape[1] - n_orbs = n_orbs_alpha + n_orbs_beta - if n_orbs_alpha != n_orbs_beta: - raise ValueError( - "adcc cannot deal with different number of alpha and " - "beta orbitals like in a restricted " - "open-shell reference at the moment." - ) - - # Determine number of electrons - n_alpha = np.sum(mo_occ[0] > 0) - n_beta = np.sum(mo_occ[1] > 0) - if n_alpha != np.sum(mo_occ[0]) or n_beta != np.sum(mo_occ[1]): - raise ValueError("Fractional occupation numbers are not supported " - "in adcc.") - - # conv_tol is energy convergence, conv_tol_grad is gradient convergence - if scfres.conv_tol_grad is None: - conv_tol_grad = np.sqrt(scfres.conv_tol) - else: - conv_tol_grad = scfres.conv_tol_grad - threshold = max(10 * scfres.conv_tol, conv_tol_grad) - - # - # Put basic data into HDF5 file - # - data.create_dataset("n_orbs_alpha", shape=(), data=int(n_orbs_alpha)) - data.create_dataset("energy_scf", shape=(), data=float(scfres.e_tot)) - data.create_dataset("restricted", shape=(), data=restricted) - data.create_dataset("conv_tol", shape=(), data=float(threshold)) - - if restricted: - # Note: In the pyscf world spin is 2S, so the multiplicity - # is spin + 1 - data.create_dataset( - "spin_multiplicity", shape=(), data=int(scfres.mol.spin) + 1 - ) - else: - data.create_dataset("spin_multiplicity", shape=(), data=0) - - # - # Orbital reordering - # - # TODO This should not be needed any more - # adcc assumes that the occupied orbitals are specified first, - # followed by the virtual orbitals. Pyscf does this by means of the - # mo_occ numpy arrays, so we need to reorder in order to agree - # with what is expected in adcc. - # - # First build a structured numpy array with the negative occupation - # in the primary field and the energy in the secondary - # for each alpha and beta - order_array = ( - np.array(list(zip(-mo_occ[0], mo_energy[0])), - dtype=np.dtype("float,float")), - np.array(list(zip(-mo_occ[1], mo_energy[1])), - dtype=np.dtype("float,float")), - ) - sort_indices = tuple(np.argsort(ary) for ary in order_array) - - # Use the indices which sort order_array (== stort_indices) to reorder - mo_occ = tuple(mo_occ[i][sort_indices[i]] for i in range(2)) - mo_energy = tuple(mo_energy[i][sort_indices[i]] for i in range(2)) - mo_coeff = tuple(mo_coeff[i][:, sort_indices[i]] for i in range(2)) - fock = tuple(fock[i][sort_indices[i]][:, sort_indices[i]] for i in range(2)) - - # - # SCF orbitals and SCF results - # - data.create_dataset("occupation_f", data=np.hstack((mo_occ[0], mo_occ[1]))) - data.create_dataset("orben_f", data=np.hstack((mo_energy[0], mo_energy[1]))) - fullfock_ff = np.zeros((n_orbs, n_orbs)) - fullfock_ff[:n_orbs_alpha, :n_orbs_alpha] = fock[0] - fullfock_ff[n_orbs_alpha:, n_orbs_alpha:] = fock[1] - data.create_dataset("fock_ff", data=fullfock_ff, compression=8) - - non_canonical = np.max(np.abs(data["fock_ff"] - np.diag(data["orben_f"]))) - if non_canonical > data["conv_tol"][()]: - raise ValueError("Running adcc on top of a non-canonical fock " - "matrix is not implemented.") - - cf_bf = np.hstack((mo_coeff[0], mo_coeff[1])) - data.create_dataset("orbcoeff_fb", data=cf_bf.transpose(), compression=8) - - # - # ERI AO to MO transformation - # - if hasattr(scfres, "_eri") and scfres._eri is not None: - # eri is stored ... use it directly - eri_ao = scfres._eri - else: - # eri is not stored ... generate it now. - eri_ao = scfres.mol.intor("int2e", aosym="s8") - - aro = slice(0, n_alpha) - bro = slice(n_orbs_alpha, n_orbs_alpha + n_beta) - arv = slice(n_alpha, n_orbs_alpha) - brv = slice(n_orbs_alpha + n_beta, n_orbs) - - # compute full ERI tensor (with really everything) - eri = ao2mo.general(eri_ao, (cf_bf, cf_bf, cf_bf, cf_bf), compact=False) - eri = eri.reshape(n_orbs, n_orbs, n_orbs, n_orbs) - del eri_ao - - # Adjust spin-forbidden blocks to be exactly zero - eri[aro, bro, :, :] = 0 - eri[aro, brv, :, :] = 0 - eri[arv, bro, :, :] = 0 - eri[arv, brv, :, :] = 0 - - eri[bro, aro, :, :] = 0 - eri[bro, arv, :, :] = 0 - eri[brv, aro, :, :] = 0 - eri[brv, arv, :, :] = 0 - - eri[:, :, aro, bro] = 0 - eri[:, :, aro, brv] = 0 - eri[:, :, arv, bro] = 0 - eri[:, :, arv, brv] = 0 - - eri[:, :, bro, aro] = 0 - eri[:, :, bro, arv] = 0 - eri[:, :, brv, aro] = 0 - eri[:, :, brv, arv] = 0 - data.create_dataset("eri_ffff", data=eri, compression=8) - - # Calculate mass center and charge center - mass = scfres.mol.atom_mass_list() - charges = scfres.mol.atom_charges() - coords = scfres.mol.atom_coords() - mass_center = np.einsum('i,ij->j', mass, coords) / mass.sum() - print(mass_center) - charge_center = np.einsum('i,ij->j', charges, coords) / charges.sum() - - # Compute electric and nuclear multipole moments - mmp = data.create_group("multipoles") - mmp.create_dataset("nuclear_0", shape=(), data=int(np.sum(charges))) - mmp.create_dataset("nuclear_1", data=np.einsum("i,ix->x", charges, coords)) - mmp.create_dataset("elec_0", shape=(), data=-int(n_alpha + n_beta)) - mmp.create_dataset("elec_1", - data=scfres.mol.intor_symmetric("int1e_r", comp=3)) - with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): - mmp.create_dataset("elec_2", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) - with scfres.mol.with_common_orig(mass_center): - mmp.create_dataset("elec_2_mass_center", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) - with scfres.mol.with_common_orig(charge_center): - mmp.create_dataset("elec_2_charge_center", - data=scfres.mol.intor_symmetric('int1e_rr', comp=9)) - - magm = data.create_group("magnetic_moments") - derivs = data.create_group("derivatives") - with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): - magm.create_dataset( - "mag_1", - data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) - ) - derivs.create_dataset( - "nabla", - data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) - with scfres.mol.with_common_orig(mass_center): - magm.create_dataset( - "mag_1_mass_center", - data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) - ) - derivs.create_dataset( - "nabla_mass_center", - data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) - with scfres.mol.with_common_orig(charge_center): - magm.create_dataset( - "mag_1_charge_center", - data=0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) - ) - derivs.create_dataset( - "nabla_charge_center", - data=-1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) - - data.attrs["backend"] = "pyscf" - return data diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 818f1cfc..3d39b043 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -31,18 +31,20 @@ import h5py -#sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) +sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -#import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 -def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): +def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", + generator="adcc", dump_gauge_origin=True): assert spec in ["gen", "cvs"] for method in ["adc0", "adc1", "adc2", "adc2x", "adc3"]: - for gauge_origin in ["origin", "mass_center", "charge_center"]: - kw = kwargs_overwrite.get(method, kwargs) - dump_method(case, method, kw, spec, generator=generator, - dump_gauge_origin=gauge_origin) + if dump_gauge_origin: + for gauge_origin in ["origin", "mass_center", "charge_center"]: + kw = kwargs_overwrite.get(method, kwargs) + dump_method(case, method, kw, spec, generator=generator, + dump_gauge_origin=gauge_origin) def dump_method(case, method, kwargs, spec, generator="adcc", @@ -78,14 +80,13 @@ def dump_method(case, method, kwargs, spec, generator="adcc", if generator == "atd": dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) - elif generator == "adcc" and dump_gauge_origin == 'origin': + elif generator == "adcc" and dump_gauge_origin == "origin": dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) else: dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, prefix, method) - # if not os.path.isfile(dumpfile): - # if os.path.isfile(dumpfile): - dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, + if not os.path.isfile(dumpfile): + dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -134,12 +135,15 @@ def dump_cn_sto3g(): # CN unrestricted case = "cn_sto3g" for gauge_origin in ["origin", "mass_center", "charge_center"]: dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 12, - "max_subspace": 30}, spec="fc", dump_gauge_origin = gauge_origin) + "max_subspace": 30}, spec="fc", + dump_gauge_origin=gauge_origin) dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 14, - "max_subspace": 30}, spec="fc-fv", dump_gauge_origin = gauge_origin) + "max_subspace": 30}, spec="fc-fv", + dump_gauge_origin=gauge_origin) dump_method(case, "adc2x", {"n_states": 4, "n_guess_singles": 8}, spec="fv", - dump_gauge_origin = gauge_origin) - dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs", dump_gauge_origin = gauge_origin) + dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs", + dump_gauge_origin=gauge_origin) def dump_cn_ccpvdz(): # CN unrestricted @@ -158,8 +162,10 @@ def dump_h2s_sto3g(): kwargs = {"n_singlets": 3, "n_triplets": 3} for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec="fc-cvs", dump_gauge_origin = gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc-cvs", + dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs", + dump_gauge_origin=gauge_origin) def dump_h2s_6311g(): @@ -167,34 +173,39 @@ def dump_h2s_6311g(): kwargs = {"n_singlets": 3, "n_triplets": 3} for spec in ["gen", "fc", "fv", "fc-fv"]: for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec=spec, dump_gauge_origin = gauge_origin) + dump_method(case, "adc2", kwargs, spec=spec, + dump_gauge_origin=gauge_origin) kwargs = {"n_singlets": 3, "n_triplets": 3, "n_guess_singles": 6, "max_subspace": 60} for spec in ["fv-cvs", "fc-cvs", "fc-fv-cvs"]: for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2x", kwargs, spec=spec, dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", kwargs, spec=spec, + dump_gauge_origin=gauge_origin) kwargs["n_guess_singles"] = 8 for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2x", kwargs, spec="cvs", dump_gauge_origin = gauge_origin) + dump_method(case, "adc2x", kwargs, spec="cvs", + dump_gauge_origin=gauge_origin) def dump_methox_sto3g(): # (R)-2-methyloxirane kwargs = {"n_singlets": 2} - dump_all("methox_sto3g", kwargs, spec="gen", generator="adcc") - dump_all("methox_sto3g", kwargs, spec="cvs", generator="adcc") + dump_all("methox_sto3g", kwargs, spec="gen", + generator="adcc", dump_gauge_origin=False) + dump_all("methox_sto3g", kwargs, spec="cvs", + generator="adcc", dump_gauge_origin=False) def main(): - # dump_h2o_sto3g() - # dump_h2o_def2tzvp() - # dump_cn_sto3g() - # dump_cn_ccpvdz() - # dump_hf3_631g() - # dump_h2s_sto3g() + dump_h2o_sto3g() + dump_h2o_def2tzvp() + dump_cn_sto3g() + dump_cn_ccpvdz() + dump_hf3_631g() + dump_h2s_sto3g() dump_h2s_6311g() - # dump_methox_sto3g() + dump_methox_sto3g() if __name__ == "__main__": diff --git a/adcc/workflow.py b/adcc/workflow.py index 2db2a155..e42d82be 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -135,7 +135,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, gauge_origin: list or str, optional Define the gauge origin for operator integrals. - Either by specifying a list directly ([x,y,z]) + Either by specifying a list in atomic units directly ([x,y,z]) or by using one of the keywords (mass_center, charge_center, origin). Default: origin @@ -225,7 +225,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, # Individual steps # def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, - frozen_virtual=None, method=None, gauge_origin='origin'): + frozen_virtual=None, method=None, gauge_origin="origin"): """ Use the provided data or AdcMatrix object to check consistency of the other passed parameters and construct the AdcMatrix object representing diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 76c7cf47..94587b05 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -485,13 +485,18 @@ void export_HartreeFockProvider(py::module& m) { .def("get_nuclear_multipole", &HartreeFockProvider::get_nuclear_multipole, "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " - "dipole moment as an array of size 3.") + "dipole moment as an array of size 3, for `2` returns the nuclear " + "quadrupole moment as an array of the 6 unique entries " + "(xx, xy, xz, yy, yz, zz) dependent on the selected gauge origin." ) .def("get_nuclear_charges", &HartreeFockProvider::get_nuclear_charges, - "Returns nuclear charges of SCF reference, array size 3") + "Returns nuclear charges of SCF reference, " + "array of size of number of atoms.") .def("get_coordinates", &HartreeFockProvider::get_coordinates, - "Returns coordinates of SCF REfernece State in Bohr") + "Returns coordinates of SCF referece state in Bohr, array of " + "size 3 * number of atoms.") .def("get_nuclear_masses", &HartreeFockProvider::get_nuclear_masses, - "Returns the nuclear masses") + "Returns the nuclear masses of the SCF reference, " + "array of size of number of atoms.") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 7fb0f659..feacaf9f 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -126,13 +126,16 @@ void export_ReferenceState(py::module& m) { return res; }) .def_property_readonly("nuclear_charges", &ReferenceState::nuclear_charges, - "Nuclear chages") + "Returns the nuclear charges of the SCF reference " + "as a list of the size of number of atoms.") .def_property_readonly("nuclear_masses", &ReferenceState::nuclear_masses, - "Nuclear masses") + "Returns the nuclear masses of the SCF reference " + "as a list of the size of number of atoms.") .def_property_readonly("coordinates", &ReferenceState::coordinates, - "Returns coordinates of the SCF Reference as list e.g. [x1, y1, z1, x2, y2, z2,...]") + "Returns coordinates of the SCF reference as list " + "of size 3 * number of atoms in atomic units.") .def_property_readonly("n_atoms", &ReferenceState::n_atoms, - "Returns the number of atoms") + "Returns the number of atoms.") .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From a1592e4576356e794218a9587fb072c4e1ca5247 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 10 May 2023 14:42:05 +0200 Subject: [PATCH 14/42] small changes --- adcc/DataHfProvider.py | 2 +- adcc/OperatorIntegrals.py | 6 +++--- adcc/backends/pyscf.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 8340d6fd..2ff4c1b9 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -160,7 +160,7 @@ def __init__(self, data, gauge_origin="origin"): keys see details above. gauge_origin : str - Selection of the gauge origin for opertaor integrals. + Selection of the gauge origin for operator integrals. Choose from the keywords: mass_center, charge_center, origin default: origin """ diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 4c81ef95..d24123a5 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -104,7 +104,7 @@ def available(self): "nabla", "electric_quadrupole_traceless", "electric_quadrupole", - "diag_mag", + "dia_magnet", "pe_induction_elec", "pcm_potential_elec" ) @@ -197,12 +197,12 @@ def electric_quadrupole(self): @property @timed_member_call("_import_timer") - def diag_mag(self): + def dia_magnet(self): """ Return the diamagnetic magnetizability integrals in the molecular orbital basis. """ - return self.import_quadrupole_like_operator("diag_mag", + return self.import_quadrupole_like_operator("dia_magnet", is_symmetric=True) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index b1806b7a..f5b62f70 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -83,7 +83,7 @@ def nabla(self): ) @cached_property - def diag_mag(self): + def dia_magnet(self): with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) From 3932880ac9ae6dc267ed536ab8040cdc4011c9c1 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 24 Aug 2023 09:58:46 +0200 Subject: [PATCH 15/42] GitHub comments, backend warning and solver --- adcc/backends/molsturm.py | 6 +++--- adcc/backends/psi4.py | 6 +++--- adcc/backends/pyscf.py | 7 ------- adcc/backends/veloxchem.py | 6 +++--- adcc/solver/conjugate_gradient.py | 12 ------------ 5 files changed, 9 insertions(+), 28 deletions(-) diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index 9e3f1c4d..2a2a2c6d 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -29,9 +29,9 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn("Gauge origin selection only available in PySCF " - "not in Molsturm. " - "The gauge origin is selected as [0, 0, 0]") + warnings.warn("Gauge origin selection not available " + "in Molsturm. " + "The gauge origin is selected as [0, 0, 0].") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 7c99d1d2..034d43e9 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -39,9 +39,9 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) - warnings.warn("Gauge origin selection only available in PySCF " - f"not in {self.backend}. " - "The gauge origin is selected as [0, 0, 0]") + warnings.warn("Gauge origin selection not available in " + f"{self.backend}. " + "The gauge origin is selected as [0, 0, 0].") @cached_property def electric_dipole(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index f5b62f70..9a20327d 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -56,13 +56,6 @@ def __init__(self, scfres, gauge_origin): " mass_center, charge_center or origin." " Or by declaring a list eg. [x, y, z]" " in atomic units.") - if isinstance(gauge_origin, str): - print("Gauge origin for operator integrals is selected as:" - f" {self.gauge_origin}, ({gauge_origin.replace('_',' ')})") - else: - print(f"Gauge origin for operator integrals is selected as: \ - {gauge_origin}") - warnings.warn("Gauge origin selection is not tested") @cached_property def electric_dipole(self): diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index b2183b05..ad17bd33 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -44,9 +44,9 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" - warnings.warn("Gauge origin selection only available in PySCF " - f"not in {self.backend}. " - "The gauge origin is selected as [0, 0, 0]") + warnings.warn("Gauge origin selection not available " + f"in {self.backend}. " + "The gauge origin is selected as [0, 0, 0].") @cached_property def electric_dipole(self): diff --git a/adcc/solver/conjugate_gradient.py b/adcc/solver/conjugate_gradient.py index b6ff31e1..77982cc3 100644 --- a/adcc/solver/conjugate_gradient.py +++ b/adcc/solver/conjugate_gradient.py @@ -78,8 +78,6 @@ def default_print(state, identifier, file=sys.stdout): elif identifier == "is_converged": print("=== Converged ===", file=file) print(" Number of matrix applies: ", state.n_applies) - elif identifier == "is_zero": - print("=== Is Zero, Converged ===", file=file) def conjugate_gradient(matrix, rhs, x0=None, conv_tol=1e-9, max_iter=100, @@ -163,16 +161,6 @@ def is_converged(state): Apk = matrix @ pk state.n_applies += 1 res_dot_zk = dot(state.residual, zk) - - # if rhs is zero (for example, mtms of z components - # magnetic dipole moments of linear molecules) - # no iterations are needed. - if res_dot_zk == 0: - if is_converged(state): - state.converged = True - callback(state, "is_zero") - return state - ak = float(res_dot_zk / dot(pk, Apk)) state.solution = evaluate(state.solution + ak * pk) From 1b923f480ca483920699f0277535c16b7943579c Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 19 Sep 2024 11:32:01 +0200 Subject: [PATCH 16/42] make gauge origin selection only property related --- adcc/DataHfProvider.py | 81 ++++----- adcc/ElectronicTransition.py | 33 ++-- adcc/OperatorIntegrals.py | 135 ++++++++++----- adcc/ReferenceState.py | 22 +-- .../test_modified_transition_moments.py | 2 +- adcc/backends/__init__.py | 8 +- adcc/backends/psi4.py | 22 ++- adcc/backends/pyscf.py | 157 ++++++++++-------- adcc/backends/testing.py | 9 +- adcc/backends/veloxchem.py | 32 ++-- adcc/test_ExcitedStates.py | 4 + adcc/test_IsrMatrix.py | 6 +- adcc/test_hardcoded.py | 2 +- adcc/test_properties.py | 18 +- adcc/test_properties_consistency.py | 38 ++++- adcc/testdata/cache.py | 41 +---- adcc/workflow.py | 17 +- 17 files changed, 341 insertions(+), 286 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 2ff4c1b9..d62a499e 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -69,7 +69,7 @@ def __init__(self, backend="data"): class DataHfProvider(HartreeFockProvider): - def __init__(self, data, gauge_origin="origin"): + def __init__(self, data): """ Initialise the DataHfProvider class with the `data` being a supported data container (currently python dictionary or HDF5 file). @@ -158,11 +158,6 @@ def __init__(self, data, gauge_origin="origin"): data : dict or h5py.File Dictionary containing the HartreeFock data to use. For the required keys see details above. - - gauge_origin : str - Selection of the gauge origin for operator integrals. - Choose from the keywords: mass_center, charge_center, origin - default: origin """ # Do not forget the next line, otherwise weird errors result @@ -208,50 +203,40 @@ def __init__(self, data, gauge_origin="origin"): + str((3, nb, nb)) + " not " + str(mmp["elec_1"].shape)) opprov.electric_dipole = np.asarray(mmp["elec_1"]) + if "elec_2" in mmp: + def get_integral_quad(gauge_origin): + integral_string = "elec_2" + if gauge_origin != 'origin': + integral_string = f'{integral_string}_{gauge_origin}' + return np.asarray(mmp[integral_string]) + print('hello') + if mmp["elec_2"].shape != (9, nb, nb): + raise ValueError("multipoles/elec_2 is expected to have " + "shape " + str((9, nb, nb)) + " not " + + str(mmp["elec_2"].shape)) + opprov.electric_quadrupole = get_integral_quad magm = data.get("magnetic_moments", {}) + if "mag_1" in magm: + def get_integral_magm(gauge_origin): + integral_string = "mag_1" + if gauge_origin != 'origin': + integral_string = f'{integral_string}_{gauge_origin}' + return np.asarray(magm[integral_string]) + if magm["mag_1"].shape != (3, nb, nb): + raise ValueError("magnetic_moments/mag_1 is expected to have " + "shape " + str((3, nb, nb)) + " not " + + str(magm["mag_1"].shape)) + opprov.magnetic_dipole = get_integral_magm derivs = data.get("derivatives", {}) - if gauge_origin == "origin": - if "elec_2" in mmp: - if mmp["elec_2"].shape != (9, nb, nb): - raise ValueError("multipoles/elec_2 is expected to have " - "shape " + str((9, nb, nb)) + " not " - + str(mmp["elec_2"].shape)) - opprov.electric_quadrupole = np.asarray(mmp["elec_2"]) - if "mag_1" in magm: - if magm["mag_1"].shape != (3, nb, nb): - raise ValueError("magnetic_moments/mag_1 is expected to have " - "shape " + str((3, nb, nb)) + " not " - + str(magm["mag_1"].shape)) - opprov.magnetic_dipole = np.asarray(magm["mag_1"]) - if "nabla" in derivs: - if derivs["nabla"].shape != (3, nb, nb): - raise ValueError("derivatives/nabla is expected to " - "have shape " - + str((3, nb, nb)) + " not " - + str(derivs["nabla"].shape)) - opprov.nabla = np.asarray(derivs["nabla"]) - else: - if f"elec_2_{gauge_origin}" in mmp: - if mmp["elec_2"].shape != (9, nb, nb): - raise ValueError(f"multipoles/elec_2_{gauge_origin}" - " is expected to have shape " - + str((9, nb, nb)) + " not " - + str(mmp[f"elec_2_{gauge_origin}"].shape)) - opprov.electric_quadrupole = np.asarray(mmp["elec_2"]) - if f"mag_1_{gauge_origin}" in magm: - if magm[f"mag_1_{gauge_origin}"].shape != (3, nb, nb): - raise ValueError(f"magnetic_moments/mag_1_{gauge_origin}" - " is expected to have shape " - + str((3, nb, nb)) + " not " - + str(magm[f"mag_1_{gauge_origin}"].shape)) - opprov.magnetic_dipole = np.asarray(magm[f"mag_1_{gauge_origin}"]) - if f"nabla_{gauge_origin}" in derivs: - if derivs[f"nabla_{gauge_origin}"].shape != (3, nb, nb): - raise ValueError(f"derivatives/nabla_{gauge_origin}" - " is expected to have shape " - + str((3, nb, nb)) + " not " - + str(derivs[f"nabla_{gauge_origin}"].shape)) - opprov.nabla = np.asarray(derivs[f"nabla_{gauge_origin}"]) + if "nabla" in derivs: + def get_integral_deriv(gauge_origin): + return np.asarray(derivs["nabla"]) + if derivs["nabla"].shape != (3, nb, nb): + raise ValueError("derivatives/nabla is expected to " + "have shape " + + str((3, nb, nb)) + " not " + + str(derivs["nabla"].shape)) + opprov.nabla = get_integral_deriv self.operator_integral_provider = opprov # diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index c9878614..5f80d73f 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -165,44 +165,45 @@ def transition_dipole_moment(self): for tdm in self.transition_dm ]) - @cached_property + # @property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_dipole_moment_velocity(self): + def transition_dipole_moment_velocity(self, gauge_string): """List of transition dipole moments in the velocity gauge of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition velocity dipole moments " "are known to be faulty in some cases.") - dipole_integrals = self.operators.nabla + dipole_integrals = self.operators.nabla(gauge_string) return np.array([ [product_trace(comp, tdm) for comp in dipole_integrals] for tdm in self.transition_dm ]) - @cached_property + # @cached_property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_magnetic_dipole_moment(self): + def transition_magnetic_dipole_moment(self, gauge_string): """List of transition magnetic dipole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition magnetic dipole moments " "are known to be faulty in some cases.") - mag_dipole_integrals = self.operators.magnetic_dipole + mag_dipole_integrals = self.operators.magnetic_dipole(gauge_string) return np.array([ [product_trace(comp, tdm) for comp in mag_dipole_integrals] for tdm in self.transition_dm ]) - @cached_property + # @cached_property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_quadrupole_moment(self): + def transition_quadrupole_moment(self, gauge_string): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition dipole moments are known to be " "faulty in some cases.") - operator_integrals = np.array(self.operators.electric_quadrupole) + operator_integrals = np.array(self.operators.electric_quadrupole(gauge_string)) + print(operator_integrals[0]) return np.array([ [[product_trace(quad1, tdm) for quad1 in quad] for quad in operator_integrals] @@ -219,25 +220,25 @@ def oscillator_strength(self): self.excitation_energy) ]) - @cached_property + # @cached_property @mark_excitation_property() - def oscillator_strength_velocity(self): + def oscillator_strength_velocity(self, gauge_string): """List of oscillator strengths in velocity gauge of all computed states""" return 2. / 3. * np.array([ np.linalg.norm(tdm)**2 / np.abs(ev) - for tdm, ev in zip(self.transition_dipole_moment_velocity, + for tdm, ev in zip(self.transition_dipole_moment_velocity(gauge_string), self.excitation_energy) ]) - @cached_property + # @property @mark_excitation_property() - def rotatory_strength(self): + def rotatory_strength(self, gauge_string): """List of rotatory strengths of all computed states""" return np.array([ np.dot(tdm, magmom) / ee - for tdm, magmom, ee in zip(self.transition_dipole_moment_velocity, - self.transition_magnetic_dipole_moment, + for tdm, magmom, ee in zip(self.transition_dipole_moment_velocity(gauge_string), + self.transition_magnetic_dipole_moment(gauge_string), self.excitation_energy) ]) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index d24123a5..6444e44b 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -128,6 +128,38 @@ def import_dipole_like_operator(self, integral, is_symmetric=True): dipoles.append(dip_ff) return dipoles + def __import_gauge_dependent_dipole_like(self, callback, is_symmetric=True): + """Returns a function that imports a gauge-dependent dipole like + operator. + The returned function imports the operator and transforms it to the + molecular orbital basis. + + Parameters + ---------- + callback : callable + Function that computes the operator in atomic orbitals using + a the gauge-origin (str or list) as single argument + is_symmetric : bool, optional + if the imported operator is symmetric, by default True + """ + if not callable(callback): + raise TypeError + + def process_operator(gauge_origin, callback=callback, + is_symmetric=is_symmetric): + dipoles = [] + for i, component in enumerate(["x", "y", "z"]): + dip_backend = callback(gauge_origin)[i] + dip_bb = replicate_ao_block(self.mospaces, dip_backend, + is_symmetric=is_symmetric) + dip_ff = OneParticleOperator(self.mospaces, + is_symmetric=is_symmetric) + transform_operator_ao2mo(dip_bb, dip_ff, self.__coefficients, + self.__conv_tol) + dipoles.append(dip_ff) + return dipoles + return process_operator + @property @timed_member_call("_import_timer") def electric_dipole(self): @@ -138,72 +170,93 @@ def electric_dipole(self): @property @timed_member_call("_import_timer") def magnetic_dipole(self): - """Return the magnetic dipole integrals in the molecular orbital basis.""" - return self.import_dipole_like_operator("magnetic_dipole", - is_symmetric=False) + """ + Returns a function to obtain magnetic dipole intergrals + in the molecular orbital basis dependent on the selected gauge origin. + """ + callback = self.provider_ao.magnetic_dipole + return self.__import_gauge_dependent_dipole_like(callback, + is_symmetric=False) @property @timed_member_call("_import_timer") def nabla(self): """ - Return the momentum (nabla operator) integrals - in the molecular orbital basis. + Returns a function to obtain nabla intergrals + in the molecular orbital basis dependent on the selected gauge origin. """ - return self.import_dipole_like_operator("nabla", is_symmetric=False) + callback = self.provider_ao.nabla + return self.__import_gauge_dependent_dipole_like(callback, + is_symmetric=False) - def import_quadrupole_like_operator(self, integral, is_symmetric=True): - if integral not in self.available: - raise NotImplementedError(f"{integral.replace('_', ' ')} operator " - "not implemented " - f"in {self.provider_ao.backend} backend.") - quad = [] - quadrupoles = [] - for i, component in enumerate(["xx", "xy", "xz", - "yx", "yy", "yz", - "zx", "zy", "zz"]): - quad_backend = getattr(self.provider_ao, integral)[i] - quad_bb = replicate_ao_block(self.mospaces, quad_backend, - is_symmetric=is_symmetric) - quad_ff = OneParticleOperator(self.mospaces, - is_symmetric=is_symmetric) - transform_operator_ao2mo(quad_bb, quad_ff, - self.__coefficients, - self.__conv_tol) - quad.append(quad_ff) - quadrupoles.append(quad[:3]) - quadrupoles.append(quad[3:6]) - quadrupoles.append(quad[6:]) - return quadrupoles + def __import_quadrupole_like_operator(self, callback, is_symmetric=True): + """Returns a function that imports a gauge-dependent quadrupole like + operator. + The returned function imports the operator and transforms it to the + molecular orbital basis. + + Parameters + ---------- + callback : callable + Function that computes the operator in atomic orbitals using + a the gauge-origin (str or list) as single argument + is_symmetric : bool, optional + if the imported operator is symmetric, by default True + """ + if not callable(callback): + raise TypeError + + def process_operator(gauge_origin, callback=callback, + is_symmetric=is_symmetric): + quad = [] + quadrupoles = [] + for i, component in enumerate(["xx", "xy", "xz", + "yx", "yy", "yz", + "zx", "zy", "zz"]): + quad_backend = callback(gauge_origin)[i] + quad_bb = replicate_ao_block(self.mospaces, quad_backend, + is_symmetric=is_symmetric) + quad_ff = OneParticleOperator(self.mospaces, + is_symmetric=is_symmetric) + transform_operator_ao2mo(quad_bb, quad_ff, + self.__coefficients, + self.__conv_tol) + quad.append(quad_ff) + quadrupoles.append(quad[:3]) + quadrupoles.append(quad[3:6]) + quadrupoles.append(quad[6:]) + return quadrupoles + return process_operator @property @timed_member_call("_import_timer") def electric_quadrupole_traceless(self): """ - Return the traceless electric quadrupole integrals - in the molecular orbital basis. + Returns a function to obtain traceless electric quadrupole integrals + in the molecular orbital basis dependent on the selected gauge origin. """ - return self.import_quadrupole_like_operator("electric_quadrupole_traceless", - is_symmetric=True) + callback = self.provider_ao.electric_quadrupole_traceless + return self.__import_quadrupole_like_operator(callback, is_symmetric=False) @property @timed_member_call("_import_timer") def electric_quadrupole(self): """ - Return the traced electric quadrupole integrals - in the molecular orbital basis. + Returns a function to obtain electric quadrupole integrals + in the molecular orbital basis dependent on the selected gauge origin. """ - return self.import_quadrupole_like_operator("electric_quadrupole", - is_symmetric=True) + callback = self.provider_ao.electric_quadrupole + return self.__import_quadrupole_like_operator(callback, is_symmetric=False) @property @timed_member_call("_import_timer") def dia_magnet(self): """ - Return the diamagnetic magnetizability integrals - in the molecular orbital basis. + Returns a function to obtain diamagnetic magnetizability integrals + in the molecular orbital basis dependent on the selected gauge origin. """ - return self.import_quadrupole_like_operator("dia_magnet", - is_symmetric=True) + callback = self.provider_ao.dia_magnet + return self.__import_gauge_dependent(callback, is_symmetric=False) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 7b852394..28e5fcd3 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -37,7 +37,7 @@ class ReferenceState(libadcc.ReferenceState): def __init__(self, hfdata, core_orbitals=None, frozen_core=None, frozen_virtual=None, symmetry_check_on_import=False, - import_all_below_n_orbs=10, gauge_origin="origin"): + import_all_below_n_orbs=10): """Construct a ReferenceState holding information about the employed SCF reference. @@ -101,12 +101,6 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, parameter, the class will thus automatically import all ERI tensor and Fock matrix blocks. - gauge_origin : str or list, optional - Select the gauge origin for operator integrals. - Either by specifying a list in atomics units [x,y,z] or by choosing - one of the keywords (mass_center, charge_center, origin) - default: origin - Examples -------- To start a calculation with the 2 lowest alpha and beta orbitals @@ -145,7 +139,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, beta orbital into the core space. """ if not isinstance(hfdata, libadcc.HartreeFockSolution_i): - hfdata = import_scf_results(hfdata, gauge_origin) + hfdata = import_scf_results(hfdata) self._mospaces = MoSpaces(hfdata, frozen_core=frozen_core, frozen_virtual=frozen_virtual, @@ -161,8 +155,6 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, self.orbital_coefficients, self.conv_tol ) - self._gauge_origin = gauge_origin - self.environment = None # no environment attached by default for name in ["excitation_energy_corrections", "environment"]: if hasattr(hfdata, name): @@ -232,13 +224,5 @@ def dipole_moment(self): # Notice the negative sign due to the negative charge of the electrons return self.nuclear_dipole - np.array([product_trace(comp, self.density) for comp in dipole_integrals]) - - @cached_property - def gauge_origin(self): - """ - Return the selected gauge origin used for operators integrals in - atomic units. Until now only available for the PySCF backend. - """ - return self._gauge_origin - + # TODO some nice describe method diff --git a/adcc/adc_pp/test_modified_transition_moments.py b/adcc/adc_pp/test_modified_transition_moments.py index 7cc5879e..511a9284 100644 --- a/adcc/adc_pp/test_modified_transition_moments.py +++ b/adcc/adc_pp/test_modified_transition_moments.py @@ -49,7 +49,7 @@ def base_test(self, system, method, kind, op_kind): dips = state.reference_state.operators.electric_dipole ref_tdm = ref["transition_dipole_moments"] elif op_kind == "magnetic": - dips = state.reference_state.operators.magnetic_dipole + dips = state.reference_state.operators.magnetic_dipole('origin') ref_tdm = ref["transition_magnetic_dipole_moments"] else: skip("Tests are only implemented for electric " diff --git a/adcc/backends/__init__.py b/adcc/backends/__init__.py index 74ff739e..8abc3424 100644 --- a/adcc/backends/__init__.py +++ b/adcc/backends/__init__.py @@ -52,7 +52,7 @@ def have_backend(backend): return backend in available() -def import_scf_results(res, gauge_origin="origin"): +def import_scf_results(res): """ Import an scf_result from an SCF program. Tries to be smart and guess what the host program was and how to import it. @@ -62,7 +62,7 @@ def import_scf_results(res, gauge_origin="origin"): from pyscf import scf if isinstance(res, scf.hf.SCF): - return backend_pyscf.import_scf(res, gauge_origin) + return backend_pyscf.import_scf(res) if have_backend("molsturm"): from . import molsturm as backend_molsturm @@ -92,12 +92,12 @@ def import_scf_results(res, gauge_origin="origin"): if isinstance(res, (dict, h5py.File)): from adcc.DataHfProvider import DataHfProvider - return DataHfProvider(res, gauge_origin) + return DataHfProvider(res) if isinstance(res, str): if os.path.isfile(res) and (res.endswith(".h5") or res.endswith(".hdf5")): - return import_scf_results(h5py.File(res, "r"), gauge_origin) + return import_scf_results(h5py.File(res, "r")) else: raise ValueError("Unrecognised path or file extension: {}" "".format(res)) diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 034d43e9..946907e2 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -47,17 +47,25 @@ def __init__(self, wfn): def electric_dipole(self): return [-1.0 * np.asarray(comp) for comp in self.mints.ao_dipole()] - @cached_property + @property def magnetic_dipole(self): - # TODO: Gauge origin? - return [ - 0.5 * np.asarray(comp) - for comp in self.mints.ao_angular_momentum() - ] + def gauge_dependent_integrals(gauge_origin): + # TODO: Gauge origin? + if gauge_origin != 'origin': + raise NotImplementedError('Only origin can be selected.') + return [ + 0.5 * np.asarray(comp) + for comp in self.mints.ao_angular_momentum() + ] + return gauge_dependent_integrals @cached_property def nabla(self): - return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] + def gauge_dependent_integrals(gauge_origin): + if gauge_origin != 'origin': + raise NotImplementedError('Only origin can be selected.') + return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] + return gauge_dependent_integrals @property def pe_induction_elec(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 9a20327d..b4d33b36 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -35,77 +35,101 @@ class PyScfOperatorIntegralProvider: - def __init__(self, scfres, gauge_origin): + def __init__(self, scfres): self.scfres = scfres self.backend = "pyscf" - coords = scfres.mol.atom_coords() - mass = scfres.mol.atom_mass_list() - charges = scfres.mol.atom_charges() - if gauge_origin == "mass_center": - self.gauge_origin = np.einsum("i,ij->j", mass, coords) / mass.sum() - elif gauge_origin == "charge_center": - self.gauge_origin = np.einsum("i,ij->j", charges, coords) / \ - charges.sum() - elif gauge_origin == "origin": - self.gauge_origin = [0.0, 0.0, 0.0] - elif type(gauge_origin) == list: - self.gauge_origin = gauge_origin + + + def _update_gauge_origin(self, gauge_origin_string): + coords = self.scfres.mol.atom_coords() + mass = self.scfres.mol.atom_mass_list() + charges = self.scfres.mol.atom_charges() + if gauge_origin_string == "mass_center": + gauge_origin = list(np.einsum("i,ij->j", mass, coords) / \ + mass.sum()) + elif gauge_origin_string == "charge_center": + gauge_origin = list(np.einsum("i,ij->j", charges, coords) / \ + charges.sum()) + elif gauge_origin_string == "origin": + gauge_origin = [0.0, 0.0, 0.0] + elif type(gauge_origin_string) == list: + gauge_origin = gauge_origin_string else: raise NotImplementedError("Gauge origin has to be defined either by" " using one of the keywords" " mass_center, charge_center or origin." " Or by declaring a list eg. [x, y, z]" " in atomic units.") + return gauge_origin @cached_property def electric_dipole(self): return list(self.scfres.mol.intor_symmetric('int1e_r', comp=3)) - @cached_property + @property def magnetic_dipole(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - return list( - 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) - ) + def magnetic_dipole_gauge(gauge_string): + gauge_origin = self._update_gauge_origin(gauge_string) + with self.scfres.mol.with_common_orig(gauge_origin): + return list( + 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) + ) + return magnetic_dipole_gauge - @cached_property + @property def nabla(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - return list( - -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) + def nabla_gauge(gauge_string): + gauge_origin = self._update_gauge_origin(gauge_string) + with self.scfres.mol.with_common_orig(gauge_origin): + # with self.scfres.mol.with_common_orig(self.gauge_origin): + return list( + -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) + return nabla_gauge - @cached_property + @property def dia_magnet(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) - r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) - for i in range(3): - r_quadr_matrix[i][i] = r_quadr - term = -0.25 * (r_quadr_matrix - r_r) - term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) - return list(term) + def dia_magnet_gauge(gauge_string): + gauge_origin = self._update_gauge_origin(gauge_string) + with self.scfres.mol.with_common_orig(gauge_origin): + # with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) + for i in range(3): + r_quadr_matrix[i][i] = r_quadr + term = -0.25 * (r_quadr_matrix - r_r) + term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + return list(term) + return dia_magnet_gauge - @cached_property + @property def electric_quadrupole_traceless(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) - r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) - for i in range(3): - r_quadr_matrix[i][i] = r_quadr - term = 0.5 * (3 * r_r - r_quadr_matrix) - term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) - return list(term) + def electric_quadrupole_traceless_gauge(gauge_string): + gauge_origin = self._update_gauge_origin(gauge_string) + with self.scfres.mol.with_common_orig(gauge_origin): + # with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) + for i in range(3): + r_quadr_matrix[i][i] = r_quadr + term = 0.5 * (3 * r_r - r_quadr_matrix) + term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + return list(term) + return electric_quadrupole_traceless_gauge - @cached_property + @property def electric_quadrupole(self): - with self.scfres.mol.with_common_orig(self.gauge_origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) - return list(r_r) + def electric_quadrupole_gauge(gauge_string): + gauge_origin = self._update_gauge_origin(gauge_string) + with self.scfres.mol.with_common_orig(gauge_origin): + # with self.scfres.mol.with_common_orig(self.gauge_origin): + r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + return list(r_r) + return electric_quadrupole_gauge @property def pe_induction_elec(self): @@ -176,18 +200,17 @@ class PyScfHFProvider(HartreeFockProvider): This implementation is only valid if no orbital reordering is required. """ - def __init__(self, scfres, gauge_origin): + def __init__(self, scfres): # Do not forget the next line, # otherwise weird errors result super().__init__() self.scfres = scfres - self.gauge_origin = gauge_origin n_alpha, n_beta = scfres.mol.nelec self.eri_builder = PyScfEriBuilder(self.scfres, self.n_orbs, self.n_orbs_alpha, n_alpha, n_beta, self.restricted) self.operator_integral_provider = PyScfOperatorIntegralProvider( - self.scfres, gauge_origin + self.scfres ) if not self.restricted: assert self.scfres.mo_coeff[0].shape[1] == \ @@ -307,19 +330,19 @@ def get_nuclear_multipole(self, order): # electric quadrupole Q_jk = sum_i (q_i * r_ij *r_ik ) coords = self.scfres.mol.atom_coords() mass = self.scfres.mol.atom_mass_list() - if self.gauge_origin == "mass_center": - mass_center = np.einsum("i,ij->j", mass, coords) / mass.sum() - coords = coords - mass_center - elif self.gauge_origin == "charge_center": - charge_center = np.einsum("i,ij->j", charges, coords) / \ - charges.sum() - coords = coords - charge_center - elif self.gauge_origin == "origin": - coords = coords - elif type(self.gauge_origin) == list: - coords = coords - self.gauge_origin - else: - raise NotImplementedError() + # if self.gauge_origin == "mass_center": + # mass_center = np.einsum("i,ij->j", mass, coords) / mass.sum() + # coords = coords - mass_center + # elif self.gauge_origin == "charge_center": + # charge_center = np.einsum("i,ij->j", charges, coords) / \ + # charges.sum() + # coords = coords - charge_center + # elif self.gauge_origin == "origin": + # coords = coords + # elif type(self.gauge_origin) == list: + # coords = coords - self.gauge_origin + # else: + # raise NotImplementedError() r_r = np.einsum("ij,ik->ijk", coords, coords) res = np.einsum("i,ijk->jk", charges, r_r) res = np.array([res[0][0], res[0][1], res[0][2], @@ -372,7 +395,7 @@ def flush_cache(self): self.eri_builder.flush_cache() -def import_scf(scfres, gauge_origin): +def import_scf(scfres): # TODO The error messages here could be a bit more verbose if not isinstance(scfres, scf.hf.SCF): @@ -387,7 +410,7 @@ def import_scf(scfres, gauge_origin): # TODO Check for point-group symmetry, # check for density-fitting or choleski - return PyScfHFProvider(scfres, gauge_origin) + return PyScfHFProvider(scfres) def run_hf(xyz, basis, charge=0, multiplicity=1, conv_tol=1e-11, diff --git a/adcc/backends/testing.py b/adcc/backends/testing.py index d41261df..770afc92 100644 --- a/adcc/backends/testing.py +++ b/adcc/backends/testing.py @@ -150,7 +150,14 @@ def operator_import_from_ao_test(scfres, ao_dict, operator="electric_dipole"): virta = refstate.orbital_coefficients_alpha("v1b").to_ndarray() virtb = refstate.orbital_coefficients_beta("v1b").to_ndarray() - dip_imported = getattr(refstate.operators, operator) + if operator == 'magnetic_dipole': + callback = refstate.operators.magnetic_dipole + dip_imported = callback('origin') + elif operator == 'nabla': + callback = refstate.operators.nabla + dip_imported = callback('origin') + else: + dip_imported = getattr(refstate.operators, operator) for i, ao_component in enumerate(ao_dict): dip_oo = np.einsum('ib,ba,ja->ij', occa, ao_component, occa) diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index ad17bd33..2f9b16d0 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -58,20 +58,30 @@ def electric_dipole(self): @cached_property def magnetic_dipole(self): - # TODO: Gauge origin? - task = self.scfdrv.task - angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) - angmom_mats = angmom_drv.compute(task.molecule, task.ao_basis) - return (0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), - 0.5 * angmom_mats.z_to_numpy()) + def gauge_dependent_integrals(gauge_origin): + # TODO: Gauge origin? + if gauge_origin != 'origin': + raise NotImplementedError('Only origin can be selected.') + # TODO: Gauge origin? + task = self.scfdrv.task + angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) + angmom_mats = angmom_drv.compute(task.molecule, task.ao_basis) + return (0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), + 0.5 * angmom_mats.z_to_numpy()) + return gauge_dependent_integrals @cached_property def nabla(self): - task = self.scfdrv.task - linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) - linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) - return (-1.0 * linmom_mats.x_to_numpy(), -1.0 * linmom_mats.y_to_numpy(), - -1.0 * linmom_mats.z_to_numpy()) + def gauge_dependent_integrals(gauge_origin): + # TODO: Gauge origin? + if gauge_origin != 'origin': + raise NotImplementedError('Only origin can be selected.') + task = self.scfdrv.task + linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) + linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) + return (-1.0 * linmom_mats.x_to_numpy(), -1.0 * linmom_mats.y_to_numpy(), + -1.0 * linmom_mats.z_to_numpy()) + return gauge_dependent_integrals # VeloxChem is a special case... not using coefficients at all diff --git a/adcc/test_ExcitedStates.py b/adcc/test_ExcitedStates.py index b4d1e0b3..a3d432d9 100644 --- a/adcc/test_ExcitedStates.py +++ b/adcc/test_ExcitedStates.py @@ -51,6 +51,10 @@ def base_test(self, system, method, kind): except NotImplementedError: # nabla, etc. not implemented in dict backend continue + except TypeError: + # gauge origin dependent properties are collables + ref = getattr(state, key)()[i] + res = getattr(exci, key)() if isinstance(ref, OneParticleOperator): assert ref.blocks == res.blocks for b in ref.blocks: diff --git a/adcc/test_IsrMatrix.py b/adcc/test_IsrMatrix.py index e1596d01..21d6c63e 100644 --- a/adcc/test_IsrMatrix.py +++ b/adcc/test_IsrMatrix.py @@ -46,7 +46,7 @@ def template_matrix_vector_product(self, case, method, op_kind): if op_kind == "electric": # example of a symmetric operator dips = state.reference_state.operators.electric_dipole elif op_kind == "magnetic": # example of an asymmetric operator - dips = state.reference_state.operators.magnetic_dipole + dips = state.reference_state.operators.magnetic_dipole('origin') else: skip("Tests are only implemented for electric " "and magnetic dipole operators.") @@ -68,7 +68,7 @@ def template_matrix_vector_product(self, case, method, op_kind): if op_kind == "electric": s2s_tdm_ref = state2state.transition_dipole_moment[j] else: - s2s_tdm_ref = state2state.transition_magnetic_dipole_moment[j] + s2s_tdm_ref = state2state.transition_magnetic_dipole_moment('origin')[j] np.testing.assert_allclose(s2s_tdm, s2s_tdm_ref, atol=1e-12) @@ -81,7 +81,7 @@ def test_matvec(self): state = cache.adc_states[system][method][kind] mp = state.ground_state dips = state.reference_state.operators.electric_dipole - magdips = state.reference_state.operators.magnetic_dipole + magdips = state.reference_state.operators.magnetic_dipole('origin') vecs = [exc.excitation_vector for exc in state.excitations[:2]] matrix_ref = IsrMatrix(method, mp, dips) diff --git a/adcc/test_hardcoded.py b/adcc/test_hardcoded.py index d1001799..9a6851b3 100644 --- a/adcc/test_hardcoded.py +++ b/adcc/test_hardcoded.py @@ -48,6 +48,6 @@ def test_methox_sto3g_singlet(self): np.testing.assert_allclose(vlx_result['excitation_energies'], state.excitation_energy, atol=1e-6) np.testing.assert_allclose(vlx_result['rotatory_strengths'], - state.rotatory_strength, atol=1e-4) + state.rotatory_strength('origin'), atol=1e-4) np.testing.assert_allclose(vlx_result['oscillator_strengths'], state.oscillator_strength, atol=1e-4) diff --git a/adcc/test_properties.py b/adcc/test_properties.py index 19e13777..8ff962dc 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -78,7 +78,7 @@ def base_test(self, system, method, kind): kind = "any" if kind == "state" else kind refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - + res_oscs = state.oscillator_strength ref_tdms = refdata[method][kind]["transition_dipole_moments"] refevals = refdata[method][kind]["eigenvalues"] @@ -127,13 +127,13 @@ def base_test(self, system, method, kind): basemethods = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = [m for bm in basemethods for m in [bm, "cvs_" + bm]] gauge_origins = ["origin", "mass_center", "charge_center"] +backends = ['pyscf'] - -@expand_test_templates(list(itertools.product(methods, gauge_origins))) +@expand_test_templates(list(itertools.product(backends, methods, gauge_origins))) class TestMagneticTransitionDipoleMoments(unittest.TestCase): - def template_linear_molecule(self, method, gauge_origin): + def template_linear_molecule(self, backend, method, gauge_origin): method = method.replace("_", "-") - backend = available()[0] + xyz = """ C 0 0 0 O 0 0 2.7023 @@ -146,12 +146,10 @@ def template_linear_molecule(self, method, gauge_origin): scfres = run_hf(backend, xyz, basis) if "cvs" in method: - state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2, - gauge_origin=gauge_origin) + state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) else: - state = run_adc(scfres, method=method, n_singlets=10, - gauge_origin=gauge_origin) - tdms = state.transition_magnetic_dipole_moment + state = run_adc(scfres, method=method, n_singlets=10) + tdms = state.transition_magnetic_dipole_moment(gauge_origin) # For linear molecules lying on the z-axis, the z-component must be zero for tdm in tdms: diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 0d69e448..07b9ee6c 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -40,8 +40,12 @@ def base_test(self, system, method, kind): for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - - res_dms = state.transition_magnetic_dipole_moment + # TODO: change ref data + if 'center' in system: + gauge_origin = '_'.join(system.split('_')[-2:]) + else: + gauge_origin = 'origin' + res_dms = state.transition_magnetic_dipole_moment(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( @@ -58,8 +62,12 @@ def base_test(self, system, method, kind): for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - - res_dms = state.transition_dipole_moment_velocity + if 'center' in system: + print('_'.join(system.split('_')[:-2])) + gauge_origin = '_'.join(system.split('_')[-2:]) + else: + gauge_origin = 'origin' + res_dms = state.transition_dipole_moment_velocity(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( @@ -76,13 +84,21 @@ def base_test(self, system, method, kind): for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - - res_dms = state.transition_quadrupole_moment + if 'center' in system: + gauge_origin = '_'.join(system.split('_')[-2:]) + else: + gauge_origin = 'origin' + res_dms = state.transition_quadrupole_moment(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) + print('*'*80) + print(system) + print(res_dms) + print(ref["transition_quadrupole_moments"][:n_ref]) + print('*'*80) assert_allclose( res_dms, ref["transition_quadrupole_moments"][:n_ref], atol=1e-4 - ) + ) class TestRotatoryStrengths(unittest.TestCase, Runners): @@ -93,8 +109,12 @@ def base_test(self, system, method, kind): for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - - res_rots = state.rotatory_strength + if 'center' in system: + print('_'.join(system.split('_')[:-2])) + gauge_origin = '_'.join(system.split('_')[-2:]) + else: + gauge_origin = 'origin' + res_rots = state.rotatory_strength(gauge_origin) ref_tmdm = refdata[method][kind]["transition_magnetic_dipole_moments"] ref_tdmvel = refdata[method][kind]["transition_dipole_moments_velocity"] refevals = refdata[method][kind]["eigenvalues"] diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index edd38095..3f6493d0 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -137,16 +137,7 @@ def cache_eri(refstate): return refstate ret = {} for k in self.testcases + self.testcases_gauge_origin: - if k in self.testcases: - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin="origin")) - else: - if k.endswith("mass_center"): - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin="mass_center")) - else: - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k], - gauge_origin="charge_center")) + ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k])) return ret # return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) @@ -162,19 +153,8 @@ def refstate_cvs(self): refcases = ast.literal_eval(self.hfdata[case]["reference_cases"]) if "cvs" not in refcases: continue - if case in self.testcases: - ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"], - gauge_origin="origin") - else: - if case.endswith("mass_center"): - ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"], - gauge_origin="mass_center") - else: - ret[case] = adcc.ReferenceState(self.hfdata[case], - **refcases["cvs"], - gauge_origin="charge_center") + ret[case] = adcc.ReferenceState(self.hfdata[case], + **refcases["cvs"]) ret[case].import_all() return ret @@ -182,18 +162,7 @@ def refstate_nocache(self, case, spec): # TODO once hfdata is an HDF5 file # refcases = ast.literal_eval(self.hfdata[case]["reference_cases"][()]) refcases = ast.literal_eval(self.hfdata[case]["reference_cases"]) - if case in self.testcases: - return adcc.ReferenceState(self.hfdata[case], **refcases[spec], - gauge_origin="origin") - else: - if case.endswith("mass_center"): - return adcc.ReferenceState(self.hfdata[case], - **refcases[spec], - gauge_origin="mass_center") - else: - return adcc.ReferenceState(self.hfdata[case], - **refcases[spec], - gauge_origin="charge_center") + return adcc.ReferenceState(self.hfdata[case], **refcases[spec]) @cached_property def hfimport(self): @@ -204,7 +173,7 @@ def hfimport(self): ret[k] = hdf5io.load(datafile) return ret - def read_reference_data(self, refname, read_gauge=False): + def read_reference_data(self, refname): prefixes = ["", "cvs", "fc", "fv", "fc_cvs", "fv_cvs", "fc_fv", "fc_fv_cvs"] raws = ["adc0", "adc1", "adc2", "adc2x", "adc3"] diff --git a/adcc/workflow.py b/adcc/workflow.py index e42d82be..7cd94496 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -47,7 +47,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, n_guesses_doubles=None, output=sys.stdout, core_orbitals=None, frozen_core=None, frozen_virtual=None, method=None, n_singlets=None, n_triplets=None, n_spin_flip=None, - environment=None, gauge_origin="origin", **solverargs): + environment=None, **solverargs): """Run an ADC calculation. Main entry point to run an ADC calculation. The reference to build the ADC @@ -133,13 +133,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, The keywords to specify how coupling to an environment model, e.g. PE, is treated. For details see :ref:`environment`. - gauge_origin: list or str, optional - Define the gauge origin for operator integrals. - Either by specifying a list in atomic units directly ([x,y,z]) - or by using one of the keywords (mass_center, charge_center, origin). - Default: origin - - Other parameters + Other parameters ---------------- max_subspace : int, optional Maximal subspace size @@ -184,7 +178,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, """ matrix = construct_adcmatrix( data_or_matrix, core_orbitals=core_orbitals, frozen_core=frozen_core, - frozen_virtual=frozen_virtual, method=method, gauge_origin=gauge_origin) + frozen_virtual=frozen_virtual, method=method) n_states, kind = validate_state_parameters( matrix.reference_state, n_states=n_states, n_singlets=n_singlets, @@ -225,7 +219,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, # Individual steps # def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, - frozen_virtual=None, method=None, gauge_origin="origin"): + frozen_virtual=None, method=None): """ Use the provided data or AdcMatrix object to check consistency of the other passed parameters and construct the AdcMatrix object representing @@ -251,8 +245,7 @@ def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None, refstate = adcc_ReferenceState(data_or_matrix, core_orbitals=core_orbitals, frozen_core=frozen_core, - frozen_virtual=frozen_virtual, - gauge_origin=gauge_origin) + frozen_virtual=frozen_virtual) except ValueError as e: raise InputError(str(e)) # In case of an issue with the spaces data_or_matrix = refstate From 70d9f9026e9ab4d6451bd57db6043013e32743ec Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 19 Sep 2024 13:35:29 +0200 Subject: [PATCH 17/42] clean up code, pyscf nuclear mutlipoles --- adcc/ElectronicTransition.py | 16 +++++-------- adcc/ReferenceState.py | 1 - adcc/backends/pyscf.py | 37 +++++++---------------------- adcc/backends/veloxchem.py | 3 ++- adcc/test_ExcitedStates.py | 2 +- adcc/test_IsrMatrix.py | 3 ++- adcc/test_properties.py | 7 +++--- adcc/test_properties_consistency.py | 13 +++------- adcc/workflow.py | 2 +- 9 files changed, 28 insertions(+), 56 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 5f80d73f..fcbc113c 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -165,7 +165,6 @@ def transition_dipole_moment(self): for tdm in self.transition_dm ]) - # @property @mark_excitation_property() @timed_member_call(timer="_property_timer") def transition_dipole_moment_velocity(self, gauge_string): @@ -180,7 +179,6 @@ def transition_dipole_moment_velocity(self, gauge_string): for tdm in self.transition_dm ]) - # @cached_property @mark_excitation_property() @timed_member_call(timer="_property_timer") def transition_magnetic_dipole_moment(self, gauge_string): @@ -194,7 +192,6 @@ def transition_magnetic_dipole_moment(self, gauge_string): for tdm in self.transition_dm ]) - # @cached_property @mark_excitation_property() @timed_member_call(timer="_property_timer") def transition_quadrupole_moment(self, gauge_string): @@ -202,8 +199,8 @@ def transition_quadrupole_moment(self, gauge_string): if self.property_method.level == 0: warnings.warn("ADC(0) transition dipole moments are known to be " "faulty in some cases.") - operator_integrals = np.array(self.operators.electric_quadrupole(gauge_string)) - print(operator_integrals[0]) + operator_integrals = np.array( + self.operators.electric_quadrupole(gauge_string)) return np.array([ [[product_trace(quad1, tdm) for quad1 in quad] for quad in operator_integrals] @@ -220,7 +217,6 @@ def oscillator_strength(self): self.excitation_energy) ]) - # @cached_property @mark_excitation_property() def oscillator_strength_velocity(self, gauge_string): """List of oscillator strengths in @@ -231,15 +227,15 @@ def oscillator_strength_velocity(self, gauge_string): self.excitation_energy) ]) - # @property @mark_excitation_property() def rotatory_strength(self, gauge_string): """List of rotatory strengths of all computed states""" return np.array([ np.dot(tdm, magmom) / ee - for tdm, magmom, ee in zip(self.transition_dipole_moment_velocity(gauge_string), - self.transition_magnetic_dipole_moment(gauge_string), - self.excitation_energy) + for tdm, magmom, ee in zip( + self.transition_dipole_moment_velocity(gauge_string), + self.transition_magnetic_dipole_moment(gauge_string), + self.excitation_energy) ]) @property diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 28e5fcd3..1f1a4a56 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -224,5 +224,4 @@ def dipole_moment(self): # Notice the negative sign due to the negative charge of the electrons return self.nuclear_dipole - np.array([product_trace(comp, self.density) for comp in dipole_integrals]) - # TODO some nice describe method diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 7652a216..924cbf1f 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -21,7 +21,6 @@ ## ## --------------------------------------------------------------------- import numpy as np -import warnings from libadcc import HartreeFockProvider from adcc.misc import cached_property @@ -39,20 +38,18 @@ def __init__(self, scfres): self.scfres = scfres self.backend = "pyscf" - def _update_gauge_origin(self, gauge_origin_string): coords = self.scfres.mol.atom_coords() mass = self.scfres.mol.atom_mass_list() charges = self.scfres.mol.atom_charges() if gauge_origin_string == "mass_center": - gauge_origin = list(np.einsum("i,ij->j", mass, coords) / \ - mass.sum()) + gauge_origin = list(np.einsum("i,ij->j", mass, coords) / mass.sum()) elif gauge_origin_string == "charge_center": - gauge_origin = list(np.einsum("i,ij->j", charges, coords) / \ - charges.sum()) + gauge_origin = list(np.einsum("i,ij->j", charges, coords) + / charges.sum()) elif gauge_origin_string == "origin": gauge_origin = [0.0, 0.0, 0.0] - elif type(gauge_origin_string) == list: + elif isinstance(gauge_origin_string, list): gauge_origin = gauge_origin_string else: raise NotImplementedError("Gauge origin has to be defined either by" @@ -81,7 +78,6 @@ def nabla(self): def nabla_gauge(gauge_string): gauge_origin = self._update_gauge_origin(gauge_string) with self.scfres.mol.with_common_orig(gauge_origin): - # with self.scfres.mol.with_common_orig(self.gauge_origin): return list( -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) @@ -92,11 +88,11 @@ def dia_magnet(self): def dia_magnet_gauge(gauge_string): gauge_origin = self._update_gauge_origin(gauge_string) with self.scfres.mol.with_common_orig(gauge_origin): - # with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], + r_quadr.shape[0])) for i in range(3): r_quadr_matrix[i][i] = r_quadr term = -0.25 * (r_quadr_matrix - r_r) @@ -109,11 +105,11 @@ def electric_quadrupole_traceless(self): def electric_quadrupole_traceless_gauge(gauge_string): gauge_origin = self._update_gauge_origin(gauge_string) with self.scfres.mol.with_common_orig(gauge_origin): - # with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], r_quadr.shape[0])) + r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], + r_quadr.shape[0])) for i in range(3): r_quadr_matrix[i][i] = r_quadr term = 0.5 * (3 * r_r - r_quadr_matrix) @@ -126,7 +122,6 @@ def electric_quadrupole(self): def electric_quadrupole_gauge(gauge_string): gauge_origin = self._update_gauge_origin(gauge_string) with self.scfres.mol.with_common_orig(gauge_origin): - # with self.scfres.mol.with_common_orig(self.gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) return list(r_r) return electric_quadrupole_gauge @@ -327,22 +322,8 @@ def get_nuclear_multipole(self, order): coords = self.scfres.mol.atom_coords() return np.einsum("i,ix->x", charges, coords) elif order == 2: - # electric quadrupole Q_jk = sum_i (q_i * r_ij *r_ik ) + # TODO: gauge-origin coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() - # if self.gauge_origin == "mass_center": - # mass_center = np.einsum("i,ij->j", mass, coords) / mass.sum() - # coords = coords - mass_center - # elif self.gauge_origin == "charge_center": - # charge_center = np.einsum("i,ij->j", charges, coords) / \ - # charges.sum() - # coords = coords - charge_center - # elif self.gauge_origin == "origin": - # coords = coords - # elif type(self.gauge_origin) == list: - # coords = coords - self.gauge_origin - # else: - # raise NotImplementedError() r_r = np.einsum("ij,ik->ijk", coords, coords) res = np.einsum("i,ijk->jk", charges, r_r) res = np.array([res[0][0], res[0][1], res[0][2], diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 2f9b16d0..ca150a3a 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -79,7 +79,8 @@ def gauge_dependent_integrals(gauge_origin): task = self.scfdrv.task linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) - return (-1.0 * linmom_mats.x_to_numpy(), -1.0 * linmom_mats.y_to_numpy(), + return (-1.0 * linmom_mats.x_to_numpy(), + -1.0 * linmom_mats.y_to_numpy(), -1.0 * linmom_mats.z_to_numpy()) return gauge_dependent_integrals diff --git a/adcc/test_ExcitedStates.py b/adcc/test_ExcitedStates.py index a3d432d9..80b51849 100644 --- a/adcc/test_ExcitedStates.py +++ b/adcc/test_ExcitedStates.py @@ -52,7 +52,7 @@ def base_test(self, system, method, kind): # nabla, etc. not implemented in dict backend continue except TypeError: - # gauge origin dependent properties are collables + # gauge origin dependent properties are callables ref = getattr(state, key)()[i] res = getattr(exci, key)() if isinstance(ref, OneParticleOperator): diff --git a/adcc/test_IsrMatrix.py b/adcc/test_IsrMatrix.py index 21d6c63e..38751213 100644 --- a/adcc/test_IsrMatrix.py +++ b/adcc/test_IsrMatrix.py @@ -68,7 +68,8 @@ def template_matrix_vector_product(self, case, method, op_kind): if op_kind == "electric": s2s_tdm_ref = state2state.transition_dipole_moment[j] else: - s2s_tdm_ref = state2state.transition_magnetic_dipole_moment('origin')[j] + s2s_tdm_ref = \ + state2state.transition_magnetic_dipole_moment('origin')[j] np.testing.assert_allclose(s2s_tdm, s2s_tdm_ref, atol=1e-12) diff --git a/adcc/test_properties.py b/adcc/test_properties.py index 8ff962dc..d8b32eb1 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -28,7 +28,7 @@ from adcc.State2States import State2States from adcc.testdata.cache import cache -from adcc.backends import run_hf, available +from adcc.backends import run_hf from adcc import run_adc from .misc import assert_allclose_signfix, expand_test_templates @@ -78,7 +78,7 @@ def base_test(self, system, method, kind): kind = "any" if kind == "state" else kind refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - + res_oscs = state.oscillator_strength ref_tdms = refdata[method][kind]["transition_dipole_moments"] refevals = refdata[method][kind]["eigenvalues"] @@ -129,11 +129,12 @@ def base_test(self, system, method, kind): gauge_origins = ["origin", "mass_center", "charge_center"] backends = ['pyscf'] + @expand_test_templates(list(itertools.product(backends, methods, gauge_origins))) class TestMagneticTransitionDipoleMoments(unittest.TestCase): def template_linear_molecule(self, backend, method, gauge_origin): method = method.replace("_", "-") - + xyz = """ C 0 0 0 O 0 0 2.7023 diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 07b9ee6c..609f18cd 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -40,7 +40,7 @@ def base_test(self, system, method, kind): for system in systems_gauge_origin: refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] - # TODO: change ref data + # TODO: change ref data if 'center' in system: gauge_origin = '_'.join(system.split('_')[-2:]) else: @@ -63,7 +63,6 @@ def base_test(self, system, method, kind): refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] if 'center' in system: - print('_'.join(system.split('_')[:-2])) gauge_origin = '_'.join(system.split('_')[-2:]) else: gauge_origin = 'origin' @@ -86,19 +85,14 @@ def base_test(self, system, method, kind): state = cache.adcc_states[system][method][kind] if 'center' in system: gauge_origin = '_'.join(system.split('_')[-2:]) + continue # No reference data for mass_center and charge_center else: gauge_origin = 'origin' res_dms = state.transition_quadrupole_moment(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) - print('*'*80) - print(system) - print(res_dms) - print(ref["transition_quadrupole_moments"][:n_ref]) - print('*'*80) assert_allclose( - res_dms, ref["transition_quadrupole_moments"][:n_ref], atol=1e-4 - ) + res_dms, ref["transition_quadrupole_moments"][:n_ref], atol=1e-4) class TestRotatoryStrengths(unittest.TestCase, Runners): @@ -110,7 +104,6 @@ def base_test(self, system, method, kind): refdata = cache.adcc_reference_data[system] state = cache.adcc_states[system][method][kind] if 'center' in system: - print('_'.join(system.split('_')[:-2])) gauge_origin = '_'.join(system.split('_')[-2:]) else: gauge_origin = 'origin' diff --git a/adcc/workflow.py b/adcc/workflow.py index 7cd94496..a290032e 100644 --- a/adcc/workflow.py +++ b/adcc/workflow.py @@ -133,7 +133,7 @@ def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None, The keywords to specify how coupling to an environment model, e.g. PE, is treated. For details see :ref:`environment`. - Other parameters + Other parameters ---------------- max_subspace : int, optional Maximal subspace size From dfeb7cbc699106edfa273e7ba9938428e72b0740 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 19 Sep 2024 14:15:43 +0200 Subject: [PATCH 18/42] remove debug prints --- adcc/DataHfProvider.py | 1 - adcc/ReferenceState.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index d62a499e..b89928bf 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -209,7 +209,6 @@ def get_integral_quad(gauge_origin): if gauge_origin != 'origin': integral_string = f'{integral_string}_{gauge_origin}' return np.asarray(mmp[integral_string]) - print('hello') if mmp["elec_2"].shape != (9, nb, nb): raise ValueError("multipoles/elec_2 is expected to have " "shape " + str((9, nb, nb)) + " not " diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 1f1a4a56..a3274c61 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -224,4 +224,5 @@ def dipole_moment(self): # Notice the negative sign due to the negative charge of the electrons return self.nuclear_dipole - np.array([product_trace(comp, self.density) for comp in dipole_integrals]) + # TODO some nice describe method From 56ece3f2cc4fccfdb519a1de8531afc1113be9b6 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 11 Oct 2024 10:12:38 +0200 Subject: [PATCH 19/42] Github comments part 1 --- adcc/ElectronicTransition.py | 27 +++++++++++++------------- adcc/OperatorIntegrals.py | 13 +++++++++---- adcc/backends/psi4.py | 10 ++++++---- adcc/backends/pyscf.py | 37 ++++++++++++++++++------------------ adcc/backends/veloxchem.py | 11 ++++++----- 5 files changed, 53 insertions(+), 45 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index fcbc113c..47361110 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -167,13 +167,13 @@ def transition_dipole_moment(self): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_dipole_moment_velocity(self, gauge_string): + def transition_dipole_moment_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): """List of transition dipole moments in the velocity gauge of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition velocity dipole moments " "are known to be faulty in some cases.") - dipole_integrals = self.operators.nabla(gauge_string) + dipole_integrals = self.operators.nabla(gauge_origin) return np.array([ [product_trace(comp, tdm) for comp in dipole_integrals] for tdm in self.transition_dm @@ -181,12 +181,12 @@ def transition_dipole_moment_velocity(self, gauge_string): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_magnetic_dipole_moment(self, gauge_string): + def transition_magnetic_dipole_moment(self, gauge_origin = [0.0, 0.0, 0.0]): """List of transition magnetic dipole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition magnetic dipole moments " "are known to be faulty in some cases.") - mag_dipole_integrals = self.operators.magnetic_dipole(gauge_string) + mag_dipole_integrals = self.operators.magnetic_dipole(gauge_origin) return np.array([ [product_trace(comp, tdm) for comp in mag_dipole_integrals] for tdm in self.transition_dm @@ -194,16 +194,15 @@ def transition_magnetic_dipole_moment(self, gauge_string): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_quadrupole_moment(self, gauge_string): + def transition_quadrupole_moment(self, gauge_origin = [0.0, 0.0, 0.0]): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: - warnings.warn("ADC(0) transition dipole moments are known to be " + warnings.warn("ADC(0) transition quadrupole moments are known to be " "faulty in some cases.") - operator_integrals = np.array( - self.operators.electric_quadrupole(gauge_string)) + quadrupole_integrals = self.operators.electric_quadrupole(gauge_origin) return np.array([ [[product_trace(quad1, tdm) - for quad1 in quad] for quad in operator_integrals] + for quad1 in quad] for quad in quadrupole_integrals] for tdm in self.transition_dm ]) @@ -218,23 +217,23 @@ def oscillator_strength(self): ]) @mark_excitation_property() - def oscillator_strength_velocity(self, gauge_string): + def oscillator_strength_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): """List of oscillator strengths in velocity gauge of all computed states""" return 2. / 3. * np.array([ np.linalg.norm(tdm)**2 / np.abs(ev) - for tdm, ev in zip(self.transition_dipole_moment_velocity(gauge_string), + for tdm, ev in zip(self.transition_dipole_moment_velocity(gauge_origin), self.excitation_energy) ]) @mark_excitation_property() - def rotatory_strength(self, gauge_string): + def rotatory_strength(self, gauge_origin = [0.0, 0.0, 0.0]): """List of rotatory strengths of all computed states""" return np.array([ np.dot(tdm, magmom) / ee for tdm, magmom, ee in zip( - self.transition_dipole_moment_velocity(gauge_string), - self.transition_magnetic_dipole_moment(gauge_string), + self.transition_dipole_moment_velocity(gauge_origin), + self.transition_magnetic_dipole_moment(gauge_origin), self.excitation_energy) ]) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index b0979f89..57b08890 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -138,14 +138,14 @@ def __import_gauge_dependent_dipole_like(self, callback, is_symmetric=True): ---------- callback : callable Function that computes the operator in atomic orbitals using - a the gauge-origin (str or list) as single argument + the gauge-origin (str or list) as single argument is_symmetric : bool, optional if the imported operator is symmetric, by default True """ if not callable(callback): raise TypeError - def process_operator(gauge_origin, callback=callback, + def process_operator(gauge_origin = [0.0, 0.0, 0.0], callback=callback, is_symmetric=is_symmetric): dipoles = [] for i, component in enumerate(["x", "y", "z"]): @@ -173,6 +173,7 @@ def magnetic_dipole(self): """ Returns a function to obtain magnetic dipole intergrals in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. """ callback = self.provider_ao.magnetic_dipole return self.__import_gauge_dependent_dipole_like(callback, @@ -184,6 +185,7 @@ def nabla(self): """ Returns a function to obtain nabla intergrals in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. """ callback = self.provider_ao.nabla return self.__import_gauge_dependent_dipole_like(callback, @@ -206,7 +208,7 @@ def __import_quadrupole_like_operator(self, callback, is_symmetric=True): if not callable(callback): raise TypeError - def process_operator(gauge_origin, callback=callback, + def process_operator(gauge_origin = [0.0, 0.0, 0.0], callback=callback, is_symmetric=is_symmetric): quad = [] quadrupoles = [] @@ -234,6 +236,7 @@ def electric_quadrupole_traceless(self): """ Returns a function to obtain traceless electric quadrupole integrals in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. """ callback = self.provider_ao.electric_quadrupole_traceless return self.__import_quadrupole_like_operator(callback, is_symmetric=False) @@ -244,6 +247,7 @@ def electric_quadrupole(self): """ Returns a function to obtain electric quadrupole integrals in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. """ callback = self.provider_ao.electric_quadrupole return self.__import_quadrupole_like_operator(callback, is_symmetric=False) @@ -254,9 +258,10 @@ def dia_magnet(self): """ Returns a function to obtain diamagnetic magnetizability integrals in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. """ callback = self.provider_ao.dia_magnet - return self.__import_gauge_dependent(callback, is_symmetric=False) + return self.__import_quadrupole_like_operator(callback, is_symmetric=False) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index aa82c92b..5ad9f556 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -51,8 +51,9 @@ def electric_dipole(self): def magnetic_dipole(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != 'origin': - raise NotImplementedError('Only origin can be selected.') + if gauge_origin != [0.0, 0.0, 0.0]: + raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' + ' gauge origin.') return [ 0.5 * np.asarray(comp) for comp in self.mints.ao_angular_momentum() @@ -62,8 +63,9 @@ def gauge_dependent_integrals(gauge_origin): @cached_property def nabla(self): def gauge_dependent_integrals(gauge_origin): - if gauge_origin != 'origin': - raise NotImplementedError('Only origin can be selected.') + if gauge_origin != [0.0, 0.0, 0.0]: + raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' + ' gauge origin.') return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] return gauge_dependent_integrals diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 924cbf1f..b4baebcf 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -38,25 +38,26 @@ def __init__(self, scfres): self.scfres = scfres self.backend = "pyscf" - def _update_gauge_origin(self, gauge_origin_string): + def _update_gauge_origin(self, gauge_origin): coords = self.scfres.mol.atom_coords() - mass = self.scfres.mol.atom_mass_list() + masses = self.scfres.mol.atom_mass_list() charges = self.scfres.mol.atom_charges() - if gauge_origin_string == "mass_center": - gauge_origin = list(np.einsum("i,ij->j", mass, coords) / mass.sum()) - elif gauge_origin_string == "charge_center": + if gauge_origin == "mass_center": + gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) + elif gauge_origin == "charge_center": gauge_origin = list(np.einsum("i,ij->j", charges, coords) / charges.sum()) - elif gauge_origin_string == "origin": + elif gauge_origin == "origin": gauge_origin = [0.0, 0.0, 0.0] - elif isinstance(gauge_origin_string, list): - gauge_origin = gauge_origin_string + elif isinstance(gauge_origin, list): + gauge_origin = gauge_origin else: raise NotImplementedError("Gauge origin has to be defined either by" " using one of the keywords" " mass_center, charge_center or origin." " Or by declaring a list eg. [x, y, z]" " in atomic units.") + print(gauge_origin) return gauge_origin @cached_property @@ -65,8 +66,8 @@ def electric_dipole(self): @property def magnetic_dipole(self): - def magnetic_dipole_gauge(gauge_string): - gauge_origin = self._update_gauge_origin(gauge_string) + def magnetic_dipole_gauge(gauge_origin): + gauge_origin = self._update_gauge_origin(gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) @@ -75,8 +76,8 @@ def magnetic_dipole_gauge(gauge_string): @property def nabla(self): - def nabla_gauge(gauge_string): - gauge_origin = self._update_gauge_origin(gauge_string) + def nabla_gauge(gauge_origin): + gauge_origin = self._update_gauge_origin(gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) @@ -85,8 +86,8 @@ def nabla_gauge(gauge_string): @property def dia_magnet(self): - def dia_magnet_gauge(gauge_string): - gauge_origin = self._update_gauge_origin(gauge_string) + def dia_magnet_gauge(gauge_origin): + gauge_origin = self._update_gauge_origin(gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) @@ -102,8 +103,8 @@ def dia_magnet_gauge(gauge_string): @property def electric_quadrupole_traceless(self): - def electric_quadrupole_traceless_gauge(gauge_string): - gauge_origin = self._update_gauge_origin(gauge_string) + def electric_quadrupole_traceless_gauge(gauge_origin): + gauge_origin = self._update_gauge_origin(gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) @@ -119,8 +120,8 @@ def electric_quadrupole_traceless_gauge(gauge_string): @property def electric_quadrupole(self): - def electric_quadrupole_gauge(gauge_string): - gauge_origin = self._update_gauge_origin(gauge_string) + def electric_quadrupole_gauge(gauge_origin): + gauge_origin = self._update_gauge_origin(gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) return list(r_r) diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index ca150a3a..35675081 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -60,9 +60,9 @@ def electric_dipole(self): def magnetic_dipole(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != 'origin': - raise NotImplementedError('Only origin can be selected.') - # TODO: Gauge origin? + if gauge_origin != [0.0, 0.0, 0.0]: + raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' + ' gauge origin.') task = self.scfdrv.task angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) angmom_mats = angmom_drv.compute(task.molecule, task.ao_basis) @@ -74,8 +74,9 @@ def gauge_dependent_integrals(gauge_origin): def nabla(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != 'origin': - raise NotImplementedError('Only origin can be selected.') + if gauge_origin != [0.0, 0.0, 0.0]: + raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' + ' gauge origin.') task = self.scfdrv.task linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) From e94b913e62855b0050b1b3d826c47b7189aed6ab Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 15 Oct 2024 14:52:08 +0200 Subject: [PATCH 20/42] Update Tests --- .../test_modified_transition_moments.py | 7 +- adcc/backends/psi4.py | 4 +- adcc/backends/test_backends_crossref.py | 8 +- adcc/backends/testing.py | 8 +- adcc/backends/veloxchem.py | 4 +- adcc/test_properties.py | 33 ++++---- adcc/test_properties_consistency.py | 65 +++++++--------- adcc/testdata/cache.py | 35 +++------ adcc/testdata/dump_reference_adcc.py | 25 +++++-- adcc/testdata/generate_hfdata_cn_sto3g.py | 6 +- adcc/testdata/generate_hfimport.py | 4 +- adcc/testdata/generate_reference.py | 75 +++++++------------ 12 files changed, 115 insertions(+), 159 deletions(-) diff --git a/adcc/adc_pp/test_modified_transition_moments.py b/adcc/adc_pp/test_modified_transition_moments.py index 511a9284..58f0c8ed 100644 --- a/adcc/adc_pp/test_modified_transition_moments.py +++ b/adcc/adc_pp/test_modified_transition_moments.py @@ -36,7 +36,7 @@ methods = [m for bm in basemethods for m in [bm, "cvs-" + bm]] operator_kinds = ["electric", "magnetic"] - +gauge_origins = ["origin", "mass_center", "charge_center"] @expand_test_templates(list(itertools.product(methods, operator_kinds))) class TestModifiedTransitionMoments(unittest.TestCase): @@ -49,8 +49,9 @@ def base_test(self, system, method, kind, op_kind): dips = state.reference_state.operators.electric_dipole ref_tdm = ref["transition_dipole_moments"] elif op_kind == "magnetic": - dips = state.reference_state.operators.magnetic_dipole('origin') - ref_tdm = ref["transition_magnetic_dipole_moments"] + for gauge_origin in gauge_origins: + dips = state.reference_state.operators.magnetic_dipole(gauge_origin) + ref_tdm = ref[f"transition_magnetic_dipole_moments_{gauge_origin}"] else: skip("Tests are only implemented for electric " "and magnetic dipole operators.") diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 5ad9f556..04d46293 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -51,7 +51,7 @@ def electric_dipole(self): def magnetic_dipole(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != [0.0, 0.0, 0.0]: + if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' ' gauge origin.') return [ @@ -63,7 +63,7 @@ def gauge_dependent_integrals(gauge_origin): @cached_property def nabla(self): def gauge_dependent_integrals(gauge_origin): - if gauge_origin != [0.0, 0.0, 0.0]: + if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' ' gauge origin.') return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] diff --git a/adcc/backends/test_backends_crossref.py b/adcc/backends/test_backends_crossref.py index f69031d0..90a95223 100644 --- a/adcc/backends/test_backends_crossref.py +++ b/adcc/backends/test_backends_crossref.py @@ -139,13 +139,13 @@ def compare_adc_results(adc_results, atol): if "nabla" in state1.operators.available and \ "nabla" in state2.operators.available: - assert_allclose(state1.oscillator_strength_velocity, - state2.oscillator_strength_velocity, atol=atol) + assert_allclose(state1.oscillator_strength_velocity(), + state2.oscillator_strength_velocity(), atol=atol) has_rotatory1 = all(op in state1.operators.available for op in ["magnetic_dipole", "nabla"]) has_rotatory2 = all(op in state2.operators.available for op in ["magnetic_dipole", "nabla"]) if has_rotatory1 and has_rotatory2: - assert_allclose(state1.rotatory_strength, - state2.rotatory_strength, atol=atol) + assert_allclose(state1.rotatory_strength(), + state2.rotatory_strength(), atol=atol) diff --git a/adcc/backends/testing.py b/adcc/backends/testing.py index 770afc92..468c321b 100644 --- a/adcc/backends/testing.py +++ b/adcc/backends/testing.py @@ -150,12 +150,12 @@ def operator_import_from_ao_test(scfres, ao_dict, operator="electric_dipole"): virta = refstate.orbital_coefficients_alpha("v1b").to_ndarray() virtb = refstate.orbital_coefficients_beta("v1b").to_ndarray() - if operator == 'magnetic_dipole': + if operator == "magnetic_dipole": callback = refstate.operators.magnetic_dipole - dip_imported = callback('origin') - elif operator == 'nabla': + dip_imported = callback([0.0, 0.0, 0.0]) + elif operator == "nabla": callback = refstate.operators.nabla - dip_imported = callback('origin') + dip_imported = callback([0.0, 0.0, 0.0]) else: dip_imported = getattr(refstate.operators, operator) diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 35675081..f88e74ad 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -60,7 +60,7 @@ def electric_dipole(self): def magnetic_dipole(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != [0.0, 0.0, 0.0]: + if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' ' gauge origin.') task = self.scfdrv.task @@ -74,7 +74,7 @@ def gauge_dependent_integrals(gauge_origin): def nabla(self): def gauge_dependent_integrals(gauge_origin): # TODO: Gauge origin? - if gauge_origin != [0.0, 0.0, 0.0]: + if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' ' gauge origin.') task = self.scfdrv.task diff --git a/adcc/test_properties.py b/adcc/test_properties.py index d8b32eb1..d6b52029 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -127,12 +127,11 @@ def base_test(self, system, method, kind): basemethods = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = [m for bm in basemethods for m in [bm, "cvs_" + bm]] gauge_origins = ["origin", "mass_center", "charge_center"] -backends = ['pyscf'] -@expand_test_templates(list(itertools.product(backends, methods, gauge_origins))) +@expand_test_templates(list(itertools.product(methods))) class TestMagneticTransitionDipoleMoments(unittest.TestCase): - def template_linear_molecule(self, backend, method, gauge_origin): + def template_linear_molecule(self, method): method = method.replace("_", "-") xyz = """ @@ -140,18 +139,16 @@ def template_linear_molecule(self, backend, method, gauge_origin): O 0 0 2.7023 """ basis = "sto-3g" - - if backend != "pyscf" and gauge_origin != "origin": - skip("Gauge origin selection is only implemented " - "for pyscf backend") - scfres = run_hf(backend, xyz, basis) - - if "cvs" in method: - state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) - else: - state = run_adc(scfres, method=method, n_singlets=10) - tdms = state.transition_magnetic_dipole_moment(gauge_origin) - - # For linear molecules lying on the z-axis, the z-component must be zero - for tdm in tdms: - assert tdm[2] < 1e-10 + backend = "pyscf" + for gauge_origin in gauge_origins: + scfres = run_hf(backend, xyz, basis) + + if "cvs" in method: + state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) + else: + state = run_adc(scfres, method=method, n_singlets=10) + tdms = state.transition_magnetic_dipole_moment(gauge_origin) + + # For linear molecules lying on the z-axis, the z-component must be zero + for tdm in tdms: + assert tdm[2] < 1e-10 diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 609f18cd..5b70cb92 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -29,27 +29,23 @@ from .test_state_densities import Runners from pytest import approx -gauge_origins = ['mass_center', 'charge_center'] +gauge_origins = ["origin", "mass_center", "charge_center"] class TestMagneticTransitionDipoleMoments(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") - systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - # TODO: change ref data - if 'center' in system: - gauge_origin = '_'.join(system.split('_')[-2:]) - else: - gauge_origin = 'origin' + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] + + for gauge_origin in gauge_origins: res_dms = state.transition_magnetic_dipole_moment(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( - res_dms, ref["transition_magnetic_dipole_moments"][:n_ref], + res_dms, + ref[f"transition_magnetic_dipole_moments_{gauge_origin}"][:n_ref], atol=1e-4 ) @@ -57,20 +53,17 @@ def base_test(self, system, method, kind): class TestTransitionDipoleMomentsVelocity(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") - systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - if 'center' in system: - gauge_origin = '_'.join(system.split('_')[-2:]) - else: - gauge_origin = 'origin' + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] + + for gauge_origin in gauge_origins: res_dms = state.transition_dipole_moment_velocity(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( - res_dms, ref["transition_dipole_moments_velocity"][:n_ref], + res_dms, + ref[f"transition_dipole_moments_velocity_{gauge_origin}"][:n_ref], atol=1e-4 ) @@ -78,38 +71,30 @@ def base_test(self, system, method, kind): class TestTransitionQuadrupoleMoments(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") - systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - if 'center' in system: - gauge_origin = '_'.join(system.split('_')[-2:]) - continue # No reference data for mass_center and charge_center - else: - gauge_origin = 'origin' + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] + + for gauge_origin in gauge_origins: res_dms = state.transition_quadrupole_moment(gauge_origin) ref = refdata[method][kind] n_ref = len(state.excitation_vector) assert_allclose( - res_dms, ref["transition_quadrupole_moments"][:n_ref], atol=1e-4) + res_dms, + ref[f"transition_quadrupole_moments_{gauge_origin}"][:n_ref], + atol=1e-4) class TestRotatoryStrengths(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") - systems_gauge_origin = [system] + [system + "_" + g for g in gauge_origins] kind = "any" if kind == "state" else kind - for system in systems_gauge_origin: - refdata = cache.adcc_reference_data[system] - state = cache.adcc_states[system][method][kind] - if 'center' in system: - gauge_origin = '_'.join(system.split('_')[-2:]) - else: - gauge_origin = 'origin' + refdata = cache.adcc_reference_data[system] + state = cache.adcc_states[system][method][kind] + for gauge_origin in gauge_origins: res_rots = state.rotatory_strength(gauge_origin) - ref_tmdm = refdata[method][kind]["transition_magnetic_dipole_moments"] - ref_tdmvel = refdata[method][kind]["transition_dipole_moments_velocity"] + ref_tmdm = refdata[method][kind][f"transition_magnetic_dipole_moments_{gauge_origin}"] + ref_tdmvel = refdata[method][kind][f"transition_dipole_moments_velocity_{gauge_origin}"] refevals = refdata[method][kind]["eigenvalues"] n_ref = len(state.excitation_vector) for i in range(n_ref): diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index 3f6493d0..bb24ac72 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -89,7 +89,6 @@ def fullfile(fn): class TestdataCache(): cases = ["h2o_sto3g", "cn_sto3g", "hf3_631g", "h2s_sto3g", "ch2nh2_sto3g", "methox_sto3g"] - gauge_origins = ["mass_center", "charge_center"] mode_full = False @staticmethod @@ -106,26 +105,14 @@ def testcases(self): return [k for k in TestdataCache.cases if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] - @property - def testcases_gauge_origin(self): - """ - The definition of additional gauge origin dependent - test cases: Data generator and reference file - """ - - return [k + "_" + g for k in TestdataCache.cases - for g in TestdataCache.gauge_origins - if os.path.isfile(fullfile(k + "_hfdata.hdf5"))] - @cached_property def hfdata(self): """ The HF data a testcase is based upon """ ret = {} - for k in self.testcases + self.testcases_gauge_origin: - hf_k = k.replace("_mass_center", "").replace("_charge_center", "") - datafile = fullfile(hf_k + "_hfdata.hdf5") + for k in self.testcases: + datafile = fullfile(k + "_hfdata.hdf5") # TODO This could be made a plain HDF5.File ret[k] = hdf5io.load(datafile) return ret @@ -135,18 +122,13 @@ def refstate(self): def cache_eri(refstate): refstate.import_all() return refstate - ret = {} - for k in self.testcases + self.testcases_gauge_origin: - ret[k] = cache_eri(adcc.ReferenceState(self.hfdata[k])) - return ret - - # return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) - # for k in self.testcases + self.testcases_gauge_origin } + return {k: cache_eri(adcc.ReferenceState(self.hfdata[k])) + for k in self.testcases} @cached_property def refstate_cvs(self): ret = {} - for case in self.testcases + self.testcases_gauge_origin: + for case in self.testcases: # TODO once hfdata is an HDF5 file # refcases = ast.literal_eval( # self.hfdata[case]["reference_cases"][()]) @@ -179,8 +161,9 @@ def read_reference_data(self, refname): raws = ["adc0", "adc1", "adc2", "adc2x", "adc3"] methods = raws + ["_".join([p, r]) for p in prefixes for r in raws if p != ""] + ret = {} - for k in self.testcases + self.testcases_gauge_origin: + for k in self.testcases: fulldict = {} for m in methods: datafile = fullfile(k + "_" + refname + "_" + m + ".hdf5") @@ -205,7 +188,7 @@ def construct_adc_states(self, refdata): for all test cases, all methods and all kinds (singlet, triplet) """ res = {} - for case in self.testcases + self.testcases_gauge_origin: + for case in self.testcases: if case not in refdata: continue available_kinds = refdata[case]["available_kinds"] @@ -289,4 +272,4 @@ def read_yaml_data(fname): qchem_data = read_yaml_data("qchem_dump.yml") tmole_data = read_yaml_data("tmole_dump.yml") psi4_data = read_yaml_data("psi4_dump.yml") -pyscf_data = read_yaml_data("pyscf_dump.yml") +pyscf_data = read_yaml_data("pyscf_dump.yml") \ No newline at end of file diff --git a/adcc/testdata/dump_reference_adcc.py b/adcc/testdata/dump_reference_adcc.py index 8803921e..d1014ca5 100644 --- a/adcc/testdata/dump_reference_adcc.py +++ b/adcc/testdata/dump_reference_adcc.py @@ -185,12 +185,25 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", adc[kind + "/ground_to_excited_tdm_bb_b"] = np.asarray(tdm_bb_b) adc[kind + "/state_dipole_moments"] = state.state_dipole_moment adc[kind + "/transition_dipole_moments"] = state.transition_dipole_moment - adc[kind + "/transition_dipole_moments_velocity"] = \ - state.transition_dipole_moment_velocity - adc[kind + "/transition_magnetic_dipole_moments"] = \ - state.transition_magnetic_dipole_moment - adc[kind + "/transition_quadrupole_moments"] = \ - state.transition_quadrupole_moment + # gauge origin dependent + adc[kind + "/transition_dipole_moments_velocity_origin"] = \ + state.transition_dipole_moment_velocity("origin") + adc[kind + "/transition_dipole_moments_velocity_mass_center"] = \ + state.transition_dipole_moment_velocity("mass_center") + adc[kind + "/transition_dipole_moments_velocity_charge_center"] = \ + state.transition_dipole_moment_velocity("charge_center") + adc[kind + "/transition_magnetic_dipole_moments_origin"] = \ + state.transition_magnetic_dipole_moment("origin") + adc[kind + "/transition_magnetic_dipole_moments_mass_center"] = \ + state.transition_magnetic_dipole_moment("mass_center") + adc[kind + "/transition_magnetic_dipole_moments_charge_center"] = \ + state.transition_magnetic_dipole_moment("charge_center") + adc[kind + "/transition_quadrupole_moments_origin"] = \ + state.transition_quadrupole_moment("origin") + adc[kind + "/transition_quadrupole_moments_mass_center"] = \ + state.transition_quadrupole_moment("mass_center") + adc[kind + "/transition_quadrupole_moments_charge_center"] = \ + state.transition_quadrupole_moment("charge_center") adc[kind + "/eigenvalues"] = state.excitation_energy adc[kind + "/eigenvectors_singles"] = np.asarray( eigenvectors_singles) diff --git a/adcc/testdata/generate_hfdata_cn_sto3g.py b/adcc/testdata/generate_hfdata_cn_sto3g.py index 23d86411..ff7f0ea2 100755 --- a/adcc/testdata/generate_hfdata_cn_sto3g.py +++ b/adcc/testdata/generate_hfdata_cn_sto3g.py @@ -29,7 +29,8 @@ sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +# import adcctestdata as atd # noqa: E402 +from dump_pyscf import dump_pyscf # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -47,7 +48,8 @@ mf.max_cycle = 500 mf = scf.addons.frac_occ(mf) mf.kernel() -h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") +# h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") +h5f = dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfimport.py b/adcc/testdata/generate_hfimport.py index 786ec63a..8b0979e6 100755 --- a/adcc/testdata/generate_hfimport.py +++ b/adcc/testdata/generate_hfimport.py @@ -79,11 +79,11 @@ def dump_imported(key, dump_cvs=True): def main(): # H2O restricted dump_imported("h2o_sto3g") - dump_imported("h2o_def2tzvp") + # dump_imported("h2o_def2tzvp") # CN unrestricted dump_imported("cn_sto3g") - dump_imported("cn_ccpvdz") + # dump_imported("cn_ccpvdz") # CH2NH2 unrestricted (no symmetries) dump_imported("ch2nh2_sto3g") diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 3d39b043..9ccd88d5 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -33,22 +33,18 @@ sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -import adcctestdata as atd # noqa: E402 +# import adcctestdata as atd # noqa: E402 def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", - generator="adcc", dump_gauge_origin=True): + generator="adcc"): assert spec in ["gen", "cvs"] for method in ["adc0", "adc1", "adc2", "adc2x", "adc3"]: - if dump_gauge_origin: - for gauge_origin in ["origin", "mass_center", "charge_center"]: - kw = kwargs_overwrite.get(method, kwargs) - dump_method(case, method, kw, spec, generator=generator, - dump_gauge_origin=gauge_origin) + kw = kwargs_overwrite.get(method, kwargs) + dump_method(case, method, kw, spec, generator=generator) -def dump_method(case, method, kwargs, spec, generator="adcc", - dump_gauge_origin="origin"): +def dump_method(case, method, kwargs, spec, generator="adcc"): h5file = case + "_hfdata.hdf5" if not os.path.isfile(h5file): raise ValueError("HfData not found: " + h5file) @@ -58,8 +54,7 @@ def dump_method(case, method, kwargs, spec, generator="adcc", hfdata = atd.HdfProvider(h5file) else: dumpfunction = dump_reference_adcc - hfdata = adcc.DataHfProvider(h5py.File(h5file, "r"), - gauge_origin=dump_gauge_origin) + hfdata = adcc.DataHfProvider(h5py.File(h5file, "r")) # Get dictionary of parameters for the reference cases. refcases = ast.literal_eval(hfdata.data["reference_cases"][()].decode()) kwargs = dict(kwargs) @@ -80,11 +75,9 @@ def dump_method(case, method, kwargs, spec, generator="adcc", if generator == "atd": dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) - elif generator == "adcc" and dump_gauge_origin == "origin": - dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) else: - dumpfile = "{}_{}_adcc_reference_{}{}.hdf5".format(case, dump_gauge_origin, - prefix, method) + dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) + if not os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -110,14 +103,10 @@ def dump_h2o_sto3g(): # H2O restricted case = "h2o_sto3g" # Just ADC(2) and ADC(2)-x kwargs = {"n_singlets": 3, "n_triplets": 3} - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec="fc", dump_gauge_origin=gauge_origin) - dump_method(case, "adc2", kwargs, spec="fc-fv", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fv", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fv-cvs", - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc") + dump_method(case, "adc2", kwargs, spec="fc-fv") + dump_method(case, "adc2x", kwargs, spec="fv") + dump_method(case, "adc2x", kwargs, spec="fv-cvs") def dump_h2o_def2tzvp(): # H2O restricted @@ -133,17 +122,12 @@ def dump_cn_sto3g(): # CN unrestricted # Just ADC(2) and ADC(2)-x for the other methods case = "cn_sto3g" - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 12, - "max_subspace": 30}, spec="fc", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 14, - "max_subspace": 30}, spec="fc-fv", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2x", {"n_states": 4, "n_guess_singles": 8}, spec="fv", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs", - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 12, + "max_subspace": 30}, spec="fc") + dump_method(case, "adc2", {"n_states": 4, "n_guess_singles": 14, + "max_subspace": 30}, spec="fc-fv") + dump_method(case, "adc2x", {"n_states": 4, "n_guess_singles": 8}, spec="fv") + dump_method(case, "adc2x", {"n_states": 4}, spec="fv-cvs") def dump_cn_ccpvdz(): # CN unrestricted @@ -161,40 +145,31 @@ def dump_h2s_sto3g(): case = "h2s_sto3g" kwargs = {"n_singlets": 3, "n_triplets": 3} - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec="fc-cvs", - dump_gauge_origin=gauge_origin) - dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs", - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2", kwargs, spec="fc-cvs") + dump_method(case, "adc2x", kwargs, spec="fc-fv-cvs") def dump_h2s_6311g(): case = "h2s_6311g" kwargs = {"n_singlets": 3, "n_triplets": 3} for spec in ["gen", "fc", "fv", "fc-fv"]: - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2", kwargs, spec=spec, - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2", kwargs, spec=spec) kwargs = {"n_singlets": 3, "n_triplets": 3, "n_guess_singles": 6, "max_subspace": 60} for spec in ["fv-cvs", "fc-cvs", "fc-fv-cvs"]: - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2x", kwargs, spec=spec, - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", kwargs, spec=spec) kwargs["n_guess_singles"] = 8 - for gauge_origin in ["origin", "mass_center", "charge_center"]: - dump_method(case, "adc2x", kwargs, spec="cvs", - dump_gauge_origin=gauge_origin) + dump_method(case, "adc2x", kwargs, spec="cvs") def dump_methox_sto3g(): # (R)-2-methyloxirane kwargs = {"n_singlets": 2} dump_all("methox_sto3g", kwargs, spec="gen", - generator="adcc", dump_gauge_origin=False) + generator="adcc") dump_all("methox_sto3g", kwargs, spec="cvs", - generator="adcc", dump_gauge_origin=False) + generator="adcc") def main(): From 58b1ee605b2cea5de2c73cf506c28a769d45d9e5 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 15 Oct 2024 16:15:27 +0200 Subject: [PATCH 21/42] Code Style --- adcc/ElectronicTransition.py | 10 +-- adcc/OperatorIntegrals.py | 4 +- .../test_modified_transition_moments.py | 1 + adcc/backends/pyscf.py | 1 - adcc/test_properties.py | 3 +- adcc/test_properties_consistency.py | 9 +-- adcc/testdata/cache.py | 2 +- adcc/testdata/generate_hfdata_cn_sto3g.py | 6 +- adcc/testdata/generate_hfimport.py | 4 +- adcc/testdata/generate_reference.py | 6 +- libadcc/HartreeFockSolution_i.hh | 16 ++--- libadcc/ReferenceState.cc | 14 ++-- libadcc/ReferenceState.hh | 7 +- libadcc/pyiface/export_HartreeFockProvider.cc | 64 +++++++++---------- libadcc/pyiface/export_ReferenceState.cc | 24 +++---- 15 files changed, 84 insertions(+), 87 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 47361110..3443c2bd 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -167,7 +167,7 @@ def transition_dipole_moment(self): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_dipole_moment_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): + def transition_dipole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): """List of transition dipole moments in the velocity gauge of all computed states""" if self.property_method.level == 0: @@ -181,7 +181,7 @@ def transition_dipole_moment_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_magnetic_dipole_moment(self, gauge_origin = [0.0, 0.0, 0.0]): + def transition_magnetic_dipole_moment(self, gauge_origin=[0.0, 0.0, 0.0]): """List of transition magnetic dipole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition magnetic dipole moments " @@ -194,7 +194,7 @@ def transition_magnetic_dipole_moment(self, gauge_origin = [0.0, 0.0, 0.0]): @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_quadrupole_moment(self, gauge_origin = [0.0, 0.0, 0.0]): + def transition_quadrupole_moment(self, gauge_origin=[0.0, 0.0, 0.0]): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition quadrupole moments are known to be " @@ -217,7 +217,7 @@ def oscillator_strength(self): ]) @mark_excitation_property() - def oscillator_strength_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): + def oscillator_strength_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): """List of oscillator strengths in velocity gauge of all computed states""" return 2. / 3. * np.array([ @@ -227,7 +227,7 @@ def oscillator_strength_velocity(self, gauge_origin = [0.0, 0.0, 0.0]): ]) @mark_excitation_property() - def rotatory_strength(self, gauge_origin = [0.0, 0.0, 0.0]): + def rotatory_strength(self, gauge_origin=[0.0, 0.0, 0.0]): """List of rotatory strengths of all computed states""" return np.array([ np.dot(tdm, magmom) / ee diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 57b08890..401dd254 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -145,7 +145,7 @@ def __import_gauge_dependent_dipole_like(self, callback, is_symmetric=True): if not callable(callback): raise TypeError - def process_operator(gauge_origin = [0.0, 0.0, 0.0], callback=callback, + def process_operator(gauge_origin=[0.0, 0.0, 0.0], callback=callback, is_symmetric=is_symmetric): dipoles = [] for i, component in enumerate(["x", "y", "z"]): @@ -208,7 +208,7 @@ def __import_quadrupole_like_operator(self, callback, is_symmetric=True): if not callable(callback): raise TypeError - def process_operator(gauge_origin = [0.0, 0.0, 0.0], callback=callback, + def process_operator(gauge_origin=[0.0, 0.0, 0.0], callback=callback, is_symmetric=is_symmetric): quad = [] quadrupoles = [] diff --git a/adcc/adc_pp/test_modified_transition_moments.py b/adcc/adc_pp/test_modified_transition_moments.py index 58f0c8ed..6747b46e 100644 --- a/adcc/adc_pp/test_modified_transition_moments.py +++ b/adcc/adc_pp/test_modified_transition_moments.py @@ -38,6 +38,7 @@ operator_kinds = ["electric", "magnetic"] gauge_origins = ["origin", "mass_center", "charge_center"] + @expand_test_templates(list(itertools.product(methods, operator_kinds))) class TestModifiedTransitionMoments(unittest.TestCase): def base_test(self, system, method, kind, op_kind): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index b4baebcf..61f7407f 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -57,7 +57,6 @@ def _update_gauge_origin(self, gauge_origin): " mass_center, charge_center or origin." " Or by declaring a list eg. [x, y, z]" " in atomic units.") - print(gauge_origin) return gauge_origin @cached_property diff --git a/adcc/test_properties.py b/adcc/test_properties.py index d6b52029..bb4b1e38 100644 --- a/adcc/test_properties.py +++ b/adcc/test_properties.py @@ -144,7 +144,8 @@ def template_linear_molecule(self, method): scfres = run_hf(backend, xyz, basis) if "cvs" in method: - state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) + state = run_adc(scfres, method=method, n_singlets=5, + core_orbitals=2) else: state = run_adc(scfres, method=method, n_singlets=10) tdms = state.transition_magnetic_dipole_moment(gauge_origin) diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index 5b70cb92..b015dd50 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -89,13 +89,14 @@ class TestRotatoryStrengths(unittest.TestCase, Runners): def base_test(self, system, method, kind): method = method.replace("_", "-") kind = "any" if kind == "state" else kind - refdata = cache.adcc_reference_data[system] + refdata = cache.adcc_reference_data[system][method][kind] state = cache.adcc_states[system][method][kind] for gauge_origin in gauge_origins: res_rots = state.rotatory_strength(gauge_origin) - ref_tmdm = refdata[method][kind][f"transition_magnetic_dipole_moments_{gauge_origin}"] - ref_tdmvel = refdata[method][kind][f"transition_dipole_moments_velocity_{gauge_origin}"] - refevals = refdata[method][kind]["eigenvalues"] + ref_tmdm = refdata[f"transition_magnetic_dipole_moments_{gauge_origin}"] + ref_tdmvel = \ + refdata[f"transition_dipole_moments_velocity_{gauge_origin}"] + refevals = refdata["eigenvalues"] n_ref = len(state.excitation_vector) for i in range(n_ref): assert state.excitation_energy[i] == refevals[i] diff --git a/adcc/testdata/cache.py b/adcc/testdata/cache.py index bb24ac72..6810dca5 100644 --- a/adcc/testdata/cache.py +++ b/adcc/testdata/cache.py @@ -272,4 +272,4 @@ def read_yaml_data(fname): qchem_data = read_yaml_data("qchem_dump.yml") tmole_data = read_yaml_data("tmole_dump.yml") psi4_data = read_yaml_data("psi4_dump.yml") -pyscf_data = read_yaml_data("pyscf_dump.yml") \ No newline at end of file +pyscf_data = read_yaml_data("pyscf_dump.yml") diff --git a/adcc/testdata/generate_hfdata_cn_sto3g.py b/adcc/testdata/generate_hfdata_cn_sto3g.py index ff7f0ea2..23d86411 100755 --- a/adcc/testdata/generate_hfdata_cn_sto3g.py +++ b/adcc/testdata/generate_hfdata_cn_sto3g.py @@ -29,8 +29,7 @@ sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# import adcctestdata as atd # noqa: E402 -from dump_pyscf import dump_pyscf +import adcctestdata as atd # noqa: E402 # Run SCF in pyscf and converge super-tight using an EDIIS mol = gto.M( @@ -48,8 +47,7 @@ mf.max_cycle = 500 mf = scf.addons.frac_occ(mf) mf.kernel() -# h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") -h5f = dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") +h5f = atd.dump_pyscf(mf, "cn_sto3g_hfdata.hdf5") h5f["reference_cases"] = str({ "gen": {}, diff --git a/adcc/testdata/generate_hfimport.py b/adcc/testdata/generate_hfimport.py index 8b0979e6..786ec63a 100755 --- a/adcc/testdata/generate_hfimport.py +++ b/adcc/testdata/generate_hfimport.py @@ -79,11 +79,11 @@ def dump_imported(key, dump_cvs=True): def main(): # H2O restricted dump_imported("h2o_sto3g") - # dump_imported("h2o_def2tzvp") + dump_imported("h2o_def2tzvp") # CN unrestricted dump_imported("cn_sto3g") - # dump_imported("cn_ccpvdz") + dump_imported("cn_ccpvdz") # CH2NH2 unrestricted (no symmetries) dump_imported("ch2nh2_sto3g") diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 9ccd88d5..e30a9dd4 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -33,7 +33,7 @@ sys.path.insert(0, join(dirname(__file__), "adcc-testdata")) -# import adcctestdata as atd # noqa: E402 +import adcctestdata as atd # noqa: E402 def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", @@ -77,7 +77,7 @@ def dump_method(case, method, kwargs, spec, generator="adcc"): dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) else: dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) - + if not os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -158,7 +158,7 @@ def dump_h2s_6311g(): kwargs = {"n_singlets": 3, "n_triplets": 3, "n_guess_singles": 6, "max_subspace": 60} for spec in ["fv-cvs", "fc-cvs", "fc-fv-cvs"]: - dump_method(case, "adc2x", kwargs, spec=spec) + dump_method(case, "adc2x", kwargs, spec=spec) kwargs["n_guess_singles"] = 8 dump_method(case, "adc2x", kwargs, spec="cvs") diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index d7f25703..07a35561 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -70,7 +70,7 @@ class HartreeFockSolution_i { virtual size_t n_bas() const = 0; ///@} // - //returns number of atoms + // returns number of atoms virtual size_t n_atoms() const = 0; /** \name Access to HF SCF results */ @@ -79,14 +79,14 @@ class HartreeFockSolution_i { * SCF program used to provide the data */ virtual std::string backend() const = 0; - //nuclear charges - virtual void nuclear_charges(scalar_type* buffer, size_t size) const = 0; + // nuclear charges + virtual void nuclear_charges(scalar_type* buffer, size_t size) const = 0; + + // nuclear masses + virtual void nuclear_masses(scalar_type* buffer, size_t size) const = 0; + // coordinates + virtual void coordinates(scalar_type* buffer, size_t size) const = 0; - //nuclear masses - virtual void nuclear_masses(scalar_type* buffer, size_t size) const = 0; - //coordinates - virtual void coordinates(scalar_type* buffer, size_t size) const = 0; - /** SCF convergence threshold */ virtual real_type conv_tol() const = 0; diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index 185d9004..a5782536 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -350,9 +350,7 @@ std::string ReferenceState::irreducible_representation() const { throw not_implemented_error("Only C1 is implemented."); } -size_t ReferenceState::n_atoms() const{ - return m_hfsoln_ptr -> n_atoms(); -} +size_t ReferenceState::n_atoms() const { return m_hfsoln_ptr->n_atoms(); } std::vector ReferenceState::nuclear_multipole(size_t order) const { std::vector ret((order + 2) * (order + 1) / 2); @@ -361,25 +359,25 @@ std::vector ReferenceState::nuclear_multipole(size_t order) const { } std::vector ReferenceState::nuclear_charges() const { - size_t n_atoms = ReferenceState::n_atoms(); + size_t n_atoms = ReferenceState::n_atoms(); std::vector ret(n_atoms); m_hfsoln_ptr->nuclear_charges(ret.data(), ret.size()); return ret; } std::vector ReferenceState::nuclear_masses() const { - size_t n_atoms = ReferenceState::n_atoms(); + size_t n_atoms = ReferenceState::n_atoms(); std::vector ret(n_atoms); m_hfsoln_ptr->nuclear_masses(ret.data(), ret.size()); return ret; } -std::vector ReferenceState::coordinates() const{ - size_t n_atoms = ReferenceState::n_atoms(); +std::vector ReferenceState::coordinates() const { + size_t n_atoms = ReferenceState::n_atoms(); std::vector ret(3 * n_atoms); m_hfsoln_ptr->coordinates(ret.data(), ret.size()); return ret; - } +} // // Tensor data diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index 4283415f..d35f4d00 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -96,16 +96,15 @@ class ReferenceState { * (in standard ordering, i.e. xx, xy, xz, yy, yz, zz) of the given order. */ std::vector nuclear_multipole(size_t order) const; - //nuclear charges + // nuclear charges std::vector nuclear_charges() const; - //coordinates + // coordinates std::vector coordinates() const; - //nuclear masses + // nuclear masses std::vector nuclear_masses() const; - /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 94587b05..cecfd40c 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -44,32 +44,32 @@ class HartreeFockProvider : public HartreeFockSolution_i { bool restricted() const override { return get_restricted(); } size_t spin_multiplicity() const override { return get_spin_multiplicity(); } real_type energy_scf() const override { return get_energy_scf(); } - void nuclear_masses(scalar_type* buffer, size_t size) const override{ - py::array_t ret = get_nuclear_masses(); + void nuclear_masses(scalar_type* buffer, size_t size) const override { + py::array_t ret = get_nuclear_masses(); if (static_cast(size) != ret.size()) { throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + ") does not agree with buffer size (" + std::to_string(size) + ")."); } - std::copy(ret.data(), ret.data()+ size, buffer); - } - void nuclear_charges(scalar_type* buffer, size_t size) const override{ - py::array_t ret = get_nuclear_charges(); + std::copy(ret.data(), ret.data() + size, buffer); + } + void nuclear_charges(scalar_type* buffer, size_t size) const override { + py::array_t ret = get_nuclear_charges(); if (static_cast(size) != ret.size()) { throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + ") does not agree with buffer size (" + std::to_string(size) + ")."); } - std::copy(ret.data(), ret.data()+ size, buffer); - } - void coordinates(scalar_type* buffer, size_t size) const override{ + std::copy(ret.data(), ret.data() + size, buffer); + } + void coordinates(scalar_type* buffer, size_t size) const override { py::array_t ret = get_coordinates(); if (static_cast(size) != ret.size()) { throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + ") does not agree with buffer size (" + std::to_string(size) + ")."); } - std::copy(ret.data(), ret.data()+ size, buffer); + std::copy(ret.data(), ret.data() + size, buffer); } // @@ -283,9 +283,9 @@ class HartreeFockProvider : public HartreeFockSolution_i { virtual size_t get_spin_multiplicity() const = 0; virtual real_type get_energy_scf() const = 0; virtual std::string get_backend() const = 0; - virtual py::array_t get_nuclear_charges() const = 0; - virtual py::array_t get_coordinates() const = 0; - virtual py::array_t get_nuclear_masses() const = 0; + virtual py::array_t get_nuclear_charges() const = 0; + virtual py::array_t get_coordinates() const = 0; + virtual py::array_t get_nuclear_masses() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -315,17 +315,17 @@ class PyHartreeFockProvider : public HartreeFockProvider { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order); } - py::array_t get_nuclear_charges() const override{ + py::array_t get_nuclear_charges() const override { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclear_charges, ); + get_nuclear_charges, ); } - py::array_t get_nuclear_masses() const override{ + py::array_t get_nuclear_masses() const override { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclear_masses, ); + get_nuclear_masses, ); } - py::array_t get_coordinates() const override{ + py::array_t get_coordinates() const override { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_coordinates, ); + get_coordinates, ); } real_type get_conv_tol() const override { PYBIND11_OVERLOAD_PURE(real_type, HartreeFockProvider, get_conv_tol, ); @@ -480,23 +480,23 @@ void export_HartreeFockProvider(py::module& m) { .def("get_n_bas", &HartreeFockProvider::get_n_bas, "Returns the number of *spatial* one-electron basis functions. This value " "is abbreviated by `nb` in the documentation.") - .def("get_n_atoms", &HartreeFockProvider::get_n_atoms, - "Returns the number of atoms.") + .def("get_n_atoms", &HartreeFockProvider::get_n_atoms, + "Returns the number of atoms.") .def("get_nuclear_multipole", &HartreeFockProvider::get_nuclear_multipole, "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " "dipole moment as an array of size 3, for `2` returns the nuclear " - "quadrupole moment as an array of the 6 unique entries " - "(xx, xy, xz, yy, yz, zz) dependent on the selected gauge origin." ) - .def("get_nuclear_charges", &HartreeFockProvider::get_nuclear_charges, - "Returns nuclear charges of SCF reference, " - "array of size of number of atoms.") - .def("get_coordinates", &HartreeFockProvider::get_coordinates, - "Returns coordinates of SCF referece state in Bohr, array of " - "size 3 * number of atoms.") - .def("get_nuclear_masses", &HartreeFockProvider::get_nuclear_masses, - "Returns the nuclear masses of the SCF reference, " - "array of size of number of atoms.") + "quadrupole moment as an array of the 6 unique entries " + "(xx, xy, xz, yy, yz, zz) dependent on the selected gauge origin.") + .def("get_nuclear_charges", &HartreeFockProvider::get_nuclear_charges, + "Returns nuclear charges of SCF reference, " + "array of size of number of atoms.") + .def("get_coordinates", &HartreeFockProvider::get_coordinates, + "Returns coordinates of SCF referece state in Bohr, array of " + "size 3 * number of atoms.") + .def("get_nuclear_masses", &HartreeFockProvider::get_nuclear_masses, + "Returns the nuclear masses of the SCF reference, " + "array of size of number of atoms.") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index feacaf9f..2e70f61d 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -118,24 +118,24 @@ void export_ReferenceState(py::module& m) { std::copy(res.begin(), res.end(), ret.mutable_data()); return res; }) - .def_property_readonly("nuclear_dipole", + .def_property_readonly("nuclear_dipole", [](const ReferenceState& ref) { py::array_t ret(std::vector{3}); auto res = ref.nuclear_multipole(1); std::copy(res.begin(), res.end(), ret.mutable_data()); return res; }) - .def_property_readonly("nuclear_charges", &ReferenceState::nuclear_charges, - "Returns the nuclear charges of the SCF reference " - "as a list of the size of number of atoms.") - .def_property_readonly("nuclear_masses", &ReferenceState::nuclear_masses, - "Returns the nuclear masses of the SCF reference " - "as a list of the size of number of atoms.") - .def_property_readonly("coordinates", &ReferenceState::coordinates, - "Returns coordinates of the SCF reference as list " - "of size 3 * number of atoms in atomic units.") - .def_property_readonly("n_atoms", &ReferenceState::n_atoms, - "Returns the number of atoms.") + .def_property_readonly("nuclear_charges", &ReferenceState::nuclear_charges, + "Returns the nuclear charges of the SCF reference " + "as a list of the size of number of atoms.") + .def_property_readonly("nuclear_masses", &ReferenceState::nuclear_masses, + "Returns the nuclear masses of the SCF reference " + "as a list of the size of number of atoms.") + .def_property_readonly("coordinates", &ReferenceState::coordinates, + "Returns coordinates of the SCF reference as list " + "of size 3 * number of atoms in atomic units.") + .def_property_readonly("n_atoms", &ReferenceState::n_atoms, + "Returns the number of atoms.") .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From 83b5c62e88a721e4d827ee85f6e84bca22e32d32 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Mon, 21 Oct 2024 10:57:08 +0200 Subject: [PATCH 22/42] move nuclear moments from c++ to python. --- adcc/ReferenceState.py | 16 +++- adcc/backends/pyscf.py | 9 +- libadcc/HartreeFockSolution_i.hh | 18 ---- libadcc/ReferenceState.cc | 29 ------ libadcc/ReferenceState.hh | 15 --- libadcc/pyiface/export_HartreeFockProvider.cc | 93 ++----------------- libadcc/pyiface/export_ReferenceState.cc | 28 ------ 7 files changed, 26 insertions(+), 182 deletions(-) diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index a3274c61..6f6414d9 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -140,7 +140,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, """ if not isinstance(hfdata, libadcc.HartreeFockSolution_i): hfdata = import_scf_results(hfdata) - + self.hfdata = hfdata self._mospaces = MoSpaces(hfdata, frozen_core=frozen_core, frozen_virtual=frozen_virtual, core_orbitals=core_orbitals) @@ -160,6 +160,20 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, if hasattr(hfdata, name): setattr(self, name, getattr(hfdata, name)) + @property + def nuclear_charge(self): + return self.hfdata.get_nuclear_multipole(0) + + @property + def nuclear_dipole(self): + return self.hfdata.get_nuclear_multipole(1) + + @property + def nuclear_quadrupole(self): + def gauge_dependent(gauge_origin=[0.0, 0.0, 0.0]): + return self.hfdata.get_nuclear_multipole(2, gauge_origin) + return gauge_dependent + def __getattr__(self, attr): from . import block as b diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 61f7407f..556eb500 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -313,7 +313,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return int(self.scfres.mol.nao_nr()) - def get_nuclear_multipole(self, order): + def get_nuclear_multipole(self, order, gauge_origin=[0.0, 0.0, 0.0]): charges = self.scfres.mol.atom_charges() if order == 0: # The function interface needs to be a np.array on return @@ -322,12 +322,11 @@ def get_nuclear_multipole(self, order): coords = self.scfres.mol.atom_coords() return np.einsum("i,ix->x", charges, coords) elif order == 2: - # TODO: gauge-origin - coords = self.scfres.mol.atom_coords() + gauge_origin = \ + self.operator_integral_provider._update_gauge_origin(gauge_origin) + coords = self.scfres.mol.atom_coords() - gauge_origin r_r = np.einsum("ij,ik->ijk", coords, coords) res = np.einsum("i,ijk->jk", charges, r_r) - res = np.array([res[0][0], res[0][1], res[0][2], - res[1][1], res[1][2], res[2][2]]) return res else: raise NotImplementedError("get_nuclear_multipole with order > 2") diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 07a35561..52ea7323 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -41,14 +41,6 @@ namespace libadcc { */ class HartreeFockSolution_i { public: - /** \name System information */ - ///@{ - /** Fill a buffer with nuclear multipole data for the nuclear multipole of - * given order. */ - virtual void nuclear_multipole(size_t order, scalar_type* buffer, - size_t size) const = 0; - //@} - /** \name Sizes of the data */ ///@{ /** Return the number of HF *spin* molecular orbitals of alpha spin. @@ -70,8 +62,6 @@ class HartreeFockSolution_i { virtual size_t n_bas() const = 0; ///@} // - // returns number of atoms - virtual size_t n_atoms() const = 0; /** \name Access to HF SCF results */ ///@{ @@ -79,14 +69,6 @@ class HartreeFockSolution_i { * SCF program used to provide the data */ virtual std::string backend() const = 0; - // nuclear charges - virtual void nuclear_charges(scalar_type* buffer, size_t size) const = 0; - - // nuclear masses - virtual void nuclear_masses(scalar_type* buffer, size_t size) const = 0; - // coordinates - virtual void coordinates(scalar_type* buffer, size_t size) const = 0; - /** SCF convergence threshold */ virtual real_type conv_tol() const = 0; diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index a5782536..d64df7c4 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -350,35 +350,6 @@ std::string ReferenceState::irreducible_representation() const { throw not_implemented_error("Only C1 is implemented."); } -size_t ReferenceState::n_atoms() const { return m_hfsoln_ptr->n_atoms(); } - -std::vector ReferenceState::nuclear_multipole(size_t order) const { - std::vector ret((order + 2) * (order + 1) / 2); - m_hfsoln_ptr->nuclear_multipole(order, ret.data(), ret.size()); - return ret; -} - -std::vector ReferenceState::nuclear_charges() const { - size_t n_atoms = ReferenceState::n_atoms(); - std::vector ret(n_atoms); - m_hfsoln_ptr->nuclear_charges(ret.data(), ret.size()); - return ret; -} - -std::vector ReferenceState::nuclear_masses() const { - size_t n_atoms = ReferenceState::n_atoms(); - std::vector ret(n_atoms); - m_hfsoln_ptr->nuclear_masses(ret.data(), ret.size()); - return ret; -} - -std::vector ReferenceState::coordinates() const { - size_t n_atoms = ReferenceState::n_atoms(); - std::vector ret(3 * n_atoms); - m_hfsoln_ptr->coordinates(ret.data(), ret.size()); - return ret; -} - // // Tensor data // diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index d35f4d00..1c2dc219 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -90,21 +90,6 @@ class ReferenceState { /** Return the number of beta electrons */ size_t n_beta() const { return m_n_beta; } - size_t n_atoms() const; - - /** Return the nuclear contribution to the cartesian multipole moment - * (in standard ordering, i.e. xx, xy, xz, yy, yz, zz) of the given order. */ - std::vector nuclear_multipole(size_t order) const; - - // nuclear charges - std::vector nuclear_charges() const; - - // coordinates - std::vector coordinates() const; - - // nuclear masses - std::vector nuclear_masses() const; - /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index cecfd40c..8d735637 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -39,51 +39,14 @@ class HartreeFockProvider : public HartreeFockSolution_i { std::string backend() const override { return get_backend(); } size_t n_orbs_alpha() const override { return get_n_orbs_alpha(); } size_t n_bas() const override { return get_n_bas(); } - size_t n_atoms() const override { return get_n_atoms(); } real_type conv_tol() const override { return get_conv_tol(); } bool restricted() const override { return get_restricted(); } size_t spin_multiplicity() const override { return get_spin_multiplicity(); } real_type energy_scf() const override { return get_energy_scf(); } - void nuclear_masses(scalar_type* buffer, size_t size) const override { - py::array_t ret = get_nuclear_masses(); - if (static_cast(size) != ret.size()) { - throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + - ") does not agree with buffer size (" + - std::to_string(size) + ")."); - } - std::copy(ret.data(), ret.data() + size, buffer); - } - void nuclear_charges(scalar_type* buffer, size_t size) const override { - py::array_t ret = get_nuclear_charges(); - if (static_cast(size) != ret.size()) { - throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + - ") does not agree with buffer size (" + - std::to_string(size) + ")."); - } - std::copy(ret.data(), ret.data() + size, buffer); - } - void coordinates(scalar_type* buffer, size_t size) const override { - py::array_t ret = get_coordinates(); - if (static_cast(size) != ret.size()) { - throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + - ") does not agree with buffer size (" + - std::to_string(size) + ")."); - } - std::copy(ret.data(), ret.data() + size, buffer); - } // // Translate C++-like interface to python-like interface // - void nuclear_multipole(size_t order, scalar_type* buffer, size_t size) const override { - py::array_t ret = get_nuclear_multipole(order); - if (static_cast(size) != ret.size()) { - throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + - ") does not agree with buffer size (" + - std::to_string(size) + ")."); - } - std::copy(ret.data(), ret.data() + size, buffer); - } void occupation_f(scalar_type* buffer, size_t size) const override { const ssize_t ssize = static_cast(size); @@ -274,18 +237,13 @@ class HartreeFockProvider : public HartreeFockSolution_i { // // Interface for the python world // - virtual size_t get_n_orbs_alpha() const = 0; - virtual size_t get_n_bas() const = 0; - virtual size_t get_n_atoms() const = 0; - virtual py::array_t get_nuclear_multipole(size_t order) const = 0; - virtual real_type get_conv_tol() const = 0; - virtual bool get_restricted() const = 0; - virtual size_t get_spin_multiplicity() const = 0; - virtual real_type get_energy_scf() const = 0; - virtual std::string get_backend() const = 0; - virtual py::array_t get_nuclear_charges() const = 0; - virtual py::array_t get_coordinates() const = 0; - virtual py::array_t get_nuclear_masses() const = 0; + virtual size_t get_n_orbs_alpha() const = 0; + virtual size_t get_n_bas() const = 0; + virtual real_type get_conv_tol() const = 0; + virtual bool get_restricted() const = 0; + virtual size_t get_spin_multiplicity() const = 0; + virtual real_type get_energy_scf() const = 0; + virtual std::string get_backend() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -308,25 +266,6 @@ class PyHartreeFockProvider : public HartreeFockProvider { size_t get_n_bas() const override { PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_bas, ); } - size_t get_n_atoms() const override { - PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_atoms, ); - } - py::array_t get_nuclear_multipole(size_t order) const override { - PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclear_multipole, order); - } - py::array_t get_nuclear_charges() const override { - PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclear_charges, ); - } - py::array_t get_nuclear_masses() const override { - PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_nuclear_masses, ); - } - py::array_t get_coordinates() const override { - PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_coordinates, ); - } real_type get_conv_tol() const override { PYBIND11_OVERLOAD_PURE(real_type, HartreeFockProvider, get_conv_tol, ); } @@ -480,24 +419,6 @@ void export_HartreeFockProvider(py::module& m) { .def("get_n_bas", &HartreeFockProvider::get_n_bas, "Returns the number of *spatial* one-electron basis functions. This value " "is abbreviated by `nb` in the documentation.") - .def("get_n_atoms", &HartreeFockProvider::get_n_atoms, - "Returns the number of atoms.") - .def("get_nuclear_multipole", &HartreeFockProvider::get_nuclear_multipole, - "Returns the nuclear multipole of the requested order. For `0` returns the " - "total nuclear charge as an array of size 1, for `1` returns the nuclear " - "dipole moment as an array of size 3, for `2` returns the nuclear " - "quadrupole moment as an array of the 6 unique entries " - "(xx, xy, xz, yy, yz, zz) dependent on the selected gauge origin.") - .def("get_nuclear_charges", &HartreeFockProvider::get_nuclear_charges, - "Returns nuclear charges of SCF reference, " - "array of size of number of atoms.") - .def("get_coordinates", &HartreeFockProvider::get_coordinates, - "Returns coordinates of SCF referece state in Bohr, array of " - "size 3 * number of atoms.") - .def("get_nuclear_masses", &HartreeFockProvider::get_nuclear_masses, - "Returns the nuclear masses of the SCF reference, " - "array of size of number of atoms.") - // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " "number for each SCF orbital.") diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 2e70f61d..dc78a40c 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -108,34 +108,6 @@ void export_ReferenceState(py::module& m) { "Number of alpha electrons") .def_property_readonly("n_beta", &ReferenceState::n_beta, "Number of beta electrons") - .def_property_readonly( - "nuclear_total_charge", - [](const ReferenceState& ref) { return ref.nuclear_multipole(0)[0]; }) - .def_property_readonly("nuclear_quadrupole", - [](const ReferenceState& ref) { - py::array_t ret(std::vector{9}); - auto res = ref.nuclear_multipole(2); - std::copy(res.begin(), res.end(), ret.mutable_data()); - return res; - }) - .def_property_readonly("nuclear_dipole", - [](const ReferenceState& ref) { - py::array_t ret(std::vector{3}); - auto res = ref.nuclear_multipole(1); - std::copy(res.begin(), res.end(), ret.mutable_data()); - return res; - }) - .def_property_readonly("nuclear_charges", &ReferenceState::nuclear_charges, - "Returns the nuclear charges of the SCF reference " - "as a list of the size of number of atoms.") - .def_property_readonly("nuclear_masses", &ReferenceState::nuclear_masses, - "Returns the nuclear masses of the SCF reference " - "as a list of the size of number of atoms.") - .def_property_readonly("coordinates", &ReferenceState::coordinates, - "Returns coordinates of the SCF reference as list " - "of size 3 * number of atoms in atomic units.") - .def_property_readonly("n_atoms", &ReferenceState::n_atoms, - "Returns the number of atoms.") .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From ba41ef703571a3c1fb01c656eca3b79076738857 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Mon, 21 Oct 2024 13:54:41 +0200 Subject: [PATCH 23/42] typo: nuclear_total_charge --- adcc/ReferenceState.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 6f6414d9..7550236e 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -161,7 +161,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, setattr(self, name, getattr(hfdata, name)) @property - def nuclear_charge(self): + def nuclear_total_charge(self): return self.hfdata.get_nuclear_multipole(0) @property From a77f3bc934db9c288ab0b9df0604d143f1ef20a5 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 25 Oct 2024 15:59:57 +0200 Subject: [PATCH 24/42] Github comments, nuclear quadrupole moments --- adcc/DataHfProvider.py | 2 +- adcc/OperatorIntegrals.py | 6 +- adcc/ReferenceState.py | 21 +--- .../test_modified_transition_moments.py | 2 +- adcc/backends/psi4.py | 5 +- adcc/backends/pyscf.py | 114 ++++++++---------- adcc/backends/veloxchem.py | 5 +- adcc/test_properties_consistency.py | 10 +- adcc/testdata/dump_reference_adcc.py | 27 ++--- adcc/testdata/generate_reference.py | 11 +- libadcc/HartreeFockSolution_i.hh | 14 ++- libadcc/ReferenceState.cc | 13 ++ libadcc/ReferenceState.hh | 9 ++ libadcc/pyiface/export_HartreeFockProvider.cc | 55 +++++++-- libadcc/pyiface/export_ReferenceState.cc | 20 +++ 15 files changed, 188 insertions(+), 126 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index b89928bf..f5e5f03e 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -278,7 +278,7 @@ def get_backend(self): def get_energy_scf(self): return get_scalar_value(self.data, "energy_scf", 0.0) - def get_nuclear_multipole(self, order): + def get_nuclear_multipole(self, order, gauge_origin): if order == 0: # The function interface needs an np.array on return nuc_0 = get_scalar_value(self.data, "multipoles/nuclear_0", 0.0) return np.array([nuc_0]) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 401dd254..80ad6438 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -104,7 +104,7 @@ def available(self): "nabla", "electric_quadrupole_traceless", "electric_quadrupole", - "dia_magnet", + "diamagnetic_magnetizability", "pe_induction_elec", "pcm_potential_elec", ) @@ -254,13 +254,13 @@ def electric_quadrupole(self): @property @timed_member_call("_import_timer") - def dia_magnet(self): + def diamagnetic_magnetizability(self): """ Returns a function to obtain diamagnetic magnetizability integrals in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to [0.0, 0.0, 0.0]. """ - callback = self.provider_ao.dia_magnet + callback = self.provider_ao.diamagnetic_magnetizability return self.__import_quadrupole_like_operator(callback, is_symmetric=False) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 7550236e..760aa0f9 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -140,7 +140,7 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, """ if not isinstance(hfdata, libadcc.HartreeFockSolution_i): hfdata = import_scf_results(hfdata) - self.hfdata = hfdata + self._mospaces = MoSpaces(hfdata, frozen_core=frozen_core, frozen_virtual=frozen_virtual, core_orbitals=core_orbitals) @@ -160,20 +160,6 @@ def __init__(self, hfdata, core_orbitals=None, frozen_core=None, if hasattr(hfdata, name): setattr(self, name, getattr(hfdata, name)) - @property - def nuclear_total_charge(self): - return self.hfdata.get_nuclear_multipole(0) - - @property - def nuclear_dipole(self): - return self.hfdata.get_nuclear_multipole(1) - - @property - def nuclear_quadrupole(self): - def gauge_dependent(gauge_origin=[0.0, 0.0, 0.0]): - return self.hfdata.get_nuclear_multipole(2, gauge_origin) - return gauge_dependent - def __getattr__(self, attr): from . import block as b @@ -239,4 +225,9 @@ def dipole_moment(self): return self.nuclear_dipole - np.array([product_trace(comp, self.density) for comp in dipole_integrals]) + def nuclear_quadrupole(self, gauge_origin="origin"): + if isinstance(gauge_origin, str): + gauge_origin = self.determine_gauge_origin(gauge_origin) + return super().nuclear_quadrupole(np.array(gauge_origin)) + # TODO some nice describe method diff --git a/adcc/adc_pp/test_modified_transition_moments.py b/adcc/adc_pp/test_modified_transition_moments.py index 6747b46e..069ccd64 100644 --- a/adcc/adc_pp/test_modified_transition_moments.py +++ b/adcc/adc_pp/test_modified_transition_moments.py @@ -52,7 +52,7 @@ def base_test(self, system, method, kind, op_kind): elif op_kind == "magnetic": for gauge_origin in gauge_origins: dips = state.reference_state.operators.magnetic_dipole(gauge_origin) - ref_tdm = ref[f"transition_magnetic_dipole_moments_{gauge_origin}"] + ref_tdm = ref["transition_magnetic_dipole_moments"][gauge_origin] else: skip("Tests are only implemented for electric " "and magnetic dipole operators.") diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index dbe4d9a0..8c6c7775 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -212,7 +212,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return self.wfn.basisset().nbf() - def get_nuclear_multipole(self, order): + def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): molecule = self.wfn.molecule() if order == 0: # The function interface needs to be a np.array on return @@ -224,6 +224,9 @@ def get_nuclear_multipole(self, order): else: raise NotImplementedError("get_nuclear_multipole with order > 1") + def get_gauge_origin(self, gauge_origin): + raise NotImplementedError("get_gauge_origin not implemented.") + def fill_orbcoeff_fb(self, out): mo_coeff_a = np.asarray(self.wfn.Ca()) mo_coeff_b = np.asarray(self.wfn.Cb()) diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 8c80b6b7..af5cec5c 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -38,35 +38,14 @@ def __init__(self, scfres): self.scfres = scfres self.backend = "pyscf" - def _update_gauge_origin(self, gauge_origin): - coords = self.scfres.mol.atom_coords() - masses = self.scfres.mol.atom_mass_list() - charges = self.scfres.mol.atom_charges() - if gauge_origin == "mass_center": - gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) - elif gauge_origin == "charge_center": - gauge_origin = list(np.einsum("i,ij->j", charges, coords) - / charges.sum()) - elif gauge_origin == "origin": - gauge_origin = [0.0, 0.0, 0.0] - elif isinstance(gauge_origin, list): - gauge_origin = gauge_origin - else: - raise NotImplementedError("Gauge origin has to be defined either by" - " using one of the keywords" - " mass_center, charge_center or origin." - " Or by declaring a list eg. [x, y, z]" - " in atomic units.") - return gauge_origin - @cached_property def electric_dipole(self): return list(self.scfres.mol.intor_symmetric('int1e_r', comp=3)) @property def magnetic_dipole(self): - def magnetic_dipole_gauge(gauge_origin): - gauge_origin = self._update_gauge_origin(gauge_origin) + def magnetic_dipole_gauge(gauge_origin="origin"): + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) @@ -76,7 +55,7 @@ def magnetic_dipole_gauge(gauge_origin): @property def nabla(self): def nabla_gauge(gauge_origin): - gauge_origin = self._update_gauge_origin(gauge_origin) + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) @@ -84,32 +63,23 @@ def nabla_gauge(gauge_origin): return nabla_gauge @property - def dia_magnet(self): - def dia_magnet_gauge(gauge_origin): - gauge_origin = self._update_gauge_origin(gauge_origin) + def electric_quadrupole(self): + def electric_quadrupole_gauge(gauge_origin): + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) - r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) - r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], - r_quadr.shape[0])) - for i in range(3): - r_quadr_matrix[i][i] = r_quadr - term = -0.25 * (r_quadr_matrix - r_r) - term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) - return list(term) - return dia_magnet_gauge + return list(r_r) + return electric_quadrupole_gauge @property def electric_quadrupole_traceless(self): def electric_quadrupole_traceless_gauge(gauge_origin): - gauge_origin = self._update_gauge_origin(gauge_origin) + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) - r_quadr_matrix = np.zeros((3, 3, r_quadr.shape[0], - r_quadr.shape[0])) + r_quadr_matrix = np.zeros_like(r_r) for i in range(3): r_quadr_matrix[i][i] = r_quadr term = 0.5 * (3 * r_r - r_quadr_matrix) @@ -118,13 +88,20 @@ def electric_quadrupole_traceless_gauge(gauge_origin): return electric_quadrupole_traceless_gauge @property - def electric_quadrupole(self): - def electric_quadrupole_gauge(gauge_origin): - gauge_origin = self._update_gauge_origin(gauge_origin) + def diamagnetic_magnetizability(self): + def diamagnetic_magnetizability_gauge(gauge_origin): + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) - return list(r_r) - return electric_quadrupole_gauge + r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) + r_quadr = self.scfres.mol.intor_symmetric('int1e_r2', comp=1) + r_quadr_matrix = np.zeros_like(r_r) + for i in range(3): + r_quadr_matrix[i][i] = r_quadr + term = -0.25 * (r_quadr_matrix - r_r) + term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + return list(term) + return diamagnetic_magnetizability_gauge @property def pe_induction_elec(self): @@ -278,23 +255,6 @@ def get_restricted(self): "not determine restricted / unrestricted.") return restricted - def get_n_atoms(self): - n_atoms = self.scfres.mol.natm - return n_atoms - - def get_nuclear_charges(self): - charges = self.scfres.mol.atom_charges() - return charges - - def get_nuclear_masses(self): - masses = self.scfres.mol.atom_mass_list() - return masses - - def get_coordinates(self): - coords = self.scfres.mol.atom_coords() - coords = np.reshape(coords, (3 * coords.shape[0])) - return coords - def get_energy_scf(self): return float(self.scfres.e_tot) @@ -312,7 +272,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return int(self.scfres.mol.nao_nr()) - def get_nuclear_multipole(self, order, gauge_origin=[0.0, 0.0, 0.0]): + def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): charges = self.scfres.mol.atom_charges() if order == 0: # The function interface needs to be a np.array on return @@ -321,15 +281,17 @@ def get_nuclear_multipole(self, order, gauge_origin=[0.0, 0.0, 0.0]): coords = self.scfres.mol.atom_coords() return np.einsum("i,ix->x", charges, coords) elif order == 2: - gauge_origin = \ - self.operator_integral_provider._update_gauge_origin(gauge_origin) coords = self.scfres.mol.atom_coords() - gauge_origin r_r = np.einsum("ij,ik->ijk", coords, coords) res = np.einsum("i,ijk->jk", charges, r_r) - return res + res = [res[0, 0], res[0, 1], res[0, 2], res[1, 1], res[1, 2], res[2, 2]] + return np.array(res) else: raise NotImplementedError("get_nuclear_multipole with order > 2") + def get_gauge_origin(self, gauge_origin): + return _determine_gauge_origin(self.scfres, gauge_origin) + def fill_occupation_f(self, out): if self.restricted: out[:] = np.hstack((self.scfres.mo_occ / 2, @@ -470,3 +432,23 @@ def run_core_hole(xyz, basis, charge=0, multiplicity=1, mf_chole.diis_space = 3 mf_chole.kernel(dm0) return mf_chole + + +def _determine_gauge_origin(scfres, gauge_origin): + coords = scfres.mol.atom_coords() + masses = scfres.mol.atom_mass_list() + charges = scfres.mol.atom_charges() + if gauge_origin == "mass_center": + gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) + elif gauge_origin == "charge_center": + gauge_origin = list(np.einsum("i,ij->j", charges, coords) + / charges.sum()) + elif gauge_origin == "origin": + gauge_origin = [0.0, 0.0, 0.0] + elif isinstance(gauge_origin, list): + gauge_origin = gauge_origin + else: + raise NotImplementedError("Gauge origin has to be defined either by" + " using one of the keywords" + " mass_center, charge_center or origin.") + return gauge_origin diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index f88e74ad..0b83e79a 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -183,7 +183,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return self.mol_orbs.number_aos() - def get_nuclear_multipole(self, order): + def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): mol = self.scfdrv.task.molecule nuc_charges = mol.elem_ids_to_numpy() if order == 0: @@ -197,6 +197,9 @@ def get_nuclear_multipole(self, order): else: raise NotImplementedError("get_nuclear_multipole with order > 1") + def get_gauge_origin(self, gauge_origin): + raise NotImplementedError("get_gauge_origin not implemented.") + def fill_orbcoeff_fb(self, out): mo_coeff_a = self.mol_orbs.alpha_to_numpy() mo_coeff_b = self.mol_orbs.beta_to_numpy() diff --git a/adcc/test_properties_consistency.py b/adcc/test_properties_consistency.py index b015dd50..8ed07a94 100644 --- a/adcc/test_properties_consistency.py +++ b/adcc/test_properties_consistency.py @@ -45,7 +45,7 @@ def base_test(self, system, method, kind): n_ref = len(state.excitation_vector) assert_allclose( res_dms, - ref[f"transition_magnetic_dipole_moments_{gauge_origin}"][:n_ref], + ref["transition_magnetic_dipole_moments"][gauge_origin][:n_ref], atol=1e-4 ) @@ -63,7 +63,7 @@ def base_test(self, system, method, kind): n_ref = len(state.excitation_vector) assert_allclose( res_dms, - ref[f"transition_dipole_moments_velocity_{gauge_origin}"][:n_ref], + ref["transition_dipole_moments_velocity"][gauge_origin][:n_ref], atol=1e-4 ) @@ -81,7 +81,7 @@ def base_test(self, system, method, kind): n_ref = len(state.excitation_vector) assert_allclose( res_dms, - ref[f"transition_quadrupole_moments_{gauge_origin}"][:n_ref], + ref["transition_quadrupole_moments"][gauge_origin][:n_ref], atol=1e-4) @@ -93,9 +93,9 @@ def base_test(self, system, method, kind): state = cache.adcc_states[system][method][kind] for gauge_origin in gauge_origins: res_rots = state.rotatory_strength(gauge_origin) - ref_tmdm = refdata[f"transition_magnetic_dipole_moments_{gauge_origin}"] + ref_tmdm = refdata["transition_magnetic_dipole_moments"][gauge_origin] ref_tdmvel = \ - refdata[f"transition_dipole_moments_velocity_{gauge_origin}"] + refdata["transition_dipole_moments_velocity"][gauge_origin] refevals = refdata["eigenvalues"] n_ref = len(state.excitation_vector) for i in range(n_ref): diff --git a/adcc/testdata/dump_reference_adcc.py b/adcc/testdata/dump_reference_adcc.py index d1014ca5..1f305ca3 100644 --- a/adcc/testdata/dump_reference_adcc.py +++ b/adcc/testdata/dump_reference_adcc.py @@ -54,6 +54,7 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", if not len(states): raise ValueError("No excited states obtained.") + # # MP # @@ -186,24 +187,14 @@ def dump_reference_adcc(data, method, dumpfile, mp_tree="mp", adc_tree="adc", adc[kind + "/state_dipole_moments"] = state.state_dipole_moment adc[kind + "/transition_dipole_moments"] = state.transition_dipole_moment # gauge origin dependent - adc[kind + "/transition_dipole_moments_velocity_origin"] = \ - state.transition_dipole_moment_velocity("origin") - adc[kind + "/transition_dipole_moments_velocity_mass_center"] = \ - state.transition_dipole_moment_velocity("mass_center") - adc[kind + "/transition_dipole_moments_velocity_charge_center"] = \ - state.transition_dipole_moment_velocity("charge_center") - adc[kind + "/transition_magnetic_dipole_moments_origin"] = \ - state.transition_magnetic_dipole_moment("origin") - adc[kind + "/transition_magnetic_dipole_moments_mass_center"] = \ - state.transition_magnetic_dipole_moment("mass_center") - adc[kind + "/transition_magnetic_dipole_moments_charge_center"] = \ - state.transition_magnetic_dipole_moment("charge_center") - adc[kind + "/transition_quadrupole_moments_origin"] = \ - state.transition_quadrupole_moment("origin") - adc[kind + "/transition_quadrupole_moments_mass_center"] = \ - state.transition_quadrupole_moment("mass_center") - adc[kind + "/transition_quadrupole_moments_charge_center"] = \ - state.transition_quadrupole_moment("charge_center") + gauge_origins = ["origin", "mass_center", "charge_center"] + for gauge_origin in gauge_origins: + adc[kind + "/transition_dipole_moments_velocity/" + gauge_origin] = \ + state.transition_dipole_moment_velocity(gauge_origin) + adc[kind + "/transition_magnetic_dipole_moments/" + gauge_origin] = \ + state.transition_magnetic_dipole_moment(gauge_origin) + adc[kind + "/transition_quadrupole_moments/" + gauge_origin] = \ + state.transition_quadrupole_moment(gauge_origin) adc[kind + "/eigenvalues"] = state.excitation_energy adc[kind + "/eigenvectors_singles"] = np.asarray( eigenvectors_singles) diff --git a/adcc/testdata/generate_reference.py b/adcc/testdata/generate_reference.py index 5a46911c..10ffc9c5 100755 --- a/adcc/testdata/generate_reference.py +++ b/adcc/testdata/generate_reference.py @@ -36,8 +36,7 @@ import adcctestdata as atd # noqa: E402 -def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", - generator="adcc"): +def dump_all(case, kwargs, kwargs_overwrite={}, spec="gen", generator="adcc"): assert spec in ["gen", "cvs"] for method in ["adc0", "adc1", "adc2", "adc2x", "adc3"]: kw = kwargs_overwrite.get(method, kwargs) @@ -55,6 +54,7 @@ def dump_method(case, method, kwargs, spec, generator="adcc"): else: dumpfunction = dump_reference_adcc hfdata = adcc.DataHfProvider(h5py.File(h5file, "r")) + # Get dictionary of parameters for the reference cases. refcases = ast.literal_eval(hfdata.data["reference_cases"][()].decode()) kwargs = dict(kwargs) @@ -77,7 +77,6 @@ def dump_method(case, method, kwargs, spec, generator="adcc"): dumpfile = "{}_reference_{}{}.hdf5".format(case, prefix, method) else: dumpfile = "{}_adcc_reference_{}{}.hdf5".format(case, prefix, method) - if not os.path.isfile(dumpfile): dumpfunction(hfdata, fullmethod, dumpfile, mp_tree=mp_tree, adc_tree=adc_tree, n_states_full=2, **kwargs) @@ -169,10 +168,8 @@ def dump_h2s_6311g(): def dump_methox_sto3g(): # (R)-2-methyloxirane kwargs = {"n_singlets": 2} - dump_all("methox_sto3g", kwargs, spec="gen", - generator="adcc") - dump_all("methox_sto3g", kwargs, spec="cvs", - generator="adcc") + dump_all("methox_sto3g", kwargs, spec="gen", generator="adcc") + dump_all("methox_sto3g", kwargs, spec="cvs", generator="adcc") def main(): diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 52ea7323..1a4c494d 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -21,6 +21,7 @@ #include "config.hh" #include #include +#include namespace libadcc { /** Access to a Hartree-Fock solution where all quantities are @@ -41,6 +42,18 @@ namespace libadcc { */ class HartreeFockSolution_i { public: + /** \name System information */ + ///@{ + /** Fill a buffer with nuclear multipole data for the nuclear multipole of + * given order. */ + virtual void nuclear_multipole(size_t order, std::vector gauge_origin, + scalar_type* buffer, size_t size) const = 0; + //@} + + /** Determine the gauge origin. */ + virtual std::vector determine_gauge_origin( + std::string gauge_origin) const = 0; + /** \name Sizes of the data */ ///@{ /** Return the number of HF *spin* molecular orbitals of alpha spin. @@ -61,7 +74,6 @@ class HartreeFockSolution_i { * */ virtual size_t n_bas() const = 0; ///@} - // /** \name Access to HF SCF results */ ///@{ diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index d64df7c4..d5e69d44 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -350,6 +350,19 @@ std::string ReferenceState::irreducible_representation() const { throw not_implemented_error("Only C1 is implemented."); } +std::vector ReferenceState::nuclear_multipole( + size_t order, std::vector gauge_origin) const { + std::vector ret((order + 2) * (order + 1) / 2); + m_hfsoln_ptr->nuclear_multipole(order, gauge_origin, ret.data(), ret.size()); + return ret; +} + +std::vector ReferenceState::determine_gauge_origin( + std::string gauge_origin) const { + std::vector ret = m_hfsoln_ptr->determine_gauge_origin(gauge_origin); + return ret; +} + // // Tensor data // diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index 1c2dc219..338b0211 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -90,6 +90,15 @@ class ReferenceState { /** Return the number of beta electrons */ size_t n_beta() const { return m_n_beta; } + /** Return the nuclear contribution to the cartesian multipole moment + * (in standard ordering, i.e. xx, xy, xz, yy, yz, zz) of the given order. */ + std::vector nuclear_multipole(size_t order, + std::vector gauge_origin = { + 0, 0, 0}) const; + + /** Determine the gauge origin for nuclear multipoles. */ + std::vector determine_gauge_origin(std::string gauge_origin) const; + /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 8d735637..9ac0b802 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -47,6 +47,27 @@ class HartreeFockProvider : public HartreeFockSolution_i { // // Translate C++-like interface to python-like interface // + void nuclear_multipole(size_t order, std::vector gauge_origin, + scalar_type* buffer, size_t size) const override { + py::array_t ret = get_nuclear_multipole(order, py::cast(gauge_origin)); + if (static_cast(size) != ret.size()) { + throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + + ") does not agree with buffer size (" + + std::to_string(size) + ")."); + } + std::copy(ret.data(), ret.data() + size, buffer); + } + + std::vector determine_gauge_origin( + std::string gauge_origin) const override { + std::vector ret = + py::cast>(get_gauge_origin(py::cast(gauge_origin))); + if (ret.size() != 3) { + throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + + ") needs to be 3."); + } + return ret; + } void occupation_f(scalar_type* buffer, size_t size) const override { const ssize_t ssize = static_cast(size); @@ -237,13 +258,17 @@ class HartreeFockProvider : public HartreeFockSolution_i { // // Interface for the python world // - virtual size_t get_n_orbs_alpha() const = 0; - virtual size_t get_n_bas() const = 0; - virtual real_type get_conv_tol() const = 0; - virtual bool get_restricted() const = 0; - virtual size_t get_spin_multiplicity() const = 0; - virtual real_type get_energy_scf() const = 0; - virtual std::string get_backend() const = 0; + virtual size_t get_n_orbs_alpha() const = 0; + virtual size_t get_n_bas() const = 0; + + virtual py::array_t get_nuclear_multipole( + size_t order, py::array_t gauge_origin) const = 0; + virtual py::list get_gauge_origin(py::str gauge_origin) const = 0; + virtual real_type get_conv_tol() const = 0; + virtual bool get_restricted() const = 0; + virtual size_t get_spin_multiplicity() const = 0; + virtual real_type get_energy_scf() const = 0; + virtual std::string get_backend() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -266,6 +291,15 @@ class PyHartreeFockProvider : public HartreeFockProvider { size_t get_n_bas() const override { PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_bas, ); } + py::array_t get_nuclear_multipole( + size_t order, py::array_t gauge_origin) const override { + PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, + get_nuclear_multipole, order, gauge_origin); + } + py::list get_gauge_origin(py::str gauge_origin) const override { + PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, + get_gauge_origin, gauge_origin); + } real_type get_conv_tol() const override { PYBIND11_OVERLOAD_PURE(real_type, HartreeFockProvider, get_conv_tol, ); } @@ -419,6 +453,13 @@ void export_HartreeFockProvider(py::module& m) { .def("get_n_bas", &HartreeFockProvider::get_n_bas, "Returns the number of *spatial* one-electron basis functions. This value " "is abbreviated by `nb` in the documentation.") + .def("get_nuclear_multipole", &HartreeFockProvider::get_nuclear_multipole, + "Returns the nuclear multipole of the requested order. For `0` returns the " + "total nuclear charge as an array of size 1, for `1` returns the nuclear " + "dipole moment as an array of size 3.") + .def("get_gauge_origin", &HartreeFockProvider::get_gauge_origin, + "Determines the gauge origin.") + // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " "number for each SCF orbital.") diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index dc78a40c..f22a74cd 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -108,6 +108,26 @@ void export_ReferenceState(py::module& m) { "Number of alpha electrons") .def_property_readonly("n_beta", &ReferenceState::n_beta, "Number of beta electrons") + .def_property_readonly( + "nuclear_total_charge", + [](const ReferenceState& ref) { return ref.nuclear_multipole(0)[0]; }) + .def_property_readonly("nuclear_dipole", + [](const ReferenceState& ref) { + py::array_t ret(std::vector{3}); + auto res = ref.nuclear_multipole(1); + std::copy(res.begin(), res.end(), ret.mutable_data()); + return res; + }) + .def("nuclear_quadrupole", + [](const ReferenceState& ref, py::array_t gauge_origin) { + py::array_t ret(std::vector{6}); + auto res = ref.nuclear_multipole( + 2, py::cast>(gauge_origin)); + std::copy(res.begin(), res.end(), ret.mutable_data()); + return res; + }) + .def("determine_gauge_origin", &ReferenceState::determine_gauge_origin, + "Determine the gauge origin.") .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From c43e16eabfa6fba4115b716bc271c7400dd4afdb Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 29 Oct 2024 09:31:46 +0100 Subject: [PATCH 25/42] Github comments, new testdata --- adcc/ReferenceState.py | 2 +- adcc/adc_pp/modified_transition_moments.py | 1 - adcc/backends/pyscf.py | 2 +- adcc/testdata/0_download_testdata.sh | 2 +- adcc/testdata/SHA256SUMS | 174 ++++++++++----------- libadcc/pyiface/export_ReferenceState.cc | 2 +- 6 files changed, 91 insertions(+), 92 deletions(-) diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index 760aa0f9..35124910 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -228,6 +228,6 @@ def dipole_moment(self): def nuclear_quadrupole(self, gauge_origin="origin"): if isinstance(gauge_origin, str): gauge_origin = self.determine_gauge_origin(gauge_origin) - return super().nuclear_quadrupole(np.array(gauge_origin)) + return super().nuclear_quadrupole(gauge_origin) # TODO some nice describe method diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index 3c2facf4..2fc8856c 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -85,7 +85,6 @@ def mtm_cvs_adc2(mp, op, intermediates): "adc0": mtm_adc0, "adc1": mtm_adc1, "adc2": mtm_adc2, - "adc2x": mtm_adc2, # Identical to ADC(2) "cvs-adc0": mtm_cvs_adc0, "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0) "cvs-adc2": mtm_cvs_adc2, diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index af5cec5c..64cb4f56 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -448,7 +448,7 @@ def _determine_gauge_origin(scfres, gauge_origin): elif isinstance(gauge_origin, list): gauge_origin = gauge_origin else: - raise NotImplementedError("Gauge origin has to be defined either by" + raise NotImplementedError("Gauge origin has to be defined by" " using one of the keywords" " mass_center, charge_center or origin.") return gauge_origin diff --git a/adcc/testdata/0_download_testdata.sh b/adcc/testdata/0_download_testdata.sh index d6562dc7..be748057 100755 --- a/adcc/testdata/0_download_testdata.sh +++ b/adcc/testdata/0_download_testdata.sh @@ -1,6 +1,6 @@ #!/bin/bash -SOURCE="https://wwwagdreuw.iwr.uni-heidelberg.de/adcc_test_data/0.6.0/" +SOURCE="https://wwwagdreuw.iwr.uni-heidelberg.de/adcc_test_data/0.7.0/" DATAFILES=( ch2nh2_sto3g_hfdata.hdf5 ch2nh2_sto3g_hfimport.hdf5 diff --git a/adcc/testdata/SHA256SUMS b/adcc/testdata/SHA256SUMS index 700c20e8..0f32b5bd 100644 --- a/adcc/testdata/SHA256SUMS +++ b/adcc/testdata/SHA256SUMS @@ -1,17 +1,17 @@ -d382636d873f26f9872a9165b398317ae0bf81179f4c77e6cbf227af29ee9635 ch2nh2_sto3g_hfdata.hdf5 -424db3133e90ba113ef2800e115054a382099202976bf58dc560da53389f7da5 ch2nh2_sto3g_hfimport.hdf5 -a90209394acddb851b9695baea3fb3709fc28737a3ad73efb7d4ac86ed72f045 cn_ccpvdz_adcc_reference_adc0.hdf5 -9d098c82d626805612722de7459b5f3ef6dfe093e0609c3a9b149923a3a1b540 cn_ccpvdz_adcc_reference_adc1.hdf5 -378b397cb61a8a653288ff8d78dc931956f530d5165f12dbaab8086dae4d6cdc cn_ccpvdz_adcc_reference_adc2.hdf5 -f6359517efe657c09f4b944915cbe552d4173952416398feb729a32b8b22e30b cn_ccpvdz_adcc_reference_adc2x.hdf5 -6c047f5d7eabfcd96c3f55964f0f1e0e182aa736f1286f9552c72cb7d5f01a6b cn_ccpvdz_adcc_reference_adc3.hdf5 -bf944aa1a8862328e8029e4248e2499e31df82ccb0efd21c849464b2b61cd36d cn_ccpvdz_adcc_reference_cvs_adc0.hdf5 -eae8d19a594d69fcc19b14a82c334dd863a32e1195fd0a16d2bbdc77aefd9d85 cn_ccpvdz_adcc_reference_cvs_adc1.hdf5 -9fe47f96196e5248b0d12ea3c95a4b1394a20f48cb87b14dc3b2daee8e90cbd2 cn_ccpvdz_adcc_reference_cvs_adc2.hdf5 -0394d08b0367fcc9dda40158a54473241b9d3c2c6ec0a569cb4c82ec2b82c8b8 cn_ccpvdz_adcc_reference_cvs_adc2x.hdf5 -cb4fc229e3c4db7c7dd538af400b0f12c8604ad8d7b44144a58ea021a28758f7 cn_ccpvdz_adcc_reference_cvs_adc3.hdf5 -37b3af4a4f1b70c7bc7588f5bfcd542fc96056ccb7fe2e8c9fbda93b546788a0 cn_ccpvdz_hfdata.hdf5 -a15001b874327b1cec2c84bfe4a24695c71633f31aaf618dc25744dda2c1022b cn_ccpvdz_hfimport.hdf5 +21937691636c86b29191ca9a767ed541b464cc54ed9e24c79f3a455d9b218367 ch2nh2_sto3g_hfdata.hdf5 +c2a2083dfbce2ccccf22e0f2262479819e8cbe0d7eea96a06c15f4e3124e3ce4 ch2nh2_sto3g_hfimport.hdf5 +d88f79d2519302656c971d83446c4c5cf03638dcb9caf22ae94004bbf6ea6aa1 cn_ccpvdz_adcc_reference_adc0.hdf5 +4c7820a3b16cfb6252d025cc960999bb80b4b104a75e4952a233a609143271bd cn_ccpvdz_adcc_reference_adc1.hdf5 +7eb4668f0e1d4364fdfbcb45d764a61650c925ee8860c941ab8527f2860ed14c cn_ccpvdz_adcc_reference_adc2.hdf5 +1f29424276feff45c0e3d0aa51f7bb2bead4071d8b955f7c9b1741aa9cce645c cn_ccpvdz_adcc_reference_adc2x.hdf5 +dfe702b8f51409f71181376ff768d22110153385d88ef904b17f97b30dc65ad2 cn_ccpvdz_adcc_reference_adc3.hdf5 +e410d0a2733825712137b7ca76c6a3cf5677359466d10badabe78d05a8de2fa0 cn_ccpvdz_adcc_reference_cvs_adc0.hdf5 +802530dc440ddd3c18c9f19276ff39269204bd702133b5d82736efdede697edb cn_ccpvdz_adcc_reference_cvs_adc1.hdf5 +d0992fa6217c077f6aa2f91ddd4e6059b7a14ed3cb4a5b3efbb7faad0d5cdc23 cn_ccpvdz_adcc_reference_cvs_adc2.hdf5 +bb67b0147b2f971ea5d391bffa28ac0a78a75ad3c458c3bbde21ca613c5987a4 cn_ccpvdz_adcc_reference_cvs_adc2x.hdf5 +d3ab446f0cb10d6480e85e5dbde53dd3f091b74f40a611039f5788483c02931c cn_ccpvdz_adcc_reference_cvs_adc3.hdf5 +ccc2ad480ba09ffdafc8f546b68307937e44f1f6370e05fab8319729c5cf1d6c cn_ccpvdz_hfdata.hdf5 +f0be83b542631fbca1a9896d7ec7fb2418680199f6245aeaaf173c4f84ac98a9 cn_ccpvdz_hfimport.hdf5 7e3a04d69a8110a78c90ada8e3dbccefaf83bee20fc1bf15fbef1d0a610edd44 cn_ccpvdz_reference_adc0.hdf5 ddcfbff7d784eca92ff8150f6938cf765444f41d2fb6140ba462b54378e6bdd5 cn_ccpvdz_reference_adc1.hdf5 d0486fe1ce6605d26ad210e35620b3fb9ffe6ecbce91b89f0f1b845781ef9adf cn_ccpvdz_reference_adc2.hdf5 @@ -22,22 +22,22 @@ a424ae21c0b391c66b5326b70c740f1f2350c67121c1c110afee129d374eda75 cn_ccpvdz_refe f4c11821f40518fc137b88c7975b83b7e273bef578e5663a6072265d295015d1 cn_ccpvdz_reference_cvs_adc2.hdf5 aedb569d840eb1194e64049c7ed215a6a3f7796c33509ce402a6ccdab8c51238 cn_ccpvdz_reference_cvs_adc2x.hdf5 231e170e5b79aee977b010761030be61d4769ea6757f2f6eaea9b7059bbbb796 cn_ccpvdz_reference_cvs_adc3.hdf5 -37d16bd9d69a17ef4037e7045752d9daf2b7331dfdf0e00664e0db519d1cf36c cn_sto3g_adcc_reference_adc0.hdf5 -89695b8b981adbd79ec758194a9fca7e60632f1d7d471a2479051d0038bc7e67 cn_sto3g_adcc_reference_adc1.hdf5 -4f39a7d3e86ee70f10236d719e201549a8fe3473e038d6bc172ea794458ad55f cn_sto3g_adcc_reference_adc2.hdf5 -1cf7aa710e44b83ab35a134cf77e47394dfb9d673520eccfd69317b8beea2d96 cn_sto3g_adcc_reference_adc2x.hdf5 -8a1dcdc71c6a0c6a6e2a62909505864b6fd5e54a68694b056240e3458c6c5ea8 cn_sto3g_adcc_reference_adc3.hdf5 -c96fe3a0e6510e52515960b43443fb2ffa028db566cb293c1840fc2ab0132216 cn_sto3g_adcc_reference_cvs_adc0.hdf5 -f1542392e102a0dcfacaf7f2c15ea5d57f8d86702093b5d5acbf7dc51345f970 cn_sto3g_adcc_reference_cvs_adc1.hdf5 -94a55f810038f7b80afbf5d08b72b317a932ea88ca421c60811b6ee59d3595f3 cn_sto3g_adcc_reference_cvs_adc2.hdf5 -d9f3292549c0614dba5b799999d716eed50046827cc2542e0de7f474fbdce9ef cn_sto3g_adcc_reference_cvs_adc2x.hdf5 -91ffaa47d7618cf5b58543b31c4c67b9f992dafb6f2587b23b8fdf8d88bc51fe cn_sto3g_adcc_reference_cvs_adc3.hdf5 -f23b98ca45be67d78aeedea2bd205d508cb24dfcf490a785aded9c74dd6b9555 cn_sto3g_adcc_reference_fc_adc2.hdf5 -1d909b97af8d0a26844df103fbd427e2e0ed6321a8aae1afc6f5cfd6699cd383 cn_sto3g_adcc_reference_fc_fv_adc2.hdf5 -8001e0ab108f60cac9cbec2d1b2eb5b176a6d36d6f41e483578f30c787314c75 cn_sto3g_adcc_reference_fv_adc2x.hdf5 -f02d78157c457b1064a23c34b95d044cc5a35e46796e9ff22176be88dad1a8d8 cn_sto3g_adcc_reference_fv_cvs_adc2x.hdf5 -31cea3a0bdfe517d1be6615c98849578704377f2ad6134ce127531798e43ad95 cn_sto3g_hfdata.hdf5 -a305861c174acddcdffaa886775ee79c8ed5f41aa19493bbe7f053a7e6996a5f cn_sto3g_hfimport.hdf5 +54f870d4260d68b7689eeaccddc0ecb796e2afe78064250287146646d3d01e96 cn_sto3g_adcc_reference_adc0.hdf5 +68f1327fca3529d8a185c5eecb9b327ab6b8eb02c9183553c843c4d8e01ea1d3 cn_sto3g_adcc_reference_adc1.hdf5 +17c98f054997369d8c3b2fc288c6580a858ca945eb50e780bd057a134a9bd0f3 cn_sto3g_adcc_reference_adc2.hdf5 +25e944d1e1ed2addf4eec9780dadfe66e87785d1c1eb017b88be775a6c4c56ce cn_sto3g_adcc_reference_adc2x.hdf5 +cba958f76dac1c6f945c1d53d3e54f96502d70d36c2c697d8af92f378601f8bc cn_sto3g_adcc_reference_adc3.hdf5 +417a9dceb61af0fc957c1c00d467faf0638ac91d04f89252ce0a9baf7e3bf30c cn_sto3g_adcc_reference_cvs_adc0.hdf5 +40833b1f66204489cbd311cc9bf0e19b193b00c49741e5e26aa4a73d0694075f cn_sto3g_adcc_reference_cvs_adc1.hdf5 +523bb256ff245776e46cfa1310d80d13320d902aac6042c6edb35fbc33f72c6c cn_sto3g_adcc_reference_cvs_adc2.hdf5 +f2fdd3650da410b1d6dc7811412ebc7cd41100f2d6bb967dbe6c10007196a9b7 cn_sto3g_adcc_reference_cvs_adc2x.hdf5 +3508f4a10089543eca984db2152e02483c9d8757e3f0d23b3074051d149cce03 cn_sto3g_adcc_reference_cvs_adc3.hdf5 +d85f5022afadb0439f01b421012fd697d83e5b4d7b4478b8c5ebcaff58c51d08 cn_sto3g_adcc_reference_fc_adc2.hdf5 +e33a61938551ab57f975857c3882480adafd8701236c5f0248abffc65c8723c1 cn_sto3g_adcc_reference_fc_fv_adc2.hdf5 +fac802bb8743d2d9e333915dec0f9d1b2f00c0c6e348bde4ec96b1a2449bb7f3 cn_sto3g_adcc_reference_fv_adc2x.hdf5 +68d7a7391485102456656ad4d052051991023eaa1c41683ed963560d4dc9bc68 cn_sto3g_adcc_reference_fv_cvs_adc2x.hdf5 +84fc3ccad5a08876a757389e171689c10aba1214661b9f28c82ba266df93c236 cn_sto3g_hfdata.hdf5 +023f541f3a467363652a3439314442283e0e4e6f882c2da27f1abf26cff21a2e cn_sto3g_hfimport.hdf5 f28b3a47be0cbe025a002762b7d2f4617116f3c9d7e2485c5d9152409046c2c5 cn_sto3g_reference_adc0.hdf5 0a9142c913e54f2e424fa7acc97677d06b81b93b25c45733e6336ff9be64ea56 cn_sto3g_reference_adc1.hdf5 fe788ddb20a175f10090aae392c0a290911f1bdf85bd1d848a0c71fad5fb469a cn_sto3g_reference_adc2.hdf5 @@ -52,18 +52,18 @@ d26bfe00e8b3cfa8891028e0d3d3dd85b7de3f21450bae6eb11f8ad2db340678 cn_sto3g_refer 5a5442ee98f2b5b34474a030764036d227e17efb9c2c728d0bbb672e1149ff0d cn_sto3g_reference_fc_fv_adc2.hdf5 9b7cfc0c05478837fb919202ddda2d7a0a4a767d1081dc2056fb425dd29b7acf cn_sto3g_reference_fv_adc2x.hdf5 16a2362662192d9ffa1f7e8762ef8e3713bce6a92bc2028668c6679d3f646806 cn_sto3g_reference_fv_cvs_adc2x.hdf5 -b62cc64beadcaef25f81775741a5a67387a322779b8ebc71d0459db683726a8c h2o_def2tzvp_adcc_reference_adc0.hdf5 -a241498449f5a213340d8271fe67e8d7193dd81b02e03e3196cf29690c5c8c6c h2o_def2tzvp_adcc_reference_adc1.hdf5 -53223719acf3f7dcf521f7cfbf2a216e6922d1ce5838a077c8da0dccdb045532 h2o_def2tzvp_adcc_reference_adc2.hdf5 -a22a5c80881b5558dcade03edcc9d5ee6c43c04450d229b803f3e4535486caea h2o_def2tzvp_adcc_reference_adc2x.hdf5 -0d5ada7f23c2d769335dc83675d57aca24733cf6d8f8f5600c3f1ff700d2b0cc h2o_def2tzvp_adcc_reference_adc3.hdf5 -131c8b71fb78a1bcaa027527b7b0df4809e88c4a70c01184a270c12c3865bd79 h2o_def2tzvp_adcc_reference_cvs_adc0.hdf5 -1b5ac0f6c2e361eeb408cf2ccec568d8ad3a43cb181fbbe734b7fd7d6795b29f h2o_def2tzvp_adcc_reference_cvs_adc1.hdf5 -cc47447854895458c3a5cd7a7ad907bc3e34b70d742526907c2e1fc97ebacec3 h2o_def2tzvp_adcc_reference_cvs_adc2.hdf5 -9231fe94045ee86fa387ae9496a95de3c7a7228dc92fa76dcf9c0e7c7d494ff6 h2o_def2tzvp_adcc_reference_cvs_adc2x.hdf5 -0ff53441c178f866ec41e08af12492850451f4993957f45eb448f40f22d712d0 h2o_def2tzvp_adcc_reference_cvs_adc3.hdf5 -60bd2f9e165ba7dde03fc7ddb36279fd58fc48a70fd196b131d56cff46f4da55 h2o_def2tzvp_hfdata.hdf5 -d1ae1621fa8ccaaba07ec6127b331c5e9e356708d16fd77a965a351d504ca74d h2o_def2tzvp_hfimport.hdf5 +6eca816f51ac556c9b07c595e4665b2ce2284c18dd191d282cf1127c000451b9 h2o_def2tzvp_adcc_reference_adc0.hdf5 +2ab3d04e0d73b223290b495cd0979320277d30291728236d945591bfcbb5915e h2o_def2tzvp_adcc_reference_adc1.hdf5 +cbd6933d13c4f1a8414cc19ffc7e10ee1a1fa9ce7e036a95913b02790384598d h2o_def2tzvp_adcc_reference_adc2.hdf5 +4a8ea7a5a293f4a956f854a12f7eae51dc907ab9fa6882d1792682631a91a7d7 h2o_def2tzvp_adcc_reference_adc2x.hdf5 +e58e4687f3e1f84cafdbd9cb2068be4b5a64496d593bb4751741e1c63aec7177 h2o_def2tzvp_adcc_reference_adc3.hdf5 +ce1d36fccab8b841d2928373257d2c16130a0d088be9c2c9fdbf7b226d209930 h2o_def2tzvp_adcc_reference_cvs_adc0.hdf5 +1d2d693d429aa9d8d49e7bc0acbcf91455edaaee48ba03fac2179ab81d0358f6 h2o_def2tzvp_adcc_reference_cvs_adc1.hdf5 +c0942dda0efb680f686f667a85d07b68fcfb2d44a2a69f85648cbc26b1479bab h2o_def2tzvp_adcc_reference_cvs_adc2.hdf5 +e669f51001cec83d99e13ef7bd56658bf750e1d057000f31f3327f00b1eeb46b h2o_def2tzvp_adcc_reference_cvs_adc2x.hdf5 +ed7bc320798f7f831bd6f0894a9f92e43f454438174e2012a362baf550d22dad h2o_def2tzvp_adcc_reference_cvs_adc3.hdf5 +e30ae7dd6d5bbb48c74c1d9e1b84f43a6b7af337f285d293c95a47202331eada h2o_def2tzvp_hfdata.hdf5 +9efb05dfec07da41f8f684ddce2e22b6546d798fa6dad0bd7da1e42c4657bcb2 h2o_def2tzvp_hfimport.hdf5 ec9c7fa17528f2858f96e36d73e18cbfec41a97923ecc4adaafe249510e087e2 h2o_def2tzvp_reference_adc0.hdf5 2a0833a0f62c5d77d3ca1222d0f94763c03e10eb6c0d5a4224ea02faaa374ca9 h2o_def2tzvp_reference_adc1.hdf5 051d2e6415ff014d5e6a8d06c70ed8ceea7e0c948839e8dc5d1afd00d46ee4d3 h2o_def2tzvp_reference_adc2.hdf5 @@ -74,22 +74,22 @@ ded6e11ac23516170d0abf8c5e654d4c55aeb5ca79d4e5b77d933ce50f6167a7 h2o_def2tzvp_r c5f6330421604e47e573512d51b1de20cac9b7931a1c129d9b64e41a702929aa h2o_def2tzvp_reference_cvs_adc2.hdf5 0726b458f688ec93f77a0ed2a238d14a497b937b02bd2b7056c784aaaf12b165 h2o_def2tzvp_reference_cvs_adc2x.hdf5 370369bb87640cdbdbae03fa45466409900301c8d785bcd8de0aeb14ee9a8792 h2o_def2tzvp_reference_cvs_adc3.hdf5 -a28056b345f7931aedc15adebfa7eecb6be816fbae7c3ba3b258becbcc6b5097 h2o_sto3g_adcc_reference_adc0.hdf5 -5aac72ad92903392911ff6259be67cdba10bc81dd38aa2c495013b04249751b8 h2o_sto3g_adcc_reference_adc1.hdf5 -12c416e18754e466694ff5d91adcd94e3f0cca1b764b969bf795b803cd2fbaa0 h2o_sto3g_adcc_reference_adc2.hdf5 -52a22b5c74b046782064895a9406d4473c2fa7cbc8621042acc62b8dd5fa88e8 h2o_sto3g_adcc_reference_adc2x.hdf5 -5f9ce29b4e9444070b4906c34d86c72d4e9e10c10e2d44bbdadf273b455935dd h2o_sto3g_adcc_reference_adc3.hdf5 -310030de96c7a3003bfc6449bed01e1edc7d284ded2799886c8f22c8a2d67a04 h2o_sto3g_adcc_reference_cvs_adc0.hdf5 -3eb257d23f65baba416b222a30a201bf409ef6da4213cfb44fc981a5573d3033 h2o_sto3g_adcc_reference_cvs_adc1.hdf5 -a4f496f4bb82e515066c78bb8e3a3b075ab56eb92e3fb4204dd36549f8d93c58 h2o_sto3g_adcc_reference_cvs_adc2.hdf5 -18492031edeac1098926b57f7a56744ac087fe25f6e067b0dee02ed30c454755 h2o_sto3g_adcc_reference_cvs_adc2x.hdf5 -121b3ccc559e477e609e703d8cdd24223c60bbf7fd920056658d45d766fcc28e h2o_sto3g_adcc_reference_cvs_adc3.hdf5 -eb46e11ce5909075e915830ba4b36a04eb19fa84dd640186ff7dc3454ec62e21 h2o_sto3g_adcc_reference_fc_adc2.hdf5 -e8269c3578079ab65875c6d23d66bc2ea696984db89b3dfbcd2c2682600bb13e h2o_sto3g_adcc_reference_fc_fv_adc2.hdf5 -3e4b71fe50163a9e5070b5e9427cadc6b8fb023e4117917b0de594665d667d2e h2o_sto3g_adcc_reference_fv_adc2x.hdf5 -35829d19c33ca83d89bfc497fcb9b862a0b2362f67e0e452f34cd877efa110df h2o_sto3g_adcc_reference_fv_cvs_adc2x.hdf5 -e0e17c4cc9618edbba2be2ad77c41c4b212deb9e88b458a2666a2a803a0cdba5 h2o_sto3g_hfdata.hdf5 -9bf19ad2a3e3f8d70188c7bdba0b39b044a760ddc668ae4a419ef2fb41337d9e h2o_sto3g_hfimport.hdf5 +f7287ea780312dae228725dd7d3cd22ab36e216b5a0bc78dad2d61d6e689e2e0 h2o_sto3g_adcc_reference_adc0.hdf5 +df6580d646a46fae7aea85a8941a50bfc109473af67a5fb2b6f50af8a4415ba9 h2o_sto3g_adcc_reference_adc1.hdf5 +9f82d23076bad4f05be8d1f6b7e0949407815ac835eb0cff230f0f9c6d0b2580 h2o_sto3g_adcc_reference_adc2.hdf5 +8b9f9ba5f55cc18b272a911df1c305a62139569081f43479b91001df8333928f h2o_sto3g_adcc_reference_adc2x.hdf5 +3dfb1784584e8c4a5216cc2809f54e6945532dc1a865ed47bfb0b17a109c93dc h2o_sto3g_adcc_reference_adc3.hdf5 +89ee787a49bcbaaace366520c36ae8c03d2337e175ff4afc504533f78f5aa3ad h2o_sto3g_adcc_reference_cvs_adc0.hdf5 +a9efed2683829e5a5cec7b7dd171c03cd775cd0abc1ca23b238906d0069de9a3 h2o_sto3g_adcc_reference_cvs_adc1.hdf5 +7b64ab5010505aeda70450e6512a4dca8aca0852d925d7793302ece0fb01ef86 h2o_sto3g_adcc_reference_cvs_adc2.hdf5 +d2e431a536fcd193723949cbd600f6fae9dc6cdce04c4ee21e6e0f70a174c54a h2o_sto3g_adcc_reference_cvs_adc2x.hdf5 +f7d804df546f5e3afe5e4ae413462784dd8753bb3c6cbf60c99e79a48d406128 h2o_sto3g_adcc_reference_cvs_adc3.hdf5 +128ee8657d012888338d6acaec26443dca6a6447a4c780558681671dc2598657 h2o_sto3g_adcc_reference_fc_adc2.hdf5 +245d48e951a640b8d2a68825b9fa4dc2d1eca3fc4ee95dcd7395e860cd8e9ccd h2o_sto3g_adcc_reference_fc_fv_adc2.hdf5 +704cb27513afa4b7e58e299c02e26d098aca90e99db2c2a9b095f92d889bfefe h2o_sto3g_adcc_reference_fv_adc2x.hdf5 +965970f1567b3f28c40c78dae8e95fd4e98954138201be4d147e67f27c4947af h2o_sto3g_adcc_reference_fv_cvs_adc2x.hdf5 +0bf8ae74668122ee290a7857fe9caece37fc0388527541dfcc859979924d3878 h2o_sto3g_hfdata.hdf5 +dc87a84324456a0313750e82d1351a8bb0d406b34f92af796ab3bad6b9fbec71 h2o_sto3g_hfimport.hdf5 41311ff99ea440583fb653108263bba37e644fba99507cc1b228818c535d21bf h2o_sto3g_reference_adc0.hdf5 27a4844e53e5af0d9e4e318958e8b0b4377ab959d9303743d9ee030490818f57 h2o_sto3g_reference_adc1.hdf5 dc62e796373ba77f78ac11d558961be4b5964f7400a733316540f0d70993de07 h2o_sto3g_reference_adc2.hdf5 @@ -104,15 +104,15 @@ c32008939174db29392760f46e75826ad266bcdced4c7bddddfa6408f14f3a02 h2o_sto3g_refe de7fea54d9f21e3a5c28aa724a35946f8bb65c49563025e0446e3ad6f09e8689 h2o_sto3g_reference_fc_fv_adc2.hdf5 20d40379e52c8b5abe2c618f23da82c4f275722601e3484b3b5ed813ab98c69c h2o_sto3g_reference_fv_adc2x.hdf5 db372645e0519fad40508402484bc53aaae62e1c46b74ad118e042498fd2907c h2o_sto3g_reference_fv_cvs_adc2x.hdf5 -2ce0dbdc6d490a9e30b7ca381e9419ef6851e73eb65df0723c3ac4e1181ba0ed h2s_6311g_adcc_reference_adc2.hdf5 -ddf34e94c92bc5d3fa881123039272c3295978fba7b44c5cd21d6721baa892bf h2s_6311g_adcc_reference_cvs_adc2x.hdf5 -c159bc66a77c91b0f09f07efb62b7ed9fb327a2c130a2b05d350af9a3ee22cd3 h2s_6311g_adcc_reference_fc_adc2.hdf5 -fcdb75cb273a80f085cff74181bff63cb77de017da9af5009e4a0dcaa1f337f9 h2s_6311g_adcc_reference_fc_cvs_adc2x.hdf5 -45ee13b5d6e5588ec1333e84f3cd09119042787c5d8b4b7235fc7e9fe7ab63c7 h2s_6311g_adcc_reference_fc_fv_adc2.hdf5 -5fab0cd953b59a7933acb9eb4070155de9281626c5a7d67c0045514c585524be h2s_6311g_adcc_reference_fc_fv_cvs_adc2x.hdf5 -f81cdd5fdbd41965b093aa7044c2e28cfcb9aa54a0b6fb8b1267e8cd1555c57e h2s_6311g_adcc_reference_fv_adc2.hdf5 -3161dcfc17b949ea0d0e050062aaf0718ee888b72ae27fb52207dacdc44dfad3 h2s_6311g_adcc_reference_fv_cvs_adc2x.hdf5 -057ef124dc4b3fdfe4958f74009500a6507c8f2bb2a24f0a9cb4bc8119334c10 h2s_6311g_hfdata.hdf5 +a140c510893fad52cec8524fdd0bc1e781efc7f04875de57b9c3c11af7957e83 h2s_6311g_adcc_reference_adc2.hdf5 +c1192f1d393aec6b2fdd7ecda101194860b9a4a929b6ebcbe84812f628a4db66 h2s_6311g_adcc_reference_cvs_adc2x.hdf5 +d186fe096831ce6afae1439d986c51544122b025bc87613a7c62232472da423d h2s_6311g_adcc_reference_fc_adc2.hdf5 +80a37db5463685553db809daeff3b7c83c4dee21918427e9bc94c150d02c40bf h2s_6311g_adcc_reference_fc_cvs_adc2x.hdf5 +cc3e6259929350eec5449357d7833ffd1f55bf23b618ea500f9eaad9b785c4b5 h2s_6311g_adcc_reference_fc_fv_adc2.hdf5 +b3bc01e2d2e66d6073dd6f54f565a5012d1911f19888661eef5b1f6548dedc89 h2s_6311g_adcc_reference_fc_fv_cvs_adc2x.hdf5 +569d5d6b1226f32679d02256b654b8e8203c88163a49beccd67a17135150de59 h2s_6311g_adcc_reference_fv_adc2.hdf5 +57ed59e0315e2736b411f1ed15f2532eaf4415c876f5ec6212f4fc3d7fd40f7d h2s_6311g_adcc_reference_fv_cvs_adc2x.hdf5 +0b93546953eb2d0f824dbec26377c7f432c32e710062b971db9a1983532011ad h2s_6311g_hfdata.hdf5 457e5a4cc77d25fe6021065c43a7cb52fe353e4c0bff12c805650e93c2256d30 h2s_6311g_reference_adc2.hdf5 de4b464d8275e0941c76f047d3ea21b2c3396b1b27fbf870fa715c9c2a0d5b62 h2s_6311g_reference_cvs_adc2x.hdf5 ba02b69f3c320e4d1eee7caf1201e310b344ce749e7bfa9451a99953281ad0bf h2s_6311g_reference_fc_adc2.hdf5 @@ -121,33 +121,33 @@ e75f1193409af67502ea4b0e44cc5077d219d1efa96e054713bc2c693f8ee035 h2s_6311g_refe f1aae3bc870c1a11c2f3eded009369ba4502577542819bd40d018f9db015a62c h2s_6311g_reference_fc_fv_cvs_adc2x.hdf5 6721731cbd54998f325e49878f142a6ffea3fa4022f76e8e2af185ce83c80935 h2s_6311g_reference_fv_adc2.hdf5 5be5c174e016eb8a5482b016409285ea52ee3ecc3580a85ae7c426307633735f h2s_6311g_reference_fv_cvs_adc2x.hdf5 -868215be06fe6b84c595404907dff71f41124879ee7709c68e78836324544bdd h2s_sto3g_adcc_reference_fc_cvs_adc2.hdf5 -eee4b916e7c1c10d083dc378529f4c1fbf6c95a0760e49ed79ae22979ad6d55e h2s_sto3g_adcc_reference_fc_fv_cvs_adc2x.hdf5 -4354d9a8f676370454deb43116775f111d9e1a32508ef7e0adaa0cca67c1a87d h2s_sto3g_hfdata.hdf5 +bc06ab7fcf648a625c47d2f8f55dd45ac366c3229f2ed602073d214ce1b4eec5 h2s_sto3g_adcc_reference_fc_cvs_adc2.hdf5 +16d565a1c20183a3e99dc94eee6e53c9cd6225e65e86a2ccf7a13b5996d0accb h2s_sto3g_adcc_reference_fc_fv_cvs_adc2x.hdf5 +c345d2aac6246e327ae69f5fc0281d6f16661b89ffb7ee025d153dc0eafdb97e h2s_sto3g_hfdata.hdf5 63312155f2dcdb8a342014506e10897e738bf5adb23215d12664d3f90e512ad5 h2s_sto3g_reference_fc_cvs_adc2.hdf5 16c373e332f9d845d35f02a7c0b20050127ef4ddaadb8ae89e98131370ce958d h2s_sto3g_reference_fc_fv_cvs_adc2x.hdf5 -4538929e3fc6cecb6f7600978432ac0fbb5e3584e2c58b87764533ed98c85238 hf3_631g_adcc_reference_adc0.hdf5 -accf92145e5feabbd56dd7a95bb3faf969004b31ca181df23d768b072d39718d hf3_631g_adcc_reference_adc1.hdf5 -a61d174ab634524a3d3f3bfcdaad4b1ec295201c68d7cb323c910d9a42b54e17 hf3_631g_adcc_reference_adc2.hdf5 -313bd8bf55eb66c4118179729db851bc6a1206860dd62babca2c5d5b8912c484 hf3_631g_adcc_reference_adc2x.hdf5 -a4468b496516a05ba413c550751494aecbde0042b8cb8cd419f95bed55791a5e hf3_631g_adcc_reference_adc3.hdf5 -93bf9f69b8ab2c524b499bc60790f245401ab0d65cc342e92479c68051b0f34d hf3_631g_hfdata.hdf5 +841c71acda2631c355a877df3c5592ed7ed67b55e7bf25cd74eb6e1f6050cca6 hf3_631g_adcc_reference_adc0.hdf5 +727576fe7d3299cf02bffd7e4873afecd965732533072d9353de8f11d2a055d6 hf3_631g_adcc_reference_adc1.hdf5 +3216a03d5269b44afb014dfde00172ee028e5f11578536c7fb5485aee866093c hf3_631g_adcc_reference_adc2.hdf5 +24774f99176c40da0870e00819970293f6b9a2e644702d6486c1e57ba439711f hf3_631g_adcc_reference_adc2x.hdf5 +26e7bfdaf6ae0e88f0154fcc6e943261cbb2c3dffdde0b035d117b5fa5d9fea6 hf3_631g_adcc_reference_adc3.hdf5 +1dfdc20e4c651482ade7ca10b35aba99a4cd60cde15f682e6145397744f75f99 hf3_631g_hfdata.hdf5 a3d40fe926cd3bf4a0d8e81747cc449238aecc3b34366e0d4214b2a2ad61130f hf3_631g_reference_adc0.hdf5 0c11fdcb42b1004844c85f9da7427a611f77d3315040c3abccc8b60033275ea6 hf3_631g_reference_adc1.hdf5 4c950719db7ae7b799cb55c82235c87c811b2a9ca8bb99bb727196a334ecee99 hf3_631g_reference_adc2.hdf5 5d43387bda90cd51e2da2a904a34e63ad5ddd3e3b891d41ac306ee1233b2a293 hf3_631g_reference_adc2x.hdf5 a64f0d44387dcc56a73c986da34070a4ec5888191cb42511b5c4ef6acbc324b4 hf3_631g_reference_adc3.hdf5 -f37dd8e58d711733edd3691b0ad25c8f85dc425a245af843b29258fb27285e12 methox_sto3g_adcc_reference_adc0.hdf5 -d552d1b4b2a24756fcf938cc62d85df5b0cc388fbb7de0897b98f5e586956607 methox_sto3g_adcc_reference_adc1.hdf5 -f64b3320d42f065337c3f82ffe2ae6055d4fd2a4898e10f8b300cd4c047db00d methox_sto3g_adcc_reference_adc2.hdf5 -dccd8196452170f06bb4036c7363b9594cc2a36c64c9247b083dbb7cc4615f4a methox_sto3g_adcc_reference_adc2x.hdf5 -cbea968982f537fb547841c2efb997e840fdb8cae799019345770d5e05a052f4 methox_sto3g_adcc_reference_adc3.hdf5 -c900a0569c2903584e22ea84283c90a74e08dba4e7769e700b5cc3b5927077dd methox_sto3g_adcc_reference_cvs_adc0.hdf5 -42e73dc90b9931a5394ce3b6721358d744172725df0855cd2890e5e321594721 methox_sto3g_adcc_reference_cvs_adc1.hdf5 -4c25cc5b55299ab1dea4e265d768bdde5ef8282216e9c7702b5298e4c1c89db1 methox_sto3g_adcc_reference_cvs_adc2.hdf5 -9714de09ff968344c35546394fc3b71f2f607176035e9145391751503c566a06 methox_sto3g_adcc_reference_cvs_adc2x.hdf5 -7ee061a73f3724581b0af04052923fcb2f66fe88e29d7d92e8a5ab026b34d052 methox_sto3g_adcc_reference_cvs_adc3.hdf5 -5877bf4fc4554759b2f0ba35c58cee1e9a7ae2de4d47673a98793d4769eec841 methox_sto3g_hfdata.hdf5 +af8780b20c4c7d8434bbf5018474c97b16444d4fe60624150be86603284adb84 methox_sto3g_adcc_reference_adc0.hdf5 +b2a1e519b140b5e57db1902ac9fee2fed50d3fef82e83c7bfcd983593881abd2 methox_sto3g_adcc_reference_adc1.hdf5 +bee3d839e9d2debe1c3edb0cf458a7c858b3ae3491782ca16e734d07f8f226e5 methox_sto3g_adcc_reference_adc2.hdf5 +918ed4e82cfd1d60efa69c428ab9bc0103307b92d91b90e43fcd437f00986c1a methox_sto3g_adcc_reference_adc2x.hdf5 +154acf0a39dff7accbf295e6bfba1fff5859079a00e1b9d4542e84ac0e0947d3 methox_sto3g_adcc_reference_adc3.hdf5 +f63d6b58eb9727ec2312287bbb353370b5c1403611b354b55146d80ca4dba4e1 methox_sto3g_adcc_reference_cvs_adc0.hdf5 +7ff6d87c06ebccd08c83e4ba8d0a94a97f7f5bde340cbc62d72aa6c9eee7b22b methox_sto3g_adcc_reference_cvs_adc1.hdf5 +e61545e2c25418ffcde2c21d63189bcc1b99a274e1e7e97f85e7f22774d97970 methox_sto3g_adcc_reference_cvs_adc2.hdf5 +2e8db02e4fde21223253c06431fd0f57fb1fe2bfee4fa5f9e93464fefb161597 methox_sto3g_adcc_reference_cvs_adc2x.hdf5 +330a91568c82573167b8fc72061fafb2bf8be374f15d406e6f063af5ccdf36e6 methox_sto3g_adcc_reference_cvs_adc3.hdf5 +f09ef67eb853d09a8a43c17f0725afdb4864d564cce245983039fe1e33707c39 methox_sto3g_hfdata.hdf5 fd0e374f6590b5145bb255e77763f96217abc5912764091e0fc97fb387b9c643 methox_sto3g_reference_adc0.hdf5 0f617bf0ff98c15f7d514197391b1da4737758d948ce2c865f1d8bf76b8af6af methox_sto3g_reference_adc1.hdf5 9064a58a5f27827f480f396848b1cbbf419078cb957a3d293d6652db5c5f338b methox_sto3g_reference_adc2.hdf5 diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index f22a74cd..527fb727 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -119,7 +119,7 @@ void export_ReferenceState(py::module& m) { return res; }) .def("nuclear_quadrupole", - [](const ReferenceState& ref, py::array_t gauge_origin) { + [](const ReferenceState& ref, py::list gauge_origin) { py::array_t ret(std::vector{6}); auto res = ref.nuclear_multipole( 2, py::cast>(gauge_origin)); From 0a3e0391f74af731fad9c3c4f948d512dd441258 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 29 Oct 2024 10:29:24 +0100 Subject: [PATCH 26/42] C++ tests --- libadcc/tests/HFSolutionMock.hh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libadcc/tests/HFSolutionMock.hh b/libadcc/tests/HFSolutionMock.hh index bb72af1d..e4dad3b5 100644 --- a/libadcc/tests/HFSolutionMock.hh +++ b/libadcc/tests/HFSolutionMock.hh @@ -49,10 +49,14 @@ struct HFSolutionMock : public HartreeFockSolution_i { size_t spin_multiplicity() const override { return restricted() ? 1 : 0; } size_t n_bas() const override { return exposed_n_bas; } - void nuclear_multipole(size_t /*order*/, scalar_type* /*buffer*/, - size_t /*size*/) const override { + void nuclear_multipole(size_t /*order*/, std::vector /*gauge_origin*/, + scalar_type* /*buffer*/, size_t /*size*/) const override { throw not_implemented_error("Not implemented."); } + std::vector determine_gauge_origin(std::string /*gauge_origin*/) + const override { + throw not_implemented_error("Not implemented."); + } real_type energy_scf() const override { return 0; } void occupation_f(scalar_type* buffer, size_t size) const override { if (exposed_occupation.size() != size) { From 22c8d1331b2db2b79c2732596a78532863fbc08f Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 29 Oct 2024 14:04:19 +0100 Subject: [PATCH 27/42] Code style --- libadcc/tests/HFSolutionMock.hh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libadcc/tests/HFSolutionMock.hh b/libadcc/tests/HFSolutionMock.hh index e4dad3b5..1354ccfd 100644 --- a/libadcc/tests/HFSolutionMock.hh +++ b/libadcc/tests/HFSolutionMock.hh @@ -50,12 +50,12 @@ struct HFSolutionMock : public HartreeFockSolution_i { size_t n_bas() const override { return exposed_n_bas; } void nuclear_multipole(size_t /*order*/, std::vector /*gauge_origin*/, - scalar_type* /*buffer*/, size_t /*size*/) const override { + scalar_type* /*buffer*/, size_t /*size*/) const override { throw not_implemented_error("Not implemented."); } - std::vector determine_gauge_origin(std::string /*gauge_origin*/) - const override { - throw not_implemented_error("Not implemented."); + std::vector determine_gauge_origin( + std::string /*gauge_origin*/) const override { + throw not_implemented_error("Not implemented."); } real_type energy_scf() const override { return 0; } void occupation_f(scalar_type* buffer, size_t size) const override { From 8ac3baf03b21c7273a353e157d81fbae6dfd9f54 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 20 Dec 2024 14:42:55 +0100 Subject: [PATCH 28/42] quadrupole velocity --- adcc/ElectronicTransition.py | 14 +++++++++++++ adcc/OperatorIntegrals.py | 12 +++++++++++ adcc/adc_pp/modified_transition_moments.py | 1 + adcc/backends/pyscf.py | 24 +++++++++++++++++++++- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 3443c2bd..837020bb 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -206,6 +206,20 @@ def transition_quadrupole_moment(self, gauge_origin=[0.0, 0.0, 0.0]): for tdm in self.transition_dm ]) + @mark_excitation_property() + @timed_member_call(timer="_property_timer") + def transition_quadrupole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): + """List of transition quadrupole moments of all computed states""" + if self.property_method.level == 0: + warnings.warn("ADC(0) transition quadrupole moments are known to be " + "faulty in some cases.") + quadrupole_integrals = self.operators.electric_quadrupole_velocity(gauge_origin) + return np.array([ + [[product_trace(quad1, tdm) + for quad1 in quad] for quad in quadrupole_integrals] + for tdm in self.transition_dm + ]) + @cached_property @mark_excitation_property() def oscillator_strength(self): diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 80ad6438..17a1b741 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -104,6 +104,7 @@ def available(self): "nabla", "electric_quadrupole_traceless", "electric_quadrupole", + "electric_quadrupole_velocity", "diamagnetic_magnetizability", "pe_induction_elec", "pcm_potential_elec", @@ -252,6 +253,17 @@ def electric_quadrupole(self): callback = self.provider_ao.electric_quadrupole return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + @property + @timed_member_call("_import_timer") + def electric_quadrupole_velocity(self): + """ + Returns a function to obtain electric quadrupole integrals in velocity gauge + in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to [0.0, 0.0, 0.0]. + """ + callback = self.provider_ao.electric_quadrupole_velocity + return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + @property @timed_member_call("_import_timer") def diamagnetic_magnetizability(self): diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index 2fc8856c..1965f5bb 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -85,6 +85,7 @@ def mtm_cvs_adc2(mp, op, intermediates): "adc0": mtm_adc0, "adc1": mtm_adc1, "adc2": mtm_adc2, + "adc2x": mtm_adc2, # identical to ADC(2) "cvs-adc0": mtm_cvs_adc0, "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0) "cvs-adc2": mtm_cvs_adc2, diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 64cb4f56..8f922bb6 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -87,6 +87,22 @@ def electric_quadrupole_traceless_gauge(gauge_origin): return list(term) return electric_quadrupole_traceless_gauge + @property + def electric_quadrupole_velocity(self): + def electric_quadrupole_velocity_gauge(gauge_origin): + gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + with self.scfres.mol.with_common_orig(gauge_origin): + r_p = self.scfres.mol.intor('int1e_irp', comp=9, hermi=0) + r_p = np.reshape(r_p, (3, 3, r_p.shape[1], r_p.shape[1])) + ovlp = self.scfres.mol.intor_symmetric('int1e_ovlp', comp=1) + ovlp_matrix = np.zeros_like(r_p) + for i in range(3): + ovlp_matrix[i][i] = ovlp + term = r_p + r_p.transpose(1,0,2,3) - ovlp_matrix + term = np.reshape(term, (9, ovlp.shape[0], ovlp.shape[0])) + return list(term) + return electric_quadrupole_velocity_gauge + @property def diamagnetic_magnetizability(self): def diamagnetic_magnetizability_gauge(gauge_origin): @@ -435,6 +451,10 @@ def run_core_hole(xyz, basis, charge=0, multiplicity=1, def _determine_gauge_origin(scfres, gauge_origin): + """ + Determines the gauge origin. If the gauge origin is defined as a list + the coordinates need to be given in atomic units! + """ coords = scfres.mol.atom_coords() masses = scfres.mol.atom_mass_list() charges = scfres.mol.atom_charges() @@ -450,5 +470,7 @@ def _determine_gauge_origin(scfres, gauge_origin): else: raise NotImplementedError("Gauge origin has to be defined by" " using one of the keywords" - " mass_center, charge_center or origin.") + " mass_center, charge_center or origin. " + "If they are defined with a list, they need " + "to be given in atomic units") return gauge_origin From 034ca4d754c193ac347242f844d9aab88eaffd77 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 24 Jan 2025 10:36:10 +0100 Subject: [PATCH 29/42] code style --- adcc/ElectronicTransition.py | 3 ++- adcc/adc_pp/modified_transition_moments.py | 2 +- adcc/backends/pyscf.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 837020bb..daacaf09 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -213,7 +213,8 @@ def transition_quadrupole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): if self.property_method.level == 0: warnings.warn("ADC(0) transition quadrupole moments are known to be " "faulty in some cases.") - quadrupole_integrals = self.operators.electric_quadrupole_velocity(gauge_origin) + quadrupole_integrals = \ + self.operators.electric_quadrupole_velocity(gauge_origin) return np.array([ [[product_trace(quad1, tdm) for quad1 in quad] for quad in quadrupole_integrals] diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py index 1965f5bb..3c2facf4 100644 --- a/adcc/adc_pp/modified_transition_moments.py +++ b/adcc/adc_pp/modified_transition_moments.py @@ -85,7 +85,7 @@ def mtm_cvs_adc2(mp, op, intermediates): "adc0": mtm_adc0, "adc1": mtm_adc1, "adc2": mtm_adc2, - "adc2x": mtm_adc2, # identical to ADC(2) + "adc2x": mtm_adc2, # Identical to ADC(2) "cvs-adc0": mtm_cvs_adc0, "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0) "cvs-adc2": mtm_cvs_adc2, diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 8f922bb6..9448453e 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -98,7 +98,7 @@ def electric_quadrupole_velocity_gauge(gauge_origin): ovlp_matrix = np.zeros_like(r_p) for i in range(3): ovlp_matrix[i][i] = ovlp - term = r_p + r_p.transpose(1,0,2,3) - ovlp_matrix + term = r_p + r_p.transpose(1, 0, 2, 3) - ovlp_matrix term = np.reshape(term, (9, ovlp.shape[0], ovlp.shape[0])) return list(term) return electric_quadrupole_velocity_gauge From 312664458d3706feb7603cb93323f18cc708ffd7 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 7 Feb 2025 10:19:59 +0100 Subject: [PATCH 30/42] code style --- adcc/ElectronicTransition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index daacaf09..e49641fd 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -201,8 +201,8 @@ def transition_quadrupole_moment(self, gauge_origin=[0.0, 0.0, 0.0]): "faulty in some cases.") quadrupole_integrals = self.operators.electric_quadrupole(gauge_origin) return np.array([ - [[product_trace(quad1, tdm) - for quad1 in quad] for quad in quadrupole_integrals] + [[product_trace(quad1, tdm) for quad1 in quad] + for quad in quadrupole_integrals] for tdm in self.transition_dm ]) @@ -216,8 +216,8 @@ def transition_quadrupole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): quadrupole_integrals = \ self.operators.electric_quadrupole_velocity(gauge_origin) return np.array([ - [[product_trace(quad1, tdm) - for quad1 in quad] for quad in quadrupole_integrals] + [[product_trace(quad1, tdm) for quad1 in quad] + for quad in quadrupole_integrals] for tdm in self.transition_dm ]) From da2f0f49b233318d47188128bfb42877ba7696af Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 5 Mar 2025 11:33:34 +0100 Subject: [PATCH 31/42] add gauge origin selection for veloxchem, add tests (crossref pyscf vs veloxchem) and adjust old tests --- adcc/DataHfProvider.py | 31 ++++---- adcc/ElectronicTransition.py | 20 ++--- adcc/OperatorIntegrals.py | 9 +-- adcc/backends/molsturm.py | 9 +-- adcc/backends/psi4.py | 14 ++-- adcc/backends/pyscf.py | 45 +++++------ adcc/backends/veloxchem.py | 78 ++++++++++++------- adcc/tests/IsrMatrix_test.py | 4 +- adcc/tests/ReferenceState_refdata_test.py | 15 ++++ .../modified_transition_moments_test.py | 2 +- adcc/tests/backends/backends_crossref_test.py | 33 +++++++- adcc/tests/backends/pyscf_test.py | 27 +++++-- adcc/tests/backends/testing.py | 45 +++++------ adcc/tests/backends/veloxchem_test.py | 30 ++++--- adcc/tests/generators/dump_adcc.py | 12 ++- adcc/tests/generators/dump_pyscf.py | 15 +++- adcc/tests/hardcoded_test.py | 2 +- adcc/tests/properties_test.py | 56 +++++++++---- 18 files changed, 284 insertions(+), 163 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 42c2df52..dd22bcd8 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -203,31 +203,25 @@ def __init__(self, data): + str((3, nb, nb)) + " not " + str(mmp["elec_1"].shape)) opprov.electric_dipole = np.asarray(mmp["elec_1"]) - if "elec_2" in mmp: - def get_integral_quad(gauge_origin): - integral_string = "elec_2" - if gauge_origin != 'origin': - integral_string = f'{integral_string}_{gauge_origin}' - return np.asarray(mmp[integral_string]) - if mmp["elec_2"].shape != (9, nb, nb): - raise ValueError("multipoles/elec_2 is expected to have " - "shape " + str((9, nb, nb)) + " not " + if "elec_2_origin" in mmp: + def get_integral_elquad(gauge_origin): + return np.asarray(mmp[f"elec_2_{gauge_origin}"]) + if mmp["elec_2_origin"].shape != (9, nb, nb): + raise ValueError("multipoles/elec_2_origin is expected to " + "have shape " + str((9, nb, nb)) + " not " + str(mmp["elec_2"].shape)) - opprov.electric_quadrupole = get_integral_quad + opprov.electric_quadrupole = get_integral_elquad magm = data.get("magnetic_moments", {}) if "mag_1_origin" in magm: + def get_integral_magdip(gauge_origin): + return np.asarray(magm[f"mag_1_{gauge_origin}"]) if magm["mag_1_origin"].shape != (3, nb, nb): raise ValueError("magnetic_moments/mag_1_origin is expected to have" " shape " + str((3, nb, nb)) + " not " + str(magm["mag_1_origin"].shape)) - opprov.magnetic_dipole = np.asarray(magm["mag_1_origin"]) + opprov.magnetic_dipole = get_integral_magdip derivs = data.get("derivatives", {}) if "nabla_origin" in derivs: - if derivs["nabla_origin"].shape != (3, nb, nb): - raise ValueError("derivatives/nabla_origin is expected to " - "have shape " - + str((3, nb, nb)) + " not " - + str(derivs["nabla"].shape)) opprov.nabla = np.asarray(derivs["nabla_origin"]) self.operator_integral_provider = opprov @@ -271,10 +265,13 @@ def get_backend(self): def get_energy_scf(self): return get_scalar_value(self.data, "energy_scf", 0.0) - def get_nuclear_multipole(self, order: int) -> np.ndarray: + def get_nuclear_multipole(self, order: int, gauge_origin) -> np.ndarray: key = f"multipoles/nuclear_{order}" if order == 0: nuc_multipole = get_scalar_value(self.data, key, default=None) + elif order == 2: + key_quad = key + f"_{gauge_origin}" + nuc_multipole = get_array_value(self.data, key_quad, default=None) else: nuc_multipole = get_array_value(self.data, key, default=None) if nuc_multipole is None: diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index e49641fd..b4f9afb2 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -165,15 +165,16 @@ def transition_dipole_moment(self): for tdm in self.transition_dm ]) + @cached_property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_dipole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): + def transition_dipole_moment_velocity(self): """List of transition dipole moments in the velocity gauge of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition velocity dipole moments " "are known to be faulty in some cases.") - dipole_integrals = self.operators.nabla(gauge_origin) + dipole_integrals = self.operators.nabla return np.array([ [product_trace(comp, tdm) for comp in dipole_integrals] for tdm in self.transition_dm @@ -200,8 +201,8 @@ def transition_quadrupole_moment(self, gauge_origin=[0.0, 0.0, 0.0]): warnings.warn("ADC(0) transition quadrupole moments are known to be " "faulty in some cases.") quadrupole_integrals = self.operators.electric_quadrupole(gauge_origin) - return np.array([ - [[product_trace(quad1, tdm) for quad1 in quad] + return np.array([[ + [product_trace(quad1, tdm) for quad1 in quad] for quad in quadrupole_integrals] for tdm in self.transition_dm ]) @@ -215,8 +216,8 @@ def transition_quadrupole_moment_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): "faulty in some cases.") quadrupole_integrals = \ self.operators.electric_quadrupole_velocity(gauge_origin) - return np.array([ - [[product_trace(quad1, tdm) for quad1 in quad] + return np.array([[ + [product_trace(quad1, tdm) for quad1 in quad] for quad in quadrupole_integrals] for tdm in self.transition_dm ]) @@ -231,13 +232,14 @@ def oscillator_strength(self): self.excitation_energy) ]) + @cached_property @mark_excitation_property() - def oscillator_strength_velocity(self, gauge_origin=[0.0, 0.0, 0.0]): + def oscillator_strength_velocity(self): """List of oscillator strengths in velocity gauge of all computed states""" return 2. / 3. * np.array([ np.linalg.norm(tdm)**2 / np.abs(ev) - for tdm, ev in zip(self.transition_dipole_moment_velocity(gauge_origin), + for tdm, ev in zip(self.transition_dipole_moment_velocity, self.excitation_energy) ]) @@ -247,7 +249,7 @@ def rotatory_strength(self, gauge_origin=[0.0, 0.0, 0.0]): return np.array([ np.dot(tdm, magmom) / ee for tdm, magmom, ee in zip( - self.transition_dipole_moment_velocity(gauge_origin), + self.transition_dipole_moment_velocity, self.transition_magnetic_dipole_moment(gauge_origin), self.excitation_energy) ]) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 17a1b741..1418d1a1 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -117,7 +117,6 @@ def import_dipole_like_operator(self, integral, is_symmetric=True): raise NotImplementedError(f"{integral.replace('_', ' ')} operator " "not implemented " f"in {self.provider_ao.backend} backend.") - dipoles = [] for i, component in enumerate(["x", "y", "z"]): dip_backend = getattr(self.provider_ao, integral)[i] @@ -185,12 +184,10 @@ def magnetic_dipole(self): def nabla(self): """ Returns a function to obtain nabla intergrals - in the molecular orbital basis dependent on the selected gauge origin. - The default gauge origin is set to [0.0, 0.0, 0.0]. + in the molecular orbital basis. """ - callback = self.provider_ao.nabla - return self.__import_gauge_dependent_dipole_like(callback, - is_symmetric=False) + return self.import_dipole_like_operator("nabla", + is_symmetric=False) def __import_quadrupole_like_operator(self, callback, is_symmetric=True): """Returns a function that imports a gauge-dependent quadrupole like diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index fbc45141..0f16d22f 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -22,6 +22,7 @@ ## --------------------------------------------------------------------- import numpy as np import warnings + from molsturm.State import State from adcc.DataHfProvider import DataHfProvider @@ -29,9 +30,9 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn("Gauge origin selection not available " - "in Molsturm. " - "The gauge origin is selected as [0, 0, 0].") + warnings.warn("Gauge origin selection not available in Molsturm." + "The gauge origin is selected as origin of the " + "Cartesian coordinate system [0.0, 0.0, 0.0].") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] @@ -69,8 +70,6 @@ def convert_scf_to_dict(scfres): data["multipoles"]["nuclear_0"] = int(np.sum(charges)), data["multipoles"]["nuclear_1"] = np.einsum('i,ix->x', charges, coords) else: - import warnings - # We have no information about this, so we can just provide dummies data["multipoles"]["nuclear_0"] = -1 data["multipoles"]["nuclear_1"] = np.zeros(3) diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 8c6c7775..ce70b8fb 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -41,7 +41,8 @@ def __init__(self, wfn): self.mints = psi4.core.MintsHelper(self.wfn) warnings.warn("Gauge origin selection not available in " f"{self.backend}. " - "The gauge origin is selected as [0, 0, 0].") + "The gauge origin is selected as origin of the " + "Cartesian coordinate system [0.0, 0.0, 0.0].") @cached_property def electric_dipole(self): @@ -49,7 +50,7 @@ def electric_dipole(self): @property def magnetic_dipole(self): - def gauge_dependent_integrals(gauge_origin): + def gauge_origin_dependent_integrals(gauge_origin): # TODO: Gauge origin? if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' @@ -58,16 +59,11 @@ def gauge_dependent_integrals(gauge_origin): 0.5 * np.asarray(comp) for comp in self.mints.ao_angular_momentum() ] - return gauge_dependent_integrals + return gauge_origin_dependent_integrals @cached_property def nabla(self): - def gauge_dependent_integrals(gauge_origin): - if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": - raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' - ' gauge origin.') - return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] - return gauge_dependent_integrals + return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] @property def pe_induction_elec(self): diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 9448453e..f22eec85 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -44,36 +44,33 @@ def electric_dipole(self): @property def magnetic_dipole(self): - def magnetic_dipole_gauge(gauge_origin="origin"): + def g_origin_dep_ints_mag_dip(gauge_origin="origin"): gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( 0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - return magnetic_dipole_gauge + return g_origin_dep_ints_mag_dip - @property + @cached_property def nabla(self): - def nabla_gauge(gauge_origin): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) - with self.scfres.mol.with_common_orig(gauge_origin): - return list( - -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) - return nabla_gauge + with self.scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + return list( + -1.0 * self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) @property def electric_quadrupole(self): - def electric_quadrupole_gauge(gauge_origin): + def g_origin_dep_ints_el_quad(gauge_origin): gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) return list(r_r) - return electric_quadrupole_gauge + return g_origin_dep_ints_el_quad @property def electric_quadrupole_traceless(self): - def electric_quadrupole_traceless_gauge(gauge_origin): + def g_origin_dep_ints_el_quad_traceless(gauge_origin): gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) @@ -85,11 +82,11 @@ def electric_quadrupole_traceless_gauge(gauge_origin): term = 0.5 * (3 * r_r - r_quadr_matrix) term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) return list(term) - return electric_quadrupole_traceless_gauge + return g_origin_dep_ints_el_quad_traceless @property def electric_quadrupole_velocity(self): - def electric_quadrupole_velocity_gauge(gauge_origin): + def g_origin_dep_ints_el_quad_vel(gauge_origin): gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_p = self.scfres.mol.intor('int1e_irp', comp=9, hermi=0) @@ -101,11 +98,11 @@ def electric_quadrupole_velocity_gauge(gauge_origin): term = r_p + r_p.transpose(1, 0, 2, 3) - ovlp_matrix term = np.reshape(term, (9, ovlp.shape[0], ovlp.shape[0])) return list(term) - return electric_quadrupole_velocity_gauge + return g_origin_dep_ints_el_quad_vel @property def diamagnetic_magnetizability(self): - def diamagnetic_magnetizability_gauge(gauge_origin): + def g_origin_dep_ints_diamag_magn(gauge_origin): gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) @@ -117,7 +114,7 @@ def diamagnetic_magnetizability_gauge(gauge_origin): term = -0.25 * (r_quadr_matrix - r_r) term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) return list(term) - return diamagnetic_magnetizability_gauge + return g_origin_dep_ints_diamag_magn @property def pe_induction_elec(self): @@ -456,7 +453,7 @@ def _determine_gauge_origin(scfres, gauge_origin): the coordinates need to be given in atomic units! """ coords = scfres.mol.atom_coords() - masses = scfres.mol.atom_mass_list() + masses = scfres.mol.atom_mass_list(isotope_avg=True) charges = scfres.mol.atom_charges() if gauge_origin == "mass_center": gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) @@ -468,9 +465,9 @@ def _determine_gauge_origin(scfres, gauge_origin): elif isinstance(gauge_origin, list): gauge_origin = gauge_origin else: - raise NotImplementedError("Gauge origin has to be defined by" - " using one of the keywords" - " mass_center, charge_center or origin. " - "If they are defined with a list, they need " - "to be given in atomic units") + raise NotImplementedError("The gauge origin can be defined either by a " + "keyword (origin, mass_center or charge_center) " + "or by a list defining the Cartesian components " + "e.g. [x, y, z]." + ) return gauge_origin diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 0a730dc1..bffadf68 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -23,7 +23,6 @@ import os import tempfile import numpy as np -import warnings from mpi4py import MPI from libadcc import HartreeFockProvider @@ -44,9 +43,6 @@ class VeloxChemOperatorIntegralProvider: def __init__(self, scfdrv): self.scfdrv = scfdrv self.backend = "veloxchem" - warnings.warn("Gauge origin selection not available " - f"in {self.backend}. " - "The gauge origin is selected as [0, 0, 0].") @cached_property def electric_dipole(self): @@ -60,29 +56,24 @@ def electric_dipole(self): @cached_property def magnetic_dipole(self): - # TODO: Gauge origin? - task = self.scfdrv.task - angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) - # define the origin for magnetic dipole integrals - angmom_drv.origin = tuple(np.zeros(3)) - angmom_mats = angmom_drv.compute(task.molecule, task.ao_basis) - return (0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), - 0.5 * angmom_mats.z_to_numpy()) + def g_origin_dep_ints_mag_dip(gauge_origin="origin"): + gauge_origin = _determine_gauge_origin(self.scfdrv, gauge_origin) + task = self.scfdrv.task + angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) + angmom_drv.origin = tuple(gauge_origin) + angmom_mats = angmom_drv.compute(task.molecule, task.ao_basis) + return (0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), + 0.5 * angmom_mats.z_to_numpy()) + return g_origin_dep_ints_mag_dip @cached_property def nabla(self): - def gauge_dependent_integrals(gauge_origin): - # TODO: Gauge origin? - if gauge_origin != [0.0, 0.0, 0.0] and gauge_origin != "origin": - raise NotImplementedError('Only [0.0, 0.0, 0.0] can be selected as' - ' gauge origin.') - task = self.scfdrv.task - linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) - linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) - return (-1.0 * linmom_mats.x_to_numpy(), - -1.0 * linmom_mats.y_to_numpy(), - -1.0 * linmom_mats.z_to_numpy()) - return gauge_dependent_integrals + task = self.scfdrv.task + linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) + linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) + return (-1.0 * linmom_mats.x_to_numpy(), + -1.0 * linmom_mats.y_to_numpy(), + -1.0 * linmom_mats.z_to_numpy()) class VeloxChemEriBuilder(EriBuilder): @@ -183,11 +174,17 @@ def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): (mol.x_to_numpy(), mol.y_to_numpy(), mol.z_to_numpy()) ).transpose() return np.einsum('i,ix->x', nuc_charges, coords) + elif order == 2: + coords = mol.get_coordinates_in_bohr() - gauge_origin + r_r = np.einsum("ij,ik->ijk", coords, coords) + res = np.einsum("i,ijk->jk", nuc_charges, r_r) + res = [res[0, 0], res[0, 1], res[0, 2], res[1, 1], res[1, 2], res[2, 2]] + return np.array(res) else: - raise NotImplementedError("get_nuclear_multipole with order > 1") + raise NotImplementedError("get_nuclear_multipole with order > 2") def get_gauge_origin(self, gauge_origin): - raise NotImplementedError("get_gauge_origin not implemented.") + return _determine_gauge_origin(self.scfdrv, gauge_origin) def fill_occupation_f(self, out): n_mo = self.mol_orbs.number_mos() @@ -282,3 +279,32 @@ def run_hf(xyz, basis, charge=0, multiplicity=1, conv_tol=None, conv_tol_grad=1e scfdrv.compute(task.molecule, task.ao_basis, task.min_basis) scfdrv.task = task return scfdrv + + +def _determine_gauge_origin(scfdrv, gauge_origin): + """ + Determines the gauge origin. If the gauge origin is defined as a list + the coordinates need to be given in atomic units! + """ + molecule = scfdrv.task.molecule + coords = molecule.get_coordinates_in_bohr() + charges = molecule.elem_ids_to_numpy() + masses = molecule.masses_to_numpy() + + if gauge_origin == "mass_center": + # gauge_origin = list(molecule.center_of_mass_in_bohr()) + gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) + elif gauge_origin == "charge_center": + gauge_origin = list(np.einsum("i,ij->j", charges, coords) + / charges.sum()) + elif gauge_origin == "origin": + gauge_origin = [0.0, 0.0, 0.0] + elif isinstance(gauge_origin, list): + gauge_origin = gauge_origin + else: + raise NotImplementedError("The gauge origin can be defined either by a " + "keyword (origin, mass_center or charge_center) " + "or by a list defining the Cartesian components " + "e.g. [x, y, z]." + ) + return gauge_origin diff --git a/adcc/tests/IsrMatrix_test.py b/adcc/tests/IsrMatrix_test.py index 37b2560d..d25772ea 100644 --- a/adcc/tests/IsrMatrix_test.py +++ b/adcc/tests/IsrMatrix_test.py @@ -53,8 +53,8 @@ def test_matrix_vector_product(self, system: str, case: str, kind: str, mp = state.ground_state if operator_kind == "electric": # example of a symmetric operator dips = state.reference_state.operators.electric_dipole - elif op_kind == "magnetic": # example of an asymmetric operator - dips = state.reference_state.operators.magnetic_dipole('origin') + elif operator_kind == "magnetic": # example of an asymmetric operator + dips = state.reference_state.operators.magnetic_dipole("origin") else: raise NotImplementedError(f"Unexptected operator kind {operator_kind}") diff --git a/adcc/tests/ReferenceState_refdata_test.py b/adcc/tests/ReferenceState_refdata_test.py index 50d8b493..86c7255b 100644 --- a/adcc/tests/ReferenceState_refdata_test.py +++ b/adcc/tests/ReferenceState_refdata_test.py @@ -46,12 +46,15 @@ def compare_refstate_with_reference(system: str, case: str, if scfres is None: import_data = data atol = data["conv_tol"] + backend = data["backend"] else: import_data = scfres if hasattr(scfres, "conv_tol"): atol = scfres.conv_tol + backend = scfres.backend else: atol = data["conv_tol"] + backend = None # construct the ReferenceState core_orbitals = system.core_orbitals if "cvs" in case else None frozen_core = system.frozen_core if "fc" in case else None @@ -90,6 +93,18 @@ def compare_refstate_with_reference(system: str, case: str, multipoles["nuclear_0"], atol=atol) assert_allclose(refstate.nuclear_dipole, multipoles["nuclear_1"], atol=atol) + if backend is not None and backend in ["pyscf", "veloxchem"]: + gauge_origins = ["origin", "mass_center", "charge_center"] + for g_origin in gauge_origins: + # adjust tolerance criteria for mass_center, because of different iso- + # tropic mass averages. + if g_origin == "mass_center": + atol_nuc_quad = 2e-4 + else: + atol_nuc_quad = atol + assert_allclose(refstate.nuclear_quadrupole(g_origin), + multipoles[f"nuclear_2_{g_origin}"], + atol=atol_nuc_quad) # This only makes sense if we used scfres to construct refstate. if "electric_dipole" in refstate.operators.available \ diff --git a/adcc/tests/adc_pp/modified_transition_moments_test.py b/adcc/tests/adc_pp/modified_transition_moments_test.py index 6128084e..64222acc 100644 --- a/adcc/tests/adc_pp/modified_transition_moments_test.py +++ b/adcc/tests/adc_pp/modified_transition_moments_test.py @@ -59,7 +59,7 @@ def test_modified_transition_moments(system: str, case: str, method: str, kind: dips = state.reference_state.operators.electric_dipole ref_tdm = ref["transition_dipole_moments"] elif op_kind == "magnetic": - dips = state.reference_state.operators.magnetic_dipole + dips = state.reference_state.operators.magnetic_dipole("origin") ref_tdm = ref["transition_magnetic_dipole_moments"] else: raise NotImplementedError( diff --git a/adcc/tests/backends/backends_crossref_test.py b/adcc/tests/backends/backends_crossref_test.py index 72c569df..68a81ff7 100644 --- a/adcc/tests/backends/backends_crossref_test.py +++ b/adcc/tests/backends/backends_crossref_test.py @@ -33,6 +33,7 @@ # molsturm is super slow backends = [b for b in adcc.backends.available() if b != "molsturm"] +backends_with_gauge_origin = ["pyscf", "veloxchem"] h2o = testcases.get_by_filename("h2o_sto3g", "h2o_def2tzvp") h2o_cases = [(case.file_name, c) for case in h2o for c in case.cases] @@ -154,6 +155,14 @@ def compare_hf_properties(results, atol): refstate1 = results[comb[0]] refstate2 = results[comb[1]] + # assert same gauge origins + if len(set(comb) & set(backends_with_gauge_origin)) == 2: + gauge_origins = ["origin", "mass_center", "charge_center"] + for gauge_origin in gauge_origins: + assert_allclose(refstate1.determine_gauge_origin(gauge_origin), + refstate2.determine_gauge_origin(gauge_origin), + atol=1e-5) + assert_allclose(refstate1.nuclear_total_charge, refstate2.nuclear_total_charge, atol=atol) assert_allclose(refstate1.nuclear_dipole, @@ -206,10 +215,30 @@ def compare_adc_results(adc_results, atol): assert_allclose(state1.oscillator_strength_velocity, state2.oscillator_strength_velocity, atol=atol) + # Only in two backends the gauge origin selection is implemented. + if len(set(comb) & set(backends_with_gauge_origin)) == 2: + gauge_origins = ["origin", "mass_center", "charge_center"] + else: + gauge_origins = ["origin"] has_rotatory1 = all(op in state1.operators.available for op in ["magnetic_dipole", "nabla"]) has_rotatory2 = all(op in state2.operators.available for op in ["magnetic_dipole", "nabla"]) if has_rotatory1 and has_rotatory2: - assert_allclose(state1.rotatory_strength, - state2.rotatory_strength, atol=atol) + for gauge_origin in gauge_origins: + # reduce the tolerance criteria for mass_center as there are diff. + # isotropic mass values implemented in veloxchem and pyscf. + if gauge_origin == "mass_center": + atol_tdm_mag = 2e-4 + else: + atol_tdm_mag = atol + assert_allclose(state1.rotatory_strength(gauge_origin), + state2.rotatory_strength(gauge_origin), atol=atol) + + # Test the absolut values of transition magnetic dipole moments + # to account for differences with different gauge origins. + # (The rotatory strength is zero for achiral molecules.) + assert_allclose( + abs(state1.transition_magnetic_dipole_moment(gauge_origin)), + abs(state2.transition_magnetic_dipole_moment(gauge_origin)), + atol=atol_tdm_mag) diff --git a/adcc/tests/backends/pyscf_test.py b/adcc/tests/backends/pyscf_test.py index cf97d2d9..cdadd053 100644 --- a/adcc/tests/backends/pyscf_test.py +++ b/adcc/tests/backends/pyscf_test.py @@ -106,19 +106,34 @@ def base_test(self, scfres): assert_almost_equal(eri_perm, eri) def operators_test(self, mf): + from adcc.backends.pyscf import _determine_gauge_origin + gauge_origins = ["origin", "mass_center", "charge_center"] + # Test dipole ao_dip = mf.mol.intor_symmetric('int1e_r', comp=3) operator_import_from_ao_test(mf, list(ao_dip)) - # Test magnetic dipole - with mf.mol.with_common_orig([0.0, 0.0, 0.0]): - ao_magdip = 0.5 * mf.mol.intor('int1e_cg_irxp', comp=3, hermi=2) - operator_import_from_ao_test(mf, list(ao_magdip), "magnetic_dipole") - # Test nabla - ao_linmom = -1.0 * mf.mol.intor('int1e_ipovlp', comp=3, hermi=2) + with mf.mol.with_common_orig([0.0, 0.0, 0.0]): + ao_linmom = -1.0 * mf.mol.intor('int1e_ipovlp', comp=3, hermi=2) operator_import_from_ao_test(mf, list(ao_linmom), "nabla") + # Test gauge origin dependent integrals + for gauge_origin in gauge_origins: + gauge_origin = _determine_gauge_origin(mf, gauge_origin) + + # Test magnetic dipole + with mf.mol.with_common_orig(gauge_origin): + ao_magdip = 0.5 * mf.mol.intor('int1e_cg_irxp', comp=3, hermi=2) + operator_import_from_ao_test(mf, list(ao_magdip), "magnetic_dipole", + gauge_origin) + + # Test electric quadrupole + with mf.mol.with_common_orig(gauge_origin): + ao_quad = mf.mol.intor_symmetric('int1e_rr', comp=9) + operator_import_from_ao_test(mf, list(ao_quad), + "electric_quadrupole", gauge_origin) + @pytest.mark.parametrize("system", h2o, ids=[case.file_name for case in h2o]) def test_rhf(self, system: testcases.TestCase): mf = adcc.backends.run_hf("pyscf", system.xyz, system.basis) diff --git a/adcc/tests/backends/testing.py b/adcc/tests/backends/testing.py index 4f1ca5f5..79c5059e 100644 --- a/adcc/tests/backends/testing.py +++ b/adcc/tests/backends/testing.py @@ -143,43 +143,44 @@ def eri_asymm_construction_test(scfres, core_orbitals=0): ) -def operator_import_from_ao_test(scfres, ao_dict, operator="electric_dipole"): +def operator_import_from_ao_test(scfres, ao_dict, operator="electric_dipole", + gauge_origin=[0.0, 0.0, 0.0]): refstate = adcc.ReferenceState(scfres) occa = refstate.orbital_coefficients_alpha("o1b").to_ndarray() occb = refstate.orbital_coefficients_beta("o1b").to_ndarray() virta = refstate.orbital_coefficients_alpha("v1b").to_ndarray() virtb = refstate.orbital_coefficients_beta("v1b").to_ndarray() - if operator == "magnetic_dipole": - callback = refstate.operators.magnetic_dipole - dip_imported = callback([0.0, 0.0, 0.0]) - elif operator == "nabla": - callback = refstate.operators.nabla - dip_imported = callback([0.0, 0.0, 0.0]) + if operator in ["magnetic_dipole"]: + int_imported = getattr(refstate.operators, operator)(gauge_origin) + elif operator in ["electric_quadrupole"]: + callback = getattr(refstate.operators, operator)(gauge_origin) + # flatten the list of lists of OneParticleOperators + int_imported = [quad for quads in callback for quad in quads] else: - dip_imported = getattr(refstate.operators, operator) + int_imported = getattr(refstate.operators, operator) for i, ao_component in enumerate(ao_dict): - dip_oo = np.einsum('ib,ba,ja->ij', occa, ao_component, occa) - dip_oo += np.einsum('ib,ba,ja->ij', occb, ao_component, occb) + int_oo = np.einsum('ib,ba,ja->ij', occa, ao_component, occa) + int_oo += np.einsum('ib,ba,ja->ij', occb, ao_component, occb) - dip_ov = np.einsum('ib,ba,ja->ij', occa, ao_component, virta) - dip_ov += np.einsum('ib,ba,ja->ij', occb, ao_component, virtb) + int_ov = np.einsum('ib,ba,ja->ij', occa, ao_component, virta) + int_ov += np.einsum('ib,ba,ja->ij', occb, ao_component, virtb) - dip_vv = np.einsum('ib,ba,ja->ij', virta, ao_component, virta) - dip_vv += np.einsum('ib,ba,ja->ij', virtb, ao_component, virtb) + int_vv = np.einsum('ib,ba,ja->ij', virta, ao_component, virta) + int_vv += np.einsum('ib,ba,ja->ij', virtb, ao_component, virtb) - dip_mock = {"o1o1": dip_oo, "o1v1": dip_ov, "v1v1": dip_vv} + int_mock = {"o1o1": int_oo, "o1v1": int_ov, "v1v1": int_vv} - dip_imported_comp = dip_imported[i] - if not dip_imported_comp.is_symmetric: - dip_vo = np.einsum('ib,ba,ja->ij', virta, ao_component, occa) - dip_vo += np.einsum('ib,ba,ja->ij', virtb, ao_component, occb) - dip_mock["v1o1"] = dip_vo + int_imported_comp = int_imported[i] + if not int_imported_comp.is_symmetric: + int_vo = np.einsum('ib,ba,ja->ij', virta, ao_component, occa) + int_vo += np.einsum('ib,ba,ja->ij', virtb, ao_component, occb) + int_mock["v1o1"] = int_vo - for b in dip_imported_comp.blocks: + for b in int_imported_comp.blocks: np.testing.assert_allclose( - dip_mock[b], dip_imported_comp[b].to_ndarray(), + int_mock[b], int_imported_comp[b].to_ndarray(), atol=refstate.conv_tol ) diff --git a/adcc/tests/backends/veloxchem_test.py b/adcc/tests/backends/veloxchem_test.py index 8c8b1857..13ab64ec 100644 --- a/adcc/tests/backends/veloxchem_test.py +++ b/adcc/tests/backends/veloxchem_test.py @@ -127,6 +127,9 @@ def base_test(self, scfdrv): assert_almost_equal(eri_perm, eri) def operators_test(self, scfdrv): + from adcc.backends.veloxchem import _determine_gauge_origin + gauge_origins = ["origin", "mass_center", "charge_center"] + # Test dipole dipole_drv = vlx.ElectricDipoleIntegralsDriver(scfdrv.task.mpi_comm) dipole_drv.origin = tuple(np.zeros(3)) @@ -137,18 +140,6 @@ def operators_test(self, scfdrv): operator_import_from_ao_test(scfdrv, integrals, operator="electric_dipole") - # Test magnetic dipole - angmom_drv = AngularMomentumIntegralsDriver(scfdrv.task.mpi_comm) - angmom_drv.origin = tuple(np.zeros(3)) - angmom_mats = angmom_drv.compute(scfdrv.task.molecule, - scfdrv.task.ao_basis) - integrals = ( - 0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), - 0.5 * angmom_mats.z_to_numpy() - ) - operator_import_from_ao_test(scfdrv, integrals, - operator="magnetic_dipole") - # Test nabla linmom_drv = LinearMomentumIntegralsDriver(scfdrv.task.mpi_comm) linmom_mats = linmom_drv.compute(scfdrv.task.molecule, @@ -158,6 +149,21 @@ def operators_test(self, scfdrv): -1.0 * linmom_mats.z_to_numpy()) operator_import_from_ao_test(scfdrv, integrals, operator="nabla") + for gauge_origin in gauge_origins: + gauge_origin = _determine_gauge_origin(scfdrv, gauge_origin) + + # Test magnetic dipole + angmom_drv = AngularMomentumIntegralsDriver(scfdrv.task.mpi_comm) + angmom_drv.origin = tuple(gauge_origin) + angmom_mats = angmom_drv.compute(scfdrv.task.molecule, + scfdrv.task.ao_basis) + integrals = ( + 0.5 * angmom_mats.x_to_numpy(), 0.5 * angmom_mats.y_to_numpy(), + 0.5 * angmom_mats.z_to_numpy() + ) + operator_import_from_ao_test(scfdrv, integrals, + "magnetic_dipole", gauge_origin) + @pytest.mark.parametrize("system", h2o, ids=[case.file_name for case in h2o]) def test_rhf(self, system: testcases.TestCase): scfdrv = adcc.backends.run_hf("veloxchem", system.xyz, system.basis) diff --git a/adcc/tests/generators/dump_adcc.py b/adcc/tests/generators/dump_adcc.py index 038541fa..0fd07bf5 100644 --- a/adcc/tests/generators/dump_adcc.py +++ b/adcc/tests/generators/dump_adcc.py @@ -99,9 +99,15 @@ def dump_excited_states(states: ExcitedStates, hdf5_file: h5py.Group, kind_data["transition_dipole_moments_velocity"] = ( states.transition_dipole_moment_velocity[:n_states] ) - kind_data["transition_magnetic_dipole_moments"] = ( - states.transition_magnetic_dipole_moment[:n_states] - ) + + gauge_origins = ["origin", "mass_center", "charge_center"] + for g_origin in gauge_origins: + kind_data[f"transition_magnetic_dipole_moments_{g_origin}"] = ( + states.transition_magnetic_dipole_moment(g_origin)[:n_states] + ) + kind_data[f"transition_quadrupole_moments_{g_origin}"] = ( + states.transition_quadrupole_moment(g_origin)[:n_states] + ) # state diffdm and ground to excited state tdm kind_data["state_diffdm_bb_a"] = np.asarray(dm_bb_a) kind_data["state_diffdm_bb_b"] = np.asarray(dm_bb_b) diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index 111002bc..892963a9 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -174,16 +174,29 @@ def dump_pyscf(scfres: scf.hf.SCF, hdf5_file: h5py.Group): data["eri_ffff"] = eri # Calculate mass center and charge center - masses = scfres.mol.atom_mass_list() + masses = scfres.mol.atom_mass_list(isotope_avg=True) charges = scfres.mol.atom_charges() coords = scfres.mol.atom_coords() mass_center = np.einsum('i,ij->j', masses, coords) / masses.sum() charge_center = np.einsum('i,ij->j', charges, coords) / charges.sum() + def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): + coords = coordinates - gauge_origin + r_r = np.einsum("ij,ik->ijk", coords, coords) + res = np.einsum("i,ijk->jk", charges, r_r) + res = [res[0, 0], res[0, 1], res[0, 2], res[1, 1], res[1, 2], res[2, 2]] + return np.array(res) + # Compute electric and nuclear multipole moments data["multipoles"] = {} data["multipoles"]["nuclear_0"] = int(np.sum(charges)) data["multipoles"]["nuclear_1"] = np.einsum("i,ix->x", charges, coords) + data["multipoles"]["nuclear_2_origin"] = \ + calculate_nuclear_quadrupole(charges, coords, [0, 0, 0]) + data["multipoles"]["nuclear_2_mass_center"] = \ + calculate_nuclear_quadrupole(charges, coords, mass_center) + data["multipoles"]["nuclear_2_charge_center"] = \ + calculate_nuclear_quadrupole(charges, coords, charge_center) data["multipoles"]["elec_0"] = -int(n_alpha + n_beta) data["multipoles"]["elec_1"] = scfres.mol.intor_symmetric("int1e_r", comp=3) diff --git a/adcc/tests/hardcoded_test.py b/adcc/tests/hardcoded_test.py index a1bcf403..5aad0ad6 100644 --- a/adcc/tests/hardcoded_test.py +++ b/adcc/tests/hardcoded_test.py @@ -48,6 +48,6 @@ def test_methox_sto3g_singlet(self): np.testing.assert_allclose(vlx_result['excitation_energies'], state.excitation_energy, atol=1e-6) np.testing.assert_allclose(vlx_result['rotatory_strengths'], - state.rotatory_strength('origin'), atol=1e-4) + state.rotatory_strength("origin"), atol=1e-4) np.testing.assert_allclose(vlx_result['oscillator_strengths'], state.oscillator_strength, atol=1e-4) diff --git a/adcc/tests/properties_test.py b/adcc/tests/properties_test.py index 44ec1570..d70c47c7 100644 --- a/adcc/tests/properties_test.py +++ b/adcc/tests/properties_test.py @@ -48,6 +48,7 @@ cases = [(case.file_name, "gen", kind) for case in test_cases for kind in ["singlet", "any", "spin_flip"] if kind in case.kinds.pp] +gauge_origins = ["origin", "mass_center", "charge_center"] @pytest.mark.parametrize("method", methods) @@ -180,7 +181,7 @@ def test_magnetic_transition_dipole_moments_z_component(self, method: str, state = run_adc(scfres, method=method, n_singlets=5, core_orbitals=2) else: state = run_adc(scfres, method=method, n_singlets=10) - tdms = state.transition_magnetic_dipole_moment + tdms = state.transition_magnetic_dipole_moment("origin") # For linear molecules lying on the z-axis, the z-component must be zero for tdm in tdms: @@ -196,13 +197,14 @@ def test_magnetic_transition_dipole_moments(self, system: str, case: str, state = testdata_cache._make_mock_adc_state( system=system, method=method, case=case, kind=kind, source="adcc" ) - - res_dms = state.transition_magnetic_dipole_moment - n_ref = len(state.excitation_vector) - assert_allclose( - res_dms, refdata["transition_magnetic_dipole_moments"][:n_ref], - atol=1e-4 - ) + for g_origin in gauge_origins: + res_dms = state.transition_magnetic_dipole_moment(g_origin) + n_ref = len(state.excitation_vector) + assert_allclose( + res_dms, + refdata[f"transition_magnetic_dipole_moments_{g_origin}"][:n_ref], + atol=1e-4 + ) # Only adcc reference data available. @pytest.mark.parametrize("system,case,kind", cases) @@ -222,6 +224,25 @@ def test_transition_dipole_moments_velocity(self, system: str, case: str, atol=1e-4 ) + # Only adcc reference data available. + @pytest.mark.parametrize("system,case,kind", cases) + def test_transition_quadrupole_moments(self, system: str, case: str, + kind: str, method: str): + refdata = testdata_cache._load_data( + system=system, method=method, case=case, source="adcc" + )[kind] + state = testdata_cache._make_mock_adc_state( + system=system, method=method, case=case, kind=kind, source="adcc" + ) + for g_origin in gauge_origins: + res_dms = state.transition_quadrupole_moment(g_origin) + n_ref = len(state.excitation_vector) + assert_allclose( + res_dms, + refdata[f"transition_quadrupole_moments_{g_origin}"][:n_ref], + atol=1e-4 + ) + # Only adcc reference data available. @pytest.mark.parametrize("system,case,kind", cases) def test_rotatory_strengths(self, system: str, case: str, kind: str, @@ -233,12 +254,13 @@ def test_rotatory_strengths(self, system: str, case: str, kind: str, system=system, method=method, case=case, kind=kind, source="adcc" ) - res_rots = state.rotatory_strength - ref_tmdm = refdata["transition_magnetic_dipole_moments"] - ref_tdmvel = refdata["transition_dipole_moments_velocity"] - refevals = refdata["eigenvalues"] - n_ref = len(state.excitation_vector) - for i in range(n_ref): - assert state.excitation_energy[i] == refevals[i] - ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) - assert res_rots[i] == pytest.approx(ref_dot / refevals[i]) + for g_origin in gauge_origins: + res_rots = state.rotatory_strength(g_origin) + ref_tmdm = refdata[f"transition_magnetic_dipole_moments_{g_origin}"] + ref_tdmvel = refdata["transition_dipole_moments_velocity"] + refevals = refdata["eigenvalues"] + n_ref = len(state.excitation_vector) + for i in range(n_ref): + assert state.excitation_energy[i] == refevals[i] + ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) + assert res_rots[i] == pytest.approx(ref_dot / refevals[i]) From 09d9e82f52467c09eff02df4a7743510f3ea71cb Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 5 Mar 2025 11:35:59 +0100 Subject: [PATCH 32/42] make flake8 happy --- adcc/tests/backends/backends_crossref_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adcc/tests/backends/backends_crossref_test.py b/adcc/tests/backends/backends_crossref_test.py index 68a81ff7..f93cf336 100644 --- a/adcc/tests/backends/backends_crossref_test.py +++ b/adcc/tests/backends/backends_crossref_test.py @@ -226,7 +226,7 @@ def compare_adc_results(adc_results, atol): for op in ["magnetic_dipole", "nabla"]) if has_rotatory1 and has_rotatory2: for gauge_origin in gauge_origins: - # reduce the tolerance criteria for mass_center as there are diff. + # reduce the tolerance criteria for mass_center as there are diff. # isotropic mass values implemented in veloxchem and pyscf. if gauge_origin == "mass_center": atol_tdm_mag = 2e-4 From 31bcd9f033c4b231a2dc1b6b5a6919acd41afd3b Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 5 Mar 2025 14:59:01 +0100 Subject: [PATCH 33/42] fix remaining tests, skip ci: new referencedata needed [skip ci] --- adcc/tests/ReferenceState_refdata_test.py | 2 +- adcc/tests/adc_pp/modified_transition_moments_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adcc/tests/ReferenceState_refdata_test.py b/adcc/tests/ReferenceState_refdata_test.py index 86c7255b..5fe21dbb 100644 --- a/adcc/tests/ReferenceState_refdata_test.py +++ b/adcc/tests/ReferenceState_refdata_test.py @@ -46,7 +46,7 @@ def compare_refstate_with_reference(system: str, case: str, if scfres is None: import_data = data atol = data["conv_tol"] - backend = data["backend"] + backend = None else: import_data = scfres if hasattr(scfres, "conv_tol"): diff --git a/adcc/tests/adc_pp/modified_transition_moments_test.py b/adcc/tests/adc_pp/modified_transition_moments_test.py index 64222acc..4c05f1d3 100644 --- a/adcc/tests/adc_pp/modified_transition_moments_test.py +++ b/adcc/tests/adc_pp/modified_transition_moments_test.py @@ -60,7 +60,7 @@ def test_modified_transition_moments(system: str, case: str, method: str, kind: ref_tdm = ref["transition_dipole_moments"] elif op_kind == "magnetic": dips = state.reference_state.operators.magnetic_dipole("origin") - ref_tdm = ref["transition_magnetic_dipole_moments"] + ref_tdm = ref["transition_magnetic_dipole_moments_origin"] else: raise NotImplementedError( f"Test not implemented for operator kind {op_kind}" From 58939ce3833ef9c4a9922ebc98af78c6596678f4 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 11 Mar 2025 14:48:14 +0100 Subject: [PATCH 34/42] gauge origin as tuple --- adcc/backends/pyscf.py | 14 +++++++------- adcc/backends/veloxchem.py | 14 +++++++------- libadcc/HartreeFockSolution_i.hh | 4 ++-- libadcc/ReferenceState.cc | 6 +++--- libadcc/ReferenceState.hh | 4 ++-- libadcc/pyiface/export_HartreeFockProvider.cc | 16 ++++++++-------- libadcc/pyiface/export_ReferenceState.cc | 16 +++++++++++----- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index f22eec85..6440def0 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -449,25 +449,25 @@ def run_core_hole(xyz, basis, charge=0, multiplicity=1, def _determine_gauge_origin(scfres, gauge_origin): """ - Determines the gauge origin. If the gauge origin is defined as a list + Determines the gauge origin. If the gauge origin is defined as a tuple the coordinates need to be given in atomic units! """ coords = scfres.mol.atom_coords() masses = scfres.mol.atom_mass_list(isotope_avg=True) charges = scfres.mol.atom_charges() if gauge_origin == "mass_center": - gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) + gauge_origin = tuple(np.einsum("i,ij->j", masses, coords) / masses.sum()) elif gauge_origin == "charge_center": - gauge_origin = list(np.einsum("i,ij->j", charges, coords) + gauge_origin = tuple(np.einsum("i,ij->j", charges, coords) / charges.sum()) elif gauge_origin == "origin": - gauge_origin = [0.0, 0.0, 0.0] - elif isinstance(gauge_origin, list): + gauge_origin = (0.0, 0.0, 0.0) + elif isinstance(gauge_origin, tuple): gauge_origin = gauge_origin else: raise NotImplementedError("The gauge origin can be defined either by a " "keyword (origin, mass_center or charge_center) " - "or by a list defining the Cartesian components " - "e.g. [x, y, z]." + "or by a tuple defining the Cartesian components " + "e.g. (x, y, z)." ) return gauge_origin diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index bffadf68..fc9ba49e 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -283,7 +283,7 @@ def run_hf(xyz, basis, charge=0, multiplicity=1, conv_tol=None, conv_tol_grad=1e def _determine_gauge_origin(scfdrv, gauge_origin): """ - Determines the gauge origin. If the gauge origin is defined as a list + Determines the gauge origin. If the gauge origin is defined as a tuple the coordinates need to be given in atomic units! """ molecule = scfdrv.task.molecule @@ -293,18 +293,18 @@ def _determine_gauge_origin(scfdrv, gauge_origin): if gauge_origin == "mass_center": # gauge_origin = list(molecule.center_of_mass_in_bohr()) - gauge_origin = list(np.einsum("i,ij->j", masses, coords) / masses.sum()) + gauge_origin = tuple(np.einsum("i,ij->j", masses, coords) / masses.sum()) elif gauge_origin == "charge_center": - gauge_origin = list(np.einsum("i,ij->j", charges, coords) + gauge_origin = tuple(np.einsum("i,ij->j", charges, coords) / charges.sum()) elif gauge_origin == "origin": - gauge_origin = [0.0, 0.0, 0.0] - elif isinstance(gauge_origin, list): + gauge_origin = (0.0, 0.0, 0.0) + elif isinstance(gauge_origin, tuple): gauge_origin = gauge_origin else: raise NotImplementedError("The gauge origin can be defined either by a " "keyword (origin, mass_center or charge_center) " - "or by a list defining the Cartesian components " - "e.g. [x, y, z]." + "or by a tuple defining the Cartesian components " + "e.g. (x, y, z)." ) return gauge_origin diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 1a4c494d..0d4db2e6 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -46,12 +46,12 @@ class HartreeFockSolution_i { ///@{ /** Fill a buffer with nuclear multipole data for the nuclear multipole of * given order. */ - virtual void nuclear_multipole(size_t order, std::vector gauge_origin, + virtual void nuclear_multipole(size_t order, std::array gauge_origin, scalar_type* buffer, size_t size) const = 0; //@} /** Determine the gauge origin. */ - virtual std::vector determine_gauge_origin( + virtual const std::array determine_gauge_origin( std::string gauge_origin) const = 0; /** \name Sizes of the data */ diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index d5e69d44..ca1eab2c 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -351,15 +351,15 @@ std::string ReferenceState::irreducible_representation() const { } std::vector ReferenceState::nuclear_multipole( - size_t order, std::vector gauge_origin) const { + size_t order, std::array gauge_origin) const { std::vector ret((order + 2) * (order + 1) / 2); m_hfsoln_ptr->nuclear_multipole(order, gauge_origin, ret.data(), ret.size()); return ret; } -std::vector ReferenceState::determine_gauge_origin( +const std::array ReferenceState::determine_gauge_origin( std::string gauge_origin) const { - std::vector ret = m_hfsoln_ptr->determine_gauge_origin(gauge_origin); + std::array ret = m_hfsoln_ptr->determine_gauge_origin(gauge_origin); return ret; } diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index 338b0211..41ae7beb 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -93,11 +93,11 @@ class ReferenceState { /** Return the nuclear contribution to the cartesian multipole moment * (in standard ordering, i.e. xx, xy, xz, yy, yz, zz) of the given order. */ std::vector nuclear_multipole(size_t order, - std::vector gauge_origin = { + std::array gauge_origin = { 0, 0, 0}) const; /** Determine the gauge origin for nuclear multipoles. */ - std::vector determine_gauge_origin(std::string gauge_origin) const; + const std::array determine_gauge_origin(std::string gauge_origin) const; /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 9ac0b802..6a27e4c4 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -47,7 +47,7 @@ class HartreeFockProvider : public HartreeFockSolution_i { // // Translate C++-like interface to python-like interface // - void nuclear_multipole(size_t order, std::vector gauge_origin, + void nuclear_multipole(size_t order, std::array gauge_origin, scalar_type* buffer, size_t size) const override { py::array_t ret = get_nuclear_multipole(order, py::cast(gauge_origin)); if (static_cast(size) != ret.size()) { @@ -58,10 +58,10 @@ class HartreeFockProvider : public HartreeFockSolution_i { std::copy(ret.data(), ret.data() + size, buffer); } - std::vector determine_gauge_origin( + const std::array determine_gauge_origin( std::string gauge_origin) const override { - std::vector ret = - py::cast>(get_gauge_origin(py::cast(gauge_origin))); + const std::array ret = + py::cast>(get_gauge_origin(py::cast(gauge_origin))); if (ret.size() != 3) { throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + ") needs to be 3."); @@ -263,7 +263,7 @@ class HartreeFockProvider : public HartreeFockSolution_i { virtual py::array_t get_nuclear_multipole( size_t order, py::array_t gauge_origin) const = 0; - virtual py::list get_gauge_origin(py::str gauge_origin) const = 0; + virtual py::tuple get_gauge_origin(py::str gauge_origin) const = 0; virtual real_type get_conv_tol() const = 0; virtual bool get_restricted() const = 0; virtual size_t get_spin_multiplicity() const = 0; @@ -296,9 +296,9 @@ class PyHartreeFockProvider : public HartreeFockProvider { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order, gauge_origin); } - py::list get_gauge_origin(py::str gauge_origin) const override { - PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, - get_gauge_origin, gauge_origin); + py::tuple get_gauge_origin(py::str gauge_origin) const override { + PYBIND11_OVERLOAD_PURE(py::tuple, HartreeFockProvider, get_gauge_origin, + gauge_origin); } real_type get_conv_tol() const override { PYBIND11_OVERLOAD_PURE(real_type, HartreeFockProvider, get_conv_tol, ); diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 527fb727..7da4358b 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -119,15 +119,21 @@ void export_ReferenceState(py::module& m) { return res; }) .def("nuclear_quadrupole", - [](const ReferenceState& ref, py::list gauge_origin) { + [](const ReferenceState& ref, std::array gauge_origin) { py::array_t ret(std::vector{6}); - auto res = ref.nuclear_multipole( - 2, py::cast>(gauge_origin)); + auto res = ref.nuclear_multipole(2, gauge_origin); std::copy(res.begin(), res.end(), ret.mutable_data()); return res; }) - .def("determine_gauge_origin", &ReferenceState::determine_gauge_origin, - "Determine the gauge origin.") + .def("determine_gauge_origin", + [](const ReferenceState& ref, std::string gauge_origin) { + auto vec = ref.determine_gauge_origin(gauge_origin); + py::tuple coords(3); + for (size_t i = 0; i < vec.size(); ++i) { + coords[i] = vec[i]; // Assign values to the tuple + } + return coords; + }) .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance") .def_property_readonly("energy_scf", &ReferenceState::energy_scf, From 56a18178d7f397be2be0dbc20ed9ea86696eabb0 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 11 Mar 2025 14:53:54 +0100 Subject: [PATCH 35/42] Rename nabla to electric_dipole_velocity --- adcc/DataHfProvider.py | 4 ++-- adcc/tests/generators/dump_pyscf.py | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index dd22bcd8..75a604e6 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -221,8 +221,8 @@ def get_integral_magdip(gauge_origin): + str(magm["mag_1_origin"].shape)) opprov.magnetic_dipole = get_integral_magdip derivs = data.get("derivatives", {}) - if "nabla_origin" in derivs: - opprov.nabla = np.asarray(derivs["nabla_origin"]) + if "elec_vel_1" in derivs: + opprov.electric_dipole_velocity = np.asarray(derivs["elec_vel_1"]) self.operator_integral_provider = opprov # diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index 892963a9..4c4e6db0 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -213,29 +213,25 @@ def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): scfres.mol.intor_symmetric('int1e_rr', comp=9) ) - data["magnetic_moments"] = {} data["derivatives"] = {} + data["derivatives"]["elec_vel_1"] = ( + -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) + + data["magnetic_moments"] = {} with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): data["magnetic_moments"]["mag_1_origin"] = ( 0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - data["derivatives"]["nabla_origin"] = ( - -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) + with scfres.mol.with_common_orig(mass_center): data["magnetic_moments"]["mag_1_mass_center"] = ( 0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - data["derivatives"]["nabla_mass_center"] = ( - -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) with scfres.mol.with_common_orig(charge_center): data["magnetic_moments"]["mag_1_charge_center"] = ( 0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - data["derivatives"]["nabla_charge_center"] = ( - -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) hdf5io.emplace_dict(data, hdf5_file, compression="gzip") hdf5_file.attrs["backend"] = "pyscf" From 2c724b1e9d50c705c889599bbf2a2429cfcaa592 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 11 Mar 2025 16:23:32 +0100 Subject: [PATCH 36/42] make flake8 happy --- adcc/ElectronicTransition.py | 5 +++-- adcc/backends/pyscf.py | 2 +- adcc/backends/veloxchem.py | 2 +- adcc/tests/generators/dump_pyscf.py | 6 +++--- adcc/tests/properties_test.py | 1 - 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 3af011da..22c024fa 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -258,8 +258,9 @@ def rotatory_strength_length(self, gauge_origin=(0.0, 0.0, 0.0)): """List of rotatory strengths in length gauge of all computed states""" return np.array([ -1.0 * np.dot(tdm, magmom) - for tdm, magmom in zip(self.transition_dipole_moment, - self.transition_magnetic_dipole_moment(gauge_origin)) + for tdm, magmom in zip( + self.transition_dipole_moment, + self.transition_magnetic_dipole_moment(gauge_origin)) ]) @property diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 9f8961b8..1f8862d6 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -464,7 +464,7 @@ def _determine_gauge_origin(scfres, gauge_origin): gauge_origin = tuple(np.einsum("i,ij->j", masses, coords) / masses.sum()) elif gauge_origin == "charge_center": gauge_origin = tuple(np.einsum("i,ij->j", charges, coords) - / charges.sum()) + / charges.sum()) elif gauge_origin == "origin": gauge_origin = (0.0, 0.0, 0.0) elif isinstance(gauge_origin, tuple): diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index ec090fd1..1c0c973d 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -299,7 +299,7 @@ def _determine_gauge_origin(scfdrv, gauge_origin): gauge_origin = tuple(np.einsum("i,ij->j", masses, coords) / masses.sum()) elif gauge_origin == "charge_center": gauge_origin = tuple(np.einsum("i,ij->j", charges, coords) - / charges.sum()) + / charges.sum()) elif gauge_origin == "origin": gauge_origin = (0.0, 0.0, 0.0) elif isinstance(gauge_origin, tuple): diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index eea8a674..a5b1a4ba 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -217,15 +217,15 @@ def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): data["derivatives"] = {} data["derivatives"]["elec_vel_1"] = ( - -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) - ) + -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + ) data["magnetic_moments"] = {} with scfres.mol.with_common_orig([0.0, 0.0, 0.0]): data["magnetic_moments"]["mag_1_origin"] = ( -0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - + with scfres.mol.with_common_orig(mass_center): data["magnetic_moments"]["mag_1_mass_center"] = ( -0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) diff --git a/adcc/tests/properties_test.py b/adcc/tests/properties_test.py index b058f7fa..55d43145 100644 --- a/adcc/tests/properties_test.py +++ b/adcc/tests/properties_test.py @@ -209,7 +209,6 @@ def test_magnetic_transition_dipole_moments(self, system: str, case: str, res_dms = state.transition_magnetic_dipole_moment n_ref = len(state.excitation_vector) - # Only adcc reference data available. @pytest.mark.parametrize("system,case,kind", cases) From 5dc18a15b68a9c650b28d5258fefecdb49a07c3e Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Wed, 12 Mar 2025 14:37:40 +0100 Subject: [PATCH 37/42] merge conflicts --- adcc/backends/psi4.py | 2 +- adcc/backends/pyscf.py | 2 +- adcc/backends/veloxchem.py | 2 +- adcc/tests/ExcitedStates_test.py | 1 - adcc/tests/IsrMatrix_test.py | 2 +- adcc/tests/generators/dump_pyscf.py | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 6c06d0cf..3c21d924 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -211,7 +211,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return self.wfn.basisset().nbf() - def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): + def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): molecule = self.wfn.molecule() if order == 0: # The function interface needs to be a np.array on return diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 1f8862d6..960d5f90 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -290,7 +290,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return int(self.scfres.mol.nao_nr()) - def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): + def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): charges = self.scfres.mol.atom_charges() if order == 0: # The function interface needs to be a np.array on return diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 1c0c973d..fd6451f7 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -166,7 +166,7 @@ def get_n_orbs_alpha(self): def get_n_bas(self): return self.mol_orbs.number_aos() - def get_nuclear_multipole(self, order, gauge_origin=[0, 0, 0]): + def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): mol = self.scfdrv.task.molecule nuc_charges = mol.elem_ids_to_numpy() if order == 0: diff --git a/adcc/tests/ExcitedStates_test.py b/adcc/tests/ExcitedStates_test.py index 25178510..ed35ed56 100644 --- a/adcc/tests/ExcitedStates_test.py +++ b/adcc/tests/ExcitedStates_test.py @@ -50,7 +50,6 @@ def test_excitation_view(self): ref = getattr(state, key)[i] res = getattr(exci, key) except NotImplementedError: - # nabla, etc. not implemented in dict backend continue except TypeError: # gauge origin dependent properties are callables diff --git a/adcc/tests/IsrMatrix_test.py b/adcc/tests/IsrMatrix_test.py index d25772ea..3a174fa9 100644 --- a/adcc/tests/IsrMatrix_test.py +++ b/adcc/tests/IsrMatrix_test.py @@ -83,7 +83,7 @@ def test_matrix_vector_product(self, system: str, case: str, kind: str, s2s_tdm_ref = state2state.transition_dipole_moment[j] else: s2s_tdm_ref = \ - state2state.transition_magnetic_dipole_moment('origin')[j] + state2state.transition_magnetic_dipole_moment("origin")[j] np.testing.assert_allclose(s2s_tdm, s2s_tdm_ref, atol=1e-12) diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index a5b1a4ba..ccfce558 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -217,7 +217,7 @@ def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): data["derivatives"] = {} data["derivatives"]["elec_vel_1"] = ( - -1.0 * scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) + scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) data["magnetic_moments"] = {} From 2266a5e7bdf204bf687a4c8afae7d179f444b7f2 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Thu, 13 Mar 2025 15:06:13 +0100 Subject: [PATCH 38/42] Github comments, new referencedata --- adcc/DataHfProvider.py | 7 +- adcc/ElectronicTransition.py | 13 ++-- adcc/OperatorIntegrals.py | 58 ++++++++------- adcc/ReferenceState.py | 2 +- adcc/backends/molsturm.py | 2 +- adcc/backends/psi4.py | 6 +- adcc/backends/pyscf.py | 36 +++++---- adcc/backends/veloxchem.py | 9 +-- adcc/tests/backends/backends_crossref_test.py | 17 ++--- adcc/tests/backends/pyscf_test.py | 6 +- adcc/tests/backends/veloxchem_test.py | 4 +- adcc/tests/data/SHA256SUMS | 74 +++++++++---------- adcc/tests/data/update_testdata.sh | 2 +- adcc/tests/hardcoded_test.py | 2 +- adcc/tests/properties_test.py | 19 +++-- libadcc/HartreeFockSolution_i.hh | 2 +- libadcc/ReferenceState.cc | 4 +- libadcc/ReferenceState.hh | 2 +- libadcc/pyiface/export_HartreeFockProvider.cc | 35 ++++----- libadcc/pyiface/export_ReferenceState.cc | 4 +- 20 files changed, 157 insertions(+), 147 deletions(-) diff --git a/adcc/DataHfProvider.py b/adcc/DataHfProvider.py index 75a604e6..924bea9a 100644 --- a/adcc/DataHfProvider.py +++ b/adcc/DataHfProvider.py @@ -222,7 +222,12 @@ def get_integral_magdip(gauge_origin): opprov.magnetic_dipole = get_integral_magdip derivs = data.get("derivatives", {}) if "elec_vel_1" in derivs: + if derivs["elec_vel_1"].shape != (3, nb, nb): + raise ValueError("derivatives/elec_vel_1 is expected to have shape " + + str((3, nb, nb)) + " not " + + str(derivs["elec_vel_1"].shape)) opprov.electric_dipole_velocity = np.asarray(derivs["elec_vel_1"]) + self.operator_integral_provider = opprov # @@ -265,7 +270,7 @@ def get_backend(self): def get_energy_scf(self): return get_scalar_value(self.data, "energy_scf", 0.0) - def get_nuclear_multipole(self, order: int, gauge_origin) -> np.ndarray: + def get_nuclear_multipole(self, order: int, gauge_origin: str) -> np.ndarray: key = f"multipoles/nuclear_{order}" if order == 0: nuc_multipole = get_scalar_value(self.data, key, default=None) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 22c024fa..2fd8bafd 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -212,8 +212,8 @@ def transition_quadrupole_moment(self, gauge_origin=(0.0, 0.0, 0.0)): def transition_quadrupole_moment_velocity(self, gauge_origin=(0.0, 0.0, 0.0)): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: - warnings.warn("ADC(0) transition quadrupole moments are known to be " - "faulty in some cases.") + warnings.warn("ADC(0) transition velocity quadrupole moments are known " + "to be faulty in some cases.") quadrupole_integrals = \ self.operators.electric_quadrupole_velocity(gauge_origin) return np.array([[ @@ -242,14 +242,17 @@ def oscillator_strength_velocity(self): self.excitation_energy) ]) + @cached_property @mark_excitation_property() - def rotatory_strength(self, gauge_origin=(0.0, 0.0, 0.0)): - """List of rotatory strengths (in velocity gauge) of all computed states""" + def rotatory_strength(self): + """List of rotatory strengths (in velocity gauge) of all computed states. + This property is gauge-origin invariant, thus, it is not possible to + select a gauge origin.""" return np.array([ np.dot(tdm, magmom) / ee for tdm, magmom, ee in zip( self.transition_dipole_moment_velocity, - self.transition_magnetic_dipole_moment(gauge_origin), + self.transition_magnetic_dipole_moment("origin"), self.excitation_energy) ]) diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 613a2872..42b728f0 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -128,8 +128,8 @@ def import_dipole_like_operator(self, integral, is_symmetric=True): dipoles.append(dip_ff) return dipoles - def __import_gauge_dependent_dipole_like(self, callback, is_symmetric=True): - """Returns a function that imports a gauge-dependent dipole like + def __import_g_origin_dep_dip_like_operator(self, callback, is_symmetric=True): + """Returns a function that imports a gauge-origin dependent dipole like operator. The returned function imports the operator and transforms it to the molecular orbital basis. @@ -145,7 +145,7 @@ def __import_gauge_dependent_dipole_like(self, callback, is_symmetric=True): if not callable(callback): raise TypeError - def process_operator(gauge_origin=[0.0, 0.0, 0.0], callback=callback, + def process_operator(gauge_origin=(0.0, 0.0, 0.0), callback=callback, is_symmetric=is_symmetric): dipoles = [] for i, component in enumerate(["x", "y", "z"]): @@ -167,18 +167,6 @@ def electric_dipole(self): return self.import_dipole_like_operator("electric_dipole", is_symmetric=True) - @property - @timed_member_call("_import_timer") - def magnetic_dipole(self): - """ - Returns a function to obtain magnetic dipole intergrals - in the molecular orbital basis dependent on the selected gauge origin. - The default gauge origin is set to [0.0, 0.0, 0.0]. - """ - callback = self.provider_ao.magnetic_dipole - return self.__import_gauge_dependent_dipole_like(callback, - is_symmetric=False) - @property @timed_member_call("_import_timer") def electric_dipole_velocity(self): @@ -189,8 +177,20 @@ def electric_dipole_velocity(self): return self.import_dipole_like_operator("electric_dipole_velocity", is_symmetric=False) - def __import_quadrupole_like_operator(self, callback, is_symmetric=True): - """Returns a function that imports a gauge-dependent quadrupole like + @property + @timed_member_call("_import_timer") + def magnetic_dipole(self): + """ + Returns a function to obtain magnetic dipole intergrals + in the molecular orbital basis dependent on the selected gauge origin. + The default gauge origin is set to (0.0, 0.0, 0.0). + """ + callback = self.provider_ao.magnetic_dipole + return self.__import_g_origin_dep_dip_like_operator(callback, + is_symmetric=False) + + def __import_g_origin_dep_quad_like_operator(self, callback, is_symmetric=True): + """Returns a function that imports a gauge-origin dependent quadrupole like operator. The returned function imports the operator and transforms it to the molecular orbital basis. @@ -230,25 +230,27 @@ def process_operator(gauge_origin=(0.0, 0.0, 0.0), callback=callback, @property @timed_member_call("_import_timer") - def electric_quadrupole_traceless(self): + def electric_quadrupole(self): """ - Returns a function to obtain traceless electric quadrupole integrals + Returns a function to obtain electric quadrupole integrals in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ - callback = self.provider_ao.electric_quadrupole_traceless - return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + callback = self.provider_ao.electric_quadrupole + return self.__import_g_origin_dep_quad_like_operator(callback, + is_symmetric=True) @property @timed_member_call("_import_timer") - def electric_quadrupole(self): + def electric_quadrupole_traceless(self): """ - Returns a function to obtain electric quadrupole integrals + Returns a function to obtain traceless electric quadrupole integrals in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ - callback = self.provider_ao.electric_quadrupole - return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + callback = self.provider_ao.electric_quadrupole_traceless + return self.__import_g_origin_dep_quad_like_operator(callback, + is_symmetric=True) @property @timed_member_call("_import_timer") @@ -259,7 +261,8 @@ def electric_quadrupole_velocity(self): The default gauge origin is set to (0.0, 0.0, 0.0). """ callback = self.provider_ao.electric_quadrupole_velocity - return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + return self.__import_g_origin_dep_quad_like_operator(callback, + is_symmetric=False) @property @timed_member_call("_import_timer") @@ -270,7 +273,8 @@ def diamagnetic_magnetizability(self): The default gauge origin is set to (0.0, 0.0, 0.0). """ callback = self.provider_ao.diamagnetic_magnetizability - return self.__import_quadrupole_like_operator(callback, is_symmetric=False) + return self.__import_g_origin_dep_quad_like_operator(callback, + is_symmetric=True) def __import_density_dependent_operator(self, ao_callback, is_symmetric=True): """Returns a function that imports a density-dependent operator. diff --git a/adcc/ReferenceState.py b/adcc/ReferenceState.py index f931a162..fcb8d0c6 100644 --- a/adcc/ReferenceState.py +++ b/adcc/ReferenceState.py @@ -226,7 +226,7 @@ def dipole_moment(self): def nuclear_quadrupole(self, gauge_origin="origin"): if isinstance(gauge_origin, str): - gauge_origin = self.determine_gauge_origin(gauge_origin) + gauge_origin = self.gauge_origin_to_xyz(gauge_origin) return super().nuclear_quadrupole(gauge_origin) # TODO some nice describe method diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index 0f16d22f..9f9ba8be 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -32,7 +32,7 @@ def convert_scf_to_dict(scfres): raise TypeError("Unsupported type for backends.molsturm.import_scf.") warnings.warn("Gauge origin selection not available in Molsturm." "The gauge origin is selected as origin of the " - "Cartesian coordinate system [0.0, 0.0, 0.0].") + "Cartesian coordinate system (0.0, 0.0, 0.0).") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 3c21d924..6c6462b7 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -42,7 +42,7 @@ def __init__(self, wfn): warnings.warn("Gauge origin selection not available in " f"{self.backend}. " "The gauge origin is selected as origin of the " - "Cartesian coordinate system [0.0, 0.0, 0.0].") + "Cartesian coordinate system (0.0, 0.0, 0.0).") @cached_property def electric_dipole(self): @@ -223,8 +223,8 @@ def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): else: raise NotImplementedError("get_nuclear_multipole with order > 1") - def get_gauge_origin(self, gauge_origin): - raise NotImplementedError("get_gauge_origin not implemented.") + def transform_gauge_origin_to_xyz(self, gauge_origin): + raise NotImplementedError("transform_gauge_origin_to_xyz not implemented.") def fill_orbcoeff_fb(self, out): mo_coeff_a = np.asarray(self.wfn.Ca()) diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 960d5f90..4a43393d 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -49,7 +49,7 @@ def electric_dipole(self): def magnetic_dipole(self): """-0.5 * sum_i r_i x p_i""" def g_origin_dep_ints_mag_dip(gauge_origin="origin"): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): return list( -0.5 * self.scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) @@ -59,24 +59,28 @@ def g_origin_dep_ints_mag_dip(gauge_origin="origin"): @cached_property def electric_dipole_velocity(self): """-sum_i p_i""" - with self.scfres.mol.with_common_orig([0.0, 0.0, 0.0]): + with self.scfres.mol.with_common_orig((0.0, 0.0, 0.0)): return list( self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) ) @property def electric_quadrupole(self): + """-sum_i r_{i, alpha} r_{i, beta}""" def g_origin_dep_ints_el_quad(gauge_origin): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): - r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) + r_r = -1.0 * self.scfres.mol.intor_symmetric('int1e_rr', comp=9) return list(r_r) return g_origin_dep_ints_el_quad @property def electric_quadrupole_traceless(self): + """ + -0.5 * sum_i (r_{i, alpha} r_{i, beta} - delta_{alpha, beta} r^2) + """ def g_origin_dep_ints_el_quad_traceless(gauge_origin): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) @@ -85,14 +89,19 @@ def g_origin_dep_ints_el_quad_traceless(gauge_origin): for i in range(3): r_quadr_matrix[i][i] = r_quadr term = 0.5 * (3 * r_r - r_quadr_matrix) - term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) + term = -1.0 * np.reshape(term, (9, r_quadr.shape[0], + r_quadr.shape[0])) return list(term) return g_origin_dep_ints_el_quad_traceless @property def electric_quadrupole_velocity(self): + """ + -sum_i (r_{i, beta} p_{i, alpha} - delta_{alpha, beta} S + + r_{i, alpha} p_{i, beta}) + """ def g_origin_dep_ints_el_quad_vel(gauge_origin): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_p = self.scfres.mol.intor('int1e_irp', comp=9, hermi=0) r_p = np.reshape(r_p, (3, 3, r_p.shape[1], r_p.shape[1])) @@ -101,14 +110,15 @@ def g_origin_dep_ints_el_quad_vel(gauge_origin): for i in range(3): ovlp_matrix[i][i] = ovlp term = r_p + r_p.transpose(1, 0, 2, 3) - ovlp_matrix - term = np.reshape(term, (9, ovlp.shape[0], ovlp.shape[0])) + term = -1.0 * np.reshape(term, (9, ovlp.shape[0], ovlp.shape[0])) return list(term) return g_origin_dep_ints_el_quad_vel @property def diamagnetic_magnetizability(self): + """0.25 * sum_i (r_{i, alpha} r_{i, beta} - delta_{alpha, beta} r^2""" def g_origin_dep_ints_diamag_magn(gauge_origin): - gauge_origin = _determine_gauge_origin(self.scfres, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): r_r = self.scfres.mol.intor_symmetric('int1e_rr', comp=9) r_r = np.reshape(r_r, (3, 3, r_r.shape[1], r_r.shape[1])) @@ -116,7 +126,7 @@ def g_origin_dep_ints_diamag_magn(gauge_origin): r_quadr_matrix = np.zeros_like(r_r) for i in range(3): r_quadr_matrix[i][i] = r_quadr - term = -0.25 * (r_quadr_matrix - r_r) + term = 0.25 * (r_quadr_matrix - r_r) term = np.reshape(term, (9, r_quadr.shape[0], r_quadr.shape[0])) return list(term) return g_origin_dep_ints_diamag_magn @@ -307,8 +317,8 @@ def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): else: raise NotImplementedError("get_nuclear_multipole with order > 2") - def get_gauge_origin(self, gauge_origin): - return _determine_gauge_origin(self.scfres, gauge_origin) + def transform_gauge_origin_to_xyz(self, gauge_origin): + return _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) def fill_occupation_f(self, out): if self.restricted: @@ -452,7 +462,7 @@ def run_core_hole(xyz, basis, charge=0, multiplicity=1, return mf_chole -def _determine_gauge_origin(scfres, gauge_origin): +def _transform_gauge_origin_to_xyz(scfres, gauge_origin): """ Determines the gauge origin. If the gauge origin is defined as a tuple the coordinates need to be given in atomic units! diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index fd6451f7..8e73954a 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -59,7 +59,7 @@ def electric_dipole(self): def magnetic_dipole(self): """-0.5 * sum_i r_i x p_i""" def g_origin_dep_ints_mag_dip(gauge_origin="origin"): - gauge_origin = _determine_gauge_origin(self.scfdrv, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(self.scfdrv, gauge_origin) task = self.scfdrv.task angmom_drv = AngularMomentumIntegralsDriver(task.mpi_comm) angmom_drv.origin = tuple(gauge_origin) @@ -186,8 +186,8 @@ def get_nuclear_multipole(self, order, gauge_origin=(0, 0, 0)): else: raise NotImplementedError("get_nuclear_multipole with order > 2") - def get_gauge_origin(self, gauge_origin): - return _determine_gauge_origin(self.scfdrv, gauge_origin) + def transform_gauge_origin_to_xyz(self, gauge_origin): + return _transform_gauge_origin_to_xyz(self.scfdrv, gauge_origin) def fill_occupation_f(self, out): n_mo = self.mol_orbs.number_mos() @@ -284,7 +284,7 @@ def run_hf(xyz, basis, charge=0, multiplicity=1, conv_tol=None, conv_tol_grad=1e return scfdrv -def _determine_gauge_origin(scfdrv, gauge_origin): +def _transform_gauge_origin_to_xyz(scfdrv, gauge_origin): """ Determines the gauge origin. If the gauge origin is defined as a tuple the coordinates need to be given in atomic units! @@ -295,7 +295,6 @@ def _determine_gauge_origin(scfdrv, gauge_origin): masses = molecule.masses_to_numpy() if gauge_origin == "mass_center": - # gauge_origin = list(molecule.center_of_mass_in_bohr()) gauge_origin = tuple(np.einsum("i,ij->j", masses, coords) / masses.sum()) elif gauge_origin == "charge_center": gauge_origin = tuple(np.einsum("i,ij->j", charges, coords) diff --git a/adcc/tests/backends/backends_crossref_test.py b/adcc/tests/backends/backends_crossref_test.py index 990e498d..337855f0 100644 --- a/adcc/tests/backends/backends_crossref_test.py +++ b/adcc/tests/backends/backends_crossref_test.py @@ -160,8 +160,8 @@ def compare_hf_properties(results, atol): if len(set(comb) & set(backends_with_gauge_origin)) == 2: gauge_origins = ["origin", "mass_center", "charge_center"] for gauge_origin in gauge_origins: - assert_allclose(refstate1.determine_gauge_origin(gauge_origin), - refstate2.determine_gauge_origin(gauge_origin), + assert_allclose(refstate1.gauge_origin_to_xyz(gauge_origin), + refstate2.gauge_origin_to_xyz(gauge_origin), atol=1e-5) assert_allclose(refstate1.nuclear_total_charge, @@ -314,16 +314,9 @@ def assert_allclose_signs(actual, desired): for op in ["magnetic_dipole", "electric_dipole_velocity"]) if has_rotatory1 and has_rotatory2: - for gauge_origin in gauge_origins: - # reduce the tolerance criteria for mass_center as there are diff. - # isotropic mass values implemented in veloxchem and pyscf. - if gauge_origin == "mass_center": - atol_tdm_mag = 2e-4 - else: - atol_tdm_mag = atol - assert_allclose(state1.rotatory_strength(gauge_origin), - state2.rotatory_strength(gauge_origin), - atol=atol_tdm_mag) + assert_allclose(state1.rotatory_strength, + state2.rotatory_strength, + atol=atol) has_rotatory_len1 = all(op in state1.operators.available for op diff --git a/adcc/tests/backends/pyscf_test.py b/adcc/tests/backends/pyscf_test.py index f2b1760f..ceb98ea4 100644 --- a/adcc/tests/backends/pyscf_test.py +++ b/adcc/tests/backends/pyscf_test.py @@ -106,7 +106,7 @@ def base_test(self, scfres): assert_almost_equal(eri_perm, eri) def operators_test(self, mf): - from adcc.backends.pyscf import _determine_gauge_origin + from adcc.backends.pyscf import _transform_gauge_origin_to_xyz gauge_origins = ["origin", "mass_center", "charge_center"] # Test electric dipole @@ -120,7 +120,7 @@ def operators_test(self, mf): # Test gauge origin dependent integrals for gauge_origin in gauge_origins: - gauge_origin = _determine_gauge_origin(mf, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(mf, gauge_origin) # Test magnetic dipole with mf.mol.with_common_orig(gauge_origin): @@ -130,7 +130,7 @@ def operators_test(self, mf): # Test electric quadrupole with mf.mol.with_common_orig(gauge_origin): - ao_quad = mf.mol.intor_symmetric('int1e_rr', comp=9) + ao_quad = -1.0 * mf.mol.intor_symmetric('int1e_rr', comp=9) operator_import_from_ao_test(mf, list(ao_quad), "electric_quadrupole", gauge_origin) diff --git a/adcc/tests/backends/veloxchem_test.py b/adcc/tests/backends/veloxchem_test.py index 566937b1..3b740cee 100644 --- a/adcc/tests/backends/veloxchem_test.py +++ b/adcc/tests/backends/veloxchem_test.py @@ -127,7 +127,7 @@ def base_test(self, scfdrv): assert_almost_equal(eri_perm, eri) def operators_test(self, scfdrv): - from adcc.backends.veloxchem import _determine_gauge_origin + from adcc.backends.veloxchem import _transform_gauge_origin_to_xyz gauge_origins = ["origin", "mass_center", "charge_center"] # Test dipole @@ -152,7 +152,7 @@ def operators_test(self, scfdrv): operator="electric_dipole_velocity") for gauge_origin in gauge_origins: - gauge_origin = _determine_gauge_origin(scfdrv, gauge_origin) + gauge_origin = _transform_gauge_origin_to_xyz(scfdrv, gauge_origin) # Test magnetic dipole angmom_drv = AngularMomentumIntegralsDriver(scfdrv.task.mpi_comm) diff --git a/adcc/tests/data/SHA256SUMS b/adcc/tests/data/SHA256SUMS index 9ef4eb85..4fbce1ae 100644 --- a/adcc/tests/data/SHA256SUMS +++ b/adcc/tests/data/SHA256SUMS @@ -1,32 +1,32 @@ -7b42f084aa8361a34b325d07dff50ba80b995aec93d747020f992c92cc22987d ch2nh2_sto3g_hfdata.hdf5 +87e83e6a96e419a14e159d40827c0e8b8d98734e6886655b0494945dae552a11 ch2nh2_sto3g_hfdata.hdf5 69c78d4f5b364f418ecdc7d31eb6c42433d291db661a0c3a594ca7afcd500e3d ch2nh2_sto3g_hfimport.hdf5 -fa743fd3b71d5c670468a98c33145c34c5809a8546bd21480aa9cded566684c8 cn_ccpvdz_adcc_adc0.hdf5 -fc65d61873ae1f056e3e16e46ace767155053ca76a8f29af98b385904165a8ce cn_ccpvdz_adcc_adc1.hdf5 -e74a55b3703113f8629e08a878843f39de9297498926f8c357cd7f5385ac0d12 cn_ccpvdz_adcc_adc2.hdf5 -eabccf384a196f7f20cf94642d50ca03f22d3576436bef75a93427337c7b8719 cn_ccpvdz_adcc_adc2x.hdf5 -ddeb396026067654837391ec490c85dd1578c0e90a6b806205ffdeb1ce6c3146 cn_ccpvdz_adcc_adc3.hdf5 -0d4272b3dedd7a6f2bedecfe815376e2df1f88dfedb11fae251e165594612334 cn_ccpvdz_adcc_mpdata.hdf5 +564e0363921d54ac7a9efd572f853f53fc59d032c0c44d3fc82b0f6a9bbe7d52 cn_ccpvdz_adcc_adc0.hdf5 +ab184c014f0d389b80758b6bd5778ba019eda5ecc3caf525e7930db1615d0297 cn_ccpvdz_adcc_adc1.hdf5 +ebc9247307cb497e2cc86673fa547c459006d0d38d2766c273488c70b7bc7fb9 cn_ccpvdz_adcc_adc2.hdf5 +31b31dad326f7c339c10a5f726af6ec80e5094920f51da91a6b72138a837a587 cn_ccpvdz_adcc_adc2x.hdf5 +e4b2d33a6d651c5538473586f5a89284692ccebbd56e451a8770726acaa4370f cn_ccpvdz_adcc_adc3.hdf5 +46a62b543009c1d1747d32bc60c6db9ca580bdf1179a22faba2e4a810332f0c1 cn_ccpvdz_adcc_mpdata.hdf5 263da58485f677f23ccc3a602d53289e30cb801eee9cffdf3e916434da15941e cn_ccpvdz_adcman_adc0.hdf5 026b80586f367d858b2c9d3f8a3c5a49e4fcc53443726d9cea7d94dc7b4e5a0e cn_ccpvdz_adcman_adc1.hdf5 b6a53c29557c9f9c19ab7fcb8be59dc1880c8daba3aaab466f903de766b02196 cn_ccpvdz_adcman_adc2.hdf5 9edf680a529a79d3b7ed074dbd42086d482927935b28e886b553a329c5287014 cn_ccpvdz_adcman_adc2x.hdf5 9a9270d59b453f2d575adedc0544145c791b65ddb5af18a494e617be727d5481 cn_ccpvdz_adcman_adc3.hdf5 68c2b8f9bc577541bf0c40ba74c4564683dc668c45fdcffea6d02e01d2347c0e cn_ccpvdz_adcman_mpdata.hdf5 -d142c71468c6de09145867bb9df206c94c47ecdaf91b2a55fc336b515bead750 cn_ccpvdz_hfdata.hdf5 +56b0f5e98fa41ab9559318fb36d71f2c378ff04fd862dd75a8f12f2d67d75a0b cn_ccpvdz_hfdata.hdf5 796ef0755f749f4eec2367be3fdfec018e54a62d0136b6edf32ee0a13b0f6126 cn_ccpvdz_hfimport.hdf5 -ec2ad87b9bf91ef344ae85277eccfbc23b9e67ede49c6e1becaf80f8b85da0c6 cn_sto3g_adcc_adc0.hdf5 -df6d0550e1650e570defcd7104a42dcabb7c8165c4faf8f5fd70959168c00ef1 cn_sto3g_adcc_adc1.hdf5 -d26a82fa439474690a71c424201b7a3a1351d28e36351fd4fbbb2c27d249722f cn_sto3g_adcc_adc2.hdf5 -acf590b6cd2f5d06c24645ac407084de5f31f2d04e0be6a8d107486119babca6 cn_sto3g_adcc_adc2x.hdf5 -f63ffff916e51750512bfc6d21ba9e1fd4cabd2a1ef3c5f99deea9dfd5dee428 cn_sto3g_adcc_adc3.hdf5 -c09f375d10351968180256e376425eac16dedd30a8cb2299b0fa25c155e5e90d cn_sto3g_adcc_mpdata.hdf5 +9501927aa1b8bd485ac728857519bbb001203cfa1f7a6c502b874eee4118be8e cn_sto3g_adcc_adc0.hdf5 +f89399e3fef83070c2244462cac91b368fca6b9defb93b0c7d62fe7df9d5efb9 cn_sto3g_adcc_adc1.hdf5 +17d711a648acb6cbb75aa5fbe983ecf4c8c5b96b4648926ba11bdc09ad6c4cfe cn_sto3g_adcc_adc2.hdf5 +217be0819872e0f7407ea3339b96c8d1ce7b6d810fdbd38a46b2c9c6be2e1679 cn_sto3g_adcc_adc2x.hdf5 +df18079738516cbc83d9f077cc31379fbf99af80cb0ec0bdff1bbba9c983bbb8 cn_sto3g_adcc_adc3.hdf5 +641a7e7aca00683caa432a644e763ce45fa838875e02068d5c4833c3192360c6 cn_sto3g_adcc_mpdata.hdf5 a53caff3e0dfaf7979613d79142adfecc1698dce9a65d95a467ca3e7c35efbda cn_sto3g_adcman_adc0.hdf5 370e521200efb82191ccc438b67f420d12967c2930de3f377d629226ae80764b cn_sto3g_adcman_adc1.hdf5 27e0718d5a12b36a60a971c694006f54901a103d1f0c413e604082464c049561 cn_sto3g_adcman_adc2.hdf5 6962c318d10918bff59af64802c629b7c4181c6c4cf242d3825062ddf2a82248 cn_sto3g_adcman_adc2x.hdf5 fe1e8805bfb289865597130d092e236dfb9675705f953507010d3d0c0f63a4d7 cn_sto3g_adcman_adc3.hdf5 20eb9f96d40c1eb8f89fad7b85f54401ec33e752413eefc5d94a6cec41909820 cn_sto3g_adcman_mpdata.hdf5 -66341776865ae8b32790af46167f1607d22f5bdec0cc65efa95f6f6499234a21 cn_sto3g_hfdata.hdf5 +04ffabacc4460b286fa966586344af48093c00f79413e30960ae2cf03380f9b4 cn_sto3g_hfdata.hdf5 5c3e7229d46e7243a56480afe209946309101386cfcb8f19b4c42e2e951cdf73 cn_sto3g_hfimport.hdf5 8c7d88af083003a190a5561d31d1c9c231c0cb1e149a00f8b6cd92d020871332 formaldehyde_ccpvdz_adcman_adc0.hdf5 c55ccf23bd94fcd2e3660cc73915e4994c59143eaf92ea96a99df295bfd90be6 formaldehyde_ccpvdz_adcman_adc1.hdf5 @@ -38,45 +38,45 @@ a7fa29698a7338edd56444b7ee1e2f2db68fe8f50219dc60911e78cb97644c8e formaldehyde_s 7c6e4cc536784af51135fcf99ef8335c408ccc76b2613719fc4dffdbbe22121d formaldehyde_sto3g_adcman_adc2.hdf5 b579b2e62d4d02c0e5d16ab8b8e30009582152dba63ac12cea77f7dbbd80c29d formaldehyde_sto3g_adcman_adc2x.hdf5 197fc726d99375350c18cc7255443f0d4c670057b030b45a04a3ba16cc219c04 formaldehyde_sto3g_adcman_adc3.hdf5 -9909a4655409d557007e19e5d2b517aeb2987e73c044e8d2779f48f6dbb20bd7 h2o_def2tzvp_adcc_adc0.hdf5 -e4974f7ee6b2931dc76122d61d7cb4cfaae682ee6a43017d21c41bc51ffa0653 h2o_def2tzvp_adcc_adc1.hdf5 -51719613c76a5b39396e2cb47a6b77fcb23ffb182aa350a2ac803d15bbc8cadb h2o_def2tzvp_adcc_adc2.hdf5 -9f27420e6a2f835b14a273fda9bab8c1eae00362e8bbaccfec3e89bb71484d77 h2o_def2tzvp_adcc_adc2x.hdf5 -e093d55f09b05990ed91c53ed16d35d0a67558b3e8f2d196fb3dc30022c3b68f h2o_def2tzvp_adcc_adc3.hdf5 -2ead2eb4576cfb43b44a027ff96b7290c16d5c6acfbc533a08c73d17377794a1 h2o_def2tzvp_adcc_mpdata.hdf5 +cfd9e9c2c7b99d3f3ea739b1eb9aed02109a6deef01f42a08b9b75400626247d h2o_def2tzvp_adcc_adc0.hdf5 +0f233517ae4c483748b68eada805e91e4ce363c6f508c67c5a6188bd1e86281d h2o_def2tzvp_adcc_adc1.hdf5 +1725796e01786c731bee4a5eccc8f6851e0843622e1ed11a2ff196572ac7462b h2o_def2tzvp_adcc_adc2.hdf5 +32a1e259ffb3acb98091f7494b27f9ca3c37b3287f4773a09fb85a67ba226bf7 h2o_def2tzvp_adcc_adc2x.hdf5 +210f348cd827a28900957e5ec90f8b863f93b390af428a8c02c84f52936c39c4 h2o_def2tzvp_adcc_adc3.hdf5 +a66b3878a9c57b17b63c5def88231138a6b1ee6977920c69a251c913e126aec4 h2o_def2tzvp_adcc_mpdata.hdf5 7b33eb80fa0e1f1f0a7cef5113712b49180fdaac2c608393be5d27221f330926 h2o_def2tzvp_adcman_adc0.hdf5 9229b44794be8c1443018ed891afe45f30d0144a3bb5d2599ab9db5e664b1597 h2o_def2tzvp_adcman_adc1.hdf5 a66c479c34a8685e109ba169c09bde50990c7f57905f055e42bd1a7ace5af7e7 h2o_def2tzvp_adcman_adc2.hdf5 cc0d66b5b7482f5256c83da5a24a433564b46bb8ab087dd1d8c4d9f466d46e06 h2o_def2tzvp_adcman_adc2x.hdf5 1da669c4e4e771e0f7fe9eaee4ff953aa5c6499ae33deb1b50db2f37ccf06189 h2o_def2tzvp_adcman_adc3.hdf5 6bb2aa91dc21ec06557b913069594d0f6c1bc1f743df7e0774cc54f37e967375 h2o_def2tzvp_adcman_mpdata.hdf5 -834abc0382932301f0789e07d9f4a92523f437d4ecc6da0a35230b5f5a567b5e h2o_def2tzvp_hfdata.hdf5 +a9894ad871039a29a3f149a9bea650177de025935ab29236486b894be0bdb861 h2o_def2tzvp_hfdata.hdf5 24c43ff288af789de5b25d5d616be2f28cc4acc602d90124d564136b96b8f026 h2o_def2tzvp_hfimport.hdf5 -a1744db1cdca5f629e22b9e215d6c7174b2b7ee9cf04679ff8c23b67b3377e24 h2o_sto3g_adcc_adc0.hdf5 -bac73a67b5ceef5b2aa34435463e30c1a1690c71a475e2be981946b913dfec6c h2o_sto3g_adcc_adc1.hdf5 -c077512f01ef0649dfb4f91e05202f3ef63bbbcb4e92d9d51e93a0613b16d6f0 h2o_sto3g_adcc_adc2.hdf5 -9d04cb83518a9feddafcc2b00e3b99ccf229ad98b4d6d0c07d7c9f0353b2c292 h2o_sto3g_adcc_adc2x.hdf5 -0126f7b097079de20bdecf332cfd27e1c5928b32d4a77b8350f79cfa1518ef3c h2o_sto3g_adcc_adc3.hdf5 -a2d602005114c0c8d784fc019a6f866c330eaf546514bb3a29f634dc74a276f8 h2o_sto3g_adcc_mpdata.hdf5 +3c53822bd319b0b22e423fbbd882a59d28884aa5cfe115e115c3830252f70f64 h2o_sto3g_adcc_adc0.hdf5 +a2e6b48439a10abe616c9e5745abcfef262a9c2a827aad45c7903b4a05ef3d49 h2o_sto3g_adcc_adc1.hdf5 +2b2097ff079804825efa7d411935ceb0526db5c253d21eb156ec3746274eeb85 h2o_sto3g_adcc_adc2.hdf5 +d17260bf4e06620e68568a12770fd6e40335dee4315f18684924ff4816c70742 h2o_sto3g_adcc_adc2x.hdf5 +a604dcf40b56bf5a3ce9c2ec83183d0b90ff59a3e9a8fadf65d536f8bfea0b7f h2o_sto3g_adcc_adc3.hdf5 +40bd1d8253cc66a5e4958bbfe6095bfea3b75c00e32c72bd62f33800ba173995 h2o_sto3g_adcc_mpdata.hdf5 5d7b37bef5edaa0fac0d51d2f65958d7715248b7c8a96c3de06938a47071f20a h2o_sto3g_adcman_adc0.hdf5 e138a793b32073a8d2ca62d60dcb26fce33882be1307da4109e4432b6b44fe0d h2o_sto3g_adcman_adc1.hdf5 40085f475c8122096fd0231cb75cf6febfd04e098048206bc021c027d45c0cce h2o_sto3g_adcman_adc2.hdf5 657b7a5737c7492c9c68e01603ad753a357ba1c7706906c8aa36aa44ac8efcd7 h2o_sto3g_adcman_adc2x.hdf5 e058d5612c0a308aa69fe290d713a5a8c845acda65e0977bded7e633cf18ab01 h2o_sto3g_adcman_adc3.hdf5 b0eb11ca764b81331d8607cb0a423cab95b87ab25cb8a10d4d00acc3d59b57a9 h2o_sto3g_adcman_mpdata.hdf5 -56c0b52d597d2438633226db454631941ac48900c17e8fafa991ddb2d50372f6 h2o_sto3g_hfdata.hdf5 +5f5cdd33502cb0009edaf53c4ba7535f8b5c8f887c626c9c289011f223f4fd54 h2o_sto3g_hfdata.hdf5 029177eab46ba7309496d5fd5b1d428e434f6e50df16870964811f3452d09763 h2o_sto3g_hfimport.hdf5 -7742ae8a5998297250cd47f0caf949459bc00d1e15b573c90860590e5754875c hf_631g_adcc_adc0.hdf5 -d47ebd06f397acd0105106aa59d220d589f381675943999d97cdb4322425e3fc hf_631g_adcc_adc1.hdf5 -07156e35977b0edf40dc696da3d2e709e9eccc64be3be52ce903ff40e35152cd hf_631g_adcc_adc2.hdf5 -5fe10ed9869ebb89c676114e0170f38f5469b4136d1c508be3c29ee38b8c8b94 hf_631g_adcc_adc2x.hdf5 -2a977de9522becbaccb22a667ff7612473fe9654027d1e90187c11ee70e7a63d hf_631g_adcc_adc3.hdf5 -9dd1aff9858c356e83c82f4969d141ff0f9b609a3122a1b9eb8fc5c5a14c0d71 hf_631g_adcc_mpdata.hdf5 +5ed8a5434fdfc87c6ecf527db3f7e2d8bd6648139e7a724ccb1ff37be0f95a09 hf_631g_adcc_adc0.hdf5 +62d5a027377f695e0d66f15fc673973b521218b1c6751ef93a256b89489cdb43 hf_631g_adcc_adc1.hdf5 +384280dfc04711b94a1ba3b076f2bf721d922d9ebe2b5c0b88841c78d571d092 hf_631g_adcc_adc2.hdf5 +f667bb4bbf1ee95167dc26cfcfd7e6ccfc8c7eb964519d72c6ea2ecdf7a69f5c hf_631g_adcc_adc2x.hdf5 +ff785bd4d4192944f49138464e51d96d5b3a90491d3569ca490a6e7b5e335173 hf_631g_adcc_adc3.hdf5 +46edac4607e3589842bec8861229eed53f6e75ecd2093284fa0af7536cad0c72 hf_631g_adcc_mpdata.hdf5 c880c9d9be40b9421337d05c09cfb5b4077f395b44844eafecaa8d13aa2b3017 hf_631g_adcman_adc0.hdf5 aced5b320017354500375e1489b28156284e54e4f94e9361eab5f11ed7aeb307 hf_631g_adcman_adc1.hdf5 5c8acf37f4d36d3af387bc4f1220791a67b84880de4ba9f85a0cc20ba45a005b hf_631g_adcman_adc2.hdf5 9c17e71e5b8425f5c5a2e0c7fea84d7542c9da1201f78f75f84d04f59a439979 hf_631g_adcman_adc2x.hdf5 1f78d12f19c7b0efe7ed38a41138744fb4164f28a07378fe1b0336fb60c0eb8c hf_631g_adcman_adc3.hdf5 191ad40fe1094240646dceb5d57e58b05d2afbf3a66b48d27a78fadedd677474 hf_631g_adcman_mpdata.hdf5 -564bd82343cb141f88fd9e7cfc80d2905c5d62dd7e4907cdda911c0223ba60fc hf_631g_hfdata.hdf5 -346dbf647878abe3f88dbc1eb4ea380d41ed572939dd98e1d1d8a8b8219932a7 r2methyloxirane_sto3g_hfdata.hdf5 +771dade260eed12278f46357e8a06ee84b2faf2540b0cfb7bdda49dbe2576b05 hf_631g_hfdata.hdf5 +08c3b2319fccca2180e21b2dd2bc4790a5001b689131e39f02f8dea28f7a68d6 r2methyloxirane_sto3g_hfdata.hdf5 diff --git a/adcc/tests/data/update_testdata.sh b/adcc/tests/data/update_testdata.sh index 3fe57d1a..67f2c27a 100755 --- a/adcc/tests/data/update_testdata.sh +++ b/adcc/tests/data/update_testdata.sh @@ -4,7 +4,7 @@ trap "exit 1" TERM export SCRIPT_PID=$$ -SOURCE="https://wwwagdreuw.iwr.uni-heidelberg.de/adcc_test_data/0.7.1/" +SOURCE="https://wwwagdreuw.iwr.uni-heidelberg.de/adcc_test_data/0.7.2/" SHAFILE="SHA256SUMS" diff --git a/adcc/tests/hardcoded_test.py b/adcc/tests/hardcoded_test.py index 5aad0ad6..2b2dde2a 100644 --- a/adcc/tests/hardcoded_test.py +++ b/adcc/tests/hardcoded_test.py @@ -48,6 +48,6 @@ def test_methox_sto3g_singlet(self): np.testing.assert_allclose(vlx_result['excitation_energies'], state.excitation_energy, atol=1e-6) np.testing.assert_allclose(vlx_result['rotatory_strengths'], - state.rotatory_strength("origin"), atol=1e-4) + state.rotatory_strength, atol=1e-4) np.testing.assert_allclose(vlx_result['oscillator_strengths'], state.oscillator_strength, atol=1e-4) diff --git a/adcc/tests/properties_test.py b/adcc/tests/properties_test.py index 55d43145..a63a2836 100644 --- a/adcc/tests/properties_test.py +++ b/adcc/tests/properties_test.py @@ -259,13 +259,12 @@ def test_rotatory_strengths(self, system: str, case: str, kind: str, system=system, method=method, case=case, kind=kind, source="adcc" ) - for g_origin in gauge_origins: - res_rots = state.rotatory_strength(g_origin) - ref_tmdm = refdata[f"transition_magnetic_dipole_moments_{g_origin}"] - ref_tdmvel = refdata["transition_dipole_moments_velocity"] - refevals = refdata["eigenvalues"] - n_ref = len(state.excitation_vector) - for i in range(n_ref): - assert state.excitation_energy[i] == refevals[i] - ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) - assert res_rots[i] == pytest.approx(ref_dot / refevals[i]) + res_rots = state.rotatory_strength + ref_tmdm = refdata["transition_magnetic_dipole_moments_origin"] + ref_tdmvel = refdata["transition_dipole_moments_velocity"] + refevals = refdata["eigenvalues"] + n_ref = len(state.excitation_vector) + for i in range(n_ref): + assert state.excitation_energy[i] == refevals[i] + ref_dot = np.dot(ref_tmdm[i], ref_tdmvel[i]) + assert res_rots[i] == pytest.approx(ref_dot / refevals[i]) diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 0d4db2e6..7a01b7aa 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -51,7 +51,7 @@ class HartreeFockSolution_i { //@} /** Determine the gauge origin. */ - virtual const std::array determine_gauge_origin( + virtual const std::array gauge_origin_to_xyz( std::string gauge_origin) const = 0; /** \name Sizes of the data */ diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index ca1eab2c..ec991a19 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -357,9 +357,9 @@ std::vector ReferenceState::nuclear_multipole( return ret; } -const std::array ReferenceState::determine_gauge_origin( +const std::array ReferenceState::gauge_origin_to_xyz( std::string gauge_origin) const { - std::array ret = m_hfsoln_ptr->determine_gauge_origin(gauge_origin); + std::array ret = m_hfsoln_ptr->gauge_origin_to_xyz(gauge_origin); return ret; } diff --git a/libadcc/ReferenceState.hh b/libadcc/ReferenceState.hh index 41ae7beb..aa715480 100644 --- a/libadcc/ReferenceState.hh +++ b/libadcc/ReferenceState.hh @@ -97,7 +97,7 @@ class ReferenceState { 0, 0, 0}) const; /** Determine the gauge origin for nuclear multipoles. */ - const std::array determine_gauge_origin(std::string gauge_origin) const; + const std::array gauge_origin_to_xyz(std::string gauge_origin) const; /** Return the SCF convergence tolerance */ double conv_tol() const { return m_hfsoln_ptr->conv_tol(); } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 6a27e4c4..27cebd21 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -58,15 +58,10 @@ class HartreeFockProvider : public HartreeFockSolution_i { std::copy(ret.data(), ret.data() + size, buffer); } - const std::array determine_gauge_origin( + const std::array gauge_origin_to_xyz( std::string gauge_origin) const override { - const std::array ret = - py::cast>(get_gauge_origin(py::cast(gauge_origin))); - if (ret.size() != 3) { - throw dimension_mismatch("Array size (==" + std::to_string(ret.size()) + - ") needs to be 3."); - } - return ret; + return py::cast>( + transform_gauge_origin_to_xyz(py::cast(gauge_origin))); } void occupation_f(scalar_type* buffer, size_t size) const override { @@ -262,13 +257,13 @@ class HartreeFockProvider : public HartreeFockSolution_i { virtual size_t get_n_bas() const = 0; virtual py::array_t get_nuclear_multipole( - size_t order, py::array_t gauge_origin) const = 0; - virtual py::tuple get_gauge_origin(py::str gauge_origin) const = 0; - virtual real_type get_conv_tol() const = 0; - virtual bool get_restricted() const = 0; - virtual size_t get_spin_multiplicity() const = 0; - virtual real_type get_energy_scf() const = 0; - virtual std::string get_backend() const = 0; + size_t order, py::array_t gauge_origin) const = 0; + virtual py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const = 0; + virtual real_type get_conv_tol() const = 0; + virtual bool get_restricted() const = 0; + virtual size_t get_spin_multiplicity() const = 0; + virtual real_type get_energy_scf() const = 0; + virtual std::string get_backend() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -296,8 +291,8 @@ class PyHartreeFockProvider : public HartreeFockProvider { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order, gauge_origin); } - py::tuple get_gauge_origin(py::str gauge_origin) const override { - PYBIND11_OVERLOAD_PURE(py::tuple, HartreeFockProvider, get_gauge_origin, + py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const override { + PYBIND11_OVERLOAD_PURE(py::tuple, HartreeFockProvider, transform_gauge_origin_to_xyz, gauge_origin); } real_type get_conv_tol() const override { @@ -457,8 +452,10 @@ void export_HartreeFockProvider(py::module& m) { "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " "dipole moment as an array of size 3.") - .def("get_gauge_origin", &HartreeFockProvider::get_gauge_origin, - "Determines the gauge origin.") + .def("transform_gauge_origin_to_xyz", + &HartreeFockProvider::transform_gauge_origin_to_xyz, + "Transforms the gauge origin given string to a string containig the " + "x, y, z Catesian components.") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 7da4358b..0402141d 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -125,9 +125,9 @@ void export_ReferenceState(py::module& m) { std::copy(res.begin(), res.end(), ret.mutable_data()); return res; }) - .def("determine_gauge_origin", + .def("gauge_origin_to_xyz", [](const ReferenceState& ref, std::string gauge_origin) { - auto vec = ref.determine_gauge_origin(gauge_origin); + auto vec = ref.gauge_origin_to_xyz(gauge_origin); py::tuple coords(3); for (size_t i = 0; i < vec.size(); ++i) { coords[i] = vec[i]; // Assign values to the tuple From 08e2af40c05372ee49c37ad5a13c5b1ba5f798ce Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 14 Mar 2025 10:19:20 +0100 Subject: [PATCH 39/42] add #include --- libadcc/HartreeFockSolution_i.hh | 1 + libadcc/tests/HFSolutionMock.hh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 7a01b7aa..562ba25f 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -19,6 +19,7 @@ #pragma once #include "config.hh" +#include #include #include #include diff --git a/libadcc/tests/HFSolutionMock.hh b/libadcc/tests/HFSolutionMock.hh index 8117d26a..6be6a98a 100644 --- a/libadcc/tests/HFSolutionMock.hh +++ b/libadcc/tests/HFSolutionMock.hh @@ -49,11 +49,11 @@ struct HFSolutionMock : public HartreeFockSolution_i { size_t spin_multiplicity() const override { return restricted() ? 1 : 0; } size_t n_bas() const override { return exposed_n_bas; } - void nuclear_multipole(size_t /*order*/, std::vector /*gauge_origin*/, + void nuclear_multipole(size_t /*order*/, std::array /*gauge_origin*/, scalar_type* /*buffer*/, size_t /*size*/) const override { throw not_implemented_error("Not implemented."); } - std::vector determine_gauge_origin( + const std::array gauge_origin_to_xyz( std::string /*gauge_origin*/) const override { throw not_implemented_error("Not implemented."); } From 64aa32332c7190b25d560cc98f062354b88300f5 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 14 Mar 2025 13:11:44 +0100 Subject: [PATCH 40/42] mark_excitation_property with callables --- adcc/ElectronicTransition.py | 71 +++++++++++++++++++------------- adcc/Excitation.py | 8 +++- adcc/tests/ExcitedStates_test.py | 10 +++-- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/adcc/ElectronicTransition.py b/adcc/ElectronicTransition.py index 2fd8bafd..baa996f0 100644 --- a/adcc/ElectronicTransition.py +++ b/adcc/ElectronicTransition.py @@ -180,47 +180,59 @@ def transition_dipole_moment_velocity(self): for tdm in self.transition_dm ]) + @property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_magnetic_dipole_moment(self, gauge_origin=(0.0, 0.0, 0.0)): + def transition_magnetic_dipole_moment(self): """List of transition magnetic dipole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition magnetic dipole moments " "are known to be faulty in some cases.") - mag_dipole_integrals = self.operators.magnetic_dipole(gauge_origin) - return np.array([ - [product_trace(comp, tdm) for comp in mag_dipole_integrals] - for tdm in self.transition_dm - ]) + def g_origin_dep_trans_magdip_moment(gauge_origin="origin"): + mag_dipole_integrals = self.operators.magnetic_dipole(gauge_origin) + return np.array([ + [product_trace(comp, tdm) for comp in mag_dipole_integrals] + for tdm in self.transition_dm + ]) + return g_origin_dep_trans_magdip_moment + + @property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_quadrupole_moment(self, gauge_origin=(0.0, 0.0, 0.0)): + def transition_quadrupole_moment(self): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition quadrupole moments are known to be " "faulty in some cases.") - quadrupole_integrals = self.operators.electric_quadrupole(gauge_origin) - return np.array([[ - [product_trace(quad1, tdm) for quad1 in quad] - for quad in quadrupole_integrals] - for tdm in self.transition_dm - ]) + def g_origin_dep_trans_el_quad_moment(gauge_origin="origin"): + quadrupole_integrals = self.operators.electric_quadrupole(gauge_origin) + return np.array([[ + [product_trace(quad1, tdm) for quad1 in quad] + for quad in quadrupole_integrals] + for tdm in self.transition_dm + ]) + return g_origin_dep_trans_el_quad_moment + + @property @mark_excitation_property() @timed_member_call(timer="_property_timer") - def transition_quadrupole_moment_velocity(self, gauge_origin=(0.0, 0.0, 0.0)): + def transition_quadrupole_moment_velocity(self): """List of transition quadrupole moments of all computed states""" if self.property_method.level == 0: warnings.warn("ADC(0) transition velocity quadrupole moments are known " "to be faulty in some cases.") - quadrupole_integrals = \ - self.operators.electric_quadrupole_velocity(gauge_origin) - return np.array([[ - [product_trace(quad1, tdm) for quad1 in quad] - for quad in quadrupole_integrals] - for tdm in self.transition_dm - ]) + + def g_origin_dep_trans_el_quad_vel_moment(gauge_origin="origin"): + quadrupole_integrals = \ + self.operators.electric_quadrupole_velocity(gauge_origin) + return np.array([[ + [product_trace(quad1, tdm) for quad1 in quad] + for quad in quadrupole_integrals] + for tdm in self.transition_dm + ]) + return g_origin_dep_trans_el_quad_vel_moment @cached_property @mark_excitation_property() @@ -256,15 +268,18 @@ def rotatory_strength(self): self.excitation_energy) ]) + @property @mark_excitation_property() - def rotatory_strength_length(self, gauge_origin=(0.0, 0.0, 0.0)): + def rotatory_strength_length(self): """List of rotatory strengths in length gauge of all computed states""" - return np.array([ - -1.0 * np.dot(tdm, magmom) - for tdm, magmom in zip( - self.transition_dipole_moment, - self.transition_magnetic_dipole_moment(gauge_origin)) - ]) + def g_origin_dep_rot_str_len(gauge_origin="origin"): + return np.array([ + -1.0 * np.dot(tdm, magmom) + for tdm, magmom in zip( + self.transition_dipole_moment, + self.transition_magnetic_dipole_moment(gauge_origin)) + ]) + return g_origin_dep_rot_str_len @property @mark_excitation_property() diff --git a/adcc/Excitation.py b/adcc/Excitation.py index 1419e4f6..20e315c0 100644 --- a/adcc/Excitation.py +++ b/adcc/Excitation.py @@ -72,7 +72,13 @@ def __init__(self, parent_state, index, method): kwargs = getattr(fget, "__excitation_property").copy() def get_parent_property(self, key=key, kwargs=kwargs): - return getattr(self.parent_state, key)[self.index] + if callable(getattr(self.parent_state, key)): + def wrapper(*args, **kwargs): + callback = getattr(self.parent_state, key) + return callback(*args, **kwargs)[self.index] + return wrapper + else: + return getattr(self.parent_state, key)[self.index] setattr(Excitation, key, property(get_parent_property)) diff --git a/adcc/tests/ExcitedStates_test.py b/adcc/tests/ExcitedStates_test.py index ed35ed56..afe5aa70 100644 --- a/adcc/tests/ExcitedStates_test.py +++ b/adcc/tests/ExcitedStates_test.py @@ -52,9 +52,13 @@ def test_excitation_view(self): except NotImplementedError: continue except TypeError: - # gauge origin dependent properties are callables - ref = getattr(state, key)()[i] - res = getattr(exci, key)() + try: + ref = getattr(state, key)("origin")[i] + res = getattr(exci, key)("origin") + except AttributeError: + # electric dipole velocity integrals are not implemented in + # the reference backend + continue if isinstance(ref, OneParticleOperator): assert ref.blocks == res.blocks for b in ref.blocks: From 69fad82655a5b977ddd1152dde56361136011c53 Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Fri, 14 Mar 2025 18:09:45 +0100 Subject: [PATCH 41/42] GitHub comments part 1 --- adcc/ExcitedStates.py | 14 +++++++-- adcc/OperatorIntegrals.py | 29 +++++++++++++++++-- adcc/backends/molsturm.py | 6 ++-- adcc/backends/psi4.py | 20 ++++++------- adcc/backends/pyscf.py | 21 ++++++++++---- adcc/backends/veloxchem.py | 10 +++++-- adcc/tests/ExcitedStates_test.py | 24 ++++++++++++--- adcc/tests/generators/dump_pyscf.py | 2 +- adcc/tests/properties_test.py | 20 ++++++------- libadcc/HartreeFockSolution_i.hh | 1 - libadcc/ReferenceState.cc | 2 +- libadcc/pyiface/export_HartreeFockProvider.cc | 25 ++++++++-------- 12 files changed, 119 insertions(+), 55 deletions(-) diff --git a/adcc/ExcitedStates.py b/adcc/ExcitedStates.py index c31ba927..56c32aed 100644 --- a/adcc/ExcitedStates.py +++ b/adcc/ExcitedStates.py @@ -440,7 +440,7 @@ def describe_amplitudes(self, tolerance=0.01, index_format=None): return ret[:-1] @requires_module("pandas") - def to_dataframe(self): + def to_dataframe(self, gauge_origin="origin"): """ Exports the ExcitedStates object as :class:`pandas.DataFrame`. Atomic units are used for all values. @@ -458,6 +458,12 @@ def to_dataframe(self): except NotImplementedError: # some properties are not available for every backend continue + if callable(d): + try: + d = d(gauge_origin) + except NotImplementedError: + # some properties are not available for every backend + continue if not isinstance(d, np.ndarray): continue if not np.issubdtype(d.dtype, np.number): @@ -467,7 +473,11 @@ def to_dataframe(self): elif d.ndim == 2 and d.shape[1] == 3: for i, p in enumerate(["x", "y", "z"]): data[f"{key}_{p}"] = d[:, i] - elif d.ndim > 2: + elif d.ndim == 3 and d.shape[1:] == (3, 3): + for i, p in enumerate(["x", "y", "z"]): + for j, q in enumerate(["x", "y", "z"]): + data[f"{key}_{p}{q}"] = d[:, i, j] + elif d.ndim > 3: warnings.warn(f"Exporting NumPy array for property {key}" f" with shape {d.shape} not supported.") continue diff --git a/adcc/OperatorIntegrals.py b/adcc/OperatorIntegrals.py index 42b728f0..5f7db298 100644 --- a/adcc/OperatorIntegrals.py +++ b/adcc/OperatorIntegrals.py @@ -138,7 +138,7 @@ def __import_g_origin_dep_dip_like_operator(self, callback, is_symmetric=True): ---------- callback : callable Function that computes the operator in atomic orbitals using - the gauge-origin (str or list) as single argument + the gauge-origin (str or tuple) as single argument is_symmetric : bool, optional if the imported operator is symmetric, by default True """ @@ -185,6 +185,11 @@ def magnetic_dipole(self): in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ + if "magnetic_dipole" not in self.available: + raise NotImplementedError(f"magnetic dipole operator " + "not implemented " + f"in {self.provider_ao.backend} backend.") + callback = self.provider_ao.magnetic_dipole return self.__import_g_origin_dep_dip_like_operator(callback, is_symmetric=False) @@ -199,7 +204,7 @@ def __import_g_origin_dep_quad_like_operator(self, callback, is_symmetric=True): ---------- callback : callable Function that computes the operator in atomic orbitals using - a the gauge-origin (str or list) as single argument + a the gauge-origin (str or tuple) as single argument is_symmetric : bool, optional if the imported operator is symmetric, by default True """ @@ -236,6 +241,11 @@ def electric_quadrupole(self): in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ + if "electric_quadrupole" not in self.available: + raise NotImplementedError(f"electric quadrupole operator " + "not implemented " + f"in {self.provider_ao.backend} backend.") + callback = self.provider_ao.electric_quadrupole return self.__import_g_origin_dep_quad_like_operator(callback, is_symmetric=True) @@ -248,6 +258,11 @@ def electric_quadrupole_traceless(self): in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ + if "electric_quadrupole_traceless" not in self.available: + raise NotImplementedError(f"electric quadrupole traceless " + "operator not implemented " + f"in {self.provider_ao.backend} backend.") + callback = self.provider_ao.electric_quadrupole_traceless return self.__import_g_origin_dep_quad_like_operator(callback, is_symmetric=True) @@ -260,6 +275,11 @@ def electric_quadrupole_velocity(self): in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ + if "electric_quadrupole_velocity" not in self.available: + raise NotImplementedError(f"electric quadrupole velocity " + "operator not implemented " + f"in {self.provider_ao.backend} backend.") + callback = self.provider_ao.electric_quadrupole_velocity return self.__import_g_origin_dep_quad_like_operator(callback, is_symmetric=False) @@ -272,6 +292,11 @@ def diamagnetic_magnetizability(self): in the molecular orbital basis dependent on the selected gauge origin. The default gauge origin is set to (0.0, 0.0, 0.0). """ + if "diamagnetic_magnetizability" not in self.available: + raise NotImplementedError(f"diamagnetic magnetizability " + "operator not implemented " + f"in {self.provider_ao.backend} backend.") + callback = self.provider_ao.diamagnetic_magnetizability return self.__import_g_origin_dep_quad_like_operator(callback, is_symmetric=True) diff --git a/adcc/backends/molsturm.py b/adcc/backends/molsturm.py index 9f9ba8be..9aaec104 100644 --- a/adcc/backends/molsturm.py +++ b/adcc/backends/molsturm.py @@ -21,7 +21,6 @@ ## ## --------------------------------------------------------------------- import numpy as np -import warnings from molsturm.State import State from adcc.DataHfProvider import DataHfProvider @@ -30,9 +29,6 @@ def convert_scf_to_dict(scfres): if not isinstance(scfres, State): raise TypeError("Unsupported type for backends.molsturm.import_scf.") - warnings.warn("Gauge origin selection not available in Molsturm." - "The gauge origin is selected as origin of the " - "Cartesian coordinate system (0.0, 0.0, 0.0).") n_alpha = scfres["n_alpha"] n_beta = scfres["n_beta"] @@ -70,6 +66,8 @@ def convert_scf_to_dict(scfres): data["multipoles"]["nuclear_0"] = int(np.sum(charges)), data["multipoles"]["nuclear_1"] = np.einsum('i,ix->x', charges, coords) else: + import warnings + # We have no information about this, so we can just provide dummies data["multipoles"]["nuclear_0"] = -1 data["multipoles"]["nuclear_1"] = np.zeros(3) diff --git a/adcc/backends/psi4.py b/adcc/backends/psi4.py index 6c6462b7..6d38a3a7 100644 --- a/adcc/backends/psi4.py +++ b/adcc/backends/psi4.py @@ -27,8 +27,6 @@ import psi4 -import warnings - from .EriBuilder import EriBuilder from ..exceptions import InvalidReference from ..ExcitedStates import EnergyCorrection @@ -39,10 +37,6 @@ def __init__(self, wfn): self.wfn = wfn self.backend = "psi4" self.mints = psi4.core.MintsHelper(self.wfn) - warnings.warn("Gauge origin selection not available in " - f"{self.backend}. " - "The gauge origin is selected as origin of the " - "Cartesian coordinate system (0.0, 0.0, 0.0).") @cached_property def electric_dipole(self): @@ -51,8 +45,11 @@ def electric_dipole(self): @property def magnetic_dipole(self): - """-0.5 * sum_i r_i x p_i""" - def gauge_origin_dependent_integrals(gauge_origin): + """ + The imaginary part of the integral is returned. + -0.5 * sum_i r_i x p_i + """ + def g_origin_dep_ints_mag_dip(gauge_origin): # TODO: Gauge origin? if gauge_origin != (0.0, 0.0, 0.0) and gauge_origin != "origin": raise NotImplementedError('Only (0.0, 0.0, 0.0) can be selected as' @@ -61,11 +58,14 @@ def gauge_origin_dependent_integrals(gauge_origin): 0.5 * np.asarray(comp) for comp in self.mints.ao_angular_momentum() ] - return gauge_origin_dependent_integrals + return g_origin_dep_ints_mag_dip @cached_property def electric_dipole_velocity(self): - """-sum_i p_i""" + """ + The imaginary part of the integral is returned. + -sum_i p_i + """ return [-1.0 * np.asarray(comp) for comp in self.mints.ao_nabla()] @property diff --git a/adcc/backends/pyscf.py b/adcc/backends/pyscf.py index 4a43393d..f2a0a72f 100644 --- a/adcc/backends/pyscf.py +++ b/adcc/backends/pyscf.py @@ -47,7 +47,10 @@ def electric_dipole(self): @property def magnetic_dipole(self): - """-0.5 * sum_i r_i x p_i""" + """ + The imaginary part of the integral is returned. + -0.5 * sum_i r_i x p_i + """ def g_origin_dep_ints_mag_dip(gauge_origin="origin"): gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): @@ -58,7 +61,10 @@ def g_origin_dep_ints_mag_dip(gauge_origin="origin"): @cached_property def electric_dipole_velocity(self): - """-sum_i p_i""" + """ + The imaginary part of the integral is returned. + -sum_i p_i + """ with self.scfres.mol.with_common_orig((0.0, 0.0, 0.0)): return list( self.scfres.mol.intor('int1e_ipovlp', comp=3, hermi=2) @@ -77,7 +83,8 @@ def g_origin_dep_ints_el_quad(gauge_origin): @property def electric_quadrupole_traceless(self): """ - -0.5 * sum_i (r_{i, alpha} r_{i, beta} - delta_{alpha, beta} r^2) + -0.5 * sum_i (3 * r_{i, alpha} r_{i, beta} + - delta_{alpha, beta} r_{i}^2) """ def g_origin_dep_ints_el_quad_traceless(gauge_origin): gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) @@ -97,7 +104,8 @@ def g_origin_dep_ints_el_quad_traceless(gauge_origin): @property def electric_quadrupole_velocity(self): """ - -sum_i (r_{i, beta} p_{i, alpha} - delta_{alpha, beta} S + The imaginary part of the integral is returned. + -sum_i (r_{i, beta} p_{i, alpha} - i delta_{alpha, beta} + r_{i, alpha} p_{i, beta}) """ def g_origin_dep_ints_el_quad_vel(gauge_origin): @@ -116,7 +124,10 @@ def g_origin_dep_ints_el_quad_vel(gauge_origin): @property def diamagnetic_magnetizability(self): - """0.25 * sum_i (r_{i, alpha} r_{i, beta} - delta_{alpha, beta} r^2""" + """ + 0.25 * sum_i (r_{i, alpha} r_{i, beta} + - delta_{alpha, beta} r_{i}^2) + """ def g_origin_dep_ints_diamag_magn(gauge_origin): gauge_origin = _transform_gauge_origin_to_xyz(self.scfres, gauge_origin) with self.scfres.mol.with_common_orig(gauge_origin): diff --git a/adcc/backends/veloxchem.py b/adcc/backends/veloxchem.py index 8e73954a..3be09752 100644 --- a/adcc/backends/veloxchem.py +++ b/adcc/backends/veloxchem.py @@ -57,7 +57,10 @@ def electric_dipole(self): @cached_property def magnetic_dipole(self): - """-0.5 * sum_i r_i x p_i""" + """ + The imaginary part of the integral is returned. + -0.5 * sum_i r_i x p_i + """ def g_origin_dep_ints_mag_dip(gauge_origin="origin"): gauge_origin = _transform_gauge_origin_to_xyz(self.scfdrv, gauge_origin) task = self.scfdrv.task @@ -70,7 +73,10 @@ def g_origin_dep_ints_mag_dip(gauge_origin="origin"): @cached_property def electric_dipole_velocity(self): - """-sum_i p_i""" + """ + The imaginary part of the integral is returned. + -sum_i p_i + """ task = self.scfdrv.task linmom_drv = LinearMomentumIntegralsDriver(task.mpi_comm) linmom_mats = linmom_drv.compute(task.molecule, task.ao_basis) diff --git a/adcc/tests/ExcitedStates_test.py b/adcc/tests/ExcitedStates_test.py index afe5aa70..7e28ab6d 100644 --- a/adcc/tests/ExcitedStates_test.py +++ b/adcc/tests/ExcitedStates_test.py @@ -55,7 +55,7 @@ def test_excitation_view(self): try: ref = getattr(state, key)("origin")[i] res = getattr(exci, key)("origin") - except AttributeError: + except NotImplementedError: # electric dipole velocity integrals are not implemented in # the reference backend continue @@ -105,7 +105,7 @@ def test_custom_excitation_energy_corrections(self): state_corrected2.excitation_energy[i]) state_corrected2.describe() - def test_dataframe_export(self): + def test_dataframe_export(self, gauge_origin="origin"): state = testdata_cache.adcc_states( system="h2o_sto3g", method="adc2", kind="singlet", case="gen" ) @@ -116,10 +116,26 @@ def test_dataframe_export(self): assert len(df.columns) for key in df.columns: if hasattr(state, key): - assert_allclose(df[key], getattr(state, key)) + try: + assert_allclose(df[key], getattr(state, key)) + except TypeError: + assert_allclose(df[key], getattr(state, key)(gauge_origin)) elif hasattr(state, key[:-2]): newkey = key[:-2] i = components.index(key[-1]) - assert_allclose(df[key], getattr(state, newkey)[:, i]) + try: + assert_allclose(df[key], getattr(state, newkey)[:, i]) + except TypeError: + assert_allclose(df[key], + getattr(state, newkey)(gauge_origin)[:, i]) + elif hasattr(state, key[:-3]): + newkey = key[:-3] + i = components.index(key[-1]) + j = components.index(key[-2]) + try: + assert_allclose(df[key], getattr(state, newkey)[:, i, j]) + except TypeError: + assert_allclose(df[key], + getattr(state, newkey)(gauge_origin)[:, i, j]) else: raise KeyError(f"Key {key} not found in ExcitedStates object.") diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index ccfce558..ffb01edd 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -192,7 +192,7 @@ def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): data["multipoles"]["nuclear_0"] = int(np.sum(charges)) data["multipoles"]["nuclear_1"] = np.einsum("i,ix->x", charges, coords) data["multipoles"]["nuclear_2_origin"] = \ - calculate_nuclear_quadrupole(charges, coords, [0, 0, 0]) + calculate_nuclear_quadrupole(charges, coords, (0, 0, 0)) data["multipoles"]["nuclear_2_mass_center"] = \ calculate_nuclear_quadrupole(charges, coords, mass_center) data["multipoles"]["nuclear_2_charge_center"] = \ diff --git a/adcc/tests/properties_test.py b/adcc/tests/properties_test.py index a63a2836..f63498c7 100644 --- a/adcc/tests/properties_test.py +++ b/adcc/tests/properties_test.py @@ -197,9 +197,10 @@ def test_magnetic_transition_dipole_moments(self, system: str, case: str, state = testdata_cache._make_mock_adc_state( system=system, method=method, case=case, kind=kind, source="adcc" ) + + n_ref = len(state.excitation_vector) for g_origin in gauge_origins: res_dms = state.transition_magnetic_dipole_moment(g_origin) - n_ref = len(state.excitation_vector) for i in range(n_ref): assert_allclose_signfix( res_dms[i], @@ -207,9 +208,6 @@ def test_magnetic_transition_dipole_moments(self, system: str, case: str, atol=1e-4 ) - res_dms = state.transition_magnetic_dipole_moment - n_ref = len(state.excitation_vector) - # Only adcc reference data available. @pytest.mark.parametrize("system,case,kind", cases) def test_transition_dipole_moments_velocity(self, system: str, case: str, @@ -239,14 +237,16 @@ def test_transition_quadrupole_moments(self, system: str, case: str, state = testdata_cache._make_mock_adc_state( system=system, method=method, case=case, kind=kind, source="adcc" ) + + n_ref = len(state.excitation_vector) for g_origin in gauge_origins: res_dms = state.transition_quadrupole_moment(g_origin) - n_ref = len(state.excitation_vector) - assert_allclose( - res_dms, - refdata[f"transition_quadrupole_moments_{g_origin}"][:n_ref], - atol=1e-4 - ) + for i in range(n_ref): + assert_allclose_signfix( + res_dms[i], + refdata[f"transition_quadrupole_moments_{g_origin}"][i], + atol=1e-4 + ) # Only adcc reference data available. @pytest.mark.parametrize("system,case,kind", cases) diff --git a/libadcc/HartreeFockSolution_i.hh b/libadcc/HartreeFockSolution_i.hh index 562ba25f..0a2f86fd 100644 --- a/libadcc/HartreeFockSolution_i.hh +++ b/libadcc/HartreeFockSolution_i.hh @@ -22,7 +22,6 @@ #include #include #include -#include namespace libadcc { /** Access to a Hartree-Fock solution where all quantities are diff --git a/libadcc/ReferenceState.cc b/libadcc/ReferenceState.cc index ec991a19..a0e3afed 100644 --- a/libadcc/ReferenceState.cc +++ b/libadcc/ReferenceState.cc @@ -359,7 +359,7 @@ std::vector ReferenceState::nuclear_multipole( const std::array ReferenceState::gauge_origin_to_xyz( std::string gauge_origin) const { - std::array ret = m_hfsoln_ptr->gauge_origin_to_xyz(gauge_origin); + const std::array ret = m_hfsoln_ptr->gauge_origin_to_xyz(gauge_origin); return ret; } diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 27cebd21..325850a9 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -255,15 +255,14 @@ class HartreeFockProvider : public HartreeFockSolution_i { // virtual size_t get_n_orbs_alpha() const = 0; virtual size_t get_n_bas() const = 0; - virtual py::array_t get_nuclear_multipole( - size_t order, py::array_t gauge_origin) const = 0; - virtual py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const = 0; - virtual real_type get_conv_tol() const = 0; - virtual bool get_restricted() const = 0; - virtual size_t get_spin_multiplicity() const = 0; - virtual real_type get_energy_scf() const = 0; - virtual std::string get_backend() const = 0; + size_t order, py::tuple gauge_origin) const = 0; + virtual const py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const = 0; + virtual real_type get_conv_tol() const = 0; + virtual bool get_restricted() const = 0; + virtual size_t get_spin_multiplicity() const = 0; + virtual real_type get_energy_scf() const = 0; + virtual std::string get_backend() const = 0; virtual void fill_occupation_f(py::array out) const = 0; virtual void fill_orben_f(py::array out) const = 0; @@ -286,12 +285,12 @@ class PyHartreeFockProvider : public HartreeFockProvider { size_t get_n_bas() const override { PYBIND11_OVERLOAD_PURE(size_t, HartreeFockProvider, get_n_bas, ); } - py::array_t get_nuclear_multipole( - size_t order, py::array_t gauge_origin) const override { + py::array_t get_nuclear_multipole(size_t order, + py::tuple gauge_origin) const override { PYBIND11_OVERLOAD_PURE(py::array_t, HartreeFockProvider, get_nuclear_multipole, order, gauge_origin); } - py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const override { + const py::tuple transform_gauge_origin_to_xyz(py::str gauge_origin) const override { PYBIND11_OVERLOAD_PURE(py::tuple, HartreeFockProvider, transform_gauge_origin_to_xyz, gauge_origin); } @@ -454,8 +453,8 @@ void export_HartreeFockProvider(py::module& m) { "dipole moment as an array of size 3.") .def("transform_gauge_origin_to_xyz", &HartreeFockProvider::transform_gauge_origin_to_xyz, - "Transforms the gauge origin given string to a string containig the " - "x, y, z Catesian components.") + "Transforms a string specifying the gauge origin to a tuple containing " + "the x, y, z, Cartesian components.") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " From 567608c16ab0b366683ea7c4faf720a7655dffad Mon Sep 17 00:00:00 2001 From: Friederike Schneider Date: Tue, 18 Mar 2025 13:23:20 +0100 Subject: [PATCH 42/42] return types libadcc, typo --- adcc/tests/generators/dump_pyscf.py | 1 - libadcc/pyiface/export_HartreeFockProvider.cc | 3 ++- libadcc/pyiface/export_ReferenceState.cc | 11 ++++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/adcc/tests/generators/dump_pyscf.py b/adcc/tests/generators/dump_pyscf.py index ffb01edd..db430d46 100644 --- a/adcc/tests/generators/dump_pyscf.py +++ b/adcc/tests/generators/dump_pyscf.py @@ -225,7 +225,6 @@ def calculate_nuclear_quadrupole(charges, coordinates, gauge_origin): data["magnetic_moments"]["mag_1_origin"] = ( -0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) ) - with scfres.mol.with_common_orig(mass_center): data["magnetic_moments"]["mag_1_mass_center"] = ( -0.5 * scfres.mol.intor('int1e_cg_irxp', comp=3, hermi=2) diff --git a/libadcc/pyiface/export_HartreeFockProvider.cc b/libadcc/pyiface/export_HartreeFockProvider.cc index 325850a9..1935a065 100644 --- a/libadcc/pyiface/export_HartreeFockProvider.cc +++ b/libadcc/pyiface/export_HartreeFockProvider.cc @@ -451,10 +451,11 @@ void export_HartreeFockProvider(py::module& m) { "Returns the nuclear multipole of the requested order. For `0` returns the " "total nuclear charge as an array of size 1, for `1` returns the nuclear " "dipole moment as an array of size 3.") + // .def("transform_gauge_origin_to_xyz", &HartreeFockProvider::transform_gauge_origin_to_xyz, "Transforms a string specifying the gauge origin to a tuple containing " - "the x, y, z, Cartesian components.") + "the x, y, z Cartesian components.") // .def("fill_occupation_f", &HartreeFockProvider::fill_orben_f, "Fill the passed numpy array of size `(2 * nf, )` with the occupation " diff --git a/libadcc/pyiface/export_ReferenceState.cc b/libadcc/pyiface/export_ReferenceState.cc index 0402141d..7cbd059a 100644 --- a/libadcc/pyiface/export_ReferenceState.cc +++ b/libadcc/pyiface/export_ReferenceState.cc @@ -116,23 +116,20 @@ void export_ReferenceState(py::module& m) { py::array_t ret(std::vector{3}); auto res = ref.nuclear_multipole(1); std::copy(res.begin(), res.end(), ret.mutable_data()); - return res; + return ret; }) .def("nuclear_quadrupole", [](const ReferenceState& ref, std::array gauge_origin) { py::array_t ret(std::vector{6}); auto res = ref.nuclear_multipole(2, gauge_origin); std::copy(res.begin(), res.end(), ret.mutable_data()); - return res; + return ret; }) .def("gauge_origin_to_xyz", [](const ReferenceState& ref, std::string gauge_origin) { auto vec = ref.gauge_origin_to_xyz(gauge_origin); - py::tuple coords(3); - for (size_t i = 0; i < vec.size(); ++i) { - coords[i] = vec[i]; // Assign values to the tuple - } - return coords; + // Make sure a tuple is returned + return py::make_tuple(vec[0], vec[1], vec[2]); }) .def_property_readonly("conv_tol", &ReferenceState::conv_tol, "SCF convergence tolererance")