| import math |
|
|
| import torch |
| import torch.nn as nn |
|
|
|
|
| def cylindrical_conv_pad(x, w_pad): |
| return torch.cat([x[..., -w_pad:], x, x[..., :w_pad]], axis=-1) |
|
|
|
|
| class CylindricalConv2D(nn.Conv2d): |
| """ |
| UNet with cylinderical boundary conditions |
| """ |
|
|
| def __init__( |
| self, |
| in_channels: int, |
| out_channels: int, |
| kernel_size: int, |
| stride: int, |
| ): |
| super().__init__( |
| in_channels=in_channels, |
| out_channels=out_channels, |
| kernel_size=kernel_size, |
| stride=stride, |
| ) |
|
|
| assert self.kernel_size[0] % 2 == 1 |
| assert self.kernel_size[1] % 2 == 1 |
|
|
| self.h_pad = self.kernel_size[0] // 2 |
| self.w_pad = self.kernel_size[1] // 2 |
|
|
| def forward(self, x: torch.Tensor): |
| x = nn.functional.pad(x, (0, 0, self.h_pad, self.h_pad)) |
| return super().forward(cylindrical_conv_pad(x, self.w_pad)) |
|
|
|
|
| class CylindricalConvTranspose2D(nn.ConvTranspose2d): |
| def __init__( |
| self, |
| in_channels: int, |
| out_channels: int, |
| kernel_size: int, |
| stride: int, |
| ): |
| super().__init__( |
| in_channels=in_channels, |
| out_channels=out_channels, |
| kernel_size=kernel_size, |
| stride=stride, |
| ) |
|
|
| assert self.kernel_size[0] % 2 == 1 |
| assert self.kernel_size[1] % 2 == 1 |
|
|
| self.sh, self.sw = self.stride |
| self.kh, self.kw = self.kernel_size |
|
|
| self.h_pad = math.ceil(((self.sh - 1) + 2 * (self.kh // 2)) / self.sh) |
| self.w_pad = math.ceil(((self.sw - 1) + 2 * (self.kw // 2)) / self.sw) |
|
|
| self.h0 = self.sh * self.h_pad - (self.sh - 1) + (self.kh // 2) |
| self.w0 = self.sw * self.w_pad - (self.sw - 1) + (self.kw // 2) |
|
|
| self._bias = nn.Parameter(10**-3 * torch.randn(out_channels)) |
|
|
| def forward(self, x: torch.Tensor): |
|
|
| Nh = x.shape[2] * self.sh |
| Nw = x.shape[3] * self.sw |
|
|
| x = cylindrical_conv_pad(x, self.w_pad) |
| x = nn.functional.pad(x, (0, 0, self.h_pad, self.h_pad)) |
|
|
| x = super().forward(x) |
|
|
| return x[:, :, self.h0 : self.h0 + Nh, self.w0 : self.w0 + Nw] |
|
|
|
|
| class Down(nn.Module): |
|
|
| def __init__( |
| self, |
| in_channels, |
| out_channels, |
| p=0, |
| film=False, |
| down=True, |
| attn=False, |
| ): |
|
|
| super().__init__() |
|
|
| self.film = film |
| self.attn = attn |
|
|
| self.conv_1 = CylindricalConv2D( |
| in_channels, out_channels, kernel_size=3, stride=1 |
| ) |
| if down: |
| self.conv_2 = CylindricalConv2D( |
| out_channels, out_channels, kernel_size=3, stride=2 |
| ) |
| else: |
| self.conv_2 = CylindricalConv2D( |
| out_channels, out_channels, kernel_size=3, stride=1 |
| ) |
|
|
| self.bn_1 = nn.BatchNorm2d(out_channels) |
| self.bn_2 = nn.BatchNorm2d(out_channels) |
|
|
| self.activation = nn.GELU() |
|
|
| if film: |
|
|
| self.gamma_1 = torch.nn.Parameter( |
| torch.ones(10, out_channels, 1, 1), |
| ) |
| self.gamma_2 = torch.nn.Parameter( |
| torch.ones(10, out_channels, 1, 1), |
| ) |
| self.beta_1 = torch.nn.Parameter( |
| torch.zeros(10, out_channels, 1, 1), |
| ) |
| self.beta_2 = torch.nn.Parameter( |
| torch.zeros(10, out_channels, 1, 1), |
| ) |
|
|
| if self.attn: |
| self.mha = AttentionBlock(n_channels=out_channels, n_heads=8) |
|
|
| def forward(self, xi, film_index=None): |
|
|
| film_index = film_index[:, 0].int() |
|
|
| x = self.conv_1(xi) |
| x = self.bn_1(x) |
| if self.film: |
| g1 = torch.index_select(self.gamma_1, 0, film_index) |
| b1 = torch.index_select(self.beta_1, 0, film_index) |
|
|
| x = g1 * x + b1 |
|
|
| x = self.activation(x) |
| x = self.conv_2(x) |
| x = self.bn_2(x) |
| if self.film: |
| g2 = torch.index_select(self.gamma_2, 0, film_index) |
| b2 = torch.index_select(self.beta_2, 0, film_index) |
| x = g2 * x + b2 |
| x = self.activation(x) |
|
|
| if self.attn: |
| x = self.mha(x) |
|
|
| return x |
|
|
|
|
| class Up(nn.Module): |
|
|
| def __init__( |
| self, |
| in_channels, |
| out_channels, |
| p, |
| bilinear=False, |
| film=False, |
| stride=2, |
| attn=False, |
| ): |
| super().__init__() |
|
|
| self.film = film |
|
|
| if bilinear: |
| self.up = nn.Upsample( |
| scale_factor=2, |
| mode="bilinear", |
| align_corners=True, |
| ) |
| else: |
| self.up = CylindricalConvTranspose2D( |
| in_channels, out_channels, kernel_size=3, stride=stride |
| ) |
|
|
| self.conv = Down( |
| out_channels, |
| out_channels, |
| p=0, |
| film=film, |
| down=False, |
| attn=attn, |
| ) |
|
|
| def forward(self, x1, x2, film_index=None): |
| x1 = self.up(x1) |
| x1 = self.conv(x1, film_index=film_index) |
|
|
| if x1.shape[-1] != x2.shape[-1]: |
| x1 = x1[..., :, :-1] |
| if x1.shape[-2] != x2.shape[-2]: |
| x1 = x1[..., :-1, :] |
|
|
| return torch.cat([x2, x1], dim=1) |
|
|
|
|
| class Unet(nn.Module): |
|
|
| def __init__( |
| self, |
| in_channels, |
| out_channels, |
| div_factor=1, |
| p=0.0, |
| context=True, |
| film=False, |
| film_base=True, |
| ): |
| super(Unet, self).__init__() |
|
|
| self.n_channels = in_channels |
| self.bilinear = True |
| self.fp = nn.Softplus() |
| self.variances = nn.Parameter(torch.zeros([out_channels])) |
| self.context = context |
| self.film = film |
|
|
| m = 1 |
|
|
| self.down1 = Down( |
| self.n_channels, |
| m * 128 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.down2 = Down( |
| m * 128 // div_factor, |
| m * 256 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.down3 = Down( |
| m * 256 // div_factor, |
| m * 512 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.down4 = Down( |
| m * 512 // div_factor, |
| m * 512 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.up1 = Up( |
| m * 512 // div_factor, |
| m * 512 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.up2 = Up( |
| m * 1024 // div_factor, |
| m * 256 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.up3 = Up( |
| m * 512 // div_factor, |
| m * 128 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
| self.up4 = Up( |
| m * 256 // div_factor, |
| m * 64 // div_factor, |
| p=0, |
| film=film, |
| attn=False, |
| ) |
|
|
| self.out = nn.Conv2d( |
| m * 64 // div_factor + in_channels, |
| out_channels, |
| kernel_size=1, |
| bias=False, |
| ) |
|
|
| def forward(self, x, film_index=None): |
|
|
| x1 = x.contiguous() |
| x2 = self.down1(x1, film_index=film_index) |
| x3 = self.down2(x2, film_index=film_index) |
| x4 = self.down3(x3, film_index=film_index) |
| x5 = self.down4(x4, film_index=film_index) |
| x = self.up1(x5, x4, film_index=film_index) |
| x = self.up2(x, x3, film_index=film_index) |
| x = self.up3(x, x2, film_index=film_index) |
| x = self.up4(x, x1, film_index=film_index) |
| x = self.out(x) |
|
|
| return x.permute(0, 2, 3, 1) |
|
|