Как объединить значения из словаря по разным ключам при его повторении? Алгоритм слияния сетки конечных элементов

Я работаю над своей докторской диссертацией, и я застрял на этом шаге. Проблема состоит в реализации алгоритма слияния сетки конечных элементов, и, возможно, мое решение не самое лучшее, поэтому, если вы придумаете лучшее, я открыт для предложений.

Что касается проблемы: у меня есть сетка конечных элементов, которая состоит из элементов QUAD (квадраты с 4 узлами) и элементов TRIA (треугольники с 3 узлами). Эти элементы соединены ребрами, ребро определяется двумя узлами (ребро=[узел1,узел2]). У меня есть список ребер, которые я не хочу объединять, но для остальных ребер я хочу, чтобы программа объединила элементы с общим ребром.

В качестве простого примера: предположим, что у меня есть 4 элемента A, B, C и D (QUAD elms, определяемые 4 узлами). Сетка выглядит примерно так

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

Эти элементы определены в словаре:

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

У меня также есть словарь для позиции узла со значениями координат X, Y, Z. Допустим, я хочу объединить ребра [4,5] и [5,6].

Мое решение следующее: я начинаю перебирать элементы в mesh_dict, нахожу соседей элемента функцией get_elm_neighbors(element), проверяю угол между элементами функцией check_angle(elm1,elm2,angle) (мне нужен угол между элементами должен быть ниже определенного порога), затем я проверяю, какое ребро должно быть объединено с помощью get_edge_not_bar(), затем у меня есть функция, которая обновляет узлы для первого элемента, чтобы завершить слияние.

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)

Моя проблема в том, что мне нужно удалить элементы, которые остались после слияния. Например, в приведенном выше случае я начинаю с элемента A и объединяю на ребре [4,5], после этого определение вяза A будет 'A':[1,2,8,7], затем мне нужно удалить вяз C и продолжить итерацию.

Мое решение состояло в том, чтобы создать дубликат словаря mesh_dict_merge, в котором я обновляю значения для элементов, а затем удаляю те, которые мне не нужны, при повторении исходного словаря, но принимая во внимание, что удаленные элементы (список удаленных_ключей) не идут через них

Я предполагаю, что мой вопрос заключается в том, есть ли способ перебирать словарь, обновлять значения и удалять ключи при этом? Или, если есть лучшее решение для решения этой проблемы, может быть, перебирать узлы вместо элементов?

РЕДАКТИРОВАТЬ: изменено «A»: [1,2,4,5] на «A»: [1,2,5,4]


person Cosmin    schedule 27.11.2019    source источник
comment
Правильно ли A: [1,2,4,5]? Не должно быть [1,2,5,4], чтобы сохранить постоянную ориентацию?   -  person Rockcat    schedule 28.11.2019
comment
да, это правильно @Rockcat, я отредактировал сообщение, чтобы устранить ошибку, спасибо.   -  person Cosmin    schedule 28.11.2019


Ответы (2)


Это можно сделать, обновляя элементы на лету. Но я не должен его рекомендовать, потому что ваш алгоритм будет зависеть от порядка повторения элементов и может быть недетерминированным. Это означает, что две сетки с одинаковой геометрией и топологией могут давать разные результаты в зависимости от используемых вами меток.

Рекомендация такова:

  1. Вычислите все двугранные углы в сетке. Сохраните те, которые находятся ниже вашего порога слияния.
  2. Найдите минимальный угол и объедините два элемента, которые разделяют это ребро.
  3. Обновите двугранные углы вокруг нового элемента. Это включает в себя удаление углов из объединенных элементов и, при необходимости, добавление новых углов для нового элемента.
  4. Повторяйте с шага 2, пока каждый угол не превысит пороговое значение или пока количество элементов не станет желаемым.

Необязательная часть шага 3 позволяет определить агрессивность вашего метода. Иногда лучше не включать новые ракурсы и повторять весь процесс несколько раз, чтобы избежать слишком сильного сосредоточения уменьшения в зоне.

person Rockcat    schedule 28.11.2019
comment
Ограничением проблемы является не угол, а края. У меня есть список ребер, которые я не хочу объединять, любые другие ребра, которых нет в списке, должны быть объединены. Порог угла больше похож на проверку, чтобы увидеть, находятся ли элементы в одной плоскости, если угол между ними выше порога, они не должны быть объединены, даже если ребро допускает слияние. - person Cosmin; 28.11.2019
comment
Конечно... когда я говорю сохранить двугранные углы, я имею в виду сохранить ребро и его двугранный угол и выбрать ребро с минимальным двугранным углом, чтобы объединить элементы, которые разделяют это ребро - person Rockcat; 28.11.2019
comment
что происходит, когда несколько элементов имеют одинаковые углы (как будто они находятся на одной и той же плоской поверхности)? мне не нужно удалять элементы, которые объединились? - person Cosmin; 28.11.2019
comment
Когда несколько элементов имеют одинаковый угол, минимум не уникален. Вы объединяете какой-либо элемент, а затем, вероятно, вы объедините другой позже, потому что шаг 4 является циклом, который повторяется из шага 2. Может быть, после объединения первого ребра и обновления углов вокруг нового элемента вы воздерживаетесь от объединения, потому что новый элемент угол увеличился, но это нормально. - person Rockcat; 28.11.2019

Я думал о том, как найти соседние элементы, найдя элементы, которые имеют одно и то же ребро, но мне нужно было иметь ребра в виде пары конечных индексов в отсортированном порядке. Затем я мог бы разработать touches (должен работать и для треугольных элементов).

Я представляю dont_merge как набор упорядоченных индексов ребер, которые нельзя объединить, затем объединить в merged_ordered_edges и, наконец, преобразовать обратно в исходный формат сетки с ребрами, окружающими каждый элемент.

Я закомментировал вызов check_angle(name1, name2), который вам нужно будет добавить. Я предполагаю, что проверка будет успешной каждый раз по комментарию.

# -*- 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. Есть ли шанс на упоминание, если вы используете это?

person Paddy3118    schedule 29.11.2019
comment
Интересная идея для итерации на общих ребрах. Я расширю это и проверю на своей модели, и если я в конечном итоге использую это, я обязательно упомяну вас. Спасибо! - person Cosmin; 01.12.2019