From 0d8c129c7bad0c6c1e5202833bb4a370d480d30c Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Mon, 19 Mar 2018 18:09:37 +0100
Subject: [PATCH] debug utility

---
 ci/docker_images/ubuntu/bionic/Dockerfile     |  11 +-
 examples/shear_layer/shear_layer.py           |  11 +-
 hysop/backend/device/kernel_autotuner.py      |   3 +-
 .../device/opencl/operator/custom_symbolic.py |  26 +---
 .../operator/directional/advection_dir.py     |   2 +-
 hysop/core/graph/graph.py                     |  14 +-
 .../operator/base/custom_symbolic_operator.py |   6 -
 hysop/operator/base/redistribute_operator.py  |   7 -
 hysop/tools/debug_utils.py                    | 132 ++++++++++++++++++
 hysop/tools/io_utils.py                       |   3 +-
 10 files changed, 172 insertions(+), 43 deletions(-)
 create mode 100644 hysop/tools/debug_utils.py

diff --git a/ci/docker_images/ubuntu/bionic/Dockerfile b/ci/docker_images/ubuntu/bionic/Dockerfile
index 424381739..c5c854a08 100644
--- a/ci/docker_images/ubuntu/bionic/Dockerfile
+++ b/ci/docker_images/ubuntu/bionic/Dockerfile
@@ -101,17 +101,20 @@ RUN cd /tmp                                     \
 
 # python graphtools
 RUN cd /tmp                                       \
- && git clone https://github.com/antmd/graph-tool \
- && cd graph-tool                                 \
+ && wget https://downloads.skewed.de/graph-tool/graph-tool-2.26.tar.bz2 \
+ && tar -xvjf graph-tool-2.26.tar.bz2             \
+ && cd graph-tool-2.26                            \
  && ./autogen.sh                                  \
  && mkdir pycairo                                 \
  && find /usr/ -name 'pycairo.h' -exec cp {} ./pycairo/pycairo.h \; \
- && for f in $(grep -Rl '[^:]placeholders::'); do echo '%s/[^:]\zs\zeplaceholders::/std::/g | w' | vim -e $f; done; \
  && CPPFLAGS=-I. ./configure                      \
  && CPPFLAGS=-I. make                             \
  && make install                                  \
  && cd -                                          \
- && rm -Rf /tmp/graphtool
+ && rm -Rf /tmp/graph-tool-2.26
+ 
+ #&& for f in $(grep -Rl '[^:]placeholders::'); do echo '%s/[^:]\zs\zeplaceholders::/std::/g | w' | vim -e $f; done; \
+    #CPPFLAGS=-I. ./configure                      \
 
 RUN echo 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main'     >> /etc/apt/sources.list \
  && echo 'deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic main' >> /etc/apt/sources.list \
diff --git a/examples/shear_layer/shear_layer.py b/examples/shear_layer/shear_layer.py
index 1d76375bb..12e0a33fc 100644
--- a/examples/shear_layer/shear_layer.py
+++ b/examples/shear_layer/shear_layer.py
@@ -3,6 +3,8 @@
 ## See Brown 1995: 
 ## Performance of under-resolved two dimensional incompressible flow simulations
 
+import matplotlib
+matplotlib.use('gtk3cairo')
 import sympy as sm
 import numpy as np
 
@@ -159,8 +161,15 @@ def compute(args):
     dfields = problem.input_discrete_fields
     dfields[vorti].initialize(formula=init_vorticity)
 
+    from hysop.tools.debug_utils import ImshowDebugger
+    dbg = ImshowDebugger(data={'Wz':(dfields[vorti],0)}, 
+            ntimes=1,
+            enable_on_op_apply=True)
+    dbg.synchronize_queue(cl_env.default_queue)
+    dbg('initialization')
+
     # Finally solve the problem 
-    problem.solve(simu, dry_run=args.dry_run)
+    problem.solve(simu, dry_run=args.dry_run, dbg=dbg)
     
     # Finalize
     problem.finalize()
