Compare commits

...

7 Commits

  1. 2
      misc/all-graph/02-dump_igraph.py
  2. 151
      misc/all-graph/03-convert_graphml.py
  3. 111
      misc/all-graph/04-combine_layout.py
  4. 95
      misc/all-graph/05-dump_combined.py
  5. 62
      misc/all-graph/3d_demo.py
  6. 116
      misc/all-graph/graphml.py
  7. 4
      misc/graph/build.py
  8. 36
      misc/graph/combine.py

2
misc/all-graph/02-dump_igraph.py

@ -82,7 +82,7 @@ def convert_all(json_dir: str, output_dir: str) -> None:
pool = multiprocessing.Pool() pool = multiprocessing.Pool()
for name in sorted(os.listdir(json_dir)): for name in sorted(os.listdir(json_dir)):
json_file = f'{json_dir}/{name}' json_file = f'{json_dir}/{name}'
output_prefix = f"{output_dir}/{name.removesuffix('.json')}" output_prefix = f'{output_dir}/{name.removesuffix('.json')}'
pool.apply_async(convert_and_dump, args=(json_file, output_prefix)) pool.apply_async(convert_and_dump, args=(json_file, output_prefix))
pool.close() pool.close()
pool.join() pool.join()

151
misc/all-graph/03-convert_graphml.py

