Iteration Progress Reports

As an Iterative calculation runs, you’ll likely want to keep track of several quantities in order to monitor the progress and convergence of the calculation. During the setup() stage of an Iterative calculation, you can specify what quantities you want to track as a list of strings.

This process is enabled by the pydmqmc.report_registry, a dictionary-like object that associates the name of a reportable quantity (such as the trace or energy expectation of a matrix being iterated on) with the function and dependencies used to calculate it.

Specifying Reportable Quantities

You can specify the quantities you want to track in an Iterative calculation when calling the Method’s setup() function. This is done by providing a list of strings to the report_quants parameter:

import pydmqmc

# create your system and method objects
sys = pydmqmc.systems...(...)
mtd = pydmqmc.methods...(sys)

# setup your Iterative calculation
mtd.setup(
    ...     # specify parameters specific to your chosen method
    report_quants = ["trace", "energy numerator", "energy expectation"]
)

As long as the specified quantities are in pydmqmdc.report_registry, their associated quantities will be calculated and stored periodically throughout the iteration.

Note that the iteration variable is always reported. For example, for a Density Matrix Quantum Monte Carlo (DMQMC) calculation, this would be the inverse temperature \(\beta\).

You may change your mind and rerun the setup() method any number of times before starting a calculation with the run() method.

The Standard Reportable Quantities

The pydmqmc.report_registry comes with several pre-defined reportable quantities. Some of these quantities are expectation values while others like the matrix trace have no physical meaning but are useful for tracking the progress of a calculation.

The list of standard, pre-defined quantities are listed along with a brief description in the following table:

pydmqmc.report.trace(matrix)

Calculate the trace of a given matrix.

pydmqmc.report.energy_numerator(matrix, ...)

Numerator of the energy estimator.

pydmqmc.report.energy_expectation(matrix, ...)

Return the expectation value of the energy estimator.

pydmqmc.report.von_neumann_numerator(matrix)

Numerator of the von Neumann estimator.

pydmqmc.report.von_neumann_expectation(matrix)

Return the expectation value of the von Neumann estimator.

Accessing the Iteration Report

Once an Iterative calculation has been run, the report of requested quantities can be accessed with the report member. This attribute contains a list of dictionaries, one dictionary for each report period of the Iterative calculation.

Note

The requested report quantities are not recorded every iteration (or “cycle”), but rather every few cycles. How many exactly? That depends on the method used; for example, for DMQMC methods, the reporting frequency is the same as the cycles_per_shift parameter passed to the run() method.

Saving the Iteration Report

Every Iterative Method has some sort of save_data() method that will, at minimum, save the iteration report to disk. Most of these methods will also save important results of the calcuation. In the case of DMQMC calcuations, for example, the save_data() method will also save the final density matrix.

The iteration report can be saved with multiple file types: comma-separated value (.csv), text file (.txt), or as a Python pickle file (.pkl). For example:

mtd.save_data(
    "my_report",  # base of the filename
    report_filetype = "txt",
    )

Advanced Usage

The following document advanced use cases for the pydmqmc.report_registry.

Defining Your Own Reportable Quantities

You may have a custom function that you’d like to use in an iteration report. This function may, for instance, report an observable not included in pydmqmc. The pydmqmc.report_registry allows you to enroll your own functions, assigning them a name and keeping track of what dependencies that function has. We’ll see how to do this in the steps below.

First, you’ll need to write a function for your custom observable. This function should accept, at minimum, a NumPy array. This generic NumPy array will represent the heart of the Iterative calculation, calculation. For a DMQMC calculation, for example, this matrix will be the density matrix.

In the example below, we’ll recreate the existing function for calculating the energy expectation value (but instead of energy_expectation(), we’ll call it custom_energy). This observable requires a second parameter: the Hamiltonian for the system in question. We’ll see how to communicate this dependency in the following steps; for now, let’s just define our function:

