@@ -1060,18 +1060,238 @@ def du_xyz(x, y, z):
10601060 assert err_ux < 2.5e-2
10611061 else :
10621062 assert err_ux < 1.84e-1
1063+
1064+
1065+ @pytest .mark .parametrize ("boxes_per_dim" , [(12 , 12 , 1 )])
1066+ @pytest .mark .parametrize ("kernel" , ["trigonometric_2d" , "gaussian_2d" , "linear_2d" ])
1067+ @pytest .mark .parametrize ("derivative" , [0 , 1 , 2 ])
1068+ @pytest .mark .parametrize ("bc_x" , ["periodic" , "mirror" , "fixed" ])
1069+ @pytest .mark .parametrize ("bc_y" , ["periodic" , "mirror" , "fixed" ])
1070+ @pytest .mark .parametrize ("eval_pts" , [11 , 16 ])
1071+ def test_sph_velocity_evaluation_2d (
1072+ boxes_per_dim ,
1073+ kernel ,
1074+ derivative ,
1075+ bc_x ,
1076+ bc_y ,
1077+ eval_pts ,
1078+ tesselation ,
1079+ show_plot = False ,
1080+ ):
1081+
1082+ if isinstance (MPI .COMM_WORLD , MockComm ):
1083+ comm = None
1084+ rank = 0
1085+ else :
1086+ comm = MPI .COMM_WORLD
1087+ rank = comm .Get_rank ()
1088+
1089+ dom_type = "Cuboid"
1090+ dom_params = {"l1" : 0.0 , "r1" : 1.0 , "l2" : 0.0 , "r2" : 1.0 , "l3" : 0.0 , "r3" : 1.0 }
1091+ domain_class = getattr (domains , dom_type )
1092+ domain = domain_class (** dom_params )
1093+
1094+ if tesselation :
1095+ ppb = 50
1096+ loading_params = LoadingParameters (ppb = ppb , seed = 1607 , loading = "tesselation" )
1097+ else :
1098+ ppb = 400
1099+ loading_params = LoadingParameters (ppb = ppb , seed = 223 )
1100+
1101+ Lx = dom_params ["r1" ] - dom_params ["l1" ]
1102+ Ly = dom_params ["r2" ] - dom_params ["l2" ]
1103+
1104+ # analytic 2D velocity field:
1105+ # u_x = cos(2π e1) * cos(2π e2)
1106+ # u_y = sin(2π e1) * sin(2π e2)
1107+ # u_z = 0
1108+ def u_xyz (e1 , e2 , e3 ):
1109+ ux = xp .cos (2 * xp .pi / Lx * e1 ) * xp .cos (2 * xp .pi / Ly * e2 )
1110+ uy = xp .sin (2 * xp .pi / Lx * e1 ) * xp .sin (2 * xp .pi / Ly * e2 )
1111+ uz = 0.0 * e1
1112+ return (ux , uy , uz )
1113+
1114+ # derivatives:
1115+ # derivative == 1 -> ∂/∂e1
1116+ # derivative == 2 -> ∂/∂e2
1117+ def du_de1 (e1 , e2 , e3 ):
1118+ dux = - 2 * xp .pi / Lx * xp .sin (2 * xp .pi / Lx * e1 ) * xp .cos (2 * xp .pi / Ly * e2 )
1119+ duy = 2 * xp .pi / Lx * xp .cos (2 * xp .pi / Lx * e1 ) * xp .sin (2 * xp .pi / Ly * e2 )
1120+ duz = 0.0 * e1
1121+ return (dux , duy , duz )
1122+
1123+ def du_de2 (e1 , e2 , e3 ):
1124+ dux = - 2 * xp .pi / Ly * xp .cos (2 * xp .pi / Lx * e1 ) * xp .sin (2 * xp .pi / Ly * e2 )
1125+ duy = 2 * xp .pi / Ly * xp .sin (2 * xp .pi / Lx * e1 ) * xp .cos (2 * xp .pi / Ly * e2 )
1126+ duz = 0.0 * e1
1127+ return (dux , duy , duz )
1128+
1129+ background = GenericCartesianFluidEquilibrium (u_xyz = u_xyz )
1130+ background .domain = domain
1131+
1132+ boundary_params = BoundaryParameters (bc_sph = (bc_x , bc_y , "periodic" ))
1133+
1134+ particles = ParticlesSPH (
1135+ comm_world = comm ,
1136+ loading_params = loading_params ,
1137+ boundary_params = boundary_params ,
1138+ boxes_per_dim = boxes_per_dim ,
1139+ bufsize = 2.0 ,
1140+ box_bufsize = 4.0 ,
1141+ domain = domain ,
1142+ background = background ,
1143+ n_as_volume_form = True ,
1144+ verbose = False ,
1145+ )
1146+
1147+ # evaluation grid
1148+ eta1 = xp .linspace (0 , 1.0 , eval_pts )
1149+ eta2 = xp .linspace (0 , 1.0 , eval_pts )
1150+ eta3 = xp .array ([0.0 ])
1151+ ee1 , ee2 , ee3 = xp .meshgrid (eta1 , eta2 , eta3 , indexing = "ij" )
1152+
1153+ # initialize particles
1154+ particles .draw_markers (sort = True , verbose = False )
1155+ if comm is not None :
1156+ particles .mpi_sort_markers ()
1157+ particles .initialize_weights ()
1158+
1159+ # evaluate velocity (and derivatives) via SPH
1160+ h1 = 1 / boxes_per_dim [0 ]
1161+ h2 = 1 / boxes_per_dim [1 ]
1162+ h3 = 1 / boxes_per_dim [2 ]
1163+
1164+ v1 , v2 , v3 = particles .eval_velocity (
1165+ ee1 ,
1166+ ee2 ,
1167+ ee3 ,
1168+ h1 = h1 ,
1169+ h2 = h2 ,
1170+ h3 = h3 ,
1171+ kernel_type = kernel ,
1172+ derivative = derivative ,
1173+ )
1174+
1175+ if derivative == 0 :
1176+ v1_e , v2_e , v3_e = background .u_xyz (ee1 , ee2 , ee3 )
1177+ elif derivative == 1 :
1178+ v1_e , v2_e , v3_e = du_de1 (ee1 , ee2 , ee3 )
1179+ else : # derivative == 2
1180+ v1_e , v2_e , v3_e = du_de2 (ee1 , ee2 , ee3 )
1181+
1182+
1183+ if comm is not None :
1184+ all_velo1 = xp .zeros_like (v1 )
1185+ all_velo2 = xp .zeros_like (v2 )
1186+ all_velo3 = xp .zeros_like (v3 )
1187+ comm .Allreduce (v1 , all_velo1 , op = MPI .SUM )
1188+ comm .Allreduce (v2 , all_velo2 , op = MPI .SUM )
1189+ comm .Allreduce (v3 , all_velo3 , op = MPI .SUM )
1190+ else :
1191+ all_velo1 , all_velo2 , all_velo3 = v1 , v2 , v3
1192+
1193+ def abs_err (num , exact ):
1194+ max_exact = xp .max (xp .abs (exact ))
1195+ #if max_exact == 0:
1196+ #return xp.max(xp.abs(num))
1197+ return xp .max (xp .abs (num - exact )) / max_exact
1198+
1199+ err_ux = abs_err (all_velo1 , v1_e )
1200+ err_uy = abs_err (all_velo2 , v2_e )
1201+
1202+
1203+ if rank == 0 :
1204+ print (f"\n { boxes_per_dim = } " )
1205+ print (f"{ kernel = } , { derivative = } " )
1206+ print (f"{ bc_x = } , { bc_y = } , { eval_pts = } " )
1207+ print (f"Velocity errors: ux={ err_ux :.3e} , uy={ err_uy :.3e} " )
1208+
1209+ if show_plot :
1210+ plt .figure (figsize = (12 , 24 ))
1211+ # --- vx plots ---
1212+ plt .subplot (3 , 2 , 1 )
1213+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), v1_e .squeeze ())
1214+ plt .title ("Exact v₁ (uₓ)" )
1215+ plt .colorbar ()
1216+
1217+ plt .subplot (3 , 2 , 3 )
1218+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), all_velo1 .squeeze ())
1219+ plt .title ("SPH v₁ (uₓ)" )
1220+ plt .colorbar ()
1221+
1222+ plt .subplot (3 , 2 , 5 )
1223+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), (all_velo1 - v1_e ).squeeze ())
1224+ plt .title ("Error v₁ (uₓ)" )
1225+ plt .colorbar ()
1226+
1227+ # --- vy plots ---
1228+ plt .subplot (3 , 2 , 2 )
1229+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), v2_e .squeeze ())
1230+ plt .title ("Exact v₂ (u_y)" )
1231+ plt .colorbar ()
1232+
1233+ plt .subplot (3 , 2 , 4 )
1234+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), all_velo2 .squeeze ())
1235+ plt .title ("SPH v₂ (u_y)" )
1236+ plt .colorbar ()
1237+
1238+ plt .subplot (3 , 2 , 6 )
1239+ plt .pcolor (ee1 .squeeze (), ee2 .squeeze (), (all_velo2 - v2_e ).squeeze ())
1240+ plt .title ("Error v₂ (u_y)" )
1241+ plt .colorbar ()
1242+
1243+ plt .tight_layout ()
1244+ plt .savefig ("image_test_2d.png" )
1245+ plt .show ()
1246+
1247+ plt .figure (figsize = (8 , 8 ))
1248+ plt .quiver (ee1 .squeeze (), ee2 .squeeze (), all_velo1 .squeeze (), all_velo2 .squeeze (),
1249+ scale = 30 , pivot = 'mid' , color = 'blue' )
1250+ plt .title ("SPH Velocity Field (v₁, v₂)" )
1251+ plt .xlabel ("x" )
1252+ plt .ylabel ("y" )
1253+ plt .axis ("equal" )
1254+ plt .tight_layout ()
1255+ plt .savefig ("image_test_2d_quiver.png" )
1256+ plt .show ()
1257+
1258+
1259+ # tolerances: conservative values aligned with your 2D density thresholds
1260+ #if derivative == 0:
1261+ # assert err_ux < 0.031
1262+ # assert err_uy < 0.031
1263+ #else:
1264+ # assert err_ux < 0.069
1265+ # assert err_uy < 0.069
1266+
1267+ # ensure z-component is negligible (absolute)
1268+ #assert err_uz_abs < 1e-6
1269+
10631270
10641271
10651272if __name__ == "__main__" :
1066- test_sph_velocity_evaluation (
1067- (12 , 1 , 1 ),
1068- "gaussian_1d" ,
1069- 1 ,
1273+ test_sph_velocity_evaluation_2d (
1274+ (24 ,24 ,1 ),
1275+ "gaussian_2d" ,
1276+ 0 ,
1277+ "periodic" ,
10701278 "periodic" ,
10711279 11 ,
1072- tesselation = False ,
1073- show_plot = True ,
1280+ tesselation = True ,
1281+ show_plot = True
10741282 )
1283+
1284+
1285+
1286+ # test_sph_velocity_evaluation(
1287+ # (12, 1, 1),
1288+ # "gaussian_1d",
1289+ # 1,
1290+ # "periodic",
1291+ # 11,
1292+ # tesselation=False,
1293+ # show_plot=True,
1294+ # )
10751295
10761296 # test_sph_evaluation_1d(
10771297 # (24, 1, 1),
0 commit comments