2

I am working on my phd and I am stuck on this step. The problem consists of implementing a finite element mesh merging algorithm and maybe my solution is not the best, so if you think of a better one I am open to suggestions.

Regarding the problem: I have a finite element mesh, which is composed of QUAD elements (squares with 4 nodes) and TRIA elements (triangles with 3 nodes). These elements are connected on edges, an edge is defined by 2 nodes (edge=[node1,node2]). I have a list of edges that I do not want to merge, but for the rest of the edges I want the program to merge the elements with the common edge.

As a simple example: assume I have 4 elements A,B,C and D (QUAD elms, defined by 4 nodes). The mesh looks something like this

1--------------2----------------3  
|              |                |      
|      A       |        B       |   
|              |                |  
4--------------5----------------6
|              |                |
|      C       |        D       |
|              |                |
7--------------8----------------9

These elements are defined in a dictionary:

mesh_dict={'A': [1,2,5,4], 'B':[2,3,6,5], 'C':[4,5,8,7],'D':[5,6,9,8]}

I also have a dictionary for the node position with values for X,Y,Z coordinates. Let's say I want to merge on edge [4,5] and [5,6].

My solution is the following: I start iterating through the elements in mesh_dict, I find the neighbors of the element with a function get_elm_neighbors(element), I check the angle between elements with function check_angle(elm1,elm2,angle) (I need the angle between elements to be below a certain threshold), than I check for which edge should be merged by get_edge_not_bar(), than I have a function which updates the nodes for the first element to complete the merging.

for e in mesh_dict:
        if e not in delete_keys:
            neighbors=get_elm_neighbors(e)
            for key,value in neighbors.items():
                check = check_angle(e,key,0.5)
                if check:
                    nodes = get_edge_not_bar(value)
                    if nodes:
                        new_values=merge_elms(e,key,nodes)
                        d = {e: new_values}
                        mesh_dict_merged.update(d)
                        mesh_dict.update(d)
                        delete_keys.append(key)

My problem is that I need to delete the elements that remain after the merging. For example in the above case I start on element A and I merge on the edge [4,5], after that the elm A definition will be 'A':[1,2,8,7], then I need to delete elm C and proceed with the iteration.

My solution was to create a duplicate dictionary mesh_dict_merge in which I update the values for the elements and then delete the ones that I don't want to while iterating through the original dict but taking into consideration the deleted elements (deleted_keys list) to not go through them

I guess my question is if there is a way to iterate through the dictionary, update values and delete keys while doing so ? Or if there is a better solution to approach this problem, maybe iterate through nodes instead of elements ?

EDIT: changed 'A': [1,2,4,5] to 'A': [1,2,5,4]

Cosmin
  • 21
  • 3

2 Answers2

1

It can be done updating the elements on-the-fly. But I should not recommend it because your algorithm will depend on the order you iterate the elements, and may be not deterministic. This mean that two meshes with identical geometry and topology could give different results depending on the labels you use.

The recommendation is :

  1. Compute all dihedral angles in your mesh. Store those that are under your merge threshold.
  2. Find the minimum angle and merge the two elements that share that edge.
  3. Update the dihedral angles around the new element. This include removing angles from elements that have merged, and optionally include new angles for the new element.
  4. Repeat from step 2 until every angle is over the threshold, or until the number of elements is the desired.

The optional part in step 3 allows to determine the aggressiveness of your method. Sometimes it is better not to include new angles and repeat several times the complete process to avoid focus the reduction too much in a zone.

Rockcat
  • 3,002
  • 2
  • 14
  • 28
  • The contraint of the problem is not the angle, but the edges. I have a list of edges that I do not want to merge, any other edges that are not in the list must be merged. The angle threshold is more like a check to see if the elements are in the same plane, if the angle between them is higher than the threshold they must not be merged even if the edge allows for a merge. – Cosmin Nov 28 '19 at 10:38
  • Of course... when I say "store the dihedral angles" I mean "Store the edge and its dihedral angle" and "choose the edge with minimun dihedral angle" to "merge elements that share that edge" – Rockcat Nov 28 '19 at 11:34
  • what happens when multiple elements have the same angles (like they are on the same plane surface) ? don't I need to remove the elements that have merged ? – Cosmin Nov 28 '19 at 13:48
  • When multiple elements have the same angle, the minimum is not unique. You merge "any" element, and then probably you will merge the other later, because step 4 is a loop that repeat from step 2. Maybe that after merging the first edge and updating the angles around the new element you desist of merging because new element has increased its angle, but that's normal. – Rockcat Nov 28 '19 at 16:33