diff --git a/hysop/backend/device/kernel_autotuner.py b/hysop/backend/device/kernel_autotuner.py
index 303a40ea2..9ac783478 100644
--- a/hysop/backend/device/kernel_autotuner.py
+++ b/hysop/backend/device/kernel_autotuner.py
@@ -22,7 +22,7 @@ class KernelAutotuner(object):
 
     FULL_RESULTS_KEY = '__FULL_RESULTS__'
     DUMP_LAST_TUNED_KERNEL    = False
-    STORE_FULL_KERNEL_SOURCES = True
+    STORE_FULL_KERNEL_SOURCES = False
     
     @staticmethod 
     def _hash_func():
@@ -144,7 +144,6 @@ class KernelAutotuner(object):
 
         assert prg is None
         assert kernel is None
-        assert (cached_kernel_src is None)^(self.STORE_FULL_KERNEL_SOURCES), cached_kernel_src
         global_work_size = npw.asintegerarray(global_work_size)
         local_work_size  = npw.asintegerarray(local_work_size)
         
diff --git a/hysop/backend/device/opencl/operator/custom_symbolic.py b/hysop/backend/device/opencl/operator/custom_symbolic.py
index 7eac15b15..57585e053 100644
--- a/hysop/backend/device/opencl/operator/custom_symbolic.py
+++ b/hysop/backend/device/opencl/operator/custom_symbolic.py
@@ -14,28 +14,16 @@ class OpenClCustomSymbolicOperator(CustomSymbolicOperatorBase, OpenClOperator):
     def __init__(self, **kwds):
         super(OpenClCustomSymbolicOperator, self).__init__(**kwds)
     
-    @debug
-    def get_work_properties(self):
-        requests  = super(OpenClCustomSymbolicOperator, self).get_work_properties()
-
-        for sout in self.output_discrete_fields.values():
-            full_outer_ghosts = sout.get_outer_ghost_slices()
-            for direction in xrange(sout.dim):
-                if sout.ghosts[direction]==0:
-                    continue
-                dirlabel = DirectionLabels[sout.dim-direction-1]
-                full_ghost_layer_shape = full_outer_ghosts[direction][-1]
-                request = MemoryRequest.empty_like(a=sout, shape=full_ghost_layer_shape, nb_components=2)
-                requests.push_mem_request(sout.name+'_ghost_layers_'+dirlabel, request)
-        return requests
-
     @debug
     def setup(self, work):
         super(OpenClCustomSymbolicOperator, self).setup(work)
-        self._collect_kernels(work)
+        self._collect_kernels()
 
-    def _collect_kernels(self, work):
-        kl = self._collect_symbolic_kernel()
+    def _collect_kernels(self):
+        kl  = OpenClKernelListLauncher(name='advec_remesh')
+        kl  += self._collect_symbolic_kernel()
+        for sout in self.output_discrete_fields.values():
+            kl += sout.exchange_ghosts(build_launcher=True)
         self.kl = kl
     
     def _collect_symbolic_kernel(self):
@@ -60,8 +48,6 @@ class OpenClCustomSymbolicOperator(CustomSymbolicOperatorBase, OpenClOperator):
     def apply(self, **kwds):
         queue = self.cl_env.default_queue
         evt = self.kl(queue=queue, **self._update_input_params())
-        for sout in self.output_discrete_fields.values():
-            evt = sout.exchange_ghosts(queue=queue, evt=evt)
     
     @classmethod
     def supports_mpi(cls):
