From de890256ce5040a6fd1186ea01e9df60648754ed Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 20 Sep 2023 03:11:58 +0000 Subject: [PATCH 01/27] update WIP code --- ppsci/arch/__init__.py | 4 + ppsci/arch/amgnet.py | 614 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 618 insertions(+) create mode 100644 ppsci/arch/amgnet.py diff --git a/ppsci/arch/__init__.py b/ppsci/arch/__init__.py index 986e31893..77e7bf47d 100644 --- a/ppsci/arch/__init__.py +++ b/ppsci/arch/__init__.py @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import copy from ppsci.arch.base import Arch # isort:skip +from ppsci.arch.amgnet import AMGNet # isort:skip from ppsci.arch.mlp import MLP # isort:skip from ppsci.arch.deeponet import DeepONet # isort:skip from ppsci.arch.embedding_koopman import LorenzEmbedding # isort:skip @@ -31,6 +34,7 @@ __all__ = [ "Arch", + "AMGNet", "MLP", "DeepONet", "LorenzEmbedding", diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py new file mode 100644 index 000000000..d7c0a4f86 --- /dev/null +++ b/ppsci/arch/amgnet.py @@ -0,0 +1,614 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import functools +from typing import Optional +from typing import Tuple + +import numpy as np +import paddle +import paddle.nn as nn +import pgl +from pyamg.classical.split import RS +from scipy import sparse as sci_sparse + + +def _knn_interpolate(features, coarse_nodes, fine_nodes): + coarse_nodes_input = paddle.repeat_interleave( + coarse_nodes.unsqueeze(0), fine_nodes.shape[0], 0 + ) # [6684,352,2] + fine_nodes_input = paddle.repeat_interleave( + fine_nodes.unsqueeze(1), coarse_nodes.shape[0], 1 + ) # [6684,352,2] + dist_w = 1.0 / ( + paddle.norm(x=coarse_nodes_input - fine_nodes_input, p=2, axis=-1) + 1e-9 + ) # [6684,352] + knn_value, knn_index = paddle.topk(dist_w, k=3, largest=True) # [6684,3],[6684,3] + weight = knn_value.unsqueeze(-2) + features_input = features[knn_index] + output = paddle.bmm(weight, features_input).squeeze(-2) / paddle.sum( + knn_value, axis=-1, keepdim=True + ) + return output + + +def MyCopy(graph): + data = pgl.Graph( + num_nodes=graph.num_nodes, + edges=graph.edges, + ) + data.x = graph.x + data.y = graph.y + data.pos = graph.pos + data.edge_index = graph.edge_index + data.edge_attr = graph.edge_attr + return data + + +def Myadd(g1, g2): + g1.x = paddle.concat([g1.x, g2.x], axis=0) + g1.y = paddle.concat([g1.y, g2.y], axis=0) + g1.edge_index = paddle.concat([g1.edge_index, g2.edge_index], axis=1) + g1.edge_attr = paddle.concat([g1.edge_attr, g2.edge_attr], axis=0) + g1.pos = paddle.concat([g1.pos, g2.pos], axis=0) + return g1 + + +def getcorsenode(latent_graph): + row = latent_graph.edge_index[0].numpy() + col = latent_graph.edge_index[1].numpy() + data = paddle.ones(shape=[row.size]).numpy() + A = sci_sparse.coo_matrix((data, (row, col))).tocsr() + splitting = RS(A) + index = np.array(np.nonzero(splitting)) + b = paddle.to_tensor(index) + b = paddle.squeeze(b) + return b + + +def StAS(index_A, value_A, index_S, value_S, N, kN, nor): + r"""come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + sp_x = paddle.sparse.sparse_coo_tensor(index_A, value_A) + sp_x = paddle.sparse.coalesce(sp_x) + index_A = sp_x.indices() + value_A = sp_x.values() + + sp_s = paddle.sparse.sparse_coo_tensor(index_S, value_S) + sp_s = paddle.sparse.coalesce(sp_s) + index_S = sp_s.indices() + value_S = sp_s.values() + + indices_A = index_A.numpy() + values_A = value_A.numpy() + coo_A = sci_sparse.coo_matrix( + (values_A, (indices_A[0], indices_A[1])), shape=(N, N) + ) + + indices_S = index_S.numpy() + values_S = value_S.numpy() + coo_S = sci_sparse.coo_matrix( + (values_S, (indices_S[0], indices_S[1])), shape=(N, kN) + ) + + ans = coo_A.dot(coo_S).tocoo() + row = paddle.to_tensor(ans.row) + col = paddle.to_tensor(ans.col) + index_B = paddle.stack([row, col], axis=0) + value_B = paddle.to_tensor(ans.data) + + indices_A = index_S + values_A = value_S + coo_A = paddle.sparse.sparse_coo_tensor(indices_A, values_A) + out = paddle.sparse.transpose(coo_A, [1, 0]) + index_St = out.indices() + value_St = out.values() + + sp_x = paddle.sparse.sparse_coo_tensor(index_B, value_B) + sp_x = paddle.sparse.coalesce(sp_x) + index_B = sp_x.indices() + value_B = sp_x.values() + + indices_A = index_St.numpy() + values_A = value_St.numpy() + coo_A = sci_sparse.coo_matrix( + (values_A, (indices_A[0], indices_A[1])), shape=(kN, N) + ) + + indices_S = index_B.numpy() + values_S = value_B.numpy() + coo_S = sci_sparse.coo_matrix( + (values_S, (indices_S[0], indices_S[1])), shape=(N, kN) + ) + + ans = coo_A.dot(coo_S).tocoo() + row = paddle.to_tensor(ans.row) + col = paddle.to_tensor(ans.col) + index_E = paddle.stack([row, col], axis=0) + value_E = paddle.to_tensor(ans.data) + + # index_E排序 + sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) + sp_x = paddle.sparse.coalesce(sp_x) + index_E = sp_x.indices() + value_E = sp_x.values() + + return index_E, value_E + + +def FillZeros(index_E, value_E, standard_index, kN): + shape = [kN, kN] + row_E = index_E[0] + col_E = index_E[1] + # coo_E = paddle.sparse.sparse_coo_tensor(index_E, value_E, shape) + DenseMatrix_E = sci_sparse.coo_matrix( + (paddle.ones_like(value_E), (row_E, col_E)), shape + ).toarray() + + row_S = standard_index[0] + col_S = standard_index[1] + DenseMatrix_S = sci_sparse.coo_matrix( + (paddle.ones([row_S.shape[0]]), (row_S, col_S)), shape + ).toarray() + + diff = DenseMatrix_S - DenseMatrix_E + rows, cols = np.nonzero(diff) + rows = paddle.to_tensor(rows, dtype="int32") + cols = paddle.to_tensor(cols, dtype="int32") + index = paddle.stack([rows, cols], axis=0) + value = paddle.zeros([index.shape[1]]) + index_E = paddle.concat([index_E, index], axis=1) + value_E = paddle.concat([value_E, value], axis=-1) + + sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) + sp_x = paddle.sparse.coalesce(sp_x) + index_E = sp_x.indices() + value_E = sp_x.values() + + return index_E, value_E + + +# @brief:删除图中的自循环的边 +def remove_self_loops( + edge_index: paddle.Tensor, edge_attr: Optional[paddle.Tensor] = None +) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: + mask = edge_index[0] != edge_index[1] + mask = mask.tolist() + # edge_index = edge_index[:, mask] + edge_index = edge_index.t() + edge_index = edge_index[mask] + edge_index = edge_index.t() + if edge_attr is None: + return edge_index, None + else: + return edge_index, edge_attr[mask] + + +def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): + """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + kN = perm.shape[0] + perm2 = perm.reshape((-1, 1)) + mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") + + S0 = edge_index[1][mask].reshape((1, -1)) + S1 = edge_index[0][mask].reshape((1, -1)) + index_S = paddle.concat([S0, S1], axis=0) + value_S = score[mask].detach().squeeze() + n_idx = paddle.zeros([N], dtype=paddle.int64) + n_idx[perm] = paddle.arange(perm.shape[0]) + index_S = index_S.astype("int64") + index_S[1] = n_idx[index_S[1]] + subgraphnode_pos = pos[perm] + index_A = edge_index.clone() + if edge_weight is None: + value_A = value_S.new_ones(edge_index[0].shape[0]) + else: + value_A = edge_weight.clone() + + value_A = paddle.squeeze(value_A) + model_1 = paddle.nn.Sequential( + ("l1", paddle.nn.Linear(128, 256)), + ("act1", paddle.nn.ReLU()), + ("l2", paddle.nn.Linear(256, 256)), + ("act2", paddle.nn.ReLU()), + # ('l3', paddle.nn.Linear(256, 256)), ('act3', paddle.nn.ReLU()), + ("l4", paddle.nn.Linear(256, 128)), + ("act4", paddle.nn.ReLU()), + ("l5", paddle.nn.Linear(128, 1)), + ) + model_2 = paddle.nn.Sequential( + ("l1", paddle.nn.Linear(1, 64)), + ("act1", paddle.nn.ReLU()), + ("l2", paddle.nn.Linear(64, 128)), + ("act2", paddle.nn.ReLU()), + # ('l3', paddle.nn.Linear(128, 128)),('act3', paddle.nn.ReLU()), + ("l4", paddle.nn.Linear(128, 128)), + ) + + val_A = model_1(value_A) + val_A = paddle.squeeze(val_A) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + value_E = paddle.reshape(value_E, shape=[-1, 1]) + edge_weight = model_2(value_E) + + return index_E, edge_weight, subgraphnode_pos + + +def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): + """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + kN = perm.shape[0] + perm2 = perm.reshape((-1, 1)) + mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") + S0 = edge_index[1][mask].reshape((1, -1)) + S1 = edge_index[0][mask].reshape((1, -1)) + index_S = paddle.concat([S0, S1], axis=0) + value_S = score[mask].detach().squeeze() + n_idx = paddle.zeros([N], dtype=paddle.int64) + n_idx[perm] = paddle.arange(perm.shape[0]) + index_S = index_S.astype("int64") + index_S[1] = n_idx[index_S[1]] + subgraphnode_pos = pos[perm] + index_A = edge_index.clone() + if edge_weight is None: + value_A = value_S.new_ones(edge_index[0].shape[0]) + else: + value_A = edge_weight.clone() + + value_A = paddle.squeeze(value_A) + value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) + attrlist = [] + standard_index, _ = StAS( + index_A, + paddle.ones_like(value_A[:, 0]), + index_S, + paddle.ones_like(value_S), + N, + kN, + nor, + ) + for i in range(128): + val_A = paddle.where( + value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] + ) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + + if index_E.shape[1] != standard_index.shape[1]: + index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) + + index_E, value_E = remove_self_loops(edge_index=index_E, edge_attr=value_E) + attrlist.append(value_E) + edge_weight = paddle.stack(attrlist, axis=1) + + return index_E, edge_weight, subgraphnode_pos + + +class GraphNetBlock(nn.Layer): + """Multi-Edge Interaction Network with residual connections.""" + + def __init__( + self, model_fn, output_dim, message_passing_aggregator, attention=False + ): + super().__init__() + self.edge_model = model_fn(output_dim, 384) + self.node_model = model_fn(output_dim, 256) + self.message_passing_aggregator = message_passing_aggregator + + def _update_edge_features(self, graph): + """Aggregrates node features, and applies edge function.""" + senders = graph.edge_index[0] + receivers = graph.edge_index[1] + sender_features = paddle.index_select(x=graph.x, index=senders, axis=0) + receiver_features = paddle.index_select(x=graph.x, index=receivers, axis=0) + features = [sender_features, receiver_features, graph.edge_attr] + features = paddle.concat(features, axis=-1) + return self.edge_model(features) + + def unsorted_segment_operation(self, data, segment_ids, num_segments, operation): + """ + Computes the sum along segments of a tensor. Analogous to tf.unsorted_segment_sum. + + :param data: A tensor whose segments are to be summed. + :param segment_ids: The segment indices tensor. + :param num_segments: The number of segments. + :return: A tensor of same data type as the data argument. + """ + assert all( + [i in data.shape for i in segment_ids.shape] + ), "segment_ids.shape should be a prefix of data.shape" + assert ( + data.shape[0] == segment_ids.shape[0] + ), "data.shape and segment_ids.shape should be equal" + shape = [num_segments] + list(data.shape[1:]) + result_shape = paddle.zeros(shape) + if operation == "sum": + result = paddle.scatter(result_shape, segment_ids, data, overwrite=False) + return result + + def _update_node_features(self, node_features, edge_attr, edge_index): + """Aggregrates edge features, and applies node function.""" + num_nodes = node_features.shape[0] + features = [node_features] + features.append( + self.unsorted_segment_operation( + edge_attr, + edge_index[1], + num_nodes, + operation=self.message_passing_aggregator, + ) + ) + features = paddle.concat(features, axis=-1) + return self.node_model(features) + + def forward(self, graph): + """Applies GraphNetBlock and returns updated MultiGraph.""" + new_edge_features = self._update_edge_features(graph) + new_node_features = self._update_node_features( + graph.x, graph.edge_attr, graph.edge_index + ) + + new_node_features += graph.x + new_edge_features += graph.edge_attr + latent_graph = pgl.Graph( + num_nodes=new_node_features.shape[0], edges=graph.edge_index + ) + latent_graph.x = new_node_features + latent_graph.edge_attr = new_edge_features + latent_graph.pos = graph.pos + latent_graph.edge_index = graph.edge_index + return latent_graph + + +class Processor(nn.Layer): + """ + This class takes the nodes with the most influential feature (sum of square) + The the chosen numbers of nodes in each ripple will establish connection(features and distances) with the most influential nodes and this connection will be learned + Then the result is add to output latent graph of encoder and the modified latent graph will be feed into original processor + + Option: choose whether to normalize the high rank node connection + """ + + # Each mesh can be coarsened to have no fewer points than this value + min_nodes = 2000 + + def __init__( + self, + make_mlp, + output_dim, + message_passing_steps, + message_passing_aggregator, + attention=False, + stochastic_message_passing_used=False, + ): + super().__init__() + self.stochastic_message_passing_used = stochastic_message_passing_used + self.graphnet_blocks = nn.LayerList() + self.cofe_edge_blocks = nn.LayerList() + self.pool_blocks = nn.LayerList() + self.latent_dim = output_dim + self.normalization = nn.LayerNorm(128) + for index in range(message_passing_steps): + self.graphnet_blocks.append( + GraphNetBlock( + model_fn=make_mlp, + output_dim=output_dim, + message_passing_aggregator=message_passing_aggregator, + attention=attention, + ) + ) + + self.pool_blocks.append( + GraphNetBlock( + model_fn=make_mlp, + output_dim=output_dim, + message_passing_aggregator=message_passing_aggregator, + attention=attention, + ) + ) + + def forward(self, latent_graph, speed, normalized_adj_mat=None): + x = [] + pos = [] + new = [] + for graphnet_block, pool in zip(self.graphnet_blocks, self.pool_blocks): + if latent_graph.x.shape[0] > self.min_nodes: + pre_matrix = graphnet_block(latent_graph) + x.append(pre_matrix) + cofe_graph = pool(pre_matrix) + coarsenodes = getcorsenode(pre_matrix) + nodesfeatures = cofe_graph.x[coarsenodes] + if speed == "fast": + subedge_index, edge_weight, subpos = faster_graph_connectivity( + perm=coarsenodes, + edge_index=cofe_graph.edge_index, + edge_weight=cofe_graph.edge_attr, + score=cofe_graph.edge_attr[:, 0], + pos=cofe_graph.pos, + N=cofe_graph.x.shape[0], + nor=self.normalization, + ) + elif speed == "norm": + subedge_index, edge_weight, subpos = norm_graph_connectivity( + perm=coarsenodes, + edge_index=cofe_graph.edge_index, + edge_weight=cofe_graph.edge_attr, + score=cofe_graph.edge_attr[:, 0], + pos=cofe_graph.pos, + N=cofe_graph.x.shape[0], + nor=self.normalization, + ) + edge_weight = self.normalization(edge_weight) + pos.append(subpos) + latent_graph = pgl.Graph( + num_nodes=nodesfeatures.shape[0], edges=subedge_index + ) + latent_graph.x = nodesfeatures + latent_graph.edge_attr = edge_weight + latent_graph.pos = subpos + latent_graph.edge_index = subedge_index + else: + latent_graph = graphnet_block(latent_graph) + new.append(latent_graph) + if len(new): + x.append(new[-1]) + return x, pos + + +class LazyMLP(nn.Layer): + def __init__(self, layer, input_dim): + super(LazyMLP, self).__init__() + num_layers = len(layer) + self._layers_ordered_dict = {} + self.in_dim = input_dim + for index, output_dim in enumerate(layer): + self._layers_ordered_dict["linear_" + str(index)] = nn.Linear( + self.in_dim, output_dim + ) + if index < (num_layers - 1): + self._layers_ordered_dict["relu_" + str(index)] = nn.ReLU() + self.in_dim = output_dim + + self.layers = nn.LayerDict(self._layers_ordered_dict) + + def forward(self, input): + for k in self.layers: + l = self.layers[k] + output = l(input) + input = output + return input + + +class Encoder(nn.Layer): + """Encodes node and edge features into latent features.""" + + def __init__(self, input_dim, make_mlp, latent_dim, mode): + super(Encoder, self).__init__() + self._make_mlp = make_mlp + self._latent_dim = latent_dim + # if mode == "airfoil": + # self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 5 + # else: + self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 4 + + self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=input_dim) # 1 + """ + for _ in graph.edge_sets: + edge_model = make_mlp(latent_dim) + self.edge_models.append(edge_model) + """ + + def forward(self, graph): + node_latents = self.node_model(graph.x) + # print(f">>> graph.x = {graph.x.shape}") # [26736, 5] + edge_latent = self.mesh_edge_model(graph.edge_attr) + # print(f">>> graph.edge_attr = {graph.edge_attr.shape}") # [105344, 1] + graph.x = node_latents + graph.edge_attr = edge_latent + return graph + + +class Decoder(nn.Layer): + """Decodes node features from graph.""" + + """Encodes node and edge features into latent features.""" + + def __init__(self, make_mlp, output_dim): + super(Decoder, self).__init__() + self.model = make_mlp(output_dim, 128) + + def forward(self, node_features): + return self.model(node_features) + + +class AMGNet(nn.Layer): + """Encode-Process-Decode GraphNet model.""" + + def __init__( + self, + input_dim, + output_dim, + latent_dim, + num_layers, + message_passing_aggregator, + message_passing_steps, + mode, + speed, + nodes=6684, + ): + super().__init__() + self._latent_dim = latent_dim + self.speed = speed + self._output_dim = output_dim + self._num_layers = num_layers + self.min_nodes = nodes + self._message_passing_steps = message_passing_steps + self._message_passing_aggregator = message_passing_aggregator + # self.mode = mode + self.encoder = Encoder( + make_mlp=self._make_mlp, latent_dim=self._latent_dim, mode=self.mode + ) + self.processor = Processor( + make_mlp=self._make_mlp, + output_dim=self._latent_dim, + message_passing_steps=self._message_passing_steps, + message_passing_aggregator=self._message_passing_aggregator, + stochastic_message_passing_used=False, + ) + self.post_processor = self._make_mlp(self._latent_dim, 128) + self.decoder = Decoder( + make_mlp=functools.partial(self._make_mlp, layer_norm=False), + output_dim=self._output_dim, + ) + + def _make_mlp(self, output_dim, input_dim=5, layer_norm=True): + """Builds an MLP.""" + widths = [self._latent_dim] * self._num_layers + [output_dim] + network = LazyMLP(widths, input_dim) + if layer_norm: + network = nn.Sequential(network, nn.LayerNorm(normalized_shape=widths[-1])) + return network + + def _spa_compute(self, x, p): + j = len(x) - 1 + node_features = x[j].x + for k in range(1, j + 1): + pos = p[-k] + fine_nodes = x[-(k + 1)].pos + feature = _knn_interpolate(node_features, pos, fine_nodes) + node_features = x[-(k + 1)].x + feature + node_features = self.post_processor(node_features) + return node_features + + def forward(self, graphs): + batch = MyCopy(graphs[0]) + + for index, graph in enumerate(graphs): + if index > 0: + batch = Myadd(batch, graph) + + latent_graph = self.encoder(batch) + + x, p = self.processor(latent_graph, speed=self.speed) + + node_features = self._spa_compute(x, p) + + pred_field = self.decoder(node_features) + + return pred_field From 38659ca2dec80cebf6b08362deb6d91e89660cde Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 03:33:17 +0000 Subject: [PATCH 02/27] (WIP)update AMGNet code --- docs/zh/api/data/dataset.md | 1 + examples/amgnet/amgnet.py | 170 ++++++++++++++++++ ppsci/arch/amgnet.py | 56 +++--- ppsci/data/__init__.py | 35 ++-- ppsci/data/dataset/__init__.py | 2 + .../data/process/batch_transform/__init__.py | 25 ++- ppsci/solver/eval.py | 58 ++++-- ppsci/solver/train.py | 2 + ppsci/utils/misc.py | 39 ++++ 9 files changed, 337 insertions(+), 51 deletions(-) create mode 100644 examples/amgnet/amgnet.py diff --git a/docs/zh/api/data/dataset.md b/docs/zh/api/data/dataset.md index f2e8f0708..1f65d38a7 100644 --- a/docs/zh/api/data/dataset.md +++ b/docs/zh/api/data/dataset.md @@ -16,4 +16,5 @@ - LorenzDataset - RosslerDataset - VtuDataset + - MeshAirfoilDataset show_root_heading: false diff --git a/examples/amgnet/amgnet.py b/examples/amgnet/amgnet.py new file mode 100644 index 000000000..81d868536 --- /dev/null +++ b/examples/amgnet/amgnet.py @@ -0,0 +1,170 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.nn import functional as F + +import ppsci +from ppsci.utils import config +from ppsci.utils import logger + + +def train_mse_func(output_dict, label_dict, *args): + return F.mse_loss(output_dict["pred"], label_dict["label"].y) + + +def eval_rmse_func(output_dict, label_dict, *args): + mse_losses = [ + F.mse_loss(pred, label.y) + for (pred, label) in zip(output_dict["pred"], label_dict["label"]) + ] + return {"RMSE": (sum(mse_losses) / len(mse_losses)) ** 0.5} + + +if __name__ == "__main__": + args = config.parse_args() + # set random seed for reproducibility + ppsci.utils.misc.set_random_seed(42) + # set output directory + OUTPUT_DIR = "./output_AMGNet" if not args.output_dir else args.output_dir + # initialize logger + logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") + + # set model + # model for airfoil + model = ppsci.arch.AMGNet( + 5, + 3, + 128, + num_layers=2, + message_passing_aggregator="sum", + message_passing_steps=6, + speed="norm", + ) + # # model for cylinder + # model = ppsci.arch.AMGNet( + # 5, + # 3, + # 128, + # num_layers=2, + # message_passing_aggregator="sum", + # message_passing_steps=6, + # speed="norm", + # ) + + # set dataloader config + ITERS_PER_EPOCH = 42 + train_dataloader_cfg = { + "dataset": { + "name": "MeshAirfoilDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_root": "./data/NACA0012_interpolate/outputs_train", + "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", + }, + "batch_size": 4, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": True, + }, + "num_workers": 1, + } + + # set constraint + sup_constraint = ppsci.constraint.SupervisedConstraint( + train_dataloader_cfg, + output_expr={"pred": lambda out: out["pred"]}, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + name="Sup", + ) + # wrap constraints together + constraint = { + sup_constraint.name: sup_constraint, + } + + # set training hyper-parameters + EPOCHS = 500 if not args.epochs else args.epochs + + # set optimizer + optimizer = ppsci.optimizer.Adam(5e-4)(model) + + # set validator + eval_dataloader_cfg = { + "dataset": { + "name": "MeshAirfoilDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_root": "./data/NACA0012_interpolate/outputs_test", + "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", + }, + "batch_size": 1, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": False, + }, + } + rmse_validator = ppsci.validate.SupervisedValidator( + eval_dataloader_cfg, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + output_expr={"pred": lambda out: out["pred"]}, + metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, + name="RMSE_validator", + ) + validator = {rmse_validator.name: rmse_validator} + + # initialize solver + solver = ppsci.solver.Solver( + model, + constraint, + OUTPUT_DIR, + optimizer, + None, + EPOCHS, + ITERS_PER_EPOCH, + eval_during_train=True, + eval_freq=50, + validator=validator, + eval_with_no_grad=True, + # pretrained_model_path="./output_AMGNet/checkpoints/latest" + ) + # train model + solver.train() + # solver.eval() + # with solver.no_grad_context_manager(True): + # sum_loss = 0 + # for index, batch in enumerate(loader): + # truefield = batch[0].y + # prefield = model(batch) + # # print(f"{index }prefield.mean() = {prefield.shape} {prefield.mean().item():.10f}") + # # log_images( + # # batch[0].pos, + # # prefield, + # # truefield, + # # trainer.data.elems_list, + # # "test", + # # index, + # # flag=my_type, + # # ) + # mes_loss = criterion(prefield, truefield) + # # print(f">>> mes_loss = {mes_loss.item():.10f}") + # sum_loss += mes_loss.item() + # print(index) + # # exit() + # avg_loss = sum_loss / (len(loader)) + # avg_loss = np.sqrt(avg_loss) + # root_logger.info(" trajectory_loss") + # root_logger.info(" " + str(avg_loss)) + # print("trajectory_loss=", avg_loss) + # print("============finish============") diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index d7c0a4f86..769990491 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -95,6 +95,7 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_A.numpy() values_A = value_A.numpy() + # with misc.Timer("coo_matrix1"): coo_A = sci_sparse.coo_matrix( (values_A, (indices_A[0], indices_A[1])), shape=(N, N) ) @@ -125,6 +126,7 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_St.numpy() values_A = value_St.numpy() + # with misc.Timer("coo_matrix2"): coo_A = sci_sparse.coo_matrix( (values_A, (indices_A[0], indices_A[1])), shape=(kN, N) ) @@ -259,21 +261,26 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") S0 = edge_index[1][mask].reshape((1, -1)) S1 = edge_index[0][mask].reshape((1, -1)) + index_S = paddle.concat([S0, S1], axis=0) value_S = score[mask].detach().squeeze() n_idx = paddle.zeros([N], dtype=paddle.int64) n_idx[perm] = paddle.arange(perm.shape[0]) + index_S = index_S.astype("int64") index_S[1] = n_idx[index_S[1]] subgraphnode_pos = pos[perm] index_A = edge_index.clone() + if edge_weight is None: value_A = value_S.new_ones(edge_index[0].shape[0]) else: value_A = edge_weight.clone() value_A = paddle.squeeze(value_A) - value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) + eps_mask = (value_S == 0).astype(paddle.get_default_dtype()) + value_S = paddle.full_like(value_S, 1e-4) * eps_mask + (1 - eps_mask) * value_S + # value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) attrlist = [] standard_index, _ = StAS( index_A, @@ -284,15 +291,21 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): kN, nor, ) + # with misc.Timer("range 128"): for i in range(128): - val_A = paddle.where( - value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] - ) + mask = (value_A[:, i] == 0).astype(paddle.get_default_dtype()) + val_A = paddle.full_like(mask, 1e-4) * mask + (1 - mask) * value_A[:, i] + # val_A = paddle.where( + # value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] + # ) + # with misc.Timer("inner StAS"): index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) if index_E.shape[1] != standard_index.shape[1]: + # with misc.Timer("FillZeros"): index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) + # with misc.Timer("remove_self_loops"): index_E, value_E = remove_self_loops(edge_index=index_E, edge_attr=value_E) attrlist.append(value_E) edge_weight = paddle.stack(attrlist, axis=1) @@ -445,6 +458,7 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): nor=self.normalization, ) elif speed == "norm": + # with misc.Timer("norm_graph_connectivity"): subedge_index, edge_weight, subpos = norm_graph_connectivity( perm=coarsenodes, edge_index=cofe_graph.edge_index, @@ -498,7 +512,7 @@ def forward(self, input): class Encoder(nn.Layer): """Encodes node and edge features into latent features.""" - def __init__(self, input_dim, make_mlp, latent_dim, mode): + def __init__(self, input_dim, make_mlp, latent_dim): super(Encoder, self).__init__() self._make_mlp = make_mlp self._latent_dim = latent_dim @@ -507,7 +521,7 @@ def __init__(self, input_dim, make_mlp, latent_dim, mode): # else: self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 4 - self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=input_dim) # 1 + self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=1) # 1 """ for _ in graph.edge_sets: edge_model = make_mlp(latent_dim) @@ -516,9 +530,8 @@ def __init__(self, input_dim, make_mlp, latent_dim, mode): def forward(self, graph): node_latents = self.node_model(graph.x) - # print(f">>> graph.x = {graph.x.shape}") # [26736, 5] edge_latent = self.mesh_edge_model(graph.edge_attr) - # print(f">>> graph.edge_attr = {graph.edge_attr.shape}") # [105344, 1] + graph.x = node_latents graph.edge_attr = edge_latent return graph @@ -548,7 +561,6 @@ def __init__( num_layers, message_passing_aggregator, message_passing_steps, - mode, speed, nodes=6684, ): @@ -560,10 +572,7 @@ def __init__( self.min_nodes = nodes self._message_passing_steps = message_passing_steps self._message_passing_aggregator = message_passing_aggregator - # self.mode = mode - self.encoder = Encoder( - make_mlp=self._make_mlp, latent_dim=self._latent_dim, mode=self.mode - ) + self.encoder = Encoder(input_dim, self._make_mlp, latent_dim=self._latent_dim) self.processor = Processor( make_mlp=self._make_mlp, output_dim=self._latent_dim, @@ -596,19 +605,14 @@ def _spa_compute(self, x, p): node_features = self.post_processor(node_features) return node_features - def forward(self, graphs): - batch = MyCopy(graphs[0]) - - for index, graph in enumerate(graphs): - if index > 0: - batch = Myadd(batch, graph) - - latent_graph = self.encoder(batch) - + def forward(self, x): + graphs = x["input"] + # with misc.Timer("encoder"): + latent_graph = self.encoder(graphs) + # with misc.Timer("processor"): x, p = self.processor(latent_graph, speed=self.speed) - + # with misc.Timer("_spa_compute"): node_features = self._spa_compute(x, p) - + # with misc.Timer("decoder"): pred_field = self.decoder(node_features) - - return pred_field + return {"pred": pred_field} diff --git a/ppsci/data/__init__.py b/ppsci/data/__init__.py index f39a663ff..0faea3beb 100644 --- a/ppsci/data/__init__.py +++ b/ppsci/data/__init__.py @@ -83,8 +83,8 @@ def build_dataloader(_dataset, cfg): # build collate_fn if specified batch_transforms_cfg = cfg.pop("batch_transforms", None) - collate_fn = None - if isinstance(batch_transforms_cfg, dict) and batch_transforms_cfg: + collate_fn = batch_transform.default_collate_fn + if isinstance(batch_transforms_cfg, (list, tuple)): collate_fn = batch_transform.build_batch_transforms(batch_transforms_cfg) # build init function @@ -96,15 +96,28 @@ def build_dataloader(_dataset, cfg): ) # build dataloader - dataloader_ = io.DataLoader( - dataset=_dataset, - places=device.get_device(), - batch_sampler=sampler, - collate_fn=collate_fn, - num_workers=cfg.get("num_workers", 0), - use_shared_memory=cfg.get("use_shared_memory", False), - worker_init_fn=init_fn, - ) + if getattr(_dataset, "use_pgl", False): + # Use special dataloader from "Paddle Graph Learning" toolkit. + from pgl.utils import data as pgl_data + + dataloader_ = pgl_data.Dataloader( + dataset=_dataset, + batch_size=cfg["batch_size"], + drop_last=sampler_cfg.get("drop_last", False), + shuffle=sampler_cfg.get("shuffle", False), + num_workers=cfg.get("num_workers", 1), + collate_fn=collate_fn, + ) + else: + dataloader_ = io.DataLoader( + dataset=_dataset, + places=device.get_device(), + batch_sampler=sampler, + collate_fn=collate_fn, + num_workers=cfg.get("num_workers", 0), + use_shared_memory=cfg.get("use_shared_memory", False), + worker_init_fn=init_fn, + ) if len(dataloader_) == 0: raise ValueError( diff --git a/ppsci/data/dataset/__init__.py b/ppsci/data/dataset/__init__.py index 58fb2c1f8..6a8412fdc 100644 --- a/ppsci/data/dataset/__init__.py +++ b/ppsci/data/dataset/__init__.py @@ -14,6 +14,7 @@ import copy +from ppsci.data.dataset.airfoil_dataset import MeshAirfoilDataset from ppsci.data.dataset.array_dataset import IterableNamedArrayDataset from ppsci.data.dataset.array_dataset import NamedArrayDataset from ppsci.data.dataset.csv_dataset import CSVDataset @@ -46,6 +47,7 @@ "LorenzDataset", "RosslerDataset", "VtuDataset", + "MeshAirfoilDataset", "build_dataset", ] diff --git a/ppsci/data/process/batch_transform/__init__.py b/ppsci/data/process/batch_transform/__init__.py index 2887aa027..91c44c8f9 100644 --- a/ppsci/data/process/batch_transform/__init__.py +++ b/ppsci/data/process/batch_transform/__init__.py @@ -22,9 +22,14 @@ import numpy as np import paddle +try: + import pgl +except ImportError: + pass + from ppsci.data.process import transform -__all__ = ["build_batch_transforms"] +__all__ = ["build_batch_transforms", "default_collate_fn"] def default_collate_fn(batch: List[Any]) -> Any: @@ -39,7 +44,23 @@ def default_collate_fn(batch: List[Any]) -> Any: Any: Collated batch data. """ sample = batch[0] - if isinstance(sample, np.ndarray): + if sample is None: + return None + elif isinstance(sample, pgl.Graph): + graph = pgl.Graph( + num_nodes=sample.num_nodes, + edges=sample.edges, + ) + graph.x = paddle.concat([g.x for g in batch]) + graph.y = paddle.concat([g.y for g in batch]) + graph.edge_index = paddle.concat([g.edge_index for g in batch], axis=1) + graph.edge_attr = paddle.concat([g.edge_attr for g in batch]) + graph.pos = paddle.concat([g.pos for g in batch]) + graph.shape = [ + len(batch), + ] + return graph + elif isinstance(sample, np.ndarray): batch = np.stack(batch, axis=0) return batch elif isinstance(sample, (paddle.Tensor, paddle.framework.core.eager.Tensor)): diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index 95a2832d1..4cac41b6d 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import time from typing import TYPE_CHECKING from typing import Dict from typing import Tuple +from typing import Union import paddle from paddle import io @@ -26,13 +29,41 @@ # from ppsci.utils import profiler if TYPE_CHECKING: + from pgl.utils import data as pgl_data + from ppsci import solver +def _get_datset_length( + data_loader: Union["io.DataLoader", "pgl_data.Dataloader", "io.IterableDataset"] +) -> int: + """Get full dataset length of given dataloader. + + Args: + data_loader (Union[io.DataLoader, pgl_data.Dataloader, io.IterableDataset]): + Given dataloader. + + Returns: + int: Length of full dataset. + """ + if isinstance(data_loader, io.DataLoader): + num_samples = len(data_loader.dataset) + elif isinstance(data_loader, io.IterableDataset): + num_samples = data_loader.num_samples + elif str(type(data_loader)) == "": + num_samples = len(data_loader.dataset) + else: + raise NotImplementedError( + f"Can not fetch the length of given dataset({type(data_loader)})." + ) + + return num_samples + + def _eval_by_dataset( solver: "solver.Solver", epoch_id: int, log_freq: int ) -> Tuple[float, Dict[str, Dict[str, float]]]: - """Evaluate with computing metric on total samples. + """Evaluate with computing metric on total samples(default progress). Args: solver (solver.Solver): Main Solver. @@ -48,10 +79,7 @@ def _eval_by_dataset( all_input = misc.Prettydefaultdict(list) all_output = misc.Prettydefaultdict(list) all_label = misc.Prettydefaultdict(list) - if isinstance(_validator.data_loader, io.DataLoader): - num_samples = len(_validator.data_loader.dataset) - else: - num_samples = _validator.data_loader.num_samples + num_samples = _get_datset_length(_validator.data_loader) loss_dict = misc.Prettydefaultdict(float) reader_tic = time.perf_counter() @@ -87,18 +115,24 @@ def _eval_by_dataset( for key, input in input_dict.items(): all_input[key].append( input.detach() + if hasattr(input, "detach") + else input if solver.world_size == 1 else misc.all_gather(input.detach()) ) for key, output in output_dict.items(): all_output[key].append( output.detach() + if hasattr(output, "detach") + else output if solver.world_size == 1 else misc.all_gather(output.detach()) ) for key, label in label_dict.items(): all_label[key].append( label.detach() + if hasattr(label, "detach") + else label if solver.world_size == 1 else misc.all_gather(label.detach()) ) @@ -122,15 +156,18 @@ def _eval_by_dataset( # concate all data and discard padded sample(s) for key in all_input: - all_input[key] = paddle.concat(all_input[key]) + if paddle.is_tensor(all_input[key]): + all_input[key] = paddle.concat(all_input[key]) if len(all_input[key]) > num_samples: all_input[key] = all_input[key][:num_samples] for key in all_output: - all_output[key] = paddle.concat(all_output[key]) + if paddle.is_tensor(all_input[key]): + all_output[key] = paddle.concat(all_output[key]) if len(all_output[key]) > num_samples: all_output[key] = all_output[key][:num_samples] for key in all_label: - all_label[key] = paddle.concat(all_label[key]) + if paddle.is_tensor(all_input[key]): + all_label[key] = paddle.concat(all_label[key]) if len(all_label[key]) > num_samples: all_label[key] = all_label[key][:num_samples] @@ -174,10 +211,7 @@ def _eval_by_batch( """ target_metric: float = None for _, _validator in solver.validator.items(): - if isinstance(_validator.data_loader, io.DataLoader): - num_samples = len(_validator.data_loader.dataset) - else: - num_samples = _validator.data_loader.num_samples + num_samples = _get_datset_length(_validator.data_loader) loss_dict = misc.Prettydefaultdict(float) metric_dict_group = misc.PrettyOrderedDict() diff --git a/ppsci/solver/train.py b/ppsci/solver/train.py index a251e668b..26de4a11f 100644 --- a/ppsci/solver/train.py +++ b/ppsci/solver/train.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import time from typing import TYPE_CHECKING diff --git a/ppsci/utils/misc.py b/ppsci/utils/misc.py index bf4298860..a26fc3818 100644 --- a/ppsci/utils/misc.py +++ b/ppsci/utils/misc.py @@ -18,6 +18,7 @@ import functools import os import random +import time from typing import Callable from typing import Dict from typing import List @@ -29,6 +30,8 @@ from matplotlib import pyplot as plt from paddle import distributed as dist +from ppsci.utils import logger + __all__ = [ "all_gather", "AverageMeter", @@ -44,6 +47,7 @@ "run_on_eval_mode", "run_at_rank0", "plot_curve", + "Timer", ] @@ -381,3 +385,38 @@ def plot_curve( plt.savefig(os.path.join(output_dir, f"{xlabel}-{ylabel}_curve.jpg")) plt.clf() + + +class Timer: + """Count time cost for code block within context. + + Args: + name (str, optional): Name of timer discriminate different code block. + Defaults to "Timer". + auto_print (bool, optional): Whether print time cost when exit context. + Defaults to True. + + Examples: + >>> import paddle + >>> from ppsci.utils import misc + >>> with misc.Timer(auto_print=False) as timer: + ... w = sum(range(0, 10)) + >>> print(f"time cost of 'sum(range(0, 10))' is {timer.interval:.2f}") + time cost of 'sum(range(0, 10))' is 0.00 + """ + + interval: float # Time cost for code within Timer context + + def __init__(self, name: str = "Timer", auto_print: bool = True): + self.name = name + self.auto_print = auto_print + + def __enter__(self): + self.start_time = time.perf_counter() + return self + + def __exit__(self, type, value, traceback): + self.end_time = time.perf_counter() + self.interval = self.end_time - self.start_time + if self.auto_print: + logger.message(f"{self.name}.time_cost = {self.interval:.2f} s") From 24d4d09a6d4599850d98f6cf0100f184cb972c0e Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 04:37:47 +0000 Subject: [PATCH 03/27] try import pgl to avoid importerror --- ppsci/arch/amgnet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 769990491..92fb3b9cb 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -21,7 +21,12 @@ import numpy as np import paddle import paddle.nn as nn -import pgl + +try: + import pgl +except ImportError: + pass + from pyamg.classical.split import RS from scipy import sparse as sci_sparse From f1bd74474bfe980460d90dcfe389b913c07da0be Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 05:13:28 +0000 Subject: [PATCH 04/27] try import pyamg to avoid importerror --- examples/amgnet/amgnet.py | 1 + ppsci/arch/amgnet.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/amgnet/amgnet.py b/examples/amgnet/amgnet.py index 81d868536..fa2f123c3 100644 --- a/examples/amgnet/amgnet.py +++ b/examples/amgnet/amgnet.py @@ -133,6 +133,7 @@ def eval_rmse_func(output_dict, label_dict, *args): None, EPOCHS, ITERS_PER_EPOCH, + save_freq=50, eval_during_train=True, eval_freq=50, validator=validator, diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 92fb3b9cb..974078428 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -27,7 +27,11 @@ except ImportError: pass -from pyamg.classical.split import RS +try: + from pyamg.classical.split import RS +except ImportError: + pass + from scipy import sparse as sci_sparse From 83f707c398e8ec589138d2d65a1511351e0da540 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 05:44:18 +0000 Subject: [PATCH 05/27] add airfoil_dataset.py --- ppsci/data/dataset/airfoil_dataset.py | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 ppsci/data/dataset/airfoil_dataset.py diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py new file mode 100644 index 000000000..ea0c9096d --- /dev/null +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -0,0 +1,220 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os +import pickle +from os import PathLike +from os import path as osp +from typing import Dict +from typing import List +from typing import Tuple +from typing import Union + +import numpy as np +import paddle +import pgl + +try: + from pgl.utils import data as pgl_data +except ImportError: + pass + +SU2_SHAPE_IDS = { + "line": 3, + "triangle": 5, + "quad": 9, +} + + +def _get_mesh_graph( + mesh_filename: Union[str, PathLike], dtype: np.dtype = np.float32 +) -> Tuple[np.ndarray, np.ndarray, List[List[List[int]]], Dict[str, List[List[int]]]]: + def get_rhs(s: str) -> str: + return s.split("=")[-1] + + marker_dict = {} + with open(mesh_filename) as f: + for line in f: + if line.startswith("NPOIN"): + num_points = int(get_rhs(line)) + mesh_points = [ + [float(p) for p in f.readline().split()[:2]] + for _ in range(num_points) + ] + nodes = np.array(mesh_points, dtype=dtype) + if line.startswith("NMARK"): + num_markers = int(get_rhs(line)) + for _ in range(num_markers): + line = f.readline() + assert line.startswith("MARKER_TAG") + marker_tag = get_rhs(line).strip() + num_elems = int(get_rhs(f.readline())) + marker_elems = [ + [int(e) for e in f.readline().split()[-2:]] + for _ in range(num_elems) + ] + marker_dict[marker_tag] = marker_elems + if line.startswith("NELEM"): + edges = [] + triangles = [] + quads = [] + num_edges = int(get_rhs(line)) + for _ in range(num_edges): + elem = [int(p) for p in f.readline().split()] + if elem[0] == SU2_SHAPE_IDS["triangle"]: + n = 3 + triangles.append(elem[1 : 1 + n]) + elif elem[0] == SU2_SHAPE_IDS["quad"]: + n = 4 + quads.append(elem[1 : 1 + n]) + else: + raise NotImplementedError + elem = elem[1 : 1 + n] + edges += [[elem[i], elem[(i + 1) % n]] for i in range(n)] + edges = np.array(edges, dtype=np.compat.long).transpose() + elems = [triangles, quads] + return nodes, edges, elems, marker_dict + + +class MeshAirfoilDataset(pgl_data.Dataset): + """Dataset for `MeshAirfoil`. + + Args: + input_keys (Tuple[str, ...]): Name of input data. + label_keys (Tuple[str, ...]): Name of label data. + data_root (str): Directory of MeshAirfoil data. + mesh_graph_path (str): Path of mesh graph. + """ + + use_pgl: bool = True + + def __init__( + self, + input_keys: Tuple[str, ...], + label_keys: Tuple[str, ...], + data_root: str, + mesh_graph_path: str, + ): + self.input_keys = input_keys + self.label_keys = label_keys + self.data_dir = osp.join(data_root) + self.file_list = os.listdir(self.data_dir) + self.len = len(self.file_list) + self.mesh_graph = _get_mesh_graph(mesh_graph_path) + + with open(osp.join(osp.dirname(self.data_dir), "train_max_min.pkl"), "rb") as f: + self.normalization_factors = pickle.load(f) + + self.nodes = paddle.to_tensor(self.mesh_graph[0]) + self.edges = paddle.to_tensor(self.mesh_graph[1]) + self.elems_list = self.mesh_graph[2] + self.marker_dict = self.mesh_graph[3] + self.node_markers = paddle.full([self.nodes.shape[0], 1], fill_value=-1) + for i, (marker_tag, marker_elems) in enumerate(self.marker_dict.items()): + for elem in marker_elems: + self.node_markers[elem[0]] = i + self.node_markers[elem[1]] = i + + self.raw_graphs = [self.get(i) for i in range(self.len)] + + def __len__(self): + return self.len + + def __getitem__(self, idx): + return ( + { + self.input_keys[0]: self.raw_graphs[idx], + }, + { + self.label_keys[0]: self.raw_graphs[idx], + }, + None, + ) + + def get(self, idx): + with open(osp.join(self.data_dir, self.file_list[idx]), "rb") as f: + fields = pickle.load(f) + fields = paddle.to_tensor(self.preprocess(fields)) + aoa, reynolds, mach = self.get_params_from_name(self.file_list[idx]) + aoa = paddle.to_tensor(aoa) + mach_or_reynolds = mach if reynolds is None else reynolds + mach_or_reynolds = paddle.to_tensor(mach_or_reynolds) + norm_aoa = paddle.to_tensor(aoa / 10) + norm_mach_or_reynolds = paddle.to_tensor( + mach_or_reynolds if reynolds is None else (mach_or_reynolds - 1.5e6) / 1.5e6 + ) + + nodes = np.concatenate( + [ + self.nodes, + np.repeat(a=norm_aoa, repeats=self.nodes.shape[0])[:, np.newaxis], + np.repeat(a=norm_mach_or_reynolds, repeats=self.nodes.shape[0])[ + :, np.newaxis + ], + self.node_markers, + ], + axis=-1, + ).astype(np.float32) + nodes = paddle.to_tensor(nodes) + data = pgl.Graph( + num_nodes=nodes.shape[0], + edges=self.edges, + ) + + data.x = nodes + data.y = fields + data.pos = self.nodes + data.edge_index = self.edges + sender = data.x[data.edge_index[0]] + receiver = data.x[data.edge_index[1]] + relation_pos = sender[:, 0:2] - receiver[:, 0:2] + post = paddle.linalg.norm(relation_pos, p=2, axis=1, keepdim=True) + data.edge_attr = post + std_epsilon = paddle.to_tensor([1e-8]) + a = paddle.mean(data.edge_attr, axis=0) + b = data.edge_attr.std(axis=0) + b = paddle.maximum(b, std_epsilon) + data.edge_attr = (data.edge_attr - a) / b + data.aoa = aoa + data.norm_aoa = norm_aoa + data.mach_or_reynolds = mach_or_reynolds + data.norm_mach_or_reynolds = norm_mach_or_reynolds + return data + + def preprocess(self, tensor_list, stack_output=True): + data_max, data_min = self.normalization_factors + normalized_tensors = [] + for i in range(len(tensor_list)): + normalized = (tensor_list[i] - data_min[i]) / ( + data_max[i] - data_min[i] + ) * 2 - 1 + normalized_tensors.append(normalized) + if stack_output: + normalized_tensors = np.stack(normalized_tensors, axis=1) + return normalized_tensors + + @staticmethod + def get_params_from_name(filename): + s = filename.rsplit(".", 1)[0].split("_") + aoa = np.array(s[s.index("aoa") + 1])[np.newaxis].astype(np.float32) + reynolds = s[s.index("re") + 1] + reynolds = ( + np.array(reynolds)[np.newaxis].astype(np.float32) + if reynolds != "None" + else None + ) + mach = np.array(s[s.index("mach") + 1])[np.newaxis].astype(np.float32) + return aoa, reynolds, mach From e77084212c8bb655234a893c03b5e438e100bf58 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 07:24:21 +0000 Subject: [PATCH 06/27] add type checking for amgnet --- examples/amgnet/amgnet.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/examples/amgnet/amgnet.py b/examples/amgnet/amgnet.py index fa2f123c3..dcae7ca53 100644 --- a/examples/amgnet/amgnet.py +++ b/examples/amgnet/amgnet.py @@ -12,18 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Dict +from typing import List + from paddle.nn import functional as F import ppsci from ppsci.utils import config from ppsci.utils import logger +if TYPE_CHECKING: + import paddle + import pgl -def train_mse_func(output_dict, label_dict, *args): + +def train_mse_func( + output_dict: Dict[str, "paddle.Tensor"], label_dict: Dict[str, "pgl.Graph"], *args +) -> paddle.Tensor: return F.mse_loss(output_dict["pred"], label_dict["label"].y) -def eval_rmse_func(output_dict, label_dict, *args): +def eval_rmse_func( + output_dict: Dict[str, List["paddle.Tensor"]], + label_dict: Dict[str, List["pgl.Graph"]], + *args, +) -> Dict[str, float]: mse_losses = [ F.mse_loss(pred, label.y) for (pred, label) in zip(output_dict["pred"], label_dict["label"]) @@ -40,18 +56,17 @@ def eval_rmse_func(output_dict, label_dict, *args): # initialize logger logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") - # set model - # model for airfoil + # set airfoil model model = ppsci.arch.AMGNet( - 5, - 3, - 128, + input_dim=5, + output_dim=3, + latent_dim=128, num_layers=2, message_passing_aggregator="sum", message_passing_steps=6, speed="norm", ) - # # model for cylinder + # set cylinder model # model = ppsci.arch.AMGNet( # 5, # 3, @@ -89,9 +104,7 @@ def eval_rmse_func(output_dict, label_dict, *args): name="Sup", ) # wrap constraints together - constraint = { - sup_constraint.name: sup_constraint, - } + constraint = {sup_constraint.name: sup_constraint} # set training hyper-parameters EPOCHS = 500 if not args.epochs else args.epochs From e773e20d11273149c6685f04815c125033446475 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 07:27:58 +0000 Subject: [PATCH 07/27] try import pgl to avoid importerror --- ppsci/data/dataset/airfoil_dataset.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index ea0c9096d..a578bb8a0 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -25,13 +25,17 @@ import numpy as np import paddle -import pgl try: from pgl.utils import data as pgl_data except ImportError: pass +try: + import pgl +except ImportError: + pass + SU2_SHAPE_IDS = { "line": 3, "triangle": 5, From fa2addacd9c14ce1355ecbf43ca13704aa72c6ff Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 07:41:54 +0000 Subject: [PATCH 08/27] refine Timer --- ppsci/utils/misc.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ppsci/utils/misc.py b/ppsci/utils/misc.py index a26fc3818..e25e28e4b 100644 --- a/ppsci/utils/misc.py +++ b/ppsci/utils/misc.py @@ -19,6 +19,7 @@ import os import random import time +from contextlib import ContextDecorator from typing import Callable from typing import Dict from typing import List @@ -387,7 +388,7 @@ def plot_curve( plt.clf() -class Timer: +class Timer(ContextDecorator): """Count time cost for code block within context. Args: @@ -399,15 +400,22 @@ class Timer: Examples: >>> import paddle >>> from ppsci.utils import misc - >>> with misc.Timer(auto_print=False) as timer: + >>> with misc.Timer("test1", auto_print=False) as timer: ... w = sum(range(0, 10)) >>> print(f"time cost of 'sum(range(0, 10))' is {timer.interval:.2f}") time cost of 'sum(range(0, 10))' is 0.00 + + >>> @misc.Timer("test2", auto_print=True) + ... def func(): + ... w = sum(range(0, 10)) + >>> func() # doctest: +SKIP + """ interval: float # Time cost for code within Timer context def __init__(self, name: str = "Timer", auto_print: bool = True): + super().__init__() self.name = name self.auto_print = auto_print From c524a27c2f32cb0068b1ef4886c1e28f63976042 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Fri, 22 Sep 2023 08:55:45 +0000 Subject: [PATCH 09/27] replace pgl.Dataset with io.Dataset --- ppsci/data/dataset/airfoil_dataset.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index a578bb8a0..45f89e240 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -16,20 +16,14 @@ import os import pickle -from os import PathLike from os import path as osp from typing import Dict from typing import List from typing import Tuple -from typing import Union import numpy as np import paddle - -try: - from pgl.utils import data as pgl_data -except ImportError: - pass +from paddle import io try: import pgl @@ -44,7 +38,7 @@ def _get_mesh_graph( - mesh_filename: Union[str, PathLike], dtype: np.dtype = np.float32 + mesh_filename: str, dtype: np.dtype = np.float32 ) -> Tuple[np.ndarray, np.ndarray, List[List[List[int]]], Dict[str, List[List[int]]]]: def get_rhs(s: str) -> str: return s.split("=")[-1] @@ -93,7 +87,7 @@ def get_rhs(s: str) -> str: return nodes, edges, elems, marker_dict -class MeshAirfoilDataset(pgl_data.Dataset): +class MeshAirfoilDataset(io.Dataset): """Dataset for `MeshAirfoil`. Args: From 66954a6306773fddb8c0563321f513db848b96af Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 03:01:59 +0000 Subject: [PATCH 10/27] update reproded code --- docs/zh/api/data/dataset.md | 1 + .../amgnet/{amgnet.py => amgnet_airfoil.py} | 54 +- examples/amgnet/amgnet_cylinder.py | 162 ++++++ examples/amgnet/utils.py | 491 ++++++++++++++++++ ppsci/data/dataset/__init__.py | 2 + ppsci/data/dataset/airfoil_dataset.py | 9 +- ppsci/data/dataset/cylinder_dataset.py | 208 ++++++++ 7 files changed, 886 insertions(+), 41 deletions(-) rename examples/amgnet/{amgnet.py => amgnet_airfoil.py} (75%) create mode 100644 examples/amgnet/amgnet_cylinder.py create mode 100644 examples/amgnet/utils.py create mode 100644 ppsci/data/dataset/cylinder_dataset.py diff --git a/docs/zh/api/data/dataset.md b/docs/zh/api/data/dataset.md index 1f65d38a7..d17da305c 100644 --- a/docs/zh/api/data/dataset.md +++ b/docs/zh/api/data/dataset.md @@ -17,4 +17,5 @@ - RosslerDataset - VtuDataset - MeshAirfoilDataset + - MeshCylinderDataset show_root_heading: false diff --git a/examples/amgnet/amgnet.py b/examples/amgnet/amgnet_airfoil.py similarity index 75% rename from examples/amgnet/amgnet.py rename to examples/amgnet/amgnet_airfoil.py index dcae7ca53..9e71a6108 100644 --- a/examples/amgnet/amgnet.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -19,6 +19,7 @@ from typing import List from paddle.nn import functional as F +from utils import log_images import ppsci from ppsci.utils import config @@ -66,16 +67,6 @@ def eval_rmse_func( message_passing_steps=6, speed="norm", ) - # set cylinder model - # model = ppsci.arch.AMGNet( - # 5, - # 3, - # 128, - # num_layers=2, - # message_passing_aggregator="sum", - # message_passing_steps=6, - # speed="norm", - # ) # set dataloader config ITERS_PER_EPOCH = 42 @@ -151,34 +142,21 @@ def eval_rmse_func( eval_freq=50, validator=validator, eval_with_no_grad=True, - # pretrained_model_path="./output_AMGNet/checkpoints/latest" ) # train model solver.train() - # solver.eval() - # with solver.no_grad_context_manager(True): - # sum_loss = 0 - # for index, batch in enumerate(loader): - # truefield = batch[0].y - # prefield = model(batch) - # # print(f"{index }prefield.mean() = {prefield.shape} {prefield.mean().item():.10f}") - # # log_images( - # # batch[0].pos, - # # prefield, - # # truefield, - # # trainer.data.elems_list, - # # "test", - # # index, - # # flag=my_type, - # # ) - # mes_loss = criterion(prefield, truefield) - # # print(f">>> mes_loss = {mes_loss.item():.10f}") - # sum_loss += mes_loss.item() - # print(index) - # # exit() - # avg_loss = sum_loss / (len(loader)) - # avg_loss = np.sqrt(avg_loss) - # root_logger.info(" trajectory_loss") - # root_logger.info(" " + str(avg_loss)) - # print("trajectory_loss=", avg_loss) - # print("============finish============") + + # visualize prediction + with solver.no_grad_context_manager(True): + for index, batch in enumerate(rmse_validator.data_loader): + truefield = batch[0]["input"].y + prefield = model(batch[0]) + log_images( + batch[0]["input"].pos, + prefield["pred"], + truefield, + rmse_validator.data_loader.dataset.elems_list, + "test", + index, + flag="airfoil", + ) diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py new file mode 100644 index 000000000..cdd326e61 --- /dev/null +++ b/examples/amgnet/amgnet_cylinder.py @@ -0,0 +1,162 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Dict +from typing import List + +from paddle.nn import functional as F +from utils import log_images + +import ppsci +from ppsci.utils import config +from ppsci.utils import logger + +if TYPE_CHECKING: + import paddle + import pgl + + +def train_mse_func( + output_dict: Dict[str, "paddle.Tensor"], label_dict: Dict[str, "pgl.Graph"], *args +) -> paddle.Tensor: + return F.mse_loss(output_dict["pred"], label_dict["label"].y) + + +def eval_rmse_func( + output_dict: Dict[str, List["paddle.Tensor"]], + label_dict: Dict[str, List["pgl.Graph"]], + *args, +) -> Dict[str, float]: + mse_losses = [ + F.mse_loss(pred, label.y) + for (pred, label) in zip(output_dict["pred"], label_dict["label"]) + ] + return {"RMSE": (sum(mse_losses) / len(mse_losses)) ** 0.5} + + +if __name__ == "__main__": + args = config.parse_args() + # set random seed for reproducibility + ppsci.utils.misc.set_random_seed(42) + # set output directory + OUTPUT_DIR = "./output_AMGNet_Cylinder" if not args.output_dir else args.output_dir + # initialize logger + logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") + + # set cylinder model + model = ppsci.arch.AMGNet( + input_dim=4, + output_dim=3, + latent_dim=128, + num_layers=2, + message_passing_aggregator="sum", + message_passing_steps=6, + speed="norm", + ) + + # set dataloader config + ITERS_PER_EPOCH = 42 + train_dataloader_cfg = { + "dataset": { + "name": "MeshCylinderDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_root": "./data/cylinderdata/train", + "mesh_graph_path": "./data/cylinderdata/cylinder.su2", + }, + "batch_size": 4, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": True, + }, + "num_workers": 1, + } + + # set constraint + sup_constraint = ppsci.constraint.SupervisedConstraint( + train_dataloader_cfg, + output_expr={"pred": lambda out: out["pred"]}, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + name="Sup", + ) + # wrap constraints together + constraint = {sup_constraint.name: sup_constraint} + + # set training hyper-parameters + EPOCHS = 500 if not args.epochs else args.epochs + + # set optimizer + optimizer = ppsci.optimizer.Adam(5e-4)(model) + + # set validator + eval_dataloader_cfg = { + "dataset": { + "name": "MeshCylinderDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_root": "./data/cylinderdata/test", + "mesh_graph_path": "./data/cylinderdata/cylinder.su2", + }, + "batch_size": 1, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": False, + }, + } + rmse_validator = ppsci.validate.SupervisedValidator( + eval_dataloader_cfg, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + output_expr={"pred": lambda out: out["pred"]}, + metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, + name="RMSE_validator", + ) + validator = {rmse_validator.name: rmse_validator} + + # initialize solver + solver = ppsci.solver.Solver( + model, + constraint, + OUTPUT_DIR, + optimizer, + None, + EPOCHS, + ITERS_PER_EPOCH, + save_freq=50, + eval_during_train=True, + eval_freq=50, + validator=validator, + eval_with_no_grad=True, + ) + # train model + solver.train() + + # visualize prediction + with solver.no_grad_context_manager(True): + for index, batch in enumerate(rmse_validator.data_loader): + truefield = batch[0]["input"].y + prefield = model(batch[0]) + log_images( + batch[0]["input"].pos, + prefield["pred"], + truefield, + rmse_validator.data_loader.dataset.elems_list, + "test", + index, + flag="cylinder", + ) diff --git a/examples/amgnet/utils.py b/examples/amgnet/utils.py new file mode 100644 index 000000000..57e1c027c --- /dev/null +++ b/examples/amgnet/utils.py @@ -0,0 +1,491 @@ +import math +import pathlib +import warnings +from typing import BinaryIO +from typing import List +from typing import Optional +from typing import Text +from typing import Tuple +from typing import Union + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import paddle +import scipy.sparse as sp +from paddle.vision.transforms import ToTensor +from PIL import Image +from pyamg.classical.split import RS +from scipy.sparse import coo_matrix + +matplotlib.use("Agg") + + +def getcorsenode(latent_graph): + row = latent_graph.edge_index[0].numpy() + col = latent_graph.edge_index[1].numpy() + data = paddle.ones(shape=[row.size]).numpy() + A = coo_matrix((data, (row, col))).tocsr() + splitting = RS(A) + index = np.array(np.nonzero(splitting)) + b = paddle.to_tensor(index) + b = paddle.squeeze(b) + return b + + +def StAS(index_A, value_A, index_S, value_S, N, kN, nor): + r"""come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + sp_x = paddle.sparse.sparse_coo_tensor(index_A, value_A) + sp_x = paddle.sparse.coalesce(sp_x) + index_A = sp_x.indices() + value_A = sp_x.values() + + sp_s = paddle.sparse.sparse_coo_tensor(index_S, value_S) + sp_s = paddle.sparse.coalesce(sp_s) + index_S = sp_s.indices() + value_S = sp_s.values() + + indices_A = index_A.numpy() + values_A = value_A.numpy() + coo_A = coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(N, N)) + + indices_S = index_S.numpy() + values_S = value_S.numpy() + coo_S = coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) + + ans = coo_A.dot(coo_S).tocoo() + row = paddle.to_tensor(ans.row) + col = paddle.to_tensor(ans.col) + index_B = paddle.stack([row, col], axis=0) + value_B = paddle.to_tensor(ans.data) + + indices_A = index_S + values_A = value_S + coo_A = paddle.sparse.sparse_coo_tensor(indices_A, values_A) + out = paddle.sparse.transpose(coo_A, [1, 0]) + index_St = out.indices() + value_St = out.values() + + sp_x = paddle.sparse.sparse_coo_tensor(index_B, value_B) + sp_x = paddle.sparse.coalesce(sp_x) + index_B = sp_x.indices() + value_B = sp_x.values() + + indices_A = index_St.numpy() + values_A = value_St.numpy() + coo_A = coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(kN, N)) + + indices_S = index_B.numpy() + values_S = value_B.numpy() + coo_S = coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) + + ans = coo_A.dot(coo_S).tocoo() + row = paddle.to_tensor(ans.row) + col = paddle.to_tensor(ans.col) + index_E = paddle.stack([row, col], axis=0) + value_E = paddle.to_tensor(ans.data) + + # index_E排序 + sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) + sp_x = paddle.sparse.coalesce(sp_x) + index_E = sp_x.indices() + value_E = sp_x.values() + + return index_E, value_E + + +def FillZeros(index_E, value_E, standard_index, kN): + shape = [kN, kN] + row_E = index_E[0] + col_E = index_E[1] + DenseMatrix_E = sp.coo_matrix( + (paddle.ones_like(value_E), (row_E, col_E)), shape + ).toarray() + + row_S = standard_index[0] + col_S = standard_index[1] + DenseMatrix_S = sp.coo_matrix( + (paddle.ones([row_S.shape[0]]), (row_S, col_S)), shape + ).toarray() + + diff = DenseMatrix_S - DenseMatrix_E + rows, cols = np.nonzero(diff) + rows = paddle.to_tensor(rows, dtype="int32") + cols = paddle.to_tensor(cols, dtype="int32") + index = paddle.stack([rows, cols], axis=0) + value = paddle.zeros([index.shape[1]]) + index_E = paddle.concat([index_E, index], axis=1) + value_E = paddle.concat([value_E, value], axis=-1) + + sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) + sp_x = paddle.sparse.coalesce(sp_x) + index_E = sp_x.indices() + value_E = sp_x.values() + + return index_E, value_E + + +# @brief:删除图中的自循环的边 +def remove_self_loops( + edge_index: paddle.Tensor, edge_attr: Optional[paddle.Tensor] = None +) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: + mask = edge_index[0] != edge_index[1] + mask = mask.tolist() + edge_index = edge_index.t() + edge_index = edge_index[mask] + edge_index = edge_index.t() + if edge_attr is None: + return edge_index, None + else: + return edge_index, edge_attr[mask] + + +def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): + """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + kN = perm.shape[0] + perm2 = perm.reshape((-1, 1)) + mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") + + S0 = edge_index[1][mask].reshape((1, -1)) + S1 = edge_index[0][mask].reshape((1, -1)) + index_S = paddle.concat([S0, S1], axis=0) + value_S = score[mask].detach().squeeze() + n_idx = paddle.zeros([N], dtype=paddle.int64) + n_idx[perm] = paddle.arange(perm.shape[0]) + index_S = index_S.astype("int64") + index_S[1] = n_idx[index_S[1]] + subgraphnode_pos = pos[perm] + index_A = edge_index.clone() + if edge_weight is None: + value_A = value_S.new_ones(edge_index[0].shape[0]) + else: + value_A = edge_weight.clone() + + value_A = paddle.squeeze(value_A) + model_1 = paddle.nn.Sequential( + ("l1", paddle.nn.Linear(128, 256)), + ("act1", paddle.nn.ReLU()), + ("l2", paddle.nn.Linear(256, 256)), + ("act2", paddle.nn.ReLU()), + ("l4", paddle.nn.Linear(256, 128)), + ("act4", paddle.nn.ReLU()), + ("l5", paddle.nn.Linear(128, 1)), + ) + model_2 = paddle.nn.Sequential( + ("l1", paddle.nn.Linear(1, 64)), + ("act1", paddle.nn.ReLU()), + ("l2", paddle.nn.Linear(64, 128)), + ("act2", paddle.nn.ReLU()), + ("l4", paddle.nn.Linear(128, 128)), + ) + + val_A = model_1(value_A) + val_A = paddle.squeeze(val_A) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + value_E = paddle.reshape(value_E, shape=[-1, 1]) + edge_weight = model_2(value_E) + + return index_E, edge_weight, subgraphnode_pos + + +def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): + """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + for learning hierarchical graph representations. AAAI(2020)""" + + kN = perm.shape[0] + perm2 = perm.reshape((-1, 1)) + mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") + S0 = edge_index[1][mask].reshape((1, -1)) + S1 = edge_index[0][mask].reshape((1, -1)) + index_S = paddle.concat([S0, S1], axis=0) + value_S = score[mask].detach().squeeze() + n_idx = paddle.zeros([N], dtype=paddle.int64) + n_idx[perm] = paddle.arange(perm.shape[0]) + index_S = index_S.astype("int64") + index_S[1] = n_idx[index_S[1]] + subgraphnode_pos = pos[perm] + index_A = edge_index.clone() + if edge_weight is None: + value_A = value_S.new_ones(edge_index[0].shape[0]) + else: + value_A = edge_weight.clone() + + value_A = paddle.squeeze(value_A) + value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) + attrlist = [] + standard_index, _ = StAS( + index_A, + paddle.ones_like(value_A[:, 0]), + index_S, + paddle.ones_like(value_S), + N, + kN, + nor, + ) + for i in range(128): + val_A = paddle.where( + value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] + ) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + + if index_E.shape[1] != standard_index.shape[1]: + index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) + + index_E, value_E = remove_self_loops(edge_index=index_E, edge_attr=value_E) + attrlist.append(value_E) + edge_weight = paddle.stack(attrlist, axis=1) + + return index_E, edge_weight, subgraphnode_pos + + +@paddle.no_grad() +def make_grid( + tensor: Union[paddle.Tensor, List[paddle.Tensor]], + nrow: int = 8, + padding: int = 2, + normalize: bool = False, + value_range: Optional[Tuple[int, int]] = None, + scale_each: bool = False, + pad_value: int = 0, + **kwargs, +) -> paddle.Tensor: + if not ( + isinstance(tensor, paddle.Tensor) + or ( + isinstance(tensor, list) + and all(isinstance(t, paddle.Tensor) for t in tensor) + ) + ): + raise TypeError(f"tensor or list of tensors expected, got {type(tensor)}") + + if "range" in kwargs.keys(): + warning = "range will be deprecated, please use value_range instead." + warnings.warn(warning) + value_range = kwargs["range"] + + # if list of tensors, convert to a 4D mini-batch Tensor + if isinstance(tensor, list): + tensor = paddle.stack(tensor, axis=0) + + if tensor.dim() == 2: # single image H x W + tensor = tensor.unsqueeze(0) + if tensor.dim() == 3: # single image + if tensor.shape[0] == 1: # if single-channel, convert to 3-channel + tensor = paddle.concat((tensor, tensor, tensor), 0) + tensor = tensor.unsqueeze(0) + if tensor.dim() == 4 and tensor.shape[1] == 1: # single-channel images + tensor = paddle.concat((tensor, tensor, tensor), 1) + + if normalize is True: + if value_range is not None: + assert isinstance( + value_range, tuple + ), "value_range has to be a tuple (min, max) if specified. min and max are numbers" + + def norm_ip(img, low, high): + img.clip(min=low, max=high) + img = img - low + img = img / max(high - low, 1e-5) + + def norm_range(t, value_range): + if value_range is not None: + norm_ip(t, value_range[0], value_range[1]) + else: + norm_ip(t, float(t.min()), float(t.max())) + + if scale_each is True: + for t in tensor: # loop over mini-batch dimension + norm_range(t, value_range) + else: + norm_range(tensor, value_range) + + if tensor.shape[0] == 1: + return tensor.squeeze(0) + + # make the mini-batch of images into a grid + nmaps = tensor.shape[0] + xmaps = min(nrow, nmaps) + ymaps = int(math.ceil(float(nmaps) / xmaps)) + height, width = int(tensor.shape[2] + padding), int(tensor.shape[3] + padding) + num_channels = tensor.shape[1] + grid = paddle.full( + (num_channels, height * ymaps + padding, width * xmaps + padding), pad_value + ) + k = 0 + for y in range(ymaps): + for x in range(xmaps): + if k >= nmaps: + break + grid[ + :, + y * height + padding : (y + 1) * height, + x * width + padding : (x + 1) * width, + ] = tensor[k] + k = k + 1 + return grid + + +@paddle.no_grad() +def save_image( + tensor: Union[paddle.Tensor, List[paddle.Tensor]], + fp: Union[Text, pathlib.Path, BinaryIO], + format: Optional[str] = None, + **kwargs, +) -> None: + grid = make_grid(tensor, **kwargs) + ndarr = ( + paddle.clip(grid * 255 + 0.5, 0, 255).transpose([1, 2, 0]).cast("uint8").numpy() + ) + im = Image.fromarray(ndarr) + im.save(fp, format=format) + + +def log_images( + nodes, + pred, + true, + elems_list, + mode, + index, + flag, + aoa=0, + mach=0, + iterate=0, + file="field.png", +): + for field in range(pred.shape[1]): + true_img = plot_field( + nodes, + elems_list, + true[:, field], + title="true", + clim=(-0.8, 0.8), + model=flag, + col=field, + ) + true_img = ToTensor()(true_img) + # min_max = (true[:, field].min().item(), true[:, field].max().item()) + + pred_img = plot_field( + nodes, + elems_list, + pred[:, field], + title="pred", + clim=(-0.8, 0.8), + model=flag, + col=field, + ) + pred_img = ToTensor()(pred_img) + imgs = [pred_img, true_img] + grid = make_grid(paddle.stack(imgs), padding=0) + out_file = file + f"{field}" + if flag == "airfoil": + if aoa == 8.0 and mach == 0.65: + save_image( + grid, "./result/image/" + str(index) + out_file + "_field.png" + ) + save_image( + grid, "./result/image/airfoil/" + str(index) + out_file + "_field.png" + ) + else: + if aoa == 39.0: + save_image( + grid, "./result/image/" + str(index) + out_file + "_field.png" + ) + save_image( + grid, "./result/image/cylinder/" + str(index) + out_file + "_field.png" + ) + + +def plot_field( + nodes, + elems_list, + field, + model, + col, + contour=False, + clim=None, + zoom=True, + get_array=True, + out_file=None, + show=False, + title="", +): + elems_list = sum(elems_list, []) + tris, _ = quad2tri(elems_list) + tris = np.array(tris) + x, y = nodes[:, :2].t().detach().numpy() + field = field.detach().numpy() + fig = plt.figure(dpi=800) + if contour: + plt.tricontourf(x, y, tris, field) + else: + plt.tripcolor(x, y, tris, field) + if clim: + plt.clim(*clim) + colorbar = plt.colorbar() + if model == "airfoil": + if col == 0: + colorbar.set_label("x-velocity", fontsize=16) + elif col == 1: + colorbar.set_label("pressure", fontsize=16) + elif col == 2: + colorbar.set_label("y-velocity", fontsize=16) + if model == "cylinder": + if col == 0: + colorbar.set_label("pressure", fontsize=16) + elif col == 1: + colorbar.set_label("x-velocity", fontsize=16) + elif col == 2: + colorbar.set_label("y-velocity", fontsize=16) + if zoom: + if model == "airfoil": + plt.xlim(left=-0.5, right=1.5) + plt.ylim(bottom=-0.5, top=0.5) + else: + plt.xlim(left=-5, right=5.0) + plt.ylim(bottom=-5, top=5.0) + + if title: + plt.title(title) + + if out_file is not None: + plt.savefig(out_file) + plt.close() + + if show: + plt.show() + raise NotImplementedError + + if get_array: + if model == "airfoil": + plt.gca().invert_yaxis() + fig.canvas.draw() + a = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) + a = a.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + fig.clf() + fig.clear() + plt.close() + return a + + +def quad2tri(elems): + new_elems = [] + new_edges = [] + for e in elems: + if len(e) <= 3: + new_elems.append(e) + else: + new_elems.append([e[0], e[1], e[2]]) + new_elems.append([e[0], e[2], e[3]]) + new_edges.append(paddle.to_tensor(([[e[0]], [e[2]]]), dtype=paddle.int64)) + new_edges = ( + paddle.concat(new_edges, axis=1) + if new_edges + else paddle.to_tensor([], dtype=paddle.int64) + ) + return new_elems, new_edges diff --git a/ppsci/data/dataset/__init__.py b/ppsci/data/dataset/__init__.py index 6a8412fdc..f5238bc60 100644 --- a/ppsci/data/dataset/__init__.py +++ b/ppsci/data/dataset/__init__.py @@ -19,6 +19,7 @@ from ppsci.data.dataset.array_dataset import NamedArrayDataset from ppsci.data.dataset.csv_dataset import CSVDataset from ppsci.data.dataset.csv_dataset import IterableCSVDataset +from ppsci.data.dataset.cylinder_dataset import MeshCylinderDataset from ppsci.data.dataset.era5_dataset import ERA5Dataset from ppsci.data.dataset.era5_dataset import ERA5SampledDataset from ppsci.data.dataset.mat_dataset import IterableMatDataset @@ -48,6 +49,7 @@ "RosslerDataset", "VtuDataset", "MeshAirfoilDataset", + "MeshCylinderDataset", "build_dataset", ] diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index 45f89e240..9064c0c62 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -37,6 +37,7 @@ } +# HACK: Simplify code below def _get_mesh_graph( mesh_filename: str, dtype: np.dtype = np.float32 ) -> Tuple[np.ndarray, np.ndarray, List[List[List[int]]], Dict[str, List[List[int]]]]: @@ -93,7 +94,7 @@ class MeshAirfoilDataset(io.Dataset): Args: input_keys (Tuple[str, ...]): Name of input data. label_keys (Tuple[str, ...]): Name of label data. - data_root (str): Directory of MeshAirfoil data. + data_dir (str): Directory of MeshAirfoil data. mesh_graph_path (str): Path of mesh graph. """ @@ -103,12 +104,12 @@ def __init__( self, input_keys: Tuple[str, ...], label_keys: Tuple[str, ...], - data_root: str, + data_dir: str, mesh_graph_path: str, ): self.input_keys = input_keys self.label_keys = label_keys - self.data_dir = osp.join(data_root) + self.data_dir = data_dir self.file_list = os.listdir(self.data_dir) self.len = len(self.file_list) self.mesh_graph = _get_mesh_graph(mesh_graph_path) @@ -126,6 +127,8 @@ def __init__( self.node_markers[elem[0]] = i self.node_markers[elem[1]] = i + # HACK: For unsupporting convert to tensor at different workers in runtime, + # so load all graph into GPU-memory at onec. This need to be optimized. self.raw_graphs = [self.get(i) for i in range(self.len)] def __len__(self): diff --git a/ppsci/data/dataset/cylinder_dataset.py b/ppsci/data/dataset/cylinder_dataset.py new file mode 100644 index 000000000..c8415591c --- /dev/null +++ b/ppsci/data/dataset/cylinder_dataset.py @@ -0,0 +1,208 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os +from os import path as osp +from typing import Tuple + +import numpy as np +import paddle +from paddle import io + +from ppsci.data.dataset import airfoil_dataset + +try: + import pgl +except ImportError: + pass + +SU2_SHAPE_IDS = { + "line": 3, + "triangle": 5, + "quad": 9, +} + + +class MeshCylinderDataset(io.Dataset): + """Dataset for `MeshCylinder`. + + Args: + input_keys (Tuple[str, ...]): Name of input data. + label_keys (Tuple[str, ...]): Name of label data. + data_root (str): Directory of MeshCylinder data. + mesh_graph_path (str): Path of mesh graph. + """ + + use_pgl: bool = True + + def __init__( + self, + input_keys: Tuple[str, ...], + label_keys: Tuple[str, ...], + data_root: str, + mesh_graph_path: str, + ): + self.input_keys = input_keys + self.label_keys = label_keys + self.data_dir = data_root + self.file_list = os.listdir(self.data_dir) + self.len = len(self.file_list) + self.mesh_graph = airfoil_dataset._get_mesh_graph(mesh_graph_path) + + self.normalization_factors = paddle.to_tensor( + [[978.6001, 48.9258, 24.8404], [-692.3159, -6.9950, -24.8572]] + ) + + self.nodes = paddle.to_tensor(self.mesh_graph[0]) + self.meshnodes = self.mesh_graph[0] + self.edges = paddle.to_tensor(self.mesh_graph[1]) + self.elems_list = self.mesh_graph[2] + self.marker_dict = self.mesh_graph[3] + self.bounder = [] + self.node_markers = paddle.full([self.nodes.shape[0], 1], fill_value=-1) + for i, (marker_tag, marker_elems) in enumerate(self.marker_dict.items()): + for elem in marker_elems: + self.node_markers[elem[0]] = i + self.node_markers[elem[1]] = i + + # HACK: For unsupporting convert to tensor at different workers in runtime, + # so load all graph into GPU-memory at onec. This need to be optimized. + self.raw_graphs = [self.get(i) for i in range(self.len)] + + def __len__(self): + return self.len + + def __getitem__(self, idx): + return ( + { + self.input_keys[0]: self.raw_graphs[idx], + }, + { + self.label_keys[0]: self.raw_graphs[idx], + }, + None, + ) + + def get(self, idx): + with open(osp.join(self.data_dir, self.file_list[idx]), "r") as f: + field = [] + pos = [] + for line in f.read().splitlines()[1:]: + lines_pos = line.split(",")[1:3] + lines_field = line.split(",")[3:] + numbers_float = list(eval(i) for i in lines_pos) + array = np.array(numbers_float, paddle.get_default_dtype()) + a = paddle.to_tensor(array) + pos.append(a) + numbers_float = list(eval(i) for i in lines_field) + array = np.array(numbers_float, paddle.get_default_dtype()) + a = paddle.to_tensor(array) + field.append(a) + + field = paddle.stack(field, axis=0) + pos = paddle.stack(pos, axis=0) + indexlist = [] + for i in range(self.meshnodes.shape[0]): + b = paddle.to_tensor(self.meshnodes[i : (i + 1)]) + b = paddle.squeeze(b) + index = paddle.nonzero( + paddle.sum((pos == b), axis=1, dtype="float32") == pos.shape[1] + ) + indexlist.append(index) + indexlist = paddle.stack(indexlist, axis=0) + indexlist = paddle.squeeze(indexlist) + fields = field[indexlist] + velocity = self.get_params_from_name(self.file_list[idx]) + aoa = paddle.to_tensor(velocity) + + norm_aoa = paddle.to_tensor(aoa / 40) + # add physics parameters to graph + nodes = np.concatenate( + [ + self.nodes, + np.repeat(a=norm_aoa, repeats=self.nodes.shape[0])[:, np.newaxis], + self.node_markers, + ], + axis=-1, + ).astype(paddle.get_default_dtype()) + nodes = paddle.to_tensor(nodes) + + data = pgl.Graph( + num_nodes=nodes.shape[0], + edges=self.edges, + ) + data.x = nodes + data.y = fields + data.pos = self.nodes + data.edge_index = self.edges + data.velocity = aoa + + sender = data.x[data.edge_index[0]] + receiver = data.x[data.edge_index[1]] + relation_pos = sender[:, 0:2] - receiver[:, 0:2] + post = paddle.linalg.norm(relation_pos, p=2, axis=1, keepdim=True) + data.edge_attr = post + std_epsilon = paddle.to_tensor([1e-8]) + a = paddle.mean(data.edge_attr, axis=0) + b = data.edge_attr.std(axis=0) + b = paddle.maximum(b, std_epsilon) + data.edge_attr = (data.edge_attr - a) / b + a = paddle.mean(data.y, axis=0) + b = data.y.std(axis=0) + b = paddle.maximum(b, std_epsilon) + data.y = (data.y - a) / b + data.norm_max = a + data.norm_min = b + + # find the face of the boundery,our cylinder dataset come from fluent solver + with open(osp.join(osp.dirname(self.data_dir), "bounder"), "r") as f: + field = [] + pos = [] + for line in f.read().splitlines()[1:]: + lines_pos = line.split(",")[1:3] + lines_field = line.split(",")[3:] + numbers_float = list(eval(i) for i in lines_pos) + array = np.array(numbers_float, paddle.get_default_dtype()) + a = paddle.to_tensor(array) + pos.append(a) + numbers_float = list(eval(i) for i in lines_field) + array = np.array(numbers_float, paddle.get_default_dtype()) + a = paddle.to_tensor(array) + field.append(a) + + field = paddle.stack(field, axis=0) + pos = paddle.stack(pos, axis=0) + + indexlist = [] + for i in range(pos.shape[0]): + b = pos[i : (i + 1)] + b = paddle.squeeze(b) + index = paddle.nonzero( + paddle.sum((self.nodes == b), axis=1, dtype="float32") + == self.nodes.shape[1] + ) + indexlist.append(index) + + indexlist = paddle.stack(indexlist, axis=0) + indexlist = paddle.squeeze(indexlist) + self.bounder = indexlist + return data + + @staticmethod + def get_params_from_name(filename): + s = filename.rsplit(".", 1)[0] + reynolds = np.array(s[13:])[np.newaxis].astype(paddle.get_default_dtype()) + return reynolds From ac136933817cbe02ea6965f8a7c5c507e3885187 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 03:27:46 +0000 Subject: [PATCH 11/27] replace ImportError with ModuleNotFoundError --- ppsci/arch/amgnet.py | 4 ++-- ppsci/data/dataset/airfoil_dataset.py | 2 +- ppsci/data/dataset/cylinder_dataset.py | 2 +- ppsci/data/process/batch_transform/__init__.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 974078428..aa225c8ae 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -24,12 +24,12 @@ try: import pgl -except ImportError: +except ModuleNotFoundError: pass try: from pyamg.classical.split import RS -except ImportError: +except ModuleNotFoundError: pass from scipy import sparse as sci_sparse diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index 9064c0c62..f6bab2452 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -27,7 +27,7 @@ try: import pgl -except ImportError: +except ModuleNotFoundError: pass SU2_SHAPE_IDS = { diff --git a/ppsci/data/dataset/cylinder_dataset.py b/ppsci/data/dataset/cylinder_dataset.py index c8415591c..0e43226f4 100644 --- a/ppsci/data/dataset/cylinder_dataset.py +++ b/ppsci/data/dataset/cylinder_dataset.py @@ -26,7 +26,7 @@ try: import pgl -except ImportError: +except ModuleNotFoundError: pass SU2_SHAPE_IDS = { diff --git a/ppsci/data/process/batch_transform/__init__.py b/ppsci/data/process/batch_transform/__init__.py index 91c44c8f9..badee27b7 100644 --- a/ppsci/data/process/batch_transform/__init__.py +++ b/ppsci/data/process/batch_transform/__init__.py @@ -24,7 +24,7 @@ try: import pgl -except ImportError: +except ModuleNotFoundError: pass from ppsci.data.process import transform From f0ae8445fee71d53f40259c029a3043ee64e825d Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 03:58:30 +0000 Subject: [PATCH 12/27] refine amgnet.py --- ppsci/arch/amgnet.py | 158 +++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 90 deletions(-) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index aa225c8ae..301272e5f 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -15,12 +15,16 @@ from __future__ import annotations import functools +from typing import Callable +from typing import Dict +from typing import List from typing import Optional from typing import Tuple import numpy as np import paddle import paddle.nn as nn +from typing_extensions import Literal try: import pgl @@ -28,19 +32,21 @@ pass try: - from pyamg.classical.split import RS + from pyamg.classical import split except ModuleNotFoundError: pass from scipy import sparse as sci_sparse -def _knn_interpolate(features, coarse_nodes, fine_nodes): +def _knn_interpolate( + features: paddle.Tensor, coarse_nodes: paddle.Tensor, fine_nodes: paddle.Tensor +) -> paddle.Tensor: coarse_nodes_input = paddle.repeat_interleave( - coarse_nodes.unsqueeze(0), fine_nodes.shape[0], 0 + coarse_nodes.unsqueeze(0), fine_nodes.shape[0], axis=0 ) # [6684,352,2] fine_nodes_input = paddle.repeat_interleave( - fine_nodes.unsqueeze(1), coarse_nodes.shape[0], 1 + fine_nodes.unsqueeze(1), coarse_nodes.shape[0], axis=1 ) # [6684,352,2] dist_w = 1.0 / ( paddle.norm(x=coarse_nodes_input - fine_nodes_input, p=2, axis=-1) + 1e-9 @@ -54,34 +60,12 @@ def _knn_interpolate(features, coarse_nodes, fine_nodes): return output -def MyCopy(graph): - data = pgl.Graph( - num_nodes=graph.num_nodes, - edges=graph.edges, - ) - data.x = graph.x - data.y = graph.y - data.pos = graph.pos - data.edge_index = graph.edge_index - data.edge_attr = graph.edge_attr - return data - - -def Myadd(g1, g2): - g1.x = paddle.concat([g1.x, g2.x], axis=0) - g1.y = paddle.concat([g1.y, g2.y], axis=0) - g1.edge_index = paddle.concat([g1.edge_index, g2.edge_index], axis=1) - g1.edge_attr = paddle.concat([g1.edge_attr, g2.edge_attr], axis=0) - g1.pos = paddle.concat([g1.pos, g2.pos], axis=0) - return g1 - - -def getcorsenode(latent_graph): +def getcorsenode(latent_graph: "pgl.Graph") -> paddle.Tensor: row = latent_graph.edge_index[0].numpy() col = latent_graph.edge_index[1].numpy() data = paddle.ones(shape=[row.size]).numpy() A = sci_sparse.coo_matrix((data, (row, col))).tocsr() - splitting = RS(A) + splitting = split.RS(A) index = np.array(np.nonzero(splitting)) b = paddle.to_tensor(index) b = paddle.squeeze(b) @@ -89,8 +73,10 @@ def getcorsenode(latent_graph): def StAS(index_A, value_A, index_S, value_S, N, kN, nor): - r"""come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling - for learning hierarchical graph representations. AAAI(2020)""" + """ + Asap: Adaptive structure aware pooling for learning hierarchical graph representations. + Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). AAAI(2020) + """ sp_x = paddle.sparse.sparse_coo_tensor(index_A, value_A) sp_x = paddle.sparse.coalesce(sp_x) @@ -104,7 +90,6 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_A.numpy() values_A = value_A.numpy() - # with misc.Timer("coo_matrix1"): coo_A = sci_sparse.coo_matrix( (values_A, (indices_A[0], indices_A[1])), shape=(N, N) ) @@ -135,7 +120,6 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_St.numpy() values_A = value_St.numpy() - # with misc.Timer("coo_matrix2"): coo_A = sci_sparse.coo_matrix( (values_A, (indices_A[0], indices_A[1])), shape=(kN, N) ) @@ -193,13 +177,11 @@ def FillZeros(index_E, value_E, standard_index, kN): return index_E, value_E -# @brief:删除图中的自循环的边 def remove_self_loops( edge_index: paddle.Tensor, edge_attr: Optional[paddle.Tensor] = None ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: mask = edge_index[0] != edge_index[1] mask = mask.tolist() - # edge_index = edge_index[:, mask] edge_index = edge_index.t() edge_index = edge_index[mask] edge_index = edge_index.t() @@ -238,7 +220,6 @@ def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor) ("act1", paddle.nn.ReLU()), ("l2", paddle.nn.Linear(256, 256)), ("act2", paddle.nn.ReLU()), - # ('l3', paddle.nn.Linear(256, 256)), ('act3', paddle.nn.ReLU()), ("l4", paddle.nn.Linear(256, 128)), ("act4", paddle.nn.ReLU()), ("l5", paddle.nn.Linear(128, 1)), @@ -248,7 +229,6 @@ def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor) ("act1", paddle.nn.ReLU()), ("l2", paddle.nn.Linear(64, 128)), ("act2", paddle.nn.ReLU()), - # ('l3', paddle.nn.Linear(128, 128)),('act3', paddle.nn.ReLU()), ("l4", paddle.nn.Linear(128, 128)), ) @@ -289,7 +269,6 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): value_A = paddle.squeeze(value_A) eps_mask = (value_S == 0).astype(paddle.get_default_dtype()) value_S = paddle.full_like(value_S, 1e-4) * eps_mask + (1 - eps_mask) * value_S - # value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) attrlist = [] standard_index, _ = StAS( index_A, @@ -304,17 +283,11 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): for i in range(128): mask = (value_A[:, i] == 0).astype(paddle.get_default_dtype()) val_A = paddle.full_like(mask, 1e-4) * mask + (1 - mask) * value_A[:, i] - # val_A = paddle.where( - # value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] - # ) - # with misc.Timer("inner StAS"): index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) if index_E.shape[1] != standard_index.shape[1]: - # with misc.Timer("FillZeros"): index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) - # with misc.Timer("remove_self_loops"): index_E, value_E = remove_self_loops(edge_index=index_E, edge_attr=value_E) attrlist.append(value_E) edge_weight = paddle.stack(attrlist, axis=1) @@ -412,12 +385,12 @@ class Processor(nn.Layer): def __init__( self, - make_mlp, - output_dim, - message_passing_steps, - message_passing_aggregator, - attention=False, - stochastic_message_passing_used=False, + make_mlp: Callable, + output_dim: int, + message_passing_steps: int, + message_passing_aggregator: str, + attention: bool = False, + stochastic_message_passing_used: bool = False, ): super().__init__() self.stochastic_message_passing_used = stochastic_message_passing_used @@ -467,7 +440,6 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): nor=self.normalization, ) elif speed == "norm": - # with misc.Timer("norm_graph_connectivity"): subedge_index, edge_weight, subpos = norm_graph_connectivity( perm=coarsenodes, edge_index=cofe_graph.edge_index, @@ -477,6 +449,10 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): N=cofe_graph.x.shape[0], nor=self.normalization, ) + else: + raise ValueError( + f"Argument 'speed' should be 'sum' or 'fast', bot got {speed}." + ) edge_weight = self.normalization(edge_weight) pos.append(subpos) latent_graph = pgl.Graph( @@ -495,7 +471,7 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): class LazyMLP(nn.Layer): - def __init__(self, layer, input_dim): + def __init__(self, layer: Tuple[int, ...], input_dim: int): super(LazyMLP, self).__init__() num_layers = len(layer) self._layers_ordered_dict = {} @@ -525,17 +501,8 @@ def __init__(self, input_dim, make_mlp, latent_dim): super(Encoder, self).__init__() self._make_mlp = make_mlp self._latent_dim = latent_dim - # if mode == "airfoil": - # self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 5 - # else: self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 4 - self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=1) # 1 - """ - for _ in graph.edge_sets: - edge_model = make_mlp(latent_dim) - self.edge_models.append(edge_model) - """ def forward(self, graph): node_latents = self.node_model(graph.x) @@ -560,33 +527,47 @@ def forward(self, node_features): class AMGNet(nn.Layer): - """Encode-Process-Decode GraphNet model.""" + """A Multi-scale Graph neural Network model + based on Encoder-Process-Decoder structure for flow field prediction. + + https://doi.org/10.1080/09540091.2022.2131737 + + Code reference: https://github.com/baoshiaijhin/amgnet + + Args: + input_dim (int): Number of input dimension. + output_dim (int): Number of output dimension. + latent_dim (int): Number of hidden(feature) dimension. + num_layers (int): NUmbe of layer(s). + message_passing_aggregator (Literal["sum"]): Message aggregator method in graph. + Only "sum" available now. + message_passing_steps (int): Message passing steps in graph. + speed (str): Whether use vanilla method or fast method for graph_connectivity + computation. + """ def __init__( self, - input_dim, - output_dim, - latent_dim, - num_layers, - message_passing_aggregator, - message_passing_steps, - speed, - nodes=6684, + input_dim: int, + output_dim: int, + latent_dim: int, + num_layers: int, + message_passing_aggregator: Literal["sum"], + message_passing_steps: int, + speed: Literal["norm", "fast"], ): super().__init__() self._latent_dim = latent_dim self.speed = speed self._output_dim = output_dim self._num_layers = num_layers - self.min_nodes = nodes - self._message_passing_steps = message_passing_steps - self._message_passing_aggregator = message_passing_aggregator + self.encoder = Encoder(input_dim, self._make_mlp, latent_dim=self._latent_dim) self.processor = Processor( make_mlp=self._make_mlp, output_dim=self._latent_dim, - message_passing_steps=self._message_passing_steps, - message_passing_aggregator=self._message_passing_aggregator, + message_passing_steps=message_passing_steps, + message_passing_aggregator=message_passing_aggregator, stochastic_message_passing_used=False, ) self.post_processor = self._make_mlp(self._latent_dim, 128) @@ -595,33 +576,30 @@ def __init__( output_dim=self._output_dim, ) - def _make_mlp(self, output_dim, input_dim=5, layer_norm=True): - """Builds an MLP.""" - widths = [self._latent_dim] * self._num_layers + [output_dim] + def forward(self, x: Dict[str, "pgl.Graph"]) -> Dict[str, paddle.Tensor]: + graphs = x["input"] + latent_graph = self.encoder(graphs) + x, p = self.processor(latent_graph, speed=self.speed) + node_features = self._spa_compute(x, p) + pred_field = self.decoder(node_features) + return {"pred": pred_field} + + def _make_mlp(self, output_dim: int, input_dim: int = 5, layer_norm: bool = True): + widths = (self._latent_dim,) * self._num_layers + (output_dim,) network = LazyMLP(widths, input_dim) if layer_norm: network = nn.Sequential(network, nn.LayerNorm(normalized_shape=widths[-1])) return network - def _spa_compute(self, x, p): + def _spa_compute(self, x: List["pgl.Graph"], p): j = len(x) - 1 node_features = x[j].x + for k in range(1, j + 1): pos = p[-k] fine_nodes = x[-(k + 1)].pos feature = _knn_interpolate(node_features, pos, fine_nodes) node_features = x[-(k + 1)].x + feature node_features = self.post_processor(node_features) - return node_features - def forward(self, x): - graphs = x["input"] - # with misc.Timer("encoder"): - latent_graph = self.encoder(graphs) - # with misc.Timer("processor"): - x, p = self.processor(latent_graph, speed=self.speed) - # with misc.Timer("_spa_compute"): - node_features = self._spa_compute(x, p) - # with misc.Timer("decoder"): - pred_field = self.decoder(node_features) - return {"pred": pred_field} + return node_features From 283301cf81cf3e62f32af3cfc39702e4ecceb2c3 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 04:00:06 +0000 Subject: [PATCH 13/27] refine amgnet_airfoil.py and amgnet_cylinder.py --- examples/amgnet/amgnet_airfoil.py | 4 ++-- examples/amgnet/amgnet_cylinder.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 9e71a6108..672b81b93 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -18,8 +18,8 @@ from typing import Dict from typing import List +import utils from paddle.nn import functional as F -from utils import log_images import ppsci from ppsci.utils import config @@ -151,7 +151,7 @@ def eval_rmse_func( for index, batch in enumerate(rmse_validator.data_loader): truefield = batch[0]["input"].y prefield = model(batch[0]) - log_images( + utils.log_images( batch[0]["input"].pos, prefield["pred"], truefield, diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index cdd326e61..f4de24054 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -18,8 +18,8 @@ from typing import Dict from typing import List +import utils from paddle.nn import functional as F -from utils import log_images import ppsci from ppsci.utils import config @@ -151,7 +151,7 @@ def eval_rmse_func( for index, batch in enumerate(rmse_validator.data_loader): truefield = batch[0]["input"].y prefield = model(batch[0]) - log_images( + utils.log_images( batch[0]["input"].pos, prefield["pred"], truefield, From 8fee479a1e1cede8e6422045c453785bbcfaa55f Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 04:58:10 +0000 Subject: [PATCH 14/27] refine utils.py --- examples/amgnet/utils.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/amgnet/utils.py b/examples/amgnet/utils.py index 57e1c027c..2eb0eeba2 100644 --- a/examples/amgnet/utils.py +++ b/examples/amgnet/utils.py @@ -1,6 +1,8 @@ import math +import os import pathlib import warnings +from os import path as osp from typing import BinaryIO from typing import List from typing import Optional @@ -13,10 +15,9 @@ import numpy as np import paddle import scipy.sparse as sp -from paddle.vision.transforms import ToTensor +from paddle.vision import transforms as T from PIL import Image -from pyamg.classical.split import RS -from scipy.sparse import coo_matrix +from pyamg.classical import split matplotlib.use("Agg") @@ -25,8 +26,8 @@ def getcorsenode(latent_graph): row = latent_graph.edge_index[0].numpy() col = latent_graph.edge_index[1].numpy() data = paddle.ones(shape=[row.size]).numpy() - A = coo_matrix((data, (row, col))).tocsr() - splitting = RS(A) + A = sp.coo_matrix((data, (row, col))).tocsr() + splitting = split.RS(A) index = np.array(np.nonzero(splitting)) b = paddle.to_tensor(index) b = paddle.squeeze(b) @@ -49,11 +50,11 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_A.numpy() values_A = value_A.numpy() - coo_A = coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(N, N)) + coo_A = sp.coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(N, N)) indices_S = index_S.numpy() values_S = value_S.numpy() - coo_S = coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) + coo_S = sp.coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) ans = coo_A.dot(coo_S).tocoo() row = paddle.to_tensor(ans.row) @@ -75,11 +76,11 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): indices_A = index_St.numpy() values_A = value_St.numpy() - coo_A = coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(kN, N)) + coo_A = sp.coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(kN, N)) indices_S = index_B.numpy() values_S = value_B.numpy() - coo_S = coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) + coo_S = sp.coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) ans = coo_A.dot(coo_S).tocoo() row = paddle.to_tensor(ans.row) @@ -341,6 +342,7 @@ def save_image( paddle.clip(grid * 255 + 0.5, 0, 255).transpose([1, 2, 0]).cast("uint8").numpy() ) im = Image.fromarray(ndarr) + os.makedirs(osp.basename(fp), exist_ok=True) im.save(fp, format=format) @@ -367,8 +369,7 @@ def log_images( model=flag, col=field, ) - true_img = ToTensor()(true_img) - # min_max = (true[:, field].min().item(), true[:, field].max().item()) + true_img = T.ToTensor()(true_img) pred_img = plot_field( nodes, @@ -379,7 +380,7 @@ def log_images( model=flag, col=field, ) - pred_img = ToTensor()(pred_img) + pred_img = T.ToTensor()(pred_img) imgs = [pred_img, true_img] grid = make_grid(paddle.stack(imgs), padding=0) out_file = file + f"{field}" @@ -391,7 +392,7 @@ def log_images( save_image( grid, "./result/image/airfoil/" + str(index) + out_file + "_field.png" ) - else: + elif flag == "cylinder": if aoa == 39.0: save_image( grid, "./result/image/" + str(index) + out_file + "_field.png" @@ -399,6 +400,10 @@ def log_images( save_image( grid, "./result/image/cylinder/" + str(index) + out_file + "_field.png" ) + else: + raise ValueError( + f"Argument 'flag' should be 'airfoil' or 'cylinder', but got {flag}." + ) def plot_field( From f2427568baa3d000f714021b1328924e5417b70b Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 05:17:48 +0000 Subject: [PATCH 15/27] refine collate_fn --- ppsci/data/__init__.py | 9 ++++-- .../data/process/batch_transform/__init__.py | 28 ++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/ppsci/data/__init__.py b/ppsci/data/__init__.py index 0faea3beb..5e2b36057 100644 --- a/ppsci/data/__init__.py +++ b/ppsci/data/__init__.py @@ -83,7 +83,7 @@ def build_dataloader(_dataset, cfg): # build collate_fn if specified batch_transforms_cfg = cfg.pop("batch_transforms", None) - collate_fn = batch_transform.default_collate_fn + collate_fn = None if isinstance(batch_transforms_cfg, (list, tuple)): collate_fn = batch_transform.build_batch_transforms(batch_transforms_cfg) @@ -98,8 +98,13 @@ def build_dataloader(_dataset, cfg): # build dataloader if getattr(_dataset, "use_pgl", False): # Use special dataloader from "Paddle Graph Learning" toolkit. - from pgl.utils import data as pgl_data + try: + from pgl.utils import data as pgl_data + except ModuleNotFoundError: + logger.error("Please install pgl with `pip install pgl`.") + raise ModuleNotFoundError("pgl is not installed.") + collate_fn = batch_transform.default_collate_fn dataloader_ = pgl_data.Dataloader( dataset=_dataset, batch_size=cfg["batch_size"], diff --git a/ppsci/data/process/batch_transform/__init__.py b/ppsci/data/process/batch_transform/__init__.py index badee27b7..e45279bda 100644 --- a/ppsci/data/process/batch_transform/__init__.py +++ b/ppsci/data/process/batch_transform/__init__.py @@ -46,20 +46,6 @@ def default_collate_fn(batch: List[Any]) -> Any: sample = batch[0] if sample is None: return None - elif isinstance(sample, pgl.Graph): - graph = pgl.Graph( - num_nodes=sample.num_nodes, - edges=sample.edges, - ) - graph.x = paddle.concat([g.x for g in batch]) - graph.y = paddle.concat([g.y for g in batch]) - graph.edge_index = paddle.concat([g.edge_index for g in batch], axis=1) - graph.edge_attr = paddle.concat([g.edge_attr for g in batch]) - graph.pos = paddle.concat([g.pos for g in batch]) - graph.shape = [ - len(batch), - ] - return graph elif isinstance(sample, np.ndarray): batch = np.stack(batch, axis=0) return batch @@ -77,10 +63,20 @@ def default_collate_fn(batch: List[Any]) -> Any: if not all(len(sample) == sample_fields_num for sample in iter(batch)): raise RuntimeError("fileds number not same among samples in a batch") return [default_collate_fn(fields) for fields in zip(*batch)] + elif str(type(sample)) == "": + # use str(type()) instead of isinstance() in case of pgl is not installed. + graph = pgl.Graph(num_nodes=sample.num_nodes, edges=sample.edges) + graph.x = paddle.concat([g.x for g in batch]) + graph.y = paddle.concat([g.y for g in batch]) + graph.edge_index = paddle.concat([g.edge_index for g in batch], axis=1) + graph.edge_attr = paddle.concat([g.edge_attr for g in batch]) + graph.pos = paddle.concat([g.pos for g in batch]) + graph.shape = [len(batch)] + return graph raise TypeError( - "batch data can only contains: tensor, numpy.ndarray, " - f"dict, list, number, None, but got {type(sample)}" + "batch data can only contains: Tensor, numpy.ndarray, " + f"dict, list, number, None, pgl.Graph, but got {type(sample)}" ) From 031f2103a5a80db1807f706136b93f8396770e8f Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 26 Sep 2023 05:50:41 +0000 Subject: [PATCH 16/27] fix bug in eval.py --- ppsci/solver/eval.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index 4cac41b6d..1c6c8cf0d 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -156,17 +156,17 @@ def _eval_by_dataset( # concate all data and discard padded sample(s) for key in all_input: - if paddle.is_tensor(all_input[key]): + if paddle.is_tensor(all_input[key][0]): all_input[key] = paddle.concat(all_input[key]) if len(all_input[key]) > num_samples: all_input[key] = all_input[key][:num_samples] for key in all_output: - if paddle.is_tensor(all_input[key]): + if paddle.is_tensor(all_output[key][0]): all_output[key] = paddle.concat(all_output[key]) if len(all_output[key]) > num_samples: all_output[key] = all_output[key][:num_samples] for key in all_label: - if paddle.is_tensor(all_input[key]): + if paddle.is_tensor(all_label[key][0]): all_label[key] = paddle.concat(all_label[key]) if len(all_label[key]) > num_samples: all_label[key] = all_label[key][:num_samples] From 781d21782674fb0d8008970391968f936213863e Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Sun, 8 Oct 2023 03:05:08 +0000 Subject: [PATCH 17/27] refine codes --- examples/amgnet/amgnet_airfoil.py | 4 +- examples/amgnet/amgnet_cylinder.py | 4 +- examples/amgnet/utils.py | 22 ++++- ppsci/arch/amgnet.py | 122 ++++++++++++++----------- ppsci/data/dataset/cylinder_dataset.py | 6 +- ppsci/solver/eval.py | 2 + ppsci/utils/__init__.py | 6 +- ppsci/utils/misc.py | 88 +++++++++--------- 8 files changed, 146 insertions(+), 108 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 672b81b93..0120538db 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -75,7 +75,7 @@ def eval_rmse_func( "name": "MeshAirfoilDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_root": "./data/NACA0012_interpolate/outputs_train", + "data_dir": "./data/NACA0012_interpolate/outputs_train", "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", }, "batch_size": 4, @@ -109,7 +109,7 @@ def eval_rmse_func( "name": "MeshAirfoilDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_root": "./data/NACA0012_interpolate/outputs_test", + "data_dir": "./data/NACA0012_interpolate/outputs_test", "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", }, "batch_size": 1, diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index f4de24054..324cb60cf 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -75,7 +75,7 @@ def eval_rmse_func( "name": "MeshCylinderDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_root": "./data/cylinderdata/train", + "data_dir": "./data/cylinderdata/train", "mesh_graph_path": "./data/cylinderdata/cylinder.su2", }, "batch_size": 4, @@ -109,7 +109,7 @@ def eval_rmse_func( "name": "MeshCylinderDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_root": "./data/cylinderdata/test", + "data_dir": "./data/cylinderdata/test", "mesh_graph_path": "./data/cylinderdata/cylinder.su2", }, "batch_size": 1, diff --git a/examples/amgnet/utils.py b/examples/amgnet/utils.py index 2eb0eeba2..eb2db7205 100644 --- a/examples/amgnet/utils.py +++ b/examples/amgnet/utils.py @@ -1,8 +1,25 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + import math import os import pathlib import warnings from os import path as osp +from typing import TYPE_CHECKING from typing import BinaryIO from typing import List from typing import Optional @@ -21,8 +38,11 @@ matplotlib.use("Agg") +if TYPE_CHECKING: + import pgl + -def getcorsenode(latent_graph): +def get_corse_node(latent_graph: "pgl.Graph") -> paddle.Tensor: row = latent_graph.edge_index[0].numpy() col = latent_graph.edge_index[1].numpy() data = paddle.ones(shape=[row.size]).numpy() diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 301272e5f..289349b84 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -32,10 +32,11 @@ pass try: - from pyamg.classical import split + import pyamg except ModuleNotFoundError: pass +from paddle import sparse as pd_sparse from scipy import sparse as sci_sparse @@ -60,31 +61,49 @@ def _knn_interpolate( return output -def getcorsenode(latent_graph: "pgl.Graph") -> paddle.Tensor: +def _get_corse_node(latent_graph: "pgl.Graph") -> paddle.Tensor: row = latent_graph.edge_index[0].numpy() col = latent_graph.edge_index[1].numpy() data = paddle.ones(shape=[row.size]).numpy() A = sci_sparse.coo_matrix((data, (row, col))).tocsr() - splitting = split.RS(A) + splitting = pyamg.classical.split.RS(A) index = np.array(np.nonzero(splitting)) b = paddle.to_tensor(index) b = paddle.squeeze(b) return b -def StAS(index_A, value_A, index_S, value_S, N, kN, nor): - """ - Asap: Adaptive structure aware pooling for learning hierarchical graph representations. +def StAS( + index_A: paddle.Tensor, + value_A: paddle.Tensor, + index_S: paddle.Tensor, + value_S: paddle.Tensor, + N: int, + kN: int, + norm_layer: nn.Layer, +) -> Tuple[paddle.Tensor, paddle.Tensor]: + """ASAP: Adaptive Structure Aware Pooling for Learning Hierarchical Graph Representations. Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). AAAI(2020) - """ - sp_x = paddle.sparse.sparse_coo_tensor(index_A, value_A) - sp_x = paddle.sparse.coalesce(sp_x) + Args: + index_A (paddle.Tensor): Indices of sparse matrix A. + value_A (paddle.Tensor): Values of sparse matrix A. + index_S (paddle.Tensor): Indices of sparse matrix S. + value_S (paddle.Tensor): Values of sparse matrix S. + N (int): Dimension N. + kN (int): Dimension kN. + norm_layer (nn.Layer): Normalization layer. + + Returns: + Tuple[paddle.Tensor, paddle.Tensor]: Indices and values of result matrix E. + """ + sp_x = pd_sparse.sparse_coo_tensor(index_A, value_A) + sp_x = pd_sparse.coalesce(sp_x) index_A = sp_x.indices() value_A = sp_x.values() - sp_s = paddle.sparse.sparse_coo_tensor(index_S, value_S) - sp_s = paddle.sparse.coalesce(sp_s) + sp_s = pd_sparse.sparse_coo_tensor(index_S, value_S) + sp_s = pd_sparse.coalesce(sp_s) index_S = sp_s.indices() value_S = sp_s.values() @@ -100,21 +119,21 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): (values_S, (indices_S[0], indices_S[1])), shape=(N, kN) ) - ans = coo_A.dot(coo_S).tocoo() + ans = coo_A.dot(coo_S).tocoo() # sp_x @ sp_s row = paddle.to_tensor(ans.row) col = paddle.to_tensor(ans.col) index_B = paddle.stack([row, col], axis=0) - value_B = paddle.to_tensor(ans.data) + value_B = paddle.to_tensor(ans.data) # sp_x @ sp_s indices_A = index_S values_A = value_S - coo_A = paddle.sparse.sparse_coo_tensor(indices_A, values_A) - out = paddle.sparse.transpose(coo_A, [1, 0]) + coo_A = pd_sparse.sparse_coo_tensor(indices_A, values_A) # sp_s + out = pd_sparse.transpose(coo_A, [1, 0]) index_St = out.indices() value_St = out.values() - sp_x = paddle.sparse.sparse_coo_tensor(index_B, value_B) - sp_x = paddle.sparse.coalesce(sp_x) + sp_x = pd_sparse.sparse_coo_tensor(index_B, value_B) # sp_b + sp_x = pd_sparse.coalesce(sp_x) index_B = sp_x.indices() value_B = sp_x.values() @@ -137,19 +156,20 @@ def StAS(index_A, value_A, index_S, value_S, N, kN, nor): value_E = paddle.to_tensor(ans.data) # index_E排序 - sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) - sp_x = paddle.sparse.coalesce(sp_x) + sp_x = pd_sparse.sparse_coo_tensor(index_E, value_E) + sp_x = pd_sparse.coalesce(sp_x) index_E = sp_x.indices() value_E = sp_x.values() return index_E, value_E -def FillZeros(index_E, value_E, standard_index, kN): +def FillZeros( + index_E: paddle.Tensor, value_E: paddle.Tensor, standard_index, kN: int +) -> Tuple[paddle.Tensor, paddle.Tensor]: shape = [kN, kN] row_E = index_E[0] col_E = index_E[1] - # coo_E = paddle.sparse.sparse_coo_tensor(index_E, value_E, shape) DenseMatrix_E = sci_sparse.coo_matrix( (paddle.ones_like(value_E), (row_E, col_E)), shape ).toarray() @@ -169,8 +189,8 @@ def FillZeros(index_E, value_E, standard_index, kN): index_E = paddle.concat([index_E, index], axis=1) value_E = paddle.concat([value_E, value], axis=-1) - sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) - sp_x = paddle.sparse.coalesce(sp_x) + sp_x = pd_sparse.sparse_coo_tensor(index_E, value_E) + sp_x = pd_sparse.coalesce(sp_x) index_E = sp_x.indices() value_E = sp_x.values() @@ -192,7 +212,7 @@ def remove_self_loops( def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): - """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling + """Adapted from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling for learning hierarchical graph representations. AAAI(2020)""" kN = perm.shape[0] @@ -241,7 +261,7 @@ def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor) return index_E, edge_weight, subgraphnode_pos -def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): +def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_layer): """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling for learning hierarchical graph representations. AAAI(2020)""" @@ -277,13 +297,13 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): paddle.ones_like(value_S), N, kN, - nor, + norm_layer, ) # with misc.Timer("range 128"): for i in range(128): mask = (value_A[:, i] == 0).astype(paddle.get_default_dtype()) val_A = paddle.full_like(mask, 1e-4) * mask + (1 - mask) * value_A[:, i] - index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, norm_layer) if index_E.shape[1] != standard_index.shape[1]: index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) @@ -299,11 +319,11 @@ class GraphNetBlock(nn.Layer): """Multi-Edge Interaction Network with residual connections.""" def __init__( - self, model_fn, output_dim, message_passing_aggregator, attention=False + self, model_fn, output_dims, message_passing_aggregator, attention=False ): super().__init__() - self.edge_model = model_fn(output_dim, 384) - self.node_model = model_fn(output_dim, 256) + self.edge_model = model_fn(output_dims, 384) + self.node_model = model_fn(output_dims, 256) self.message_passing_aggregator = message_passing_aggregator def _update_edge_features(self, graph): @@ -403,7 +423,7 @@ def __init__( self.graphnet_blocks.append( GraphNetBlock( model_fn=make_mlp, - output_dim=output_dim, + output_dims=output_dim, message_passing_aggregator=message_passing_aggregator, attention=attention, ) @@ -412,7 +432,7 @@ def __init__( self.pool_blocks.append( GraphNetBlock( model_fn=make_mlp, - output_dim=output_dim, + output_dims=output_dim, message_passing_aggregator=message_passing_aggregator, attention=attention, ) @@ -427,7 +447,7 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): pre_matrix = graphnet_block(latent_graph) x.append(pre_matrix) cofe_graph = pool(pre_matrix) - coarsenodes = getcorsenode(pre_matrix) + coarsenodes = _get_corse_node(pre_matrix) nodesfeatures = cofe_graph.x[coarsenodes] if speed == "fast": subedge_index, edge_weight, subpos = faster_graph_connectivity( @@ -470,28 +490,24 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): return x, pos -class LazyMLP(nn.Layer): - def __init__(self, layer: Tuple[int, ...], input_dim: int): - super(LazyMLP, self).__init__() - num_layers = len(layer) - self._layers_ordered_dict = {} - self.in_dim = input_dim - for index, output_dim in enumerate(layer): - self._layers_ordered_dict["linear_" + str(index)] = nn.Linear( - self.in_dim, output_dim - ) - if index < (num_layers - 1): - self._layers_ordered_dict["relu_" + str(index)] = nn.ReLU() - self.in_dim = output_dim +class FullyConnectedLayer(nn.Layer): + def __init__(self, input_dim: int, hidden_size: Tuple[int, ...]): + super(FullyConnectedLayer, self).__init__() + num_layers = len(hidden_size) + self.layers = nn.LayerList() - self.layers = nn.LayerDict(self._layers_ordered_dict) + cur_dim = input_dim + for i, hidden_size_ in enumerate(hidden_size): + self.layers.append(nn.Linear(cur_dim, hidden_size_)) + if i < (num_layers - 1): + self.layers.append(nn.ReLU()) + cur_dim = hidden_size_ def forward(self, input): - for k in self.layers: - l = self.layers[k] - output = l(input) - input = output - return input + output = input + for layer in self.layers: + output = layer(output) + return output class Encoder(nn.Layer): @@ -586,7 +602,7 @@ def forward(self, x: Dict[str, "pgl.Graph"]) -> Dict[str, paddle.Tensor]: def _make_mlp(self, output_dim: int, input_dim: int = 5, layer_norm: bool = True): widths = (self._latent_dim,) * self._num_layers + (output_dim,) - network = LazyMLP(widths, input_dim) + network = FullyConnectedLayer(input_dim, widths) if layer_norm: network = nn.Sequential(network, nn.LayerNorm(normalized_shape=widths[-1])) return network diff --git a/ppsci/data/dataset/cylinder_dataset.py b/ppsci/data/dataset/cylinder_dataset.py index 0e43226f4..b07aacc31 100644 --- a/ppsci/data/dataset/cylinder_dataset.py +++ b/ppsci/data/dataset/cylinder_dataset.py @@ -42,7 +42,7 @@ class MeshCylinderDataset(io.Dataset): Args: input_keys (Tuple[str, ...]): Name of input data. label_keys (Tuple[str, ...]): Name of label data. - data_root (str): Directory of MeshCylinder data. + data_dir (str): Directory of MeshCylinder data. mesh_graph_path (str): Path of mesh graph. """ @@ -52,12 +52,12 @@ def __init__( self, input_keys: Tuple[str, ...], label_keys: Tuple[str, ...], - data_root: str, + data_dir: str, mesh_graph_path: str, ): self.input_keys = input_keys self.label_keys = label_keys - self.data_dir = data_root + self.data_dir = data_dir self.file_list = os.listdir(self.data_dir) self.len = len(self.file_list) self.mesh_graph = airfoil_dataset._get_mesh_graph(mesh_graph_path) diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index 1c6c8cf0d..a3be30a45 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -160,11 +160,13 @@ def _eval_by_dataset( all_input[key] = paddle.concat(all_input[key]) if len(all_input[key]) > num_samples: all_input[key] = all_input[key][:num_samples] + for key in all_output: if paddle.is_tensor(all_output[key][0]): all_output[key] = paddle.concat(all_output[key]) if len(all_output[key]) > num_samples: all_output[key] = all_output[key][:num_samples] + for key in all_label: if paddle.is_tensor(all_label[key][0]): all_label[key] = paddle.concat(all_label[key]) diff --git a/ppsci/utils/__init__.py b/ppsci/utils/__init__.py index e6e327ffe..8f34ed12d 100644 --- a/ppsci/utils/__init__.py +++ b/ppsci/utils/__init__.py @@ -35,6 +35,9 @@ from ppsci.utils.symbolic import lambdify __all__ = [ + "AttrDict", + "AverageMeter", + "ExpressionSolver", "initializer", "logger", "misc", @@ -48,9 +51,6 @@ "dynamic_import_to_globals", "run_check", "run_check_mesh", - "AttrDict", - "ExpressionSolver", - "AverageMeter", "set_random_seed", "load_checkpoint", "load_pretrain", diff --git a/ppsci/utils/misc.py b/ppsci/utils/misc.py index e25e28e4b..61334ab9f 100644 --- a/ppsci/utils/misc.py +++ b/ppsci/utils/misc.py @@ -34,10 +34,11 @@ from ppsci.utils import logger __all__ = [ - "all_gather", "AverageMeter", "PrettyOrderedDict", "Prettydefaultdict", + "Timer", + "all_gather", "concat_dict_list", "convert_to_array", "convert_to_dict", @@ -48,7 +49,6 @@ "run_on_eval_mode", "run_at_rank0", "plot_curve", - "Timer", ] @@ -116,6 +116,48 @@ def __str__(self): return "".join([str((k, v)) for k, v in self.items()]) +class Timer(ContextDecorator): + """Count time cost for code block within context. + + Args: + name (str, optional): Name of timer discriminate different code block. + Defaults to "Timer". + auto_print (bool, optional): Whether print time cost when exit context. + Defaults to True. + + Examples: + >>> import paddle + >>> from ppsci.utils import misc + >>> with misc.Timer("test1", auto_print=False) as timer: + ... w = sum(range(0, 10)) + >>> print(f"time cost of 'sum(range(0, 10))' is {timer.interval:.2f}") + time cost of 'sum(range(0, 10))' is 0.00 + + >>> @misc.Timer("test2", auto_print=True) + ... def func(): + ... w = sum(range(0, 10)) + >>> func() # doctest: +SKIP + + """ + + interval: float # Time cost for code within Timer context + + def __init__(self, name: str = "Timer", auto_print: bool = True): + super().__init__() + self.name = name + self.auto_print = auto_print + + def __enter__(self): + self.start_time = time.perf_counter() + return self + + def __exit__(self, type, value, traceback): + self.end_time = time.perf_counter() + self.interval = self.end_time - self.start_time + if self.auto_print: + logger.message(f"{self.name}.time_cost = {self.interval:.2f} s") + + def convert_to_dict(array: np.ndarray, keys: Tuple[str, ...]) -> Dict[str, np.ndarray]: """Split given array into single channel array at axis -1 in order of given keys. @@ -386,45 +428,3 @@ def plot_curve( plt.savefig(os.path.join(output_dir, f"{xlabel}-{ylabel}_curve.jpg")) plt.clf() - - -class Timer(ContextDecorator): - """Count time cost for code block within context. - - Args: - name (str, optional): Name of timer discriminate different code block. - Defaults to "Timer". - auto_print (bool, optional): Whether print time cost when exit context. - Defaults to True. - - Examples: - >>> import paddle - >>> from ppsci.utils import misc - >>> with misc.Timer("test1", auto_print=False) as timer: - ... w = sum(range(0, 10)) - >>> print(f"time cost of 'sum(range(0, 10))' is {timer.interval:.2f}") - time cost of 'sum(range(0, 10))' is 0.00 - - >>> @misc.Timer("test2", auto_print=True) - ... def func(): - ... w = sum(range(0, 10)) - >>> func() # doctest: +SKIP - - """ - - interval: float # Time cost for code within Timer context - - def __init__(self, name: str = "Timer", auto_print: bool = True): - super().__init__() - self.name = name - self.auto_print = auto_print - - def __enter__(self): - self.start_time = time.perf_counter() - return self - - def __exit__(self, type, value, traceback): - self.end_time = time.perf_counter() - self.interval = self.end_time - self.start_time - if self.auto_print: - logger.message(f"{self.name}.time_cost = {self.interval:.2f} s") From 2b8b7546c3632b601b9668fc52c3757410bbdd3f Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Sun, 8 Oct 2023 08:50:50 +0000 Subject: [PATCH 18/27] refine codes --- examples/amgnet/amgnet_airfoil.py | 11 +- examples/amgnet/amgnet_cylinder.py | 11 +- examples/amgnet/utils.py | 276 +++----------------------- ppsci/arch/amgnet.py | 53 ++--- ppsci/data/__init__.py | 2 +- ppsci/data/dataset/airfoil_dataset.py | 34 ++-- 6 files changed, 81 insertions(+), 306 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 0120538db..110398a55 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -148,15 +148,14 @@ def eval_rmse_func( # visualize prediction with solver.no_grad_context_manager(True): - for index, batch in enumerate(rmse_validator.data_loader): - truefield = batch[0]["input"].y - prefield = model(batch[0]) + for index, (input_, label, _) in enumerate(rmse_validator.data_loader): + truefield = label["input"].y + prefield = model(input_) utils.log_images( - batch[0]["input"].pos, + input_["input"].pos, prefield["pred"], truefield, rmse_validator.data_loader.dataset.elems_list, - "test", index, - flag="airfoil", + "airfoil", ) diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index 324cb60cf..efbf2b856 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -148,15 +148,14 @@ def eval_rmse_func( # visualize prediction with solver.no_grad_context_manager(True): - for index, batch in enumerate(rmse_validator.data_loader): - truefield = batch[0]["input"].y - prefield = model(batch[0]) + for index, (input_, label, _) in enumerate(rmse_validator.data_loader): + truefield = label["input"].y + prefield = model(input_) utils.log_images( - batch[0]["input"].pos, + input_["input"].pos, prefield["pred"], truefield, rmse_validator.data_loader.dataset.elems_list, - "test", index, - flag="cylinder", + "cylinder", ) diff --git a/examples/amgnet/utils.py b/examples/amgnet/utils.py index eb2db7205..44ba34446 100644 --- a/examples/amgnet/utils.py +++ b/examples/amgnet/utils.py @@ -19,7 +19,6 @@ import pathlib import warnings from os import path as osp -from typing import TYPE_CHECKING from typing import BinaryIO from typing import List from typing import Optional @@ -31,237 +30,11 @@ import matplotlib.pyplot as plt import numpy as np import paddle -import scipy.sparse as sp from paddle.vision import transforms as T from PIL import Image -from pyamg.classical import split matplotlib.use("Agg") -if TYPE_CHECKING: - import pgl - - -def get_corse_node(latent_graph: "pgl.Graph") -> paddle.Tensor: - row = latent_graph.edge_index[0].numpy() - col = latent_graph.edge_index[1].numpy() - data = paddle.ones(shape=[row.size]).numpy() - A = sp.coo_matrix((data, (row, col))).tocsr() - splitting = split.RS(A) - index = np.array(np.nonzero(splitting)) - b = paddle.to_tensor(index) - b = paddle.squeeze(b) - return b - - -def StAS(index_A, value_A, index_S, value_S, N, kN, nor): - r"""come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling - for learning hierarchical graph representations. AAAI(2020)""" - - sp_x = paddle.sparse.sparse_coo_tensor(index_A, value_A) - sp_x = paddle.sparse.coalesce(sp_x) - index_A = sp_x.indices() - value_A = sp_x.values() - - sp_s = paddle.sparse.sparse_coo_tensor(index_S, value_S) - sp_s = paddle.sparse.coalesce(sp_s) - index_S = sp_s.indices() - value_S = sp_s.values() - - indices_A = index_A.numpy() - values_A = value_A.numpy() - coo_A = sp.coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(N, N)) - - indices_S = index_S.numpy() - values_S = value_S.numpy() - coo_S = sp.coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) - - ans = coo_A.dot(coo_S).tocoo() - row = paddle.to_tensor(ans.row) - col = paddle.to_tensor(ans.col) - index_B = paddle.stack([row, col], axis=0) - value_B = paddle.to_tensor(ans.data) - - indices_A = index_S - values_A = value_S - coo_A = paddle.sparse.sparse_coo_tensor(indices_A, values_A) - out = paddle.sparse.transpose(coo_A, [1, 0]) - index_St = out.indices() - value_St = out.values() - - sp_x = paddle.sparse.sparse_coo_tensor(index_B, value_B) - sp_x = paddle.sparse.coalesce(sp_x) - index_B = sp_x.indices() - value_B = sp_x.values() - - indices_A = index_St.numpy() - values_A = value_St.numpy() - coo_A = sp.coo_matrix((values_A, (indices_A[0], indices_A[1])), shape=(kN, N)) - - indices_S = index_B.numpy() - values_S = value_B.numpy() - coo_S = sp.coo_matrix((values_S, (indices_S[0], indices_S[1])), shape=(N, kN)) - - ans = coo_A.dot(coo_S).tocoo() - row = paddle.to_tensor(ans.row) - col = paddle.to_tensor(ans.col) - index_E = paddle.stack([row, col], axis=0) - value_E = paddle.to_tensor(ans.data) - - # index_E排序 - sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) - sp_x = paddle.sparse.coalesce(sp_x) - index_E = sp_x.indices() - value_E = sp_x.values() - - return index_E, value_E - - -def FillZeros(index_E, value_E, standard_index, kN): - shape = [kN, kN] - row_E = index_E[0] - col_E = index_E[1] - DenseMatrix_E = sp.coo_matrix( - (paddle.ones_like(value_E), (row_E, col_E)), shape - ).toarray() - - row_S = standard_index[0] - col_S = standard_index[1] - DenseMatrix_S = sp.coo_matrix( - (paddle.ones([row_S.shape[0]]), (row_S, col_S)), shape - ).toarray() - - diff = DenseMatrix_S - DenseMatrix_E - rows, cols = np.nonzero(diff) - rows = paddle.to_tensor(rows, dtype="int32") - cols = paddle.to_tensor(cols, dtype="int32") - index = paddle.stack([rows, cols], axis=0) - value = paddle.zeros([index.shape[1]]) - index_E = paddle.concat([index_E, index], axis=1) - value_E = paddle.concat([value_E, value], axis=-1) - - sp_x = paddle.sparse.sparse_coo_tensor(index_E, value_E) - sp_x = paddle.sparse.coalesce(sp_x) - index_E = sp_x.indices() - value_E = sp_x.values() - - return index_E, value_E - - -# @brief:删除图中的自循环的边 -def remove_self_loops( - edge_index: paddle.Tensor, edge_attr: Optional[paddle.Tensor] = None -) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: - mask = edge_index[0] != edge_index[1] - mask = mask.tolist() - edge_index = edge_index.t() - edge_index = edge_index[mask] - edge_index = edge_index.t() - if edge_attr is None: - return edge_index, None - else: - return edge_index, edge_attr[mask] - - -def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): - """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling - for learning hierarchical graph representations. AAAI(2020)""" - - kN = perm.shape[0] - perm2 = perm.reshape((-1, 1)) - mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") - - S0 = edge_index[1][mask].reshape((1, -1)) - S1 = edge_index[0][mask].reshape((1, -1)) - index_S = paddle.concat([S0, S1], axis=0) - value_S = score[mask].detach().squeeze() - n_idx = paddle.zeros([N], dtype=paddle.int64) - n_idx[perm] = paddle.arange(perm.shape[0]) - index_S = index_S.astype("int64") - index_S[1] = n_idx[index_S[1]] - subgraphnode_pos = pos[perm] - index_A = edge_index.clone() - if edge_weight is None: - value_A = value_S.new_ones(edge_index[0].shape[0]) - else: - value_A = edge_weight.clone() - - value_A = paddle.squeeze(value_A) - model_1 = paddle.nn.Sequential( - ("l1", paddle.nn.Linear(128, 256)), - ("act1", paddle.nn.ReLU()), - ("l2", paddle.nn.Linear(256, 256)), - ("act2", paddle.nn.ReLU()), - ("l4", paddle.nn.Linear(256, 128)), - ("act4", paddle.nn.ReLU()), - ("l5", paddle.nn.Linear(128, 1)), - ) - model_2 = paddle.nn.Sequential( - ("l1", paddle.nn.Linear(1, 64)), - ("act1", paddle.nn.ReLU()), - ("l2", paddle.nn.Linear(64, 128)), - ("act2", paddle.nn.ReLU()), - ("l4", paddle.nn.Linear(128, 128)), - ) - - val_A = model_1(value_A) - val_A = paddle.squeeze(val_A) - index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) - value_E = paddle.reshape(value_E, shape=[-1, 1]) - edge_weight = model_2(value_E) - - return index_E, edge_weight, subgraphnode_pos - - -def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): - """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling - for learning hierarchical graph representations. AAAI(2020)""" - - kN = perm.shape[0] - perm2 = perm.reshape((-1, 1)) - mask = (edge_index[0] == perm2).sum(axis=0).astype("bool") - S0 = edge_index[1][mask].reshape((1, -1)) - S1 = edge_index[0][mask].reshape((1, -1)) - index_S = paddle.concat([S0, S1], axis=0) - value_S = score[mask].detach().squeeze() - n_idx = paddle.zeros([N], dtype=paddle.int64) - n_idx[perm] = paddle.arange(perm.shape[0]) - index_S = index_S.astype("int64") - index_S[1] = n_idx[index_S[1]] - subgraphnode_pos = pos[perm] - index_A = edge_index.clone() - if edge_weight is None: - value_A = value_S.new_ones(edge_index[0].shape[0]) - else: - value_A = edge_weight.clone() - - value_A = paddle.squeeze(value_A) - value_S = paddle.where(value_S == 0, paddle.to_tensor(0.0001), value_S) - attrlist = [] - standard_index, _ = StAS( - index_A, - paddle.ones_like(value_A[:, 0]), - index_S, - paddle.ones_like(value_S), - N, - kN, - nor, - ) - for i in range(128): - val_A = paddle.where( - value_A[:, i] == 0, paddle.to_tensor(0.0001), value_A[:, i] - ) - index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) - - if index_E.shape[1] != standard_index.shape[1]: - index_E, value_E = FillZeros(index_E, value_E, standard_index, kN) - - index_E, value_E = remove_self_loops(edge_index=index_E, edge_attr=value_E) - attrlist.append(value_E) - edge_weight = paddle.stack(attrlist, axis=1) - - return index_E, edge_weight, subgraphnode_pos - @paddle.no_grad() def make_grid( @@ -292,13 +65,13 @@ def make_grid( if isinstance(tensor, list): tensor = paddle.stack(tensor, axis=0) - if tensor.dim() == 2: # single image H x W + if tensor.ndim == 2: # single image H x W tensor = tensor.unsqueeze(0) - if tensor.dim() == 3: # single image + if tensor.ndim == 3: # single image if tensor.shape[0] == 1: # if single-channel, convert to 3-channel tensor = paddle.concat((tensor, tensor, tensor), 0) tensor = tensor.unsqueeze(0) - if tensor.dim() == 4 and tensor.shape[1] == 1: # single-channel images + if tensor.ndim == 4 and tensor.shape[1] == 1: # single-channel images tensor = paddle.concat((tensor, tensor, tensor), 1) if normalize is True: @@ -371,12 +144,10 @@ def log_images( pred, true, elems_list, - mode, index, - flag, + mode, aoa=0, mach=0, - iterate=0, file="field.png", ): for field in range(pred.shape[1]): @@ -384,10 +155,10 @@ def log_images( nodes, elems_list, true[:, field], - title="true", - clim=(-0.8, 0.8), - model=flag, + mode=mode, col=field, + clim=(-0.8, 0.8), + title="true", ) true_img = T.ToTensor()(true_img) @@ -395,16 +166,16 @@ def log_images( nodes, elems_list, pred[:, field], - title="pred", - clim=(-0.8, 0.8), - model=flag, + mode=mode, col=field, + clim=(-0.8, 0.8), + title="pred", ) pred_img = T.ToTensor()(pred_img) imgs = [pred_img, true_img] grid = make_grid(paddle.stack(imgs), padding=0) out_file = file + f"{field}" - if flag == "airfoil": + if mode == "airfoil": if aoa == 8.0 and mach == 0.65: save_image( grid, "./result/image/" + str(index) + out_file + "_field.png" @@ -412,7 +183,7 @@ def log_images( save_image( grid, "./result/image/airfoil/" + str(index) + out_file + "_field.png" ) - elif flag == "cylinder": + elif mode == "cylinder": if aoa == 39.0: save_image( grid, "./result/image/" + str(index) + out_file + "_field.png" @@ -422,15 +193,15 @@ def log_images( ) else: raise ValueError( - f"Argument 'flag' should be 'airfoil' or 'cylinder', but got {flag}." + f"Argument 'mode' should be 'airfoil' or 'cylinder', but got {mode}." ) def plot_field( - nodes, + nodes: paddle.Tensor, elems_list, - field, - model, + field: paddle.Tensor, + mode, col, contour=False, clim=None, @@ -453,14 +224,14 @@ def plot_field( if clim: plt.clim(*clim) colorbar = plt.colorbar() - if model == "airfoil": + if mode == "airfoil": if col == 0: colorbar.set_label("x-velocity", fontsize=16) elif col == 1: colorbar.set_label("pressure", fontsize=16) elif col == 2: colorbar.set_label("y-velocity", fontsize=16) - if model == "cylinder": + if mode == "cylinder": if col == 0: colorbar.set_label("pressure", fontsize=16) elif col == 1: @@ -468,7 +239,7 @@ def plot_field( elif col == 2: colorbar.set_label("y-velocity", fontsize=16) if zoom: - if model == "airfoil": + if mode == "airfoil": plt.xlim(left=-0.5, right=1.5) plt.ylim(bottom=-0.5, top=0.5) else: @@ -484,18 +255,17 @@ def plot_field( if show: plt.show() - raise NotImplementedError if get_array: - if model == "airfoil": + if mode == "airfoil": plt.gca().invert_yaxis() fig.canvas.draw() - a = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) - a = a.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + array = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) + array = array.reshape(fig.canvas.get_width_height()[::-1] + (3,)) fig.clf() fig.clear() plt.close() - return a + return array def quad2tri(elems): diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 289349b84..12660b770 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -211,7 +211,7 @@ def remove_self_loops( return edge_index, edge_attr[mask] -def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor): +def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_layer): """Adapted from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling for learning hierarchical graph representations. AAAI(2020)""" @@ -254,7 +254,7 @@ def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, nor) val_A = model_1(value_A) val_A = paddle.squeeze(val_A) - index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, nor) + index_E, value_E = StAS(index_A, val_A, index_S, value_S, N, kN, norm_layer) value_E = paddle.reshape(value_E, shape=[-1, 1]) edge_weight = model_2(value_E) @@ -299,7 +299,6 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_l kN, norm_layer, ) - # with misc.Timer("range 128"): for i in range(128): mask = (value_A[:, i] == 0).astype(paddle.get_default_dtype()) val_A = paddle.full_like(mask, 1e-4) * mask + (1 - mask) * value_A[:, i] @@ -337,20 +336,23 @@ def _update_edge_features(self, graph): return self.edge_model(features) def unsorted_segment_operation(self, data, segment_ids, num_segments, operation): - """ - Computes the sum along segments of a tensor. Analogous to tf.unsorted_segment_sum. + """Computes the sum along segments of a tensor. Analogous to tf.unsorted_segment_sum. + + Args: + data (paddle.Tensor): A tensor whose segments are to be summed. + segment_ids (paddle.Tensor): The segment indices tensor. + num_segments (int): The number of segments. + operation (str): _description_ - :param data: A tensor whose segments are to be summed. - :param segment_ids: The segment indices tensor. - :param num_segments: The number of segments. - :return: A tensor of same data type as the data argument. + Returns: + paddle.Tensor: A tensor of same data type as the data argument. """ - assert all( - [i in data.shape for i in segment_ids.shape] - ), "segment_ids.shape should be a prefix of data.shape" - assert ( - data.shape[0] == segment_ids.shape[0] - ), "data.shape and segment_ids.shape should be equal" + if not all([i in data.shape for i in segment_ids.shape]): + raise ValueError("segment_ids.shape should be a prefix of data.shape") + + if not (data.shape[0] == segment_ids.shape[0]): + raise ValueError("data.shape and segment_ids.shape should be equal") + shape = [num_segments] + list(data.shape[1:]) result_shape = paddle.zeros(shape) if operation == "sum": @@ -392,12 +394,17 @@ def forward(self, graph): class Processor(nn.Layer): - """ - This class takes the nodes with the most influential feature (sum of square) + """This class takes the nodes with the most influential feature (sum of square) The the chosen numbers of nodes in each ripple will establish connection(features and distances) with the most influential nodes and this connection will be learned Then the result is add to output latent graph of encoder and the modified latent graph will be feed into original processor - Option: choose whether to normalize the high rank node connection + Args: + make_mlp (Callable): Function to make MLP. + output_dim (int): Number of dimension of output. + message_passing_steps (int): Message passing steps. + message_passing_aggregator (str): Message passing aggregator. + attention (bool, optional): Whether use attention. Defaults to False. + use_stochastic_message_passing (bool, optional): Whether use stochastic message passing. Defaults to False. """ # Each mesh can be coarsened to have no fewer points than this value @@ -410,10 +417,10 @@ def __init__( message_passing_steps: int, message_passing_aggregator: str, attention: bool = False, - stochastic_message_passing_used: bool = False, + use_stochastic_message_passing: bool = False, ): super().__init__() - self.stochastic_message_passing_used = stochastic_message_passing_used + self.use_stochastic_message_passing = use_stochastic_message_passing self.graphnet_blocks = nn.LayerList() self.cofe_edge_blocks = nn.LayerList() self.pool_blocks = nn.LayerList() @@ -457,7 +464,7 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): score=cofe_graph.edge_attr[:, 0], pos=cofe_graph.pos, N=cofe_graph.x.shape[0], - nor=self.normalization, + norm_layer=self.normalization, ) elif speed == "norm": subedge_index, edge_weight, subpos = norm_graph_connectivity( @@ -467,7 +474,7 @@ def forward(self, latent_graph, speed, normalized_adj_mat=None): score=cofe_graph.edge_attr[:, 0], pos=cofe_graph.pos, N=cofe_graph.x.shape[0], - nor=self.normalization, + norm_layer=self.normalization, ) else: raise ValueError( @@ -584,7 +591,7 @@ def __init__( output_dim=self._latent_dim, message_passing_steps=message_passing_steps, message_passing_aggregator=message_passing_aggregator, - stochastic_message_passing_used=False, + use_stochastic_message_passing=False, ) self.post_processor = self._make_mlp(self._latent_dim, 128) self.decoder = Decoder( diff --git a/ppsci/data/__init__.py b/ppsci/data/__init__.py index 5e2b36057..6ab46f40a 100644 --- a/ppsci/data/__init__.py +++ b/ppsci/data/__init__.py @@ -119,7 +119,7 @@ def build_dataloader(_dataset, cfg): places=device.get_device(), batch_sampler=sampler, collate_fn=collate_fn, - num_workers=cfg.get("num_workers", 0), + num_workers=cfg.get("num_workers", 1), use_shared_memory=cfg.get("use_shared_memory", False), worker_init_fn=init_fn, ) diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index f6bab2452..73120c915 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -54,7 +54,7 @@ def get_rhs(s: str) -> str: for _ in range(num_points) ] nodes = np.array(mesh_points, dtype=dtype) - if line.startswith("NMARK"): + elif line.startswith("NMARK"): num_markers = int(get_rhs(line)) for _ in range(num_markers): line = f.readline() @@ -66,7 +66,7 @@ def get_rhs(s: str) -> str: for _ in range(num_elems) ] marker_dict[marker_tag] = marker_elems - if line.startswith("NELEM"): + elif line.startswith("NELEM"): edges = [] triangles = [] quads = [] @@ -127,20 +127,17 @@ def __init__( self.node_markers[elem[0]] = i self.node_markers[elem[1]] = i - # HACK: For unsupporting convert to tensor at different workers in runtime, - # so load all graph into GPU-memory at onec. This need to be optimized. - self.raw_graphs = [self.get(i) for i in range(self.len)] - def __len__(self): return self.len def __getitem__(self, idx): + raw_graph = self.get(idx) return ( { - self.input_keys[0]: self.raw_graphs[idx], + self.input_keys[0]: raw_graph, }, { - self.label_keys[0]: self.raw_graphs[idx], + self.label_keys[0]: raw_graph, }, None, ) @@ -148,8 +145,8 @@ def __getitem__(self, idx): def get(self, idx): with open(osp.join(self.data_dir, self.file_list[idx]), "rb") as f: fields = pickle.load(f) - fields = paddle.to_tensor(self.preprocess(fields)) - aoa, reynolds, mach = self.get_params_from_name(self.file_list[idx]) + fields = paddle.to_tensor(self._preprocess(fields)) + aoa, reynolds, mach = self._get_params_from_name(self.file_list[idx]) aoa = paddle.to_tensor(aoa) mach_or_reynolds = mach if reynolds is None else reynolds mach_or_reynolds = paddle.to_tensor(mach_or_reynolds) @@ -168,7 +165,7 @@ def get(self, idx): self.node_markers, ], axis=-1, - ).astype(np.float32) + ).astype(paddle.get_default_dtype()) nodes = paddle.to_tensor(nodes) data = pgl.Graph( num_nodes=nodes.shape[0], @@ -195,7 +192,7 @@ def get(self, idx): data.norm_mach_or_reynolds = norm_mach_or_reynolds return data - def preprocess(self, tensor_list, stack_output=True): + def _preprocess(self, tensor_list, stack_output=True): data_max, data_min = self.normalization_factors normalized_tensors = [] for i in range(len(tensor_list)): @@ -207,15 +204,18 @@ def preprocess(self, tensor_list, stack_output=True): normalized_tensors = np.stack(normalized_tensors, axis=1) return normalized_tensors - @staticmethod - def get_params_from_name(filename): + def _get_params_from_name(self, filename): s = filename.rsplit(".", 1)[0].split("_") - aoa = np.array(s[s.index("aoa") + 1])[np.newaxis].astype(np.float32) + aoa = np.array(s[s.index("aoa") + 1])[np.newaxis].astype( + paddle.get_default_dtype() + ) reynolds = s[s.index("re") + 1] reynolds = ( - np.array(reynolds)[np.newaxis].astype(np.float32) + np.array(reynolds)[np.newaxis].astype(paddle.get_default_dtype()) if reynolds != "None" else None ) - mach = np.array(s[s.index("mach") + 1])[np.newaxis].astype(np.float32) + mach = np.array(s[s.index("mach") + 1])[np.newaxis].astype( + paddle.get_default_dtype() + ) return aoa, reynolds, mach From c3e560a92440861fbf7c02808f405469690c049e Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Sun, 8 Oct 2023 08:55:18 +0000 Subject: [PATCH 19/27] modify atol from 1e-8 to 1e-7 of UT test_navierstokes --- ppsci/data/dataset/cylinder_dataset.py | 14 +++++--------- test/equation/test_navier_stokes.py | 4 +++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ppsci/data/dataset/cylinder_dataset.py b/ppsci/data/dataset/cylinder_dataset.py index b07aacc31..f31129d76 100644 --- a/ppsci/data/dataset/cylinder_dataset.py +++ b/ppsci/data/dataset/cylinder_dataset.py @@ -78,20 +78,17 @@ def __init__( self.node_markers[elem[0]] = i self.node_markers[elem[1]] = i - # HACK: For unsupporting convert to tensor at different workers in runtime, - # so load all graph into GPU-memory at onec. This need to be optimized. - self.raw_graphs = [self.get(i) for i in range(self.len)] - def __len__(self): return self.len def __getitem__(self, idx): + raw_graph = self.get(idx) return ( { - self.input_keys[0]: self.raw_graphs[idx], + self.input_keys[0]: raw_graph, }, { - self.label_keys[0]: self.raw_graphs[idx], + self.label_keys[0]: raw_graph, }, None, ) @@ -125,7 +122,7 @@ def get(self, idx): indexlist = paddle.stack(indexlist, axis=0) indexlist = paddle.squeeze(indexlist) fields = field[indexlist] - velocity = self.get_params_from_name(self.file_list[idx]) + velocity = self._get_params_from_name(self.file_list[idx]) aoa = paddle.to_tensor(velocity) norm_aoa = paddle.to_tensor(aoa / 40) @@ -201,8 +198,7 @@ def get(self, idx): self.bounder = indexlist return data - @staticmethod - def get_params_from_name(filename): + def _get_params_from_name(self, filename): s = filename.rsplit(".", 1)[0] reynolds = np.array(s[13:])[np.newaxis].astype(paddle.get_default_dtype()) return reynolds diff --git a/test/equation/test_navier_stokes.py b/test/equation/test_navier_stokes.py index 0279374ac..b2ba2cfb4 100644 --- a/test/equation/test_navier_stokes.py +++ b/test/equation/test_navier_stokes.py @@ -173,7 +173,9 @@ def test_navierstokes(nu, rho, dim, time): # check result whether is equal for name in test_output_names: - assert paddle.allclose(expected_output[name], test_output[name]), f"{name}" + assert paddle.allclose( + expected_output[name], test_output[name], atol=1e-7 + ), f"{name}" if __name__ == "__main__": From 997fa3f2b49d3af657d4117a8a7cce4b3600d22b Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Mon, 9 Oct 2023 05:35:18 +0000 Subject: [PATCH 20/27] refine code --- examples/amgnet/amgnet_airfoil.py | 6 ++++-- examples/amgnet/amgnet_cylinder.py | 6 ++++-- ppsci/arch/amgnet.py | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 110398a55..78263e6d6 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -40,7 +40,7 @@ def eval_rmse_func( output_dict: Dict[str, List["paddle.Tensor"]], label_dict: Dict[str, List["pgl.Graph"]], *args, -) -> Dict[str, float]: +) -> Dict[str, paddle.Tensor]: mse_losses = [ F.mse_loss(pred, label.y) for (pred, label) in zip(output_dict["pred"], label_dict["label"]) @@ -53,12 +53,14 @@ def eval_rmse_func( # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - OUTPUT_DIR = "./output_AMGNet" if not args.output_dir else args.output_dir + OUTPUT_DIR = "./output_AMGNet_airfoil" if not args.output_dir else args.output_dir # initialize logger logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") # set airfoil model model = ppsci.arch.AMGNet( + input_keys=("input",), + output_keys=("pred",), input_dim=5, output_dim=3, latent_dim=128, diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index efbf2b856..48e239cca 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -40,7 +40,7 @@ def eval_rmse_func( output_dict: Dict[str, List["paddle.Tensor"]], label_dict: Dict[str, List["pgl.Graph"]], *args, -) -> Dict[str, float]: +) -> Dict[str, paddle.Tensor]: mse_losses = [ F.mse_loss(pred, label.y) for (pred, label) in zip(output_dict["pred"], label_dict["label"]) @@ -53,12 +53,14 @@ def eval_rmse_func( # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - OUTPUT_DIR = "./output_AMGNet_Cylinder" if not args.output_dir else args.output_dir + OUTPUT_DIR = "./output_AMGNet_cylinder" if not args.output_dir else args.output_dir # initialize logger logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") # set cylinder model model = ppsci.arch.AMGNet( + input_keys=("input",), + output_keys=("pred",), input_dim=4, output_dim=3, latent_dim=128, diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 12660b770..118da5354 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -571,6 +571,8 @@ class AMGNet(nn.Layer): def __init__( self, + input_keys: Tuple[str, ...], + output_keys: Tuple[str, ...], input_dim: int, output_dim: int, latent_dim: int, @@ -580,6 +582,8 @@ def __init__( speed: Literal["norm", "fast"], ): super().__init__() + self.input_keys = input_keys + self.output_keys = output_keys self._latent_dim = latent_dim self.speed = speed self._output_dim = output_dim @@ -600,12 +604,12 @@ def __init__( ) def forward(self, x: Dict[str, "pgl.Graph"]) -> Dict[str, paddle.Tensor]: - graphs = x["input"] + graphs = x[self.input_keys[0]] latent_graph = self.encoder(graphs) x, p = self.processor(latent_graph, speed=self.speed) node_features = self._spa_compute(x, p) pred_field = self.decoder(node_features) - return {"pred": pred_field} + return {self.output_keys[0]: pred_field} def _make_mlp(self, output_dim: int, input_dim: int = 5, layer_norm: bool = True): widths = (self._latent_dim,) * self._num_layers + (output_dim,) From 3a6e9850c642877e3b6e894b026bdafd9d5873e5 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Mon, 9 Oct 2023 05:35:43 +0000 Subject: [PATCH 21/27] add AMGNet document --- docs/zh/examples/amgnet.md | 288 +++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 289 insertions(+) create mode 100644 docs/zh/examples/amgnet.md diff --git a/docs/zh/examples/amgnet.md b/docs/zh/examples/amgnet.md new file mode 100644 index 000000000..28fad67d1 --- /dev/null +++ b/docs/zh/examples/amgnet.md @@ -0,0 +1,288 @@ +# 2D-Darcy + + + +## 1. 背景简介 + +近年来,深度学习在计算机视觉和自然语言处理方面的成功应用,促使人们探索人工智能在科学计算领域的应用,尤其是在计算流体力学(CFD)领域的应用。 + +流体是非常复杂的物理系统,流体的行为由 Navier-Stokes 方程控制。基于网格的有限体积或有限元模拟方法是 CFD 中广泛使用的数值方法。计算流体动力学研究的物理问题往往非常复杂,通常需要大量的计算资源才能求出问题的解,因此需要在求解精度和计算成本之间进行权衡。为了进行数值模拟,计算域通常被网格离散化,由于网格具有良好的几何和物理问题表示能力,同时和图结构相契合,所以这篇文章的作者使用图神经网络,通过训练 CFD 仿真数据,构建了一种数据驱动模型来进行流场预测。 + +## 2. 问题定义 + +作者提出了一种基于图神经网络的 CFD 计算模型,称为 AMGNET(A Multi-scale Graph neural Network),该模型可以预测在不同物理参数下的流场。该方法有以下几个特点: + +- AMGNET 把 CFD 中的网格转化为图结构,通过图神经网络进行信息的处理和聚合,相比于传统的 GCN 方法,该方法的预测误差明显更低。 + +- AMGNET 可以同时计算流体在 x 和 y 方向的速度,同时还能计算流体压强。 + +- AMGNET 通过 RS 算法(Olson and Schroder, 2018)进行了图的粗化,仅使用少量节点即可进行预测,进一步提高了预测速度。 + +下图为该方法的网络结构图。该模型的基本原理就是将网格结构转化为图结构,然后通过网格中节点的物理信息,位置信息以及节点类型对图中的节点和边进行编码。接着对得到的图神经网络使用基于代数多重网格算法(RS)的粗化层进行粗化,将所有节点分类为粗节点集和细节点集,其中粗节点集是细节点集的子集。粗图的节点集合就是粗节点集,于是完成了图的粗化,缩小了图的规模。粗化完成后通过设计的图神经网络信息传递块(GN)来总结和提取图的特征。之后图恢复层采用反向操作,使用空间插值法(Qi et al.,2017)对图进行上采样。例如要对节点 $i$ 插值,则在粗图中找到距离节点 $i$ 最近的 $k$ 个节点,然后通过公式计算得到节点 $i$ 的特征。最后,通过解码器得到每个节点的速度与压力信息。 + +![AMGNet_overview](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/amgnet.png) + +## 3. 问题求解 + +接下来开始讲解如何将问题一步一步地转化为 PaddleScience 代码,用深度学习的方法求解该问题。 +为了快速理解 PaddleScience,接下来仅对模型构建、方程构建、计算域构建等关键步骤进行阐述,而其余细节请参考 [API文档](../api/arch.md)。 + +!!! info "注意事项" + + 本案例运行前需通过 `pip install pgl` 命令,安装 [**P**addle **G**raph **L**earning](https://github.com/PaddlePaddle/PGL) 图学习工具 + +### 3.1 数据集下载 + +该案例使用的机翼数据集 Airfoi l来自 de Avila Belbute-Peres 等人,其中翼型数据集采用 NACA0012 翼型,包括 train, test 以及对应的网格数据 mesh_fine;圆柱数据集是原作者利用软件计算的 CFD 算例。 + +执行以下命令,下载并解压数据集。 + +``` shell +wget https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip +unzip data.zip +``` + +### 3.2 模型构建 + +在本问题中,我们使用图神经网络 `AMGNet` 作为模型,其接收图结构数据,输出预测结果。 + +=== "airfoil" + + ``` py linenums="60" + --8<-- + examples/amgnet/amgnet_airfoil.py:60:71 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="60" + --8<-- + examples/amgnet/amgnet_cylinder.py:60:71 + --8<-- + ``` + +为了在计算时,准确快速地访问具体变量的值,我们在这里指定网络模型的输入变量名是 `("input", )`,输出变量名是 `("pred", )`,这些命名与后续代码保持一致。 + +### 3.3 约束构建 + +在本案例中,我们使用监督数据集对模型进行训练,因此需要构建监督约束。 + +在定义约束之前,我们需要指定数据集的路径、`batch_size` 等相关配置,将这些信息封装到字典中,如下所示。 + +=== "airfoil" + + ``` py linenums="73" + --8<-- + examples/amgnet/amgnet_airfoil.py:73:90 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="73" + --8<-- + examples/amgnet/amgnet_cylinder.py:73:90 + --8<-- + ``` + +接着定义训练损失函数的计算过程,如下所示。 + +=== "airfoil" + + ``` py linenums="33" + --8<-- + examples/amgnet/amgnet_airfoil.py:33:36 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="33" + --8<-- + examples/amgnet/amgnet_cylinder.py:33:36 + --8<-- + ``` + +最后构建监督约束,如下所示。 + +=== "airfoil" + + ``` py linenums="92" + --8<-- + examples/amgnet/amgnet_airfoil.py:92:100 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="92" + --8<-- + examples/amgnet/amgnet_cylinder.py:92:100 + --8<-- + ``` + +### 3.4 超参数设定 + +接下来我们需要指定训练轮数,此处使用 500 轮训练轮数。 + +=== "airfoil" + + ``` py linenums="102" + --8<-- + examples/amgnet/amgnet_airfoil.py:102:103 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="102" + --8<-- + examples/amgnet/amgnet_cylinder.py:102:103 + --8<-- + ``` + +### 3.5 优化器构建 + +训练过程会调用优化器来更新模型参数,此处选择较为常用的 `Adam` 优化器,并使用固定的 `5e-4` 作为学习率。 + +=== "airfoil" + + ``` py linenums="105" + --8<-- + examples/amgnet/amgnet_airfoil.py:105:106 + --8<-- + ``` + +=== "cylinder" + + ``` py linenums="105" + --8<-- + examples/amgnet/amgnet_cylinder.py:105:106 + --8<-- + ``` + +### 3.6 评估器构建 + +在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 `ppsci.validate.SupervisedValidator` 构建评估器,构建过程与 [约束构建](#34) 类似,只需把数据目录改为测试集的目录,并设置 `batch_size=1` 即可。 + +=== "airfoil" + + ``` py linenums="108" + --8<-- + examples/amgnet/amgnet_airfoil.py:108:131 + --8<-- + ``` +=== "cylinder" + + ``` py linenums="108" + --8<-- + examples/amgnet/amgnet_cylinder.py:108:131 + --8<-- + ``` + +评估指标为预测结果和真实结果的 RMSE 值,因此需自定义指标计算函数,如下所示。 + +=== "airfoil" + + ``` py linenums="39" + --8<-- + examples/amgnet/amgnet_airfoil.py:39:48 + --8<-- + ``` +=== "cylinder" + + ``` py linenums="39" + --8<-- + examples/amgnet/amgnet_cylinder.py:39:48 + --8<-- + ``` + +### 3.7 模型训练 + +完成上述设置之后,只需要将上述实例化的对象按顺序传递给 `ppsci.solver.Solver`,然后启动训练。 + +=== "airfoil" + + ``` py linenums="133" + --8<-- + examples/amgnet/amgnet_airfoil.py:133:149 + --8<-- + ``` +=== "cylinder" + + ``` py linenums="133" + --8<-- + examples/amgnet/amgnet_cylinder.py:133:149 + --8<-- + ``` + +### 3.8 结果可视化 + +训练完毕之后程序会对测试集中的数据进行预测,并以图片的形式对结果进行可视化,如下所示。 + +=== "airfoil" + + ``` py linenums="151" + --8<-- + examples/amgnet/amgnet_airfoil.py:151: + --8<-- + ``` +=== "cylinder" + + ``` py linenums="151" + --8<-- + examples/amgnet/amgnet_cylinder.py:151: + --8<-- + ``` + +## 4. 完整代码 + +=== "airfoil" + + ``` py linenums="1" title="amgnet_airfoil.py" + --8<-- + examples/amgnet/amgnet_airfoil.py + --8<-- + ``` +=== "cylinder" + + ``` py linenums="1" title="amgnet_airfoil.py" + --8<-- + examples/amgnet/amgnet_cylinder.py + --8<-- + ``` + +## 5. 结果展示 + +下方展示了模型对计算域中每个点的压力$p(x,y)$、x(水平)方向流速$u(x,y)$、y(垂直)方向流速$v(x,y)$的预测结果与参考结果。 + +=== "airfoil" + +
+ ![Airfoil_0_vec_x](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/airfoil_0field.png0_field.png){ loading=lazy } +
左:预测 x 方向流速 p,右:实际 x 方向流速
+ ![Airfoil_0_p](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/airfoil_0field.png1_field.png){ loading=lazy } +
左:预测压力 p,右:实际压力 p
+ ![Airfoil_0_vec_y](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/airfoil_0field.png2_field.png){ loading=lazy } +
左:预测y方向流速 p,右:实际 y 方向流速
+
+ +=== "cylinder" + +
+ ![Cylinder_0_vec_x](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/cylinder_0field.png0_field.png){ loading=lazy } +
左:预测 x 方向流速 p,右:实际 x 方向流速
+ ![Cylinder_0_p](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/cylinder_0field.png1_field.png){ loading=lazy } +
左:预测压力 p,右:实际压力 p
+ ![Cylinder_0_vec_y](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/cylinder_0field.png2_field.png){ loading=lazy } +
左:预测y方向流速 p,右:实际 y 方向流速
+
+ +可以看到模型预测结果与真实结果基本一致。 + +## 6. 参考文献 + +- [AMGNET: multi-scale graph neural networks for flow field prediction](https://doi.org/10.1080/09540091.2022.2131737) +- [AMGNet - Github](https://github.com/baoshiaijhin/amgnet) +- [AMGNet - AIStudio](https://aistudio.baidu.com/projectdetail/5592458) diff --git a/mkdocs.yml b/mkdocs.yml index e1cdd713c..83547ac5f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - FourCastNet: zh/examples/fourcastnet.md - 流体: - Cylinder2D_unsteady_transform_physx: zh/examples/cylinder2d_unsteady_transformer_physx.md + - AMGNet: zh/examples/amgnet.md - tempoGAN: zh/examples/tempoGAN.md - 数理融合: - 流体: From d5937553d7935ea775283a0e22d739c8866da4e1 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 11 Oct 2023 06:40:23 +0000 Subject: [PATCH 22/27] fix --- examples/amgnet/amgnet_airfoil.py | 2 +- examples/amgnet/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 78263e6d6..1e259ff8a 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -151,7 +151,7 @@ def eval_rmse_func( # visualize prediction with solver.no_grad_context_manager(True): for index, (input_, label, _) in enumerate(rmse_validator.data_loader): - truefield = label["input"].y + truefield = label["label"].y prefield = model(input_) utils.log_images( input_["input"].pos, diff --git a/examples/amgnet/utils.py b/examples/amgnet/utils.py index 44ba34446..c9a7a5942 100644 --- a/examples/amgnet/utils.py +++ b/examples/amgnet/utils.py @@ -135,7 +135,7 @@ def save_image( paddle.clip(grid * 255 + 0.5, 0, 255).transpose([1, 2, 0]).cast("uint8").numpy() ) im = Image.fromarray(ndarr) - os.makedirs(osp.basename(fp), exist_ok=True) + os.makedirs(osp.dirname(fp), exist_ok=True) im.save(fp, format=format) From 11c803d25bae6bab8987609e0235eb2bb81913e1 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 11 Oct 2023 08:51:37 +0000 Subject: [PATCH 23/27] fix --- examples/amgnet/amgnet_airfoil.py | 2 +- examples/amgnet/amgnet_cylinder.py | 2 +- ppsci/arch/amgnet.py | 4 ++-- ppsci/data/__init__.py | 4 ++-- ppsci/solver/eval.py | 14 +++++--------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 1e259ff8a..c65e17480 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -124,7 +124,7 @@ def eval_rmse_func( rmse_validator = ppsci.validate.SupervisedValidator( eval_dataloader_cfg, loss=ppsci.loss.FunctionalLoss(train_mse_func), - output_expr={"pred": lambda out: out["pred"]}, + output_expr={"pred": lambda out: out["pred"].unsqueeze(0)}, metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, name="RMSE_validator", ) diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index 48e239cca..10e393833 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -124,7 +124,7 @@ def eval_rmse_func( rmse_validator = ppsci.validate.SupervisedValidator( eval_dataloader_cfg, loss=ppsci.loss.FunctionalLoss(train_mse_func), - output_expr={"pred": lambda out: out["pred"]}, + output_expr={"pred": lambda out: out["pred"].unsqueeze(0)}, metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, name="RMSE_validator", ) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 118da5354..d5d9eb089 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -279,12 +279,12 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_l index_S = index_S.astype("int64") index_S[1] = n_idx[index_S[1]] subgraphnode_pos = pos[perm] - index_A = edge_index.clone() + index_A = edge_index if edge_weight is None: value_A = value_S.new_ones(edge_index[0].shape[0]) else: - value_A = edge_weight.clone() + value_A = edge_weight value_A = paddle.squeeze(value_A) eps_mask = (value_S == 0).astype(paddle.get_default_dtype()) diff --git a/ppsci/data/__init__.py b/ppsci/data/__init__.py index 6ab46f40a..afe359d7a 100644 --- a/ppsci/data/__init__.py +++ b/ppsci/data/__init__.py @@ -100,9 +100,9 @@ def build_dataloader(_dataset, cfg): # Use special dataloader from "Paddle Graph Learning" toolkit. try: from pgl.utils import data as pgl_data - except ModuleNotFoundError: + except ModuleNotFoundError as e: logger.error("Please install pgl with `pip install pgl`.") - raise ModuleNotFoundError("pgl is not installed.") + raise ModuleNotFoundError(str(e)) collate_fn = batch_transform.default_collate_fn dataloader_ = pgl_data.Dataloader( diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index a3be30a45..9c0be8251 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -114,25 +114,21 @@ def _eval_by_dataset( # collect batch data for key, input in input_dict.items(): all_input[key].append( - input.detach() - if hasattr(input, "detach") - else input + (input.detach() if hasattr(input, "detach") else input) if solver.world_size == 1 else misc.all_gather(input.detach()) ) + for key, output in output_dict.items(): all_output[key].append( - output.detach() - if hasattr(output, "detach") - else output + (output.detach() if hasattr(output, "detach") else output) if solver.world_size == 1 else misc.all_gather(output.detach()) ) + for key, label in label_dict.items(): all_label[key].append( - label.detach() - if hasattr(label, "detach") - else label + (label.detach() if hasattr(label, "detach") else label) if solver.world_size == 1 else misc.all_gather(label.detach()) ) From 23812900b4fabf170eb62cfdcc09cd51d0da2a59 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 17 Oct 2023 02:55:46 +0000 Subject: [PATCH 24/27] avoid tensor converion in dataset, and move in to collate_fn --- examples/amgnet/amgnet_airfoil.py | 4 +- ppsci/arch/amgnet.py | 67 ++++++++------- ppsci/data/dataset/airfoil_dataset.py | 37 +++++---- ppsci/data/dataset/cylinder_dataset.py | 81 +++++++++---------- .../data/process/batch_transform/__init__.py | 11 +-- ppsci/solver/train.py | 6 +- 6 files changed, 111 insertions(+), 95 deletions(-) diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index c65e17480..8f1e35542 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -31,7 +31,9 @@ def train_mse_func( - output_dict: Dict[str, "paddle.Tensor"], label_dict: Dict[str, "pgl.Graph"], *args + output_dict: Dict[str, "paddle.Tensor"], + label_dict: Dict[str, "pgl.Graph"], + *args, ) -> paddle.Tensor: return F.mse_loss(output_dict["pred"], label_dict["label"].y) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index d5d9eb089..5a3eb45cd 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -119,20 +119,20 @@ def StAS( (values_S, (indices_S[0], indices_S[1])), shape=(N, kN) ) - ans = coo_A.dot(coo_S).tocoo() # sp_x @ sp_s + ans = coo_A.dot(coo_S).tocoo() row = paddle.to_tensor(ans.row) col = paddle.to_tensor(ans.col) index_B = paddle.stack([row, col], axis=0) - value_B = paddle.to_tensor(ans.data) # sp_x @ sp_s + value_B = paddle.to_tensor(ans.data) indices_A = index_S values_A = value_S - coo_A = pd_sparse.sparse_coo_tensor(indices_A, values_A) # sp_s + coo_A = pd_sparse.sparse_coo_tensor(indices_A, values_A) out = pd_sparse.transpose(coo_A, [1, 0]) index_St = out.indices() value_St = out.values() - sp_x = pd_sparse.sparse_coo_tensor(index_B, value_B) # sp_b + sp_x = pd_sparse.sparse_coo_tensor(index_B, value_B) sp_x = pd_sparse.coalesce(sp_x) index_B = sp_x.indices() value_B = sp_x.values() @@ -200,6 +200,7 @@ def FillZeros( def remove_self_loops( edge_index: paddle.Tensor, edge_attr: Optional[paddle.Tensor] = None ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: + # remove self-loop mask = edge_index[0] != edge_index[1] mask = mask.tolist() edge_index = edge_index.t() @@ -262,8 +263,10 @@ def faster_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_layer): - """come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive structure aware pooling - for learning hierarchical graph representations. AAAI(2020)""" + """ + come from Ranjan, E., Sanyal, S., Talukdar, P. (2020, April). Asap: Adaptive + structure aware pooling for learning hierarchical graph representations. AAAI(2020) + """ kN = perm.shape[0] perm2 = perm.reshape((-1, 1)) @@ -279,12 +282,12 @@ def norm_graph_connectivity(perm, edge_index, edge_weight, score, pos, N, norm_l index_S = index_S.astype("int64") index_S[1] = n_idx[index_S[1]] subgraphnode_pos = pos[perm] - index_A = edge_index + index_A = edge_index.clone() if edge_weight is None: value_A = value_S.new_ones(edge_index[0].shape[0]) else: - value_A = edge_weight + value_A = edge_weight.clone() value_A = paddle.squeeze(value_A) eps_mask = (value_S == 0).astype(paddle.get_default_dtype()) @@ -318,11 +321,11 @@ class GraphNetBlock(nn.Layer): """Multi-Edge Interaction Network with residual connections.""" def __init__( - self, model_fn, output_dims, message_passing_aggregator, attention=False + self, model_fn, output_dim, message_passing_aggregator, attention=False ): super().__init__() - self.edge_model = model_fn(output_dims, 384) - self.node_model = model_fn(output_dims, 256) + self.edge_model = model_fn(output_dim, 384) + self.node_model = model_fn(output_dim, 256) self.message_passing_aggregator = message_passing_aggregator def _update_edge_features(self, graph): @@ -430,7 +433,7 @@ def __init__( self.graphnet_blocks.append( GraphNetBlock( model_fn=make_mlp, - output_dims=output_dim, + output_dim=output_dim, message_passing_aggregator=message_passing_aggregator, attention=attention, ) @@ -439,7 +442,7 @@ def __init__( self.pool_blocks.append( GraphNetBlock( model_fn=make_mlp, - output_dims=output_dim, + output_dim=output_dim, message_passing_aggregator=message_passing_aggregator, attention=attention, ) @@ -501,20 +504,24 @@ class FullyConnectedLayer(nn.Layer): def __init__(self, input_dim: int, hidden_size: Tuple[int, ...]): super(FullyConnectedLayer, self).__init__() num_layers = len(hidden_size) - self.layers = nn.LayerList() + self._layers_ordered_dict = {} + self.in_dim = input_dim + for index, output_dim in enumerate(hidden_size): + self._layers_ordered_dict["linear_" + str(index)] = nn.Linear( + self.in_dim, output_dim + ) + if index < (num_layers - 1): + self._layers_ordered_dict["relu_" + str(index)] = nn.ReLU() + self.in_dim = output_dim - cur_dim = input_dim - for i, hidden_size_ in enumerate(hidden_size): - self.layers.append(nn.Linear(cur_dim, hidden_size_)) - if i < (num_layers - 1): - self.layers.append(nn.ReLU()) - cur_dim = hidden_size_ + self.layers = nn.LayerDict(self._layers_ordered_dict) def forward(self, input): - output = input - for layer in self.layers: - output = layer(output) - return output + for key in self.layers: + layer = self.layers[key] + output = layer(input) + input = output + return input class Encoder(nn.Layer): @@ -524,8 +531,8 @@ def __init__(self, input_dim, make_mlp, latent_dim): super(Encoder, self).__init__() self._make_mlp = make_mlp self._latent_dim = latent_dim - self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) # 4 - self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=1) # 1 + self.node_model = self._make_mlp(latent_dim, input_dim=input_dim) + self.mesh_edge_model = self._make_mlp(latent_dim, input_dim=1) def forward(self, graph): node_latents = self.node_model(graph.x) @@ -537,9 +544,9 @@ def forward(self, graph): class Decoder(nn.Layer): - """Decodes node features from graph.""" - - """Encodes node and edge features into latent features.""" + """Decodes node features from graph. + Encodes node and edge features into latent features. + """ def __init__(self, make_mlp, output_dim): super(Decoder, self).__init__() @@ -558,6 +565,8 @@ class AMGNet(nn.Layer): Code reference: https://github.com/baoshiaijhin/amgnet Args: + input_keys (Tuple[str, ...]): Name of input keys, such as ("x", "y", "z"). + output_keys (Tuple[str, ...]): Name of output keys, such as ("u", "v", "w"). input_dim (int): Number of input dimension. output_dim (int): Number of output dimension. latent_dim (int): Number of hidden(feature) dimension. diff --git a/ppsci/data/dataset/airfoil_dataset.py b/ppsci/data/dataset/airfoil_dataset.py index 73120c915..8c454a469 100644 --- a/ppsci/data/dataset/airfoil_dataset.py +++ b/ppsci/data/dataset/airfoil_dataset.py @@ -117,27 +117,28 @@ def __init__( with open(osp.join(osp.dirname(self.data_dir), "train_max_min.pkl"), "rb") as f: self.normalization_factors = pickle.load(f) - self.nodes = paddle.to_tensor(self.mesh_graph[0]) - self.edges = paddle.to_tensor(self.mesh_graph[1]) + self.nodes = self.mesh_graph[0] + self.edges = self.mesh_graph[1] self.elems_list = self.mesh_graph[2] self.marker_dict = self.mesh_graph[3] - self.node_markers = paddle.full([self.nodes.shape[0], 1], fill_value=-1) + self.node_markers = np.full([self.nodes.shape[0], 1], fill_value=-1) for i, (marker_tag, marker_elems) in enumerate(self.marker_dict.items()): for elem in marker_elems: self.node_markers[elem[0]] = i self.node_markers[elem[1]] = i + self.raw_graphs = [self.get(i) for i in range(len(self))] + def __len__(self): return self.len def __getitem__(self, idx): - raw_graph = self.get(idx) return ( { - self.input_keys[0]: raw_graph, + self.input_keys[0]: self.raw_graphs[idx], }, { - self.label_keys[0]: raw_graph, + self.label_keys[0]: self.raw_graphs[idx], }, None, ) @@ -145,13 +146,13 @@ def __getitem__(self, idx): def get(self, idx): with open(osp.join(self.data_dir, self.file_list[idx]), "rb") as f: fields = pickle.load(f) - fields = paddle.to_tensor(self._preprocess(fields)) + fields = self._preprocess(fields) aoa, reynolds, mach = self._get_params_from_name(self.file_list[idx]) - aoa = paddle.to_tensor(aoa) + # aoa = aoa mach_or_reynolds = mach if reynolds is None else reynolds - mach_or_reynolds = paddle.to_tensor(mach_or_reynolds) - norm_aoa = paddle.to_tensor(aoa / 10) - norm_mach_or_reynolds = paddle.to_tensor( + # mach_or_reynolds = mach_or_reynolds + norm_aoa = aoa / 10 + norm_mach_or_reynolds = ( mach_or_reynolds if reynolds is None else (mach_or_reynolds - 1.5e6) / 1.5e6 ) @@ -166,25 +167,27 @@ def get(self, idx): ], axis=-1, ).astype(paddle.get_default_dtype()) - nodes = paddle.to_tensor(nodes) + data = pgl.Graph( num_nodes=nodes.shape[0], edges=self.edges, ) - data.x = nodes data.y = fields data.pos = self.nodes data.edge_index = self.edges + sender = data.x[data.edge_index[0]] receiver = data.x[data.edge_index[1]] relation_pos = sender[:, 0:2] - receiver[:, 0:2] - post = paddle.linalg.norm(relation_pos, p=2, axis=1, keepdim=True) + post = np.linalg.norm(relation_pos, ord=2, axis=1, keepdims=True).astype( + paddle.get_default_dtype() + ) data.edge_attr = post - std_epsilon = paddle.to_tensor([1e-8]) - a = paddle.mean(data.edge_attr, axis=0) + std_epsilon = [1e-8] + a = np.mean(data.edge_attr, axis=0) b = data.edge_attr.std(axis=0) - b = paddle.maximum(b, std_epsilon) + b = np.maximum(b, std_epsilon).astype(paddle.get_default_dtype()) data.edge_attr = (data.edge_attr - a) / b data.aoa = aoa data.norm_aoa = norm_aoa diff --git a/ppsci/data/dataset/cylinder_dataset.py b/ppsci/data/dataset/cylinder_dataset.py index f31129d76..f45e44dd6 100644 --- a/ppsci/data/dataset/cylinder_dataset.py +++ b/ppsci/data/dataset/cylinder_dataset.py @@ -62,33 +62,35 @@ def __init__( self.len = len(self.file_list) self.mesh_graph = airfoil_dataset._get_mesh_graph(mesh_graph_path) - self.normalization_factors = paddle.to_tensor( - [[978.6001, 48.9258, 24.8404], [-692.3159, -6.9950, -24.8572]] + self.normalization_factors = np.array( + [[978.6001, 48.9258, 24.8404], [-692.3159, -6.9950, -24.8572]], + dtype=paddle.get_default_dtype(), ) - self.nodes = paddle.to_tensor(self.mesh_graph[0]) + self.nodes = self.mesh_graph[0] self.meshnodes = self.mesh_graph[0] - self.edges = paddle.to_tensor(self.mesh_graph[1]) + self.edges = self.mesh_graph[1] self.elems_list = self.mesh_graph[2] self.marker_dict = self.mesh_graph[3] self.bounder = [] - self.node_markers = paddle.full([self.nodes.shape[0], 1], fill_value=-1) + self.node_markers = np.full([self.nodes.shape[0], 1], fill_value=-1) for i, (marker_tag, marker_elems) in enumerate(self.marker_dict.items()): for elem in marker_elems: self.node_markers[elem[0]] = i self.node_markers[elem[1]] = i + self.raw_graphs = [self.get(i) for i in range(len(self))] + def __len__(self): return self.len def __getitem__(self, idx): - raw_graph = self.get(idx) return ( { - self.input_keys[0]: raw_graph, + self.input_keys[0]: self.raw_graphs[idx], }, { - self.label_keys[0]: raw_graph, + self.label_keys[0]: self.raw_graphs[idx], }, None, ) @@ -102,30 +104,28 @@ def get(self, idx): lines_field = line.split(",")[3:] numbers_float = list(eval(i) for i in lines_pos) array = np.array(numbers_float, paddle.get_default_dtype()) - a = paddle.to_tensor(array) - pos.append(a) + pos.append(array) numbers_float = list(eval(i) for i in lines_field) array = np.array(numbers_float, paddle.get_default_dtype()) - a = paddle.to_tensor(array) - field.append(a) + field.append(array) - field = paddle.stack(field, axis=0) - pos = paddle.stack(pos, axis=0) + field = np.stack(field, axis=0) + pos = np.stack(pos, axis=0) indexlist = [] for i in range(self.meshnodes.shape[0]): - b = paddle.to_tensor(self.meshnodes[i : (i + 1)]) - b = paddle.squeeze(b) - index = paddle.nonzero( - paddle.sum((pos == b), axis=1, dtype="float32") == pos.shape[1] + b = self.meshnodes[i : (i + 1)] + b = np.squeeze(b) + index = np.nonzero( + np.sum((pos == b), axis=1, dtype=paddle.get_default_dtype()) + == pos.shape[1] ) indexlist.append(index) - indexlist = paddle.stack(indexlist, axis=0) - indexlist = paddle.squeeze(indexlist) + indexlist = np.stack(indexlist, axis=0) + indexlist = np.squeeze(indexlist) fields = field[indexlist] velocity = self._get_params_from_name(self.file_list[idx]) - aoa = paddle.to_tensor(velocity) - norm_aoa = paddle.to_tensor(aoa / 40) + norm_aoa = velocity / 40 # add physics parameters to graph nodes = np.concatenate( [ @@ -135,7 +135,6 @@ def get(self, idx): ], axis=-1, ).astype(paddle.get_default_dtype()) - nodes = paddle.to_tensor(nodes) data = pgl.Graph( num_nodes=nodes.shape[0], @@ -145,21 +144,23 @@ def get(self, idx): data.y = fields data.pos = self.nodes data.edge_index = self.edges - data.velocity = aoa + data.velocity = velocity sender = data.x[data.edge_index[0]] receiver = data.x[data.edge_index[1]] relation_pos = sender[:, 0:2] - receiver[:, 0:2] - post = paddle.linalg.norm(relation_pos, p=2, axis=1, keepdim=True) + post = np.linalg.norm(relation_pos, ord=2, axis=1, keepdims=True).astype( + paddle.get_default_dtype() + ) data.edge_attr = post - std_epsilon = paddle.to_tensor([1e-8]) - a = paddle.mean(data.edge_attr, axis=0) + std_epsilon = [1e-8] + a = np.mean(data.edge_attr, axis=0) b = data.edge_attr.std(axis=0) - b = paddle.maximum(b, std_epsilon) + b = np.maximum(b, std_epsilon).astype(paddle.get_default_dtype()) data.edge_attr = (data.edge_attr - a) / b - a = paddle.mean(data.y, axis=0) + a = np.mean(data.y, axis=0) b = data.y.std(axis=0) - b = paddle.maximum(b, std_epsilon) + b = np.maximum(b, std_epsilon).astype(paddle.get_default_dtype()) data.y = (data.y - a) / b data.norm_max = a data.norm_min = b @@ -173,28 +174,26 @@ def get(self, idx): lines_field = line.split(",")[3:] numbers_float = list(eval(i) for i in lines_pos) array = np.array(numbers_float, paddle.get_default_dtype()) - a = paddle.to_tensor(array) - pos.append(a) + pos.append(array) numbers_float = list(eval(i) for i in lines_field) array = np.array(numbers_float, paddle.get_default_dtype()) - a = paddle.to_tensor(array) - field.append(a) + field.append(array) - field = paddle.stack(field, axis=0) - pos = paddle.stack(pos, axis=0) + field = np.stack(field, axis=0) + pos = np.stack(pos, axis=0) indexlist = [] for i in range(pos.shape[0]): b = pos[i : (i + 1)] - b = paddle.squeeze(b) - index = paddle.nonzero( - paddle.sum((self.nodes == b), axis=1, dtype="float32") + b = np.squeeze(b) + index = np.nonzero( + np.sum((self.nodes == b), axis=1, dtype=paddle.get_default_dtype()) == self.nodes.shape[1] ) indexlist.append(index) - indexlist = paddle.stack(indexlist, axis=0) - indexlist = paddle.squeeze(indexlist) + indexlist = np.stack(indexlist, axis=0) + indexlist = np.squeeze(indexlist) self.bounder = indexlist return data diff --git a/ppsci/data/process/batch_transform/__init__.py b/ppsci/data/process/batch_transform/__init__.py index e45279bda..2f50850d2 100644 --- a/ppsci/data/process/batch_transform/__init__.py +++ b/ppsci/data/process/batch_transform/__init__.py @@ -66,11 +66,12 @@ def default_collate_fn(batch: List[Any]) -> Any: elif str(type(sample)) == "": # use str(type()) instead of isinstance() in case of pgl is not installed. graph = pgl.Graph(num_nodes=sample.num_nodes, edges=sample.edges) - graph.x = paddle.concat([g.x for g in batch]) - graph.y = paddle.concat([g.y for g in batch]) - graph.edge_index = paddle.concat([g.edge_index for g in batch], axis=1) - graph.edge_attr = paddle.concat([g.edge_attr for g in batch]) - graph.pos = paddle.concat([g.pos for g in batch]) + graph.x = np.concatenate([g.x for g in batch]) + graph.y = np.concatenate([g.y for g in batch]) + graph.edge_index = np.concatenate([g.edge_index for g in batch], axis=1) + graph.edge_attr = np.concatenate([g.edge_attr for g in batch]) + graph.pos = np.concatenate([g.pos for g in batch]) + graph.tensor() graph.shape = [len(batch)] return graph diff --git a/ppsci/solver/train.py b/ppsci/solver/train.py index 26de4a11f..b31363091 100644 --- a/ppsci/solver/train.py +++ b/ppsci/solver/train.py @@ -64,7 +64,8 @@ def train_epoch_func(solver: "solver.Solver", epoch_id: int, log_freq: int): solver.train_time_info[key].reset() reader_cost += time.perf_counter() - reader_tic for v in input_dict.values(): - v.stop_gradient = False + if hasattr(v, "stop_gradient"): + v.stop_gradient = False # gather each constraint's input, label, weight to a list input_dicts.append(input_dict) @@ -165,7 +166,8 @@ def train_LBFGS_epoch_func(solver: "solver.Solver", epoch_id: int, log_freq: int input_dict, label_dict, weight_dict = next(_constraint.data_iter) reader_cost += time.perf_counter() - reader_tic for v in input_dict.values(): - v.stop_gradient = False + if hasattr(v, "stop_gradient"): + v.stop_gradient = False # gather all constraint data into list input_dicts.append(input_dict) From 7eb4aab40d22bb3eb6217702a3f5989182abee40 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 18 Oct 2023 05:33:49 +0000 Subject: [PATCH 25/27] update final code --- docs/index.md | 32 ++--- docs/zh/examples/amgnet.md | 147 +++++++++++++++------- docs/zh/examples/bracket.md | 2 +- examples/amgnet/amgnet_airfoil.py | 127 +++++++++++++------ examples/amgnet/amgnet_cylinder.py | 133 ++++++++++++++------ examples/amgnet/conf/amgnet_airfoil.yaml | 57 +++++++++ examples/amgnet/conf/amgnet_cylinder.yaml | 57 +++++++++ 7 files changed, 418 insertions(+), 137 deletions(-) create mode 100644 examples/amgnet/conf/amgnet_airfoil.yaml create mode 100644 examples/amgnet/conf/amgnet_cylinder.yaml diff --git a/docs/index.md b/docs/index.md index 0beb25285..d2c20b615 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,29 +10,29 @@ ![panorama](https://paddle-org.bj.bcebos.com/paddlescience/docs/overview/panorama.png) -!!! tip "快速安装" +--8<-- +./README.md:update +--8<-- - === "方式1: 源码安装[推荐]" +## 快速安装 - --8<-- - ./README.md:git_install - --8<-- +=== "方式1: 源码安装[推荐]" - === "方式2: pip安装" + --8<-- + ./README.md:git_install + --8<-- - ``` shell - pip install paddlesci - ``` +=== "方式2: pip安装" - === "[完整安装流程](./zh/install_setup.md)" + ``` shell + pip install paddlesci + ``` - ``` shell - pip install paddlesci - ``` +=== "[完整安装流程](./zh/install_setup.md)" ---8<-- -./README.md:update ---8<-- + ``` shell + pip install paddlesci + ``` --8<-- ./README.md:feature diff --git a/docs/zh/examples/amgnet.md b/docs/zh/examples/amgnet.md index 28fad67d1..2f2843e71 100644 --- a/docs/zh/examples/amgnet.md +++ b/docs/zh/examples/amgnet.md @@ -1,7 +1,62 @@ -# 2D-Darcy +# AMGNet +=== "模型训练命令" + + === "amgnet_airfoil" + + ``` sh + # linux + wget https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip + # windows + # curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip --output data.zip + # unzip it + unzip data.zip + python amgnet_airfoil.py + ``` + === "amgnet_cylinder" + + ``` sh + # linux + wget https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip + # windows + # curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip --output data.zip + # unzip it + unzip data.zip + python amgnet_cylinder.py + ``` + +=== "模型评估命令" + + === "amgnet_airfoil" + + ``` sh + # linux + wget https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip + # windows + # curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip --output data.zip + # unzip it + unzip data.zip + python amgnet_airfoil.py mode=eval EVAL.pretained_model_path=https://paddle-org.bj.bcebos.com/paddlescience/models/amgnet/amgnet_airfoil_pretrained.pdparams + ``` + === "amgnet_cylinder" + + ``` sh + # linux + wget https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip + # windows + # curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/AMGNet/data.zip --output data.zip + # unzip it + unzip data.zip + python amgnet_cylinder.py mode=eval EVAL.pretrained_model_path=https://paddle-org.bj.bcebos.com/paddlescience/models/amgnet/amgnet_cylinder_pretrained.pdparams + ``` + +| 预训练模型 | 指标 | +|:--| :--| +| [amgnet_airfoil_pretrained.pdparams](https://paddle-org.bj.bcebos.com/paddlescience/models/amgnet/amgnet_airfoil_pretrained.pdparams) | loss(RMSE_validator): 0.0001
RMSE.RMSE(RMSE_validator): 0.01315 | +| [amgnet_cylinder_pretrained.pdparams](https://paddle-org.bj.bcebos.com/paddlescience/models/amgnet/amgnet_cylinder_pretrained.pdparams) | loss(RMSE_validator): 0.00048
RMSE.RMSE(RMSE_validator): 0.02197 | + ## 1. 背景简介 近年来,深度学习在计算机视觉和自然语言处理方面的成功应用,促使人们探索人工智能在科学计算领域的应用,尤其是在计算流体力学(CFD)领域的应用。 @@ -29,7 +84,7 @@ !!! info "注意事项" - 本案例运行前需通过 `pip install pgl` 命令,安装 [**P**addle **G**raph **L**earning](https://github.com/PaddlePaddle/PGL) 图学习工具 + 本案例运行前需通过 `pip install pgl pyamg` 命令,安装 [**P**addle **G**raph **L**earning](https://github.com/PaddlePaddle/PGL) 图学习工具和 [PyAMG](https://github.com/pyamg/pyamg) 代数多重网格工具。 ### 3.1 数据集下载 @@ -48,17 +103,17 @@ unzip data.zip === "airfoil" - ``` py linenums="60" + ``` py linenums="61" --8<-- - examples/amgnet/amgnet_airfoil.py:60:71 + examples/amgnet/amgnet_airfoil.py:61:62 --8<-- ``` === "cylinder" - ``` py linenums="60" + ``` py linenums="61" --8<-- - examples/amgnet/amgnet_cylinder.py:60:71 + examples/amgnet/amgnet_cylinder.py:61:62 --8<-- ``` @@ -68,21 +123,21 @@ unzip data.zip 在本案例中,我们使用监督数据集对模型进行训练,因此需要构建监督约束。 -在定义约束之前,我们需要指定数据集的路径、`batch_size` 等相关配置,将这些信息封装到字典中,如下所示。 +在定义约束之前,我们需要指定数据集的路径等相关配置,将这些信息存放到对应的 YAML 文件中,如下所示。 === "airfoil" - ``` py linenums="73" + ``` py linenums="21" --8<-- - examples/amgnet/amgnet_airfoil.py:73:90 + examples/amgnet/conf/amgnet_airfoil.yaml:21:27 --8<-- ``` === "cylinder" - ``` py linenums="73" + ``` py linenums="21" --8<-- - examples/amgnet/amgnet_cylinder.py:73:90 + examples/amgnet/conf/amgnet_cylinder.yaml:21:27 --8<-- ``` @@ -90,17 +145,17 @@ unzip data.zip === "airfoil" - ``` py linenums="33" + ``` py linenums="35" --8<-- - examples/amgnet/amgnet_airfoil.py:33:36 + examples/amgnet/amgnet_airfoil.py:35:40 --8<-- ``` === "cylinder" - ``` py linenums="33" + ``` py linenums="35" --8<-- - examples/amgnet/amgnet_cylinder.py:33:36 + examples/amgnet/amgnet_cylinder.py:35:40 --8<-- ``` @@ -108,37 +163,37 @@ unzip data.zip === "airfoil" - ``` py linenums="92" + ``` py linenums="82" --8<-- - examples/amgnet/amgnet_airfoil.py:92:100 + examples/amgnet/amgnet_airfoil.py:82:90 --8<-- ``` === "cylinder" - ``` py linenums="92" + ``` py linenums="82" --8<-- - examples/amgnet/amgnet_cylinder.py:92:100 + examples/amgnet/amgnet_cylinder.py:82:90 --8<-- ``` ### 3.4 超参数设定 -接下来我们需要指定训练轮数,此处使用 500 轮训练轮数。 +设置训练轮数等参数,如下所示。 === "airfoil" - ``` py linenums="102" + ``` py linenums="41" --8<-- - examples/amgnet/amgnet_airfoil.py:102:103 + examples/amgnet/conf/amgnet_airfoil.yaml:41:51 --8<-- ``` === "cylinder" - ``` py linenums="102" + ``` py linenums="41" --8<-- - examples/amgnet/amgnet_cylinder.py:102:103 + examples/amgnet/conf/amgnet_cylinder.yaml:41:51 --8<-- ``` @@ -148,36 +203,36 @@ unzip data.zip === "airfoil" - ``` py linenums="105" + ``` py linenums="92" --8<-- - examples/amgnet/amgnet_airfoil.py:105:106 + examples/amgnet/amgnet_airfoil.py:92:93 --8<-- ``` === "cylinder" - ``` py linenums="105" + ``` py linenums="92" --8<-- - examples/amgnet/amgnet_cylinder.py:105:106 + examples/amgnet/amgnet_cylinder.py:92:93 --8<-- ``` ### 3.6 评估器构建 -在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 `ppsci.validate.SupervisedValidator` 构建评估器,构建过程与 [约束构建](#34) 类似,只需把数据目录改为测试集的目录,并设置 `batch_size=1` 即可。 +在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 `ppsci.validate.SupervisedValidator` 构建评估器,构建过程与 [约束构建](#34) 类似,只需把数据目录改为测试集的目录,并在配置文件中设置 `EVAL.batch_size=1` 即可。 === "airfoil" - ``` py linenums="108" + ``` py linenums="95" --8<-- - examples/amgnet/amgnet_airfoil.py:108:131 + examples/amgnet/amgnet_airfoil.py:95:118 --8<-- ``` === "cylinder" - ``` py linenums="108" + ``` py linenums="95" --8<-- - examples/amgnet/amgnet_cylinder.py:108:131 + examples/amgnet/amgnet_cylinder.py:95:118 --8<-- ``` @@ -185,16 +240,16 @@ unzip data.zip === "airfoil" - ``` py linenums="39" + ``` py linenums="43" --8<-- - examples/amgnet/amgnet_airfoil.py:39:48 + examples/amgnet/amgnet_airfoil.py:43:52 --8<-- ``` === "cylinder" - ``` py linenums="39" + ``` py linenums="43" --8<-- - examples/amgnet/amgnet_cylinder.py:39:48 + examples/amgnet/amgnet_cylinder.py:43:52 --8<-- ``` @@ -204,16 +259,16 @@ unzip data.zip === "airfoil" - ``` py linenums="133" + ``` py linenums="120" --8<-- - examples/amgnet/amgnet_airfoil.py:133:149 + examples/amgnet/amgnet_airfoil.py:120:136 --8<-- ``` === "cylinder" - ``` py linenums="133" + ``` py linenums="120" --8<-- - examples/amgnet/amgnet_cylinder.py:133:149 + examples/amgnet/amgnet_cylinder.py:120:136 --8<-- ``` @@ -223,16 +278,16 @@ unzip data.zip === "airfoil" - ``` py linenums="151" + ``` py linenums="138" --8<-- - examples/amgnet/amgnet_airfoil.py:151: + examples/amgnet/amgnet_airfoil.py:138: --8<-- ``` === "cylinder" - ``` py linenums="151" + ``` py linenums="138" --8<-- - examples/amgnet/amgnet_cylinder.py:151: + examples/amgnet/amgnet_cylinder.py:138: --8<-- ``` @@ -276,7 +331,7 @@ unzip data.zip ![Cylinder_0_p](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/cylinder_0field.png1_field.png){ loading=lazy }
左:预测压力 p,右:实际压力 p
![Cylinder_0_vec_y](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/cylinder_0field.png2_field.png){ loading=lazy } -
左:预测y方向流速 p,右:实际 y 方向流速
+
左:预测 y 方向流速 p,右:实际 y 方向流速
可以看到模型预测结果与真实结果基本一致。 diff --git a/docs/zh/examples/bracket.md b/docs/zh/examples/bracket.md index 82f0bad4d..3fed01dab 100644 --- a/docs/zh/examples/bracket.md +++ b/docs/zh/examples/bracket.md @@ -28,7 +28,7 @@ | 预训练模型 | 指标 | |:--| :--| -| [bracket_pretrained.pdparams](https://paddle-org.bj.bcebos.com/paddlescience/models/bracket/bracket_pretrained.pdparams) | loss(commercial_ref_u_v_w_sigmas): 32.28704, MSE.u(commercial_ref_u_v_w_sigmas): 0.00005, MSE.v(commercial_ref_u_v_w_sigmas): 0.00000, MSE.w(commercial_ref_u_v_w_sigmas): 0.00734, MSE.sigma_xx(commercial_ref_u_v_w_sigmas): 27.64751, MSE.sigma_yy(commercial_ref_u_v_w_sigmas): 1.23101, MSE.sigma_zz(commercial_ref_u_v_w_sigmas): 0.89106, MSE.sigma_xy(commercial_ref_u_v_w_sigmas): 0.84370, MSE.sigma_xz(commercial_ref_u_v_w_sigmas): 1.42126, MSE.sigma_yz(commercial_ref_u_v_w_sigmas): 0.24510 | +| [bracket_pretrained.pdparams](https://paddle-org.bj.bcebos.com/paddlescience/models/bracket/bracket_pretrained.pdparams) | loss(commercial_ref_u_v_w_sigmas): 32.28704
MSE.u(commercial_ref_u_v_w_sigmas): 0.00005
MSE.v(commercial_ref_u_v_w_sigmas): 0.00000
MSE.w(commercial_ref_u_v_w_sigmas): 0.00734
MSE.sigma_xx(commercial_ref_u_v_w_sigmas): 27.64751
MSE.sigma_yy(commercial_ref_u_v_w_sigmas): 1.23101
MSE.sigma_zz(commercial_ref_u_v_w_sigmas): 0.89106
MSE.sigma_xy(commercial_ref_u_v_w_sigmas): 0.84370
MSE.sigma_xz(commercial_ref_u_v_w_sigmas): 1.42126
MSE.sigma_yz(commercial_ref_u_v_w_sigmas): 0.24510 | ## 1. 背景简介 diff --git a/examples/amgnet/amgnet_airfoil.py b/examples/amgnet/amgnet_airfoil.py index 8f1e35542..77434bbfc 100644 --- a/examples/amgnet/amgnet_airfoil.py +++ b/examples/amgnet/amgnet_airfoil.py @@ -14,15 +14,17 @@ from __future__ import annotations +from os import path as osp from typing import TYPE_CHECKING from typing import Dict from typing import List +import hydra import utils +from omegaconf import DictConfig from paddle.nn import functional as F import ppsci -from ppsci.utils import config from ppsci.utils import logger if TYPE_CHECKING: @@ -50,39 +52,25 @@ def eval_rmse_func( return {"RMSE": (sum(mse_losses) / len(mse_losses)) ** 0.5} -if __name__ == "__main__": - args = config.parse_args() +def train(cfg: DictConfig): # set random seed for reproducibility - ppsci.utils.misc.set_random_seed(42) - # set output directory - OUTPUT_DIR = "./output_AMGNet_airfoil" if not args.output_dir else args.output_dir + ppsci.utils.misc.set_random_seed(cfg.seed) # initialize logger - logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") + logger.init_logger("ppsci", osp.join(cfg.output_dir, "train.log"), "info") # set airfoil model - model = ppsci.arch.AMGNet( - input_keys=("input",), - output_keys=("pred",), - input_dim=5, - output_dim=3, - latent_dim=128, - num_layers=2, - message_passing_aggregator="sum", - message_passing_steps=6, - speed="norm", - ) + model = ppsci.arch.AMGNet(**cfg.MODEL) # set dataloader config - ITERS_PER_EPOCH = 42 train_dataloader_cfg = { "dataset": { "name": "MeshAirfoilDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_dir": "./data/NACA0012_interpolate/outputs_train", - "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", + "data_dir": cfg.TRAIN_DATA_DIR, + "mesh_graph_path": cfg.TRAIN_MESH_GRAPH_PATH, }, - "batch_size": 4, + "batch_size": cfg.TRAIN.batch_size, "sampler": { "name": "BatchSampler", "drop_last": False, @@ -101,11 +89,8 @@ def eval_rmse_func( # wrap constraints together constraint = {sup_constraint.name: sup_constraint} - # set training hyper-parameters - EPOCHS = 500 if not args.epochs else args.epochs - # set optimizer - optimizer = ppsci.optimizer.Adam(5e-4)(model) + optimizer = ppsci.optimizer.Adam(cfg.TRAIN.learning_rate)(model) # set validator eval_dataloader_cfg = { @@ -113,10 +98,10 @@ def eval_rmse_func( "name": "MeshAirfoilDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_dir": "./data/NACA0012_interpolate/outputs_test", - "mesh_graph_path": "./data/NACA0012_interpolate/mesh_fine.su2", + "data_dir": cfg.EVAL_DATA_DIR, + "mesh_graph_path": cfg.EVAL_MESH_GRAPH_PATH, }, - "batch_size": 1, + "batch_size": cfg.EVAL.batch_size, "sampler": { "name": "BatchSampler", "drop_last": False, @@ -136,16 +121,16 @@ def eval_rmse_func( solver = ppsci.solver.Solver( model, constraint, - OUTPUT_DIR, + cfg.output_dir, optimizer, None, - EPOCHS, - ITERS_PER_EPOCH, - save_freq=50, - eval_during_train=True, - eval_freq=50, + cfg.TRAIN.epochs, + cfg.TRAIN.iters_per_epoch, + save_freq=cfg.TRAIN.save_freq, + eval_during_train=cfg.TRAIN.eval_during_train, + eval_freq=cfg.TRAIN.eval_freq, validator=validator, - eval_with_no_grad=True, + eval_with_no_grad=cfg.EVAL.eval_with_no_grad, ) # train model solver.train() @@ -163,3 +148,73 @@ def eval_rmse_func( index, "airfoil", ) + + +def evaluate(cfg: DictConfig): + # set airfoil model + model = ppsci.arch.AMGNet(**cfg.MODEL) + + # set validator + eval_dataloader_cfg = { + "dataset": { + "name": "MeshAirfoilDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_dir": cfg.EVAL_DATA_DIR, + "mesh_graph_path": cfg.EVAL_MESH_GRAPH_PATH, + }, + "batch_size": cfg.EVAL.batch_size, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": False, + }, + } + rmse_validator = ppsci.validate.SupervisedValidator( + eval_dataloader_cfg, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + output_expr={"pred": lambda out: out["pred"].unsqueeze(0)}, + metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, + name="RMSE_validator", + ) + validator = {rmse_validator.name: rmse_validator} + + solver = ppsci.solver.Solver( + model, + output_dir=cfg.output_dir, + log_freq=cfg.log_freq, + seed=cfg.seed, + validator=validator, + pretrained_model_path=cfg.EVAL.pretrained_model_path, + eval_with_no_grad=cfg.EVAL.eval_with_no_grad, + ) + # evaluate model + solver.eval() + + # visualize prediction + with solver.no_grad_context_manager(True): + for index, (input_, label, _) in enumerate(rmse_validator.data_loader): + truefield = label["label"].y + prefield = model(input_) + utils.log_images( + input_["input"].pos, + prefield["pred"], + truefield, + rmse_validator.data_loader.dataset.elems_list, + index, + "airfoil", + ) + + +@hydra.main(version_base=None, config_path="./conf", config_name="amgnet_airfoil.yaml") +def main(cfg: DictConfig): + if cfg.mode == "train": + train(cfg) + elif cfg.mode == "eval": + evaluate(cfg) + else: + raise ValueError(f"cfg.mode should in ['train', 'eval'], but got '{cfg.mode}'") + + +if __name__ == "__main__": + main() diff --git a/examples/amgnet/amgnet_cylinder.py b/examples/amgnet/amgnet_cylinder.py index 10e393833..366226453 100644 --- a/examples/amgnet/amgnet_cylinder.py +++ b/examples/amgnet/amgnet_cylinder.py @@ -14,15 +14,17 @@ from __future__ import annotations +from os import path as osp from typing import TYPE_CHECKING from typing import Dict from typing import List +import hydra import utils +from omegaconf import DictConfig from paddle.nn import functional as F import ppsci -from ppsci.utils import config from ppsci.utils import logger if TYPE_CHECKING: @@ -31,7 +33,9 @@ def train_mse_func( - output_dict: Dict[str, "paddle.Tensor"], label_dict: Dict[str, "pgl.Graph"], *args + output_dict: Dict[str, "paddle.Tensor"], + label_dict: Dict[str, "pgl.Graph"], + *args, ) -> paddle.Tensor: return F.mse_loss(output_dict["pred"], label_dict["label"].y) @@ -48,39 +52,25 @@ def eval_rmse_func( return {"RMSE": (sum(mse_losses) / len(mse_losses)) ** 0.5} -if __name__ == "__main__": - args = config.parse_args() +def train(cfg: DictConfig): # set random seed for reproducibility - ppsci.utils.misc.set_random_seed(42) - # set output directory - OUTPUT_DIR = "./output_AMGNet_cylinder" if not args.output_dir else args.output_dir + ppsci.utils.misc.set_random_seed(cfg.seed) # initialize logger - logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") + logger.init_logger("ppsci", osp.join(cfg.output_dir, "train.log"), "info") # set cylinder model - model = ppsci.arch.AMGNet( - input_keys=("input",), - output_keys=("pred",), - input_dim=4, - output_dim=3, - latent_dim=128, - num_layers=2, - message_passing_aggregator="sum", - message_passing_steps=6, - speed="norm", - ) + model = ppsci.arch.AMGNet(**cfg.MODEL) # set dataloader config - ITERS_PER_EPOCH = 42 train_dataloader_cfg = { "dataset": { "name": "MeshCylinderDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_dir": "./data/cylinderdata/train", - "mesh_graph_path": "./data/cylinderdata/cylinder.su2", + "data_dir": cfg.TRAIN_DATA_DIR, + "mesh_graph_path": cfg.TRAIN_MESH_GRAPH_PATH, }, - "batch_size": 4, + "batch_size": cfg.TRAIN.batch_size, "sampler": { "name": "BatchSampler", "drop_last": False, @@ -99,11 +89,8 @@ def eval_rmse_func( # wrap constraints together constraint = {sup_constraint.name: sup_constraint} - # set training hyper-parameters - EPOCHS = 500 if not args.epochs else args.epochs - # set optimizer - optimizer = ppsci.optimizer.Adam(5e-4)(model) + optimizer = ppsci.optimizer.Adam(cfg.TRAIN.learning_rate)(model) # set validator eval_dataloader_cfg = { @@ -111,10 +98,10 @@ def eval_rmse_func( "name": "MeshCylinderDataset", "input_keys": ("input",), "label_keys": ("label",), - "data_dir": "./data/cylinderdata/test", - "mesh_graph_path": "./data/cylinderdata/cylinder.su2", + "data_dir": cfg.EVAL_DATA_DIR, + "mesh_graph_path": cfg.EVAL_MESH_GRAPH_PATH, }, - "batch_size": 1, + "batch_size": cfg.EVAL.batch_size, "sampler": { "name": "BatchSampler", "drop_last": False, @@ -134,16 +121,16 @@ def eval_rmse_func( solver = ppsci.solver.Solver( model, constraint, - OUTPUT_DIR, + cfg.output_dir, optimizer, None, - EPOCHS, - ITERS_PER_EPOCH, - save_freq=50, - eval_during_train=True, - eval_freq=50, + cfg.TRAIN.epochs, + cfg.TRAIN.iters_per_epoch, + save_freq=cfg.TRAIN.save_freq, + eval_during_train=cfg.TRAIN.eval_during_train, + eval_freq=cfg.TRAIN.eval_freq, validator=validator, - eval_with_no_grad=True, + eval_with_no_grad=cfg.EVAL.eval_with_no_grad, ) # train model solver.train() @@ -151,7 +138,7 @@ def eval_rmse_func( # visualize prediction with solver.no_grad_context_manager(True): for index, (input_, label, _) in enumerate(rmse_validator.data_loader): - truefield = label["input"].y + truefield = label["label"].y prefield = model(input_) utils.log_images( input_["input"].pos, @@ -161,3 +148,73 @@ def eval_rmse_func( index, "cylinder", ) + + +def evaluate(cfg: DictConfig): + # set airfoil model + model = ppsci.arch.AMGNet(**cfg.MODEL) + + # set validator + eval_dataloader_cfg = { + "dataset": { + "name": "MeshCylinderDataset", + "input_keys": ("input",), + "label_keys": ("label",), + "data_dir": cfg.EVAL_DATA_DIR, + "mesh_graph_path": cfg.EVAL_MESH_GRAPH_PATH, + }, + "batch_size": cfg.EVAL.batch_size, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": False, + }, + } + rmse_validator = ppsci.validate.SupervisedValidator( + eval_dataloader_cfg, + loss=ppsci.loss.FunctionalLoss(train_mse_func), + output_expr={"pred": lambda out: out["pred"].unsqueeze(0)}, + metric={"RMSE": ppsci.metric.FunctionalMetric(eval_rmse_func)}, + name="RMSE_validator", + ) + validator = {rmse_validator.name: rmse_validator} + + solver = ppsci.solver.Solver( + model, + output_dir=cfg.output_dir, + log_freq=cfg.log_freq, + seed=cfg.seed, + validator=validator, + pretrained_model_path=cfg.EVAL.pretrained_model_path, + eval_with_no_grad=cfg.EVAL.eval_with_no_grad, + ) + # evaluate model + solver.eval() + + # visualize prediction + with solver.no_grad_context_manager(True): + for index, (input_, label, _) in enumerate(rmse_validator.data_loader): + truefield = label["label"].y + prefield = model(input_) + utils.log_images( + input_["input"].pos, + prefield["pred"], + truefield, + rmse_validator.data_loader.dataset.elems_list, + index, + "cylinder", + ) + + +@hydra.main(version_base=None, config_path="./conf", config_name="amgnet_cylinder.yaml") +def main(cfg: DictConfig): + if cfg.mode == "train": + train(cfg) + elif cfg.mode == "eval": + evaluate(cfg) + else: + raise ValueError(f"cfg.mode should in ['train', 'eval'], but got '{cfg.mode}'") + + +if __name__ == "__main__": + main() diff --git a/examples/amgnet/conf/amgnet_airfoil.yaml b/examples/amgnet/conf/amgnet_airfoil.yaml new file mode 100644 index 000000000..4f413a401 --- /dev/null +++ b/examples/amgnet/conf/amgnet_airfoil.yaml @@ -0,0 +1,57 @@ +hydra: + run: + # dynamic output directory according to running time and override name + dir: outputs_amgnet_airfoil/${now:%Y-%m-%d}/${now:%H-%M-%S}/${hydra.job.override_dirname} + job: + name: ${mode} # name of logfile + chdir: false # keep current working direcotry unchaned + sweep: + # output directory for multirun + dir: ${hydra.run.dir} + subdir: ./ + +# general settings +mode: train # running mode: train/eval +seed: 42 +output_dir: ${hydra:run.dir} +log_freq: 20 + +# set working condition + +# set training data path +TRAIN_DATA_DIR: "./data/NACA0012_interpolate/outputs_train" +TRAIN_MESH_GRAPH_PATH: "./data/NACA0012_interpolate/mesh_fine.su2" + +# set evaluate data path +EVAL_DATA_DIR: "./data/NACA0012_interpolate/outputs_test" +EVAL_MESH_GRAPH_PATH: "./data/NACA0012_interpolate/mesh_fine.su2" + +# model settings +MODEL: + input_keys: ["input"] + output_keys: ["pred"] + input_dim: 5 + output_dim: 3 + latent_dim: 128 + num_layers: 2 + message_passing_aggregator: "sum" + message_passing_steps: 6 + speed: "norm" + +# training settings +TRAIN: + epochs: 500 + iters_per_epoch: 42 + save_freq: 50 + eval_during_train: true + eval_freq: 50 + learning_rate: 5.0e-4 + batch_size: 4 + pretrained_model_path: null + checkpoint_path: null + +# evaluation settings +EVAL: + batch_size: 1 + pretrained_model_path: null + eval_with_no_grad: true diff --git a/examples/amgnet/conf/amgnet_cylinder.yaml b/examples/amgnet/conf/amgnet_cylinder.yaml new file mode 100644 index 000000000..6458e4862 --- /dev/null +++ b/examples/amgnet/conf/amgnet_cylinder.yaml @@ -0,0 +1,57 @@ +hydra: + run: + # dynamic output directory according to running time and override name + dir: outputs_amgnet_cylinder/${now:%Y-%m-%d}/${now:%H-%M-%S}/${hydra.job.override_dirname} + job: + name: ${mode} # name of logfile + chdir: false # keep current working direcotry unchaned + sweep: + # output directory for multirun + dir: ${hydra.run.dir} + subdir: ./ + +# general settings +mode: train # running mode: train/eval +seed: 42 +output_dir: ${hydra:run.dir} +log_freq: 20 + +# set working condition + +# set training data path +TRAIN_DATA_DIR: "./data/cylinderdata/train" +TRAIN_MESH_GRAPH_PATH: "./data/cylinderdata/cylinder.su2" + +# set evaluate data path +EVAL_DATA_DIR: "./data/cylinderdata/test" +EVAL_MESH_GRAPH_PATH: "./data/cylinderdata/cylinder.su2" + +# model settings +MODEL: + input_keys: ["input"] + output_keys: ["pred"] + input_dim: 4 + output_dim: 3 + latent_dim: 128 + num_layers: 2 + message_passing_aggregator: "sum" + message_passing_steps: 6 + speed: "norm" + +# training settings +TRAIN: + epochs: 500 + iters_per_epoch: 42 + save_freq: 50 + eval_during_train: true + eval_freq: 50 + learning_rate: 5.0e-4 + batch_size: 4 + pretrained_model_path: null + checkpoint_path: null + +# evaluation settings +EVAL: + batch_size: 1 + pretrained_model_path: null + eval_with_no_grad: true From 876ef4dad58d1ee62768ad510667274c0af07d70 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 18 Oct 2023 05:38:15 +0000 Subject: [PATCH 26/27] add example for AMGNet --- ppsci/arch/amgnet.py | 8 ++++++-- ppsci/data/process/batch_transform/__init__.py | 2 +- ppsci/solver/eval.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ppsci/arch/amgnet.py b/ppsci/arch/amgnet.py index 5a3eb45cd..a0bf534db 100644 --- a/ppsci/arch/amgnet.py +++ b/ppsci/arch/amgnet.py @@ -565,8 +565,8 @@ class AMGNet(nn.Layer): Code reference: https://github.com/baoshiaijhin/amgnet Args: - input_keys (Tuple[str, ...]): Name of input keys, such as ("x", "y", "z"). - output_keys (Tuple[str, ...]): Name of output keys, such as ("u", "v", "w"). + input_keys (Tuple[str, ...]): Name of input keys, such as ("input", ). + output_keys (Tuple[str, ...]): Name of output keys, such as ("pred", ). input_dim (int): Number of input dimension. output_dim (int): Number of output dimension. latent_dim (int): Number of hidden(feature) dimension. @@ -576,6 +576,10 @@ class AMGNet(nn.Layer): message_passing_steps (int): Message passing steps in graph. speed (str): Whether use vanilla method or fast method for graph_connectivity computation. + + Examples: + >>> import ppsci + >>> model = ppsci.arch.AMGNet(("input", ), ("pred", ), 5, 3, 64, 2) """ def __init__( diff --git a/ppsci/data/process/batch_transform/__init__.py b/ppsci/data/process/batch_transform/__init__.py index 2f50850d2..5eceb2bb5 100644 --- a/ppsci/data/process/batch_transform/__init__.py +++ b/ppsci/data/process/batch_transform/__init__.py @@ -76,7 +76,7 @@ def default_collate_fn(batch: List[Any]) -> Any: return graph raise TypeError( - "batch data can only contains: Tensor, numpy.ndarray, " + "batch data can only contains: paddle.Tensor, numpy.ndarray, " f"dict, list, number, None, pgl.Graph, but got {type(sample)}" ) diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index fb4a570e6..09a97bba3 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -63,7 +63,7 @@ def _get_datset_length( def _eval_by_dataset( solver: "solver.Solver", epoch_id: int, log_freq: int ) -> Tuple[float, Dict[str, Dict[str, float]]]: - """Evaluate with computing metric on total samples(default progress). + """Evaluate with computing metric on total samples(default process). Args: solver (solver.Solver): Main Solver. From 9c04c5b47338a2b75fc7fe90d84c2a94a8829701 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Thu, 19 Oct 2023 13:03:59 +0000 Subject: [PATCH 27/27] fix doc --- docs/zh/examples/amgnet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh/examples/amgnet.md b/docs/zh/examples/amgnet.md index 2f2843e71..9975e4d1c 100644 --- a/docs/zh/examples/amgnet.md +++ b/docs/zh/examples/amgnet.md @@ -73,7 +73,7 @@ - AMGNET 通过 RS 算法(Olson and Schroder, 2018)进行了图的粗化,仅使用少量节点即可进行预测,进一步提高了预测速度。 -下图为该方法的网络结构图。该模型的基本原理就是将网格结构转化为图结构,然后通过网格中节点的物理信息,位置信息以及节点类型对图中的节点和边进行编码。接着对得到的图神经网络使用基于代数多重网格算法(RS)的粗化层进行粗化,将所有节点分类为粗节点集和细节点集,其中粗节点集是细节点集的子集。粗图的节点集合就是粗节点集,于是完成了图的粗化,缩小了图的规模。粗化完成后通过设计的图神经网络信息传递块(GN)来总结和提取图的特征。之后图恢复层采用反向操作,使用空间插值法(Qi et al.,2017)对图进行上采样。例如要对节点 $i$ 插值,则在粗图中找到距离节点 $i$ 最近的 $k$ 个节点,然后通过公式计算得到节点 $i$ 的特征。最后,通过解码器得到每个节点的速度与压力信息。 +下图为该方法的网络结构图。该模型的基本原理就是将网格结构转化为图结构,然后通过网格中节点的物理信息、位置信息以及节点类型对图中的节点和边进行编码。接着对得到的图神经网络使用基于代数多重网格算法(RS)的粗化层进行粗化,将所有节点分类为粗节点集和细节点集,其中粗节点集是细节点集的子集。粗图的节点集合就是粗节点集,于是完成了图的粗化,缩小了图的规模。粗化完成后通过设计的图神经网络信息传递块(GN)来总结和提取图的特征。之后图恢复层采用反向操作,使用空间插值法(Qi et al.,2017)对图进行上采样。例如要对节点 $i$ 插值,则在粗图中找到距离节点 $i$ 最近的 $k$ 个节点,然后通过公式计算得到节点 $i$ 的特征。最后,通过解码器得到每个节点的速度与压力信息。 ![AMGNet_overview](https://paddle-org.bj.bcebos.com/paddlescience/docs/AMGNet/amgnet.png) @@ -219,7 +219,7 @@ unzip data.zip ### 3.6 评估器构建 -在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 `ppsci.validate.SupervisedValidator` 构建评估器,构建过程与 [约束构建](#34) 类似,只需把数据目录改为测试集的目录,并在配置文件中设置 `EVAL.batch_size=1` 即可。 +在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 `ppsci.validate.SupervisedValidator` 构建评估器,构建过程与 [约束构建](#33) 类似,只需把数据目录改为测试集的目录,并在配置文件中设置 `EVAL.batch_size=1` 即可。 === "airfoil"