0

I thought about how to find adjacent elements by finding elements that shared the same edge - but I had to have edges as a pair of end indices in sorted order. I could then work out touches (should work for triangle elements too).

I introduce dont_merge as a set of ordered edge indices that cannot be merged away then merge into merged_ordered_edges and finally convert back to the mesh format of your original with edges going around each element.

I have commented out a call to check_angle(name1, name2) which you would have to add in. I assume that the check would succeed every time by the comment.

# -*- coding: utf-8 -*-
"""
Finite element mesh merge algorithm
    https://stackoverflow.com/questions/59079755/how-to-merge-values-from-dictionary-on-different-keys-while-iterating-through-it

Created on Thu Nov 28 21:59:07 2019

@author: Paddy3118
"""

#%%
mesh_dict={'A': [1,2,5,4], 'B':[2,3,6,5], 'C':[4,5,8,7],'D':[5,6,9,8]}
# 
ordered_edges = {k: {tuple(sorted(endpoints))
                     for endpoints in zip(v, v[1:] + v[:1])}
                 for k, v in mesh_dict.items()}
# = {'A': {(1, 2), (1, 4), (2, 5), (4, 5)},
#    'B': {(2, 3), (2, 5), (3, 6), (5, 6)},
#    'C': {(4, 5), (4, 7), (5, 8), (7, 8)},
#    'D': {(5, 6), (5, 8), (6, 9), (8, 9)}}

#%%
from collections import defaultdict

touching = defaultdict(list)
for name, edges in ordered_edges.items():
    for edge in edges:
        touching[edge].append(name)
touches = {edge: names 
           for edge, names in touching.items()
           if len(names) > 1}
# = {(2, 5): ['A', 'B'],
#    (4, 5): ['A', 'C'],
#    (5, 6): ['B', 'D'],
#    (5, 8): ['C', 'D']}

#%%
dont_merge = set([(4, 5), (23, 24)])
for edge, (name1, name2) in touches.items():
    if (edge not in dont_merge
        and ordered_edges[name1] and ordered_edges[name2]
        #and check_angle(name1, name2)
        ):
        # merge
        ordered_edges[name1].update(ordered_edges[name2])
        ordered_edges[name1].discard(edge)  # that edge is merged away
        ordered_edges[name2] = set()  # gone
merged_ordered_edges = {}
for name, edges in ordered_edges.items():
    if edges:
        merged_ordered_edges[name] = sorted(edges)
        edges.clear()  # Only one name of shared object used
# = {'A': [(1, 2), (1, 4), (2, 3), (3, 6), (4, 5), (5, 6)],
#    'C': [(4, 5), (4, 7), (5, 6), (6, 9), (7, 8), (8, 9)]}
## You would then need a routine to change the ordered edges format 
## back to your initial mesh_dict format that goes around the periphery
## (Or would you)?

#%%
def ordered_to_periphery(edges):
    """
In [124]: ordered_to_periphery([(1, 2), (1, 4), (2, 3), (3, 6), (4, 5), (5, 8), (6, 9), (8, 9)])
Out[124]: [(1, 2), (2, 3), (3, 6), (6, 9), (9, 8), (8, 5), (5, 4), (4, 1)]
    """
    p = [edges.pop(0)] if edges else []
    last = p[-1][-1] if p else None
    while edges:
        for n, (i, j) in enumerate(edges):
            if i == last:
                p.append((i, j))
                last = j
                edges.pop(n)
                break
            elif j == last:
                p.append((j, i))
                last = i
                edges.pop(n)
                break

    return p

#%%
merged_mesh = {name: ordered_to_periphery(edges)
               for name, edges in merged_ordered_edges.items()}

# = {'A': [(1, 2), (2, 3), (3, 6), (6, 5), (5, 4), (4, 1)],
#    'C': [(4, 5), (5, 6), (6, 9), (9, 8), (8, 7), (7, 4)]}

P.S. Any chance of a mention if you use this?

Paddy3118
  • 4,704
  • 27
  • 38
  • Interesting idea for iterating on common edges. I'll expand on this and check it out on my model, and if I end up using this I will be sure to mention you. Thanks! – Cosmin Dec 01 '19 at 17:30