Skip to content

Commit 3112ed0

Browse files
committed
La^2 for each a
1 parent 2ef5a46 commit 3112ed0

7 files changed

Lines changed: 204 additions & 101 deletions

links/canonical_momenta.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def __init__(self, U: GaugeConfiguration):
7676
"""
7777
self.Nc = U.Nc # number of colors of the group
7878
self.Ng = U.Ng # number of generators in the algebra
79+
self.n_links = U.n_links # number of gauge links on the lattice
7980
self.batchsize = U.batch_size # number of configurations
8081
self.tau = suN.get_generators(Nc=self.Nc, device=U.device, dtype=U.dtype)
8182

@@ -209,10 +210,28 @@ def La_chain_rule(self, f: typing.Callable, U: GaugeConfiguration):
209210
f_U = f(GaugeConfiguration(U + delta)) # f(U+delta)
210211
# NOTE: I can differentiate the sum over configurations
211212
# because f(U) acts configuration-wise
212-
f_U_flat = f_U.sum() + 0.0*1j # flattened view
213+
# f_U_flat = f_U.sum() + 0.0*1j # flattened view
213214
A = -1j * torch.einsum("aij,B...jk->B...aik", self.tau, U.as_subclass(torch.Tensor)) # d(e^{-i*omega*tau_a})/domega at omega==0
214215
dRef_dU = my_autograd(y=f_U.real, x=delta, grad_outputs=torch.ones_like(f_U.real), create_graph=True, retain_graph=True).unsqueeze(-3)
215216
dImf_dU = my_autograd(y=f_U.imag, x=delta, grad_outputs=torch.ones_like(f_U.imag), create_graph=True, retain_graph=True).unsqueeze(-3)
216217
df_domega = chain_rule_contributions(A=A, dRef_dU=dRef_dU, dImf_dU=dImf_dU).sum(dim=(-2,-1)) # summing over the color components
217218
La_f = -1j * df_domega
218219
return La_f
220+
221+
# def LaLa_chain_rule_EXPERIMENTAL(self, f: typing.Callable, U: GaugeConfiguration):
222+
# delta1 = torch.zeros(size=U.shape, dtype=U.dtype, device=U.device, requires_grad=True) # Note: delta should be complex
223+
# delta2 = torch.zeros(size=U.shape, dtype=U.dtype, device=U.device, requires_grad=True) # Note: delta should be complex
224+
# Up = U + delta1 + delta2
225+
# f_U = f(GaugeConfiguration(Up)) # f(U+delta)
226+
# A = -1j * torch.einsum("aij,B...jk->B...aik", self.tau, Up.as_subclass(torch.Tensor)) # d(e^{-i*omega*tau_a})/domega at omega==0
227+
# dRef_dU = my_autograd(y=f_U.real, x=delta, grad_outputs=torch.ones_like(f_U.real), create_graph=True, retain_graph=True).unsqueeze(-3)
228+
# dImf_dU = my_autograd(y=f_U.imag, x=delta, grad_outputs=torch.ones_like(f_U.imag), create_graph=True, retain_graph=True).unsqueeze(-3)
229+
# df_domega_ab = chain_rule_contributions(A=A, dRef_dU=dRef_dU, dImf_dU=dImf_dU).sum(dim=(-2,-1)) # summing over the color components
230+
# Laf_ab = -1j * df_domega
231+
# # print(dRef_dU.shape, Laf_ab.shape, delta.shape)
232+
# cosd_Laf = torch.einsum("...ij,...aij->...aij", torch.cos(delta.abs()), Laf_ab)
233+
# dReLaf_dU = my_autograd(y=cosd_Laf.real, x=delta, grad_outputs=torch.ones_like(cosd_Laf.real), create_graph=True, retain_graph=True).unsqueeze(-3)
234+
# dImLaf_dU = my_autograd(y=cosd_Laf.imag, x=delta, grad_outputs=torch.ones_like(cosd_Laf.imag), create_graph=True, retain_graph=True).unsqueeze(-3)
235+
# d2f_domega = chain_rule_contributions(A=A, dRef_dU=dReLaf_dU, dImf_dU=dImLaf_dU).sum(dim=(-2,-1)) # summing over the color components
236+
# LaLa_f = -1j * d2f_domega
237+
# return LaLa_f

links/canonical_momenta_squared.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ def get_La2_per_link(self, f: typing.Callable, U: GaugeConfiguration):
5555
f(..., e^{-i \\omega \\tau_a } U(x,\\mu), ...)|_{\\omega = 0}
5656
$$
5757
"""
58-
Nc = U.Nc # number of colors
59-
Ng = U.Ng # number of generators in the Lie algebra
60-
n_links = U.n_links # number of links
61-
batchsize = U.batch_size # number of configurations
58+
Nc = self.Nc # number of colors
59+
Ng = self.Ng # number of generators in the Lie algebra
60+
n_links = self.n_links # number of links
61+
batchsize = self.batchsize # number of configurations
6262
Id = torch.eye(Nc).to(device=U.device)
6363
Id_arr = Id.expand(n_links, Nc, Nc)
6464
omega = torch.tensor(0.0, requires_grad=True, dtype=U.real.dtype, device=U.device)
@@ -100,7 +100,7 @@ def f_i(i):
100100
#---
101101
La2_per_link.append(torch.stack(sum_La_squared, dim=0))
102102
#---
103-
return torch.stack(La2_per_link, dim=1)
103+
return -torch.stack(La2_per_link, dim=1)
104104

