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

adc-connect / adcc / 28938658741

08 Jul 2026 11:20AM UTC coverage: 75.519% (-0.1%) from 75.632%
28938658741

Pull #210

github

web-flow
Merge ddc3eaec4 into cbd7e3d72
Pull Request #210: ISR(3) properties implementation

1362 of 2122 branches covered (64.18%)

Branch coverage included in aggregate %.

193 of 241 new or added lines in 11 files covered. (80.08%)

1 existing line in 1 file now uncovered.

8673 of 11166 relevant lines covered (77.67%)

238517.59 hits per line

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

91.62
/adcc/workflow.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 sys
2✔
24
import warnings
2✔
25

26
from libadcc import ReferenceState
2✔
27

28
from . import solver
2✔
29
from .guess import (guesses_any, guesses_singlet, guesses_spin_flip,
2✔
30
                    guesses_triplet)
31
from .LazyMp import LazyMp
2✔
32
from .AdcMatrix import AdcMatrix, AdcMatrixlike, AdcExtraTerm
2✔
33
from .AdcMethod import AdcMethod, IsrMethod
2✔
34
from .exceptions import InputError
2✔
35
from .ExcitedStates import ExcitedStates
2✔
36
from .ReferenceState import ReferenceState as adcc_ReferenceState
2✔
37
from .solver.lanczos import lanczos
2✔
38
from .solver.davidson import jacobi_davidson
2✔
39
from .solver.explicit_symmetrisation import (IndexSpinSymmetrisation,
2✔
40
                                             IndexSymmetrisation)
41

42
__all__ = ["run_adc"]
2✔
43

44

45
def run_adc(data_or_matrix, n_states=None, kind="any", conv_tol=None,
2✔
46
            eigensolver=None, guesses=None, n_guesses=None, isr_order=None,
47
            n_guesses_doubles=None, output=sys.stdout, core_orbitals=None,
48
            frozen_core=None, frozen_virtual=None, method=None,
49
            n_singlets=None, n_triplets=None, n_spin_flip=None,
50
            environment=None, **solverargs):
