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

adc-connect / adcc / 25791221606

13 May 2026 09:42AM UTC coverage: 74.928% (-0.02%) from 74.951%
25791221606

Pull #211

github

web-flow
Merge d31b671f6 into f7be28bad
Pull Request #211: Full 2p-ISR(2) difference density

1321 of 2062 branches covered (64.06%)

Branch coverage included in aggregate %.

88 of 107 new or added lines in 8 files covered. (82.24%)

6 existing lines in 1 file now uncovered.

8239 of 10697 relevant lines covered (77.02%)

167932.58 hits per line

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

89.38
/adcc/AdcMethod.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
from collections import Counter
2✔
24
from typing import Any, Optional, Union, TypeVar
2✔
25
from enum import Enum
2✔
26

27

28
T = TypeVar("T", bound="Method")
2✔
29

30

31
class MethodLevel(Enum):
2✔
32
    # numeric levels
33
    ZERO = 0
2✔
34
    ONE = 1
2✔
35
    TWO = 2
2✔
36
    THREE = 3
2✔
37
    FOUR = 4
2✔
38
    FIVE = 5
2✔
39

40
    # special levels
41
    TWO_X = "2x"    # extended 2nd-order ADC: 2p2h-2p2h in 1st order
2✔
42
    # 1st-order ISR: in singles excitation space only (starting from 1-particle
43
    # operators, doubles are required for a consistent first-order description)
44
    ONE_S = "1s"
2✔
45
    # 2nd-order ISR: in doubles excitation space only (starting from 2-particle
46
    # operators, triples are required for a consistent second-order description)
47
    TWO_D = "2d"
2✔
48
    # 3rd-order ISR: in doubles excitation space only (starting from 1-particle
49
    # operators, triples are required for a consistent third-order description)
50
    THREE_D = "3d"
2✔
51

52
    def to_str(self) -> str:
2✔
53
        return str(self.value)
2✔
54

55
    def to_int(self) -> int:
2✔
56
        """
57
        Converts the level to an integer. This also resolves special
58
        levels. For instance, 'TWO_X' resolves as 2.
59
        """
60
        # numerical methods
61
        if isinstance(self.value, int):
2✔
62
            return self.value
2✔
63
        # return base int for special methods
64
        elif isinstance(self.value, str):
2!
65
            return int(self.value[0])
2✔
66
        else:
NEW
67
            raise ValueError(f"Unknown value type {type(self.value)}.")
×
68

69

70
class AdcType(Enum):
2✔
71
    PP = "pp"
2✔
72

73
    def to_str(self) -> str:
2✔
74
        return self.value
2✔
75

76

77
class GroundStateType(Enum):
2✔
78
    MP = "mp"
2✔
79

80
    def to_str(self) -> str:
2✔
NEW
81
        return self.value
×
82

83

84
class Method:
2✔
85
    # this has to be set on the child classes
86
    _method_base_name: Optional[str] = None
2✔
87
    max_level: int = 0
2✔
88
    special_levels: tuple[MethodLevel, ...] = tuple()
2✔
89

90
    def __init__(self, method: str):
2✔
91
        assert self._method_base_name is not None
2✔
92

93
        # validate base method type
94
        split = method.split("-")
2✔
95
        if not split[-1].startswith(self._method_base_name):
2✔
96
            raise ValueError(f"{split[-1]} is not a valid method type")
2✔
97

98
        # validate method level
99
        level = split[-1][len(self._method_base_name):]
2✔
100
        if level.isnumeric():
2✔
101
            self.level: MethodLevel = MethodLevel(int(level))
2✔
102
        else:
103
            self.level: MethodLevel = MethodLevel(level)
2✔
104

105
        split = split[:-1]
2✔
106
        # validate and set the adc_type
107
        try:
2✔
108
            self.adc_type: AdcType = AdcType(split[-1])
2✔
NEW
109
            split = split[:-1]
×
110
        except (ValueError, IndexError):
2✔
111
            self.adc_type: AdcType = AdcType("pp")
2✔
112

113
        self._validate_level(self.level)
2✔
114
        # validate prefixes
115
        valid_prefixes: tuple[str, ...] = ("cvs", "mp")
2✔
116
        if len(split) > len(valid_prefixes):
2!
117
            raise ValueError("Invalid number of method prefixes provided "
×
118
                             f"in {split}.")
119
        if any(pref not in valid_prefixes for pref in split):
2✔
120
            raise ValueError(f"Invalid method prefix in {split}.")
2✔
121
        if any(count != 1 for count in Counter(split).values()):
