• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adc-connect / adcc / 16384966787

03 Jul 2025 11:29AM UTC coverage: 73.883% (+0.3%) from 73.602%
16384966787

push

github

web-flow
Restructure ExcitedStates etc for IP/EA Integration (#195)

* allow timer customization in cached_member_function

* add option to exclude the args in the timer task

* timer description if nothing was recorded

* basic restructuring of ElectronicTransition, ExcitedStates and State2States

* basic restructuring of Excitation

* add _module member variable to dispatch to the corresponding working equations

* basic restructuring for excited state properties

* enable excitation for s2s

* port dataframe export

* add state_dm

* port the remaining properties - tests passing

* * cache the MO integrals instead of the AO integrals
* remove unnecessary property decorated returns of callbacks
* implement available integrals directly in the backends

* raise Exception directly in the backends for PE and PCM

* add to_qcvars back in

* split plot_spectrum in common function on base class and adc type dependent function on child classes

* invert order of unit conversion and broadening for plot_spectrum

* add input options for lower and upper bounds of the broadened spectrum

* remove broadening min/max and allow variable width_units

* refactor the ExcitedStates.describe method

* determine the column width automatically

* move describe_amplitudes to ElectronicStates and adapt for IP/EA

* store operators in tuple on IsrMatrix

* make flake happy and remove the cache for now since it is not used anyway

* reintroduce the cache for available backends and cache them lazily

* explicitly install setupstools

* enable libtensorlight download for macos arm64

* naively update CI to macos-latest

* install llvm and use arm64 compilers

* explicitly only build for the current platform

* only set architecture for macos

* generic block_orders for secular and isr matrix

* move init complete back to secular and isr matrix

* raise more explicit exception for not implemented methods

* patch coverage not required for status check

* add token for github api request... (continued)

1176 of 1884 branches covered (62.42%)

Branch coverage included in aggregate %.

700 of 965 new or added lines in 21 files covered. (72.54%)

6 existing lines in 6 files now uncovered.

7376 of 9691 relevant lines covered (76.11%)

175662.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.25
/adcc/OperatorIntegrals.py
1
#!/usr/bin/env python3
2
## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab
3
## ---------------------------------------------------------------------
4
##
5
## Copyright (C) 2018 by the adcc authors
6
##
7
## This file is part of adcc.
8
##
9
## adcc is free software: you can redistribute it and/or modify
10
## it under the terms of the GNU General Public License as published
11
## by the Free Software Foundation, either version 3 of the License, or
12
## (at your option) any later version.
13
##
14
## adcc is distributed in the hope that it will be useful,
15
## but WITHOUT ANY WARRANTY; without even the implied warranty of
16
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
## GNU General Public License for more details.
18
##
19
## You should have received a copy of the GNU General Public License
20
## along with adcc. If not, see <http://www.gnu.org/licenses/>.
21
##
22
## ---------------------------------------------------------------------
23
import numpy as np
2✔
24

25
from .misc import cached_property, cached_member_function
2✔
26
from .Tensor import Tensor
2✔
27
from .timings import Timer, timed_member_call
2✔
28
from .OneParticleOperator import OneParticleOperator
2✔
29

30
import libadcc
2✔
31

32

33
def transform_operator_ao2mo(tensor_bb, tensor_ff, coefficients,
2✔
34
                             conv_tol=1e-14):
35
    """Take a block-diagonal tensor in the atomic orbital basis
36
    and transform it into the molecular orbital basis in the
37
    convention used by adcc.
38

39
    Parameters
40
    ----------
41
    tensor_bb : Tensor
42
        Block-diagonal tensor in the atomic orbital basis
43
    tensor_ff : Tensor
44
        Output tensor with the symmetry set-up to contain
45
        the operator in the molecular orbital representation
46
    coefficients : callable
47
        Function providing coefficient blocks
48
    conv_tol : float, optional
49
        SCF convergence tolerance, by default 1e-14
50
    """
51
    for blk in tensor_ff.blocks:
2✔
52
        assert len(blk) == 4
2✔
53
        cleft = coefficients(blk[:2] + "b")
2✔
54
        cright = coefficients(blk[2:] + "b")
2✔
55
        temp = cleft @ tensor_bb @ cright.transpose()
2✔
56

57
        # TODO: once the permutational symmetry is correct:
58
        # tensor_ff.set_block(blk, tensor_ff)
59
        tensor_ff[blk].set_from_ndarray(temp.to_ndarray(), conv_tol)
2✔
60

61

62
def replicate_ao_block(mospaces, tensor, is_symmetric=True):
2✔
63
    """
64
    transform_operator_ao2mo requires the operator in AO to be
65
    replicated in a block-diagonal fashion (i.e. like [A 0
66
                                                       0 A].
67
    This is achieved using this function.
68
    """
69
    sym = libadcc.make_symmetry_operator_basis(
2✔
70
        mospaces, tensor.shape[0], is_symmetric
71
    )
72
    result = Tensor(sym)
2✔
73

74
    zerobk = np.zeros_like(tensor)
2✔
75
    result.set_from_ndarray(np.block([
2✔
76
        [tensor, zerobk],
77
        [zerobk, tensor],
78
    ]), 1e-14)
79
    return result
2✔
80

81

82
class OperatorIntegrals:
2✔
83
    def __init__(self, provider, mospaces, coefficients, conv_tol):
2✔
84
        self._provider_ao = provider
2✔
85
        self.mospaces = mospaces
2✔
86
        self._coefficients = coefficients
2✔
87
        self._conv_tol = conv_tol
2✔
88
        self._import_timer = Timer()
2✔
89

90
    @property
2✔
91
    def provider_ao(self):
2✔
92
        """
93
        The data structure which provides the integral data in the
94
        atomic orbital basis from the backend.
95
        """
96
        return self._provider_ao
2✔
97

98
    @property
2✔
99
    def available(self) -> tuple[str, ...]:
2✔
100
        """Which integrals are available in the underlying backend"""
101
        return self.provider_ao.available
2✔
102

103
    def _import_dipole_like_operator(self, integral: str, is_symmetric: bool = True
2✔
104
                                     ) -> tuple[OneParticleOperator, ...]:
105
        if integral not in self.available:
2!
106
            raise NotImplementedError(f"{integral.replace('_', ' ')} operator "
×
107
                                      "not implemented "
108
                                      f"in {self.provider_ao.backend} backend.")
109

110
        ao_operator = getattr(self.provider_ao, integral)
2✔
111
        assert len(ao_operator) == 3  # has to have a x, y and z component
2✔
112

113
        dipoles = []
2✔
114
        for comp in range(3):  # [x, y, z]
2✔
115
            dip_bb = replicate_ao_block(self.mospaces, ao_operator[comp],
2✔
116
                                        is_symmetric=is_symmetric)
117
            dip_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric)