51
    """Run an ADC calculation.
52

53
    Main entry point to run an ADC calculation. The reference to build the ADC
54
    calculation upon is supplied using the `data_or_matrix` argument.
55
    `adcc` is pretty flexible here. Possible options include:
56

57
        a. Hartree-Fock data from a host program, e.g. a molsturm SCF
58
           state, a pyscf SCF object or any class implementing the
59
           :py:class:`adcc.HartreeFockProvider` interface. From this data all
60
           objects mentioned in (b) to (d) will be implicitly created and will
61
           become available in the returned state.
62
        b. A :py:class:`adcc.ReferenceState` object
63
        c. A :py:class:`adcc.LazyMp` object
64
        d. A :py:class:`adcc.AdcMatrix` object
65

66
    Parameters
67
    ----------
68
    data_or_matrix
69
        Data containing the SCF reference
70
    n_states : int, optional
71
    kind : str, optional
72
    n_singlets : int, optional
73
    n_triplets : int, optional
74
    n_spin_flip : int, optional
75
        Specify the number and kind of states to be computed. Possible values
76
        for kind are "singlet", "triplet", "spin_flip" and "any", which is
77
        the default. For unrestricted references clamping spin-pure
78
        singlets/triplets is currently not possible and kind has to remain as
79
        "any". For restricted references `kind="singlets"` or `kind="triplets"`
80
        may be employed to enforce a particular excited states manifold.
81
        Specifying `n_singlets` is equivalent to setting `kind="singlet"` and
82
        `n_states=5`. Similarly for `n_triplets` and `n_spin_flip`.
83
        `n_spin_flip` is only valid for unrestricted references.
84

85
    conv_tol : float, optional
86
        Convergence tolerance to employ in the iterative solver for obtaining
87
        the ADC vectors (default: `1e-6` or 10 * SCF tolerance,
88
        whatever is larger)
89

90
    eigensolver : str, optional
91
        The eigensolver algorithm to use.
92

93
    n_guesses : int, optional
94
        Total number of guesses to compute. By default only guesses derived from
95
        the singles block of the ADC matrix are employed. See
96
        `n_guesses_doubles` for alternatives. If no number is given here
97
        `n_guesses = min(4, 2 * number of excited states to compute)`
98
        or a smaller number if the number of excitation is estimated to be less
99
        than the outcome of above formula.
100

101
    n_guesses_doubles : int, optional
102
        Number of guesses to derive from the doubles block. By default none
103
        unless n_guesses as explicitly given or automatically determined is
104
        larger than the number of singles guesses, which can be possibly found.
105

106
    guesses : list, optional
107
        Provide the guess vectors to be employed for the ADC run. Takes
108
        preference over `n_guesses` and `n_guesses_doubles`, such that these
109
        parameters are ignored.
110

111
    isr_order: int or str, optional
112
        Order of the ISR expansion used for property calculations.
113
        Can be an integer (e.g. 3 for ISR(3)) or a string (e.g. "isr1s")
114

115
    output : stream, optional
116
        Python stream to which output will be written. If `None` all output
117
        is disabled.
118

119
    core_orbitals : int or list or tuple, optional
120
        The orbitals to be put into the core-occupied space. For ways to
121
        define the core orbitals see the description in
122
        :py:class:`adcc.ReferenceState`.
123
        Required if core-valence separation is applied and the input data is
124
        given as data from the host program (i.e. option (a) discussed above)
125

126
    frozen_core : int or list or tuple, optional
127
        The orbitals to select as frozen core orbitals (i.e. inactive occupied
128
        orbitals for both the MP and ADC methods performed). For ways to define
129
        these see the description in :py:class:`adcc.ReferenceState`.
130

131
    frozen_virtual : int or list or tuple, optional
132
        The orbitals to select as frozen virtual orbitals (i.e. inactive
133
        virtuals for both the MP and ADC methods performed). For ways to define
134
        these see the description in :py:class:`adcc.ReferenceState`.
135

136
    environment : bool or list or dict, optional
137
        The keywords to specify how coupling to an environment model,
138
        e.g. PE, is treated. For details see :ref:`environment`.
139

140
    Other parameters
141
    ----------------
142
    max_subspace : int, optional
143
        Maximal subspace size
144
    max_iter : int, optional
145
        Maximal number of iterations
146
    max_subspace_iter : int, optional
147
        Maximum number of iterations for diagonalizing the subspace matrix
148
        during the Davidson solver procedure
149

150
    Returns
151
    -------
152
    ExcitedStates
153
        An :class:`adcc.ExcitedStates` object containing the
154
        :class:`adcc.AdcMatrix`, the :class:`adcc.LazyMp` ground state and the
155
        :class:`adcc.ReferenceState` as well as computed eigenpairs.
156

157
    Examples
158
    --------
159

160
    Run an ADC(2) calculation on top of a `pyscf` RHF reference of
161
    hydrogen flouride.
162

163
    >>> from pyscf import gto, scf
164
    ... mol = gto.mole.M(atom="H 0 0 0; F 0 0 1.1", basis="sto-3g")
165
    ... mf = scf.RHF(mol)
166
    ... mf.conv_tol_grad = 1e-8
167
    ... mf.kernel()
168
    ...
169
    ... state = adcc.run_adc(mf, method="adc2", n_singlets=3)
170

171
    The same thing can also be achieved using the `adcc.adcN` family of
172
    short-hands (see e.g. :py:func:`adcc.adc2`, :py:func:`adcc.cvs_adc2x`):
173

174
    >>> state = adcc.adc2(mf, n_singlets=3)
175

176
    Run a CVS-ADC(3) calculation of O2 with one core-occupied orbital
177

178
    >>> from pyscf import gto, scf
179
    ... mol = gto.mole.M(atom="O 0 0 0; O 0 0 1.2", basis="sto-3g")
180
    ... mf = scf.RHF(mol)
181
    ... mf.conv_tol_grad = 1e-8
182
    ... mf.kernel()
183
    ...
184
    ... state = adcc.cvs_adc3(mf, core_orbitals=1, n_singlets=3)
185
    """
