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 Observable
s 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]
Generator
s 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
but, as advertised some observables only make sense when \(W\neq 1\).
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.)
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.
Derived Quantities
Like the primary observables, derived quantities also inherit from a common supervillain.observable.DerivedQuantity
class.
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 Ensemble
s but to Bootstrap
s, 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