From e240a3e2d724f694752d23da0fee3d9208ae00bc Mon Sep 17 00:00:00 2001
From: caron <j.caron@fz-juelich.de>
Date: Tue, 12 Dec 2017 17:00:02 +0100
Subject: [PATCH] forwardmodel: DistributedForwardModel now correctly
 distributes images! also: now works with logging instead of printing!
 projector: Fixed bug with shape of impacts (was erroneously transposed)!
 regularisator: Regularisator base class now in __all__!

---
 pyramid/dataset.py       | 11 ++++----
 pyramid/forwardmodel.py  | 54 ++++++++++++++++++++++------------------
 pyramid/kernel.py        |  2 +-
 pyramid/projector.py     | 22 ++++++++--------
 pyramid/regularisator.py |  4 +--
 5 files changed, 50 insertions(+), 43 deletions(-)

diff --git a/pyramid/dataset.py b/pyramid/dataset.py
index cae8f64..3e6a7a3 100644
--- a/pyramid/dataset.py
+++ b/pyramid/dataset.py
@@ -142,7 +142,7 @@ class DataSet(object):
         self.dim = dim
         self.b_0 = b_0  # TODO: Not very general!!! get rid off!
         self.mask = mask
-        self.Se_inv = Se_inv
+        self.Se_inv = Se_inv  # TODO: Should be constructed in the costfunction, not here!!
         self._phasemaps = []
         self._projectors = []
         self._phasemappers = []
@@ -219,7 +219,7 @@ class DataSet(object):
         for i in range(len(phasemap)):
             self._append_single(phasemap[i], projector[i], phasemapper[i])
         # Reset the Se_inv matrix from phasemaps confidence matrices:
-        self.set_Se_inv_diag_with_conf()
+        self.set_Se_inv_diag_with_conf()  # TODO: Very clunky and redundant when adding a lot!
 
     def create_phasemaps(self, magdata, difference=False, ramp=None):
         """Create a list of phasemaps with the projectors in the dataset for a given
@@ -560,12 +560,13 @@ class DataSetCharge(object):
 
     def __repr__(self):
         self._log.debug('Calling __repr__')
-        return '%s(a=%r, dim=%r, electrode_vec=%r, mask=%r, Se_inv=%r)' % (self.__class__, self.a, self.dim,
-                                                                 self.electrode_vec, self.mask, self.Se_inv)
+        return '%s(a=%r, dim=%r, electrode_vec=%r, mask=%r, Se_inv=%r)' % \
+               (self.__class__, self.a, self.dim, self.electrode_vec, self.mask, self.Se_inv)
 
     def __str__(self):
         self._log.debug('Calling __str__')
-        return 'DataSetCharge(a=%s, dim=%s, electrode_vec=%s)' % (self.a, self.dim, self.electrode_vec)
+        return 'DataSetCharge(a=%s, dim=%s, electrode_vec=%s)' % \
+               (self.a, self.dim, self.electrode_vec)
 
     def _append_single(self, phasemap, projector, phasemapper=None):
         self._log.debug('Calling _append')
diff --git a/pyramid/forwardmodel.py b/pyramid/forwardmodel.py
index 90f08b3..806465e 100644
--- a/pyramid/forwardmodel.py
+++ b/pyramid/forwardmodel.py
@@ -365,28 +365,41 @@ class DistributedForwardModel(ForwardModel):
         All ramp parameters have to be at the end of the input vector and are split automatically.
         Default is None (no ramps are added).
     nprocs: int
-        Number of processes which should be created. Default is 1 (not recommended).
+        Number of processes which should be created. Default is 1 (not recommended). # TODO: <<<!!!
 
     """
 
-    def __init__(self, data_set, ramp_order=None, nprocs=1):
+    _log = mp.log_to_stderr()
+
+    def __init__(self, data_set, ramp_order=None, nprocs='auto'):
         # Evoke super constructor to set up the normal ForwardModel:
         super().__init__(data_set, ramp_order)
         # Initialize multirocessing specific stuff:
+        if nprocs == 'auto':
+            nprocs = mp.cpu_count() - 2  # Use two cores less to reserve cpu for the system.
         self.nprocs = nprocs