2✔
118
            transform_operator_ao2mo(dip_bb, dip_ff, self._coefficients,
2✔
119
                                     self._conv_tol)
120
            dipoles.append(dip_ff)
2✔
121
        return tuple(dipoles)
2✔
122

123
    @cached_property
2✔
124
    @timed_member_call("_import_timer")
2✔
125
    def electric_dipole(self) -> tuple[OneParticleOperator, ...]:
2✔
126
        """Return the electric dipole integrals in the molecular orbital basis."""
127
        return self._import_dipole_like_operator("electric_dipole",
2✔
128
                                                 is_symmetric=True)
129

130
    @cached_property
2✔
131
    @timed_member_call("_import_timer")
2✔
132
    def electric_dipole_velocity(self) -> tuple[OneParticleOperator, ...]:
2✔
133
        """
134
        Return the electric dipole integrals (in the velocity gauge)
135
        in the molecular orbital basis.
136
        """
137
        return self._import_dipole_like_operator("electric_dipole_velocity",
2✔
138
                                                 is_symmetric=False)
139

140
    def _import_g_origin_dep_dip_like_operator(self, integral: str,
2✔
141
                                               gauge_origin="origin",
142
                                               is_symmetric: bool = True
143
                                               ) -> tuple[OneParticleOperator, ...]:
144
        """
145
        Imports the operator and transforms it to the molecular orbital basis.
146

147
        Parameters
148
        ----------
149
        integral : str
150
            The dipole like gauge dependent integral to import: an integral
151
            that consists of 3 components (x, y, z) and whose AO import function
152
            takes the gauge origin as argument.
153
        gauge_origin: str or tuple[str]
154
            The gauge origin used for the generation of the AO integrals.
155
        is_symmetric : bool, optional
156
            If the imported operator is symmetric, by default True
157
        """
158
        if integral not in self.available:
2!
NEW
159
            raise NotImplementedError(f"{integral} operator is not implemented "
×
160
                                      f"in {self.provider_ao.backend} backend.")
