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 -:
Observableobjects can be summed with and subtracted from otherObservableobjects,Quantityobjects, floats or integers. When twoObservableobjects are summed / subtracted, their gradients are combined by summing / subtracting also. When anObservableis summed / subtracted with aQuantity,floatorintobject it is assumed that these objects do not depend on any force field parameters.*:
Observableobjects may be multiplied by otherObservableobjects,Quantityobjects, andfloatorintobjects. When twoObservableobjects are multiplied their gradients are propagated using the product rule. When anObservableis multiplied by aQuantity,floatorintobject it is assumed that these objects do not depend on any force field parameters./:
Observableobjects may be divided by otherObservableobjects,Quantityobjects, andfloatorintobjects. Gradients are propagated through the division using the quotient rule. When anObservableis divided by aQuantity,floatorintobject (or when these objects are divided by anObservableobject) 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.