From 79a097e9f469348a0369466f8b478b7cec4272dc Mon Sep 17 00:00:00 2001 From: hww <1> Date: Wed, 12 Nov 2025 05:36:29 +0000 Subject: [PATCH] rerun fon1d --- .../data_driven/burgers/fno1d/FNO1D.ipynb | 600 ++++++++++++++++++ .../data_driven/burgers/fno1d/FNO1D_CN.ipynb | 598 +++++++++++++++++ .../data_driven/burgers/fno1d/README.MD | 98 +++ .../data_driven/burgers/fno1d/README_CN.md | 97 +++ .../data_driven/burgers/fno1d/__init__.py | 20 + .../burgers/fno1d/configs/fno1d.yaml | 27 + .../burgers/fno1d/images/FNO-2.png | Bin 0 -> 19629 bytes .../data_driven/burgers/fno1d/images/FNO.png | Bin 0 -> 45253 bytes .../burgers/fno1d/images/resolution_test.jpg | Bin 0 -> 22102 bytes .../burgers/fno1d/images/result.jpg | Bin 0 -> 28848 bytes .../data_driven/burgers/fno1d/src/__init__.py | 20 + .../data_driven/burgers/fno1d/src/dataset.py | 94 +++ .../data_driven/burgers/fno1d/train.py | 154 +++++ MindFlow/data/__init__.py | 30 + MindFlow/data/boundary.py | 174 +++++ MindFlow/data/data_base.py | 145 +++++ MindFlow/data/dataset.py | 340 ++++++++++ MindFlow/data/equation.py | 142 +++++ MindFlow/data/existed_data.py | 166 +++++ mindscience/models/neural_operator/fno.py | 39 +- 20 files changed, 2727 insertions(+), 17 deletions(-) create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/FNO1D.ipynb create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/FNO1D_CN.ipynb create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/README.MD create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/README_CN.md create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/__init__.py create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/images/FNO-2.png create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/images/FNO.png create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/images/resolution_test.jpg create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/images/result.jpg create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/src/__init__.py create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/src/dataset.py create mode 100644 MindFlow/applications/data_driven/burgers/fno1d/train.py create mode 100644 MindFlow/data/__init__.py create mode 100644 MindFlow/data/boundary.py create mode 100644 MindFlow/data/data_base.py create mode 100644 MindFlow/data/dataset.py create mode 100644 MindFlow/data/equation.py create mode 100644 MindFlow/data/existed_data.py diff --git a/MindFlow/applications/data_driven/burgers/fno1d/FNO1D.ipynb b/MindFlow/applications/data_driven/burgers/fno1d/FNO1D.ipynb new file mode 100644 index 000000000..95aa9d383 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/FNO1D.ipynb @@ -0,0 +1,600 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Solve Burgers' equation based on Fourier Neural Operator\n", + "\n", + "[![DownloadNotebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook_en.png)](https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/master/mindflow/en/data_driven/mindspore_burgers_FNO1D.ipynb) [![DownloadCode](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code_en.png)](https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/master/mindflow/en/data_driven/mindspore_burgers_FNO1D.py) [![ViewSource](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source_en.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindflow/docs/source_en/data_driven/burgers_FNO1D.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Overview\n", + "\n", + "Computational fluid dynamics is one of the most important techniques in the field of fluid mechanics in the 21st century. The flow analysis, prediction and control can be realized by solving the governing equations of fluid mechanics by numerical method. Traditional finite element method (FEM) and finite difference method (FDM) are inefficient because of the complex simulation process (physical modeling, meshing, numerical discretization, iterative solution, etc.) and high computing costs. Therefore, it is necessary to improve the efficiency of fluid simulation with AI.\n", + "\n", + "Machine learning methods provide a new paradigm for scientific computing by providing a fast solver similar to traditional methods. Classical neural networks learn mappings between finite dimensional spaces and can only learn solutions related to a specific discretization. Different from traditional neural networks, Fourier Neural Operator (FNO) is a new deep learning architecture that can learn mappings between infinite-dimensional function spaces. It directly learns mappings from arbitrary function parameters to solutions to solve a class of partial differential equations. Therefore, it has a stronger generalization capability. More information can be found in the paper, [Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/abs/2010.08895).\n", + "\n", + "This tutorial describes how to solve the 1-d Burgers' equation using Fourier neural operator." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Burgers' equation\n", + "\n", + "The 1-d Burgers’ equation is a non-linear PDE with various applications including modeling the one\n", + "dimensional flow of a viscous fluid. It takes the form\n", + "\n", + "$$\n", + "\\partial_t u(x, t)+\\partial_x (u^2(x, t)/2)=\\nu \\partial_{xx} u(x, t), \\quad x \\in(0,1), t \\in(0, 1]\n", + "$$\n", + "\n", + "$$\n", + "u(x, 0)=u_0(x), \\quad x \\in(0,1)\n", + "$$\n", + "\n", + "where $u$ is the velocity field, $u_0$ is the initial condition and $\\nu$ is the viscosity coefficient.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem Description\n", + "\n", + "We aim to learn the operator mapping the initial condition to the solution at time one:\n", + "\n", + "$$\n", + "u_0 \\mapsto u(\\cdot, 1)\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Technology Path\n", + "\n", + "MindFlow solves the problem as follows:\n", + "\n", + "1. Training Dataset Construction.\n", + "2. Model Construction.\n", + "3. Optimizer and Loss Function.\n", + "4. Model Training." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fourier Neural Operator\n", + "\n", + "The Fourier Neural Operator consists of the Lifting Layer, Fourier Layers, and the Decoding Layer.\n", + "\n", + "![Fourier Neural Operator model structure](images/FNO.png)\n", + "\n", + "Fourier layers: Start from input V. On top: apply the Fourier transform $\\mathcal{F}$; a linear transform R on the lower Fourier modes and filters out the higher modes; then apply the inverse Fourier transform $\\mathcal{F}^{-1}$. On the bottom: apply a local linear transform W. Finally, the Fourier Layer output vector is obtained through the activation function.\n", + "\n", + "![Fourier Layer structure](images/FNO-2.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "#add search path\n", + "import sys\n", + "import os\n", + "project_root = os.path.abspath(\"../../../../..\")\n", + "sys.path.append(project_root)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for type is zero.\n", + " setattr(self, word, getattr(machar, word).flat[0])\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for type is zero.\n", + " return self._float_to_str(self.smallest_subnormal)\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for type is zero.\n", + " setattr(self, word, getattr(machar, word).flat[0])\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for type is zero.\n", + " return self._float_to_str(self.smallest_subnormal)\n" + ] + } + ], + "source": [ + "import sys\n", + "sys.path.append(\"/home/mindspore/work/mindscience_newest\")\n", + "\n", + "import os\n", + "import time\n", + "import numpy as np\n", + "\n", + "from mindspore.amp import DynamicLossScaler, auto_mixed_precision, all_finite\n", + "from mindspore import context, nn, Tensor, set_seed, ops, data_sink, jit, save_checkpoint\n", + "from mindspore import dtype as mstype\n", + "from mindscience import FNO1D, RelativeRMSELoss, load_yaml_config, get_warmup_cosine_annealing_lr\n", + "from mindscience.pde import UnsteadyFlowWithLoss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following `src` pacakage can be downloaded in [applications/data_driven/burgers/fno1d/src](https://gitee.com/mindspore/mindscience/tree/master/MindFlow/applications/data_driven/burgers/fno1d/src)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from src import create_training_dataset\n", + "\n", + "set_seed(0)\n", + "np.random.seed(0)\n", + "\n", + "context.set_context(mode=context.GRAPH_MODE, device_target=\"Ascend\", device_id=0,max_device_memory=\"32GB\")\n", + "use_ascend = context.get_context(attr_key='device_target') == \"Ascend\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get parameters of model, data and optimizer from `config`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "config = load_yaml_config('./configs/fno1d.yaml')\n", + "data_params = config[\"data\"]\n", + "model_params = config[\"model\"]\n", + "optimizer_params = config[\"optimizer\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Training Dataset Construction\n", + "\n", + "Download the training and test dataset: [data_driven/burgers/dataset](https://download.mindspore.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/dataset/) .\n", + "\n", + "In this case, training datasets and test datasets are generated according to Zongyi Li's dataset in [Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/pdf/2010.08895.pdf) . The settings are as follows:\n", + "\n", + "the initial condition $u_0(x)$ is generated according to periodic boundary conditions:\n", + "\n", + "$$\n", + "u_0 \\sim \\mu, \\mu=\\mathcal{N}\\left(0,625(-\\Delta+25 I)^{-2}\\right)\n", + "$$\n", + "\n", + "We set the viscosity to $\\nu=0.1$ and solve the equation using a split step method where the heat equation part is solved exactly in Fourier space then the non-linear part is advanced, again in Fourier space, using a very fine forward Euler method. The number of samples in the training set is 1000, and the number of samples in the test set is 200.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data preparation finished\n", + "input_path: (1000, 1024, 1)\n", + "label_path: (1000, 1024)\n" + ] + } + ], + "source": [ + "# create training dataset\n", + "train_dataset = create_training_dataset(data_params, model_params, shuffle=True)\n", + "\n", + "# create test dataset\n", + "test_input, test_label = np.load(os.path.join(data_params[\"root_dir\"], \"test/inputs.npy\")), \\\n", + " np.load(os.path.join(data_params[\"root_dir\"], \"test/label.npy\"))\n", + "test_input = Tensor(np.expand_dims(test_input, -2), mstype.float32)\n", + "test_label = Tensor(np.expand_dims(test_label, -2), mstype.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Model Construction\n", + "\n", + "The network is composed of 1 lifting layer, multiple Fourier layers and 1 decoding layer:\n", + "\n", + "- The Lifting layer corresponds to the `FNO1D.fc0` in the case, and maps the output data $x$ to the high dimension;\n", + "\n", + "- Multi-layer Fourier Layer corresponds to the `FNO1D.fno_seq` in the case. Discrete Fourier transform is used to realize the conversion between time domain and frequency domain;\n", + "\n", + "- The Decoding layer corresponds to `FNO1D.fc1` and `FNO1D.fc2` in the case to obtain the final predictive value.\n", + "\n", + "The initialization of the model based on the network above, parameters can be modified in [configuration file](https://gitee.com/mindspore/mindscience/blob/master/MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name:FNO1D_in_channels:1_out_channels:1_modes:16_resolutions:1024_hidden_channels:64_depths:4\n" + ] + } + ], + "source": [ + "model = FNO1D(in_channels=model_params[\"in_channels\"],\n", + " out_channels=model_params[\"out_channels\"],\n", + " n_modes=model_params[\"modes\"],\n", + " resolutions=model_params[\"resolutions\"],\n", + " hidden_channels=model_params[\"hidden_channels\"],\n", + " n_layers=model_params[\"depths\"],\n", + " projection_channels=4*model_params[\"hidden_channels\"],\n", + " )\n", + "\n", + "model_params_list = []\n", + "for k, v in model_params.items():\n", + " model_params_list.append(f\"{k}:{v}\")\n", + "model_name = \"_\".join(model_params_list)\n", + "print(model_name)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Optimizer and Loss Function" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "steps_per_epoch = train_dataset.get_dataset_size()\n", + "lr = get_warmup_cosine_annealing_lr(lr_init=optimizer_params[\"learning_rate\"],\n", + " last_epoch=optimizer_params[\"epochs\"],\n", + " steps_per_epoch=steps_per_epoch,\n", + " warmup_epochs=1)\n", + "optimizer = nn.Adam(model.trainable_params(), learning_rate=Tensor(lr))\n", + "\n", + "if use_ascend:\n", + " from mindspore.amp import DynamicLossScaler, auto_mixed_precision, all_finite\n", + " loss_scaler = DynamicLossScaler(1024, 2, 100)\n", + " auto_mixed_precision(model, 'O3')\n", + "else:\n", + " loss_scaler = None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Model Training\n", + "\n", + "With **MindSpore version >= 2.0.0**, we can use the functional programming for training neural networks. `MindFlow` provide a training interface for unsteady problems `UnsteadyFlowWithLoss` for model training and evaluation." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "./summary/name:FNO1D_in_channels:1_out_channels:1_modes:16_resolutions:1024_hidden_channels:64_depths:4\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[ERROR] CORE(31531,ffffb57ca020,python3.9):2025-11-15-09:43:56.448.210 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31531/2341691899.py]\n", + "[ERROR] CORE(31531,ffffb57ca020,python3.9):2025-11-15-09:43:56.450.846 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31531/2341691899.py]\n", + "[ERROR] CORE(31531,ffffb57ca020,python3.9):2025-11-15-09:43:56.450.902 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31531/2341691899.py]\n", + "[ERROR] CORE(31531,ffffb57ca020,python3.9):2025-11-15-09:43:56.451.086 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31531/2341691899.py]\n", + "[ERROR] CORE(31531,ffffb57ca020,python3.9):2025-11-15-09:43:56.451.135 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31531/2341691899.py]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "..epoch: 1 train loss: 0.23465186 epoch time: 19.78s step time: 0.1582s\n", + "epoch: 2 train loss: 0.28419363 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 3 train loss: 0.31122661 epoch time: 2.07s step time: 0.0165s\n", + "epoch: 4 train loss: 0.28644967 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 5 train loss: 0.20785393 epoch time: 2.07s step time: 0.0165s\n", + "epoch: 6 train loss: 0.19288626 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 7 train loss: 0.23312779 epoch time: 2.08s step time: 0.0167s\n", + "epoch: 8 train loss: 0.18624401 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 9 train loss: 0.10345152 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 10 train loss: 0.18798406 epoch time: 2.18s step time: 0.0174s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.021879202\n", + "=================================End Evaluation=================================\n", + "epoch: 11 train loss: 0.17524227 epoch time: 2.25s step time: 0.0180s\n", + "epoch: 12 train loss: 0.16727453 epoch time: 2.26s step time: 0.0181s\n", + "epoch: 13 train loss: 0.10675637 epoch time: 2.22s step time: 0.0178s\n", + "epoch: 14 train loss: 0.13700883 epoch time: 2.16s step time: 0.0173s\n", + "epoch: 15 train loss: 0.14147925 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 16 train loss: 0.10691701 epoch time: 2.08s step time: 0.0167s\n", + "epoch: 17 train loss: 0.16218747 epoch time: 2.04s step time: 0.0163s\n", + "epoch: 18 train loss: 0.20996606 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 19 train loss: 0.15155949 epoch time: 2.02s step time: 0.0162s\n", + "epoch: 20 train loss: 0.18981066 epoch time: 2.04s step time: 0.0163s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.019013295\n", + "=================================End Evaluation=================================\n", + "epoch: 21 train loss: 0.07383593 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 22 train loss: 0.11531584 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 23 train loss: 0.17910039 epoch time: 2.08s step time: 0.0167s\n", + "epoch: 24 train loss: 0.09044659 epoch time: 2.15s step time: 0.0172s\n", + "epoch: 25 train loss: 0.15973650 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 26 train loss: 0.08857839 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 27 train loss: 0.06426008 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 28 train loss: 0.09480067 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 29 train loss: 0.22773507 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 30 train loss: 0.12277951 epoch time: 2.08s step time: 0.0166s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.013496651\n", + "=================================End Evaluation=================================\n", + "epoch: 31 train loss: 0.09083001 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 32 train loss: 0.10202033 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 33 train loss: 0.12928744 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 34 train loss: 0.06271134 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 35 train loss: 0.07041215 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 36 train loss: 0.12557518 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 37 train loss: 0.10148985 epoch time: 2.04s step time: 0.0163s\n", + "epoch: 38 train loss: 0.08175293 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 39 train loss: 0.11296615 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 40 train loss: 0.08916582 epoch time: 2.01s step time: 0.0161s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.014983387\n", + "=================================End Evaluation=================================\n", + "epoch: 41 train loss: 0.06424745 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 42 train loss: 0.07220607 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 43 train loss: 0.09584412 epoch time: 2.06s step time: 0.0164s\n", + "epoch: 44 train loss: 0.08036990 epoch time: 2.08s step time: 0.0167s\n", + "epoch: 45 train loss: 0.05654721 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 46 train loss: 0.04830655 epoch time: 2.04s step time: 0.0163s\n", + "epoch: 47 train loss: 0.10450873 epoch time: 2.13s step time: 0.0171s\n", + "epoch: 48 train loss: 0.06309631 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 49 train loss: 0.05044140 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 50 train loss: 0.08654185 epoch time: 2.11s step time: 0.0169s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.009487706\n", + "=================================End Evaluation=================================\n", + "epoch: 51 train loss: 0.04118668 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 52 train loss: 0.08913346 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 53 train loss: 0.04456577 epoch time: 2.08s step time: 0.0167s\n", + "epoch: 54 train loss: 0.05342043 epoch time: 2.09s step time: 0.0168s\n", + "epoch: 55 train loss: 0.06059274 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 56 train loss: 0.05875401 epoch time: 2.09s step time: 0.0168s\n", + "epoch: 57 train loss: 0.05666578 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 58 train loss: 0.04027025 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 59 train loss: 0.07292267 epoch time: 2.14s step time: 0.0171s\n", + "epoch: 60 train loss: 0.04893249 epoch time: 2.06s step time: 0.0165s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.006333221\n", + "=================================End Evaluation=================================\n", + "epoch: 61 train loss: 0.03615757 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 62 train loss: 0.06545272 epoch time: 2.16s step time: 0.0173s\n", + "epoch: 63 train loss: 0.03222132 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 64 train loss: 0.04665821 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 65 train loss: 0.04956944 epoch time: 2.17s step time: 0.0173s\n", + "epoch: 66 train loss: 0.03793489 epoch time: 2.12s step time: 0.0169s\n", + "epoch: 67 train loss: 0.04018638 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 68 train loss: 0.02908281 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 69 train loss: 0.04153252 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 70 train loss: 0.03012481 epoch time: 2.09s step time: 0.0167s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0041090883\n", + "=================================End Evaluation=================================\n", + "epoch: 71 train loss: 0.02333428 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 72 train loss: 0.02513113 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 73 train loss: 0.02411744 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 74 train loss: 0.02313637 epoch time: 2.02s step time: 0.0162s\n", + "epoch: 75 train loss: 0.03571169 epoch time: 1.99s step time: 0.0159s\n", + "epoch: 76 train loss: 0.01535346 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 77 train loss: 0.02628051 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 78 train loss: 0.02273233 epoch time: 2.00s step time: 0.0160s\n", + "epoch: 79 train loss: 0.02078306 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 80 train loss: 0.01021176 epoch time: 2.03s step time: 0.0162s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0017531833\n", + "=================================End Evaluation=================================\n", + "epoch: 81 train loss: 0.01558476 epoch time: 2.01s step time: 0.0161s\n", + "epoch: 82 train loss: 0.01213225 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 83 train loss: 0.00979056 epoch time: 2.06s step time: 0.0165s\n", + "epoch: 84 train loss: 0.01600446 epoch time: 2.13s step time: 0.0171s\n", + "epoch: 85 train loss: 0.00978592 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 86 train loss: 0.01050989 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 87 train loss: 0.01243659 epoch time: 2.02s step time: 0.0161s\n", + "epoch: 88 train loss: 0.00832084 epoch time: 2.04s step time: 0.0163s\n", + "epoch: 89 train loss: 0.01216878 epoch time: 2.03s step time: 0.0163s\n", + "epoch: 90 train loss: 0.00763223 epoch time: 2.02s step time: 0.0162s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0011959259\n", + "=================================End Evaluation=================================\n", + "epoch: 91 train loss: 0.00671979 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 92 train loss: 0.00736447 epoch time: 2.05s step time: 0.0164s\n", + "epoch: 93 train loss: 0.00652055 epoch time: 2.02s step time: 0.0162s\n", + "epoch: 94 train loss: 0.00615171 epoch time: 2.07s step time: 0.0165s\n", + "epoch: 95 train loss: 0.00544963 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 96 train loss: 0.00478581 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 97 train loss: 0.00580032 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 98 train loss: 0.00527284 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 99 train loss: 0.00610725 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 100 train loss: 0.00590835 epoch time: 2.12s step time: 0.0170s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.001089375\n", + "=================================End Evaluation=================================\n" + ] + } + ], + "source": [ + "problem = UnsteadyFlowWithLoss(model, loss_fn=RelativeRMSELoss(), data_format=\"NHWTC\")\n", + "\n", + "summary_dir = os.path.join(config[\"summary\"][\"summary_dir\"], model_name)\n", + "print(summary_dir)\n", + "\n", + "def forward_fn(data, label):\n", + " loss = problem.get_loss(data, label)\n", + " if use_ascend:\n", + " loss = loss_scaler.scale(loss)\n", + " return loss\n", + "\n", + "grad_fn = ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=False)\n", + "\n", + "@jit\n", + "def train_step(data, label):\n", + " loss, grads = grad_fn(data, label)\n", + " if use_ascend:\n", + " loss = loss_scaler.unscale(loss)\n", + " if all_finite(grads):\n", + " grads = loss_scaler.unscale(grads)\n", + " loss = ops.depend(loss, optimizer(grads))\n", + " return loss\n", + "\n", + "sink_process = data_sink(train_step, train_dataset, 1)\n", + "#summary_dir = os.path.join(config[\"summary_dir\"], model_name)\n", + "ckpt_dir = os.path.join(summary_dir, \"ckpt\")\n", + "if not os.path.exists(ckpt_dir):\n", + " os.makedirs(ckpt_dir)\n", + "\n", + "for epoch in range(1, optimizer_params[\"epochs\"] + 1):\n", + " model.set_train()\n", + " local_time_beg = time.time()\n", + " for _ in range(steps_per_epoch):\n", + " cur_loss = sink_process()\n", + " print(\n", + " f\"epoch: {epoch} train loss: {cur_loss.asnumpy():.8f}\"\\\n", + " f\" epoch time: {time.time() - local_time_beg:.2f}s\"\\\n", + " f\" step time: {(time.time() - local_time_beg)/steps_per_epoch:.4f}s\")\n", + "\n", + " if epoch % config['summary']['test_interval'] == 0:\n", + " model.set_train(False)\n", + " print(\"================================Start Evaluation================================\")\n", + " rms_error = problem.get_loss(test_input, test_label)/test_input.shape[0]\n", + " print(f\"mean rms_error: {rms_error}\")\n", + " print(\"=================================End Evaluation=================================\")\n", + " save_checkpoint(model, os.path.join(ckpt_dir, model_params[\"name\"] + '_epoch' + str(epoch)))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.23" + }, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/MindFlow/applications/data_driven/burgers/fno1d/FNO1D_CN.ipynb b/MindFlow/applications/data_driven/burgers/fno1d/FNO1D_CN.ipynb new file mode 100644 index 000000000..ba9ffc5d9 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/FNO1D_CN.ipynb @@ -0,0 +1,598 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# 基于Fourier Neural Operator的Burgers' equation求解\n", + "\n", + "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook.png)](https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/master/mindflow/zh_cn/data_driven/mindspore_burgers_FNO1D.ipynb) [![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code.png)](https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/master/mindflow/zh_cn/data_driven/mindspore_burgers_FNO1D.py) [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindflow/docs/source_zh_cn/data_driven/burgers_FNO1D.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 概述\n", + "\n", + "计算流体力学是21世纪流体力学领域的重要技术之一,其通过使用数值方法在计算机中对流体力学的控制方程进行求解,从而实现流动的分析、预测和控制。传统的有限元法(finite element method,FEM)和有限差分法(finite difference method,FDM)常用于复杂的仿真流程(物理建模、网格划分、数值离散、迭代求解等)和较高的计算成本,往往效率低下。因此,借助AI提升流体仿真效率是十分必要的。\n", + "\n", + "近年来,随着神经网络的迅猛发展,为科学计算提供了新的范式。经典的神经网络是在有限维度的空间进行映射,只能学习与特定离散化相关的解。与经典神经网络不同,傅里叶神经算子(Fourier Neural Operator,FNO)是一种能够学习无限维函数空间映射的新型深度学习架构。该架构可直接学习从任意函数参数到解的映射,用于解决一类偏微分方程的求解问题,具有更强的泛化能力。更多信息可参考[Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/abs/2010.08895)。\n", + "\n", + "本案例教程介绍利用傅里叶神经算子的1-d Burgers方程求解方法。" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 伯格斯方程(Burgers' equation)\n", + "\n", + "一维伯格斯方程(1-d Burgers' equation)是一个非线性偏微分方程,具有广泛应用,包括一维粘性流体流动建模。它的形式如下:\n", + "\n", + "$$\n", + "\\partial_t u(x, t)+\\partial_x (u^2(x, t)/2)=\\nu \\partial_{xx} u(x, t), \\quad x \\in(0,1), t \\in(0, 1]\n", + "$$\n", + "\n", + "$$\n", + "u(x, 0)=u_0(x), \\quad x \\in(0,1)\n", + "$$\n", + "\n", + "其中$u$表示速度场,$u_0$表示初始条件,$\\nu$表示粘度系数。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 问题描述\n", + "\n", + "本案例利用Fourier Neural Operator学习初始状态到下一时刻状态的映射,实现一维Burgers'方程的求解:\n", + "\n", + "$$\n", + "u_0 \\mapsto u(\\cdot, 1)\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 技术路径\n", + "\n", + "MindFlow求解该问题的具体流程如下:\n", + "\n", + "1. 创建数据集。\n", + "2. 构建模型。\n", + "3. 优化器与损失函数。\n", + "4. 模型训练。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fourier Neural Operator\n", + "\n", + "Fourier Neural Operator模型构架如下图所示。图中$w_0(x)$表示初始涡度,通过Lifting Layer实现输入向量的高维映射,然后将映射结果作为Fourier Layer的输入,进行频域信息的非线性变换,最后由Decoding Layer将变换结果映射至最终的预测结果$w_1(x)$。\n", + "\n", + "Lifting Layer、Fourier Layer以及Decoding Layer共同组成了Fourier Neural Operator。\n", + "\n", + "![Fourier Neural Operator模型构架](images/FNO.png)\n", + "\n", + "Fourier Layer网络结构如下图所示。图中V表示输入向量,上框表示向量经过傅里叶变换后,经过线性变换R,过滤高频信息,然后进行傅里叶逆变换;另一分支经过线性变换W,最后通过激活函数,得到Fourier Layer输出向量。\n", + "\n", + "![Fourier Layer网络结构](images/FNO-2.png)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#添加搜索路径\n", + "import sys\n", + "import os\n", + "project_root = os.path.abspath(\"../../../../..\")\n", + "sys.path.append(project_root)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for type is zero.\n", + " setattr(self, word, getattr(machar, word).flat[0])\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for type is zero.\n", + " return self._float_to_str(self.smallest_subnormal)\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:549: UserWarning: The value of the smallest subnormal for type is zero.\n", + " setattr(self, word, getattr(machar, word).flat[0])\n", + "/home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for type is zero.\n", + " return self._float_to_str(self.smallest_subnormal)\n" + ] + } + ], + "source": [ + "import os\n", + "import time\n", + "import numpy as np\n", + "\n", + "from mindspore.amp import DynamicLossScaler, auto_mixed_precision, all_finite\n", + "from mindspore import context, nn, Tensor, set_seed, ops, data_sink, jit, save_checkpoint\n", + "from mindspore import dtype as mstype\n", + "from mindscience import FNO1D, RelativeRMSELoss, load_yaml_config, get_warmup_cosine_annealing_lr\n", + "from mindscience.pde import UnsteadyFlowWithLoss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下述`src`包可以在[applications/data_driven/burgers/fno1d/src](https://gitee.com/mindspore/mindscience/tree/master/MindFlow/applications/data_driven/burgers/fno1d/src)下载。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from src import create_training_dataset\n", + "\n", + "set_seed(0)\n", + "np.random.seed(0)\n", + "\n", + "context.set_context(mode=context.GRAPH_MODE, device_target=\"Ascend\", device_id=0,max_device_memory=\"32GB\")\n", + "use_ascend = context.get_context(attr_key='device_target') == \"Ascend\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从`config`中获得模型、数据、优化器的参数。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "config = load_yaml_config('./configs/fno1d.yaml')\n", + "data_params = config[\"data\"]\n", + "model_params = config[\"model\"]\n", + "optimizer_params = config[\"optimizer\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 创建数据集\n", + "\n", + "下载训练与测试数据集: [data_driven/burgers/fno1d/dataset](https://download.mindspore.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/dataset/)。\n", + "\n", + "本案例根据Zongyi Li在 [Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/pdf/2010.08895.pdf) 一文中对数据集的设置生成训练数据集与测试数据集。具体设置如下:\n", + "基于周期性边界,生成满足如下分布的初始条件$u_0(x)$:\n", + "\n", + "$$\n", + "u_0 \\sim \\mu, \\mu=\\mathcal{N}\\left(0,625(-\\Delta+25 I)^{-2}\\right)\n", + "$$\n", + "\n", + "本案例选取粘度系数$\\nu=0.1$,并使用分步法求解方程,其中热方程部分在傅里叶空间中精确求解,然后使用前向欧拉方法求解非线性部分。训练集样本量为1000个,测试集样本量为200个。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data preparation finished\n", + "input_path: (1000, 1024, 1)\n", + "label_path: (1000, 1024)\n" + ] + } + ], + "source": [ + "# create training dataset\n", + "train_dataset = create_training_dataset(data_params, model_params, shuffle=True)\n", + "\n", + "# create test dataset\n", + "test_input, test_label = np.load(os.path.join(data_params[\"root_dir\"], \"test/inputs.npy\")), \\\n", + " np.load(os.path.join(data_params[\"root_dir\"], \"test/label.npy\"))\n", + "test_input = Tensor(np.expand_dims(test_input, -2), mstype.float32)\n", + "test_label = Tensor(np.expand_dims(test_label, -2), mstype.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 构建模型\n", + "\n", + "网络由1层Lifting layer、1层Decoding layer以及多层Fourier Layer叠加组成:\n", + "\n", + "- Lifting layer对应样例代码中`FNO1D.fc0`,将输出数据$x$映射至高维;\n", + "\n", + "- 多层Fourier Layer的叠加对应样例代码中`FNO1D.fno_seq`,本案例采用离散傅里叶变换实现时域与频域的转换;\n", + "\n", + "- Decoding layer对应代码中`FNO1D.fc1`与`FNO1D.fc2`,获得最终的预测值。\n", + "\n", + "基于上述网络结构,进行模型初始化,其中模型参数可在[配置文件](https://gitee.com/mindspore/mindscience/blob/master/MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml)中修改。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name:FNO1D_in_channels:1_out_channels:1_modes:16_resolutions:1024_hidden_channels:64_depths:4\n" + ] + } + ], + "source": [ + "model = FNO1D(in_channels=model_params[\"in_channels\"],\n", + " out_channels=model_params[\"out_channels\"],\n", + " n_modes=model_params[\"modes\"],\n", + " resolutions=model_params[\"resolutions\"],\n", + " hidden_channels=model_params[\"hidden_channels\"],\n", + " n_layers=model_params[\"depths\"],\n", + " projection_channels=4*model_params[\"hidden_channels\"],\n", + " )\n", + "model_params_list = []\n", + "for k, v in model_params.items():\n", + " model_params_list.append(f\"{k}:{v}\")\n", + "model_name = \"_\".join(model_params_list)\n", + "print(model_name)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 优化器与损失函数\n", + "\n", + "使用相对均方根误差作为网络训练损失函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "steps_per_epoch = train_dataset.get_dataset_size()\n", + "lr = get_warmup_cosine_annealing_lr(lr_init=optimizer_params[\"learning_rate\"],\n", + " last_epoch=optimizer_params[\"epochs\"],\n", + " steps_per_epoch=steps_per_epoch,\n", + " warmup_epochs=1)\n", + "optimizer = nn.Adam(model.trainable_params(), learning_rate=Tensor(lr))\n", + "\n", + "if use_ascend:\n", + " from mindspore.amp import DynamicLossScaler, auto_mixed_precision, all_finite\n", + " loss_scaler = DynamicLossScaler(1024, 2, 100)\n", + " auto_mixed_precision(model, 'O3')\n", + "else:\n", + " loss_scaler = None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 模型训练\n", + "\n", + "使用 **MindSpore version >= 2.0.0**, 我们可以使用函数式编程来训练神经网络。 `MindFlow` 为非稳态问题 `UnsteadyFlowWithLoss` 提供了一个训练接口,用于模型训练和评估." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "./summary/name:FNO1D_in_channels:1_out_channels:1_modes:16_resolutions:1024_hidden_channels:64_depths:4\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[ERROR] CORE(31549,ffffbd982020,python):2025-11-15-09:43:58.445.122 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31549/2341691899.py]\n", + "[ERROR] CORE(31549,ffffbd982020,python):2025-11-15-09:43:58.447.639 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31549/2341691899.py]\n", + "[ERROR] CORE(31549,ffffbd982020,python):2025-11-15-09:43:58.447.695 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31549/2341691899.py]\n", + "[ERROR] CORE(31549,ffffbd982020,python):2025-11-15-09:43:58.447.875 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31549/2341691899.py]\n", + "[ERROR] CORE(31549,ffffbd982020,python):2025-11-15-09:43:58.447.922 [mindspore/core/utils/file_utils.cc:253] GetRealPath] Get realpath failed, path[/tmp/ipykernel_31549/2341691899.py]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "..epoch: 1 train loss: 0.23476513 epoch time: 19.20s step time: 0.1536s\n", + "epoch: 2 train loss: 0.25118420 epoch time: 2.14s step time: 0.0171s\n", + "epoch: 3 train loss: 0.21490771 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 4 train loss: 0.22294529 epoch time: 2.28s step time: 0.0183s\n", + "epoch: 5 train loss: 0.15385811 epoch time: 2.19s step time: 0.0176s\n", + "epoch: 6 train loss: 0.11222684 epoch time: 2.16s step time: 0.0173s\n", + "epoch: 7 train loss: 0.26857409 epoch time: 2.21s step time: 0.0177s\n", + "epoch: 8 train loss: 0.23994932 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 9 train loss: 0.12968877 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 10 train loss: 0.14602631 epoch time: 2.30s step time: 0.0184s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.020715814\n", + "=================================End Evaluation=================================\n", + "epoch: 11 train loss: 0.12445749 epoch time: 2.21s step time: 0.0176s\n", + "epoch: 12 train loss: 0.17673939 epoch time: 2.05s step time: 0.0164s\n", + "epoch: 13 train loss: 0.09387001 epoch time: 2.03s step time: 0.0162s\n", + "epoch: 14 train loss: 0.20918289 epoch time: 2.07s step time: 0.0165s\n", + "epoch: 15 train loss: 0.20073509 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 16 train loss: 0.19236282 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 17 train loss: 0.12148518 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 18 train loss: 0.13007648 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 19 train loss: 0.14730214 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 20 train loss: 0.11193341 epoch time: 2.15s step time: 0.0172s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.009921882\n", + "=================================End Evaluation=================================\n", + "epoch: 21 train loss: 0.13001360 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 22 train loss: 0.13757694 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 23 train loss: 0.12695193 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 24 train loss: 0.13907336 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 25 train loss: 0.10276581 epoch time: 2.18s step time: 0.0175s\n", + "epoch: 26 train loss: 0.11725406 epoch time: 2.27s step time: 0.0182s\n", + "epoch: 27 train loss: 0.09574451 epoch time: 2.25s step time: 0.0180s\n", + "epoch: 28 train loss: 0.15954311 epoch time: 2.25s step time: 0.0180s\n", + "epoch: 29 train loss: 0.08458930 epoch time: 2.25s step time: 0.0180s\n", + "epoch: 30 train loss: 0.16492826 epoch time: 2.18s step time: 0.0174s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.015584474\n", + "=================================End Evaluation=================================\n", + "epoch: 31 train loss: 0.06738295 epoch time: 2.23s step time: 0.0178s\n", + "epoch: 32 train loss: 0.11054665 epoch time: 2.19s step time: 0.0175s\n", + "epoch: 33 train loss: 0.09714314 epoch time: 2.34s step time: 0.0187s\n", + "epoch: 34 train loss: 0.05955748 epoch time: 2.40s step time: 0.0192s\n", + "epoch: 35 train loss: 0.12066956 epoch time: 2.28s step time: 0.0183s\n", + "epoch: 36 train loss: 0.10293169 epoch time: 2.36s step time: 0.0189s\n", + "epoch: 37 train loss: 0.13184263 epoch time: 2.31s step time: 0.0185s\n", + "epoch: 38 train loss: 0.09791068 epoch time: 2.16s step time: 0.0173s\n", + "epoch: 39 train loss: 0.09240112 epoch time: 2.23s step time: 0.0178s\n", + "epoch: 40 train loss: 0.12446348 epoch time: 2.25s step time: 0.0180s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.012187392\n", + "=================================End Evaluation=================================\n", + "epoch: 41 train loss: 0.06516904 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 42 train loss: 0.13848309 epoch time: 2.23s step time: 0.0178s\n", + "epoch: 43 train loss: 0.10850596 epoch time: 2.42s step time: 0.0194s\n", + "epoch: 44 train loss: 0.10974278 epoch time: 2.32s step time: 0.0186s\n", + "epoch: 45 train loss: 0.05560862 epoch time: 2.22s step time: 0.0178s\n", + "epoch: 46 train loss: 0.06424462 epoch time: 2.18s step time: 0.0174s\n", + "epoch: 47 train loss: 0.07734193 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 48 train loss: 0.05293225 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 49 train loss: 0.11110708 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 50 train loss: 0.07038455 epoch time: 2.20s step time: 0.0176s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.008971626\n", + "=================================End Evaluation=================================\n", + "epoch: 51 train loss: 0.12441821 epoch time: 2.19s step time: 0.0175s\n", + "epoch: 52 train loss: 0.06804045 epoch time: 2.18s step time: 0.0174s\n", + "epoch: 53 train loss: 0.05506612 epoch time: 2.49s step time: 0.0199s\n", + "epoch: 54 train loss: 0.05211698 epoch time: 2.21s step time: 0.0177s\n", + "epoch: 55 train loss: 0.05002218 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 56 train loss: 0.07349440 epoch time: 2.19s step time: 0.0175s\n", + "epoch: 57 train loss: 0.07091989 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 58 train loss: 0.05169376 epoch time: 2.28s step time: 0.0182s\n", + "epoch: 59 train loss: 0.05403522 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 60 train loss: 0.04980489 epoch time: 2.14s step time: 0.0171s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.006142739\n", + "=================================End Evaluation=================================\n", + "epoch: 61 train loss: 0.05079499 epoch time: 2.12s step time: 0.0169s\n", + "epoch: 62 train loss: 0.06567945 epoch time: 2.17s step time: 0.0173s\n", + "epoch: 63 train loss: 0.05983985 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 64 train loss: 0.03497960 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 65 train loss: 0.04502716 epoch time: 2.18s step time: 0.0174s\n", + "epoch: 66 train loss: 0.04706389 epoch time: 2.15s step time: 0.0172s\n", + "epoch: 67 train loss: 0.04403052 epoch time: 2.20s step time: 0.0176s\n", + "epoch: 68 train loss: 0.02547505 epoch time: 2.16s step time: 0.0173s\n", + "epoch: 69 train loss: 0.03348733 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 70 train loss: 0.02987053 epoch time: 2.16s step time: 0.0173s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0036494005\n", + "=================================End Evaluation=================================\n", + "epoch: 71 train loss: 0.02783433 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 72 train loss: 0.02211184 epoch time: 2.12s step time: 0.0169s\n", + "epoch: 73 train loss: 0.01878185 epoch time: 2.10s step time: 0.0168s\n", + "epoch: 74 train loss: 0.01822488 epoch time: 2.14s step time: 0.0172s\n", + "epoch: 75 train loss: 0.02666300 epoch time: 2.15s step time: 0.0172s\n", + "epoch: 76 train loss: 0.02968729 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 77 train loss: 0.01373592 epoch time: 2.13s step time: 0.0171s\n", + "epoch: 78 train loss: 0.01685985 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 79 train loss: 0.01720855 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 80 train loss: 0.00984283 epoch time: 2.18s step time: 0.0174s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0022107356\n", + "=================================End Evaluation=================================\n", + "epoch: 81 train loss: 0.01453377 epoch time: 2.13s step time: 0.0170s\n", + "epoch: 82 train loss: 0.01104419 epoch time: 2.22s step time: 0.0177s\n", + "epoch: 83 train loss: 0.00970412 epoch time: 2.17s step time: 0.0174s\n", + "epoch: 84 train loss: 0.00941654 epoch time: 2.12s step time: 0.0169s\n", + "epoch: 85 train loss: 0.00973161 epoch time: 2.17s step time: 0.0173s\n", + "epoch: 86 train loss: 0.01063135 epoch time: 2.22s step time: 0.0178s\n", + "epoch: 87 train loss: 0.01009477 epoch time: 2.29s step time: 0.0183s\n", + "epoch: 88 train loss: 0.01052440 epoch time: 2.11s step time: 0.0169s\n", + "epoch: 89 train loss: 0.00695687 epoch time: 2.15s step time: 0.0172s\n", + "epoch: 90 train loss: 0.00711782 epoch time: 2.13s step time: 0.0171s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0012141274\n", + "=================================End Evaluation=================================\n", + "epoch: 91 train loss: 0.00646544 epoch time: 2.12s step time: 0.0170s\n", + "epoch: 92 train loss: 0.00788212 epoch time: 2.18s step time: 0.0174s\n", + "epoch: 93 train loss: 0.00627555 epoch time: 2.17s step time: 0.0173s\n", + "epoch: 94 train loss: 0.00621355 epoch time: 2.08s step time: 0.0166s\n", + "epoch: 95 train loss: 0.00569737 epoch time: 2.15s step time: 0.0172s\n", + "epoch: 96 train loss: 0.00505786 epoch time: 2.09s step time: 0.0167s\n", + "epoch: 97 train loss: 0.00565789 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 98 train loss: 0.00527059 epoch time: 2.07s step time: 0.0166s\n", + "epoch: 99 train loss: 0.00606439 epoch time: 2.07s step time: 0.0165s\n", + "epoch: 100 train loss: 0.00620594 epoch time: 2.08s step time: 0.0166s\n", + "================================Start Evaluation================================\n", + "mean rms_error: 0.0011061892\n", + "=================================End Evaluation=================================\n" + ] + } + ], + "source": [ + "problem = UnsteadyFlowWithLoss(model, loss_fn=RelativeRMSELoss(), data_format=\"NHWTC\")\n", + "\n", + "summary_dir = os.path.join(config[\"summary\"][\"summary_dir\"], model_name)\n", + "print(summary_dir)\n", + "\n", + "def forward_fn(data, label):\n", + " loss = problem.get_loss(data, label)\n", + " if use_ascend:\n", + " loss = loss_scaler.scale(loss)\n", + " return loss\n", + "\n", + "grad_fn = ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=False)\n", + "\n", + "@jit\n", + "def train_step(data, label):\n", + " loss, grads = grad_fn(data, label)\n", + " if use_ascend:\n", + " loss = loss_scaler.unscale(loss)\n", + " if all_finite(grads):\n", + " grads = loss_scaler.unscale(grads)\n", + " loss = ops.depend(loss, optimizer(grads))\n", + " return loss\n", + "\n", + "sink_process = data_sink(train_step, train_dataset, 1)\n", + "#summary_dir = os.path.join(config[\"summary_dir\"], model_name)\n", + "ckpt_dir = os.path.join(summary_dir, \"ckpt\")\n", + "if not os.path.exists(ckpt_dir):\n", + " os.makedirs(ckpt_dir)\n", + "\n", + "for epoch in range(1, optimizer_params[\"epochs\"] + 1):\n", + " model.set_train()\n", + " local_time_beg = time.time()\n", + " for _ in range(steps_per_epoch):\n", + " cur_loss = sink_process()\n", + " print(\n", + " f\"epoch: {epoch} train loss: {cur_loss.asnumpy():.8f}\"\\\n", + " f\" epoch time: {time.time() - local_time_beg:.2f}s\"\\\n", + " f\" step time: {(time.time() - local_time_beg)/steps_per_epoch:.4f}s\")\n", + "\n", + " if epoch % config['summary']['test_interval'] == 0:\n", + " model.set_train(False)\n", + " print(\"================================Start Evaluation================================\")\n", + " rms_error = problem.get_loss(test_input, test_label)/test_input.shape[0]\n", + " print(f\"mean rms_error: {rms_error}\")\n", + " print(\"=================================End Evaluation=================================\")\n", + " save_checkpoint(model, os.path.join(ckpt_dir, model_params[\"name\"] + '_epoch' + str(epoch)))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "jupyter", + "language": "python", + "name": "jupyter" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.23" + }, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/MindFlow/applications/data_driven/burgers/fno1d/README.MD b/MindFlow/applications/data_driven/burgers/fno1d/README.MD new file mode 100644 index 000000000..8ec1db7ea --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/README.MD @@ -0,0 +1,98 @@ +# FNO operator solves Burgers equation + +## Introduction + +### Background + +Computational fluid dynamics is one of the most important techniques in the field of fluid mechanics in the 21st century. The flow analysis, prediction and control can be realized by solving the governing equations of fluid mechanics by numerical method. Traditional finite element method (FEM) and finite difference method (FDM) are inefficient because of the complex simulation process (physical modeling, meshing, numerical discretization, iterative solution, etc.) and high computing costs. Therefore, it is necessary to improve the efficiency of fluid simulation with AI. + +Machine learning methods provide a new paradigm for scientific computing by providing a fast solver similar to traditional methods. Classical neural networks learn mappings between finite dimensional spaces and can only learn solutions related to a specific discretization. Different from traditional neural networks, Fourier Neural Operator (FNO) is a new deep learning architecture that can learn mappings between infinite-dimensional function spaces. It directly learns mappings from arbitrary function parameters to solutions to solve a class of partial differential equations. Therefore, it has a stronger generalization capability. More information can be found in the paper, [Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/abs/2010.08895). + +This tutorial describes how to solve the 1-d Burgers' equation using Fourier neural operator. + +### 问题描述 + +## Burgers' equation + +The 1-d Burgers’ equation is a non-linear PDE with various applications including modeling the one +dimensional flow of a viscous fluid. It takes the form + +$$ +\partial_t u(x, t)+\partial_x (u^2(x, t)/2)=\nu \partial_{xx} u(x, t), \quad x \in(0,1), t \in(0, 1] +$$ + +$$ +u(x, 0)=u_0(x), \quad x \in(0,1) +$$ + +where $u$ is the velocity field, $u_0$ is the initial condition and $\nu$ is the viscosity coefficient. + +We aim to learn the operator mapping the initial condition to the solution at time one: + +$$ +u_0 \mapsto u(\cdot, 1) +$$ + +### Technical path + +The Fourier Neural Operator consists of the Lifting Layer, Fourier Layers, and the Decoding Layer. + +![Fourier Neural Operator model structure](images/FNO.png) + +Fourier layers: Start from input V. On top: apply the Fourier transform $\mathcal{F}$; a linear transform R on the lower Fourier modes and filters out the higher modes; then apply the inverse Fourier transform $\mathcal{F}^{-1}$. On the bottom: apply a local linear transform W. Finally, the Fourier Layer output vector is obtained through the activation function. + +![Fourier Layer structure](images/FNO-2.png) + +## QuickStart + +You can download dataset from [data_driven/burgers/](https://download.mindspore.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/). Save these dataset at `./dataset`. + +### Run Method 1: Call `train.py` from command line + +```shell +export PYTHONPATH=$(cd ../../../../../ && pwd):$PYTHONPATH +python train.py --config_file_path ./configs/fno1d.yaml --mode GRAPH --device_target Ascend --device_id 0 +``` + +where: + +`--config_file_path` indicates the path of the parameter file. Default './configs/fno1d.yaml'; + +`--device_target` indicates the computing platform. You can choose 'Ascend' or 'GPU'. Default 'Ascend'. + +`--device_id` indicates the index of NPU or GPU. Default 0. + +`--mode` is the running mode. 'GRAPH' indicates static graph mode. 'PYNATIVE' indicates dynamic graph mode. + +### Run Method 2: Run Jupyter Notebook + +You can run the training and validation code line by line using the Chinese or English version of the Jupyter Notebook [Chinese Version](./FNO1D_CN.ipynb) and [English Version](./FNO1D.ipynb). + +## Results Display + +![FNO1D Solves Burgers Equation](images/result.jpg) + +## Performance + +| Parameter | Ascend | GPU | +|:----------------------:|:--------------------------:|:---------------:| +| Hardware | Ascend, 32G | NVIDIA V100, 32G | +| MindSpore版本 | 2.7.0 | >=2.1.0 | +| Dataset | [1D Burgers Equation Resolution Dataset](https://download-mindspore.osinfra.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/) | [1D Burgers Equation Resolution Dataset](https://download-mindspore.osinfra.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/) | +| Parameters | 5.57e5 | 5.5e5 | +| Train Config | resolution=1024, modes=16, hidden_channels=64, depth=4, batch_size=8, epoch=100 | resolution=256, modes=16, hidden_channels=64, depth=10, batch_size=64, epoch=1000 | +| Evaluation Config | batch_size=8 | batch_size=64 | +| Optimizer | Adam | Adam | +| Train Loss(MSE) | 0.004872 | 0.011212 | +| Evaluation Error(RMSE) | 0.001088 | 0.000279 | +| Speed(ms/step) | 15 | 17 | + +The datasets at different resolutions are taken for testing and according to the following results it can be concluded that the dataset resolution has no effect on the training results. + +![FNO Solves Burgers Equation](images/resolution_test.jpg) + +## Contributor + +gitee id:[liulei277](https://gitee.com/liulei277), [yezhenghao2023](https://gitee.com/yezhenghao2023) + +email: liulei2770919@163.com, yezhenghao@isrc.iscas.ac.cn \ No newline at end of file diff --git a/MindFlow/applications/data_driven/burgers/fno1d/README_CN.md b/MindFlow/applications/data_driven/burgers/fno1d/README_CN.md new file mode 100644 index 000000000..ad4b6c780 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/README_CN.md @@ -0,0 +1,97 @@ +# FNO算子求解Burgers方程 + +## 概述 + +### 背景 + +计算流体力学是21世纪流体力学领域的重要技术之一,其通过使用数值方法在计算机中对流体力学的控制方程进行求解,从而实现流动的分析、预测和控制。传统的有限元法(finite element method,FEM)和有限差分法(finite difference method,FDM)常用于复杂的仿真流程(物理建模、网格划分、数值离散、迭代求解等)和较高的计算成本,往往效率低下。因此,借助AI提升流体仿真效率是十分必要的。 + +近年来,随着神经网络的迅猛发展,为科学计算提供了新的范式。经典的神经网络是在有限维度的空间进行映射,只能学习与特定离散化相关的解。与经典神经网络不同,傅里叶神经算子(Fourier Neural Operator,FNO)是一种能够学习无限维函数空间映射的新型深度学习架构。该架构可直接学习从任意函数参数到解的映射,用于解决一类偏微分方程的求解问题,具有更强的泛化能力。更多信息可参考[Fourier Neural Operator for Parametric Partial Differential Equations](https://arxiv.org/abs/2010.08895)。 + +本案例教程介绍利用傅里叶神经算子的1-d Burgers方程求解方法。 + +### 问题描述 + +一维伯格斯方程(1-d Burgers' equation)是一个非线性偏微分方程,具有广泛应用,包括一维粘性流体流动建模。它的形式如下: + +$$ +\partial_t u(x, t)+\partial_x (u^2(x, t)/2)=\nu \partial_{xx} u(x, t), \quad x \in(0,1), t \in(0, 1] +$$ + +$$ +u(x, 0)=u_0(x), \quad x \in(0,1) +$$ + +其中$u$表示速度场,$u_0$表示初始条件,$\nu$表示粘度系数。 + +本案例利用Fourier Neural Operator学习初始状态到下一时刻状态的映射,实现一维Burgers'方程的求解: + +$$ +u_0 \mapsto u(\cdot, 1) +$$ + +### 技术路径 + +Fourier Neural Operator模型构架如下图所示。图中$w_0(x)$表示初始涡度,通过Lifting Layer实现输入向量的高维映射,然后将映射结果作为Fourier Layer的输入,进行频域信息的非线性变换,最后由Decoding Layer将变换结果映射至最终的预测结果$w_1(x)$。 + +Lifting Layer、Fourier Layer以及Decoding Layer共同组成了Fourier Neural Operator。 + +![Fourier Neural Operator模型构架](images/FNO.png) + +Fourier Layer网络结构如下图所示。图中V表示输入向量,上框表示向量经过傅里叶变换后,经过线性变换R,过滤高频信息,然后进行傅里叶逆变换;另一分支经过线性变换W,最后通过激活函数,得到Fourier Layer输出向量。 + +![Fourier Layer网络结构](images/FNO-2.png) + +## 快速开始 + +数据集下载地址:[data_driven/burgers/fno1d/dataset](https://download.mindspore.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/dataset/). 将数据集保存在`./dataset`路径下. + +### 训练方式一:在命令行中调用`train.py`脚本 + +```shell +export PYTHONPATH=$(cd ../../../../../ && pwd):$PYTHONPATH +python train.py --config_file_path ./configs/fno1d.yaml --device_target Ascend --device_id 0 --mode GRAPH +``` + +其中, + +`--config_file_path`表示配置文件的路径,默认值'./configs/fno1d.yaml'; + +`--device_target`表示使用的计算平台类型,可以选择'Ascend'或'GPU',默认值'GPU'; + +`--device_id`表示使用的计算卡编号,可按照实际情况填写,默认值 0; + +`--mode`表示运行的模式,'GRAPH'表示静态图模式, 'PYNATIVE'表示动态图模式。 + +### 训练方式二:运行 Jupyter Notebook + +您可以使用[中文版](./FNO1D_CN.ipynb)和[英文版](./FNO1D.ipynb)Jupyter Notebook 逐行运行训练和验证代码。 + +## 结果展示 + +![FNO1D Solves Burgers](images/result.jpg) + +## 性能 + +| 参数 | Ascend | GPU | +|:----------------------:|:--------------------------:|:---------------:| +| 硬件资源 | Ascend, 显存32G | NVIDIA V100, 显存32G | +| MindSpore版本 | 2.7.0 | >=2.1.0 | +| 数据集 | [一维Burgers方程分辨率数据集](https://download-mindspore.osinfra.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/) | [一维Burgers方程分辨率数据集](https://download-mindspore.osinfra.cn/mindscience/mindflow/dataset/applications/data_driven/burgers/) | +| 参数量 | 5.57e5 | 5.5e5 | +| 训练参数 | resolution=1024, modes=16, hidden_channels=64, depth=4, batch_size=8, epoch=100 | resolution=256, modes=16, hidden_channels=64, depth=10, batch_size=64, epoch=1000 | +| 测试参数 | batch_size=8 | batch_size=64 | +| 优化器 | Adam | Adam | +| 训练损失(MSE) | 0.004872 | 0.011212 | +| 验证损失(RMSE) | 0.001088 | 0.000279 | +| 速度(ms/step) | 15 | 17 | + +取不同分辨率下的数据集进行测试,根据以下结果可得出数据集分辨率对训练结果没有影响。 + +![FNO求解burgers方程](images/resolution_test.jpg) + +## Contributor + +gitee id:[liulei277](https://gitee.com/liulei277), [yezhenghao2023](https://gitee.com/yezhenghao2023) + +email: liulei2770919@163.com, yezhenghao@isrc.iscas.ac.cn \ No newline at end of file diff --git a/MindFlow/applications/data_driven/burgers/fno1d/__init__.py b/MindFlow/applications/data_driven/burgers/fno1d/__init__.py new file mode 100644 index 000000000..a5ebd3442 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2023 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================ +"""init""" +from train import train + +__all__ = [ + "train", +] diff --git a/MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml b/MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml new file mode 100644 index 000000000..af8295056 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/configs/fno1d.yaml @@ -0,0 +1,27 @@ +model: + name: FNO1D + in_channels: 1 + out_channels: 1 + modes: 16 + resolutions: 1024 + hidden_channels: 64 + depths: 4 +data: + name: "burgers1d" + root_dir: "./dataset" + train: + num_samples: 1000 + test: + num_samples: 200 + batch_size: 8 + resolution: 1024 + t_in: 1 + t_out: 1 + step: 8 +optimizer: + learning_rate: 0.001 + epochs: 100 +summary: + test_interval: 10 + summary_dir: "./summary" + ckpt_dir: "./checkpoints" \ No newline at end of file diff --git a/MindFlow/applications/data_driven/burgers/fno1d/images/FNO-2.png b/MindFlow/applications/data_driven/burgers/fno1d/images/FNO-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1556f366ae893a855b37ebe1666fc4fc0e59ef99 GIT binary patch literal 19629 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 A_5c6? literal 0 HcmV?d00001 diff --git a/MindFlow/applications/data_driven/burgers/fno1d/images/FNO.png b/MindFlow/applications/data_driven/burgers/fno1d/images/FNO.png new file mode 100644 index 0000000000000000000000000000000000000000..63c860f85f9a34644202175c2e4da55aaf96ba80 GIT binary patch literal 45253 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ൿIS*#e+DDqVy;`BxW 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 W!G+c#!;s6rEh)*V%hpMohx|VTCnO;N literal 0 HcmV?d00001 diff --git a/MindFlow/applications/data_driven/burgers/fno1d/images/resolution_test.jpg b/MindFlow/applications/data_driven/burgers/fno1d/images/resolution_test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc06d7598596b9d169fdb3564851689914eb38a5 GIT binary patch literal 22102 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 literal 0 HcmV?d00001 diff --git a/MindFlow/applications/data_driven/burgers/fno1d/images/result.jpg b/MindFlow/applications/data_driven/burgers/fno1d/images/result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8fdfb1b9af70d2cd15ba5161288b6fd554eb8a6e GIT binary patch literal 28848 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 literal 0 HcmV?d00001 diff --git a/MindFlow/applications/data_driven/burgers/fno1d/src/__init__.py b/MindFlow/applications/data_driven/burgers/fno1d/src/__init__.py new file mode 100644 index 000000000..d1638cb04 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/src/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2023 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================ +"""init""" +from .dataset import create_training_dataset + +__all__ = [ + "create_training_dataset", +] diff --git a/MindFlow/applications/data_driven/burgers/fno1d/src/dataset.py b/MindFlow/applications/data_driven/burgers/fno1d/src/dataset.py new file mode 100644 index 000000000..e8366d39c --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/src/dataset.py @@ -0,0 +1,94 @@ +# Copyright 2023 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +"""dataset""" +import os +import numpy as np +from MindFlow.data import Dataset, ExistedDataConfig +from mindscience.utils import print_log +EPS = 1e-8 + + +def create_npy(data_params, step=8): + '''create inputs and label data for trainset and testset''' + data_path = data_params["root_dir"] + train_size = data_params["train"]["num_samples"] + test_size = data_params["test"]["num_samples"] + s = 2 ** 13 // data_params["step"] + + train_path = os.path.join(data_path, "train") + test_path = os.path.join(data_path, "test") + + if not os.path.exists(train_path): + os.makedirs(train_path) + else: + print_log("Data preparation finished") + return + if not os.path.exists(test_path): + os.makedirs(test_path) + + inputs = np.load(os.path.join(train_path, "inputs.npy")) + label = np.load(os.path.join(train_path, "label.npy")) + + x_train = inputs[:train_size, :][:, ::step] + y_train = label[:train_size, :][:, ::step] + x_test = inputs[-test_size:, :][:, ::step] + y_test = label[-test_size:, :][:, ::step] + x_train = x_train.reshape(train_size, s, 1) + x_test = x_test.reshape(test_size, s, 1) + + train_input_path = os.path.join(train_path, "inputs.npy") + train_label_path = os.path.join(train_path, "label.npy") + test_input_path = os.path.join(test_path, "inputs.npy") + test_label_path = os.path.join(test_path, "label.npy") + + with os.fdopen(train_input_path, "wb") as f: + np.save(f, x_train) + with os.fdopen(train_label_path, "wb") as f: + np.save(f, y_train) + with os.fdopen(test_input_path, "wb") as f: + np.save(f, x_test) + with os.fdopen(test_label_path, "wb") as f: + np.save(f, y_test) + + +def create_training_dataset(data_params, model_params, shuffle=True, drop_remainder=True, is_train=True): + """create dataset""" + create_npy(data_params) + data_path = data_params["root_dir"] + if is_train: + train_path = os.path.abspath(os.path.join(data_path, "train")) + input_path = os.path.join(train_path, "inputs.npy") + print_log('input_path: ', np.load(input_path).shape) + label_path = os.path.join(train_path, "label.npy") + print_log('label_path: ', np.load(label_path).shape) + else: + test_path = os.path.abspath(os.path.join(data_path, "test")) + input_path = os.path.join(test_path, "inputs.npy") + label_path = os.path.join(test_path, "label.npy") + burgers_1d_data = ExistedDataConfig(name=data_params["name"], + data_dir=[input_path, label_path], + columns_list=["inputs", "label"], + data_format="npy") + dataset = Dataset(existed_data_list=[burgers_1d_data]) + data_loader = dataset.create_dataset(batch_size=data_params["batch_size"], + shuffle=shuffle, + drop_remainder=drop_remainder) + + operations = [lambda x, y: (x.reshape(-1, data_params["resolution"], + data_params["t_in"], model_params["out_channels"]), + y.reshape(-1, data_params["resolution"], + data_params["t_out"], model_params["out_channels"]))] + data_loader = data_loader.map(operations, input_columns=["burgers1d_inputs", "burgers1d_label"]) + return data_loader diff --git a/MindFlow/applications/data_driven/burgers/fno1d/train.py b/MindFlow/applications/data_driven/burgers/fno1d/train.py new file mode 100644 index 000000000..f28417d98 --- /dev/null +++ b/MindFlow/applications/data_driven/burgers/fno1d/train.py @@ -0,0 +1,154 @@ +"""train""" +import os +import time +import argparse +import datetime +import numpy as np + +from mindspore import context, nn, Tensor, set_seed, ops, data_sink, jit, save_checkpoint +from mindspore import dtype as mstype +from mindspore.amp import DynamicLossScaler, auto_mixed_precision, all_finite +from src import create_training_dataset +from mindscience import FNO1D, RelativeRMSELoss, load_yaml_config, get_warmup_cosine_annealing_lr +from mindscience.pde import UnsteadyFlowWithLoss +from mindscience.utils import log_config, print_log + + + +set_seed(0) +np.random.seed(0) + + +def parse_args(): + '''Parse input args''' + parser = argparse.ArgumentParser(description='Burgers 1D problem') + parser.add_argument("--mode", type=str, default="GRAPH", choices=["GRAPH", "PYNATIVE"], + help="Context mode, support 'GRAPH', 'PYNATIVE'") + parser.add_argument("--save_graphs", type=bool, default=False, choices=[True, False], + help="Whether to save intermediate compilation graphs") + parser.add_argument("--save_graphs_path", type=str, default="./graphs") + parser.add_argument("--device_target", type=str, default="GPU", choices=["GPU", "Ascend"], + help="The target device to run, support 'Ascend', 'GPU'") + parser.add_argument("--device_id", type=int, default=0, + help="ID of the target device") + parser.add_argument("--config_file_path", type=str, + default="./configs/fno1d.yaml") + input_args = parser.parse_args() + return input_args + + +def train(input_args): + '''Train and evaluate the network''' + use_ascend = context.get_context(attr_key='device_target') == "Ascend" + print_log(f"use_ascend: {use_ascend}") + + config = load_yaml_config(input_args.config_file_path) + data_params = config["data"] + model_params = config["model"] + optimizer_params = config["optimizer"] + + # create training dataset + train_dataset = create_training_dataset(data_params, model_params, shuffle=True) + + # create test dataset + test_input, test_label = np.load(os.path.join(data_params["root_dir"], "test/inputs.npy")), \ + np.load(os.path.join(data_params["root_dir"], "test/label.npy")) + test_input = Tensor(np.expand_dims(test_input, -2), mstype.float32) + test_label = Tensor(np.expand_dims(test_label, -2), mstype.float32) + + model = FNO1D(in_channels=model_params["in_channels"], + out_channels=model_params["out_channels"], + n_modes=model_params["modes"], + resolutions=model_params["resolutions"], + hidden_channels=model_params["hidden_channels"], + n_layers=model_params["depths"], + projection_channels=4*model_params["hidden_channels"], + ) + + steps_per_epoch = train_dataset.get_dataset_size() + lr = get_warmup_cosine_annealing_lr(lr_init=optimizer_params["learning_rate"], + last_epoch=optimizer_params["epochs"], + steps_per_epoch=steps_per_epoch, + warmup_epochs=1) + optimizer = nn.Adam(model.trainable_params(), learning_rate=Tensor(lr)) + + if use_ascend: + loss_scaler = DynamicLossScaler(1024, 2, 100) + auto_mixed_precision(model, 'O3') + else: + loss_scaler = None + + problem = UnsteadyFlowWithLoss( + model, loss_fn=RelativeRMSELoss(), data_format="NHWTC") + + summary_dir = config["summary"]["summary_dir"] + print_log(summary_dir) + + def forward_fn(data, label): + loss = problem.get_loss(data, label) + if use_ascend: + loss = loss_scaler.scale(loss) + return loss + + grad_fn = ops.value_and_grad( + forward_fn, None, optimizer.parameters, has_aux=False) + + @jit + def train_step(data, label): + loss, grads = grad_fn(data, label) + if use_ascend: + loss = loss_scaler.unscale(loss) + if all_finite(grads): + grads = loss_scaler.unscale(grads) + loss = ops.depend(loss, optimizer(grads)) + return loss + + sink_process = data_sink(train_step, train_dataset, 1) + ckpt_dir = "./checkpoints" + if not os.path.exists(ckpt_dir): + os.makedirs(ckpt_dir) + min_train_loss = float('inf') + min_eval_loss = float('inf') + min_step_time = float('inf') + for epoch in range(1, optimizer_params["epochs"] + 1): + model.set_train() + local_time_beg = time.time() + for _ in range(steps_per_epoch): + cur_loss = sink_process() + print_log( + f"epoch: {epoch} train loss: {cur_loss.asnumpy():.8f}"\ + f" epoch time: {time.time() - local_time_beg:.2f}s"\ + f" step time: {(time.time() - local_time_beg)/steps_per_epoch:.4f}s") + min_train_loss = min(min_train_loss, cur_loss) + min_step_time = min(min_step_time, (time.time() - local_time_beg) / steps_per_epoch) + if epoch % config['summary']['test_interval'] == 0: + eval_time_start = time.time() + model.set_train(False) + print_log( + "================================Start Evaluation================================") + rms_error = problem.get_loss( + test_input, test_label)/test_input.shape[0] + print_log(f"mean rms_error: {rms_error}") + min_eval_loss = min(min_eval_loss, rms_error) + print_log( + "=================================End Evaluation=================================") + print_log(f'evaluation time: {time.time() - eval_time_start}s') + save_checkpoint(model, os.path.join( + ckpt_dir, f"{model_params['name']}_epoch{epoch}")) + print_log( + f"the minimum train mse is {min_train_loss}\n", + f"the minimum step time is {min_step_time}\n", + f"the minimum eval rmse is {min_eval_loss}\n", + ) +if __name__ == '__main__': + log_config('./logs', 'fno1d') + print_log(f"pid: {os.getpid()}") + print_log(datetime.datetime.now()) + args = parse_args() + context.set_context(mode=context.GRAPH_MODE if args.mode.upper().startswith("GRAPH") else context.PYNATIVE_MODE, + save_graphs=args.save_graphs, save_graphs_path=args.save_graphs_path, + device_target=args.device_target, device_id=args.device_id,max_device_memory="32GB") + set_max_mem = context.get_context(attr_key='max_device_memory') + print(f"设置的最大显存:{set_max_mem } GB") + print_log(f"device_id: {context.get_context(attr_key='device_id')}") + train(args) diff --git a/MindFlow/data/__init__.py b/MindFlow/data/__init__.py new file mode 100644 index 000000000..2a15f218f --- /dev/null +++ b/MindFlow/data/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================ +"""init""" +from .dataset import Dataset +#from .mind_dataset import MindDataset +from .boundary import BoundaryBC, BoundaryIC, Boundary +from .equation import Equation +from .existed_data import ExistedDataset +from .data_base import ExistedDataConfig, CONSTRAINT_TYPES + +__all__ = ["Dataset", + #"MindDataset", + "Equation", + "BoundaryBC", + "BoundaryIC", + "ExistedDataset", + "ExistedDataConfig", + "CONSTRAINT_TYPES"] diff --git a/MindFlow/data/boundary.py b/MindFlow/data/boundary.py new file mode 100644 index 000000000..3033ff7a5 --- /dev/null +++ b/MindFlow/data/boundary.py @@ -0,0 +1,174 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +#pylint: disable=W0223 +#pylint: disable=W0221 +""" +Sampling data of boundary condition and initial condition. +""" +from __future__ import absolute_import + +import numpy as np +from mindspore import log as logger + + +from mindscience.data.flow.geometry.geometry_base import Geometry, SamplingConfig +from mindscience.utils.check_func import check_param_type + +from .data_base import Data + + +class Boundary(Data): + """ + Base class of boundary condition and initial condition. + + Args: + geometry (Geometry): specifies geometry information of boundary condition and initial condition. + + Raises: + ValueError: if `sampling_config` of geometry is ``None``. + TypeError: if `geometry` is not an instance of Geometry. + """ + + def __init__(self, geometry): + check_param_type(geometry, "geometry", data_type=Geometry) + self.geometry = geometry + check_param_type(geometry.sampling_config, geometry.name, data_type=SamplingConfig) + + self.data = None + self.data_size = None + self.batch_size = 1 + self.shuffle = False + self.batched_data_size = None + self.columns_list = None + self._bound_index = None + self._bound_index_num = 0 + + super().__init__() + + def _get_sampling_data(self, geom_type="BC"): + """get sampling data""" + sample_data = self.geometry.sampling(geom_type=geom_type) + if not isinstance(sample_data, tuple): + sample_data = (sample_data,) + logger.info(f"Get {geom_type} sample_data size: {len(sample_data[0])}") + return sample_data, self.geometry.columns_dict[geom_type] + + def _initialize(self, batch_size=1, shuffle=False, geom_type="BC"): + """initialization: sampling and set attrs.""" + data, self.columns_list = self._get_sampling_data(geom_type=geom_type) + if not isinstance(data, tuple): + data = (data,) + self.data = data + self.data_size = len(self.data[0]) + self.batch_size = batch_size + + if batch_size > self.data_size: + raise ValueError( + f"If prebatch data, batch_size: {batch_size} " + f"should not be larger than data size: {self.data_size}." + ) + + self.batched_data_size = self.data_size // batch_size + self.shuffle = shuffle + self._bound_index = np.arange(self.data_size) + + logger.info( + f"Get {geom_type} dataset: {self.name}, columns: {self.columns_list}, " + f"size: {self.data_size}, batched_size: {self.batched_data_size}, shuffle: {self.shuffle}" + ) + return data + + def _get_index_when_sample_iter(self, index, geom_type="BC"): + if self._bound_index_num == self.batched_data_size: + self.data = self._initialize(self.batch_size, self.shuffle, geom_type=geom_type) + self._bound_index_num = 0 + index = self._bound_index_num + self._bound_index_num += 1 + return index + + def _get_index_when_sample_all(self, index): + data_size = len(self) + if (self._random_merge or self.shuffle) and index % data_size == 0: + self._bound_index = np.random.permutation(self.data_size) + index = index % data_size if index >= data_size else index + return index + + def __len__(self): + return self.batched_data_size + + def _get_item(self, index): + col_data = None + for i in range(len(self.columns_list)): + temp_data = ( + self.data[i][self._bound_index[index]] + if self.batch_size == 1 + else self.data[i][index * self.batch_size : (index + 1) * self.batch_size] + ) + col_data = (temp_data,) if col_data is None else col_data + (temp_data,) + return col_data + + +class BoundaryBC(Boundary): + """Sampling data of boundary condition.""" + + def __init__(self, geometry): + super().__init__(geometry) + self.sampling_config = self.geometry.sampling_config.bc + if not self.sampling_config: + raise ValueError( + f"BC info for the current geometry: {geometry.name} should not be None" + ) + self.name = geometry.name + "_BC" + self.constraint_type = "BC" + self._random_merge = self.sampling_config.random_merge + + def __getitem__(self, bc_index): + if not self.data: + self._initialization() + if self.sampling_config.random_sampling: + bc_index = self._get_index_when_sample_iter(bc_index, geom_type="BC") + else: + bc_index = self._get_index_when_sample_all(bc_index) + return self._get_item(bc_index) + + def _initialization(self, batch_size=1, shuffle=False): + return self._initialize(batch_size=batch_size, shuffle=shuffle, geom_type="BC") + + +class BoundaryIC(Boundary): + """Sampling data of initial condition.""" + + def __init__(self, geometry): + super().__init__(geometry) + self.sampling_config = self.geometry.sampling_config.ic + if not self.sampling_config: + raise ValueError( + f"IC info for the current geometry: {geometry.name} should not be None" + ) + self.name = geometry.name + "_IC" + self.constraint_type = "IC" + self._random_merge = self.sampling_config.random_merge + + def __getitem__(self, ic_index): + if not self.data: + self._initialization() + if self.sampling_config.random_sampling: + ic_index = self._get_index_when_sample_iter(ic_index, geom_type="IC") + else: + ic_index = self._get_index_when_sample_all(ic_index) + return self._get_item(ic_index) + + def _initialization(self, batch_size=1, shuffle=False): + return self._initialize(batch_size=batch_size, shuffle=shuffle, geom_type="IC") diff --git a/MindFlow/data/data_base.py b/MindFlow/data/data_base.py new file mode 100644 index 000000000..6ca591c53 --- /dev/null +++ b/MindFlow/data/data_base.py @@ -0,0 +1,145 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +""" +This dataset module supports various type of datasets, including .... Some of the operations that are +provided to users to preprocess data include shuffle, batch, repeat, map, and zip. +""" +from __future__ import absolute_import + +import os +import abc + +from mindscience.utils.check_func import ( + check_param_type, + check_param_value, + check_param_type_value +) + +CONSTRAINT_TYPES = ["equation", "bc", "ic", "label", "function", "custom"] +DATA_FORMATS = ["npy"] + + +class Data: + """ + Base class of Dataset, Equation, Boundary and ExistedDataset. + + Args: + name (str): Dataset name. + columns_list (list/tuple): Column names. + constraint_type (str): Type of constraint. + + Supported Platforms: + ``Ascend`` + """ + + def __init__(self, name=None, columns_list=None, constraint_type=None): + none_type = type(None) + + # type checks + check_param_type(name, "name", data_type=[str, none_type]) + check_param_type(columns_list, "columns_list", data_type=[list, tuple, none_type]) + check_param_type(constraint_type, "constraint_type", data_type=[str, none_type]) + + if constraint_type: + check_param_value(constraint_type.lower(), "constraint_type", CONSTRAINT_TYPES) + + self.name = name + self.columns_list = columns_list + self.constraint_type = constraint_type + self.dataset_type = type(self).__name__ + + def set_constraint_type(self, constraint_type="Equation"): + """Set dataset constraint type.""" + check_param_type(constraint_type, "constraint_type", data_type=str) + check_param_value(constraint_type.lower(), "constraint_type", CONSTRAINT_TYPES) + self.constraint_type = constraint_type + + @abc.abstractmethod + def create_dataset(self): + """Return a dataset (abstract).""" + raise NotImplementedError(f"{self.dataset_type}.create_dataset not implemented") + + @abc.abstractmethod + def _initialization(self): + """Initialize dataset (abstract).""" + raise NotImplementedError(f"{self.dataset_type}._initialization not implemented") + + @abc.abstractmethod + def __getitem__(self, index): + """Return item by index (abstract).""" + raise NotImplementedError(f"{self.dataset_type}.__getitem__ not implemented") + + @abc.abstractmethod + def __len__(self): + """Return dataset length (abstract).""" + raise NotImplementedError(f"{self.dataset_type}.__len__ not implemented") + + +class ExistedDataConfig: + """ + Configuration of ExistedDataset. + + Args: + name (str): Dataset name. + data_dir (str/list): Path(s) to existing data files. + columns_list (str/list): Column names. + data_format (str): File format (supports 'npy'). + constraint_type (str): Constraint type. + random_merge (bool): Whether to randomly merge datasets. + """ + + def __init__( + self, + name, + data_dir, + columns_list, + data_format="npy", + constraint_type="Label", + random_merge=True + ): + # name + check_param_type(name, "name", data_type=str) + self.name = name + + # data_dir + if isinstance(data_dir, str): + data_dir = [data_dir] + + check_param_type(data_dir, "data_dir", data_type=[str, list, tuple]) + for path in data_dir: + if not os.path.exists(path): + raise ValueError(f"ExistedDataset file: {path} does not exist") + + self.data_dir = data_dir + + # columns_list + if isinstance(columns_list, str): + columns_list = [columns_list] + + check_param_type(columns_list, "columns_list", data_type=[str, tuple, list]) + self.columns_list = columns_list + + # constraint_type + check_param_type(constraint_type, "constraint_type", data_type=str) + check_param_value(constraint_type.lower(), "constraint_type", CONSTRAINT_TYPES) + self.constraint_type = constraint_type + + # data_format + check_param_type_value(data_format, "data_format", DATA_FORMATS, data_type=str) + self.data_format = data_format + + # random_merge + check_param_type(random_merge, "random_merge", data_type=bool) + self.random_merge = random_merge diff --git a/MindFlow/data/dataset.py b/MindFlow/data/dataset.py new file mode 100644 index 000000000..0bcf5903a --- /dev/null +++ b/MindFlow/data/dataset.py @@ -0,0 +1,340 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +#pylint: disable=W0223 +#pylint: disable=W0221 +#pylint: disable=W0212 +""" +Combine pde/ic/bc datasets together +""" +from __future__ import absolute_import + +import copy + +import mindspore.dataset as ds +from mindspore import log as logger + + +from mindscience.data.flow.geometry import Geometry +from mindscience.utils.check_func import check_param_type, check_dict_type_value + +from .data_base import Data, ExistedDataConfig +from .existed_data import ExistedDataset +from .equation import Equation +from .boundary import BoundaryIC, BoundaryBC + +_geomdata_dict = { + "domain": Equation, + "IC": BoundaryIC, + "BC": BoundaryBC, +} + + +class Dataset(Data): + r""" + Combine datasets together. + """ + + def __init__(self, geometry_dict=None, existed_data_list=None, dataset_list=None): + super().__init__() + if all((geometry_dict is None, existed_data_list is None, dataset_list is None)): + raise ValueError("Dataset should have at least one sub-dataset, but got None") + + if geometry_dict is not None: + check_param_type(geometry_dict, "geometry_dict", data_type=dict) + check_dict_type_value( + geometry_dict, + "geometry_dict", + key_type=Geometry, + value_type=str, + value_value=list(_geomdata_dict.keys()) + ) + + if existed_data_list is not None: + if isinstance(existed_data_list, ExistedDataConfig): + existed_data_list = [existed_data_list] + check_param_type(existed_data_list, "existed_data_list", (list, tuple)) + + for data_config in existed_data_list: + check_param_type(data_config, "element in existed_data_list", ExistedDataConfig) + + if dataset_list is not None: + if isinstance(dataset_list, Data): + dataset_list = [dataset_list] + check_param_type(dataset_list, "dataset_list", (list, tuple)) + for dataset in dataset_list: + check_param_type(dataset, "element in dataset_list", Data) + + self.existed_data_list = existed_data_list + self.geometry_dict = geometry_dict + self.dataset_list = dataset_list + self.all_datasets = dataset_list if dataset_list else [] + self.columns_list = None + self._iterable_datasets = None + + self.num_dataset = len(dataset_list) if dataset_list else 0 + if existed_data_list: + self.num_dataset += len(existed_data_list) + if geometry_dict: + for geom in geometry_dict: + self.num_dataset += len(geometry_dict[geom]) + + logger.info(f"Total datasets number: {self.num_dataset}") + self.dataset_columns_map = {} + self.column_index_map = {} + self.dataset_constraint_map = {} + + def _create_dataset_from_geometry(self, geometry, geom_type="domain"): + """create dataset from geometry.""" + dataset_instance = _geomdata_dict.get(geom_type)(geometry) + return dataset_instance + + def _get_all_datasets(self): + """get all datasets""" + if self.geometry_dict: + for geom, types in self.geometry_dict.items(): + for geom_type in types: + dataset = self._create_dataset_from_geometry(geom, geom_type) + self.all_datasets.append(dataset) + + if self.existed_data_list: + for data_config in self.existed_data_list: + dataset = ExistedDataset(data_config=data_config) + self.all_datasets.append(dataset) + + def create_dataset(self, + batch_size=1, + preprocess_fn=None, + input_output_columns_map=None, + shuffle=True, + drop_remainder=True, + prebatched_data=False, + num_parallel_workers=1, + num_shards=None, + shard_id=None, + python_multiprocessing=False, + sampler=None): + """ + create the final mindspore type dataset to merge all the sub-datasets. + """ + self._get_all_datasets() + check_param_type(prebatched_data, "prebatched_data", data_type=bool) + check_param_type(drop_remainder, "drop_remainder", data_type=bool) + check_param_type(shuffle, "shuffle", data_type=bool) + check_param_type(batch_size, "batch_size", data_type=int, exclude_type=bool) + + if prebatched_data and not drop_remainder: + raise ValueError( + "prebatched_data is not supported when drop_remainder is set to be False" + ) + + for dataset in self.all_datasets: + prebatch_size = batch_size if prebatched_data else 1 + prebatch_shuffle = shuffle if prebatched_data else False + dataset._initialization(batch_size=prebatch_size, shuffle=prebatch_shuffle) + self.columns_list = ( + dataset.columns_list if not self.columns_list + else self.columns_list + dataset.columns_list + ) + logger.info( + f"Check initial all dataset, dataset: {dataset.name}, " + f"columns_list: {dataset.columns_list}, data_size: {len(dataset)}" + ) + + dataset = self._merge_all_datasets( + shuffle=False if prebatched_data else shuffle, + num_parallel_workers=num_parallel_workers, + num_shards=num_shards, + shard_id=shard_id, + python_multiprocessing=python_multiprocessing + ) + logger.info(f"Initial dataset size: {dataset.get_dataset_size()}") + logger.info(f"Get all dataset columns names: {self.columns_list}") + + self.dataset_columns_map, self.dataset_constraint_map, self.column_index_map = \ + self._create_trace_maps() + logger.info(f"Dataset columns map: {self.dataset_columns_map}") + logger.info(f"Dataset column index map: {self.column_index_map}") + logger.info(f"Dataset constraints map: {self.dataset_constraint_map}") + + if sampler: + logger.info("Dataset uses sampler") + dataset.use_sampler(sampler) + + if preprocess_fn: + input_columns = copy.deepcopy(self.columns_list) + check_param_type( + input_output_columns_map, + "input_output_columns_map", + (type(None), dict) + ) + if input_output_columns_map: + new_columns_list, new_dataset_columns_map = self._update_columns_list( + input_output_columns_map + ) + self.columns_list = new_columns_list + self.dataset_columns_map = new_dataset_columns_map + self.column_index_map = {} + for idx, name in enumerate(self.columns_list): + self.column_index_map[name] = idx + logger.info( + f"Dataset columns map after preprocess: {self.dataset_columns_map}" + ) + logger.info( + f"Dataset column index after preprocess: {self.column_index_map}" + ) + logger.info( + f"Dataset constraints after preprocess: {self.dataset_constraint_map}" + ) + output_columns = self.columns_list + + dataset = dataset.map( + operations=preprocess_fn, + input_columns=input_columns, + output_columns=output_columns, + num_parallel_workers=num_parallel_workers, + python_multiprocessing=python_multiprocessing + ) + dataset = dataset.project(output_columns) + logger.info( + f"Get all dataset columns names after preprocess: {self.columns_list}" + ) + + if not prebatched_data: + dataset = dataset.batch( + batch_size=batch_size, + drop_remainder=drop_remainder, + num_parallel_workers=num_parallel_workers + ) + logger.info(f"Final dataset size: {dataset.get_dataset_size()}") + return dataset + + def _merge_all_datasets(self, shuffle=True, num_parallel_workers=1, num_shards=1, + shard_id=0, python_multiprocessing=False): + """merge all datasets""" + self._iterable_datasets = _IterableDatasets(self.all_datasets) + dataset = ds.GeneratorDataset( + source=self._iterable_datasets, + column_names=self.columns_list, + shuffle=shuffle, + num_parallel_workers=num_parallel_workers, + num_shards=num_shards, + shard_id=shard_id, + python_multiprocessing=python_multiprocessing + ) + return dataset + + def _update_columns_list(self, input_output_columns_map): + """update columns list""" + new_dataset_columns_map = {} + for dataset in self.all_datasets: + columns_list = dataset.columns_list + new_dataset_columns_map[dataset.name] = [] + for column in columns_list: + if column in input_output_columns_map.keys(): + new_column = input_output_columns_map[column] + if isinstance(new_column, list): + new_dataset_columns_map[dataset.name] += new_column + else: + new_dataset_columns_map.get(dataset.name).append(new_column) + else: + new_dataset_columns_map.get(dataset.name).append(column) + + new_columns_list = [] + for _, columns in new_dataset_columns_map.items(): + new_columns_list += columns + return new_columns_list, new_dataset_columns_map + + def get_columns_list(self): + """ + get columns list + + Returns: + list[str]. column names list of the final unified dataset. + """ + if not self.columns_list: + raise ValueError( + "Please call create_dataset() first before get final columns list to avoid unexpected error" + ) + return self.columns_list + + def _create_trace_maps(self): + """create trace maps""" + dataset_columns_map = {} + dataset_constraint_map = {} + column_index_map = {} + for dataset in self.all_datasets: + name = dataset.name + dataset_columns_map[name] = dataset.columns_list + dataset_constraint_map[name] = dataset.constraint_type + + for i, column in enumerate(self.columns_list): + column_index_map[column] = i + return dataset_columns_map, dataset_constraint_map, column_index_map + + def __getitem__(self, index): + if not self._iterable_datasets: + raise ValueError( + "Call create_dataset() before getting item by index to avoid unexpected error" + ) + return self._iterable_datasets[index] + + def set_constraint_type(self, constraint_type="Equation"): + """set constraint type of dataset""" + if isinstance(constraint_type, str): + logger.warning( + f"Argument constraint_type: {constraint_type} is str, " + "the same type will be set for all of the sub-datasets" + ) + for datasets in self.all_datasets: + datasets.set_constraint_type(constraint_type) + elif isinstance(constraint_type, dict): + for dataset in constraint_type.keys(): + if dataset not in self.all_datasets: + raise ValueError( + f"Unknown dataset: {dataset}. All sub-dataset are: " + f"{[data.name for data in self.all_datasets]}" + ) + dataset.set_constraint_type(constraint_type[dataset]) + else: + raise TypeError( + f"the type of constraint_type should be dict or str but got {type(constraint_type)}" + ) + + def __len__(self): + if not self._iterable_datasets: + raise ValueError( + "Call create_dataset() before getting item by index to avoid unexpected error" + ) + return len(self._iterable_datasets) + + +class _IterableDatasets: + """get data iteratively""" + + def __init__(self, dataset_list): + self.dataset_list = dataset_list + dataset_size = [len(dataset) for dataset in dataset_list] + logger.info(f"Get all dataset sizes: {dataset_size}") + self.longest = max(dataset_size) + + def __getitem__(self, index): + col_data = None + for dataset_instance in self.dataset_list: + item = dataset_instance[index] + col_data = col_data + item if col_data else item + return col_data + + def __len__(self): + return self.longest diff --git a/MindFlow/data/equation.py b/MindFlow/data/equation.py new file mode 100644 index 000000000..134a5dc6b --- /dev/null +++ b/MindFlow/data/equation.py @@ -0,0 +1,142 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +#pylint: disable=W0223 +#pylint: disable=W0221 +""" +Sampling data of equation domain. +""" +from __future__ import absolute_import + +import numpy as np +from mindspore import log as logger + +from mindscience.data.flow.geometry import Geometry, SamplingConfig +from mindscience.utils.check_func import check_param_type +from .data_base import Data + + +_SPACE = "" + + +class Equation(Data): + """ + Sampling data of equation domain. + """ + + def __init__(self, geometry): + check_param_type(geometry, "geometry", data_type=Geometry) + check_param_type( + geometry.sampling_config, + f"sampling_config of geometry:{geometry.name}", + data_type=SamplingConfig, + ) + + self.sampling_config = geometry.sampling_config.domain + if not self.sampling_config: + raise ValueError(f"domain info of geometry: {geometry.name} should not be None") + + self.geometry = geometry + + # dataset internal fields + self.data = None + self.data_size = None + self.batch_size = 1 + self.shuffle = False + self.batched_data_size = None + self.columns_list = None + + self._domain_index = None + self._domain_index_num = 0 + self._random_merge = self.sampling_config.random_merge + + name = f"{geometry.name}_domain" + columns_list = [f"{geometry.name}_domain_points"] + constraint_type = "Equation" + + super().__init__(name=name, columns_list=columns_list, constraint_type=constraint_type) + + def _get_sampling_data(self): + """Sampling domain points.""" + sample_data = self.geometry.sampling(geom_type="domain") + return sample_data, self.geometry.columns_dict["domain"] + + def _initialization(self, batch_size=1, shuffle=False): + """initialization: sampling and set attrs.""" + data, self.columns_list = self._get_sampling_data() + if not isinstance(data, tuple): + data = (data,) + + self.data = data + self.data_size = len(self.data[0]) + self.batch_size = batch_size + + if batch_size > self.data_size: + raise ValueError( + f"If prebatch data, batch_size: {batch_size} should not be larger than " + f"data size: {self.data_size}." + ) + + self.batched_data_size = self.data_size // batch_size + self.shuffle = shuffle + self._domain_index = np.arange(self.data_size) + + logger.info( + f"Get domain dataset: {self.name}, columns: {self.columns_list}, " + f"size: {self.data_size}, batched_size: {self.batched_data_size}, shuffle: {self.shuffle}" + ) + + def _get_index_when_sample_iter(self, index): + """Index logic for random_sampling=True.""" + if self._domain_index_num == self.batched_data_size: + self._initialization(self.batch_size, self.shuffle) + self._domain_index_num = 0 + index = self._domain_index_num + self._domain_index_num += 1 + return index + + def _get_index_when_sample_all(self, index): + """Index logic for random_sampling=False.""" + data_size = len(self) + if (self._random_merge or self.shuffle) and index % data_size == 0: + self._domain_index = np.random.permutation(self.data_size) + return index % data_size + + def __getitem__(self, index): + if self.data is None: + self._initialization() + + if self.sampling_config.random_sampling: + index = self._get_index_when_sample_iter(index) + else: + index = self._get_index_when_sample_all(index) + + col_data = None + for i in range(len(self.columns_list)): + if self.batch_size == 1: + idx = self._domain_index[index] + else: + idx = self._domain_index[ + index * self.batch_size : (index + 1) * self.batch_size + ] + + temp_data = self.data[i][idx] + col_data = (temp_data,) if col_data is None else col_data + (temp_data,) + + return col_data + + def __len__(self): + if self.data is None: + self._initialization() + return self.batched_data_size diff --git a/MindFlow/data/existed_data.py b/MindFlow/data/existed_data.py new file mode 100644 index 000000000..8d77576f2 --- /dev/null +++ b/MindFlow/data/existed_data.py @@ -0,0 +1,166 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================== +#pylint: disable=W0223 +#pylint: disable=W0221 +""" +This dataset module supports npy type of datasets. Some of the operations that are +provided to users to preprocess data include shuffle, batch, repeat, map, and zip. +""" +from __future__ import absolute_import + +import numpy as np +from mindspore import log as logger + +from mindscience.utils.check_func import check_param_type +from .data_base import Data, ExistedDataConfig + + + +class ExistedDataset(Data): + """ + Load existing dataset (currently supports npy format only). + """ + + def __init__( + self, + name=None, + data_dir=None, + columns_list=None, + data_format="npy", + constraint_type="Label", + random_merge=True, + data_config=None + ): + # Handle configuration source + if data_config is None: + if not name or not data_dir or not columns_list: + raise ValueError( + f"If data_config is None, name/data_dir/columns_list must not be None, " + f"but got name={name}, data_dir={data_dir}, columns_list={columns_list}" + ) + data_config = ExistedDataConfig( + name=name, + data_dir=data_dir, + columns_list=columns_list, + data_format=data_format, + constraint_type=constraint_type, + random_merge=random_merge + ) + + check_param_type(data_config, "data_config", data_type=ExistedDataConfig) + + # Assign from config + name = data_config.name + columns_list = [f"{name}_{col}" for col in data_config.columns_list] + constraint_type = data_config.constraint_type + + self.data_dir = data_config.data_dir + self._data_format = data_config.data_format + self._random_merge = data_config.random_merge + + # Internal dataset storage + self.data = None + self.data_size = None + self.batch_size = 1 + self.shuffle = False + self.batched_data_size = None + self._index = None + + # Data loader mapping + self.load_data = { + "npy": self._load_npy_data + } + + super().__init__(name=name, columns_list=columns_list, constraint_type=constraint_type) + + def _initialization(self, batch_size=1, shuffle=False): + """Load data once before training starts.""" + loader = self.load_data.get(self._data_format.lower()) + if loader is None: + raise ValueError(f"Unsupported data format: {self._data_format}") + + data = loader() + if not isinstance(data, tuple): + data = (data,) + + self.data = data + self.data_size = len(data[0]) + self.batch_size = batch_size + + if batch_size > self.data_size: + raise ValueError( + f"If prebatch data, batch_size={batch_size} cannot exceed data_size={self.data_size}" + ) + + self.batched_data_size = self.data_size // batch_size + self.shuffle = shuffle + self._index = np.arange(self.data_size) + + logger.info( + f"Loaded existed dataset: {self.name}, columns={self.columns_list}, " + f"size={self.data_size}, batched_size={self.batched_data_size}, shuffle={self.shuffle}" + ) + + def __getitem__(self, index): + if self.data is None: + self._initialization() + + if self._random_merge: + # Select random batch index + index = ( + np.random.randint(0, self.batched_data_size) + if index >= self.batched_data_size + else index + ) + else: + index = index % self.batched_data_size + + if self.shuffle and index % self.batched_data_size == 0: + self._index = np.random.permutation(self.data_size) + + col_data = None + for i in range(len(self.columns_list)): + + if self.batch_size == 1: + idx = self._index[index] + else: + idx = self._index[ + index * self.batch_size : (index + 1) * self.batch_size + ] + + temp = self.data[i][idx] + col_data = (temp,) if col_data is None else col_data + (temp,) + + return col_data + + def _load_npy_data(self): + """Load data from npy files.""" + results = [] + for path in self.data_dir: + logger.info(f"Loading npy data from: {path}") + arr = np.load(path).astype(np.float32) + + if arr.ndim < 1: + raise ValueError(f"Loaded npy file must have at least 1 dimension: {path}") + + results.append(arr) + + logger.info(f"Loaded npy dataset size: {len(results[0])}") + return tuple(results) + + def __len__(self): + if self.data is None: + self._initialization() + return self.batched_data_size diff --git a/mindscience/models/neural_operator/fno.py b/mindscience/models/neural_operator/fno.py index edd214bb5..800a1872d 100644 --- a/mindscience/models/neural_operator/fno.py +++ b/mindscience/models/neural_operator/fno.py @@ -94,9 +94,10 @@ class FNOBlocks(nn.Cell): self.resolutions = resolutions if len(self.n_modes) != len(self.resolutions): raise ValueError( - "The dimension of n_modes should be equal to that of resolutions\ - but got dimension of n_modes {} and dimension of resolutions {}".format(len(self.n_modes), - len(self.resolutions))) + f"The dimension of n_modes should be equal to that of resolutions " + f"but got dimension of n_modes {len(self.n_modes)} " + f"and dimension of resolutions {len(self.resolutions)}" + ) self.act = get_activation(act) if isinstance(act, str) else act self.add_residual = add_residual self.dft_compute_dtype = dft_compute_dtype @@ -136,8 +137,10 @@ class FNOBlocks(nn.Cell): has_bias=False, weight_init="HeUniform" ).to_float(self.fno_compute_dtype) else: - raise ValueError("The length of input resolutions dimensions should be in [1, 2, 3], but got: {}".format( - len(self.resolutions))) + raise ValueError( + f"The length of input resolutions dimensions should be in [1, 2, 3], " + f"but got: {len(self.resolutions)}" + ) def construct(self, x: Tensor): if self.add_residual: @@ -247,9 +250,10 @@ class FNO(nn.Cell): self.resolutions = resolutions if len(self.n_modes) != len(self.resolutions): raise ValueError( - "The dimension of n_modes should be equal to that of resolutions\ - but got dimension of n_modes {} and dimension of resolutions {}".format(len(self.n_modes), - len(self.resolutions))) + f"The dimension of n_modes should be equal to that of resolutions " + f"but got dimension of n_modes {len(self.n_modes)} " + f"and dimension of resolutions {len(self.resolutions)}" + ) self.n_layers = n_layers self.data_format = data_format if fnoblock_act == "identity": @@ -324,7 +328,8 @@ class FNO(nn.Cell): output_perm = (0, 2, 3, 4, 1) else: raise ValueError( - "The length of input resolutions dimensions should be in [1, 2, 3], but got: {}".format(n_dim)) + f"The length of input resolutions dimensions should be in [1, 2, 3], but got: {n_dim}" + ) return positional_embedding, input_perm, output_perm @@ -519,13 +524,13 @@ class FNO2D(FNO): resolutions = [resolutions, resolutions] if len(n_modes) != 2: raise ValueError( - "The dimension of n_modes should be equal to 2 when using FNO2D " - "but got dimension of n_modes {}".format(len(n_modes)) + f"The dimension of n_modes should be equal to 2 when using FNO2D " + f"but got dimension of n_modes {len(n_modes)}" ) if len(resolutions) != 2: raise ValueError( - "The dimension of resolutions should be equal to 2 when using FNO2D " - "but got dimension of resolutions {}".format(len(resolutions)) + f"The dimension of resolutions should be equal to 2 when using FNO2D " + f"but got dimension of resolutions {len(resolutions)}" ) super().__init__( in_channels, @@ -637,13 +642,13 @@ class FNO3D(FNO): resolutions = [resolutions, resolutions, resolutions] if len(n_modes) != 3: raise ValueError( - "The dimension of n_modes should be equal to 3 when using FNO3D " - "but got dimension of n_modes {}".format(len(n_modes)) + f"The dimension of n_modes should be equal to 3 when using FNO3D " + f"but got dimension of n_modes {len(n_modes)}" ) if len(resolutions) != 3: raise ValueError( - "The dimension of resolutions should be equal to 3 when using FNO3D " - "but got dimension of resolutions {}".format(len(resolutions)) + f"The dimension of resolutions should be equal to 3 when using FNO3D " + f"but got dimension of resolutions {len(resolutions)}" ) super().__init__( in_channels, -- Gitee