CFDBench / model /unet.py
OneScience's picture
Upload folder using huggingface_hub
338c3e4 verified
Raw
History Blame Contribute Delete
4.48 kB
from typing import Optional, List
import torch
import torch.nn as nn
from torch import Tensor
from .base_model import AutoCfdModel
from onescience.modules.decoder.unet_decoder import UNetDecoder2D
from onescience.modules.encoder.unet_encoder import UNetEncoder2D
from onescience.modules.head.unet_head import UNetHead2D
class UNet(AutoCfdModel):
def __init__(
self,
in_chan: int,
out_chan: int,
loss_fn: nn.Module,
n_case_params: int,
insert_case_params_at: str = "hidden",
bilinear: bool = False,
dim: int = 8,
):
assert insert_case_params_at in ["hidden", "input"]
super().__init__(loss_fn)
self.in_chan = in_chan
self.out_chan = out_chan
self.n_case_params = n_case_params
self.insert_case_params_at = insert_case_params_at
self.dim = dim
# 计算 Encoder 输入通道
encoder_in_chan = in_chan + 1 # + Mask
if insert_case_params_at == "input":
encoder_in_chan += n_case_params
# 1. Encoder
self.encoder = UNetEncoder2D(
in_channels=encoder_in_chan,
base_channels=dim,
num_stages=4,
bilinear=bilinear,
normtype="bn"
)
# 2. Hidden Injection
self.case_params_fc = None
if insert_case_params_at == "hidden":
bottleneck_dim = dim * 16
self.case_params_fc = nn.Linear(n_case_params, bottleneck_dim)
# 3. Decoder
self.decoder = UNetDecoder2D(
base_channels=dim,
num_stages=4,
bilinear=bilinear,
normtype="bn"
)
# 4. Head
self.head = UNetHead2D(
in_channels=dim,
out_channels=out_chan
)
def forward(
self,
inputs: Tensor,
case_params: Tensor,
mask: Optional[Tensor] = None,
label: Optional[Tensor] = None,
):
batch_size, n_chan, height, width = inputs.shape
residual = inputs[:, : self.out_chan]
# 构造 Mask
if mask is None:
mask = torch.ones((batch_size, 1, height, width)).to(inputs.device)
else:
if mask.dim() == 3:
mask = mask.unsqueeze(1) # (B, H, W) -> (B, 1, H, W)
# 拼接 Mask
x_in = torch.cat([inputs, mask], dim=1)
# 拼接 Case Params
if self.insert_case_params_at == "input":
cp_spatial = case_params.view(batch_size, self.n_case_params, 1, 1)
cp_spatial = cp_spatial.expand(-1, -1, height, width)
x_in = torch.cat([x_in, cp_spatial], dim=1)
# Encoder
features = self.encoder(x_in)
# Hidden 注入
if self.insert_case_params_at == "hidden":
bottleneck = features[-1]
conds = self.case_params_fc(case_params)
conds = conds.view(batch_size, -1, 1, 1)
features[-1] = bottleneck + conds
# Decoder
decoded = self.decoder(features)
# Head
preds = self.head(decoded)
# Residual & Mask
preds = preds + residual
preds = preds * mask
if label is not None:
label = label * mask
loss = self.loss_fn(labels=label, preds=preds)
return {"preds": preds, "loss": loss}
return {"preds": preds}
def generate_many(
self, inputs: Tensor, case_params: Tensor, mask: Tensor, steps: int
) -> List[Tensor]:
preds = []
# 处理单样本输入 (增加 Batch 维)
if inputs.dim() == 3:
inputs = inputs.unsqueeze(0)
case_params = case_params.unsqueeze(0)
if mask.dim() == 2:
mask = mask.unsqueeze(0)
# 确保 Mask 是 (B, 1, H, W) 以匹配 forward 逻辑
if mask.dim() == 3:
mask = mask.unsqueeze(1)
cur_frame = inputs
for _ in range(steps):
out_dict = self.forward(cur_frame, case_params=case_params, mask=mask)
cur_frame = out_dict["preds"]
preds.append(cur_frame)
return preds
def generate(
self,
inputs: Tensor,
case_params: Tensor,
mask: Optional[Tensor] = None,
) -> Tensor:
outputs = self.forward(inputs, case_params=case_params, mask=mask)
return outputs["preds"]