From 7a9d15eaf381b85aa3b404e9c8a2b14c7adecf22 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <jean-baptiste.keck@imag.fr>
Date: Sun, 18 Mar 2018 13:31:26 +0100
Subject: [PATCH] better domain and operator reports

---
 hysop/backend/device/opencl/opencl_env.py |  73 +++++++-------
 hysop/core/graph/computational_graph.py   | 114 ++++++++++++++--------
 hysop/domain/box.py                       |   3 +-
 hysop/fields/cartesian_discrete_field.py  |   4 +-
 hysop/tools/string_utils.py               |  11 +++
 hysop/topology/cartesian_topology.py      |   4 +-
 6 files changed, 124 insertions(+), 85 deletions(-)

diff --git a/hysop/backend/device/opencl/opencl_env.py b/hysop/backend/device/opencl/opencl_env.py
index 0ae68d67c..49f11d0e0 100644
--- a/hysop/backend/device/opencl/opencl_env.py
+++ b/hysop/backend/device/opencl/opencl_env.py
@@ -8,6 +8,7 @@ from hysop.tools.types import check_instance, first_not_None
 from hysop.tools.io_utils import IO
 from hysop.tools.units import bytes2str
 from hysop.tools.warning import HysopWarning
+from hysop.tools.string_utils import framed_str
 
 from hysop.backend.device.opencl import cl, clTools, __OPENCL_PROFILE__, OPENCL_KERNEL_DUMP_FOLDER
 from hysop.backend.device.opencl.opencl_tools   import convert_device_type, convert_precision
@@ -69,25 +70,28 @@ class OpenClEnvironment(TaggedObject):
         platform_id = first_not_None(platform_id, __DEFAULT_PLATFORM_ID__)
         device_id   = first_not_None(device_id,   __DEFAULT_DEVICE_ID__)
         device_type = first_not_None(device_type, DeviceType.ALL)
-          
+
         msg=\
-'''== Creating OpenCL environment ==
- name:         {}
- platform_id:  {}
- device_id:    {}
- device_type:  {}
- gl_sharing:   {}
- communicator size: {} 
-'''.format(name, platform_id, device_id, 
+'''  name:         {}
+  platform_id:  {}
+  device_id:    {}
+  device_type:  {}
+  gl_sharing:   {}
+  comm  size:   {}'''.format(
+          name, platform_id, device_id, 
        device_type, gl_sharing, mpi_params.size)
-        vprint(msg)
-
+        
         device_type = convert_device_type(device_type)
-
-        # OpenCL platform
-        platform = get_platform(platform_id, strict=strict)
-        # OpenCL device
-        device = get_device(platform, device_id, device_type, strict=strict)
+        try:
+            # OpenCL platform
+            platform = get_platform(platform_id, strict=strict)
+            # OpenCL device
+            device = get_device(platform, device_id, device_type, strict=strict)
+        except:
+            title=' while creating the following OpenCL environment '
+            msg=framed_str(title=title, msg=msg)
+            print msg+'\n'
+            raise
         # OpenCL context
         context = get_context(device, gl_sharing)
         # OpenCL default queue
@@ -103,6 +107,7 @@ class OpenClEnvironment(TaggedObject):
             if pos>0:
                 name = name[:pos]
         name=name.strip()
+        
 
         self._platform  = platform
         self._device    = device
@@ -111,14 +116,16 @@ class OpenClEnvironment(TaggedObject):
         self._cl_version = self._parse_opencl_version()
 
         try:
-            device_type_str = cl.device_type.to_string(device.type),
+            device_type_str = cl.device_type.to_string(device.type)
         except ValueError:
             device_type_str = 'UNKNOWN DEVICE TYPE {}'.format(device.type)
 
         self.device_type_str = device_type_str
 
-        msg=\
-''' -- Platform --
+        msg+=\
+'''
+
+ -- Platform --
   *plat id: {}
   *name:    {}
   *version: {}
@@ -128,19 +135,21 @@ class OpenClEnvironment(TaggedObject):
   *name:    {}
   *type:    {}
   *version: {}
-  *global mem size: {}
-'''.format(
-platform_id, platform.name, platform.version,
-device_id, device.name, device_type_str,
+  *global mem size: {}'''.format(
+platform_id, platform.name.strip(), platform.version,
+device_id, device.name.strip(), device_type_str,
 device.opencl_c_version, bytes2str(device.global_mem_size))
         if context.properties:
+            msg+='\n'
             msg+='\n -- Context --'
             msg+='\n  *properties: {}'.format(context.properties)
-            msg+='\n'
         if queue.properties:
+            msg+='\n'
             msg+='\n -- Queue --'
             msg+='\n  *properties: {}'.format(queue.properties)
-            msg+='\n'
+        
+        title=' Creating OpenCL environment {} '.format(self.tag)
+        msg=framed_str(title=title, msg=msg)
         vprint(msg)
 
         # Floating point codegeneration mode
@@ -156,20 +165,6 @@ device.opencl_c_version, bytes2str(device.global_mem_size))
             self.default_build_opts.append('-cl-nv-verbose')
         self.macros = {}
 
