Adding Methods to Driver

proc.py

This is concerned at present with normal methods added first to the procedures table in driver.py that associates method names with functions to run them located in proc.py .

The function should start with a declaration, as below. methodname is never seen by users, so it’s good to be specific; if there’s lots of modules that can run mp2, call methodname modulenamemethodname, perhaps. The function must always take as arguments (name, **kwargs).

1
2
3
4
5
# energy method
def run_methodname(name, **kwargs):

# gradient method
def run_methodname_gradient(name, **kwargs):

If the function needs to test the identity of name several times, it can be convenient to predefine the lowercase version of the variable. The case of all other py-side options (in kwargs) has already been handled by energy(), etc. in driver.py and need not be repeated here.

1
2
3
4
5
# include if convenient
lowername = name.lower()

# never include
kwargs = kwargs_lower(kwargs)

It’s often necessary to The function often needs to set options for the c-side modules it calls. In order that the state of the options set by the user remains when control is returned to the user, an OptionsState object is set up. See LibOptions: globals, locals, has_changed and all that for details. All options set by the function need to be included here, and only options set by the function should be included. Most options should be associated with a particular module, but a few (see below) are given without module.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# include if any options set
optstash = OptionsState(
    # these and other basis options should have no associated module
    ['BASIS'],
    ['DF_BASIS_SCF'],
    ['DF_BASIS_MP2'],
    ['PUREAM'],
    ['FREEZE_CORE'],
    # all others should have an associated module
    ['SCF', 'SCF_TYPE'],
    ['SCF', 'GUESS'],
    ['DFMP2', 'MP2_OS_SCALE'],
    )

If options need to be set, set them anywhere here. Options should be set locally to a module, except for those without a module in OptionsState.

1
2
3
4
5
6
7
8
# include if necessary as globals
psi4.set_global_option('BASIS', guessbasis)
psi4.set_global_option('DF_BASIS_SCF', guessbasisdf)

# include if necessary as locals
psi4.set_local_option('TRANSQT2', 'WFN', 'MP2')
psi4.set_local_option('CCSORT', 'WFN', 'MP2')
psi4.set_local_option('MP2', 'WFN', 'MP2')

If the regular scf module is to be run, run it through scf_helper() so that cast-up can be used. Also, add the option to pass the reference wavefunction by pre-running scf, then running the module with the ref_wfn kwarg. Also, if the full two-electron integrals are necessary for the post-scf, compute them if only the df integrals were run previously.

1
2
3
4
5
6
7
8
9
# Bypass the scf call if a reference wavefunction is given

ref_wfn = kwargs.get('ref_wfn', None)
if ref_wfn is None:
    ref_wfn = scf_helper(name, **kwargs)

    # If the scf type is DF/CD, then the AO integrals were never written to disk
    if psi4.get_option('SCF', 'SCF_TYPE') in ['DF', 'CD']:
        psi4.MintsHelper(ref_wfn.basisset()).integrals()

Direct any post-scf modules to be run.

1
2
3
4
# include if further post-scf modules are needed
psi4.transqt2()
psi4.ccsort()
psi4.mp2()

If an OptionsState object was set up, those options need to be returned to the original user state with the following.

1
2
# include if optstash = OptionsState( was set up previously
optstash.restore()

No function should return anything. CURRENT ENERGY will be set by energy(), etc.

1
2
# never include
return returnvalue

Managed Methods

When functionality overlaps between modules, a pattern is needed to (1) access each route through the code without contrivances like ccsd2, _ccsd, sdci and (2) apportion defaulting among the modules, taking into account reference (RHF/UHF/ROHF) and calc type (CONV, DF, CD). Managed methods handle both these cases through the addition of a new keyword QC_MODULE and a set of type keywords analogous to MP2_TYPE: MP_TYPE, CI_TYPE, CC_TYPE, which can have values CONV, DF, and CD. These are all global keywords, as their values are shared among modules rather than (or in addition to) being used internally by the module). We’re sticking with SCF_TYPE and MP2_TYPE defaulting to DF, while everything higher defaults to CONV. In psi4/share/python/driver.py, a managed method calls a “select” function rather than a “run” function.

1
2
3
4
5
procedures = {
    'energy': {
        'scf'           : run_scf,
        'mp3'           : select_mp3,
        'dcft'          : run_dcft,

Then in psi4/share/python/proc.py, the select function runs through reference (always outer loop) and type (inner loop) to specify the proc function to call for any able, non-default module (e.g., mtd_type == 'DETCI' ) or able, default module (e.g., mtd_typd == ['', 'FNOCC'] ). Don’t worry about ‘else’ statements as anything that falls through will be caught and a readable error generated.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def select_mp3(name, **kwargs):
    """Function selecting the algorithm for a MP3 energy call
    and directing to specified or best-performance default modules.

    """
    reference = psi4.get_option('SCF', 'REFERENCE')
    mtd_type = psi4.get_global_option('MP_TYPE')
    module = psi4.get_global_option('QC_MODULE')
    # Considering only [df]occ/fnocc/detci

    func = None
    if reference == 'RHF':
        if mtd_type == 'CONV':
            if module == 'DETCI':
                func = run_detci
            elif module == 'FNOCC':
                func = run_fnocc
            elif module in ['', 'OCC']:
                func = run_occ
        elif mtd_type == 'DF':
            if module in ['', 'OCC']:
                func = run_dfocc
        elif mtd_type == 'CD':
            if module in ['', 'OCC']:
                func = run_dfocc
    elif reference == 'UHF':
        if mtd_type == 'CONV':
            if module in ['', 'OCC']:
                func = run_occ
        elif mtd_type == 'DF':
            if module in ['', 'OCC']:
                func = run_dfocc
        elif mtd_type == 'CD':
            if module in ['', 'OCC']:
                func = run_dfocc
    elif reference == 'ROHF':
        if mtd_type == 'CONV':
            if module in ['DETCI']:
                func = run_detci

    if func is None:
        raise ManagedMethodError(['select_mp3', name, 'MP_TYPE', mtd_type, reference, module])

    return func(name, **kwargs)

Naturally, in the run function, you must either use the type keyword for type switching or translate it into whatever DO_CD-like keywords your module uses. At run time with a closed-shell molecule,

1
energy('mp3')

will run OCC, while

1
2
set qc_module fnocc
energy('mp3')

will run FNOCC mp3.

A special case is DETCI that can run mp3, but oughtn’t to be used for such. So above, ROHF CONV mp3 has no default, but can still access the detci code with

1
2
3
set reference rohf
set qc_module detci
energy('mp3')

While the below gives an error

1
2
set reference rohf
energy('mp3')

Again, whenever a single method name needs to call multiple proc.py run functions, it should be managed. In Overlapping capabilities of <span style=”font-family: Optima, sans-serif; text-transform: none;”>P<span style=”font-size: 82%;”>SI</span>4</span>. “Y” is available; “D” is default. “Y” means method available in module, “D” means module is default for that method, “” mean method not available.