From 3ef17bf6c45826fe89709d0f7d8389332cb8b190 Mon Sep 17 00:00:00 2001 From: Eran Date: Sun, 21 Jan 2024 10:38:36 -0500 Subject: [PATCH 01/14] getting started --- bigraph_viz/diagram.py | 109 +++++++++++++++++++++++++++++++++++++++++ bigraph_viz/plot.py | 2 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 bigraph_viz/diagram.py diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py new file mode 100644 index 0000000..d5af9b7 --- /dev/null +++ b/bigraph_viz/diagram.py @@ -0,0 +1,109 @@ +""" +Bigraph diagram +""" +from bigraph_schema import TypeSystem, Edge +from bigraph_schema.registry import type_schema_keys, function_keys + +SCHEMA_KEYS = type_schema_keys | set(function_keys) +EDGE_TYPES = ['process', 'step', 'edge'] + + +def extend_bigraph(bigraph, bigraph2): + for key, values in bigraph.items(): + if isinstance(values, list): + bigraph[key] += bigraph2[key] + elif isinstance(values, dict): + bigraph[key].update(bigraph2[key]) + return bigraph + + +def check_if_path_in_removed_nodes(path, remove_nodes): + if remove_nodes: + return any(remove_path == path[:len(remove_path)] for remove_path in remove_nodes) + return False + + +def get_flattened_graph( + schema, + path=None, + remove_nodes=None, +): + path = path or () + + # initialize bigraph + bigraph = { + 'state_nodes': [], + 'process_nodes': [], + 'place_edges': [], + 'hyper_edges': {}, + 'disconnected_hyper_edges': {}, + 'bridges': {}, + 'flow': {}, + } + + for key, child in schema.items(): + + path_here = path + (key,) + if check_if_path_in_removed_nodes(path_here, remove_nodes): + continue # skip node if path in removed_nodes + node = {'path': path_here} + + if not isinstance(child, dict): + # this is a leaf node + node['_value'] = child + bigraph['state_nodes'] += [node] + continue + + # what kind of node? + if child.get('_type') in EDGE_TYPES: + # this is a hyperedge/process + bigraph['process_nodes'] += [node] + else: + bigraph['state_nodes'] += [node] + + # check inner states + if isinstance(child, dict): + # this is a branch node + child_bigraph_network = get_flattened_graph( + child, + path=path_here, + remove_nodes=remove_nodes) + bigraph = extend_bigraph(bigraph, child_bigraph_network) + + # add place edges to this layer + for node in child.keys(): + child_path = path_here + (node,) + if node in SCHEMA_KEYS or check_if_path_in_removed_nodes(child_path, remove_nodes): + continue + place_edge = [(path_here, child_path)] + bigraph['place_edges'] += place_edge + + return bigraph + + +def plot_diagram(schema): + bigraph_network = get_flattened_graph(schema) + print(bigraph_network) + + +def test_diagram_plot(): + cell_schema = { + 'cell': { + '_type': 'process', + 'config': {}, + 'address': 'local:cell', + 'inputs': { + 'nutrients': 'any', + }, + 'outputs': { + 'secretions': 'any', + 'biomass': 'any', + }, + } + } + plot_diagram(cell_schema) + + + +if __name__ == '__main__': + test_diagram_plot() diff --git a/bigraph_viz/plot.py b/bigraph_viz/plot.py index 7ed55be..5c8b086 100644 --- a/bigraph_viz/plot.py +++ b/bigraph_viz/plot.py @@ -67,7 +67,7 @@ def get_bigraph_network( for key, child in bigraph_dict.items(): # skip process schema - if not show_process_schema and key in process_schema_keys: + if not show_process_schema and key in process_schema_keys: continue if key in schema_keys: continue From 38d5ff23ddec115aec3543bec515740314ae4721 Mon Sep 17 00:00:00 2001 From: Eran Date: Sun, 21 Jan 2024 10:50:55 -0500 Subject: [PATCH 02/14] more set up --- bigraph_viz/diagram.py | 159 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 5 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index d5af9b7..45fc353 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -1,13 +1,26 @@ """ Bigraph diagram """ +import os from bigraph_schema import TypeSystem, Edge from bigraph_schema.registry import type_schema_keys, function_keys +import graphviz + SCHEMA_KEYS = type_schema_keys | set(function_keys) EDGE_TYPES = ['process', 'step', 'edge'] +def absolute_path(path, relative): + progress = list(path) + for step in relative: + if step == '..' and len(progress) > 0: + progress = progress[:-1] + else: + progress.append(step) + return tuple(progress) + + def extend_bigraph(bigraph, bigraph2): for key, values in bigraph.items(): if isinstance(values, list): @@ -23,6 +36,10 @@ def check_if_path_in_removed_nodes(path, remove_nodes): return False +def make_label(label): + return f'<{label}>' + + def get_flattened_graph( schema, path=None, @@ -42,7 +59,6 @@ def get_flattened_graph( } for key, child in schema.items(): - path_here = path + (key,) if check_if_path_in_removed_nodes(path_here, remove_nodes): continue # skip node if path in removed_nodes @@ -81,9 +97,143 @@ def get_flattened_graph( return bigraph -def plot_diagram(schema): +def get_graphviz_fig( + bigraph_network, + label_margin='0.05', + node_label_size='12pt', + size='16,10', + rankdir='TB', + dpi='70', +): + """make a graphviz figure from a bigraph_network""" + + node_names = [] + + # node specs + state_node_spec = { + 'shape': 'circle', 'penwidth': '2', 'margin': label_margin, 'fontsize': node_label_size} + process_node_spec = { + 'shape': 'box', 'penwidth': '2', 'constraint': 'false', 'margin': label_margin, 'fontsize': node_label_size} + hyper_edge_spec = { + 'style': 'dashed', 'penwidth': '1', 'arrowhead': 'dot', 'arrowsize': '0.5'} + + # initialize graph + graph_name = 'bigraph' + graph = graphviz.Digraph(graph_name, engine='dot') + graph.attr(size=size, overlap='false', rankdir=rankdir, dpi=dpi) + + # check if multiple layers + multilayer = False + for node in bigraph_network['state_nodes']: + node_path = node['path'] + if len(node_path) > 1: + multilayer = True + + # state nodes + graph.attr('node', **state_node_spec) + for node in bigraph_network['state_nodes']: + node_path = node['path'] + node_name = str(node_path) + node_names.append(node_name) + + # make the label + label = node_path[-1] + schema_label = None + if schema_label: + label += schema_label + label = make_label(label) + + if len(node_path) == 1 and multilayer: + # the top node gets a double circle + graph.node(node_name, label=label, peripheries='2') + else: + graph.node(node_name, label=label) + + # process nodes + process_paths = [] + graph.attr('node', **process_node_spec) + for node in bigraph_network['process_nodes']: + node_path = node['path'] + process_paths.append(node_path) + node_name = str(node_path) + node_names.append(node_name) + label = make_label(node_path[-1]) + + # composite processes have sub-nodes + composite_process = False + for edge in bigraph_network['place_edges']: + if edge[0] == node_path: + composite_process = True + if len(node_path) == 1 and composite_process: + # composite processes gets a double box + graph.node(node_name, label=label, peripheries='2') + else: + graph.node(node_name, label=label) + + # place edges + graph.attr('edge', arrowhead='none', penwidth='2') + for edge in bigraph_network['place_edges']: + + # show edge + graph.attr('edge', style='filled') + + node_name1 = str(edge[0]) + node_name2 = str(edge[1]) + graph.edge(node_name1, node_name2) + + # hyper edges + graph.attr('edge', **hyper_edge_spec) + for node_path, wires in bigraph_network['hyper_edges'].items(): + node_name = str(node_path) + with graph.subgraph(name=node_name) as c: + for port, state_path in wires.items(): + absolute_state_path = absolute_path(node_path, state_path) + node_name1 = str(node_path) + node_name2 = str(absolute_state_path) + + # are the nodes already in the graph? + if node_name2 not in graph.body: + label = make_label(absolute_state_path[-1]) + graph.node(node_name2, label=label, **state_node_spec) + c.edge(node_name2, node_name1) + + # disconnected hyper edges + graph.attr('edge', **hyper_edge_spec) + for node_path, wires in bigraph_network['disconnected_hyper_edges'].items(): + node_name = str(node_path) + with graph.subgraph(name=node_name) as c: + # c.attr(rank='source', rankdir='TB') + for port, state_path in wires.items(): + absolute_state_path = absolute_path(node_path, state_path) + node_name1 = str(node_path) + node_name2 = str(absolute_state_path) + # add invisible node for port + graph.node(node_name2, label='', style='invis', width='0') + c.edge(node_name2, node_name1) + + return graph + + +def plot_diagram( + schema, + out_dir=None, + filename=None, + file_format='png', +): + # parse out the network bigraph_network = get_flattened_graph(schema) - print(bigraph_network) + + # make a figure + graph = get_graphviz_fig(bigraph_network) + + # display or save results + if filename is not None: + out_dir = out_dir or 'out' + os.makedirs(out_dir, exist_ok=True) + fig_path = os.path.join(out_dir, filename) + print(f"Writing {fig_path}") + graph.render(filename=fig_path, format=file_format) + return graph def test_diagram_plot(): @@ -101,8 +251,7 @@ def test_diagram_plot(): }, } } - plot_diagram(cell_schema) - + plot_diagram(cell_schema, filename='bigraph_cell') if __name__ == '__main__': From 953b67350d6b538feb8f31de5912cd721bb46432 Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 26 Jan 2024 18:07:59 -0500 Subject: [PATCH 03/14] wip --- bigraph_viz/diagram.py | 174 +++++++++++++++++------------------------ bigraph_viz/test.py | 2 +- 2 files changed, 72 insertions(+), 104 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 45fc353..a4dc97f 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -3,96 +3,66 @@ """ import os from bigraph_schema import TypeSystem, Edge -from bigraph_schema.registry import type_schema_keys, function_keys +# from process_bigraph import core +from bigraph_viz.plot import ( + absolute_path, extend_bigraph, get_state_path_extended, + check_if_path_in_removed_nodes, make_label, +) import graphviz -SCHEMA_KEYS = type_schema_keys | set(function_keys) -EDGE_TYPES = ['process', 'step', 'edge'] - - -def absolute_path(path, relative): - progress = list(path) - for step in relative: - if step == '..' and len(progress) > 0: - progress = progress[:-1] - else: - progress.append(step) - return tuple(progress) +# make a local type system +core = TypeSystem() -def extend_bigraph(bigraph, bigraph2): - for key, values in bigraph.items(): - if isinstance(values, list): - bigraph[key] += bigraph2[key] - elif isinstance(values, dict): - bigraph[key].update(bigraph2[key]) - return bigraph - - -def check_if_path_in_removed_nodes(path, remove_nodes): - if remove_nodes: - return any(remove_path == path[:len(remove_path)] for remove_path in remove_nodes) - return False +SCHEMA_KEYS = ['_type', 'config', 'address'] # get these from bigraph_schema +PROCESS_INTERFACE_KEYS = ['inputs', 'outputs'] +EDGE_TYPES = ['process', 'step', 'edge'] -def make_label(label): - return f'<{label}>' +def get_flattened_wires(wires, hyperedges): + pass def get_flattened_graph( schema, + state, + bigraph=None, path=None, - remove_nodes=None, + # remove_nodes=None, ): path = path or () # initialize bigraph - bigraph = { + bigraph = bigraph or { 'state_nodes': [], 'process_nodes': [], 'place_edges': [], - 'hyper_edges': {}, - 'disconnected_hyper_edges': {}, - 'bridges': {}, - 'flow': {}, + 'hyper_edges': [], + 'disconnected_hyper_edges': [], + 'bridges': [], } - for key, child in schema.items(): - path_here = path + (key,) - if check_if_path_in_removed_nodes(path_here, remove_nodes): - continue # skip node if path in removed_nodes - node = {'path': path_here} - - if not isinstance(child, dict): - # this is a leaf node - node['_value'] = child - bigraph['state_nodes'] += [node] - continue - - # what kind of node? - if child.get('_type') in EDGE_TYPES: - # this is a hyperedge/process - bigraph['process_nodes'] += [node] + for key, value in state.items(): + subpath = path + (key,) + + if core.check('edge', value): + bigraph['process_nodes'].append({'path': subpath}) + + # this is an edge, get its inputs and outputs + input_wires = value.get('inputs', {}) + output_wires = value.get('outputs', {}) + + for port, input_wire in input_wires.items(): + bigraph['hyperedges'].append({ + 'source': subpath, + 'target': input_wires, + }) + else: - bigraph['state_nodes'] += [node] - - # check inner states - if isinstance(child, dict): - # this is a branch node - child_bigraph_network = get_flattened_graph( - child, - path=path_here, - remove_nodes=remove_nodes) - bigraph = extend_bigraph(bigraph, child_bigraph_network) - - # add place edges to this layer - for node in child.keys(): - child_path = path_here + (node,) - if node in SCHEMA_KEYS or check_if_path_in_removed_nodes(child_path, remove_nodes): - continue - place_edge = [(path_here, child_path)] - bigraph['place_edges'] += place_edge + bigraph['state_nodes'].append(subpath) + + return bigraph @@ -106,7 +76,6 @@ def get_graphviz_fig( dpi='70', ): """make a graphviz figure from a bigraph_network""" - node_names = [] # node specs @@ -122,13 +91,6 @@ def get_graphviz_fig( graph = graphviz.Digraph(graph_name, engine='dot') graph.attr(size=size, overlap='false', rankdir=rankdir, dpi=dpi) - # check if multiple layers - multilayer = False - for node in bigraph_network['state_nodes']: - node_path = node['path'] - if len(node_path) > 1: - multilayer = True - # state nodes graph.attr('node', **state_node_spec) for node in bigraph_network['state_nodes']: @@ -142,12 +104,7 @@ def get_graphviz_fig( if schema_label: label += schema_label label = make_label(label) - - if len(node_path) == 1 and multilayer: - # the top node gets a double circle - graph.node(node_name, label=label, peripheries='2') - else: - graph.node(node_name, label=label) + graph.node(node_name, label=label) # process nodes process_paths = [] @@ -158,17 +115,7 @@ def get_graphviz_fig( node_name = str(node_path) node_names.append(node_name) label = make_label(node_path[-1]) - - # composite processes have sub-nodes - composite_process = False - for edge in bigraph_network['place_edges']: - if edge[0] == node_path: - composite_process = True - if len(node_path) == 1 and composite_process: - # composite processes gets a double box - graph.node(node_name, label=label, peripheries='2') - else: - graph.node(node_name, label=label) + graph.node(node_name, label=label) # place edges graph.attr('edge', arrowhead='none', penwidth='2') @@ -176,7 +123,6 @@ def get_graphviz_fig( # show edge graph.attr('edge', style='filled') - node_name1 = str(edge[0]) node_name2 = str(edge[1]) graph.edge(node_name1, node_name2) @@ -202,7 +148,6 @@ def get_graphviz_fig( for node_path, wires in bigraph_network['disconnected_hyper_edges'].items(): node_name = str(node_path) with graph.subgraph(name=node_name) as c: - # c.attr(rank='source', rankdir='TB') for port, state_path in wires.items(): absolute_state_path = absolute_path(node_path, state_path) node_name1 = str(node_path) @@ -214,14 +159,18 @@ def get_graphviz_fig( return graph -def plot_diagram( - schema, +def plot_bigraph( + state, + schema=None, out_dir=None, filename=None, file_format='png', ): + schema = schema or {} + schema = core.infer_schema(schema, state) + # parse out the network - bigraph_network = get_flattened_graph(schema) + bigraph_network = get_flattened_graph(schema, state) # make a figure graph = get_graphviz_fig(bigraph_network) @@ -237,21 +186,40 @@ def plot_diagram( def test_diagram_plot(): - cell_schema = { + # metabolic_process_type = { + # '_type': 'process', + # '_inputs': { + # 'nutrients': 'any', + # }, + # '_outputs': { + # 'se' + # } + # } + # register('metabolic_process', metabolic_process_type) # TODO -- register where? + + + cell = { 'cell': { '_type': 'process', 'config': {}, - 'address': 'local:cell', - 'inputs': { + 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from + '_inputs': { 'nutrients': 'any', }, - 'outputs': { + '_outputs': { 'secretions': 'any', 'biomass': 'any', }, + 'inputs': { + 'nutrients': ['nutrients_store'], + }, + 'outputs': { + 'secretions': ['secretions_store'], + 'biomass': ['biomass_store'], + } } } - plot_diagram(cell_schema, filename='bigraph_cell') + plot_bigraph(cell, filename='bigraph_cell') if __name__ == '__main__': diff --git a/bigraph_viz/test.py b/bigraph_viz/test.py index bd23168..088bf99 100644 --- a/bigraph_viz/test.py +++ b/bigraph_viz/test.py @@ -47,7 +47,7 @@ def test_composite_spec(): '_type': 'int' }, 'process1': { - '_ports': { + '_inputs': { 'port1': {'_type': 'type'}, 'port2': {'_type': 'type'}, }, From 96024c7ad7b08c3c951bdb38c7fb49d61fac60b9 Mon Sep 17 00:00:00 2001 From: Eran Date: Sat, 27 Jan 2024 09:47:00 -0500 Subject: [PATCH 04/14] wip --- bigraph_viz/diagram.py | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index a4dc97f..6d917f8 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -45,22 +45,35 @@ def get_flattened_graph( for key, value in state.items(): subpath = path + (key,) + node_spec = {'name': key, + 'path': subpath} if core.check('edge', value): - bigraph['process_nodes'].append({'path': subpath}) + bigraph['process_nodes'].append(node_spec) # this is an edge, get its inputs and outputs input_wires = value.get('inputs', {}) output_wires = value.get('outputs', {}) for port, input_wire in input_wires.items(): - bigraph['hyperedges'].append({ + target = input_wire # todo get absolute path + bigraph['hyper_edges'].append({ 'source': subpath, - 'target': input_wires, + 'target': target, + 'port': port, + 'type': 'input', + }) + for port, output_wire in output_wires.items(): + target = output_wire # todo get absolute path + bigraph['hyper_edges'].append({ + 'source': subpath, + 'target': target, + 'port': port, + 'type': 'output', }) else: - bigraph['state_nodes'].append(subpath) + bigraph['state_nodes'].append(node_spec) @@ -172,17 +185,19 @@ def plot_bigraph( # parse out the network bigraph_network = get_flattened_graph(schema, state) - # make a figure - graph = get_graphviz_fig(bigraph_network) + print(bigraph_network) - # display or save results - if filename is not None: - out_dir = out_dir or 'out' - os.makedirs(out_dir, exist_ok=True) - fig_path = os.path.join(out_dir, filename) - print(f"Writing {fig_path}") - graph.render(filename=fig_path, format=file_format) - return graph + # # make a figure + # graph = get_graphviz_fig(bigraph_network) + # + # # display or save results + # if filename is not None: + # out_dir = out_dir or 'out' + # os.makedirs(out_dir, exist_ok=True) + # fig_path = os.path.join(out_dir, filename) + # print(f"Writing {fig_path}") + # graph.render(filename=fig_path, format=file_format) + # return graph def test_diagram_plot(): @@ -200,7 +215,7 @@ def test_diagram_plot(): cell = { 'cell': { - '_type': 'process', + '_type': 'edge', 'config': {}, 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from '_inputs': { From 12f93a6ebe3c73411d9c5228cf008d34928ebdb4 Mon Sep 17 00:00:00 2001 From: Eran Date: Sat, 27 Jan 2024 13:47:13 -0500 Subject: [PATCH 05/14] rename things --- bigraph_viz/diagram.py | 97 ++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 6d917f8..6433e7a 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -11,9 +11,6 @@ import graphviz -# make a local type system -core = TypeSystem() - SCHEMA_KEYS = ['_type', 'config', 'address'] # get these from bigraph_schema PROCESS_INTERFACE_KEYS = ['inputs', 'outputs'] @@ -24,17 +21,18 @@ def get_flattened_wires(wires, hyperedges): pass -def get_flattened_graph( +def get_graph_dict( schema, state, - bigraph=None, + core, + graph_dict=None, path=None, # remove_nodes=None, ): path = path or () # initialize bigraph - bigraph = bigraph or { + graph_dict = graph_dict or { 'state_nodes': [], 'process_nodes': [], 'place_edges': [], @@ -49,7 +47,7 @@ def get_flattened_graph( 'path': subpath} if core.check('edge', value): - bigraph['process_nodes'].append(node_spec) + graph_dict['process_nodes'].append(node_spec) # this is an edge, get its inputs and outputs input_wires = value.get('inputs', {}) @@ -57,38 +55,38 @@ def get_flattened_graph( for port, input_wire in input_wires.items(): target = input_wire # todo get absolute path - bigraph['hyper_edges'].append({ - 'source': subpath, - 'target': target, + graph_dict['hyper_edges'].append({ + 'edge_path': subpath, + 'target_path': target, 'port': port, 'type': 'input', }) for port, output_wire in output_wires.items(): target = output_wire # todo get absolute path - bigraph['hyper_edges'].append({ - 'source': subpath, - 'target': target, + graph_dict['hyper_edges'].append({ + 'edge_path': subpath, + 'target_path': target, 'port': port, 'type': 'output', }) else: - bigraph['state_nodes'].append(node_spec) + graph_dict['state_nodes'].append(node_spec) - return bigraph + return graph_dict def get_graphviz_fig( - bigraph_network, + graph_dict, label_margin='0.05', node_label_size='12pt', size='16,10', rankdir='TB', dpi='70', ): - """make a graphviz figure from a bigraph_network""" + """make a graphviz figure from a graph_dict""" node_names = [] # node specs @@ -106,7 +104,7 @@ def get_graphviz_fig( # state nodes graph.attr('node', **state_node_spec) - for node in bigraph_network['state_nodes']: + for node in graph_dict['state_nodes']: node_path = node['path'] node_name = str(node_path) node_names.append(node_name) @@ -122,7 +120,7 @@ def get_graphviz_fig( # process nodes process_paths = [] graph.attr('node', **process_node_spec) - for node in bigraph_network['process_nodes']: + for node in graph_dict['process_nodes']: node_path = node['path'] process_paths.append(node_path) node_name = str(node_path) @@ -132,42 +130,36 @@ def get_graphviz_fig( # place edges graph.attr('edge', arrowhead='none', penwidth='2') - for edge in bigraph_network['place_edges']: + for edge in graph_dict['place_edges']: # show edge graph.attr('edge', style='filled') node_name1 = str(edge[0]) - node_name2 = str(edge[1]) - graph.edge(node_name1, node_name2) + target_name = str(edge[1]) + graph.edge(node_name1, target_name) # hyper edges graph.attr('edge', **hyper_edge_spec) - for node_path, wires in bigraph_network['hyper_edges'].items(): - node_name = str(node_path) - with graph.subgraph(name=node_name) as c: - for port, state_path in wires.items(): - absolute_state_path = absolute_path(node_path, state_path) - node_name1 = str(node_path) - node_name2 = str(absolute_state_path) - - # are the nodes already in the graph? - if node_name2 not in graph.body: - label = make_label(absolute_state_path[-1]) - graph.node(node_name2, label=label, **state_node_spec) - c.edge(node_name2, node_name1) + for edge in graph_dict['hyper_edges']: + edge_path = edge['edge_path'] + edge_name = str(edge_path) + target_path = edge['target_path'] + port = edge['port'] + edge_type = edge['type'] # input or output + target_name = str(target_path) + + # place it in the graph + if target_name not in graph.body: # is the source node already in the graph? + label = make_label(target_path[-1]) + graph.node(target_name, label=label, **state_node_spec) + else: + with graph.subgraph(name=edge_name) as c: + c.edge(target_name, edge_name) # disconnected hyper edges graph.attr('edge', **hyper_edge_spec) - for node_path, wires in bigraph_network['disconnected_hyper_edges'].items(): - node_name = str(node_path) - with graph.subgraph(name=node_name) as c: - for port, state_path in wires.items(): - absolute_state_path = absolute_path(node_path, state_path) - node_name1 = str(node_path) - node_name2 = str(absolute_state_path) - # add invisible node for port - graph.node(node_name2, label='', style='invis', width='0') - c.edge(node_name2, node_name1) + for edge in graph_dict['disconnected_hyper_edges']: + pass return graph @@ -175,21 +167,26 @@ def get_graphviz_fig( def plot_bigraph( state, schema=None, + core=None, out_dir=None, filename=None, file_format='png', ): + core = core or TypeSystem() schema = schema or {} schema = core.infer_schema(schema, state) # parse out the network - bigraph_network = get_flattened_graph(schema, state) + graph_dict = get_graph_dict( + schema=schema, + state=state, + core=core) + + print(graph_dict) - print(bigraph_network) + # make a figure + graph = get_graphviz_fig(graph_dict) - # # make a figure - # graph = get_graphviz_fig(bigraph_network) - # # # display or save results # if filename is not None: # out_dir = out_dir or 'out' From 57bbd869c06fe896e2dfb0a7954b347627b4e73e Mon Sep 17 00:00:00 2001 From: Eran Date: Tue, 30 Jan 2024 18:57:55 -0500 Subject: [PATCH 06/14] work in progress --- bigraph_viz/diagram.py | 69 ++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 6433e7a..9d23484 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -17,8 +17,25 @@ EDGE_TYPES = ['process', 'step', 'edge'] -def get_flattened_wires(wires, hyperedges): - pass +def get_graph_wires(schema, wires, graph_dict, schema_key, edge_path, port): + + if isinstance(wires, dict): + for port, subwire in wires.items(): + subschema = schema.get(port, schema) + graph_dict = get_graph_wires( + subschema, subwire, graph_dict, schema_key, edge_path, port) + elif isinstance(wires, (list, tuple)): + absolute_path = edge_path + tuple(wires) # TODO -- make sure this resolves ... + graph_dict['hyper_edges'].append({ + 'edge_path': edge_path, + 'target_path': absolute_path, + 'port': port, + 'type': schema_key, + }) + else: + raise ValueError(f"Unexpected wire type: {wires}") + + return graph_dict def get_graph_dict( @@ -27,9 +44,11 @@ def get_graph_dict( core, graph_dict=None, path=None, + top_state=None, # remove_nodes=None, ): path = path or () + top_state = top_state or state # initialize bigraph graph_dict = graph_dict or { @@ -46,34 +65,44 @@ def get_graph_dict( node_spec = {'name': key, 'path': subpath} - if core.check('edge', value): + if core.check('edge', value): # Maybe make a more specific type for this + # this is a process/edge node graph_dict['process_nodes'].append(node_spec) # this is an edge, get its inputs and outputs input_wires = value.get('inputs', {}) output_wires = value.get('outputs', {}) + input_schema = value.get('_inputs', {}) + output_schema = value.get('_outputs', {}) - for port, input_wire in input_wires.items(): - target = input_wire # todo get absolute path - graph_dict['hyper_edges'].append({ - 'edge_path': subpath, - 'target_path': target, - 'port': port, - 'type': 'input', - }) - for port, output_wire in output_wires.items(): - target = output_wire # todo get absolute path - graph_dict['hyper_edges'].append({ - 'edge_path': subpath, - 'target_path': target, - 'port': port, - 'type': 'output', - }) + # (schema, wires, graph_dict, schema_key, edge_path, port) + graph_dict = get_graph_wires( + input_schema, input_wires, graph_dict, schema_key='inputs', edge_path=subpath, port=()) + graph_dict = get_graph_wires( + output_schema, output_wires, graph_dict, schema_key='outputs', edge_path=subpath, port=()) else: + # this is a state node graph_dict['state_nodes'].append(node_spec) - + if isinstance(value, dict): + sub_graph_dict = get_graph_dict( + schema=schema, + state=value, + core=core, + # graph_dict=graph_dict, + path=subpath, + top_state=top_state, + ) + + # TODO -- merge this in + + # get the place edge + for node in value.keys(): + child_path = subpath + (node,) + graph_dict['place_edges'].append({ + 'parent': subpath, + 'child': child_path}) return graph_dict From a4a2da4ea445bb55d2a16e6b9c7d7993e6ac619a Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 2 Feb 2024 16:06:49 -0500 Subject: [PATCH 07/14] wip --- bigraph_viz/diagram.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 9d23484..baa1751 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -1,7 +1,6 @@ """ Bigraph diagram """ -import os from bigraph_schema import TypeSystem, Edge # from process_bigraph import core from bigraph_viz.plot import ( @@ -11,8 +10,7 @@ import graphviz - -SCHEMA_KEYS = ['_type', 'config', 'address'] # get these from bigraph_schema +PROCESS_SCHEMA_KEYS = ['_type', 'config', 'address'] # get these from bigraph_schema PROCESS_INTERFACE_KEYS = ['inputs', 'outputs'] EDGE_TYPES = ['process', 'step', 'edge'] @@ -25,10 +23,10 @@ def get_graph_wires(schema, wires, graph_dict, schema_key, edge_path, port): graph_dict = get_graph_wires( subschema, subwire, graph_dict, schema_key, edge_path, port) elif isinstance(wires, (list, tuple)): - absolute_path = edge_path + tuple(wires) # TODO -- make sure this resolves ... + target_path = absolute_path(edge_path[:-1], tuple(wires)) # TODO -- make sure this resolves ".." graph_dict['hyper_edges'].append({ 'edge_path': edge_path, - 'target_path': absolute_path, + 'target_path': target_path, 'port': port, 'type': schema_key, }) @@ -203,7 +201,7 @@ def plot_bigraph( ): core = core or TypeSystem() schema = schema or {} - schema = core.infer_schema(schema, state) + schema, state = core.complete(schema, state) # parse out the network graph_dict = get_graph_dict( @@ -213,8 +211,8 @@ def plot_bigraph( print(graph_dict) - # make a figure - graph = get_graphviz_fig(graph_dict) + # # make a figure + # graph = get_graphviz_fig(graph_dict) # # display or save results # if filename is not None: @@ -240,8 +238,9 @@ def test_diagram_plot(): cell = { + # 'secretions_store': {}, 'cell': { - '_type': 'edge', + '_type': 'edge', # TODO -- this should also accept process, step, but how in bigraph-schema? 'config': {}, 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from '_inputs': { From 9afe3d8c1bb42847ec039364c93d2bcef9166c88 Mon Sep 17 00:00:00 2001 From: Ryan Spangler Date: Tue, 6 Feb 2024 15:25:46 -0800 Subject: [PATCH 08/14] dealing with step and process edge types --- .gitignore | 3 +++ bigraph_viz/diagram.py | 55 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 480e8c4..cd5fb9c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ wheels/ *.egg *.out out/ +.python-version +__pycache__/ +.ipynb_checkpoints/ diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index baa1751..d920f47 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -10,9 +10,20 @@ import graphviz -PROCESS_SCHEMA_KEYS = ['_type', 'config', 'address'] # get these from bigraph_schema -PROCESS_INTERFACE_KEYS = ['inputs', 'outputs'] -EDGE_TYPES = ['process', 'step', 'edge'] +PROCESS_SCHEMA_KEYS = ['config', 'address', 'interval', 'inputs', 'outputs'] + + +step_type = { + '_type': 'step', + '_inherit': 'edge', + 'address': 'string', + 'config': 'schema'} + + +process_type = { + '_type': 'process', + '_inherit': 'step', + 'interval': 'float'} def get_graph_wires(schema, wires, graph_dict, schema_key, edge_path, port): @@ -43,6 +54,8 @@ def get_graph_dict( graph_dict=None, path=None, top_state=None, + retain_type_keys=False, + retain_process_keys=False, # remove_nodes=None, ): path = path or () @@ -59,12 +72,18 @@ def get_graph_dict( } for key, value in state.items(): + if key.startswith('_') and not retain_type_keys: + continue + subpath = path + (key,) node_spec = {'name': key, 'path': subpath} if core.check('edge', value): # Maybe make a more specific type for this # this is a process/edge node + if key in PROCESS_SCHEMA_KEYS and not retain_process_keys: + continue + graph_dict['process_nodes'].append(node_spec) # this is an edge, get its inputs and outputs @@ -97,6 +116,12 @@ def get_graph_dict( # get the place edge for node in value.keys(): + if node.startswith('_') and not retain_type_keys: + continue + + if not retain_process_keys and core.check('edge', value): + continue + child_path = subpath + (node,) graph_dict['place_edges'].append({ 'parent': subpath, @@ -199,10 +224,19 @@ def plot_bigraph( filename=None, file_format='png', ): + core = core or TypeSystem() schema = schema or {} + + if not core.exists('step'): + core.register('step', step_type) + if not core.exists('process'): + core.register('process', process_type) + schema, state = core.complete(schema, state) + import ipdb; ipdb.set_trace() + # parse out the network graph_dict = get_graph_dict( schema=schema, @@ -237,18 +271,25 @@ def test_diagram_plot(): # register('metabolic_process', metabolic_process_type) # TODO -- register where? + # TODO: where does the 'map[float]' go for the schema for the + # 'config' key here? + cell = { # 'secretions_store': {}, + 'config': { + '_type': 'map[float]', + 'a': 11.0, + 'b': 3333.33}, 'cell': { - '_type': 'edge', # TODO -- this should also accept process, step, but how in bigraph-schema? + '_type': 'process', # TODO -- this should also accept process, step, but how in bigraph-schema? 'config': {}, 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from '_inputs': { - 'nutrients': 'any', + 'nutrients': 'float', }, '_outputs': { - 'secretions': 'any', - 'biomass': 'any', + 'secretions': 'float', + 'biomass': 'float', }, 'inputs': { 'nutrients': ['nutrients_store'], From da78cefdb02003675bf2c7b1749eae8ed83f73cc Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 11:14:13 -0500 Subject: [PATCH 09/14] make the diagram from bigraph_dict --- bigraph_viz/__init__.py | 14 ++++++ bigraph_viz/diagram.py | 99 +++++++++++++++-------------------------- 2 files changed, 49 insertions(+), 64 deletions(-) diff --git a/bigraph_viz/__init__.py b/bigraph_viz/__init__.py index 80d6991..12991ad 100644 --- a/bigraph_viz/__init__.py +++ b/bigraph_viz/__init__.py @@ -1,3 +1,17 @@ +import pprint from bigraph_viz.plot import plot_bigraph, plot_flow, plot_multitimestep from bigraph_viz.dict_utils import pp, pf, schema_state_to_dict from bigraph_viz.convert import convert_vivarium_composite + + +pretty = pprint.PrettyPrinter(indent=2) + + +def pp(x): + """Print ``x`` in a pretty format.""" + pretty.pprint(x) + + +def pf(x): + """Format ``x`` for display.""" + return pretty.pformat(x) \ No newline at end of file diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index d920f47..3d4f030 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -1,12 +1,9 @@ """ Bigraph diagram """ +import os from bigraph_schema import TypeSystem, Edge -# from process_bigraph import core -from bigraph_viz.plot import ( - absolute_path, extend_bigraph, get_state_path_extended, - check_if_path_in_removed_nodes, make_label, -) +from bigraph_viz.plot import absolute_path, make_label import graphviz @@ -56,7 +53,6 @@ def get_graph_dict( top_state=None, retain_type_keys=False, retain_process_keys=False, - # remove_nodes=None, ): path = path or () top_state = top_state or state @@ -76,11 +72,9 @@ def get_graph_dict( continue subpath = path + (key,) - node_spec = {'name': key, - 'path': subpath} + node_spec = {'name': key, 'path': subpath} - if core.check('edge', value): # Maybe make a more specific type for this - # this is a process/edge node + if core.check('edge', value): # this is a process/edge node if key in PROCESS_SCHEMA_KEYS and not retain_process_keys: continue @@ -92,33 +86,29 @@ def get_graph_dict( input_schema = value.get('_inputs', {}) output_schema = value.get('_outputs', {}) - # (schema, wires, graph_dict, schema_key, edge_path, port) + # get the input and output wires graph_dict = get_graph_wires( input_schema, input_wires, graph_dict, schema_key='inputs', edge_path=subpath, port=()) graph_dict = get_graph_wires( output_schema, output_wires, graph_dict, schema_key='outputs', edge_path=subpath, port=()) - else: - # this is a state node + else: # this is a state node graph_dict['state_nodes'].append(node_spec) - if isinstance(value, dict): - sub_graph_dict = get_graph_dict( + if isinstance(value, dict): # get subgraph + graph_dict = get_graph_dict( schema=schema, state=value, core=core, - # graph_dict=graph_dict, + graph_dict=graph_dict, path=subpath, top_state=top_state, ) - # TODO -- merge this in - # get the place edge for node in value.keys(): if node.startswith('_') and not retain_type_keys: continue - if not retain_process_keys and core.check('edge', value): continue @@ -127,6 +117,7 @@ def get_graph_dict( 'parent': subpath, 'child': child_path}) + return graph_dict @@ -150,8 +141,7 @@ def get_graphviz_fig( 'style': 'dashed', 'penwidth': '1', 'arrowhead': 'dot', 'arrowsize': '0.5'} # initialize graph - graph_name = 'bigraph' - graph = graphviz.Digraph(graph_name, engine='dot') + graph = graphviz.Digraph(name='bigraph', engine='dot') graph.attr(size=size, overlap='false', rankdir=rankdir, dpi=dpi) # state nodes @@ -183,12 +173,10 @@ def get_graphviz_fig( # place edges graph.attr('edge', arrowhead='none', penwidth='2') for edge in graph_dict['place_edges']: - - # show edge graph.attr('edge', style='filled') - node_name1 = str(edge[0]) - target_name = str(edge[1]) - graph.edge(node_name1, target_name) + parent_node = str(edge['parent']) + child_node = str(edge['child']) + graph.edge(parent_node, child_node) # hyper edges graph.attr('edge', **hyper_edge_spec) @@ -201,17 +189,18 @@ def get_graphviz_fig( target_name = str(target_path) # place it in the graph - if target_name not in graph.body: # is the source node already in the graph? + if target_name not in graph.body: # is the source node already in the graph? label = make_label(target_path[-1]) graph.node(target_name, label=label, **state_node_spec) - else: - with graph.subgraph(name=edge_name) as c: - c.edge(target_name, edge_name) - # disconnected hyper edges - graph.attr('edge', **hyper_edge_spec) - for edge in graph_dict['disconnected_hyper_edges']: - pass + with graph.subgraph(name=edge_name) as c: + label = make_label(port) + c.edge(target_name, edge_name, label=label, labelloc="t") + + # # disconnected hyper edges + # graph.attr('edge', **hyper_edge_spec) + # for edge in graph_dict['disconnected_hyper_edges']: + # pass return graph @@ -235,49 +224,31 @@ def plot_bigraph( schema, state = core.complete(schema, state) - import ipdb; ipdb.set_trace() - # parse out the network graph_dict = get_graph_dict( schema=schema, state=state, core=core) - print(graph_dict) + # print(graph_dict) - # # make a figure - # graph = get_graphviz_fig(graph_dict) + # make a figure + graph = get_graphviz_fig(graph_dict) - # # display or save results - # if filename is not None: - # out_dir = out_dir or 'out' - # os.makedirs(out_dir, exist_ok=True) - # fig_path = os.path.join(out_dir, filename) - # print(f"Writing {fig_path}") - # graph.render(filename=fig_path, format=file_format) - # return graph + # display or save results + if filename is not None: + out_dir = out_dir or 'out' + os.makedirs(out_dir, exist_ok=True) + fig_path = os.path.join(out_dir, filename) + print(f"Writing {fig_path}") + graph.render(filename=fig_path, format=file_format) + return graph def test_diagram_plot(): - # metabolic_process_type = { - # '_type': 'process', - # '_inputs': { - # 'nutrients': 'any', - # }, - # '_outputs': { - # 'se' - # } - # } - # register('metabolic_process', metabolic_process_type) # TODO -- register where? - - - # TODO: where does the 'map[float]' go for the schema for the - # 'config' key here? - cell = { - # 'secretions_store': {}, 'config': { - '_type': 'map[float]', + # '_type': 'map[float]', 'a': 11.0, 'b': 3333.33}, 'cell': { From bb400cb84f0805b288ebb5483d1913eb08ba8df8 Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 12:13:31 -0500 Subject: [PATCH 10/14] add input/output edge arrows --- bigraph_viz/diagram.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 3d4f030..ee29001 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -117,7 +117,6 @@ def get_graph_dict( 'parent': subpath, 'child': child_path}) - return graph_dict @@ -139,6 +138,10 @@ def get_graphviz_fig( 'shape': 'box', 'penwidth': '2', 'constraint': 'false', 'margin': label_margin, 'fontsize': node_label_size} hyper_edge_spec = { 'style': 'dashed', 'penwidth': '1', 'arrowhead': 'dot', 'arrowsize': '0.5'} + input_edge_spec = { + 'style': 'dashed', 'penwidth': '1', 'arrowhead': 'normal', 'arrowsize': '1.0'} + output_edge_spec = { + 'style': 'dashed', 'penwidth': '1', 'arrowhead': 'normal', 'arrowsize': '1.0', 'dir': 'back'} # initialize graph graph = graphviz.Digraph(name='bigraph', engine='dot') @@ -179,10 +182,9 @@ def get_graphviz_fig( graph.edge(parent_node, child_node) # hyper edges - graph.attr('edge', **hyper_edge_spec) for edge in graph_dict['hyper_edges']: - edge_path = edge['edge_path'] - edge_name = str(edge_path) + process_path = edge['edge_path'] + process_name = str(process_path) target_path = edge['target_path'] port = edge['port'] edge_type = edge['type'] # input or output @@ -193,14 +195,20 @@ def get_graphviz_fig( label = make_label(target_path[-1]) graph.node(target_name, label=label, **state_node_spec) - with graph.subgraph(name=edge_name) as c: + if edge_type == 'inputs': + graph.attr('edge', **input_edge_spec) + elif edge_type == 'outputs': + graph.attr('edge', **output_edge_spec) + else: + graph.attr('edge', **hyper_edge_spec) + with graph.subgraph(name=process_name) as c: label = make_label(port) - c.edge(target_name, edge_name, label=label, labelloc="t") + c.edge(target_name, process_name, label=label, labelloc="t") - # # disconnected hyper edges - # graph.attr('edge', **hyper_edge_spec) - # for edge in graph_dict['disconnected_hyper_edges']: - # pass + # disconnected hyper edges + graph.attr('edge', **hyper_edge_spec) + for edge in graph_dict['disconnected_hyper_edges']: + pass return graph From b3fd50cb46924de94badb1153bad54d87c11d651 Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 15:11:36 -0500 Subject: [PATCH 11/14] get disconnected hyperedge --- bigraph_viz/diagram.py | 56 +++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index ee29001..dfac626 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -25,19 +25,30 @@ def get_graph_wires(schema, wires, graph_dict, schema_key, edge_path, port): - if isinstance(wires, dict): - for port, subwire in wires.items(): - subschema = schema.get(port, schema) - graph_dict = get_graph_wires( - subschema, subwire, graph_dict, schema_key, edge_path, port) + # if isinstance(wires, dict): + # for port, subwire in wires.items(): + # subschema = schema.get(port, schema) + # graph_dict = get_graph_wires( + # subschema, subwire, graph_dict, schema_key, edge_path, port) + + if isinstance(schema, dict): + for port, subschema in schema.items(): + subwire = wires.get(port) + if subwire: + graph_dict = get_graph_wires( + subschema, subwire, graph_dict, schema_key, edge_path, port) + else: # this is a disconnected port + graph_dict['disconnected_hyper_edges'].append({ + 'edge_path': edge_path, + 'port': port, + 'type': schema_key}) elif isinstance(wires, (list, tuple)): - target_path = absolute_path(edge_path[:-1], tuple(wires)) # TODO -- make sure this resolves ".." + target_path = absolute_path(edge_path[:-1], tuple(wires)) # TODO -- make sure this resolves ".." graph_dict['hyper_edges'].append({ 'edge_path': edge_path, 'target_path': target_path, 'port': port, - 'type': schema_key, - }) + 'type': schema_key}) else: raise ValueError(f"Unexpected wire type: {wires}") @@ -102,8 +113,7 @@ def get_graph_dict( core=core, graph_dict=graph_dict, path=subpath, - top_state=top_state, - ) + top_state=top_state) # get the place edge for node in value.keys(): @@ -208,7 +218,25 @@ def get_graphviz_fig( # disconnected hyper edges graph.attr('edge', **hyper_edge_spec) for edge in graph_dict['disconnected_hyper_edges']: - pass + process_path = edge['edge_path'] + process_name = str(process_path) + port = edge['port'] + edge_type = edge['type'] # input or output + + # add invisible node for port + node_name2 = str(absolute_path(process_path, port)) + graph.node(node_name2, label='', style='invis', width='0') + + # add the edge + if edge_type == 'inputs': + graph.attr('edge', **input_edge_spec) + elif edge_type == 'outputs': + graph.attr('edge', **output_edge_spec) + else: + graph.attr('edge', **hyper_edge_spec) + with graph.subgraph(name=process_name) as c: + label = make_label(port) + c.edge(node_name2, process_name, label=label, labelloc="t") return graph @@ -238,8 +266,6 @@ def plot_bigraph( state=state, core=core) - # print(graph_dict) - # make a figure graph = get_graphviz_fig(graph_dict) @@ -256,7 +282,7 @@ def plot_bigraph( def test_diagram_plot(): cell = { 'config': { - # '_type': 'map[float]', + '_type': 'map[float]', 'a': 11.0, 'b': 3333.33}, 'cell': { @@ -274,7 +300,7 @@ def test_diagram_plot(): 'nutrients': ['nutrients_store'], }, 'outputs': { - 'secretions': ['secretions_store'], + # 'secretions': ['secretions_store'], 'biomass': ['biomass_store'], } } From 57c21cf004f1e249c5553a6065fc3dadcf04ff97 Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 15:46:05 -0500 Subject: [PATCH 12/14] begin bringing back formatting options --- bigraph_viz/diagram.py | 82 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index dfac626..328ed64 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -83,7 +83,12 @@ def get_graph_dict( continue subpath = path + (key,) - node_spec = {'name': key, 'path': subpath} + node_spec = { + 'name': key, + 'path': subpath, + 'value': None, + 'type': None + } if core.check('edge', value): # this is a process/edge node if key in PROCESS_SCHEMA_KEYS and not retain_process_keys: @@ -104,6 +109,11 @@ def get_graph_dict( output_schema, output_wires, graph_dict, schema_key='outputs', edge_path=subpath, port=()) else: # this is a state node + if not isinstance(value, dict): # this is a leaf node + node_spec['value'] = value + # else: # TODO -- this is not getting all values/types + # node_spec['value'] = value.get('_value') + # node_spec['type'] = value.get('_type') graph_dict['state_nodes'].append(node_spec) if isinstance(value, dict): # get subgraph @@ -137,6 +147,10 @@ def get_graphviz_fig( size='16,10', rankdir='TB', dpi='70', + show_values=False, + show_types=False, + port_labels=True, + port_label_size='10pt', ): """make a graphviz figure from a graph_dict""" node_names = [] @@ -167,6 +181,16 @@ def get_graphviz_fig( # make the label label = node_path[-1] schema_label = None + if show_values: + if node.get('value'): + if not schema_label: + schema_label = '
' + schema_label += f"value: {node['value']}" + if show_types: + if node.get('type'): + if not schema_label: + schema_label = '
' + schema_label += f"type: {node['type']}" if schema_label: label += schema_label label = make_label(label) @@ -212,8 +236,11 @@ def get_graphviz_fig( else: graph.attr('edge', **hyper_edge_spec) with graph.subgraph(name=process_name) as c: - label = make_label(port) - c.edge(target_name, process_name, label=label, labelloc="t") + if port_labels: + label = make_label(port) + c.edge(target_name, process_name, label=label, labelloc="t", fontsize=port_label_size) + else: + c.edge(target_name, process_name) # disconnected hyper edges graph.attr('edge', **hyper_edge_spec) @@ -235,8 +262,12 @@ def get_graphviz_fig( else: graph.attr('edge', **hyper_edge_spec) with graph.subgraph(name=process_name) as c: - label = make_label(port) - c.edge(node_name2, process_name, label=label, labelloc="t") + if port_labels: + label = make_label(port) + c.edge(node_name2, process_name, label=label, labelloc="t", fontsize=port_label_size + ) + else: + c.edge(node_name2, process_name) return graph @@ -248,8 +279,39 @@ def plot_bigraph( out_dir=None, filename=None, file_format='png', + size='16,10', + node_label_size='12pt', + show_values=False, + show_types=False, + # show_process_schema=False, + # collapse_processes=False, + port_labels=True, + port_label_size='10pt', + # rankdir='TB', + # node_border_colors=None, + # node_fill_colors=None, + # node_groups=False, + # remove_nodes=None, + # invisible_edges=False, + # mark_top=False, + # remove_process_place_edges=False, + print_source=False, + # dpi='70', + # label_margin='0.05', ): - + # get kwargs dict and remove plotting-specific kwargs + kwargs = locals() + state = kwargs.pop('state') + schema = kwargs.pop('schema') + core = kwargs.pop('core') + file_format = kwargs.pop('file_format') + out_dir = kwargs.pop('out_dir') + filename = kwargs.pop('filename') + print_source = kwargs.pop('print_source') + # remove_nodes = kwargs.pop('remove_nodes') + # show_process_schema = kwargs.pop('show_process_schema') + + # set defaults if none provided core = core or TypeSystem() schema = schema or {} @@ -267,9 +329,11 @@ def plot_bigraph( core=core) # make a figure - graph = get_graphviz_fig(graph_dict) + graph = get_graphviz_fig(graph_dict, **kwargs) # display or save results + if print_source: + print(graph.source) if filename is not None: out_dir = out_dir or 'out' os.makedirs(out_dir, exist_ok=True) @@ -282,7 +346,7 @@ def plot_bigraph( def test_diagram_plot(): cell = { 'config': { - '_type': 'map[float]', + # '_type': 'map[float]', 'a': 11.0, 'b': 3333.33}, 'cell': { @@ -305,7 +369,7 @@ def test_diagram_plot(): } } } - plot_bigraph(cell, filename='bigraph_cell') + plot_bigraph(cell, filename='bigraph_cell', show_values=True, show_types=True, port_labels=False) if __name__ == '__main__': From e145fa1a25996fd218a2b6b4b6f8a3723987dca3 Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 15:57:38 -0500 Subject: [PATCH 13/14] more formatting options --- bigraph_viz/diagram.py | 45 +++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index 328ed64..df1f3ac 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -3,7 +3,7 @@ """ import os from bigraph_schema import TypeSystem, Edge -from bigraph_viz.plot import absolute_path, make_label +from bigraph_viz.plot import absolute_path, make_label, check_if_path_in_removed_nodes import graphviz @@ -64,6 +64,7 @@ def get_graph_dict( top_state=None, retain_type_keys=False, retain_process_keys=False, + remove_nodes=None, ): path = path or () top_state = top_state or state @@ -83,6 +84,10 @@ def get_graph_dict( continue subpath = path + (key,) + if check_if_path_in_removed_nodes(subpath, remove_nodes): + # skip node if path in removed_nodes + continue + node_spec = { 'name': key, 'path': subpath, @@ -123,7 +128,9 @@ def get_graph_dict( core=core, graph_dict=graph_dict, path=subpath, - top_state=top_state) + top_state=top_state, + remove_nodes=remove_nodes + ) # get the place edge for node in value.keys(): @@ -133,6 +140,8 @@ def get_graph_dict( continue child_path = subpath + (node,) + if check_if_path_in_removed_nodes(child_path, remove_nodes): + continue graph_dict['place_edges'].append({ 'parent': subpath, 'child': child_path}) @@ -283,21 +292,21 @@ def plot_bigraph( node_label_size='12pt', show_values=False, show_types=False, - # show_process_schema=False, - # collapse_processes=False, port_labels=True, port_label_size='10pt', - # rankdir='TB', + rankdir='TB', + print_source=False, + dpi='70', + label_margin='0.05', + # show_process_schema=False, + # collapse_processes=False, # node_border_colors=None, # node_fill_colors=None, # node_groups=False, - # remove_nodes=None, + remove_nodes=None, # invisible_edges=False, # mark_top=False, # remove_process_place_edges=False, - print_source=False, - # dpi='70', - # label_margin='0.05', ): # get kwargs dict and remove plotting-specific kwargs kwargs = locals() @@ -308,7 +317,7 @@ def plot_bigraph( out_dir = kwargs.pop('out_dir') filename = kwargs.pop('filename') print_source = kwargs.pop('print_source') - # remove_nodes = kwargs.pop('remove_nodes') + remove_nodes = kwargs.pop('remove_nodes') # show_process_schema = kwargs.pop('show_process_schema') # set defaults if none provided @@ -326,7 +335,9 @@ def plot_bigraph( graph_dict = get_graph_dict( schema=schema, state=state, - core=core) + core=core, + remove_nodes=remove_nodes, + ) # make a figure graph = get_graphviz_fig(graph_dict, **kwargs) @@ -369,7 +380,17 @@ def test_diagram_plot(): } } } - plot_bigraph(cell, filename='bigraph_cell', show_values=True, show_types=True, port_labels=False) + plot_bigraph(cell, filename='bigraph_cell', + # show_values=True, + # show_types=True, + # port_labels=False, + # rankdir='BT', + # remove_nodes=[ + # ('cell', 'address',), + # ('cell', 'config'), + # ('cell', 'interval'), + # ] + ) if __name__ == '__main__': From e5a9c46a57ce548f9d0484e5505671252b48beb4 Mon Sep 17 00:00:00 2001 From: Eran Date: Wed, 7 Feb 2024 21:01:04 -0500 Subject: [PATCH 14/14] many improvements. filtering out process schema keys. --- bigraph_viz/diagram.py | 74 ++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/bigraph_viz/diagram.py b/bigraph_viz/diagram.py index df1f3ac..fede0a8 100644 --- a/bigraph_viz/diagram.py +++ b/bigraph_viz/diagram.py @@ -25,23 +25,22 @@ def get_graph_wires(schema, wires, graph_dict, schema_key, edge_path, port): - # if isinstance(wires, dict): - # for port, subwire in wires.items(): - # subschema = schema.get(port, schema) - # graph_dict = get_graph_wires( - # subschema, subwire, graph_dict, schema_key, edge_path, port) - - if isinstance(schema, dict): + if isinstance(schema, dict) and schema: for port, subschema in schema.items(): subwire = wires.get(port) if subwire: graph_dict = get_graph_wires( subschema, subwire, graph_dict, schema_key, edge_path, port) - else: # this is a disconnected port + else: # this is a disconnected port graph_dict['disconnected_hyper_edges'].append({ 'edge_path': edge_path, 'port': port, 'type': schema_key}) + elif isinstance(wires, dict): + for port, subwire in wires.items(): + subschema = schema.get(port, schema) + graph_dict = get_graph_wires( + subschema, subwire, graph_dict, schema_key, edge_path, port) elif isinstance(wires, (list, tuple)): target_path = absolute_path(edge_path[:-1], tuple(wires)) # TODO -- make sure this resolves ".." graph_dict['hyper_edges'].append({ @@ -68,6 +67,7 @@ def get_graph_dict( ): path = path or () top_state = top_state or state + remove_nodes = remove_nodes or [] # initialize bigraph graph_dict = graph_dict or { @@ -95,7 +95,8 @@ def get_graph_dict( 'type': None } - if core.check('edge', value): # this is a process/edge node + is_edge = core.check('edge', value) + if is_edge: # this is a process/edge node if key in PROCESS_SCHEMA_KEYS and not retain_process_keys: continue @@ -116,14 +117,19 @@ def get_graph_dict( else: # this is a state node if not isinstance(value, dict): # this is a leaf node node_spec['value'] = value - # else: # TODO -- this is not getting all values/types - # node_spec['value'] = value.get('_value') - # node_spec['type'] = value.get('_type') + node_spec['type'] = schema.get(key, {}).get('_type') + else: + # node_spec['value'] = str(value) + node_spec['type'] = schema.get(key, {}).get('_type') graph_dict['state_nodes'].append(node_spec) if isinstance(value, dict): # get subgraph + if is_edge: + removed_process_schema_keys = [subpath + (schema_key,) for schema_key in PROCESS_SCHEMA_KEYS] + remove_nodes.extend(removed_process_schema_keys) + graph_dict = get_graph_dict( - schema=schema, + schema=schema.get(key, schema), state=value, core=core, graph_dict=graph_dict, @@ -136,8 +142,6 @@ def get_graph_dict( for node in value.keys(): if node.startswith('_') and not retain_type_keys: continue - if not retain_process_keys and core.check('edge', value): - continue child_path = subpath + (node,) if check_if_path_in_removed_nodes(child_path, remove_nodes): @@ -193,13 +197,13 @@ def get_graphviz_fig( if show_values: if node.get('value'): if not schema_label: - schema_label = '
' - schema_label += f"value: {node['value']}" + schema_label = '' + schema_label += f": {node['value']}" if show_types: if node.get('type'): if not schema_label: schema_label = '
' - schema_label += f"type: {node['type']}" + schema_label += f"[{node['type']}]" if schema_label: label += schema_label label = make_label(label) @@ -357,13 +361,14 @@ def plot_bigraph( def test_diagram_plot(): cell = { 'config': { - # '_type': 'map[float]', - 'a': 11.0, + '_type': 'map[float]', + 'a': 11.0, #{'_type': 'float', '_value': 11.0}, 'b': 3333.33}, 'cell': { '_type': 'process', # TODO -- this should also accept process, step, but how in bigraph-schema? 'config': {}, 'address': 'local:cell', # TODO -- this is where the ports/inputs/outputs come from + 'internal': 1.0, '_inputs': { 'nutrients': 'float', }, @@ -372,7 +377,7 @@ def test_diagram_plot(): 'biomass': 'float', }, 'inputs': { - 'nutrients': ['nutrients_store'], + 'nutrients': ['down', 'nutrients_store'], }, 'outputs': { # 'secretions': ['secretions_store'], @@ -381,8 +386,8 @@ def test_diagram_plot(): } } plot_bigraph(cell, filename='bigraph_cell', - # show_values=True, - # show_types=True, + show_values=True, + show_types=True, # port_labels=False, # rankdir='BT', # remove_nodes=[ @@ -392,6 +397,27 @@ def test_diagram_plot(): # ] ) +def test_bio_schema(): + b = { + 'environment': { + 'cells': {}, + 'fields': {}, + 'barriers': {}, + 'diffusion': { + '_type': 'process', + # '_inputs': { + # 'fields': 'array' + # }, + 'inputs': { + 'fields': ['fields',] + } + } + }} + + plot_bigraph(b, filename='bioschema') + + if __name__ == '__main__': - test_diagram_plot() + # test_diagram_plot() + test_bio_schema()