| from itertools import product |
| from typing import Dict, Optional |
|
|
| import torch |
| from torch import nn, Tensor |
|
|
| from .base_model import CfdModel |
| from .ffn import Ffn |
| from .act_fn import get_act_fn |
| from .loss import MseLoss |
|
|
|
|
| class DeepONet(CfdModel): |
| """ |
| DeepONet for CFD. |
| |
| Branch net accepts the boundary and physics properties as inputs. |
| Trunk net accepts the query location (t, x, y) as input. |
| """ |
|
|
| def __init__( |
| self, |
| branch_dim: int, |
| trunk_dim: int, |
| loss_fn: MseLoss, |
| num_label_samples: int = 1000, |
| branch_depth: int = 4, |
| trunk_depth: int = 3, |
| width: int = 100, |
| act_name: str = "relu", |
| act_norm: bool = False, |
| act_on_output: bool = False, |
| ): |
| """ |
| Args: |
| - branch_dim: int, the dimension of the branch net input. |
| - trunk_dim: int, the dimension of the trunk net input. |
| """ |
| super().__init__(loss_fn) |
| self.branch_dim = branch_dim |
| self.trunk_dim = trunk_dim |
| self.loss_fn = loss_fn |
| self.branch_depth = branch_depth |
| self.trunk_depth = trunk_depth |
| self.width = width |
| self.act_name = act_name |
| self.act_norm = act_norm |
| self.act_on_output = act_on_output |
| self.num_label_samples = num_label_samples |
|
|
| self.branch_dims = [branch_dim] + [width] * branch_depth |
|
|
| act_fn = get_act_fn(act_name, act_norm) |
| self.branch_net = Ffn( |
| self.branch_dims, act_fn=act_fn, act_on_output=self.act_on_output |
| ) |
| |
| |
| self.fc_trunk_t = nn.Linear(1, width) |
| self.fc_trunk_xy = nn.Linear(2, width) |
| self.trunk_dims = [width] * trunk_depth |
| self.trunk_net = Ffn(self.trunk_dims, act_fn=act_fn) |
|
|
| |
| self.bias = nn.Parameter(torch.zeros(1)) |
| |
|
|
| |
| |
| |
| |
| def forward_vanilla( |
| self, |
| x_branch: Tensor, |
| x_trunk: Tensor, |
| label: Optional[Tensor] = None, |
| query_idxs: Optional[Tensor] = None, |
| ): |
| """ |
| NOTE: This is no long used!! |
| This is the implementation of the ordinary DeepONet, but it is very |
| slow! Our `forward` is much faster because it re-uses the output |
| of the branch net for different queries in one batch. |
| |
| Args: |
| - x_branch: (b, branch_dim), input to the branch net. |
| - x_trunk: (b), input to the trunk net, a batch of (t, x, y) |
| - label: (b), the label for the query location. |
| |
| Return: |
| - if label is None: |
| - preds: (b, k), the prediction for the query location. |
| - else: |
| - preds: (b, k), the prediction for the query location. |
| - loss: (b, k), the loss for the query location. |
| """ |
|
|
| |
| |
| if query_idxs is None: |
| assert label is not None |
| height, width = label.shape[-2:] |
| query_idxs = torch.stack( |
| [ |
| torch.randint( |
| 0, |
| height, |
| (self.num_label_samples,), |
| device=x_trunk.device, |
| ), |
| torch.randint( |
| 0, |
| width, |
| (self.num_label_samples,), |
| device=x_trunk.device, |
| ), |
| ], |
| dim=-1, |
| ) |
| t = x_trunk.unsqueeze(1).float() |
| x_trunk_t = self.fc_trunk_t(t) |
| |
| x_trunk_xy = ( |
| query_idxs.float() - 32.0 |
| ) / 64.0 |
| x_trunk_xy = self.fc_trunk_xy(x_trunk_xy) |
| x_trunk_t = x_trunk_t.unsqueeze(1) |
| x_trunk_xy = x_trunk_xy.unsqueeze(0) |
| x_trunk = x_trunk_t + x_trunk_xy |
|
|
| x_branch = self.branch_net(x_branch) |
| x_trunk = self.trunk_net(x_trunk) |
|
|
| |
| x_branch = x_branch.unsqueeze(1) |
| preds = torch.sum(x_branch * x_trunk, dim=-1) + self.bias |
| print(preds.dtype) |
|
|
| if label is not None: |
| print(label.dtype) |
| |
| label = label[:, 0] |
| |
| |
| labels = label[:, query_idxs[:, 0], query_idxs[:, 1]] |
| |
| loss = self.loss_fn(preds=preds, labels=labels) |
| print(labels.dtype) |
| print(loss.dtype) |
| return preds, loss |
| return preds |
|
|
| def forward( |
| self, |
| case_params: Tensor, |
| t: Tensor, |
| label: Optional[Tensor] = None, |
| query_idxs: Optional[Tensor] = None, |
| ) -> Dict[str, Tensor]: |
| """ |
| A faster forward by using all the points in the frame (`label`) at |
| time step `t` as training examples. |
| |
| Args: |
| - x_branch: (b, branch_dim), input to the branch net. |
| - t: (b), input to the trunk net, a batch of t |
| - label: (b, w, h), the frame to be predicted. |
| - query_idxs: (b, k, 2), the query locations. |
| """ |
| if query_idxs is None: |
| |
| |
| assert label is not None |
| height, width = label.shape[-2:] |
| query_idxs = torch.stack( |
| [ |
| torch.randint( |
| 0, |
| height, |
| (self.num_label_samples,), |
| device=label.device, |
| ), |
| torch.randint( |
| 0, |
| width, |
| (self.num_label_samples,), |
| device=label.device, |
| ), |
| ], |
| dim=-1, |
| ) |
|
|
| x_trunk_t = self.fc_trunk_t(t) |
| |
| x_trunk_xy = query_idxs.float() |
| x_trunk_xy = self.fc_trunk_xy(x_trunk_xy) |
| x_trunk_t = x_trunk_t.unsqueeze(1) |
| x_trunk_xy = x_trunk_xy.unsqueeze(0) |
| x_trunk = x_trunk_t + x_trunk_xy |
|
|
| case_params = self.branch_net(case_params) |
| x_trunk = self.trunk_net(x_trunk) |
|
|
| case_params = case_params.unsqueeze(1) |
| preds = torch.sum(case_params * x_trunk, dim=-1) + self.bias |
|
|
| if label is not None: |
| |
| label = label[:, 0] |
| |
| |
| labels = label[:, query_idxs[:, 0], query_idxs[:, 1]] |
| assert ( |
| preds.shape == labels.shape |
| ), f"{preds.shape}, {labels.shape}" |
| loss = self.loss_fn(preds=preds, labels=labels) |
| return dict( |
| preds=preds, |
| loss=loss, |
| ) |
| return dict( |
| preds=preds, |
| ) |
|
|
| def generate_one( |
| self, case_params: Tensor, t: Tensor, height: int, width: int |
| ) -> Tensor: |
| """ |
| Generate one frame at time t. |
| |
| Args: |
| - x_branch: Tensor, (b, branch_dim) |
| - t: Tensor, (b) |
| - height: int |
| - width: int |
| |
| Returns: |
| (b, c, h, w) |
| """ |
| if len(case_params.shape) == 1: |
| case_params = case_params.unsqueeze(0) |
| if len(t.shape) == 0: |
| t = t.unsqueeze(0).unsqueeze(0) |
| elif len(t.shape) == 1: |
| t = t.unsqueeze(0) |
|
|
| |
| query_idxs = torch.tensor( |
| list(product(range(height), range(width))), |
| |
| device=case_params.device, |
| ) |
| |
| |
| output = self.forward(case_params, t=t, query_idxs=query_idxs)["preds"] |
| output = output.view(-1, 1, height, width) |
| return output |
|
|