Automatic derivation of CCSD theory

This notebook serves as an example of interactive usage of drudge for complex symbolic manipulations in Jupyter notebooks. Here we can see how the classical CCSD theory can be derived automatically.

Preparatory work

First, we need to set up the Spark environment. Here we just use parallelization on the local machine.


In [1]:
from pyspark import SparkContext
ctx = SparkContext('local[*]', 'ccsd')

Or we can also use the dummy spark to emulate the Spark environment in a purely serial way. Note that we need just one Spark context. These two cells should not be both evaluated.


In [ ]:
from dummy_spark import SparkContext
ctx = SparkContext()

With the Spark context, we can construct the drudge specific for this problem. Then we can define some names that is going to be used frequently.


In [2]:
from sympy import *
from drudge import *

dr = PartHoleDrudge(ctx)
dr.full_simplify = False
p = dr.names

c_ = p.c_
c_dag = p.c_dag
a, b = p.V_dumms[:2]
i, j = p.O_dumms[:2]

Cluster excitation operator

Here, we by using the Einstein summation convention tensor creator, we can just define the cluster operator in a way very similar to how we would writen them down on paper.


In [3]:
t = IndexedBase('t')

clusters = dr.einst(
    t[a, i] * c_dag[a] * c_[i] +
    t[a, b, i, j] * c_dag[a] * c_dag[b] * c_[j] * c_[i] / 4
)

We can have a peek at the cluster operator.


In [4]:
clusters.display()


Out[4]:
$$\sum_{i \in O} \sum_{a \in V} t_{a,i} c^{\dagger}_{a} c^{}_{i} + \sum_{i \in O} \sum_{j \in O} \sum_{a \in V} \sum_{b \in V} \frac{1}{4} t_{a,b,i,j} c^{\dagger}_{a} c^{\dagger}_{b} c^{}_{j} c^{}_{i}$$

Now we need tell the system about the symmetry on $t^2$, so that it can be used in simplification.


In [5]:
dr.set_dbbar_base(t, 2)

Similarity transform of the Hamiltonian

Here we can use a loop to nest the commutation conveniently. And IPython magic can be used to time the operation. Note that after the simplification, we explicitly redistribute the terms in the transformed Hamiltonian for better parallel performance in later operations. Note that drudge does not automatically cache the result of tensor computations. The cache method should be called explicitly when a tensor is going to be used multiple times.


In [6]:
%%time

curr = dr.ham
h_bar = dr.ham
for order in range(0, 4):
    curr = (curr | clusters).simplify() / (order + 1)
    curr.cache()
    h_bar += curr
h_bar.repartition(cache=True)


CPU times: user 348 ms, sys: 50.3 ms, total: 398 ms
Wall time: 30.4 s

The transformed Hamiltonian can be very complex. Instead of reading its terms, we can just have a peek by get a count of the number of terms it contains.


In [7]:
h_bar.n_terms


Out[7]:
183

Working equation derivation

With the similarity transformed Hamiltonian, we are now ready to derive the actual working equations. First, the energy equation can be derived by taking the vacuum expectation value of the transformed Hamiltonian.


In [8]:
en_eqn = h_bar.eval_fermi_vev().simplify()

We can have a look at its contents to see if it is what we would expect.


In [9]:
en_eqn.display()


Out[9]:
$$\sum_{i \in O} \sum_{a \in V} f_{i,a} t_{a,i} + \sum_{i \in O} \sum_{j \in O} \sum_{a \in V} \sum_{b \in V} \frac{1}{4} t_{a,b,i,j} u_{i,j,a,b} + \sum_{i \in O} \sum_{j \in O} \sum_{a \in V} \sum_{b \in V} \frac{1}{2} t_{a,i} t_{b,j} u_{i,j,a,b} $$

Next, we can create a projector to derive the working equation for the singles amplitude.


In [10]:
proj = c_dag[i] * c_[a]
t1_eqn = (proj * h_bar).eval_fermi_vev().simplify()

In the same way, we can display its content.


In [11]:
t1_eqn.display()


