Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 71 additions & 15 deletions meshmode/discretization/connection/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ def _build_boundary_connection(actx, vol_discr, bdry_discr, connection_data,
data = connection_data[igrp, face_id]

bdry_unit_nodes_01 = (bdry_grp.unit_nodes + 1)*0.5
result_unit_nodes = (np.dot(data.A, bdry_unit_nodes_01).T + data.b).T
result_unit_nodes = (
np.dot(data.face_basis.T, bdry_unit_nodes_01).T
+ data.face_offset).T

batches.append(
InterpolationBatch(
Expand Down Expand Up @@ -208,7 +210,7 @@ def make_face_restriction(actx, discr, group_factory, boundary_tag,

# }}}

from meshmode.mesh import Mesh, SimplexElementGroup
from meshmode.mesh import Mesh, SimplexElementGroup, TensorProductElementGroup
bdry_mesh_groups = []
connection_data = {}

Expand All @@ -220,9 +222,10 @@ def make_face_restriction(actx, discr, group_factory, boundary_tag,

mgrp = grp.mesh_el_group

if not isinstance(mgrp, SimplexElementGroup):
if not isinstance(mgrp, (SimplexElementGroup, TensorProductElementGroup)):
raise NotImplementedError("can only take boundary of "
"SimplexElementGroup-based meshes")
"meshes based on SimplexElementGroup and "
"TensorProductElementGroup")

# {{{ pull together per-group face lists

Expand Down Expand Up @@ -282,14 +285,31 @@ def make_face_restriction(actx, discr, group_factory, boundary_tag,
else:
ngroup_bdry_elements = len(group_boundary_faces)

# make up some not-terrible nodes for the boundary Mesh
if isinstance(mgrp, SimplexElementGroup):
bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order)
vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order)

nbdry_el_vertices = (mgrp.dim-1) + 1

elif isinstance(mgrp, TensorProductElementGroup):
bdry_unit_nodes = \
mp.legendre_gauss_lobatto_tensor_product_nodes(
mgrp.dim-1, mgrp.order)
vol_basis = mp.legendre_tensor_product_basis(
mgrp.dim, mgrp.order)

nbdry_el_vertices = 2**(mgrp.dim-1)

else:
assert False

vertex_indices = np.empty(
(ngroup_bdry_elements, mgrp.dim+1-1),
(ngroup_bdry_elements, nbdry_el_vertices),
mgrp.vertex_indices.dtype)

bdry_unit_nodes = mp.warp_and_blend_nodes(mgrp.dim-1, mgrp.order)
bdry_unit_nodes_01 = (bdry_unit_nodes + 1)*0.5

vol_basis = mp.simplex_onb(mgrp.dim, mgrp.order)
nbdry_unit_nodes = bdry_unit_nodes_01.shape[-1]
nodes = np.empty(
(discr.ambient_dim, ngroup_bdry_elements, nbdry_unit_nodes),
Expand All @@ -310,16 +330,52 @@ def make_face_restriction(actx, discr, group_factory, boundary_tag,
face_vertex_unit_coordinates = \
grp_vertex_unit_coordinates[loc_face_vertices]

# Find A, b such that A [e_1 e_2] + b = [r_1 r_2]
# Find face_basis, face_offset such that
# face_basis.T @ [e_1 e_2] + face_offset == [r_1 r_2].
#
# (Notation assumes that the volume is 3D and the face is 2D.
# Code does not.)

b = face_vertex_unit_coordinates[0]
A = ( # noqa
face_offset = face_vertex_unit_coordinates[0]
face_basis = (
face_vertex_unit_coordinates[1:]
- face_vertex_unit_coordinates[0]).T
- face_offset)

if isinstance(mgrp, SimplexElementGroup):
# no further action required
pass

elif isinstance(mgrp, TensorProductElementGroup):
if mgrp.dim <= 2:
# faces are simplices, no action required
pass

elif mgrp.dim == 3:
# Drop the trailing basis vector, so that the convex
# combination of the remaining two is a subset of the face.
reduced_face_basis = face_basis[:-1]

# FIXME: This makes the tests pass, but I do not understand
# why this reversal is necessary.
reduced_face_basis = reduced_face_basis[::-1]
Comment thread
alexfikl marked this conversation as resolved.

# assert that all basis vectors are axis-aligned
for bvec in reduced_face_basis:
assert sum(abs(entry) > 1e-13 for entry in bvec) == 1

face_basis = reduced_face_basis
del reduced_face_basis

else:
raise NotImplementedError(
f"face restriction of a {mgrp.dim}D hypercube")

else:
assert False

face_unit_nodes = (np.dot(A, bdry_unit_nodes_01).T + b).T
face_unit_nodes = (
np.dot(face_basis.T, bdry_unit_nodes_01).T
+ face_offset).T

resampling_mat = mp.resampling_matrix(
vol_basis,
Expand All @@ -346,14 +402,14 @@ def make_face_restriction(actx, discr, group_factory, boundary_tag,
connection_data[igrp, face_id] = _ConnectionBatchData(
group_source_element_indices=batch_boundary_el_numbers_in_grp,
group_target_element_indices=new_el_numbers,
A=A,
b=b,
face_basis=face_basis,
face_offset=face_offset,
)

is_last_face = face_id + 1 == mgrp.nfaces

if per_face_groups or is_last_face:
bdry_mesh_group = SimplexElementGroup(
bdry_mesh_group = type(mgrp)(
mgrp.order, vertex_indices, nodes,
unit_nodes=bdry_unit_nodes)
bdry_mesh_groups.append(bdry_mesh_group)
Expand Down
38 changes: 31 additions & 7 deletions meshmode/mesh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,14 +385,31 @@ class TensorProductElementGroup(MeshElementGroup):
def __init__(self, order, vertex_indices, nodes,
element_nr_base=None, node_nr_base=None,
unit_nodes=None):
"""
r"""
:arg order: the maximum total degree used for interpolation.
:arg nodes: ``[ambient_dim, nelements, nunit_nodes]``
The nodes are assumed to be mapped versions of *unit_nodes*.
:arg unit_nodes: ``[dim, nunit_nodes]``
The unit nodes of which *nodes* is a mapped
version.

The vertex order in 3D is as follows ::


^ s
| 6-----7
| /| /|
|/ | / |
2-----3 |
| | | |
| 4--|--5
| / | /
|/ |/
0-----1---->r

For general dimensions, follow binary upward counting:
000, 001, 010, 011, ...

Do not supply *element_nr_base* and *node_nr_base*, they will be
automatically assigned.
"""
Expand Down Expand Up @@ -426,12 +443,19 @@ def face_vertex_indices(self):
)
elif self.dim == 3:
return (
(0, 2, 1, 3),
(4, 6, 5, 7),
(0, 4, 5, 1),
(2, 6, 7, 3),
(0, 4, 6, 2),
(1, 5, 7, 3),
# binary is tsr

# rs-aligned
(0b000, 0b001, 0b010, 0b011,),
(0b100, 0b101, 0b110, 0b111,),

# st-aligned
(0b000, 0b010, 0b100, 0b110,),
(0b001, 0b011, 0b101, 0b111,),

# rt-aligned
(0b000, 0b001, 0b100, 0b101,),
(0b010, 0b011, 0b110, 0b111,),
)
else:
raise NotImplementedError(f"dimension {self.dim}")
Expand Down
20 changes: 16 additions & 4 deletions test/test_meshmode.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,11 @@ def test_box_boundary_tags(dim, nelem, mesh_type, group_cls, visualize=False):
InterpolatoryQuadratureSimplexGroupFactory,
PolynomialWarpAndBlendGroupFactory,
partial(PolynomialRecursiveNodesGroupFactory, family="lgl"),

# Redundant, no information gain.
# partial(PolynomialRecursiveNodesGroupFactory, family="gc"),

# FIXME: need support in make_face_restriction
# LegendreGaussLobattoTensorProductGroupFactory,
LegendreGaussLobattoTensorProductGroupFactory,
])
@pytest.mark.parametrize("boundary_tag", [
BTAG_ALL,
Expand All @@ -383,10 +384,14 @@ def test_box_boundary_tags(dim, nelem, mesh_type, group_cls, visualize=False):
])
@pytest.mark.parametrize(("mesh_name", "dim", "mesh_pars"), [
("blob", 2, ["8e-2", "6e-2", "4e-2"]),

# If "warp" works, "rect" likely does, too.
# ("rect", 3, [10, 20, 30]),

("warp", 2, [10, 20, 30]),
("warp", 3, [10, 20, 30]),
])
@pytest.mark.parametrize("per_face_groups", [False, True])
@pytest.mark.parametrize("per_face_groups", [True, False])
def test_boundary_interpolation(actx_factory, group_factory, boundary_tag,
mesh_name, dim, mesh_pars, per_face_groups):
if (group_factory is LegendreGaussLobattoTensorProductGroupFactory
Expand Down Expand Up @@ -439,6 +444,13 @@ def f(x):
group_cls=group_cls)

h = 1/mesh_par

elif mesh_name == "rect":
from meshmode.mesh.generation import generate_regular_rect_mesh
mesh = generate_regular_rect_mesh(a=(0,)*dim, b=(1,)*dim,
order=4, n=(mesh_par,)*dim, group_cls=group_cls)

h = 1/mesh_par
else:
raise ValueError("mesh_name not recognized")

Expand Down Expand Up @@ -478,7 +490,7 @@ def f(x):
print(eoc_rec)
assert (
eoc_rec.order_estimate() >= order-order_slack
or eoc_rec.max_error() < 3e-14)
or eoc_rec.max_error() < 1e-13)

# }}}

Expand Down