эффективная проекция двудольного графа в Python (с использованием networkx)

Используя модуль networkx, я провожу сетевой анализ под Python 3.2, где мне нужно спроецировать двудольный граф (заключенных, связанных с их ячейкой: входной граф B в приведенном ниже коде) на подграф (связывающий сокамерников друг с другом, если оба имеют перекрывающееся заклинание в той же ячейке: используя входные данные множества узлов, определяющих узлы-заключенные графа B, генерируя выходной граф G). Мне не нужен специальный алгоритм, чтобы найти какое-либо или оптимальное соответствие, мне просто нужно собрать все ссылки, которые удовлетворяют некоторым условиям. Таким образом, другие сообщения SO, которые я нашел, на самом деле не применимы. Но:

Мой текущий код взрывается (RAM, swap и CPU), поскольку я передаю ему все больше и больше данных. Пожалуйста, дайте мне знать, если вы видите способы упростить приведенный ниже код с помощью 5 уровней циклов. Я не уверен, что какие-либо знания о networkx необходимы или важны подробности моей маркировки краевых атрибутов. Спасибо!

def time_overlap_projected_graph_parallel(B, nodes):
    G=nx.MultiGraph()
    G.add_nodes_from((n,B.node[n]) for n in nodes)
    for u in nodes:
        unbrs = set(B[u])
        nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u])
        for v in nbrs2:
            for mutual_cell in set(B[u]) & set(B[v]):
                for uspell in B.get_edge_data(u,mutual_cell).values():
                    ustart = uspell[1]
                    uend = uspell[2]
                    for vspell in B.get_edge_data(v,mutual_cell).values():
                        vstart = vspell[1]
                        vend = vspell[2]
                        if uend > vstart and vend > ustart:
                            ostart = max(ustart,vstart)
                            oend = min(uend,vend)
                            olen = (oend-ostart+1)/86400
                            ocell = mutual_cell
                            if (v not in G[u] or ostart not in [ edict[1] for edict in G[u][v].values() ]):
                                G.add_edges_from([(u,v,{0: olen,1: ostart,2: oend,3: ocell})])
    return G

