x_ft = antorcha.fft.rfft2(x) out_ft = torch.zeros( tamaño_lote, self.out_channels, x.size(-2), x.size(-1) // 2 + 1, dtype=torch.cfloat, dispositivo=x.dispositivo ) out_ft[:, :, :self.modes1, :self.modes2] = \ self.compl_mul2d(x_ft[:, :, :self.modes1, :self.modes2]pesos propios1) out_ft[:, :, -self.modes1:, :self.modes2] = \ self.compl_mul2d(x_ft[:, :, -self.modes1:, :self.modes2]self.weights2) x = torch.fft.irfft2(out_ft, s=(x.size(-2), x.size(-1))) return x class FNOBlock(nn.Module): “”” Bloque FNO que combina convolución espectral con transformación lineal local. salida = σ(SpectralConv(x) + LocalLinear(x)) “”” def __init__( self, canales: int, modos1: int, modos2: int, activación: str=”gelu” ): super().__init__() self.spectral_conv = SpectralConv2d(canales, canales, modos1, modos2) self.local_linear = nn.Conv2d(canales, canales, 1) self.activation = nn.GELU() if activación == ‘gelu’ else nn.ReLU() def forward(self, x: torch.Tensor) -> torch.Tensor: return self.activation(self.spectral_conv(x) + self.local_linear(x)) class FourierNeuralOperator2D(nn.Module): “”” Operador neuronal de Fourier 2D completo para aprender operadores. Arquitectura: 1. Elevar la entrada a un espacio de canal de dimensiones superiores 2. Aplicar múltiples bloques FNO (convoluciones espectrales + residuales) 3. Proyectar de nuevo al espacio de salida Esto aprende el mapeo: k(x,y) -> u(x,y) para el flujo de Darcy “”” def __init__( self, in_channels: int = 1, out_channels: int = 1, modos1: int = 12, modos2: int = 12, ancho: int = 32, n_layers: int = 4, relleno: int = 9): super().__init__() self.modes1 = modos1 self.modes2 = modos2 self.width = ancho self.padding = relleno self.fc0 = nn.Linear(in_channels + 2, ancho) self.fno_blocks = nn.ModuleList([
FNOBlock(width, modes1, modes2) for _ in range(n_layers)
]) self.fc1 = nn.Linear(ancho, 128) self.fc2 = nn.Linear(128, out_channels) def get_grid(self, forma: Tupla, dispositivo: torch.device) -> torch.Tensor: “””Crear coordenadas de cuadrícula normalizadas.””” tamaño_lote, tamaño_x, tamaño_y = forma[0]forma[2]forma[3]
gridx = torch.linspace(0, 1, size_x, dispositivo=dispositivo) gridy = torch.linspace(0, 1, size_y, dispositivo=dispositivo) gridx, gridy = torch.meshgrid(gridx, gridy, indexing=’ij’) grid = torch.stack([gridx, gridy]dim=-1) grid = grid.unsqueeze(0).repeat(batch_size, 1, 1, 1) return grid def forward(self, x: torch.Tensor) -> torch.Tensor: batch_size = x.shape[0]
grid = self.get_grid(x.shape, x.device) x = x.permute(0, 2, 3, 1) x = torch.cat([x, grid]dim=-1) x = self.fc0(x) x = x.permute(0, 3, 1, 2) si self.padding > 0: x = F.pad(x, [0, self.padding, 0, self.padding]) para bloque en self.fno_blocks: x = block(x) si self.padding > 0: x = x[…, :-self.padding, :-self.padding]
x = x.permute(0, 2, 3, 1) x = F.gelu(self.fc1(x)) x = self.fc2(x) x = x.permute(0, 3, 1, 2) return x print(“\nCreando el modelo de operador neuronal de Fourier…”) fno_model = FourierNeuralOperator2D( in_channels=1, out_channels=1, modos1=8, modos2=8, ancho=32, n_capas=4, relleno=5 ).to(dispositivo) n_params = sum(p.numel() for p in fno_model.parameters() if p.requires_grad) print(f” ✓ Modelo FNO creado con {n_params:,} parámetros entrenables”)