Measurement and Expectation Values

Observables are physical quantities that can be measured on ensembles. We distinguish between primary observables and derived quantities, using language from Ref. [5]. A primary observable can be measured directly on a single configuration. A derived quantity is a generally nonlinear function of primary observables which can only be estimated using expectation values from a whole ensemble.

The same observable or derived quantity might be computed differently using different actions.

Primary Observables

You can construct observables by writing a class that inherits from the supervillain.observable.Observable class.

class supervillain.observable.Observable[source]
classmethod autocorrelation(ensemble)[source]

Deciding whether an observable is included in an ensemble’s autocorrelation_time() computation is ensemble-dependent.

For example, if \(W=1\) then certain vortex observables are independent of configuration and thus look like they have an infinite autocorrelation time. However, that’s expected and not an ergodicity problem. That’s real physics!

So, to decide whether an observable should be included in the ensemble’s autocorrelation time requires in general evaluating a function on the observable itself and the ensemble.

By default observables just return False but observables can override this function to make more clever decisions.

Your observable can provide different implementations for different actions. Implementations are staticmethods named for their corresponding action. Implementations always take the action first, and then can take field variables or other primary observables.

Note

Implementations always take the action S first.

Note

The names of the arguments matter; they’re used to look up the correct field variables or other observables.

A simple example, since actions are already callable, is

class InternalEnergyDensity(Scalar, Observable):
    r'''If we think of $\kappa$ like a thermodynamic $\beta$, then we may compute the internal energy $U$

    .. math::
        \begin{align}
        U &= -  \partial_\kappa \log Z
        \\
          &= \left\langle -  \partial_\kappa (-S) \right\rangle
        \\
          &= \left\langle  \partial_\kappa S \right\rangle
        \end{align}

    It is extensive in the spacetime volume, so we calculate the density

    .. math ::
       \texttt{InternalEnergyDensity} =  U / \Lambda

    where $\Lambda$ is the number of sites in our spacetime.
    '''

    @staticmethod
    def Villain(S, phi, n):
        r'''
        In the :class:`~.Villain` case differentiating the action $S_0$ is the same as dividing it by $\kappa$!
        '''
        L = S.Lattice
        return S(phi, n) / (L.sites * S.kappa)


    @staticmethod
    def Worldline(S, Links):
        r'''
        In the :class:`~.Worldline` formulation we differentiate to find

        .. math ::
           \begin{align}
            U &= \left\langle \partial_\kappa S \right\rangle = - \frac{1}{2\kappa^2} \sum_{\ell} (m-\delta v/W)_\ell^2 + \frac{|\ell|}{2 \kappa}.
           \end{align}

        '''

        L = S.Lattice
        return (L.links / 2 - 0.5 / S.kappa * (Links**2).sum()) / (L.sites * S.kappa)

Under the hood Observables are attached to the Ensemble class. In particular, you can evaluate the observable for every configuration in an ensemble by just calling for the ensemble’s property with the name of the observable. The result is cached and repeated calls for that ensemble require no further computation.

For example, to evaluate the action density you would ask for ensemble.ActionDensity. If the ensemble was constructed from a Villain action you will get the Villain implementation; if it was constructed from a Worldline action you will get the Worldline implementation.

All of these nice features are accomplished using the Descriptor protocol but the implementation is unimportant.

If the observable does not provide an implementation for the ensemble’s action, asking for it will raise a NotImplemented exception. However, some observables can provide a default implementation, which is particularly useful for simple functions of other primary observables. For example, the TWrapping is just the time component of the TorusWrapping.

class TWrapping(Scalar, Observable):
    r'''
    Just the time component of :class:`~.TorusWrapping`.
    '''

    @staticmethod
    def default(S, TorusWrapping):
        return TorusWrapping[0]

Generators may return inline measurements of some observables in the dictionary of field variables. If those observables match any particular observable, the inline measurements take precedence and short-circuit any other computation.

Inclusion in Autocorrelation Computation

Scalar observables are good candidates for consideration in the autocorrelation time

class supervillain.observable.Scalar[source]
classmethod autocorrelation(ensemble)[source]

Scalars are simple to understand and can be included in the autocorrelation computation.

Returns True.

but, as advertised some observables only make sense when \(W\neq 1\).

class supervillain.observable.Constrained[source]
classmethod autocorrelation(ensemble)[source]

If \(W=1\) the observable should not be included in the autocorrelation computation.

If \(W\neq 1\) then use all other considerations to decide.

We can restrict observables to only one action if need be. (Usually this only makes sense as a stop-gap while the observable is implemented for that action.)

class supervillain.observable.NotVillain[source]
classmethod autocorrelation(ensemble)[source]

False if the ensemble’s action is Villain, otherwise use all other considerations.

class supervillain.observable.OnlyVillain[source]
classmethod autocorrelation(ensemble)[source]

True if the ensemble’s action is Villain and all other considerations are true.

class supervillain.observable.NotWorldline[source]
classmethod autocorrelation(ensemble)[source]

False if the ensemble’s action is Worldline, otherwise use all other considerations.

class supervillain.observable.OnlyWorldline[source]
classmethod autocorrelation(ensemble)[source]

True if the ensemble’s action is Worldline and all other considerations are true.

We can use these to control the inclusion in an ensemble’s autocorrelation computation. For example, Spin_Spin is a two-point function, the InternalEnergyDensity is a scalar, and the VortexSusceptibility is a constrained scalar that only makes sense when \(W\neq 1\).

