# AlienX 3D — spectral physics utilities (Taylor–Green vortex) # Extracted verbatim from the released training/eval code. import math import torch import torch.nn.functional as F def make_wavenumbers(G, device): kx = 2*math.pi*torch.fft.fftfreq(G, d=1.0/G, device=device) ky = 2*math.pi*torch.fft.fftfreq(G, d=1.0/G, device=device) kz = 2*math.pi*torch.fft.fftfreq(G, d=1.0/G, device=device) KX, KY, KZ = torch.meshgrid(kx, ky, kz, indexing='ij') K2 = KX**2 + KY**2 + KZ**2 K2[0, 0, 0] = 1.0 return KX, KY, KZ, K2 def projection(u_hat, v_hat, w_hat, KX, KY, KZ, K2): kd = KX*u_hat + KY*v_hat + KZ*w_hat return (u_hat - KX*kd/K2, v_hat - KY*kd/K2, w_hat - KZ*kd/K2) def nonlinear_term(u_hat, v_hat, w_hat, KX, KY, KZ, K2): u = torch.fft.ifftn(u_hat, dim=(-3, -2, -1)).real v = torch.fft.ifftn(v_hat, dim=(-3, -2, -1)).real w = torch.fft.ifftn(w_hat, dim=(-3, -2, -1)).real du_dx = torch.fft.ifftn(1j*KX*u_hat, dim=(-3, -2, -1)).real du_dy = torch.fft.ifftn(1j*KY*u_hat, dim=(-3, -2, -1)).real du_dz = torch.fft.ifftn(1j*KZ*u_hat, dim=(-3, -2, -1)).real dv_dx = torch.fft.ifftn(1j*KX*v_hat, dim=(-3, -2, -1)).real dv_dy = torch.fft.ifftn(1j*KY*v_hat, dim=(-3, -2, -1)).real dv_dz = torch.fft.ifftn(1j*KZ*v_hat, dim=(-3, -2, -1)).real dw_dx = torch.fft.ifftn(1j*KX*w_hat, dim=(-3, -2, -1)).real dw_dy = torch.fft.ifftn(1j*KY*w_hat, dim=(-3, -2, -1)).real dw_dz = torch.fft.ifftn(1j*KZ*w_hat, dim=(-3, -2, -1)).real ax = u*du_dx + v*du_dy + w*du_dz ay = u*dv_dx + v*dv_dy + w*dv_dz az = u*dw_dx + v*dw_dy + w*dw_dz ax_hat = torch.fft.fftn(ax, dim=(-3, -2, -1)) ay_hat = torch.fft.fftn(ay, dim=(-3, -2, -1)) az_hat = torch.fft.fftn(az, dim=(-3, -2, -1)) G = u_hat.shape[-1] kmax = G // 2 kmask = ((torch.abs(KX) <= 2*kmax/3) & (torch.abs(KY) <= 2*kmax/3) & (torch.abs(KZ) <= 2*kmax/3)) ax_hat, ay_hat, az_hat = ax_hat*kmask, ay_hat*kmask, az_hat*kmask ax_hat, ay_hat, az_hat = projection(ax_hat, ay_hat, az_hat, KX, KY, KZ, K2) return -ax_hat, -ay_hat, -az_hat def tgv_imex_step(u_hat, v_hat, w_hat, KX, KY, KZ, K2, nu, dt): nhx, nhy, nhz = nonlinear_term(u_hat, v_hat, w_hat, KX, KY, KZ, K2) vf = 1.0 + dt*nu*K2 return (u_hat + dt*nhx)/vf, (v_hat + dt*nhy)/vf, (w_hat + dt*nhz)/vf def generate_tgv_dataset(G=32, nu=0.01, T=10.0, dt=0.005, n_samples=74, device='cpu'): """Pseudo-spectral DNS of the Taylor-Green vortex. IMEX-Euler, Orszag 2/3 de-aliasing, Leray projection every step. Returns [n_samples, G, G, G, 3] velocity snapshots.""" x = torch.arange(G, device=device, dtype=torch.float32) * (2*math.pi/G) X, Y, Z = torch.meshgrid(x, x, x, indexing='ij') u = torch.sin(X)*torch.cos(Y)*torch.cos(Z) v = -torch.cos(X)*torch.sin(Y)*torch.cos(Z) w = torch.zeros_like(u) u_hat = torch.fft.fftn(u, dim=(-3, -2, -1)) v_hat = torch.fft.fftn(v, dim=(-3, -2, -1)) w_hat = torch.fft.fftn(w, dim=(-3, -2, -1)) KX, KY, KZ, K2 = make_wavenumbers(G, device) snaps = [] total = int(T/dt) save_every = max(1, total//n_samples) for step in range(total): u_hat, v_hat, w_hat = tgv_imex_step(u_hat, v_hat, w_hat, KX, KY, KZ, K2, nu, dt) u_hat, v_hat, w_hat = projection(u_hat, v_hat, w_hat, KX, KY, KZ, K2) if torch.isnan(u_hat).any(): return None if step % save_every == 0 and len(snaps) < n_samples: snaps.append(torch.stack([ torch.fft.ifftn(u_hat, dim=(-3, -2, -1)).real.cpu(), torch.fft.ifftn(v_hat, dim=(-3, -2, -1)).real.cpu(), torch.fft.ifftn(w_hat, dim=(-3, -2, -1)).real.cpu(), ], dim=-1)) return torch.stack(snaps, dim=0) def velocity_to_vorticity(v, KX, KY, KZ): u = torch.fft.fftn(v[..., 0], dim=(-3, -2, -1)) vv = torch.fft.fftn(v[..., 1], dim=(-3, -2, -1)) w = torch.fft.fftn(v[..., 2], dim=(-3, -2, -1)) wx = torch.fft.ifftn(1j*(KY*w - KZ*vv), dim=(-3, -2, -1)).real wy = torch.fft.ifftn(1j*(KZ*u - KX*w), dim=(-3, -2, -1)).real wz = torch.fft.ifftn(1j*(KX*vv - KY*u), dim=(-3, -2, -1)).real return torch.stack([wx, wy, wz], dim=-1) def vorticity_to_velocity(omega, KX, KY, KZ, K2): ox = torch.fft.fftn(omega[..., 0], dim=(-3, -2, -1)) oy = torch.fft.fftn(omega[..., 1], dim=(-3, -2, -1)) oz = torch.fft.fftn(omega[..., 2], dim=(-3, -2, -1)) ux_hat = 1j*(KY*oz - KZ*oy)/K2 uy_hat = 1j*(KZ*ox - KX*oz)/K2 uz_hat = 1j*(KX*oy - KY*ox)/K2 ux_hat[..., 0, 0, 0] = 0 uy_hat[..., 0, 0, 0] = 0 uz_hat[..., 0, 0, 0] = 0 return torch.stack([ torch.fft.ifftn(ux_hat, dim=(-3, -2, -1)).real, torch.fft.ifftn(uy_hat, dim=(-3, -2, -1)).real, torch.fft.ifftn(uz_hat, dim=(-3, -2, -1)).real, ], dim=-1) def scalar_derivatives(k_grid, KX, KY, KZ): """One FFT call -> gradient + 6 Hessian components of the scalar field.""" B, G_, _, _ = k_grid.shape k_hat = torch.fft.fftn(k_grid, dim=(-3, -2, -1)) ops = torch.stack([1j*KX, 1j*KY, 1j*KZ, -(KX**2), -(KY**2), -(KZ**2), -(KX*KY), -(KX*KZ), -(KY*KZ)], dim=0) d = torch.fft.ifftn(ops.unsqueeze(1)*k_hat.unsqueeze(0), dim=(-3, -2, -1)).real d = d.permute(1, 2, 3, 4, 0).reshape(B, -1, 9) gv = d[..., :3] gm = gv.norm(dim=-1) k_flat = k_grid.reshape(B, -1) H_xx, H_yy, H_zz, H_xy, H_xz, H_yz = d[..., 3:].unbind(-1) row0 = torch.stack([H_xx, H_xy, H_xz], dim=-1) row1 = torch.stack([H_xy, H_yy, H_yz], dim=-1) row2 = torch.stack([H_xz, H_yz, H_zz], dim=-1) H = torch.stack([row0, row1, row2], dim=-2) return k_flat, gm, gv, H def kinetic_energy(v): return 0.5 * (v**2).sum(dim=-1).mean(dim=(-3, -2, -1)) def enstrophy(v, KX, KY, KZ): om = velocity_to_vorticity(v, KX, KY, KZ) return 0.5 * (om**2).sum(dim=-1).mean(dim=(-3, -2, -1)) def divergence_spectral(v, KX, KY, KZ): u = torch.fft.fftn(v[..., 0], dim=(-3, -2, -1)) vv = torch.fft.fftn(v[..., 1], dim=(-3, -2, -1)) w = torch.fft.fftn(v[..., 2], dim=(-3, -2, -1)) return torch.fft.ifftn(1j*(KX*u + KY*vv + KZ*w), dim=(-3, -2, -1)).real def rms_velocity(v): return torch.sqrt((v**2).sum(dim=-1).mean(dim=(1, 2, 3)))