|
| 1 | +""" |
| 2 | +The compilation woth torch.compile requires that I use all real numbers to be effective. |
| 3 | +This file contains a minimal implementation of the canonical momenta, |
| 4 | +for a function f(U) that is non-trivial in gauge configuration of links. |
| 5 | +The latter is casted into real by extending the last dimension to contain real and imaginary part |
| 6 | +
|
| 7 | +""" |
| 8 | + |
| 9 | +import sys |
| 10 | +sys.path.append("../../") |
| 11 | + |
| 12 | +import time |
| 13 | +import torch |
| 14 | +import typing |
| 15 | +#import warnings |
| 16 | +#warnings.filterwarnings("always") |
| 17 | +import torch._dynamo |
| 18 | +torch._dynamo.config.verbose = True # prints every graph break with reason |
| 19 | + |
| 20 | + |
| 21 | +from lattice_data_tools.links.configuration import GaugeConfiguration |
| 22 | +import lattice_data_tools.links.suN as suN |
| 23 | +from lattice_data_tools.autodifferentiation.with_torch_func_grad import get_compiled_function |
| 24 | + |
| 25 | + |
| 26 | +def complex2ri(x): |
| 27 | + return torch.stack([x.real,x.imag], dim=-1) |
| 28 | + |
| 29 | +def adjoint(M): |
| 30 | + M_T = torch.transpose(M, -3, -2) |
| 31 | + M_H = torch.stack([M_T[...,0], -M_T[...,1]], dim=-1) |
| 32 | + return M_H |
| 33 | + |
| 34 | +def complex_matmul(A, B): |
| 35 | + ReA, ImA = A[..., 0], A[..., 1] |
| 36 | + ReB, ImB = B[..., 0], B[..., 1] |
| 37 | + Re_AB = ReA@ ReB - ImA @ ImB |
| 38 | + Im_AB = ReA@ ImB + ImA @ ReB |
| 39 | + AB = torch.stack([Re_AB, Im_AB], dim=-1) |
| 40 | + return AB |
| 41 | + |
| 42 | + |
| 43 | +class La_Generator: |
| 44 | + """ |
| 45 | + Object generating the momenta L_a, for each a and each link in the configuration, and each configuration |
| 46 | +
|
| 47 | + The output is a lambda function (potentially compiled with `toch.compile`), which takes U in the usual shape (it is flattened iternally) |
| 48 | +
|
| 49 | + """ |
| 50 | + def __init__(self, f: typing.Callable, U: GaugeConfiguration, do_compile: bool): |
| 51 | + """ |
| 52 | + Initialize the lambda function for the La^2, |
| 53 | + using the function, |
| 54 | + an example gauge configuration (for the number of links). U.shape==(batchsize, n_links) |
| 55 | + and when `do_compile==True` it is compiled |
| 56 | + """ |
| 57 | + #assert(len(U.shape) == 2) # (batchsize, n_var) |
| 58 | + batchsize = U.shape[0] # number of configurations |
| 59 | + n_links = U.n_links # number of links |
| 60 | + Nc = U.Nc # number of colors |
| 61 | + Ng = U.Ng # number of generators of the Lie algebra |
| 62 | + lattice_shape = U.lattice_shape |
| 63 | + device = U.device |
| 64 | + dtype = U.dtype |
| 65 | + |
| 66 | + # diagonalization of the tau_a |
| 67 | + tau = suN.get_generators(Nc=Nc, device=device, dtype=dtype) |
| 68 | + tau_eigh_complex = torch.linalg.eigh(tau) |
| 69 | + tau_eigh = (tau_eigh_complex[0], complex2ri(tau_eigh_complex[1])) |
| 70 | + |
| 71 | + Id = torch.stack( |
| 72 | + [ |
| 73 | + torch.eye(Nc, device=U.device, dtype=U.real.dtype), |
| 74 | + torch.zeros(Nc, Nc, device=U.device, dtype=U.real.dtype) |
| 75 | + ], dim=-1) |
| 76 | + Id_arr = Id.expand(n_links, Nc, Nc, 2) |
| 77 | + single_conf_shape = (1,)+U.shape[1:]+(2,) |
| 78 | + def f_shift(tau_a_eigh, Ub, eps, i): |
| 79 | + # d, M = tau_eigh[a] |
| 80 | + d, M = tau_a_eigh # torch.linalg.eigh(tau_a) |
| 81 | + exp_iD = torch.stack( |
| 82 | + [ |
| 83 | + torch.diag_embed(torch.cos(eps * d)), |
| 84 | + torch.diag_embed(torch.sin(eps * d)) |
| 85 | + ], |
| 86 | + dim=-1) |
| 87 | + Va = complex_matmul(complex_matmul(M, exp_iD), adjoint(M)) |
| 88 | + ei = (torch.arange(n_links, device=Ub.device) == i).to(Ub.dtype) |
| 89 | + Va_arr = Id_arr + torch.einsum("abC,i->iabC", Va-Id, ei) |
| 90 | + VaU_i = complex_matmul(Va_arr, Ub).reshape(*single_conf_shape) |
| 91 | + res = f(VaU_i) # shape==(1,1) |
| 92 | + return res[0,:] |
| 93 | + def Re_f_shift(tau_a_eigh, Ub, eps, i): |
| 94 | + return f_shift(tau_a_eigh, Ub, eps, i)[0] |
| 95 | + def Im_f_shift(tau_a_eigh, Ub, eps, i): |
| 96 | + return f_shift(tau_a_eigh, Ub, eps, i)[1] |
| 97 | + |
| 98 | + Re_df = torch.func.grad(Re_f_shift, argnums=2) # abstract gradient object |
| 99 | + Im_df = torch.func.grad(Im_f_shift, argnums=2) # abstract gradient object |
| 100 | + |
| 101 | + eps = torch.tensor(0.0, device=device, dtype=U.real.dtype) |
| 102 | + idx_links = torch.arange(n_links, device=device) |
| 103 | + # idx_generators = torch.arange(Ng, device=device) |
| 104 | + La_f_shape = (batchsize,Ng,*(U.shape[1:-2])) |
| 105 | + |
| 106 | + def get_compiled(df_i): |
| 107 | + # vmap can parallelize only along dimension with the same size |
| 108 | + df_i_vmapped = torch.func.vmap( |
| 109 | + torch.func.vmap( |
| 110 | + torch.func.vmap( |
| 111 | + df_i, |
| 112 | + in_dims=(None,None,None, 0) # parallelizing only over the variable index --> \\partial_{eps_i}^2 f(V_eps @ U) |
| 113 | + ), |
| 114 | + in_dims=(0,None,None,None) # parallelize along generators a=1,...,Ng |
| 115 | + ), |
| 116 | + in_dims=(None,0,None,None), # parallelizing only over the batch index f(x1^{i}, x2^{i},...) |
| 117 | + ) |
| 118 | + # df_i_vmapped = torch.func.vmap( # over batch |
| 119 | + # torch.func.vmap( # over generators |
| 120 | + # torch.func.vmap(df_i, # over link index i |
| 121 | + # in_dims=(None, None, None, 0) |
| 122 | + # ), |
| 123 | + # in_dims=(0, None, None, None) |
| 124 | + # ), |
| 125 | + # in_dims=(None, 0, None, None) |
| 126 | + # ) |
| 127 | + |
| 128 | + def uncompiled_df(U_arr): |
| 129 | + U_flat = U_arr.view(batchsize,-1,Nc,Nc,2) |
| 130 | + res = df_i_vmapped(tau_eigh, U_flat, eps, idx_links) |
| 131 | + return res.reshape(La_f_shape) |
| 132 | + |
| 133 | + U_tens = complex2ri(U.as_subclass(torch.Tensor)) |
| 134 | + dummy = uncompiled_df(U_tens) |
| 135 | + if do_compile: |
| 136 | + compiled_df_i = get_compiled_function(uncompiled_df, U_tens) |
| 137 | + else: |
| 138 | + compiled_df_i = uncompiled_df |
| 139 | + #--- |
| 140 | + return compiled_df_i |
| 141 | + |
| 142 | + compiled_Re_df = get_compiled(Re_df) |
| 143 | + compiled_Im_df = get_compiled(Im_df) |
| 144 | + # including the factor "i": -i*d/d\\epsilon |
| 145 | + self._df_function = lambda U: -1j*(compiled_Re_df(U) + 1j*compiled_Im_df(U)) |
| 146 | + |
| 147 | + @property |
| 148 | + def df_function(self): |
| 149 | + return self._df_function |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | +def perf(fun, info: str): |
| 155 | + torch.cuda.synchronize() |
| 156 | + t1 = time.time() |
| 157 | + res = fun() |
| 158 | + torch.cuda.synchronize() |
| 159 | + t2 = time.time() |
| 160 | + print(f"dt ({info}): {t2-t1} sec.") |
| 161 | + return res |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | + |
| 166 | +def f(U_ri): |
| 167 | + n = len(U_ri.shape) |
| 168 | + # U_xp1 = torch.roll(U_ri, shifts=1, dims=0) |
| 169 | + # res = (U_xp1*U_ri).sum(dim=tuple(torch.arange(1,n-1))) |
| 170 | + res = (U_ri).sum(dim=tuple(torch.arange(1,n-1))) |
| 171 | + return res |
| 172 | + |
| 173 | +B = 5 |
| 174 | +L_mu = [2,2] |
| 175 | +Nc = 3 |
| 176 | +#device = torch.device("cpu") |
| 177 | +device = torch.device("cpu") |
| 178 | + |
| 179 | +U = GaugeConfiguration.from_hotstart( |
| 180 | + batchsize=B, L_mu=L_mu, Nc=Nc, |
| 181 | + seed=20260501, dtype=torch.complex128, device=device, |
| 182 | + requires_grad=False |
| 183 | +) |
| 184 | + |
| 185 | +U_tens = U.as_subclass(torch.Tensor) |
| 186 | +U_ri = complex2ri(U_tens) |
| 187 | + |
| 188 | + |
| 189 | +LaG = La_Generator(f=f, U=U, do_compile=False) |
| 190 | + |
| 191 | +N = 10 |
| 192 | + |
| 193 | +t0 = time.time() |
| 194 | +for i in range(N): |
| 195 | + _ = LaG.df_function(U=U_ri) |
| 196 | +t1 = time.time() |
| 197 | +print((t1-t0)/N) |
| 198 | + |
| 199 | +LaG_compiled = La_Generator(f=f, U=U, do_compile=True) |
| 200 | +t0 = time.time() |
| 201 | +for i in range(N): |
| 202 | + _ = LaG_compiled.df_function(U=U_ri) |
| 203 | +t1 = time.time() |
| 204 | +print((t1-t0)/N) |
| 205 | + |
| 206 | + |
| 207 | +#La_arr_vmap_compiled = perf(lambda: LaG_compiled.df_function(U=U_ri), "La compiled") |
| 208 | +# #print(La_arr_vmap) |
| 209 | + |
| 210 | + |
0 commit comments