-        # Kernels configuration dictionary
-        if self.device.name == 'Cayman':
-            from hysop.backend.device.opencl.device_config.config_cayman import \
-                    kernels_config as _kernel_cfg
-        elif self.device.name in ['Tesla K20m', 'Tesla K20Xm']:
-            from hysop.backend.device.opencl.device_config.config_k20m import \
-                    kernels_config as _kernel_cfg
-        else:
-            vprint('/!\\ Get a default kernels config for {}'.format(self.device.name.strip()))
-            from hysop.backend.device.opencl.device_config.config_default import \
-                    kernels_config as _kernel_cfg
-        self.kernels_config = _kernel_cfg
-
-        vprint('=================================\n')
         
         self._mpi_params = mpi_params
         self.is_master       = (mpi_params.rank==0)
diff --git a/hysop/core/graph/computational_graph.py b/hysop/core/graph/computational_graph.py
index 73b441fac..c47877b82 100644
--- a/hysop/core/graph/computational_graph.py
+++ b/hysop/core/graph/computational_graph.py
@@ -1,6 +1,8 @@
 from hysop import __DEBUG__, __VERBOSE__, vprint, dprint
 from hysop.tools.decorators  import debug
 from hysop.tools.types import to_list, to_set
+from hysop.tools.string_utils import framed_str
+from hysop.tools.numpywrappers import npw
 from hysop.core.graph.graph import not_implemented, initialized, discretized, \
                               ready, graph_built, not_initialized
 from hysop.core.graph.graph import Graph, ComputationalGraphNodeData, gt, graph_draw
@@ -67,7 +69,7 @@ class ComputationalGraph(ComputationalGraphNode):
         sinputs  = ''
         def sorted_reqs(reqs):
             return sorted(reqs, key=lambda x: \
-                    '{:15s}'.format('{}::{}'.format(x.operator.name, x.field.name)))
+                    '{}::{}'.format(x.operator.name, x.field.name))
         for field, mreqs in requirements.input_field_requirements.iteritems():
             for td, reqs in mreqs.requirements.iteritems():
                 for req in reqs:
@@ -106,60 +108,91 @@ class ComputationalGraph(ComputationalGraphNode):
         return ss
 
     def domain_report(self):
-        ss = '== ComputationalGraph {} domain and operator report =='.format(self.name)
         domains = self.get_domains()
-        for (domain,ops) in domains.iteritems():
+        ops={}
+        for (domain,operators) in domains.iteritems():
             if (domain is None):
                 continue
