From b6a803277fc42c34c170f7ca157137d65488e00d Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 25 Apr 2023 09:32:24 +0000 Subject: [PATCH 01/10] finishe geometry --- examples/bracket/bracket.py | 377 ++++++++++++++++++++++++ examples/bracket/stl/aux_lower.stl | Bin 0 -> 684 bytes examples/bracket/stl/aux_upper.stl | Bin 0 -> 684 bytes examples/bracket/stl/bracket.stl | Bin 0 -> 684 bytes examples/bracket/stl/cylinder_hole.stl | Bin 0 -> 50084 bytes examples/bracket/stl/cylinder_lower.stl | Bin 0 -> 50084 bytes examples/bracket/stl/cylinder_upper.stl | Bin 0 -> 50084 bytes examples/bracket/stl/support.stl | Bin 0 -> 684 bytes ppsci/constraint/boundary_constraint.py | 4 +- ppsci/data/dataset/array_dataset.py | 6 +- ppsci/equation/__init__.py | 2 + ppsci/equation/pde/__init__.py | 2 + ppsci/equation/pde/linear_elasticity.py | 267 +++++++++++++++++ ppsci/geometry/mesh.py | 16 + ppsci/solver/eval.py | 3 +- ppsci/solver/train.py | 3 +- ppsci/utils/reader.py | 3 +- 17 files changed, 676 insertions(+), 7 deletions(-) create mode 100644 examples/bracket/bracket.py create mode 100644 examples/bracket/stl/aux_lower.stl create mode 100644 examples/bracket/stl/aux_upper.stl create mode 100644 examples/bracket/stl/bracket.stl create mode 100644 examples/bracket/stl/cylinder_hole.stl create mode 100644 examples/bracket/stl/cylinder_lower.stl create mode 100644 examples/bracket/stl/cylinder_upper.stl create mode 100644 examples/bracket/stl/support.stl create mode 100644 ppsci/equation/pde/linear_elasticity.py diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py new file mode 100644 index 000000000..7dda15b61 --- /dev/null +++ b/examples/bracket/bracket.py @@ -0,0 +1,377 @@ +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from paddle import fluid + +import ppsci +from ppsci.utils import config +from ppsci.utils import logger + +if __name__ == "__main__": + # enable prim_eager mode for high-order autodiff + fluid.core.set_prim_eager_enabled(True) + + args = config.parse_args() + # set random seed for reproducibility + ppsci.utils.misc.set_random_seed(42) + # set output directory + output_dir = "./output_bracket" if not args.output_dir else args.output_dir + # initialize logger + logger.init_logger("ppsci", f"{output_dir}/train.log", "info") + + # set model + disp_net = ppsci.arch.MLP(("x", "y", "z"), ("u", "v", "w"), 6, 512, "silu", True) + + stress_net = ppsci.arch.MLP( + ("x", "y", "z"), + ("sigma_xx", "sigma_yy", "sigma_zz", "sigma_xy", "sigma_xz", "sigma_yz"), + 6, + 512, + "silu", + weight_norm=True, + ) + # wrap to a model_list + model = ppsci.arch.ModelList((disp_net, stress_net)) + + # Specify parameters + nu = 0.3 + E = 100.0e9 + lambda_ = nu * E / ((1 + nu) * (1 - 2 * nu)) + mu = E / (2 * (1 + nu)) + mu_c = 0.01 * mu + lambda_ = lambda_ / mu_c + mu = mu / mu_c + characteristic_length = 1.0 + characteristic_displacement = 1.0e-4 + sigma_normalization = characteristic_length / (characteristic_displacement * mu_c) + T = -4.0e4 * sigma_normalization + + # set equation + equation = { + "LinearElasticity": ppsci.equation.LinearElasticity( + E=None, nu=None, lambda_=lambda_, mu=mu, dim=3 + ) + } + + # set geometry + support = ppsci.geometry.Mesh("./stl/support.stl") + bracket = ppsci.geometry.Mesh("./stl/bracket.stl") + aux_lower = ppsci.geometry.Mesh("./stl/aux_lower.stl") + aux_upper = ppsci.geometry.Mesh("./stl/aux_upper.stl") + cylinder_hole = ppsci.geometry.Mesh("./stl/cylinder_hole.stl") + cylinder_lower = ppsci.geometry.Mesh("./stl/cylinder_lower.stl") + cylinder_upper = ppsci.geometry.Mesh("./stl/cylinder_upper.stl") + + curve_lower = aux_lower - cylinder_lower + curve_upper = aux_upper - cylinder_upper + geo = support + bracket + curve_lower + curve_upper - cylinder_hole + + geom = {"geo": ppsci.geometry.Mesh(geo)} + + # set dataloader config + iters_per_epoch = 1000 + train_dataloader_cfg = { + "dataset": "NamedArrayDataset", + "iters_per_epoch": iters_per_epoch, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": True, + }, + } + + support_origin = (-1, -1, -1) + support_dim = (0.25, 2, 2) + bracket_origin = (-0.75, -1, -0.1) + bracket_dim = (1.75, 2, 0.2) + cylinder_radius = 0.1 + cylinder_height = 2.0 + aux_lower_origin = (-0.75, -1, -0.1 - cylinder_radius) + aux_lower_dim = (cylinder_radius, 2, cylinder_radius) + aux_upper_origin = (-0.75, -1, 0.1) + aux_upper_dim = (cylinder_radius, 2, cylinder_radius) + cylinder_lower_center = (-0.75 + cylinder_radius, 0, 0) + cylinder_upper_center = (-0.75 + cylinder_radius, 0, 0) + cylinder_hole_radius = 0.7 + cylinder_hole_height = 0.5 + cylinder_hole_center = (0.125, 0, 0) + + # set constraint + bc_back = ppsci.constraint.BoundaryConstraint( + {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, + {"u": 1, "v": 0, "w": 0}, + geom["rect"], + {**train_dataloader_cfg, "batch_size": 1024}, + ppsci.loss.MSELoss("sum"), + criteria=lambda x, y, z: np.isclose(x, support_origin[0]), + weight_dict={"u": 10, "v": 10, "w": 10}, + name="BC_BACK", + ) + bc_front = ppsci.constraint.BoundaryConstraint( + equation["LinearElasticity"].equations, + {"traction_x": 0, "traction_y": 0, "traction_z": T}, + geom["rect"], + {**train_dataloader_cfg, "batch_size": 128}, + ppsci.loss.MSELoss("sum"), + criteria=lambda x, y, z: np.isclose(x, bracket_origin[0] + bracket_dim[0]), + name="BC_FRONT", + ) + bc_surface = ppsci.constraint.BoundaryConstraint( + equation["LinearElasticity"].equations, + {"traction_x": 0, "traction_y": 0, "traction_z": 0}, + geom["rect"], + {**train_dataloader_cfg, "batch_size": 4096}, + ppsci.loss.MSELoss("sum"), + criteria=lambda x, y, z: np.logical_and( + x > support_origin[0], x < bracket_origin[0] + bracket_dim[0] + ), + name="BC_SURFACE", + ) + support_interior_constraint = ppsci.constraint.InteriorConstraint( + equation["LinearElasticity"].equations, + { + "equilibrium_x": 0, + "equilibrium_y": 0, + "equilibrium_z": 0, + "stress_disp_xx": 0, + "stress_disp_yy": 0, + "stress_disp_zz": 0, + "stress_disp_xy": 0, + "stress_disp_xz": 0, + "stress_disp_yz": 0, + }, + geom["rect"], + {**train_dataloader_cfg, "batch_size": 2048}, + ppsci.loss.MSELoss("sum"), + # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} + # lambda_weighting={ + # "equilibrium_x": Symbol("sdf"), + # "equilibrium_y": Symbol("sdf"), + # "equilibrium_z": Symbol("sdf"), + # "stress_disp_xx": Symbol("sdf"), + # "stress_disp_yy": Symbol("sdf"), + # "stress_disp_zz": Symbol("sdf"), + # "stress_disp_xy": Symbol("sdf"), + # "stress_disp_xz": Symbol("sdf"), + # "stress_disp_yz": Symbol("sdf"), + # } + ) + bracket_interior_constraint = ppsci.constraint.InteriorConstraint( + equation["LinearElasticity"].equations, + { + "equilibrium_x": 0, + "equilibrium_y": 0, + "equilibrium_z": 0, + "stress_disp_xx": 0, + "stress_disp_yy": 0, + "stress_disp_zz": 0, + "stress_disp_xy": 0, + "stress_disp_xz": 0, + "stress_disp_yz": 0, + }, + geom["rect"], + {**train_dataloader_cfg, "batch_size": 2048}, + ppsci.loss.MSELoss("sum"), + # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} + # lambda_weighting={ + # "equilibrium_x": Symbol("sdf"), + # "equilibrium_y": Symbol("sdf"), + # "equilibrium_z": Symbol("sdf"), + # "stress_disp_xx": Symbol("sdf"), + # "stress_disp_yy": Symbol("sdf"), + # "stress_disp_zz": Symbol("sdf"), + # "stress_disp_xy": Symbol("sdf"), + # "stress_disp_xz": Symbol("sdf"), + # "stress_disp_yz": Symbol("sdf"), + # } + ) + # wrap constraints together + constraint = { + bc_back.name: bc_back, + bc_front.name: bc_front, + bc_surface.name: bc_surface, + support_interior_constraint.name: support_interior_constraint, + bracket_interior_constraint.name: bracket_interior_constraint, + } + + # set training hyper-parameters + epochs = 2000 if not args.epochs else args.epochs + lr_scheduler = ppsci.optimizer.lr_scheduler.ExponentialDecay( + epochs, + iters_per_epoch, + 1.0e-3, + 0.95, + 15000, + by_epoch=False, + )() + + # set optimizer + optimizer = ppsci.optimizer.Adam(lr_scheduler)((model,)) + + # set validator + std_xyzu = ppsci.utils.reader.load_csv_file( + "./data/deformation_x.txt", + ("x", "y", "z", "u"), + { + "x": "X Location (m)", + "y": "Y Location (m)", + "z": "Z Location (m)", + "u": "Directional Deformation (m)", + }, + "\t", + ) + std_v = ppsci.utils.reader.load_csv_file( + "./data/deformation_y.txt", + ("v",), + {"v": "Directional Deformation (m)"}, + "\t", + ) + std_w = ppsci.utils.reader.load_csv_file( + "./data/deformation_z.txt", + ("w",), + {"w": "Directional Deformation (m)"}, + "\t", + ) + + std_sxx = ppsci.utils.reader.load_csv_file( + "./data/normal_y.txt", + ("sigma_xx",), + {"sigma_xx": "Normal Stress (Pa)"}, + "\t", + ) + std_syy = ppsci.utils.reader.load_csv_file( + "./data/normal_y.txt", + ("sigma_yy",), + {"sigma_yy": "Normal Stress (Pa)"}, + "\t", + ) + std_szz = ppsci.utils.reader.load_csv_file( + "./data/normal_z.txt", + ("sigma_zz",), + {"sigma_zz": "Normal Stress (Pa)"}, + "\t", + ) + + std_sxy = ppsci.utils.reader.load_csv_file( + "./data/shear_xy.txt", + ("sigma_xy",), + {"sigma_xy": "Shear Stress (Pa)"}, + "\t", + ) + std_sxz = ppsci.utils.reader.load_csv_file( + "./data/shear_xz.txt", + ("sigma_xz",), + {"sigma_xz": "Shear Stress (Pa)"}, + "\t", + ) + std_syz = ppsci.utils.reader.load_csv_file( + "./data/shear_yz.txt", + ("sigma_yz",), + {"sigma_yz": "Shear Stress (Pa)"}, + "\t", + ) + input_dict = { + "x": std_xyzu["x"], + "y": std_xyzu["y"], + "z": std_xyzu["z"], + } + label_dict = { + "u": std_xyzu["u"] / characteristic_displacement, + "v": std_v["v"] / characteristic_displacement, + "w": std_w["w"] / characteristic_displacement, + "sigma_xx": std_sxx["sigma_xx"] * sigma_normalization, + "sigma_yy": std_syy["sigma_yy"] * sigma_normalization, + "sigma_zz": std_szz["sigma_zz"] * sigma_normalization, + "sigma_xy": std_sxy["sigma_xy"] * sigma_normalization, + "sigma_xz": std_sxz["sigma_xz"] * sigma_normalization, + "sigma_yz": std_syz["sigma_yz"] * sigma_normalization, + } + eval_dataloader_cfg = { + "dataset": { + "name": "NamedArrayDataset", + "input": input_dict, + "label": label_dict, + "weight": {key: np.ones_like(label_dict[key]) for key in label_dict}, + }, + "sampler": { + "name": "BatchSampler", + "drop_last": False, + "shuffle": False, + }, + } + sup_validator = ppsci.validate.SupervisedValidator( + {**eval_dataloader_cfg, "batch_size": 128}, + ppsci.loss.MSELoss("mean"), + { + "u": lambda out: out["u"], + "v": lambda out: out["v"], + "w": lambda out: out["w"], + "sigma_xx": lambda out: out["sigma_xx"], + "sigma_yy": lambda out: out["sigma_yy"], + "sigma_zz": lambda out: out["sigma_zz"], + "sigma_xy": lambda out: out["sigma_xy"], + "sigma_xz": lambda out: out["sigma_xz"], + "sigma_yz": lambda out: out["sigma_yz"], + }, + metric={"MSE": ppsci.metric.MSE()}, + ) + validator = {sup_validator.name: sup_validator} + + # set visualizer(optional) + visualizer = { + "visulzie_u_v": ppsci.visualize.VisualizerVtu( + input_dict, + {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, + prefix="result_u_v_w", + ) + } + + # initialize solver + solver = ppsci.solver.Solver( + model, + constraint, + output_dir, + optimizer, + lr_scheduler, + epochs, + iters_per_epoch, + eval_during_train=True, + eval_freq=200, + equation=equation, + geom=geom, + validator=validator, + visualizer=visualizer, + ) + # train model + solver.train() + # evaluate after finished training + solver.eval() + # visualize prediction after finished training + solver.visualize() + + # directly evaluate pretrained model(optional) + solver = ppsci.solver.Solver( + model, + constraint, + output_dir, + equation=equation, + geom=geom, + validator=validator, + visualizer=visualizer, + pretrained_model_path=f"{output_dir}/checkpoints/latest", + ) + solver.eval() + # visualize prediction for pretrained model(optional) + solver.visualize() diff --git a/examples/bracket/stl/aux_lower.stl b/examples/bracket/stl/aux_lower.stl new file mode 100644 index 0000000000000000000000000000000000000000..75f704daba6621b7c717e7feeb6818a63e67f560 GIT binary patch literal 684 zcmbu6%?-jZ3`RWwL-f=I7$8XWScw@xq(_7h7j9Xs%>rR3;`$e*5(jd!qVMI|&hZh? z&v@-FukLh@xBUSBot@$-PeGApWOeNb%if|0Q*VJ^8f$< literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/aux_upper.stl b/examples/bracket/stl/aux_upper.stl new file mode 100644 index 0000000000000000000000000000000000000000..031ea4c320994846a5c12cde0e19feba16dbcb44 GIT binary patch literal 684 zcmbu6Jr2S!3`Ttb4$+AVFt=aLd%3}sd;ay5UO?$4#!Rpcr(h>kddtw}rNcAl_Ybv9IiCSYy zXmV^3KILoI6?@FVy>IxaAM*H=)#~65aW^uBrTngZjr4Cqccl(7piAcXPO&@C-Xu@1 fhTznC-x76SLg;_gl+KaqpPVE6$^EZcSNPHg|316( literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/bracket.stl b/examples/bracket/stl/bracket.stl new file mode 100644 index 0000000000000000000000000000000000000000..3c2d446e7ca4d83da631afaacfa000fee949d44a GIT binary patch literal 684 zcmbu6!41MN3`H|Qhv*KRt0KXj89?NUD#V3b7puBJ*@+&8O7w73#&Pex*kged- be$np74Ez@oQ*c3ZE9g%N5l@)wM0tfb#CN>i literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/cylinder_hole.stl b/examples/bracket/stl/cylinder_hole.stl new file mode 100644 index 0000000000000000000000000000000000000000..928cb146fe1f9a3088654229ae3cefff9cef9200 GIT binary patch literal 50084 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 literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/cylinder_lower.stl b/examples/bracket/stl/cylinder_lower.stl new file mode 100644 index 0000000000000000000000000000000000000000..cfccb775df1d11abc65e72b7296d8804a9c79ab8 GIT binary patch literal 50084 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 literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/cylinder_upper.stl b/examples/bracket/stl/cylinder_upper.stl new file mode 100644 index 0000000000000000000000000000000000000000..2ac96f561389aaaf4579b0d482e9b7d0afb5651d GIT binary patch literal 50084 zcmbuIeXw<9S;m*5rjv@M`Gwf~p7(?}CMswq%DWk5v_aDv%87#1QXIdPG*b(<4Gv=n z;oxAPWE@T(j$a9tW5`+7$c*5NC`3xgM21Qu*(iTtY9H&m?{!_zwa@GGthFcq@g3In zd#-c#Ui(@1bKlqO^X$`K{?a$S{AGK7^R0Vc_=cCi?wAw0|KI=aIE4JNKl;h%Zk~49 z5q;M!`|A!ofB$s{?yrAed2{~zOUItHU&LY{wnt(CVpJ`l8tM4&A`bHKb>G%KApBmm zb#lXFR4t%#zq*I7`?cfKef831sV^qca;eK@wU-vafKR!I#_tmJ1`^x?59=`6=KKnD_ z(f)jls`z}kU){skz2JpMg-82yKC0q#?tXO-U-vsVJTW}l`_-t5{mT979=`6i&wp}w zwD;Xn75lFH)jfRO4_2Y#-TmqwzV5Y$JTpAn z=Z{ep=MVR*d-%Gi-jp8gbJVDcbCmnlJ$&8goPS*W9_{nssEYHT`_(;s-OnAfKRnv! z+EEqfTKB7a_`3W1^k|>YM^&89-LLN9>mGLWB7To{pJ7x*pTYg=9=`6k&Pk7UzhqQJ zzr_9O9=`5L2hyY6_Zd~u_i?|vhp+qKyV9fGzZzB1zjD92$LhMr$NPa!n23XVWL+1k z=!?0Zh=Y1$BN070`d#xI&uMsY-BcC7x`(gJ*X@M67Yz@-K2WXtrOUznL|>Qh|KEP# z=?xFQ&rrEv{rB*7c^{wiE5|fEc)vpBesvFDm!HSOj(kGHgP%L7+^_E8>vCVY>`{+x zcyRxK%KhpdzApF2NVf~$%Y4? zOHjFA-NV=AbNAE_JgDKp=POk1SNHIBSw9?p_K!C_SSLW`esvFDmvzwfSKO!J!FmTO z_p5vOx~%6;dh9(L9<1A-a=*HVugkjhtOGwdPQ*d`F6&RI+)uwCc`uh5RzpLTF zIvOhXt9$smJSQwac4xzb=K-kPukPXN^1O5V-fuKKc&>rU{pudRF3)WjpLkotgXc4- z+^_E8>+<~huA^>gc<`JFmHX8_d|jTSZ@%H58Xi0^L*;&T4_}x4fbTxz#)b#a{ZP4I z-NV;qU*pZM{bIv|{R^nvukPXNvOn{*SAC)3!9EOB?pOElb=hZn)>E!)c(9)YmHX8_ zd|md-t~+p`;laKbRPI;z@O9buYyTeX??L5$bq`;c{mb_I!9FEa?pOElb=ik)-&gFn zLgjvS4_}x4K?u> z=iKeOo%3v{+^_E8>vCS-K7Vj750(4XJ$zl}4%+7^<_n;5zq*I7%lt(9Jjk2`RPI;z z@O7C3X`gGE_kha%>K?u>^DOQ2IddyexnJGG*JUoJ-DhC_1}gWfd-%G{7q$B(%n?E5 zesvFDmpQ3+--mf9sNApa;p;N*)$U(0*9DdP)jfP&&#j3#=sf88H23Se5B!*B@jbV6ThLQP*Mo4n>pphVTNjs|_B;E&^AcW374Kb3 zw*@^#bUg^CyYBj%-n{tC`Iqdw^ULRrS5n2#przY_o+7#)gwtJj{;h9V{Lc&eeTTmO zqvMrSaWAoSThLQP*Mo4n>%RA$S1s;(-fjE7_oB~_S5n12(b8=}PZ3=Y!s)KN$0J|1 zIO3RY^Nnx3W4w|o?(LRt3wny^dJs-`-TO{HWpUlkp__mBi}zgQl~nN=W$Ct{r--fx z;dIxXd*w-sV?Xtn&C72;bdgt5#b>Rh+k&1Vx*mknU3bxQPh6b%kSA=u?VFEY8nVrQ3p@BDx-g(_MGhy`H+b&qtrT ziT}?lsbURh>9(M!h^`0Wbl2hMzu`+KZQ}RIE2&~FYU#G1r--fx;dIyGeYj-Li#PGU z4=MQ-}r@IdK)A!GPK{(xYc>Y~+?YlPdJj^SpVlTteZ9z{FT@S+PuETTuNpCu56VLU$ zk}CFOEZr9L6w&n{obEc*BcD6}-J7US@=B`MTe5Up&{IU$gK)a*P*uefX)?A@V15I)3x3_*&(aRIz7?S9cvEUumb~9o*r2 zo>x-E-Yf2sb%?xUoQ^wjhxcw?Nfmp@xZBqu^3HcUK2baT4Ca+ou@{a{?K(t$f}M_? zVTXH3UP%>u`q+KeA#x{jI(E1n?umINRh&&=7hQ+Q9o6aBsdu=y=ap1(#)7B8Iz&Dh zoQ@|>&u3I#Nfl>Bc&e>K@uUEt*$S#hEaw z*L8@jWSx!*xo1tCS5n27mdobJ{khC6>CoQ@M@&ogIUNfomnIHj&bDF~fq>_BuqKyq%5{ea{|1UP%?RIOrOzLu3cR>F7-K>}BMYR54S8?#DVrc0!zv z4olCTOkPP9vr*{ctV3i+$LZ)4_3SO>l~gg~g>KY3M0Tc}j*eH)9$8*V)sM=mW#(?E z_8LTX*qoNf)=)2Al~+>5pTyE_r4I*>3eoi-R_US6#cQ=S)V){n_kfD8kEPp!)~j0~ zx*mknde1K#>h~SKA5igqwsc$2Q$*K;a9a27Wy3jShxZj!yk9Nd7W5R+^&p(qXYjJ& zJhj8m2UPsrS-LIgDWdB^IIX?pvf*5~!_PTX+&?Vc7W5R+^&p(qo_N`CzTM$|1r_%} zOSc6*MRYv~r?t0l59jn9?z>QNKeu#S&{IU$gK%2UsO_QO(DV5L6`xC%ZVP&f=z0)N z>sh-!^i6s`2chEg)zWQ2PZ3=Y!fDkE+e81S=kpvY)(Mtw3wny^dJs;l_Sqi#SUu|! zs95hx{EK z^x=A*KcM2d#?oy;PZ7hDddM?4t+U$p(1+`Jj)IEkGfTGxJw*&%iXoTbw9cH{Lm#f^ zc@Qd|GcDZ~^c2zcAe`3Od3)%?^*q-?#q+YI+k&1Vx*mkn>H%yIeYl?IbEtUkw{%<3 zQ$*K;a9X{L?V%6Xv(Eq(`xlmO3wny^dJs;lC$l~D;d=HqBK~E8=&JHlE7XMb~E?Mml^lpO z4s!rlmx%;Rw~gnIF`ZB$@*L%~5)g4#W1a!)GFf5iw(%S_rYtH%o(G+d6C|=9apue` zsbWIJ(rx2;a7>F-h&9NSOSg@EhB1XyA+lfMw31HIlgTToVuH%jZDYS=OjA{e z?E5&a#8z~Q@=B_h1haJ8*!LMzVHG0#S57N=79B6<$gr+w;MO6s!{)R+l!rU(7f#tz zzU?jhex8$S6W=}e$h{>G$FAY{n{O}=M-^YUl82i^eEZsW?=5+_?0)J!S00WkzW*f; zH-~u3S3kSAyN6ee(qyB@dTV4(;vA z!%@Y3zU1NN5LdkJ6&ocFm(vxSXD;W>6lED%KAr4>yO{^R){%N**q!fU13zhog#hP|3s1Ax=H}(v6ab z%W0-+IOXA}Vm(*#aC3-L&;Rg7$;0JTR<)?|a8$7_EqS;(#1Fsw*Bd1dm(yj{)XKwA z#rnGB;pPxKe|FVI$-@nIdwiqZ?mQe-JSUVq+#KST4_>`d@^Crr*BM87II4KwDS5a# z#ItWcuu<}GnOe|UO?fz~cy23sxH-g!PP}oW9+KJRDW*2b4VA9OA}L{Od-^!)2;Ry$t2y zsA6BE9Def2QQ&<`92#&u?y&JY1%%)LT*>jw<$K@9;tEyl zCzm`N?<-7QJwfLL?|i4#*^bX3u299kc*(=@^MR?WC+M8uC)jCq=wmOz6{^_ZFL^kA z&M|fM1f3JyiJXo-aU~B&73UNs56ArqQ&&&WIl&#(X-!OGZ^sp?IBzL=IPSZcx_W}n z2|gK|*5oIiQMf`C=SC$D$L9y8uAZQCf=@W7HNlGPIj&H}`B%xq@i~a8t0(B3;FH>E zP2!?vz!j=E$18a_KF=|A^#q+0tT>$3L@{a~T%n5d$dZR+U4p5rC+M7D<>a&`qfx`* z3RRq|mOLEmD@i$Qad3qy<_=08j^__d zT|GhP1W!m#D*+K_HC&;J`H7N;<2edbS5MG6!IPfTapqjf!%@W?NXf(TJcy~QC+M8u ziPdQ(QsV54D^xMhQu1&-*JA4G2|6cu@^)Iun&<)G3RTSIlsp{I=a{;Bg3bwc5S&&* zD0&&VLKX8xB@f3w1E#K?pmTzq5T})Nik=LvP{o{7$-}WG3y@4kWHn#aFcC;j$a4SNBH}hrRysmzF$Sc0cu=M<*5QQpG!?+_dvfpA*2XiRuujJr9929)U|v6;IOLX3Zh>%GXPih5;0jed2_bhdpLq2{`z;Vo>#PgHc?*Qo>d8dX z30J6M=LR{c`NXZq-Mj_DY4w&Ov4tyCu>*y?*L>pY3;uZvgwyJgDGx^#`*0->mnpP1 z@o)FOWvk@jG8?B}pYm{2v7cD-aB~R$gifnxsdO7v?2DE>9Dk49c{nHdN;|FItI};$ zvADmN$BBx_dT*<>x#ra3c!)2PTO>jqbS~C`<+o<9kr{v)>P1h#)WN=!u zBBk4?;ykM4;WACvCisMN`rteqRh+ApJY1&f+614}PHT3jbQ@Ki50*S!rs>)QD-NeM zgH*bWD$Y4e9xl^#ZGx4P)0(9!-9{DXwIvUiX}UJS3eaiIgq3ciigV|Zhs!iwn_wmD zv}W5%w^7CUdC9|NnyyW-B6nIddZpW_Vh*6>;WACvCU~-NT3G|7+o)omq2%E*P1h!P zLULM}38mYpVlJcP;WACvCV0|wI?kLcc{r+=FDZFAo*;MU;hf-!)oEo|ly0MnIhm4& zs+hkjc{uhHc|(QQhy6?Z_jv?7n1>^+_gr~6s&HqptLlDj0yOh* zq;>Bq4@VX5aCV2?uT6kv9*(p=gUZ8Eg-A|a8zN(VYlA>+5~9k z;Ye#wR345h?3|oBxL=z9%{&}w?bOP{QH3V}rzh^$CO|U}M_NxD<>9EplZ;a!_iGcN znTI2-C#UjoRN;xtX_ou73DC^LkyZtuJRDW1EI5^OzcvAyc{tLlWR!=a3KbHki|*GZ zKr;_VS{0e{a8#kv+5~9k;Yh2pR345hRIHr#x?h_B%{&}wRmjT2QH9EzQ)~BY z6QG%gBdtn5X7po4A1a(6IDL1&HUSUj;YjPmqC6Z`I0-RD;C^iaH1lwzb@Eakjw+n! zn1*n_HUXM>IMO;nDi22$PNqzCxL=z9%{&}worIN#qY5W%rc>OnO@L+|j~voeAaPs6xksX(9J(6QG%gBdrdL@^Dn4bHmh=`?U$s%)^mZZ%KJL zs?dRAddvOV1Zd{rNUKMtJRDV?TXVlQ0h)O@rry-KQyz{g&#k#%o8V9Aw0f4x!%>Ak zC126`1Zcb;PRBdAl5V34{Z`%?^9j(nubhrMaV6bG75c`!!{-yA@%eB%K2a;_HmcD7 z<|lPN0UDokr(e%IT=cR?=-$VSdU=Z$1GUb)wTzS+1npsKOkW73+Kg zH0oWaqe5Ouw^4<8HY@M>1ZdRlPDiD`l5V34b9tU1<`bZC{%|@@EGy|Ys*o?>NoYO+ z8s{jdJhwKV0F864)5@?Y4@Z^f*5(tSaXxoi zSsdlzsPf#}d;&E33{ERkq&yr|o?DwwfJVQ>X=S67hoj1KYx4=v==(UW#Fo-+RC#V~ zJ^>p2E2ouJQyz{gIMUkNm4~AW`#I+= z?$;(jGY>~v&nV^LsKRrJb0ha_6QG%gBduqx@^Dn)`O5j1`?U$s%)^mZ&7eFSRj3m< z$8*0n0h)O@(yD!828ng4LcPOzr2DlA(9FY;Rt={-995{>I9GMQHUXM>IMS*`m4~AW z^(W`U?$;(jGY>~vHMR0^RH2UMoZJ1{1Zd{rNb78%JRDUx4{%=Zer*CY^Khhf#!()Q zDx7PWJ8-`?0h)O@(mJat4@VWwXUtEyUz-5UJRE7AIhBW_3g=AbK-{lQfMyCvGV?6%*Cs$S4@X)(0OjGR!nvQh9QSJzpqYmwtzL%ma8#jx!F-YXwF%J7!;w}` zMtL}@(1&48%Kh2|Xy)NatGA>)998HiF}>w}Z2~m&aHQ4oQXY;f^u?H4bH6qLnt3?V zLEvveC=W*!`g{CI+^D)d`k6QHqQIUPIP zN*<0X%p^8g-)6n(#&~iYruM4$O*mJ^>o_uG5-SD-TB%=Gm;g=M$h&w>zzgyYg^U zVJ^=T#C!rY&L2)IIiNfoRmd0cBs8A@jdPUKN95<<$uQH2~4 zJ09~1(CC*qt)!Fka8w}=#m>!q0yO$QPAlUTy(L_s3b`(Jpym^x(Z6z9$us5QsPcT; zd;&E3a8Ap^@^DM#;Xt4}I40=EE*}2!7yo)hUU6cnJRE7gR+fiL6<;@WIq`eUC6>y= zfdH-d-12a#!ut#ry0z{%MJ$zvBdvSa@^Gob{R$Pj&h9rwER}~Nt(EGLWL=r`%Mu`<>5%H zX0SY5s!%6Dg=wGrO%Y4w;Yh3Y*~B;Z=DMk39fYZ+`%Mu`<>BA~S~Z;I;ZlXV4Ju4u z-EWFmDi23mwW#IcQib{xDol~xZ;Dtd4@X)xwdLVbg*qB4OvBx8iWu(vfpA)91Ixpu zisuAO_17Vm%ERF&1g$fU<>69=a}88TC%E4fu~Z(8w9aalhf5XCXHX$!;eJ!ZQh7Mi zI&)ecE>$>ZLWQ)5`%Mu`<>5%{>}+|sRN=e~6;dzmH$^O!ha;^XfaT#*g>yetNbk7c z6tPqujUdcmE>-A@L1o_$7xWadR346W5WD_fYO2uR<4@v#Q$+rRPOAfH zdAL-ePYDnEez@`X*!}%*UFSr;(oUJucQine0bRR z!-d}zkvoyovBT}k!=(!I2zc1{!-d}zkvpo>nwZ4io>x+Zxe7e&`{BZGipVE})0+Im zGb*p73iBa&;7PU)kxw|MHNlE!ZC*(g=3MZ=(|H{tpVUrk5*IZ?UP%?^b?~t7hYPB~{25z{9>DF8rp5JRv!)1Vo(G@=B_ZlYobPKV0}t5yPFY zzn7Zyu>0)#wl}-k$BE|A>sX}fA9`^lk;WtI($=hiqYb+0! zD&%kAVc!oIep5tt5S&&*$ntQhLXHR?_Wf|-H$`M8#Azj+EDx6|?_>sY0#`9`^lkW2b2M_ru{X!B3JzcBY(G^33vZsT#kpE&5{X5ZNztS{{~%OBL6( z?}saWxLtoQ)rqC@aCjAxJ~-W$D!!uVa<2NdiKX&z@Bpp%-12a#;`@wlt@}+8OXcC< z0a|yW<>6ArI~-kS_nRV?%EQ3}v_4Umhf5Vdspy`&-xRS_9u6L$wKG^AE>+xdFa>bG zDPpNS96Ug4hqF9fs<`iBn&Ey^#8P=Uc!1VUZF#s<@d<#bjQdRyOXcC<0a{NS%fqFL zPclrG+;56lDh~$_(0X!O9xhdUB4bMCepAF!c{q50Rs~>rxKy#Sz_ic(rii8TaPR=F z+GiWz+{?NIS6CroYUzGc#8P=Ucz{+7XL-0(v2Mfk)%~W3rSfp_0IkZ>@^GnQ#fmAi z`%Mu`<>BA~S{1V8;ZnuQ8`E(2n<9o+p?p7F{99)O%fqFL=K)Oh-EWFmDi4R>1GLUK zmWN9f&oxLVxZe~pJgLj~!^OXK^0GW!s(7M9%EJAoh@neSz8^0BJ?uXD4da%FOBGM1 zNQ=1N6tPqu4zCqxorEn9mnxpHk$Q2zDPpNS96UhlL~nVxRIyWl^p5*Y5liLa-~n2l z3CqK!iX9K6klb&ISSk+(576q#SRO7_?A#zt<$hDdQh7LdfL3qG@^GnQ2g<%5ZWggr z9u6L$)$y`CT&ma?v+swSMWEK=-%IV{-|F019xhevC))SJ%_8zAbXpxq%fqFLeNnuk z>k#=$JFQNt<>6Arek<;bb%?xUoQ^wj|L|8AEDx6|_Kk6euS4XW?{s{k_78t`!SZma zV*eYT)OCpb1Unr&!~Wr~E?6EeRqW$q$61HSoyh6f;dbTWQpI@$cF}c++))Zl;Yqd*kxw|MHS@AOT&g(d!qa&jBA?VwM+LAL z{_29|;ZntU9V&};h^#oAj!I@R{M7}^!=;LIM^s4b5Lr1nt;wk6;ZnuRIho(iL7^>jtY4({M7}^!=;MzY*gOs5LuBs9hLrK z_^S(+hf5Xb@;E`PL*&WA={T`0hQGRCdAL+DUx1U)Iz*n3oQ{*%V)&~ImWN9fa}qew ztwZEV&*?Zp?#jcZig^#5OxGdu#OidMgcrkKU9db{s+e2B340wPPu@<)iGDHs)dkDL zrHc6*bPCoXvV-7sbS4(VUtO>~T&kEOLdRnrB0C{YE9qo;xKuF@h0e`7M0RwXR>sTn haH(Rh3mvF+i0n)`t*n~m;Zo)Kv~`H=mpR>a{|7;!Y3cv~ literal 0 HcmV?d00001 diff --git a/examples/bracket/stl/support.stl b/examples/bracket/stl/support.stl new file mode 100644 index 0000000000000000000000000000000000000000..aca0cf913eaed40b07b8d86f97b897e68b92b839 GIT binary patch literal 684 zcmbu6K@P$&3o-l6A6UJMD3*muI~V z*Vk}<)cgKa{)=7W^I?_qQVKQqF*NejIBQOvGMq^5gnYb-kPG={hbw7$yM()3i(vhr zy!MD&&!s`H!HnUq)Nsko3L@e=b&2K{P5m1Im-o{!TH-%wf)CfWM=)KYO%hf71@^kd SGzsMD(axj8_f6IfqS0@zrh)MQ literal 0 HcmV?d00001 diff --git a/ppsci/constraint/boundary_constraint.py b/ppsci/constraint/boundary_constraint.py index 2685914e4..ae7551aad 100644 --- a/ppsci/constraint/boundary_constraint.py +++ b/ppsci/constraint/boundary_constraint.py @@ -48,7 +48,7 @@ class BoundaryConstraint(base.Constraint): Defaults to None. evenly (bool, optional): Whether to use evenly distribution sampling. Defaults to False. - weight_dict (Optional[Dict[str, Callable]]): Define the weight of each + weight_dict (Optional[Dict[str, Union[float, Callable]]]): Define the weight of each constraint variable. Defaults to None. name (str, optional): Name of constraint object. Defaults to "BC". """ @@ -63,7 +63,7 @@ def __init__( random: Literal["pseudo", "LHS"] = "pseudo", criteria: Optional[Callable] = None, evenly: bool = False, - weight_dict: Optional[Dict[str, Callable]] = None, + weight_dict: Optional[Dict[str, Union[float, Callable]]] = None, name: str = "BC", ): self.label_expr = label_expr diff --git a/ppsci/data/dataset/array_dataset.py b/ppsci/data/dataset/array_dataset.py index 24af0b05d..595c5f1fe 100644 --- a/ppsci/data/dataset/array_dataset.py +++ b/ppsci/data/dataset/array_dataset.py @@ -28,7 +28,7 @@ class NamedArrayDataset(io.Dataset): input (Dict[str, np.ndarray]): Input dict. label (Dict[str, np.ndarray]): Label dict. weight (Dict[str, np.ndarray], optional): Weight dict. - transforms (vision.Compose, optional): Compose object contains sample wise + transforms (Optional[vision.Compose]): Compose object contains sample wise transform(s). """ @@ -37,11 +37,13 @@ def __init__( input: Dict[str, np.ndarray], label: Dict[str, np.ndarray], weight: Dict[str, np.ndarray], - transforms: vision.Compose = None, + transforms: Optional[vision.Compose] = None, ): super().__init__() self.input = input self.label = label + self.input_keys = tuple(input.keys()) + self.label_keys = tuple(label.keys()) self.weight = weight self.transforms = transforms self._len = len(next(iter(input.values()))) diff --git a/ppsci/equation/__init__.py b/ppsci/equation/__init__.py index 06a47d774..c8aecbdb3 100644 --- a/ppsci/equation/__init__.py +++ b/ppsci/equation/__init__.py @@ -15,6 +15,7 @@ import copy from ppsci.equation.pde import PDE +from ppsci.equation.pde import LinearElasticity from ppsci.equation.pde import NavierStokes from ppsci.equation.pde import NormalDotVec from ppsci.equation.pde import Vibration @@ -23,6 +24,7 @@ __all__ = [ "PDE", + "LinearElasticity", "NavierStokes", "NormalDotVec", "Vibration", diff --git a/ppsci/equation/pde/__init__.py b/ppsci/equation/pde/__init__.py index ebef48fd6..e29e59884 100644 --- a/ppsci/equation/pde/__init__.py +++ b/ppsci/equation/pde/__init__.py @@ -14,6 +14,7 @@ from ppsci.equation.pde.base import PDE from ppsci.equation.pde.laplace import Laplace +from ppsci.equation.pde.linear_elasticity import LinearElasticity from ppsci.equation.pde.navier_stokes import NavierStokes from ppsci.equation.pde.normal_dot_vec import NormalDotVec from ppsci.equation.pde.viv import Vibration @@ -21,6 +22,7 @@ __all__ = [ "PDE", "Laplace", + "LinearElasticity", "NavierStokes", "NormalDotVec", "Vibration", diff --git a/ppsci/equation/pde/linear_elasticity.py b/ppsci/equation/pde/linear_elasticity.py new file mode 100644 index 000000000..02093bb0a --- /dev/null +++ b/ppsci/equation/pde/linear_elasticity.py @@ -0,0 +1,267 @@ +"""Equations related to linear elasticity +""" + +from typing import Optional + +from ppsci.autodiff import hessian +from ppsci.autodiff import jacobian +from ppsci.equation.pde import base + + +class LinearElasticity(base.PDE): + """Linear elasticity equations. + Use either (E, nu) or (lambda_, mu) to define the material properties. + + Args: + E (Optional[float]): The Young's modulus. Defaults to None. + nu (Optional[float]): The Poisson's ratio. Defaults to None. + lambda_ (Optional[float]): Lamé's first parameter. Defaults to None. + mu (Optional[float], optional): Lamé's second parameter (shear modulus). Defaults to None. Defaults to None. + rho (float, optional): Mass density.. Defaults to 1. + dim (int, optional): Dimension of the linear elasticity (2 or 3). Defaults to 3. + time (bool, optional): Whether contains time data. Defaults to False. + """ + + def __init__( + self, + E: Optional[float], + nu: Optional[float], + lambda_: Optional[float], + mu: Optional[float], + rho: float = 1, + dim: int = 3, + time: bool = False, + ): + super().__init__() + self.E = E + self.nu = nu + self.lambda_ = lambda_ + self.mu = mu + self.rho = rho + self.dim = dim + self.time = time + + # Stress equations + def stress_disp_xx_compute_func(out): + x, y, u, v = out["x"], out["y"], out["u"], out["v"] + sigma_xx = out["sigma_xx"] + stress_disp_xx = ( + self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + + 2 * self.mu * jacobian(u, x) + - sigma_xx + ) + if self.dim == 3: + z, w = out["z"], out["w"] + stress_disp_xx += self.lambda_ * jacobian(w, z) + return stress_disp_xx + + self.add_equation("stress_disp_xx", stress_disp_xx_compute_func) + + def stress_disp_yy_compute_func(out): + x, y, u, v = out["x"], out["y"], out["u"], out["v"] + sigma_yy = out["sigma_yy"] + stress_disp_yy = ( + self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + + 2 * self.mu * jacobian(v, y) + - sigma_yy + ) + if self.dim == 3: + z, w = out["z"], out["w"] + stress_disp_yy += self.lambda_ * jacobian(w, z) + return stress_disp_yy + + self.add_equation("stress_disp_yy", stress_disp_yy_compute_func) + + if self.dim == 3: + + def stress_disp_zz_compute_func(out): + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) + sigma_zz = out["sigma_zz"] + stress_disp_zz = ( + self.lambda_ * (jacobian(u, x) + jacobian(v, y) + jacobian(w, z)) + + 2 * self.mu * jacobian(w, z) + - sigma_zz + ) + return stress_disp_zz + + self.add_equation("stress_disp_zz", stress_disp_zz_compute_func) + + def stress_disp_xy_compute_func(out): + x, y, u, v = out["x"], out["y"], out["u"], out["v"] + sigma_xy = out["sigma_xy"] + stress_disp_xy = self.mu * (jacobian(u, y) + jacobian(v, x)) - sigma_xy + return stress_disp_xy + + self.add_equation("stress_disp_xy", stress_disp_xy_compute_func) + + if self.dim == 3: + + def stress_disp_xz_compute_func(out): + x, z, u, w = out["x"], out["z"], out["u"], out["w"] + sigma_xz = out["sigma_xz"] + stress_disp_xz = self.mu * (jacobian(u, z) + jacobian(w, x)) - sigma_xz + return stress_disp_xz + + self.add_equation("stress_disp_xz", stress_disp_xz_compute_func) + + def stress_disp_yz_compute_func(out): + y, z, v, w = out["y"], out["z"], out["v"], out["w"] + sigma_yz = out["sigma_yz"] + stress_disp_yz = self.mu * (jacobian(v, z) + jacobian(w, y)) - sigma_yz + return stress_disp_yz + + self.add_equation("stress_disp_yz", stress_disp_yz_compute_func) + + # Equations of equilibrium + def equilibrium_x_compute_func(out): + x, y = out["x"], out["y"] + sigma_xx, sigma_xy = out["sigma_xx"], out["sigma_xy"] + equilibrium_x = -(jacobian(sigma_xx, x) + jacobian(sigma_xy, y)) + if self.dim == 3: + z = out["z"] + sigma_xz = out["sigma_xz"] + equilibrium_x -= jacobian(sigma_xz, z) + if self.time: + t, u = out["t"], out["u"] + equilibrium_x += self.rho * hessian(u, t) + return equilibrium_x + + self.add_equation("equilibrium_x", equilibrium_x_compute_func) + + def equilibrium_y_compute_func(out): + x, y = out["x"], out["y"] + sigma_xy, sigma_yy = out["sigma_xy"], out["sigma_yy"] + equilibrium_y = -(jacobian(sigma_xy, x) + jacobian(sigma_yy, y)) + if self.dim == 3: + z = out["z"] + sigma_yz = out["sigma_yz"] + equilibrium_y -= jacobian(sigma_yz, z) + if self.time: + t, v = out["t"], out["v"] + equilibrium_y += self.rho * hessian(v, t) + return equilibrium_y + + self.add_equation("equilibrium_y", equilibrium_y_compute_func) + + if self.dim == 3: + + def equilibrium_z_compute_func(out): + x, y, z = out["x"], out["y"], out["z"] + sigma_xz, sigma_yz, sigma_zz = ( + out["sigma_xz"], + out["sigma_yz"], + out["sigma_zz"], + ) + equilibrium_z = -( + jacobian(sigma_xz, x) + + jacobian(sigma_yz, y) + + jacobian(sigma_zz, z) + ) + if self.time: + t, w = out["t"], out["w"] + equilibrium_z += self.rho * hessian(w, t) + return equilibrium_z + + self.add_equation("equilibrium_z", equilibrium_z_compute_func) + + # Traction equations + def traction_x_compute_func(out): + normal_x, normal_y = out["normal_x"], out["normal_y"] + sigma_xx, sigma_xy = out["sigma_xx"], out["sigma_xy"] + traction_x = normal_x * sigma_xx + normal_y * sigma_xy + if self.dim == 3: + normal_z = out["normal_z"] + sigma_xz = out["sigma_xz"] + traction_x += normal_z * sigma_xz + return traction_x + + self.add_equation("traction_x", traction_x_compute_func) + + def traction_y_compute_func(out): + normal_x, normal_y = out["normal_x"], out["normal_y"] + sigma_xy, sigma_yy = out["sigma_xy"], out["sigma_yy"] + traction_y = normal_x * sigma_xy + normal_y * sigma_yy + if self.dim == 3: + normal_z = out["normal_z"] + sigma_yz = out["sigma_yz"] + traction_y += normal_z * sigma_yz + return traction_y + + self.add_equation("traction_y", traction_y_compute_func) + + def traction_z_compute_func(out): + normal_x, normal_y, normal_z = ( + out["normal_x"], + out["normal_y"], + out["normal_z"], + ) + sigma_xz, sigma_yz, sigma_zz = ( + out["sigma_xz"], + out["sigma_yz"], + out["sigma_zz"], + ) + traction_z = normal_x * sigma_xz + normal_y * sigma_yz + normal_z * sigma_zz + return traction_z + + self.add_equation("traction_z", traction_z_compute_func) + + # Navier equations + def navier_x_compute_func(out): + x, y, u, v = out["x"], out["y"], out["u"], out["v"] + duxvywz = jacobian(u, x) + jacobian(v, y) + duxxuyyuzz = hessian(u, x) + hessian(u, y) + if self.dim == 3: + z, w = out["z"], out["w"] + duxvywz += jacobian(w, z) + duxxuyyuzz += hessian(u, z) + navier_x = -(lambda_ + mu) * jacobian(duxvywz, x) - mu * duxxuyyuzz + if self.time: + t = out["t"] + navier_x += rho * hessian(u, t) + return navier_x + + self.add_equation("navier_x", navier_x_compute_func) + + def navier_y_compute_func(out): + x, y, u, v = out["x"], out["y"], out["u"], out["v"] + duxvywz = jacobian(u, x) + jacobian(v, y) + dvxxvyyvzz = hessian(v, x) + hessian(v, y) + if self.dim == 3: + z, w = out["z"], out["w"] + duxvywz += jacobian(w, z) + dvxxvyyvzz += hessian(v, z) + navier_y = -(lambda_ + mu) * jacobian(duxvywz, y) - mu * dvxxvyyvzz + if self.time: + t = out["t"] + navier_y += rho * hessian(v, t) + return navier_y + + self.add_equation("navier_y", navier_y_compute_func) + + if self.dim == 3: + + def navier_z_compute_func(out): + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) + duxvywz = jacobian(u, x) + jacobian(v, y) + jacobian(w, z) + dwxxvyyvzz = hessian(w, x) + hessian(w, y) + hessian(w, z) + navier_z = -(lambda_ + mu) * jacobian(duxvywz, z) - mu * dwxxvyyvzz + if self.time: + t = out["t"] + navier_z += rho * hessian(w, t) + return navier_z + + self.add_equation("navier_z", navier_z_compute_func) diff --git a/ppsci/geometry/mesh.py b/ppsci/geometry/mesh.py index 2227d59ad..270bceeca 100644 --- a/ppsci/geometry/mesh.py +++ b/ppsci/geometry/mesh.py @@ -17,6 +17,7 @@ from typing import Union import numpy as np +import pymesh import pysdf from ppsci.geometry import geometry @@ -388,13 +389,24 @@ def sample_interior(self, n, random="pseudo", criteria=None, evenly=False): return {**x_dict, **area_dict} def union(self, rhs): + if not checker.dynamic_import_to_globals(["pymesh"]): + raise ModuleNotFoundError + import pymesh + csg = pymesh.CSGTree({"union": [{"mesh": self.py_mesh}, {"mesh": rhs.py_mesh}]}) return Mesh(csg.mesh) def __or__(self, rhs): return self.union(rhs) + def __add__(self, rhs): + return self.union(rhs) + def difference(self, rhs): + if not checker.dynamic_import_to_globals(["pymesh"]): + raise ModuleNotFoundError + import pymesh + csg = pymesh.CSGTree( {"difference": [{"mesh": self.py_mesh}, {"mesh": rhs.py_mesh}]} ) @@ -404,6 +416,10 @@ def __sub__(self, rhs): return self.difference(rhs) def intersection(self, rhs): + if not checker.dynamic_import_to_globals(["pymesh"]): + raise ModuleNotFoundError + import pymesh + csg = pymesh.CSGTree( {"intersection": [{"mesh": self.py_mesh}, {"mesh": rhs.py_mesh}]} ) diff --git a/ppsci/solver/eval.py b/ppsci/solver/eval.py index 7394c35db..18d0733fb 100644 --- a/ppsci/solver/eval.py +++ b/ppsci/solver/eval.py @@ -64,7 +64,8 @@ def eval_func(solver, epoch_id, log_freq) -> float: _validator.input_keys, _validator.output_keys, solver.model ) for label_name, label_formula in _validator.label_expr.items(): - evaluator.add_target_expr(label_formula, label_name) + if label_name in label_dict: + evaluator.add_target_expr(label_formula, label_name) # forward if solver.use_amp: diff --git a/ppsci/solver/train.py b/ppsci/solver/train.py index a18b10898..ac99951a5 100644 --- a/ppsci/solver/train.py +++ b/ppsci/solver/train.py @@ -58,7 +58,8 @@ def train_epoch_func(solver, epoch_id, log_freq): _constraint.input_keys, _constraint.output_keys, solver.model ) for label_name, label_formula in _constraint.label_expr.items(): - evaluator.add_target_expr(label_formula, label_name) + if label_name in label_dict: + evaluator.add_target_expr(label_formula, label_name) # forward for every constraint if solver.use_amp: diff --git a/ppsci/utils/reader.py b/ppsci/utils/reader.py index d0b936d2f..934470327 100644 --- a/ppsci/utils/reader.py +++ b/ppsci/utils/reader.py @@ -28,6 +28,7 @@ def load_csv_file( file_path: str, keys: Tuple[str, ...], alias_dict: Optional[Dict[str, str]] = None, + delimeter: str = ",", encoding: str = "utf-8", ) -> Dict[str, np.ndarray]: """Load *.csv file and fetch data as given keys. @@ -48,7 +49,7 @@ def load_csv_file( try: # read all data from csv file with open(file_path, "r", encoding=encoding) as csv_file: - reader = csv.DictReader(csv_file) + reader = csv.DictReader(csv_file, delimiter=delimeter) raw_data = collections.defaultdict(list) for _, line_dict in enumerate(reader): for key, value in line_dict.items(): From e4444f5f321ea3c949ab080675eaae7ed37cff73 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Thu, 4 May 2023 02:26:37 +0000 Subject: [PATCH 02/10] =?UTF-8?q?update=20bracket=20code(=E9=99=A4constrai?= =?UTF-8?q?nt=E5=A4=96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/bracket/bracket.py | 115 +++++++++++------------------- ppsci/arch/mlp.py | 30 +++++++- ppsci/autodiff/ad.py | 8 +-- ppsci/data/__init__.py | 1 - ppsci/data/dataset/csv_dataset.py | 1 + ppsci/solver/solver.py | 4 +- 6 files changed, 80 insertions(+), 79 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 1a8133c2b..defade32d 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -55,39 +55,6 @@ ) logger.info("load pytorch's init weight") - rand_input = { - "x": paddle.to_tensor( - np.load( - "/workspace/hesensen/modulus/examples/bracket/outputs/bracket/x.npy" - ), - stop_gradient=False, - ), - "y": paddle.to_tensor( - np.load( - "/workspace/hesensen/modulus/examples/bracket/outputs/bracket/y.npy" - ), - stop_gradient=False, - ), - "z": paddle.to_tensor( - np.load( - "/workspace/hesensen/modulus/examples/bracket/outputs/bracket/z.npy" - ), - stop_gradient=False, - ), - } - output_disp = disp_net(rand_input) - for k, v in output_disp.items(): - print( - f"disp_net: {k} {tuple(v.shape)} {v.min().item():.10f} {v.max().item():.10f} {v.mean().item():.10f} {v.std().item():.10f}" - ) - - output_stress = stress_net(rand_input) - for k, v in output_stress.items(): - print( - f"stress_net: {k} {tuple(v.shape)} {v.min().item():.10f} {v.max().item():.10f} {v.mean().item():.10f} {v.std().item():.10f}" - ) - exit() - # Specify parameters nu = 0.3 E = 100.0e9 @@ -133,6 +100,7 @@ "drop_last": False, "shuffle": True, }, + "num_workers": 2, } support_origin = (-1, -1, -1) @@ -154,7 +122,7 @@ # set constraint bc_back = ppsci.constraint.BoundaryConstraint( {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, - {"u": 1, "v": 0, "w": 0}, + {"u": 0, "v": 0, "w": 0}, geom["geo"], {**train_dataloader_cfg, "batch_size": 1024}, ppsci.loss.MSELoss("sum"), @@ -199,17 +167,18 @@ {**train_dataloader_cfg, "batch_size": 2048}, ppsci.loss.MSELoss("sum"), # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} - # lambda_weighting={ - # "equilibrium_x": Symbol("sdf"), - # "equilibrium_y": Symbol("sdf"), - # "equilibrium_z": Symbol("sdf"), - # "stress_disp_xx": Symbol("sdf"), - # "stress_disp_yy": Symbol("sdf"), - # "stress_disp_zz": Symbol("sdf"), - # "stress_disp_xy": Symbol("sdf"), - # "stress_disp_xz": Symbol("sdf"), - # "stress_disp_yz": Symbol("sdf"), + # weight={ + # "equilibrium_x": "sdf", + # "equilibrium_y": "sdf", + # "equilibrium_z": "sdf", + # "stress_disp_xx": "sdf", + # "stress_disp_yy": "sdf", + # "stress_disp_zz": "sdf", + # "stress_disp_xy": "sdf", + # "stress_disp_xz": "sdf", + # "stress_disp_yz": "sdf", # } + name="support_interior", ) bracket_interior_constraint = ppsci.constraint.InteriorConstraint( equation["LinearElasticity"].equations, @@ -225,20 +194,21 @@ "stress_disp_yz": 0, }, geom["geo"], - {**train_dataloader_cfg, "batch_size": 2048}, + {**train_dataloader_cfg, "batch_size": 1024}, ppsci.loss.MSELoss("sum"), # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} - # lambda_weighting={ - # "equilibrium_x": Symbol("sdf"), - # "equilibrium_y": Symbol("sdf"), - # "equilibrium_z": Symbol("sdf"), - # "stress_disp_xx": Symbol("sdf"), - # "stress_disp_yy": Symbol("sdf"), - # "stress_disp_zz": Symbol("sdf"), - # "stress_disp_xy": Symbol("sdf"), - # "stress_disp_xz": Symbol("sdf"), - # "stress_disp_yz": Symbol("sdf"), + # weight={ + # "equilibrium_x": "sdf", + # "equilibrium_y": "sdf", + # "equilibrium_z": "sdf", + # "stress_disp_xx": "sdf", + # "stress_disp_yy": "sdf", + # "stress_disp_zz": "sdf", + # "stress_disp_xy": "sdf", + # "stress_disp_xz": "sdf", + # "stress_disp_yz": "sdf", # } + name="bracket_interior", ) # wrap constraints together constraint = { @@ -390,9 +360,10 @@ lr_scheduler, epochs, iters_per_epoch, + save_freq=20, eval_during_train=True, - log_freq=1, - eval_freq=200, + log_freq=20, + eval_freq=10, equation=equation, geom=geom, validator=validator, @@ -400,23 +371,23 @@ ) # train model solver.train() - exit() + # evaluate after finished training solver.eval() # visualize prediction after finished training solver.visualize() - # directly evaluate pretrained model(optional) - solver = ppsci.solver.Solver( - model, - constraint, - output_dir, - equation=equation, - geom=geom, - validator=validator, - visualizer=visualizer, - pretrained_model_path=f"{output_dir}/checkpoints/latest", - ) - solver.eval() - # visualize prediction for pretrained model(optional) - solver.visualize() + # # directly evaluate pretrained model(optional) + # solver = ppsci.solver.Solver( + # model, + # constraint, + # output_dir, + # equation=equation, + # geom=geom, + # validator=validator, + # visualizer=visualizer, + # pretrained_model_path=f"{output_dir}/checkpoints/latest", + # ) + # solver.eval() + # # visualize prediction for pretrained model(optional) + # solver.visualize() diff --git a/ppsci/arch/mlp.py b/ppsci/arch/mlp.py index 981751e83..4f8a16336 100644 --- a/ppsci/arch/mlp.py +++ b/ppsci/arch/mlp.py @@ -15,10 +15,37 @@ from typing import Tuple from typing import Union +import paddle import paddle.nn as nn from ppsci.arch import activation as act_mod from ppsci.arch import base +from ppsci.utils import initializer + + +class WeightNormLinear(nn.Layer): + def __init__(self, in_features: int, out_features: int, bias: bool = True) -> None: + super().__init__() + self.in_features = in_features + self.out_features = out_features + self.weight_v = paddle.create_parameter((in_features, out_features), "float32") + self.weight_g = paddle.create_parameter((out_features,), "float32") + if bias: + self.bias = paddle.create_parameter((out_features,), "float32") + else: + self.bias = None + self.reset_parameters() + + def reset_parameters(self) -> None: + initializer.xavier_uniform_(self.weight_v) + initializer.constant_(self.weight_g, 1.0) + if self.bias is not None: + initializer.constant_(self.bias, 0.0) + + def forward(self, input): + norm = self.weight_v.norm(p=2, axis=0, keepdim=True) + weight = self.weight_g * self.weight_v / norm + return nn.functional.linear(input, weight, self.bias) class MLP(base.NetBase): @@ -75,7 +102,8 @@ def __init__( for _size in hidden_size: self.linears.append(nn.Linear(cur_size, _size)) if weight_norm: - self.linears[-1] = nn.utils.weight_norm(self.linears[-1], dim=1) + # self.linears[-1] = nn.utils.weight_norm(self.linears[-1], dim=1) + self.linears[-1] = WeightNormLinear(cur_size, _size) cur_size = _size self.linears = nn.LayerList(self.linears) diff --git a/ppsci/autodiff/ad.py b/ppsci/autodiff/ad.py index e98e5801e..8f0835749 100644 --- a/ppsci/autodiff/ad.py +++ b/ppsci/autodiff/ad.py @@ -85,10 +85,10 @@ def __call__( Examples: >>> import ppsci - >>> x = paddle.randn([4, 3]) + >>> x = paddle.randn([4, 1]) >>> x.stop_gradient = False - >>> y = (x * x).sum() - >>> dy_dx = ppsci.autodiff.jacoian(y, x) # doctest: +SKIP + >>> y = x * x + >>> dy_dx = ppsci.autodiff.jacobian(y, x) """ key = (ys, xs) if key not in self.Js: @@ -187,7 +187,7 @@ def __call__( >>> x = paddle.randn([4, 3]) >>> x.stop_gradient = False >>> y = (x * x).sin() - >>> dy_dxx = ppsci.autodiff.hessian(y, x, component=0) # doctest: +SKIP + >>> dy_dxx = ppsci.autodiff.hessian(y, x, component=0) """ key = (ys, xs, component) if key not in self.Hs: diff --git a/ppsci/data/__init__.py b/ppsci/data/__init__.py index ef4cdccce..dbbee4048 100644 --- a/ppsci/data/__init__.py +++ b/ppsci/data/__init__.py @@ -17,7 +17,6 @@ from functools import partial import numpy as np -import paddle import paddle.device as device import paddle.distributed as dist import paddle.io as io diff --git a/ppsci/data/dataset/csv_dataset.py b/ppsci/data/dataset/csv_dataset.py index a828d20bf..ac0682190 100644 --- a/ppsci/data/dataset/csv_dataset.py +++ b/ppsci/data/dataset/csv_dataset.py @@ -20,6 +20,7 @@ from typing import Union import numpy as np +import paddle from paddle import io from paddle import vision diff --git a/ppsci/solver/solver.py b/ppsci/solver/solver.py index 7969337be..317fbeed2 100644 --- a/ppsci/solver/solver.py +++ b/ppsci/solver/solver.py @@ -428,7 +428,9 @@ def visualize(self, epoch_id=0): self.model.train() @paddle.no_grad() - def predict(self, input_dict: Dict[str, paddle.Tensor], batch_size: int = 64): + def predict( + self, input_dict: Dict[str, paddle.Tensor], batch_size: int = 64 + ) -> Dict[str, paddle.Tensor]: """Pure prediction using model.forward(...), support single device prediction yet. Args: From 8bc0fe7ee57d5cc98ccc1665439586af18f504bb Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Thu, 4 May 2023 08:53:13 +0000 Subject: [PATCH 03/10] =?UTF-8?q?update=20training=20code(=E5=8C=85?= =?UTF-8?q?=E5=90=ABnan=E7=9A=84debug=E4=BB=A3=E7=A0=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/bracket/bracket.py | 3 +- ppsci/constraint/boundary_constraint.py | 68 ++++++++++++++--------- ppsci/constraint/interior_constraint.py | 73 +++++++++++++++---------- ppsci/solver/train.py | 27 +++++++++ 4 files changed, 116 insertions(+), 55 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index defade32d..a7ac6b2dc 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -360,7 +360,7 @@ lr_scheduler, epochs, iters_per_epoch, - save_freq=20, + save_freq=10, eval_during_train=True, log_freq=20, eval_freq=10, @@ -368,6 +368,7 @@ geom=geom, validator=validator, visualizer=visualizer, + checkpoint_path="./output_bracket/checkpoints/epoch_20", ) # train model solver.train() diff --git a/ppsci/constraint/boundary_constraint.py b/ppsci/constraint/boundary_constraint.py index d85a21fc7..90621c411 100644 --- a/ppsci/constraint/boundary_constraint.py +++ b/ppsci/constraint/boundary_constraint.py @@ -98,38 +98,54 @@ def __init__( criteria = eval(criteria) # prepare input - input = geom.sample_boundary( - dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], - random, - criteria, - evenly, + # input = geom.sample_boundary( + # dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], + # random, + # criteria, + # evenly, + # ) + input = np.load( + f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.input.npz" ) + input = dict(input) + for k in input: + input[k] = input[k].astype("float32") + if "area" in input: input["area"] *= dataloader_cfg["iters_per_epoch"] + # for v in input: + # print(f"BoundaryConstraint.input: {v} {input[v].shape} {input[v].mean().item():.10f} {input[v].std().item():.10f}") + # prepare label label = {} - for key, value in label_dict.items(): - if isinstance(value, (int, float)): - label[key] = np.full_like(next(iter(input.values())), float(value)) - elif isinstance(value, sympy.Basic): - func = sympy.lambdify( - sympy.symbols(geom.dim_keys), - value, - [{"amax": lambda xy, axis: np.maximum(xy[0], xy[1])}, "numpy"], - ) - label[key] = func( - **{k: v for k, v in input.items() if k in geom.dim_keys} - ) - elif isinstance(value, types.FunctionType): - func = value - label[key] = func(input) - if isinstance(label[key], (int, float)): - label[key] = np.full_like( - next(iter(input.values())), float(label[key]) - ) - else: - raise NotImplementedError(f"type of {type(value)} is invalid yet.") + # for key, value in label_dict.items(): + # if isinstance(value, (int, float)): + # label[key] = np.full_like(next(iter(input.values())), float(value)) + # elif isinstance(value, sympy.Basic): + # func = sympy.lambdify( + # sympy.symbols(geom.dim_keys), + # value, + # [{"amax": lambda xy, axis: np.maximum(xy[0], xy[1])}, "numpy"], + # ) + # label[key] = func( + # **{k: v for k, v in input.items() if k in geom.dim_keys} + # ) + # elif isinstance(value, types.FunctionType): + # func = value + # label[key] = func(input) + # if isinstance(label[key], (int, float)): + # label[key] = np.full_like( + # next(iter(input.values())), float(label[key]) + # ) + # else: + # raise NotImplementedError(f"type of {type(value)} is invalid yet.") + label = np.load( + f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.label.npz" + ) + label = dict(label) + for k in label: + label[k] = label[k].astype("float32") # prepare weight weight = {key: np.ones_like(next(iter(label.values()))) for key in label} diff --git a/ppsci/constraint/interior_constraint.py b/ppsci/constraint/interior_constraint.py index 1645f357b..f0ce04280 100644 --- a/ppsci/constraint/interior_constraint.py +++ b/ppsci/constraint/interior_constraint.py @@ -98,43 +98,60 @@ def __init__( criteria = eval(criteria) # prepare input - input = geom.sample_interior( - dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], - random, - criteria, - evenly, + # input = geom.sample_interior( + # dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], + # random, + # criteria, + # evenly, + # ) + input = np.load( + f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.input.npz" ) + input = dict(input) + for k in input: + input[k] = input[k].astype("float32") if "area" in input: input["area"] *= dataloader_cfg["iters_per_epoch"] + # for v in input: + # print(f"InteriorConstraint.input: {v} {input[v].shape} {input[v].mean().item():.10f} {input[v].std().item():.10f}") + # prepare label label = {} - for key, value in label_dict.items(): - if isinstance(value, str): - value = sp_parser.parse_expr(value) - if isinstance(value, (int, float)): - label[key] = np.full_like(next(iter(input.values())), float(value)) - elif isinstance(value, sympy.Basic): - func = sympy.lambdify( - sympy.symbols(geom.dim_keys), - value, - [{"amax": lambda xy, _: np.maximum(xy[0], xy[1])}, "numpy"], - ) - label[key] = func( - **{k: v for k, v in input.items() if k in geom.dim_keys} - ) - elif isinstance(value, types.FunctionType): - func = value - label[key] = func(input) - if isinstance(label[key], (int, float)): - label[key] = np.full_like( - next(iter(input.values())), float(label[key]) - ) - else: - raise NotImplementedError(f"type of {type(value)} is invalid yet.") + # for key, value in label_dict.items(): + # if isinstance(value, str): + # value = sp_parser.parse_expr(value) + # if isinstance(value, (int, float)): + # label[key] = np.full_like(next(iter(input.values())), float(value)) + # elif isinstance(value, sympy.Basic): + # func = sympy.lambdify( + # sympy.symbols(geom.dim_keys), + # value, + # [{"amax": lambda xy, _: np.maximum(xy[0], xy[1])}, "numpy"], + # ) + # label[key] = func( + # **{k: v for k, v in input.items() if k in geom.dim_keys} + # ) + # elif isinstance(value, types.FunctionType): + # func = value + # label[key] = func(input) + # if isinstance(label[key], (int, float)): + # label[key] = np.full_like( + # next(iter(input.values())), float(label[key]) + # ) + # else: + # raise NotImplementedError(f"type of {type(value)} is invalid yet.") + label = np.load( + f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.label.npz" + ) + label = dict(label) + for k in label: + label[k] = label[k].astype("float32") # prepare weight weight = {key: np.ones_like(next(iter(label.values()))) for key in label} + weight = {key: input["sdf"].astype("float32") for key in weight} + input.pop("sdf") if weight_dict is not None: for key, value in weight_dict.items(): if isinstance(value, str): diff --git a/ppsci/solver/train.py b/ppsci/solver/train.py index 25205607d..708273379 100644 --- a/ppsci/solver/train.py +++ b/ppsci/solver/train.py @@ -18,6 +18,7 @@ from ppsci.solver import printer from ppsci.utils import expression +from ppsci.utils import logger from ppsci.utils import misc from ppsci.utils import profiler @@ -93,6 +94,32 @@ def train_epoch_func(solver, epoch_id, log_freq): solver.lr_scheduler.step() else: total_loss.backward() + max_grad = None + max_grad_name = None + max_param = None + max_param_name = None + for name, param in solver.model.named_parameters(): + if hasattr(param, "grad") and param.grad is not None: + if max_grad is None or param.grad.abs().max().item() > max_grad: + max_grad = param.grad.abs().max().item() + max_grad_name = name + if max_param is None or param.abs().max().item() > max_param: + max_param = param.abs().max().item() + max_param_name = name + if max_grad > 1e3: + logger.warning( + f"current max_grad ({max_grad:.5f}) is bigger than 1e3, " + f"layer name is {max_grad_name}" + ) + if max_param > 1e5: + logger.warning( + f"current max_param ({max_param:.5f}) is bigger than 1e5, " + f"layer name is {max_param_name}" + ) + # exit() + # else: + # logger.info(f"max_grad = {max_grad:.10f} at layer name {max_grad_name}") + if iter_id % solver.update_freq == 0: solver.optimizer.step() solver.optimizer.clear_grad() From a7bf4e4681396cd2a0540f58b48d8b74aa89c82b Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Wed, 10 May 2023 06:29:46 +0000 Subject: [PATCH 04/10] update newest code --- examples/bracket/bracket.py | 91 ++++++------ ppsci/arch/activation.py | 12 +- ppsci/arch/mlp.py | 16 ++- ppsci/equation/pde/linear_elasticity.py | 176 ++++++++++++++++-------- ppsci/loss/mse.py | 4 + ppsci/solver/train.py | 27 ---- ppsci/solver/visu.py | 4 +- ppsci/visualize/visualizer.py | 1 + 8 files changed, 194 insertions(+), 137 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index a7ac6b2dc..1ab52c80a 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Reference: https://www.mathworks.com/help/pde/ug/deflection-analysis-of-a-bracket.html +""" + import numpy as np import paddle from paddle import fluid @@ -25,16 +29,24 @@ fluid.core.set_prim_eager_enabled(True) args = config.parse_args() + # paddle.set_default_dtype("float64") # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - output_dir = "./output_bracket" if not args.output_dir else args.output_dir + output_dir = ( + "./output_bracket_eager_True_mse_stable_silu" + if not args.output_dir + else args.output_dir + ) + # output_dir = "./output_bracket_eager_True_mse_paddle_WN_lr_decay" if not args.output_dir else args.output_dir # initialize logger logger.init_logger("ppsci", f"{output_dir}/train.log", "info") # set model + act_str = "silu" + wn = True disp_net = ppsci.arch.MLP( - ("x", "y", "z"), ("u", "v", "w"), 6, 512, "silu", weight_norm=True + ("x", "y", "z"), ("u", "v", "w"), 6, 512, act_str, weight_norm=wn ) stress_net = ppsci.arch.MLP( @@ -42,8 +54,8 @@ ("sigma_xx", "sigma_yy", "sigma_zz", "sigma_xy", "sigma_xz", "sigma_yz"), 6, 512, - "silu", - weight_norm=True, + act_str, + weight_norm=wn, ) # wrap to a model_list model = ppsci.arch.ModelList((disp_net, stress_net)) @@ -97,29 +109,17 @@ "iters_per_epoch": iters_per_epoch, "sampler": { "name": "BatchSampler", - "drop_last": False, + "drop_last": True, "shuffle": True, }, - "num_workers": 2, + "num_workers": 1, } + # set constraint support_origin = (-1, -1, -1) - support_dim = (0.25, 2, 2) bracket_origin = (-0.75, -1, -0.1) bracket_dim = (1.75, 2, 0.2) cylinder_radius = 0.1 - cylinder_height = 2.0 - aux_lower_origin = (-0.75, -1, -0.1 - cylinder_radius) - aux_lower_dim = (cylinder_radius, 2, cylinder_radius) - aux_upper_origin = (-0.75, -1, 0.1) - aux_upper_dim = (cylinder_radius, 2, cylinder_radius) - cylinder_lower_center = (-0.75 + cylinder_radius, 0, 0) - cylinder_upper_center = (-0.75 + cylinder_radius, 0, 0) - cylinder_hole_radius = 0.7 - cylinder_hole_height = 0.5 - cylinder_hole_center = (0.125, 0, 0) - - # set constraint bc_back = ppsci.constraint.BoundaryConstraint( {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, {"u": 0, "v": 0, "w": 0}, @@ -224,7 +224,7 @@ lr_scheduler = ppsci.optimizer.lr_scheduler.ExponentialDecay( epochs, iters_per_epoch, - 1.0e-3, + 0.001, 0.95, 15000, by_epoch=False, @@ -234,7 +234,7 @@ optimizer = ppsci.optimizer.Adam(lr_scheduler)((model,)) # set validator - std_xyzu = ppsci.utils.reader.load_csv_file( + ref_xyzu = ppsci.utils.reader.load_csv_file( "./data/deformation_x.txt", ("x", "y", "z", "u"), { @@ -245,71 +245,72 @@ }, "\t", ) - std_v = ppsci.utils.reader.load_csv_file( + ref_v = ppsci.utils.reader.load_csv_file( "./data/deformation_y.txt", ("v",), {"v": "Directional Deformation (m)"}, "\t", ) - std_w = ppsci.utils.reader.load_csv_file( + ref_w = ppsci.utils.reader.load_csv_file( "./data/deformation_z.txt", ("w",), {"w": "Directional Deformation (m)"}, "\t", ) - std_sxx = ppsci.utils.reader.load_csv_file( - "./data/normal_y.txt", + ref_sxx = ppsci.utils.reader.load_csv_file( + "./data/normal_x.txt", ("sigma_xx",), {"sigma_xx": "Normal Stress (Pa)"}, "\t", ) - std_syy = ppsci.utils.reader.load_csv_file( + ref_syy = ppsci.utils.reader.load_csv_file( "./data/normal_y.txt", ("sigma_yy",), {"sigma_yy": "Normal Stress (Pa)"}, "\t", ) - std_szz = ppsci.utils.reader.load_csv_file( + ref_szz = ppsci.utils.reader.load_csv_file( "./data/normal_z.txt", ("sigma_zz",), {"sigma_zz": "Normal Stress (Pa)"}, "\t", ) - std_sxy = ppsci.utils.reader.load_csv_file( + ref_sxy = ppsci.utils.reader.load_csv_file( "./data/shear_xy.txt", ("sigma_xy",), {"sigma_xy": "Shear Stress (Pa)"}, "\t", ) - std_sxz = ppsci.utils.reader.load_csv_file( + ref_sxz = ppsci.utils.reader.load_csv_file( "./data/shear_xz.txt", ("sigma_xz",), {"sigma_xz": "Shear Stress (Pa)"}, "\t", ) - std_syz = ppsci.utils.reader.load_csv_file( + ref_syz = ppsci.utils.reader.load_csv_file( "./data/shear_yz.txt", ("sigma_yz",), {"sigma_yz": "Shear Stress (Pa)"}, "\t", ) + input_dict = { - "x": std_xyzu["x"], - "y": std_xyzu["y"], - "z": std_xyzu["z"], + "x": ref_xyzu["x"], + "y": ref_xyzu["y"], + "z": ref_xyzu["z"], } label_dict = { - "u": std_xyzu["u"] / characteristic_displacement, - "v": std_v["v"] / characteristic_displacement, - "w": std_w["w"] / characteristic_displacement, - "sigma_xx": std_sxx["sigma_xx"] * sigma_normalization, - "sigma_yy": std_syy["sigma_yy"] * sigma_normalization, - "sigma_zz": std_szz["sigma_zz"] * sigma_normalization, - "sigma_xy": std_sxy["sigma_xy"] * sigma_normalization, - "sigma_xz": std_sxz["sigma_xz"] * sigma_normalization, - "sigma_yz": std_syz["sigma_yz"] * sigma_normalization, + "u": ref_xyzu["u"] / characteristic_displacement, + "v": ref_v["v"] / characteristic_displacement, + "w": ref_w["w"] / characteristic_displacement, + "sigma_xx": ref_sxx["sigma_xx"] * sigma_normalization, + "sigma_yy": ref_syy["sigma_yy"] * sigma_normalization, + "sigma_zz": ref_szz["sigma_zz"] * sigma_normalization, + "sigma_xy": ref_sxy["sigma_xy"] * sigma_normalization, + "sigma_xz": ref_sxz["sigma_xz"] * sigma_normalization, + "sigma_yz": ref_syz["sigma_yz"] * sigma_normalization, } eval_dataloader_cfg = { "dataset": { @@ -339,6 +340,7 @@ "sigma_yz": lambda out: out["sigma_yz"], }, metric={"MSE": ppsci.metric.MSE()}, + name="commercial_ref_u_v_w_sigma", ) validator = {sup_validator.name: sup_validator} @@ -360,15 +362,14 @@ lr_scheduler, epochs, iters_per_epoch, - save_freq=10, + save_freq=20, eval_during_train=True, log_freq=20, - eval_freq=10, + eval_freq=20, equation=equation, geom=geom, validator=validator, visualizer=visualizer, - checkpoint_path="./output_bracket/checkpoints/epoch_20", ) # train model solver.train() diff --git a/ppsci/arch/activation.py b/ppsci/arch/activation.py index 2e287e024..e08bae5f5 100644 --- a/ppsci/arch/activation.py +++ b/ppsci/arch/activation.py @@ -18,15 +18,23 @@ import paddle.nn as nn import paddle.nn.functional as F + +def stable_silu(x): + """ + numeric stable silu, prevent from Nan in backward. + """ + return x * F.sigmoid(x) + + act_func_dict = { "elu": F.elu, "relu": F.relu, "selu": F.selu, "sigmoid": F.sigmoid, - "silu": F.silu, + "silu": stable_silu, "sin": paddle.sin, "cos": paddle.cos, - "swish": F.silu, + "swish": stable_silu, "tanh": F.tanh, "identity": nn.Identity(), } diff --git a/ppsci/arch/mlp.py b/ppsci/arch/mlp.py index 4f8a16336..ccd7be1c7 100644 --- a/ppsci/arch/mlp.py +++ b/ppsci/arch/mlp.py @@ -28,15 +28,21 @@ def __init__(self, in_features: int, out_features: int, bias: bool = True) -> No super().__init__() self.in_features = in_features self.out_features = out_features - self.weight_v = paddle.create_parameter((in_features, out_features), "float32") - self.weight_g = paddle.create_parameter((out_features,), "float32") + self.weight_v = paddle.create_parameter( + (in_features, out_features), paddle.get_default_dtype() + ) + self.weight_g = paddle.create_parameter( + (out_features,), paddle.get_default_dtype() + ) if bias: - self.bias = paddle.create_parameter((out_features,), "float32") + self.bias = paddle.create_parameter( + (out_features,), paddle.get_default_dtype() + ) else: self.bias = None - self.reset_parameters() + self._init_weights() - def reset_parameters(self) -> None: + def _init_weights(self) -> None: initializer.xavier_uniform_(self.weight_v) initializer.constant_(self.weight_g, 1.0) if self.bias is not None: diff --git a/ppsci/equation/pde/linear_elasticity.py b/ppsci/equation/pde/linear_elasticity.py index 02093bb0a..c124ff0fd 100644 --- a/ppsci/equation/pde/linear_elasticity.py +++ b/ppsci/equation/pde/linear_elasticity.py @@ -43,31 +43,45 @@ def __init__( # Stress equations def stress_disp_xx_compute_func(out): - x, y, u, v = out["x"], out["y"], out["u"], out["v"] + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) sigma_xx = out["sigma_xx"] stress_disp_xx = ( - self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + self.lambda_ * (jacobian(u, x) + jacobian(v, y) + jacobian(w, z)) + 2 * self.mu * jacobian(u, x) - sigma_xx ) - if self.dim == 3: - z, w = out["z"], out["w"] - stress_disp_xx += self.lambda_ * jacobian(w, z) + # if self.dim == 3: + # z, w = out["z"], out["w"] + # stress_disp_xx += self.lambda_ * jacobian(w, z) return stress_disp_xx self.add_equation("stress_disp_xx", stress_disp_xx_compute_func) def stress_disp_yy_compute_func(out): - x, y, u, v = out["x"], out["y"], out["u"], out["v"] + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) sigma_yy = out["sigma_yy"] stress_disp_yy = ( - self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + self.lambda_ * (jacobian(u, x) + jacobian(v, y) + jacobian(w, z)) + 2 * self.mu * jacobian(v, y) - sigma_yy ) - if self.dim == 3: - z, w = out["z"], out["w"] - stress_disp_yy += self.lambda_ * jacobian(w, z) + # if self.dim == 3: + # z, w = out["z"], out["w"] + # stress_disp_yy += self.lambda_ * jacobian(w, z) return stress_disp_yy self.add_equation("stress_disp_yy", stress_disp_yy_compute_func) @@ -121,13 +135,19 @@ def stress_disp_yz_compute_func(out): # Equations of equilibrium def equilibrium_x_compute_func(out): - x, y = out["x"], out["y"] - sigma_xx, sigma_xy = out["sigma_xx"], out["sigma_xy"] - equilibrium_x = -(jacobian(sigma_xx, x) + jacobian(sigma_xy, y)) - if self.dim == 3: - z = out["z"] - sigma_xz = out["sigma_xz"] - equilibrium_x -= jacobian(sigma_xz, z) + x, y, z = out["x"], out["y"], out["z"] + sigma_xx, sigma_xy, sigma_xz = ( + out["sigma_xx"], + out["sigma_xy"], + out["sigma_xz"], + ) + equilibrium_x = -( + jacobian(sigma_xx, x) + jacobian(sigma_xy, y) + jacobian(sigma_xz, z) + ) + # if self.dim == 3: + # z = out["z"] + # sigma_xz = out["sigma_xz"] + # equilibrium_x -= jacobian(sigma_xz, z) if self.time: t, u = out["t"], out["u"] equilibrium_x += self.rho * hessian(u, t) @@ -136,13 +156,19 @@ def equilibrium_x_compute_func(out): self.add_equation("equilibrium_x", equilibrium_x_compute_func) def equilibrium_y_compute_func(out): - x, y = out["x"], out["y"] - sigma_xy, sigma_yy = out["sigma_xy"], out["sigma_yy"] - equilibrium_y = -(jacobian(sigma_xy, x) + jacobian(sigma_yy, y)) - if self.dim == 3: - z = out["z"] - sigma_yz = out["sigma_yz"] - equilibrium_y -= jacobian(sigma_yz, z) + x, y, z = out["x"], out["y"], out["z"] + sigma_xy, sigma_yy, sigma_yz = ( + out["sigma_xy"], + out["sigma_yy"], + out["sigma_yz"], + ) + equilibrium_y = -( + jacobian(sigma_xy, x) + jacobian(sigma_yy, y) + jacobian(sigma_yz, z) + ) + # if self.dim == 3: + # z = out["z"] + # sigma_yz = out["sigma_yz"] + # equilibrium_y -= jacobian(sigma_yz, z) if self.time: t, v = out["t"], out["v"] equilibrium_y += self.rho * hessian(v, t) @@ -173,25 +199,41 @@ def equilibrium_z_compute_func(out): # Traction equations def traction_x_compute_func(out): - normal_x, normal_y = out["normal_x"], out["normal_y"] - sigma_xx, sigma_xy = out["sigma_xx"], out["sigma_xy"] - traction_x = normal_x * sigma_xx + normal_y * sigma_xy - if self.dim == 3: - normal_z = out["normal_z"] - sigma_xz = out["sigma_xz"] - traction_x += normal_z * sigma_xz + normal_x, normal_y, normal_z = ( + out["normal_x"], + out["normal_y"], + out["normal_z"], + ) + sigma_xx, sigma_xy, sigma_xz = ( + out["sigma_xx"], + out["sigma_xy"], + out["sigma_xz"], + ) + traction_x = normal_x * sigma_xx + normal_y * sigma_xy + normal_z * sigma_xz + # if self.dim == 3: + # normal_z = out["normal_z"] + # sigma_xz = out["sigma_xz"] + # traction_x += normal_z * sigma_xz return traction_x self.add_equation("traction_x", traction_x_compute_func) def traction_y_compute_func(out): - normal_x, normal_y = out["normal_x"], out["normal_y"] - sigma_xy, sigma_yy = out["sigma_xy"], out["sigma_yy"] - traction_y = normal_x * sigma_xy + normal_y * sigma_yy - if self.dim == 3: - normal_z = out["normal_z"] - sigma_yz = out["sigma_yz"] - traction_y += normal_z * sigma_yz + normal_x, normal_y, normal_z = ( + out["normal_x"], + out["normal_y"], + out["normal_z"], + ) + sigma_xy, sigma_yy, sigma_yz = ( + out["sigma_xy"], + out["sigma_yy"], + out["sigma_yz"], + ) + traction_y = normal_x * sigma_xy + normal_y * sigma_yy + normal_z * sigma_yz + # if self.dim == 3: + # normal_z = out["normal_z"] + # sigma_yz = out["sigma_yz"] + # traction_y += normal_z * sigma_yz return traction_y self.add_equation("traction_y", traction_y_compute_func) @@ -214,14 +256,23 @@ def traction_z_compute_func(out): # Navier equations def navier_x_compute_func(out): - x, y, u, v = out["x"], out["y"], out["u"], out["v"] - duxvywz = jacobian(u, x) + jacobian(v, y) - duxxuyyuzz = hessian(u, x) + hessian(u, y) - if self.dim == 3: - z, w = out["z"], out["w"] - duxvywz += jacobian(w, z) - duxxuyyuzz += hessian(u, z) - navier_x = -(lambda_ + mu) * jacobian(duxvywz, x) - mu * duxxuyyuzz + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) + # duxvywz = jacobian(u, x) + jacobian(v, y) + # duxxuyyuzz = hessian(u, x) + hessian(u, y) + # if self.dim == 3: + # z, w = out["z"], out["w"] + # duxvywz += jacobian(w, z) + # duxxuyyuzz += hessian(u, z) + navier_x = -(self.lambda_ + self.mu) * ( + jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), x) + ) - self.mu * (hessian(u, x) + hessian(u, y) + hessian(u, z)) if self.time: t = out["t"] navier_x += rho * hessian(u, t) @@ -230,14 +281,23 @@ def navier_x_compute_func(out): self.add_equation("navier_x", navier_x_compute_func) def navier_y_compute_func(out): - x, y, u, v = out["x"], out["y"], out["u"], out["v"] - duxvywz = jacobian(u, x) + jacobian(v, y) - dvxxvyyvzz = hessian(v, x) + hessian(v, y) - if self.dim == 3: - z, w = out["z"], out["w"] - duxvywz += jacobian(w, z) - dvxxvyyvzz += hessian(v, z) - navier_y = -(lambda_ + mu) * jacobian(duxvywz, y) - mu * dvxxvyyvzz + x, y, z, u, v, w = ( + out["x"], + out["y"], + out["z"], + out["u"], + out["v"], + out["w"], + ) + # duxvywz = jacobian(u, x) + jacobian(v, y) + # dvxxvyyvzz = hessian(v, x) + hessian(v, y) + # if self.dim == 3: + # z, w = out["z"], out["w"] + # duxvywz += jacobian(w, z) + # dvxxvyyvzz += hessian(v, z) + navier_y = -(self.lambda_ + self.mu) * ( + jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), y) + ) - self.mu * (hessian(v, x) + hessian(v, y) + hessian(v, z)) if self.time: t = out["t"] navier_y += rho * hessian(v, t) @@ -256,9 +316,11 @@ def navier_z_compute_func(out): out["v"], out["w"], ) - duxvywz = jacobian(u, x) + jacobian(v, y) + jacobian(w, z) - dwxxvyyvzz = hessian(w, x) + hessian(w, y) + hessian(w, z) - navier_z = -(lambda_ + mu) * jacobian(duxvywz, z) - mu * dwxxvyyvzz + # duxvywz = jacobian(u, x) + jacobian(v, y) + jacobian(w, z) + # dwxxvyyvzz = hessian(w, x) + hessian(w, y) + hessian(w, z) + navier_z = -(self.lambda_ + self.mu) * ( + jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), z) + ) - self.mu * (hessian(w, x) + hessian(w, y) + hessian(w, z)) if self.time: t = out["t"] navier_z += rho * hessian(w, t) diff --git a/ppsci/loss/mse.py b/ppsci/loss/mse.py index e72f5f5b4..29e545ecb 100644 --- a/ppsci/loss/mse.py +++ b/ppsci/loss/mse.py @@ -21,6 +21,8 @@ from ppsci.loss import base +# import paddle + class MSELoss(base.LossBase): r"""Class for mean squared error loss. @@ -53,6 +55,8 @@ def forward(self, output_dict, label_dict, weight_dict=None): losses = 0.0 for key in label_dict: loss = F.mse_loss(output_dict[key], label_dict[key], "none") + # loss = paddle.abs(output_dict[key] - label_dict[key]).pow(2) + if weight_dict is not None: loss *= weight_dict[key] if "area" in output_dict: diff --git a/ppsci/solver/train.py b/ppsci/solver/train.py index 708273379..25205607d 100644 --- a/ppsci/solver/train.py +++ b/ppsci/solver/train.py @@ -18,7 +18,6 @@ from ppsci.solver import printer from ppsci.utils import expression -from ppsci.utils import logger from ppsci.utils import misc from ppsci.utils import profiler @@ -94,32 +93,6 @@ def train_epoch_func(solver, epoch_id, log_freq): solver.lr_scheduler.step() else: total_loss.backward() - max_grad = None - max_grad_name = None - max_param = None - max_param_name = None - for name, param in solver.model.named_parameters(): - if hasattr(param, "grad") and param.grad is not None: - if max_grad is None or param.grad.abs().max().item() > max_grad: - max_grad = param.grad.abs().max().item() - max_grad_name = name - if max_param is None or param.abs().max().item() > max_param: - max_param = param.abs().max().item() - max_param_name = name - if max_grad > 1e3: - logger.warning( - f"current max_grad ({max_grad:.5f}) is bigger than 1e3, " - f"layer name is {max_grad_name}" - ) - if max_param > 1e5: - logger.warning( - f"current max_param ({max_param:.5f}) is bigger than 1e5, " - f"layer name is {max_param_name}" - ) - # exit() - # else: - # logger.info(f"max_grad = {max_grad:.10f} at layer name {max_grad_name}") - if iter_id % solver.update_freq == 0: solver.optimizer.step() solver.optimizer.clear_grad() diff --git a/ppsci/solver/visu.py b/ppsci/solver/visu.py index d2cd4c236..a56a171cc 100644 --- a/ppsci/solver/visu.py +++ b/ppsci/solver/visu.py @@ -49,7 +49,9 @@ def visualize_func(solver, epoch_id): # prepare batch input dict for key in input_dict: if not paddle.is_tensor(input_dict[key]): - batch_input_dict[key] = paddle.to_tensor(input_dict[key][st:ed]) + batch_input_dict[key] = paddle.to_tensor( + input_dict[key][st:ed], paddle.get_default_dtype() + ) else: batch_input_dict[key] = input_dict[key][st:ed] batch_input_dict[key].stop_gradient = False diff --git a/ppsci/visualize/visualizer.py b/ppsci/visualize/visualizer.py index 5387500b1..027bb88fc 100644 --- a/ppsci/visualize/visualizer.py +++ b/ppsci/visualize/visualizer.py @@ -18,6 +18,7 @@ from typing import Tuple import numpy as np +import paddle from ppsci.visualize import base from ppsci.visualize import plot From 39946eb18fad68180dbc614096efc805ff773551 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 16 May 2023 04:55:43 +0000 Subject: [PATCH 05/10] update reprod code --- examples/bracket/bracket.py | 114 +++++++++++++++++----------------- ppsci/visualize/visualizer.py | 1 - 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 1ab52c80a..0d445810d 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -29,61 +29,48 @@ fluid.core.set_prim_eager_enabled(True) args = config.parse_args() - # paddle.set_default_dtype("float64") # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - output_dir = ( - "./output_bracket_eager_True_mse_stable_silu" - if not args.output_dir - else args.output_dir - ) - # output_dir = "./output_bracket_eager_True_mse_paddle_WN_lr_decay" if not args.output_dir else args.output_dir + output_dir = "./output_bracket" if not args.output_dir else args.output_dir # initialize logger logger.init_logger("ppsci", f"{output_dir}/train.log", "info") # set model - act_str = "silu" - wn = True disp_net = ppsci.arch.MLP( - ("x", "y", "z"), ("u", "v", "w"), 6, 512, act_str, weight_norm=wn + ("x", "y", "z"), ("u", "v", "w"), 6, 512, "silu", weight_norm=True ) - stress_net = ppsci.arch.MLP( ("x", "y", "z"), ("sigma_xx", "sigma_yy", "sigma_zz", "sigma_xy", "sigma_xz", "sigma_yz"), 6, 512, - act_str, - weight_norm=wn, + "silu", + weight_norm=True, ) # wrap to a model_list model = ppsci.arch.ModelList((disp_net, stress_net)) - model.load_dict( - paddle.load( - "/workspace/hesensen/PaddleScience_docs/examples/bracket/converted_bracket_initial_ckpt.pdparams" - ) - ) + model.load_dict(paddle.load("./converted_bracket_initial_ckpt.pdparams")) logger.info("load pytorch's init weight") # Specify parameters - nu = 0.3 + NU = 0.3 E = 100.0e9 - lambda_ = nu * E / ((1 + nu) * (1 - 2 * nu)) - mu = E / (2 * (1 + nu)) - mu_c = 0.01 * mu - lambda_ = lambda_ / mu_c - mu = mu / mu_c - characteristic_length = 1.0 - characteristic_displacement = 1.0e-4 - sigma_normalization = characteristic_length / (characteristic_displacement * mu_c) - T = -4.0e4 * sigma_normalization + LAMBDA_ = NU * E / ((1 + NU) * (1 - 2 * NU)) + MU = E / (2 * (1 + NU)) + MU_C = 0.01 * MU + LAMBDA_ = LAMBDA_ / MU_C + MU = MU / MU_C + CHARACTERISTIC_LENGTH = 1.0 + CHARACTERISTIC_DISPLACEMENT = 1.0e-4 + SIGMA_NORMALIZATION = CHARACTERISTIC_LENGTH / (CHARACTERISTIC_DISPLACEMENT * MU_C) + T = -4.0e4 * SIGMA_NORMALIZATION # set equation equation = { "LinearElasticity": ppsci.equation.LinearElasticity( - E=None, nu=None, lambda_=lambda_, mu=mu, dim=3 + E=None, nu=None, lambda_=LAMBDA_, mu=MU, dim=3 ) } @@ -302,22 +289,22 @@ "z": ref_xyzu["z"], } label_dict = { - "u": ref_xyzu["u"] / characteristic_displacement, - "v": ref_v["v"] / characteristic_displacement, - "w": ref_w["w"] / characteristic_displacement, - "sigma_xx": ref_sxx["sigma_xx"] * sigma_normalization, - "sigma_yy": ref_syy["sigma_yy"] * sigma_normalization, - "sigma_zz": ref_szz["sigma_zz"] * sigma_normalization, - "sigma_xy": ref_sxy["sigma_xy"] * sigma_normalization, - "sigma_xz": ref_sxz["sigma_xz"] * sigma_normalization, - "sigma_yz": ref_syz["sigma_yz"] * sigma_normalization, + "u": ref_xyzu["u"] / CHARACTERISTIC_DISPLACEMENT, + "v": ref_v["v"] / CHARACTERISTIC_DISPLACEMENT, + "w": ref_w["w"] / CHARACTERISTIC_DISPLACEMENT, + "sigma_xx": ref_sxx["sigma_xx"] * SIGMA_NORMALIZATION, + "sigma_yy": ref_syy["sigma_yy"] * SIGMA_NORMALIZATION, + "sigma_zz": ref_szz["sigma_zz"] * SIGMA_NORMALIZATION, + "sigma_xy": ref_sxy["sigma_xy"] * SIGMA_NORMALIZATION, + "sigma_xz": ref_sxz["sigma_xz"] * SIGMA_NORMALIZATION, + "sigma_yz": ref_syz["sigma_yz"] * SIGMA_NORMALIZATION, } eval_dataloader_cfg = { "dataset": { "name": "NamedArrayDataset", "input": input_dict, "label": label_dict, - "weight": {key: np.ones_like(label_dict[key]) for key in label_dict}, + "weight": {k: np.ones_like(v) for k, v in label_dict.items()}, }, "sampler": { "name": "BatchSampler", @@ -346,10 +333,20 @@ # set visualizer(optional) visualizer = { - "visulzie_u_v": ppsci.visualize.VisualizerVtu( + "visulzie_u_v_w_sigmas": ppsci.visualize.VisualizerVtu( input_dict, - {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, - prefix="result_u_v_w", + { + "u": lambda d: d["u"], + "v": lambda d: d["v"], + "w": lambda d: d["w"], + "sigma_xx": lambda out: out["sigma_xx"], + "sigma_yy": lambda out: out["sigma_yy"], + "sigma_zz": lambda out: out["sigma_zz"], + "sigma_xy": lambda out: out["sigma_xy"], + "sigma_xz": lambda out: out["sigma_xz"], + "sigma_yz": lambda out: out["sigma_yz"], + }, + prefix="result_u_v_w_sigmas", ) } @@ -371,25 +368,26 @@ validator=validator, visualizer=visualizer, ) - # train model + # # train model solver.train() - # evaluate after finished training + # # evaluate after finished training solver.eval() - # visualize prediction after finished training + # # visualize prediction after finished training solver.visualize() # # directly evaluate pretrained model(optional) - # solver = ppsci.solver.Solver( - # model, - # constraint, - # output_dir, - # equation=equation, - # geom=geom, - # validator=validator, - # visualizer=visualizer, - # pretrained_model_path=f"{output_dir}/checkpoints/latest", - # ) - # solver.eval() - # # visualize prediction for pretrained model(optional) - # solver.visualize() + logger.init_logger("ppsci", f"{output_dir}/eval.log", "info") + solver = ppsci.solver.Solver( + model, + constraint, + output_dir, + equation=equation, + geom=geom, + validator=validator, + visualizer=visualizer, + pretrained_model_path=f"{output_dir}/checkpoints/best_model", + ) + solver.eval() + # visualize prediction for pretrained model(optional) + solver.visualize() diff --git a/ppsci/visualize/visualizer.py b/ppsci/visualize/visualizer.py index 027bb88fc..5387500b1 100644 --- a/ppsci/visualize/visualizer.py +++ b/ppsci/visualize/visualizer.py @@ -18,7 +18,6 @@ from typing import Tuple import numpy as np -import paddle from ppsci.visualize import base from ppsci.visualize import plot From a8e285b10de8d2774cbcbc582c2fb0faae0af2d2 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 23 May 2023 07:01:37 +0000 Subject: [PATCH 06/10] update refined code --- examples/bracket/bracket.py | 115 +++++---- ppsci/arch/activation.py | 10 +- ppsci/arch/mlp.py | 21 +- ppsci/constraint/boundary_constraint.py | 68 +++--- ppsci/constraint/interior_constraint.py | 84 +++---- ppsci/geometry/mesh.py | 296 ++++++++++++++---------- ppsci/solver/solver.py | 28 ++- ppsci/visualize/__init__.py | 2 + ppsci/visualize/vtu.py | 33 ++- 9 files changed, 379 insertions(+), 278 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 0d445810d..65ee3ba87 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -26,15 +26,18 @@ if __name__ == "__main__": # enable prim_eager mode for high-order autodiff - fluid.core.set_prim_eager_enabled(True) - + # fluid.core.set_prim_eager_enabled(True) args = config.parse_args() # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - output_dir = "./output_bracket" if not args.output_dir else args.output_dir + OUTPUT_DIR = ( + "./output_bracket_pdsci_mesh_prim_silu_nan_debug" + if not args.output_dir + else args.output_dir + ) # initialize logger - logger.init_logger("ppsci", f"{output_dir}/train.log", "info") + logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") # set model disp_net = ppsci.arch.MLP( @@ -90,10 +93,10 @@ geom = {"geo": geo} # set dataloader config - iters_per_epoch = 1000 + ITERS_PER_EPOCH = 1000 train_dataloader_cfg = { "dataset": "NamedArrayDataset", - "iters_per_epoch": iters_per_epoch, + "iters_per_epoch": ITERS_PER_EPOCH, "sampler": { "name": "BatchSampler", "drop_last": True, @@ -103,17 +106,23 @@ } # set constraint - support_origin = (-1, -1, -1) - bracket_origin = (-0.75, -1, -0.1) - bracket_dim = (1.75, 2, 0.2) - cylinder_radius = 0.1 + SUPPORT_ORIGIN = (-1, -1, -1) + BRACKET_ORIGIN = (-0.75, -1, -0.1) + BRACKET_DIM = (1.75, 2, 0.2) + BOUNDS_SUPPORT_X = (-1, -0.65) + BOUNDS_SUPPORT_Y = (-1, 1) + BOUNDS_SUPPORT_Z = (-1, 1) + BOUNDS_BRACKET_X = (-0.65, 1) + BOUNDS_BRACKET_Y = (-1, 1) + BOUNDS_BRACKET_Z = (-0.1, 0.1) + bc_back = ppsci.constraint.BoundaryConstraint( {"u": lambda d: d["u"], "v": lambda d: d["v"], "w": lambda d: d["w"]}, {"u": 0, "v": 0, "w": 0}, geom["geo"], {**train_dataloader_cfg, "batch_size": 1024}, ppsci.loss.MSELoss("sum"), - criteria=lambda x, y, z: np.isclose(x, support_origin[0]), + criteria=lambda x, y, z: x == SUPPORT_ORIGIN[0], weight_dict={"u": 10, "v": 10, "w": 10}, name="BC_BACK", ) @@ -123,7 +132,7 @@ geom["geo"], {**train_dataloader_cfg, "batch_size": 128}, ppsci.loss.MSELoss("sum"), - criteria=lambda x, y, z: np.isclose(x, bracket_origin[0] + bracket_dim[0]), + criteria=lambda x, y, z: x == BRACKET_ORIGIN[0] + BRACKET_DIM[0], name="BC_FRONT", ) bc_surface = ppsci.constraint.BoundaryConstraint( @@ -133,7 +142,7 @@ {**train_dataloader_cfg, "batch_size": 4096}, ppsci.loss.MSELoss("sum"), criteria=lambda x, y, z: np.logical_and( - x > support_origin[0], x < bracket_origin[0] + bracket_dim[0] + x > SUPPORT_ORIGIN[0] + 1e-7, x < BRACKET_ORIGIN[0] + BRACKET_DIM[0] - 1e-7 ), name="BC_SURFACE", ) @@ -153,18 +162,25 @@ geom["geo"], {**train_dataloader_cfg, "batch_size": 2048}, ppsci.loss.MSELoss("sum"), - # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} - # weight={ - # "equilibrium_x": "sdf", - # "equilibrium_y": "sdf", - # "equilibrium_z": "sdf", - # "stress_disp_xx": "sdf", - # "stress_disp_yy": "sdf", - # "stress_disp_zz": "sdf", - # "stress_disp_xy": "sdf", - # "stress_disp_xz": "sdf", - # "stress_disp_yz": "sdf", - # } + criteria=lambda x, y, z: ( + (BOUNDS_SUPPORT_X[0] < x) + & (x < BOUNDS_SUPPORT_X[1]) + & (BOUNDS_SUPPORT_Y[0] < y) + & (y < BOUNDS_SUPPORT_Y[1]) + & (BOUNDS_SUPPORT_Z[0] < z) + & (z < BOUNDS_SUPPORT_Z[1]) + ), + weight_dict={ + "equilibrium_x": "sdf", + "equilibrium_y": "sdf", + "equilibrium_z": "sdf", + "stress_disp_xx": "sdf", + "stress_disp_yy": "sdf", + "stress_disp_zz": "sdf", + "stress_disp_xy": "sdf", + "stress_disp_xz": "sdf", + "stress_disp_yz": "sdf", + }, name="support_interior", ) bracket_interior_constraint = ppsci.constraint.InteriorConstraint( @@ -183,18 +199,25 @@ geom["geo"], {**train_dataloader_cfg, "batch_size": 1024}, ppsci.loss.MSELoss("sum"), - # bounds={x: bounds_bracket_x, y: bounds_bracket_y, z: bounds_bracket_z} - # weight={ - # "equilibrium_x": "sdf", - # "equilibrium_y": "sdf", - # "equilibrium_z": "sdf", - # "stress_disp_xx": "sdf", - # "stress_disp_yy": "sdf", - # "stress_disp_zz": "sdf", - # "stress_disp_xy": "sdf", - # "stress_disp_xz": "sdf", - # "stress_disp_yz": "sdf", - # } + criteria=lambda x, y, z: ( + (BOUNDS_BRACKET_X[0] < x) + & (x < BOUNDS_BRACKET_X[1]) + & (BOUNDS_BRACKET_Y[0] < y) + & (y < BOUNDS_BRACKET_Y[1]) + & (BOUNDS_BRACKET_Z[0] < z) + & (z < BOUNDS_BRACKET_Z[1]) + ), + weight_dict={ + "equilibrium_x": "sdf", + "equilibrium_y": "sdf", + "equilibrium_z": "sdf", + "stress_disp_xx": "sdf", + "stress_disp_yy": "sdf", + "stress_disp_zz": "sdf", + "stress_disp_xy": "sdf", + "stress_disp_xz": "sdf", + "stress_disp_yz": "sdf", + }, name="bracket_interior", ) # wrap constraints together @@ -207,10 +230,10 @@ } # set training hyper-parameters - epochs = 2000 if not args.epochs else args.epochs + EPOCHS = 2000 if not args.epochs else args.epochs lr_scheduler = ppsci.optimizer.lr_scheduler.ExponentialDecay( - epochs, - iters_per_epoch, + EPOCHS, + ITERS_PER_EPOCH, 0.001, 0.95, 15000, @@ -354,11 +377,11 @@ solver = ppsci.solver.Solver( model, constraint, - output_dir, + OUTPUT_DIR, optimizer, lr_scheduler, - epochs, - iters_per_epoch, + EPOCHS, + ITERS_PER_EPOCH, save_freq=20, eval_during_train=True, log_freq=20, @@ -377,16 +400,16 @@ solver.visualize() # # directly evaluate pretrained model(optional) - logger.init_logger("ppsci", f"{output_dir}/eval.log", "info") + logger.init_logger("ppsci", f"{OUTPUT_DIR}/eval.log", "info") solver = ppsci.solver.Solver( model, constraint, - output_dir, + OUTPUT_DIR, equation=equation, geom=geom, validator=validator, visualizer=visualizer, - pretrained_model_path=f"{output_dir}/checkpoints/best_model", + pretrained_model_path=f"{OUTPUT_DIR}/checkpoints/best_model", ) solver.eval() # visualize prediction for pretrained model(optional) diff --git a/ppsci/arch/activation.py b/ppsci/arch/activation.py index e08bae5f5..6014b4810 100644 --- a/ppsci/arch/activation.py +++ b/ppsci/arch/activation.py @@ -19,10 +19,8 @@ import paddle.nn.functional as F -def stable_silu(x): - """ - numeric stable silu, prevent from Nan in backward. - """ +def silu(x): + """numeric stable silu""" return x * F.sigmoid(x) @@ -31,10 +29,10 @@ def stable_silu(x): "relu": F.relu, "selu": F.selu, "sigmoid": F.sigmoid, - "silu": stable_silu, + "silu": silu, "sin": paddle.sin, "cos": paddle.cos, - "swish": stable_silu, + "swish": silu, "tanh": F.tanh, "identity": nn.Identity(), } diff --git a/ppsci/arch/mlp.py b/ppsci/arch/mlp.py index ccd7be1c7..90c5f1c98 100644 --- a/ppsci/arch/mlp.py +++ b/ppsci/arch/mlp.py @@ -28,15 +28,15 @@ def __init__(self, in_features: int, out_features: int, bias: bool = True) -> No super().__init__() self.in_features = in_features self.out_features = out_features - self.weight_v = paddle.create_parameter( - (in_features, out_features), paddle.get_default_dtype() + self.weight_v = self.create_parameter( + (in_features, out_features), dtype=paddle.get_default_dtype() ) - self.weight_g = paddle.create_parameter( - (out_features,), paddle.get_default_dtype() + self.weight_g = self.create_parameter( + (out_features,), dtype=paddle.get_default_dtype() ) if bias: - self.bias = paddle.create_parameter( - (out_features,), paddle.get_default_dtype() + self.bias = self.create_parameter( + (out_features,), dtype=paddle.get_default_dtype() ) else: self.bias = None @@ -106,10 +106,11 @@ def __init__( # initialize FC layer(s) cur_size = len(self.input_keys) for _size in hidden_size: - self.linears.append(nn.Linear(cur_size, _size)) - if weight_norm: - # self.linears[-1] = nn.utils.weight_norm(self.linears[-1], dim=1) - self.linears[-1] = WeightNormLinear(cur_size, _size) + self.linears.append( + WeightNormLinear(cur_size, _size) + if weight_norm + else nn.Linear(cur_size, _size) + ) cur_size = _size self.linears = nn.LayerList(self.linears) diff --git a/ppsci/constraint/boundary_constraint.py b/ppsci/constraint/boundary_constraint.py index 90621c411..d85a21fc7 100644 --- a/ppsci/constraint/boundary_constraint.py +++ b/ppsci/constraint/boundary_constraint.py @@ -98,54 +98,38 @@ def __init__( criteria = eval(criteria) # prepare input - # input = geom.sample_boundary( - # dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], - # random, - # criteria, - # evenly, - # ) - input = np.load( - f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.input.npz" + input = geom.sample_boundary( + dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], + random, + criteria, + evenly, ) - input = dict(input) - for k in input: - input[k] = input[k].astype("float32") - if "area" in input: input["area"] *= dataloader_cfg["iters_per_epoch"] - # for v in input: - # print(f"BoundaryConstraint.input: {v} {input[v].shape} {input[v].mean().item():.10f} {input[v].std().item():.10f}") - # prepare label label = {} - # for key, value in label_dict.items(): - # if isinstance(value, (int, float)): - # label[key] = np.full_like(next(iter(input.values())), float(value)) - # elif isinstance(value, sympy.Basic): - # func = sympy.lambdify( - # sympy.symbols(geom.dim_keys), - # value, - # [{"amax": lambda xy, axis: np.maximum(xy[0], xy[1])}, "numpy"], - # ) - # label[key] = func( - # **{k: v for k, v in input.items() if k in geom.dim_keys} - # ) - # elif isinstance(value, types.FunctionType): - # func = value - # label[key] = func(input) - # if isinstance(label[key], (int, float)): - # label[key] = np.full_like( - # next(iter(input.values())), float(label[key]) - # ) - # else: - # raise NotImplementedError(f"type of {type(value)} is invalid yet.") - label = np.load( - f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.label.npz" - ) - label = dict(label) - for k in label: - label[k] = label[k].astype("float32") + for key, value in label_dict.items(): + if isinstance(value, (int, float)): + label[key] = np.full_like(next(iter(input.values())), float(value)) + elif isinstance(value, sympy.Basic): + func = sympy.lambdify( + sympy.symbols(geom.dim_keys), + value, + [{"amax": lambda xy, axis: np.maximum(xy[0], xy[1])}, "numpy"], + ) + label[key] = func( + **{k: v for k, v in input.items() if k in geom.dim_keys} + ) + elif isinstance(value, types.FunctionType): + func = value + label[key] = func(input) + if isinstance(label[key], (int, float)): + label[key] = np.full_like( + next(iter(input.values())), float(label[key]) + ) + else: + raise NotImplementedError(f"type of {type(value)} is invalid yet.") # prepare weight weight = {key: np.ones_like(next(iter(label.values()))) for key in label} diff --git a/ppsci/constraint/interior_constraint.py b/ppsci/constraint/interior_constraint.py index f0ce04280..2eeeccf78 100644 --- a/ppsci/constraint/interior_constraint.py +++ b/ppsci/constraint/interior_constraint.py @@ -98,66 +98,51 @@ def __init__( criteria = eval(criteria) # prepare input - # input = geom.sample_interior( - # dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], - # random, - # criteria, - # evenly, - # ) - input = np.load( - f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.input.npz" + input = geom.sample_interior( + dataloader_cfg["batch_size"] * dataloader_cfg["iters_per_epoch"], + random, + criteria, + evenly, ) - input = dict(input) - for k in input: - input[k] = input[k].astype("float32") if "area" in input: input["area"] *= dataloader_cfg["iters_per_epoch"] - # for v in input: - # print(f"InteriorConstraint.input: {v} {input[v].shape} {input[v].mean().item():.10f} {input[v].std().item():.10f}") - # prepare label label = {} - # for key, value in label_dict.items(): - # if isinstance(value, str): - # value = sp_parser.parse_expr(value) - # if isinstance(value, (int, float)): - # label[key] = np.full_like(next(iter(input.values())), float(value)) - # elif isinstance(value, sympy.Basic): - # func = sympy.lambdify( - # sympy.symbols(geom.dim_keys), - # value, - # [{"amax": lambda xy, _: np.maximum(xy[0], xy[1])}, "numpy"], - # ) - # label[key] = func( - # **{k: v for k, v in input.items() if k in geom.dim_keys} - # ) - # elif isinstance(value, types.FunctionType): - # func = value - # label[key] = func(input) - # if isinstance(label[key], (int, float)): - # label[key] = np.full_like( - # next(iter(input.values())), float(label[key]) - # ) - # else: - # raise NotImplementedError(f"type of {type(value)} is invalid yet.") - label = np.load( - f"/workspace/hesensen/modulus/examples/bracket/outputs/bracket/{name}.label.npz" - ) - label = dict(label) - for k in label: - label[k] = label[k].astype("float32") + for key, value in label_dict.items(): + if isinstance(value, str): + value = sp_parser.parse_expr(value) + if isinstance(value, (int, float)): + label[key] = np.full_like(next(iter(input.values())), float(value)) + elif isinstance(value, sympy.Basic): + func = sympy.lambdify( + sympy.symbols(geom.dim_keys), + value, + [{"amax": lambda xy, _: np.maximum(xy[0], xy[1])}, "numpy"], + ) + label[key] = func( + **{k: v for k, v in input.items() if k in geom.dim_keys} + ) + elif isinstance(value, types.FunctionType): + func = value + label[key] = func(input) + if isinstance(label[key], (int, float)): + label[key] = np.full_like( + next(iter(input.values())), float(label[key]) + ) + else: + raise NotImplementedError(f"type of {type(value)} is invalid yet.") # prepare weight weight = {key: np.ones_like(next(iter(label.values()))) for key in label} - weight = {key: input["sdf"].astype("float32") for key in weight} - input.pop("sdf") if weight_dict is not None: for key, value in weight_dict.items(): if isinstance(value, str): - value = sp_parser.parse_expr(value) - - if isinstance(value, (int, float)): + if value == "sdf": + weight[key] = input["sdf"] + else: + raise NotImplementedError(f"string {value} is invalid yet.") + elif isinstance(value, (int, float)): weight[key] = np.full_like(next(iter(label.values())), float(value)) elif isinstance(value, sympy.Basic): func = sympy.lambdify( @@ -178,6 +163,9 @@ def __init__( else: raise NotImplementedError(f"type of {type(value)} is invalid yet.") + if "sdf" in input: + input.pop("sdf") + # wrap input, label, weight into a dataset _dataset = getattr(dataset, dataloader_cfg["dataset"])(input, label, weight) diff --git a/ppsci/geometry/mesh.py b/ppsci/geometry/mesh.py index b4068ec5c..9ae953eac 100644 --- a/ppsci/geometry/mesh.py +++ b/ppsci/geometry/mesh.py @@ -14,9 +14,13 @@ from __future__ import annotations +from typing import Callable +from typing import Optional from typing import Union import numpy as np +import open3d +import paddle import pymesh import pysdf @@ -62,6 +66,12 @@ def init_mesh(self): self.py_mesh.add_attribute("face_area") self.face_area = self.py_mesh.get_attribute("face_area").reshape([-1]) + self.open3d_mesh = open3d.geometry.TriangleMesh( + open3d.utility.Vector3dVector(np.array(self.py_mesh.vertices)), + open3d.utility.Vector3iVector(np.array(self.py_mesh.faces)), + ) + self.open3d_mesh.compute_vertex_normals() + self.vertices = self.py_mesh.vertices self.faces = self.py_mesh.faces self.vectors = self.vertices[self.faces] @@ -75,20 +85,39 @@ def init_mesh(self): self.v2 = self.vectors[:, 2] self.num_vertices = self.py_mesh.num_vertices self.num_faces = self.py_mesh.num_faces - self.sdf = pysdf.SDF(self.vertices, self.faces) + self.pysdf = pysdf.SDF(self.vertices, self.faces) self.bounds = ( ((np.min(self.vectors[:, :, 0])), np.max(self.vectors[:, :, 0])), ((np.min(self.vectors[:, :, 1])), np.max(self.vectors[:, :, 1])), ((np.min(self.vectors[:, :, 2])), np.max(self.vectors[:, :, 2])), ) + def sdf_func(self, points: np.ndarray) -> np.ndarray: + """Compute signed distance field. + + Args: + points (np.ndarray): The coordinate points used to calculate the SDF value, + the shape is [N, 3] + + Returns: + np.ndarray: Unsquared SDF values of input points, the shape is [N, 1]. + + NOTE: This function usually returns ndarray with negative values, because + according to the definition of SDF, the SDF value of the coordinate point inside + the object(interior points) is negative, the outside is positive, and the edge + is 0. Therefore, when used for weighting, a negative sign is often added before + the result of this function. + """ + sdf, _, _, _ = pymesh.signed_distance_to_mesh(self.py_mesh, points) + sdf = sdf[..., np.newaxis] + return sdf + def is_inside(self, x): # NOTE: point on boundary is included - return self.sdf.contains(x) + return self.pysdf.contains(x) def on_boundary(self, x): - x_sdf = self.sdf(x) - return np.isclose(x_sdf, 0.0) + return np.isclose(self.sdf_func(x), 0.0).flatten() def translate(self, translation, relative=True): vertices = np.array(self.vertices) @@ -130,7 +159,7 @@ def scale(self, scale, center=(0, 0, 0)): def uniform_boundary_points(self, n: int): """Compute the equispaced points on the boundary.""" - return self.sdf.sample_surface(n) + return self.pysdf.sample_surface(n) def inflated_random_points(self, n, distance, random="pseudo"): if not isinstance(n, (tuple, list)): @@ -158,7 +187,7 @@ def inflated_random_points(self, n, distance, random="pseudo"): for e in inflated_mesh.bounds ] random_points = np.concatenate(random_points, axis=1) - inner_mask = inflated_mesh.sdf.contains(random_points) + inner_mask = inflated_mesh.pysdf.contains(random_points) valid_random_points = random_points[inner_mask] inflated_points.append(valid_random_points) @@ -235,27 +264,57 @@ def inflated_random_boundary_points(self, n, distance, random="pseudo"): all_area = np.concatenate(all_area, axis=0, dtype="float32") return all_points, all_normal, all_area - def _approximate_area(self, n_appr, random="pseudo") -> float: - triangle_areas = area_of_triangles(self.v0, self.v1, self.v2) - triangle_prob = triangle_areas / np.linalg.norm(triangle_areas, ord=1) - triangle_index = np.arange(triangle_prob.shape[0]) - points_per_triangle = np.random.choice(triangle_index, n_appr, p=triangle_prob) - points_per_triangle, _ = np.histogram( - points_per_triangle, np.arange(triangle_prob.shape[0] + 1) - 0.5 - ) - - all_area = [] - for index, nr_p in enumerate(points_per_triangle): - if nr_p == 0: - continue - area = np.full([nr_p, 1], triangle_areas[index] / nr_p) - all_area.append(area) - - all_area = np.concatenate(all_area, axis=0, dtype="float32") - return all_area.sum() - - def random_boundary_points(self, n, random="pseudo"): - triangle_areas = area_of_triangles(self.v0, self.v1, self.v2) + def _approximate_area( + self, + random: str = "pseudo", + criteria: Optional[Callable] = None, + n_appr: int = 20000, + ) -> float: + """Approximate area with given `criteria` using `n_appr` points. + + Args: + n_appr (int): Number of points for approximating area. + criteria (Callable): Criteria function. + random (str, optional): Random method. Defaults to "pseudo". + + Returns: + float: Approximated area. + """ + areas = [] + for i in range(self.num_faces): + sampled_points = sample_in_triangle( + self.v0[i], self.v1[i], self.v2[i], n_appr, random + ) + if criteria is not None: + criteria_mask = criteria( + *np.split(sampled_points, self.ndim, 1) + ).flatten() + else: + criteria_mask = np.full((n_appr,), True) + valid_area = (criteria_mask.sum() / n_appr) * self.face_area[i] + areas.append(valid_area) + + return np.asarray(areas, paddle.get_default_dtype()) + + # def precise_on_boundary(self, points: np.ndarray, normals: np.ndarray): + # """judge whether points is accurately on boundary. + + # Args: + # points (np.ndarray): Points. + # normals (np.ndarray): Normals for each points. + + # Returns: + # np.ndarray: If on boundary, true for yes, false for not. + # """ + # EPS = 1e-6 + # points_pos_normals = points + normals * EPS + # points_neg_normals = points - normals * EPS + # pos_sdf = self.sdf_func(points_pos_normals) + # neg_sdf = self.sdf_func(points_neg_normals) + # return (pos_sdf * neg_sdf <= 0)[:, 0] + + def random_boundary_points(self, n, random="pseudo", criteria=None): + triangle_areas = self._approximate_area(random, criteria) triangle_prob = triangle_areas / np.linalg.norm(triangle_areas, ord=1) triangle_index = np.arange(triangle_prob.shape[0]) points_per_triangle = np.random.choice(triangle_index, n, p=triangle_prob) @@ -270,7 +329,7 @@ def random_boundary_points(self, n, random="pseudo"): if nr_p == 0: continue sampled_points = sample_in_triangle( - self.v0[index], self.v1[index], self.v2[index], nr_p, random + self.v0[index], self.v1[index], self.v2[index], nr_p, random, criteria ) normal = np.tile(self.face_normal[index], [nr_p, 1]) area = np.full([nr_p, 1], triangle_areas[index] / nr_p) @@ -279,10 +338,14 @@ def random_boundary_points(self, n, random="pseudo"): all_normal.append(normal) all_area.append(area) - all_points = np.concatenate(all_points, axis=0, dtype="float32") - all_normal = np.concatenate(all_normal, axis=0, dtype="float32") - all_area = np.concatenate(all_area, axis=0, dtype="float32") - all_area = np.full_like(all_area, all_area.sum() / n) + all_points = np.concatenate( + all_points, axis=0, dtype=paddle.get_default_dtype() + ) + all_normal = np.concatenate( + all_normal, axis=0, dtype=paddle.get_default_dtype() + ) + all_area = np.concatenate(all_area, axis=0, dtype=paddle.get_default_dtype()) + all_area = np.full_like(all_area, all_area.mean()) return all_points, all_normal, all_area def sample_boundary( @@ -290,107 +353,77 @@ def sample_boundary( ): # TODO(sensen): support for time-dependent points(repeat data in time) if inflation_dist is not None: - x, normal, area = self.inflated_random_boundary_points( + points, normals, areas = self.inflated_random_boundary_points( n, inflation_dist, random ) else: - x = np.empty(shape=(n, self.ndim), dtype="float32") - _size, _ntry, _nsuc = 0, 0, 0 - while _size < n: - if evenly: - raise ValueError( - "Can't sample evenly on mesh now, please set evenly=False." - ) - # points, normal, area = self.uniform_boundary_points(n, False) - else: - points, normal, area = self.random_boundary_points(n, random) - - if criteria is not None: - criteria_mask = criteria(*np.split(points, self.ndim, 1)).flatten() - points = points[criteria_mask] - - if len(points) > n - _size: - points = points[: n - _size] - x[_size : _size + len(points)] = points - - _size += len(points) - _ntry += 1 - if len(points) > 0: - _nsuc += 1 - - if _ntry >= 1000 and _nsuc == 0: - raise ValueError( - "Sample boundary points failed, " - "please check correctness of geometry and given creteria." - ) + if evenly: + raise ValueError( + "Can't sample evenly on mesh now, please set evenly=False." + ) + # points, normal, area = self.uniform_boundary_points(n, False) + else: + points, normals, areas = self.random_boundary_points( + n, random, criteria + ) + x_dict = misc.convert_to_dict(points, self.dim_keys) normal_dict = misc.convert_to_dict( - normal, [f"normal_{key}" for key in self.dim_keys if key != "t"] + normals, [f"normal_{key}" for key in self.dim_keys if key != "t"] ) - area_dict = misc.convert_to_dict(area, ["area"]) - x_dict = misc.convert_to_dict(x, self.dim_keys) + area_dict = misc.convert_to_dict(areas, ["area"]) return {**x_dict, **normal_dict, **area_dict} - def random_points(self, n, random="pseudo"): - cur_n = 0 + def random_points(self, n, random="pseudo", criteria=None): + _size = 0 all_points = [] cuboid = geometry_3d.Cuboid( [bound[0] for bound in self.bounds], [bound[1] for bound in self.bounds], ) - while cur_n < n: + _ntry, _nsuc = 0, 0 + while _size < n: random_points = cuboid.random_points(n, random) - inner_mask = self.sdf.contains(random_points) - valid_random_points = random_points[inner_mask] + valid_mask = self.is_inside(random_points) + if criteria: + valid_mask &= criteria( + *np.split(random_points, self.ndim, axis=1) + ).flatten() + valid_random_points = random_points[valid_mask] + _nsuc += len(valid_random_points) + + if len(valid_random_points) > n - _size: + valid_random_points = valid_random_points[: n - _size] all_points.append(valid_random_points) - cur_n += len(valid_random_points) + _size += len(valid_random_points) + _ntry += n all_points = np.concatenate(all_points, axis=0) - if cur_n > n: - all_points = all_points[:n] - - return all_points + all_areas = np.full( + (n, 1), np.prod([b[1] - b[0] for b in self.bounds]) * (_nsuc / _ntry) / n + ) + return all_points, all_areas def sample_interior(self, n, random="pseudo", criteria=None, evenly=False): """Sample random points in the geometry and return those meet criteria.""" - x = np.empty(shape=(n, self.ndim), dtype="float32") - _size, _ntry, _nsuc = 0, 0, 0 - while _size < n: - if evenly: - # TODO(sensen): implement uniform sample for mesh interior. - raise NotImplementedError( - "uniformly sample for interior in mesh is not support yet" - ) - points = self.uniform_points(n) - else: - points = self.random_points(n, random) - - if criteria is not None: - criteria_mask = criteria(*np.split(points, self.ndim, axis=1)).flatten() - points = points[criteria_mask] - - if len(points) > n - _size: - points = points[: n - _size] - x[_size : _size + len(points)] = points - - _size += len(points) - _ntry += 1 - if len(points) > 0: - _nsuc += 1 + if evenly: + # TODO(sensen): implement uniform sample for mesh interior. + raise NotImplementedError( + "uniformly sample for interior in mesh is not support yet" + ) + # points, area = self.uniform_points(n) + else: + points, area = self.random_points(n, random, criteria) - if _ntry >= 1000 and _nsuc == 0: - raise ValueError( - "Sample interior points failed, " - "please check correctness of geometry and given creteria." - ) + x_dict = misc.convert_to_dict(points, self.dim_keys) + area_dict = misc.convert_to_dict(area, ["area"]) - x_dict = misc.convert_to_dict(x, self.dim_keys) + # NOTE: add negtive to the sdf values for positive weight. + sdf = -self.sdf_func(points) + sdf_dict = misc.convert_to_dict(sdf, ["sdf"]) - volume = np.prod([bound[1] - bound[0] for bound in self.bounds]) - area = np.full((n, 1), volume / n, "float32") - area_dict = misc.convert_to_dict(area, ["area"]) - return {**x_dict, **area_dict} + return {**x_dict, **area_dict, **sdf_dict} def union(self, rhs): if not checker.dynamic_import_to_globals(["pymesh"]): @@ -474,12 +507,12 @@ def area_of_triangles(v0, v1, v2): + (v0[:, 2] - v2[:, 2]) ** 2 + 1e-10 ) - s = (a + b + c) / 2 - area = np.sqrt(s * (s - a) * (s - b) * (s - c) + 1e-10) + p = (a + b + c) / 2 + area = np.sqrt(p * (p - a) * (p - b) * (p - c) + 1e-10) return area -def sample_in_triangle(v0, v1, v2, n, random="pseudo"): +def sample_in_triangle(v0, v1, v2, n, random="pseudo", criteria=None): """ Uniformly sample n points in an 3D triangle defined by 3 vertices v0, v1, v2 https://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle @@ -493,10 +526,35 @@ def sample_in_triangle(v0, v1, v2, n, random="pseudo"): Returns: np.ndarray: Coordinates of sampled n points. """ - r1 = sampler.sample(n, 1, random).flatten() - r2 = sampler.sample(n, 1, random).flatten() - s1 = np.sqrt(r1) - x = v0[0] * (1.0 - s1) + v1[0] * (1.0 - r2) * s1 + v2[0] * r2 * s1 - y = v0[1] * (1.0 - s1) + v1[1] * (1.0 - r2) * s1 + v2[1] * r2 * s1 - z = v0[2] * (1.0 - s1) + v1[2] * (1.0 - r2) * s1 + v2[2] * r2 * s1 - return np.stack([x, y, z], axis=1) + all_x, all_y, all_z = [], [], [] + _size = 0 + while _size < n: + r1 = sampler.sample(n, 1, random).flatten() + r2 = sampler.sample(n, 1, random).flatten() + s1 = np.sqrt(r1) + x = v0[0] * (1.0 - s1) + v1[0] * (1.0 - r2) * s1 + v2[0] * r2 * s1 + y = v0[1] * (1.0 - s1) + v1[1] * (1.0 - r2) * s1 + v2[1] * r2 * s1 + z = v0[2] * (1.0 - s1) + v1[2] * (1.0 - r2) * s1 + v2[2] * r2 * s1 + + if criteria is not None: + criteria_mask = criteria(x, y, z).flatten() + x = x[criteria_mask] + y = y[criteria_mask] + z = z[criteria_mask] + + if len(x) > n - _size: + x = x[: n - _size] + y = y[: n - _size] + z = z[: n - _size] + + all_x.append(x) + all_y.append(y) + all_z.append(z) + + _size += len(x) + + all_x = np.concatenate(all_x, axis=0) + all_y = np.concatenate(all_y, axis=0) + all_z = np.concatenate(all_z, axis=0) + + return np.stack([all_x, all_y, all_z], axis=1) diff --git a/ppsci/solver/solver.py b/ppsci/solver/solver.py index 317fbeed2..61fa87c38 100644 --- a/ppsci/solver/solver.py +++ b/ppsci/solver/solver.py @@ -14,8 +14,10 @@ from __future__ import annotations +import contextlib import copy import os +import sys from typing import Any from typing import Dict from typing import Optional @@ -461,16 +463,15 @@ def predict( # prepare batch input dict for key in input_dict: if not paddle.is_tensor(input_dict[key]): - batch_input_dict[key] = paddle.to_tensor(input_dict[key][st:ed]) + batch_input_dict[key] = paddle.to_tensor( + input_dict[key][st:ed], paddle.get_default_dtype() + ) else: batch_input_dict[key] = input_dict[key][st:ed] batch_input_dict[key].stop_gradient = False # forward - if self.use_amp: - with amp.auto_cast(level=self.amp_level): - batch_output_dict = self.model(batch_input_dict) - else: + with self.autocast_context_manager(): batch_output_dict = self.model(batch_input_dict) # collect batch data @@ -499,3 +500,20 @@ def export(self): save_path = os.path.join(export_dir, "inference") paddle.jit.save(static_model, save_path) logger.info(f"The inference model has been exported to {export_dir}.") + + def autocast_context_manager(self) -> contextlib.AbstractContextManager: + """Autocast context manager for Auto Mix Precision. + + Returns: + Union[contextlib.AbstractContextManager]: Context manager. + """ + if self.use_amp: + ctx_manager = amp.auto_cast(level=self.amp_level) + else: + ctx_manager = ( + contextlib.nullcontext() + if sys.version_info >= (3, 7) + else contextlib.suppress() + ) + + return ctx_manager diff --git a/ppsci/visualize/__init__.py b/ppsci/visualize/__init__.py index 0040b3576..ac22fdf47 100644 --- a/ppsci/visualize/__init__.py +++ b/ppsci/visualize/__init__.py @@ -21,6 +21,7 @@ from ppsci.visualize.visualizer import VisualizerScatter1D # isort:skip from ppsci.visualize.visualizer import VisualizerScatter3D # isort:skip from ppsci.visualize.visualizer import VisualizerVtu # isort:skip +from ppsci.visualize.vtu import save_vtu_to_mesh # isort:skip from ppsci.visualize.vtu import save_vtu_from_dict # isort:skip from ppsci.visualize.plot import save_plot_from_1d_dict # isort:skip from ppsci.visualize.plot import save_plot_from_3d_dict # isort:skip @@ -34,6 +35,7 @@ "Visualizer2D", "Visualizer2DPlot", "Visualizer3D", + "save_vtu_to_mesh", "save_vtu_from_dict", "save_plot_from_1d_dict", "save_plot_from_3d_dict", diff --git a/ppsci/visualize/vtu.py b/ppsci/visualize/vtu.py index 3e7f69614..ade0b4ce6 100644 --- a/ppsci/visualize/vtu.py +++ b/ppsci/visualize/vtu.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict +from typing import Tuple + +import meshio import numpy as np import paddle from pyevtk import hl @@ -82,10 +86,10 @@ def _save_vtu_from_array(filename, coord, value, value_keys, num_timestamp=1): if num_timestamp > 1: logger.info( - f"Visualization results are saved to {filename}_t-0 ~ {filename}_t-{num_timestamp - 1}" + f"Visualization results are saved to {filename}_t-0.vtu ~ {filename}_t-{num_timestamp - 1}.vtu" ) else: - logger.info(f"Visualization result is saved to {filename}") + logger.info(f"Visualization result is saved to {filename}.vtu") def save_vtu_from_dict(filename, data_dict, coord_keys, value_keys, num_timestamp=1): @@ -118,3 +122,28 @@ def save_vtu_from_dict(filename, data_dict, coord_keys, value_keys, num_timestam value = np.concatenate(value, axis=1) _save_vtu_from_array(filename, coord, value, value_keys, num_timestamp) + + +def save_vtu_to_mesh( + filename: str, + data_dict: Dict[str, np.ndarray], + coord_keys: Tuple[str, ...], + value_keys: Tuple[str, ...], +): + """Save data into .vtu format by meshio. + + Args: + filename (str): File name. + data_dict (Dict[str, Union[np.ndarray, paddle.Tensor]]): Data in dict. + coord_keys (Tuple[str, ...]): Tuple of coord key. such as ("x", "y"). + value_keys (Tuple[str, ...]): Tuple of value key. such as ("u", "v"). + """ + ndim = len(coord_keys) + npoint = len(next(iter(data_dict.values()))) + # get the list variable transposed + points = np.stack((data_dict[key] for key in coord_keys)).reshape(ndim, npoint) + mesh = meshio.Mesh( + points=points.T, cells=[("vertex", np.arange(npoint).reshape(npoint, 1))] + ) + mesh.point_data = {key: data_dict[key] for key in value_keys} + mesh.write(filename) From 44c8a98ea0d9adb4f6840d468e8be0471b185a75 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 23 May 2023 11:15:09 +0000 Subject: [PATCH 07/10] refine bracket code --- examples/bracket/bracket.py | 16 +-- ppsci/equation/pde/linear_elasticity.py | 152 +++++++++++------------- ppsci/geometry/mesh.py | 9 ++ 3 files changed, 85 insertions(+), 92 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 65ee3ba87..811323ff1 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Reference: https://www.mathworks.com/help/pde/ug/deflection-analysis-of-a-bracket.html +Reference: https://docs.nvidia.com/deeplearning/modulus/modulus-v2209/user_guide/foundational/linear_elasticity.html """ import numpy as np @@ -25,17 +25,12 @@ from ppsci.utils import logger if __name__ == "__main__": - # enable prim_eager mode for high-order autodiff # fluid.core.set_prim_eager_enabled(True) args = config.parse_args() # set random seed for reproducibility ppsci.utils.misc.set_random_seed(42) # set output directory - OUTPUT_DIR = ( - "./output_bracket_pdsci_mesh_prim_silu_nan_debug" - if not args.output_dir - else args.output_dir - ) + OUTPUT_DIR = "./output_bracket" if not args.output_dir else args.output_dir # initialize logger logger.init_logger("ppsci", f"{OUTPUT_DIR}/train.log", "info") @@ -54,9 +49,6 @@ # wrap to a model_list model = ppsci.arch.ModelList((disp_net, stress_net)) - model.load_dict(paddle.load("./converted_bracket_initial_ckpt.pdparams")) - logger.info("load pytorch's init weight") - # Specify parameters NU = 0.3 E = 100.0e9 @@ -102,7 +94,7 @@ "drop_last": True, "shuffle": True, }, - "num_workers": 1, + "num_workers": 2, } # set constraint @@ -350,7 +342,7 @@ "sigma_yz": lambda out: out["sigma_yz"], }, metric={"MSE": ppsci.metric.MSE()}, - name="commercial_ref_u_v_w_sigma", + name="commercial_ref_u_v_w_sigmas", ) validator = {sup_validator.name: sup_validator} diff --git a/ppsci/equation/pde/linear_elasticity.py b/ppsci/equation/pde/linear_elasticity.py index c124ff0fd..cf9503b6e 100644 --- a/ppsci/equation/pde/linear_elasticity.py +++ b/ppsci/equation/pde/linear_elasticity.py @@ -1,5 +1,16 @@ -"""Equations related to linear elasticity -""" +# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from typing import Optional @@ -53,13 +64,13 @@ def stress_disp_xx_compute_func(out): ) sigma_xx = out["sigma_xx"] stress_disp_xx = ( - self.lambda_ * (jacobian(u, x) + jacobian(v, y) + jacobian(w, z)) + self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + 2 * self.mu * jacobian(u, x) - sigma_xx ) - # if self.dim == 3: - # z, w = out["z"], out["w"] - # stress_disp_xx += self.lambda_ * jacobian(w, z) + if self.dim == 3: + z, w = out["z"], out["w"] + stress_disp_xx += self.lambda_ * jacobian(w, z) return stress_disp_xx self.add_equation("stress_disp_xx", stress_disp_xx_compute_func) @@ -75,13 +86,13 @@ def stress_disp_yy_compute_func(out): ) sigma_yy = out["sigma_yy"] stress_disp_yy = ( - self.lambda_ * (jacobian(u, x) + jacobian(v, y) + jacobian(w, z)) + self.lambda_ * (jacobian(u, x) + jacobian(v, y)) + 2 * self.mu * jacobian(v, y) - sigma_yy ) - # if self.dim == 3: - # z, w = out["z"], out["w"] - # stress_disp_yy += self.lambda_ * jacobian(w, z) + if self.dim == 3: + z, w = out["z"], out["w"] + stress_disp_yy += self.lambda_ * jacobian(w, z) return stress_disp_yy self.add_equation("stress_disp_yy", stress_disp_yy_compute_func) @@ -136,18 +147,11 @@ def stress_disp_yz_compute_func(out): # Equations of equilibrium def equilibrium_x_compute_func(out): x, y, z = out["x"], out["y"], out["z"] - sigma_xx, sigma_xy, sigma_xz = ( - out["sigma_xx"], - out["sigma_xy"], - out["sigma_xz"], - ) - equilibrium_x = -( - jacobian(sigma_xx, x) + jacobian(sigma_xy, y) + jacobian(sigma_xz, z) - ) - # if self.dim == 3: - # z = out["z"] - # sigma_xz = out["sigma_xz"] - # equilibrium_x -= jacobian(sigma_xz, z) + sigma_xx, sigma_xy = out["sigma_xx"], out["sigma_xy"] + equilibrium_x = -jacobian(sigma_xx, x) - jacobian(sigma_xy, y) + if self.dim == 3: + z, sigma_xz = out["z"], out["sigma_xz"] + equilibrium_x -= jacobian(sigma_xz, z) if self.time: t, u = out["t"], out["u"] equilibrium_x += self.rho * hessian(u, t) @@ -162,13 +166,10 @@ def equilibrium_y_compute_func(out): out["sigma_yy"], out["sigma_yz"], ) - equilibrium_y = -( - jacobian(sigma_xy, x) + jacobian(sigma_yy, y) + jacobian(sigma_yz, z) - ) - # if self.dim == 3: - # z = out["z"] - # sigma_yz = out["sigma_yz"] - # equilibrium_y -= jacobian(sigma_yz, z) + equilibrium_y = -jacobian(sigma_xy, x) - jacobian(sigma_yy, y) + if self.dim == 3: + z, sigma_yz = out["z"], out["sigma_yz"] + equilibrium_y -= jacobian(sigma_yz, z) if self.time: t, v = out["t"], out["v"] equilibrium_y += self.rho * hessian(v, t) @@ -185,10 +186,10 @@ def equilibrium_z_compute_func(out): out["sigma_yz"], out["sigma_zz"], ) - equilibrium_z = -( - jacobian(sigma_xz, x) - + jacobian(sigma_yz, y) - + jacobian(sigma_zz, z) + equilibrium_z = ( + -jacobian(sigma_xz, x) + - jacobian(sigma_yz, y) + - jacobian(sigma_zz, z) ) if self.time: t, w = out["t"], out["w"] @@ -199,41 +200,35 @@ def equilibrium_z_compute_func(out): # Traction equations def traction_x_compute_func(out): - normal_x, normal_y, normal_z = ( + normal_x, normal_y = ( out["normal_x"], out["normal_y"], - out["normal_z"], ) - sigma_xx, sigma_xy, sigma_xz = ( + sigma_xx, sigma_xy = ( out["sigma_xx"], out["sigma_xy"], - out["sigma_xz"], ) - traction_x = normal_x * sigma_xx + normal_y * sigma_xy + normal_z * sigma_xz - # if self.dim == 3: - # normal_z = out["normal_z"] - # sigma_xz = out["sigma_xz"] - # traction_x += normal_z * sigma_xz + traction_x = normal_x * sigma_xx + normal_y * sigma_xy + if self.dim == 3: + normal_z, sigma_xz = out["normal_z"], out["sigma_xz"] + traction_x += normal_z * sigma_xz return traction_x self.add_equation("traction_x", traction_x_compute_func) def traction_y_compute_func(out): - normal_x, normal_y, normal_z = ( + normal_x, normal_y = ( out["normal_x"], out["normal_y"], - out["normal_z"], ) - sigma_xy, sigma_yy, sigma_yz = ( + sigma_xy, sigma_yy = ( out["sigma_xy"], out["sigma_yy"], - out["sigma_yz"], ) - traction_y = normal_x * sigma_xy + normal_y * sigma_yy + normal_z * sigma_yz - # if self.dim == 3: - # normal_z = out["normal_z"] - # sigma_yz = out["sigma_yz"] - # traction_y += normal_z * sigma_yz + traction_y = normal_x * sigma_xy + normal_y * sigma_yy + if self.dim == 3: + normal_z, sigma_yz = out["normal_z"], out["sigma_yz"] + traction_y += normal_z * sigma_yz return traction_y self.add_equation("traction_y", traction_y_compute_func) @@ -256,23 +251,21 @@ def traction_z_compute_func(out): # Navier equations def navier_x_compute_func(out): - x, y, z, u, v, w = ( + x, y, u, v = ( out["x"], out["y"], - out["z"], out["u"], out["v"], - out["w"], ) - # duxvywz = jacobian(u, x) + jacobian(v, y) - # duxxuyyuzz = hessian(u, x) + hessian(u, y) - # if self.dim == 3: - # z, w = out["z"], out["w"] - # duxvywz += jacobian(w, z) - # duxxuyyuzz += hessian(u, z) - navier_x = -(self.lambda_ + self.mu) * ( - jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), x) - ) - self.mu * (hessian(u, x) + hessian(u, y) + hessian(u, z)) + duxvywz = jacobian(u, x) + jacobian(v, y) + duxxuyyuzz = hessian(u, x) + hessian(u, y) + if self.dim == 3: + z, w = out["z"], out["w"] + duxvywz += jacobian(w, z) + duxxuyyuzz += hessian(u, z) + navier_x = ( + -(self.lambda_ + self.mu) * jacobian(duxvywz, x) - self.mu * duxxuyyuzz + ) if self.time: t = out["t"] navier_x += rho * hessian(u, t) @@ -281,23 +274,21 @@ def navier_x_compute_func(out): self.add_equation("navier_x", navier_x_compute_func) def navier_y_compute_func(out): - x, y, z, u, v, w = ( + x, y, u, v = ( out["x"], out["y"], - out["z"], out["u"], out["v"], - out["w"], ) - # duxvywz = jacobian(u, x) + jacobian(v, y) - # dvxxvyyvzz = hessian(v, x) + hessian(v, y) - # if self.dim == 3: - # z, w = out["z"], out["w"] - # duxvywz += jacobian(w, z) - # dvxxvyyvzz += hessian(v, z) - navier_y = -(self.lambda_ + self.mu) * ( - jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), y) - ) - self.mu * (hessian(v, x) + hessian(v, y) + hessian(v, z)) + duxvywz = jacobian(u, x) + jacobian(v, y) + dvxxvyyvzz = hessian(v, x) + hessian(v, y) + if self.dim == 3: + z, w = out["z"], out["w"] + duxvywz += jacobian(w, z) + dvxxvyyvzz += hessian(v, z) + navier_y = ( + -(self.lambda_ + self.mu) * jacobian(duxvywz, y) - self.mu * dvxxvyyvzz + ) if self.time: t = out["t"] navier_y += rho * hessian(v, t) @@ -316,11 +307,12 @@ def navier_z_compute_func(out): out["v"], out["w"], ) - # duxvywz = jacobian(u, x) + jacobian(v, y) + jacobian(w, z) - # dwxxvyyvzz = hessian(w, x) + hessian(w, y) + hessian(w, z) - navier_z = -(self.lambda_ + self.mu) * ( - jacobian(jacobian(u, x) + jacobian(v, y) + jacobian(w, z), z) - ) - self.mu * (hessian(w, x) + hessian(w, y) + hessian(w, z)) + duxvywz = jacobian(u, x) + jacobian(v, y) + jacobian(w, z) + dwxxvyyvzz = hessian(w, x) + hessian(w, y) + hessian(w, z) + navier_z = ( + -(self.lambda_ + self.mu) * jacobian(duxvywz, z) + - self.mu * dwxxvyyvzz + ) if self.time: t = out["t"] navier_z += rho * hessian(w, t) diff --git a/ppsci/geometry/mesh.py b/ppsci/geometry/mesh.py index 2d5800d7c..b5b8aa15c 100644 --- a/ppsci/geometry/mesh.py +++ b/ppsci/geometry/mesh.py @@ -65,6 +65,10 @@ def init_mesh(self): self.py_mesh.add_attribute("face_area") self.face_area = self.py_mesh.get_attribute("face_area").reshape([-1]) + if not checker.dynamic_import_to_globals(["open3d"]): + raise ModuleNotFoundError + import open3d + self.open3d_mesh = open3d.geometry.TriangleMesh( open3d.utility.Vector3dVector(np.array(self.py_mesh.vertices)), open3d.utility.Vector3iVector(np.array(self.py_mesh.faces)), @@ -107,6 +111,10 @@ def sdf_func(self, points: np.ndarray) -> np.ndarray: is 0. Therefore, when used for weighting, a negative sign is often added before the result of this function. """ + if not checker.dynamic_import_to_globals(["pymesh"]): + raise ModuleNotFoundError + import pymesh + sdf, _, _, _ = pymesh.signed_distance_to_mesh(self.py_mesh, points) sdf = sdf[..., np.newaxis] return sdf @@ -394,6 +402,7 @@ def random_points(self, n, random="pseudo", criteria=None): while _size < n: random_points = cuboid.random_points(n, random) valid_mask = self.is_inside(random_points) + if criteria: valid_mask &= criteria( *np.split(random_points, self.ndim, axis=1) From 1e4a90a83351acb409c96749bb34b28d951c66ff Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Tue, 23 May 2023 11:21:08 +0000 Subject: [PATCH 08/10] remove float, int int type hint for PEP 484 --- examples/bracket/bracket.py | 1 - examples/bracket/stl/aux_lower.stl | Bin 684 -> 0 bytes examples/bracket/stl/aux_upper.stl | Bin 684 -> 0 bytes examples/bracket/stl/bracket.stl | Bin 684 -> 0 bytes examples/bracket/stl/cylinder_hole.stl | Bin 50084 -> 0 bytes examples/bracket/stl/cylinder_lower.stl | Bin 50084 -> 0 bytes examples/bracket/stl/cylinder_upper.stl | Bin 50084 -> 0 bytes examples/bracket/stl/support.stl | Bin 684 -> 0 bytes ppsci/constraint/boundary_constraint.py | 2 +- ppsci/data/dataset/csv_dataset.py | 2 +- ppsci/data/dataset/mat_dataset.py | 2 +- ppsci/data/process/transform/preprocess.py | 4 ++-- ppsci/utils/reader.py | 4 ++-- 13 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 examples/bracket/stl/aux_lower.stl delete mode 100644 examples/bracket/stl/aux_upper.stl delete mode 100644 examples/bracket/stl/bracket.stl delete mode 100644 examples/bracket/stl/cylinder_hole.stl delete mode 100644 examples/bracket/stl/cylinder_lower.stl delete mode 100644 examples/bracket/stl/cylinder_upper.stl delete mode 100644 examples/bracket/stl/support.stl diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 811323ff1..1771494dd 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -17,7 +17,6 @@ """ import numpy as np -import paddle from paddle import fluid import ppsci diff --git a/examples/bracket/stl/aux_lower.stl b/examples/bracket/stl/aux_lower.stl deleted file mode 100644 index 75f704daba6621b7c717e7feeb6818a63e67f560..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmbu6%?-jZ3`RWwL-f=I7$8XWScw@xq(_7h7j9Xs%>rR3;`$e*5(jd!qVMI|&hZh? z&v@-FukLh@xBUSBot@$-PeGApWOeNb%if|0Q*VJ^8f$< diff --git a/examples/bracket/stl/aux_upper.stl b/examples/bracket/stl/aux_upper.stl deleted file mode 100644 index 031ea4c320994846a5c12cde0e19feba16dbcb44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmbu6Jr2S!3`Ttb4$+AVFt=aLd%3}sd;ay5UO?$4#!Rpcr(h>kddtw}rNcAl_Ybv9IiCSYy zXmV^3KILoI6?@FVy>IxaAM*H=)#~65aW^uBrTngZjr4Cqccl(7piAcXPO&@C-Xu@1 fhTznC-x76SLg;_gl+KaqpPVE6$^EZcSNPHg|316( diff --git a/examples/bracket/stl/bracket.stl b/examples/bracket/stl/bracket.stl deleted file mode 100644 index 3c2d446e7ca4d83da631afaacfa000fee949d44a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmbu6!41MN3`H|Qhv*KRt0KXj89?NUD#V3b7puBJ*@+&8O7w73#&Pex*kged- be$np74Ez@oQ*c3ZE9g%N5l@)wM0tfb#CN>i diff --git a/examples/bracket/stl/cylinder_hole.stl b/examples/bracket/stl/cylinder_hole.stl deleted file mode 100644 index 928cb146fe1f9a3088654229ae3cefff9cef9200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50084 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 diff --git a/examples/bracket/stl/cylinder_lower.stl b/examples/bracket/stl/cylinder_lower.stl deleted file mode 100644 index cfccb775df1d11abc65e72b7296d8804a9c79ab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50084 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 diff --git a/examples/bracket/stl/cylinder_upper.stl b/examples/bracket/stl/cylinder_upper.stl deleted file mode 100644 index 2ac96f561389aaaf4579b0d482e9b7d0afb5651d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50084 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 diff --git a/examples/bracket/stl/support.stl b/examples/bracket/stl/support.stl deleted file mode 100644 index aca0cf913eaed40b07b8d86f97b897e68b92b839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmbu6K@P$&3o-l6A6UJMD3*muI~V z*Vk}<)cgKa{)=7W^I?_qQVKQqF*NejIBQOvGMq^5gnYb-kPG={hbw7$yM()3i(vhr zy!MD&&!s`H!HnUq)Nsko3L@e=b&2K{P5m1Im-o{!TH-%wf)CfWM=)KYO%hf71@^kd SGzsMD(axj8_f6IfqS0@zrh)MQ diff --git a/ppsci/constraint/boundary_constraint.py b/ppsci/constraint/boundary_constraint.py index ebb279bfb..ea079078a 100644 --- a/ppsci/constraint/boundary_constraint.py +++ b/ppsci/constraint/boundary_constraint.py @@ -71,7 +71,7 @@ class BoundaryConstraint(base.Constraint): def __init__( self, output_expr: Dict[str, Callable], - label_dict: Dict[str, Union[int, float, Callable]], + label_dict: Dict[str, Union[float, Callable]], geom: geometry.Geometry, dataloader_cfg: Dict[str, Any], loss: loss.Loss, diff --git a/ppsci/data/dataset/csv_dataset.py b/ppsci/data/dataset/csv_dataset.py index cbee44738..a488dd714 100644 --- a/ppsci/data/dataset/csv_dataset.py +++ b/ppsci/data/dataset/csv_dataset.py @@ -178,7 +178,7 @@ def __init__( label_keys: Tuple[str, ...], alias_dict: Optional[Dict[str, str]] = None, weight_dict: Optional[Dict[str, Union[Callable, float]]] = None, - timestamps: Optional[Tuple[Union[int, float], ...]] = None, + timestamps: Optional[Tuple[float, ...]] = None, transforms: Optional[vision.Compose] = None, ): super().__init__() diff --git a/ppsci/data/dataset/mat_dataset.py b/ppsci/data/dataset/mat_dataset.py index 50aa2b752..2fcb3afcd 100644 --- a/ppsci/data/dataset/mat_dataset.py +++ b/ppsci/data/dataset/mat_dataset.py @@ -178,7 +178,7 @@ def __init__( label_keys: Tuple[str, ...] = (), alias_dict: Optional[Dict[str, str]] = None, weight_dict: Optional[Dict[str, Union[Callable, float]]] = None, - timestamps: Optional[Tuple[Union[int, float], ...]] = None, + timestamps: Optional[Tuple[float, ...]] = None, transforms: Optional[vision.Compose] = None, ): super().__init__() diff --git a/ppsci/data/process/transform/preprocess.py b/ppsci/data/process/transform/preprocess.py index e040d7a36..44f078a63 100644 --- a/ppsci/data/process/transform/preprocess.py +++ b/ppsci/data/process/transform/preprocess.py @@ -31,7 +31,7 @@ class Translate: >>> translate = ppsci.data.transform.Translate({"x": 1.0, "y": -1.0}) """ - def __init__(self, offset: Dict[str, Union[int, float]]): + def __init__(self, offset: Dict[str, float]): self.offset = offset def __call__(self, data_dict): @@ -53,7 +53,7 @@ class Scale: >>> translate = ppsci.data.transform.Scale({"x": 1.5, "y": 2.0}) """ - def __init__(self, scale: Dict[str, Union[int, float]]): + def __init__(self, scale: Dict[str, float]): self.scale = scale def __call__(self, data_dict): diff --git a/ppsci/utils/reader.py b/ppsci/utils/reader.py index f0f97a19f..f73e56344 100644 --- a/ppsci/utils/reader.py +++ b/ppsci/utils/reader.py @@ -122,7 +122,7 @@ def load_mat_file( def load_vtk_file( filename_without_timeid: str, - time_step: Union[float, int], + time_step: float, time_index: Tuple[int, ...], input_keys: Tuple[str, ...], label_keys: Optional[Tuple[str, ...]], @@ -131,7 +131,7 @@ def load_vtk_file( Args: filename_without_timeid (str): File name without time id. - time_step (Union[float, Dict]): Physical time step. + time_step (float): Physical time step. time_index (Tuple[int, ...]): Physical time indexes. input_keys (Tuple[str, ...]): Input coordinates name keys. label_keys (Optional[Tuple[str, ...]]): Input label name keys. From a7b7ffe744305766496cab5fca976eb91944b1d3 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Thu, 25 May 2023 09:10:31 +0000 Subject: [PATCH 09/10] update doc and doctest for LinearElasticity --- docs/zh/api/equation.md | 1 + examples/bracket/bracket.py | 12 ++++++------ ppsci/equation/pde/linear_elasticity.py | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/zh/api/equation.md b/docs/zh/api/equation.md index 2e46edff5..e391cdad1 100644 --- a/docs/zh/api/equation.md +++ b/docs/zh/api/equation.md @@ -7,6 +7,7 @@ - PDE - Biharmonic - Laplace + - LinearElasticity - NavierStokes - NormalDotVec - Poisson diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index 1771494dd..da30a7d69 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -76,7 +76,7 @@ cylinder_hole = ppsci.geometry.Mesh("./stl/cylinder_hole.stl") cylinder_lower = ppsci.geometry.Mesh("./stl/cylinder_lower.stl") cylinder_upper = ppsci.geometry.Mesh("./stl/cylinder_upper.stl") - + # geometry bool operation curve_lower = aux_lower - cylinder_lower curve_upper = aux_upper - cylinder_upper geo = support + bracket + curve_lower + curve_upper - cylinder_hole @@ -93,7 +93,7 @@ "drop_last": True, "shuffle": True, }, - "num_workers": 2, + "num_workers": 1, } # set constraint @@ -382,15 +382,15 @@ validator=validator, visualizer=visualizer, ) - # # train model + # train model solver.train() - # # evaluate after finished training + # evaluate after finished training solver.eval() - # # visualize prediction after finished training + # visualize prediction after finished training solver.visualize() - # # directly evaluate pretrained model(optional) + # directly evaluate pretrained model(optional) logger.init_logger("ppsci", f"{OUTPUT_DIR}/eval.log", "info") solver = ppsci.solver.Solver( model, diff --git a/ppsci/equation/pde/linear_elasticity.py b/ppsci/equation/pde/linear_elasticity.py index cf9503b6e..c25fc2639 100644 --- a/ppsci/equation/pde/linear_elasticity.py +++ b/ppsci/equation/pde/linear_elasticity.py @@ -21,24 +21,30 @@ class LinearElasticity(base.PDE): """Linear elasticity equations. - Use either (E, nu) or (lambda_, mu) to define the material properties. + Use either (E, nu) or (lambda_, mu) to define the material properties. Args: E (Optional[float]): The Young's modulus. Defaults to None. nu (Optional[float]): The Poisson's ratio. Defaults to None. lambda_ (Optional[float]): Lamé's first parameter. Defaults to None. - mu (Optional[float], optional): Lamé's second parameter (shear modulus). Defaults to None. Defaults to None. + mu (Optional[float]): Lamé's second parameter (shear modulus). Defaults to None. rho (float, optional): Mass density.. Defaults to 1. dim (int, optional): Dimension of the linear elasticity (2 or 3). Defaults to 3. time (bool, optional): Whether contains time data. Defaults to False. + + Examples: + >>> import ppsci + >>> pde = ppsci.equation.LinearElasticity( + ... E=None, nu=None, lambda_=1e4, mu=100, dim=3 + ... ) """ def __init__( self, - E: Optional[float], - nu: Optional[float], - lambda_: Optional[float], - mu: Optional[float], + E: Optional[float] = None, + nu: Optional[float] = None, + lambda_: Optional[float] = None, + mu: Optional[float] = None, rho: float = 1, dim: int = 3, time: bool = False, From edabd2e550663ea55f0b9f480e306a9acadc6904 Mon Sep 17 00:00:00 2001 From: HydrogenSulfate <490868991@qq.com> Date: Sun, 28 May 2023 13:23:51 +0000 Subject: [PATCH 10/10] update success reprod code --- examples/bracket/bracket.py | 9 ++++----- ppsci/geometry/mesh.py | 4 +++- ppsci/utils/expression.py | 8 ++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/bracket/bracket.py b/examples/bracket/bracket.py index da30a7d69..a0c8d6853 100644 --- a/examples/bracket/bracket.py +++ b/examples/bracket/bracket.py @@ -17,7 +17,6 @@ """ import numpy as np -from paddle import fluid import ppsci from ppsci.utils import config @@ -27,7 +26,7 @@ # fluid.core.set_prim_eager_enabled(True) args = config.parse_args() # set random seed for reproducibility - ppsci.utils.misc.set_random_seed(42) + ppsci.utils.misc.set_random_seed(2023) # set output directory OUTPUT_DIR = "./output_bracket" if not args.output_dir else args.output_dir # initialize logger @@ -350,9 +349,9 @@ "visulzie_u_v_w_sigmas": ppsci.visualize.VisualizerVtu( input_dict, { - "u": lambda d: d["u"], - "v": lambda d: d["v"], - "w": lambda d: d["w"], + "u": lambda out: out["u"], + "v": lambda out: out["v"], + "w": lambda out: out["w"], "sigma_xx": lambda out: out["sigma_xx"], "sigma_yy": lambda out: out["sigma_yy"], "sigma_zz": lambda out: out["sigma_zz"], diff --git a/ppsci/geometry/mesh.py b/ppsci/geometry/mesh.py index acfc2ba19..cc83aa2c3 100644 --- a/ppsci/geometry/mesh.py +++ b/ppsci/geometry/mesh.py @@ -348,7 +348,9 @@ def random_boundary_points(self, n, random="pseudo", criteria=None): face_points = sample_in_triangle( self.v0[i], self.v1[i], self.v2[i], npoint, random, criteria ) - face_normal = np.tile(self.face_normal[i], [npoint, 1]) + face_normal = np.tile(self.face_normal[i], [npoint, 1]).astype( + paddle.get_default_dtype() + ) valid_area = np.full( [npoint, 1], valid_areas[i] / npoint, diff --git a/ppsci/utils/expression.py b/ppsci/utils/expression.py index e9ec5619d..8c9bb92df 100644 --- a/ppsci/utils/expression.py +++ b/ppsci/utils/expression.py @@ -65,6 +65,10 @@ def train_forward( else: raise TypeError(f"expr type({type(expr)}) is invalid") + # put field 'area' into output_dict + if "area" in input_dicts[i]: + output_dict["area"] = input_dicts[i]["area"] + output_dicts.append(output_dict) # clear differentiation cache @@ -104,6 +108,10 @@ def eval_forward( else: raise TypeError(f"expr type({type(expr)}) is invalid") + # put field 'area' into output_dict + if "area" in input_dict: + output_dict["area"] = input_dict["area"] + # clear differentiation cache clear()