Source code for supervillain.lattice.two_dimensional
#!/usr/bin/env python
import matplotlib.colors as colors
import numpy as np
from supervillain.lattice.compact import Lattice, push
[docs]class Lattice2D(Lattice):
r'''
A two-dimensional square lattice — a thin wrapper around
:class:`~supervillain.lattice.Lattice` with ``D=2``.
All lattice machinery (forms, exterior derivative, codifferential,
Fourier transforms, symmetrize, linearize/coordinatize, …) is inherited
from :class:`~supervillain.lattice.Lattice`. ``Lattice2D`` adds only the
:py:meth:`plot_form` visualisation helper and the ``nt``/``nx`` aliases.
Parameters
----------
n: int
The number of sites on a side.
'''
def __init__(self, n):
super().__init__(D=2, N=n)
[docs] @classmethod
def from_h5(cls, group, strict=True, _top=True):
from supervillain.h5 import Data
N = Data.read(group['N'], strict)
return cls(N)
@property
def plaquettes(self):
"""Number of plaquettes (2-cells): $C(2,2) N^2 = N^2$."""
return self.cells_of_degree[2]
@property
def nt(self):
"""Temporal extent N."""
return self.N
@property
def nx(self):
"""Spatial extent N."""
return self.N
@property
def t(self):
"""FFT-convention t-coordinates (first direction), shape (N,)."""
return self._coord_1d
@property
def x(self):
"""FFT-convention x-coordinates (second direction), shape (N,)."""
return self._coord_1d
@property
def T(self):
"""Array of shape (N, N) with the t-coordinate at each site."""
return np.tile(self.t, (self.N, 1)).T
@property
def X(self):
"""Array of shape (N, N) with the x-coordinate at each site."""
return np.tile(self.x, (self.N, 1))
def __str__(self):
return f'Lattice2D({self.nt},{self.nx})'
def __repr__(self):
return str(self)
[docs] def plot_form(self, form, axis, label=None, zorder=None,
cmap=None, cbar_kw=dict(), norm=colors.CenteredNorm(),
pointsize=200, linkwidth=0.025,
background='white',
markerstyle='o'
):
r'''
Plots the form on the axis. The degree of the form determines
whether sites, links, or plaquettes are visualized.
The following figure shows a 0-form plotted on sites, a 1-form on links, and a 2-form on plaquettes.
See the source for details.
.. plot:: example/plot/forms.py
Parameters
----------
form: Form
The p-form to plot; ``form.degree`` determines the visualization.
axis: matplotlib.pyplot.axis
The axis on which to plot.
Returns
-------
matplotlib.image.AxesImage:
A handle for the data-sensitive part of the plot.
Other Parameters
----------------
label: string
If specified, show a colorbar with the title given by the label.
zorder: float
If `None` defaults to ``-form.degree`` to layer plaquettes, links, and sites well.
cmap: string or matplotlib.colors.Colormap
If a string, it should name `a colormap known to matplotlib <https://matplotlib.org/stable/users/explain/colors/colormaps.html>`_.
cbar_kw: dict
A dictionary of keyword arguments forwarded to `the colorbar constructor <https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.colorbar>`_.
norm: matplotlib.colors.Normalize
A `matplotlib color normalization <https://matplotlib.org/stable/users/explain/colors/colormapnorms.html>`_.
'''
p = form.degree
zorder = {'zorder': -p if zorder is None else zorder}
marker_size = {
's': pointsize,
'edgecolor': background,
'linewidth': 2,
'marker': markerstyle
}
marker_color = {
'cmap': cmap,
'norm': norm,
}
no_arrowhead = {'headwidth': 0, 'headlength': 0, 'headaxislength': 0,}
linkpadding = {'edgecolor': background, 'linewidth': 4}
links = {
'scale_units': 'xy', 'scale': 1,
'width': linkwidth,
**no_arrowhead,
**linkpadding,
'cmap': cmap,
'norm': norm,
}
if p == 0:
f = axis.scatter(self.T, self.X, c=form[0], **zorder, **marker_size, **marker_color)
if p == 1:
# To get the horizontal links and vertical links to have the same coloring the simplest
# thing is to combine them into a single quiver. We'll just completely flatten the 1-form
# which puts all the 0-direction links first and then all the 1-direction links.
# So, we need two copies of their starting directions...
T = np.tile(self.T.flatten(), 2)
X = np.tile(self.X.flatten(), 2)
# ... and to say that the first half point in the 0 direction and the latter half in the 1 direction ...
U = np.concatenate((np.ones_like (self.T.flatten()), np.zeros_like(self.T.flatten())))
V = np.concatenate((np.zeros_like(self.T.flatten()), np.ones_like (self.T.flatten())))
# and then we can plot the whole form together.
f = axis.quiver(T, X, U, V, form.flatten(), **zorder, **links)
axis.scatter(self.T, self.X, color=background, **zorder, **marker_size)
if p == 2:
# Center (0,0) in the plot; imshow expects row-major so transpose.
data = push(form[0], ((self.nt-1) // 2, (self.nx-1) // 2)).transpose()
f = axis.imshow(data, **zorder, cmap=cmap,
origin='lower', extent=(min(self.t), max(self.t)+1, min(self.x), max(self.x)+1),
norm=norm,
)
axis.quiver(self.T, self.X, 1, 0, color='white', **zorder, **links)
axis.quiver(self.T, self.X, 0, 1, color='white', **zorder, **links)
axis.scatter(self.T, self.X, color=background, **zorder, **marker_size)
axis.xaxis.set_zorder(-p)
axis.yaxis.set_zorder(-p)
if label:
cbar = axis.figure.colorbar(f, ax=axis, **cbar_kw)
cbar.ax.set_title(label)
axis.set_xlim(min(self.t)-0.5, max(self.t)+1.5)
axis.set_ylim(min(self.x)-0.5, max(self.x)+1.5)
axis.set_xlabel('t')
axis.set_ylabel('x')
return f
import numba
from numba.experimental import jitclass
@jitclass([
('nt', numba.int64),
('nx', numba.int64),
('dims', numba.int64[:]),
('t', numba.int64[:]),
('x', numba.int64[:]),
])
class _Lattice2D:
r'''
A numba-accelerated collection of lattice functions.
.. warning::
NOT ALL LATTICES METHODS ARE INCLUDED; THEY MAY BE ADDED OVER TIME AS NEEDED.
Moreover, not all methods have the same signature due to limitations in numba.
Seriously this is to be used but rarely. Used only in :class:`~.worldline.ClassicWorm`.
.. note ::
Currently `numba jitclasses do not support classmethods <https://numba.readthedocs.io/en/stable/proposals/jit-classes.html>`_.
In particular, that makes they incompatible with
our HDF5 infrastructure; they cannot be made :class:`~.ReadWriteable`, for instance.
Therefore, they should be set as class members; you may need to reconstruct them.
'''
def __init__(self, dims):
self.nt = dims[0]
self.nx = dims[1]
self.dims = np.array(dims)
self.t = self._dimension(self.nt)
self.x = self._dimension(self.nx)
def _dimension(self, n):
return np.concatenate((
np.arange(0, n // 2 + 1, dtype=np.int64),
np.arange( - n // 2 + 1, 0, dtype=np.int64),
))
def mod(self, points=np.array([[]])):
r'''
.. warning ::
The return value is unpacked along each dimension.
This seemed to be a peculiar requirement of numba.
Parameters
----------
points: 2D np.array
An n×2 array of points to be modded.
Returns
-------
t: np.array
The first coordinate of each point modded into the lattice.
x: np.array
The second coordinate of each point modded into the lattice.
'''
flip = points.T
t_modded = np.mod(flip[0], self.nt)
x_modded = np.mod(flip[1], self.nx)
return self.t[t_modded], self.x[x_modded]
def neighboring_sites(self, here):
# east, north, west, south
return self.mod(here + np.array([[+1,0], [0,+1], [-1,0], [0,-1]]))
def neighboring_plaquettes(self, here):
# east, north, west, south
return self.mod(here + np.array([[0,-1], [+1,0], [0,+1], [-1,0]]))
def adjacent_links(self, form, site):
if form == 0:
# Links to the east, north, west, and south
t, x = self.neighboring_sites(site)
east = np.array([t[0], x[0]])
north= np.array([t[1], x[1]])
west = np.array([t[2], x[2]])
south= np.array([t[3], x[3]])
# n
# | x
# w-h-e ^
# | |
# s o-->t
return ((0, site [0], site [1]), # t link to the east
(1, site [0], site [1]), # x link to the north
(0, west [0], west [1]), # t link to the west
(1, south[0], south[1])) # x link to the south
if form == 2:
t, x = self.neighboring_plaquettes(site)
east = np.array([t[0], x[0]])
north= np.array([t[1], x[1]])
west = np.array([t[2], x[2]])
south= np.array([t[3], x[3]])
# n
# +-+ t
# w|h|e ^
# o-+ |
# s x<--o
return ((0, site [0], site [1]), # t link to the east
(1, north[0], north[1]), # x link to the north
(0, west [0], west [1]), # t link to the west
(1, site [0], site [1])) # x link to the south