2✔
122
            raise ValueError(f"Invalid method string {method}. Duplicate "
2✔
123
                             f"prefix detected in {split}.")
124
        # set and remove cvs
125
        self.is_core_valence_separated: bool = "cvs" in split
2✔
126
        if "cvs" in split:
2✔
127
            split.remove("cvs")
2✔
128
        # finally set and validate gs type (the only allowed prefix left
129
        # at this point)
130
        if split:
2✔
131
            self.gs_type: GroundStateType = GroundStateType(split[0])
2✔
132
            split.pop(0)
2✔
133
        else:
134
            self.gs_type: GroundStateType = GroundStateType("mp")
2✔
135
        # at this point all prefixes should have been handled
136
        if split:
2!
NEW
137
            raise ValueError(f"Invalid prefix in {split} detected."
×
138
                             f"Parsed from method string {method}.")
139

140
    def _validate_level(self, level: MethodLevel) -> None:
2✔
141
        if isinstance(level.value, int) and level.value <= self.max_level:
2✔
142
            return
2✔
143

144
        # special cases
145
        if level in self.special_levels:
2✔
146
            return
2✔
147

148
        raise NotImplementedError(f"{self._base_method} is not implemented.")
2✔
149

150
    @property
2✔
151
    def name(self) -> str:
2✔
152
        """The name of the Method as string."""
153
        if self.prefixes:
2✔
154
            return f"{self.prefixes}-{self._base_method}"
2✔
155
        else:
156
            return self._base_method
2✔
157

158
    @property
2✔
159
    def _base_method(self) -> str:
2✔
160
        assert self._method_base_name is not None
2✔
161
        if self.adc_type is AdcType.PP:
2!
162
            return f"{self._method_base_name}{self.level.to_str()}"
2✔
163
        else:
NEW
164
            return (
×
165
                f"{self.adc_type.to_str()}-"
166
                f"{self._method_base_name}{self.level.to_str()}"
167
            )
168

169
    @property
2✔
170
    def prefixes(self) -> str:
2✔
171
        """String containing all prefixes of the method separated by '-'."""
172
        ret = []
2✔
173
        if self.is_core_valence_separated:
2✔
174
            ret.append("cvs")
2✔
175
        if self.gs_type is not GroundStateType.MP:
2!
NEW
176
            ret.append(self.gs_type.to_str())
×
177
        return "-".join(ret)
2✔
178

179
    @property
2✔
180
    def base_method(self: T) -> T:
2✔
181
        """
182
        The base (full) method, i.e. with all approximations such as
183
        CVS stripped off.
184
        """
185
        return self.__class__(self._base_method)
2✔
186

187
    def at_level(self: T, newlevel: Union[int, str]) -> T:
2✔
188
        """
189
        Return an equivalent method, where only the level is changed
190
        (e.g. calling this on a CVS method returns a CVS method)
191
        """
192
        assert self._method_base_name is not None
2✔
193
        if self.prefixes:
2✔
194
            return self.__class__(
2✔
195
                f"{self.prefixes}-{self._method_base_name}{newlevel}"
196
            )
197
        else:
198
            return self.__class__(
2✔
199
                f"{self._method_base_name}{newlevel}"
200
            )
201

202
    def as_method(self, method_cls: type[T]) -> T:
2✔
203
        """
204
        Return a equivalent Method with the method base name replaced
205
        by the provided name.
206
        """
207
        assert self._method_base_name is not None
2✔
208
        assert method_cls._method_base_name is not None
2✔
209
        return method_cls(
2✔
210
            self.name.replace(self._method_base_name, method_cls._method_base_name)
211
        )
212

213
    def __eq__(self, other: Any) -> bool:
2✔
214
        if not isinstance(other, Method):
2!
NEW
215
            return NotImplemented
×
216
        return self.name == other.name
2✔
217

218
    def __ne__(self, other: Any):
2✔
219
        if not isinstance(other, Method):
2!
NEW
220
            return NotImplemented
×
221
        return self.name != other.name
2✔
222

223
    def __repr__(self) -> str:
2✔
NEW
224
        return f"Method(name={self.name})"
×
225

226

227
class AdcMethod(Method):
2✔
228
    _method_base_name = "adc"
2✔
229
    max_level = 3
2✔
230
    special_levels = (MethodLevel.TWO_X,)
2✔
231

232

233
class IsrMethod(Method):
2✔
234
    _method_base_name = "isr"
2✔
235
    max_level = 2
2✔
236
    special_levels = (MethodLevel.ONE_S, MethodLevel.TWO_D)
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