diff --git a/hysop/backend/device/opencl/operator/directional/advection_dir.py b/hysop/backend/device/opencl/operator/directional/advection_dir.py
index 2235cccb7..1e10d22cb 100644
--- a/hysop/backend/device/opencl/operator/directional/advection_dir.py
+++ b/hysop/backend/device/opencl/operator/directional/advection_dir.py
@@ -159,7 +159,7 @@ class OpenClDirectionalAdvection(DirectionalAdvectionBase, OpenClDirectionalOper
         return kl
     
     @op_apply
-    def apply(self, **kargs):
+    def apply(self, dbg=None, **kargs):
         queue = self.cl_env.default_queue
         dt  = self.precision(self.dt() * self.dt_coeff)
         
diff --git a/hysop/core/graph/graph.py b/hysop/core/graph/graph.py
index 3c138f8d0..b0c680a60 100644
--- a/hysop/core/graph/graph.py
+++ b/hysop/core/graph/graph.py
@@ -126,5 +126,17 @@ def op_apply(f):
     @profile
     @ready
     def apply(*args, **kwds):
-        return f(*args, **kwds)
+        dbg  = ('dbg' in kwds) 
+        dbg &= (kwds['dbg'] is not None)
+        dbg &= (kwds['dbg'].enable_on_op_apply)
+        if dbg:
+            import inspect
+            msg=inspect.getsourcefile(f)
+            kwds['dbg']('pre '+msg, nostack=True)
+            ret = f(*args, **kwds)
+            kwds['dbg']('post '+msg, nostack=True)
+            return ret
+        else:
+            return f(*args, **kwds)
+        return ret
     return apply
diff --git a/hysop/operator/base/custom_symbolic_operator.py b/hysop/operator/base/custom_symbolic_operator.py
index 981af0e8b..7cbbb2988 100644
--- a/hysop/operator/base/custom_symbolic_operator.py
+++ b/hysop/operator/base/custom_symbolic_operator.py
@@ -1229,12 +1229,6 @@ class CustomSymbolicOperatorBase(DirectionalOperatorBase):
                 input_dfields=self.input_discrete_fields,
                 output_dfields=self.output_discrete_fields)
     
-    @debug
-    def get_work_properties(self):
-        """Extract work properties from discrete symbolic expressions."""
-        requests = super(CustomSymbolicOperatorBase, self).get_work_properties()
-        return requests
-    
     @debug
     def setup(self, work):
         """Setup required work."""
diff --git a/hysop/operator/base/redistribute_operator.py b/hysop/operator/base/redistribute_operator.py
index 857118a64..fdeb3908a 100644
--- a/hysop/operator/base/redistribute_operator.py
+++ b/hysop/operator/base/redistribute_operator.py
@@ -93,13 +93,6 @@ class RedistributeOperatorBase(ComputationalGraphOperator):
         self._vsource = {v: self.input_discrete_fields[v]}
         self._vtarget = {v: self.output_discrete_fields[v]}
 
-    @debug
-    def get_work_properties(self):
-        return None
-    @debug
-    def setup(self, work=None):
-        super(RedistributeOperatorBase,self).setup(work=work)
-    
     @classmethod
     def supports_multiple_field_topologies(cls):
         return True
