#!/usr/bin/env python
import numpy as np
from supervillain.h5 import ReadWriteable
from supervillain.batch import Batch
from supervillain.configurations import Configurations
from supervillain.lattice import Lattice, Form, d
import logging
logger = logging.getLogger(__name__)
[docs]class Villain(ReadWriteable):
r'''
'The' Villain action is just the straightforward
.. math::
\begin{aligned}
Z[J] &= \sum\hspace{-1.33em}\int D\phi\; Dn\; Dv\; e^{-S_J[\phi, n, v]}
\\
S_J[\phi, n, v] &= \frac{\kappa}{2} \sum_{\ell} (d\phi - 2\pi n)_\ell^2 + 2\pi i \sum_p \left(v/W + J/2\pi \right)_p (dn)_p
\end{aligned}
with $\phi$ a real-valued 0-form that lives on sites, $n$ an integer-valued one form that lives on links $l$, and $J$ a two-form that lives on plaquettes $p$.
In this formulation, if $J$ is real and nonzero we expect a sign problem because the action is complex. However, we can think of $J$ as an external source, take functional derivatives to get observables, and then set $J$ to zero so that we only need sample according to the first term.
.. warning::
Because $W\neq1$ suffers from a sign problem if we try to sample $v$, we assume an ensemble will be generated
with a clever algorithm that that maintains :ref:`the winding constraint <winding constraint>`, so that $v$ need not be included in the field content.
Parameters
----------
lattice: supervillain.lattice.Lattice
The lattice on which $\phi$ and $n$ live.
kappa: float
The $\kappa$ in the overall coefficient.
W: int
The constraint integer $W$. When $W>1$ we must integrate out $v$ to avoid a horrible sign problem and sample carefully to maintain the winding constraint.
'''
def __init__(self, lattice, kappa, W=1):
if not isinstance(lattice, Lattice):
raise TypeError(f'Villain requires a supervillain.lattice.Lattice, got {type(lattice).__name__}')
self.Lattice = lattice
self.kappa = kappa
self.W = W
def __str__(self):
return f'Villain({self.Lattice}, κ={self.kappa}, W={self.W})'
[docs] def __call__(self, phi, n, **kwargs):
r'''
Parameters
----------
phi: Form or np.ndarray
A real-valued 0-form.
n: Form or np.ndarray
An integer-valued 1-form.
Returns
-------
float
$S_0[\phi, n]$. We assume the path integration over $v$ implements :ref:`the winding constraint <winding constraint>` in some clever way,
so the action does not depend on $v$.
'''
return (self.kappa / 2) * ((d(phi) - 2 * np.pi * n)**2).sum()
[docs] def links(self, phi, n):
r'''Gauge-invariant link variables $d\phi - 2\pi n$ as a 1-form.'''
return d(phi) - 2 * np.pi * n
[docs] def local(self, phi, n):
r'''Per-link contribution $\frac{\kappa}{2}(d\phi - 2\pi n)^2$ as a 1-form.'''
return (self.kappa / 2) * self.links(phi, n)**2
[docs] def configurations(self, count):
r'''
Parameters
----------
count: int
Returns
-------
Configurations
A dictionary of ``count`` Batched Forms, ``phi`` a real 0-form and ``n`` an integer-valued 1-form.
'''
L = self.Lattice
return Configurations({
'phi': Batch(count, cls=Form, degree=0, lattice=L),
'n': Batch(count, cls=Form, degree=1, lattice=L, dtype=int),
})
[docs] def valid(self, configuration):
r'''
Returns true if the constraint $[dn \equiv 0 \text{ mod } W]$ is satisfied everywhere.
When $W=\infty$, check that $dn = 0$ everywhere.
Parameters
----------
configuration: dict
A dictionary that at least contains n.
Returns
-------
bool:
Is the constraint satisfied everywhere?
'''
dn = d(configuration['n'])
zero = (np.mod(dn, self.W) if self.W < float('inf') else dn)
return (zero == 0).all()