Observables

A key feature of this framework is its ability to compute the gradients of physical properties with respect to the force field parameters used to estimate them. This requires the framework be able to, internally, be able to not only track the gradients of all quantities which combine to yield the final observable of interest, but to also be able to propagate the gradients of those composite quantities through to the final value.

The framework offers three such objects to this end (Observable, ObservableArray and ObservableFrame objects) which will be covered in this document.

Note

In future versions of the framework the objects described here will likely be at least in part deprecated in favour of using full automatic differentiation libraries such as jax. Supporting these libraries will take a large re-write of the framework however, as well as full support between differentiable simulation engines like timemachine and the OpenFF toolkit. As such, these objects are implemented as stepping stones which can be gently phased out while working towards that larger, more modern goal.

Observable Objects

The base object used to track observables is the Observable object. It stores the average value, the standard error in the value and the gradient of the value with respect to force field parameters of interest.

Currently the value and error are internally stored in a composite Measurement object, which themselves wrap around the uncertainties package. This allows uncertainties to be automatically propagated through operations without the need for user intervention.

Note

Although uncertainties are automatically propagated, it is still up to property estimation workflow authors to ensure that such propagation (assuming a Gaussian error model) is appropriate. An alternative, which is employed throughout the framework is to make use of the bootstrapping technique.

Gradients are stored in a list as ParameterGradient gradient objects, which store both the floating value of the gradient alongside an identifying ParameterGradientKey.

Supported Operations

  • + and -: Observable objects can be summed with and subtracted from other Observable objects, Quantity objects, floats or integers. When two Observable objects are summed / subtracted, their gradients are combined by summing / subtracting also. When an Observable is summed / subtracted with a Quantity, float or int object it is assumed that these objects do not depend on any force field parameters.

  • *: Observable objects may be multiplied by other Observable objects, Quantity objects, and float or int objects. When two Observable objects are multiplied their gradients are propagated using the product rule. When an Observable is multiplied by a Quantity, float or int object it is assumed that these objects do not depend on any force field parameters.

  • /: Observable objects may be divided by other Observable objects, Quantity objects, and float or int objects. Gradients are propagated through the division using the quotient rule. When an Observable is divided by a Quantity, float or int object (or when these objects are divided by an Observable object) it is assumed that these objects do not depend on any force field parameters.

In all cases two Observable objects can only be operated on provided the contain gradient information with respect to the same set of force field parameters.

Observable Arrays

An extension of the Observable object is the ObservableArray object. Unlike an Observable, an ObservableArray object does not contain error information, but rather the value it stores and the gradients of that value should be a numpy array with shape=(n_data_points, n_dimensions). It is designed to store information such as the potential energy evaluated at each configuration sampled during a simulation, as well as the gradient of the potential, which can then be ensemble averaged using a fluctuation formula to propagate the gradients through to the average.

Like with Observable objects, gradients are stored in a list as ParameterGradient gradient objects. The length of the gradients is required to match the length of the value array.

ObservableArray objects may be concatenated together using their join() method or sub-sampled using their subset() method.

Supported Operations

The ObservableArray object supports the same operations as the Observable object, whereby all operations are applied elementwise to the stored arrays.

Observable Frames

An ObservableFrame is a wrapper around a collection of ObservableArray which contain the types of observable specified by the ObservableType enum. It behaves as a dictionary which can take either an ObservableType or a string value of an ObservableType as an index.

Like an ObservableArray, observable frames may be concatenated together using their join() method or sub-sampled using their subset() method.

Supported Operations

No operations are supported between observable frames.