person László    schedule 20.09.2011    source источник
comment
Вы можете уточнить больше? Я предполагаю, что B - это исходный граф, тогда что такое узлы? И я не уверен, что понимаю, что вы хотите. У вас есть двудольный граф камер и сокамерников. Тогда вам нужен график чего? Просто сокамерники? Подключены ли они в одной ячейке?   -  person Avaris    schedule 20.09.2011
comment
Спасибо, Аварис, вы правильно поняли, но я тоже уточню свой вопрос.   -  person László    schedule 20.09.2011
comment
В ПОРЯДКЕ. Термин подграф меня бросил, поскольку технически новый граф не будет подграфом. В таком случае, почему бы вам просто не перебрать ячейки и не соединить соседей ячейки в новом графе?   -  person Avaris    schedule 20.09.2011
comment
циклы в Python не так эффективны (wiki.python.org/moin/PythonSpeed/PerformanceTips# Петли). Если ваши 2 внутренние петли станут большими, это действительно может стать очень медленным. Каковы типичные размеры вещей, которые вы перебираете?   -  person steabert    schedule 20.09.2011
comment
@Avaris: Я думаю, что это то, что я делаю в опубликованном коде, но я сжигаю ресурсы с неприемлемой скоростью. Ищу способы сократить использование памяти.   -  person László    schedule 20.09.2011
comment
@steabert: узлов: сотни тысяч (крайний цикл); nbrs2: ячейки, в которых находился имат, не более нескольких десятков (не проверено); Mutual_cell: только несколько, хотя определение цикла вызывает продажу u и v, десятки каждого; vspell: однозначными числами (количество перекрывающихся заклинаний между людьми); Звучит неразумно, чтобы использовать 30 ГБ ОЗУ и более 40 ГБ пространства подкачки? Возможно, я что-то упускаю ...   -  person László    schedule 20.09.2011
comment
Пытаюсь понять из кода (поправьте меня, если я ошибаюсь): nodes = сокамерники, u = первый заключенный, unbrs = камеры, в которых вы находились, nbrs2 = все заключенные, которые остались в unbrs, v = другой заключенный, duplic_cell = ячейка они поделились.   -  person Avaris    schedule 20.09.2011
comment
@ Аварис: Думаю, вы правы. Должно быть, это было моим намерением. (К вашему сведению: летом я адаптировал для этого другой код и проверил, что он работает с тестовыми данными. Вот почему я немного осторожен в выборе того или иного объекта. Но я думаю, что именно так работают эти циклы.)   -  person László    schedule 20.09.2011
comment
Значит, вопрос все равно не стоит одобрения? ;)   -  person László    schedule 20.09.2011
comment
Тогда 2-я (v) и 3-я (absolute_cell) петли мне кажутся задом наперед. Попробуйте переключить их: т.е. for mutual_cell in B[u], а затем for v in B[mutual_cell]. Тогда, конечно, вы должны пропустить, если v == u.   -  person Avaris    schedule 20.09.2011
comment
Если у вас так много узлов, это может быть либо просто строка G.add_nodes_from(...), поскольку в ней тоже есть цикл, либо остальная часть вашего кода, которая занимает много времени. Вы делали профилирование?   -  person steabert    schedule 20.09.2011
comment
@steabert: Я недостаточно научился профилированию, хотя кое-что пробовал.   -  person László    schedule 20.09.2011
comment
@Avaris: Пожалуйста, посмотрите исправленный код, который я публикую в качестве ответа, делает ли он то же самое, но без наборов. С итераторами все могло бы стать намного лучше, и я думаю, что я использовал наборы только для перехода через u == v. Тем не менее, для меня было бы важным уроком, если бы эти якобы небольшие наборы занимали десятки ГБ.   -  person László    schedule 20.09.2011
comment
обновление: я не могу (самостоятельно) отвечать в течение еще 8 часов с моей репутацией, извините. А исправленный код слишком длинный для комментариев.   -  person László    schedule 20.09.2011


Ответы (4)


Думаю, теперь можно использовать двудольные графы. Нравится

import networkx as nx
from networkx.algorithms import bipartite

B.add_nodes_from(inmates_list, bipartite=0)
B.add_nodes_from(cells_list, bipartite=1)

inmates = set(n for n,d in B.nodes(data=True) if d['bipartite']==0)
cells = = set(B) - inmates
G = bipartite.projected_graph(B, inmates)

