diff --git a/hysop/core/graph/computational_graph.py b/hysop/core/graph/computational_graph.py index d95fde0e6469a4d99b0741112341498f5832e81e..eda63ad2307c6fbdf046a2dce6bbaba6e3b909b7 100644 --- a/hysop/core/graph/computational_graph.py +++ b/hysop/core/graph/computational_graph.py @@ -702,16 +702,51 @@ class ComputationalGraph(ComputationalGraphNode): print self.variable_report() print self.operator_report() - @debug - @graph_built - def display(self, visu_rank=0, vertex_font_size=10, edge_font_size=16): + def display(self, visu_rank=0, show_buttons=False): """ Display the reduced computational graph. """ from hysop import main_rank if (visu_rank is None) or (main_rank != visu_rank): return - raise NotImplementedError('This feature has not been implemented yet.') + + net = self.to_pyvis() + + import tempfile + with tempfile.NamedTemporaryFile(suffix='.html') as f: + net.show(f.name) + + def to_file(self, path, io_rank=0, show_buttons=False): + """ + Generate an interactive computational graph in an html file. + """ + from hysop import main_rank + if (io_rank is None) or (main_rank != io_rank): + return + + net = self.to_pyvis() + net.write_html(path) + + @graph_built + def to_pyvis(self): + """ + Convert the graph to a pyvis network for vizualization. + """ + try: + import pyvis + except ImportError: + msg='\nFATAL ERROR: Graph vizualization requires the pyvis module.\n' + print(msg) + raise + + graph = self.reduced_graph + network = pyvis.network.Network() + for node in graph: + node_id = int(node) + network.add_node(node_id, label=node.label, title=node.title, + color=node.color) + return network + @debug @graph_built diff --git a/hysop/core/graph/graph.py b/hysop/core/graph/graph.py index 6c2f37affefc5f464c96fc76eff1acbb188d8ee8..1d8dadb31cd5cb806fe390bc612e3a7719423621 100644 --- a/hysop/core/graph/graph.py +++ b/hysop/core/graph/graph.py @@ -1,5 +1,6 @@ import inspect, networkx from hysop import dprint +from hysop.constants import MemoryOrdering from hysop.tools.types import check_instance, first_not_None from hysop.tools.decorators import not_implemented, debug, wraps, profile @@ -25,9 +26,23 @@ def new_edge(graph, u, v, *args, **kwds): # /!\ We have to use networkx 2.2 which has a different interface for attributes graph.add_edge(u, v, object=EdgeAttributes(*args, **kwds)) return (u,v) + +def generate_vertex_colors(): + import matplotlib + from matplotlib import cm + c0 = cm.get_cmap('tab20c').colors + c1 = cm.get_cmap('tab20b').colors + colors = [] + for i in range(4): + colors += c0[i::4] + c1[i::4] + colors = tuple(map(matplotlib.colors.to_hex, colors)) + return colors class VertexAttributes(object): """Simple class to hold vertex data.""" + + colors = generate_vertex_colors() + def __init__(self, graph, operator=None): if not hasattr(graph, '_hysop_node_counter'): graph._hysop_node_counter = 0 @@ -61,6 +76,7 @@ class VertexAttributes(object): self.output_states = output_states return self + # hashing for networkx def hash(self): return self.node_id def __eq__(self, other): @@ -68,6 +84,87 @@ class VertexAttributes(object): def __int__(self): return self.node_id + # pyvis attributes for display + @property + def label(self): + return self.operator.pretty_name + @property + def title(self): + return self.node_info().replace('\n','<br>') + + @property + def color(self): + cq = self.command_queue + if (cq is None): + return None + assert isinstance(cq, int) and cq >= 0 + colors = self.colors + ncolors = len(colors) + return colors[cq%ncolors] + + def node_info(self): + op = self.operator + istates = self.input_states + ostates = self.output_states + + ifields = op.input_fields + ofields = op.output_fields + iparams = op.input_params + oparams = op.output_params + + memorder2str = { + MemoryOrdering.C_CONTIGUOUS: 'C', + MemoryOrdering.F_CONTIGUOUS: 'F', + } + + def ifinfo(field, topo): + info = (field.name, topo.id) + if istates: + assert field in istates + istate = istates[field] + assert (istate is not None) + info+=(memorder2str[istate.memory_order],) + info+=(str(istate.tstate),) + return ', '.join(map(str,info)) + def ofinfo(field, topo): + info = (field.name, topo.id) + if ostates: + assert field in ostates + ostate = ostates[field] + assert (ostate is not None) + info+=(memorder2str[ostate.memory_order],) + info+=(str(ostate.tstate),) + return ', '.join(map(str,info)) + def ipinfo(param): + return param.name + def opinfo(param): + return param.name + + prefix='  <b>' + suffix='</b>  ' + sep = '\n'+' '*14 + + ss = '<h2>Operator {}</h2>{}{}{}{}{}\n{}'.format(op.name, + '{p}Rank:{s}{}\n\n'.format(self.op_ordering, p=prefix, s=suffix) + if self.op_ordering else '', + '{p}Pin:{s}{}\n'.format(sep.join(ipinfo(param) + for param in iparams.values()), p=prefix, s=suffix+'  ') + if iparams else '', + '{p}Fin:{s}{}\n'.format(sep.join([ifinfo(f,topo) + for (f,topo) in ifields.iteritems()]), p=prefix, s=suffix+'  ') + if ifields else '', + '{p}Pout:{s}{}\n'.format(sep.join([opinfo(param) + for param in oparams.values()]), p=prefix, s=suffix) + if oparams else '', + '{p}Fout:{s}{}\n'.format(sep.join([ofinfo(f,topo) + for (f,topo) in ofields.iteritems()]), p=prefix, s=suffix) + if ofields else '', + '{p}Type:{s} {}'.format( + sep.join(map(lambda x: x.__name__, type(op).__mro__[:-2])), + p=prefix, s=suffix)) + return ss + + class EdgeAttributes(object): """Simple class to hold edge data.""" def __init__(self, variable=None, topology=None): @@ -242,53 +339,3 @@ def op_apply(f): return return ret return apply - -def _op_info(op, istates=None, ostates=None, jmp=False): - ifields = op.input_fields - ofields = op.output_fields - iparams = op.input_params - oparams = op.output_params - - memorder2str = { - MemoryOrdering.C_CONTIGUOUS: 'C', - MemoryOrdering.F_CONTIGUOUS: 'F', - } - - def ifinfo(field, topo): - info = (field.name, topo.id) - if istates: - assert field in istates - istate = istates[field] - assert (istate is not None) - info+=(memorder2str[istate.memory_order],) - info+=(str(istate.tstate),) - return info - def ofinfo(field, topo): - info = (field.name, topo.id) - if ostates: - assert field in ostates - ostate = ostates[field] - assert (ostate is not None) - info+=(memorder2str[ostate.memory_order],) - info+=(str(ostate.tstate),) - return info - def ipinfo(param): - return param.name - def opinfo(param): - return param.name - - ss = 'Operator {} => \n {}{}{}{}\n {}'.format(op.name, - 'Pin:{}\n '.format([ ipinfo(param) for param in iparams.values() ]) - if iparams else '', - 'Fin:{}\n '.format([ ifinfo(f,topo) for (f,topo) in ifields.iteritems() ]) - if ifields else '', - 'Pout:{}\n '.format([ opinfo(param) for param in oparams.values() ]) - if oparams else '', - 'Fout:{}\n '.format([ ofinfo(f,topo) for (f,topo) in ofields.iteritems() ]) - if ofields else '', - op.__class__) - if jmp: - return ss - else: - return ss.replace('\n',' ') - diff --git a/hysop/core/graph/tests/test_graph.py b/hysop/core/graph/tests/test_graph.py index a359a69b1368d63ab28d1530216f5905673f8d10..b91380728945a9a189ae39e84686a07fbf21ad5a 100644 --- a/hysop/core/graph/tests/test_graph.py +++ b/hysop/core/graph/tests/test_graph.py @@ -101,4 +101,4 @@ class TestGraph(object): if __name__ == '__main__': test = TestGraph() - test.test_graph_build(display=False) + test.test_graph_build(display=True) diff --git a/hysop/numerics/splitting/test/test_strang.py b/hysop/numerics/splitting/test/test_strang.py index ac6048a007526bf67185159abb49987678dbf953..b6c543fb082f7523ca16d127dc3dc88b3f9c51cf 100644 --- a/hysop/numerics/splitting/test/test_strang.py +++ b/hysop/numerics/splitting/test/test_strang.py @@ -81,8 +81,6 @@ class TestStrang(object): problem.insert(splitting) problem.insert(poisson) problem.build() - - problem.display() problem.finalize() def test_strang_2d(self, n=33): diff --git a/hysop/operator/tests/test_fd_derivative.py b/hysop/operator/tests/test_fd_derivative.py index 12f7f188da354a1cc86787d5cad43389c8590872..036e75cc0e7039479d40fcdbf8d03faa17acfc0a 100644 --- a/hysop/operator/tests/test_fd_derivative.py +++ b/hysop/operator/tests/test_fd_derivative.py @@ -187,7 +187,6 @@ class TestFiniteDifferencesDerivative(object): for impl in implementations: for op in iter_impl(impl): op.build(outputs_are_inputs=True) - #op.display() dF = op.get_input_discrete_field(F) dgradF = op.get_output_discrete_field(gradF) diff --git a/hysop/operator/tests/test_spectral_derivative.py b/hysop/operator/tests/test_spectral_derivative.py index fbed428d10b327d316a237160509ffe1ea5c7003..a02ae7c9aa9b56a80578fed5e7f8ca5898afabcc 100644 --- a/hysop/operator/tests/test_spectral_derivative.py +++ b/hysop/operator/tests/test_spectral_derivative.py @@ -223,7 +223,6 @@ class TestSpectralDerivative(object): for impl in implementations: for op in iter_impl(impl): op.build(outputs_are_inputs=False) - # op.display() Fd = op.get_input_discrete_field(F) dFd = op.get_output_discrete_field(dF) diff --git a/hysop_examples/examples/particles_above_salt/particles_above_salt_bc.py b/hysop_examples/examples/particles_above_salt/particles_above_salt_bc.py index fecea38c9b450792b57ea28391187d576a196b8f..cc3c345fc7d89c42a58e69d61dcf75059c0f50f5 100644 --- a/hysop_examples/examples/particles_above_salt/particles_above_salt_bc.py +++ b/hysop_examples/examples/particles_above_salt/particles_above_salt_bc.py @@ -296,7 +296,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here) diff --git a/hysop_examples/examples/particles_above_salt/particles_above_salt_bc_3d.py b/hysop_examples/examples/particles_above_salt/particles_above_salt_bc_3d.py index a54c4163326f7a1705b802d87fb62230132ae5e8..00b82718e6ae2ae7db18dc8ede62697cff4d4685 100644 --- a/hysop_examples/examples/particles_above_salt/particles_above_salt_bc_3d.py +++ b/hysop_examples/examples/particles_above_salt/particles_above_salt_bc_3d.py @@ -311,7 +311,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here) diff --git a/hysop_examples/examples/particles_above_salt/particles_above_salt_periodic.py b/hysop_examples/examples/particles_above_salt/particles_above_salt_periodic.py index fda6b5c66e064e2dc25d87ad7c3dacd19cd21fea..c4a635967025c25e804c91f3ea1a64016b8cee42 100644 --- a/hysop_examples/examples/particles_above_salt/particles_above_salt_periodic.py +++ b/hysop_examples/examples/particles_above_salt/particles_above_salt_periodic.py @@ -305,7 +305,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here) diff --git a/hysop_examples/examples/particles_above_salt/particles_above_salt_symmetrized.py b/hysop_examples/examples/particles_above_salt/particles_above_salt_symmetrized.py index 42587f275edaf28481346bf21ece04e375be3764..0fcfdcb60a68791b79d04732a87334d8c4c60ae0 100644 --- a/hysop_examples/examples/particles_above_salt/particles_above_salt_symmetrized.py +++ b/hysop_examples/examples/particles_above_salt/particles_above_salt_symmetrized.py @@ -293,7 +293,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here) diff --git a/hysop_examples/examples/sediment_deposit/sediment_deposit.py b/hysop_examples/examples/sediment_deposit/sediment_deposit.py index 1cd16b714349a9731696919a3452260500882279..8bae7c71c8b0bc59737349199c4068f57c91ba10 100644 --- a/hysop_examples/examples/sediment_deposit/sediment_deposit.py +++ b/hysop_examples/examples/sediment_deposit/sediment_deposit.py @@ -307,7 +307,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here) diff --git a/hysop_examples/examples/sediment_deposit/sediment_deposit_levelset.py b/hysop_examples/examples/sediment_deposit/sediment_deposit_levelset.py index 4b8622f08c045429f2a24844174d74cc67e671da..9adbca147b9e70f16e08ec2ae36d4194eb43ffcf 100644 --- a/hysop_examples/examples/sediment_deposit/sediment_deposit_levelset.py +++ b/hysop_examples/examples/sediment_deposit/sediment_deposit_levelset.py @@ -368,7 +368,7 @@ def compute(args): # If a visu_rank was provided, and show_graph was set, # display the graph on the given process rank. if args.display_graph: - problem.display() + problem.display(args.visu_rank) # Create a simulation # (do not forget to specify the t and dt parameters here)