@ -1,39 +1,72 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import os import os
import re from typing import override
from dataclasses import dataclass
import igraph as ig import igraph as ig
from lxml import etree from lxml import etree
from dataclasses import dataclass from graphml import INode, IEdge, Config, GraphML
class GraphML: @dataclass(frozen=True)
@dataclass(frozen=True) class Node(INode):
class Node:
id: str id: str
code: str
step: int step: int
code: str
@staticmethod
@override
def add_keys(graphml: etree.Element, cfg: Config) -> None:
etree.SubElement(graphml, 'key', id='code', attrib={
'id': 'code',
'for': 'node',
'attr.name': 'code',
'attr.type': 'string'
})
etree.SubElement(graphml, 'key', id='step', attrib={
'for': 'node',
'attr.name': 'step',
'attr.type': 'int'
})
if cfg.is_yed:
INode._add_yed_key(graphml)
@override
def render(self, cfg: Config) -> etree.Element:
node_xml = etree.Element('node', id=self.id)
etree.SubElement(node_xml, 'data', key='code').text = self.code
etree.SubElement(node_xml, 'data', key='step').text = str(self.step)
if cfg.is_yed:
color = cfg.colors[self.step]
label = f'{self.code}
({self.step})'
node_xml.append(self._yed_render(cfg, color, label))
return node_xml
@dataclass(frozen=True)
class Edge:
src: str # node id
dst: str # node id
@dataclass(frozen=True) @dataclass(frozen=True)
class Graph: class Edge(IEdge):
name: str src: str
nodes: list[GraphML.Node] dst: str
edges: list[GraphML.Edge]
@staticmethod @staticmethod
def __load(name: str, graph: ig.Graph) -> GraphML.Graph: @override
def add_keys(graphml: etree.Element, cfg: Config) -> None:
pass
@override
def render(self, cfg: Config) -> etree.Element:
return etree.Element('edge', source=self.src, target=self.dst)
def load_graph(tag: str, graph: ig.Graph) -> tuple[GraphML, int]:
nodes = [] nodes = []
id_len = len(str(graph.vcount() - 1)) id_len = len(str(graph.vcount() - 1))
for index in range(graph.vcount()): for index in range(graph.vcount()):
info = graph.vs[index] info = graph.vs[index]
nodes.append(GraphML.Node(f'n{index:0{id_len}d}', info['code'], info['step'])) nodes.append(Node(f'n{index:0{id_len}d}', info['step'], info['code']))
edges = [] edges = []
for n1, n2 in graph.get_edgelist(): for n1, n2 in graph.get_edgelist():
@ -41,74 +74,24 @@ class GraphML:
node_2 = nodes[n2] node_2 = nodes[n2]
if node_1.step < node_2.step: if node_1.step < node_2.step:
node_1, node_2 = node_2, node_1 node_1, node_2 = node_2, node_1
edges.append(GraphML.Edge(node_1.id, node_2.id)) edges.append(Edge(node_1.id, node_2.id))
return GraphML.Graph(name, nodes, edges)
def __init__(self, *graphs: tuple[str, ig.Graph]):
self.__graphs = [self.__load(*x) for x in graphs]
def __dump_node(self, node: Node) -> etree.Element:
node_xml = etree.Element('node', id=node.id)
etree.SubElement(node_xml, 'data', key='v_code').text = node.code
etree.SubElement(node_xml, 'data', key='v_step').text = str(node.step)
return node_xml
def __dump_edge(self, edge: Edge) -> etree.Element:
return etree.Element('edge', source=edge.src, target=edge.dst)
def __dump_graph(self, graph: Graph) -> etree.Element:
graph_xml = etree.Element('graph', id=graph.name, edgedefault='undirected')
for node in graph.nodes:
graph_xml.append(self.__dump_node(node))
for edge in graph.edges:
graph_xml.append(self.__dump_edge(edge))
return graph_xml
def save_graph(self, output: str) -> None:
graphml = etree.Element('graphml', nsmap={
None: 'http://graphml.graphdrawing.org/xmlns',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
})
graphml.set(
'{http://www.w3.org/2001/XMLSchema-instance}schemaLocation',
'http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'
)
etree.SubElement(graphml, 'key', attrib={
'id': 'v_code',
'for': 'node',
'attr.name': 'code',
'attr.type': 'string'
})
etree.SubElement(graphml, 'key', attrib={
'id': 'v_step',
'for': 'node',
'attr.name': 'step',
'attr.type': 'int'
})
for graph in self.__graphs:
graphml.append(self.__dump_graph(graph))
xml_tree = etree.ElementTree(graphml)
xml_tree.write(output, pretty_print=True, xml_declaration=True, encoding='utf-8')
def to_graphml(inputs: list[tuple[str, str]], output: str) -> None:
print(f'Convert into {output}')
gml = GraphML(*((x, ig.Graph.Read_Pickle(y)) for x, y in inputs))
gml.save_graph(output)
return GraphML(tag, nodes, edges), max(x.step for x in nodes)
def convert_graphs(input_dir: str, output_dir: str) -> None:
files = [x.removesuffix('.pkl') for x in os.listdir(input_dir) if x.endswith('.pkl')]
tags = sorted(x for x in files if re.match(r'^\d\-\d\d(L|R|M)$', x))
for tag in tags: def to_graphml(tag: str, input: str, output: str, is_yed: bool) -> None:
output_file = os.path.join(output_dir, f'{tag}.graphml') print(f'Convert graph {input} into {output}')
pkls = sorted(x for x in files if x.startswith(tag)) gml, max_step = load_graph(tag, ig.Graph.Read_Pickle(input))
to_graphml([(x, os.path.join(input_dir, f'{x}.pkl')) for x in pkls], output_file) colors = GraphML.build_colors(max_step + 1, ['#0000ff', '#e8daef', '#ff0000'])
cfg = Config(is_yed=is_yed, colors=colors)
gml.save_graphml(output, cfg)
if __name__ == "__main__": if __name__ == '__main__':
os.makedirs('output-gml', exist_ok=True) os.makedirs('output-gml', exist_ok=True)
convert_graphs('output-ig', 'output-gml') os.makedirs('output-yed', exist_ok=True)
for name in sorted(os.listdir('output-ig')):
name = name.removesuffix('.pkl')
tag = name.split('_')[0] if '_' in name else name
to_graphml(tag, f'output-ig/{name}.pkl', f'output-gml/{name}.graphml', False)
to_graphml(tag, f'output-ig/{name}.pkl', f'output-yed/{name}.graphml', True)

111
misc/all-graph/04-combine_layout.py