-        img_per_proc = np.ceil(self.data_set.count / self.nprocs).astype(np.int)
-        hp = self.data_set.hook_points
-        self.proc_hook_points = [0]
         self.pipes = []
         self.processes = []
-        print('NPROCS:', self.nprocs)    # TODO: Logging instead of printing!
+        self.proc_hook_points = [0]  # Hook points of the processes in the output vector
+        hp = self.data_set.hook_points  # Hook points of the images in the output vector
+        # Calculate the best distribution of images to the processes:
+        proc_img_range = []
+        n = self.data_set.count // nprocs  # min items per process
+        r = self.data_set.count % nprocs  # remainder items
+        start, stop = 0, n + (r != 0)
         for proc_id in range(self.nprocs):
+            proc_img_range.append((start, stop))
+            if r > 0:  # use up remainder:
+                r -= 1
+            start = stop
+            stop = stop + n + (r != 0)
+        # Set up the workers:
+        self._log.info('Creating {} processes'.format(self.nprocs))
+        for proc_id, (start, stop) in enumerate(proc_img_range):
             # Create SubDataSets:
             sub_data = DataSet(self.data_set.a, self.data_set.dim, self.data_set.b_0,
-                               self.data_set.mask, self.data_set.Se_inv)
+                               self.data_set.mask, Se_inv=None)  # Se_inv is set later!
             # Distribute data to SubDataSets:
-            start = proc_id * img_per_proc
-            stop = np.min(((proc_id + 1) * img_per_proc, self.data_set.count))
             self.proc_hook_points.append(hp[stop])
             phasemaps = self.data_set.phasemaps[start:stop]
             projectors = self.data_set.projectors[start:stop]
@@ -396,7 +409,7 @@ class DistributedForwardModel(ForwardModel):
             # Create communication pipe:
             self.pipes.append(mp.Pipe())
             # Create process:
-            p = mp.Process(name='Worker {}'.format(proc_id), target=_worker,
+            p = mp.Process(name='Worker {}'.format(proc_id), target=self._worker,
                            args=(sub_fwd_model, self.pipes[proc_id][1]))
             self.processes.append(p)
             # Start process:
@@ -423,6 +436,13 @@ class DistributedForwardModel(ForwardModel):
         # Return result:
         return result
 
+    def _worker(self, fwd_model, pipe):
+        for method, arguments in iter(pipe.recv, 'STOP'):
+            sys.stdout.flush()
+            result = getattr(fwd_model, method)(*arguments)
+            pipe.send(result)
+        sys.stdout.flush()
+
     def jac_dot(self, x, vector):
         """Calculate the product of the Jacobi matrix with a given `vector`.
 
@@ -511,17 +531,3 @@ class DistributedForwardModel(ForwardModel):
         # Exit the completed processes:
         for p in self.processes:
             p.join()
-
-
-def _worker(fwd_model, pipe):
-    # Has to be directly accessible in the module as a function, NOT a method of a class instance!
-    # TODO: Logging instead of printing!
-    print('... {} starting!'.format(mp.current_process().name))
-    sys.stdout.flush()
-    for method, arguments in iter(pipe.recv, 'STOP'):
-        # '... {} processes method {}'.format(mp.current_process().name, method)
-        sys.stdout.flush()
-        result = getattr(fwd_model, method)(*arguments)
-        pipe.send(result)
-    print('... ', mp.current_process().name, 'exiting!')
-    sys.stdout.flush()
diff --git a/pyramid/kernel.py b/pyramid/kernel.py
index 1e1d149..c78aa4d 100644
--- a/pyramid/kernel.py
+++ b/pyramid/kernel.py
@@ -240,7 +240,7 @@ class KernelCharge(object):
         self.slice_c = (slice(0, dim_uv[0]),  # Charge is padded on the far end!
                         slice(0, dim_uv[1]))  # (Phase cutout is shifted as listed above)
         # Calculate kernel (single pixel phase):
-        coeff = a * C_e * Q_E / (4 * np.pi * EPS_0)  # Minus is gone because of negative z-direction
+        coeff = a * C_e * Q_E / (4 * np.pi * EPS_0)  # Minus gone because of negative z-direction
         v_dim, u_dim = dim_uv
         u = np.linspace(-(u_dim - 1), u_dim - 1, num=2 * u_dim - 1)
         v = np.linspace(-(v_dim - 1), v_dim - 1, num=2 * v_dim - 1)
diff --git a/pyramid/projector.py b/pyramid/projector.py
index 99ff190..9830be2 100644
--- a/pyramid/projector.py
+++ b/pyramid/projector.py
@@ -427,17 +427,17 @@ class RotTiltProjector(Projector):
         dim_v, dim_u = dim_uv
         # Creating coordinate list of all voxels:
         voxels = list(itertools.product(range(dim_z), range(dim_y), range(dim_x)))
-        # Calculate vectors to voxels relative to rotation center (each row contains (z, y, x)):
-        voxel_vecs = np.asarray(voxels) + 0.5 - np.asarray(center)
-        # Change to coordinate order of quaternions (x, y, z) instead of (z, y, x):
-        voxel_vecs = np.fliplr(voxel_vecs)
+        # Calculate vectors to voxels relative to rotation center (each column contains (z, y, x)):
+        voxel_vecs = (np.asarray(voxels) + 0.5 - np.asarray(center)).T  # .T: row to column!
+        # Change to coordinate order of quaternions (x, y, z) instead of (z, y, x) per column:
+        voxel_vecs = np.flipud(voxel_vecs)
         # Calculate impact positions (x, y) on the projected pixel coordinate grid (z is dropped):
-        impacts = quat.matrix[:2, :].dot(voxel_vecs.T).T  # Only x and y row of matrix is used!
-        # Reverse transpose and change back coordinate order from (x, y) to (y, x)/(v, u):
-        impacts = np.fliplr(impacts.T)  # Now contains rows with (v, u) entries as desired!
-        # First index: voxel, second index: 0 -> v, 1 -> u!
-        impacts[:, 1] += dim_u / 2.  # Shift back to normal indices
-        impacts[:, 0] += dim_v / 2.  # Shift back to normal indices
+        impacts = quat.matrix[:2, :].dot(voxel_vecs)  # Only x and y row of matrix is used!
+        # Change back coordinate order from (x, y) to (y, x)/(v, u) per column:
+        impacts = np.flipud(impacts)  # Now contains columns with (v, u) entries (pythonic index)!
+        # First index (column): 0 -> v, 1 -> u, second index (row): voxel!
+        impacts[0, :] += dim_v / 2.  # Shift back to normal indices
+        impacts[1, :] += dim_u / 2.  # Shift back to normal indices
         # Calculate equivalence radius:
         R = (3 / (4 * np.pi)) ** (1 / 3.)
         # Prepare weight matrix calculation:
@@ -451,7 +451,7 @@ class RotTiltProjector(Projector):
         for i, voxel in enumerate(tqdm(voxels, disable=disable, leave=False,
                                        desc='Set up projector')):
             column_index = voxel[0] * dim_y * dim_x + voxel[1] * dim_x + voxel[2]
-            remainder, impact = np.modf(impacts[i, :])  # split index of impact and remainder!
+            remainder, impact = np.modf(impacts[:, i])  # split index of impact and remainder!
             sub_pixel = (remainder * subcount).astype(dtype=np.int)  # sub_pixel inside impact px.
             # Go over all influenced pixels (impact and neighbours, indices are [0, 1, 2]!):
             for px_ind in list(itertools.product(range(3), range(3))):
diff --git a/pyramid/regularisator.py b/pyramid/regularisator.py
index 8088ccd..4c4caf2 100644
--- a/pyramid/regularisator.py
+++ b/pyramid/regularisator.py
@@ -14,8 +14,8 @@ from scipy import sparse
 import jutil.diff as jdiff
 import jutil.norms as jnorm
 
-__all__ = ['NoneRegularisator', 'ZeroOrderRegularisator', 'FirstOrderRegularisator',
-           'ComboRegularisator']
+__all__ = ['Regularisator', 'NoneRegularisator', 'ZeroOrderRegularisator',
+           'FirstOrderRegularisator', 'ComboRegularisator']
 
 
 class Regularisator(object):
-- 
GitLab