Out[11]:
$$\sum_{j \in O} \sum_{b \in V} \sum_{c \in V} \frac{1}{2} t_{b,c,i,j} u_{a,j,b,c} + \sum_{j \in O} \sum_{b \in V} \sum_{c \in V} t_{b,i} t_{c,j} u_{a,j,b,c} + \sum_{j \in O} \sum_{b \in V} f_{j,b} t_{a,b,i,j} + \sum_{j \in O} \sum_{b \in V} t_{b,j} u_{a,j,i,b} - \sum_{j \in O} \sum_{b \in V} f_{j,b} t_{a,j} t_{b,i} - \sum_{j \in O} f_{j,i} t_{a,j} + \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \sum_{c \in V} t_{c,k} t_{a,b,i,j} u_{j,k,b,c} + \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \sum_{c \in V} \frac{1}{2} t_{a,k} t_{b,c,i,j} u_{j,k,b,c} + \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \sum_{c \in V} \frac{1}{2} t_{c,i} t_{a,b,j,k} u_{j,k,b,c} - \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \sum_{c \in V} t_{a,j} t_{b,i} t_{c,k} u_{j,k,b,c} + \sum_{b \in V} f_{a,b} t_{b,i} + f_{a,i} - \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \frac{1}{2} t_{a,b,j,k} u_{j,k,i,b} - \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} t_{a,j} t_{b,k} u_{j,k,i,b} $$

The working equation for the doubles amplitude can be done in the same way, just it can be slower.


In [12]:
%%time

proj = c_dag[i] * c_dag[j] * c_[b] * c_[a]
t2_eqn = (proj * h_bar).eval_fermi_vev().simplify()


CPU times: user 103 ms, sys: 13.6 ms, total: 116 ms
Wall time: 8.45 s

Since the equation can be slightly complex, we can vaguely sort the terms in increasing complexity before display them.


In [13]:
t2_eqn = t2_eqn.sort()
t2_eqn.display()


