Skip to content

Commit fddd294

Browse files
committed
implementing Lie derivatives. TODO: batching
1 parent 43b81ef commit fddd294

9 files changed

Lines changed: 348 additions & 186 deletions

File tree

links/configuration.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ class ColorMatrix(torch.Tensor):
1414
Tensor representing a set of color matrices:
1515
(..., Nc, Nc)
1616
"""
17-
@staticmethod
18-
def __new__(cls, x, *args, **kwargs):
19-
instance = torch.Tensor._make_subclass(cls, x)
20-
instance._Nc = instance.shape[-1]
21-
return instance
17+
# @staticmethod
18+
# def __new__(cls, x, *args, **kwargs):
19+
# instance = torch.Tensor._make_subclass(cls, x)
20+
# return instance
2221

2322
def __init__(self, x, *args, **kwargs):
24-
pass
23+
self._data = x
24+
self._Nc = x.shape[-1] # read from x, not instance
2525

2626
def validate(self):
2727
"""Check that it is actually a set of color matrice of size Nc \\times Nc"""
@@ -104,19 +104,18 @@ class GaugeConfiguration(ColorMatrix):
104104
Tensor representing a gauge configuration of links (in the fundamental representation) with shape:
105105
(B, L1, ..., Ld, d, Nc, Nc)
106106
"""
107-
@staticmethod
108-
def __new__(cls, x, *args, **kwargs):
109-
instance = super().__new__(cls, x, *args, **kwargs)
110-
instance._batch_size = instance.shape[0]
111-
instance._lattice_shape = instance.shape[1:-3]
112-
instance._d = instance.shape[1 + len(instance._lattice_shape)]
113-
instance._Nc = instance.shape[-1]
114-
instance.validate()
115-
return instance
107+
# @staticmethod
108+
# def __new__(cls, x, *args, **kwargs):
109+
# instance = torch.Tensor._make_subclass(cls, x)
110+
# return instance
116111

