pdaggerq is a fermionic computer algebra package for bringing strings of creation / annihilation operators to normal order with respect to a true vacuum or the Fermi vacuum. The code can evaluate expressions like projections that arise in coupled cluster theory and can be used to generate working many-body electronic structure codes. In the examples section we provide worked examples that generate equations and Python code for CCSD, lambda-CCSD, CC3, CCSDT, CCSDTQ and more.
Installing pdaggerq requires cmake is installed on your system. To install, first clone the package
git clone git@github.com:edeprince3/pdaggerq.git
Then, you can install like you would a normal python package. From the package top level directory, run either
python setup.py install
or
pip install .
which should compile pdaggerq. These commands will produce build
anddist
folders which contain
the compiled c++ shared library. If you are a developer and make changes to the c++ code the package can
be rebuilt by running
python setup.py clean; python setup.py install
The following is an example that generates the energy expression for CCSD.
Python:
import pdaggerq
pq = pdaggerq.pq_helper("fermi")
print('')
print(' < 0 | e(-T) H e(T) | 0> :')
print('')
pq.add_st_operator(1.0,['f'],['t1','t2'])
pq.add_st_operator(1.0,['v'],['t1','t2'])
pq.simplify()
energy_terms = pq.fully_contracted_strings()
for my_term in energy_terms:
print(my_term)
pq.clear()
Output:
< 0 | e(-T) H e(T) | 0> :
['+1.000000', 'f(i,i)']
['+1.000000', 'f(i,a)', 't1(a,i)']
['-0.500000', '<i,j||i,j>']
['-0.250000', '<i,j||a,b>', 't2(a,b,j,i)']
['+0.500000', '<i,j||a,b>', 't1(a,i)', 't1(b,j)']
The same result can be generated by explicitly setting commutators, double commutators, etc., that arise in the similarity transformation. For the sake of readability, commutators that evaluate to zero are excluded. Also, here a different mechanism is used to print the strings:
Python:
import pdaggerq
pq = pdaggerq.pq_helper("fermi")
print('')
print(' < 0 | e(-T) H e(T) | 0> :')
print('')
# one-electron part:
# f
pq.add_operator_product(1.0,['f'])
# [f, T1]
pq.add_commutator(1.0,['f'],['t1'])
# [f, T2]
pq.add_commutator(1.0,['f'],['t2'])
# two-electron part:
# v
pq.add_operator_product(1.0,['v'])
# [v, T1]
pq.add_commutator(1.0,['v'],['t1'])
# [v, T2]
pq.add_commutator(1.0,['v'],['t2(a,b,i,j)'])
# [[v, T1], T1]]
pq.add_double_commutator(0.5, ['v'],['t1'],['t1'])
pq.simplify()
pq.print()
pq.clear()
Output:
< 0 | e(-T) H e(T) | 0> :
// fully-contracted strings:
// + 1.00000 f(i,i)
// + 1.00000 f(i,a) t1(a,i)
// - 0.50000 <i,j||i,j>
// - 0.25000 <i,j||a,b> t2(a,b,j,i)
// + 0.50000 <i,j||a,b> t1(a,i) t1(b,j)
We'd love to accept your contributions and patches. All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult GitHub Help for more information on using pull requests. Furthermore, please make sure your new code comes with extensive tests! Make sure you adhere to our style guide. Just have a look at our code for clues. We mostly follow PEP 8 and use the corresponding pylint to check. Code should always come with documentation.
We use Github issues for tracking requests and bugs.
When using pdaggerq for research projects, please cite:
@misc{pdaggerq_2021,
author = {A. Eugene DePrince and Nicholas C. Rubin},
title = {{pdaggerq}: https://github.com/edeprince3/pdaggerq},
month = {June},
year = {2021},
url = {https://github.com/edeprince3/pdaggerq}
}
Normal order may be defined relative to the true vacuum or the fermi vacuum. This selection is made when creating the pq_helper class:
# true vacuum
pq = pdaggerq.pq_helper("")
or
# true vacuum
pq = pdaggerq.pq_helper("true")
or
# fermi vacuum
pq = pdaggerq.pq_helper("fermi")
We follow the usual convention for labeling orbitals: i, j, k, l, m, and n represent occupied orbitals and a, b, c, d, e, and f represent virtual orbitals. Additionally, any label starting with i or a will be considered occupied or virtual, respectively (e.g., i_1 or a2). All other labels are considered general labels. When normal order is defined relative to the fermi vacuum, sums involving general labels are split into sums involving occupied and virtual orbitals using internal labels o1, o2, o3, and o4 (occupied) or v1, v2, v3, and v4 (virtual). So, we recommend avoiding using these labels when specifying any other components of your strings.
Orbital labels refer to spin orbitals. There is functionality to integrate out spin in final expressions, see below.
a general one-body operator
'h'
a general two-body operator
'g'
the fock operator
'f'
the fluctuation potental operator
'v'
fermionic creation / annihilation operators. e.g.,
a*(i)
a(j)
up to four-body transition operators, e.g., p*q, p*q*rs, etc.
'e1(p,q)'
'e2(p,q,r,s)'
singles, doubles, triples, and quadruples t-amplitudes
't1'
't2'
't3'
't4'
reference, singles, doubles, triples, and quadruples left-hand amplitudes
'l0'
'l1'
'l2'
'l3'
'l4'
reference, singles, doubles, triples, and quadruples right-hand amplitudes
'r0'
'r1'
'r2'
'r3'
'r4'
Note that all factors such as the 1/4 associated with t2, l2, and r2 are handled internally.
boson creation / annihilation operators,
b+
b-
cluster operators that include both n-body electron excitations and m-body photon creation (
't0,1'
't1,1'
etc.
left-hand operators that include both n-body electron de-excitations and m-body photon annihilation (
'l2,2'
etc.
right-hand operators that include both n-body electron excitations and m-body photon creation (
'r1,2'
etc.
a one-body fermionic operator times a boson creation operator,
'd+'
a one-body fermionic operator times a boson annihilation operator,
'd-'
a diagonal boson operator,
'w0'
the unit operator
'1'
Strings are defined in Python using the pq_helper class, which has the following functions:
set strings corresponding to a product of operators.
add_operator_product( 0.5, ['h','t1','t2'])
set strings corresponding to a commutator of operators. Note that the arguments are lists to allow for commutators of products of operators.
add_commutator(1.0, ['f'], ['t2'])
set strings corresponding to a double commutator involving three operators. Note that the arguments are lists to allow for commutators of products of operators.
add_double_commutator(1.0/2.0, ['f'], ['t2'], ['t1'])
set strings corresponding to a triple commutator involving four operators. Note that the arguments are lists to allow for commutators of products of operators.
add_triple_commutator(1.0/6.0, ['f'], ['t2'], ['t1'], ['t1'])
set strings corresponding to a quadruple commutator involving five operators. Note that the arguments are lists to allow for commutators of products of operators.
add_quadruple_commutator(1.0/24.0, ['f'], ['t2'], ['t1'], ['t1'], ['t1'])
set strings corresponding to a similarity transformed operator commutator involving five operators. The first argument after the numerical value is a list of operators; the product of these operators will be similarity transformed. The next argument is a list of operators appearing as a sum the exponential function. The similarity transformation is performed by applying the BCH expansion with four nested commutators.
add_st_operator(1.0, ['v'],['t1','t2'])
Control the amount of output. Any value greater than the default value of 0 will cause the code to print starting strings.
set_print_level(0)
set a sum (outer list) of products (inner lists) of operators that define the bra state
set_left_operators([['1'],['l1'],['l2']])
set the type of operator defined by 'l1', 'l2', etc., for different flavors of EOM-CC methods. Valid options are 'EE' (excitation energy), 'IP' (ionization potential), 'DIP' (double ionization potential, 'EA' (electron attachment), and 'DEA' (double electron attachment).
set a sum (outer list) of products (inner lists) of operators that define the ket state
set_right_operators([['r0'],['r1'],['r2']])
set the type of operator defined by 'r1', 'r2', etc., for different flavors of EOM-CC methods. Valid options are 'EE' (excitation energy), 'IP' (ionization potential), 'DIP' (double ionization potential, 'EA' (electron attachment), and 'DEA' (double electron attachment).
consolidate/cancel terms and zero any delta functions that involve occupied / virtual combinations.
simplify()
print current list of strings. the type of strings is dictated by the string_type flag. the default value is string_type = 'fully-contracted'
print(string_type = 'all/fully-contracted/one-body/two-body')
returns strings involving no creation / annihilation operators
fully_contracted_strings()
returns strings involving no creation / annihilation operators. Terms are spin integrated to eliminate non-spin-conserving terms, given a dictionary of spins for non-summed labels.
spin_labels = {
'e' : 'a',
'f' : 'b',
'm' : 'a',
'n' : 'b'
}
fully_contracted_strings_with_spin(spin_labels)
returns strings involving no creation / annihilation operators. Dictionary of label ranges specifies orbital spaces over which amplitues are defined for active-space methods [e.g., CCSDt/CCSDtq, J. Chem. Phys. 110, 6103–6122 (1999)]
label_ranges = {
't3' : ['act', 'act', 'all', 'act', 'act', 'all'],
't2' : ['all', 'all', 'all', 'all'],
't1' : ['all', 'all'],
'a' : ['act'],
'i' : ['act']
}
fully_contracted_strings_with_ranges(label_ranges)
clear the current set of strings. Note that this function will not reset operator types specified using set_right_operators_type and set_left_operators_type.
clear()