161

162
        ao_operator = getattr(self.provider_ao, integral)(gauge_origin)
2✔
163
        assert len(ao_operator) == 3  # has to have a x, y and z component
2✔
164

165
        dipoles = []
2✔
166
        for comp in range(3):  # [x, y, z]
2✔
167
            dip_bb = replicate_ao_block(self.mospaces, ao_operator[comp],
2✔
168
                                        is_symmetric=is_symmetric)
169
            dip_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric)
2✔
170
            transform_operator_ao2mo(dip_bb, dip_ff, self._coefficients,
2✔
171
                                     self._conv_tol)
172
            dipoles.append(dip_ff)
2✔
173
        return tuple(dipoles)
2✔
174

175
    # separate the timings, so one can easily see in the timings how many different
176
    # gauge_origins were used throughout the calculation
177
    @cached_member_function(timer="_import_timer", separate_timings_by_args=True)
2✔
178
    def magnetic_dipole(self, gauge_origin="origin"
2✔
179
                        ) -> tuple[OneParticleOperator, ...]:
180
        """
181
        Returns the magnetic dipole intergrals
182
        in the molecular orbital basis dependent on the selected gauge origin.
183
        The default gauge origin is set to (0.0, 0.0, 0.0) (= 'origin').
184
        """
185
        return self._import_g_origin_dep_dip_like_operator(
2✔
186
            integral="magnetic_dipole", gauge_origin=gauge_origin,
187
            is_symmetric=False
188
        )
189

190
    def _import_g_origin_dep_quad_like_operator(self, integral: str,
2✔
191
                                                gauge_origin="origin",
192
                                                is_symmetric: bool = True
193
                                                ) -> tuple[tuple[OneParticleOperator, ...], ...]:  # noqa E501
194
        """
195
        Imports the operator and transforms it to the molecular orbital basis.
196

197
        Parameters
198
        ----------
199
        integral : str
200
            The quadrupole like gauge dependent integral to import: an integral
201
            that consists of 9 components (xx, xy, xz, ... zz)
202
            and whose AO import function takes the gauge origin as single argument.
203
        gauge_origin: str or tuple[str]
204
            The gauge origin used for the generation of the AO integrals.
205
        is_symmetric : bool, optional
206
            if the imported operator is symmetric, by default True
207
        """
208
        if integral not in self.available:
2✔
209
            raise NotImplementedError(f"{integral} operator is not implemented "
2✔
210
                                      f"in {self.provider_ao.backend} backend.")
211

212
        ao_operator = getattr(self.provider_ao, integral)(gauge_origin)
2✔
213
        assert len(ao_operator) == 9
2✔
214

215
        flattened = []
2✔
216
        for comp in range(9):  # [xx, xy, xz, yx, yy, yz, zx, zy, zz]
2✔
217
            quad_bb = replicate_ao_block(self.mospaces, ao_operator[comp],
2✔
218
                                         is_symmetric=is_symmetric)
219
            quad_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric)
2✔
220
            transform_operator_ao2mo(quad_bb, quad_ff, self._coefficients,
2✔
221
                                     self._conv_tol)
222
            flattened.append(quad_ff)
2✔
223
        return (tuple(flattened[:3]), tuple(flattened[3:6]), tuple(flattened[6:]))
2✔
224

225
    @cached_member_function(timer="_import_timer", separate_timings_by_args=True)
2✔
226
    def electric_quadrupole(self, gauge_origin="origin"
2✔
227
                            ) -> tuple[tuple[OneParticleOperator, ...], ...]:
228
        """
229
        Returns the electric quadrupole integrals
230
        in the molecular orbital basis dependent on the selected gauge origin.
231
        The default gauge origin is set to (0.0, 0.0, 0.0) (= 'origin').
232
        """
233
        return self._import_g_origin_dep_quad_like_operator(
2✔
234
            integral="electric_quadrupole", gauge_origin=gauge_origin,
235
            is_symmetric=True
236
        )
237

238
    @cached_member_function(timer="_import_timer", separate_timings_by_args=True)
2✔
239
    def electric_quadrupole_traceless(self, gauge_origin="origin"
2✔
240
                                      ) -> tuple[tuple[OneParticleOperator, ...], ...]:  # noqa E501
241
        """
242
        Returns the traceless electric quadrupole integrals
243
        in the molecular orbital basis dependent on the selected gauge origin.
244
        The default gauge origin is set to (0.0, 0.0, 0.0) (= 'origin').
245
        """