def custom_energy(matrix, hamiltonian):

    eng = np.trace(hamiltonian @ matrix)
    expect = eng / np.trace(matrix)

    return expect

Once we’ve defined the function for calculating an observable, we need to enroll it in the pydmqmc.report_registry. This can be done in two ways as outlined below. Regardless of the method used, we’ll need to supply the same information:

  1. The name for our observable

  2. The function used to calculate it

  3. Any dependencies the observable may have (other than the matrix being iterated on)

Note that the dependencies must be members of either a Method object or its associated System. For our example, the custom_energy function requires the Hamiltonian. This is associated with a System object attached to whatever Method is generating a report.

The first approach is to use the enroll() method of the pydmqmc.report_registry:

from pydmqmc import report_registry

report_registry.enroll(
    name = "my_energy",
    function = custom_energy,
    requires = "method.system.hamiltonian"
)

Note that the dependency (system.hamiltonian) is specified as attached to a generic method object. Alternatively, we could just write

report_registry.enroll(
    name = "my_energy",
    function = custom_energy,
    requires = "hamiltonian"
)

In this case, the Method object trying to calculate this observable and its associated System will automatically be searched for a member called hamiltonian.

We could also enroll our function when defining it by using the @enroll decorator. We can optionally pass a name and any requirements to the decorator. If we don’t supply a name, the name of the function itself will be used. For example:

from pydmqmc import enroll

@enroll(name = "my_energy", requires = "hamiltonian")
def custom_energy(matrix, hamiltonian):

    eng = np.trace(hamiltonian @ matrix)
    expect = eng / np.trace(matrix)

    return expect

Using the Report Registry Directly

The pydmqmc.report_registry exists to streamline specifying quantities when setting up an Iterative calculation but it can also be used by you, the user, to easily calculate derived and observable quantities. The pydmqmc.report_registry has several methods available to facilitate this.

First and foremost, we can use the keys() method to see which keys are currently registered:

>>> from pydmqmc import report_registry
>>> report_registry.keys()
dict_keys(['trace', 'energy numerator', 'energy expectation', ... ])

Next is the list_requirements() method. This method returns a tuple of all the dependencies that are required by a function enrolled in the registry. Note that the matrix being iterated upon is not considered a dependency. For example:

>>> report_registry.list_requirements("trace")
>>> report_registry.list_requirements("energy expectation")
('hamiltonian',)

We see from above that the "trace" key isn’t associated with any requirements, while the "energy expectation" key has one—the Hamiltonian.

For the simple case of "trace", we can easily retrieve the associated function with square bracket notation:

>>> import numpy as np
>>> matrix = np.array([[0.2, 0.4],[0.1, 0.3]])
>>> trace = report_registry["trace"]
>>> type(trace)
function
>>> trace(matrix)
np.float64(0.5)

We can do the same with "energy expectation", but recall that this function requires a Hamiltonian. Let’s go ahead an set up a Integral object to provide us with our Hamiltonian. Then, we can fetch and call the function associated with "energy expectation" by manually supplying its requirements:

>>> from pydmqmc.systems import Integral
>>> sys = Integral("pydmqmc/tests/inputs/integrals/H2-STO-3G-0.74Ang.fcidump")
>>> sys.generate_hamiltonian()
>>> eng = report_registry["energy expectation"]
>>> eng(matrix, sys.hamiltonian)
np.float64(0.01207762620230829)

What if we don’t want to manually collect the additional requirements? If our Integral has been associated with a Method (like SymmetricBlochDMQMC) we can supply this to get_requirements() to return a dictionary of requirements that can be fed to function we pull from the registry:

>>> from pydmqmc.methods import SymmetricBlochDMQMC
>>> mtd = SymmetricBlochDMQMC(sys)
>>> reqs = report_registry.get_requirements("energy expectation", mtd)
>>> reqs
{'hamiltonian': array([[...]])}
>>> eng = report_registry["energy expectation"]
>>> eng(matrix, **reqs)
np.float64(0.01207762620230829)