186
    matrix = construct_adcmatrix(
2✔
187
        data_or_matrix, core_orbitals=core_orbitals, frozen_core=frozen_core,
188
        frozen_virtual=frozen_virtual, method=method)
189

190
    n_states, kind = validate_state_parameters(
2✔
191
        matrix.reference_state, n_states=n_states, n_singlets=n_singlets,
192
        n_triplets=n_triplets, n_spin_flip=n_spin_flip, kind=kind)
193

194
    # Determine spin change during excitation. If guesses is not None,
195
    # i.e. user-provided, we cannot guarantee for obtaining a particular
196
    # spin_change in case of a spin_flip calculation.
197
    spin_change = None
2✔
198
    if kind == "spin_flip" and guesses is None:
2✔
199
        spin_change = -1
2✔
200

201
    # Select solver to run
202
    if eigensolver is None:
2!
203
        eigensolver = "davidson"
2✔
204

205
    # Setup environment coupling terms and energy corrections
206
    ret = setup_environment(matrix, environment)
2✔
207
    env_matrix_term, env_energy_corrections = ret
2✔
208
    # add terms to matrix
209
    if env_matrix_term:
2✔
210
        matrix += env_matrix_term
2✔
211

212
    property_method = None
2✔
213
    if isr_order is not None:
2!
NEW
214
        property_method = matrix.method.as_method_at_level(IsrMethod, isr_order)
×
215

216
    diagres = diagonalise_adcmatrix(
2✔
217
        matrix, n_states, kind, guesses=guesses, n_guesses=n_guesses,
218
        n_guesses_doubles=n_guesses_doubles, conv_tol=conv_tol, output=output,
219
        eigensolver=eigensolver, **solverargs)
220

221
    exstates = ExcitedStates(diagres, property_method=property_method)
2✔
222
    exstates.kind = kind
2✔
223
    exstates.spin_change = spin_change
2✔
224

225
    # add environment corrections to excited states
226
    exstates += env_energy_corrections
2✔
227
    return exstates
2✔
228

229

230
#
231
# Individual steps
232
#
233
def construct_adcmatrix(data_or_matrix, core_orbitals=None, frozen_core=None,
2✔
234
                        frozen_virtual=None, method=None):
235
    """
236
    Use the provided data or AdcMatrix object to check consistency of the
237
    other passed parameters and construct the AdcMatrix object representing
238
    the problem to be solved.
239
    Internal function called from run_adc.
240
    """
241
    if not isinstance(data_or_matrix, AdcMatrixlike) and method is None:
2✔
242
        raise InputError("method needs to be explicitly provided unless "
2✔
243
                         "data_or_matrix is an AdcMatrixlike.")
244
    if method is not None and not isinstance(method, AdcMethod):
2✔
245
        try:
2✔
246
            method = AdcMethod(method)
2✔
247
        except ValueError as e:
2✔
248
            raise InputError(str(e))  # In case the method is unknown
2✔
249

250
    if not isinstance(data_or_matrix, (ReferenceState, AdcMatrixlike, LazyMp)):
2✔
251
        if method.is_core_valence_separated and core_orbitals is None:
2✔
252
            raise InputError("If core-valence separation approximation is "
2✔
253
                             "applied then the number of core orbitals needs "
254
                             "to be specified via the parameter "
255
                             "core_orbitals.")
256
        try:
2✔
257
            refstate = adcc_ReferenceState(data_or_matrix,
2✔
258
                                           core_orbitals=core_orbitals,
259
                                           frozen_core=frozen_core,
260
                                           frozen_virtual=frozen_virtual)
261
        except ValueError as e:
2✔
262
            raise InputError(str(e))  # In case of an issue with the spaces
2✔
263
        data_or_matrix = refstate
2✔
264
    elif core_orbitals is not None:
2✔
265
        mospaces = data_or_matrix.mospaces
2✔
266
        warnings.warn("Ignored core_orbitals parameter because data_or_matrix"
2✔
267
                      " is a ReferenceState, a LazyMp or an AdcMatrixlike object "
268
                      " (which has a value of core_orbitals={})."
269
                      "".format(mospaces.n_orbs_alpha("o2")))
