Есть ли более элегантный/оптимизированный способ сделать этот алгоритм подключения?

Я сделал алгоритм, который определяет, какие атомы связаны в молекуле из списка атомов (учитывая их расстояние друг от друга). Чтобы думать об этой проблеме вне физического контекста, это просто проблема замкнутой сети, в которой узлы - это атомы, а ребра - это атомные связи, соединяющие атомы. У меня есть список узлов и список ребер, которые соединяют узлы, и мне нужно выяснить список каждой уникальной молекулы. Я сделал это в коде ниже, однако это довольно медленно и довольно некрасиво. Есть ли способ оптимизировать этот алгоритм?

Вот мой код, который работает, с соответствующей информацией, которую вы можете попробовать (я предоставлю другой список атомов, который будет пытаться называть pair_1 и selected_atom_1, просто заменим pair_1 на пары и selected_atom_1 на selected_atom, чтобы он работал)

pairs = [[0, 1],
 [0, 2],
 [3, 4],
 [3, 5],
 [6, 7],
 [6, 8],
 [9, 10],
 [9, 11],
 [12, 13],
 [12, 14],
 [15, 16],
 [15, 17],
 [18, 19],
 [18, 20],
 [21, 22],
 [21, 23],
 [24, 25],
 [24, 26],
 [27, 28],
 [27, 29],
 [30, 31],
 [30, 32],
 [33, 34],
 [33, 35],
 [36, 37],
 [36, 38],
 [39, 40],
 [39, 41],
 [42, 43],
 [42, 44],
 [45, 46],
 [45, 47]]

chosen_atom = [np.random.rand() for i in range(48)]

pairs_1 = [[0, 6],
 [1, 7],
 [2, 8],
 [3, 9],
 [4, 10],
 [5, 6],
 [5, 10],
 [6, 7],
 [7, 8],
 [8, 9],
 [9, 10]]

chosen_atom_1 = [np.random.rand() for i in range(11)]

# use list of lists to define unique molecules
molecule_list = []

for i in pairs:
    temp_array = []
    for ii in pairs:
        temp_pair = [i[0], i[1]]

        if temp_pair[0] == ii[0]:
            temp_array.append(ii[1])
            temp_array = set(temp_array)
            temp_array = list(temp_array)
        if temp_pair[1] == ii[1]:
            temp_array.append(ii[0])
            temp_array = set(temp_array)
            temp_array = list(temp_array)

        for iii in temp_array:
            for j in pairs:
                if iii == j[0]:
                    temp_array.append(j[1])
                    temp_array = set(temp_array)
                    temp_array = list(temp_array)
                if iii == j[1]:
                    temp_array.append(j[0])
                    temp_array = set(temp_array)
                    temp_array = list(temp_array)

        if len(temp_array) > len(chosen_atom):
            break
    molecule_list.append(temp_array)

molecule_list = [list(item) for item in set(tuple(row) for row in molecule_list)]

# the output of pairs should be
molecule_list = [[8, 6, 7],
 [27, 28, 29],
 [9, 10, 11],
 [0, 1, 2],
 [32, 30, 31],
 [18, 19, 20],
 [45, 46, 47],
 [33, 34, 35],
 [24, 25, 26],
 [42, 43, 44],
 [16, 17, 15],
 [12, 13, 14],
 [21, 22, 23],
 [3, 4, 5],
 [40, 41, 39],
 [36, 37, 38]]
# the output of pairs_1 should be:
molecule_list = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]

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

+2
источник поделиться
2 ответа

Как я уже говорил в комментариях, вам нужен алгоритм подключенных компонентов, и вы можете легко решить его с networkx пакета networkx:

import networkx as nx

G = nx.from_edgelist(pairs)

print([i for i in nx.connected_components(G)])

# jupyter notebook
%matplotlib inline
nx.draw(G, with_labels=True)

Выход:

[{0, 1, 2}, {3, 4, 5}, {8, 6, 7}, {9, 10, 11}, {12, 13, 14}, {16, 17, 15}, {18, 19, 20}, {21, 22, 23}, {24, 25, 26}, {27, 28, 29}, {32, 30, 31}, {33, 34, 35}, {36, 37, 38}, {40, 41, 39}, {42, 43, 44}, {45, 46, 47}]

Graph

Вот мой сценарий для построения и визуализации молекулярного графа из атомных координат.

+3
источник

Это может быть решено с использованием метода "union-find".

Свяжите каждый атом с другим атомом в той же молекуле. Ссылка может быть недействительной. Если нет, то это приводит к другому атому, который сам имеет связь. Перейдя по ссылкам рекурсивно, вы в конце концов достигнете атома с пустой ссылкой. Назовем это главным атомом в молекуле.

Алгоритм работает следующим образом:

  • возьмите каждый край по очереди, пусть ab;

  • найти главный атом a, пусть main (a);

  • связать main (a) с b, чтобы перегруппировать их в одну молекулу.

При этом main (a) становится таким же, как main (b), и, в конце концов, все атомы данной молекулы имеют одинаковые main.

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

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


Варианты и оптимизации:

  • вместо того, чтобы связать main (a) с b, вы можете связать его с main (b);

  • пока вы ищете main (a), вы можете связать все атомы с main (a) в пути; это сокращает пути для будущих поисков;

  • если вы также сохраняете длину пути от a до main (a), вы можете предпочесть присоединить main (a) к main (b) или main (b) к main (a);

  • вместо того, чтобы позволить основному атому ссылаться на void, вы можете дать ему ссылку на себя.

+2
источник

Посмотрите другие вопросы по меткам или Задайте вопрос