@@ -193,6 +193,10 @@ def validate(self, sim, atol=1e-15):
193193 self .validate1d (sim , atol )
194194 elif sim .ndim == 2 :
195195 self .validate2d (sim , atol )
196+ elif sim .ndim == 3 :
197+ self .validate3d (sim , atol )
198+ else :
199+ raise ValueError ("Unknown dimension" )
196200
197201 def validate1d (self , sim , atol ):
198202 domain_box = Box ([0 ] * sim .ndim , sim .cells )
@@ -301,3 +305,79 @@ def getCoord(L, R, idir):
301305 )
302306 if sim .strict :
303307 raise RuntimeError ("Simulation is not periodic" )
308+
309+ def validate3d (self , sim , atol ):
310+ domain_box = Box ([0 ] * sim .ndim , sim .cells )
311+ layout = GridLayout (domain_box , domain_box .lower , sim .dl , sim .interp_order )
312+ nbrDualGhosts = layout .nbrGhostsPrimal (sim .interp_order )
313+ nbrPrimalGhosts = layout .nbrGhostsPrimal (sim .interp_order )
314+ directions = ["X" , "Y" , "Z" ]
315+ domain = sim .simulation_domain ()
316+ bx = self .model_dict ["bx" ]
317+ by = self .model_dict ["by" ]
318+ bz = self .model_dict ["bz" ]
319+ is_periodic = True
320+ not_periodic = []
321+
322+ def getCoord (L , R , idir ):
323+ if idir == 0 :
324+ return (L , np .zeros_like (L ), np .zeros_like (L )), (
325+ R ,
326+ np .zeros_like (R ),
327+ np .zeros_like (R ),
328+ )
329+ else :
330+ return (np .zeros_like (L ), np .zeros_like (L ), L ), (
331+ np .zeros_like (R ),
332+ np .zeros_like (R ),
333+ R ,
334+ )
335+
336+ phare_utilities .debug_print ("3d periodic validation" )
337+ for idir in np .arange (sim .ndim ):
338+ phare_utilities .debug_print ("validating direction ..." , idir )
339+ if sim .boundary_types [idir ] == "periodic" :
340+ phare_utilities .debug_print (f"direction { idir } is periodic?" )
341+ dual_left = (np .arange (- nbrDualGhosts , nbrDualGhosts ) + 0.5 ) * sim .dl [0 ]
342+ dual_right = dual_left + domain [0 ]
343+ primal_left = np .arange (- nbrPrimalGhosts , nbrPrimalGhosts ) * sim .dl [0 ]
344+ primal_right = primal_left + domain [0 ]
345+
346+ direction = directions [idir ]
347+
348+ for b_i , b_name in zip ((bx , by , bz ), ("Bx" , "By" , "Bz" )):
349+ if layout .qtyIsDual (b_name , direction ):
350+ L , R = dual_left , dual_right
351+ else :
352+ L , R = primal_left , primal_right
353+
354+ coordsL , coordsR = getCoord (L , R , idir )
355+ check = np .allclose (b_i (* coordsL ), b_i (* coordsR ), atol = atol , rtol = 0 )
356+ if not check :
357+ not_periodic += [(b_name , idir )]
358+ is_periodic &= check
359+
360+ for pop in self .populations :
361+ functions = ("vx" , "vy" , "vz" , "vthx" , "vthy" , "vthz" )
362+ L , R = primal_left , primal_right
363+ coordsL , coordsR = getCoord (L , R , idir )
364+
365+ for fn in functions :
366+ f = self .model_dict [pop ][fn ]
367+ fL = f (* coordsL )
368+ fR = f (* coordsR )
369+ check = np .allclose (fL , fR , atol = atol , rtol = 0 )
370+ phare_utilities .debug_print (
371+ f"checked { fn } : fL = { fL } and fR = { fR } and check = { check } "
372+ )
373+ if not check :
374+ not_periodic += [(fn , idir )]
375+ is_periodic &= check
376+
377+ if not is_periodic :
378+ print (
379+ "Warning: Simulation is periodic but some functions are not : " ,
380+ not_periodic ,
381+ )
382+ if sim .strict :
383+ raise RuntimeError ("Simulation is not periodic" )
0 commit comments