270
    elif frozen_core is not None:
2✔
271
        mospaces = data_or_matrix.mospaces
2✔
272
        warnings.warn("Ignored frozen_core parameter because data_or_matrix"
2✔
273
                      " is a ReferenceState, a LazyMp or an AdcMatrixlike object "
274
                      " (which has a value of frozen_core={})."
275
                      "".format(mospaces.n_orbs_alpha("o3")))
276
    elif frozen_virtual is not None:
2✔
277
        mospaces = data_or_matrix.mospaces
2✔
278
        warnings.warn("Ignored frozen_virtual parameter because data_or_matrix"
2✔
279
                      " is a ReferenceState, a LazyMp or an AdcMatrixlike object "
280
                      " (which has a value of frozen_virtual={})."
281
                      "".format(mospaces.n_orbs_alpha("v2")))
282

283
    # Make AdcMatrix (if not done)
284
    if isinstance(data_or_matrix, (ReferenceState, LazyMp)):
2✔
285
        try:
2✔
286
            return AdcMatrix(method, data_or_matrix)
2✔
287
        except ValueError as e:
2✔
288
            # In case of an issue with CVS <-> chosen spaces
289
            raise InputError(str(e))
2✔
290
    elif method is not None and method != data_or_matrix.method:
2✔
291
        warnings.warn("Ignored method parameter because data_or_matrix is an"
2✔
292
                      " AdcMatrixlike, which implicitly sets the method")
293
    if isinstance(data_or_matrix, AdcMatrixlike):
2!
294
        return data_or_matrix
2✔
295

296

297
def validate_state_parameters(reference_state, n_states=None, n_singlets=None,
2✔
298
                              n_triplets=None, n_spin_flip=None, kind="any"):
299
    """
300
    Check the passed state parameters for consistency with itself and with
301
    the passed reference and normalise them. In the end return the number of
302
    states and the corresponding kind parameter selected.
303
    Internal function called from run_adc.
304
    """
305
    if sum(nst is not None for nst in [n_states, n_singlets,
2✔
306
                                       n_triplets, n_spin_flip]) > 1:
307
        raise InputError("One may only specify one out of n_states, "
2✔
308
                         "n_singlets, n_triplets and n_spin_flip")
309

310
    if n_singlets is not None:
2✔
311
        if not reference_state.restricted:
2✔
312
            raise InputError("The n_singlets parameter may only be employed "
2✔
313
                             "for restricted references")
314
        if kind not in ["singlet", "any"]:
2!
315
            raise InputError(f"Kind parameter {kind} not compatible "
×
316
                             "with n_singlets > 0")
317
        kind = "singlet"
2✔
318
        n_states = n_singlets
2✔
319
    if n_triplets is not None:
2✔
320
        if not reference_state.restricted:
2✔
321
            raise InputError("The n_triplets parameter may only be employed "
2✔
322
                             "for restricted references")
323
        if kind not in ["triplet", "any"]:
2✔
324
            raise InputError(f"Kind parameter {kind} not compatible "
2✔
325
                             "with n_triplets > 0")
326
        kind = "triplet"
2✔
327
        n_states = n_triplets
2✔
328
    if n_spin_flip is not None:
2✔
329
        if reference_state.restricted:
2✔
330
            raise InputError("The n_spin_flip parameter may only be employed "
2✔
331
                             "for unrestricted references")
332
        if kind not in ["spin_flip", "any"]:
2✔
333
            raise InputError(f"Kind parameter {kind} not compatible "
2✔
334
                             "with n_spin_flip > 0")
335
        kind = "spin_flip"
2✔
336
        n_states = n_spin_flip
2✔
337

338
    # Check if there are states to be computed
339
    if n_states is None or n_states == 0:
2✔
340
        raise InputError("No excited states to be computed. Specify at least "
2✔
341
                         "one of n_states, n_singlets, n_triplets, "
342
                         "or n_spin_flip")
343
    if n_states < 0:
2✔
344
        raise InputError("n_states needs to be positive")
2✔
345

346
    if kind not in ["any", "spin_flip", "singlet", "triplet"]:
2✔
347
        raise InputError("The kind parameter may only take the values 'any', "
2✔
348
                         "'singlet', 'triplet' or 'spin_flip'")
349
    if kind in ["singlet", "triplet"] and not reference_state.restricted:
2✔
350
        raise InputError("kind==singlet and kind==triplet are only valid for "
2✔
351
                         "ADC calculations in combination with a restricted "
352
                         "ground state.")
353
    if kind in ["spin_flip"] and reference_state.restricted:
2!
354
        raise InputError("kind==spin_flip is only valid for "
×
355
                         "ADC calculations in combination with an unrestricted "
356
                         "ground state.")
357
    return n_states, kind
2✔
358

359

360
def diagonalise_adcmatrix(matrix, n_states, kind, eigensolver="davidson",
2✔
361
                          guesses=None, n_guesses=None, n_guesses_doubles=None,
362
                          conv_tol=None, output=sys.stdout, **solverargs):
363
    """
364
    This function seeks appropriate guesses and afterwards proceeds to
365
    diagonalise the ADC matrix using the specified eigensolver.
366
    Internal function called from run_adc.
367
    """
368
    reference_state = matrix.reference_state
2✔
369

370
    # Determine default ADC convergence tolerance
371
    if conv_tol is None:
2✔
372
        conv_tol = max(10 * reference_state.conv_tol, 1e-6)
2✔
373
    if reference_state.conv_tol > conv_tol:
2✔
374
        raise InputError(
2✔
375
            "Convergence tolerance of SCF results "
376
            f"(== {reference_state.conv_tol}) needs to be lower than ADC "
377
            f"convergence tolerance parameter conv_tol (== {conv_tol})."
378
        )
379

380
    # Determine explicit_symmetrisation
381
    explicit_symmetrisation = IndexSymmetrisation
2✔
382
    if kind in ["singlet", "triplet"]:
2✔
383
        explicit_symmetrisation = IndexSpinSymmetrisation(
2✔
384
            matrix, enforce_spin_kind=kind
385
        )
386

387
    # Set some solver-specific parameters
388
    if eigensolver == "davidson":
2✔
389
        n_guesses_per_state = 2
2✔
390
        callback = setup_solver_printing(
2✔
391
            "Jacobi-Davidson", matrix, kind, solver.davidson.default_print,
392
            output=output)
393
        run_eigensolver = jacobi_davidson
2✔
394
    elif eigensolver == "lanczos":
2!
395
        n_guesses_per_state = 1
×
396
        callback = setup_solver_printing(
×
397
            "Lanczos", matrix, kind, solver.lanczos.default_print,
398
            output=output)
399
        run_eigensolver = lanczos
×
400
    else:
401
        raise InputError(f"Solver {eigensolver} unknown, try 'davidson'.")
2✔
402

403
    # Obtain or check guesses
404
    if guesses is None:
2✔
405
        if n_guesses is None:
2✔
406
            # restrict to the number of available singles guesses if no doubles
407
            # are available
408
            n_guesses = estimate_n_guesses(
2✔
409
                matrix=matrix, n_states=n_states,
410
                singles_only=("pphh" not in matrix.axis_blocks),
411
                n_guesses_per_state=n_guesses_per_state
412
            )
413
        guesses = obtain_guesses_by_inspection(matrix, n_guesses, kind,
2✔
414
                                               n_guesses_doubles)
415
    else:
416
        if len(guesses) < n_states:
2✔
417
            raise InputError("Less guesses provided via guesses (== {}) "
2✔
418
                             "than states to be computed (== {})"
419
                             "".format(len(guesses), n_states))
420
        if n_guesses is not None:
2!
421
            warnings.warn("Ignoring n_guesses parameter, since guesses are "
×
422
                          "explicitly provided.")
423
        if n_guesses_doubles is not None:
2!
424
            warnings.warn("Ignoring n_guesses_doubles parameter, since guesses "
×
425
                          "are explicitly provided.")