-            ss += '\n>{}'.format(domain.short_description())
-            name_size = max(len(op.name) for op in ops)
-            for op in sorted(ops, key=lambda x: x.name):
+            for op in sorted(operators, key=lambda x: x.name):
                 finputs = ','.join( sorted([f.name for f in op.input_fields    if f.domain is domain]))
                 foutputs =','.join( sorted([f.name for f in op.output_fields   if f.domain is domain]))
                 pinputs = ','.join( sorted([p for p in op.input_params]))
                 poutputs =','.join( sorted([p for p in op.output_params]))
-                infields  = '[{}]'.format(finputs) if finputs  else ''
+                infields  = '[{}]'.format(finputs)  if finputs  else ''
                 outfields = '[{}]'.format(foutputs) if foutputs else ''
-                inparams  = '[{}]'.format(pinputs) if pinputs  else ''
+                inparams  = '[{}]'.format(pinputs)  if pinputs  else ''
                 outparams = '[{}]'.format(poutputs) if poutputs else ''
-                ss += '\n  *{:<{name_size}}  {:<{in_size}} -> {}'.format(
-                        op.name, 
-                        'in={}{}{}'.format(infields,   'x' if infields  and inparams  else '', inparams),
-                        'out={}{}{}'.format(outfields, 'x' if outfields and outparams else '', outparams),
-                        name_size=name_size,
-                        in_size=(max(len(x) for x in finputs) + 7) if finputs else 7)
-        if None in domains:
-            ops = domains[None]
-            ss += '\n>Domainless operators:'
-            name_size = max(len(op.name) for op in ops)
-            for op in sorted(ops, key=lambda x: x.name):
+                        
+                inputs  = '{}{}{}'.format(infields,  'x' if infields  and inparams  else '', inparams)
+                outputs = '{}{}{}'.format(outfields, 'x' if outfields and outparams else '', outparams)
+                
+                if inputs == '':
+                    outputs='no inputs'
+                if outputs == '':
+                    outputs='no outputs'
+                ops.setdefault(domain, []).append( (op.name, inputs, outputs, type(op).__name__) )
+
+        if (None in domains):
+            opeators = domains[None]
+            for op in sorted(operators, key=lambda x: x.name):
                 pinputs = ','.join( sorted([p for p in op.input_params]))
                 poutputs =','.join( sorted([p for p in op.output_params]))
                 inparams  = '[{}]'.format(pinputs) if pinputs  else ''
                 outparams = '[{}]'.format(poutputs) if poutputs else ''
-                ss += '\n  *{:<{name_size}}  {:<{in_size}} -> {}'.format(
-                        op.name, 
-                        'in={}'.format(inparams),
-                        'out={}'.format(outparams),
-                        name_size=name_size,
-                        in_size=max(len(x) for x in pinputs) + 7)
-        l=len('== ComputationalGraph {} domain and operator report =='.format(self.name))
-        ss += '\n{}\n'.format(l*'=')
-        return ss
+
+                inputs='{}'.format(inparams)
+                outputs='{}'.format(outparams)
+                if inputs == '':
+                    outputs='no inputs'
+                if outputs == '':
+                    outputs='no outputs'
+                ops.setdefault(None, []).append( (op.name, inputs, outputs, type(op).__name__) )
+        
+        name_size = max(len(s[0]) for ss in ops.values() for s in ss)
+        in_size   = max(len(s[1]) for ss in ops.values() for s in ss)
+        out_size  = max(len(s[2]) for ss in ops.values() for s in ss)
+        type_size = max(len(s[3]) for ss in ops.values() for s in ss)
+
+        ss = ''
+        for (domain,dops) in ops.iteritems():
+            if (domain is None):
+                continue
+            ss += '\n>{}'.format(domain.short_description())
+            ss += '\n   {:<{name_size}}  {:<{in_size}}       {:<{out_size}}    {:<{type_size}}'.format(
+                        'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE',
+                        name_size=name_size, in_size=in_size, 
+                        out_size=out_size, type_size=type_size)
+            for (opname, inputs, outputs, optype) in dops:
+                ss += '\n   {:<{name_size}}  {:<{in_size}}  ->   {:<{out_size}}    {:<{type_size}}'.format(
+                        opname, inputs, outputs, optype,
+                        name_size=name_size, in_size=in_size,
+                        out_size=out_size, type_size=type_size)
+        if (None in domains):
+            ss += '\n>Domainless operators:'
+            ss += '\n   {:<{name_size}}  {:<{in_size}}       {:<{out_size}}    {:<{type_size}}'.format(
+                        'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE',
+                        name_size=name_size, in_size=in_size, 
+                        out_size=out_size, type_size=type_size)
+            for (opname, inputs, outputs, optype) in ops[None]:
+                ss += '\n   {:<{name_size}}  {:<{in_size}}  ->   {:<{out_size}}    {:<{type_size}}'.format(
+                        opname, inputs, outputs, optype,
+                        name_size=name_size, in_size=in_size,
+                        out_size=out_size, type_size=type_size)
+        
+        title=' ComputationalGraph {} domain and operator report '.format(self.name)
+        return '\n{}\n'.format(framed_str(title=title, msg=ss[1:]))
             
 
     def topology_report(self):
