Download model/deeponet.py from OneScience-Group/CFDBench: direct link, hf CLI and curl.
- Browser
- Download file 9.01 kB
-
https://huggingface.co/OneScience-Group/CFDBench/resolve/main/model/deeponet.py
- Command line
-
hf download hf://OneScience-Group/CFDBench/model/deeponet.py
-
curl -L -o deeponet.py https://huggingface.co/OneScience-Group/CFDBench/resolve/main/model/deeponet.py
9.01 kB
| 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, # (t, x, y) | |
| 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 | |
| ) | |
| # t and x, y uses separate fc layers because during training | |
| # we will be using an entire frame as label. | |
| 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.params = self.__init_params() | |
| self.bias = nn.Parameter(torch.zeros(1)) # type: ignore | |
| # self.__initialize() | |
| # 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. | |
| 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. | |
| """ | |
| # Create k query locations. | |
| # (b, k, 3), where each element is (t, x, y) | |
| 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, | |
| ) # (k, 2) | |
| t = x_trunk.unsqueeze(1).float() # (b, 1) | |
| x_trunk_t = self.fc_trunk_t(t) # (b, p) | |
| # Normalize query location | |
| x_trunk_xy = ( | |
| query_idxs.float() - 32.0 | |
| ) / 64.0 # (k, 2) # TODO: update this | |
| x_trunk_xy = self.fc_trunk_xy(x_trunk_xy) # (k, p) | |
| x_trunk_t = x_trunk_t.unsqueeze(1) # (b, 1, p) | |
| x_trunk_xy = x_trunk_xy.unsqueeze(0) # (1, k, p) | |
| x_trunk = x_trunk_t + x_trunk_xy # (b, k, p) | |
| x_branch = self.branch_net(x_branch) # (b, p) | |
| x_trunk = self.trunk_net(x_trunk) # (b, k, p) | |
| # x_trunk = x_trunk.unsqueeze(0) # (1, k, p) | |
| x_branch = x_branch.unsqueeze(1) # (b, 1, p) | |
| preds = torch.sum(x_branch * x_trunk, dim=-1) + self.bias # (b) | |
| print(preds.dtype) | |
| if label is not None: | |
| print(label.dtype) | |
| # Use only the u channel | |
| label = label[:, 0] # (B, w, h) | |
| # we have labels[i, j] = label[ | |
| # i, query_points[i, j, 0], query_points[i, j, 1]] | |
| labels = label[:, query_idxs[:, 0], query_idxs[:, 1]] # (b, k) | |
| # assert preds.shape == label.shape, f"{preds.shape} {label.shape}" | |
| loss = self.loss_fn(preds=preds, labels=labels) # (b, k) | |
| print(labels.dtype) | |
| print(loss.dtype) | |
| return preds, loss | |
| return preds | |
| def forward( | |
| self, | |
| case_params: Tensor, # Case parameters | |
| 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: | |
| # Create k query locations on a lattice. | |
| # (b, k, 2), where each element is (x, y) | |
| 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, | |
| ) # (k, 2) | |
| x_trunk_t = self.fc_trunk_t(t) # (b, p) | |
| # Normalize query location | |
| x_trunk_xy = query_idxs.float() # (k, 2) | |
| x_trunk_xy = self.fc_trunk_xy(x_trunk_xy) # (k, p) | |
| x_trunk_t = x_trunk_t.unsqueeze(1) # (b, 1, p) | |
| x_trunk_xy = x_trunk_xy.unsqueeze(0) # (1, k, p) | |
| x_trunk = x_trunk_t + x_trunk_xy # (b, k, p) | |
| case_params = self.branch_net(case_params) # (b, p) | |
| x_trunk = self.trunk_net(x_trunk) # (b, k, p) | |
| case_params = case_params.unsqueeze(1) # (b, 1, p) | |
| preds = torch.sum(case_params * x_trunk, dim=-1) + self.bias # (b, k) | |
| if label is not None: | |
| # Only predict u | |
| label = label[:, 0] # (B, w, h) | |
| # we have labels[i, j] = label[ | |
| # i, query_points[i, j, 0], query_points[i, j, 1]] | |
| labels = label[:, query_idxs[:, 0], query_idxs[:, 1]] # (b, k) | |
| assert ( | |
| preds.shape == labels.shape | |
| ), f"{preds.shape}, {labels.shape}" | |
| loss = self.loss_fn(preds=preds, labels=labels) # (b, k) | |
| 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) | |
| # Create 2D lattice of query points to infer the frame. | |
| query_idxs = torch.tensor( | |
| list(product(range(height), range(width))), | |
| # dtype=torch.long, | |
| device=case_params.device, | |
| ) # (h * w, 2) | |
| # query_points = query_points / 100 | |
| # (b, 1, h * w) | |
| output = self.forward(case_params, t=t, query_idxs=query_idxs)["preds"] | |
| output = output.view(-1, 1, height, width) # (b, 1, h, w) | |
| return output | |