NEW
246
        return self._import_g_origin_dep_quad_like_operator(
×
247
            integral="electric_quadrupole_traceless", gauge_origin=gauge_origin,
248
            is_symmetric=True
249
        )
250

251
    @cached_member_function(timer="_import_timer", separate_timings_by_args=True)
2✔
252
    def electric_quadrupole_velocity(self, gauge_origin="origin"
2✔
253
                                     ) -> tuple[tuple[OneParticleOperator, ...], ...]:  # noqa E501
254
        """
255
        Returns the electric quadrupole integrals in velocity gauge
256
        in the molecular orbital basis dependent on the selected gauge origin.
257
        The default gauge origin is set to (0.0, 0.0, 0.0) (= 'origin').
258
        """
259
        return self._import_g_origin_dep_quad_like_operator(
2✔
260
            integral="electric_quadrupole_velocity", gauge_origin=gauge_origin,
261
            is_symmetric=False
262
        )
263

264
    @cached_member_function(timer="_import_timer", separate_timings_by_args=True)
2✔
265
    def diamagnetic_magnetizability(self, gauge_origin="origin"
2✔
266
                                    ) -> tuple[tuple[OneParticleOperator, ...], ...]:  # noqa E501
267
        """
268
        Returns the diamagnetic magnetizability integrals
269
        in the molecular orbital basis dependent on the selected gauge origin.
270
        The default gauge origin is set to (0.0, 0.0, 0.0) (= 'origin').
271
        """
NEW
272
        return self._import_g_origin_dep_quad_like_operator(
×
273
            integral="diamagnetic_magnetizability", gauge_origin=gauge_origin,
274
            is_symmetric=True
275
        )
276

277
    def _import_density_dependent_operator(self, operator: str,
2✔
278
                                           density_mo: OneParticleOperator,
279
                                           is_symmetric: bool = True
280
                                           ) -> OneParticleOperator:
281
        """
282
        Import the density-dependent operator and transform it to the
283
        molecular orbital basis.
284

285
        Parameters
286
        ----------
287
        integral : str
288
            The density-dependent operator to import: an operator
289
            whose AO import function takes a density matrix as single argument.
290
        density_mo: OneParticleOperator
291
            The density in the MO basis for which to compute the operator.
292
        is_symmetric : bool, optional
293
            if the imported operator is symmetric, by default True
294
        """
295
        dm_ao = sum(density_mo.to_ao_basis())
2✔
296
        v_ao = getattr(self.provider_ao, operator)(dm_ao)
2✔
297
        v_bb = replicate_ao_block(
2✔
298
            self.mospaces, v_ao, is_symmetric=is_symmetric
299
        )
300
        v_ff = OneParticleOperator(self.mospaces, is_symmetric=is_symmetric)
2✔
301
        transform_operator_ao2mo(
2✔
302
            v_bb, v_ff, self._coefficients, self._conv_tol
303
        )
304
        return v_ff
2✔
305

306
    def pe_induction_elec(self,
2✔
307
                          density_mo: OneParticleOperator) -> OneParticleOperator:
308
        """
309
        Returns the (density-dependent) PE electronic induction operator in the
310
        molecular orbital basis.
311
        """
312
        if "pe_induction_elec" not in self.available:
2!
313
            raise NotImplementedError("PE electronic induction operator "
×
314
                                      "not implemented "
315
                                      f"in {self.provider_ao.backend} backend.")
316
        return self._import_density_dependent_operator(
2✔
317
            operator="pe_induction_elec", density_mo=density_mo, is_symmetric=True
318
        )
319

320
    def pcm_potential_elec(self,
2✔
321
                           density_mo: OneParticleOperator) -> OneParticleOperator:
322
        """
323
        Returns the (density-dependent) electronic PCM potential operator in the
324
        molecular orbital basis
325
        """
326
        if "pcm_potential_elec" not in self.available:
2!
327
            raise NotImplementedError("Electronic PCM potential operator "
×
328
                                      "not implemented "
329
                                      f"in {self.provider_ao.backend} backend.")
330
        return self._import_density_dependent_operator(
2✔
331
            operator="pcm_potential_elec", density_mo=density_mo, is_symmetric=True
332
        )
333

334
    @property
2✔
335
    def timer(self):
2✔
336
        ret = Timer()
2✔
337
        ret.attach(self._import_timer, subtree="import")
2✔
338
        return ret
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc