• 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

91.74
/adcc/misc.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 warnings
2✔
24
import numpy as np
2✔
25
import inspect
2✔
26
from functools import wraps
2✔
27
from packaging.version import parse
2✔
28

29
from .timings import Timer
2✔
30

31

32
def cached_property(f):
2✔
33
    """
34
    Decorator for a cached property. From
35
    https://stackoverflow.com/questions/6428723/python-are-property-fields-being-cached-automatically
36
    """
37
    def get(self):
2✔
38
        try:
2✔
39
            return self._property_cache[f]
2✔
40
        except AttributeError:
2✔
41
            self._property_cache = {}
2✔
42
            x = self._property_cache[f] = f(self)
2✔
43
            return x
2✔
44
        except KeyError:
2✔
45
            x = self._property_cache[f] = f(self)
2✔
46
            return x
2✔
47

48
    get.__doc__ = f.__doc__
2✔
49
    # TODO: find more elegant solution for this
50
    if hasattr(f, "__excitation_property"):
2!
UNCOV
51
        get.__excitation_property = f.__excitation_property
×
52

53
    return property(get)
2✔
54

55

56
def cached_member_function(timer: str = "timer",
2✔
57
                           separate_timings_by_args: bool = True):
58
    """
59
    Decorates a member function being called with
60
    one or more arguments and stores the results
61
    in field `_function_cache` of the class instance.
62
    If the class has a timer (defined under the provided name)
63
    the timings of the (first) call will be measured.
64

65
    Parameters
66
    ----------
67
    timer: str, optional
68
        Name of the member variable the :class:`Timer` instance can be found on the
69
        class instance (default: 'timer'). If the timer is not found no timings
70
        are measured.
71
    separate_timings_by_args: bool, optional
72
        If set the arguments passed to the decorated functions will be included
73
        in the key under which the timings are stored, i.e., a distinct timer task
74
        will be generated for each set of arguments. (default: True)
75
    """
76
    def decorator(function):
2✔
77
        fname = function.__name__
2✔
78

79
        # get the function signature and ensure that we don't have any
80
        # keyword only arguments:
81
        # func(..., *, kwarg=None, ...) or func(..., **kwargs).
82
        # If we want to support them we need to add them in a well defined
83
        # order to the cache key (sort them by name)
84
        func_signature = inspect.signature(function)
2✔
85
        bad_arg_types = (
2✔
86
            inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.VAR_KEYWORD
87
        )
88
        if any(arg.kind in bad_arg_types for arg in
2✔
89
               func_signature.parameters.values()):
90
            raise ValueError("Member functions with keyword only arguments can not "
2✔
91
                             "be wrapped with the cached_member_function.")
92

93
        @wraps(function)
2✔
94
        def wrapper(self, *args, **kwargs):
2✔
95
            # convert all arguments to poisitonal arguments and add default
96
            # arguments for not provided arguments
97
            bound_args = func_signature.bind(self, *args, **kwargs)
2✔
98
            bound_args.apply_defaults()
2✔
99
            assert not bound_args.kwargs
2✔
100
            args = bound_args.args[1:]  # remove self from args
2✔
101

102
            try:
2✔
103
                fun_cache = self._function_cache[fname]
2✔
104
            except AttributeError:
2✔
105
                self._function_cache = {}
2✔
106
                fun_cache = self._function_cache[fname] = {}
2✔
107
            except KeyError:
2✔
108
                fun_cache = self._function_cache[fname] = {}
2✔
109

110
            try:
2✔
111
                return fun_cache[args]
2✔
112
            except KeyError:
2✔
113
                # Record with a timer if possible
114
                instance_timer = getattr(self, timer, None)
2✔
115
                if isinstance(instance_timer, Timer):
2✔
116
                    timer_task = fname
2✔
117
                    if separate_timings_by_args:
2✔
118
                        descr = '_'.join([str(a) for a in args])
2✔
119
                        timer_task += f"/{descr}"
2✔
120

121
                    with instance_timer.record(timer_task):
2✔
122
                        # try to evaluate the result if possible
123
                        result = function(self, *args)
2✔
124
                        if hasattr(result, "evaluate"):
2✔
125
                            result = result.evaluate()
2✔
126
                        fun_cache[args] = result
2✔
127
                else:
128
                    result = function(self, *args)
2✔
129
                    if hasattr(result, "evaluate"):
2✔
130
                        result = result.evaluate()
2✔
131
                    fun_cache[args] = result
2✔
132
                return result
2✔
133
        return wrapper
2✔
134
    return decorator
2✔
135

136

137
def is_module_available(module, min_version=None):
2✔
138
    """Check using importlib if a module is available."""
139
    import importlib
2✔
140

141
    try:
2✔
142
        mod = importlib.import_module(module)
2✔
143
    except ImportError:
2✔
144
        return False
2✔
145

146
    if not min_version:  # No version check
2✔
147
        return True
2✔
148

149
    if not hasattr(mod, "__version__"):
2!
150
        warnings.warn(
×
151
            f"Could not check module {module} minimal version, "
152
            "since __version__ tag not found. Proceeding anyway."
153
        )
154
        return True
×
155

156
    if parse(mod.__version__) < parse(min_version):
2!
157
        warnings.warn(
×
158
            f"Found module {module}, but its version {mod.__version__} is below "
159
            f"the least required (== {min_version}). This module will be ignored."
160
        )
161
        return False
×
162
    return True
2✔
163

164

165
def requires_module(name, min_version=None):
2✔
166
    """
167
    Decorator to check if the module 'name' is available,
168
    throw ModuleNotFoundError on call if not.
169
    """
170
    def inner(function):
2✔
171
        def wrapper(*args, **kwargs):
2✔
172
            fname = function.__name__
2✔
173
            if not is_module_available(name, min_version):
2!
174
                raise ModuleNotFoundError(
×
175
                    f"Function '{fname}' needs module {name}, but it was "
176
                    f"not found. Solve by running 'pip install {name}' or "
177
                    f"'conda install {name}' on your system."
178
                )
179
            return function(*args, **kwargs)
2✔
180
        wrapper.__doc__ = function.__doc__
2✔
181
        return wrapper
2✔
182
    return inner
2✔
183

184

185
def assert_allclose_signfix(actual, desired, atol=0, **kwargs):
2✔
186
    """
187
    Call assert_allclose, but beforehand normalise the sign
188
    of the involved arrays (i.e. the two arrays may differ
189
    up to a sign factor of -1)
190
    """
191
    actual, desired = normalise_sign(actual, desired, atol=atol)
2✔
192
    np.testing.assert_allclose(actual, desired, atol=atol, **kwargs)
2✔
193

194

195
def normalise_sign(*items, atol=0):
2✔
196
    """
197
    Normalise the sign of a list of numpy arrays
198
    """
199
    def sign(item):
2✔
200
        flat = np.ravel(item)
2✔
201
        flat = flat[np.abs(flat) > atol]
2✔
202
        if flat.size == 0:
2✔
203
            return 1
2✔
204
        else:
205
            return np.sign(flat[0])
2✔
206
    desired_sign = sign(items[0])
2✔
207
    return tuple(desired_sign / sign(item) * item for item in items)
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