Out[13]:
$$u_{a,b,i,j} + \sum_{k \in O} t_{a,k} u_{b,k,i,j} - \sum_{k \in O} f_{k,i} t_{a,b,k,j} - \sum_{k \in O} f_{k,j} t_{a,b,i,k} - \sum_{k \in O} t_{b,k} u_{a,k,i,j} + \sum_{c \in V} f_{b,c} t_{a,c,i,j} + \sum_{c \in V} t_{c,i} u_{a,b,c,j} + \sum_{c \in V} t_{c,j} u_{a,b,i,c} - \sum_{c \in V} f_{a,c} t_{b,c,i,j} + \sum_{k \in O} \sum_{l \in O} \frac{1}{2} t_{a,b,k,l} u_{k,l,i,j} + \sum_{k \in O} \sum_{l \in O} t_{a,k} t_{b,l} u_{k,l,i,j} + \sum_{k \in O} \sum_{c \in V} t_{a,c,k,j} u_{b,k,i,c} + \sum_{k \in O} \sum_{c \in V} t_{b,c,i,k} u_{a,k,c,j} - \sum_{k \in O} \sum_{c \in V} t_{a,c,i,k} u_{b,k,c,j} - \sum_{k \in O} \sum_{c \in V} t_{b,c,k,j} u_{a,k,i,c} + \sum_{k \in O} \sum_{c \in V} f_{k,c} t_{a,k} t_{b,c,i,j} + \sum_{k \in O} \sum_{c \in V} t_{a,k} t_{c,i} u_{b,k,c,j} + \sum_{k \in O} \sum_{c \in V} t_{a,k} t_{c,j} u_{b,k,i,c} - \sum_{k \in O} \sum_{c \in V} f_{k,c} t_{c,j} t_{a,b,i,k} - \sum_{k \in O} \sum_{c \in V} f_{k,c} t_{c,i} t_{a,b,k,j} - \sum_{k \in O} \sum_{c \in V} f_{k,c} t_{b,k} t_{a,c,i,j} - \sum_{k \in O} \sum_{c \in V} t_{b,k} t_{c,i} u_{a,k,c,j} - \sum_{k \in O} \sum_{c \in V} t_{b,k} t_{c,j} u_{a,k,i,c} + \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{c,d,i,j} u_{a,b,c,d} + \sum_{c \in V} \sum_{d \in V} t_{c,i} t_{d,j} u_{a,b,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{c,l} t_{a,b,i,k} u_{k,l,c,j} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{b,l} t_{a,c,k,j} u_{k,l,i,c} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{a,l} t_{b,c,i,k} u_{k,l,c,j} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{c,l} t_{a,b,k,j} u_{k,l,i,c} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \frac{1}{2} t_{c,i} t_{a,b,k,l} u_{k,l,c,j} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \frac{1}{2} t_{c,j} t_{a,b,k,l} u_{k,l,i,c} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{b,l} t_{a,c,i,k} u_{k,l,c,j} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{a,l} t_{b,c,k,j} u_{k,l,i,c} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{a,k} t_{b,l} t_{c,i} u_{k,l,c,j} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} t_{a,k} t_{b,l} t_{c,j} u_{k,l,i,c} + \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,k} t_{a,c,i,j} u_{b,k,c,d} + \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,j} t_{b,c,i,k} u_{a,k,c,d} + \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,i} t_{b,c,k,j} u_{a,k,c,d} - \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,j} t_{a,c,i,k} u_{b,k,c,d} - \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,i} t_{a,c,k,j} u_{b,k,c,d} + \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,k} t_{c,d,i,j} u_{b,k,c,d} - \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{d,k} t_{b,c,i,j} u_{a,k,c,d} + \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{a,k} t_{c,i} t_{d,j} u_{b,k,c,d} - \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{b,k} t_{c,d,i,j} u_{a,k,c,d} - \sum_{k \in O} \sum_{c \in V} \sum_{d \in V} t_{b,k} t_{c,i} t_{d,j} u_{a,k,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,c,k,j} t_{b,d,i,l} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{4} t_{a,b,k,l} t_{c,d,i,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,b,l,j} t_{c,d,i,k} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,c,i,k} t_{b,d,l,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,d,i,j} t_{b,c,k,l} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{c,l} t_{d,j} t_{a,b,i,k} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,k} t_{d,l} t_{b,c,i,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,l} t_{d,j} t_{b,c,i,k} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,l} t_{d,i} t_{b,c,k,j} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,b,i,l} t_{c,d,k,j} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,c,k,l} t_{b,d,i,j} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{c,i} t_{d,l} t_{a,b,k,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{c,i} t_{d,j} t_{a,b,k,l} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{b,k} t_{d,l} t_{a,c,i,j} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{b,l} t_{d,j} t_{a,c,i,k} u_{k,l,c,d} - \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{b,l} t_{d,i} t_{a,c,k,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{a,k} t_{b,l} t_{c,d,i,j} u_{k,l,c,d} + \sum_{k \in O} \sum_{l \in O} \sum_{c \in V} \sum_{d \in V} t_{a,k} t_{b,l} t_{c,i} t_{d,j} u_{k,l,c,d} $$

Working equation optimization

Evaluating the working equation takes a lot of effort. Outside drudge, a sister package named gristmill is available for the optimization and automatic code generation for tensor contractions. To start with, we need to put the working equations into a tensor definitions with external indices and import the gristmill package.


In [16]:
from gristmill import *

working_eqn = [
    dr.define(Symbol('e'), en_eqn), 
    dr.define(t[a, i], t1_eqn), 
    dr.define(t[a, b, i, j], t2_eqn)
]

We can have an estimation of the FLOP cost without any optimization.


In [17]:
orig_cost = get_flop_cost(working_eqn, leading=True)
init_printing()
orig_cost


Out[17]:
$$66 no^{4} nv^{4}$$

Since normally we have far more virtual orbitals than occupied orbitals, we have make the optimization based on this.


In [18]:
%%time

eval_seq = optimize(
    working_eqn, substs={p.nv: 5000, p.no: 1000}, 
    contr_strat=ContrStrat.EXHAUST
)


CPU times: user 7.31 s, sys: 239 ms, total: 7.54 s
Wall time: 21.2 s

Now we can have some inspection of the evaluation sequence.


In [19]:
len(eval_seq)


Out[19]:
$$36$$

In [20]:
opt_cost = get_flop_cost(eval_seq, leading=True)
opt_cost


Out[20]:
$$4 no^{4} nv^{2} + 8 no^{3} nv^{3} + 2 no^{2} nv^{4}$$

Significant optimization can be seen. Finally we can verify the correctness of the evaluation sequence. This step can be very slow. But it is adviced for mission-critical tasks.


In [21]:
verify_eval_seq(eval_seq, working_eqn, simplify=True)


Out[21]:
True

Finally, we have have a peek at the details of the intermediates.


In [23]:
for eqn in eval_seq:
    eqn.display(False)


$$\tau^{0}_{i,j,a,b} = t_{b,a,j,i} + 2 t_{a,i} t_{b,j} $$
$$\tau^{1}_{i,j,k,a} = \sum_{b \in V} t_{b,i} u_{k,j,b,a} $$
$$\tau^{2}_{i,j,k,a} = u_{k,j,i,a} - \tau^{1}_{i,k,j,a} $$
$$\tau^{3}_{i,a} = f_{i,a} + \sum_{j \in O} \sum_{b \in V} t_{b,j} u_{j,i,b,a} $$
$$\tau^{4}_{i,j} = \sum_{a \in V} f_{i,a} t_{a,j} $$
$$\tau^{5}_{i,j} = - \sum_{k \in O} \sum_{a \in V} t_{a,k} u_{k,i,j,a} $$
$$\tau^{6}_{i,j} = \sum_{k \in O} \sum_{a \in V} \sum_{b \in V} \tau^{0}_{k,j,a,b} u_{k,i,a,b} $$
$$\tau^{7}_{i,j} = 2 f_{i,j} + 2 \tau^{4}_{i,j} + 2 \tau^{5}_{i,j} + \tau^{6}_{i,j} $$
$$\tau^{8}_{i,j,a,b} = - \sum_{k \in O} t_{a,k} u_{k,i,j,b} $$
$$\tau^{9}_{i,j,a,b} = - \sum_{c \in V} t_{c,i} u_{j,a,c,b} $$
$$\tau^{10}_{i,j,a,b} = t_{b,a,j,i} - t_{a,j} t_{b,i} $$
$$\tau^{11}_{i,j,a,b} = \sum_{k \in O} \sum_{c \in V} \tau^{10}_{k,i,c,a} u_{k,j,c,b} $$
$$\tau^{12}_{i,j,a,b} = - u_{j,a,i,b} + \tau^{8}_{j,i,a,b} + \tau^{9}_{i,j,a,b} + \tau^{11}_{i,j,a,b} $$
$$\tau^{13}_{i,j,a,b} = \sum_{k \in O} \sum_{c \in V} t_{c,b,k,j} \tau^{12}_{i,k,a,c} $$
$$\tau^{14}_{i,a,b,c} = - u_{i,a,c,b} + \sum_{j \in O} t_{a,j} u_{j,i,b,c} $$
$$\tau^{15}_{a,b,c,d} = u_{b,a,d,c} - \sum_{i \in O} t_{a,i} u_{i,b,c,d} - \sum_{i \in O} t_{b,i} \tau^{14}_{i,a,d,c} $$
$$\tau^{16}_{i,j,a,b} = \sum_{k \in O} t_{a,k} \tau^{2}_{i,k,j,b} $$
$$\tau^{17}_{i,j,a,b} = u_{j,a,i,b} - \tau^{9}_{i,j,a,b} - \tau^{16}_{i,j,a,b} $$
$$\tau^{18}_{i,j,k,l} = - \sum_{a \in V} t_{a,i} u_{k,j,l,a} $$
$$\tau^{19}_{i,j,a,b} = - t_{b,a,j,i} + 2 t_{a,j} t_{b,i} $$
$$\tau^{20}_{i,j,k,l} = 2 u_{j,i,l,k} + 2 \tau^{18}_{k,j,i,l} - 2 \tau^{18}_{l,j,i,k} - \sum_{a \in V} \sum_{b \in V} \tau^{19}_{l,k,a,b} u_{j,i,a,b} $$
$$\tau^{21}_{a,b} = \sum_{i \in O} f_{i,a} t_{b,i} $$
$$\tau^{22}_{a,b} = - \sum_{i \in O} \sum_{c \in V} t_{c,i} u_{i,a,c,b} $$
$$\tau^{23}_{a,b} = \sum_{i \in O} \sum_{j \in O} \sum_{c \in V} \tau^{0}_{i,j,c,b} u_{i,j,c,a} $$
$$\tau^{24}_{a,b} = - 2 f_{a,b} + 2 \tau^{21}_{b,a} + 2 \tau^{22}_{a,b} + \tau^{23}_{b,a} $$
$$\tau^{25}_{i,j,a,b} = \sum_{c \in V} \tau^{24}_{a,c} t_{c,b,i,j} $$
$$\tau^{26}_{i,j,k,a} = \sum_{b \in V} t_{b,i} u_{j,a,k,b} $$
$$\tau^{27}_{i,j,a,b} = u_{i,a,j,b} - \tau^{9}_{j,i,a,b} $$
$$\tau^{28}_{i,j,k,l} = u_{j,i,l,k} - \tau^{18}_{l,j,i,k} $$
$$\tau^{29}_{i,j,k,a} = - u_{i,a,k,j} - \tau^{26}_{j,i,k,a} + \sum_{b \in V} t_{b,k} \tau^{27}_{i,j,a,b} - \sum_{l \in O} t_{a,l} \tau^{28}_{l,i,k,j} $$
$$\tau^{30}_{i,a,b,c} = - u_{b,a,i,c} + \sum_{d \in V} t_{d,i} u_{a,b,d,c} $$
$$\tau^{31}_{i,j,a,b} = \sum_{k \in O} \tau^{7}_{k,j} t_{a,b,k,i} $$
$$\tau^{32}_{i,j,k,a} = u_{j,a,k,i} + \tau^{26}_{i,j,k,a} $$
$$e = \sum_{i \in O} \sum_{a \in V} f_{i,a} t_{a,i} + \sum_{i \in O} \sum_{j \in O} \sum_{a \in V} \sum_{b \in V} \frac{1}{4} \tau^{0}_{i,j,a,b} u_{i,j,a,b} $$
$$t_{a,i} = - \sum_{j \in O} \sum_{b \in V} t_{b,j} u_{j,a,i,b} + \sum_{b \in V} f_{a,b} t_{b,i} + f_{a,i} - \sum_{j \in O} \sum_{k \in O} \sum_{b \in V} \frac{1}{2} t_{b,a,j,k} \tau^{2}_{i,j,k,b} + \sum_{j \in O} \sum_{b \in V} \sum_{c \in V} \frac{1}{2} \tau^{0}_{j,i,b,c} u_{j,a,b,c} + \sum_{j \in O} \sum_{b \in V} \tau^{3}_{j,b} t_{b,a,j,i} - \sum_{j \in O} \frac{1}{2} t_{a,j} \tau^{7}_{j,i} $$
$$t_{a,b,i,j} = u_{b,a,j,i} - \sum_{c \in V} t_{c,i} u_{a,b,j,c} + \tau^{13}_{i,j,a,b} - \tau^{13}_{j,i,a,b} + \sum_{c \in V} \sum_{d \in V} \frac{1}{2} t_{c,d,j,i} \tau^{15}_{b,a,c,d} - \sum_{k \in O} \sum_{c \in V} \tau^{10}_{k,i,c,a} \tau^{17}_{j,k,b,c} + \sum_{k \in O} \sum_{c \in V} t_{c,a,k,j} \tau^{17}_{i,k,b,c} + \sum_{k \in O} \sum_{l \in O} \frac{1}{4} t_{b,a,k,l} \tau^{20}_{k,l,j,i} - \frac{1}{2} \tau^{25}_{j,i,b,a} + \frac{1}{2} \tau^{25}_{j,i,a,b} - \sum_{k \in O} t_{b,k} \tau^{29}_{k,j,i,a} - \sum_{c \in V} t_{c,j} \tau^{30}_{i,b,a,c} + \frac{1}{2} \tau^{31}_{j,i,b,a} - \frac{1}{2} \tau^{31}_{i,j,b,a} - \sum_{k \in O} t_{a,k} \tau^{32}_{j,k,i,b} $$