diff --git a/hysop/tools/debug_utils.py b/hysop/tools/debug_utils.py
new file mode 100644
index 000000000..6d32d8f06
--- /dev/null
+++ b/hysop/tools/debug_utils.py
@@ -0,0 +1,132 @@
+import traceback, inspect
+import numpy as np
+import matplotlib
+matplotlib.use('gtk3cairo')
+
+import matplotlib.pyplot as plt
+plt.ion()
+
+from hysop.fields.discrete_field import DiscreteFieldView
+from hysop.tools.types import check_instance, first_not_None
+
+class ImshowDebugger(object):
+    def __init__(self, data, ntimes=2, cmap='coolwarm', 
+                        enable_on_op_apply=False, **kwds):
+        check_instance(data, dict, keys=str)
+        ndata = len(data)
+        assert ndata >= 1, ndata
+        assert ntimes >= 1, ntimes
+        
+        fig,_axes = plt.subplots(ndata, ntimes, **kwds)
+        _axes = np.asarray(_axes).reshape(ndata, ntimes)
+
+        axes = {}
+        for (i,k) in enumerate(data.keys()):
+            axes[k] = _axes[i]
+        
+        imgs = {}
+        for (k,v) in data.iteritems():
+            v = self.get_data(v)
+            for j in xrange(ntimes): 
+                axes[k][j].set_title('{} at t=Tn-{}'.format(k,j))
+                axes[k][j].set_xlim(0, v.shape[1]-1)
+                axes[k][j].set_ylim(0, v.shape[0]-1)
+                img = axes[k][j].imshow(self.normalize(v), cmap=cmap, 
+                        vmin=0.0, vmax=1.0, interpolation='bilinear')
+                imgs.setdefault(k,[]).append(img)
+
+        fig.canvas.mpl_connect('key_press_event', self.on_key_press)
+        fig.suptitle('HySoP Visual Debugger v1.0')
+
+        self.fig    = fig
+        self.axes   = axes
+        self.data   = data
+        self.imgs   = imgs
+        self.ntimes = ntimes
+        self.enable_on_op_apply = enable_on_op_apply
+
+        self.cl_queues = []
+        self.running = True
+        
+        mng = plt.get_current_fig_manager()
+        mng.window.maximize()
+
+        plt.draw()
+        fig.show()
+
+    def get_data(self, data):
+        if isinstance(data, tuple):
+            (field, component) = data
+            check_instance(field, DiscreteFieldView)
+            check_instance(component, int)
+            data = field.data[component].get()[field.compute_slices].handle
+        check_instance(data, np.ndarray)
+        return data
+
+    def normalize(self, data):
+        check_instance(data, np.ndarray)
+        assert data.ndim == 2
+        dmin, dmax = np.min(data), np.max(data)
+        data = data.astype(np.float32)
+        if (dmax-dmin)<1e-4:
+            data = np.clip(data, 0.0, 1.0)
+        else:
+            data = (data - dmin)/(dmax - dmin)
+        return data
+
+    def update(self):
+        for queue in self.cl_queues:
+            queue.flush()
+            queue.finish()
+        imgs = self.imgs
+        ntimes = self.ntimes
+        for (k,data) in self.data.iteritems():
+            data = self.get_data(data)
+            for j in xrange(ntimes-1,0,-1): 
+                imgs[k][j].set_data(imgs[k][j-1].get_array())
+            imgs[k][0].set_array(self.normalize(data))
+        plt.draw()
+    
+    def _break(self, msg=None, nostack=False):
+        if not self.running:
+            return
+        msg=first_not_None(msg, '')
+        if not nostack:
+            _file,_line = inspect.stack()[2][1:3]
+            msg='{}::{} {}'.format(_file, _line, msg)
+        print msg
+        self.fig.suptitle('Hysop Visual Debugger v1.0\n{}'.format(msg))
+        self.update()
+        self.blocking = True
+        while self.blocking:
+            plt.pause(0.01)
+
+    def __call__(self, msg=None, nostack=False):
+        self._break(msg=msg, nostack=nostack)
+
+    def on_key_press(self, event):
+        key = event.key
+        if (key=='n'):
+            self.blocking = False
+        elif key == 'q':
+            plt.close(self.fig)
+            self.blocking = False
+            self.running  = False
+
+    def synchronize_queue(self, queue):
+        self.cl_queues.append(queue)
+
+if __name__ == '__main__':
+    import random
+    shape0 = (100,100)
+    shape1 = (200,100)
+    img0 = np.zeros(shape=shape0, dtype=np.float32)
+    img1 = np.ones(shape=shape1, dtype=np.float32)
+    dbg  = ImshowDebugger({'F0':img0, 'F1':img1}, ntimes=3)
+    dbg('init')
+    i=0
+    while dbg.running:
+        img0[...] = np.random.rand(*shape0).astype(np.float32)
+        img1[...] = np.random.rand(*shape1).astype(np.float32)
+        dbg('msg{}'.format(str(i)))
+        i+=1
diff --git a/hysop/tools/io_utils.py b/hysop/tools/io_utils.py
index 549e22b1e..ce02db2fe 100755
--- a/hysop/tools/io_utils.py
+++ b/hysop/tools/io_utils.py
@@ -10,7 +10,6 @@
 """
 import os, h5py, psutil, warnings, tempfile
 import subprocess32 as subprocess
-import scitools.filetable as ft
 from collections import namedtuple
 from inspect import getouterframes, currentframe
 from re import findall
@@ -335,12 +334,14 @@ class Writer(object):
 
     def _fullwrite(self):
         """open, write and close"""
+        import scitools.filetable as ft
         self._file = open(self.io_params.filename, 'a')
         ft.write(self._file, self.buffer)
         self._file.close()
 
     def _partialwrite(self):
         """just write, no open, nor close"""
+        import scitools.filetable as ft
         ft.write(self._file, self.buffer)
 
     def finalize(self):
-- 
GitLab