-        ss = '== ComputationalGraph {} topology and operator report =='.format(self.name)
+        ss=''
         for (backend,topologies) in self.get_topologies().iteritems():
             ss += '\n {}:'.format(backend.short_description())
             ss += '\n  *'+'\n  *'.join(t.short_description()
                 for t in sorted(topologies, key=lambda x: x.id))
-        l=len('== ComputationalGraph {} topology and operator report =='.format(self.name))
-        ss += '\n{}\n'.format(l*'=')
-        return ss
+        title = ' ComputationalGraph {} topology report '.format(self.name)
+        return '\n{}\n'.format(framed_str(title=title, msg=ss[1:]))
     
     def operator_report(self):
-        ss = '== ComputationalGraph {} final operator report =='.format(self.name)
         reduced_graph = self.reduced_graph
         operators = reduced_graph.vertex_properties['operators']
         ops = []
@@ -186,14 +219,13 @@ class ComputationalGraph(ComputationalGraphNode):
 
             ops.append( (op.name, inputs, outputs, type(op).__name__) )
         
-        import math
-        isize = int(math.floor(math.log(len(self.sorted_nodes)-1)))
+        isize     = int(npw.ceil(npw.log10(len(self.sorted_nodes)-1)))
         name_size = max(len(s[0]) for s in ops)
         in_size   = max(len(s[1]) for s in ops)
         out_size  = max(len(s[2]) for s in ops)
         type_size = max(len(s[3]) for s in ops)
 