105105
def with_La_twice(self, f: typing.Callable, U: GaugeConfiguration) -> torch.Tensor:
106106
"""
@@ -210,12 +210,12 @@ def f_i(i):
210210
f_minus = make_perturbed_U(Va_minus[a,:,:]) # sum_i f(Va(-eps) . U_i)
211211

212212
dir_der = (f_plus - f_minus)/(2.0*eps) # 1st derivative found with finite difference
213-
laplacian_b = my_autograd(dir_der, omega, create_graph=False, retain_graph=True) # 2nd derivative through autodifferentiation
213+
laplacian_b = my_autograd(dir_der, omega, grad_outputs=torch.ones_like(dir_der), create_graph=False, retain_graph=True) # 2nd derivative through autodifferentiation
214214
sum_La_squared.append(laplacian_b)
215215
#---
216216
La2_per_link.append(torch.stack(sum_La_squared, dim=0))
217217
#---
218-
return torch.stack(La2_per_link, dim=1)
218+
return -torch.stack(La2_per_link, dim=1) # `-1` factor coming from the product of the 2 `-i` in front of the L_a
219219

220220

221221
def TODO_get_sum_La_squared_per_link_fast(self, a: int, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool, eps: float = 1e-8):

links/lie_derivatives.py

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import typing
1717

1818
import torch
19+
from torch._functorch.vmap import _flat_vmap
20+
from torch.distributions import laplace
21+
from torch.nn.functional import relu
1922

2023
import lattice_data_tools.links.suN as suN
2124
from lattice_data_tools.links.configuration import GaugeConfiguration, ColorMatrix
@@ -154,56 +157,65 @@ def L_a(self, a: int, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool
154157
return -1j*df_domega
155158

156159

157-
def La_squared_per_link(self, a: int, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool):
160+
def La_squared_per_link(self, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool):
158161
"""
159162
Returns ${ \\sum_{x,\\mu} L_a^2(x,\\mu) f(U) }$
160163
NOTE: the index `a` is NOT summed over.
161164
162165
`f(U)` should return a tensor of shape (batchsize,1)
163166
"""
167+
batchsize = U.batch_size # number of configurations
164168
Nc = U.Nc # number of colors
169+
Ng = U.Ng
165170
n_links = U.n_links # number of links
166-
tau_a = self.tau[a,:,:]
167-
d, M = torch.linalg.eigh(tau_a) # diagonalization of the generator tau_a
171+
#tau_a = self.tau[a,:,:]
172+
d, M = torch.linalg.eigh(self.tau) # diagonalization of the generator tau_a
168173
omega = torch.tensor(0.0, requires_grad=True, dtype=U.real.dtype, device=U.device)
169-
phase = omega * d
174+
phase = omega*d
170175
exp_iphase = torch.exp(-1j * phase)
171176
exp_iD = torch.diag_embed(exp_iphase)
172-
Va = (M @ exp_iD @ M.adjoint()) #.expand(n_links, Nc, Nc)
173-
batchsize = U.batch_size # number of configurations
177+
V = (M @ exp_iD @ M.adjoint()) #.expand(n_links, Nc, Nc)
174178
Id = torch.eye(Nc).to(device=U.device)
175179
Id_arr = Id.expand(n_links, Nc, Nc)
176-
sum_La_squared = []
177-
for b in range(batchsize):
178-
U_b = U[b,...].reshape(n_links, Nc, Nc)
179-
def f_i(i):
180-
e_i = torch.nn.functional.one_hot(i, n_links).to(dtype=U.dtype, device=U.device)
181-
Va_arr = Id_arr + torch.einsum("ab,i->iab", Va-Id, e_i)
182-
VaU_i = (Va_arr @ U_b).reshape(*(U[b,...].shape)).unsqueeze(0)
183-
f_VaU = f(GaugeConfiguration(VaU_i))/n_links
184-
return f_VaU
185-
#---
186-
# vectorize over all indices 0..N-1
187-
indices = torch.arange(n_links)
188-
sum_f = torch.vmap(f_i)(indices).sum()
180+
single_conf_shape = (1, *U.shape[1:])
181+
def f_bai(Ub, Va, i):
182+
Ub_flat = Ub.reshape(n_links, Nc, Nc)
183+
e_i = torch.nn.functional.one_hot(i, n_links).to(dtype=U.dtype, device=U.device)
184+
Va_arr = Id_arr + torch.einsum("ab,i->iab", Va-Id, e_i)
185+
VaU_i = (Va_arr @ Ub_flat).reshape(*(single_conf_shape))
186+
f_VaU = f(GaugeConfiguration(VaU_i))/n_links
187+
return f_VaU
188+
#---
189+
f_vmap = torch.func.vmap(
190+
torch.func.vmap(
191+
torch.func.vmap(
192+
f_bai,
193+
in_dims=(None, None, 0) # over link index
194+
),
195+
in_dims=(None, 0, None) # over generators
196+
),
197+
in_dims=(0, None, None) # over configurations
198+
)
199+
f_arr = f_vmap(U.as_subclass(torch.Tensor), V, torch.arange(n_links))
200+
sum_f = f_arr.sum()
201+
189202

190-
# \\sum_i \\partial_{x_i} f : directional derivative along (1,...,1)
191-
if f_is_real:
192-
dir_der = torch.autograd.grad(sum_f, omega, create_graph=True)[0]
193-
laplacian_b = torch.autograd.grad(dir_der, omega, create_graph=True)[0]
194-
else:
195-
Re_dir_der = torch.autograd.grad(sum_f.real, omega, create_graph=True)[0]
196-
Im_dir_der = torch.autograd.grad(sum_f.imag, omega, create_graph=True)[0]
197-
Re_laplacian_b = torch.autograd.grad(Re_dir_der, omega, create_graph=True)[0]
198-
Im_laplacian_b = torch.autograd.grad(Im_dir_der, omega, create_graph=True)[0]
199-
laplacian_b = Re_laplacian_b + 1j*Im_laplacian_b
200-
#---
201-
sum_La_squared.append(laplacian_b)
203+
# \\sum_i \\partial_{x_i} f : directional derivative along (1,...,1)
204+
if f_is_real:
205+
dir_der = torch.autograd.grad(sum_f, omega, create_graph=True)[0]
206+
laplacian_b = torch.autograd.grad(dir_der, omega, create_graph=True)[0]
207+
else:
208+
Re_dir_der = torch.autograd.grad(sum_f.real, omega, create_graph=True)[0]
209+
Im_dir_der = torch.autograd.grad(sum_f.imag, omega, create_graph=True)[0]
210+
Re_laplacian_b = torch.autograd.grad(Re_dir_der, omega, create_graph=True)[0]
211+
Im_laplacian_b = torch.autograd.grad(Im_dir_der, omega, create_graph=True)[0]
212+
laplacian_b = Re_laplacian_b + 1j*Im_laplacian_b
202213
#---
203-
return torch.stack(sum_La_squared, dim=0)
214+
La2_tensor = - laplacian_b # accounting for the two factors `i`
215+
return La2_tensor
204216

205217

206-
def La_squared_per_link_FD(self, a: int, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool, eps: float = 1e-8):
218+
def La_squared_per_link_FD(self, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool, eps: float = 1e-8):
207219
"""
208220
Returns ${ \\sum_{x,\\mu} L_a^2(x,\\mu) f(U) }$ via Finite Differences.
209221
NOTE: the index `a` is NOT summed over.
@@ -214,60 +226,65 @@ def La_squared_per_link_FD(self, a: int, f: typing.Callable, U: GaugeConfigurati
214226
`f(U)` should return a tensor of shape (batchsize,1)
215227
"""
216228
Nc = U.Nc
229+
Ng = U.Ng
217230
n_links = U.n_links
218-
tau_a = self.tau[a, :, :]
219-
batchsize = U.batch_size
220231
Id = torch.eye(Nc, dtype=U.dtype, device=U.device)
221232
Id_arr = Id.expand(n_links, Nc, Nc)
222233

223234
# Compute Va(+eps) and Va(-eps): the group elements e^{±i eps tau_a}
224235
# Use matrix exponential via diagonalization: tau_a = M D M†, e^{i w tau_a} = M e^{i w D} M†
225-
d, M = torch.linalg.eigh(tau_a)
236+
d, M = torch.linalg.eigh(self.tau)
226237

227-
def make_Va(omega: float):
238+
def make_V(omega: float):
228239
phase = omega * d
229240
exp_iD = torch.diag_embed(torch.exp(-1j * phase).to(dtype=U.dtype))
230241
return M @ exp_iD @ M.adjoint()
231242

243+
single_conf_shape = (1, *U.shape[1:])
244+
def f_bai(Ub, Va, i):
245+
Ub_flat = Ub.reshape(n_links, Nc, Nc)
246+
# Apply Va to link i, leaving all other links unchanged.
247+
e_i = torch.nn.functional.one_hot(i, n_links).to(dtype=U.dtype, device=U.device)
248+
Va_arr = Id_arr + torch.einsum("ab,i->iab", Va - Id, e_i)
249+
VaU_i = (Va_arr @ Ub_flat).reshape(*single_conf_shape)
250+
return f(GaugeConfiguration(VaU_i))/n_links/Ng
251+
#---
252+
253+
f_vmap = torch.func.vmap(
254+
torch.func.vmap(
255+
torch.func.vmap(
256+
f_bai,
257+
in_dims=(None, None, 0) # over link index
258+
),
259+
in_dims=(None, 0, None) # over generators
260+
),
261+
in_dims=(0, None, None) # over configurations
262+
)
263+
232264
omega = torch.tensor(0.0, requires_grad=True, dtype=U.real.dtype, device=U.device)
233-
Va_plus = make_Va(+eps+omega) # e^{+i eps tau_a}
234-
Va_minus = make_Va(-eps+omega) # e^{-i eps tau_a}
235-
236-
sum_La_squared = []
237-
238-
for b in range(batchsize):
239-
U_b = U[b, ...].reshape(n_links, Nc, Nc)
240-
shape_b = U[b, ...].shape
241-
242-
def make_perturbed_U(Va):
243-
"""Apply Va to link i, leaving all other links unchanged."""
244-
def f_i(i):
245-
e_i = torch.nn.functional.one_hot(i, n_links).to(dtype=U.dtype, device=U.device)
246-
Va_arr = Id_arr + torch.einsum("ab,i->iab", Va - Id, e_i)
247-
VaU_i = (Va_arr @ U_b).reshape(*shape_b).unsqueeze(0)
248-
return f(GaugeConfiguration(VaU_i))/n_links
249-
#---
250-
indices = torch.arange(n_links)
251-
return torch.vmap(f_i)(indices).sum() # sum over all links
252-
253-
f_plus = make_perturbed_U(Va_plus) # sum_i f(Va(+eps) . U_i)
254-
f_minus = make_perturbed_U(Va_minus) # sum_i f(Va(-eps) . U_i)
255-
# f_0 = f(GaugeConfiguration(U[b, ...].unsqueeze(0))) * n_links # n_links * f(U)
256-
257-
# Central difference: [f(+eps) - 2f(U) + f(-eps)] / eps^2
258-
# laplacian_b = (f_plus - 2 * f_0 + f_minus) / (eps ** 2)
259-
dir_der = (f_plus - f_minus)/(2.0*eps)
265+
Va_p1 = make_V(+eps +omega) # e^{+i eps tau_a}
266+
Va_m1 = make_V(-eps +omega) # e^{-i eps tau_a}
267+
Va_0 = make_V(+omega) # e^{-i eps tau_a}
268+
f_plus = f_vmap(U.as_subclass(torch.Tensor), Va_p1 , torch.arange(n_links)).sum() # sum_i f(Va(+eps) . U_i)
269+
f_minus = f_vmap(U.as_subclass(torch.Tensor), Va_m1, torch.arange(n_links)).sum() # sum_i f(Va(-eps) . U_i)
270+
# f_0 = f_vmap(U.as_subclass(torch.Tensor), Va_0, torch.arange(n_links)).sum() # sum_i f(Va(-eps) . U_i)
260271

261-
if f_is_real:
262-
laplacian_b = torch.autograd.grad(dir_der, omega)[0]
263-
else:
264-
Re_laplacian_b = torch.autograd.grad(dir_der.real, omega, create_graph=True)[0]
265-
Im_laplacian_b = torch.autograd.grad(dir_der.imag, omega)[0]
266-
laplacian_b = Re_laplacian_b + 1j*Im_laplacian_b
272+
# Central difference: [f(+eps) - 2f(U) + f(-eps)] / eps^2
273+
#laplacian_b = (f_plus - 2 * f_0 + f_minus) / (eps ** 2)
274+
# return laplacian_b
267275

268-
sum_La_squared.append(laplacian_b)
276+
dir_der = (f_plus - f_minus)/(2.0*eps)
277+
# dir_der = (f_plus - f_0)/eps
269278

270-
return torch.stack(sum_La_squared, dim=0)
279+
if f_is_real:
280+
laplacian_b = torch.autograd.grad(dir_der, omega)[0]
281+
else:
282+
Re_laplacian_b = torch.autograd.grad(dir_der.real, omega, create_graph=True)[0]
283+
Im_laplacian_b = torch.autograd.grad(dir_der.imag, omega)[0]
284+
laplacian_b = Re_laplacian_b + 1j*Im_laplacian_b
285+
286+
La2_tensor = - Ng * laplacian_b # accounting for the two factors `i`
287+
return La2_tensor
271288

272289

273290
def La_squared_per_link_FD_fast(self, a: int, f: typing.Callable, U: GaugeConfiguration, f_is_real: bool, eps: float = 1e-8):

machine_learning/lcnn.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ def gen_random_beta(self, N_out: int, seed: int):
184184
beta = torch.rand(*(d, N_out)).to(self.dtype_U).to(self.device_U)
185185
return beta
186186
#---
187+
def ibetaWah(self, W: LocallyGaugeCovariant, beta: torch.Tensor):
188+
""" Argument of the exponential in `self.exp_ibetaWah()` """
189+
# building the anti-hermitian part of W --> i*W_ah lies in the algebra su(N)
190+
W_ah_traceless = ColorMatrix(W).get_ah_traceless()
191+
arg_exp = torch.einsum("mi,...iab->...mab", 1j*beta, W_ah_traceless) # NOTE: arg_exp has to be hermitean --> we include the `1j`
192+
return arg_exp
193+
#---
187194
def exp_ibetaWah(self, W: LocallyGaugeCovariant, beta: torch.Tensor):
188195
"""
189196
Eq. 9 of https://arxiv.org/pdf/2012.12901
@@ -193,9 +200,7 @@ def exp_ibetaWah(self, W: LocallyGaugeCovariant, beta: torch.Tensor):
193200
W: N_ch locally transforming variables (obtained after L_conv()). shape: (batch, L1, ..., Ld, N_ch, Nc, Nc)
194201
beta: shape: (d,N_ch)
195202
"""
196-
# building the anti-hermitian part of W --> i*W_ah lies in the algebra su(N)
197-
W_ah_traceless = ColorMatrix(W).get_ah_traceless()
198-
arg_exp = torch.einsum("mi,...iab->...mab", 1j*beta, W_ah_traceless) # NOTE: arg_exp has to be hermitean --> we include the ""1j"
203+
arg_exp = self.ibetaWah(W=W, beta=beta)
199204
E = suN.get_exp_iA(arg_exp.to_tensor()) # eq. 9 of https://arxiv.org/pdf/2012.12901
200205
return LocallyGaugeCovariant(E)
201206
#---
@@ -245,6 +250,20 @@ def all_layers_with_CB_AND_Tr(self, U: GaugeConfiguration, omega_CB: torch.Tenso
245250
trace = suN.get_Tr(self.all_layers_with_CB(U=U, omega_CB=omega_CB,beta=beta))
246251
return trace
247252
#---
253+
def all_layers_Laf(self, U: GaugeConfiguration, omega_CB: torch.Tensor, beta: torch.Tensor):
254+
"""
255+
Network producing an output of shape (B, Ng, L1, ..., Ld, d)
256+
257+
Interpretation: for a given configuration `b` and fixed `a`, the outputs are the `tau_a L_a` applied to the function `f`.
258+
259+
"""
260+
U_PT = get_ParallelTransporters(U=U, K=self.K)
261+
W = self.get_W(U=U) # set of locally transforming variables
262+
Wprime = self.get_Wprime(U=U, W=W, U_PT=U_PT) # W' as in eq. III.11 of https://arxiv.org/pdf/2012.12901
263+
W_CB = self.L_CB(W=W, Wprime=Wprime, omega_CB=omega_CB) # W after eq. 18 of https://arxiv.org/pdf/2401.06481
264+
W_act = self.L_act(U=U, W=W_CB, act_fun=self.act_fun) # W after eq. 7 of https://arxiv.org/pdf/2012.12901
265+
EU = self.L_exp(U=U, W=W_act, beta=beta)
266+
W_res = self.get_W(U=EU)
248267
#---
249268

250269

test/test-canonical_momenta.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ def perf(fun, info: str):
128128
CM = CanonicalMomenta(U=U)
129129
momenta_exp = perf(lambda: CM.LaRa_with_exp(f=f, U=U, f_is_real=f_is_real), "L_a & R_a arr from exp()")
130130
momenta_cr = perf(lambda: CM.LaRa_chain_rule(f=f, U=U), "L_a & R_a arr from chain rule")
131-
131+
print(momenta_cr.shape)
132+
132133

133134
# La_chain_rule_compiled = torch.compile(lambda U_conf: CM.La_chain_rule(f=f, U=U_conf.as_subclass(torch.Tensor)))
134135
# La_chain_rule_compiled(U)

0 commit comments

Comments
 (0)