(http://networkx.lanl.gov/reference/algorithms.bipartite.html)

person Mario Alemi    schedule 13.05.2014
comment
В NetworkX 2.0 вы можете сделать что-то вроде inmates = {n for n, is_inmate in B.nodes(data='bipartite') if is_inmate}, что немного лучше (при условии, что вы поменяете местами значения атрибутов bipartite 1 и 0). - person argentpepper; 03.05.2016

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

cells = [n for n in B.nodes() if n[0] not in nodes]

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

from itertools import combinations

def time_overlap_projected_graph_parallel(B, nodes):
    G=nx.MultiGraph()
    G.add_nodes_from((n,B.node[n]) for n in nodes)
    cells = [n for n in B.nodes() if n[0] not in nodes]
    for cell in cells:
        for u,v in combinations(B[cell],2):
            for uspell in B.get_edge_data(u,cell).values():
                ustart = uspell[1]
                uend = uspell[2]
                for vspell in B.get_edge_data(v,cell).values():
                    vstart = vspell[1]
                    vend = vspell[2]
                    if uend > vstart and vend > ustart:
                        ostart = max(ustart,vstart)
                        oend = min(uend,vend)
                        olen = (oend-ostart+1)/86400
                        ocell = cell
                        if (v not in G[u] or ostart not in [ edict[1] for edict in G[u][v].values() ]):
                            G.add_edge(u,v,{0: olen,1: ostart,2: oend,3: ocell})
    return G
person Avaris    schedule 20.09.2011

Я отправляю этот ответ, чтобы предложить несколько улучшений. Я предполагаю, что ваш двудольный граф не мультиграф, а нормальный nx.Graph. Я изменил B на bi и G на uni, поскольку имена в верхнем регистре зарезервированы для классов по соглашению. Кстати, а что, если заклинания начинаются и заканчиваются в одно и то же время?

def time_overlap_projected_graph_parallel(bi, nodes):
    uni = nx.MultiGraph()
    for u in nodes: #inmate
        uni.add_node(u) # do this to prevent iterating nodes twice
        u_adj = bi.adj[u] # bi.adj is a dict of dicts
        for (w, uw_attr) in u_adj.iteritems(): # cell
            w_adj = bi.adj[w]
            for (v, wv_attr) in w_adj.iteritems():#cellmate
                if v == u:
                    continue
                elif uni.has_edge(u, v): # avoid computing twice
                    continue
                for uspell in uw_attr.itervalues():
                    ustart = uspell[1]
                    uend = uspell[2]
                    for vspell in wv_attr.itervalues():
                        vstart = vspell[1]
                        vend = vspell[2]
                        if uend > vstart and vend > ustart:
                            ostart = max(ustart, vstart)
                            oend = min(uend, vend)
                            olen = (oend - ostart + 1) / 86400 # this assumes floats or Python 3 otherwise will be 0
                            ocell = w
                            # I would change this to uni.add_edge(u, v, length=olen, start=ostart, end=oend, cell=ocell)
                            # or to uni.add_edge(u, v, spell=[olen, ostart, oend, ocell])
                            uni.add_edge(u, v, **{0: olen, 1: ostart, 2: oend, 3: ocell})
    return uni
person Midnighter    schedule 13.05.2014
comment
Спасибо, я не могу перезапустить его сейчас, чтобы проверить, насколько он быстрее. Почему именно вы ожидаете, что это будет быстрее? О заклинаниях: я в порядке, бросаю заклинания, заканчивающиеся, когда они начинаются как ошибочные. - person László; 13.05.2014
comment
Основная причина, по которой это должно быть намного быстрее, - это проверка uni.has_edge(u, v). Поскольку вы перебираете все узлы, это позволит избежать множества шагов, когда вы достигнете обратного uni.had_edge(v, u), который уже будет вычислен. Ваш код также проверяет, есть ли пересечение заклинания в списке, который является O(n) операцией. Не знаю, как долго эти списки. Если подумать, все вычисления заклинаний можно улучшить. Отредактирую соответственно. В целом, для очень больших сетей networkx, вероятно, не лучший инструмент. - person Midnighter; 13.05.2014
comment
@ Ласло Я говорил о деле if uend >= vstart and vend >= ustart. - person Midnighter; 13.05.2014
comment
Спасибо, я думаю, это будет просто двойным счетом для одних и тех же вещей, не так ли? - person László; 13.05.2014

Рассмотрим исправленный код, который может делать то же самое, но с итераторами (хотя я также пересмотрел несколько вызовов функций / методов, связанных с networkx, но я все еще проверяю, не сломал ли я что-нибудь):

def time_overlap_projected_graph_parallel(B, nodes):
    G=nx.MultiGraph()
    G.add_nodes_from(nodes)
    for u in G.nodes_iter():#inmate
        for w in B.neighbors_iter(u):#cell
            for v in B.neighbors_iter(w):#cellmate
                if v == u:
                    continue
                for uspell in B[u][w].values():
                    ustart = uspell[1]
                    uend = uspell[2]
                    for vspell in B[v][w].values():
                        vstart = vspell[1]
                        vend = vspell[2]
                        if uend > vstart and vend > ustart:
                            ostart = max(ustart,vstart)
                            oend = min(uend,vend)
                            olen = (oend-ostart+1)/86400
                            ocell = w
                            if (v not in G[u] or ostart not in [ edict[1] for edict in G[u][v].values() ]):
                                G.add_edges_from([(u,v,{0: olen,1: ostart,2: oend,3: ocell})])
    return G
person László    schedule 20.09.2011