426

427
    solverargs.setdefault("which", "SA")
2✔
428
    return run_eigensolver(matrix, guesses, n_ep=n_states, conv_tol=conv_tol,
2✔
429
                           callback=callback,
430
                           explicit_symmetrisation=explicit_symmetrisation,
431
                           **solverargs)
432

433

434
def estimate_n_guesses(matrix, n_states, singles_only=True,
2✔
435
                       n_guesses_per_state=2):
436
    """
437
    Implementation of a basic heuristic to find a good number of guess
438
    vectors to be searched for using the find_guesses function.
439
    Internal function called from run_adc.
440

441
    matrix             ADC matrix
442
    n_states           Number of states to be computed
443
    singles_only       Try to stay withing the singles excitation space
444
                       with the number of guess vectors.
445
    n_guesses_per_state  Number of guesses to search for for each state
446
    """
447
    # Try to use at least 4 or twice the number of states
448
    # to be computed as guesses
449
    n_guesses = n_guesses_per_state * max(2, n_states)
2✔
450

451
    if singles_only:
2✔
452
        # Compute the maximal number of sensible singles block guesses.
453
        # This is roughly the number of occupied alpha orbitals
454
        # times the number of virtual alpha orbitals
455
        #
456
        # If the system is core valence separated, then only the
457
        # core electrons count as "occupied".
458
        mospaces = matrix.mospaces
2✔
459
        sp_occ = "o2" if matrix.is_core_valence_separated else "o1"
2✔
460
        n_virt_a = mospaces.n_orbs_alpha("v1")
2✔
461
        n_occ_a = mospaces.n_orbs_alpha(sp_occ)
2✔
462
        n_guesses = min(n_guesses, n_occ_a * n_virt_a)
2✔
463

464
    # Adjust if we overshoot the maximal number of sensible singles block
465
    # guesses, but make sure we get at least n_states guesses
466
    return max(n_states, n_guesses)
2✔
467

468

469
def obtain_guesses_by_inspection(matrix, n_guesses, kind, n_guesses_doubles=None):
2✔
470
    """
471
    Obtain guesses by inspecting the diagonal matrix elements.
472
    If n_guesses_doubles is not None, this is number is always adhered to.
473
    Otherwise the number of doubles guesses is adjusted to fill up whatever
474
    the singles guesses cannot provide to reach n_guesses.
475
    Internal function called from run_adc.
476
    """
477
    if n_guesses_doubles is not None and n_guesses_doubles > 0 \
2✔
478
       and "pphh" not in matrix.axis_blocks:
479
        raise InputError("n_guesses_doubles > 0 is only sensible if the ADC "
2✔
480
                         "method has a doubles block (i.e. it is *not* ADC(0), "
481
                         "ADC(1) or a variant thereof.")
482

483
    # Determine guess function
484
    guess_function = {"any": guesses_any, "singlet": guesses_singlet,
2✔
485
                      "triplet": guesses_triplet,
486
                      "spin_flip": guesses_spin_flip}[kind]
487

488
    # Determine number of singles guesses to request
489
    n_guess_singles = n_guesses
2✔
490
    if n_guesses_doubles is not None:
2✔
491
        n_guess_singles = n_guesses - n_guesses_doubles
2✔
492
    singles_guesses = guess_function(matrix, n_guess_singles, block="ph")
2✔
493

494
    doubles_guesses = []
2✔
495
    if "pphh" in matrix.axis_blocks:
2✔
496
        # Determine number of doubles guesses to request if not
497
        # explicitly specified
498
        if n_guesses_doubles is None:
2✔
499
            n_guesses_doubles = n_guesses - len(singles_guesses)
2✔
500
        if n_guesses_doubles > 0:
2✔
501
            doubles_guesses = guess_function(matrix, n_guesses_doubles,
2✔
502
                                             block="pphh")
503

504
    total_guesses = singles_guesses + doubles_guesses
2✔
505
    if len(total_guesses) < n_guesses:
2✔
506
        raise InputError("Less guesses found than requested: {} found, "
2✔
507
                         "{} requested".format(len(total_guesses), n_guesses))