@ -0,0 +1,111 @@
#!/usr/bin/env python3
import os
import igraph as ig
import multiprocessing
type Union = set[int]
def split_adjacent_layers(graph: ig.Graph, step: int) -> tuple[list[Union], list[Union]]:
layouts = graph.vs.select(step_in=[step, step + 1])
mapping = {x['code']: x.index for x in layouts}
spawn_union = lambda iter: {mapping[x['code']] for x in iter}
layer_curr, layer_next = [], []
g_focus = graph.subgraph(layouts)
if isolated := g_focus.vs.select(_degree=0):
assert set(isolated['step']) == {step}
layer_curr = [spawn_union(isolated)]
g_focus.delete_vertices(isolated)
for comp in map(g_focus.vs.select, g_focus.connected_components()):
layer_curr.append(spawn_union(comp.select(step=step)))
layer_next.append(spawn_union(comp.select(step=step+1)))
return layer_curr, layer_next
def apply_layer_unions(unions_a: list[Union], unions_b: list[Union]) -> list[Union]:
layouts = {x for u in unions_a for x in u}
assert layouts == {x for u in unions_b for x in u}
unions = []
for curr_union in unions_a:
for other_union in unions_b:
if union := curr_union.intersection(other_union):
unions.append(union)
curr_union -= union
other_union -= union
assert len(curr_union) == 0
assert set(len(x) for x in unions_a) == {0}
assert set(len(x) for x in unions_b) == {0}
assert layouts == {x for u in unions for x in u}
return unions
def build_all_unions(graph: ig.Graph) -> list[Union]:
max_step = max(graph.vs['step'])
layer_unions = [[set(graph.vs.select(step=0).indices)]]
for step in range(0, max_step):
layer_unions.extend(list(split_adjacent_layers(graph, step)))
layer_unions.append([set(graph.vs.select(step=max_step).indices)])
assert len(layer_unions) == (max_step + 1) * 2
all_unions = []
for idx in range(0, len(layer_unions), 2):
all_unions.extend(apply_layer_unions(*layer_unions[idx:idx + 2]))
for unions in all_unions:
assert len(unions) > 0
assert len(set(graph.vs[x]['step'] for x in unions)) == 1
return sorted(all_unions, key=lambda u: min(graph.vs[x]['code'] for x in u))
def combine_graph(graph: ig.Graph) -> ig.Graph:
unions = build_all_unions(graph)
union_idx = sorted((x, idx) for idx, u in enumerate(unions) for x in u)
combine_idx = [x for _, x in union_idx]
assert len(combine_idx) == graph.vcount()
assert set(combine_idx) == set(range(len(unions)))
tag_len = len(str(len(unions) - 1))
graph.vs['tag'] = [f'U{x:0{tag_len}}' for x in combine_idx]
graph.contract_vertices(combine_idx, combine_attrs={'tag': 'first', 'step': 'first', 'code': list})
assert [int(x.removeprefix('U')) for x in graph.vs['tag']] == list(range(len(unions)))
assert not any(x.is_loop() for x in graph.es)
graph.simplify(multiple=True)
return graph
def do_combine(input: str, output: str) -> None:
print(f'Start combining: {input}')
g_raw = (graph := combine_graph(ig.Graph.Read_Pickle(input))).copy()
graph.vs['codes'] = graph.vs['code']
del graph.vs['code']
graph.write_pickle(output) # save combined graph
g_raw.vs['code'] = g_raw.vs['tag'] # modify as origin format
g_mod = combine_graph(g_raw.copy())
assert g_raw.vcount() == g_mod.vcount()
assert g_raw.ecount() == g_mod.ecount()
assert all(x['code'] == [x['tag']] for x in g_mod.vs)
assert g_raw.vs['step'] == g_mod.vs['step']
assert g_raw.vs['code'] == g_mod.vs['tag']
assert g_raw.isomorphic(g_mod)
def combine_all(ig_dir: str, output_dir: str) -> None:
pool = multiprocessing.Pool()
for name in sorted(os.listdir(ig_dir)):
pool.apply_async(do_combine, args=(f'{ig_dir}/{name}', f'{output_dir}/{name}'))
pool.close()
pool.join()
if __name__ == '__main__':
os.makedirs('output-combine', exist_ok=True)
combine_all('output-ig', 'output-combine')

95
misc/all-graph/05-dump_combined.py

