-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OrientedBoundary are not correct with from_meshio() when lines are given as shapely.Polygon.exterior #1150
Comments
What is mesh_from_OrderedDict and how is the orientation information represented in its output? |
That I don't quite follow. What should I be looking for? I believe the orientation is still correctly passed from tangents = (mtmp.p[:, facets[1]] - mtmp.p[:, facets[0]]) They follow the rectangle in an anticlockwise manner. The problem was on the The following code allows us to see that: def from_meshio(m,
out=None,
int_data_to_sets=False,
force_meshio_type=None,
ignore_orientation=False,
ignore_interior_facets=False):
if ignore_interior_facets:
logger.warning("kwarg ignore_interior_facets is unused.")
cells = m.cells_dict
meshio_type = None
if force_meshio_type is None:
# detect 3D
for k in cells:
if k in {'tetra',
'hexahedron',
'tetra10',
'hexahedron27',
'wedge'}:
meshio_type = k
break
if meshio_type is None:
# detect 2D
for k in cells:
if k in {'triangle',
'quad',
'triangle6',
'quad9'}:
meshio_type = k
break
if meshio_type is None:
# detect 1D
for k in cells:
if k == 'line':
meshio_type = k
break
else:
meshio_type = force_meshio_type
if meshio_type is None:
raise NotImplementedError("Mesh type(s) not supported "
"in import: {}.".format(cells.keys()))
mesh_type = MESH_TYPE_MAPPING[meshio_type]
# create p and t
p = np.ascontiguousarray(mesh_type.strip_extra_coordinates(m.points).T)
t = np.ascontiguousarray(cells[meshio_type].T)
# reorder t if needed
if meshio_type == 'hexahedron':
t = t[INV_HEX_MAPPING[:8]]
elif meshio_type == 'hexahedron27':
t = t[INV_HEX_MAPPING]
if int_data_to_sets:
m.int_data_to_sets()
subdomains = {}
boundaries = {}
# parse any subdomains from cell_sets
if m.cell_sets:
subdomains = {k: v[meshio_type].astype(np.int64)
for k, v in m.cell_sets_dict.items()
if meshio_type in v}
# create temporary mesh for matching boundary elements
mtmp = mesh_type(p, t, validate=False)
bnd_type = BOUNDARY_TYPE_MAPPING[meshio_type]
#############################################
mesh = mtmp
plt.figure()
plt.scatter(*mesh.p, marker = 'o', facecolor = 'None', edgecolor = 'black', s = 15)
#################################################
# parse boundaries from cell_sets
# print('inside meshio:', bnd_type)
if m.cell_sets and bnd_type in m.cells_dict:
p2f = mtmp.p2f
for k, v in m.cell_sets_dict.items():
if bnd_type in v and k.split(":")[0] != "gmsh":
facets = m.cells_dict[bnd_type][v[bnd_type]].T
sorted_facets = np.sort(facets, axis=0)
ind = p2f[:, sorted_facets[0]]
for itr in range(sorted_facets.shape[0] - 1):
ind = ind.multiply(p2f[:, sorted_facets[itr + 1]])
boundaries[k] = np.nonzero(ind)[0]
if not ignore_orientation:
try:
print(boundaries[k])
ori = np.zeros_like(boundaries[k], dtype=np.float64)
t1, _ = mtmp.f2t[:, boundaries[k]]
if facets.shape[0] == 2:
tangents = (mtmp.p[:, facets[1]]
- mtmp.p[:, facets[0]])
normals = np.array([-tangents[1], tangents[0]])
tangents
elif facets.shape[0] == 3:
tangents1 = (mtmp.p[:, facets[1]]
- mtmp.p[:, facets[0]])
tangents2 = (mtmp.p[:, facets[2]]
- mtmp.p[:, facets[0]])
normals = -np.cross(tangents1.T, tangents2.T).T
else:
raise NotImplementedError
for r in range(len(t1)):
plt.plot(*mtmp.p[:, mtmp.t[(0,1), t1[r]]], color = 'blue')
plt.plot(*mtmp.p[:, mtmp.t[(1,2), t1[r]]], color = 'blue')
plt.plot(*mtmp.p[:, mtmp.t[(0,2), t1[r]]], color = 'blue')
for itr in range(mtmp.t.shape[0]): #iterate over the points of the triangles that contain the boundary
plt.scatter(*mtmp.p[:, facets[0]], edgecolor = 'red', facecolor = 'none', s = 20)
plt.scatter(*mtmp.p[:, mtmp.t[itr, t1]], edgecolor = 'blue', facecolor = 'none', s = 25)
for g in range(len(facets[0])):
mesh = mtmp
x = [mesh.p[0,facets[0,g]], mesh.p[0,facets[1,g]]]
y = [mesh.p[1,facets[0,g]], mesh.p[1,facets[1,g]]]
plt.text(np.mean(x), np.mean(y), f"{g}")
# for l in range(len(facets[0])):
# plt.arrow(*mtmp.p[:, facets[0][l]],*(mtmp.p[:, facets[0][l]]-
# mtmp.p[:, mtmp.t[itr, t1][l]]))
# print('##########################################')
ori += np.sum(normals
* (mtmp.p[:, facets[0]] #This is every point of the line
- mtmp.p[:, mtmp.t[itr, t1]]),
axis=0)
ori = 1 * (ori > 0)
# print(ori)
boundaries[k] = OrientedBoundary(boundaries[k],
ori)
except Exception as e:
print(e)
logger.warning("Failure to orient a boundary.")
# print(boundaries)
# MSH 2.2 tag parsing
if len(boundaries) == 0 and m.cell_data and m.field_data:
try:
elements_tag = m.cell_data_dict['gmsh:physical'][meshio_type]
subdomains = {}
tags = np.unique(elements_tag)
def find_tagname(tag):
for key in m.field_data:
if m.field_data[key][0] == tag:
return key
return None
for tag in tags:
t_set = np.nonzero(tag == elements_tag)[0]
subdomains[find_tagname(tag)] = t_set
# find tagged boundaries
if bnd_type in m.cell_data_dict['gmsh:physical']:
facets = m.cells_dict[bnd_type]
facets_tag = m.cell_data_dict['gmsh:physical'][bnd_type]
# put meshio facets to dict
dic = {tuple(np.sort(facets[i])): facets_tag[i]
for i in range(facets.shape[0])}
# get index of corresponding Mesh.facets for each meshio
# facet found in the dict
index = np.array([[dic[tuple(np.sort(mtmp.facets[:, i]))], i]
for i in mtmp.boundary_facets()
if tuple(np.sort(mtmp.facets[:, i])) in dic])
# read meshio tag numbers and names
tags = index[:, 0]
boundaries = {}
for tag in np.unique(tags):
tagindex = np.nonzero(tags == tag)[0]
boundaries[find_tagname(tag)] = index[tagindex, 1]
except Exception:
logger.warning("Failure to parse tags from meshio.")
# attempt parsing skfem tags
if m.cell_data:
_boundaries, _subdomains = mtmp._decode_cell_data(m.cell_data)
boundaries.update(_boundaries)
subdomains.update(_subdomains)
# export mesh data
if out is not None and isinstance(out, list):
for i, field in enumerate(out):
out[i] = getattr(m, field)
return mesh_type(
p,
t,
None if len(boundaries) == 0 else boundaries,
None if len(subdomains) == 0 else subdomains,
) |
One possible fix that can work generally for any 2D mesh is by making use of the correct calculation of the tangents and normals, and extend it to the third dimension, then performing a cross product: def from_meshio(m,
out=None,
int_data_to_sets=False,
force_meshio_type=None,
ignore_orientation=False,
ignore_interior_facets=False):
if ignore_interior_facets:
logger.warning("kwarg ignore_interior_facets is unused.")
cells = m.cells_dict
meshio_type = None
if force_meshio_type is None:
# detect 3D
for k in cells:
if k in {'tetra',
'hexahedron',
'tetra10',
'hexahedron27',
'wedge'}:
meshio_type = k
break
if meshio_type is None:
# detect 2D
for k in cells:
if k in {'triangle',
'quad',
'triangle6',
'quad9'}:
meshio_type = k
break
if meshio_type is None:
# detect 1D
for k in cells:
if k == 'line':
meshio_type = k
break
else:
meshio_type = force_meshio_type
if meshio_type is None:
raise NotImplementedError("Mesh type(s) not supported "
"in import: {}.".format(cells.keys()))
mesh_type = MESH_TYPE_MAPPING[meshio_type]
# create p and t
p = np.ascontiguousarray(mesh_type.strip_extra_coordinates(m.points).T)
t = np.ascontiguousarray(cells[meshio_type].T)
# reorder t if needed
if meshio_type == 'hexahedron':
t = t[INV_HEX_MAPPING[:8]]
elif meshio_type == 'hexahedron27':
t = t[INV_HEX_MAPPING]
if int_data_to_sets:
m.int_data_to_sets()
subdomains = {}
boundaries = {}
# parse any subdomains from cell_sets
if m.cell_sets:
subdomains = {k: v[meshio_type].astype(np.int64)
for k, v in m.cell_sets_dict.items()
if meshio_type in v}
# create temporary mesh for matching boundary elements
mtmp = mesh_type(p, t, validate=False)
bnd_type = BOUNDARY_TYPE_MAPPING[meshio_type]
# parse boundaries from cell_sets
if m.cell_sets and bnd_type in m.cells_dict:
p2f = mtmp.p2f
for k, v in m.cell_sets_dict.items():
if bnd_type in v and k.split(":")[0] != "gmsh":
facets = m.cells_dict[bnd_type][v[bnd_type]].T
sorted_facets = np.sort(facets, axis=0)
ind = p2f[:, sorted_facets[0]]
for itr in range(sorted_facets.shape[0] - 1):
ind = ind.multiply(p2f[:, sorted_facets[itr + 1]])
boundaries[k] = np.nonzero(ind)[0]
if not ignore_orientation:
try:
ori = np.zeros_like(boundaries[k], dtype=np.float64)
t1, _ = mtmp.f2t[:, boundaries[k]]
if facets.shape[0] == 2:
tangents = (mtmp.p[:, facets[1]]
- mtmp.p[:, facets[0]])
normals = np.array([-tangents[1], tangents[0]])
print(tangents)
tangents = np.vstack((tangents, np.zeros(facets.shape[1])))
normals = np.vstack((normals, np.zeros(facets.shape[1])))
ori = (np.sign(-np.cross(tangents.T, normals.T).T[-1])-1)/2
elif facets.shape[0] == 3:
tangents1 = (mtmp.p[:, facets[1]]
- mtmp.p[:, facets[0]])
tangents2 = (mtmp.p[:, facets[2]]
- mtmp.p[:, facets[0]])
normals = -np.cross(tangents1.T, tangents2.T).T
for itr in range(mtmp.t.shape[0]):
ori += np.sum(normals
* (mtmp.p[:, facets[0]] #This is every point of the line
- mtmp.p[:, mtmp.t[itr, t1]]),
axis=0)
ori = 1 * (ori > 0)
else:
raise NotImplementedError
boundaries[k] = OrientedBoundary(boundaries[k],
ori)
except Exception as e:
logger.warning("Failure to orient a boundary.")
# print(boundaries)
# MSH 2.2 tag parsing
if len(boundaries) == 0 and m.cell_data and m.field_data:
try:
elements_tag = m.cell_data_dict['gmsh:physical'][meshio_type]
subdomains = {}
tags = np.unique(elements_tag)
def find_tagname(tag):
for key in m.field_data:
if m.field_data[key][0] == tag:
return key
return None
for tag in tags:
t_set = np.nonzero(tag == elements_tag)[0]
subdomains[find_tagname(tag)] = t_set
# find tagged boundaries
if bnd_type in m.cell_data_dict['gmsh:physical']:
facets = m.cells_dict[bnd_type]
facets_tag = m.cell_data_dict['gmsh:physical'][bnd_type]
# put meshio facets to dict
dic = {tuple(np.sort(facets[i])): facets_tag[i]
for i in range(facets.shape[0])}
# get index of corresponding Mesh.facets for each meshio
# facet found in the dict
index = np.array([[dic[tuple(np.sort(mtmp.facets[:, i]))], i]
for i in mtmp.boundary_facets()
if tuple(np.sort(mtmp.facets[:, i])) in dic])
# read meshio tag numbers and names
tags = index[:, 0]
boundaries = {}
for tag in np.unique(tags):
tagindex = np.nonzero(tags == tag)[0]
boundaries[find_tagname(tag)] = index[tagindex, 1]
except Exception:
logger.warning("Failure to parse tags from meshio.")
# attempt parsing skfem tags
if m.cell_data:
_boundaries, _subdomains = mtmp._decode_cell_data(m.cell_data)
boundaries.update(_boundaries)
subdomains.update(_subdomains)
# export mesh data
if out is not None and isinstance(out, list):
for i, field in enumerate(out):
out[i] = getattr(m, field)
return mesh_type(
p,
t,
None if len(boundaries) == 0 else boundaries,
None if len(subdomains) == 0 else subdomains,
) I don't know if the issue will remain for 3D, or even if this is a good permanent fix, but this is as far as I got. Hope it helps |
I believe the orientation part of from_meshio was created while testing against Gmsh generated meshes, loaded from file via meshio, and if I recall correctly, in Gmsh meshes the boundary/interface facets were listed in a specific (probably anticlockwise) order. In scikit-fem this information was made more explicit by introducing the concept of OrientedBoundary, so that a consistent normal vector could be used. However, I’ll need to study where these meshes are originating from and how do they differ with the Gmsh ones, or if there is another bug I am not aware of. Does the problem persist if you do a save-load cycle with the meshio.Mesh object? Can you save one of those meshio objects to file and post it here so I can test it more easily? |
I have done the following: mesh = from_meshio(
mesh_from_OrderedDict(polygons, {}, default_resolution_max=0.1, filename="mesh.msh")
)
mesh = from_file('mesh.msh', out = None) and the problem persists. Is this what you meant?
Do you mean the .msh file? |
Thank you. I believe this issue originates from the optimizations done in #1117. In fact, it seems that for itr in range(mtmp.t.shape[0]):
ori += np.sum(normals
* (mtmp.p[:, facets[0]]
- mtmp.p[:, mtmp.t[itr, t1]]),
axis=0) which is supposed to check the dot product between the proposed normal and the edges of the elements is completely incorrect. More precisely, it seems that the order of facets in |
I believe this is fixed by the PR #1152 |
I have released this in 10.0.1. |
I have the code below to illustrate the issue. One box I create a OrientedBoundary with
mesh.facets_around_elements()
and the other is generated by giving the exterior of ashapely.Polygon
.I'm unsure how to solve this, but my guess is that it originates on the
from_meshio()
. This is a very important feature when considering line integrals@HelgeGehring
The text was updated successfully, but these errors were encountered: