diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bcc8c4331d2e8ee7b1816e4557feb5dad480a1bd..6595dad41d04d802015e8a686903008a4b85e2b4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,8 @@ tests:
         - pip3 install 'scipy==1.4.1' -U
         - pip3 install 'matplotlib==3.1.2' -U
         - pip3 install --no-deps .
+        - pip3 freeze
+        - conda list
         - python3 tffpy/tests/ci_config.py
         - pytest-3
 
diff --git a/python/doc/conf.py b/python/doc/conf.py
index a6066609518b00a765b81d49d3dd587e98b9508d..91b6cad902a92ac4edbb722ae09df4a42839b10b 100755
--- a/python/doc/conf.py
+++ b/python/doc/conf.py
@@ -272,6 +272,7 @@ texinfo_no_detailmenu = False
 intersphinx_mapping = {
     'numpy': ('https://docs.scipy.org/doc/numpy/', None),
     'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None),
+    'pandas': ('https://pandas.pydata.org/docs/', None),
     'skpomade': ('http://valentin.emiya.pages.lis-lab.fr/skpomade/', None),
     'yafe': ('http://skmad-suite.pages.lis-lab.fr/yafe/', None),
     'ltfatpy': ('http://dev.pages.lis-lab.fr/ltfatpy/', None),
diff --git a/python/doc/references.rst b/python/doc/references.rst
index da688d76c092d428efd354e0f19ce2b8f387cfad..b695502ae93c8e0e979c9baca7d650b63eb35379 100755
--- a/python/doc/references.rst
+++ b/python/doc/references.rst
@@ -57,5 +57,6 @@ tffpy\.experiments\.exp_solve_tff module
 
 .. automodule:: tffpy.experiments.exp_solve_tff
     :members:
+    :special-members: __call__
     :undoc-members:
     :show-inheritance:
diff --git a/python/tffpy/datasets.py b/python/tffpy/datasets.py
index 8045e56f459e124e3daabeb087c461230fbacc90..2ddebc79ce7f26a2c633b9d5b38c971326989790 100644
--- a/python/tffpy/datasets.py
+++ b/python/tffpy/datasets.py
@@ -29,7 +29,7 @@ def get_dataset():
     -------
     dataset : dict
         dataset['wideband'] (resp. dataset['localized']) is a dictionary
-        containing the :py:class:`Path` object for all the wideband
+        containing the :py:class:`~pathlib.Path` object for all the wideband
         (resp. localized) sounds.
     """
     dataset = dict()
@@ -94,7 +94,7 @@ def get_mix(loc_source, wideband_src, crop=None,
     closing_first : bool
         If True, morphological closings are applied first, followed by
         openings. If False, the reverse way is used.
-    fig_dir : str or Path
+    fig_dir : None or str or Path
         If not None, folder where figures are stored. If None, figures are
         not plotted.
     prefix : str
diff --git a/python/tffpy/experiments/exp_solve_tff.py b/python/tffpy/experiments/exp_solve_tff.py
index 6877d06677be25bc56e8c03dd62598fba2629f87..2558156cd84a56984524db4132ac9930e906109c 100644
--- a/python/tffpy/experiments/exp_solve_tff.py
+++ b/python/tffpy/experiments/exp_solve_tff.py
@@ -1,5 +1,12 @@
 # -*- coding: utf-8 -*-
 """
+Class `SolveTffExperiment` uses the :class:`yafe.base.Experiment` experiment
+framework to handle the main time-frequency fading experiment: It includes
+loading the data, generating the problems, applying solvers, and exploiting
+results.
+
+See the `documentation <http://skmad-suite.pages.lis-lab.fr/yafe/>`_ of
+package :py:mod:`yafe` for the technical details.
 
 .. moduleauthor:: Valentin Emiya
 """
@@ -8,6 +15,7 @@ import numpy as np
 import matplotlib.pyplot as plt
 import matplotlib
 import pandas as pd
+from pathlib import Path
 
 from yafe import Experiment
 from madarrays import Waveform
@@ -20,6 +28,21 @@ from tffpy.utils import \
 
 
 class SolveTffExperiment(Experiment):
+    """
+    The main experiment to solve time-frequency fading problems with a
+    number of sounds mixtures and solvers.
+
+    Parameters
+    ----------
+    force_reset : bool
+        If true, reset the experiment by erasing all previous results
+        in order to run it from scratch. If False, the existing results are
+        kept in order to proceed with the existing experiment.
+    suffix : str
+        Suffix that is appended to the name of the experiment, useful to
+        save results in a specific folder.
+    """
+
     def __init__(self, force_reset=False, suffix=''):
         Experiment.__init__(self,
                             name='SolveTffExperiment' + suffix,
@@ -36,10 +59,36 @@ class SolveTffExperiment(Experiment):
 
     @property
     def n_tasks(self):
+        """
+        Number of tasks
+
+        Returns
+        -------
+        int
+        """
         return len(list((self.xp_path / 'tasks').glob('*')))
 
     @staticmethod
     def get_experiment(setting='full', force_reset=False):
+        """
+        Get the experiment instance with default values in order to handle it.
+
+        Parameters
+        ----------
+        setting : {'full', 'light'}
+            If 'full', the default values are set to run the full
+            experiment. If 'light', the default values are set to have a
+            very light experiment with few tasks, running fast, for test
+            purposes.
+        force_reset : bool
+            If true, reset the experiment by erasing all previous results
+            in order to run it from scratch. If False, the existing results are
+            kept in order to proceed with the existing experiment.
+
+        Returns
+        -------
+        SolveTffExperiment
+        """
         assert setting in ('full', 'light')
 
         dataset = get_dataset()
@@ -76,7 +125,25 @@ class SolveTffExperiment(Experiment):
         exp.generate_tasks()
         return exp
 
-    def export_task_params(self):
+    def export_task_params(self, csv_path=None):
+        """
+        Export task parameters to a csv file and to a
+        :class:`pandas.DataFrame` object.
+
+        Parameters
+        ----------
+        csv_path : str or Path
+            Name of the csv file to be written. If None, file is
+            located in the experiment folder with name 'task_params.csv'.
+
+        Returns
+        -------
+        pandas.DataFrame
+        """
+        if csv_path is None:
+            csv_path = self.xp_path / 'task_params.csv'
+        else:
+            csv_path = Path(csv_path)
         task_list = []
         for i_task in range(self.n_tasks):
             task = self.get_task_data_by_id(idt=i_task)
@@ -84,17 +151,39 @@ class SolveTffExperiment(Experiment):
                               for k in task['task_params']
                               for kk in task['task_params'][k]})
         df = pd.DataFrame(task_list)
-
-        csv_path = self.xp_path / 'task_params.csv'
         df.to_csv(csv_path)
         print('Task params exported to', csv_path)
         return df
 
     def generate_tasks(self):
+        """
+        Generate tasks and export params to a csv file
+
+        See :py:meth:`yafe.Experiment.generate_tasks`
+        """
         Experiment.generate_tasks(self)
         self.export_task_params()
 
     def get_misc_file(self, task_params=None, idt=None):
+        """
+        Get file with some additional task results.
+
+        This has been set up in order to pass additional data in a way that
+        could not be handled by the :py:mod:`yafe` framework.
+
+        Parameters
+        ----------
+        task_params : dict
+            Task parameters.
+        idt : int
+            Task identifier. Either `task_params` or `idt` should be given
+            in order to specify the task.
+
+        Returns
+        -------
+        Path
+            File containing additional task results.
+        """
         if task_params is not None:
             task = self.get_task_data_by_params(
                 data_params=task_params['data_params'],
@@ -107,6 +196,9 @@ class SolveTffExperiment(Experiment):
         return path_task / 'misc.npz'
 
     def plot_results(self):
+        """
+        Plot and save results of the experiment
+        """
         self.fig_dir.mkdir(parents=True, exist_ok=True)
         print('Figures saved in {}'.format(self.fig_dir))
         results = self.load_results(array_type='xarray')
@@ -285,6 +377,16 @@ class SolveTffExperiment(Experiment):
         return label
 
     def plot_task(self, idt, fontsize=16):
+        """
+        Plot and save figures for a specific task
+
+        Parameters
+        ----------
+        idt : int
+            Task identifier
+        fontsize : int
+            Fontsize to be used in Figures.
+        """
         matplotlib.rcParams.update({'font.size': fontsize})
         fig_dir = self.xp_path / 'figures' / 'tasks' / '{:06}'.format(idt)
         fig_dir.mkdir(parents=True, exist_ok=True)
@@ -468,14 +570,72 @@ class SolveTffExperiment(Experiment):
 
 
 def get_data(loc_source, wideband_src):
+    """
+    Prepare the input data information for the :py:class:`SolveTffExperiment`
+    experiment.
+
+    This function is only embedding its input in a dictionary
+
+    Parameters
+    ----------
+    loc_source : Path
+        File for the source localized in time-frequency (perturbation)
+    wideband_src : Path
+        File for the source of interest.
+
+    Returns
+    -------
+    dict
+        Dictionary to be given when calling the problemm (
+        see :py:meth:`Problem.__call__`), with keys `'loc_source'` and
+        `wideband_src`.
+    """
     return dict(loc_source=loc_source, wideband_src=wideband_src)
 
 
 class Problem:
-    def __init__(self, win_choice, or_mask,
-                 n_iter_closing, n_iter_opening, closing_first,
-                 delta_mix_db, delta_loc_db, wb_to_loc_ratio_db, crop,
-                 fig_dir):
+    """
+    Problem generation for the :py:class:`SolveTffExperiment` experiment.
+
+    Parameters
+    ----------
+    crop : int or None
+        If not None, a cropped, centered portion of the sound will be
+        extracted with the specified length, in samples.
+    win_choice : str
+        String of the form 'name len' where 'name' is a window name and
+        'len' is a window length, e.g. 'hann 512', 'gauss 256.
+    delta_mix_db : float
+        Coefficient energy ratio, in dB, between the wideband source and the
+        localized source in the mixture in order to select coefficients in
+        the mask.
+    delta_loc_db : float
+        Dynamic range, in dB, for the localized source in order to select
+        coefficients in the mask.
+    wb_to_loc_ratio_db : float
+        Wideband source to localized source energy ratio to be adjusted in
+        the mix.
+    or_mask : bool
+        If True, the mask is build by taking the union of the two masks
+        obtained using thresholds `delta_mix_db` and `delta_loc_db`. If
+        False, the intersection is taken.
+    n_iter_closing : int
+        Number of successive morphological closings with radius 1 (a.k.a.
+        radius of one single closing)
+    n_iter_opening : int
+        Number of successive morphological openings with radius 1 (a.k.a.
+        radius of one single opening)
+    closing_first : bool
+        If True, morphological closings are applied first, followed by
+        openings. If False, the reverse way is used.
+    fig_dir : None or str or Path
+        If not None, folder where figures are stored. If None, figures are
+        not plotted.
+    """
+
+    def __init__(self, crop, win_choice,
+                 delta_mix_db, delta_loc_db, wb_to_loc_ratio_db, or_mask,
+                 n_iter_closing, n_iter_opening, closing_first, fig_dir):
         win_type, win_len_str = win_choice.split(sep=' ')
         win_dur = int(win_len_str) / 8000
         self.win_dur = win_dur
@@ -497,6 +657,26 @@ class Problem:
         self.fig_dir = fig_dir
 
     def __call__(self, loc_source, wideband_src):
+        """
+        Generate the problem from input data.
+
+        Parameters
+        ----------
+        loc_source : Path
+            File for the source localized in time-frequency (perturbation)
+        wideband_src : Path
+            File for the source of interest.
+
+        Returns
+        -------
+        problem_data : dict
+            Dictionary to be given to a solver, with keys `'x_mix'` (mix
+            signal), `mask` (time-frequency mask), `dgt_params` (DGT
+            parameters) and `signal_params` (signal parameters).
+        solution_data : dict
+            Dictionary containing problem solutions, with keys `'x_loc'` (
+            localized signal ) and `x_wb` (wideband signal).
+        """
         x_mix, dgt_params, signal_params, mask, x_loc, x_wb = \
             get_mix(loc_source=loc_source,
                     wideband_src=wideband_src,
@@ -521,12 +701,78 @@ class Problem:
 
 
 class Solver:
+    """
+    Solver for the :py:class:`SolveTffExperiment` experiment.
+
+    This solver is computing
+
+    * the `TFF-1` of `TFF-P` solution (depending on parameter `tol_subregions`)
+      using a :py:class:`~tffpy.tf_fading.GabMulTff` instance
+    * the `Interp` solution using function
+      :py:func:`~tffpy.interpolation_solver.solve_by_interpolation`
+
+    Parameters
+    ----------
+    tol_subregions : None or float
+        Tolerance to split the mask into sub-regions in
+        :py:class:`~tffpy.tf_fading.GabMulTff`.
+    tolerance_arrf : float
+        Tolerance for the randomized EVD in
+        :py:class:`~tffpy.tf_fading.GabMulTff`, see method
+        :py:meth:`~tffpy.tf_fading.GabMulTff.compute_decomposition`.
+    proba_arrf : float
+        Probability of error for the randomized EVD in
+        :py:class:`~tffpy.tf_fading.GabMulTff`, see method
+        :py:meth:`~tffpy.tf_fading.GabMulTff.compute_decomposition`.
+    """
+
     def __init__(self, tol_subregions, tolerance_arrf, proba_arrf):
         self.tol_subregions = tol_subregions
         self.tolerance_arrf = tolerance_arrf
         self.proba_arrf = proba_arrf
 
     def __call__(self, x_mix, mask, dgt_params, signal_params):
+        """
+        Apply the solver to estimate solutions from the problem data.
+
+        The output dictionary is composed of data with keys:
+
+        * `'x_tff'`: solution estimated by :py:class:`~tffpy.tf_fading.GabMulTff`
+        * `'x_zero'`: solution when applying the Gabor
+          multiplier (i.e., :math:`\lambda=1`)
+        * `'x_interp'`: solution from function
+          :py:func:`~tffpy.interpolation_solver.solve_by_interpolation`
+        * `'gmtff'`: `GabMulTff` instance
+        * `'t_lambda_tff'`: running times to estimate hyperparameter in method
+          :py:meth:`~tffpy.tf_fading.GabMulTff.compute_lambda`
+        * `'t_arrf'`: running times to compute range approximation in method
+          :py:meth:`~tffpy.tf_fading.GabMulTff.compute_decomposition`
+        * `'t_evdn'`: running times to compute EVD in method
+          :py:meth:`~tffpy.tf_fading.GabMulTff.compute_decomposition`
+        * `'t_uh_x'`: running times to compute additional matrix products in
+          method :py:meth:`~tffpy.tf_fading.GabMulTff.compute_decomposition`
+        * `'t_subreg'`: running times to split mask into sub-regions in class
+          :py:class:`~tffpy.tf_fading.GabMulTff`
+        * `'lambda_tff'`: estimated values for hyper-parameters
+          :math:`\lambda_i` estimated by
+          :py:meth:`~tffpy.tf_fading.GabMulTff`.compute_lambda`
+
+        Parameters
+        ----------
+        x_mix : nd-array
+            Mix signal
+        mask : nd-array
+            Time-frequency mask
+        dgt_params : dict
+            DGT parameters
+        signal_params : dict
+            Signal parameters
+
+        Returns
+        -------
+        dict
+            The estimated solution and additional information
+        """
         gmtff = GabMulTff(x_mix=x_mix, mask=mask, dgt_params=dgt_params,
                         signal_params=signal_params,
                         tol_subregions=self.tol_subregions)
@@ -550,6 +796,31 @@ class Solver:
 
 def perf_measures(task_params, source_data, problem_data,
                   solution_data, solved_data, exp=None):
+    """
+    Performance measure, including computation of oracle solutions
+
+    Parameters
+    ----------
+    task_params : dict
+        Task parameters
+    source_data : dict
+        Input data
+    problem_data : dict
+        Problem data
+    solution_data : dict
+        Solver output
+    solved_data : dict
+        True solution data
+    exp : SolveTffExperiment
+        The experiment
+
+    Returns
+    -------
+    dict
+        All data useful for result analysis including SDR and Itakura-Saito
+        performance, running times, hyperparameter values, mask size and
+        number of sub-regions.
+    """
     x_tff = solved_data['x_tff']
     x_zero = solved_data['x_zero']
     gmtff = solved_data['gmtff']
@@ -607,6 +878,9 @@ def perf_measures(task_params, source_data, problem_data,
 
 
 def create_and_run_light_experiment():
+    """
+    Create a light experiment and run it
+    """
     exp = SolveTffExperiment.get_experiment(setting='light', force_reset=True)
     print('*' * 80)
     print('Created experiment')
diff --git a/python/tffpy/tf_fading.py b/python/tffpy/tf_fading.py
index f2e15b8930963f661eca84b30ddb984a25b17acf..c0be148e6ddc7241794a2ccb44b82fe1290e887a 100644
--- a/python/tffpy/tf_fading.py
+++ b/python/tffpy/tf_fading.py
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 """
+Class :class:`GabMulTff` is the main object to solve a time-frequency fading 
+problem.
 
 .. moduleauthor:: Valentin Emiya
 """
@@ -38,7 +40,7 @@ class GabMulTff:
     tol_subregions : None or float
         If None, the mask is considered as a single region. If float,
         tolerance to split the mask into sub-regions using
-        :py:func:`tffpy.create_subregions.create_subregions`.
+        :py:func:`~tffpy.create_subregions.create_subregions`.
     fig_dir : str or Path
         If not None, folder where figures are stored. If None, figures are
         not plotted.
@@ -95,16 +97,19 @@ class GabMulTff:
         followed by :py:func:`skpomade.factorization_construction.evd_nystrom`.
         The rank of each decomposition is estimated using parameters
         `tolerance_arrf` and `proba_arrf`.
-        Running times are stored in attributes `t_arrf`, `t_evdn` and `t_uh_x`.
+        Running times to compute the range approximation, the
+        EVD itself and the additional matrix products for subsequent
+        computations are stored in attributes `t_arrf`, `t_evdn` and
+        `t_uh_x`, respectively.
 
         Parameters
         ----------
         tolerance_arrf : float
             Tolerance for
-            :py:func:`skpomade.range_approximation.adaptive_randomized_range_finder`
+            :py:func:`~skpomade.range_approximation.adaptive_randomized_range_finder`
         proba_arrf : float
             Probability of error for
-            :py:func:`skpomade.range_approximation.adaptive_randomized_range_finder`
+            :py:func:`~skpomade.range_approximation.adaptive_randomized_range_finder`
 
         """
         for i in range(self.n_areas):