API Examples#

A github repository containing C and Python API samples can be found here: NVIDIA/CUDALibrarySamples.

cuEST APIs#

Two different low-level APIs are currently provided by cuEST: C and Python. Both provide full control of objects’ lifetimes to the caller, requiring explicit creation and destruction to be performed in the appropriate locations. The lifecycle of the various building blocks (basis sets, integral engines, etc.) provided by cuEST follows this pattern:

  • Parameters Create: Build a set of default parameters describing how the cuEST object is to be configured when it is created. Query methods are provided to probe the default values selected at this stage, with the possibility to override undesirable values in the next step.

  • Parameters Configure: Any of the cuEST object’s parameters can be set at this stage, allowing an easy way to override defaults.

  • Object Create: With the parameters describing its configuration in hand, the object is created and a handle returned to the caller. This handle will be used to interact with the object and should be stored until the object is no longer needed.

  • Parameters Destroy: All cuEST entities instantiated via create function need to be explicitly freed by the caller using analogous destroy routines. As soon as an object has been created, the parameters describing it are no longer needed and can be safely freed.

  • Object Use: When created, each object is used by simply passing the handle returned by the create routines into the various other cuEST functions.

  • Object Destroy: At the end of the calculation the object must be destroyed to avoid leaking resources.

Below are examples of this procedure for creating the handle to the cuEST library itself, for C and Python APIs. The two APIs have almost identical procedures and syntax, with each function returning an enum status code that is checked every call.

Additional, more detailed examples of both the C and Python APIs can be found in the examples directory contained in this PID drop. Additionally, a fully functional SCF (specifically, restricted Hartree-Fock) program can be found in the cuest_scf directory. This SCF program is written using the Python API and demonstrates how the primitives provided by the cuEST library can be combined to perform common tasks in electronic structure theory.

C API#

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <cuest.h>
#include <helper_status.h>
#include <cuest.h>

int main(int argc, char **argv)
{
    /* Declare the cuEST handle. */
    cuestHandle_t handle;

    /* Declare the parameters for cuEST handle creation. */
    cuestHandleParameters_t handle_parameters;

    /* Create the cuEST handle parameters. This sets reasonable defaults. */
    checkCuestErrors(cuestParametersCreate(
        CUEST_HANDLE_PARAMETERS,
        &handle_parameters));

    /*
     * Find the max L default value
     */
    uint64_t max_L_solid_harmonic;
    checkCuestErrors(cuestParametersQuery(
        CUEST_HANDLE_PARAMETERS,
        handle_parameters,
        CUEST_HANDLE_PARAMETERS_MAX_L_SOLID_HARMONIC,
        &max_L_solid_harmonic,
        sizeof(uint64_t)));

    fprintf(
        stdout,
        "Maximum angular momentum (L) solid harmonic transformation: %zu\n",
        max_L_solid_harmonic);

    /*
     * Override the default
     */
    max_L_solid_harmonic = 5;
    checkCuestErrors(cuestParametersConfigure(
        CUEST_HANDLE_PARAMETERS,
        handle_parameters,
        CUEST_HANDLE_PARAMETERS_MAX_L_SOLID_HARMONIC,
        &max_L_solid_harmonic,
        sizeof(uint64_t)));

    /* Create the cuEST handle. */
    checkCuestErrors(cuestCreate(
        handle_parameters,
        &handle));

    /* The cuEST handle parameters may be destroyed immediately following handle creation. */
    checkCuestErrors(cuestParametersDestroy(
        CUEST_HANDLE_PARAMETERS,
        handle_parameters));

    /*
     * This is where the cuEST handle would normally be used to make additional
     * calls to the cuEST library.
     *
     * The user would construct handles to AO shells, an AO basis, etc. The cuEST handle
     * is taken as input for all of these other calls to the cuEST library.
     */

    /*
     * Destroy the cuEST handle when no additional calls to the cuEST library will be made.
     * This destroys the CUDA stream, cuBLAS and cuSolver handles held by the cuEST handle.
     */
    checkCuestErrors(cuestDestroy(handle));

    return 0;
}

Python API#

An example illustrating the use of the cuEST python API is shown below:

import cuest.bindings as ce

def cuest_check(
    title,
    return_code
    ):

    if return_code != ce.CuestStatus.CUEST_STATUS_SUCCESS:
        raise RuntimeError(f"{title} failed with code {return_code}")

# Create default HandleParameters object to parameterize the cuEST handle.
cuest_handle_parameters = ce.cuestHandleParameters()

cuest_check('Create Handle Params',
    ce.cuestParametersCreate(
        parametersType=ce.CuestParametersType.CUEST_HANDLE_PARAMETERS,
        outParameters=cuest_handle_parameters,
        )
    )

# Query for the maximum angular momentum supported for Cartesian->Spherical
# transforms in this cuEST handle instance.
maxl_handle = ce.data_uint64_t()
cuest_check('Query Handle Max L',
    ce.cuestParametersQuery(
        parametersType=ce.CuestParametersType.CUEST_HANDLE_PARAMETERS,
        parameters=cuest_handle_parameters,
        attribute=ce.CuestHandleParametersAttributes.CUEST_HANDLE_PARAMETERS_MAX_L_SOLID_HARMONIC,
        attributeValue=maxl_handle,
        )
    )
print('Handle Max L', maxl_handle.value)

# Override the default value
maxl_handle.value = 5
cuest_check('Configure Handle Max L',
    ce.cuestParametersConfigure(
        parametersType=ce.CuestParametersType.CUEST_HANDLE_PARAMETERS,
        parameters=cuest_handle_parameters,
        attribute=ce.CuestHandleParametersAttributes.CUEST_HANDLE_PARAMETERS_MAX_L_SOLID_HARMONIC,
        attributeValue=maxl_handle,
        )
    )

cuest_handle = ce.cuestHandle()
cuest_check('Create Cuest Handle',
    ce.cuestCreate(
        parameters=cuest_handle_parameters,
        handle=cuest_handle,
        )
    )

# Once the handle itself has been created, its parameters can be destroyed
cuest_check('Destroy Handle Params',
    ce.cuestParametersDestroy(
        parametersType=ce.CuestParametersType.CUEST_HANDLE_PARAMETERS,
        parameters=cuest_handle_parameters,
        )
    )


# The handle is ready to be used for calculations


# Destroying the handle will also destoy CUDA stream, cuBLAS, and cuSolver
# handles held by the cuEST handle.
cuest_check('Destroy Cuest Handle',
    ce.cuestDestroy(
        handle=cuest_handle,
        )
    )

Resource Management#

It is incumbent on the caller to allocate and free all memory used by cuEST routines. Rigorous resource requirements can be rapidly computed using WorkspaceQuery functions, which detail the amount of host and device RAM needed. These requirements are partitioned into two spaces: persistent and temporary. The former should not be freed until no more cuEST calls are to be performed, while the latter can be freed as soon as the subsequent create or compute call has returned. A number of examples are provided to demonstrate this workflow. Actually performing the allocations/deallocations is beyond the scope of the cuEST library, and it is the caller’s responsibility. To aid in implementing these workspace functions, C and Python examples can be found in the examples folder.