Skip to content

Commit 68affce

Browse files
committed
add test case for stretch factors
1 parent 551301d commit 68affce

1 file changed

Lines changed: 370 additions & 0 deletions

File tree

test/test_symbolic.py

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)