-        ss += '\n  {:<{isize}}  {:<{name_size}}  {:<{in_size}}       {:<{out_size}}    {:<{type_size}}'.format(
+        ss = '  {:<{isize}}  {:<{name_size}}  {:<{in_size}}       {:<{out_size}}    {:<{type_size}}'.format(
                     'ID', 'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE',
                     isize=isize,
                     name_size=name_size, in_size=in_size, 
@@ -204,8 +236,9 @@ class ComputationalGraph(ComputationalGraphNode):
                     isize=isize,
                     name_size=name_size, in_size=in_size,
                     out_size=out_size, type_size=type_size)
-        return ss
         
+        title = ' ComputationalGraph {} final operator report '.format(self.name)
+        return '\n{}\n'.format(framed_str(title=title, msg=ss))
 
     def get_domains(self):
         domains = {}
@@ -491,10 +524,9 @@ class ComputationalGraph(ComputationalGraphNode):
                 requests += op.get_work_properties()
         if __DEBUG__ or (__VERBOSE__ and self.level==0) or self.__FORCE_REPORTS__:
             srequests = str(requests)
-            print '\n== ComputationalGraph {} work properties report =='.format(self.name)
-            print srequests if (srequests != '') else ' *no extra work requested*'
-            print '='*len('== ComputationalGraph {} work properties report =='.format(self.name))
-            print
+            ss = (srequests if (srequests != '') else ' *no extra work requested*')
+            title= ' ComputationalGraph {} work properties report '.format(self.name)
+            vprint('\n{}\n'.format(framed_str(title=title, msg=ss)))
         return requests 
     
     @debug
diff --git a/hysop/domain/box.py b/hysop/domain/box.py
index d8932e3fb..83a1359b9 100644
--- a/hysop/domain/box.py
+++ b/hysop/domain/box.py
@@ -76,7 +76,8 @@ class BoxView(DomainView):
         """
         Return a short description of this Box as a string.
         """
-        return 'Box(O=[{}], L=[{}], BC=[{}], current_task={})'.format(
+        return 'Box(tag={}, O=[{}], L=[{}], BC=[{}], current_task={})'.format(
+                self.tag,
                 ','.join(('{:1.1f}'.format(val) for val in self.origin)),
                 ','.join(('{:1.1f}'.format(val) for val in self.length)),
                 ','.join(('{}/{}'.format(str(lb)[:3],str(rb)[:3]) for (lb,rb) in \
diff --git a/hysop/fields/cartesian_discrete_field.py b/hysop/fields/cartesian_discrete_field.py
index db021f048..a7e9c2df7 100644
--- a/hysop/fields/cartesian_discrete_field.py
+++ b/hysop/fields/cartesian_discrete_field.py
@@ -830,9 +830,9 @@ class CartesianDiscreteField(CartesianDiscreteFieldView, DiscreteField):
         self._has_ghosts = (topology.mesh.ghosts>0).any()
         
         if allocate_data: 
-            msg='\nAllocation of field {} ({}) on backend {}'
+            msg='\nAllocation of discrete field {} ({}) on {} and {}'
             msg+='\n  (compute_res={}, ghosts={}, nb_comp={}, dtype={})'
-            msg=msg.format(self.name, self.tag, self.backend.kind, 
+            msg=msg.format(self.name, self.full_tag, self.backend.full_tag, self.topology.topology.full_tag,
                     self.compute_resolution, self.ghosts, self.nb_components, self.dtype)
             vprint(msg)
             data = tuple(self.backend.empty(shape=self.shape, dtype=self.dtype)
diff --git a/hysop/tools/string_utils.py b/hysop/tools/string_utils.py
index a6100c186..6aa9c2d97 100644
--- a/hysop/tools/string_utils.py
+++ b/hysop/tools/string_utils.py
@@ -27,3 +27,14 @@ def vprint_banner(msg, c='*'):
     vprint(c*len(msg))
     vprint(msg)
     vprint(c*len(msg))
+
+def framed_str(title, msg, c='=', at_border=2):
+    """
+    Format a message to fit between two separation lines
+    containing a title.
+    """
+    length = max(len(m) for m in msg.split('\n'))
+    title  = c*at_border + title + c*at_border
+    header = title + c*max(0, length-len(title))
+    footer = c*len(header)
+    return '{}\n{}\n{}'.format(header, msg, footer)
diff --git a/hysop/topology/cartesian_topology.py b/hysop/topology/cartesian_topology.py
index a10804390..e276861dc 100644
--- a/hysop/topology/cartesian_topology.py
+++ b/hysop/topology/cartesian_topology.py
@@ -360,10 +360,10 @@ class CartesianTopologyView(TopologyView):
         Returns a short description of the current TopologyView.
         Short version of long_description().
         """
-        s='CartesianTopology[tag={}, task_id={}, pcoords={}, pshape={}, '
+        s='CartesianTopology[tag={}, domain={}, task_id={}, pcoords={}, pshape={}, '
         s+='distr.={}, periods={}, shape={}, ghosts={}]'
         s = s.format(
-                self.tag, self.task_id, 
+                self.tag, self.domain.domain.full_tag, self.task_id, 
                 self.proc_coords, self.proc_shape, 
                 '[{}]'.format(','.join('T' if per else 'F' for per in self.is_distributed)),
                 '[{}]'.format(','.join('T' if per else 'F' for per in self.is_periodic)),
-- 
GitLab