import numpy as np
from supervillain.observable import Observable
[docs]class Spin_SpinSloppy(Observable):
r'''
This performs the same measurement as the non-Sloppy version but does not get all the juice out of every Worldline configuration.
See the :class:`~.Spin_Spin` documentation for detailed descriptions.
'''
[docs] @staticmethod
def Villain(S, phi):
r'''
The same as in the :class:`~.Spin_Spin`.
'''
L = S.Lattice
exp_i_phi = np.exp(1.j * phi)
return L.correlation(exp_i_phi, exp_i_phi)
[docs] @staticmethod
def Worldline(S, Links):
r'''
See the :class:`Spin_Spin` documentation.
'''
L = S.Lattice
kappa = S.kappa
result = L.linearize(L.form(0))
# For every displacment we will take the taxicab route, as dumb as possible.
# Just go Δt in time first and then Δx in space.
for i, (Δt, Δx) in enumerate(L.coordinates):
P = L.form(1)
if Δt >= 0:
# Follow the links in the positive t direction.
# Therefore increment P by 1
P[0][:Δt,0] = +1
else:
# Follow the links in the negative t direction.
# Therefore decrement P by 1
P[0][Δt:,0] = -1
if Δx >= 0:
# Follow the links in the positive x direction.
P[1][Δt,:Δx] = +1
else:
# Follow the links in the negative x direction.
P[1][Δt,Δx:] = -1
# The difference between the Sloppy and full versions is that here we only
# overlay the stencil on the configuration one time. That always puts the
# defect at the absolute origin and (Δt, Δx), as opposed to summing over all
# the possible origins.
result[i] += np.exp(-1/(2*kappa) * (P* (2*Links + P)).sum())
return L.coordinatize(result)
[docs]class Spin_SpinSlow(Observable):
r'''
We can deform $Z_J \rightarrow Z_{J}[x,y]$ to include the creation of a boson at $y$ and the destruction of a boson at $x$ in the action.
We define the expectation value
.. math ::
S_{x,y} = \frac{1}{Z_J} Z_J[x,y]
and reduce to a single relative coordinate
.. math ::
\texttt{Spin_Spin}_{\Delta x} = S_{\Delta x} = \frac{1}{\Lambda} \sum_x S_{x,x-\Delta x}
.. seealso::
Compared to :class:`~.reference_implementation.spin.Spin_SpinSloppy` this implementation gets more juice from each configuration.
In other words, for a fixed configuration their results will differ, but they will agree in expectation.
In contrast, this observable produces the same numerical values as the production implementation :class:`~.Spin_Spin`, which is much faster.
'''
[docs] @staticmethod
def Villain(S, phi):
r'''
The same as in :class:`~.Spin_Spin`.
'''
L = S.Lattice
exp_i_phi = np.exp(1.j * phi)
return L.correlation(exp_i_phi, exp_i_phi)
_stencils = dict()
[docs] @staticmethod
def Worldline(S, Links):
r'''
Computes the same result as :class:`~.Spin_Spin` but more slowly.
Compared to :class:`~.Spin_SpinSloppy` we measure the same correlator but get more juice from each configuration by averaging over translations.
'''
# Note: for a substantially similar but slightly simpler implementation
# which leaves a lot of information on the table, see the Spin_SpinSloppy
# observable.
L = S.Lattice
kappa = S.kappa
result = L.linearize(L.form(0))
# For every displacment we will take the taxicab route, as dumb as possible.
# Just go Δt in time first and then Δx in space.
for i, (Δt, Δx) in enumerate(L.coordinates):
try:
P = Spin_SpinSlow._stencils[(L.nt, L.nx, Δt, Δx)]
except KeyError:
# Each stencil will hold the path starting on the origin, AND a copy
# for every other starting point.
#
# We construct the one from the origin first, as it is easiest to think about...
P = L.form(1, L.sites)
if Δt >= 0:
# Follow the links in the positive t direction.
# Therefore increment P by 1
P[0, 0][:Δt,0] = +1
else:
# Follow the links in the negative t direction.
# Therefore decrement P by 1
P[0, 0][Δt:,0] = -1
if Δx >= 0:
# Follow the links in the positive x direction.
P[0, 1][Δt,:Δx] = +1
else:
# Follow the links in the negative x direction.
P[0, 1][Δt,Δx:] = -1
# ... and then we just roll it around and store every possible translation.
for j, shift in enumerate(L.coordinates):
P[j] = L.roll(P[0], shift)
Spin_SpinSlow._stencils[(L.nt, L.nx, Δt, Δx)] = P
# In the full measurement we overlay the stencil on the configuration
# in all possible ways and sum. Then we have measured the dependence on Δx
# as efficiently as possible for each configuration, summing each displacement
# over all possible starting points.
#
# Since we have stored every translation, we can use the numpy broadcasting rules
#
# https://numpy.org/doc/stable/user/basics.broadcasting.html
#
# to eliminate some python for loops, which were explicit in previous implementations.
result[i] = np.exp(-1/(2*kappa) * (P * (2*Links + P)).sum(
axis=(1,2,3) # the 0th axis is the broadcast axis, 1,2, and 3 are the vector index, time, and space.
)).mean() # <-- we should average over the different starting points.
return L.coordinatize(result)