class Spin_Spin(Observable):
class InternalEnergyDensity(Scalar, Observable):

Different considerations override one another in accordance with the python method resolution order; very roughly speaking, left-most first.

Monitoring Progress

Ensemble generation can be monitored with a progress bar. For large ensembles and slow observables it might be desirable to monitor the progress of the computation of observables. Since observables are properties of ensembles they accept no arguments and therefore unlike generate() cannot take a similar progress keyword argument.

Instead, we reluctantly introduce a little bit of module-level state.

supervillain.observable.progress(iterable, **kwargs)[source]

Like tqdm, but requires the iterable.

The default progress bar is a no-op that forwards the iterable. You can overwrite it simply.

from tqdm import tqdm
import supervillain
supervillain.observable.progress=tqdm

Derived Quantities

Like the primary observables, derived quantities also inherit from a common supervillain.observable.DerivedQuantity class.

class supervillain.observable.DerivedQuantity[source]

Just like observables, derived quantities can provide different implementations for different actions. However, because derived quantities are (possibly-)nonlinear combinations of expectation values of primary observables, they cannot be measured on single configurations and therefore are not attached to Ensembles but to Bootstraps, which automatically provide resampled expectation values of primary obervables.

DerivedQuantity implementations are staticmethods named for their corresponding action that take an action object and a bootstrap-resampled expectation value of primary observables or other derived quantities. Because DerivedQuantities are often reductions of other primary Observables or DerivedQuantities, the implementation may be shared between different actions; you can provide a common default implementation to fall back to that can be overridden by action-specific implementations. Just like an Observable, a DerivedQuantity takes the action, primary observables, and potentially other derived quantities, though it is almost certainly a mistake for a derived quantity to depend directly on field variables.

Note

Implementations always take the action S first.

Note

The arguments’ names matter and have to exactly match the needed expectation values.

The implementations are automatically threaded over the bootstrap samples, maintaining all correlations.

class Action_Action(DerivedQuantity):
    r'''
    If we imagine rewriting the actions' sums over links as a sum over sites and a sum over directions we can associate a value of κ with each site.
    Then we may compute the correlations of the action density by evaluating

    .. math::
        \begin{align}
            \mathcal{S}_{x,y} =& \left.\left(-\kappa_y \frac{\delta}{\delta \kappa_y}\right) \left(-\kappa_x \frac{\delta}{\delta \kappa_x}\right) \log Z\right|_{\kappa_{x,y} = \kappa}
            \\
            =& 
            \left.\left\langle (\kappa_y \partial_{\kappa_y} S) (\kappa_x \partial_{\kappa_x} S) - \kappa_y \kappa_x \partial_{\kappa_y} \partial_{\kappa_x} S  - \kappa_y \delta_{yx} \partial_{\kappa_x} S \right\rangle\right|_{\kappa_{x,y} = \kappa}
            \\ &
            - \left.\left\langle \kappa_x \partial_{\kappa_x} S \right\rangle\right|_{\kappa_x = \kappa}
              \left.\left\langle \kappa_y \partial_{\kappa_y} S \right\rangle\right|_{\kappa_y = \kappa}.
        \end{align}

    Using translational invariance the quantum-disconnected piece is independent of $x$ and $y$ and can be replaced by $\left\langle\texttt{ActionDensity}\right\rangle^2$.
    So, we find the simplification

    .. math ::
        \begin{align}
            \mathcal{S}_{x,y} = 
            & 
            \left\langle (\kappa_y \partial_{\kappa_y} S) (\kappa_x \partial_{\kappa_x} S) -  \kappa_y \kappa_x \partial_{\kappa_y} \partial_{\kappa_x} S - \delta_{xy} \kappa_x \partial_{\kappa_x} S\right\rangle
            \\ &
            - \left\langle \texttt{ActionDensity} \right\rangle^2
        \end{align}

    We define the spacetime-dependent correlator
    
    .. math ::
        S^2_{x,y}
        =
        \left\langle (\kappa_y \partial_{\kappa_y} S) (\kappa_x \partial_{\kappa_x} S) -  \kappa_y \kappa_x \partial_{\kappa_y} \partial_{\kappa_x} S - \delta_{xy} \kappa_x \partial_{\kappa_x} S \right\rangle

    so that $\mathcal{S}_{x,y} = S^2_{xy} - \left\langle \texttt{ActionDensity} \right\rangle^2$

    We can reduce to a function of a single relative coordinate,

    .. math ::
        \begin{align}
            \texttt{Action_Action}_{\Delta x} = \mathcal{S}_{\Delta x} = \frac{1}{\Lambda} \sum_{x} \mathcal{S}_{x, x-\Delta x}
        \end{align}


    and define a primary observable :class:`~.ActionTwoPoint`

    .. math ::
        \begin{align}
            \texttt{ActionTwoPoint}_{\Delta x} = \frac{1}{\Lambda} \sum_{x} S^2_{x,x-\Delta x}.
        \end{align}

    The quantum-disconnected term is $\Delta x$ independent, so

    .. math ::
        \texttt{Action_Action}_{\Delta x} = \texttt{ActionTwoPoint}_{\Delta x} - \left\langle \texttt{ActionDensity} \right\rangle^2.

    '''

    @staticmethod
    def default(S, ActionTwoPoint, ActionDensity):
        return ActionTwoPoint - ActionDensity**2