@ -0,0 +1,95 @@
#!/usr/bin/env python3
import os
from typing import override
from dataclasses import dataclass
import igraph as ig
from lxml import etree
from graphml import INode, IEdge, Config, GraphML
@dataclass(frozen=True)
class Node(INode):
tag: str
step: int
codes: list[str]
@staticmethod
@override
def add_keys(graphml: etree.Element, cfg: Config) -> None:
etree.SubElement(graphml, 'key', attrib={
'id': 'step',
'for': 'node',
'attr.name': 'step',
'attr.type': 'int'
})
etree.SubElement(graphml, 'key', attrib={
'id': 'codes',
'for': 'node',
'attr.name': 'codes',
'attr.type': 'string'
})
if cfg.is_yed:
INode._add_yed_key(graphml)
@override
def render(self, cfg: Config) -> etree.Element:
node_xml = etree.Element('node', id=self.tag)
etree.SubElement(node_xml, 'data', key='step').text = str(self.step)
etree.SubElement(node_xml, 'data', key='codes').text = '+'.join(self.codes)
if cfg.is_yed:
color = cfg.colors[self.step]
label = f'{self.tag}&#10;({self.step})'
node_xml.append(self._yed_render(cfg, color, label))
return node_xml
@dataclass(frozen=True)
class Edge(IEdge):
src: str
dst: str
@staticmethod
@override
def add_keys(graphml: etree.Element, cfg: Config) -> None:
pass
@override
def render(self, is_yed: bool) -> etree.Element:
return etree.Element('edge', source=self.src, target=self.dst)
def to_graphml(tag: str, file: str, output: str, is_yed: bool) -> None:
print(f'Convert graph {file} into {output}')
g = ig.Graph.Read_Pickle(file)
nodes = []
for v in g.vs:
node = Node(v['tag'], v['step'], v['codes'])
nodes.append(node)
edges = []
for n1, n2 in g.get_edgelist():
node_1 = nodes[n1]
node_2 = nodes[n2]
if node_1.step < node_2.step:
node_1, node_2 = node_2, node_1
edges.append(Edge(node_1.tag, node_2.tag))
gml = GraphML(tag, nodes, edges)
colors = GraphML.build_colors(max(x.step for x in nodes) + 1, ['#0000ff', '#e8daef', '#ff0000'])
cfg = Config(is_yed=is_yed, colors=colors)
gml.save_graphml(output, cfg)
if __name__ == '__main__':
os.makedirs('output-combine-gml', exist_ok=True)
for name in sorted(os.listdir('output-combine')):
name = name.removesuffix('.pkl')
tag = name.split('_')[0] if '_' in name else name
to_graphml(tag, f'output-combine/{name}.pkl', f'output-combine-gml/{name}.graphml', False)
to_graphml(tag, f'output-combine/{name}.pkl', f'output-combine-gml/{name}-yed.graphml', True)

62
misc/all-graph/3d_demo.py

@ -0,0 +1,62 @@
#!/usr/bin/env python3
import numpy as np
import igraph as ig
import plotly.graph_objs as go
import matplotlib.colors as mcolors
def build_colors(max_step: int) -> list[str]:
bwr = ['#0000ff', '#e8daef', '#ff0000']
cmap = mcolors.LinearSegmentedColormap.from_list('custom_bwr', bwr)
return [mcolors.to_hex(cmap(x)) for x in np.linspace(0, 1, max_step + 1)]
def build_3d_graph(graph: ig.Graph, colors: list[str], output: str) -> None:
layout = graph.layout('kk', dim=3)
Xn = [x[0] for x in layout]
Yn = [x[1] for x in layout]
Zn = [x[2] for x in layout]
Xe, Ye, Ze = [], [], []
for x, y in graph.get_edgelist():
Xe += [layout[x][0], layout[y][0], None]
Ye += [layout[x][1], layout[y][1], None]
Ze += [layout[x][2], layout[y][2], None]
edge_trace = go.Scatter3d(
x=Xe, y=Ye, z=Ze,
mode='lines',
line=dict(color='gray', width=2),
hoverinfo='none'
)
node_trace = go.Scatter3d(
x=Xn, y=Yn, z=Zn,
mode='markers',
marker=dict(
symbol='circle',
size=6,
color=[colors[x['step']] for x in graph.vs]
),
hoverinfo='text',
text=[f'{x['tag']} ({x['step']}) [{'/'.join(x['codes'])}]' for x in graph.vs]
)
fig = go.Figure(data=[edge_trace, node_trace])
fig.update_layout(
showlegend=False,
margin=dict(l=0, r=0, b=0, t=0),
scene=dict(
xaxis=dict(showbackground=False),
yaxis=dict(showbackground=False),
zaxis=dict(showbackground=False),
)
)
fig.write_html(output)
if __name__ == '__main__':
g = ig.Graph.Read_Pickle('1-00M-000X_DAAF4CC-core.pkl')
build_3d_graph(g, build_colors(93), '1-00M-000X_3d-core.html')