508
    return total_guesses
2✔
509

510

511
def setup_solver_printing(solmethod_name, matrix, kind, default_print,
2✔
512
                          output=None):
513
    """
514
    Setup default printing for solvers. Internal function called from run_adc.
515
    """
516
    kstr = " "
2✔
517
    if kind != "any":
2✔
518
        kstr = " " + kind
2✔
519
    method_name = f"{matrix}"
2✔
520
    if hasattr(matrix, "method"):
2!
521
        method_name = matrix.method.name
2✔
522

523
    if output is not None:
2!
524
        print(f"Starting {method_name}{kstr} {solmethod_name} ...",
2✔
525
              file=output)
526

527
        def inner_callback(state, identifier):
2✔
528
            default_print(state, identifier, output)
2✔
529
        return inner_callback
2✔
530

531

532
def setup_environment(matrix, environment):
2✔
533
    """
534
    Setup environment matrix terms and/or energy corrections.
535
    Internal function called from run_adc.
536
    """
537
    valid_envs = ["ptss", "ptlr", "linear_response"]
2✔
538
    hf = matrix.reference_state
2✔
539
    if hf.environment and environment is None:
2✔
540
        raise InputError(
2✔
541
            "Environment found in reference state, but no environment"
542
            " configuration specified. Please select from the following"
543
            f" schemes: {valid_envs} or set to False."
544
        )
545
    elif environment and not hf.environment:
2!
546
        raise InputError(
×
547
            "Environment specified, but no environment"
548
            " was found in reference state."
549
        )
550
    elif not hf.environment:
2✔
551
        environment = {}
2✔
552

553
    convertor = {
2✔
554
        bool: lambda value: {"ptss": True, "ptlr": True} if value else {},
555
        list: lambda value: {k: True for k in value},
556
        str: lambda value: {value: True},
557
        dict: lambda value: value,
558
    }
559
    conversion = convertor.get(type(environment), None)
2✔
560
    if conversion is None:
2!
561
        raise TypeError("Cannot convert environment parameter of type"
×
562
                        f"'{type(environment)}' to dict.")
563
    environment = conversion(environment)
2✔
564

565
    if any(env not in valid_envs for env in environment):
2!
566
        raise InputError("Invalid key specified for environment."
×
567
                         f" Valid keys are '{valid_envs}'.")
568

569
    env_matrix_term = None
2✔
570
    energy_corrections = []
2✔
571

572
    forbidden_combinations = [
2✔
573
        ["ptlr", "linear_response"],
574
    ]
575
    for fbc in forbidden_combinations:
2✔
576
        if all(environment.get(k, False) for k in fbc):
2✔
577
            raise InputError("Combination of environment schemes"
2✔
578
                             f" '{fbc}' not allowed. Check the"
579
                             " adcc documentation for more details.")
580

581
    for pt in ["ptss", "ptlr"]:
2✔
582
        if not environment.get(pt, False):
2✔
583
            continue
2✔
584
        hf_corr = hf.excitation_energy_corrections
2✔
585
        eec_key = f"{hf.environment}_{pt}_correction"
2✔
586
        if eec_key not in hf_corr:
2!
587
            raise ValueError(f"{pt} correction requested, but could not find"
×
588
                             f" the needed function {eec_key} in"
589
                             f" reference state from backend {hf.backend}.")
590
        energy_corrections.append(hf_corr[eec_key])
2✔
591
    if environment.get("linear_response", False):
2✔
592
        from adcc.adc_pp import environment as adcpp_env
2✔
593
        block_key = f"block_ph_ph_0_{hf.environment}"
2✔
594
        if not hasattr(adcpp_env, block_key):
2!
595
            raise NotImplementedError("Matrix term for linear response coupling"
×
596
                                      f" with solvent {hf.environment}"
597
                                      " not implemented.")
598
        block_fun = getattr(adcpp_env, block_key)
2✔
599
        env_matrix_term = AdcExtraTerm(matrix, {'ph_ph': block_fun})
2✔
600

601
    return env_matrix_term, energy_corrections
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc