|
| 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 | + |
0 commit comments