117112
def __init__(self, x, *args, **kwargs):
118-
pass
119-
113+
super().__init__(x, *args, **kwargs)
114+
self._batch_size = x.shape[0]
115+
self._lattice_shape = x.shape[1:-3]
116+
self._d = x.shape[-3]
117+
self.validate()
118+
120119
def validate(self):
121120
"""Check that shape == (B, L1, ..., Ld, d, Nc, Nc)"""
122121
expected = (self.batch_size, *self.lattice_shape, self.n_dims, self.Nc, self.Nc)
@@ -167,7 +166,10 @@ def from_hotstart(batchsize: int, L_mu: typing.List[int], Nc: int, seed: int, d
167166
"""
168167
d = len(L_mu)
169168
shape = (batchsize, *L_mu, d, Nc, Nc)
170-
return GaugeConfiguration(suN.get_hotstart(shape=shape, seed=seed, dtype=dtype, device=device, requires_grad=requires_grad))
169+
U_tensor = suN.get_hotstart(shape=shape, seed=seed, dtype=dtype, device=device, requires_grad=requires_grad)
170+
U = GaugeConfiguration(U_tensor)
171+
return U
172+
171173

172174
def hotstart(self, seed: int) -> None:
173175
suN.apply_hotstart(U=self, seed=seed)
@@ -243,17 +245,21 @@ class LocallyGaugeCovariant(ColorMatrix):
243245
244246
shape: (B, L1, ..., Ld, N_obs, Nc, Nc)
245247
"""
246-
@staticmethod
247-
def __new__(cls, x, *args, **kwargs):
248-
instance = super().__new__(cls, x, *args, **kwargs)
249-
instance._batch_size = instance.shape[0]
250-
instance._lattice_shape = instance.shape[1:-3]
251-
instance._N_obs = instance.shape[-3]
252-
instance.validate()
253-
return instance
248+
# @staticmethod
249+
# def __new__(cls, x, *args, **kwargs):
250+
# instance = super().__new__(cls, x, *args, **kwargs)
251+
# instance._batch_size = instance.shape[0]
252+
# instance._lattice_shape = instance.shape[1:-3]
253+
# instance._N_obs = instance.shape[-3]
254+
# instance.validate()
255+
# return instance
254256

255257
def __init__(self, x, *args, **kwargs):
256-
pass
258+
super().__init__(x, *args, **kwargs)
259+
self._batch_size = self.shape[0]
260+
self._lattice_shape = self.shape[1:-3]
261+
self._N_obs = self.shape[-3]
262+
self.validate()
257263

258264
def validate(self):
259265
"""Check that shape == (B, L1, ..., Ld, d, Nc, Nc)"""

links/lie_derivatives.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Implementation of Lie derivatives on functions of the gauge links,
3+
using autodifferentiation:
4+
5+
$${ L_a f(U) = -i\\frac{d}{d \\omega} f( e^{-i \\omega \\tau_a } U ) |_{\\omega = 0} }$$
6+
$${ R_a f(U) = +i\\frac{d}{d \\omega} f( U e^{-i \\omega \\tau_a } ) |_{\\omega = 0} }$$
7+
8+
The $L_a$ and $R_a$ are the canonical momenta associated to the link $U$,
9+
and satisfy:
10+
11+
$${ [L_a , U] = - \\tau_a U }$$
12+
$${ [R_a , U] = + U \\tau_a }$$
13+
14+
"""
15+
16+
import typing
17+
import torch
18+
from torch.func import jvp
19+
20+
import lattice_data_tools.links.suN as suN
21+
from lattice_data_tools.links.configuration import GaugeConfiguration, ColorMatrix
22+
23+
24+
25+
class LieDerivatives:
26+
"""
27+
Class for the calculation of the Lie derivatives (canonical momenta)
28+
"""
29+
def __init__(self, U: GaugeConfiguration):
30+
"""
31+
Initialization of tensors needed
32+
33+
Main idea: this object is initialized before the training,
34+
and allows to compute efficiently the derivatives with respect to $\\omega_a(x,\\mu)$ in $0$.
35+
For instance:
36+
37+
$${ L_a(x,\\mu) f(..., U(x,\\mu), ...) = \\frac{d}{d\\omega} f(..., V_a U(x,\\mu), ...)|_{\\omega=0} }$$
38+
39+
where ${ V_a = exp(-i \\omega_a(x,\\mu) \\tau_a) }$
40+
41+
Input:
42+
U: GaugeConfiguration object, used to infer the shape of `omega`, `dtype` and `device`.
43+
44+
where `Ng` is the number of generators of `SU(Nc)`.
45+
"""
46+
self.Nc = U.Nc # number of colors of the group
47+
self.Ng = U.Ng # number of generators in the algebra
48+
self.omega_glob = torch.zeros(size=(1,), dtype=U.dtype, device=U.device, requires_grad=True)
49+
omega_shape = (*U.shape[0:-2],) #, self.Ng) # (batch, x,\\mu) indices
50+
# self.omega = torch.zeros(omega_shape, dtype=U.dtype, device=U.device, requires_grad=True)
51+
# self.omega = self.omega_glob.expand(size=omega_shape) #
52+
self.omega = torch.zeros(size=omega_shape, dtype=U.dtype, device=U.device, requires_grad=True)
53+
self.tau = suN.get_generators(Nc=self.Nc, device=U.device, dtype=U.dtype)
54+
# omega_tau = torch.einsum("...a,aij->...aij", self.omega, tau)
55+
#self.V = suN.get_exp_iA(A = - omega_tau) # exp(-i*omega*tau_a)
56+
self.V = [suN.get_exp_iA(A = - torch.einsum("...,ij->...ij", self.omega, self.tau[a,:,:])) for a in range(self.Ng)] # exp(-i*omega*tau_a)
57+
#---
58+
59+
def L_a(self, a: int, f: typing.Callable, U: GaugeConfiguration):
60+
d, M = torch.linalg.eigh(self.tau[a,:,:].expand(*self.omega.shape, self.Nc, self.Nc))
61+
phase = self.omega.unsqueeze(-1) * d
62+
exp_iD = torch.diag_embed(torch.exp(1j*phase)) #.type(M.type()) # exp(d_k) for each eigenvalue d_k
63+
Va = M @ exp_iD @ M.adjoint() # U = M exp(iD) M^\\dagger
64+
Va_U = GaugeConfiguration(Va @ U)
65+
f_VaU = torch.Tensor(f(Va_U))
66+
for i in range(200):
67+
df_domega_Re = torch.autograd.grad(
68+
f_VaU.view(-1)[i].real,
69+
self.omega,
70+
retain_graph=True
71+
)[0]
72+
df_domega_Im = torch.autograd.grad(
73+
f_VaU.view(-1)[i].imag,
74+
self.omega,
75+
retain_graph=True
76+
)[0]
77+
78+
print(i, df_domega_Re.shape, df_domega_Im.shape)
79+
80+
print(d.shape, self.omega.unsqueeze(-1).shape)
81+
82+
83+
V_a = suN.get_exp_iA(A = -torch.einsum("...,ij->...ij", omega_expanded, self.tau[a]))
84+
print(torch.autograd.grad(V_a.view(-1)[0].real, omega)[0])
85+
# Now omega -> V_a -> f_VaU is one connected graph
86+
print("ciao", f_VaU[0,0].shape, f_VaU[0,0].real.requires_grad)
87+
print(torch.autograd.grad(f_VaU[0,0].real, omega)[0])
88+
f_real = torch.view_as_real(f_VaU[0,:])[..., 0] # extracts real part, grad-safe
89+
#grad = torch.autograd.grad(f_real, omega)[0]
90+
#grad = torch.autograd.grad(f_VaU[0,:], omega)[0]
91+
#print(grad)
92+
93+
# print(self.V[a].shape)
94+
# f_VaU = f(self.V[a] @ U)
95+
# print(f_VaU[0,:].requires_grad, self.omega_glob.shape)
96+
# # f_VaU has shape (batchsize, 1)
97+
# # self.omega has shape (batchsize,L1,...,Ld,d)
98+
# print(f_VaU[0,:].shape, self.omega_glob.shape)
99+
# print(torch.autograd.grad(f_VaU[0,:].real, self.omega_glob)[0])
100+
# quit()
101+
quit()
102+
103+
return f_a
104+
105+
106+
107+
def R_a(self, f: typing.Callable[[GaugeConfiguration], ColorMatrix], U: GaugeConfiguration):
108+
pass
109+
# f_prime_values = lambda V: f(U @ V)
110+
# df_domega = componentwise_autodiff(y=f_prime_values, x=self.omega)
111+
# return +1j*df_domega
112+
113+
114+

links/loops.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@
33
from lattice_data_tools.links.configuration import GaugeConfiguration, LocallyGaugeCovariant
44

55
class WilsonLoopsGenerator:
6-
def __init__(self, U: GaugeConfiguration):
7-
"""
8-
U: batch of gauge configurations U^{(b)}(x, mu) as Nc \\times Nc matrices, with x=(x_1,...x_d).
9-
It is passed as a multi-dimensional array U[b, L1,L2,...,Ld, d, Nc, Nc],
10-
where N_c is the number of colors.
11-
"""
12-
self.U = U
13-
#---
14-
def plaquettes(self):
6+
"""
7+
Generation of Wilson loops from the gauge configuration.
8+
9+
U: batch of gauge configurations U^{(b)}(x, mu) as Nc \\times Nc matrices, with x=(x_1,...x_d).
10+
It is passed as a multi-dimensional array U[b, L1,L2,...,Ld, d, Nc, Nc],
11+
where N_c is the number of colors.
12+
"""
13+
@staticmethod
14+
def plaquettes(U: GaugeConfiguration):
1515
"""
1616
Plaquettes (as loops, no trace or real part), assuming periodic boundary conditions on a L^d lattice.
1717
1818
Returns: tensor of shape (batch_size, L1,...,Ld, N_plaq, Nc, Nc)
1919
"""
20-
assert(self.U.is_complex()) # check that the gauge configuration is complex-valued
21-
d = self.U.shape[-3] # number of dimensions of the lattice
20+
assert(U.is_complex()) # check that the gauge configuration is complex-valued
21+
d = U.shape[-3] # number of dimensions of the lattice
2222
plaqs = []
2323
# loop over the positive-oriented plaquettes
2424
for mu in range(d):
2525
for nu in range(mu + 1, d):
26-
U_mu = self.U[..., mu, :, :] # U_\\mu(\cdot) : all links along the direction \\mu
27-
U_nu = self.U[..., nu, :, :] # U_\\nu(\cdot) : all links along the direction \\nu
26+
U_mu = U[..., mu, :, :] # U_\\mu(\cdot) : all links along the direction \\mu
27+
U_nu = U[..., nu, :, :] # U_\\nu(\cdot) : all links along the direction \\nu
2828
# Spatial dim \\mu is dim \\mu+1 in the full tensor (batch at 0)
2929
U_mu_fwd = torch.roll(U_mu, -1, dims=1+nu) # U_\\mu(x + \\nu): shifting **backwards**
3030
U_nu_fwd = torch.roll(U_nu, -1, dims=1+mu) # U_\\nu(x + \\mu): shifting **backwards**
@@ -34,23 +34,28 @@ def plaquettes(self):
3434
plaqs = LocallyGaugeCovariant(torch.stack(plaqs, dim=-3))
3535
return plaqs
3636
#---
37-
def Polyakov_loops(self):
37+
@staticmethod
38+
def Polyakov_loops(U: GaugeConfiguration):
3839
"""
3940
Polyakov loops for each direction mu.
4041
4142
Returns: A tensor of shape (batch, L1, ..., Ld, d, Nc, Nc)
4243
"""
43-
assert(self.U.is_complex())
44-
d = self.U.shape[-3] # number of dimensions
45-
lattice_shape = self.U.shape[1:-3] # shape of the lattice points grid
46-
Poly = self.U.clone() # copy of the links. In the loop, each is extended to its Polyakov loop
44+
assert(U.is_complex())
45+
d = U.shape[-3] # number of dimensions
46+
lattice_shape = U.shape[1:-3] # shape of the lattice points grid
47+
poly_list = []
4748
for mu in range(d):
4849
L_mu = lattice_shape[mu] # extension of the lattice over the \\mu-th direction
50+
P = U[..., mu, :, :]
4951
for k in range(1, L_mu):
50-
Poly[..., mu, :, :] @= torch.roll(self.U, -k, dims=1+mu)[..., mu, :,:] # P_\\mu(x) --> P_\\mu(x)*U_\\mu(x+mu)
51-
#-------
52+
P @= torch.roll(U, -k, dims=1+mu)[..., mu, :,:] # P_\\mu(x) --> P_\\mu(x)*U_\\mu(x+mu)
53+
#---
54+
poly_list.append(P)
55+
#---
56+
Poly = torch.stack(poly_list, dim=-3)
5257
return LocallyGaugeCovariant(Poly)
53-
#---
58+
5459

5560

5661

0 commit comments

Comments
 (0)