1616import typing
1717
1818import torch
19+ from torch ._functorch .vmap import _flat_vmap
20+ from torch .distributions import laplace
21+ from torch .nn .functional import relu
1922
2023import lattice_data_tools .links .suN as suN
2124from 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 ):
0 commit comments