Overview of adcc

ADC-connect – or adcc in short – is a Python-based framework to connect to arbitrary programs and perform calculations based on the algebraic-diagrammatic construction approach (ADC) on top of their existing self-consistent field (SCF) procedures. Four SCF codes can be used with adcc out of the box, namely molsturm, psi4, PySCF, and veloxchem, see 01_Backends.ipynb. Without expressing any particular preference these notebooks will focus on psi4 and pyscf to obtain an SCF reference, but the other codes work very similar.

This notebook and the others in the 0_basics folder will introduce adcc from the practitioners perspective and give an idea of supported features. For a more structured documentation and an API reference, see https://adc-connect.org. In particular for installation instructions see https://adc-connect.org/installation.html. If you cannot be bothered to install adcc just to try these notebooks, just go to https://try.adc-connect.org, which will allow you to play with the notebooks from 0_basics in your browser.

In contrast these notebooks will not put a strong emphasis on the theory and numerical methods behind adcc, save a small introduction in 02_Theory.ipynb. If you have the desire to dig deeper, a review of ADC literature is provided in https://adc-connect.org/theory.html.

A first calculation

Running a simple ADC calculation on top of an HF reference takes pretty much a single line of code. Let's see this in action for computing the excitation spectrum of water in the UV/vis range. First we prepare our reference in psi4 in a cc-pVTZ basis:

In [ ]:
import psi4
import adcc

# By default adcc uses all cores on the machnine. For the binder VMs we need
# to reduce this a little and tell adcc to use two threads (see 03_Tweaks.ipynb)
adcc.set_n_threads(2)

# Run SCF in Psi4 using a cc-pVTZ basis
mol = psi4.geometry("""
    O 0 0 0
    H 0 0 1.795239827225189
    H 1.693194615993441 0 -0.599043184453037
    symmetry c1
    units au
""")
psi4.core.be_quiet()
psi4.set_options({'basis': "cc-pvtz", })
scf_e, wfn = psi4.energy('SCF', return_wfn=True)

Next we perform an ADC(2) calculation on top, asking for 10 singlet states:

In [ ]:
state = adcc.adc2(wfn, n_singlets=10)

We are greeted with a nice convergence table and the result of the calculation is stored in the state. Now let's actually look at it:

In [ ]:
state

The result is again a table, summarising excitation energies, oscillator strengths as well as |v1|^2 and |v2|^2, the norms of the singles and doubles blocks of the ADC vectors (to be explained in more detail in 02_Theory.ipynb).

Note: If you are running adcc from a script, than just print(state) will not give you the same answer, instead you will have to explicitly call the state.describe() function, like so:

In [ ]:
print(state.describe())

With 10 states nicely computed, we want to plot a simulated excitation spectrum, which again can be done in a single function call:

In [ ]:
state.plot_spectrum()

The plotted spectrum shows both the exact excitation energies with dashed lines as well as a Lorenzian envelope in solid. Notice that plot spectrum is pretty flexible and takes typical arguments you can pass to a plt.plot function call from matplotlib (which it uses under the hood), for example:

In [ ]:
from matplotlib import pyplot as plt

state.plot_spectrum(color="green", label="ADC(2)")
plt.legend()

Another important way to get insight into a result is by looking at the dominating amplitudes (dominating orbital excitations) for each of the states:

In [ ]:
print(state.describe_amplitudes())

The default output is pretty detailed. You can get less printed by increasing the tolerance for printing an amplitude. For example:

In [ ]:
print(state.describe_amplitudes(0.1))

Comparing ADC methods

Let us get a feeling for adcc as well as the different ADC methods implemented by comparing the excitation spectra for our water molecule in a single plot. Since visual comparison can certainly not distinguish five digits in the energies we will also lower the convergence tolerance a little to speed up the calculation:

In [ ]:
from matplotlib import pyplot as plt

n_singlets = 10
state_1 = adcc.adc1(wfn, n_singlets=n_singlets, conv_tol=1e-3)           # ADC(1)
print()
state_2x = adcc.adc2x(state.ground_state, n_singlets=n_singlets,         # ADC(2)-x
                      guesses=state.excitation_vectors, conv_tol=1e-3)   # use ADC(2) result as guesses
print()
state_3 = adcc.adc3(state_2x.ground_state, n_singlets=n_singlets,
                    guesses=state_2x.excitation_vectors, conv_tol=1e-3)  # ADC(3)

# Plot all results in one plot
state_1.plot_spectrum(label="ADC(1)")
state.plot_spectrum(label="ADC(2)")
state_2x.plot_spectrum(label="ADC(2)-x")
state_3.plot_spectrum(label="ADC(3)")
plt.legend()

One clearly notices the increase in computational time between the methods of the ADC hierarchy. Between ADC(2) and ADC(2)-x the changes are usually most drastic due to an increase in computational scaling (from $O(N^5)$ to $O(N^6)$).

Getting help

Documentation in adcc is still a little sparser than it should. Still most functions are documented and their docstrings can be easily obtained, for example:

In [ ]:
adcc.adc2?

Last but not least, if you find a bug or need help you are always welcome to file an issue or contact us by mail: [email protected]connect.org.