@@ -394,6 +394,376 @@ def test_derivative_binder_expr():
394394# }}}
395395
396396
397+ # {{{ test_stretch_factor
398+
399+ # {{{ twisted mesh
400+
401+ def make_twisted_mesh (order , cls ):
402+ # 2 3 5
403+ # o------o-----------o
404+ # | | |
405+ # | | |
406+ # | | |
407+ # o------o-----------o
408+ # 0 1 4
409+ #
410+ #
411+ vertices = np .array ([
412+ [- 1 , - 1 , 0 ], [1 , - 1 , 0 ], [- 1 , 1 , 0 ], [1 , 1 , 0 ],
413+ [4 , - 1 , 0 ], [4 , 1 , 0 ],
414+ ], dtype = np .float64 ).T
415+
416+ import meshmode .mesh as mm
417+ if issubclass (cls , mm .SimplexElementGroup ):
418+ vertex_indices = np .array ([
419+ (0 , 1 , 2 ), (1 , 3 , 2 ),
420+ (1 , 4 , 3 ), (4 , 5 , 3 ),
421+ ], dtype = np .int32 )
422+ elif issubclass (cls , mm .TensorProductElementGroup ):
423+ vertex_indices = np .array ([
424+ (0 , 1 , 2 , 3 ), (1 , 4 , 3 , 5 ),
425+ ], dtype = np .int32 )
426+ else :
427+ raise ValueError
428+
429+ from meshmode .mesh .generation import make_group_from_vertices
430+ grp = make_group_from_vertices (
431+ vertices , vertex_indices , order ,
432+ unit_nodes = None ,
433+ group_cls = cls )
434+
435+ def wobble (x ):
436+ result = np .empty_like (x )
437+ result [0 ] = x [0 ] + 0.5 * np .sin (x [1 ])
438+ result [1 ] = x [1 ] + 0.5 * np .cos (x [0 ])
439+ result [2 ] = x [2 ] + 0.5 * np .cos (x [1 ]) + 0.5 * np .sin (x [0 ])
440+ # result[2] = np.sin(x[1]) * np.sin(x[0])
441+
442+ return result
443+
444+ from meshmode .mesh .processing import map_mesh
445+ mesh = mm .Mesh (vertices , [grp ], is_conforming = True )
446+ return map_mesh (mesh , wobble )
447+
448+ # }}}
449+
450+
451+ # {{{ torus elements
452+
453+ def make_torus_mesh (order , cls , a = 2.0 , b = 1.0 , n_major = 12 , n_minor = 6 ):
454+ # NOTE: this is the torus construction before
455+ # https://github.com/inducer/meshmode/pull/288
456+ # which caused very discontinuous stretch factors on simplices
457+ u , v = np .mgrid [0 :2 * np .pi :2 * np .pi / n_major , 0 :2 * np .pi :2 * np .pi / n_minor ]
458+
459+ x = np .cos (u ) * (a + b * np .cos (v ))
460+ y = np .sin (u ) * (a + b * np .cos (v ))
461+ z = b * np .sin (v )
462+ del u , v
463+
464+ vertices = (
465+ np .vstack ((x [np .newaxis ], y [np .newaxis ], z [np .newaxis ]))
466+ .transpose (0 , 2 , 1 ).copy ().reshape (3 , - 1 ))
467+
468+ def idx (i , j ):
469+ return (i % n_major ) + (j % n_minor ) * n_major
470+
471+ import meshmode .mesh as mm
472+ # i, j = 0, 0
473+ i , j = 0 , n_minor // 3
474+ if issubclass (cls , mm .SimplexElementGroup ):
475+ vertex_indices = [
476+ (idx (i , j ), idx (i + 1 , j ), idx (i , j + 1 )),
477+ (idx (i + 1 , j ), idx (i + 1 , j + 1 ), idx (i , j + 1 )),
478+ ]
479+ elif issubclass (cls , mm .TensorProductElementGroup ):
480+ vertex_indices = [(idx (i , j ), idx (i + 1 , j ), idx (i , j + 1 ), idx (i + 1 , j + 1 ))]
481+ else :
482+ raise TypeError (f"unsupported 'group_cls': { cls } " )
483+
484+ from meshmode .mesh .generation import make_group_from_vertices
485+ vertex_indices = np .array (vertex_indices , dtype = np .int32 )
486+ grp = make_group_from_vertices (
487+ vertices , vertex_indices , order ,
488+ group_cls = cls )
489+
490+ # NOTE: project the nodes back to the torus surface
491+ u = np .arctan2 (grp .nodes [1 ], grp .nodes [0 ])
492+ v = np .arctan2 (
493+ grp .nodes [2 ],
494+ grp .nodes [0 ] * np .cos (u ) + grp .nodes [1 ] * np .sin (u ) - a )
495+
496+ nodes = np .empty_like (grp .nodes )
497+ nodes [0 ] = np .cos (u ) * (a + b * np .cos (v ))
498+ nodes [1 ] = np .sin (u ) * (a + b * np .cos (v ))
499+ nodes [2 ] = b * np .sin (v )
500+
501+ return mm .Mesh (vertices , [grp .copy (nodes = nodes )], is_conforming = True )
502+
503+ # }}}
504+
505+
506+ # {{{ gmsh sphere
507+
508+ def make_gmsh_sphere (order : int , cls : type ):
509+ from meshmode .mesh .io import ScriptSource
510+ from meshmode .mesh import SimplexElementGroup , TensorProductElementGroup
511+ if issubclass (cls , SimplexElementGroup ):
512+ script = ScriptSource (
513+ """
514+ Mesh.CharacteristicLengthMax = 0.05;
515+ Mesh.HighOrderOptimize = 1;
516+ Mesh.Algorithm = 1;
517+
518+ SetFactory("OpenCASCADE");
519+ Sphere(1) = {0, 0, 0, 0.5};
520+ """ ,
521+ "geo" )
522+ elif issubclass (cls , TensorProductElementGroup ):
523+ script = ScriptSource (
524+ """
525+ Mesh.CharacteristicLengthMax = 0.05;
526+ Mesh.HighOrderOptimize = 1;
527+ Mesh.Algorithm = 6;
528+
529+ SetFactory("OpenCASCADE");
530+ Sphere(1) = {0, 0, 0, 0.5};
531+ Recombine Surface "*" = 0.0001;
532+ """ ,
533+ "geo" )
534+ else :
535+ raise TypeError
536+
537+ from meshmode .mesh .io import generate_gmsh
538+ return generate_gmsh (
539+ script ,
540+ order = order ,
541+ dimensions = 2 ,
542+ force_ambient_dim = 3 ,
543+ target_unit = "MM" ,
544+ )
545+
546+ # }}}
547+
548+
549+ # {{{ gmsh torus
550+
551+ def make_gmsh_torus (order : int , cls : type ):
552+ from meshmode .mesh .io import ScriptSource
553+ from meshmode .mesh import SimplexElementGroup , TensorProductElementGroup
554+ if issubclass (cls , SimplexElementGroup ):
555+ script = ScriptSource (
556+ """
557+ Mesh.CharacteristicLengthMax = 0.05;
558+ Mesh.HighOrderOptimize = 1;
559+ Mesh.Algorithm = 1;
560+
561+ SetFactory("OpenCASCADE");
562+ Torus(1) = {0, 0, 0, 1, 0.5, 2*Pi};
563+ """ ,
564+ "geo" )
565+ elif issubclass (cls , TensorProductElementGroup ):
566+ script = ScriptSource (
567+ """
568+ Mesh.CharacteristicLengthMax = 0.05;
569+ Mesh.HighOrderOptimize = 1;
570+ Mesh.Algorithm = 7;
571+
572+ SetFactory("OpenCASCADE");
573+ Torus(1) = {0, 0, 0, 1, 0.5, 2*Pi};
574+ Recombine Surface "*" = 0.0001;
575+ """ ,
576+ "geo" )
577+ else :
578+ raise TypeError
579+
580+ from meshmode .mesh .io import generate_gmsh
581+ return generate_gmsh (
582+ script ,
583+ order = order ,
584+ dimensions = 2 ,
585+ force_ambient_dim = 3 ,
586+ target_unit = "MM" ,
587+ )
588+
589+ # }}}
590+
591+
592+ # {{{ symbolic
593+
594+ def metric_from_form1 (form1 , metric_type : str ):
595+ from pytential .symbolic .primitives import _small_sym_mat_eigenvalues
596+ s0 , s1 = _small_sym_mat_eigenvalues (4 * form1 )
597+
598+ if metric_type == "singvals" :
599+ return np .array ([sym .sqrt (s0 ), sym .sqrt (s1 )], dtype = object )
600+ elif metric_type == "det" :
601+ return np .array ([s0 * s1 ], dtype = object )
602+ elif metric_type == "trace" :
603+ return np .array ([s0 + s1 ], dtype = object )
604+ elif metric_type == "norm" :
605+ return np .array ([sym .sqrt (s0 ** 2 + s1 ** 2 )], dtype = object )
606+ elif metric_type == "aspect" :
607+ return np .array ([
608+ (s0 * s1 )** (2 / 3 ) / (s0 ** 2 + s1 ** 2 ),
609+ ], dtype = object )
610+ elif metric_type == "condition" :
611+ import pymbolic .primitives as prim
612+ return np .array ([
613+ prim .Max ((s0 , s1 )) / prim .Min ((s0 , s1 ))
614+ ], dtype = object )
615+ else :
616+ raise ValueError (f"unknown metric type: '{ metric_type } '" )
617+
618+
619+ def make_simplex_stretch_factors (ambient_dim : int , metric_type : str ):
620+ from pytential .symbolic .primitives import \
621+ _equilateral_parametrization_derivative_matrix
622+ equi_pder = _equilateral_parametrization_derivative_matrix (ambient_dim )
623+ equi_form1 = sym .cse (equi_pder .T @ equi_pder , "pd_mat_jtj" )
624+
625+ return metric_from_form1 (equi_form1 , metric_type )
626+
627+
628+ def make_quad_stretch_factors (ambient_dim : int , metric_type : str ):
629+ pder = sym .parametrization_derivative_matrix (ambient_dim , ambient_dim - 1 )
630+ form1 = sym .cse (pder .T @ pder , "pd_mat_jtj" )
631+
632+ return metric_from_form1 (form1 , metric_type )
633+
634+ # }}}
635+
636+
637+ @pytest .mark .parametrize ("order" , [4 , 8 ])
638+ def test_stretch_factor (actx_factory , order ,
639+ mesh_name = "torus" , metric_type = "singvals" ,
640+ visualize = False ):
641+ logging .basicConfig (level = logging .INFO )
642+ actx = actx_factory ()
643+
644+ from meshmode .mesh import SimplexElementGroup , TensorProductElementGroup
645+ if mesh_name == "torus" :
646+ quad_mesh = make_torus_mesh (order , TensorProductElementGroup )
647+ simplex_mesh = make_torus_mesh (order , SimplexElementGroup )
648+ elif mesh_name == "twisted" :
649+ quad_mesh = make_twisted_mesh (order , TensorProductElementGroup )
650+ simplex_mesh = make_twisted_mesh (order , SimplexElementGroup )
651+ elif mesh_name == "sphere" :
652+ from meshmode .mesh .generation import generate_sphere
653+ simplex_mesh = generate_sphere (1 , order = order ,
654+ uniform_refinement_rounds = 1 ,
655+ group_cls = SimplexElementGroup )
656+ quad_mesh = generate_sphere (1 , order = order ,
657+ uniform_refinement_rounds = 1 ,
658+ group_cls = TensorProductElementGroup )
659+ elif mesh_name == "gmsh_sphere" :
660+ simplex_mesh = make_gmsh_sphere (order , cls = SimplexElementGroup )
661+ quad_mesh = make_gmsh_sphere (order , cls = TensorProductElementGroup )
662+ elif mesh_name == "gmsh_torus" :
663+ simplex_mesh = make_gmsh_torus (order , cls = SimplexElementGroup )
664+ quad_mesh = make_gmsh_torus (order , cls = TensorProductElementGroup )
665+ else :
666+ raise ValueError (f"unknown mesh: '{ mesh_name } '" )
667+
668+ ambient_dim = 3
669+ assert simplex_mesh .ambient_dim == ambient_dim
670+ assert quad_mesh .ambient_dim == ambient_dim
671+
672+ from meshmode .discretization import Discretization
673+ import meshmode .discretization .poly_element as mpoly
674+ simplex_discr = Discretization (actx , simplex_mesh ,
675+ mpoly .InterpolatoryEquidistantGroupFactory (order ))
676+ quad_discr = Discretization (actx , quad_mesh ,
677+ mpoly .InterpolatoryEquidistantGroupFactory (order ))
678+
679+ print (f"simplex_discr.ndofs: { simplex_discr .ndofs } " )
680+ print (f"quad_discr.ndofs: { quad_discr .ndofs } " )
681+
682+ sym_simplex = make_simplex_stretch_factors (ambient_dim , metric_type )
683+ sym_quad = make_quad_stretch_factors (ambient_dim , metric_type )
684+
685+ s = bind (simplex_discr , sym_simplex )(actx )
686+ q = bind (quad_discr , sym_quad )(actx )
687+
688+ def print_bounds (x , name ):
689+ for i , si in enumerate (x ):
690+ print ("{}{} [{:.12e}, {:.12e}]" .format (
691+ name , i ,
692+ actx .to_numpy (actx .np .min (si ))[()],
693+ actx .to_numpy (actx .np .min (si ))[()]
694+ ), end = " " )
695+ print ()
696+
697+ print_bounds (s , "s" )
698+ print_bounds (q , "q" )
699+
700+ if not visualize :
701+ return
702+
703+ suffix = f"{ mesh_name } _{ metric_type } _{ order :02d} "
704+
705+ # {{{ plot vtk
706+
707+ s_pder = bind (simplex_discr , sym .parametrization_derivative_matrix (3 , 2 ))(actx )
708+ q_pder = bind (quad_discr , sym .parametrization_derivative_matrix (3 , 2 ))(actx )
709+
710+ from meshmode .discretization .visualization import make_visualizer
711+ vis = make_visualizer (actx , simplex_discr , order , force_equidistant = True )
712+ vis .write_vtk_file (f"simplex_{ suffix } .vtu" ,
713+ [(f"s{ i } " , si ) for i , si in enumerate (s )]
714+ + [(f"J_{ i } _{ j } " , pder ) for (i , j ), pder in np .ndenumerate (s_pder )],
715+ overwrite = True , use_high_order = True )
716+
717+ vis = make_visualizer (actx , quad_discr , order , force_equidistant = True )
718+ vis .write_vtk_file (f"quad_{ suffix } .vtu" ,
719+ [(f"q{ i } " , qi ) for i , qi in enumerate (q )]
720+ + [(f"J_{ i } _{ j } " , pder ) for (i , j ), pder in np .ndenumerate (q_pder )],
721+ overwrite = True , use_high_order = True )
722+
723+ # }}}
724+
725+ if s .size != 2 :
726+ return
727+
728+ s0 , s1 = s
729+ q0 , q1 = q
730+
731+ # {{{ plot reference simplex
732+
733+ if quad_discr .mesh .nelements <= 2 :
734+ import matplotlib .pyplot as plt
735+ fig = plt .figure (figsize = (12 , 10 ), dpi = 300 )
736+
737+ xi = simplex_discr .groups [0 ].unit_nodes
738+ for name , sv in zip (["s0" , "s1" ], [s0 , s1 ]):
739+ sv = actx .to_numpy (sv [0 ])[0 ].ravel ()
740+
741+ ax = fig .gca ()
742+ im = ax .tricontourf (xi [0 ], xi [1 ], sv , levels = 32 )
743+ fig .colorbar (im , ax = ax )
744+ fig .savefig (f"simplex_{ suffix } _{ name } " )
745+ fig .clf ()
746+
747+ # }}}
748+
749+ # {{{ plot reference quad
750+
751+ if quad_discr .mesh .nelements <= 2 :
752+ xi = quad_discr .groups [0 ].unit_nodes
753+ for name , sv in zip (["q0" , "q1" ], [q0 , q1 ]):
754+ sv = actx .to_numpy (sv [0 ])[0 ]
755+
756+ ax = fig .gca ()
757+ im = ax .tricontourf (xi [0 ], xi [1 ], sv , levels = 32 )
758+ fig .colorbar (im , ax = ax )
759+ fig .savefig (f"quad_{ suffix } _{ name } " )
760+ fig .clf ()
761+
762+ # }}}
763+
764+ # }}}
765+
766+
397767# You can test individual routines by typing
398768# $ python test_symbolic.py 'test_routine()'
399769
0 commit comments