116
misc/all-graph/graphml.py

@ -0,0 +1,116 @@
#!/usr/bin/env python3
import io
import numpy as np
from lxml import etree
from dataclasses import dataclass
from abc import ABC, abstractmethod
import matplotlib.colors as mcolors
@dataclass
class Config:
is_yed: bool
colors: list[str]
pretty_xml: bool = True
yed_xmlns: str = 'http://www.yworks.com/xml/graphml'
yed_node_type: str = 'ellipse'
yed_node_width: int = 50
yed_node_height: int = 50
yed_node_font_size: int = 10
class INode(ABC):
@staticmethod
def _add_yed_key(graphml: etree.Element) -> None:
etree.SubElement(graphml, 'key', attrib={
'id': 'info',
'for': 'node',
'yfiles.type': 'nodegraphics'
})
@staticmethod
def _yed_render(cfg: Config, color: str, text: str) -> etree.Element:
yed_ns = f'{{{cfg.yed_xmlns}}}'
info = etree.Element('data', attrib={'key': 'info'})
shape = etree.SubElement(info, f'{yed_ns}ShapeNode')
etree.SubElement(shape, f'{yed_ns}Fill', attrib={
'color': color
})
etree.SubElement(shape, f'{yed_ns}Shape', attrib={
'type': cfg.yed_node_type
})
etree.SubElement(shape, f'{yed_ns}Geometry', attrib={
'height': str(cfg.yed_node_width),
'width': str(cfg.yed_node_height),
})
label = etree.SubElement(shape, f'{yed_ns}NodeLabel', attrib={
'fontSize': str(cfg.yed_node_font_size),
'modelName': 'internal'
})
label.text = text
return info
@staticmethod
@abstractmethod
def add_keys(graphml: etree.Element, cfg: Config) -> None:
pass
@abstractmethod
def render(self, cfg: Config) -> etree.Element:
pass
class IEdge(ABC):
@staticmethod
@abstractmethod
def add_keys(graphml: etree.Element, cfg: Config) -> None:
pass
@abstractmethod
def render(self, cfg: Config) -> etree.Element:
pass
class GraphML:
def __init__(self, tag: str, nodes: list[INode], edges: list[IEdge]):
self.__tag = tag
self.__nodes = nodes
self.__edges = edges
assert len(nodes) > 0 and len(edges) > 0
@staticmethod
def __nsmap(cfg: Config) -> dict[str | None, str]:
return {
None: 'http://graphml.graphdrawing.org/xmlns',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
**({'y': cfg.yed_xmlns} if cfg.is_yed else {})
}
@staticmethod
def build_colors(num: int, bwr: list[str]) -> list[str]:
cmap = mcolors.LinearSegmentedColormap.from_list('custom_bwr', bwr)
return [mcolors.to_hex(cmap(x)) for x in np.linspace(0, 1, num)]
def __build_graphml(self, cfg: Config) -> etree.Element:
graphml = etree.Element('graphml', nsmap=self.__nsmap(cfg))
graphml.set(
'{http://www.w3.org/2001/XMLSchema-instance}schemaLocation',
'http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'
)
self.__nodes[0].add_keys(graphml, cfg)
self.__edges[0].add_keys(graphml, cfg)
graph = etree.SubElement(graphml, 'graph', id=self.__tag, edgedefault='undirected')
[graph.append(x.render(cfg)) for x in self.__nodes]
[graph.append(x.render(cfg)) for x in self.__edges]
return graphml
def save_graphml(self, file: str, cfg: Config) -> None:
xml_tree = etree.ElementTree(self.__build_graphml(cfg))
fake_output = io.BytesIO()
xml_tree.write(fake_output, pretty_print=cfg.pretty_xml, xml_declaration=True, encoding='utf-8')
content = fake_output.getvalue().decode('utf-8')
with open(file, 'w') as fp:
fp.write(content.replace('&amp;#10;', '&#10;'))

