import torch from torch import nn class StableNNPhys(nn.Module): def __init__(self, hidden_size=32, input_size=71, output_size=68): super().__init__() self.hidden_size = hidden_size self.input_size = input_size self.output_size = output_size self.hidden = nn.Linear(input_size, hidden_size) self.output = nn.Linear(hidden_size, output_size) self.bypass = nn.Linear(input_size, output_size) nn.init.zeros_(self.output.weight) nn.init.zeros_(self.output.bias) nn.init.zeros_(self.bypass.weight) nn.init.zeros_(self.bypass.bias) def forward(self, x): return self.output(torch.relu(self.hidden(x))) + self.bypass(x) def rollout(model, initial_state, surface, advection, state_mean, state_std, tendency_mean, tendency_std, dt_seconds=10800.0): """Integrate advection trapezoidally, then neural physics with Euler.""" states = [initial_state] physics = [] state = initial_state for step in range(surface.shape[1]): adv_now = advection[:, step] adv_next = advection[:, min(step + 1, advection.shape[1] - 1)] forced = state + 0.5 * dt_seconds * (adv_now + adv_next) surface_scaled = surface[:, step] / surface.new_tensor([100.0, 100.0, 1000.0]) features = torch.cat(((forced - state_mean) / state_std, surface_scaled), dim=-1) tendency = model(features) * tendency_std + tendency_mean state = forced + dt_seconds * tendency physics.append(tendency) states.append(state) return torch.stack(states, dim=1), torch.stack(physics, dim=1) def rollout_loss(prediction, target, layer_mass, mode="paper"): error = torch.abs(prediction[:, 1:] - target[:, 1:]) if mode == "paper": weights = torch.cat((layer_mass, layer_mass), dim=-1) weights = weights / weights.mean(dim=-1, keepdim=True) return (error * weights[:, None]).mean() if mode == "official_v0_3": scale = target[:, 1:].std(dim=(0, 1), unbiased=False).clamp_min(1e-6) return (error / scale).mean() raise ValueError(f"Unknown loss mode: {mode}")