4
misc/graph/build.py

@ -32,10 +32,10 @@ def build_step_map(code: str) -> dict[Layout, int]:
def build_min_step_scope(group: Group, targets: list[str]) -> dict[Layout, dict[Layout, int]]: def build_min_step_scope(group: Group, targets: list[str]) -> dict[Layout, dict[Layout, int]]:
targets = [Layout(x) for x in targets] targets = [Layout(x) for x in targets]
all_solution = set([x for x in group.cases() if str(x).startswith('D')]) all_solution = set([x for x in list(group.cases()) if str(x).startswith('D')])
not_solved = all_solution - set(targets) not_solved = all_solution - set(targets)
step_data = {x: {} for x in group.cases() if x not in not_solved} step_data = {x: {} for x in list(group.cases()) if x not in not_solved}
for target in targets: for target in targets:
for code, step_num in build_step_map(target).items(): for code, step_num in build_step_map(target).items():
if code in not_solved: if code in not_solved:

36
misc/graph/combine.py

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import igraph as ig import igraph as ig
@ -13,6 +14,15 @@ def split_neighbor_layer(g: ig.Graph, n: int) -> tuple[list[set[ig.Vertex]], lis
select_next = lambda p: set(x for x in p.neighbors() if x['step'] == n + 1) select_next = lambda p: set(x for x in p.neighbors() if x['step'] == n + 1)
select_last = lambda p: set(x for x in p.neighbors() if x['step'] == n) select_last = lambda p: set(x for x in p.neighbors() if x['step'] == n)
layer_a_data.append(set())
for node in layer_a:
if not select_next(node):
layer_a_data[0].add(node)
for node in layer_a_data[0]:
layer_a.remove(node)
if not layer_a_data[0]:
layer_a_data.clear()
while len(layer_a) != 0: while len(layer_a) != 0:
side_a: set[ig.Vertex] = set() side_a: set[ig.Vertex] = set()
side_b: set[ig.Vertex] = set() side_b: set[ig.Vertex] = set()
@ -91,7 +101,7 @@ def combine_split_result(data_a: list[set[ig.Vertex]], data_b: list[set[ig.Verte
def split_layers(g: ig.Graph) -> list[list[set[ig.Vertex]]]: def split_layers(g: ig.Graph) -> list[list[set[ig.Vertex]]]:
assert min(g.vs['step']) == 0 assert min(g.vs['step']) == 0
layer_num = max(g.vs['step']) + 1 layer_num = max(g.vs['step']) + 1
print(f'layer_num: {layer_num}') # print(f'layer_num: {layer_num}')
layers = [{'up': [], 'down': []} for x in range(layer_num)] layers = [{'up': [], 'down': []} for x in range(layer_num)]
layers[0]['up'] = [set(g.vs.select(step=0))] layers[0]['up'] = [set(g.vs.select(step=0))]
@ -157,14 +167,20 @@ def export_new_graph(g: ig.Graph, split_data: list[list[set[ig.Vertex]]]) -> ig.
return ng return ng
if __name__ == '__main__': def do_combine(input: str, output: str):
raw = ig.Graph.Read_Pickle('data/DAA7F30.pkl') print(f'Combine {input} -> {output}')
# raw = ig.Graph.Read_Pickle('data/DBAB4CC.pkl') raw = ig.Graph.Read_Pickle(input)
# raw = ig.Graph.Read_Pickle('main_combined.pkl')
print(raw.summary())
gg = export_new_graph(raw, split_layers(raw)) gg = export_new_graph(raw, split_layers(raw))
print(gg.summary()) # print(raw.summary())
# print(gg.isomorphic(raw)) # print(gg.summary())
# print(raw.isomorphic(gg))
gg.write_pickle(output)
if __name__ == '__main__':
do_combine('data/DAA7F30.pkl', 'main_combined.pkl')
# do_combine('data/DBAB4CC.pkl', 'main_combined.pkl')
# do_combine('main_combined.pkl', 'main_combined.pkl')
gg.write_pickle('main_combined.pkl') # for name in os.listdir('data'):
# gg.write_graphml('main_combined.graphml') # do_combine(f'data/{name}', f'{name}')

Loading…
Cancel
Save