Repository: rigoudyg/climaf (language: python)

Issue:
Define the equality of 2 datasets ?
This is probably not a priority, but it could be useful to be able test if 2 datasets are equal

In the example below, I seem to have to equal datasets (at least their `crs` and `kvp` values are equal), but they don't evaluate as equal

```
pmip_exp_resolved = pmip_exp_query.explore('resolve')

>>> pmip_exp_query == pmip_exp_resolved
False

>>> pmip_exp_query.kvp
{'project': 'CMIP6', 'simulation': '', 'variable': 'pr', 'period': 2300-2399, 'domain': 'global', 'root': '/bdd', 'model': 'IPSL-CM6A-LR', 'institute': 'IPSL', 'mip': 'PMIP', 'table': 'Amon', 'experiment': 'midHolocene', 'realization': 'r1i1p1f1', 'grid': 'gr', 'version': 'latest'}

>>> pmip_exp_resolved.kvp
{'project': 'CMIP6', 'simulation': '', 'variable': 'pr', 'period': 2300-2399, 'domain': 'global', 'root': '/bdd', 'model': 'IPSL-CM6A-LR', 'institute': 'IPSL', 'mip': 'PMIP', 'table': 'Amon', 'experiment': 'midHolocene', 'realization': 'r1i1p1f1', 'grid': 'gr', 'version': 'latest'}

>>> pmip_exp_query.kvp == pmip_exp_resolved.kvp
True

>>> pmip_exp_query.crs
"ds('CMIP6%%pr%2300-2399%global%/bdd%IPSL-CM6A-LR%IPSL%PMIP%Amon%midHolocene%r1i1p1f1%gr%latest')"
>>> pmip_exp_resolved.crs

"ds('CMIP6%%pr%2300-2399%global%/bdd%IPSL-CM6A-LR%IPSL%PMIP%Amon%midHolocene%r1i1p1f1%gr%latest')"

>>> pmip_exp_query.crs == pmip_exp_resolved.crs
True

>>> dir(pmip_exp_query)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'alias', 'baseFiles', 'buildcrs', 'check', 'crs', 'domain', 'domainIsFine', 'erase', 'errata', 'explore', 'files', 'frequency', 'hasExactVariable', 'hasOneMember', 'hasRawVariable', 'isCached', 'isLocal', 'kvp', 'listfiles', 'local_copies_of_remote_files', 'matches_conditions', 'missingIsOK', 'model', 'oneVarPerFile', 'period', 'periodHasOneFile', 'periodIsFine', 'project', 'register', 'setperiod', 'simulation', 'variable']
```

cfile(some_ds) crashes if the dataset uses period='*'
When using a dataset where `period='*'`, but there is no real ambiguity in the dataset (`summary` does not complain and `explore('choices')` does not show multiple options), I get a frightening traceback when using `cfile` or other operators.

Things are OK once I use _‘resolve’ for returning a NEW DATASET with instanciated attributes_

There is probably some logic behind this, but it would be **extremely helpful** for the end user if this ambiguity problem was detected (and possibly automatically resolved) and **if CliMAF could display a nice warning message instead of crashing**

Detailed example below 

```
[jypmce@ciclad-ng ~]$ module load climaf/2.0.0-python3.6
Loading climaf/2.0.0-python3.6
  Loading requirement: nco/4.5.2 ncl/6.3.0 cdo/1.9 netcdf4/4.3.3.1-gfortran

[...]

>>> from climaf.api import *

[...]

>>> kvp_restore = {'project': 'CMIP6', 'simulation': '', 'variable': 'pr', 'period': '*', 'domain': 'global', 'root': '/bdd', 'model': 'IPSL-CM6A-LR', 'institute': '*', 'mip': '*', 'table': 'Amon', 'experiment': 'midHolocene', 'realization': 'r1i1p1f1', 'grid': 'gr', 'version': 'latest'}

>>> pmip_test = ds(**kvp_restore)
>>> pmip_test
ds('CMIP6%%pr%*%global%/bdd%IPSL-CM6A-LR%*%*%Amon%midHolocene%r1i1p1f1%gr%latest')

>>> pmip_test.explore('choices')
{'period': [1850-2399], 'institute': 'IPSL', 'mip': 'PMIP'}

>>> summary(pmip_test)
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_185001-204912.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_205001-234812.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_234901-234912.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_235001-239912.nc
{'project': 'CMIP6', 'simulation': '', 'variable': 'pr', 'period': '*', 'domain': 'global', 'root': '/bdd', 'model': 'IPSL-CM6A-LR', 'institute': '*', 'mip': '*', 'table': 'Amon', 'experiment': 'midHolocene', 'realization': 'r1i1p1f1', 'grid': 'gr', 'version': 'latest'}

>>> cfile_attempt_1 = cfile(pmip_test)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 1199, in cfile
    result = ceval(object, format='file', deep=deep)
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 665, in ceval
    derived_list=derived_list, recurse_list=recurse_list)
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 270, in ceval_for_cdataset
    rep = ceval(extract, userflags=userflags, format=format)
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 669, in ceval
    derived_list=derived_list, recurse_list=recurse_list)
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 387, in ceval_for_ctree
    recurse_list=recurse_list)  # Does return a filename, or list of filenames
  File "/ciclad-home/jservon/Evaluation/CliMAF/climaf_installs/climaf_V2.0.0/climaf/driver.py", line 761, in ceval_script
    if per and not per.fx and str(per) != "" and scriptCall.flags.canSelectTime:
AttributeError: 'str' object has no attribute 'fx'

>>> pmip_test_resolved = pmip_test.explore('resolve')

>>> pmip_test_resolved
ds('CMIP6%%pr%1850-2399%global%/bdd%IPSL-CM6A-LR%IPSL%PMIP%Amon%midHolocene%r1i1p1f1%gr%latest')

>>> pmip_test_resolved.explore('choices')
{}

>>> summary(pmip_test_resolved)
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_185001-204912.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_205001-234812.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_234901-234912.nc
/bdd/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f1/Amon/pr/gr/latest/pr_Amon_IPSL-CM6A-LR_midHolocene_r1i1p1f1_gr_235001-239912.nc
{'project': 'CMIP6', 'simulation': '', 'variable': 'pr', 'period': 1850-2399, 'domain': 'global', 'root': '/bdd', 'model': 'IPSL-CM6A-LR', 'institute': 'IPSL', 'mip': 'PMIP', 'table': 'Amon', 'experiment': 'midHolocene', 'realization': 'r1i1p1f1', 'grid': 'gr', 'version': 'latest'}

>>> cfile_attempt_2 = cfile(pmip_test_resolved)
>>> cfile_attempt_2
'/data/jypmce/climafcache/65/1b618d5501ba62a63336104e811fdd40fc8161761df77b53f9091a.nc'
>>>
```

Result of "listfiles(ensure_dataset=False)" when the ambiguity is not on period
I have a script using `listfiles(ensure_dataset=False)`, so that I can get a list of files, even if there are some holes in the periods.

This has the side effect that **I get a list of realizations instead of a list of files, when there are multiple realizations**. I don't know if there is an easy way to deal with this, or even what should be done...

I would maybe expect:
* an error message, if I have `ensure_dataset=False` but the _ambiguity_ is on something else than `period`
* an empty string (instead of a string with the realizations, when I'm expecting a string with all the files)
* possibly a string with all the files for all the realizations and all the other possible ambiguities, but it could become a very long string (we don't want to get a listing of all the files in a project!!)

```
>>> my_query = ds(project='CMIP6', experiment='midHolocene', variable='tas', table='Amon', period='*', model='IPSL-CM6A-LR')

>>> my_query.listfiles()
error    : "This dataset is ambiguous on attribute realization='r1i1p1f*'; please choose among : ['r1i1p1f1', 'r1i1p1f2', 'r1i1p1f3', 'r1i1p1f4'] or use either 'ensure_dataset=False' (with method baseFiles or listfiles) or 'option='choices' (with method explore)"

>>> my_query.explore('choices')
{'period': [1850-2399], 'institute': 'IPSL', 'mip': 'PMIP', 'realization': ['r1i1p1f1', 'r1i1p1f2', 'r1i1p1f3', 'r1i1p1f4'], 'grid': 'gr'}

>>> my_query.listfiles()
'r1i1p1f1 r1i1p1f2 r1i1p1f3 r1i1p1f4'

>>> my_query.listfiles(force=True, ensure_dataset=True)
error    : "This dataset is ambiguous on attribute realization='r1i1p1f*'; please choose among : ['r1i1p1f1', 'r1i1p1f2', 'r1i1p1f3', 'r1i1p1f4'] or use either 'ensure_dataset=False' (with method baseFiles or listfiles) or 'option='choices' (with method explore)"

>>> my_query.listfiles(force=True, ensure_dataset=False)
'r1i1p1f1 r1i1p1f2 r1i1p1f3 r1i1p1f4'

```



Discussion / hints:

Fix patch:
diff --git a/climaf/ESMValTool_diags.py b/climaf/ESMValTool_diags.py
new file mode 100644
index 00000000..b77cd8ed
--- /dev/null
+++ b/climaf/ESMValTool_diags.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Driver and utilities for launching an ESMValTool diagnostic. 
+
+Originally developped with ESMValTool version 2.2.0
+"""
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+# We assume that the dataset period has complete years (because (some) EVT diags do assume it too)
+# Created : S.Senesi - 2021
+    
+import sys
+import os
+import yaml
+import subprocess
+import time
+import datetime
+import logging
+#
+from env.environment import *
+from env.clogging import clogger, dedent as cdedent
+from env.site_settings import atIPSL
+#
+import climaf
+from climaf.utils import Climaf_Error, Climaf_Driver_Error
+from climaf.anynetcdf import ncf
+from climaf.classes import timePeriod, cens, varOf, projectOf, experimentOf, realizationOf
+
+#: Path for the wrapper script for setting ESMValTool's diag scripts environment and launching them
+wrapper = None
+
+def evt_script(climaf_name, script):
+    """Create a function named CLIMAF_NAME for launching an ESMValTool's
+    diagnostic script SCRIPT (which follows EMSValTool naming
+    convention)
+
+    The created function will actually invoke :py:func:`~climaf.driver.ceval_evt` with
+    argument SCRIPT and own arguments.
+
+    """
+    
+    doc = "CliMAF wrapper for EVT script : %s" % script
+    defs = 'def %s(*args,**dic) :\n  """%s"""\n  '% (climaf_name, doc) + \
+           'return climaf.driver.ceval_evt("%s","%s",*args,**dic)\n' \
+            %(climaf_name, script)
+    exec(defs, globals())  #
+    exec(defs, locals())  #
+    exec("from climaf.ESMValTool_diags import %s" % climaf_name, sys.modules['__main__'].__dict__)
+    clogger.debug("ESMValTool script %s has been declared as function %s" % (script, climaf_name))
+        
+        
+def call_evt_script(climaf_name, script, ensembles, *operands, **parameters) :
+    """
+    Driver for calling an ESMValTool diagnostic script (DS). 
+
+    This function is NOT supposed to be called directly except by CliMAF driver, see doc.
+
+    Arguments :
+
+    - climaf_name : name of the python function associated to the DS
+
+    - script : name of the DS, according to ESMValTool convention
+
+    - ensembles : list of datasets ensemble objects to provide to the
+      DS (one member per variable)
+
+    - operands : values of the ensemble objects (i.e. filenames)
+
+    - parameters : additional key/value pairs to provide to the DS
+
+    This drivers creates a directory dedicated to running that DS, and all 
+    necessary interface files. It checks that execution was successfull.
+
+    Returns a pair : DS working directory, dictionnary of provenance information
+
+    """
+    
+    # Initalize most settings 
+    settings = {
+        'recipe'             : 'CliMAF',
+        'script'             : script,
+        'version'            : climaf.version,
+        
+        # User may wish to change next attributes for each call
+        'auxiliary_data_dir' : None,
+        'log_level'          : _translate_loglevel(clogger.getEffectiveLevel()),
+        'output_file_type'   : 'png',
+        'profile_diagnostic' : False,
+        'write_netcdf'       : True,
+        'write_plots'        : True,
+        'quick_plot'         : {},
+        }
+    
+    # Account for dynamical, un-controlled, script call parameters to update settings
+    settings.update(parameters)
+
+    # Create a working directory according to ESMValTool habits
+    # (e.g. cvdp_20210523_044731)
+    output_dir=settings.get('output_dir','./evtscript_output/')
+    tmpdir = output_dir+"/%s_%s"%(climaf_name, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
+    tmpdir = os.path.abspath(tmpdir)+"/"
+    os.makedirs(tmpdir)
+    
+    # Create sub-directories according to ESMValTol habits
+    for adir in ['work_dir','run_dir','preproc_dir']:
+        settings[adir] = tmpdir + adir.replace('_dir','') 
+        if not os.path.exists(settings[adir]):
+            os.mkdir(settings[adir])
+    # Plot dir doesn't stick to the rule ('plot' -> 'plots')
+    settings['plot_dir'] = tmpdir + 'plots'    
+    if not os.path.exists(settings['plot_dir']):
+        os.mkdir(settings['plot_dir'])
+
+    # Create medata files (one per variable/ensemble, gouped in dict
+    # metadatas with key = variable, value = metadata file):
+    metadatas = dict()
+    for ensemble, value in zip(operands, ensembles) :
+        _create_metadata_file(script, ensemble, value, settings['preproc_dir'], metadatas)
+
+    # on pourrait faire un controle sur le fait qu'il y a bien un
+    # ensemble par variable déclarée, mais pas sûr que déclarer les
+    # variables soit pratiqué dans EVT
+
+    settings['input_files'] = [ metadatas[variable] for variable in metadatas ]
+
+    # Write settings.yaml using dict settings
+    settings_filename = settings['run_dir'] + "/settings.yml"
+    with open(settings_filename, 'w') as file:
+        yaml.safe_dump(settings, file)
+
+    if wrapper is None :
+        _init_wrapper()
+
+    # Launch the diagnsotic script using a wrapper
+    command = [ wrapper, script, settings_filename ]
+    clogger.info("Launching command : " + repr(command))
+    tim1 = time.time()
+    process = subprocess.Popen(
+        command,
+        bufsize=2**20,  # Use a large buffer to prevent NCL crash
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+        cwd=settings['run_dir'],
+        env=dict(os.environ),
+    )
+    logname = settings['run_dir'] + '/log.txt'
+    returncode = None
+    with open(logname, 'ab') as log:
+        while returncode is None:
+            returncode = process.poll()
+            log.write(process.stdout.read())
+            # wait, but not long because the stdout buffer may fill up:
+            # https://docs.python.org/3.6/library/subprocess.html#subprocess.Popen.stdout
+            time.sleep(0.001)
+                
+    if returncode == 0:
+        clogger.debug("Script %s (%s) completed successfully", climaf_name, script)
+    else:
+        raise Climaf_Error(
+            "Diagnostic script {} ({}) failed with return code {}. See the log "
+            "in {}".format(climaf_name, script, returncode, logname))
+    #
+    duration = time.time() - tim1
+    clogger.info("Done in %.1f s with %s computation "
+                 "(command was :%s )" % (duration, script, command))
+
+    # Returned value is a pair : working directory, dict of provenance information
+    try :
+        with open(settings['run_dir']+"/diagnostic_provenance.yml","r") as prov :
+            prov_dict=yaml.safe_load(prov)
+    except :
+        raise Climaf_Error("Script %s (%s) didn't produce provenance information",
+                           climaf_name, script)
+        prov_dict = {}
+
+    return tmpdir,prov_dict
+ 
+                                
+
+def _create_metadata_file(script, ensemble, value, preproc_dir, metadatas) :
+    """Create an ESMVamTool diagnostic script interface file of type 'medata file' 
+
+    This yaml file describes each input file of an objects' ENSEMBLE
+    provided to a SCRIPT. Input files are those listed in VALUE, as a
+    white-space separated list of filenames, and are CliMAF cache
+    files (or basic data files). They are provided ot the script as
+    symbolic links in directory PREPROC_DIR, and with names built with
+    ensemble key, variable name and file period
+
+    A number of descriptors are soughtread from datafiles
+
+    Arg METADATAS is a dict that allows to return the metadata
+    filename, the key being the ensemble variable
+
+    """
+    if not isinstance(ensemble, cens):
+        raise Climaf_Error("EVT scripts like %s only accepts ensembles , which is not the case for %s:"\
+                                  % (script,ensemble))
+    files=value.split(" ")
+    if value != '' and not all(map(os.path.exists, files)):
+        raise Climaf_Driver_Error("Internal error : some input file does not exist among %s:" % infile)
+
+    variable = varOf(ensemble[ensemble.order[0]])
+    data_dir = preproc_dir + "/" + variable
+    os.makedirs(data_dir)
+    #
+    i=0
+    metadata = dict()
+    for member in ensemble.order :
+        if variable != varOf(ensemble[member]) :
+            raise Climaf_Driver_Error("A member has wrong variable (%s rather than %s)"\
+                                  % (varOf(ensemble[member]),variable))
+        d=dict()
+        d['alias']                = member
+        d['dataset']              = member
+        # recipe_dataset_index : ? numero d'ordre dans la liste des datasets de la recipe ?
+        d['recipe_dataset_index'] = i + 1
+        d['project']              = projectOf(ensemble[member])
+        d['exp']                  = experimentOf(ensemble[member])
+        # We assume that the dataset period has complete years
+        d['start_year']           = int(timePeriod(ensemble[member]).pr().split("-")[0])
+        d['end_year']             = int(timePeriod(ensemble[member]).pr().split("-")[1])
+        d['short_name']           = varOf(ensemble[member])
+        d['variable_group']       = varOf(ensemble[member])
+        d['ensemble']             = realizationOf(ensemble[member])
+        d['diagnostic']           = script
+        d['preprocessor']         = 'default'
+
+        # Create a symbolic link in preproc dir for the input file
+        afile = files[i]
+        i += 1
+        link_name = data_dir + "/" + member + "_" + variable + \
+                    "_%d"%d['start_year'] + "-" + "%d"%d['end_year'] + ".nc"
+        if os.path.exists(link_name):
+            os.remove(link_name)
+        os.symlink(afile,link_name)
+        d['filename']             = link_name
+
+        # Add informations read in file
+        freq, inst, lname, table, realm, stdname, units = _read_attr_from_file(afile,variable)
+        d['frequency']           = freq
+        d['institute']           = inst.split()
+        d['long_name']           = lname
+        d['mip']                 = table 
+        d['modeling_realm']      = realm.split()
+        d['standard_name']       = stdname
+        d['units']               = units
+        metadata[d['filename']]  = d
+        
+    # Write metadata file
+    metadata_filename =  data_dir + "/metadata.yml"
+    with open(metadata_filename, 'w') as file:
+        yaml.safe_dump(metadata, file)
+
+    metadatas[variable] = metadata_filename 
+
+
+def _read_attr_from_file(afile,variable) :
+    with ncf(afile,'r') as fileobject:
+        try :
+            freq = fileobject.frequency
+        except :
+            freq = "N/A"
+        try :
+            inst = fileobject.institution_id
+        except :
+            inst = "N/A"
+        try :
+            table = fileobject.table_id
+        except :
+            table = "N/A"
+        try :
+            realm = fileobject.realm
+        except :
+            realm = "N/A"
+            
+        var = fileobject.variables[variable]
+        try :
+            lname = var.long_name
+        except :
+            lname = "N/A"
+        try :
+            stdname = var.standard_name
+        except :
+            stdname = "N/A"
+        try :
+            units = var.units
+        except :
+            units = "N/A"
+    return (freq, inst, lname, table, realm, stdname, units)
+
+
+def _translate_loglevel(level) :
+    """
+    Returns a string corresponding to the logging LEVEL, understandable by ESMVamTool
+    """
+    
+    if level == logging.INFO :
+        return "info"
+    elif level == logging.DEBUG :
+        return "debug"
+    elif level == logging.WARNING :
+        return "warning"
+    elif level == logging.ERROR :
+        return "error"
+    elif level == logging.CRITICAL :
+        return "critical"
+    else :
+        return level
+    
+def _init_wrapper():
+    """
+    Find a wrapper script for ESMValTool diags for the current platform. Its task 
+    is to set the environment for executing such diags, and to launch it. See an 
+    example of such wrapper in 
+    :download:`$CLIMAF/scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh<../scripts/EVT_python_diags_wrapper_for_ciclad.sh>`
+    """
+    if atIPSL :
+        scripts_dir = __file__ + "/../../scripts" 
+        wrapper= scripts_dir / "EVT_python_diags_wrapper_for_ciclad.sh"
+    else:
+        raise Climaf_Error(
+            "Cannot find a relevant wrapper for ESMValTool diagnostic scripts "
+            "for current platform (in directory {})".format(scripts_dir))
+
diff --git a/climaf/__init__.py b/climaf/__init__.py
index 0b23fb51..769e7495 100644
--- a/climaf/__init__.py
+++ b/climaf/__init__.py
@@ -13,10 +13,10 @@
 
 __all__ = ["cache", "classes", "dataloc", "driver", "netcdfbasics",
            "operators", "period", "standard_operators", "cmacro", "html", "functions", "plot",
-           "projects", "derived_variables"]
+           "projects", "derived_variables","ESMValTool_diags"]
 
 
-version = "2.0.0"
+version = "2.0.2"
 
 
 def tim(string=None):
@@ -35,12 +35,8 @@ def tim(string=None):
             print("Duration %.1f for step %s" % (delta, string), file=sys.stderr)
 
 
-xdg_bin = False
-if os.system("type xdg-open >/dev/null 2>&1") == 0:
-    xdg_bin = True
-
 already_inited = False
-onrtd = os.environ.get('READTHEDOCS', None) == 'True'
+onrtd = os.environ.get('READTHEDOCS', None) in ['True', ]
 
 if not already_inited and not onrtd:
     import sys
@@ -54,7 +50,7 @@ def tim(string=None):
     tim("atexit")
     #
     print("CliMAF version = " + version, file=sys.stderr)
-    print("CliMAF install => " + "/".join(__file__.split("/")[:-2]))
+    print("CliMAF install => " + "/".join(__file__.split("/")[:-2]), file=sys.stderr)
     from env.environment import *
 
     tim("softwares")
@@ -68,33 +64,19 @@ def tim(string=None):
 
     tim("imports")
     #
-    # Check that the variable TMPDIR, if defined, points to an existing directory
-    if "TMPDIR" in os.environ and not os.path.isdir(os.environ["TMPDIR"]):
-        # raise OSError("TMPDIR points to a non existing directory! Change the value of the variable to go on.")
-        if os.path.exists(os.environ["TMPDIR"]):
-            os.remove(os.environ["TMPDIR"])
-        os.makedirs(os.environ["TMPDIR"])
-
-    logdir = os.path.expanduser(os.getenv("CLIMAF_LOG_DIR", "."))
-    #
     # Set default logging levels
-    clogging.logdir = os.path.expanduser(os.getenv("CLIMAF_LOG_DIR", "."))
-    clogging.clog(os.getenv("CLIMAF_LOG_LEVEL", "warning"))
-    clogging.clog_file(os.getenv("CLIMAF_LOGFILE_LEVEL", "info"))
+    clogging.logdir = logdir
+    clogging.clog(loglevel)
+    clogging.clog_file(logfilelevel)
     tim("loggings")
     #
     # Decide for cache location
-    if site_settings.onCiclad:
-        default_cache = "/data/" + os.getenv("USER") + "/climaf_cache"
-    else:
-        default_cache = "~/tmp/climaf_cache"
-    cachedir = os.getenv("CLIMAF_CACHE", default_cache)
-    cache.setNewUniqueCache(cachedir, raz=False)
-    print("Cache directory set to : " + cachedir + " (use $CLIMAF_CACHE if set) ", file=sys.stderr)
+    cachedir = default_cache  # TODO: For compatibility, delete ones useless
+    cache.setNewUniqueCache(default_cache, raz=False)
+    print("Cache directory set to : " + default_cache + " (use $CLIMAF_CACHE if set) ", file=sys.stderr)
     tim("set cache")
     # Decide for cache location for remote data
-    remote_cachedir = os.getenv("CLIMAF_REMOTE_CACHE", cachedir + "/remote_data")
-    print("Cache directory for remote data set to : " + remote_cachedir + " (use $CLIMAF_REMOTE_CACHE if set) ",
+    print("Cache directory for remote data set to : " + default_remote_cache + " (use $CLIMAF_REMOTE_CACHE if set) ",
           file=sys.stderr)
     #
     # Init dynamic CliMAF operators, and import projects and some funcs in main
@@ -116,7 +98,7 @@ def tim(string=None):
     #
     # Load cache scalar values 
     cache.load_cvalues()
-    tim("cload_values")
+    tim("load_cvalues")
     #
     # Init and load macros
     macroFilename = os.environ.get("CLIMAF_MACROS", "~/.climaf.macros")
@@ -129,6 +111,7 @@ def tim(string=None):
     cache.cload()
     tim("cload")
     #
+    atexit.register(cache.csync)
     atexit.register(cache.sync_cvalues)
     atexit.register(cmacro.write, macroFilename)
     tim("atexit")
diff --git a/climaf/anynetcdf.py b/climaf/anynetcdf.py
index e4617c06..15df5a59 100644
--- a/climaf/anynetcdf.py
+++ b/climaf/anynetcdf.py
@@ -20,7 +20,7 @@
             except ImportError:
                 clogger.critical(
                     "Netcdf handling is yet available only with modules Scientific.IO.Netcdf or NetCDF4 or "
-                    "scipy.io.netcdf ")
+                    "or netCDF4 or scipy.io.netcdf ")
                 exit()
                 # raise Climaf_Netcdf_Error("Netcdf handling is yet available only with modules Scientific.IO.Netcdf or
                 #                            NetCDF4 or scipy.io.netcdf ")
diff --git a/climaf/api.py b/climaf/api.py
index 44351257..46cb085a 100644
--- a/climaf/api.py
+++ b/climaf/api.py
@@ -58,6 +58,8 @@
  - ``raz_cvalues`` : reset scalar values cache
 
  - ``csync``    : save cache index to disk
+ 
+ - ``ccost``    : provide compute costs for an object
 
 
 - utility functions :
@@ -74,30 +76,35 @@
 
 import os
 #
+from env.environment import *
+from env.clogging import clogger, clog, clog_file, logdir
+from env.site_settings import atCNRM, onCiclad, atTGCC, atIDRIS, atIPSL, onSpip
+#
 import climaf
 
 # Declare standard projects and standard data locations
 from .projects import *
 
-#####################################################################################################################
-# All CliMAF functions we want to provide as top-level functions when this module is loaded as "from ... import *"
-#####################################################################################################################
+#############################################################################################
+# All CliMAF functions we want to provide as top-level functions when
+# this module is loaded as "from ... import *"
+#############################################################################################
 #
-from climaf.classes import cdef, cdataset, ds, cproject, cpage, cfreqs, cens, eds, fds, cpage_pdf, varOf, crealms
+from climaf.classes import cdef, cdataset, ds, cproject, cpage, \
+    cfreqs, cens, eds, fds, cpage_pdf, varOf, crealms
 from climaf.cmacro import macro
 from climaf.driver import ceval, cfile, cshow, cMA, cvalue, cimport, cexport, calias, efile
 from climaf.dataloc import dataloc
 from climaf.operators import cscript, fixed_fields
 from climaf.operators_derive import derive
-from climaf.cache import craz, csync, cdump, cdrop, clist, cls, crm, cdu, cwc, cprotect, raz_cvalues
-from env.clogging import clogger, clog, clog_file, logdir
-from env.site_settings import atCNRM, onCiclad, atTGCC, atIDRIS, atIPSL, onSpip
+from climaf.cache import craz, csync, cdump, cdrop, clist, cls, crm, cdu, \
+    cwc, cprotect, raz_cvalues, ccost
 from climaf.plot.plot_params import plot_params, hovm_params
 from climaf.plot.varlongname import varlongname
+#
 from climaf.derived_variables import *
 from climaf.functions import *
 from climaf.easyCMIP_functions import *
-from env.environment import *
 
 
 #: Path for the CliMAF package. From here, can write e.g. ``cpath+"../scripts"``. The value shown in the doc is not
diff --git a/climaf/cache.py b/climaf/cache.py
index d76a1297..a8daa6c5 100644
--- a/climaf/cache.py
+++ b/climaf/cache.py
@@ -21,17 +21,14 @@
 import json
 from operator import itemgetter
 
+import env
 from env.environment import *
+from env.clogging import clogger
 from climaf import version
-from climaf.utils import Climaf_Cache_Error
-from climaf.classes import compare_trees, cobject, cdataset, guess_projects, allow_error_on_ds, ds
+from climaf.utils import Climaf_Cache_Error, Climaf_Error
+from climaf.classes import compare_trees, cobject, cdataset, guess_projects, allow_error_on_ds, ds, cens
 from climaf.cmacro import crewrite
-from env.clogging import clogger
 
-currentCache = None
-cachedirs = None
-#: The place to write the index
-cacheIndexFileName = None
 handle_cvalues = 'by_hash'  # Can be False, "by_crs" or anything else. 'by_crs' means key=CRS; else means key=hash
 cvalues = dict()
 #: The length for truncating the hash value of CRS expressions when forming cache filenames
@@ -58,14 +55,11 @@ def setNewUniqueCache(path, raz=True):
     """
     Define PATH as the sole cache to use from now. And clear it
     """
-    global currentCache
-    global cachedirs
-    global cacheIndexFileName
 
     path = os.path.expanduser(path)
-    cachedirs = [path]  # The list of cache directories
-    cacheIndexFileName = cachedirs[0] + "/index"  # The place to write the index
-    currentCache = cachedirs[0]
+    env.environment.cachedirs = [path]  # The list of cache directories
+    env.environment.cacheIndexFileName = path + "/index"  # The place to write the index
+    env.environment.currentCache = path
     if raz:
         craz(hideError=True)
 
@@ -79,6 +73,9 @@ def generateUniqueFileName(expression, format="nc", option="new", create_dirs=Tr
 
     Generated names drive a structure where each directory name
     has dirNameLength characters
+
+    If the filename already exists and is a symbolic link, check that the target 
+    actually exists, otherwise delete the link
     """
     #
     if format is None:
@@ -91,16 +88,27 @@ def generateUniqueFileName(expression, format="nc", option="new", create_dirs=Tr
         if create_dirs:
             dirn = os.path.dirname(rep)
             if not os.path.exists(dirn):
-                os.makedirs(dirn)
+                try:
+                    os.makedirs(dirn)
+                except OSError:
+                    if os.path.exists(dirn):
+                        # Happens when two concurrent CliMAF are
+                        # creating numerous cache files
+                        pass
+                    else:
+                        raise Climaf_Cache_Error("Cannot create dir " % dirn)
+        if os.path.islink(rep) and not os.path.exists(os.path.realpath(rep)):
+            os.remove(rep)
         clogger.debug("returning %s" % rep)
         return rep
 
 
 def hash_to_path(vhash, format, option="new", prefix=""):
-    if option == "new":
-        rep = os.sep.join([currentCache, prefix + vhash[0:2], vhash[2:]])
+    if option in ["new", ]:
+        rep = os.sep.join([env.environment.currentCache, prefix + vhash[0:2], vhash[2:]])
     else:
-        rep = os.sep.join([currentCache, prefix + stringToPath(vhash[0:fileNameLength - 1], directoryNameLength)])
+        rep = os.sep.join([env.environment.currentCache,
+                           prefix + stringToPath(vhash[0:fileNameLength - 1], directoryNameLength)])
     rep = ".".join([rep, format])
     rep = os.path.expanduser(rep)
     return rep
@@ -116,7 +124,7 @@ def alternate_filename(fpath):
     # Get file format
     format = fpath.split(".")[-1]
     # Remove cache root location prefix
-    relative_fpath = fpath[len(currentCache)+1:]
+    relative_fpath = fpath[len(env.environment.currentCache)+1:]
     # Get name without slashes nor extension
     vhash = relative_fpath.replace("/", "").split(".")[0]
     #
@@ -145,7 +153,7 @@ def searchFile(path):
     """ Search for first occurrence of PATH as a path in all
     directories listed in CACHEDIRS
     """
-    for cdir in cachedirs:
+    for cdir in env.environment.cachedirs:
         candidate = os.path.expanduser(cdir + "/" + path)
         if os.path.lexists(candidate):
             # If this is a broken link, delete it ~ silently and return None
@@ -156,7 +164,7 @@ def searchFile(path):
             return candidate
 
 
-def register(filename, crs, outfilename=None):
+def register(filename, crs, costs, outfilename=None):
     """
     Adds in FILE a metadata named 'CRS_def' and with value CRS, and a
     metadata 'CLiMAF' with CliMAF version and ref URL
@@ -168,13 +176,10 @@ def register(filename, crs, outfilename=None):
 
     Silently skip non-existing files
     """
-    # First read index from file if it is yet empty - No : done at startup
-    # if len(crs2filename.keys()) == 0 : cload()
-    # It appears that we have to let some time to the file system  for updating its inode tables
     def do_move(crs, filename, outfilename):
         if outfilename is None:
             clogger.info("%s registered as %s" % (crs, filename))
-            crs2filename[crs] = filename
+            crs2filename[crs] = (filename, costs)
             if crs in dropped_crs:
                 dropped_crs.remove(crs)
             return True
@@ -183,7 +188,7 @@ def do_move(crs, filename, outfilename):
             if os.system(cmd) == 0:
                 clogger.info("moved %s as %s " % (filename, outfilename))
                 clogger.info("%s registered as %s" % (crs, outfilename))
-                crs2filename[crs] = outfilename
+                crs2filename[crs] = (outfilename, costs)
                 if crs in dropped_crs:
                     dropped_crs.remove(crs)
                 return True
@@ -192,13 +197,14 @@ def do_move(crs, filename, outfilename):
                 raise Climaf_Cache_Error("cannot move (possibly after stamping) by %s" % cmd)
 
     global dropped_crs
+    #
+    # It appears that we have to let some time to the file system  for updating its inode tables
     waited = 0
     while waited < 50 and not os.path.exists(filename):
         time.sleep(0.1)
         waited += 1
-    # time.sleep(0.5)
     if not os.path.exists(filename):
-        raise Climaf_Cache_Error("File %s wasn't created upstream (or not quick enough)" % filename)
+        raise Climaf_Cache_Error("File %s wasn't created upstream (or not quick enough). It represents %s" % (filename, crs))
     else:
         if stamping is False:
             clogger.debug('No stamping')
@@ -283,10 +289,10 @@ def rename(filename, crs):
     crs2filename """
     newfile = generateUniqueFileName(crs, format="nc")
     if newfile:
-        for c in [f for f in crs2filename if crs2filename[f] in [filename, alternate_filename(filename)]]:
-            crs2filename.pop(c)
+        for c in [f for f in crs2filename if crs2filename[f][0] in [filename, alternate_filename(filename)]]:
+            fn, costs = crs2filename.pop(c)
         os.rename(filename, newfile)
-        register(newfile, crs)
+        register(newfile, crs, costs)
         return newfile
 
 
@@ -313,20 +319,19 @@ def op_squeezes_time(operator):
     for crs in crs2filename:
         # First, basic, screening
         if crs.split("(")[0] != cobject.crs.split("(")[0]:
-            return None, None
+            continue
         co = crs2eval.get(crs, None)
         if co is None:
             try:
                 co = eval(crs, sys.modules['__main__'].__dict__)
-                if co:
-                    crs2eval[crs] = co
             except:
-                pass  # usually case of a CRS which project is not currently defined
+                continue  # usually case of a CRS which project is not currently defined
         if co:
+            crs2eval[crs] = co
             clogger.debug("Compare trees for %s and %s" % (crs, cobject.crs))
             altperiod = compare_trees(co, cobject, ds_func, op_squeezes_time)
             if altperiod:
-                f = crs2filename[crs]
+                f, costs = crs2filename[crs]
                 if os.path.exists(f) or os.path.exists(alternate_filename(f)):
                     return co, altperiod
                 else:
@@ -369,10 +374,21 @@ def hasExactObject(cobject):
             else:
                 i += 1
     if found:
-        return f
-    elif cobject.crs in crs2filename:
-        clogger.debug("Dropping cobject.crs from cache index, because file is missing")
-        crs2filename.pop(cobject.crs)
+        crs = cobject.crs
+        # if isinstance(cobject, cdataset):
+        #    crs = "select(" + crs + ")"
+        if crs not in crs2filename:
+            clogger.warning("Next object exists in cache but was not "
+                            "registered in index. Assuming its compute cost is zero. "
+                            "Use cdrop if this is inadequate. Object %s" % crs)
+            crs2filename[crs] = (f, compute_cost())
+        return crs2filename[crs]
+        # return f
+    else:
+        if cobject.crs in crs2filename:
+            clogger.debug("Dropping cobject.crs from cache index, because file is missing")
+            crs2filename.pop(cobject.crs)
+        return None, compute_cost()
 
 
 def complement(crsb, crse, crs):
@@ -381,22 +397,28 @@ def complement(crsb, crse, crs):
     CRS. Assumes that everything is OK with args compatibility and
     file contents
     """
-    fileb = crs2filename[crsb]
+    fileb, costsb = crs2filename[crsb]
     if not os.path.exists(fileb):
         fileb = alternate_filename(fileb)
-    filee = crs2filename[crse]
+    filee, costse = crs2filename[crse]
     if not os.path.exists(filee):
         filee = alternate_filename(filee)
     filet = generateUniqueFileName(crs)
+    costs = compute_cost()
+    costs.add(costsb)
+    costs.add(costse)
+    tim1 = time.time()
     command = "ncrcat -O %s %s %s" % (fileb, filee, filet)
+    duration = time.time() - tim1
+    costs.increment(duration)
     if os.system(command) != 0:
         clogger.error("Issue when merging %s and %s in %s (using command:%s)" % (crsb, crse, crs, command))
-        return None
+        return None, costs
     else:
         cdrop(crsb)
         cdrop(crse)
-        register(filet, crs)
-        return filet
+        register(filet, crs, costs)
+        return filet, costs
 
 
 def cdrop(obj, rm=True, force=False):
@@ -433,14 +455,14 @@ def cdrop(obj, rm=True, force=False):
         return
     if crs in crs2filename:
         clogger.info("Discarding cached value for %s (except if protected)" % crs)
-        fil = crs2filename[crs]
+        fil, _ = crs2filename[crs]
         if not os.path.exists(fil):
             fil = alternate_filename(fil)
     else:
         # In case the cache index is not up-to-date
-        fil = hasExactObject(obj)
+        fil, cost = hasExactObject(obj)
         if fil:
-            crs2filename[crs] = fil
+            crs2filename[crs] = (fil, compute_cost())
     if fil:
         if rm:
             try:
@@ -484,7 +506,7 @@ def cprotect(obj, stop=False):
         clogger.error("%s is not a CliMAF object" % repr(obj))
         return
     if crs in crs2filename:
-        f = crs2filename[crs]
+        f, _ = crs2filename[crs]
         if not os.path.exists(f):
             f = alternate_filename(f)
         if stop is False:
@@ -515,6 +537,7 @@ def csync(update=False):
     import pickle
     global cacheIndexFileName
     global dropped_crs
+    global crs2filename
 
     # Merge index on file and index in memory
     file_index = cload(True)
@@ -534,7 +557,8 @@ def csync(update=False):
                 clogger.info("Rebuilding cache index from file content")
                 rebuild()
             else:
-                clogger.warning('In no stamp mode, there is no way to seriously identify CRS from files in cache  !')
+                clogger.warning('In no stamp mode, there is no way to seriously '
+                                'identify CRS from files in cache  !')
                 # clogger.warning('Removing cache files which content is not known.
                 # This is an issue in concurrent mode !')
                 # for fil in files_in_cache :
@@ -544,15 +568,10 @@ def csync(update=False):
                 # Should also remove empty files, as soon as
                 # file creation will be atomic enough
     # Save index to disk
-    fn = os.path.expanduser(cacheIndexFileName)
-    try:
-        with open(fn, "w") as cacheIndexFile:
-            pickle.dump(crs2filename, cacheIndexFile, protocol=2)  # Used for python 2 compatibility
-        dropped_crs = list()
-    except:
-        if update:
-            if os.path.isfile(fn) and len(files_in_cache > 0):
-                clogger.error("Issue when writing cache index %s" % fn)
+    fn = os.path.expanduser(env.environment.cacheIndexFileName)
+    cacheIndexFile = open(fn, "wb")
+    pickle.dump(crs2filename, cacheIndexFile, protocol=2)  # Used for python 2 compatibility
+    dropped_crs = list()
 
 
 def cload(alt=None):
@@ -561,22 +580,29 @@ def cload(alt=None):
     rep = dict()
 
     if len(crs2filename) != 0 and not alt:
-        Climaf_Cache_Error(
-            "attempt to reset file index - would lead to inconsistency !")
-    try:
-        cacheIndexFile = open(os.path.expanduser(cacheIndexFileName), "r")
-        if alt:
-            rep = pickle.load(cacheIndexFile)
+        raise Climaf_Cache_Error(
+            "attempt to reset cache index - would lead to inconsistency !")
+    cacheFilen = os.path.expanduser(env.environment.cacheIndexFileName)
+    if not os.path.exists(cacheFilen):
+        clogger.debug("no index file yet")
+        return {}
+    cacheIndexFile = open(cacheFilen, "rb")
+    rep = pickle.load(cacheIndexFile)
+    for c in rep:
+        f = rep[c]
+        if type(f) is tuple:
+            f, costs = f
         else:
-            crs2filename = pickle.load(cacheIndexFile)
-            for c in crs2filename:
-                f = crs2filename[c]
-                if len(f.split("/")[-2] == directoryNameLength):
-                    crs2filename[c] = alternate_filename(f)
-        cacheIndexFile.close()
-    except:
-        pass
-        # clogger.debug("no index file yet")
+            costs = compute_cost()
+        if len(f.split("/")[-2]) == directoryNameLength:
+            f = alternate_filename(f)
+        rep[c] = (f, costs)
+    #
+    if alt:
+        return rep
+    else:
+        crs2filename = rep
+    cacheIndexFile.close()
     #
     must_check_index_entries = False
     if must_check_index_entries:
@@ -585,7 +611,7 @@ def cload(alt=None):
         # undeclared
         crs_not_yet_evaluable = dict()
         allow_error_on_ds()
-        for crs in crs2filename.copy():
+        for crs in list(crs2filename.keys()):
             try:
                 # print "evaluating crs="+crs
                 eval(crs, sys.modules['__main__'].__dict__)
@@ -608,12 +634,6 @@ def cload(alt=None):
                         "Or you can erase corresponding data by 'crm(pattern=...project name...)'" %
                         (len(crs_not_yet_evaluable), repr(list(projects))))
         allow_error_on_ds(False)
-    if alt:
-        for c in rep:
-            f = rep[c]
-            if len(f.split("/")[-2]) == directoryNameLength:
-                rep[c] = alternate_filename(f)
-        return rep
 
 
 def cload_for_project(project):
@@ -642,8 +662,8 @@ def craz(force=False, hideError=False):
 
     """
     global crs2filename
-    cc = os.path.expanduser(currentCache)
-    if os.path.exists(currentCache) or hideError is False:
+    cc = os.path.expanduser(env.environment.currentCache)
+    if os.path.exists(env.environment.currentCache) or hideError is False:
         if force:
             os.system("chmod -R +w  " + cc)
             os.system("rm -fR " + cc + "/*")
@@ -681,11 +701,11 @@ def list_cache():
 
     """
     find_return = ""
-    for dir_cache in cachedirs:
+    for dir_cache in env.environment.cachedirs:
         rep = os.path.expanduser(dir_cache)
-        find_return += os.popen(
-            r"find %s -type f \( -name '*.png' -o -name '*.nc' -o -name '*.pdf' -o -name '*.eps' \) -print" % rep
-        ).read()
+        filter = r" \( -name '*.png' -o -name '*.nc' -o -name '*.pdf' -o -name '*.eps' \) "
+        with os.popen(r"find %s -type f " % rep + filter + " -print") as fil:
+            find_return += fil.read()
     files_in_cache = find_return.split('\n')
     files_in_cache.pop(-1)
     return files_in_cache
@@ -795,8 +815,8 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
         # Search CRS for each found file
         for filen in list_search_files_after_find:
             for crs in crs2filename:
-                if crs2filename[crs] == filen:
-                    new_dict[crs] = filen
+                if crs2filename[crs][0] == filen:
+                    new_dict[crs] = crs2filename[crs]
 
         if len(new_dict) != 0:
             if new_dict != crs2filename:
@@ -814,7 +834,7 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
     # filter on pattern
     find_pattern = False
     if pattern:
-        list_crs_to_rm = []
+        list_crs_to_rm = list()
         for crs in new_dict:
             if re.search(pattern, crewrite(crs)) or re.search(pattern, new_dict[crs]):
                 clogger.debug("Pattern found in %s: %s" % (crs, new_dict[crs]))
@@ -856,7 +876,7 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
     len_new_dict = len(new_dict)
 
     # request on new dictionary through usage, count and remove
-    work_dic = new_dict if (var_find or pattern is not "" or not_pattern is not "") else crs2filename
+    work_dic = new_dict if (var_find or pattern != "" or not_pattern != "") else crs2filename
 
     if usage is True and len_new_dict != 0:
         # construction of a dictionary containing crs and disk-usage associated
@@ -911,7 +931,7 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
 
     elif remove is True and len_new_dict != 0:
         print("Removed files:")
-        if var_find or pattern is not "" or not_pattern is not "":
+        if var_find or pattern != "" or not_pattern != "":
             list_tmp_crs = list(new_dict)
         else:
             list_tmp_crs = list(crs2filename)
@@ -920,7 +940,7 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
         return list(map(crewrite, list_tmp_crs))
 
     else:  # usage, count and remove are False
-        if var_find or pattern is not "" or not_pattern is not "":
+        if var_find or pattern != "" or not_pattern != "":
             if len(new_dict) != 0:
                 if new_dict != crs2filename:
                     print("Filtered objects :")
@@ -936,7 +956,7 @@ def clist(size="", age="", access=0, pattern="", not_pattern="", usage=False, co
     if special is True:
         global dic_special
         dic_special = dict()
-        if var_find is True or pattern is not "" or not_pattern is not "":
+        if var_find is True or pattern != "" or not_pattern != "":
             dic_special = new_dict.copy()
         else:
             dic_special = crs2filename.copy()
@@ -1025,14 +1045,14 @@ def rebuild():
     for files in files_in_cache:
         filecrs = getCRS(files)
         if filecrs:
-            crs2filename[filecrs] = files
+            crs2filename[filecrs] = (files, compute_cost())
         else:
             os.system('rm -f ' + files)
             clogger.warning("File %s is removed" % files)
     return crs2filename
 
 
-def store_cvalue(crs, index, value):
+def store_cvalue(crs, index, value, costs):
     """
     Stores a scalar, as computed by cvalue, in a scalars cache
     """
@@ -1041,7 +1061,7 @@ def store_cvalue(crs, index, value):
             key = hashlib.sha224((crs + "%d" % index).encode("utf-8")).hexdigest()
         else:
             key = crs + "[%d]" % index
-        cvalues[key] = value
+        cvalues[key] = value, costs
 
 
 def has_cvalue(crs, index):
@@ -1053,7 +1073,13 @@ def has_cvalue(crs, index):
             key = hashlib.sha224((crs + "%d" % index).encode("utf-8")).hexdigest()
         else:
             key = crs + "[%d]" % index
-        return cvalues.get(key, None)
+        value = cvalues.get(key, None)
+        if value is not None:
+            if not type(value) is tuple:
+                return value, compute_cost()
+            else:
+                return value
+    return None, compute_cost()
 
 
 def load_cvalues():
@@ -1063,10 +1089,12 @@ def load_cvalues():
     global cvalues
 
     if handle_cvalues is not False:
-        cache_file = os.sep.join([currentCache, "cvalues.json"])
+        cache_file = os.sep.join([env.environment.currentCache, "cvalues.json"])
         if os.path.exists(cache_file):
             with open(cache_file, "r") as f:
-                cvalues = json.load(f)
+                tmp = json.load(f)
+            cvalues = decode_index(tmp)
+            del tmp
 
 
 def sync_cvalues():
@@ -1077,21 +1105,25 @@ def sync_cvalues():
     global cvalues
 
     if handle_cvalues is not False:
+        currentCache = env.environment.currentCache
         if not os.path.isdir(currentCache):
             os.makedirs(currentCache)
         ccache = os.path.sep.join([currentCache, "cvalues.json"])
-        tmp = os.path.sep.join([currentCache, "cvalues_tmp.json"])
         #
-        try:
-            # Try to get pre-existing on-disk content
+        if os.path.exists(ccache):
+            # get pre-existing on-disk content
             with open(ccache, "r") as f:
-                onfile = json.load(f)
+                encoded = json.load(f)
+            onfile = decode_index(encoded)
+            del encoded
             onfile.update(cvalues)
+            del cvalues
             cvalues = onfile
-        except:
-            pass
+        tmp = ccache.replace(".json", ".tmp")
         with open(tmp, "w") as f:
-            json.dump(cvalues, f, separators=(',', ': '), indent=3, ensure_ascii=True)
+            tofile = encode_index(cvalues)
+            json.dump(tofile, f, separators=(',', ': '), indent=3, ensure_ascii=True)
+            del tofile
         os.rename(tmp, ccache)
 
 
@@ -1106,3 +1138,99 @@ def raz_cvalues():
         sync_cvalues()
 
 
+class compute_cost(object):
+    """
+    Handling compute costs for objects : 
+    - tc : a cost for all compute operations involved in object's genesis
+    - lc : another cost, for the last( top-level) operation
+    """
+    def __init__(self, cost=0., total_cost=None):
+        """
+        Create a compute_cost object. 
+        COST -> last_operation_cost 
+        TOTAL_COST -> total cost, defaukts to COST
+        """
+        # Attribute names are kept compact for saving on index size
+        # through pickle.dump
+        self.lc = cost
+        if total_cost is None:
+            total_cost = cost
+        self.tc = total_cost
+        
+    def __repr__(self):
+        return "total cost : %.1f s, last operation : %.1f s" % (self.tc, self.lc)
+
+    def add(self, other):
+        """Just sum up total costs with another cost object. Also sum up last
+        operation costs, but this is questionnable, as you could
+        consider that summing up costs implies there is some new 'last
+        operation' around
+        """
+        self.tc = self.tc + other.tc
+        self.lc = self.lc + other.lc
+
+    def increment(self, cost):
+        """Assuming that COST is the cost of a new 'last operation', updates 
+        object for representing that"""
+        self.tc = self.tc + cost
+        self.lc = cost
+        
+
+def ccost(cobject):
+    """Provide the compute costs (elapsed time) for an object 
+
+    Returned value is of class cost, which has fields tc (for
+    total_cost) and lc (for last_op_cost):
+
+    - tc is the sum of all compute operations that lead to the object
+    - lc is the cost for the last operation (top level)
+
+    When a single operation provides multiple outputs, all outputs are
+    charged with the cost of that operation (cost is not shared). This
+    may lead to an overestimated cost when such outputs are re-used
+    together in further operations
+
+    """
+    if cobject.crs in crs2filename:
+        return crs2filename[cobject.crs][1]
+    else:
+        if type(cobject) is cens:
+            cost = compute_cost()
+            for member in cobject:
+                if cobject[member].crs in crs2filename:
+                    cost.add(crs2filename[cobject[member].crs][1])
+                else:
+                    raise Climaf_Error("At least one ensemble member is not cached (%s)" % member)
+            return cost
+        else: 
+            clogger.warning("Object is not (yet) cached; try cfile()")
+            return None
+    
+
+def encode_index(values):
+    """ Encode a dict like crs2filename, i.e. a dict of crs : (filename, cost)
+    in a structure compatible with Json encoding"""
+    ret = dict()
+    for crs in values:
+        if type(values[crs]) is tuple:
+            fn, cost = values[crs]
+        else:
+            fn = values[crs]
+            cost = 0.
+        ret[crs] = (fn, cost.lc, cost.tc)
+    return ret
+
+
+def decode_index(values):
+    """Decode a dict of triplets in a a dict like crs2filename, i.e. a
+    dict of crs : (filename, cost)
+    """
+    ret = dict()
+    for crs in values:
+        if type(values[crs]) is list:
+            fn, last_op_cost, total_cost = values[crs]
+        else:
+            fn = values[crs]
+            last_op_cost, total_cost = 0., 0.
+        ret[crs] = (fn, compute_cost(last_op_cost, total_cost))
+    return ret
diff --git a/climaf/classes.py b/climaf/classes.py
index 36b25c7d..13d4bd9a 100644
--- a/climaf/classes.py
+++ b/climaf/classes.py
@@ -18,9 +18,10 @@
 import six
 
 from env.environment import *
-from climaf.utils import Climaf_Classes_Error
+from climaf.utils import Climaf_Classes_Error, remove_keys_with_same_values
 from climaf.dataloc import isLocal, getlocs, selectFiles, dataloc
-from climaf.period import init_period, cperiod, merge_periods, intersect_periods_list, lastyears, firstyears
+from climaf.period import init_period, cperiod, merge_periods, intersect_periods_list,\
+    lastyears, firstyears, group_periods
 from env.clogging import clogger
 from climaf.netcdfbasics import fileHasVar, varsOfFile, timeLimits, model_id
 
@@ -150,6 +151,25 @@ def crs2ds(self, crs):
                     kvp[f] = fields[i]
                 return cdataset(**kvp)
 
+    def cvalid(self, attribute, value=None):
+        """Set or get the list of valid values for a CliMAF dataset attribute
+        or facet (such as e.g. 'model', 'simulation' ...). Useful
+        e.g. for constraining those data files which match a dataset
+        definition
+
+        Example::
+
+        >>> cvalid('grid' , [ "gr", "gn", "gr1", "gr2" ])
+
+        """
+        #
+        if attribute not in self.facets:
+            raise Climaf_Classes_Error("project '%s' doesn't use facet '%s'" % (project, attribute))
+        if value is None:
+            return self.facet_authorized_values.get(attribute, None)
+        else:
+            self.facet_authorized_values[attribute] = value
+
 
 def cdef(attribute, value=None, project=None):
     """
@@ -219,6 +239,14 @@ def erase(self):
     def buildcrs(self):
         raise NotImplementedError
 
+    def __eq__(self, other):
+        """
+        Check the equality of two CliMAF objects.
+        :param other: CliMAF object to be compared
+        :return: boolean indicating whether the CliMAF objects are the same or not
+        """
+        return isinstance(other, type(self)) and self.crs == other.crs
+
 
 class cdummy(cobject):
     def __init__(self):
@@ -344,7 +372,10 @@ def __init__(self, **kwargs):
 
         Some attributes have a special format or processing :
 
-        - period : see :py:func:`~climaf.period.init_period`
+        - period : see :py:func:`~climaf.period.init_period`. See also
+          function :py:func:`climaf.classes.ds` for added
+          flexibility in defining periods as last of first set of years
+          among available data
 
         - domain : allowed values are either 'global' or a list for
           latlon corners ordered as in : [ latmin, latmax, lonmin,
@@ -408,6 +439,20 @@ def __init__(self, **kwargs):
         self.local_copies_of_remote_files = None
         self.register()
 
+    def __eq__(self, other):
+        res = super(cdataset, self).__eq__(other)
+        if res:
+            self_kvp = copy.deepcopy(self.kvp)
+            self_kvp["model"] = self.model
+            self_kvp["frequency"] = self.frequency
+            self_kvp["alias"] = self.alias
+            other_kvp = copy.deepcopy(other.kvp)
+            other_kvp["model"] = other.model
+            other_kvp["frequency"] = other.frequency
+            other_kvp["alias"] = other.alias
+            res = res and all([self_kvp[p] == other_kvp[p] for p in self_kvp])
+        return res
+
     def setperiod(self, period):
         if isinstance(period, six.string_types):
             period = init_period(period)
@@ -513,6 +558,107 @@ def matches_conditions(self, conditions):
                 return False
         return True
 
+    def check_if_dict_ambiguous(self, input_dict):
+        ambiguous_dict = dict()
+        non_ambigous_dict = dict()
+        for (kw, val) in input_dict.items():
+            if isinstance(val, list):
+                if len(val) > 1:
+                    ambiguous_dict[kw] = val
+                else:
+                    non_ambigous_dict[kw] = val[0]
+            elif kw in ['variable', ]:  # Should take care of aliasing to fileVar
+                matching_vars = set()
+                paliases = aliases.get(self.project, [])
+                for variable in paliases:
+                    if val == paliases[variable][0]:
+                        matching_vars.add(variable)
+                if len(matching_vars) == 0:
+                    # No filename variable in aliases matches actual filename
+                    non_ambigous_dict[kw] = val
+                elif len(matching_vars) == 1:
+                    # One variable has a filename variable which matches the retrieved filename
+                    non_ambigous_dict[kw] = matching_vars[0]
+                else:
+                    ambiguous_dict[kw] = (val, matching_vars)
+            else:
+                non_ambigous_dict[kw] = val
+        return non_ambigous_dict, ambiguous_dict
+
+    def glob(self, what=None, periods=None, split=None):
+        """Datafile exploration for a dataset which possibly has
+        wildcards (* and ?) in attributes/facets.
+
+        Returns info regarding matching datafile or directories:
+
+          - if WHAT = 'files' , returns a string of all data filenames
+
+          - otherwise, returns a list of facet/value dictionnaries for
+            matching data (or a pair, see below)
+
+        In last case, data file periods are not returned if arg
+        PERIODS is None and data search is optimized for the project.
+        In that case, the globbing is done on data directories and not
+        on data files, which is much faster.
+
+        If PERIODS is not None, individual data files periods are
+        merged among cases with same facets values
+
+        if SPLIT is not None, a pair is returned intead of the dicts list :
+
+           - first element is a dict with facets which values are the
+             same among all cases
+
+           - second element is the dicts list as above, but in which
+             facets with common values are discarded
+
+        Example :
+
+        >>> tos_data = ds(project='CMIP6', variable='tos', period='*',
+               table='Omon', model='CNRM*', realization='r1i1p1f*' )
+
+        >>> common_keys, varied_keys = tos_data.glob(periods=True, split=True)
+
+        >>> common_keys
+        {'mip': 'CMIP', 'institute': 'CNRM-CERFACS', 'experiment': 'historical',
+        'realization': 'r1i1p1f2', 'table': 'Omon', 'variable': 'tos',
+        'version': 'latest', 'period': [1850-2014], 'root': '/bdd'}
+
+        >>> varied_keys
+        [{'model': 'CNRM-ESM2-1'  , 'grid': 'gn' },
+         {'model': 'CNRM-ESM2-1'  , 'grid': 'gr1'},
+         {'model': 'CNRM-CM6-1'   , 'grid': 'gn' },
+         {'model': 'CNRM-CM6-1'   , 'grid': 'gr1'},
+         {'model': 'CNRM-CM6-1-HR', 'grid': 'gn' } ]
+
+        """
+        dic = self.kvp.copy()
+        if self.alias:
+            filevar, _, _, _, filenameVar, _, conditions = self.alias
+            req_var = dic["variable"]
+            dic["variable"] = string.Template(filevar).safe_substitute(dic)
+            if filenameVar:
+                dic["filenameVar"] = filenameVar
+        clogger.debug("glob() with dic=%s" % repr(dic))
+        cases = list()
+        files = selectFiles(with_periods=(periods is not None or what in ['files', ]),
+                            return_combinations=cases, **dic)
+        if what in ['files', ]:
+            return files
+        else:
+            if periods is not None:
+                cases = group_periods(cases)
+            else:
+                # For non-optimized cases, select_files returns periods,
+                # but we want an even behaviour
+                for case in cases:
+                    case.pop('period', None)
+            if split is not None:
+                keys = remove_keys_with_same_values(cases)
+                return keys, cases
+            else:
+                return cases
+
     def explore(self, option='check_and_store', group_periods_on=None, operation='intersection', first=None):
         """
         Versatile datafile exploration for a dataset which possibly has wildcards (* and ? ) in
@@ -682,42 +828,26 @@ def explore(self, option='check_and_store', group_periods_on=None, operation='in
                         periods[key] = merge_periods(periods[key])
                 wildcards['period'] = periods
             else:
-                raise Climaf_Classes_Error("Operation %s is not kown " % operation)
+                raise Climaf_Classes_Error("Operation %s is not known " % operation)
         #
         wildcard_attributes_list = [k for k in dic if isinstance(dic[k], six.string_types) and "*" in dic[k]]
         if option in ['resolve', ]:
             clogger.debug("Trying to resolve on attributes %s" % wildcard_attributes_list)
-            for kw in wildcards:
-                val = wildcards[kw]
-                if isinstance(val, list):
-                    if len(val) > 1:
-                        if kw in ['period', ]:
-                            raise Climaf_Classes_Error("Periods with holes are not handled %s" % val)
-                        else:
-                            raise Climaf_Classes_Error("Wildcard attribute %s is ambiguous %s" % (kw, val))
+            non_ambiguous_dict, ambiguous_dict = self.check_if_dict_ambiguous(wildcards)
+            if len(ambiguous_dict) != 0:
+                error_msg = list()
+                for kw in sorted(list(ambiguous_dict)):
+                    if kw in ["variable", ]:
+                        error_msg.append("Filename variable %s is matched by multiple variables %s" %
+                                         (ambiguous_dict[kw][0], repr(ambiguous_dict[kw][1])))
+                    elif kw in ["period", ]:
+                        error_msg.append("Periods with holes are not handled: %s" % str(ambiguous_dict[kw]))
                     else:
-                        val = val[0]
-                        dic[kw] = val
-                else:
-                    if kw in ['variable', ]:  # Should take care of aliasing to fileVar
-                        matching_vars = set()
-                        paliases = aliases.get(self.project, [])
-                        for variable in paliases:
-                            if val == paliases[variable][0]:
-                                matching_vars.add(variable)
-                        if len(matching_vars) == 0:
-                            # No filename variable in aliases matches actual filename
-                            dic[kw] = val
-                        elif len(matching_vars) == 1:
-                            # One variable has a filename variable whih matches the retrieved filename
-                            dic[kw] = matching_vars.pop()
-                        else:
-                            raise Climaf_Classes_Error("Filename variable %s is matched by multiple variables %s" %
-                                                       (val, repr(matching_vars)))
-                    else:
-                        dic[kw] = val
-            #
-            return ds(**dic)
+                        error_msg.append("Wildcard attribute %s is ambiguous %s" % (kw, str(ambiguous_dict[kw])))
+                raise Climaf_Classes_Error(" ".join(error_msg))
+            else:
+                dic.update(**non_ambiguous_dict)
+                return ds(**dic)
         elif option in ['choices', ]:
             clogger.debug("Listing possible values for these wildcard attributes %s" % wildcard_attributes_list)
             self.files = files
@@ -759,13 +889,18 @@ def baseFiles(self, force=False, ensure_dataset=True):
         for the dataset
 
         Use cached value (i.e. attribute 'files') unless called with arg force=True
+
         If ensure_dataset is True, forbid ambiguous datasets
         """
         if (force and self.project != 'file') or self.files is None:
             if ensure_dataset:
                 self.explore()
             else:
-                self.explore(option='choices')
+                cases = self.explore(option='choices')
+                list_keys = [k for k in cases if type(cases[k]) is list and k != 'period']
+                if len(list_keys) > 0:
+                    clogger.error("The dataset is ambiguous on %s; its CRS is %s" % (cases, self))
+                    return None
         return self.files
 
     def listfiles(self, force=False, ensure_dataset=True):
@@ -773,6 +908,7 @@ def listfiles(self, force=False, ensure_dataset=True):
         for the dataset
 
         Use cached value unless called with arg force=True
+
         If ensure_dataset is True, forbid ambiguous datasets
         """
         return self.baseFiles(force=force, ensure_dataset=ensure_dataset)
@@ -1076,6 +1212,12 @@ def __init__(self, dic={}, order=None, sortfunc=None):
         self.crs = self.buildcrs()
         self.register()
 
+    def __eq__(self, other):
+        res = super(cens, self).__eq__(other)
+        if res:
+            res = res and self.order == other.order and all([self.__dict__[m] == other.__dict[m] for m in self.order])
+        return res
+
     def set_order(self, order, ordered_keylist=None):
         ordered_list = [o for o in order]
         ordered_list.sort()
@@ -1316,10 +1458,15 @@ def __init__(self, climaf_operator, script, *operands, **parameters):
         """ Builds the tree of a composed object, including a dict for outputs.
 
         """
+        if len(operands) == 0 :
+            raise Climaf_Classes_Error("Cannot apply an operator to no operand")
         self.operator = climaf_operator
         self.script = script
         import copy
-        self.flags = copy.copy(script.flags)
+        if script is None :
+            self.flags = False
+        else:
+            self.flags = copy.copy(script.flags)
         self.operands = operands
         if "period" in parameters:
             p = parameters["period"]
@@ -1764,7 +1911,7 @@ def __init__(self, fig_lines=None, widths=None, heights=None,
                  orientation=None,
                  page_width=1000., page_height=1500., title="", x=0, y=26, ybox=50, pt=24,
                  font="Times-New-Roman", gravity="North", background="white",
-                 insert="", insert_width=0):
+                 insert="", insert_width=200):
         """
         Builds a CliMAF cpage object, which represents an array of figures (output:
         'png' or 'pdf' figure)
@@ -2099,6 +2246,12 @@ def modelOf(cobject): return attributeOf(cobject, "model")
 def simulationOf(cobject): return attributeOf(cobject, "simulation")
 
 
+def experimentOf(cobject): return attributeOf(cobject, "experiment")
+
+
+def realizationOf(cobject): return attributeOf(cobject, "realization")
+
+
 def projectOf(cobject): return attributeOf(cobject, "project")
 
 
@@ -2145,6 +2298,36 @@ def attributeOf(cobject, attrib):
         raise Climaf_Classes_Error("Unknown class for argument " + repr(cobject))
 
 
+def timePeriod(cobject):
+    """ Returns a time period for a CliMAF object : if object is a dataset, returns
+    its time period, otherwise analyze complex case and reurns something sensible
+    """
+    if isinstance(cobject, cdataset):
+        return cobject.period
+    elif isinstance(cobject, ctree):
+        clogger.debug("timePeriod : processing %s,operands=%s" % (cobject.script, repr(cobject.operands)))
+        if cobject.script.flags.doCatTime and len(cobject.operands) > 1:
+            clogger.debug("Building composite period for results of %s" % cobject.operator)
+            periods = [timePeriod(op) for op in cobject.operands]
+            merged_period = merge_periods(periods)
+            if len(merged_period) > 1:
+                raise Climaf_Driver_Error("Issue when time assembling with %s, periods are not consecutive : %s" %
+                                          (cobject.operator, merged_period))
+            return merged_period[0]
+        else:
+            clogger.debug("timePeriod logic for script is 'choose 1st operand' %s" % cobject.script)
+            return timePeriod(cobject.operands[0])
+    elif isinstance(cobject, scriptChild):
+        clogger.debug("for now, timePeriod logic for scriptChilds is basic - TBD")
+        return timePeriod(cobject.father)
+    elif isinstance(cobject, cens):
+        clogger.debug("for now, timePeriod logic for 'cens' objet is basic (1st member)- TBD")
+        return timePeriod(list(cobject.values())[0])
+    else:
+        return None  # clogger.error("unkown class for argument "+`cobject`)
+
+
+
 def resolve_first_or_last_years(kwargs, duration, option="last"):
     # Returns a dataset after translation of period like 'last_50y'
     kwargs['period'] = '*'
diff --git a/climaf/cmacro.py b/climaf/cmacro.py
index f8cfb595..f7ec68e2 100644
--- a/climaf/cmacro.py
+++ b/climaf/cmacro.py
@@ -17,9 +17,9 @@
 import types
 import six
 
-from .classes import cobject, cdataset, ctree, scriptChild, cpage, allow_error_on_ds, cens, cdummy
 from env.clogging import clogger, dedent
 from env.environment import *
+from climaf.classes import cobject, cdataset, ctree, scriptChild, cpage, allow_error_on_ds, cens, cdummy
 
 
 def macro(name, cobj, lobjects=[]):
diff --git a/climaf/dataloc.py b/climaf/dataloc.py
index d98e158e..3a5f9a4d 100644
--- a/climaf/dataloc.py
+++ b/climaf/dataloc.py
@@ -10,24 +10,22 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 import six
 import os.path
 import re
 import glob
-import subprocess
 from string import Template
-import ftplib as ftp
-import getpass
-import netrc
-from functools import partial
 
+import env
 from env.environment import *
-from climaf.utils import Climaf_Error, Climaf_Classes_Error, Climaf_Data_Error
-from climaf.period import init_period, sort_periods_list
-from climaf.netcdfbasics import fileHasVar
 from env.clogging import clogger
 
+from climaf.utils import Climaf_Error
+from climaf.period import init_period
+from climaf.netcdfbasics import fileHasVar
+from climaf.projects.optimize import cmip6_optimize_check_paths, cmip6_optimize_wildcards
+from climaf.find_files import selectGenericFiles
+
 
 class dataloc(object):
     def __init__(self, project="*", organization='generic', url=None, model="*", simulation="*",
@@ -97,7 +95,7 @@ def __init__(self, project="*", organization='generic', url=None, model="*", sim
                          CNRM's Lustre
            - EM : CNRM-CM post-processed outputs as organized using EM (please use a list of anyone string for arg urls)
            - generic : a data organization described by the user, using patterns such as described for
-             :py:func:`~climaf.dataloc.selectGenericFiles`. This is the default
+             :py:func:`~climaf.select_files.selectGenericFiles`. This is the default
 
            Please ask the CliMAF dev team for implementing further organizations.
            It is quite quick for data which are on the filesystem. Organizations
@@ -116,7 +114,7 @@ def __init__(self, project="*", organization='generic', url=None, model="*", sim
 
         For the sake of brievity, each attribute can have the '*'
         wildcard value; when using the dictionary, the most specific
-        entries will be used (whic means : the entry (or entries) with the lowest number of wildcards)
+        entries will be used (which means : the entry (or entries) with the lowest number of wildcards)
 
         Example :
 
@@ -244,7 +242,7 @@ def isLocal(project, model, simulation, frequency, realm="*", table="*"):
     return rep
 
 
-def selectFiles(return_wildcards=None, merge_periods_on=None, **kwargs):
+def selectFiles(return_wildcards=None, merge_periods_on=None, return_combinations=None, with_periods=None, **kwargs):
     """
     Returns the shortest list of (local or remote) files which include
     the data for the list of (facet,value) pairs provided
@@ -278,9 +276,14 @@ def selectFiles(return_wildcards=None, merge_periods_on=None, **kwargs):
     if len(ofu) == 0:
         clogger.warning("no datalocation found for %s %s %s %s  %s %s " % (project, model, simulation, frequency, realm,
                                                                            table))
-    for org, freq, urls in ofu:
+    for org, _, urls in ofu:
+        if org not in ['generic', ]:
+            clogger.warning("Organisation = %s will be deprecated quite soon." % org +
+                            "Please refer to you CliMAF wizard for removing its use")
         if return_wildcards is not None and len(return_wildcards) > 0 and org != "generic":
             raise Climaf_Error("Can handle multiple facet query only for organization=generic ")
+        if return_combinations is not None and len(return_combinations) > 0 and org != "generic":
+            raise Climaf_Error("Can handle multiple facet query only for organization=generic ")
         kwargs2 = kwargs.copy()
         # Convert normalized frequency to project-specific frequency if applicable
         if "frequency" in kwargs and project in frequencies:
@@ -294,645 +297,54 @@ def selectFiles(return_wildcards=None, merge_periods_on=None, **kwargs):
                 kwargs2['realm'] = realms[project][normrealm]
         #
         # Call organization-specific routine
-        if org == "EM":
+        if org in ["EM", ]:
             rep.extend(selectEmFiles(**kwargs2))
-        elif org == "CMIP5_DRS":
+        elif org in ["CMIP5_DRS", ]:
             rep.extend(selectCmip5DrsFiles(urls, **kwargs2))
-        elif org == "generic":
-            rep.extend(selectGenericFiles(urls, return_wildcards=return_wildcards,
-                                          merge_periods_on=merge_periods_on, **kwargs2))
+        elif org in ["generic", ]:
+            if project in ["CMIP6", ] and env.environment.optimize_cmip6_wildcards and \
+               cmip6_optimize_check_paths(urls):
+                kwargs_list = cmip6_optimize_wildcards(kwargs2)
+                if not with_periods and return_combinations is not None:
+                    # Just return the list of dicts with facet values combinations
+                    return_combinations.extend(kwargs_list)
+                    rep.append('dummy')
+                else:
+                    if return_combinations is None:
+                        # Also for glob, but must get periods
+                        clogger.warning("cdataset.explore doesn't anymore return choices with  "
+                                        "optimized CMIP6 search. Use cdataset.glob() or set "
+                                        "env.environment.optimize_cmip6_wildcards to False")
+                    for kwa in kwargs_list:
+                        rep.extend(selectGenericFiles(urls, return_combinations=return_combinations, **kwa))
+            else:
+                rep.extend(selectGenericFiles(urls, return_wildcards=return_wildcards,
+                                              return_combinations=return_combinations,
+                                              merge_periods_on=merge_periods_on,
+                                              **kwargs2))
         else:
             raise Climaf_Error("Cannot process organization " + org + " for simulation " + simulation + " and model " +
                                model + " of project " + project)
     if not ofu:
         return None
+    elif len(rep) == 0:
+        clogger.warning("no file found for %s, at these "
+                        "data locations %s " % (repr(kwargs), repr(urls)))
+        clogger.warning("i.e. at " + str([url.replace("${PERIOD}", "$PERIOD").replace("$", "").format(**kwargs)
+                                          for url in urls]))
+        if env.environment.optimize_cmip6_wildcards:
+            clogger.warning("If you think this may be due to fresh data ingest, "
+                            "you may wish to reset some of the tables used in "
+                            "optimizing CMIP6 data search. See help(climaf.projects.optimize.clear_tables)")
+        return None
     else:
-        if len(rep) == 0:
-            clogger.warning("no file found for %s, at these "
-                            "data locations %s " % (repr(kwargs), repr(urls)))
-            if any([kwargs[k] == '' for k in kwargs]):
-                clogger.warning("Please check these empty attributes %s" % [k for k in kwargs if kwargs[k] == ''])
-            return None
-    # Discard duplicates (assumes that sorting is harmless for later processing)
-    rep = sorted(list(set([f.strip() for f in rep])))
-    # Assemble filenames in one single string
-    return ' '.join(rep)
-
-
-def selectGenericFiles(urls, return_wildcards=None, merge_periods_on=None, **kwargs):
-    """
-    Allow to describe a ``generic`` file organization : the list of files returned
-    by this function is composed of files which :
-
-     - match the patterns in ``url`` once these patterns are instantiated by
-        the values in kwargs, and
-
-     - contain the ``variable`` provided in kwargs
-
-     - match the `period`` provided in kwargs
-
-    In the pattern strings, no keyword is mandatory. However, for remote files,
-    filename pattern must include ${varname}, which is instanciated by variable
-    name or ``filenameVar`` (given via :py:func:`~climaf.classes.calias()`); this is
-    for the sake of efficiency (please complain if inadequate)
-
-    Example :
-
-    >>> selectGenericFiles(project='my_projet',model='my_model', simulation='lastexp', variable='tas', period='1980',
-    ...                    urls=['~/DATA/${project}/${model}/*${variable}*${PERIOD}*.nc)']
-
-    /home/stephane/DATA/my_project/my_model/somefilewith_tas_Y1980.nc
-
-    In the pattern strings, the keywords that can be used in addition to the argument
-    names (e.g. ${model}) are:
-
-    - ${variable} : use it if the files are split by variable and
-        filenames do include the variable name, as this speed up the search
-
-    - ${PERIOD} : use it for indicating the period covered by each file, if this
-        is applicable in the file naming; this period can appear in filenames as
-        YYYY, YYYYMM, YYYYMMDD, YYYYMMDDHHMM, either once only, or twice with
-        separator ='-' or '_'
-
-    - wildcards '?' and '*' for matching respectively one and any number of characters
-
-
-    Résumé en francais :
-
-    - On construit une expression régulière pour matcher les périodes
-
-    - On boucle sur les patterns de la liste url :
-
-        - Instancier le pattern par les valeurs des facettes fournies, et par  ".*" pour $PERIOD
-
-        - on fait glob.glob
-
-        - on affine : on ne retient que les valeurs qui matchent avec la regexp de périodes (sous
-          réserve que le pattern contienne $PERIOD) si on n'a rien, on essaie aussi
-          avec filenameVar; d'où une liste de fichiers lfiles
-
-        - on cherche a connaitre les valeurs rencontrées pour chaque facette : on construit
-          une expression régulière (avec groupes) qui capture les valeurs de facettes
-          (y/c PERIOD) et une autre pour capturer la date seulement (est-ce bien encore
-          nécessaire ???)
-
-        - Boucle sur les fichiers de lfiles:
-
-            - si le pattern n'indique pas qu'on peut extraire la date,
-
-                - si la frequence indique un champ fixe, on retient le fichier;
-
-                - sinon , on le retient aussi sans filtrer sur la période
-
-            - si oui,
-
-                - on extrait la periode
-
-                - si elle convient (divers cas ...)
-
-                - si on a pu filtrer sur la variable,
-                    ou que variable="*" ou variable multiple,
-                    ou que le fichier contient la bonne variable, eventuellement après renommage
-                    on retient le fichier
-
-            - A chaque fois qu'on retient un fichier , on ajoute au dict wildcard_facets les valeurs recontrées pour les
-              attributs
-
-        - Dès qu'un pattern de la  liste url a eu des fichiers qui collent, on abandonne l'examen des patterns suivants
-
-    - A la fin , on formatte le dictionnaire de valeurs de facettes qui est rendu
-
-    """
-
-    def store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on=None,
-                                    fperiod=None, periods=None, periods_dict=None):
-        """
-        Using a (groups-capable) regexp FACETS_REGEXP for finding facet values, analyze
-        string F for finding the value of each keyword (facet name) in KWARGS, and stores
-        it in dict WILDCARDS, which keys are facet names and values are set of encountered
-        values
-        Regarding periods, ... (TBD)
-        """
-        if fperiod is not None and periods is not None:
-            clogger.debug('Adding period %s' % fperiod)
-            periods.append(fperiod)
-        project = kwargs["project"]
-        #
-        # first check that all facet values belong to the list of autorized
-        # values possibly defined for each facet. Return False otherwise
-        for kw in kwargs:
-            it = re.finditer(facets_regexp, f)
-            for oc in it:
-                try:
-                    facet_value = oc.group(kw)
-                except:
-                    continue
-                valid_values = cvalid(kw, None, project)
-                if isinstance(valid_values, list) and (facet_value not in valid_values):
-                    clogger.debug("Facet value %s for %s is not allowed" % (facet_value, kw))
-                    return False
-        #
-        for kw in kwargs:
-            it = re.finditer(facets_regexp, f)
-            for oc in it:
-                try:
-                    facet_value = oc.group(kw)
-                except:
-                    continue
-                if isinstance(kwargs[kw], six.string_types) and ("*" in kwargs[kw] or "?" in kwargs[kw]):
-                    if facet_value is not None:
-                        if kw not in wildcards:
-                            wildcards[kw] = set()
-                        wildcards[kw].add(facet_value)
-                        clogger.debug("Discover %s=%s for file=%s" % (kw, facet_value, f))
-                    else:
-                        clogger.debug("Logic issue for kw=%s and file=%s" % (kw, f))
-                    #
-                    if fperiod is not None and periods is not None:
-                        if merge_periods_on is None:
-                            key = None
-                        elif kw == merge_periods_on:
-                            key = facet_value
-                        else:
-                            # print "Skipping for kw=%s,sort=%s"%(kw,merge_periods_on)
-                            continue
-                        if key not in periods_dict:
-                            periods_dict[key] = set()
-                        clogger.debug("adding period %s for key %s in %s" % (str(fperiod), key, periods_dict))
-                        periods_dict[key].add(fperiod)
-                    else:
-                        pass
-                        # print "no Adding period for %s=%s for %s"%(kw,facet_value,f)
-        # print "end of store, periods_dict=",periods_dict, "wild=",wildcards
-        return True
-
-    rep = []
-    #
-    periods = None  # a list of periods available
-    if return_wildcards is not None:
-        periods_dict = return_wildcards.get("period", dict())
-        for val in periods_dict:
-            periods_dict[val] = set(periods_dict[val])
-    else:
-        periods_dict = dict()
-        
-    #
-    period = kwargs['period']
-    if period == "*":
-        periods = []  # Init an empty list of all periods
-    elif isinstance(period, six.string_types):
-        period = init_period(period)
-    #
-    variable = kwargs['variable']
-    altvar = kwargs.get('filenameVar', variable)
-    #
-    # a patterns for dates for globbing
-    # (we store it in a dict only for an for historcial reason)
-    #
-    digit = "[0-9]"
-    date_glob_patt = {"${PERIOD}": "*"}
-    date_keywords = ["${PERIOD}"]
-    #
-    # a pattern for dates for regexp
-    # (we store it in a dict only for an for historcial reason)
-    #
-    annee = "%s{4}" % digit
-    mois = "(01|02|03|04|05|06|07|08|09|10|11|12)"
-    jour = "([0-3][0-9])"
-    heure = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23)"
-    minutes = "[0-5][0-9]"
-    date = "%s(%s(%s(%s(%s)?)?)?)?" % (annee, mois, jour, heure, minutes)
-    rperiod = "(?P<period>(?P<start>%s)([_-](?P<end>%s))?)" % (date, date)
-    date_regexp_patt = {"${PERIOD}": rperiod}
-    # an ordered list of dates regexp keywords
-    date_regexp_keywords = ["${PERIOD}"]
-    #
-    #
-    for url in urls:
-        # First discard protocol prefix in url element
-        remote_prefix = ""
-        if re.findall(".*:.*", url):
-            remote_prefix = ':'.join(url.split(":")[0:-1]) + ':'
-        basename = url.split(":")[-1]  # This discard the remote_prefix if any
-        basename = basename.replace("//", "/")
-        #
-        # Instantiate keywords in pattern with attributes values provided in kwargs
-        my_template = Template(basename)
-        template = my_template.safe_substitute(**kwargs)
-        #
-        # Construct a pattern for globbing dates
-        temp2 = template
-        for k in date_keywords:
-            temp2 = temp2.replace(k, date_glob_patt[k])
-        #
-        # Do globbing with plain varname
-        if remote_prefix:
-            lfiles = sorted(glob_remote_data(remote_prefix, temp2))
-            clogger.debug("Remote globbing %d files for varname on %s : " % (len(lfiles), remote_prefix + temp2))
-        else:  # local data
-            lfiles = sorted(glob.glob(temp2))
-            clogger.debug("Before regexp filtering : Globbing %d files for varname on %s : " % (len(lfiles), temp2))
-            # Must filter with regexp, because * with glob for dates is too inclusive
-            alt = []
-            for f in lfiles:
-                for k in date_keywords:
-                    if re.search(date_regexp_patt[k], f) and f not in alt:
-                        alt.append(f)
-                        continue
-                # But must also consider the case where there is no date pattern in file pattern
-                if not any([k in url for k in date_regexp_patt]) and f not in alt:
-                    alt.append(f)
-            lfiles = list(set(alt))  # JS: set(alt) to avoid double files
-            clogger.debug("Globbing %d files for varname on %s : " % (len(lfiles), temp2))
-        #
-        # If unsuccessful using varname, try with filenameVar
-        if len(lfiles) == 0 and "filenameVar" in kwargs and kwargs['filenameVar']:
-            # Change value of facet 'variable'
-            kwargs['variable'] = kwargs['filenameVar']
-            template = my_template.safe_substitute(**kwargs)
-            temp2 = template
-            for k in date_keywords:
-                temp2 = temp2.replace(k, date_glob_patt[k])
-            #
-            # Do globbing with fileVarname
-            if remote_prefix:  #
-                lfiles = sorted(glob_remote_data(remote_prefix, temp2))
-                clogger.debug("Remote globbing %d files for filenamevar on %s: " % (len(lfiles), remote_prefix + temp2))
-            else:  # local data
-                lfiles = sorted(glob.glob(temp2))
-                # Must filter with regexp, because * with glob is too inclusive
-                alt = []
-                for f in lfiles:
-                    for k in date_keywords:
-                        if re.search(date_regexp_patt[k], f) and f not in alt:
-                            alt.append(f)
-                            continue
-                    # But must also consider the case where there is no date pattern in file pattern
-                    if not any([k in url for k in date_regexp_patt]) and f not in alt:
-                        alt.append(f)
-                lfiles = alt
-                clogger.debug("Globbing %d files for filenamevar on %s: " % (len(lfiles), temp2))
-        #
-        # For discovering values for those facets which are a wildcard,
-        # construct a regexp with a group name for all facets (but period)
-        alt_basename = basename.replace("?", ".").replace("*", ".*")
-        alt_kwargs = kwargs.copy()
-
-        def rreplace(thestring, replaced, replacement):
-            # Does replace only the rightmost occurrence of REPLACED in
-            # THESTRING by REPLACEMENT
-            # We choose the rightmost because matching fields in filenames
-            # (so, at right) is trickier than in pathnames when using wildcards
-            deb = thestring.rfind(replaced)
-            if deb >= 0:
-                end = deb + len(replaced)
-                return thestring[0:deb] + replacement + thestring[end:]
-            else:
-                return thestring
-        for kw in kwargs:
-            if isinstance(kwargs[kw], six.string_types):  # This excludes period attribute, which has a type
-                alt_kwargs[kw] = kwargs[kw].replace("?", ".").replace("*", ".*")
-                alt_basename = rreplace(alt_basename, r"${%s}" % kw, r"(?P<%s>%s)" % (kw, alt_kwargs[kw]))
-        facets_regexp = Template(alt_basename).safe_substitute(**alt_kwargs)
-        #
-        for k in date_regexp_keywords:
-            facets_regexp = facets_regexp.replace(k, date_regexp_patt[k], 1)
-            facets_regexp = facets_regexp.replace(k, ".*")
-        #
-        wildcards = dict()
-        # print "facets_regexp=",facets_regexp
-        #
-        # Construct regexp for extracting dates from filename
-        date_regexp = None
-        template_toreg = template.replace(r"*", r".*").replace(r"?", r".").replace(r"+", r"\+")
-        # print "template before searching dates : "+template_toreg
-        for key in date_regexp_keywords:
-            # print "searchin "+key+" in "+template
-            start = template_toreg.find(key)
-            if start >= 0:
-                date_regexp = template_toreg.replace(key, date_regexp_patt[key], 1)
-                # print "found ",key," dateregexp ->",date_regexp
-                hasEnd = False
-                start = date_regexp.find(key)
-                # start=date_regexp.find(key)
-                if start >= 0:
-                    hasEnd = True
-                    date_regexp = date_regexp.replace(key, date_regexp_patt[key], 1)
-                    # date_regexp=date_regexp.replace(key,date_regexp_patt[key],1)
-                break
-        # print "date_regexp before searching dates : "+date_regexp
-        #
-        for f in lfiles:
-            # print "processing file "+f
-            #
-            # Extract file time period
-            #
-            fperiod = None
-            if date_regexp:
-                if "P<period>" in date_regexp:
-                    # print "date_rexgep=",date_regexp
-                    # print "f=",f
-                    # print "period=",re.sub(date_regexp,r'\g<period>',f)
-                    tperiod = re.sub(date_regexp, r'\g<period>', f)
-                    if tperiod == f:
-                        raise Climaf_Error("Cannot find a period in %s with regexp %s" % (f, date_regexp))
-                    fperiod = init_period(tperiod)
-                else:
-                    date_regexp0 = date_regexp
-                    # print "date_regexp for extracting dates : "+date_regexp0, "file="+f
-                    start = re.sub(date_regexp0, r'\1', f)
-                    if start == f:
-                        raise Climaf_Data_Error("Start period not found in %s using regexp %s" % (f, date_regexp0))  # ?
-                    if hasEnd:
-                        end = re.sub(date_regexp0, r'\2', f)
-                        fperiod = init_period("%s-%s" % (start, end))
-                    else:
-                        fperiod = init_period(start)
-                # print "period for file %s is %s"%(f,fperiod)
-                #
-                # Filter file time period against required period
-            else:
-                if ('frequency' in kwargs and ((kwargs['frequency'] == "fx") or
-                                               kwargs['frequency'] == "seasonnal" or kwargs[
-                                                   'frequency'] == "annual_cycle")):
-                    # local data
-                    if not remote_prefix and \
-                            ((basename.find("${variable}") >= 0) or variable == '*' or
-                             fileHasVar(f, variable) or (variable != altvar and fileHasVar(f, altvar))):
-                        if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on):
-                            clogger.debug("adding fixed field :" + f)
-                            rep.append(f)
-                    # remote data
-                    elif remote_prefix:
-                        if (basename.find("${variable}") >= 0) or variable == '*' or \
-                                (variable != altvar and (f.find(altvar) >= 0)):
-                            if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on):
-                                clogger.debug("adding fixed field :" + remote_prefix + f)
-                                rep.append(remote_prefix + f)
-                        else:
-                            raise Climaf_Error("For remote files, filename pattern (%s) should include ${varname} " +
-                                               "(which is instanciated by variable name or filenameVar)" % f)
-                else:
-                    clogger.info("Cannot yet filter files re. time using only file content.")
-                    # if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on) :
-                    #    rep.append(f)
-
-            #
-            # If file period matches requested period, check similarly for variable
-            #
-            # print "fperiod=",fperiod
-            # print "periods=",periods
-            # print "inter=",period.intersects(fperiod)
-            # print "date_regexp=",date_regexp
-            if (fperiod and (periods is not None or period.intersects(fperiod))) \
-                    or not date_regexp:
-                #
-                clogger.debug(
-                    'Period is OK - Considering variable filtering on %s and %s for %s' % (variable, altvar, f))
-                # Filter against variable
-                if url.find("${variable}") >= 0:
-                    if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on,
-                                                   fperiod, periods, periods_dict):
-                        clogger.debug('appending %s based on variable in filename' % f)
-                        rep.append(remote_prefix + f)
-                    continue
-                if f not in rep:
-                    # local data
-                    if not remote_prefix and \
-                            (variable == '*' or "," in variable or fileHasVar(f, variable) or
-                             (altvar != variable and fileHasVar(f, altvar))):
-                        if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on,
-                                                       fperiod, periods, periods_dict):
-                            # Should check time period in the file if not date_regexp
-                            clogger.debug('appending %s based on multi-var or var exists in file ' % f)
-                            rep.append(f)
-                        continue
-                    # remote data
-                    elif remote_prefix:
-                        if variable == '*' or "," in variable or \
-                                (variable != altvar and (f.find(altvar) >= 0)):
-                            if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on,
-                                                           fperiod, periods, periods_dict):
-                                # Should check time period in the file if not date_regexp
-                                clogger.debug('appending %s based on multi-var or altvar ' % (remote_prefix + f))
-                                rep.append(remote_prefix + f)
-                            continue
-                        else:
-                            mess = "For remote files, filename pattern (%s) should include" % (remote_prefix + f)
-                            mess += " ${varname} (which is instanciated by variable name or filenameVar)"
-                            raise Climaf_Error(mess)
-            else:
-                if not fperiod:
-                    clogger.debug('not appending %s because period is None ' % f)
-                elif not period.intersects(fperiod):
-                    clogger.debug('not appending %s because period doesn t intersect %s' % (f, period))
-                else:
-                    clogger.debug('not appending %s for some other reason %s' % f)
-
-        # Break on first url with any matching data
-        if len(rep) > 0:
-            clogger.debug('url %s does match for ' % url + repr(kwargs))
-            break
-
-    #  For wildcard facets, discover facet values + checks
-    for facet in wildcards:
-        s = wildcards[facet]
-        if return_wildcards is not None:
-            if facet == "period":
-                # print "s=",s," periods_dict=",periods_dict
-                for val in periods_dict:
-                    periods_dict[val] = sort_periods_list(list(periods_dict[val]))
-                clogger.info("Attribute period='*' has values %s" % periods_dict)
-                return_wildcards["period"] = periods_dict
-            else:
-                if len(s) == 1:
-                    s = s.pop()
-                    clogger.info("Attribute %s='%s' has matching value '%s'" % (facet, kwargs[facet], s))
-                    return_wildcards[facet] = s
-                else:
-                    rep = list(s)
-                    rep.sort()
-                    return_wildcards[facet] = rep
-                    message = "Attribute %s='%s' has multiple values : %s" % (facet, kwargs[facet], list(s))
-                    if return_wildcards:
-                        clogger.info(message)
-                    else:
-                        clogger.error(message)
-        else:
-            clogger.debug("return_wildcards is None")
-    return rep
-
-
-def ftpmatch(connect, url):
-    """
-    Returns a list of files matching url with wildcars "*"  "?" on a remote machine
-    """
-
-    def parse_list_line(line, subdirs=[], files=[]):
-        parts = line.split(None, 8)
-        if line.startswith("d"):
-            return subdirs.append(parts[-1])
-        elif line.startswith("-"):
-            return files.append(parts[-1])
-        else:
-            return
-
-    if url.find('*') < 0 and url.find('?') < 0:
-        lpath_match = [url]
-    else:
-        lurl = url.split("/")
-        for n, elt in enumerate(lurl):
-            if elt.find('*') >= 0 or elt.find('?') >= 0:
-                break
-
-        prefixpath = os.path.join('/', *lurl[:n]).rstrip("/")
-        patt = lurl[n].replace("*", ".*").replace("?", r".")
-
-        lpath_match = []
-        # Filename stage
-        if len(lurl) == (n + 1):
-            all_files = []
-            cb = partial(parse_list_line, files=all_files)
-            connect.dir(prefixpath, cb)
-            for lfile in all_files:
-                if re.match(patt, lfile) is not None:
-                    lpath_match += [os.path.join(prefixpath, lfile)]
-        # directory stage
-        else:
-            all_subdirs = []
-            cb = partial(parse_list_line, subdirs=all_subdirs)
-            connect.dir(prefixpath, cb)
-            for sdir in all_subdirs:
-                if re.match(patt, sdir) is not None:
-                    lpath_match += ftpmatch(connect, os.path.join(prefixpath, sdir, *lurl[n + 1:]))
-    return lpath_match
-
-
-# def glob_remote_data(remote, pattern):
-#     """
-#     Returns a list of path names that match pattern, for remote data
-#     located atremote
-#     """
-#
-#     if len(remote.split(":")) == 3:
-#         k = 1
-#     else:
-#         k = 0
-#     k = 0
-#
-#     if re.findall("@", remote.split(":")[k]):
-#         username = remote.split(":")[k].split("@")[0]
-#         host = remote.split(":")[k].split("@")[-1]
-#     else:
-#         username = ''
-#         host = remote.split(":")[k]
-#
-#     secrets = netrc.netrc()
-#
-#     if username:
-#         if host in secrets.hosts:
-#             login, account, password = secrets.authenticators(host)
-#             if login != username:
-#                 password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-#         else:
-#             password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-#     else:
-#         if host in secrets.hosts:
-#             username, account, password = secrets.authenticators(host)
-#         else:
-#             username = eval(input("Enter login for host '%s': " % host))
-#             password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-#
-#     try:
-#         connect = ftp.FTP(host, username, password)
-#         listfiles = ftpmatch(connect, pattern)
-#         connect.quit()
-#         return listfiles
-#     except ftp.all_errors as err_ftp:
-#         print(err_ftp)
-#         raise Climaf_Error("Access problem for data %s on host '%s' and user '%s'" % (pattern, host, username))
-
-
-# def remote_to_local_filename(url):
-#     """
-#     url: an url of remote data
-#
-#     Return local filename of remote file
-#     """
-#     from climaf import remote_cachedir
-#
-#     if len(url.split(":")) == 3:
-#         k = 1
-#     else:
-#         k = 0
-#
-#     return rep
-
-
-def glob_remote_data(url, pattern):
-    """
-    Returns a list of path names that match pattern, for remote data
-    located at url
-    """
-
-    if len(url.split(":")) == 3:
-        k = 1
-    else:
-        k = 0
-
-    if re.findall("@", url.split(":")[k]):
-        username = url.split(":")[k].split("@")[0]
-        host = url.split(":")[k].split("@")[-1]
-    else:
-        username = ''
-        host = url.split(":")[k]
-
-    secrets = netrc.netrc()
-
-    if username:
-        if host in secrets.hosts:
-            login, account, password = secrets.authenticators(host)
-            if login != username:
-                password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-        else:
-            password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-    else:
-        if host in secrets.hosts:
-            username, account, password = secrets.authenticators(host)
-        else:
-            username = eval(input("Enter login for host '%s': " % host))
-            password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
-
-    try:
-        connect = ftp.FTP(host, username, password)
-        listfiles = connect.nlst(pattern)
-        connect.quit()
-        return listfiles
-    except ftp.all_errors as err_ftp:
-        print(err_ftp)
-        raise Climaf_Error("Access problem for data %s on host '%s' and user '%s'" % (url, host, username))
-
-
-def remote_to_local_filename(url):
-    """
-    url: an url of remote data
-
-    Return local filename of remote file
-    """
-    from climaf import remote_cachedir
-
-    if len(url.split(":")) == 3:
-        k = 1
-    else:
-        k = 0
-
-    if re.findall("@", url.split(":")[k]):
-        hostname = url.split(":")[k].split("@")[-1]
-    else:
-        hostname = url.split(":")[k]
-
-    local_filename = os.path.expanduser(remote_cachedir) + '/' + hostname + os.path.abspath(url.split(":")[-1])
-    return local_filename
+        # When returning strings (actually filenames), join them in a single string
+        if len(rep) > 0 and isinstance((rep[0]), six.string_types):
+            # Discard duplicates (assumes that sorting is harmless for later processing)
+            rep = sorted(list(set([f.strip() for f in rep])))
+            # Assemble filenames in one single string
+            rep = ' '.join(rep)
+        return rep
 
 
 def selectEmFiles(**kwargs):
@@ -1115,6 +527,28 @@ def selectCmip5DrsFiles(urls, **kwargs):
     return rep
 
 
+def remote_to_local_filename(url):
+    """
+    url: an url of remote data
+
+    Return local filename of remote file
+    """
+    from env.environment import default_remote_cache
+
+    if len(url.split(":")) == 3:
+        k = 1
+    else:
+        k = 0
+
+    if re.findall("@", url.split(":")[k]):
+        hostname = url.split(":")[k].split("@")[-1]
+    else:
+        hostname = url.split(":")[k]
+
+    local_filename = os.path.expanduser(default_remote_cache) + '/' + hostname + os.path.abspath(url.split(":")[-1])
+    return local_filename
+
+
 def test2():
     return
 
@@ -1123,30 +557,3 @@ def test2():
     test2()
 
 
-def cvalid(attribute, value=None, project=None):
-    """
-    Set or get the list of valid values for a CliMAF dataset attribute
-    or facet (such as e.g. 'model', 'simulation' ...). Useful for constraining
-    those data files which match a dataset definition
-
-    Argument 'project' allows to restrict the use/query
-    to the context of the given 'project'.
-
-    Example::
-
-    >>> cvalid('grid' , [ "gr", "gn", "gr1", "gr2" ] , project="CMIP6")
-    """
-    if project not in cprojects:
-        raise Climaf_Classes_Error("project '%s' has not yet been declared" % project)
-    if attribute == 'project':
-        project = None
-    #
-    if project and attribute not in cprojects[project].facets:
-        raise Climaf_Classes_Error("project '%s' doesn't use facet '%s'" % (project, attribute))
-    if value is None:
-        rep = cprojects[project].facet_authorized_values.get(attribute, None)
-        if not rep:
-            rep = cprojects[None].facet_authorized_values.get(attribute, "")
-        return rep
-    else:
-        cprojects[project].facet_authorized_values[attribute] = value
diff --git a/climaf/derived_variables/atmosphere_derived_variables.py b/climaf/derived_variables/atmosphere_derived_variables.py
index 190e793f..07c58bb4 100644
--- a/climaf/derived_variables/atmosphere_derived_variables.py
+++ b/climaf/derived_variables/atmosphere_derived_variables.py
@@ -3,8 +3,8 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.operators_derive import derive
 from env.environment import *
+from climaf.operators_derive import derive
 
 # -- DTR
 derive('*', 'dtr', 'minus', 'tasmax', 'tasmin')
diff --git a/climaf/derived_variables/cnrm_derived_variables.py b/climaf/derived_variables/cnrm_derived_variables.py
index a9953452..acd075d2 100644
--- a/climaf/derived_variables/cnrm_derived_variables.py
+++ b/climaf/derived_variables/cnrm_derived_variables.py
@@ -1,8 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-from climaf.api import derive
 from env.environment import *
+from climaf.operators_derive import derive
 
 for variable in ["ua", "va", "ta", "hur", "hus", "zg"]:
     for level in [850, 500, 200]:
diff --git a/climaf/derived_variables/ipsl_derived_variables.py b/climaf/derived_variables/ipsl_derived_variables.py
index e754e3a4..24ae5a00 100644
--- a/climaf/derived_variables/ipsl_derived_variables.py
+++ b/climaf/derived_variables/ipsl_derived_variables.py
@@ -3,9 +3,9 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.api import calias
-from climaf.operators_derive import derive
 from env.environment import *
+from climaf.driver import calias
+from climaf.operators_derive import derive
 
 # Content was sent to various places : prokect IGCM_OUT, atmposphere_derived_variables, ocean_derived_variables
 
diff --git a/climaf/derived_variables/ocean_derived_variables.py b/climaf/derived_variables/ocean_derived_variables.py
index 86b0a32b..d8e89285 100644
--- a/climaf/derived_variables/ocean_derived_variables.py
+++ b/climaf/derived_variables/ocean_derived_variables.py
@@ -3,9 +3,9 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.api import calias
-from climaf.operators_derive import derive
 from env.environment import *
+from climaf.driver import calias
+from climaf.operators_derive import derive
 
 # -- Potential Temperature and salinity @ 200m, 1000m and 2000m in depth
 # derive('*','so_onevar','cncks','so')
diff --git a/climaf/driver.py b/climaf/driver.py
index 56089a9d..94da82a5 100644
--- a/climaf/driver.py
+++ b/climaf/driver.py
@@ -10,12 +10,7 @@
 
 # Created : S.Senesi - 2014
 
-import sys
-import os
-import os.path
 import re
-import posixpath
-import subprocess
 import time
 import shutil
 import copy
@@ -25,6 +20,9 @@
 from functools import reduce
 from six import string_types
 
+from climaf.dataloc import remote_to_local_filename
+from climaf.utils import Climaf_Driver_Error
+
 try:
     from commands import getoutput, getstatusoutput
 except ImportError:
@@ -32,18 +30,19 @@
 
 
 # Climaf modules
-import climaf
+from env.environment import *
 from climaf.operators_scripts import scriptFlags
 from climaf.operators_derive import is_derived_variable, derived_variable, derive
 from climaf import classes
-from climaf import cache
+from climaf.cache import compute_cost, hasExactObject, cdrop, hasIncludingObject, hasBeginObject, complement, \
+    generateUniqueFileName, register, rename, has_cvalue, store_cvalue
 from climaf.cmacro import instantiate
 from env.clogging import clogger, indent as cindent, dedent as cdedent
 from climaf.netcdfbasics import varOfFile
-from climaf.period import init_period, cperiod, merge_periods
-from climaf import xdg_bin
-from climaf.classes import allow_errors_on_ds_call
-from env.environment import *
+from climaf.period import init_period, merge_periods
+from climaf.classes import allow_errors_on_ds_call, cens, varOf, ctree, scriptChild, cdataset, cpage, cpage_pdf, \
+    domainOf, cobject, modelOf, simulationOf, projectOf, realmOf, gridOf
+from climaf.ESMValTool_diags import call_evt_script
 
 
 # When evaluating an object, default behaviour is to search cache for including or begin objects
@@ -53,17 +52,18 @@
 
 def capply(climaf_operator, *operands, **parameters):
     """
-    Builds the object representing applying a CliMAF operator (script, function or macro) by calling the dedicated
-    function.
+    Builds the object representing applying a CliMAF operator (script, function or macro) by
+    calling the dedicated function.
     :param climaf_operator: a CliMAF operator (either a declared script, macro or operator)
     :param operands: operands to be passed to the climaf operator
     :param parameters: parameters to be passed to the climaf operator (not available for macros)
-    :return: a list of CliMAF objects (stored if auto-store is on)
+    :return: a list of CliMAF objects
     """
     res = None
-    if operands is None or operands[0] is None and not classes.allow_errors_on_ds_call:
+    if operands is None or operands[0] is None and not allow_errors_on_ds_call:
         raise Climaf_Driver_Error("Operands is None for operator %s" % climaf_operator)
     opds = list(map(str, operands))
+
     if climaf_operator in cscripts:
         # clogger.debug("applying script %s to"%climaf_operator + `opds` + `parameters`)
         res = capply_script(climaf_operator, *operands, **parameters)
@@ -71,16 +71,20 @@ def capply(climaf_operator, *operands, **parameters):
         op = cscripts[climaf_operator]
         if op.outputFormat in none_formats:
             ceval(res, userflags=copy.copy(op.flags))
+
     elif climaf_operator in cmacros:
         if len(parameters) > 0:
             raise Climaf_Driver_Error("Macros cannot be called with keyword args")
         clogger.debug("applying macro %s to" % climaf_operator + repr(opds))
         res = instantiate(cmacros[climaf_operator], *operands)
+
     elif climaf_operator in operators:
         clogger.debug("applying operator %s to" % climaf_operator + repr(opds) + repr(parameters))
         res = capply_operator(climaf_operator, *operands, **parameters)
+
     else:
         clogger.error("%s is not a known operator nor script" % climaf_operator)
+
     return res
 
 
@@ -116,13 +120,13 @@ def capply_script(script_name, *operands, **parameters):
     else:
         opscopy = list()
     # Next watch dog disabled for tests !!!!!
-    # if True in [isinstance(op, classes.cens) for op in opscopy]:
+    # if True in [isinstance(op, cens) for op in opscopy]:
     #    raise Climaf_Driver_Error("Cannot yet have an ensemble as operand except as first one")
     #
     # If first operand is an ensemble, and the script is not ensemble-capable,
     # result is the ensemble of applying the script ot each member of first operand
     # Otherwise, just call maketree
-    if isinstance(first, classes.cens) and script.flags.commuteWithEnsemble:
+    if isinstance(first, cens) and script.flags.commuteWithEnsemble:
         # Must iterate on members
         reps = []
         order = first.order
@@ -132,7 +136,7 @@ def capply_script(script_name, *operands, **parameters):
             params = parameters.copy()
             params["member_label"] = label
             reps.append(maketree(script_name, script, member, *opscopy, **params))
-        return classes.cens(dict(list(zip(order, reps))), order)
+        return cens(dict(list(zip(order, reps))), order)
     else:
         return maketree(script_name, script, *operands, **parameters)
 
@@ -150,8 +154,8 @@ def maketree(script_name, script, *operands, **parameters):
         defaultVariable = add_dict["add_variable"]
         parameters["add_variable"] = defaultVariable
     else:
-        defaultVariable = classes.varOf(operands[0])
-    rep = classes.ctree(script_name, script, *operands, **parameters)
+        defaultVariable = varOf(operands[0])
+    rep = ctree(script_name, script, *operands, **parameters)
     # TBD Analyze script inputs cardinality vs actual arguments
     # Create one child for each output
     # defaultPeriod=operands[0].period
@@ -166,7 +170,7 @@ def maketree(script_name, script, *operands, **parameters):
             rep.variable = template.substitute(parameters)
         else:
             # This is a secondary output
-            son = classes.scriptChild(rep, outname)
+            son = scriptChild(rep, outname)
             if "%s" in script.outputs[outname]:
                 son.variable = script.outputs[outname] % defaultVariable
             else:
@@ -200,19 +204,28 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: a pair : either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating dataset operand " + cobject.crs + " having kvp= " + repr(cobject.kvp))
     ds = cobject
-    cache_value = cache.hasExactObject(ds)
+    # If the dataset was not defined by its path, check that it is completely defined
+    if "path" not in ds.kvp or ds.kvp["path"] in ["", None]:
+        ds_ambiguous_args = ds.explore("choices")
+        if len(ds_ambiguous_args) != 0:
+            clogger.warning("Before doing a cfile on the dataset %s, check that it "
+                            "is completely defined with 'explore'." % cobject.crs)
+            ds = ds.explore("resolve")
+            clogger.warning("Update dataset %s with the following arguments: %s" % (cobject.crs,
+                                                                                    str(ds_ambiguous_args)))
+    cache_value, costs = hasExactObject(ds)
     if cache_value is not None:
         clogger.debug("Dataset %s exists in cache" % ds)
         cdedent()
-        if format == 'file':
-            return cache_value
+        if format in ['file', ]:
+            return cache_value, costs
         else:
-            return cread(cache_value, classes.varOf(ds))
+            return cread(cache_value, varOf(ds)), costs
     if ds.isLocal() or ds.isCached():
         clogger.debug("Dataset %s is local or cached " % ds)
         #  if the data is local, then
@@ -229,15 +242,13 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
             # and return it
             derived = derive_variable(ds)
             clogger.debug("evaluating derived variable %s as %s" % (ds.variable, repr(derived)))
-            derived_value = ceval(derived, format=format, deep=deep,
-                                  userflags=userflags,
-                                  derived_list=derived_list + [ds.variable],
-                                  recurse_list=recurse_list)
+            derived_value, costs = ceval(derived, format=format, deep=deep, userflags=userflags,
+                                         derived_list=derived_list + [ds.variable], recurse_list=recurse_list)
             if derived_value:
                 clogger.debug("succeeded in evaluating derived variable %s as %s" % (ds.variable, repr(derived)))
                 set_variable(derived_value, ds.variable, format=format)
             cdedent()
-            return derived_value
+            return derived_value, costs
         elif noselect(userflags, ds, format) and format in ["file", ]:
             # The caller is assumed to be able to select the needed sub-period or variable
             # and to select the variable
@@ -246,12 +257,12 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
             rep = ds.baseFiles()
             if not rep:
                 raise Climaf_Driver_Error("No file found for %s" % repr(ds))
-            return rep  # a single string with all filenames,
+            return rep, compute_cost()  # first element is a single string with all filenames,
             # or a list of such strings in case of ensembles
         else:
             clogger.debug("Must subset and/or aggregate and/or select " +
                           "var from data files and/or get data, or provide object result")
-            if format == 'file' or format == "MaskedArray":
+            if format in ['file', "MaskedArray"]:
                 if ds.hasOneMember():
                     clogger.debug("Fetching/selection/aggregation is done using an external script for now - TBD")
                     extract = capply('select', ds)
@@ -261,12 +272,13 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
                     extract = capply('select_member', ds)
                 if extract is None:
                     raise Climaf_Driver_Error("Cannot access dataset" + repr(ds))
-                rep = ceval(extract, userflags=userflags, format=format)
+                rep, costs = ceval(extract, userflags=userflags, format=format)
             else:
                 raise Climaf_Driver_Error("Untractable output format %s" % format)
-            userflags.unset_selectors()
+            if userflags :
+                userflags.unset_selectors()
             cdedent()
-            return rep
+            return rep, costs
     else:
         # Non-local and non-cached dataset
         #   if the user can access the dataset by one of the dataset-specific protocols
@@ -276,7 +288,7 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
         if userflags.canOpendap and format == 'file':
             clogger.debug("But user can OpenDAP ")
             cdedent()
-            return ds.adressOf()
+            return ds.adressOf(), compute_cost()
         else:
             if noselect(userflags, ds, format):
                 # ce cas-ci n'a jamais été activé !
@@ -285,15 +297,15 @@ def ceval_for_cdataset(cobject, userflags=None, format="MaskedArray", deep=None,
                 rep = ds.baseFiles()
                 if not rep:
                     raise Climaf_Driver_Error("No file found for %s" % repr(ds))
-                return rep
+                return rep, compute_cost()
             else:
                 # This matches reaching data using e.g. ftp
                 clogger.debug("Must remote read and cache ")
-                rep = ceval(capply('remote_select', ds), userflags=userflags, format=format)
+                rep, costs = ceval(capply('remote_select', ds), userflags=userflags, format=format)
                 ds.files = rep
                 userflags.unset_selectors()
                 cdedent()
-                return rep
+                return rep, costs
 
 
 def ceval_for_ctree(cobject, userflags=None, format="MaskedArray", deep=None, derived_list=list(),
@@ -306,29 +318,29 @@ def ceval_for_ctree(cobject, userflags=None, format="MaskedArray", deep=None, de
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating compound object : " + repr(cobject))
     #################################################################
     if deep is not None:
-        cache.cdrop(cobject)
+        cdrop(cobject)
     #
     clogger.debug("Searching cache for exact object : " + repr(cobject))
     #################################################################
-    filename = cache.hasExactObject(cobject)
+    filename, costs = hasExactObject(cobject)
     # filename=None
     if filename:
         clogger.info("Object found in cache: %s is at %s:  " % (cobject.crs, filename))
         cdedent()
         if format == 'file':
-            return filename
+            return filename, costs
         else:
-            return cread(filename, classes.varOf(cobject))
+            return cread(filename, varOf(cobject)), costs
     if dig_hard_into_cache:
         clogger.debug("Searching cache for including object for : " + repr(cobject))
         ########################################################################
-        it, altperiod = cache.hasIncludingObject(cobject)
+        it, altperiod = hasIncludingObject(cobject)
         # clogger.debug("Finished with searching cache for including object for : " + `cobject`)
         # it=None
         if it:
@@ -336,16 +348,16 @@ def ceval_for_ctree(cobject, userflags=None, format="MaskedArray", deep=None, de
             if format == 'file':
                 clogger.info("Selecting " + repr(cobject) + " out of it")
                 # Just select (if necessary for the user) the portion relevant to the request
-                rep = ceval_select(it, cobject, userflags, format, deep, derived_list, recurse_list)
+                rep, costs = ceval_select(it, cobject, userflags, format, deep, derived_list, recurse_list)
                 cdedent()
-                return rep
+                return rep, costs
             else:
                 clogger.debug("Because out format %s is not (yet, TBD) supported by ceval_select, cannot use "
                               "including object found for : " % format + repr(cobject))
             #
         clogger.debug("Searching cache for begin  object for : " + repr(cobject))
         ########################################################################
-        it, comp_period = cache.hasBeginObject(cobject)
+        it, comp_period = hasBeginObject(cobject)
         clogger.debug("Finished with searching cache for begin  object for : " + repr(cobject))
         # it=None
         if it:
@@ -355,14 +367,14 @@ def ceval_for_ctree(cobject, userflags=None, format="MaskedArray", deep=None, de
             # Build complement object for end, and eval it
             comp = copy.deepcopy(it)
             comp.setperiod(comp_period)
-            evalcomp = ceval(comp, userflags, format, deep, derived_list, recurse_list)
+            evalcomp, _ = ceval(comp, userflags, format, deep, derived_list, recurse_list)
             set_variable(evalcomp, cobject.variable, format=format)
-            rep = cache.complement(begcrs, comp.crs, cobject.crs)
+            rep, costs = complement(begcrs, comp.crs, cobject.crs)
             cdedent()
             if format == 'file':
-                return rep
+                return rep, costs
             else:
-                return ceval(cobject)
+                return ceval(cobject), costs
         #
     clogger.info("nothing relevant found in cache for %s" % cobject.crs)
     #
@@ -377,24 +389,24 @@ def ceval_for_ctree(cobject, userflags=None, format="MaskedArray", deep=None, de
     # TBD  : analyze if the dataset is remote and the remote place 'offers' the operator
     if cobject.operator in cscripts:
         clogger.debug("Script %s found" % cobject.operator)
-        file = ceval_script(cobject, down_deep,
-                            recurse_list=recurse_list)  # Does return a filename, or list of filenames
+        filen, costs = ceval_script(cobject, down_deep,
+                                   recurse_list=recurse_list)  # Does return a filename, or list of filenames
         cdedent()
-        if format == 'file':
-            return file
+        if format in ['file', ]:
+            return filen, costs
         else:
-            return cread(file, classes.varOf(cobject))
+            return cread(filen, varOf(cobject)), costs
     elif cobject.operator in operators:
         clogger.debug("Operator %s found" % cobject.operator)
-        # TODO: Implement ceval_operator
+        # TODO: Implement ceval_operator (and handle costs)
         obj = ceval_operator(cobject, deep)
         cdedent()
-        if format == 'file':
+        if format in ['file', ]:
             # TODO: Implement cstore
             rep = cstore(obj)
-            return rep
+            return rep, compute_cost()
         else:
-            return obj
+            return obj, compute_cost()
     else:
         raise Climaf_Driver_Error("operator %s is not a script nor known operator" % str(cobject.operator))
 
@@ -417,29 +429,29 @@ def ceval_for_scriptChild(cobject, userflags=None, format="MaskedArray", deep=No
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating compound object : " + repr(cobject))
     #################################################################
     if deep is not None:
-        cache.cdrop(cobject)
+        cdrop(cobject)
     #
     clogger.debug("Searching cache for exact object : " + repr(cobject))
     #################################################################
-    filename = cache.hasExactObject(cobject)
+    filename, costs = hasExactObject(cobject)
     # filename=None
     if filename:
         clogger.info("Object found in cache: %s is at %s:  " % (cobject.crs, filename))
         cdedent()
         if format == 'file':
-            return filename
+            return filename, costs
         else:
-            return cread(filename, classes.varOf(cobject))
+            return cread(filename, varOf(cobject)), costs
     if dig_hard_into_cache:
         clogger.debug("Searching cache for including object for : " + repr(cobject))
         ########################################################################
-        it, altperiod = cache.hasIncludingObject(cobject)
+        it, altperiod = hasIncludingObject(cobject)
         # clogger.debug("Finished with searching cache for including object for : " + `cobject`)
         # it=None
         if it:
@@ -447,16 +459,16 @@ def ceval_for_scriptChild(cobject, userflags=None, format="MaskedArray", deep=No
             if format == 'file':
                 clogger.info("Selecting " + repr(cobject) + " out of it")
                 # Just select (if necessary for the user) the portion relevant to the request
-                rep = ceval_select(it, cobject, userflags, format, deep, derived_list, recurse_list)
+                rep, costs = ceval_select(it, cobject, userflags, format, deep, derived_list, recurse_list)
                 cdedent()
-                return rep
+                return rep, costs
             else:
                 clogger.debug("Because out format %s is not (yet, TBD) supported by ceval_select, cannot use "
                               "including object found for : " % format + repr(cobject))
             #
         clogger.debug("Searching cache for begin  object for : " + repr(cobject))
         ########################################################################
-        it, comp_period = cache.hasBeginObject(cobject)
+        it, comp_period = hasBeginObject(cobject)
         clogger.debug("Finished with searching cache for begin  object for : " + repr(cobject))
         # it=None
         if it:
@@ -466,12 +478,12 @@ def ceval_for_scriptChild(cobject, userflags=None, format="MaskedArray", deep=No
             # Build complement object for end, and eval it
             comp = copy.deepcopy(it)
             comp.setperiod(comp_period)
-            evalcomp = ceval(comp, userflags, format, deep, derived_list, recurse_list)
+            evalcomp, _ = ceval(comp, userflags, format, deep, derived_list, recurse_list)
             set_variable(evalcomp, cobject.variable, format=format)
-            rep = cache.complement(begcrs, comp.crs, cobject.crs)
+            rep, costs = complement(begcrs, comp.crs, cobject.crs)
             cdedent()
-            if format == 'file':
-                return rep
+            if format in ['file', ]:
+                return rep, costs
             else:
                 return ceval(cobject)
         #
@@ -483,11 +495,12 @@ def ceval_for_scriptChild(cobject, userflags=None, format="MaskedArray", deep=No
     else:
         down_deep = None
     # Force evaluation of 'father' script
-    if ceval_script(cobject.father, down_deep, recurse_list=recurse_list) is not None:
+    script_eval, _ = ceval_script(cobject.father, down_deep, recurse_list=recurse_list)
+    if script_eval is not None:
         # Re-evaluate, which should succeed using cache
-        rep = ceval(cobject, userflags, format, None, recurse_list=recurse_list)
+        rep, costs = ceval(cobject, userflags, format, None, recurse_list=recurse_list)
         cdedent()
-        return rep
+        return rep, costs
     else:
         raise Climaf_Driver_Error("generating script aborted for " + cobject.father.crs)
 
@@ -502,37 +515,37 @@ def ceval_for_cpage(cobject, userflags=None, format="MaskedArray", deep=None, de
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating compound object : " + repr(cobject))
     #################################################################
     if deep is not None:
-        cache.cdrop(cobject)
+        cdrop(cobject)
     #
     clogger.debug("Searching cache for exact object : " + repr(cobject))
     #################################################################
-    filename = cache.hasExactObject(cobject)
+    filename, costs = hasExactObject(cobject)
     # filename=None
     if filename:
         clogger.info("Object found in cache: %s is at %s:  " % (cobject.crs, filename))
         cdedent()
-        if format == 'file':
-            return filename
+        if format in ['file', ]:
+            return filename, costs
         else:
-            return cread(filename, classes.varOf(cobject))
+            return cread(filename, varOf(cobject)), costs
     #
     #  Only deep=True can propagate downward !
     if deep:
         down_deep = True
     else:
         down_deep = None
-    file = cfilePage(cobject, down_deep, recurse_list=recurse_list)
+    filen, costs = cfilePage(cobject, down_deep, recurse_list=recurse_list)
     cdedent()
     if format in ['file', ]:
-        return file
+        return filen, costs
     else:
-        return cread(file)  # !! Does it make sense ?
+        return cread(filen), costs  # !! Does it make sense ?
 
 
 def ceval_for_cpage_pdf(cobject, userflags=None, format="MaskedArray", deep=None, derived_list=list(),
@@ -545,25 +558,25 @@ def ceval_for_cpage_pdf(cobject, userflags=None, format="MaskedArray", deep=None
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating compound object : " + repr(cobject))
     #################################################################
     if deep is not None:
-        cache.cdrop(cobject)
+        cdrop(cobject)
     #
     clogger.debug("Searching cache for exact object : " + repr(cobject))
     #################################################################
-    filename = cache.hasExactObject(cobject)
+    filename, costs = hasExactObject(cobject)
     # filename=None
     if filename:
         clogger.info("Object found in cache: %s is at %s:  " % (cobject.crs, filename))
         cdedent()
-        if format == 'file':
-            return filename
+        if format in ['file', ]:
+            return filename, costs
         else:
-            return cread(filename, classes.varOf(cobject))
+            return cread(filename, varOf(cobject)), costs
     #
     #  Only deep=True can propagate downward !
     if deep:
@@ -571,12 +584,12 @@ def ceval_for_cpage_pdf(cobject, userflags=None, format="MaskedArray", deep=None
     else:
         down_deep = None
     #
-    file = cfilePage_pdf(cobject, down_deep, recurse_list=recurse_list)
+    filen, costs = cfilePage_pdf(cobject, down_deep, recurse_list=recurse_list)
     cdedent()
-    if format == 'file':
-        return file
+    if format in ['file', ]:
+        return filen, costs
     else:
-        return cread(file)  # !! Does it make sense ?
+        return cread(filen)  # !! Does it make sense ?
 
 
 def ceval_for_cens(cobject, userflags=None, format="MaskedArray", deep=None, derived_list=list(),
@@ -589,34 +602,41 @@ def ceval_for_cens(cobject, userflags=None, format="MaskedArray", deep=None, der
     :param deep:
     :param derived_list:
     :param recurse_list:
-    :return:
+    :return: either a filename string or MaskedArray, and a cost object
     """
     recurse_list.append(cobject.crs)
     clogger.debug("Evaluating compound object : " + repr(cobject))
     #################################################################
     if deep is not None:
-        cache.cdrop(cobject)
+        cdrop(cobject)
     #
     clogger.debug("Searching cache for exact object : " + repr(cobject))
     #################################################################
-    filename = cache.hasExactObject(cobject)
+    filename, costs = hasExactObject(cobject)
     # filename=None
     if filename:
         clogger.info("Object found in cache: %s is at %s:  " % (cobject.crs, filename))
         cdedent()
         if format == 'file':
-            return filename
+            return filename, costs
         else:
-            return cread(filename, classes.varOf(cobject))
+            return cread(filename, varOf(cobject)), costs
     d = dict()
+    costs = dict()
     for member in cobject.order:
         # print ("evaluating member %s"%member)
-        d[member] = ceval(cobject[member], copy.copy(userflags), format, deep, recurse_list=recurse_list)
+        d[member], costs[member] = ceval(cobject[member],
+                                         copy.copy(userflags), format, deep, recurse_list=recurse_list)
+
     cdedent()
-    if format == "file":
-        return reduce(lambda x, y: x + " " + y, [d[m] for m in cobject.order])
+    if format in ["file", ]:
+        files = reduce(lambda x, y: x + " " + y, [d[m] for m in cobject.order])
+        total_cost = compute_cost()
+        for m in cobject.order:
+            total_cost.add(costs[m])
+        return files, total_cost
     else:
-        return d
+        return d, costs
 
 
 def ceval_for_string(cobject, userflags=None, format="MaskedArray", deep=None, derived_list=list(),
@@ -636,12 +656,42 @@ def ceval_for_string(cobject, userflags=None, format="MaskedArray", deep=None, d
     raise NotImplementedError("Evaluation from CRS is not yet implemented ( %s )" % cobject)
 
 
+def evaluate_inputs(call, deep = False ,recurse_list = []):
+    # Evaluate input data for a script call , either a CliMAF-tye one or an ESMValTool one
+    invalues = []
+    sizes = []
+    total_costs = compute_cost()
+    for op in call.operands:
+        if op:
+            if call.operator != 'remote_select' and \
+                    isinstance(op, cdataset) and \
+                    not (op.isLocal() or op.isCached()):
+                inValue, costs = ceval(op, format='file', deep=deep)
+            else:
+                inValue, costs = ceval(op, format='file', deep=deep,
+                                       userflags=call.flags, recurse_list=recurse_list)
+            clogger.debug("evaluating %s operand %s as %s" % (call.operator, op, inValue))
+            if inValue in [None, ""]:
+                raise Climaf_Driver_Error("When evaluating %s : value for %s is None" % (call.script, repr(op)))
+            if isinstance(inValue, list):
+                size = len(inValue)
+            else:
+                size = 1
+            total_costs.add(costs)
+        else:
+            inValue = ''
+            size = 0
+        sizes.append(size)
+        invalues.append(inValue)
+    return invalues, sizes, total_costs
+
+
 def ceval(cobject, userflags=None, format="MaskedArray",
           deep=None, derived_list=[], recurse_list=[]):
     """
     Actually evaluates a CliMAF object, either as an in-memory data structure or
     as a string of filenames (which either represent a superset or exactly includes
-    the desired data)
+    the desired data). Returns it in pair with a cost object
 
     - with arg deep=True , re-evaluates all components
     - with arg deep=False, re-evaluates top level operation
@@ -662,23 +712,23 @@ def ceval(cobject, userflags=None, format="MaskedArray",
     #    clogger.critical("INTERNAL ERROR : infinite loop on object: "+cobject.crs)
     #    return None
     cindent()
-    if isinstance(cobject, classes.cdataset):
+    if isinstance(cobject, cdataset):
         return ceval_for_cdataset(cobject=cobject, userflags=userflags, format=format, deep=deep,
                                   derived_list=derived_list, recurse_list=recurse_list)
     #
-    elif isinstance(cobject, classes.ctree):
+    elif isinstance(cobject, ctree):
         return ceval_for_ctree(cobject=cobject, userflags=userflags, format=format, deep=deep,
                                derived_list=derived_list, recurse_list=recurse_list)
-    elif isinstance(cobject, classes.scriptChild):
+    elif isinstance(cobject, scriptChild):
         return ceval_for_scriptChild(cobject=cobject, userflags=userflags, format=format, deep=deep,
                                      derived_list=derived_list, recurse_list=recurse_list)
-    elif isinstance(cobject, classes.cpage):
+    elif isinstance(cobject, cpage):
         return ceval_for_cpage(cobject=cobject, userflags=userflags, format=format, deep=deep,
                                derived_list=derived_list, recurse_list=recurse_list)
-    elif isinstance(cobject, classes.cpage_pdf):
+    elif isinstance(cobject, cpage_pdf):
         return ceval_for_cpage_pdf(cobject=cobject, userflags=userflags, format=format, deep=deep,
                                    derived_list=derived_list, recurse_list=recurse_list)
-    elif isinstance(cobject, classes.cens):
+    elif isinstance(cobject, cens):
         return ceval_for_cens(cobject=cobject, userflags=userflags, format=format, deep=deep,
                               derived_list=derived_list, recurse_list=recurse_list)
     elif isinstance(cobject, str):
@@ -691,7 +741,7 @@ def ceval(cobject, userflags=None, format="MaskedArray",
 def ceval_script(scriptCall, deep, recurse_list=[]):
     """ Actually applies a CliMAF-declared script on a script_call object
 
-    Prepare operands as fiels and build command from operands and parameters list
+    Prepare operands as fields and build command from operands and parameters list
     Assumes that scripts are described in dictionary 'scripts'  by templates as
     documented in operators.cscript
 
@@ -699,30 +749,10 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
     """
     script = cscripts[scriptCall.operator]
     template = Template(script.command)
+    total_costs = compute_cost()
     # Evaluate input data
-    invalues = []
-    sizes = []
-    for op in scriptCall.operands:
-        if op:
-            if scriptCall.operator != 'remote_select' and \
-                    isinstance(op, classes.cdataset) and \
-                    not (op.isLocal() or op.isCached()):
-                inValue = ceval(op, format='file', deep=deep)
-            else:
-                inValue = ceval(op, userflags=scriptCall.flags, format='file', deep=deep,
-                                recurse_list=recurse_list)
-            clogger.debug("evaluating %s operand %s as %s" % (scriptCall.operator, op, inValue))
-            if inValue is None or inValue is "":
-                raise Climaf_Driver_Error("When evaluating %s : value for %s is None" % (scriptCall.script, repr(op)))
-            if isinstance(inValue, list):
-                size = len(inValue)
-            else:
-                size = 1
-        else:
-            inValue = ''
-            size = 0
-        sizes.append(size)
-        invalues.append(inValue)
+    invalues, sizes, partial_cost =evaluate_inputs(scriptCall, deep, recurse_list)
+    total_costs.add(partial_cost)
     # print("len(invalues)=%d"%len(invalues))
     #
     # Replace input data placeholders with filenames
@@ -739,20 +769,20 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
                                       "some input file does not exist among %s:" % (scriptCall.operator, op, infile))
         subdict[label] = infile
         # if scriptCall.flags.canSelectVar :
-        subdict["var"] = classes.varOf(op)
-        subdict["Var"] = classes.varOf(op)
-        if isinstance(op, classes.cdataset) and op.alias:
+        subdict["var"] = varOf(op)
+        subdict["Var"] = varOf(op)
+        if isinstance(op, cdataset) and op.alias:
             filevar, scale, offset, units, filenameVar, missing, conditions = op.alias
             if op.matches_conditions(conditions):
-                if scriptCall.flags.canAlias and "," not in classes.varOf(op):
-                    # if script=="select" and ((classes.varOf(op) != filevar) or scale != 1.0 or offset != 0.) :
+                if scriptCall.flags.canAlias and "," not in varOf(op):
+                    # if script=="select" and ((varOf(op) != filevar) or scale != 1.0 or offset != 0.) :
                     subdict["var"] = Template(filevar).safe_substitute(op.kvp)
-                    subdict["alias"] = "%s,%s,%.4g,%.4g" % (classes.varOf(op), subdict["var"], scale, offset)
+                    subdict["alias"] = "%s,%s,%.4g,%.4g" % (varOf(op), subdict["var"], scale, offset)
                 if units:
                     subdict["units"] = units
                 if scriptCall.flags.canMissing and missing:
                     subdict["missing"] = missing
-        if isinstance(op, classes.cens):
+        if isinstance(op, cens):
             if not multiple:
                 raise Climaf_Driver_Error(
                     "Script %s 's input #%s cannot accept ensemble %s" % (scriptCall.script, 0, repr(op)))
@@ -764,17 +794,18 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
                 subdict["period"] = str(per)
                 subdict["period_iso"] = per.iso()
         if scriptCall.flags.canSelectDomain:
-            subdict["domain"] = classes.domainOf(op)
+            subdict["domain"] = domainOf(op)
     else:
-        subdict["var"] = classes.varOf(scriptCall)
-        subdict["Var"] = classes.varOf(scriptCall)
+        subdict["var"] = varOf(scriptCall)
+        subdict["Var"] = varOf(scriptCall)
+
     i = 0
     for op in scriptCall.operands:
         if op:
             opscrs += op.crs + " - "
         # print("processing %s, i=%d"%(`op`,i))
         infile = invalues[i]
-        if (scriptCall.operator != 'remote_select') and infile is not '' and \
+        if (scriptCall.operator != 'remote_select') and infile != '' and \
                 not all(map(os.path.exists, infile.split(" "))):
             raise Climaf_Driver_Error("Internal error : some input file does not exist among %s:" % infile)
         i += 1
@@ -782,15 +813,15 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
             label, multiple, serie = script.inputs[i]
             subdict[label] = infile
             # Provide the name of the variable in input file if script allows for
-            if isinstance(op, classes.cobject):
-                subdict["var_%d" % i] = classes.varOf(op)
-            if isinstance(op, classes.cdataset) and op.alias:
+            if isinstance(op, cobject):
+                subdict["var_%d" % i] = varOf(op)
+            if isinstance(op, cdataset) and op.alias:
                 filevar, scale, offset, units, filenameVar, missing, conditions = op.alias
                 if op.matches_conditions(conditions):
-                    if ((classes.varOf(op) != filevar) or (scale != 1.0) or (offset != 0.)) and \
-                            "," not in classes.varOf(op):
+                    if ((varOf(op) != filevar) or (scale != 1.0) or (offset != 0.)) and \
+                            "," not in varOf(op):
                         subdict["var_%d" % i] = Template(filevar).safe_substitute(op.kvp)
-                        subdict["alias_%d" % i] = "%s %s %f %f" % (classes.varOf(op),
+                        subdict["alias_%d" % i] = "%s %s %f %f" % (varOf(op),
                                                                    subdict["var_%d" % i], scale, offset)
                     if units:
                         subdict["units_%d" % i] = units
@@ -802,7 +833,7 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
                 if not per.fx and per != "":
                     subdict["period_%d" % i] = str(per)
                     subdict["period_iso_%d" % i] = per.iso()
-            subdict["domain_%d" % i] = classes.domainOf(op)
+            subdict["domain_%d" % i] = domainOf(op)
     clogger.debug("subdict for operands is " + repr(subdict))
     # substitution is deffered after scriptcall parameters evaluation, which may
     # redefine e.g period
@@ -823,24 +854,24 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
         # Compute a filename for each ouptut
         # Un-named main output
         # main_output_filename=tempfile.NamedTemporaryFile(suffix="."+output_fmt).name
-        # #cache.generateUniqueFileName(scriptCall.crs, format=output_fmt)
-        tmpfile, tmpfile_fmt = os.path.splitext(cache.generateUniqueFileName(scriptCall.crs, format=output_fmt))
+        # #generateUniqueFileName(scriptCall.crs, format=output_fmt)
+        tmpfile, tmpfile_fmt = os.path.splitext(generateUniqueFileName(scriptCall.crs, format=output_fmt))
         main_output_filename = "%s_%i%s" % (tmpfile, os.getpid(), tmpfile_fmt)
 
         subdict["out"] = main_output_filename
-        subdict["out_" + classes.varOf(scriptCall)] = main_output_filename
+        subdict["out_" + varOf(scriptCall)] = main_output_filename
 
-        subdict["out_final"] = cache.generateUniqueFileName(scriptCall.crs, format=output_fmt)
-        subdict["out_final_" + classes.varOf(scriptCall)] = cache.generateUniqueFileName(scriptCall.crs,
+        subdict["out_final"] = generateUniqueFileName(scriptCall.crs, format=output_fmt)
+        subdict["out_final_" + varOf(scriptCall)] = generateUniqueFileName(scriptCall.crs,
                                                                                          format=output_fmt)
 
         # Named outputs
         for output in scriptCall.outputs:
             # subdict["out_"+output]=tempfile.NamedTemporaryFile(suffix="."+output_fmt).name
             tmpfile, tmpfile_fmt = os.path.splitext(
-                cache.generateUniqueFileName(scriptCall.crs + "." + output, format=output_fmt))
+                generateUniqueFileName(scriptCall.crs + "." + output, format=output_fmt))
             subdict["out_" + output] = "%s_%i%s" % (tmpfile, os.getpid(), tmpfile_fmt)
-            subdict["out_final_" + output] = cache.generateUniqueFileName(scriptCall.crs + "." + output,
+            subdict["out_final_" + output] = generateUniqueFileName(scriptCall.crs + "." + output,
                                                                           format=output_fmt)
 
     # Account for script call parameters
@@ -853,7 +884,8 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
     #
     # Discard selection parameters if selection already occurred for first operand
     # TBD : manage the cases where other operands didn't undergo selection
-    if cache.hasExactObject(scriptCall.operands[0]):
+    exact, _ = hasExactObject(scriptCall.operands[0])
+    if exact:
         # for key in ["period","period_iso","var","domain","missing","alias","units"]:
         for key in ["period", "period_iso", "var", "domain", "missing", "alias"]:
             if key in subdict:
@@ -889,11 +921,11 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
     if script.fixedfields is not None:
         # subdict_ff=dict()
         subdict_ff = scriptCall.parameters.copy()
-        subdict_ff["model"] = classes.modelOf(scriptCall.operands[0])
-        subdict_ff["simulation"] = classes.simulationOf(scriptCall.operands[0])
-        subdict_ff["project"] = classes.projectOf(scriptCall.operands[0])
-        subdict_ff["realm"] = classes.realmOf(scriptCall.operands[0])
-        subdict_ff["grid"] = classes.gridOf(scriptCall.operands[0])
+        subdict_ff["model"] = modelOf(scriptCall.operands[0])
+        subdict_ff["simulation"] = simulationOf(scriptCall.operands[0])
+        subdict_ff["project"] = projectOf(scriptCall.operands[0])
+        subdict_ff["realm"] = realmOf(scriptCall.operands[0])
+        subdict_ff["grid"] = gridOf(scriptCall.operands[0])
         scr_fixed_fields = script.fixedfields  # return paths: (linkname, targetname)
         files_exist = dict()
         for ll, lt in scr_fixed_fields:
@@ -922,12 +954,14 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
                                       scriptCall.crs)
 
     #
+    duration = time.time() - tim1
+    total_costs.increment(duration)
     # For remote files, we supply ds.local_copies_of_remote_files
     # for local filenames in order to can use ds.check()
     if scriptCall.operator == 'remote_select':
         local_filename = []
         for el in scriptCall.operands[0].baseFiles().split(" "):
-            local_filename.append(climaf.dataloc.remote_to_local_filename(el))
+            local_filename.append(remote_to_local_filename(el))
         scriptCall.operands[0].local_copies_of_remote_files = ' '.join(local_filename)
     #
     # Clean fixed fields symbolic links (linkname, targetname)
@@ -935,38 +969,48 @@ def ceval_script(scriptCall, deep, recurse_list=[]):
         for ll, lt in script.fixedfields:
             if not files_exist[ll]:
                 os.system("rm -f " + ll)
+
     # Handle outputs
     if script.outputFormat in ["txt", ]:
         with open(logdir + "/last.out", 'r') as f:
             for line in f.readlines():
                 sys.stdout.write(line)
     if script.outputFormat in none_formats:
-        return None
+        return None, 0.0
     # Tagging output files with their CliMAF Reference Syntax definition
     # 1 - Un-named main output
-    ok = cache.register(main_output_filename, scriptCall.crs, subdict["out_final"])
+    ok = register(main_output_filename, scriptCall.crs, total_costs, subdict["out_final"])
     # 2 - Named outputs
     for output in scriptCall.outputs:
-        ok = ok and cache.register(subdict["out_" + output], scriptCall.crs + "." + output,
-                                   subdict["out_final_" + output])
+        ok = ok and register(subdict["out_" + output], scriptCall.crs + "." + output,
+                                   total_costs, subdict["out_final_" + output])
     if ok:
-        duration = time.time() - tim1
-        # print(...file=sys.stderr)
         clogger.info("Done in %.1f s with script computation for "
                      "%s (command was :%s )" % (duration, repr(scriptCall), template))
-        return subdict["out_final"]  # main_output_filename
+        return subdict["out_final"], total_costs  # main_output_filename
     else:
         raise Climaf_Driver_Error("Some output missing when executing "
                                   ": %s. \n See %s/last.out" % (template, logdir))
 
 
+def ceval_evt(climaf_name, script, *operands, **parameters):
+    """
+    Evaluates OPERANDS and forward them to function
+    :py:func:`~climaf.ESMValTool_diags.call_evt_script` together with all arguments.
+
+    This function is NOT supposed to be called directly except by CliMAF driver, see doc.
+    """
+    invalues, _, _ = evaluate_inputs(ctree(script, None, *operands, **parameters))
+    return call_evt_script(climaf_name, script, invalues, *operands, **parameters)
+
+
 def timePeriod(cobject):
     """ Returns a time period for a CliMAF object : if object is a dataset, returns
     its time period, otherwise returns time period of first operand
     """
-    if isinstance(cobject, classes.cdataset):
+    if isinstance(cobject, cdataset):
         return cobject.period
-    elif isinstance(cobject, classes.ctree):
+    elif isinstance(cobject, ctree):
         clogger.debug("timePeriod : processing %s,operands=%s" % (cobject.script, repr(cobject.operands)))
         if cobject.script.flags.doCatTime and len(cobject.operands) > 1:
             clogger.debug("Building composite period for results of %s" % cobject.operator)
@@ -979,10 +1023,10 @@ def timePeriod(cobject):
         else:
             clogger.debug("timePeriod logic for script is 'choose 1st operand' %s" % cobject.script)
             return timePeriod(cobject.operands[0])
-    elif isinstance(cobject, classes.scriptChild):
+    elif isinstance(cobject, scriptChild):
         clogger.debug("for now, timePeriod logic for scriptChilds is basic - TBD")
         return timePeriod(cobject.father)
-    elif isinstance(cobject, classes.cens):
+    elif isinstance(cobject, cens):
         clogger.debug("for now, timePeriod logic for 'cens' objet is basic (1st member)- TBD")
         return timePeriod(list(cobject.values())[0])
     else:
@@ -1005,26 +1049,29 @@ def ceval_select(includer, included, userflags, format, deep, derived_list, recu
         extract = capply('select', includer, period=repr(incperiod))
         clogger.debug("Variable considered in extract: %s" % extract.variable)
         clogger.debug("Extract crs found: %s" % extract)
-        objfile = ceval(extract, userflags, 'file', deep, derived_list, recurse_list)
+        objfile, costs = ceval(extract, userflags, 'file', deep, derived_list, recurse_list)
         if objfile:
             crs = includer.buildcrs(period=incperiod)
-            return cache.rename(objfile, crs)
+            return rename(objfile, crs), costs
         else:
             clogger.critical("Cannot evaluate " + repr(extract))
             exit()
     else:
         clogger.error("Can yet process only files - TBD")
+        return None, compute_cost()
 
 
 def cread(datafile, varname=None, period=None):
     import re
     if not datafile:
         return None
-    if re.findall(".png$", datafile) or \
-            ((re.findall(".pdf$", datafile) or re.findall(".eps$", datafile)) and (not xdg_bin)):
+    if re.findall(".png$", datafile):
         subprocess.Popen(["display", datafile, "&"])
-    elif (re.findall(".pdf$", datafile) or re.findall(".eps$", datafile)) and xdg_bin:
-        subprocess.Popen(["xdg-open", datafile])
+    elif re.findall(".pdf$", datafile) or re.findall(".eps$", datafile):
+        if xdg_bin is None:
+            subprocess.Popen(["display", datafile, "&"])
+        else:
+            subprocess.Popen([xdg_bin, datafile])
     elif re.findall(".nc$", datafile):
         clogger.debug("reading NetCDF file %s" % datafile)
         if varname is None:
@@ -1035,6 +1082,9 @@ def cread(datafile, varname=None, period=None):
             clogger.warning("Cannot yet select on period (%s) using CMa for files %s - TBD" % (period, datafile))
         from .anynetcdf import ncf
         fileobj = ncf(datafile)
+        if varname not in fileobj.variables:
+            clogger.error("File %s doesn't have requested variable %s, only %s" %
+                          (datafile, varname, fileobj.variables))
         # Note taken from the CDOpy developper : .data is not backwards
         # compatible to old scipy versions, [:] is
         data = fileobj.variables[varname][:]
@@ -1052,11 +1102,13 @@ def cread(datafile, varname=None, period=None):
 
 
 def cview(datafile):
-    if re.findall(".png$", datafile) or \
-            ((re.findall(".pdf$", datafile) or re.findall(".eps$", datafile)) and (not xdg_bin)):
+    if re.findall(".png$", datafile):
         subprocess.Popen(["display", datafile, "&"])
-    elif (re.findall(".pdf$", datafile) or re.findall(".eps$", datafile)) and xdg_bin:
-        subprocess.Popen(["xdg-open", datafile])
+    elif re.findall(".pdf$", datafile) or re.findall(".eps$", datafile):
+        if xdg_bin is None:
+            subprocess.Popen(["display", datafile, "&"])
+        else:
+            subprocess.Popen([xdg_bin, datafile])
     else:
         clogger.error("cannot yet handle %s" % datafile)
         return None
@@ -1067,7 +1119,7 @@ def derive_variable(ds):
     representing the operation needed to compute it (using information in dict
     operators_derive.derived_variable
     """
-    if not isinstance(ds, classes.cdataset):
+    if not isinstance(ds, cdataset):
         raise Climaf_Driver_Error("arg is not a dataset")
     if not is_derived_variable(ds.variable, ds.project):
         raise Climaf_Driver_Error("%s is not a derived variable" % ds.variable)
@@ -1083,7 +1135,7 @@ def derive_variable(ds):
         if not first and "version" in dic:
             dic['version'] = "latest"
         first = False
-        inVars.append(classes.cdataset(**dic))
+        inVars.append(cdataset(**dic))
     params["add_variable"] = ds.variable
     # TODO: force the output variable to be well defined
     father = capply(op, *inVars, **params)
@@ -1091,7 +1143,7 @@ def derive_variable(ds):
     if outname == "out" or outname == ds.variable:
         rep = father
     else:
-        rep = classes.scriptChild(father, outname)
+        rep = scriptChild(father, outname)
     clogger.debug("DEBUG variable>>> %s" % rep.variable)
     # TODO: check the type of the outputs
     rep.variable = ds.variable
@@ -1139,14 +1191,15 @@ def noselect(userflags, ds, format):
 
     can_select = False
 
-    if ((userflags.canSelectVar or ds.oneVarPerFile()) and
-            (userflags.canSelectTime or ds.periodIsFine()) and
-            (userflags.canSelectDomain or ds.domainIsFine()) and
-            (userflags.canAggregateTime or ds.periodHasOneFile()) and
-            (userflags.canAlias or ds.hasExactVariable()) and
-            (userflags.canMissing or ds.missingIsOK()) and
-            (ds.hasOneMember()) and
-            (format == 'file')):
+    if (userflags and
+        (userflags.canSelectVar or ds.oneVarPerFile()) and
+        (userflags.canSelectTime or ds.periodIsFine()) and
+        (userflags.canSelectDomain or ds.domainIsFine()) and
+        (userflags.canAggregateTime or ds.periodHasOneFile()) and
+        (userflags.canAlias or ds.hasExactVariable()) and
+        (userflags.canMissing or ds.missingIsOK()) and
+        (ds.hasOneMember()) and
+        (format in ['file', ])):
         can_select = True
 
     return can_select
@@ -1198,7 +1251,7 @@ def cfile(object, target=None, ln=None, hard=None, deep=None):
     clogger.debug("Starting cfile at: " + start_time.strftime("%Y-%m-%d %H:%M:%S"))
     #
     # -- Evaluate the CliMAF object
-    result = ceval(object, format='file', deep=deep)
+    result, costs = ceval(object, format='file', deep=deep)
     #
     end_time = datetime.now()
     duration = end_time - start_time
@@ -1207,9 +1260,12 @@ def cfile(object, target=None, ln=None, hard=None, deep=None):
     if target is None:
         return result
     else:
+        if isinstance(object, cens):
+            clogger.error("Cannot create a single file hosting an ensemble. See function efile()")
+            return None
         target = os.path.abspath(os.path.expanduser(target))
         target_dir = os.path.dirname(target)
-        if isinstance(object, climaf.classes.cens):
+        if isinstance(object, cens):
             raise Climaf_Driver_Error("Cannot yet copy or link result files for an ensemble")
         if result is None:
             raise Climaf_Driver_Error("Issue when evaluating %s" % object)
@@ -1260,7 +1316,8 @@ def cshow(obj):
     ( launch computation if needed. )
     """
     clogger.debug("cshow called on " + str(obj))
-    return climaf.driver.ceval(obj, format='MaskedArray')
+    value, costs = ceval(obj, format='MaskedArray')
+    return value
 
 
 def cMA(obj, deep=None):
@@ -1280,14 +1337,20 @@ def cMA(obj, deep=None):
 
     """
     clogger.debug("cMA called with arguments : " + str(obj))
-    return climaf.driver.ceval(obj, format='MaskedArray', deep=deep)
+    value, costs = ceval(obj, format='MaskedArray', deep=deep)
+    return value
 
 
-def cvalue(obj, index=0, deep=None):
+def cvalue(obj, index=0, deep=None, cost=None):
     """
-    Return the value of the array for an object, after MV flattening, at a given index
+    Return the value of the array for an object, at a given index, after MV flattening
+
+    Arg DEEP is used as for :py:func:`climaf.driver.cfile()`
+
+    If arg COST is True, a tuple is returned, which second element is the total
+    compute cost for the value
 
-    Example:
+    Example, where the target object is a scalar (0d) field:
 
      >>> data=ds(project='mine',variable='tas', ...)
      >>> data1=time_average(data)
@@ -1297,13 +1360,17 @@ def cvalue(obj, index=0, deep=None):
     Does use the file representation of the object
     """
     if deep is None:
-        rep = cache.has_cvalue(obj.crs, index)
+        rep, costs = has_cvalue(obj.crs, index)
     else:
         rep = None
     if rep is None:
-        rep = float(cMA(obj, deep=deep).data.flat[index])
-        cache.store_cvalue(obj.crs, index, rep)
-    return rep
+        value, costs = ceval(obj, format='MaskedArray', deep=deep)
+        rep = float(value.data.flat[index])
+        store_cvalue(obj.crs, index, rep, costs)
+    if cost:
+        return rep, costs.tc
+    else:
+        return rep
 
 
 def cexport(*args, **kwargs):
@@ -1312,25 +1379,23 @@ def cexport(*args, **kwargs):
     """
     clogger.debug("cexport called with arguments" + str(args))
     if "format" in kwargs:
-        if (kwargs['format'] == "NetCDF" or kwargs['format'] == "netcdf" or kwargs['format'] == "nc"
-                or kwargs['format'] == "png" or kwargs['format'] == "pdf" or kwargs['format'] == "eps"):
+        if kwargs['format'] in ["NetCDF", "netcdf", "nc", "png", "pdf", "eps"]:
             kwargs['format'] = "file"
         if kwargs['format'] == "MA":
             kwargs['format'] = "MaskedArray"
-    return climaf.driver.ceval(*args, **kwargs)
+    return ceval(*args, **kwargs)
 
 
 def cimport(cobject, crs):
     clogger.debug("cimport called with argument", cobject)
     clogger.debug("should check syntax of arg 'crs' -TBD")
     clogger.warning("cimport is not for the dummies - Playing at your own risks !")
-    import numpy
     import numpy.ma
     if isinstance(cobject, numpy.ma.MaskedArray):
         clogger.debug("for now, use a file for importing - should revisit - TBD")
         clogger.error("not yet implemented fro Masked Arrays - TBD")
     elif isinstance(cobject, string_types):
-        cache.register(cobject, crs)
+        register(cobject, crs, compute_cost())
     else:
         clogger.error("argument is not a Masked Array nor a filename", cobject)
 
@@ -1361,7 +1426,7 @@ def cfilePage(cobj, deep, recurse_list=None):
     Returns : the filename in CliMAF cache, which contains the result (and None if failure)
 
     """
-    if not isinstance(cobj, classes.cpage):
+    if not isinstance(cobj, cpage):
         raise Climaf_Driver_Error("cobj is not a cpage object")
     clogger.debug("Computing figure array for cpage %s" % cobj.crs)
     #
@@ -1378,9 +1443,9 @@ def cfilePage(cobj, deep, recurse_list=None):
     ymargin = 30.  # Vertical shift between figures
     #
     usable_height = cobj.page_height - ymargin * (len(cobj.heights) - 1.) - y_top_margin - y_bot_margin
-    if cobj.title is not "":
+    if cobj.title != "":
         usable_height -= cobj.ybox
-    if cobj.insert is not "":
+    if cobj.insert != "":
         ins_base_width, ins_base_height = get_fig_sizes(cobj.insert)
         insert_height = int((float(ins_base_height) * cobj.insert_width) / float(ins_base_width))
         usable_height -= insert_height
@@ -1389,6 +1454,7 @@ def cfilePage(cobj, deep, recurse_list=None):
     #
     # page composition
     y = y_top_margin
+    total_costs = compute_cost()
     for line, rheight in zip(cobj.fig_lines, cobj.heights):
         # Line height in pixels
         height = usable_height * rheight
@@ -1399,9 +1465,11 @@ def cfilePage(cobj, deep, recurse_list=None):
             width = usable_width * rwidth
             scaling = "%dx%d+%d+%d" % (width, height, x, y)
             if fig:
-                figfile = ceval(fig, format="file", deep=deep, recurse_list=recurse_list)
+                figfile, costs = ceval(fig, format="file", deep=deep, recurse_list=recurse_list)
             else:
                 figfile = 'xc:None'
+                costs = compute_cost()
+            total_costs.add(costs)
             clogger.debug("Compositing figure %s", fig.crs if fig else 'None')
 
             args.extend([figfile, "-geometry", scaling, "-composite"])
@@ -1410,7 +1478,7 @@ def cfilePage(cobj, deep, recurse_list=None):
             try:
                 fig_width, fig_height = get_fig_sizes(figfile)
             except:
-                raise Climaf_Driver_Error("Issue with figure "+str(fig))
+                raise Climaf_Driver_Error("Issue with figure " + str(fig))
             # Scaling and max height
             if float(fig_width) != 1. and float(fig_height) != 1.:
                 if ((float(fig_width) / float(fig_height)) * float(height)) < width:
@@ -1442,7 +1510,7 @@ def cfilePage(cobj, deep, recurse_list=None):
         args.extend([cobj.insert, "-geometry", "x%d+%d+%d" %
                      (insert_height, (cobj.page_width - cobj.insert_width) / 2, y), "-composite"])
 
-    out_fig = cache.generateUniqueFileName(cobj.crs, format=cobj.format)
+    out_fig = generateUniqueFileName(cobj.crs, format=cobj.format)
     if cobj.page_trim:
         args.append("-trim")
     if cobj.title != "":
@@ -1455,7 +1523,8 @@ def cfilePage(cobj, deep, recurse_list=None):
 
     command = " ".join(args)
     clogger.debug("Compositing figures : %s" % command)
-    
+    tim1 = time.time()
+    #
     try:
         with open("tmp.err", "w") as fic:
             out = subprocess.check_output(command, shell=True, stderr=fic)
@@ -1463,7 +1532,10 @@ def cfilePage(cobj, deep, recurse_list=None):
         with open("tmp.err") as fic:
             err = fic.read()
         raise Climaf_Driver_Error("Compositing failed : %s for %s" % (err, command))
-
+    #
+    duration = time.time() - tim1
+    total_costs.increment(duration)
+    #
     # There are cases where subprocess doesn't raise an Error, while compositing failed
     if not os.path.exists(out_fig):
         with open("tmp.err") as fic:
@@ -1471,9 +1543,11 @@ def cfilePage(cobj, deep, recurse_list=None):
         raise Climaf_Driver_Error("Compositing failed %s for %s" % (err, command))
     os.remove("tmp.err")
 
-    if cache.register(out_fig, cobj.crs):
+    if register(out_fig, cobj.crs, total_costs):
         clogger.debug("Registering file %s for cpage %s" % (out_fig, cobj.crs))
-        return out_fig
+        return out_fig, total_costs
+    else:
+        return None, compute_cost()
 
 
 def cfilePage_pdf(cobj, deep, recurse_list=None):
@@ -1486,7 +1560,7 @@ def cfilePage_pdf(cobj, deep, recurse_list=None):
     Returns : the filename in CliMAF cache, which contains the result (and None if failure)
 
     """
-    if not isinstance(cobj, classes.cpage_pdf):
+    if not isinstance(cobj, cpage_pdf):
         raise Climaf_Driver_Error("cobj is not a cpage_pdf object")
     clogger.debug("Computing figure array for cpage %s" % cobj.crs)
     #
@@ -1505,15 +1579,18 @@ def cfilePage_pdf(cobj, deep, recurse_list=None):
             fig_nb]  # "%s"%preamb
     #
     # page composition
+    total_costs = compute_cost()
     for line in cobj.fig_lines:
         for fig in line:
             if fig:
-                figfile = ceval(fig, format="file", deep=deep, recurse_list=recurse_list)
+                figfile, costs = ceval(fig, format="file", deep=deep, recurse_list=recurse_list)
                 clogger.debug("Compositing figure %s", fig.crs)
+                total_costs.add(costs)
             else:
                 raise Climaf_Driver_Error("Each figure must exist ('None' figure is not accepted)")
             args.extend([figfile])
     #
+
     # more optional options
     if cobj.openright is True:
         args.extend(["--openright", "True"])
@@ -1538,11 +1615,13 @@ def cfilePage_pdf(cobj, deep, recurse_list=None):
 
     #
     # launch process and registering output in cache
-    out_fig = cache.generateUniqueFileName(cobj.crs, format='pdf')
+    out_fig = generateUniqueFileName(cobj.crs, format='pdf')
 
     args.extend(["--outfile", out_fig])
 
     clogger.debug("Compositing figures : %s" % repr(args))
+    tim1 = time.time()
+    #
     try:
         with open("tmp.err", "w") as fic:
             out = subprocess.check_output(" ".join(args), shell=True, stderr=fic)
@@ -1553,9 +1632,15 @@ def cfilePage_pdf(cobj, deep, recurse_list=None):
     finally:
         os.remove("tmp.err")
 
-    if cache.register(out_fig, cobj.crs):
+    #
+    duration = time.time() - tim1
+    total_costs.increment(duration)
+    #
+    if register(out_fig, cobj.crs, total_costs):
         clogger.debug("Registering file %s for cpage %s" % (out_fig, cobj.crs))
-    return out_fig
+        return out_fig, total_costs
+    else:
+        return None, compute_cost()
 
 
 def calias(project, variable, fileVariable=None, **kwargs):
@@ -1604,7 +1689,7 @@ def efile(obj, filename, force=False):
          'filename' if it already exists
 
     """
-    if isinstance(obj, classes.cens):
+    if isinstance(obj, cens):
 
         if os.path.isfile(filename):
             if force:
@@ -1618,7 +1703,7 @@ def efile(obj, filename, force=False):
             ffile = cfile(memb)
 
             f = tempfile.NamedTemporaryFile(suffix=".nc")
-            command = "ncrename -O -v %s,%s_%s %s %s" % (classes.varOf(memb), classes.varOf(memb), lab, ffile, f.name)
+            command = "ncrename -O -v %s,%s_%s %s %s" % (varOf(memb), varOf(memb), lab, ffile, f.name)
             if os.system(command) != 0:
                 raise Climaf_Driver_Error("ncrename failed : %s" % command)
 
@@ -1627,16 +1712,6 @@ def efile(obj, filename, force=False):
                 raise Climaf_Driver_Error(
                     "Issue when merging %s and %s (using command: %s)" % (f.name, filename, command2))
             f.close()
-
+            return True
     else:
         clogger.warning("objet is not a 'cens' objet")
-
-
-class Climaf_Driver_Error(Exception):
-    def __init__(self, valeur):
-        self.valeur = valeur
-        clogger.error(self.__str__())
-        cdedent(100)
-
-    def __str__(self):
-        return repr(self.valeur)
diff --git a/climaf/easyCMIP_functions.py b/climaf/easyCMIP_functions.py
index 133d27de..d9f2c897 100644
--- a/climaf/easyCMIP_functions.py
+++ b/climaf/easyCMIP_functions.py
@@ -3,8 +3,8 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.api import *
 from env.environment import *
+from climaf.api import *
 
 
 # -- easyCMIP_functions contains an ensemble of functionalities for
diff --git a/climaf/find_files.py b/climaf/find_files.py
new file mode 100644
index 00000000..45f4308f
--- /dev/null
+++ b/climaf/find_files.py
@@ -0,0 +1,534 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+""" 
+
+Find files that match patterns which include facet keywords and period handling. 
+
+"""
+
+# Created :       S.Senesi - 2014
+# Re-engineered : S.Senesi - 2021
+
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+import os
+import six
+import os.path
+import re
+import glob
+from string import Template
+import ftplib as ftp
+import getpass
+import netrc
+import copy
+
+from env.environment import *
+from env.clogging import clogger
+import env
+from climaf.utils import Climaf_Error, Climaf_Classes_Error, cartesian_product_substitute
+from climaf.period import init_period, sort_periods_list
+from climaf.netcdfbasics import fileHasVar
+
+
+def selectGenericFiles(urls, return_wildcards=None, merge_periods_on=None, return_combinations=None, **kwargs):
+    """
+    Allow to describe a ``generic`` file organization : the list of files returned
+    by this function is composed of files which :
+
+     - match the patterns in ``url`` once these patterns are instantiated by
+        the values in kwargs, and
+
+     - contain the ``variable`` provided in kwargs
+
+     - match the `period`` provided in kwargs
+
+    kwargs can have entries which are list, and are then interpreted as :
+    
+    - a first element which is a pattern (i.e. which include * or ?)
+
+    - more elements which are the possible values, as diagnosed by some logic upstream
+
+    In the pattern strings, no keyword is mandatory. However, for remote files,
+    filename pattern must include ${varname}, which is instanciated by variable
+    name or ``filenameVar`` (given via :py:func:`~climaf.classes.calias()`); this is
+    for the sake of efficiency (please complain if inadequate)
+
+    Example :
+
+    >>> selectGenericFiles(project ='my_projet',model ='my_model', simulation ='lastexp', variable ='tas',
+    ...                    period ='1980', urls =['~/DATA/${project}/${model}/*${variable}*${PERIOD}*.nc)']
+
+    /home/stephane/DATA/my_project/my_model/somefilewith_tas_Y1980.nc
+
+    In the pattern strings, the keywords that can be used in addition to the argument
+    names (e.g. ${model}) are:
+
+    - ${variable} : use it if the files are split by variable and
+        filenames do include the variable name, as this speed up the search
+
+    - ${PERIOD} : use it for indicating the period covered by each file, if this
+        is applicable in the file naming; this period can appear in filenames as
+        YYYY, YYYYMM, YYYYMMDD, YYYYMMDDHHMM, either once only, or twice with
+        separator ='-' or '_'
+
+    - wildcards '?' and '*' for matching respectively one and any number of characters
+
+
+    Résumé en francais :
+
+    - On construit une expression régulière pour matcher les périodes
+
+    - On boucle sur les patterns de la liste url :
+
+        - Instancier le pattern par les valeurs des facettes fournies, et par  ".*" pour $PERIOD
+
+        - on fait glob.glob
+
+        - on affine : on ne retient que les valeurs qui matchent avec la regexp de périodes (sous
+          réserve que le pattern contienne $PERIOD) si on n'a rien, on essaie aussi
+          avec filenameVar; d'où une liste de fichiers lfiles
+
+        - on cherche a connaitre les valeurs rencontrées pour chaque facette : on construit
+          une expression régulière (avec groupes) qui capture les valeurs de facettes
+          (y/c PERIOD) et une autre pour capturer la date seulement (est-ce bien encore
+          nécessaire ???)
+
+        - Boucle sur les fichiers de lfiles:
+
+            - si le pattern n'indique pas qu'on peut extraire la date,
+
+                - si la frequence indique un champ fixe, on retient le fichier;
+
+                - sinon , on le retient aussi sans filtrer sur la période
+
+            - si oui,
+
+                - on extrait la periode
+
+                - si elle convient (divers cas ...)
+
+                - si on a pu filtrer sur la variable,
+                    ou que variable ="*" ou variable multiple,
+                    ou que le fichier contient la bonne variable, eventuellement après renommage
+                    on retient le fichier
+
+            - A chaque fois qu'on retient un fichier , on ajoute au dict wildcard_facets les valeurs recontrées pour les
+              attributs
+
+        - Dès qu'un pattern de la  liste url a eu des fichiers qui collent, on abandonne l'examen des patterns suivants
+
+    - A la fin , on formatte le dictionnaire de valeurs de facettes qui est rendu
+
+    """
+    rep = list()
+    #
+    periods = None  # a list of periods available
+    if return_wildcards is not None:
+        periods_dict = return_wildcards.get("period", dict())
+        for val in periods_dict:
+            periods_dict[val] = set(periods_dict[val])
+    else:
+        periods_dict = dict()
+    #
+    period = kwargs['period']
+    if period == "*":
+        periods = []  # Init an empty list of all periods
+    elif isinstance(period, six.string_types):
+        period = init_period(period)
+    #
+    variable = kwargs['variable']
+    altvar = kwargs.get('filenameVar', variable)
+    #
+    # a patterns for dates for globbing
+    date_glob_patt = "*"
+    date_keyword = "${PERIOD}"
+    #
+    # a pattern for dates for regexp
+    digit = "[0-9]"
+    year = "%s{4}" % digit
+    month = "(01|02|03|04|05|06|07|08|09|10|11|12)"
+    day = "([0-3][0-9])"
+    hour = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23)"
+    minutes = "[0-5][0-9]"
+    date = "%s(%s(%s(%s(%s)?)?)?)?" % (year, month, day, hour, minutes)
+    rperiod = "(?P<period>(?P<start>%s)([_-](?P<end>%s))?)" % (date, date)
+    date_regexp_patt = rperiod
+    date_regexp_keyword = "${PERIOD}"
+    #
+    wildcards = dict()
+    #
+    save_kwargs = copy.deepcopy(kwargs)
+    for one_url in urls:
+        # Some keywords in kwargs can have values of type 'set', which must then be
+        # expanded by cartesian product
+        expanded_urls, simple_kwargs, kwargs = cartesian_product_substitute(one_url, skip_keys=["variable", ],
+                                                                            **save_kwargs)
+        for url in expanded_urls:
+            # First discard protocol prefix in url element
+            remote_prefix, basename = mysplit(url)
+            #
+            # Instantiate keywords in pattern with attributes values provided in kwargs
+            my_template = Template(basename)
+            template = my_template.safe_substitute(**simple_kwargs)
+            # print("template =",template)
+            #
+            # Construct a pattern for also globbing dates
+            temp2 = template.replace(date_keyword, date_glob_patt)
+            #
+            # Do globbing with plain varname
+            lfiles = my_glob(remote_prefix, temp2, url, date_regexp_keyword, date_regexp_patt, kwargs)
+            clogger.debug("Globbed %d files with plain varname on %s : " % (len(lfiles), temp2))
+            #
+            # If unsuccessful using varname, try with filenameVar
+            if len(lfiles) == 0 and "filenameVar" in simple_kwargs and simple_kwargs['filenameVar']:
+                # Change value of facet 'variable'
+                simple_kwargs['variable'] = simple_kwargs['filenameVar']
+                template = my_template.safe_substitute(**simple_kwargs)
+                temp2 = template.replace(date_keyword, date_glob_patt)
+                #
+                # Do globbing with fileVarname
+                lfiles = my_glob(remote_prefix, temp2, url, date_regexp_keyword, date_regexp_patt, kwargs)
+                clogger.debug("Globbed %d files for filenamevar on %s: " % (len(lfiles), temp2))
+            #
+            # For registering encountered values for those facets which have a wildcard,
+            # construct a regexp with a group name for all facets (but period)
+            facets_regexp = build_facets_regexp(one_url, kwargs, date_regexp_keyword, date_regexp_patt)
+
+            #
+            for f in lfiles:
+                #
+                # Process fixed-fields case, or extract file time period 
+                #
+                fperiod = None
+                if kwargs.get('frequency') in ["fx", "seasonnal", "annual_cycle"] or kwargs.get('period') in ['fx', ] \
+                        or kwargs.get('table') in ['fx', ]:
+                    store = False
+                    # local data
+                    if not remote_prefix and ((basename.find("${variable}") >= 0) or variable in ['*', ] or
+                                              fileHasVar(f, variable) or
+                                              (variable != altvar and fileHasVar(f, altvar))):
+                        store = True
+                    elif remote_prefix:  # remote data
+                        if (basename.find("${variable}") >= 0) or variable == '*' or \
+                           (variable != altvar and (f.find(altvar) >= 0)):
+                            store = True
+                        else:
+                            raise Climaf_Error("For remote files, filename pattern (%s) should include ${varname} " +
+                                               "(which is instanciated by variable name or filenameVar)" % f)
+                    if store and store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards,
+                                                             merge_periods_on, combinations=return_combinations):
+                        clogger.debug("adding fixed field :" + remote_prefix + f)
+                        rep.append(remote_prefix + f)
+                    continue
+                else:
+                    # Extract file period period from filename
+                    fperiod = extract_period(f, template, date_regexp_keyword, date_regexp_patt)
+    
+                #
+                # For non-fixed fields, if file period matches requested period, check variable
+                #
+                if fperiod and (periods is not None or period.intersects(fperiod)):
+                    #
+                    clogger.debug('Period is OK - Considering variable filtering on %s and %s for %s' %
+                                  (variable, altvar, f))
+                    store = False
+                    # Filter against variable
+                    if url.find("${variable}") >= 0:
+                        store = True
+                        clogger.debug('appending %s based on variable in filename' % f)
+                    elif f not in rep:
+                        # local data
+                        if not remote_prefix and (variable in ['*', ] or "," in variable or fileHasVar(f, variable) or
+                                                  (altvar != variable and fileHasVar(f, altvar))):
+                            store = True
+                            clogger.debug('appending %s based on multi-var or var exists in file ' % f)
+                        # remote data
+                        elif remote_prefix:
+                            if variable in ['*', ] or "," in variable or (variable != altvar and (f.find(altvar) >= 0)):
+                                store = True
+                                # Should check time period in the file if not date_regexp
+                                clogger.debug('appending %s based on multi-var or altvar ' % (remote_prefix + f))
+                            else:
+                                mess = "For remote files, filename pattern (%s) should include" % (remote_prefix + f)
+                                mess += " ${varname} (which is instanciated by variable name or filenameVar)"
+                                raise Climaf_Error(mess)
+                    if store:
+                        if store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on,
+                                                       fperiod, periods, periods_dict,
+                                                       combinations=return_combinations):
+                            rep.append(f)
+                else:
+                    if not fperiod:
+                        clogger.debug('not appending %s because period is None ' % f)
+                    elif not period.intersects(fperiod):
+                        clogger.debug('not appending %s because period doesn t intersect %s' % (f, period))
+                    else:
+                        clogger.debug('not appending %s for some other reason %s' % f)
+    
+        # Break on first url with any matching data
+        if len(rep) > 0:
+            clogger.debug('url %s does match for ' % url + repr(kwargs))
+            break
+        
+    # Post-process wildcard facets values
+    post_process_wildcard_facets_values(wildcards, return_wildcards, kwargs, periods_dict)
+    
+    return rep
+
+
+def mysplit(url):
+    """ Splits the url in a prefix (before all ':') and basename
+    Remove double // in basename
+    """
+    remote_prefix = ""
+    if re.findall(".*:.*", url):
+        remote_prefix = ':'.join(url.split(":")[0:-1]) + ':'
+    basename = url.split(":")[-1]  # This discard the remote_prefix if any
+    basename = basename.replace("//", "/")
+    return remote_prefix, basename
+
+
+def build_facets_regexp(string, kwargs, date_regexp_keyword, date_regexp_patt):
+    """
+    STRING is a pattern which includes facet keywords, KWARGS is a dict of 
+    keyword/values
+
+    For later extracting encountered values for those facets which have a wildcard,
+    we construct and return a regexp with a group name for each facet present in KWARGS 
+    (but period)
+    """
+    _, base = mysplit(string)
+    
+    # Change glob syntax wildcards to regexp syntax
+    base = base.replace("?", ".").replace("*", ".*")
+    
+    # Substitute rightmost occurrences by a group-capable pattern which name is the facet name
+    alt_kwargs = kwargs.copy()
+    for kw in kwargs:
+        if isinstance(kwargs[kw], six.string_types):  # This excludes period attribute, which has a type
+            alt_kwargs[kw] = kwargs[kw].replace("?", ".").replace("*", ".*")
+            # We replace by the rightmost because matching fields in filenames
+            # (so, at right) is trickier than in pathnames when using wildcards
+            # (we could do that only for wildcards facets)
+            base = rreplace(base, r"${%s}" % kw, r"(?P<%s>%s)" % (kw, alt_kwargs[kw]))
+    # We substitute second and next occurrences by non-group-capable patterns
+    facets_regexp = Template(base).safe_substitute(**alt_kwargs)
+    #
+    # Same for date regexp, except from the left (why ?)  
+    facets_regexp = facets_regexp.replace(date_regexp_keyword, date_regexp_patt, 1)
+    facets_regexp = facets_regexp.replace(date_regexp_keyword, ".*")
+    #
+    return facets_regexp
+
+
+def rreplace(thestring, replaced, replacement):
+    # Does replace only the rightmost occurrence of REPLACED in
+    # THESTRING by REPLACEMENT
+    deb = thestring.rfind(replaced)
+    if deb >= 0:
+        end = deb + len(replaced)
+        return thestring[0:deb] + replacement + thestring[end:]
+    else:
+        return thestring
+
+
+def store_wildcard_facet_values(f, facets_regexp, kwargs, wildcards, merge_periods_on=None,
+                                fperiod=None, periods=None, periods_dict=None, combinations=None):
+    """Using a (groups-capable) regexp FACETS_REGEXP for finding facet
+    values, analyze string F for finding the value of each keyword
+    (facet name) in KWARGS, and stores (add) it in dict WILDCARDS
+    (which keys are facet names and values are set of encountered
+    values)
+
+    If COMBINATIONS is not None on input, it is fed as a list of dict 
+    providing key/values pairs for all cases with data
+
+    Regarding periods, ... (TBD)
+
+    Return False if any facet value is not allowed in project kwargs["project"]
+
+    """
+    
+    #
+    # first check that all facet values belong to the list of autorized
+    # values possibly defined for each facet. Return False otherwise
+    project = kwargs["project"]
+    proj = cprojects[project]
+    for kw in kwargs:
+        it = re.finditer(facets_regexp, f)
+        for oc in it:
+            try:
+                facet_value = oc.group(kw)
+            except:
+                continue
+            valid_values = proj.cvalid(kw, None)
+            if isinstance(valid_values, list) and (facet_value not in valid_values):
+                clogger.info("Facet value %s for %s is not allowed (in %s)" % (facet_value, kw, f))
+                return False
+    #
+    combination = dict()
+    if fperiod is not None and periods is not None:
+        clogger.debug('Adding period %s' % fperiod)
+        periods.append(fperiod)
+    #
+    for kw in kwargs:
+        it = re.finditer(facets_regexp, f)
+        clogger.debug("Store_wildcard... : processing facet %s with \n%s and \n%s" % (kw, facets_regexp, f))
+        for oc in it:
+            try:
+                facet_value = oc.group(kw)
+            except:
+                clogger.debug("Store_wildcard... : facet %s does not occur in %s for \n%s" % (kw, facets_regexp, f))
+                continue
+            clogger.debug("Store_wildcard... : facet %s = %s" % (kw, facet_value))
+            combination[kw] = facet_value
+            if isinstance(kwargs[kw], six.string_types) and ("*" in kwargs[kw] or "?" in kwargs[kw]):
+                if facet_value is not None:
+                    if kw not in wildcards:
+                        wildcards[kw] = set()
+                    wildcards[kw].add(facet_value)
+                    clogger.debug("Discover %s =%s for file =%s" % (kw, facet_value, f))
+                else:
+                    clogger.error("Logic issue for kw =%s and file =%s" % (kw, f))
+                #
+                if fperiod is not None and periods is not None:
+                    if merge_periods_on is None:
+                        key = None
+                    elif kw == merge_periods_on:
+                        key = facet_value
+                    else:
+                        continue
+                    if key not in periods_dict:
+                        periods_dict[key] = set()
+                    clogger.debug("adding period %s for key %s in %s" % (str(fperiod), key, periods_dict))
+                    periods_dict[key].add(fperiod)
+                else:
+                    pass
+    #
+    # Store facets combination and possibly its period
+    if combinations is not None:
+        combinations.append(combination)
+    return True
+
+
+def my_glob(remote_prefix, pattern, url, date_regexp_keyword, date_regexp_patt, kwargs):
+    if remote_prefix:
+        lfiles = sorted(glob_remote_data(remote_prefix, pattern))
+        clogger.debug("Remote globbing %d files for varname on %s : " % (len(lfiles), remote_prefix + pattern))
+    else:  # local data
+        if kwargs['project'] != 'CMIP6' or not env.environment.optimize_cmip6_wildcards:
+            lfiles = sorted(glob.glob(pattern))
+        else:
+            lfiles = sorted(glob.glob(pattern))
+            # If using cmip6_optimize_wildcards_by_subsets , should
+            # rather use a globbing which tests that leaf directory
+            # exists before globing
+            # lfiles = sorted(leaf_glob(pattern))
+        clogger.debug("Before regexp filtering : Globbing %d files for varname on %s : " % (len(lfiles), pattern))
+    # Must filter with date_regexp, because * with glob for dates is too inclusive
+    ret = set()
+    for f in lfiles:
+        if re.search(date_regexp_patt, f) or date_regexp_keyword not in url:
+            ret.add(f)
+    return list(ret) 
+    
+
+def extract_period(filename, template, date_regexp_keyword, date_regexp_patt):
+    """Test if TEMPLATE includes a DATE_REGEXP_KEYWORD and if yes,
+    replaces it with DATE_REGEXP_PATTERN, after having replaced
+    globing wildcards (*,?) by regexp exquivalent wildcards (.*,.)
+
+    The returned template can be used to extract the date values,
+    provided DATE_REGEXP_PATERN is a group capable regexp
+
+    Returns None if pattern not found
+
+    """
+    # Construct regexp for extracting dates from filename
+    date_regexp = None
+    template_toreg = template.replace(r"*", r".*").replace(r"?", r".").replace(r"+", r"\+")
+    if template_toreg.find(date_regexp_keyword) >= 0:
+        date_regexp = template_toreg.replace(date_regexp_keyword, date_regexp_patt, 1)
+    if date_regexp:
+        tperiod = re.sub(date_regexp, r'\g<period>', filename)
+        if tperiod == filename:
+            raise Climaf_Error("Cannot find a period in %s with regexp %s" % (filename, date_regexp))
+        fperiod = init_period(tperiod)
+        return fperiod
+    else:
+        clogger.info("Cannot yet filter re. time using only file content (for %s)." % filename)
+
+        
+def glob_remote_data(url, pattern):
+    """
+    Returns a list of path names that match pattern, for remote data
+    located at url
+    """
+
+    if len(url.split(":")) == 3:
+        k = 1
+    else:
+        k = 0
+
+    if re.findall("@", url.split(":")[k]):
+        username = url.split(":")[k].split("@")[0]
+        host = url.split(":")[k].split("@")[-1]
+    else:
+        username = ''
+        host = url.split(":")[k]
+
+    secrets = netrc.netrc()
+
+    if username:
+        if host in secrets.hosts:
+            login, account, password = secrets.authenticators(host)
+            if login != username:
+                password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
+        else:
+            password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
+    else:
+        if host in secrets.hosts:
+            username, account, password = secrets.authenticators(host)
+        else:
+            username = eval(input("Enter login for host '%s': " % host))
+            password = getpass.getpass("Password for host '%s' and user '%s': " % (host, username))
+
+    try:
+        connect = ftp.FTP(host, username, password)
+        listfiles = connect.nlst(pattern)
+        connect.quit()
+        return listfiles
+    except ftp.all_errors as err_ftp:
+        print(err_ftp)
+        raise Climaf_Error("Access problem for data %s on host '%s' and user '%s'" % (url, host, username))
+
+
+def post_process_wildcard_facets_values(wildcards, return_wildcards, kwargs, periods_dict):
+    #  For wildcard facets, extract facet values + checks
+    for facet in wildcards:
+        s = wildcards[facet]
+        if return_wildcards is not None:
+            if facet == "period":
+                # print "s =",s," periods_dict =",periods_dict
+                for val in periods_dict:
+                    periods_dict[val] = sort_periods_list(list(periods_dict[val]))
+                clogger.info("Attribute period ='*' has values %s" % periods_dict)
+                return_wildcards["period"] = periods_dict
+            else:
+                if len(s) == 1:
+                    s = s.pop()
+                    clogger.info("Attribute %s ='%s' has matching value '%s'" % (facet, kwargs[facet], s))
+                    return_wildcards[facet] = s
+                else:
+                    rep = list(s)
+                    rep.sort()
+                    return_wildcards[facet] = rep
+                    message = "Attribute %s ='%s' has multiple values : %s" % (facet, kwargs[facet], list(s))
+                    if return_wildcards:
+                        clogger.info(message)
+                    else:
+                        clogger.error(message)
+        else:
+            clogger.debug("return_wildcards is None")
+
diff --git a/climaf/functions.py b/climaf/functions.py
index 7db8974c..ddaf988b 100644
--- a/climaf/functions.py
+++ b/climaf/functions.py
@@ -3,16 +3,16 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
+from six import string_types
+import numpy as np
+
+from env.clogging import clogger
+from env.environment import *
 from climaf.utils import Climaf_Error
 from climaf.api import *
 from climaf.operators import *
 from climaf.driver import cvalue, cfile
 from climaf import classes
-from env.clogging import clogger
-from env.environment import *
-from six import string_types
-
-import numpy as np
 
 
 def cscalar(dat):
@@ -50,9 +50,9 @@ def ensemble_intersection(ensembles):
     if not isinstance(ensembles, list):
         ensembles = [ensembles]
     for tmpens in ensembles:
-        if not isinstance(tmpens, climaf.classes.cens):
-            raise classes.Climaf_Error("Argument number %s is not a CliMAF ensemble = %s" %
-                                       (str(ensembles.index(tmpens) + 1), tmpens))
+        if not isinstance(tmpens, cens):
+            raise Climaf_Error("Argument number %s is not a CliMAF ensemble = %s" %
+                               (str(ensembles.index(tmpens) + 1), tmpens))
     #
     members = ensembles[0].order
     for tmpens in ensembles:
@@ -67,8 +67,8 @@ def ensemble_intersection(ensembles):
         return ens_list
 
     else:
-        raise classes.Climaf_Error("Your CliMAF ensembles have no member in common among all them; "
-                                   "check them manually with .order")
+        raise classes("Your CliMAF ensembles have no member in common among all them; "
+                      "check them manually with .order")
 
 
 def fmul(dat1, dat2):
@@ -87,7 +87,7 @@ def fmul(dat1, dat2):
       >>> ds1_times_c = fmul(ds1,c) # ds1 * c
     """
     #
-    if isinstance(dat1, climaf.classes.cens) and isinstance(dat2, climaf.classes.cens):
+    if isinstance(dat1, cens) and isinstance(dat2, cens):
         if dat1.order == dat2.order:
             res_dict = dict()
             for mem in dat1:
@@ -121,17 +121,17 @@ def fdiv(dat1, dat2):
       >>> ds1_times_c = fdiv(ds1,c) # ds1 * c
 
     """
-    if isinstance(dat1, climaf.classes.cens) and isinstance(dat2, climaf.classes.cens):
+    if isinstance(dat1, cens) and isinstance(dat2, cens):
         if dat1.order == dat2.order:
             res_dict = dict()
             for mem in dat1:
                 res_dict[mem] = divide(dat1[mem], dat2[mem])
             return cens(res_dict, order=dat1.order)
         else:
-            raise classes.Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
-                                       "Members of dat1 =%s ; Members of dat2 =%s\n"
-                                       "use ensemble_intersection(dat1,dat2) to get two ensembles with only "
-                                       "their common members" % (dat1.order, dat2.order))
+            raise Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
+                               "Members of dat1 =%s ; Members of dat2 =%s\n"
+                               "use ensemble_intersection(dat1,dat2) to get two ensembles with only "
+                               "their common members" % (dat1.order, dat2.order))
     elif isinstance(dat2, string_types + (int, float, np.float32)):
         c = str(float(dat2))
         return ccdo(dat1, operator='divc,' + c)
@@ -155,17 +155,17 @@ def fadd(dat1, dat2):
       >>> ds1_plus_c = fadd(ds1,c) # ds1 + c
 
     """
-    if isinstance(dat1, climaf.classes.cens) and isinstance(dat2, climaf.classes.cens):
+    if isinstance(dat1, cens) and isinstance(dat2, cens):
         if dat1.order == dat2.order:
             res_dict = dict()
             for mem in dat1:
                 res_dict[mem] = plus(dat1[mem], dat2[mem])
             return cens(res_dict, order=dat1.order)
         else:
-            raise classes.Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
-                                       "Members of dat1 =%s ; Members of dat2 =%s\n"
-                                       "use ensemble_intersection(dat1,dat2) to get two ensembles with only"
-                                       " their common members" % (dat1.order, dat2.order))
+            raise Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
+                               "Members of dat1 =%s ; Members of dat2 =%s\n"
+                               "use ensemble_intersection(dat1,dat2) to get two ensembles with only"
+                               " their common members" % (dat1.order, dat2.order))
     elif isinstance(dat2, string_types + (int, float, np.float32)):
         c = str(float(dat2))
         return ccdo(dat1, operator='addc,' + c)
@@ -176,7 +176,7 @@ def fadd(dat1, dat2):
 
 def fsub(dat1, dat2):
     """
-    Substraction of two CliMAF object, or multiplication of the CliMAF object given as first argument
+    Substraction of two CliMAF object, or substraction of the CliMAF object given as first argument
     and a constant as second argument (string, float or integer)
 
     Shortcut to ccdo(dat,operator='subc,'+str(c)) and ccdo2(dat1,dat2,operator='sub')
@@ -190,17 +190,17 @@ def fsub(dat1, dat2):
       >>> ds1_minus_c = fsub(ds1,c) # ds1 - c
       >>> just to check
     """
-    if isinstance(dat1, climaf.classes.cens) and isinstance(dat2, climaf.classes.cens):
+    if isinstance(dat1, cens) and isinstance(dat2, cens):
         if dat1.order == dat2.order:
             res_dict = dict()
             for mem in dat1:
                 res_dict[mem] = minus(dat1[mem], dat2[mem])
             return cens(res_dict, order=dat1.order)
         else:
-            raise classes.Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
-                                       "Members of dat1 =%s ; Members of dat2 =%s\n"
-                                       "use ensemble_intersection(dat1,dat2) to get two ensembles with only their"
-                                       " common members" % (dat1.order, dat2.order))
+            raise Climaf_Error("Your CliMAF ensembles (dat1 and dat2) do not have the same members "
+                               "Members of dat1 =%s ; Members of dat2 =%s\n"
+                               "use ensemble_intersection(dat1,dat2) to get two ensembles with only their"
+                               " common members" % (dat1.order, dat2.order))
     elif isinstance(dat2, string_types + (int, float, np.float32)):
         c = str(float(dat2))
         return ccdo(dat1, operator='subc,' + c)
@@ -554,7 +554,7 @@ def summary(dat):
       >>> dat= ds(....)   # some dataset, with whatever variable
       >>> summary(dat) #
     """
-    if isinstance(dat, classes.cens):
+    if isinstance(dat, cens):
         if len(list(dat)) > 0:
             kvp = getattr(dat[list(dat)[0]], 'kvp', None)
             if kvp:
@@ -563,7 +563,7 @@ def summary(dat):
             print('-- Ensemble members:')
         for m in dat.order:
             obj = dat[m]
-            if isinstance(obj, climaf.classes.cdataset):
+            if isinstance(obj, cdataset):
                 print(m)
                 files = dat[m].baseFiles(ensure_dataset=False)
                 if files:
@@ -572,7 +572,7 @@ def summary(dat):
             else:
                 print(m + " : " + repr(obj))
             print('--')
-    elif isinstance(dat, classes.cdataset):
+    elif isinstance(dat, cdataset):
         if not dat.baseFiles(ensure_dataset=False):
             print('-- No file found for:')
         else:
@@ -854,7 +854,7 @@ def ts_plot(ts, **kwargs):
 
     """
     # -- If ts is not a CliMAF ensemble, we can't pass it directly to ensemble_ts_plot
-    if not isinstance(ts, climaf.classes.cens):
+    if not isinstance(ts, cens):
         # -- Case 1: it's a single CliMAF dataset
         if not isinstance(ts, list):
             if not isinstance(ts, dict):
diff --git a/climaf/html.py b/climaf/html.py
index 917c0bd7..a4cf2bf2 100644
--- a/climaf/html.py
+++ b/climaf/html.py
@@ -25,15 +25,15 @@
     from collections.abc import KeysView, ValuesView
 except ImportError:
     from _abcoll import KeysView, ValuesView
-from env.clogging import clogger, dedent
 from functools import reduce
 import six
 
+from env.environment import *
+from env.clogging import clogger, dedent
+
 from climaf import __path__ as cpath
 from climaf.cache import getCRS
-from climaf import cachedir
 from climaf.driver import cfile
-from env.environment import *
 
 
 def header(title, style_file=None):
@@ -202,7 +202,7 @@ def link(label, filename, thumbnail=None, hover=True):
                             int(hover)
                         except:
                             raise Climaf_Html_Error("If hover is a not empty string, it must "
-                                                    "contain width and/or height, separaed by 'x' or '*'")
+                                                    "contain width and/or height, separated by 'x' or '*'")
 
                         hover_width = hover
                         hover_height = hover
@@ -309,8 +309,7 @@ def cell(label, filename=None, thumbnail=None, hover=True, dirname=None, altdir=
     else:
         fn = filename
         if altdir and fn:  # lv
-            from climaf import cachedir
-            fn = filename.replace(cachedir, altdir)
+            fn = filename.replace(default_cache, altdir)
         return '<TD ALIGN=RIGHT>' + \
                link(label, fn, thumbnail, hover) + \
                '</TD>\n'
@@ -496,6 +495,17 @@ def foo(*args):
 # cinstantiate("index.html","inst.html")
 
 
+def exec_and_discard_test(m, should_exec):
+    expression = m.group(1)
+    if should_exec:
+        # print "Executing %s"%expression
+        # try :
+        exec(expression, globals())
+        # except :
+        #    print "Issue executing %s"%expression
+    return ""
+
+
 def cinstantiate(objin, filout=None, should_exec=True):
     """ Read file or string 'objin', extract parts of text surrounded by 'Â£',
     evaluate them as Python assignments or expressions, replaces
@@ -505,17 +515,6 @@ def cinstantiate(objin, filout=None, should_exec=True):
 
      If assign is False, assignements will not be executed
     """
-
-    def exec_and_discard_test(m):
-        expression = m.group(1)
-        if should_exec:
-            # print "Executing %s"%expression
-            # try :
-            exec(expression, globals())
-            # except :
-            #    print "Issue executing %s"%expression
-        return ""
-
     #
     def replace_text_with_evaluation(m):
         expression = m.group(1)
@@ -570,15 +569,15 @@ def start_line(title):
     return tmpindex
 
 
-blank_cell = cachedir + '/Empty.png'
+blank_cell = default_cache + '/Empty.png'
 
 
 def safe_mode_cfile_plot(myplot, do_cfile=True, safe_mode=True):
-    # Need to create cachedir if it does not exist yet
-    if not os.path.isdir(cachedir):
-        os.makedirs(cachedir)
+    # Need to create default cache directory if it does not exist yet
+    if not os.path.isdir(default_cache):
+        os.makedirs(default_cache)
     if not os.path.isfile(blank_cell):
-        shutil.copy(cpath[0] + '/plot/Empty.png', cachedir)
+        shutil.copy(cpath[0] + '/plot/Empty.png', default_cache)
 
     if not do_cfile:
         return myplot
diff --git a/climaf/netcdfbasics.py b/climaf/netcdfbasics.py
index 628e9c33..789ac010 100644
--- a/climaf/netcdfbasics.py
+++ b/climaf/netcdfbasics.py
@@ -5,9 +5,9 @@
 
 import re
 
+from env.environment import *
 from env.clogging import clogger, dedent
 from climaf.period import cperiod
-from env.environment import *
 from climaf.anynetcdf import ncf
 
 
diff --git a/climaf/operators.py b/climaf/operators.py
index 55504914..c1c92e51 100644
--- a/climaf/operators.py
+++ b/climaf/operators.py
@@ -10,15 +10,13 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 import re
-import sys
-import subprocess
 
-from climaf.operators_scripts import scriptFlags
-from climaf.utils import Climaf_Operator_Error
+
 from env.clogging import clogger
 from env.environment import *
+from climaf.operators_scripts import scriptFlags
+from climaf.utils import Climaf_Operator_Error
 from climaf.driver import capply
 
 # Next definition can be splitted in a set managed by an administrator, and
@@ -240,7 +238,7 @@ def __init__(self, name, command, format="nc", select=True, canOpendap=False,
                 clogger.debug("Found %s" % executable)
                 #
                 # Analyze inputs field keywords and populate dict
-                # attribute 'inputs' with some properties
+                # attribute 'inputs' with some properties (key is rank)
                 self.inputs = dict()
                 commuteWithEnsemble = True
                 it = re.finditer(
diff --git a/climaf/period.py b/climaf/period.py
index ae1ff970..7707edd1 100644
--- a/climaf/period.py
+++ b/climaf/period.py
@@ -429,6 +429,39 @@ def firstyears(period, nyears):
     return repr(rep)
 
 
+def group_periods(diclist):
+    """Assuming DICLIST is a list of dictionnaries which include key
+    'period', identifies all dicts which have the same content for the
+    other keys, merge the periosd for those dicts and returns the list of 
+    dicts with this merge periods
+
+    Used e.g. on 'return_combinations' output of a series of selectGenericFiles
+    """
+    tempo = dict()
+    for dic in diclist:
+        aperiod = dic['period']
+        if not isinstance(aperiod, cperiod):
+            aperiod = init_period(aperiod)
+        #
+        keys = list(dic.keys())
+        keys.remove('period')
+        keys.sort()
+        tuple_key = tuple([dic[k] for k in keys])
+        #
+        if tuple_key not in tempo:
+            tempo[tuple_key] = dic.copy()
+            tempo[tuple_key]['period'] = list()
+        tempo[tuple_key]['period'].append(aperiod)
+    #
+    output = list()
+    for key in tempo:
+        dic = tempo[key]
+        dic['period'] = merge_periods(dic['period'])
+        output.append(dic)
+    #
+    return output
+        
+
 class Climaf_Period_Error(Exception):
     def __init__(self, valeur):
         self.valeur = valeur
diff --git a/climaf/plot/__init__.py b/climaf/plot/__init__.py
index a2f805b2..583cb6f9 100644
--- a/climaf/plot/__init__.py
+++ b/climaf/plot/__init__.py
@@ -2,3 +2,6 @@
 # -*- coding: utf-8 -*-
 
 from __future__ import print_function, division, unicode_literals, absolute_import
+
+from env.environment import *
+
diff --git a/climaf/plot/varlongname.py b/climaf/plot/varlongname.py
index b35b9ac7..f1316e7e 100644
--- a/climaf/plot/varlongname.py
+++ b/climaf/plot/varlongname.py
@@ -3,382 +3,150 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
+import re
+
 from env.environment import *
 
 
-def varlongname(variable):
-    """
-   Returns the long name of variable
-   """
-    longvarname = variable
+dict_std_name = dict(
     # -- ATMOSPHERE VARIABLES
-    if variable == 'tas':
-        longvarname = '2M Temperature'
-        shortvarname = '2M Temp.'
-    if variable == 'pr':
-        longvarname = 'Precipitation'
-        shortvarname = 'Precip.'
-    if variable == 'psl':
-        longvarname = 'Sea Level Pressure'
-        shortvarname = 'Sea Level Pres.'
-    if variable == 'ua':
-        longvarname = 'Zonal Wind'
-        shortvarname = 'U-Wind'
-    if variable == 'va':
-        longvarname = 'Meridional Wind'
-        shortvarname = 'V-Wind'
-    if variable == 'uas':
-        longvarname = 'Zonal Wind at 10m'
-        shortvarname = 'U-Wind 10m'
-    if variable == 'vas':
-        longvarname = 'Meridional Wind at 10m'
-        shortvarname = 'U-Wind 10m'
-    if variable == 'ta':
-        longvarname = 'Air Temperature'
-        shortvarname = 'Air Temp.'
-    if variable == 'hur':
-        longvarname = 'Relative Humidity'
-        shortvarname = 'Rel. Humidity'
-    if variable == 'hus':
-        longvarname = 'Specific Humidity'
-        shortvarname = 'Sp. Humidity'
-    if variable == 'huss':
-        longvarname = 'Specific Humidity at Surface'
-        shortvarname = 'Sp. Humidity (surf)'
-    if variable == 'hurs':
-        longvarname = 'Relative Humidity at Surface'
-        shortvarname = 'Rel. Humidity (surf)'
-    if variable == 'rstt':
-        longvarname = 'Rad SW Total TOA'
-        shortvarname = 'Rad SW Total TOA'
-    if variable == 'rsts':
-        longvarname = 'Total SW rad surface'
-        shortvarname = 'Total SW rad surf.'
-    if variable == 'rtt':
-        longvarname = 'Total Radiation TOA'
-        shortvarname = 'Total Rad. TOA'
-    if variable == 'crelt':
-        longvarname = 'Longwave Cloud Radiative Effect TOA'
-        shortvarname = 'LW CRE TOA'
-    if variable == 'crest':
-        longvarname = 'Shortwave Cloud Radiative Effect TOA'
-        shortvarname = 'SW CRE TOA'
-    if variable == 'crett':
-        longvarname = 'Total CRE TOA'
-        shortvarname = 'Total CRE TOA'
-    if variable == 'hfls':
-        longvarname = 'Latent Heat Flux'
-        shortvarname = 'Latent HF'
-    if variable == 'hfss':
-        longvarname = 'Sensible Heat Flux'
-        shortvarname = 'Sensible HF'
-    if variable == 'hfns':
-        longvarname = 'Surface Total Heat Flux'
-        shortvarname = 'Surf. Tot. HF'
-    if variable == 'zg500':
-        longvarname = '500mb geopotential height'
-        shortvarname = ''
-    if variable == 'rsut':
-        longvarname = 'Upward SW rad TOA'
-        shortvarname = ''
-    if variable == 'rlut':
-        longvarname = 'Outgoing Long Wave Radiation'
-        shortvarname = 'OLR'
-    if variable == 'rlutcs':
-        longvarname = 'Clear Sky OLR'
-        shortvarname = 'Clear sky OLR'
-    if variable == 'albs':
-        longvarname = 'Surface albedo'
-        shortvarname = ''
-    if variable == 'albt':
-        longvarname = 'Planetary albedo'
-        shortvarname = ''
-    if variable == 'cress':
-        longvarname = 'SW CRE surface'
-        shortvarname = ''
-    if variable == 'crels':
-        longvarname = 'LW CRE surface'
-        shortvarname = ''
-    if variable == 'crets':
-        longvarname = 'Total CRE surface'
-        shortvarname = ''
-    if variable == 'rts':
-        longvarname = 'Total radiation surface'
-        shortvarname = ''
-    if variable == 'rah':
-        longvarname = 'Atm. Rad. Heat.'
-        shortvarname = ''
-    if variable == 'rahcs':
-        longvarname = 'Atm. Rad. Heat. - clear sky'
-        shortvarname = ''
-    if variable == 'rahcre':
-        longvarname = 'Atm. rad. Heat. - CRE'
-        shortvarname = ''
-    if variable == 'rsah':
-        longvarname = 'Atm. SW Heat.'
-        shortvarname = ''
-    if variable == 'rsahcs':
-        longvarname = 'Atm. SW Heat. - Clear sky'
-        shortvarname = ''
-    if variable == 'rsahcre':
-        longvarname = 'Atm. SW Heat. - CRE'
-        shortvarname = ''
-    if variable == 'rlah':
-        longvarname = 'Atm. LW Heat.'
-        shortvarname = ''
-    if variable == 'rlahcs':
-        longvarname = 'Atm. LW Heat. - Clear sky'
-        shortvarname = ''
-    if variable == 'rlahcre':
-        longvarname = 'Atm. LW Heat. - CRE'
-        shortvarname = ''
-    if variable == 'cltcalipso':
-        longvarname = 'Total Cloud Cover'
-        shortvarname = ''
-    if variable == 'cllcalipso':
-        longvarname = 'Low Cloud Cover'
-        shortvarname = ''
-    if variable == 'clmcalipso':
-        longvarname = 'Medium Cloud Cover'
-        shortvarname = ''
-    if variable == 'clhcalipso':
-        longvarname = 'High Cloud Cover'
-        shortvarname = ''
-    if variable == 'rlds':
-        longvarname = 'Downward LW rad at Surface'
-        shortvarname = ''
-    if variable == 'rldscs':
-        longvarname = 'Upward LW rad at Surface - Clear Sky'
-        shortvarname = ''
-    if variable == 'hurs':
-        longvarname = 'Relative Humidity at Surface'
-        shortvarname = ''
-    if variable == 'rlus':
-        longvarname = 'Upward SW rad at Surface'
-        shortvarname = ''
-    if variable == 'rsdscs':
-        longvarname = 'Downward SW rad at Surface - Clear Sky'
-        shortvarname = ''
-    if variable == 'rsds':
-        longvarname = 'Downward SW rad at Surface'
-        shortvarname = ''
-    if variable == 'rsucs':
-        longvarname = 'Upward SW rad at Surface - Clear Sky'
-        shortvarname = ''
-    if variable == 'rsutcs':
-        longvarname = 'Upward SW rad at TOA - Clear Sky'
-        shortvarname = ''
-    if variable == 'pme':
-        longvarname = 'P-E Precip-Evap(hfls/2.5e6) mm/day'
-        shortvarname = ''
-    #
+    tas=("2M Temperature", "2M Temp."),
+    pr=('Precipitation', 'Precip.'),
+    psl=('Sea Level Pressure', 'Sea Level Pres.'),
+    ua=('Zonal Wind', 'U-Wind'),
+    va=('Meridional Wind', 'V-Wind'),
+    uas=('Zonal Wind at 10m', 'U-Wind 10m'),
+    vas=('Meridional Wind at 10m', 'V-Wind 10m'),
+    ta=('Temperature', 'Air Temp.'),
+    hur=('Relative Humidity', 'Rel. Humidity'),
+    hus=('Specific Humidity', 'Sp. Humidity'),
+    huss=('Specific Humidity at Surface', 'Sp. Humidity (surf)'),
+    hurs=('Relative Humidity at Surface', 'Rel. Humidity (surf)'),
+    rstt=('Rad SW Total TOA', 'Rad SW Total TOA'),
+    rsts=('Total SW rad surface', 'Total SW rad surf.'),
+    rtt=('Total Radiation TOA', 'Total Rad. TOA'),
+    crelt=('Longwave Cloud Radiative Effect TOA', 'LW CRE TOA'),
+    crest=('Shortwave Cloud Radiative Effect TOA', 'SW CRE TOA'),
+    crett=('Total CRE TOA', 'Total CRE TOA'),
+    hfls=('Latent Heat Flux', 'Latent HF'),
+    hfss=('Sensible Heat Flux', 'Sensible HF'),
+    hfns=('Surface Total Heat Flux', 'Surf. Tot. HF'),
+    zg=('Geopotential Height', ''),
+    rsut=('Upward SW rad TOA', ''),
+    rlut=('Outgoing Long Wave Radiation', 'OLR'),
+    rlutcs=('Clear Sky OLR', 'Clear Sky OLR'),
+    albs=('Surface albedo', ''),
+    albt=('Planetary albedo', ''),
+    cress=('SW CRE surface', ''),
+    crels=('LW CRE surface', ''),
+    crets=('Total CRE surface', ''),
+    rst=('Total radiation surface', ''),
+    rah=('Atm. Rad. Heat.', ''),
+    rahcs=('Atm. Rad. Heat. - clear sky', ''),
+    rahcre=('Atm. rad. Heat. - CRE', ''),
+    rsah=('Atm. SW Heat.', ''),
+    rsahcs=('Atm. SW Heat. - Clear sky', ''),
+    rsahcre=('Atm. SW Heat. - CRE', ''),
+    rlah=('Atm. LW Heat.', ''),
+    rlahcs=('Atm. LW Heat. - Clear sky', ''),
+    rlahcre=('Atm. LW Heat. - CRE', ''),
+    cltcalipso=('Total Cloud Cover', ''),
+    cllcalipso=('cllcalipso', ''),
+    clmcalipso=('Medium Cloud Cover', ''),
+    clhcalipso=('High Cloud Cover', ''),
+    rlds=('Downward LW rad at Surface', ''),
+    rldscs=('Upward LW rad at Surface - Clear Sky', ''),
+    rlus=('Upward SW rad at Surface', ''),
+    rsdscs=('Downward SW rad at Surface - Clear Sky', ''),
+    rsds=('Downward SW rad at Surface', ''),
+    rsucs=('Upward SW rad at Surface - Clear Sky', ''),
+    rsutcs=('Upward SW rad at TOA - Clear Sky', ''),
+    pme=('P-E Precip-Evap(hfls/2.5e6) mm/day', ''),
     # -- OCEAN VARIABLES
-    if variable == 'tos':
-        longvarname = 'Sea Surface Temperature'
-    if variable == 'sos':
-        longvarname = 'Sea Surface Salinity'
-    if variable == 'zos':
-        longvarname = 'Sea Surface Height'
-    if variable == 'to200':
-        longvarname = 'Potential Temperature at 200m'
-    if variable == 'to1000':
-        longvarname = 'Potential Temperature at 1000m'
-    if variable == 'to2000':
-        longvarname = 'Potential Temperature at 2000m'
-    if variable == 'so200':
-        longvarname = 'Salinity at 200m'
-    if variable == 'so1000':
-        longvarname = 'Salinity at 1000m'
-    if variable == 'so2000':
-        longvarname = 'Salinity at 2000m'
-    if variable == 'mlotst':
-        longvarname = 'MLD (SigmaT 0.03)'
-    if variable == 'wfo':
-        longvarname = 'E-P Budget'
-    if variable == 'tauu':
-        longvarname = 'Zonal Wind Stress'
-    if variable == 'tauv':
-        longvarname = 'Meridional Wind Stress'
-    if variable == 'hfls':
-        longvarname = 'Latent Heat Flux'
-    if variable == 'hfss':
-        longvarname = 'Sensible Heat Flux'
-    if variable == 'sic':
-        longvarname = 'Sea Ice Concentration'
-    if variable == 'sit':
-        longvarname = 'Sea Ice Thickness'
-    if variable == 'thetao':
-        longvarname = 'Potential Temperature'
-    if variable == 'so':
-        longvarname = 'Salinity'
-    if variable.lower() == 'moc':
-        longvarname = 'Merid. Overturning Circulation'
+    tos=('Sea Surface Temperature', ''),
+    sos=('Sea Surface Salinity', ''),
+    zos=('Sea Surface Height', ''),
+    to200=('Potential Temperature at 200m', ''),
+    to1000=('Potential Temperature at 1000m', ''),
+    to2000=('Potential Temperature at 2000m', ''),
+    so200=('Salinity at 200m', ''),
+    so1000=('Salinity at 1000m', ''),
+    so2000=('Salinity at 2000m', ''),
+    mlotst=('MLD (SigmaT 0.03)', ''),
+    wfo=('E-P Budget', ''),
+    tauu=('Zonal Wind Stress', ''),
+    tauv=('Meridional Wind Stress', ''),
+    sic=('Sea Ice Concentration', ''),
+    sit=('Sea Ice Thickness', ''),
+    thetao=('Potential Temperature', ''),
+    so=('Salinity', ''),
+    moc=('Merid. Overturning Circulation', ''),
     # -- ORCHIDEE VARIABLES
-    if variable == 'fluxlat':
-        longvarname = 'Latent Heat Flux'
-    if variable == 'fluxsens':
-        longvarname = 'Sensible Heat Flux'
-    if variable == 'albvis':
-        longvarname = 'Albedo Visible'
-    if variable == 'albnir':
-        longvarname = 'Albedo Near Infra Red'
-    if variable == 'tair':
-        longvarname = 'Air Temperature'
-    if variable == 'swdown':
-        longvarname = 'ShortWave Down'
-    if variable == 'lwdown':
-        longvarname = 'LongWave Down'
-    if variable == 'evapnu':
-        longvarname = 'Evaporation Bare soil'
-    if variable == 'evap':
-        longvarname = 'Evaporation'
-    if variable == 'subli':
-        longvarname = 'Sublimation'
-    if variable == 'Qs':
-        longvarname = 'Runoff'
-    if variable == 'runoff':
-        longvarname = 'Runoff'
-    if variable == 'drainage':
-        longvarname = 'Drainage'
-    if variable == 'frac_snow':
-        longvarname = 'Snow Cover'
-    if variable == 'total_soil_carb':
-        longvarname = 'Total Soil Carbon Content'
-    if variable == 'snow':
-        longvarname = 'Snow quantity - water equivalent'
+    fluxlat=('Latent Heat Flux', ''),
+    fluxsens=('Sensible Heat Flux', ''),
+    albvis=('Albedo Visible', ''),
+    albnir=('Albedo Near Infra Red', ''),
+    tair=('Air Temperature', ''),
+    swdown=('ShortWave Down', ''),
+    lwdown=('LongWave Down', ''),
+    evapnu=('Evaporation Bare soil', ''),
+    evap=('Evaporation', ''),
+    subli=('Sublimation', ''),
+    Qs=('Runoff', ''),
+    runoff=('Runoff', ''),
+    drainage=('Drainage', ''),
+    frac_snow=('Snow Cover', ''),
+    total_soil_carb=('Total Soil Carbon Content', ''),
+    snow=('Snow quantity - water equivalent', ''),
+    O2_=('Oxygen', ''),
+    Si_=('Silicate', ''),
+    NO3_=('Nitrate', ''),
+    PO4=('Phosphate', ''),
     # -- ORCHIDEE variables by PFT
-    # -- transpir
-    shortvar = 'transpir'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Transpiration ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Transpiration'
-    # -- inter
-    shortvar = 'inter'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Interception (evaporation) ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Interception (evaporation)'
-    # -- gpp
-    shortvar = 'gpp'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Global Primary Productivity ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Global Primary Productivity'
-    # -- GPP
-    shortvar = 'GPP'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Global Primary Productivity ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Global Primary Productivity'
-    # -- lai
-    shortvar = 'lai'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Leaf Area Index ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Leaf Area Index'
-    # -- maint_resp
-    shortvar = 'maint_resp'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Maintenance Respiration ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Maintenance Respiration'
-    # -- growth_resp
-    shortvar = 'growth_resp'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Growth Respiration ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Growth Respiration'
-    # -- hetero_resp
-    shortvar = 'hetero_resp'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Heterotrophic Respiration ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Heterotrophic Respiration'
-    # -- auto_resp
-    shortvar = 'auto_resp'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Autotrophic Respiration ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Autotrophic Respiration'
-    # -- nee
-    shortvar = 'nee'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Net Carbon Flux ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Net Carbon Flux'
-    # -- vegetfrac
-    shortvar = 'vegetfrac'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Vegetation Fraction ' + str.replace(variable, shortvar + '_', '')
-        else:
-            longvarname = 'Vegetation Fraction'
-    # -- maxvegetfrac
-    shortvar = 'maxvegetfrac'
-    if shortvar in variable:
-        if 'PFT' in variable:
-            longvarname = 'Max Vegetation Fraction ' + str.replace(variable, shortvar + '_', '')
+    transpir=("Transpiration", ""),
+    inter=("Interception (evaporation)", ""),
+    gpp=('Global Primary Productivity', ''),
+    GPP=('Global Primary Productivity', ''),
+    lai=('Leaf Area Index', ''),
+    maint_resp=('Maintenance Respiration', ''),
+    growth_resp=('Growth Respiration', ''),
+    hetero_resp=('Heterotrophic Respiration', ''),
+    auto_resp=('Autotrophic Respiration', ''),
+    nee=('Net Carbon Flux', ''),
+    vegetfrac=('Vegetation Fraction', ''),
+    maxvegetfrac=('Max Vegetation Fraction', '')
+)
+
+
+variable_regexp = re.compile(r"^(?P<var>([a-zA-Z]+|\w+_))(?P<nb>\d+)(?P<meter>m?)$")
+
+
+def varlongname(variable):
+    """
+   Returns the long name of variable
+   """
+    if variable in dict_std_name:
+        longvarname, shortvarname = dict_std_name[variable]
+    else:
+        match_regexp = variable_regexp.match(variable)
+        if match_regexp is not None:
+            var = match_regexp.groupdict()["var"]
+            nb = match_regexp.groupdict()["nb"]
+            meter = match_regexp.groupdict()["meter"] in ["m", ]
+            longvarname, shortvarname = dict_std_name.get(var, ('', ''))
+            if meter:
+                suffix = "%dm" % nb
+            else:
+                suffix = "%dmb" % nb
+            if longvarname in ["", ]:
+                longvarname = variable
+            else:
+                longvarname += " at %s" % suffix
+            if shortvarname not in ["", ]:
+                shortvarname += " %s" % suffix
         else:
-            longvarname = 'Max Vegetation Fraction'
-    #
-    # -- Zonal wind on the column
-    if 'ua' in variable:
-        tmpvar = str.replace(variable, 'ua', '')
-        if tmpvar in ['850', '700', '500', '200']:
-            longvarname = 'Zonal Wind at ' + tmpvar + 'mb'
-    #
-    # -- Meridional wind on the column
-    if 'va' in variable:
-        tmpvar = str.replace(variable, 'va', '')
-        if tmpvar in ['850', '700', '500', '200']:
-            longvarname = 'Meridional Wind at ' + tmpvar + 'mb'
-    #
-    # -- Meridional wind on the column
-    if 'ta' in variable:
-        tmpvar = str.replace(variable, 'ta', '')
-        if tmpvar in ['850', '700', '500', '200']:
-            longvarname = 'Temperature at ' + tmpvar + 'mb'
-    #
-    # -- Geopotential height
-    if 'zg' in variable:
-        tmpvar = str.replace(variable, 'zg', '')
-        if tmpvar in ['850', '700', '500', '200']:
-            longvarname = 'Geopotential Height at ' + tmpvar + 'mb'
-    #
-    # -- O2
-    if 'O2_' in variable:
-        tmpvar = str.replace(variable, 'O2_', '')
-        if tmpvar in ['surf', '300m', '1000m', '2500m']:
-            longvarname = 'Oxygen at ' + tmpvar
-    #
-    # -- Si
-    if 'Si_' in variable:
-        tmpvar = str.replace(variable, 'Si_', '')
-        if tmpvar in ['surf', '300m', '1000m', '2500m']:
-            longvarname = 'Silicate at ' + tmpvar
-    #
-    # -- NO3
-    if 'NO3_' in variable:
-        tmpvar = str.replace(variable, 'NO3_', '')
-        if tmpvar in ['surf', '300m', '1000m', '2500m']:
-            longvarname = 'Nitrate at ' + tmpvar
-    #
-    # -- NO3
-    if 'PO4_' in variable:
-        tmpvar = str.replace(variable, 'PO4_', '')
-        if tmpvar in ['surf', '300m', '1000m', '2500m']:
-            longvarname = 'Phosphate at ' + tmpvar
-    #
-    #
+            longvarname = variable
+            shortvarname = ""
+    if "PFT" in variable:
+        longvarname += " %s" % variable.replace(variable + "_", '')
     return longvarname
diff --git a/climaf/projects/__init__.py b/climaf/projects/__init__.py
index 3934c229..bbfbba12 100644
--- a/climaf/projects/__init__.py
+++ b/climaf/projects/__init__.py
@@ -43,6 +43,9 @@
 
 from __future__ import print_function, division, absolute_import  # ,unicode_literals
 
+from env.environment import *
+
+
 __all__ = ["example", "cami", "cmip3", "cmip5", "ocmip5", "obs4mips", "em", "erai", "erai_land", "gpcc", "gpcp",
            "ceres", "cruts3", "file", "ref_climatos_and_ts", "igcm_out", "emn36", "nemo", "igcm_cmip6", "cmip6", "eobs",
-           "cordex", "cmip6cerfacs", "ref_era5cerfacs"]
+           "cordex", "cmip6cerfacs", "ref_era5cerfacs", "optimize"]
diff --git a/climaf/projects/cmip5.py b/climaf/projects/cmip5.py
index 71acc958..7dff2129 100644
--- a/climaf/projects/cmip5.py
+++ b/climaf/projects/cmip5.py
@@ -18,12 +18,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
-
+from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
 
 root = None
 if onCiclad:
@@ -43,7 +41,7 @@
     # ------------------------------------ >
     cproject('CMIP5_extent', 'root', 'model', 'table', 'experiment', 'extent_experiment', 'realization', 'frequency',
              'realm',
-             'version', ensemble=['model', 'realization'], separator='%')
+             'version', 'extent_version', ensemble=['model', 'realization'], separator='%')
 
     # -- Define the pattern for CMIP5
     if atCNRM:
@@ -54,20 +52,25 @@
                    '${realization}/${version}/${variable}/'
 
     # a pattern for fixed fields
-    patternf = pattern1 + '${variable}_${table}_${model}_${experiment}_${realization}.nc'
+    patternf = pattern1 + '${variable}_${table}_${model}_${experiment}_r0i0p0.nc'
+    patternf = patternf.replace("/${realization}", "/r0i0p0").replace("/${version}", "/latest")
+    patternf = patternf.replace("/${frequency}", "/fx").replace("/${table}", "/fx")
+    # On Ciclad, some fixed fields don't have the last (per-variable) directories level
+    patternf2 = patternf.replace("/${variable}/", "/")
 
     # The pattern for fields with a period
     pattern1 += '${variable}_${table}_${model}_${experiment}_${realization}_${PERIOD}.nc'
 
     # -- And the additionnal pattern for extent
     pattern2 = '${root}/CMIP5/output*/*/${model}/${extent_experiment}/${frequency}/${realm}/${table}/${realization}/' \
-               '${version}/${variable}/'
+               '${extent_version}/${variable}/'
     pattern2 += '${variable}_${table}_${model}_${extent_experiment}_${realization}_${PERIOD}.nc'
 
     # -- call the dataloc CliMAF function
     # -- CMIP5
     dataloc(project='CMIP5', organization='generic', url=pattern1)
     dataloc(project='CMIP5', organization='generic', url=patternf)
+    dataloc(project='CMIP5', organization='generic', url=patternf2)
     # -- CMIP5_extent
     dataloc(project='CMIP5_extent', organization='generic', url=pattern1)
     dataloc(project='CMIP5_extent', organization='generic', url=pattern2)
diff --git a/climaf/projects/cmip6.py b/climaf/projects/cmip6.py
index e89d7ba9..4deb8abb 100644
--- a/climaf/projects/cmip6.py
+++ b/climaf/projects/cmip6.py
@@ -19,12 +19,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
-
+from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
 from env.environment import *
-from climaf.dataloc import dataloc, cvalid
+from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
+from climaf.projects.cmip6_valid import set_CMIP6_valid_values
 
 root = None
 if atTGCC:
@@ -38,7 +37,6 @@
     # Declare a list of root directories for IPSL data at TGCC
     root = "/cnrm/cmip"
 
-# if root:
 if True:
     # -- Declare a 'CMIP6 CliMAF project
     # ------------------------------------ >
@@ -64,11 +62,11 @@
         cdef('model',       '*',                        project=project)
         cdef('mip',         '*',                        project=project)
         cdef('grid',        'g*',                       project=project)
-        cvalid('grid',      ["gr", "gn", "gr1", "gr2"], project=project)
         cdef('realization', 'r1i1p1f*',                 project=project)
         cdef('experiment',  'historical',               project=project)
         cdef('version',     'latest',                   project=project)
         cdef('table',       '*',                        project=project)
+        set_CMIP6_valid_values(project)
         #
         calias(project, 'tos', offset=273.15)
         calias(project, 'thetao', offset=273.15)
@@ -89,11 +87,11 @@
     cdef('model',       'IPSL-CM6A-LR',             project=project)
     cdef('mip',         'CMIP',                     project=project)
     cdef('grid',        'g*',                       project=project)
-    cvalid('grid',      ["gr", "gn", "gr1", "gr2"], project=project)
     cdef('realization', 'r1i1p1f*',                 project=project)
     cdef('experiment',  'historical',               project=project)
     cdef('version',     'latest',                   project=project)
     cdef('table',       '*',                        project=project)
+    set_CMIP6_valid_values(project)
 
     # -------------
     # -- Define the patterns
@@ -123,3 +121,4 @@
     dataloc(project='IPSL-CM6_historical-EXT', url=patterns3)
 
 
+
diff --git a/climaf/projects/cmip6_valid.py b/climaf/projects/cmip6_valid.py
new file mode 100644
index 00000000..3f2893d4
--- /dev/null
+++ b/climaf/projects/cmip6_valid.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+
+from env.environment import cprojects
+    
+    
+def set_CMIP6_valid_values(project):
+
+    # No list of valid values for : root, realization, version
+
+    proj = cprojects[project]
+    
+    proj.cvalid('institute', ['AS-RCEC', 'CCCma', 'CSIRO-ARCCSS', 'INM', 'MPI-M',
+                              'NIMS-KMA', 'UA', 'AWI', 'CCCR-IITM', 'E3SM-Project',
+                              'IPSL', 'MRI', 'NOAA-GFDL', 'BCC', 'CMCC', 'EC-Earth-Consortium',
+                              'KIOST', 'NASA-GISS,' 'NUIST', 'CAMS', 'CNRM-CERFACS',
+                              'FIO-QLNM', 'MIROC', 'NCAR', 'SNU', ' CAS', 'CSIRO',
+                              'HAMMOZ-Consortium', 'MOHC', 'NCC', 'THU']
+                )
+    proj.cvalid('model', ["UKESM1-0-LL", "HadGEM3-GC31-LL", "MRI-ESM2-0",
+                          "EC-Earth3", "EC-Earth3-AerChem", "ACCESS-ESM1-5", "ACCESS-CM2",
+                          "MIROC6", "CNRM-CM6-1", "CNRM-ESM2-1", "MPI-ESM1-2-LR", "MPI-ESM-1-2-HAM",
+                          "NorESM2-LM", "NorESM2-MM", "GFDL-ESM4", "GFDL-CM4", "IPSL-CM6A-LR",
+                          "4AOP-v1-5", "IPSL-CM6A-LR-INCA", "CESM2-WACCM", "CESM2", "GISS-E2-1-G",
+                          "CanESM5", "CMCC-ESM2", "CMCC-CM2-SR5", "FIO-ESM-2-0", "MIROC-ES2L",
+                          "TaiESM1-TIMCOM", "FGOALS-f3-L", "CAS-ESM2-0", "CanESM5-CanOE",
+                          "HadGEM3-GC31-MM", "BCC-CSM2-MR", "MPI-ESM1-2-HR", "NorCPM1",
+                          "CESM1-1-CAM5-CMIP5", "BCC-ESM1", "MIROC-ES2H-NB", "IPSL-CM5A2-INCA",
+                          "AWI-CM-1-1-MR", "MCM-UA-1-0", "CAMS-CSM1-0", "E3SM-1-1", "EC-Earth3-CC",
+                          "EC-Earth3-Veg-LR", "EC-Earth3-Veg", "NESM3", "KACE-1-0-G",
+                          "CNRM-CM6-1-HR", "KIOST-ESM", "IITM-ESM", "INM-CM5-0", "INM-CM4-8",
+                          "CIESM", "FGOALS-g3", "TaiESM1", "GISS-E2-1-H", "GISS-E2-2-G",
+                          "AWI-ESM-1-1-LR", "EC-Earth3-LR", "NorESM1-F", "E3SM-1-1-ECA",
+                          "GISS-E2-1-G-CC", "CMCC-CM2-HR4", "E3SM-1-0", "EC-Earth3P-VHR",
+                          "SAM0-UNICON", "MIROC-ES2H", "GFDL-AM4", "CESM2-WACCM-FV2",
+                          "CESM2-FV2", "CMCC-CM2-VHR4", "EC-Earth3P", "EC-Earth3P-HR",
+                          "BCC-CSM2-HR", "NICAM16-7S", "NICAM16-8S", "ECMWF-IFS-MR", "ECMWF-IFS-LR",
+                          "ECMWF-IFS-HR", "MPI-ESM1-2-XR", "INM-CM5-H", "IPSL-CM6A-ATM-HR",
+                          "IPSL-CM7A-ATM-LR", "IPSL-CM7A-ATM-HR", "CESM1-CAM5-SE-LR", "CESM1-CAM5-SE-HR"]
+                )
+    proj.cvalid('mip', ['AerChemMIP', 'C4MIP', 'CDRMIP', 'CFMIP', 'CMIP', 'DAMIP', 'DCPP',
+                        'GeoMIP', 'GMMIP', 'HighResMIP', 'LS3MIP', 'LUMIP', 'OMIP', 'PAMIP',
+                        'PMIP', 'RFMIP', 'ScenarioMIP']
+                )
+    proj.cvalid('grid', ["gr", "gn", "gr1", "gr2"]
+                )
+    proj.cvalid('experiment', ["piClim-lu", "piClim-ghg", "piClim-anthro", "piClim-4xCO2", "piClim-control",
+                               "piClim-histghg", "piClim-histaer", "piClim-aer", "piClim-histall", "piClim-histnat",
+                               "piClim-spAer-aer", "piClim-spAer-histall", "piClim-spAer-anthro", "hist-spAer-all",
+                               "rad-irf", "omip1", "dcppB-forecast", "dcppA-hindcast", "dcppA-assim", "dcppC-amv-neg",
+                               "dcppC-amv-pos", "dcppC-ipv-neg", "dcppC-ipv-NexTrop-neg", "dcppC-pac-control",
+                               "dcppC-amv-ExTrop-neg", "dcppC-ipv-NexTrop-pos", "dcppC-pac-pacemaker",
+                               "dcppC-atl-pacemaker", "dcppC-amv-Trop-pos", "dcppC-ipv-pos", "dcppC-atl-control",
+                               "dcppC-amv-Trop-neg", "dcppC-amv-ExTrop-pos", "ssp370", "hist-piAer", "ssp370-lowNTCF",
+                               "piClim-2xdust", "piClim-NH3", "piClim-BC", "piClim-OC", "piClim-2xss", "piClim-SO2",
+                               "amip-hist-EXT", "amip-TIP", "amip-hist", "ssp119", "ssp245", "ssp534-over", "ssp585",
+                               "ssp434", "ssp126", "ssp460", "pdSST-pdSIC", "pdSST-futArcSIC", "piSST-piSIC",
+                               "pdSST-piAntSIC", "futSST-pdSIC", "pdSST-futBKSeasSIC", "pdSST-piArcSIC", "piSST-pdSIC",
+                               "pdSST-futAntSIC", "pdSST-futOkhotskSIC", "G6sulfur", "G1", "G7cirrus", "G6solar",
+                               "amip-piForcing", "abrupt-2xCO2", "amip", "abrupt-0p5xCO2", "aqua-p4K", "piSST-4xCO2",
+                               "piSST", "piSST-pxK", "a4SSTice", "amip-4xCO2", "amip-p4K", "abrupt-solp4p", "amip-m4K",
+                               "amip-a4SST-4xCO2", "piSST-4xCO2-rad", "a4SSTice-4xCO2", "a4SST", "aqua-p4K-lwoff",
+                               "aqua-control", "aqua-4xCO2", "aqua-control-lwoff", "abrupt-solm4p", "amip-p4K-lwoff",
+                               "amip-future4K", "amip-piForcing-EXT", "amip-lwoff", "hist-GHG", "hist-aer", "hist-nat",
+                               "hist-stratO3", "ssp245-GHG", "ssp245-nat", "land-hist-cruNcep", "land-hist",
+                               "amip-lfmip-rmLC", "amip-lfmip-pdLC", "hist-noLu", "ssp370-ssp126Lu", "ssp126-ssp370Lu",
+                               "deforest-globe", "land-noLu", "land-cCO2", "past1000", "midHolocene", "lig127k", "lgm",
+                               "midPliocene-eoi400", "1pctCO2-rad", "esm-ssp585", "1pctCO2-bgc", "ssp534-over-bgc",
+                               "hist-bgc", "ssp585-bgc", "abrupt-4xCO2", "historical", "esm-hist", "piControl",
+                               "esm-piControl", "1pctCO2", "historical-EXT", "piControl-spinup", "esm-ssp534-over",
+                               "hist-1950", "highresSST-present", "spinup-1950", "control-1950"]
+                )
+    proj.cvalid('table', ['3hr', '6hrLev', '6hrPlev', '6hrPlevPt', 'AERday', 'AERhr', 'AERmon', 'AERmonZ', 'Amon',
+                          'CF3hr', 'CFday', 'CFmon', 'CFsubhr', 'day', 'E1hr', 'E3hr', 'Eday', 'EdayZ', 'Efx', 'Emon',
+                          'EmonZ', 'fx', 'LImon', 'Lmon', 'Oday', 'Ofx', 'Omon', 'SIday', 'SImon']
+                )
diff --git a/climaf/projects/cordex.py b/climaf/projects/cordex.py
index 5108d8c5..6a9790a6 100644
--- a/climaf/projects/cordex.py
+++ b/climaf/projects/cordex.py
@@ -17,12 +17,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 
+from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip, atCNRM
 
 root = None
 if onCiclad:
diff --git a/climaf/projects/cruts3.py b/climaf/projects/cruts3.py
index a969bb87..30e2588a 100644
--- a/climaf/projects/cruts3.py
+++ b/climaf/projects/cruts3.py
@@ -22,9 +22,10 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 from env.environment import *
+from env.site_settings import atCNRM
+
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cdef
-from env.site_settings import atCNRM
 
 if atCNRM:
     cproject('cruts3', 'grid')  # no grid writes as '', otherwise as e.g. 'T127'
diff --git a/climaf/projects/em.py b/climaf/projects/em.py
index 5349c7b8..764da410 100644
--- a/climaf/projects/em.py
+++ b/climaf/projects/em.py
@@ -65,9 +65,9 @@
 
     # User simulations
     pathg = "${root}/${group}/${simulation}/"
-    pathA = pathg + "${realm}/${simulation}PLYYYY.nc"  # GSAG
-    pathL = pathg + "${realm}/${simulation}SFXYYYY.nc"
-    pathI = pathg + "${realm}/${variable}_O1_YYYY-YYYY.nc"  # HISTNATr8
+    pathA = pathg + "${realm}/${simulation}PL${PERIOD}.nc"  # GSAG
+    pathL = pathg + "${realm}/${simulation}SFX${PERIOD}.nc"
+    pathI = pathg + "${realm}/${variable}_O1_${PERIOD}-${PERIOD}.nc"  # HISTNATr8
     pathO = pathg + "${realm}/${simulation}_1${frequency}_${PERIOD}_grid_${variable}.nc"  # var:T_table2.2
     pathO2 = pathg + "${realm}/${simulation}_1${frequency}_${PERIOD}_scalar_table2.2.nc"  # PICTLWS2, PRE6CPLCr2alb
 
@@ -75,8 +75,8 @@
 
     # Shared simulations - example : group=SC
     pathg = "/cnrm/cmip/cnrm/simulations/${group}/"
-    pathgA = pathg + "${realm}/Regu/${frequency}/${simulation}/${simulation}PLYYYY.nc"  # C1P60
-    pathgL = pathg + "${realm}/Regu/${frequency}/${simulation}/${simulation}SFXYYYY.nc"  # C1P60
+    pathgA = pathg + "${realm}/Regu/${frequency}/${simulation}/${simulation}PL${PERIOD}.nc"  # C1P60
+    pathgL = pathg + "${realm}/Regu/${frequency}/${simulation}/${simulation}SFX${PERIOD}.nc"  # C1P60
     pathgI = pathg + "${realm}/Origin/Monthly/${simulation}/${variable}_O1_${PERIOD}.nc"  # HISTNATr8
     pathgO = pathg + "${realm}/Origin/Monthly/${simulation}/${simulation}_1${frequency}_${PERIOD}_grid_${variable}.nc"
 
diff --git a/climaf/projects/eobs.py b/climaf/projects/eobs.py
index 198cfbb3..019eb7b1 100644
--- a/climaf/projects/eobs.py
+++ b/climaf/projects/eobs.py
@@ -13,10 +13,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.dataloc import dataloc
-from climaf.classes import cproject, calias, cfreqs, cdef
 from env.site_settings import onCiclad, atTGCC, atIDRIS, atCerfacs, atCNRM
 from env.environment import *
+from climaf.dataloc import dataloc
+from climaf.classes import cproject, calias, cfreqs, cdef
 
 if onCiclad:
     # -- Create E-OBS CliMAF project
diff --git a/climaf/projects/erai.py b/climaf/projects/erai.py
index 44ae5761..3dd3343f 100644
--- a/climaf/projects/erai.py
+++ b/climaf/projects/erai.py
@@ -21,10 +21,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.dataloc import dataloc
-from climaf.classes import cproject, calias
 from env.site_settings import atCNRM
 from env.environment import *
+from climaf.dataloc import dataloc
+from climaf.classes import cproject, calias
+
 
 if atCNRM:
     cproject('erai', ('grid', '_'),
diff --git a/climaf/projects/erai_land.py b/climaf/projects/erai_land.py
index c1c7c1c3..d726ed9b 100644
--- a/climaf/projects/erai_land.py
+++ b/climaf/projects/erai_land.py
@@ -25,10 +25,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from climaf.dataloc import dataloc
-from climaf.classes import cproject, calias
 from env.site_settings import atCNRM
 from env.environment import *
+from climaf.dataloc import dataloc
+from climaf.classes import cproject, calias
+
 
 if atCNRM:
     cproject('erai-land', 'grid')  # no grid writes as '_' , otherwise as e.g. 'T127'
diff --git a/climaf/projects/example.py b/climaf/projects/example.py
index ae654c10..804d6042 100644
--- a/climaf/projects/example.py
+++ b/climaf/projects/example.py
@@ -14,7 +14,6 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 
 from env.environment import *
 from climaf.dataloc import dataloc
@@ -27,8 +26,8 @@
 cproject("example", ("frequency", "monthly"), separator='|')
 cfreqs('example', {'monthly': 'mon'})
 
-data_pattern_L = cpath + "/../examples/data/${simulation}/L/${simulation}SFXYYYY.nc"
-data_pattern_A = cpath + "/../examples/data/${simulation}/A/${simulation}PLYYYY.nc"
+data_pattern_L = cpath + "/../examples/data/${simulation}/L/${simulation}SFX${PERIOD}.nc"
+data_pattern_A = cpath + "/../examples/data/${simulation}/A/${simulation}PL${PERIOD}.nc"
 data_pattern_histmth = cpath + "/../examples/data/${simulation}_SE_1982_1991_1M_ua_pres.nc"
 dataloc(project="example", organization="generic", url=[data_pattern_A, data_pattern_L, data_pattern_histmth])
 
diff --git a/climaf/projects/gpcc.py b/climaf/projects/gpcc.py
index 3e037184..6803abf4 100644
--- a/climaf/projects/gpcc.py
+++ b/climaf/projects/gpcc.py
@@ -22,10 +22,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
+from env.site_settings import atCNRM
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias
-from env.site_settings import atCNRM
 
 if atCNRM:
     cproject('gpcc', 'grid')  # grid writes as '05d', '1d' or 'T127'
diff --git a/climaf/projects/gpcp.py b/climaf/projects/gpcp.py
index a77861a1..d3f0b4dd 100644
--- a/climaf/projects/gpcp.py
+++ b/climaf/projects/gpcp.py
@@ -23,10 +23,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
+from env.site_settings import atCNRM
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias
-from env.site_settings import atCNRM
 
 if atCNRM:
     cproject('gpcp', 'grid', ('frequency', 'monthly'), separator="_")  # grid writes as '1d', '2.5d', 'T42' or 'T127'
diff --git a/climaf/projects/igcm_cmip6.py b/climaf/projects/igcm_cmip6.py
index 2116fc15..0b0d6a20 100644
--- a/climaf/projects/igcm_cmip6.py
+++ b/climaf/projects/igcm_cmip6.py
@@ -10,12 +10,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 
+from env.site_settings import atTGCC, onCiclad, onSpip
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, crealms, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip
 
 root = None
 login = None
diff --git a/climaf/projects/igcm_out.py b/climaf/projects/igcm_out.py
index d81567a5..9ebf6038 100644
--- a/climaf/projects/igcm_out.py
+++ b/climaf/projects/igcm_out.py
@@ -119,12 +119,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
-
+from env.site_settings import atTGCC, onCiclad, onSpip
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip
 
 root = None
 login = None
diff --git a/climaf/projects/nemo.py b/climaf/projects/nemo.py
index b7bf102e..10845909 100644
--- a/climaf/projects/nemo.py
+++ b/climaf/projects/nemo.py
@@ -119,12 +119,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
-
+from env.site_settings import atTGCC, onCiclad, onSpip
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import atTGCC, onCiclad, onSpip
 
 root = None
 if atTGCC:
diff --git a/climaf/projects/obs4mips.py b/climaf/projects/obs4mips.py
index db21773b..720325bd 100644
--- a/climaf/projects/obs4mips.py
+++ b/climaf/projects/obs4mips.py
@@ -15,10 +15,9 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os.path
 
-from env.environment import *
 from env.site_settings import atCNRM
+from env.environment import *
 
 
 if atCNRM:
diff --git a/climaf/projects/ocmip5.py b/climaf/projects/ocmip5.py
index 62e9b9de..eff76dcf 100644
--- a/climaf/projects/ocmip5.py
+++ b/climaf/projects/ocmip5.py
@@ -20,12 +20,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-import os
 
+from env.site_settings import onCiclad
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs
-from env.site_settings import onCiclad
 
 if onCiclad:
     cproject("OCMIP5", "model", "simulation", ("frequency", "monthly"), ensemble=["model"])
diff --git a/climaf/projects/optimize.py b/climaf/projects/optimize.py
new file mode 100644
index 00000000..3b682b06
--- /dev/null
+++ b/climaf/projects/optimize.py
@@ -0,0 +1,500 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Optimize searching datasets files when some facets are 
+shell-like wildcards (i.e. include * or ?)
+
+For now limited to project CMIP6 and active only if
+env.environment.optimize_cmip6_wildcards is True (which is the
+default) . See doc for
+:py:func:`~climaf.projects.optimize.cmip6_optimize_wildcards`
+
+"""
+
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+import glob
+import json
+import hashlib
+import time
+import os
+import re
+from collections import defaultdict
+
+from env.clogging import clogger
+from env.environment import cprojects
+import env
+
+dirnames = defaultdict(lambda: defaultdict(list))
+
+
+def wild(c):
+    return "?" in c or "*" in c
+
+
+def cmip6_optimize_wildcards(kwargs):
+    """Allow to optimize CMIP6 data search by analyzing CMIP6 keyword
+    values in KWARGS, and replacing some patterns using * or ?
+    by the list of their possible values, by querying the file system
+
+    It is automatically activated and used when
+    ``env.environment.optimize_cmip6_wildcards`` is True.
+
+    It assumes that all CMIP6 data are organized using CMIP6 canonical
+    DRS with a pattern like :
+    `${root}/CMIP6/${mip}/${institute}/${model}/${experiment}/${realization}/${table}/`
+    
+    It uses tables which are built automatically, stored in CLiMAF
+    cache, and can be refreshed by clearing it. See
+    :py:func:`~climaf.projects.optimize.clear_tables`
+    
+    First principle is to focus on facets which are high in the DRS
+    hierarchy, and so in the directories hierarchy. For such facets,
+    in order to speed-up search when the facet value includes a
+    wildcard, and when another facet allows to reduce significantly
+    the number of values of the wildcard facet, we build a look-up
+    table.
+    
+    This is for instance the case in CMIP6 when facet 'mip' is * and
+    facet 'experiment' is known. Or when 'institute' is * and 'model'
+    is known.
+    
+    Next principle is to the build a list of valid paths segment after
+    segment, by testing which wildcard segments values among the
+    possible ones actually lead to an existing path. For some cases,
+    when there is no way to guess a limited list of values (as
+    e.g. for 'version'), glob.glob is used
+
+    Keyword PERIOD is not processed at that level.
+
+    Returns a list of non-wildcard KWARGS which match actually
+    existing leaf directories, and which is used in later search (see
+    :py:func:`~climaf.dataloc.selectFiles`)
+
+    """
+    
+    if not env.environment.optimize_cmip6_wildcards:
+        return [kwargs.copy()]
+    else:
+        #
+        root = os.sep.join([kwargs["root", "CMIP6"]])
+        broot = root.encode('utf-8')
+        root_tag = hashlib.sha1(broot).hexdigest()[0:8]
+        #
+        mip = kwargs.get('mip')
+        experiment = kwargs.get('experiment')
+        institute = kwargs.get('institute')
+        model = kwargs.get('model')
+        realization = kwargs.get('realization')
+        table = kwargs.get('table')
+        variable = kwargs.get('variable')
+        grid = kwargs.get('grid')
+        version = kwargs.get('version')
+
+        # Mip
+        ##########
+        if wild(mip):
+            if wild(experiment):
+                raise ValueError("When requesting optimization, must provide at least mip or experiment")
+            mip = possible_values("CMIP6", "experiment2mip", root, experiment, mip)[0]
+            clogger.debug("Based on experiment = %s, attribute mip is set to %s" % (experiment, mip))
+        #
+        # Institute
+        ################
+        institutes = possible_values("CMIP6", "model2institute", root, model, institute)
+        paths = list()
+        for inst in institutes:
+            paths.extend(listdirs(os.sep.join([root, mip]), inst, test_exists=wild(model)))
+
+        # Model
+        #############
+        new_paths = list()
+        for path in paths:
+            institute = cmip6_facets(path, root, 2)[0]
+            models = possible_values("CMIP6", "mip_institute_experiment2model", root,
+                                     "_".join([mip, institute, experiment]), model)
+            for amodel in models:
+                new_paths.extend(listdirs(path, amodel, test_exists=wild(experiment)))
+        paths = new_paths
+
+        # experiment
+        ################
+        new_paths = list()
+        for path in paths:
+            model = cmip6_facets(path, root, 3)[0]
+            experiments = possible_values("CMIP6", "mip_model2experiment", root, "_".join([mip, model]), experiment)
+            for exp in experiments:
+                new_paths.extend(listdirs(path, exp))
+        paths = new_paths
+
+        # realization
+        ################
+        new_paths = list()
+        for path in paths:
+            model, experiment = cmip6_facets(path, root, 3, 4)
+            realizations = possible_values("CMIP6", "mip_model_experiment2realization",
+                                           root, "_".join([mip, model, experiment]), realization)
+            for real in realizations:
+                new_paths.extend(listdirs(path, real))
+        paths = new_paths
+
+        # Table
+        ###########
+        new_paths = list()
+        tables = [table, ]
+        if wild(table):
+            # TBD: build a list of possible tables based on variable name
+            ###############################################################
+            raise ValueError("For the time being, must provide table name")
+
+            tables = ['3hr', '6hrLev', '6hrPlev', '6hrPlevPt', 'AERday', 'AERhr', 'AERmon', 'AERmonZ', 'Amon', 'CF3hr',
+                      'CFday', 'CFmon', 'CFsubhr', 'day', 'E1hr', 'E3hr', 'Eday', 'EdayZ', 'Efx', 'Emon', 'EmonZ', 'fx',
+                      'LImon', 'Lmon', 'Oday', 'Ofx', 'Omon', 'SIday', 'SImon']
+            # clogger.debug("Attribute table = %s can have value only among %s"%tables)
+        #
+        for path in paths:
+            for table in tables:
+                new_paths.extend(listdirs(path, table))
+        paths = new_paths
+
+        # Variable
+        ###########
+        new_paths = list()
+        for path in paths:
+            variables = [variable, ]
+            #     # TBD : build a list of possible variables based on table name
+            #     ###############################################################
+            #     raise ValueError("For the time being, must provide variable name")
+            #     #clogger.debug("Attribute variable = %s can have value only among %s"%variables)
+            for var in variables:
+                new_paths.extend(listdirs(path, variable))
+        paths = new_paths
+
+        # Grid
+        ###########
+        new_paths = list()
+        for path in paths:
+            model, table, variable = cmip6_facets(path, root, 3, 6, 7)
+            grids = possible_values("CMIP6", "model_table_variable2grid", root, "_".join([model, table, variable]),
+                                    grid)
+            if len(grids) == 0:
+                # grids = ["gr", "gn", "gr1", "gr2"]
+                grids = [grid, ]  # This better matches user's request
+                clogger.info("Attribute grid has no registered set of values for %s / %s / %s" %
+                             (model, variable, table))
+            for gr in grids:
+                new_paths.extend(listdirs(path, gr))
+        paths = new_paths
+
+        # Version : no try at registering version per model+experiment+real+table+variable+grid
+        # -> use glob() to find version
+        # And also : at that final stage, test that directories exist
+        ################################
+        new_paths = list()
+        clogger.info('cmip6_optimize: before ensuring paths exists, there are %d paths' % len(paths))
+        clogger.debug('Paths: %s', paths)
+        for path in paths:
+            new_paths.extend(listdirs(path, version, test_exists=True))
+        paths = new_paths
+        clogger.info('cmip6_optimize: after ensuring paths exists, there are %d paths' % len(paths))
+        clogger.debug('Paths: %s', paths)
+
+        dicts = list()
+        for path in paths:
+            d = cmip6_path2dict(path, root)
+            d['project'] = kwargs['project']
+            d['period'] = kwargs['period']
+            d['root'] = kwargs['root']
+            dicts.append(d)
+
+        clogger.debug("There are %d optimized paths for data directories" % len(dicts))
+        return dicts
+
+
+def cmip6_path2dict(path, root):
+    """Returns a dict of facet/value pairs derived from PATH after
+    removing prefix ROOT and assuming that the path matches CMIP6
+    DRS"""
+    path = path[len(root)+1:].split(os.sep)
+    rep = {key: pos for (pos, key) in enumerate(["mip", "institute", "model", "experiment", "realization", "table",
+                                                "variable", "grid", "version"])}
+    for k in rep:
+        rep[k] = path[rep[k]]
+    return rep
+    
+
+def cmip6_facets(path, root, *fields):
+    """Returns a tuple of facets values for the ranks in FIELDS, derived
+    from PATH after removing prefix ROOT and assuming that the path
+    matches CMIP6 DRS, at least up to the max depth in FIELDS
+
+    Example :
+    >>> institute, model, experiment, realization, table, variable = cmip6_facets(path, root, 2, 3, 4, 5, 6, 7)
+
+    """
+    rep = list()
+    l = len(root)
+    path = path[l+1:].split(os.sep)
+    for field in fields:
+        rep.append(path[field-1])
+    return tuple(rep)
+
+
+def listdirs(parent, pattern, test_exists=False):
+    """List directories which may actually exists, by complementing path
+    PARENT with a single level of sub-directories, which match PATTERN
+
+    If the pattern includes no wildcard, simply complement with
+    PATTERN, and test existence only if TEST_EXITS is True
+
+    Otherwise, use glob.glob to find existing sub-directories
+
+    """
+    # clogger.debug('listdirs with %s and %s'%(parent,pattern))
+
+    if not wild(pattern):
+        path = os.sep.join([parent, pattern])
+        if not test_exists or os.path.exists(path):
+            return [path, ]
+        else:
+            return list()
+    else:
+        # Is it cost-effective to test that a path exists before
+        # globing on (some of) its subdirs?  According to various
+        # tests, it is not
+        rep = glob.glob(os.sep.join([parent, pattern]))
+        return rep
+
+    
+def cmip6_optimize_check_paths(paths):
+
+    """Check that paths patterns in PATHS fit (at least some of) the
+    requirements for optimizing data search
+    """
+    start = os.sep.join(["${root}", "CMIP6"])
+    test = [path.startswith(start) for path in paths]
+    if any(test):
+        for path in [paths[i] for (i, t) in enumerate(test) if t]:
+            clogger.debug("Path %s does not fit requirements for optimization" % path)
+        return False
+    else:
+        return True
+
+
+def dirnames_for_one_case(case_name, glob_pattern, split_index, case_value,
+                          key_index=-1, reset=False, value_pattern=None, root=None):
+
+    """Returns the ensemble of directories which have files matching a
+    given GLOB_PATTERN, which is supposed to end with a "/*" which
+    corresponds to CASE_VALUE. The directory names are extracted from
+    glob() return at a hierarchy level indicated by SPLIT_INDEX;
+    
+    Method : Uses an entry CASE_NAME in a global lookup table 
+    Try to read it from file if not present.
+    If it fails builds (stores, and writes) it by globbing according to the pattern
+    
+    If arg RESET is True, performs the globbing anyway and re-write the table
+    on disk
+
+    See examples of use in :py:func:`~climaf.projects.cmip6.cmip6_optimize_wildcards`
+
+    """
+    global dirnames
+    # print("begin:",dirnames)
+    
+    filen = _build_filename(case_name)
+    should_write = False
+    #
+    if reset: 
+        dirnames.pop(case_name, None)
+    elif case_name not in dirnames:
+        # Try to load table from file
+        try:
+            with open(filen, "r") as f:
+                dirnames[case_name] = json.load(f)
+                clogger.debug("Table %s read" % filen)
+        except:
+            clogger.debug("Table %s not found" % filen)
+    #
+    if case_name not in dirnames:
+        should_write = True
+        # Build table by globbing
+        clogger.warning("Building table %s by globbing. "
+                        "\n\tThis may take a while but will be saved for further sessions. "
+                        "\n\tTable will be stored in the cache as %s" % (case_name, filen))
+        values = set()
+        t = time.time()
+        cases = glob.glob(root + glob_pattern)
+        clogger.warning("Globbing duration was %g" % (time.time() - t))
+        clogger.info("Globbing duration was %g for %s and returned %d enties" %
+                     ((time.time() - t), glob_pattern, len(cases)))
+        for case in cases:
+            if not isinstance(key_index, list):
+                key = case.split(os.sep)[key_index]
+            else:
+                key = ""
+                for k in key_index:
+                    key += "%s_" % (case.split(os.sep)[k])
+            value = case.split(os.sep)[split_index]
+            dirnames[case_name][key].append(value)
+            clogger.debug('Adding value %s to entry %s of case %s ' % (value, key, case_name))
+        for v in dirnames[case_name]:
+            dirnames[case_name][v] = list(set(dirnames[case_name][v]))
+    #
+    if should_write:
+        with open(filen, "w") as f:
+            json.dump(dirnames[case_name], f, separators=(',', ': '), indent=3, ensure_ascii=True)
+        
+    #
+    if not wild(case_value):
+        # clogger.debug('Looking for entry %s in table %s'%(case_value,case_name))
+        try:
+            ret = dirnames[case_name][case_value]
+        except:
+            clogger.debug('No  %s in %s' % (case_value, case_name))
+            ret = list()
+    else:
+        ret = list()
+        pat = case_value.replace("?", ".").replace("*", ".*")
+        for case in dirnames[case_name]:
+            if re.search(pat, case):
+                ret.extend(dirnames[case_name][case])
+        ret = list(set(ret))
+
+    if value_pattern not in [None, "*"]:
+        rep = list()
+        pat = value_pattern.replace("?", ".").replace("*", ".*")
+        for r in ret:
+            if re.search(pat, r):
+                rep.append(r)
+        ret = rep
+    return ret
+        
+
+def clear_tables(pattern=None):
+    """Clear all search optimization tabes that include a given pattern
+    (e.g. 'CMIP6'), or all tables if no pattern is given
+
+    In order to identify the pattern for a given table :
+
+    - tables are stored in your CliMAF cache (which name is displayed
+      at the beginning of your session)
+
+    - table names are self explanatory;
+      e.g. 'CMIP6_mip_experiment_model2realization_7367d567.json'
+      stands for the table which allows to derive the list of
+      realizations from the values of mip, experiment and model. The
+      last part is a hash code for the root directory of the CMIP6 data
+
+    """
+    global dirnames
+    for case_name in dirnames:
+        if pattern is None or pattern in case_name:
+            dirnames.pop(case_name)
+            os.remove(_build_filename(case_name))
+
+
+def _build_filename(case_name):
+    return os.sep.join([env.environment.currentCache, case_name]) + ".json"
+
+
+def possible_values(project, tag, root, key, value_pattern):
+    """For a given PROJECT, returns the list of possible values for a
+    facet (here called the value facet) given the value (KEY) of
+    another facet (here called the key facet). Returns only values
+    that match VALUE_PATTERN. Return [] if None found
+
+    If VALUE_PATTERN has no wildcard, just return it as result (in a list)
+
+    Values are searched based on additional information TAG, which
+    carries two pieces of information : which is facet which value
+    (KEY) is provided, and which is the facet which values are
+    searched
+
+    Current implementation is based on globing and uses TAG to derive
+    three items :
+
+    - pattern to use for globing the filesystem
+    - index of the value facet in the file hierarchy matching the pattern
+    - index (or indices) of the key facet(s) in the file hierarchy 
+      matching the pattern
+
+    It then calls function dirnames_for_one_case which implements the
+    globbing, and which caches its results in a json file.
+
+    """
+    if not wild(value_pattern):
+        return [value_pattern, ]
+    else:
+        #
+        params = {
+            "CMIP6": {
+                # First entries, actually used
+                "experiment2mip": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*"]),
+                    "split_index": -4,
+                    "key_index": -1
+                },
+                "model2institute": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*"]),
+                    "split_index": -2,
+                    "key_index": -1
+                },
+                "mip_institute_experiment2model": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*"]),
+                    "split_index": -2,
+                    "key_index": [-4, -3, -1]
+                },
+                "mip_model2experiment": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*"]),
+                    "split_index": -1,
+                    "key_index": [-4, -2]
+                },
+                "mip_model_experiment2realization": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*", "*"]),
+                    "split_index": -1,
+                    "key_index": [-5, -3, -2]
+                },
+                "model_table_variable2grid": {
+                    "glob_pattern": os.sep.join(["", "CMIP", "*", "*", "historical", "r1i*", "*", "*", "*"]),
+                    "split_index": -1,
+                    "key_index": [-6, -3, -2]
+                },
+                # Next entries are no more used
+                "mip_institute2model": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*"]),
+                    "split_index": -1,
+                    "key_index": [-3, -2]
+                },
+                "mip_experiment2model": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*"]),
+                    "split_index": -2,
+                    "key_index": [-4, -1]
+                },
+                "mip2experiment": {
+                    "glob_pattern": os.sep.join(["", "*", "*", "*", "*"]),
+                    "split_index": -1,
+                    "key_index": -4
+                },
+            }
+        }
+        broot = root.encode('utf-8')
+        root_tag = hashlib.sha1(broot).hexdigest()[0:8]
+        case_name = "_".join([project, tag, root_tag])
+        #
+        if tag in params[project]:
+            paras = params[project][tag]
+        else:
+            paras = [params[project][a_tag] for a_tag in params[project] if a_tag in tag]
+            if len(paras) > 0:
+                paras = paras[0]
+            else:
+                paras = None
+        if paras is None:
+            raise ValueError("Unknown case %s" % tag)
+        #
+        ret = dirnames_for_one_case(case_name=case_name, case_value=key, value_pattern=value_pattern, root=root,
+                                    **paras)
+        clogger.debug('According to table %s , %s and %s lead to possible values %s' % (tag, key, value_pattern, ret))
+        return ret
+
diff --git a/climaf/projects/ref_climatos_and_ts.py b/climaf/projects/ref_climatos_and_ts.py
index e49c6217..d4b3df8c 100644
--- a/climaf/projects/ref_climatos_and_ts.py
+++ b/climaf/projects/ref_climatos_and_ts.py
@@ -46,10 +46,10 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
+from env.site_settings import onCiclad, atTGCC, atIDRIS, atCerfacs, atCNRM
 from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.site_settings import onCiclad, atTGCC, atIDRIS, atCerfacs, atCNRM
 
 root = None
 
@@ -101,6 +101,5 @@
 calias(project='ref_ts', variable='moc', fileVariable='stream_function_mar', filenameVar='moc')
 
 if root:
-    # pattern1=root+"ts/*/${frequency}/${variable}/${variable}_*mon_${product}*_YYYYMM-YYYYMM.nc"
     pattern1 = root + "ts/*/${frequency}/${variable}/${variable}_${table}_${product}_${obs_type}_${PERIOD}.nc"
     dataloc(project='ref_ts', organization='generic', url=[pattern1])
diff --git a/climaf/projects/ref_era5cerfacs.py b/climaf/projects/ref_era5cerfacs.py
index 7cdf03f5..cdd27356 100644
--- a/climaf/projects/ref_era5cerfacs.py
+++ b/climaf/projects/ref_era5cerfacs.py
@@ -44,11 +44,11 @@
 
 
 """
-
+from env.site_settings import onCiclad, atTGCC, atIDRIS, atCerfacs, atCNRM
+from env.environment import *
 from climaf.dataloc import dataloc
 from climaf.classes import cproject, calias, cfreqs, cdef
-from env.environment import *
-from env.site_settings import onCiclad, atTGCC, atIDRIS, atCerfacs, atCNRM
+
 
 root = None
 
diff --git a/climaf/standard_operators.py b/climaf/standard_operators.py
index 293c7c61..72d15d61 100644
--- a/climaf/standard_operators.py
+++ b/climaf/standard_operators.py
@@ -7,12 +7,11 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-
+from env.environment import *
+from env.site_settings import *
+from env.clogging import clogger
 from climaf import __path__ as cpath
 from climaf.operators import cscript, fixed_fields
-from env.clogging import clogger
-from env.site_settings import *
-from env.environment import *
 
 
 scriptpath = cpath[0] + "/../scripts/"
diff --git a/climaf/utils.py b/climaf/utils.py
index d993156e..ab466bf2 100644
--- a/climaf/utils.py
+++ b/climaf/utils.py
@@ -7,7 +7,93 @@
 
 from __future__ import print_function, division, unicode_literals, absolute_import
 
-from env.clogging import clogger, dedent
+from string import Template
+from collections import defaultdict
+
+from env.clogging import clogger, dedent, dedent as cdedent
+from env.environment import *
+
+
+def turn_list_to_tuple(val):
+    if isinstance(val, list):
+        return tuple(val)
+    else:
+        return val
+
+
+def remove_keys_with_same_values(diclist):
+    """Assuming DICLIST is a list of dicts which all have the same set of
+    keys, remove in each dict those keys which value is the same
+    across all dicts.
+    Returns a subdict with those keys which have a common value
+
+    """
+    if len(diclist) == 0:
+        return []
+    else:
+        #
+        values = defaultdict(set)
+        #
+        for dic in diclist:
+            for k in values:
+                values[k].add(turn_list_to_tuple(dic[k]))
+        keys_with_different_values = [k for k in values if len(values[k]) != 1]
+        for k in keys_with_different_values:
+            del values[k]
+        for k in values:
+            values[k] = list(values[k])[0]
+        return values
+
+
+def cartesian_product_substitute(input_string, skip_keys=list(), **kwargs):
+
+    """Iterate Template.safe_substitute on a list of strings, subtituting
+    for those keys in kwargs which have a value of type list, by
+    creating the cartesian product of all values in all sets
+
+    Those kwargs of type list are supposed to show as first element 
+    the pattern that allowed to derive the other list values, by some 
+    logic upstream
+
+    Returns :
+    - an augmented list after substitution, 
+    - a copy of kwargs where keys with values of type list have been removed
+    - a copy of kwargs where keys with values of type list have been replaced 
+      by the first list value (i.e. the pattern)
+
+    Example :
+
+    >>> u="aa/${x}/${y}/${z}"
+    >>> cartesian_product_substitute(u,x=["*","x","X"],y=["?","y"],z=3)
+    ['aa/X/y', 'aa/x/y', 'ab/X', 'ab/x'],
+    {'z' : 3 },
+    { 'x':'*', 'y':'?' },
+
+    """
+    set_kw = [kw for kw in kwargs if isinstance(kwargs[kw], list) and kw not in skip_keys]
+    single_kw = list(set(list(kwargs)) - set(set_kw) - set(skip_keys))
+    # Deal with substitutions for which there is one possible value
+    for kw in single_kw:
+        input_string = Template(input_string).safe_substitute({kw: kwargs[kw]})
+    # Turn input_string into a set
+    input_strings = set([input_string, ])
+    # Deal with substitutions for which there is several values possible
+    for kw in set_kw:
+        new_strings = set()
+        for s in input_strings:
+            for value in set(kwargs[kw][1:]):
+                new_strings.add(Template(s).safe_substitute({kw: value}))
+        input_strings = new_strings
+    #
+    simple_kwargs = kwargs.copy()
+    for k in set_kw:
+        del simple_kwargs[k]
+    #
+    kwargs_with_patterns = kwargs.copy()
+    for k in set_kw:
+        kwargs_with_patterns[k] = kwargs[k][0]
+    #
+    return list(input_strings), simple_kwargs, kwargs_with_patterns
 
 
 class Climaf_Classes_Error(Exception):
@@ -58,3 +144,13 @@ def __init__(self, valeur):
 
     def __str__(self):
         return repr(self.valeur)
+
+
+class Climaf_Driver_Error(Exception):
+    def __init__(self, valeur):
+        self.valeur = valeur
+        clogger.error(self.__str__())
+        cdedent(100)
+
+    def __str__(self):
+        return repr(self.valeur)
\ No newline at end of file
diff --git a/doc/API.rst b/doc/API.rst
index cef3e55f..5a3f42d6 100644
--- a/doc/API.rst
+++ b/doc/API.rst
@@ -20,19 +20,14 @@ even if they belong to another module
 .. toctree:: 
    functions_processing
 
-- :doc:`functions_results`
-
-  - for :doc:`managing numerical results <functions_results_numeric>`
-    
-    .. toctree:: functions_results_numeric
-    
-  - for :doc:`viewing results<functions_results_viewing>` (see also :ref:`the graphic operators <graphic_operators>`)  
-
-    .. toctree:: functions_results_viewing
+.. toctree::
+   functions_results_numeric
 
-  - for :doc:`listing and managing data in cache <functions_results_cache>`:   
+.. toctree::
+   functions_results_viewing
 
-    .. toctree:: functions_results_cache
+.. toctree::
+   functions_results_cache
 
 .. toctree:: 
    functions_utilities
diff --git a/doc/conf.py b/doc/conf.py
index 4e4142e2..e90a1cd3 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -49,12 +49,14 @@
 ]
 
 from env.site_settings import atCNRM, onCiclad
-if atCNRM or onCiclad:
-    # This one works at CNRM , Ciclad and RTD
+if atCNRM:
+    # This one works at CNRM
     extensions.append('sphinxcontrib.napoleon')
 else:
-    # this one works at RTD
-    extensions.append('sphinx.ext.napoleon')
+     # this one works at RTD and Ciclad
+     extensions.append('sphinx.ext.napoleon')
+
+os.environ['IN_SPHINX'] = 'yes'
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
diff --git a/doc/contents.rst b/doc/contents.rst
index 97745e8e..2873b174 100644
--- a/doc/contents.rst
+++ b/doc/contents.rst
@@ -14,6 +14,7 @@ Contents
    operators
    std_operators
    API
+   esmvaltool
    howto
    experts_corner
    contributing
diff --git a/doc/esmvaltool.rst b/doc/esmvaltool.rst
new file mode 100644
index 00000000..ef865988
--- /dev/null
+++ b/doc/esmvaltool.rst
@@ -0,0 +1,134 @@
+-----------------------------------------------------------------
+Calling ESMValTools diagnostic scripts
+-----------------------------------------------------------------
+
+
+`ESMValTool <https://docs.esmvaltool.org/>`_ is a software package for evaluating Earth Sytem Models ; it includes a large number of ``recipes`` which are configuration files for its runs (see a `recipe example
+<https://github.com/ESMValGroup/ESMValTool/blob/main/esmvaltool/recipes/examples/recipe_python.yml>`_); such ``recipes`` include a data preparation and preprocessing step, followed by the call of one or more diagnostic scripts.
+
+CliMAF can feed those diagnostic scripts and launch it. The following applies :
+
+Script declaration
+------------------
+The ESMValTool diagnostic script must be declared using function :py:func:`~climaf.ESMValTool_diags.evt_script` (and using the same name as in ESMValTool recipes); it allows to create a python function for calling the script, herafter called the ``nickname`` function.
+
+Script inputs 
+-------------
+- when using this feature, it is the user's responsibility to understand which data pre-processing is needed upstream of the diagnostic script and to reproduce that pre-processing using CliMAF, in order to feed the diagnostic consistently. The recipe describes that pre-processing;
+
+- according to ESMValTool's principles, each script input of type ``field`` must be an ensemble of fields, composed of fields representing the same geophysical variable
+
+- these ensembles must be provided as arguments to the ``nickname`` function in the same order than used in ESMValTool recipe's list of variables; 
+
+- any script parameter that can be set in a ``recipe`` can also be set through CliMAF as a keyword argument to the ``nickname`` function call
+
+- other parameters that ESMValTool users set in the config-user.yml file and that ESMValTool passes to the script can also be set through CliMAF at the stage of the ``nickname`` function call. This applies for instance to parameters :
+   - ``output_dir`` (defaults to ./evtscript_output/),
+   - ``output_type`` (png) ,
+   - ``write_netcdf`` (True)
+   - ``write_plots`` (True)
+   - ``profile_diagnostic`` (False),
+   - ``auxiliary_data_dir`` (empty)
+
+- the log level passed to the script is the current CliMAF log level, but can be superseded using parameter ``log_level``
+  
+
+Scripts outputs
+---------------
+- diagnostic script outputs are organized similarly to ESMValtool's : a subdirectory of ``output_dir`` is created for each call, which name includes a date/time stamp and is part of the value returned by the ``nickname`` function call (see below); it includes subdirectories ``preproc``,  ``work``, ``run`` and ``plots``; these sub-directories have a simpler organization than in ESMValTool (less hierarchy levels) because they represent outputs for a simpler run : only the diagnostic script (rather than a full recipe with its pre-processing)
+
+- subdir ``preproc`` includes, like in ESMValTool, one sub-directory per input variable (i.e per input ensemble); each one includes symbolic links to the input data files, which are located in CliMAF cache; the symbolic link name is built using the key (in the ensemble dictionnary), the variable name and the start and end year for the data period; these symbolic names are those used as provenance information by ESMValTool and cannot host the kind of provenance information that CliMAF could provide;
+
+- there is yet no way for CliMAF to handle ESMValTool diagnostic script's output data as CliMAF objects, because ESMValTool doesn't include any rule for **symbolic naming** of these outputs. So, calling an ESMValTool script is more or less a dead-end in the data processing flow.
+
+- ESMValTool generates ``provenance`` information using diagnostic script's outputs, and builds an html index of graphic type outputs, named ``index.html``, in the output dir. The provenance information dictionnary is included in the ``nickname`` function call returned values.
+
+
+Values returned upon diagnostic script call
+-------------------------------------------
+Upon invocation, the ``nickname`` function returns a pair of values :
+
+- the name for the script's top-level outputs directory 
+  
+- the dictionnary of provenance information (see ESMValTool documentation)
+
+
+ESMValTool scripts wrapper
+--------------------------
+
+An helper script is needed in order to set the relevant environment for calling ESMValTool scripts before calling it. Such a wrapper script must be tuned for each ESMValTool install one wants to use, and to the specifics of the platform for handling software envroinments. Variable :py:func:`~climaf.ESMValTool_diags.wrapper` should be set to the path for such a wrapper script. If it is not set, CliMAF will try to use hard-coded values depending on the current platform. At the time of writing, hard-coded values are limited to the case of Ciclad,  and the corresponding wrapper is available in CliMAF distribution :download:`as scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh <../scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh>`
+
+Example
+--------
+
+The example below is also available for :download:`download here <../examples/ESMValTool_cvdp.py>`
+
+.. code-block:: bash
+
+    # An example of declaring and calling an ESMValTool script from CliMAF
+    
+    from climaf.api import *
+    from climaf.ESMValTool_diags import evt_script
+    
+    # If your platform is not Ciclad, you must tell which is the wrapper for ESMValTool scripts
+    climaf.ESMValTool_diags.wrapper = \
+        "/home/ssenesi/climaf_installs/climaf_running/scripts/"+\
+        "ESMValTool_python_diags_wrapper_for_ciclad.sh"
+    
+    # Create a CliMAF function for calling the ESMValTool diagnostic script
+    # (use the same syntax as the ESMVaTool recipe for designating the script)
+    evt_script("call_cvdp", "cvdp/cvdp_wrapper")
+    
+    # Prepare input datasets for the diag. 
+    base      = dict(project="CMIP6", experiment="historical",
+                     realization='r1i1p1f2',  table="Amon", period="1850-1855", )
+    models    = [ "CNRM-CM6-1", "CNRM-ESM2-1"]
+    
+    variables = [ "ts", "tas", "pr", "psl" ]
+    
+    ensembles = []
+    for variable in variables:
+        ensemble = cens(
+            {
+                model :  ds(model=model, variable=variable, **base)
+                for model in models
+            })
+        ensembles.append(ensemble)
+    
+    # Note : here, for other diagnostic scripts, you may have to reproduce
+    # the preprocessing steps that ESMValTool recipes implement upstream
+    # of the diagnostic script. For CVDP, there is actually no such
+    # preprocessing
+        
+    # Call the diag. You may provide parameters that are known to ESMValTool
+    # or to the diagnostic script
+    wdir, prov = call_cvdp(*ensembles, output_dir="./out", write_netcdf=False)
+    
+    # First returned value is the diag's working directory
+    print(wdir)
+    
+    # Second one is a dictionnary of provenance information which
+    # describes all outputs (either graphics or NetCDF files) by various
+    # attributes, one of which being a 'caption'
+    one_output, its_attributes=prov.popitem()
+    print(one_output, its_attributes['caption'])
+    
+    # But there is no further established framework in ESMValTool for a
+    # diagnostic to 'publish' a list of identifiers for its outputs
+
+
+
+Public functions and variables involved
+---------------------------------------
+
+.. autofunction:: climaf.ESMValTool_diags.evt_script
+
+.. autodata:: climaf.ESMValTool_diags.wrapper
+
+Private functions and variables involved
+----------------------------------------
+
+.. autofunction:: climaf.driver.ceval_evt
+
+.. autofunction:: climaf.ESMValTool_diags.call_evt_script
+
diff --git a/doc/experts_corner.rst b/doc/experts_corner.rst
index c3a7d42e..a97dfb43 100644
--- a/doc/experts_corner.rst
+++ b/doc/experts_corner.rst
@@ -19,7 +19,7 @@ string:
 
 For that, type :
 
->>> climaf.clogging.clogger=climaf.clogging.log_e()
+>>> env.clogging.clogger=env.clogging.log_e()
 
 And reset the verbosity level for CliMAF log messages on stderr
 and/or for log file; e.g.
@@ -32,7 +32,7 @@ and/or for log file; e.g.
 If you want to return back to the standard format, you have to proceed in
 the same way but with the function 'climaf.clogging.log_l()', i.e. : 
 
->>> climaf.clogging.clogger=climaf.clogging.log_l()
+>>> env.clogging.clogger=env.clogging.log_l()
 >>> clog(level="info")  
 >>> clog_file(level="info")
 >>> csync() # For an example
diff --git a/doc/functions_data.rst b/doc/functions_data.rst
index db1f65c0..9e13231c 100644
--- a/doc/functions_data.rst
+++ b/doc/functions_data.rst
@@ -22,11 +22,16 @@ cdataset : define a dataset object
 
 .. autoclass:: climaf.classes.cdataset
 
-:py:meth:`cdataset.explore: explore data and periods, and match joker attributes <climaf.classes.cdataset.explore>`
-====================================================================================================================
+:py:meth:`cdataset.explore: explore data and periods, and match wildcard attributes <climaf.classes.cdataset.explore>`
+======================================================================================================================
 
 .. automethod:: climaf.classes.cdataset.explore
 
+:py:meth:`cdataset.glob: explore data and/or periods, and match wildcard attributes <climaf.classes.cdataset.explore>`
+======================================================================================================================
+
+.. automethod:: climaf.classes.cdataset.glob
+
 :py:meth:`cdataset.check: check time consistency of a dataset <climaf.classes.cdataset.check>`
 ====================================================================================================
 
diff --git a/doc/functions_objects.rst b/doc/functions_objects.rst
index 6eb40585..c529222c 100644
--- a/doc/functions_objects.rst
+++ b/doc/functions_objects.rst
@@ -1,5 +1,5 @@
 -------------------------------------------------------------
-Functions returning ClimAF objects 
+Functions returning CliMAF objects 
 -------------------------------------------------------------
 
 .. default-domain:: python
diff --git a/doc/functions_processing.rst b/doc/functions_processing.rst
index f20978d0..0fea544f 100644
--- a/doc/functions_processing.rst
+++ b/doc/functions_processing.rst
@@ -1,6 +1,5 @@
---------------------------------------------------------
 Functions for processing data
---------------------------------------------------------
+===============================
 
 .. default-domain:: python
 
@@ -20,10 +19,8 @@ Non-standard operators
 See :ref:`how_to_list_operators`
 
 
---------------------------------------------------------------------------------------------------------
 Functions for creating new processing functions, or tuning their behaviour
---------------------------------------------------------------------------------------------------------
-
+============================================================================
 
 cscript : define a new CliMAF operator 
 -----------------------------------------------------------------------------------
diff --git a/doc/functions_results.rst b/doc/functions_results.rst
index 1e094bf1..87e4f883 100644
--- a/doc/functions_results.rst
+++ b/doc/functions_results.rst
@@ -23,8 +23,8 @@ cMA : get the Masked Array value of a CliMAF object
 
 .. autofunction:: climaf.driver.cMA
 
-cvalue : get the scalar value of a CliMAF scalar object 
---------------------------------------------------------
+cvalue : get the scalar value of a CliMAF object (at a given index)
+-------------------------------------------------------------------
 
 .. autofunction:: climaf.driver.cvalue
 
@@ -47,36 +47,50 @@ html : create an html index, with tables of links to figures
 ------------------------------------------------------------
 
 .. automodule:: climaf.html
+   :noindex:
 
 .. autofunction:: climaf.html.header
+   :noindex:
 
 .. autofunction:: climaf.html.trailer
+   :noindex:
 
 .. autofunction:: climaf.html.section
+   :noindex:
 
 .. autofunction:: climaf.html.open_table
+   :noindex:
 
 .. autofunction:: climaf.html.close_table
+   :noindex:
 
 .. autofunction:: climaf.html.open_line
+   :noindex:
 
 .. autofunction:: climaf.html.close_line
+   :noindex:
 
 .. autofunction:: climaf.html.line
+   :noindex:
 
 .. autofunction:: climaf.html.link
+   :noindex:
 
 .. autofunction:: climaf.html.link_on_its_own_line
+   :noindex:
 
 .. autofunction:: climaf.html.cell
+   :noindex:
 
 .. autofunction:: climaf.html.fline
+   :noindex:
 
 .. autofunction:: climaf.html.flines
+   :noindex:
 
 .. autofunction:: climaf.html.vspace
-
-
+   :noindex:
+   
 
 clist : tell what's in cache, and much more
 -------------------------------------------
@@ -118,5 +132,7 @@ cprotect : protect a result's file against cdrop and craz
 
 .. autofunction:: climaf.cache.cprotect
 
+ccost : provide compute cost for an object
+-------------------------------------------
 
-
+.. autofunction:: climaf.cache.ccost
diff --git a/doc/functions_results_cache.rst b/doc/functions_results_cache.rst
index 9c9f33d6..d02468e5 100644
--- a/doc/functions_results_cache.rst
+++ b/doc/functions_results_cache.rst
@@ -1,3 +1,6 @@
+Functions for handling the cache
+==================================
+
 clist : tell what's in cache, and much more
 -------------------------------------------
 
@@ -47,3 +50,9 @@ cprotect : protect a result's file against cdrop and craz
    :noindex:
 
 
+ccost : provide compute cost for an object
+-------------------------------------------
+
+.. autofunction:: climaf.cache.ccost
+   :noindex:
+
diff --git a/doc/functions_results_numeric.rst b/doc/functions_results_numeric.rst
index b2b3d735..6ae36abe 100644
--- a/doc/functions_results_numeric.rst
+++ b/doc/functions_results_numeric.rst
@@ -1,3 +1,6 @@
+Functions for managing numerical results
+========================================
+
 cfile : get the file value of a CliMAF object 
 ----------------------------------------------
 
diff --git a/doc/functions_results_viewing.rst b/doc/functions_results_viewing.rst
index 41c9d4d0..552d36eb 100644
--- a/doc/functions_results_viewing.rst
+++ b/doc/functions_results_viewing.rst
@@ -1,6 +1,5 @@
-
-functions for inspecting data
-====================================================================================================
+Functions for inspecting data
+===============================================================================
 
 summary : describe files associated with a dataset
 ---------------------------------------------------
@@ -17,8 +16,8 @@ cshow : dump an object
    :noindex:
 
 
-functions to create images, and to display datasets and images
-====================================================================================================
+Functions to create images, and to display datasets and images
+================================================================================
 
 Warning
 ------------
@@ -65,8 +64,8 @@ cshow : display a figure using 'display'
 .. autofunction:: climaf.driver.cshow
    :noindex:
 
-functions for assembling figures in pages 
-====================================================================================================
+Functions for assembling figures in pages 
+==============================================================
 
 For choosing between next two fuctnions, see the :ref:`Note on figure pages <note_on_figure_pages>`
 
@@ -82,51 +81,37 @@ cpage_pdf : create an array of figures (output: 'pdf' figure)
 .. autoclass:: climaf.classes.cpage_pdf
    :noindex:
 
+
 html : package for creating an html index, with tables of links to figures
-====================================================================================================
+--------------------------------------------------------------------------
 
 .. automodule:: climaf.html
-   :noindex:
 
 .. autofunction:: climaf.html.header
-   :noindex:
 
 .. autofunction:: climaf.html.trailer
-   :noindex:
 
 .. autofunction:: climaf.html.section
-   :noindex:
 
 .. autofunction:: climaf.html.open_table
-   :noindex:
 
 .. autofunction:: climaf.html.close_table
-   :noindex:
 
 .. autofunction:: climaf.html.open_line
-   :noindex:
 
 .. autofunction:: climaf.html.close_line
-   :noindex:
 
 .. autofunction:: climaf.html.line
-   :noindex:
 
 .. autofunction:: climaf.html.link
-   :noindex:
 
 .. autofunction:: climaf.html.link_on_its_own_line
-   :noindex:
 
 .. autofunction:: climaf.html.cell
-   :noindex:
 
 .. autofunction:: climaf.html.fline
-   :noindex:
 
 .. autofunction:: climaf.html.flines
-   :noindex:
 
 .. autofunction:: climaf.html.vspace
-   :noindex:
-   
+
diff --git a/doc/functions_utilities.rst b/doc/functions_utilities.rst
index afd3db66..c400bb9a 100644
--- a/doc/functions_utilities.rst
+++ b/doc/functions_utilities.rst
@@ -18,12 +18,12 @@ cpath : path for CliMAF installation directory
 clog : tune verbosity
 -----------------------
 
-.. autofunction:: climaf.clogging.clog 
+.. autofunction:: env.clogging.clog 
 
 clog_file  : tune verbosity for log file
 -------------------------------------------
 
-.. autofunction:: climaf.clogging.clog_file 
+.. autofunction:: env.clogging.clog_file 
 
 csync
 ---------------------
diff --git a/doc/howto.rst b/doc/howto.rst
index b2395577..fa47ffe8 100644
--- a/doc/howto.rst
+++ b/doc/howto.rst
@@ -115,9 +115,9 @@ You may know how the operator interfaces with Climaf by ::
 CliMAF uses the Python logging package :py:mod:`logging` for informing
 about work done, at varied verbosity levels. There are two logging
 streams : one going to screen (or stderr), the other going to file
-``climaf.log`` . See function :py:func:`~climaf.clogging.clog` for
+``climaf.log`` . See function :py:func:`~env.clogging.clog` for
 setting the severity level for the former and function
-:py:func:`~climaf.clogging.clog_file` for the latter 
+:py:func:`~env.clogging.clog_file` for the latter 
 
 Note : at CliMAF startup, the severity level is set to the value of environment
 variable $CLIMAF_LOG (resp. $CLIMAF_FILE_LOG)
diff --git a/doc/index.rst b/doc/index.rst
index 34f6ef8b..c86408a0 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -22,6 +22,17 @@ Discover CliMAF: essentials of the documentation to get started
 - :download:`A notebook for CMIP6 with CliMAF at CLIMERI <../examples/CMIP6_analysis_with_CliMAF.html>`
 - `Subscribe to CliMAF users mailing list <https://climaf.readthedocs.io/en/master/community.html>`_ to stay in the loop!
 
+Breaking news: launch ESMValTool diags with CliMAF
+==================================================
+
+The development of a standard interface for diagnostic scripts within
+the IS-ENES3 European project has led to the development of an
+interface between CliMAF and ESMValTool diagnostic scripts that works
+even with the current ESMValTool interface.
+
+Hence, it is now possible to use ESMValTool diagnostic scripts and
+to reproduce its recipes from CliMAF; see the :doc:`documentation page
+here<esmvaltool>`
 
 
 CliMAF - a Climate Model Assessment Framework
@@ -166,6 +177,7 @@ See the full, extended table at :ref:`contents`
    std_operators
    known_data
    API
+   esmvaltool
    howto
    experts_corner
    contributing
diff --git a/doc/installing.rst b/doc/installing.rst
index 22b04a33..e36d78dc 100644
--- a/doc/installing.rst
+++ b/doc/installing.rst
@@ -107,8 +107,15 @@ Configuring CliMAF
 
  - CLIMAF_LOG_LEVEL and CLIMAF_LOGFILE_LEVEL : for setting the
    verbosity level on stderr (resp. on file climaf.log); defaults to
-   'error' (resp. 'info'). See :py:func:`~climaf.clogging.clog`
-   (resp. :py:func:`~climaf.clogging.clog_file`) for details
+   'error' (resp. 'info'). See :py:func:`~env.clogging.clog`
+   (resp. :py:func:`~env.clogging.clog_file`) for details
+
+.. _check_deps:
+
+ - CLIMAF_CHECK_DEPENDENCIES : if set to 'no', this will avoid that
+   CLiMAF checks external tools version at startup; this speeds up the
+   init stage but should be used only when you are sure these
+   dependencies are satisfied on your system and environment.
 
  - CLIMAF_FIX_NEMO_TIME : if set to anything but 'no', this will
    automatic fix CNRM’s Nemo old data time_axis issues. This adresses
@@ -120,9 +127,6 @@ Configuring CliMAF
    adresses the wrong variable attribute 'coordinates' with 'lat lon'
    to 'latitude longitude'.
 
- - CLIMAF_CHECK_DEPENDENCIES : if set to anything but 'yes', this will
-   avoid that CLiMAF checks and prints external tools version at startup
-
 - Configuration file : you may put in file ``~/.climaf`` any python code using CliMAF
   functions; this will be executed at the end of climaf import; the code 
   must use fully qualified names for Python functions (as in e.g. ``climaf.operators.cscript``): it des not
diff --git a/doc/known_data.rst b/doc/known_data.rst
index 43d50302..00c5c0c6 100644
--- a/doc/known_data.rst
+++ b/doc/known_data.rst
@@ -86,3 +86,12 @@ cami
 -----------------------------------------------------------
 
 .. automodule:: climaf.projects.cami
+
+
+optimize
+--------
+
+.. automodule:: climaf.projects.optimize
+   :members: 
+
+
diff --git a/doc/news.rst b/doc/news.rst
index a3aea39c..422e3e35 100644
--- a/doc/news.rst
+++ b/doc/news.rst
@@ -6,6 +6,56 @@ What's new
 
 Changes, newest first:
 
+- V2.0.2:
+
+  - CliMAF can **call ESMValTool diagnostic scripts**, feeding them with data accessed and pre-processed by CliMAF calls. See :doc:`esmvaltool`
+
+  - **The computation cost for all objects is now handled**, and can
+    be queried, using function :py:func:`~climaf.cache.ccost`. Cost is
+    expressed as elapsed time. For getting the cost of a scalar, call
+    :py:func:`~climaf.driver.cvalue` with argument``cost=True``
+
+  - log:
+
+    - CliMAF check for external softwares version at init stage can be
+    silent, using :py:func:`~env.clogging.clog`, with a log level of
+    'critical'
+
+    - Other messages at init stage can be discarded by redirecting stderr
+
+  - cache:
+
+    - Two bugs related to cache were fixed : cache content was not
+      updated on exit, and with Python 3, it was not read at
+      startup. Errors related to reading / writing cache are no more
+      hidden.
+    - The following cache variables are now handled in env.environment module: currentCache, cachedirs, indexFileName
+
+  - Fix `issue 193<https://github.com/rigoudyg/climaf/issues/193>` regarding behaviour of listfiles
+    for ambiguous cases
+
+  - **Finding CMIP6 datasets is optimized when facets/attributes have shell-like wildcards** ( * or ?):
+
+    - This relies on building tables that allows to derive wildcard
+      facet's list of values by using other facet's value. Building
+      the table may take a while, but the table is saved for further
+      sessions (in the cache).
+    - Also, an incremental testing of all possible data paths (built
+      with possible facet values) allows to avoid costly glob.glob
+      operations.
+    - As an example, searching CMIP6 'tos' data for expermient
+      historical and all models, realizations, and grids (and not for
+      peridos) takes 77 seconds on Ciclad;  searching also for
+      all periods takes 20 minutes, instead of 37 minutes.
+    - This is yet limited to CMIP6 data, and active unless you set
+      env.optimize_cmip6_wildcards to False
+    - See module documentation for :py:mod:`~climaf.projects.optimize`
+
+  - **New dataset's method glob is proposed as a replacement for
+    method explore**. It may be of simpler use, as it can return a list
+    of all facets value combinations that match data. See
+    :py:func:`~climaf.classes.cdataset.glob()`.
+
 - V2.0.1:
 
   - **Scripts can now process multiple ensembles, and ensembles which are not the first argument**:
@@ -59,11 +109,11 @@ Changes, newest first:
     - To avoid cycling import, the module :py:mod:`climaf.operator` has been split into three modules:
       :py:mod:`climaf.operator` (still here), :py:mod:`climaf.operator_derive` and :py:mod:`climaf.operator_scripts`.
 
-    - :py:func:`~climaf.operator.ceval` has been modified to call functions specific to CliMAF types:
-      :py:func:`~climaf.operator.ceval_for_cdataset`, :py:func:`~climaf.operator.ceval_for_ctree`,
-      :py:func:`~climaf.operator.ceval_for_ScriptChild`, :py:func:`~climaf.operator.ceval_for_cpage`,
-      :py:func:`~climaf.operator.ceval_for_cpage_pdf`, :py:func:`~climaf.operator.ceval_for_cens`,
-      :py:func:`~climaf.operator.ceval_for_string`.
+    - :py:func:`~climaf.driver.ceval` has been modified to call functions specific to CliMAF types:
+      :py:func:`~climaf.driver.ceval_for_cdataset`, :py:func:`~climaf.driver.ceval_for_ctree`,
+      :py:func:`~climaf.driver.ceval_for_ScriptChild`, :py:func:`~climaf.driver.ceval_for_cpage`,
+      :py:func:`~climaf.driver.ceval_for_cpage_pdf`, :py:func:`~climaf.driver.ceval_for_cens`,
+      :py:func:`~climaf.driver.ceval_for_string`.
 
     - Variables used everywhere in CliMAF and available for everyone have been moved to module
       :py:mod:`env.environment`: ``cprojects``, ``aliases``, ``frequencies``, ``realms``, ``cscripts``, ``operators``,
@@ -912,7 +962,7 @@ Changes, newest first:
   - Running CliMAF - messages, cache, errors:
 
     - Verbosity, and cache directory, can be set using environment variables. See :ref:`configuring`
-    - Simplify use of function :py:func:`~climaf.clogging.clog`
+    - Simplify use of function :py:func:`~env.clogging.clog`
     - Log messages are indented to show recursive calls of ceval()
     - Quite extended use of Python exceptions for error handling
 
diff --git a/doc/scripts/ccdo.rst b/doc/scripts/ccdo.rst
index 3064dd1c..ad2b0d26 100644
--- a/doc/scripts/ccdo.rst
+++ b/doc/scripts/ccdo.rst
@@ -5,7 +5,7 @@ ccdo : |sk| invoke CDO with a unary operator
 
 Apply CDO on a single dataset or object, with a CDO operator as argument 
 
-**References** : https://code.zmaw.de/projects/cdo/embedded/1.6.4/cdo.html
+**References** : https://code.zmaw.de/projects/cdo/embedded/index.html
 
 **Provider / contact** : climaf at meteo dot fr
 
diff --git a/env/clogging.py b/env/clogging.py
index 92933a03..aad131ec 100644
--- a/env/clogging.py
+++ b/env/clogging.py
@@ -43,7 +43,7 @@ def clog(level=None):
     Args:
      level(str) : among : \"debug\", \"info\", \"warning\", \"critical\"
 
-    Note : at CliMAF startup, the level is set to the value of envrionment variable $CLIMAF_LOG_LEVEL
+    Note : at CliMAF startup, the level is set to the value of environment variable $CLIMAF_LOG_LEVEL
 
     """
     if level:
diff --git a/env/environment.py b/env/environment.py
index 05dadd6d..e873560a 100644
--- a/env/environment.py
+++ b/env/environment.py
@@ -16,7 +16,8 @@
 except ImportError:
     from subprocess import getoutput, getstatusoutput
 
-
+from env.clogging import clogger, clog, clog_file
+import env.clogging
 from env.site_settings import atTGCC, atIPSL, onCiclad
 
 # Variables
@@ -58,7 +59,39 @@
 locs = list()
 
 #: Log directory
-logdir = "."
+logdir = os.path.expanduser(os.getenv("CLIMAF_LOG_DIR", "."))
+
+#: Log level
+loglevel = os.getenv("CLIMAF_LOG_LEVEL", "warning")
+
+#: Log file level
+logfilelevel = os.getenv("CLIMAF_LOGFILE_LEVEL", "info")
+
+#: Default cache directory
+if onCiclad:
+    default_cache = "/data/{}/climaf_cache".format(os.getenv("USER"))
+else:
+    default_cache = "~/tmp/climaf_cache"
+default_cache = os.getenv("CLIMAF_CACHE", default_cache)
+
+#: Default remote cache directory
+default_remote_cache = os.getenv("CLIMAF_REMOTE_CACHE", os.sep.join([default_cache, "remote_data"]))
+
+#: Current cache directory
+currentCache = None
+
+#: Cache directories list
+cachedirs = None
+
+#: The place to write the index
+cacheIndexFileName = None
+
+
+#: Current cache directory
+currentCache = None
+
+#: Should the search for CMI6 files be optimized by building tables
+optimize_cmip6_wildcards = True
 
 #: Define whether we stamp the data files with their CRS.
 # True means mandatory. None means : please try. False means : don't try
@@ -72,80 +105,110 @@ def my_which(soft):
         rep = rep.replace("\n", "")
     return rep
 
+#
+# Set default logging levels
+clog(loglevel)
+
+env.clogging.logdir = logdir
+if not os.access(env.clogging.logdir, mode=os.W_OK):
+    print("Cannot write logfile in non-writeable directory : " + os.path.abspath(env.clogging.logdir))
+    exit()
+clog_file(logfilelevel)
+
+# Ensure that the variable TMPDIR, if defined, points to an existing directory
+if "TMPDIR" in os.environ and not os.path.isdir(os.environ["TMPDIR"]):
+    # raise OSError("TMPDIR points to a non existing directory! Change the value of the variable to go on.")
+    tmpdir = os.environ["TMPDIR"]
+    if os.path.exists(tmpdir):
+        os.remove(tmpdir)
+    os.makedirs(tmpdir)
+
+# Check dependencies
+try:
+    xdg_bin = my_which("xdg-open")
+    print("xdg-open is available")
+except:
+    xdg_bin = None
+    print("Warning: could not find xdg-open")
 
-def bash_command_to_str(cmd):
-    return str.replace(subprocess.Popen(cmd.split(), stdout=subprocess.PIPE).stdout.readlines()[0], '\n', '')
-
-
-if os.environ.get('CLIMAF_CHECK_DEPENDENCIES', "yes") == "yes":
+# Check dependencies
+try:
+    xdg_bin = my_which("xdg-open")
+    print("xdg-open is available")
+except:
+    xdg_bin = None
+    print("Warning: could not find xdg-open")
+
+if os.environ.get('CLIMAF_CHECK_DEPENDENCIES', "yes") in ["yes", ] and \
+        os.environ.get('IN_SPHINX', "no") in ["no", ]:
     print("python => " + sys.version)
     print("---")
     print("Required softwares to run CliMAF => you are using the following versions/installations:")
     try:
         ncl_software = my_which("ncl")
-        print("ncl " + getoutput(ncl_software + ' -V') + " => " + ncl_software)
+        clogger.info("ncl " + getoutput(ncl_software + ' -V') + " => " + ncl_software)
     except:
         ncl_software = None
-        print("Warning: ncl not found -> can't use CliMAF plotting scripts")
+        clogger.warning("ncl not found -> can't use CliMAF plotting scripts")
     try:
         cdo_software = my_which("cdo")
         tmp = str.split(getstatusoutput(cdo_software + ' -V')[1], ' ')
-        print("cdo " + tmp[tmp.index('version') + 1] + " => " + cdo_software)
+        clogger.info("cdo " + tmp[tmp.index('version') + 1] + " => " + cdo_software)
     except:
         cdo_software = None
-        print("Error: cdo not found -> CDO is mandatory to run CliMAF")
+        clogger.error("cdo not found -> CDO is mandatory to run CliMAF")
     try:
         ncks_sofware = my_which("ncks")
         tmp = str.split(getstatusoutput(ncks_sofware + ' --version')[1], ' ')
-        print("nco (ncks) " + tmp[tmp.index('version') + 1] + " => " + ncks_sofware)
+        clogger.info("nco (ncks) " + tmp[tmp.index('version') + 1] + " => " + ncks_sofware)
     except:
         ncks_sofware = None
-        print("Warning: nco not found -> can't use nco from CliMAF")
+        clogger.warning("nco not found -> can't use nco from CliMAF")
     try:
         if atTGCC or atIPSL or onCiclad:
             ncdump_software = '/prodigfs/ipslfs/dods/jservon/miniconda/envs/cesmep_env/bin/ncdump'
             ncdump_ret = getstatusoutput(ncdump_software)
-            print("ncdump " + ncdump_ret[-1].split('\n')[-1].split()[3] + " => " + ncdump_software)
+            clogger.info("ncdump " + ncdump_ret[-1].split('\n')[-1].split()[3] + " => " + ncdump_software)
         else:
             ncdump_software = my_which("ncdump")
             binary_info = getstatusoutput(ncdump_software + " --version")[-1].split("\n")[-1]
             binary_info = binary_info.split("version")[-1].split("of")[0].strip()
-            print("ncdump " + binary_info + " => " + ncdump_software)
+            clogger.info("ncdump " + binary_info + " => " + ncdump_software)
     except:
         ncdump_software = None
-        print("Warning: ncdump not found -> can't use ncdump from CliMAF")
+        clogger.warning("ncdump not found -> can't use ncdump from CliMAF")
     # Check that tools for stamping are available or enforce stamping to None
-    print("Check stamping requirements")
+    clogger.info("Check stamping requirements")
     do_stamping = True
     try:
         ncatted_software = my_which("ncatted")
-        print("nco (ncatted) found -> " + ncatted_software)
+        clogger.info("nco (ncatted) found -> " + ncatted_software)
     except:
         ncatted_software = None
-        print("nco (ncatted) not available, can not stamp netcdf files")
+        clogger.warning("nco (ncatted) not available, can not stamp netcdf files")
         do_stamping = False
     try:
         convert_software = my_which("convert")
-        print("convert found -> " + convert_software)
+        clogger.info("convert found -> " + convert_software)
     except:
         convert_software = None
-        print("convert not available, can not stamp png files")
+        clogger.warning("convert not available, can not stamp png files")
         do_stamping = False
     try:
         pdftk_software = my_which("pdftk")
-        print("pdftk found -> " + pdftk_software)
+        clogger.info("pdftk found -> " + pdftk_software)
     except:
         pdftk_software = None
-        print("pdftk not available, can not stamp pdf files")
+        clogger.warning("pdftk not available, can not stamp pdf files")
         do_stamping = False
     try:
         exiv2_software = my_which("exiv2")
-        print("exiv2 found -> " + exiv2_software)
+        clogger.info("exiv2 found -> " + exiv2_software)
     except:
         exiv2_software = None
-        print("exiv2 not available, can not stamp eps files")
+        clogger.warning("exiv2 not available, can not stamp eps files")
         do_stamping = False
     if not do_stamping and stamping is True:
-        print("At least one stamping requirement is not fulfilled, turn it to None.")
+        clogger.warning("At least one stamping requirement is not fulfilled, turn it to None.")
         stamping = None
-    print("---")
+    clogger.info("---")
diff --git a/examples/ESMValTool_cvdp.py b/examples/ESMValTool_cvdp.py
new file mode 100644
index 00000000..25fbbc8c
--- /dev/null
+++ b/examples/ESMValTool_cvdp.py
@@ -0,0 +1,52 @@
+# An example of declaring and calling an ESMValTool script from CliMAF
+
+from climaf.api import *
+from climaf.ESMValTool_diags import evt_script
+
+# If your platform is not Ciclad, you must tell which is the wrapper for ESMValTool scripts
+climaf.ESMValTool_diags.wrapper = \
+    "/home/ssenesi/climaf_installs/climaf_running/scripts/"+\
+    "ESMValTool_python_diags_wrapper_for_ciclad.sh"
+
+# Create a CliMAF function for calling the ESMValTool diagnostic script
+# (use the same syntax as the ESMVaTool recipe for designating the script)
+evt_script("call_cvdp", "cvdp/cvdp_wrapper")
+
+# Prepare input datasets for the diag. 
+base      = dict(project="CMIP6", experiment="historical",
+                 realization='r1i1p1f2',  table="Amon", period="1850-1855", )
+models    = [ "CNRM-CM6-1", "CNRM-ESM2-1"]
+
+variables = [ "ts", "tas", "pr", "psl" ]
+
+ensembles = []
+for variable in variables:
+    ensemble = cens(
+        {
+            model :  ds(model=model, variable=variable, **base)
+            for model in models
+        })
+    ensembles.append(ensemble)
+
+# Note : here, for other diagnostic scripts, you may have to reproduce
+# the preprocessing steps that ESMValTool recipes implement upstream
+# of the diagnostic script. For CVDP, there is actually no such
+# preprocessing
+    
+# Call the diag. You may provide parameters that are known to ESMValTool
+# or to the diagnostic script
+wdir, prov = call_cvdp(*ensembles, output_dir="./out", write_netcdf=False)
+
+# First returned value is the diag's working directory
+print(wdir)
+
+# Second one is a dictionnary of provenance information which
+# describes all outputs (either graphics or NetCDF files) by various
+# attributes, one of which being a 'caption'
+one_output, its_attributes=prov.popitem()
+print(one_output, its_attributes['caption'])
+
+# But there is no further established framework in ESMValTool for a
+# diagnostic to 'publish' a list of identifiers for its outputs
+
+
diff --git a/requirements.txt b/requirements.txt
index 73829a79..019af97d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,4 +8,5 @@ cftime==1.0.3.4
 # Cython>0.15.1
 # cartopy==0.17
 # python-dateutil
-natsort
\ No newline at end of file
+natsort
+pyyaml
\ No newline at end of file
diff --git a/scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh b/scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh
new file mode 100755
index 00000000..281e8c01
--- /dev/null
+++ b/scripts/ESMValTool_python_diags_wrapper_for_ciclad.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# Run an ESMValTool python diagnostic script, after setting the relavant
+# environment
+
+# This script must be tuned both to the platform and to the ESMValTool install
+
+# Here, platform is Ciclad and ESMValTool install is an intermediate one
+# And we assume that command 'conda' is available
+
+diag=${1/.py/}.py
+settings=$2
+
+module load modtools-python3
+
+# Init conda
+base=$(conda info | grep -i 'base environment' | awk '{print $4}')
+source $base/etc/profile.d/conda.sh
+
+# Init ESMValTool with conda just for the sake of initing some environment
+# variables such as NCARG_ROOT for Ncl
+CENV=/modfs/modtools/miniconda3/envs/esmvaltool_dev
+conda activate $CENV
+
+# Using python from ESMValTool env is necessary for accessing ESMValTool modules,
+# and is not automatic after 'conda activate', when launched by a python's
+# subprocess with shell=False
+python=$CENV/bin/python3
+
+$python $CENV/lib/python3.9/site-packages/Esmvaltool/diag_scripts/$diag $settings
+


Test patch:
diff --git a/tests/launch_tests_with_coverage.sh b/tests/launch_tests_with_coverage.sh
index bea6e1e9..adc3317d 100755
--- a/tests/launch_tests_with_coverage.sh
+++ b/tests/launch_tests_with_coverage.sh
@@ -4,7 +4,7 @@ report_ensemble=${1:-1}
 echo $report_ensemble
 python_version=${2:-2}
 echo $python_version
-run_modules=${3:-"netcdfbasics period cache classes functions operators standard_operators operators_derive operators_scripts cmacro html example_data_plot example_data_retrieval example_index_html mcdo"}
+run_modules=${3:-"netcdfbasics period cache classes functions operators standard_operators operators_derive operators_scripts cmacro driver dataloc find_files html example_data_plot example_data_retrieval example_index_html mcdo"}
 echo $run_modules
 run_coverage=${4:-0}
 echo $run_coverage
diff --git a/tests/test_cache.py b/tests/test_cache.py
index bc60b7e9..cad552bc 100644
--- a/tests/test_cache.py
+++ b/tests/test_cache.py
@@ -8,20 +8,21 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
 import shutil
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
 
+import env
+from env.environment import *
 from climaf import __path__ as rootpath
 from climaf.cmacro import crewrite
-from env.environment import *
 
 from climaf.cache import setNewUniqueCache, generateUniqueFileName, hash_to_path, alternate_filename, stringToPath, \
     searchFile, register, getCRS, rename, hasMatchingObject, hasIncludingObject, hasBeginObject, hasExactObject, \
     complement, cdrop, cprotect, csync, cload, cload_for_project, craz, cdump, list_cache, clist, cls, crm, cdu, cwc, \
-    rebuild, Climaf_Cache_Error
+    rebuild, ccost, Climaf_Cache_Error
+from climaf.driver import cfile
 
 
 class SetNewUniqueCacheTests(unittest.TestCase):
@@ -31,15 +32,16 @@ def test_setNewUniqueCache(self):
         #       call to craz works
         new_tmp_directory = tmp_directory + "/tmp"
         setNewUniqueCache(new_tmp_directory)
-        from climaf.cache import cachedirs, currentCache, cacheIndexFileName
-        self.assertEqual(cachedirs, [new_tmp_directory])
-        self.assertEqual(currentCache, new_tmp_directory)
-        self.assertEqual(cacheIndexFileName, new_tmp_directory + "/index")
+        self.assertEqual(env.environment.cachedirs, [new_tmp_directory])
+        self.assertEqual(env.environment.currentCache, new_tmp_directory)
+        self.assertEqual(env.environment.cacheIndexFileName, new_tmp_directory + "/index")
         setNewUniqueCache(tmp_directory, raz=False)
-        from climaf.cache import cachedirs, currentCache, cacheIndexFileName
-        self.assertEqual(cachedirs, [tmp_directory])
-        self.assertEqual(currentCache, tmp_directory)
-        self.assertEqual(cacheIndexFileName, tmp_directory + "/index")
+        self.assertEqual(env.environment.cachedirs, [tmp_directory])
+        self.assertEqual(env.environment.currentCache, tmp_directory)
+        self.assertEqual(env.environment.cacheIndexFileName, tmp_directory + "/index")
+
+    def tearDown(self):
+        craz()
 
 
 class GenerateUniqueFileNameTests(unittest.TestCase):
@@ -58,6 +60,9 @@ def test_generateUniqueFileName(self):
                     os.sep.join(['7f/19fc8b622fd648549cfe53a9a57875a0b86e35ae4ae118212c6251.nc']))
         # TODO: Go on to test the usecases of the generateUniqueFileName function
 
+    def tearDown(self):
+        craz()
+
 
 class StringToPathTests(unittest.TestCase):
 
@@ -67,6 +72,9 @@ def test_stringToPath(self):
         self.assertEqual(stringToPath(name, length),
                          "7f19f/c8b62/2fd64/8549c/fe53a/9a578/75a0b/86e35/ae4ae/11821/2c625/1")
 
+    def tearDown(self):
+        craz()
+
 
 class SearchFileTests(unittest.TestCase):
 
@@ -78,6 +86,9 @@ def test_searchFile(self):
         self.assertEqual(searchFile(my_path_1), "/".join([tmp_directory, my_path_1]))
         self.assertEqual(searchFile(my_path_3), None)
 
+    def tearDown(self):
+        craz()
+
 
 class RegisterTests(unittest.TestCase):
 
@@ -86,6 +97,9 @@ def test_register(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class GetCRSTests(unittest.TestCase):
 
@@ -94,6 +108,9 @@ def test_getCRS(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class RenameTests(unittest.TestCase):
 
@@ -102,6 +119,9 @@ def test_rename(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class HasMatchingObjectTests(unittest.TestCase):
 
@@ -110,6 +130,9 @@ def test_hasMatchingObject(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class HasIncludingObjectTests(unittest.TestCase):
 
@@ -118,6 +141,9 @@ def test_hasIncludingObject(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class HasBeginObjectTests(unittest.TestCase):
 
@@ -126,6 +152,9 @@ def test_hasbeginObject(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class HasExactObjectTests(unittest.TestCase):
 
@@ -134,6 +163,9 @@ def test_hasExactObject(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ComplementTests(unittest.TestCase):
 
@@ -142,6 +174,9 @@ def test_complement(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CdropTests(unittest.TestCase):
 
@@ -150,6 +185,9 @@ def test_cdrop(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CprotectTests(unittest.TestCase):
 
@@ -158,6 +196,9 @@ def test_cprotect(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CsyncTests(unittest.TestCase):
 
@@ -166,6 +207,9 @@ def test_csync(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CloadTests(unittest.TestCase):
 
@@ -174,6 +218,9 @@ def test_cload(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CloadForProjectTests(unittest.TestCase):
 
@@ -182,6 +229,9 @@ def test_cload_for_project(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CrazTests(unittest.TestCase):
 
@@ -190,6 +240,9 @@ def test_craz(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CdumpTests(unittest.TestCase):
 
@@ -198,27 +251,30 @@ def test_cdump(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ListCacheTests(unittest.TestCase):
 
     def test_list_cache(self):
-        import os
-        from climaf.cache import cachedirs
         list_ref = list()
-        for cachedir in cachedirs:
+        for cachedir in env.environment.cachedirs:
             for (d, subd, files) in os.walk(cachedir):
                 for f in files:
-                    if True in [f.endswith(term) for term in [".png", ".nc", ".pdf", ".eps"]]:
+                    if any([f.endswith(term) for term in [".png", ".nc", ".pdf", ".eps"]]):
                         list_ref.append(os.path.sep.join([d, f]))
         self.assertEqual(list_cache(), list_ref)
 
+    def tearDown(self):
+        craz()
+
 
 class ClistTests(unittest.TestCase):
 
     @unittest.skipUnless(False, "The test is not written")
     def test_clist(self):
         # TODO: Implement the tests for this function
-        import os
         from climaf.cache import cachedirs
         # TODO: Once implemented, deal with several cachedirs
         cachedir = cachedirs[0]
@@ -279,6 +335,9 @@ def find_files(rep, size="", age="", access=0, pattern="", not_pattern="", usage
                         i += 1
                     du_list_sort[n] = (du_list_sort[n][0], "%6.1f%s" % (flt, unit[i]))
 
+    def tearDown(self):
+        craz()
+
 
 class ClsTests(unittest.TestCase):
 
@@ -287,6 +346,9 @@ def test_cls(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CrmTests(unittest.TestCase):
 
@@ -295,6 +357,9 @@ def test_crm(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CduTests(unittest.TestCase):
 
@@ -303,6 +368,9 @@ def test_cdu(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CwcTests(unittest.TestCase):
 
@@ -311,6 +379,9 @@ def test_cwc(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class RebuildTests(unittest.TestCase):
 
@@ -319,6 +390,21 @@ def test_rebuild(self):
         # TODO: Implement the tests for this function
         pass
 
+    def tearDown(self):
+        craz()
+
+
+class CCostTest(unittest.TestCase):
+    def test_ccost(self):
+        rst = ds(project="example", simulation="AMIPV6ALB2G",
+                 variable="rstcs", period="1980")
+        cdrop(rst)
+        cfile(rst)
+        ccost(rst)
+
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -329,4 +415,3 @@ def test_rebuild(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_classes.py b/tests/test_classes.py
index b84564c4..46a1175e 100644
--- a/tests/test_classes.py
+++ b/tests/test_classes.py
@@ -12,11 +12,12 @@
 
 from tests.tools_for_tests import remove_dir_and_content
 
-from climaf.cache import setNewUniqueCache
-from climaf.classes import cproject, cdef, Climaf_Classes_Error, cobject, cdummy, processDatasetArgs, cdataset, calias
-from climaf.period import Climaf_Period_Error, init_period
 from env.environment import *
 from env.site_settings import atCNRM, onCiclad
+from climaf.cache import setNewUniqueCache, craz
+from climaf.classes import cproject, cdef, Climaf_Classes_Error, cobject, cdummy, processDatasetArgs, cdataset, \
+    calias, crealms
+from climaf.period import Climaf_Period_Error, init_period
 
 
 class CprojectTests(unittest.TestCase):
@@ -58,6 +59,9 @@ def test_cproject_crs2ds(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CdefTests(unittest.TestCase):
 
@@ -75,11 +79,15 @@ def test_cdef(self):
         with self.assertRaises(Climaf_Classes_Error):
             cdef("my_attribute", value="my_value", project="my_project")
 
+    def tearDown(self):
+        craz()
+
 
 class CobjectTests(unittest.TestCase):
 
     def setUp(self):
         self.my_object = cobject()
+        self.my_other_object = cobject()
 
     def test_cobject_init(self):
         self.assertEqual(self.my_object.crs, "void")
@@ -90,6 +98,11 @@ def test_cobject_str(self):
     def test_cobject_repr(self):
         self.assertEqual(repr(self.my_object), "void")
 
+    def test_cobject_equal(self):
+        self.assertEqual(self.my_object, self.my_other_object)
+        self.assertNotEqual(self.my_object, "test")
+        self.assertNotEqual(self.my_object, cdummy())
+
     @unittest.expectedFailure
     def test_cobject_register(self):
         # TODO: Modify CliMAF to raise an exception
@@ -102,6 +115,9 @@ def test_cobject_erase(self):
         with self.assertRaises(NotImplementedError):
             self.my_object.erase()
 
+    def tearDown(self):
+        craz()
+
 
 class CdummyTests(unittest.TestCase):
 
@@ -112,6 +128,16 @@ def test_cdummy_buildcrs(self):
         my_dummy = cdummy()
         self.assertEqual(my_dummy.buildcrs(), "ARG")
 
+    def test_cdummy_equal(self):
+        my_dummy = cdummy()
+        my_other_dummy = cdummy()
+        self.assertEqual(my_dummy, my_other_dummy)
+        self.assertNotEqual(my_dummy, cobject())
+        self.assertNotEqual(my_dummy, "test")
+
+    def tearDown(self):
+        craz()
+
 
 class ProcessDatasetArgsTests(unittest.TestCase):
 
@@ -233,6 +259,9 @@ def test_without(self):
             processDatasetArgs()
         # TODO: Try to find a project in which after facets retrieval, the value is None to pass in the exception
 
+    def tearDown(self):
+        craz()
+
 
 class CdatasetTests(unittest.TestCase):
 
@@ -315,6 +344,12 @@ def test_cdataset_buildcrs(self):
         # TODO: When this option will be implemented, write the associated test
         # self.assertEqual(self.my_other_dataset.buildcrs(crsrewrite=None), "")
 
+    def test_cdataset_equal(self):
+        self.assertNotEqual(self.my_dataset, "test")
+        self.assertNotEqual(self.my_dataset, cobject())
+        my_dataset = self.my_dataset.explore("resolve")
+        self.assertEqual(self.my_dataset, my_dataset)
+
     @unittest.skipUnless(False, "Test not yet written")
     def test_cdataset_errata(self):
         # TODO: Write the test
@@ -390,6 +425,9 @@ def test_cdataset_check(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CensTests(unittest.TestCase):
 
@@ -398,6 +436,11 @@ def test_cens_init(self):
         # TODO: Write the test
         pass
 
+    @unittest.skipUnless(False, "Test not yet written")
+    def test_cens_equal(self):
+        # TODO: Write the test
+        pass
+
     @unittest.skipUnless(False, "Test not yet written")
     def test_cens_set_order(self):
         # TODO: Write the test
@@ -443,6 +486,9 @@ def test_cens_check(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class EdsTests(unittest.TestCase):
 
@@ -451,6 +497,9 @@ def test_eds(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FdsTests(unittest.TestCase):
 
@@ -459,6 +508,9 @@ def test_fds(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CtreeTests(unittest.TestCase):
 
@@ -477,6 +529,9 @@ def test_ctree_setperiod(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ScriptChildTests(unittest.TestCase):
 
@@ -495,6 +550,9 @@ def test_scriptChild_setperiod(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CompareTreesTests(unittest.TestCase):
 
@@ -503,6 +561,9 @@ def test_compare_trees(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class AllowErrorOnDsTests(unittest.TestCase):
 
@@ -511,6 +572,9 @@ def test_allow_error_on_ds(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class SelectProjectsTests(unittest.TestCase):
 
@@ -519,6 +583,9 @@ def test_select_projects(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class DsTests(unittest.TestCase):
 
@@ -527,6 +594,9 @@ def test_ds(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CfreqsTests(unittest.TestCase):
 
@@ -535,13 +605,21 @@ def test_cfreqs(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CrealmsTests(unittest.TestCase):
 
-    @unittest.skipUnless(False, "Test not yet written")
     def test_crealms(self):
-        # TODO: Write the test
-        pass
+        crealms('my_project', {'atmos': "ATM", 'ocean': "OCE"})
+        self.assertIn("my_project", realms)
+        a_realm = realms["my_project"]
+        self.assertDictEqual(a_realm, {'atmos': "ATM", 'ocean': "OCE"})
+        del realms["my_project"]
+
+    def tearDown(self):
+        craz()
 
 
 class CaliasTests(unittest.TestCase):
@@ -551,6 +629,9 @@ def test_calias(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class VarIsAliasedTests(unittest.TestCase):
 
@@ -559,6 +640,9 @@ def test_varIsAliased(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CmissingTests(unittest.TestCase):
 
@@ -567,6 +651,9 @@ def test_cmissing(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CpageTests(unittest.TestCase):
 
@@ -585,6 +672,9 @@ def test_cpage_buildcrs(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CpagePdfTests(unittest.TestCase):
 
@@ -598,6 +688,9 @@ def test_cpage_pdf_buildcrs(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class GuessProjectsTests(unittest.TestCase):
 
@@ -611,6 +704,9 @@ def test_guess_project(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class BrowseTreeTests(unittest.TestCase):
 
@@ -619,6 +715,9 @@ def test_browse_tree(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class DomainOfTests(unittest.TestCase):
 
@@ -627,6 +726,9 @@ def test_domainOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class VarOfTests(unittest.TestCase):
 
@@ -635,6 +737,9 @@ def test_varOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ModelOfTests(unittest.TestCase):
 
@@ -643,6 +748,9 @@ def test_modelOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class SimulationOfTests(unittest.TestCase):
 
@@ -651,6 +759,9 @@ def test_simulationOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ProjectOfTests(unittest.TestCase):
 
@@ -659,6 +770,9 @@ def test_projectOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class RealmOfTests(unittest.TestCase):
 
@@ -667,6 +781,9 @@ def test_realmOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class GridOfTests(unittest.TestCase):
 
@@ -675,6 +792,9 @@ def test_gridOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class AttributeOfTests(unittest.TestCase):
 
@@ -683,6 +803,9 @@ def test_attributeOf(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ResolveFirstOrLastYearsTests(unittest.TestCase):
 
@@ -691,6 +814,9 @@ def test_resolve_first_or_last_years(self):
         # TODO: Write the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -701,4 +827,3 @@ def test_resolve_first_or_last_years(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_cmacro.py b/tests/test_cmacro.py
index 91337160..a19871cd 100644
--- a/tests/test_cmacro.py
+++ b/tests/test_cmacro.py
@@ -11,12 +11,11 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
-
+from env.environment import *
 from climaf.cache import setNewUniqueCache
 from climaf.api import *
 from climaf.classes import ctree, cdummy, scriptChild
 from climaf.cmacro import macro, crewrite, cmatch, read, write, show, instantiate, Climaf_Macro_Error
-from env.environment import *
 
 
 class MacroTests(unittest.TestCase):
@@ -132,6 +131,9 @@ def test_cens(self):
         self.assertTrue(isinstance(my_macro, cens))
         self.assertEqual(my_macro.buildcrs(), "cens({'1980':ARG,'1981':ARG})")
 
+    def tearDown(self):
+        craz()
+
 
 class CrewriteTests(unittest.TestCase):
 
@@ -165,6 +167,9 @@ def test_crewrite(self):
                          "eu_cross_section(ds('example|AMIPV6ALB2G|ta|198001|global|monthly'),"
                          "ds('example|AMIPV6ALB2G|ta|19800101|global|monthly'))")
 
+    def tearDown(self):
+        craz()
+
 
 class CmatchTests(unittest.TestCase):
 
@@ -213,6 +218,9 @@ def test_cpage(self):
         cmatch_result = cmatch(my_macro, my_page)
         self.assertEqual(cmatch_result, [])
 
+    def tearDown(self):
+        craz()
+
 
 class ReadWriteTests(unittest.TestCase):
 
@@ -238,6 +246,9 @@ def test_write(self):
         write(self.my_macro_file)
         self.assertTrue(os.path.exists(self.my_macro_file))
 
+    def tearDown(self):
+        craz()
+
 
 class ShowTests(unittest.TestCase):
 
@@ -251,6 +262,9 @@ def test_show(self):
         show(interp=False)
         sys.stdout = old_stdout
 
+    def tearDown(self):
+        craz()
+
 
 class InstantiateTests(unittest.TestCase):
 
@@ -258,6 +272,9 @@ class InstantiateTests(unittest.TestCase):
     def test_instantiate(self):
         pass
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -268,4 +285,3 @@ def test_instantiate(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_dataloc.py b/tests/test_dataloc.py
new file mode 100644
index 00000000..fbaea474
--- /dev/null
+++ b/tests/test_dataloc.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Test the cache module.
+"""
+
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+
+import shutil
+import unittest
+
+from env.environment import *
+from tests.tools_for_tests import remove_dir_and_content
+from climaf.cache import setNewUniqueCache, craz
+from climaf.dataloc import dataloc, getlocs, isLocal, selectFiles, \
+	remote_to_local_filename, selectEmFiles, periodOfEmFile, selectExampleFiles, selectCmip5DrsFiles
+
+
+class DatalocTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_init(self):
+		# TODO: Implement the tests
+		pass
+
+	@unittest.skipUnless(False, "The test is not written")
+	def test_eq(self):
+		# TODO: Implement the tests
+		pass
+
+	@unittest.skipUnless(False, "The test is not written")
+	def test_ne(self):
+		# TODO: Implement the tests
+		pass
+
+	@unittest.skipUnless(False, "The test is not written")
+	def test_str(self):
+		# TODO: Implement the tests
+		pass
+
+	@unittest.skipUnless(False, "The test is not written")
+	def test_pr(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class getlocsTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_getlocs(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class isLocalTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_isLocal(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class selectFilesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_selectFiles(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class remote_to_local_filenameTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_remote_to_local_filename(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class selectEmFilesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_selectEmFiles(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class periodOfEmFileTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_periodOfEmFile(self):
+		# TODO: Implement the tests
+		pass
+
+
+	def tearDown(self):
+		craz()
+
+class selectExampleFilesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_selectExampleFiles(self):
+		# TODO: Implement the tests
+		pass
+
+
+	def tearDown(self):
+		craz()
+
+class selectCmip5DrsFilesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_selectCmip5DrsFiles(self):
+		# TODO: Implement the tests
+		pass
+
+
+def tearDown(self):
+	craz()
+
+if __name__ == '__main__':
+	# Jump into the test directory
+	tmp_directory = "/".join([os.environ["HOME"], "tmp", "tests", "test_cache"])
+	remove_dir_and_content(tmp_directory)
+	if not os.path.isdir(tmp_directory):
+		os.makedirs(tmp_directory)
+	setNewUniqueCache(tmp_directory)
+	os.chdir(tmp_directory)
+	unittest.main()
diff --git a/tests/test_driver.py b/tests/test_driver.py
new file mode 100644
index 00000000..950e1c90
--- /dev/null
+++ b/tests/test_driver.py
@@ -0,0 +1,434 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Test the driver module.
+"""
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+import os
+import unittest
+
+from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
+from env.site_settings import atCNRM, onCiclad
+from climaf.cache import setNewUniqueCache, craz
+from climaf.api import cproject, dataloc
+from climaf.driver import capply, capply_script, maketree, capply_operator, ceval_for_cdataset, ceval_for_ctree, \
+	ceval_operator, cstore, ceval_for_scriptChild, ceval_for_cpage, ceval_for_cpage_pdf, ceval_for_cens, \
+	ceval_for_string, ceval, ceval_script, timePeriod, ceval_select, cread, cview, derive_variable, set_variable, \
+	noselect, cfile, cshow, cMA, cvalue, cexport, cimport, get_fig_sizes, cfilePage, cfilePage_pdf, calias, CFlongname, \
+	efile
+from climaf.utils import Climaf_Driver_Error
+from climaf.classes import ds
+from climaf.period import Climaf_Period_Error, init_period
+
+
+class CapplyTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_capply(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CapplyScriptTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_capply_script(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class MaketreeTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_maketree(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CapplyOperatorTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_capply_operator(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForCdatasetTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_cdataset(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForCtreeTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_ctree(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalOperatorTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_operator(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CstoreTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cstore(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForScriptChildTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_scriptChild(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForCpageTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_cpage(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForCpagePdfTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_cpage_pdf(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForCensTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_cens(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalForStringTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_for_string(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalScriptTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_script(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class TimePeriodTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_timePeriod(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CevalSelectTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_ceval_select(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CreadTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cread(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CviewTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cview(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class DeriveVariableTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_derive_variable(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class SetVariableTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_set_variable(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class NoselectTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_noselect(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CfileTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cfile(self):
+		# TODO: Write the test
+		pass
+
+	def test_cdataset_cfile_not_ambiguous(self):
+		my_data_test = ds(project="example", variable="tas", period="*", simulation="AMIPV6ALB2G")
+		dict_choices = my_data_test.explore("choices")
+		self.assertDictEqual(dict_choices, {'period': [init_period("1980-1981")]})
+		cfile(my_data_test)
+
+	def tearDown(self):
+		craz()
+
+
+class CshowTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cshow(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CMATests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cMA(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CvalueTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cvalue(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CexportTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cexport(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CimportTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cimport(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class GetFigSizesTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_get_fig_sizes(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CfilePageTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cfilePage(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CfilePagePdfTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_cfilePage_pdf(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class CaliasTests(unittest.TestCase):
+
+	def setUp(self):
+		cproject('data_CNRM')
+		root = "/cnrm/est/COMMON/climaf/test_data/${simulation}/O/"
+		suffix = "${simulation}_1m_YYYYMMDD_YYYYMMDD_${variable}.nc"
+		data_url = root + suffix
+		dataloc(project='data_CNRM', organization='generic', url=data_url)
+
+	def test_calias(self):
+		# TODO: Write the test
+		if "data_CNRM" in aliases or "data_CNRM" in derived_variables:
+			raise KeyError("Should not have aliases for 'data_CNRM' at this stage")
+		calias("data_CNRM", "tos,thetao", filenameVar="grid_T_table2.2")
+		calias("data_CNRM", "uo", filenameVar="grid_U_table2.3")
+		self.assertDictEqual(aliases["data_CNRM"],
+		                     {'tos,thetao': ('tos,thetao', 1.0, 0.0, None, 'grid_T_table2.2', None, None),
+		                      'tos': ('tos', 1.0, 0.0, None, 'grid_T_table2.2', None, None),
+		                      'thetao': ('thetao', 1.0, 0.0, None, 'grid_T_table2.2', None, None),
+		                      'uo': ('uo', 1.0, 0.0, None, 'grid_U_table2.3', None, None)
+		                      })
+		self.assertDictEqual(derived_variables["data_CNRM"],
+		                     {'tos': ('ccdo', 'tos', ['tos,thetao'], {'operator': 'selname,tos'}),
+		                      'thetao': ('ccdo', 'thetao', ['tos,thetao'], {'operator': 'selname,thetao'})
+		                      })
+
+	def tearDown(self):
+		craz()
+
+
+class CFlongnameTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_CFlongname(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class EfileTests(unittest.TestCase):
+
+	@unittest.skipUnless(False, "Test not yet written")
+	def test_efile(self):
+		# TODO: Write the test
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+if __name__ == '__main__':
+	# Jump into the test directory
+	tmp_directory = "/".join([os.environ["HOME"], "tmp", "tests", "test_driver"])
+	remove_dir_and_content(tmp_directory)
+	if not os.path.isdir(tmp_directory):
+		os.makedirs(tmp_directory)
+	setNewUniqueCache(tmp_directory)
+	os.chdir(tmp_directory)
+	unittest.main()
diff --git a/tests/test_example_data_plot.py b/tests/test_example_data_plot.py
index 49e373e1..3a19e167 100644
--- a/tests/test_example_data_plot.py
+++ b/tests/test_example_data_plot.py
@@ -11,12 +11,12 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content, compare_picture_files, skipUnless_CNRM_Lustre
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __path__ as cpath
 from climaf.api import craz, plot, cdef, cfile, time_average, ds, space_average, curves, cpage, cpage_pdf, cpdfcrop, \
     cens, llbox, cproject, dataloc, calias, fixed_fields, ccdo, fds
-from env.environment import *
 
 if not isinstance(cpath, list):
     cpath = cpath.split(os.sep)
@@ -201,6 +201,9 @@ def test_gplot_maps_8(self):
         ref_plotmap_bis = os.sep.join([self.reference_directory, "test_A.10.png"])
         compare_picture_files(plot_map_bis, ref_plotmap_bis)
 
+    def tearDown(self):
+        craz()
+
 
 @skipUnless_CNRM_Lustre()
 class DataGplotMapsRotation(unittest.TestCase):
@@ -347,6 +350,9 @@ def test_gplot_maps_rotation_6(self):
         ref_plotmap = os.sep.join([self.reference_directory, "test_B.6.png"])
         compare_picture_files(plot_map, ref_plotmap)
 
+    def tearDown(self):
+        craz()
+
 
 class DataGplotCrossSections(unittest.TestCase):
 
@@ -465,6 +471,9 @@ def test_gplot_cross_sections_7(self):
         ref_plotcross_bis = os.sep.join([self.reference_directory, "test_C.8.png"])
         compare_picture_files(plot_cross_bis, ref_plotcross_bis)
 
+    def tearDown(self):
+        craz()
+
 
 class DataGplotProfiles(unittest.TestCase):
 
@@ -521,6 +530,10 @@ def test_gplot_profiles_3(self):
         ref_plotprofile = os.sep.join([self.reference_directory, "test_D.3.png"])
         compare_picture_files(plot_profile, ref_plotprofile)
 
+    def tearDown(self):
+        craz()
+
+
 
 class DataPlot(unittest.TestCase):
 
@@ -608,6 +621,9 @@ def test_data_plot_4(self):
         ref_my_plot_2 = os.sep.join([self.reference_directory, "test4.2.png"])
         compare_picture_files(my_plot_2, ref_my_plot_2)
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -619,5 +635,3 @@ def test_data_plot_4(self):
     os.chdir(tmp_directory)
     craz()
     unittest.main()
-    craz()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_example_data_retrieval.py b/tests/test_example_data_retrieval.py
index 591cd5a8..8e5f06e5 100644
--- a/tests/test_example_data_retrieval.py
+++ b/tests/test_example_data_retrieval.py
@@ -7,16 +7,15 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content, compare_picture_files, skipUnless_CNRM_Lustre
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __path__ as cpath
 from climaf.api import *
 from climaf.period import init_period
-from env.environment import *
 
 if not isinstance(cpath, list):
     cpath = cpath.split(os.sep)
@@ -58,6 +57,9 @@ def test_retrieval_subperiod_1(self):
                          "latest'),operator='fldmean'),operator='yearavg')")
         cMA(climaf_ds)
 
+    def tearDown(self):
+        craz()
+
 
 class DataRetrieval_2(unittest.TestCase):
 
@@ -95,6 +97,9 @@ def test_retrieval_subperiod_several_files(self):
         self.assertEqual(str(f), "ds('test_data%historical%tas3%185004-185104%global%monthly%CNRM-CM5%Amon%r1i1p1')")
         cfile(f)
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -105,4 +110,3 @@ def test_retrieval_subperiod_several_files(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_example_index_html.py b/tests/test_example_index_html.py
index 015a0c62..8434fc08 100644
--- a/tests/test_example_index_html.py
+++ b/tests/test_example_index_html.py
@@ -7,20 +7,16 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
-import shutil
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content, compare_html_files
-
-from climaf.cache import setNewUniqueCache
+from collections import OrderedDict
+from env.environment import *
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __path__ as cpath
 from climaf.api import ccdo, select, time_average, plot, cfile, ds, craz
 from climaf.html import header, section, vspace, link_on_its_own_line, line, open_table, close_table, open_line, \
     close_line, cell, fline, flines, trailer
-from climaf import cachedir
-from collections import OrderedDict
-from env.environment import *
 
 
 if not isinstance(cpath, list):
@@ -127,7 +123,7 @@ def test_html_index_2(self):
         # optional argument 'dirname'. The programmer must also take care of
         # writing the index in the same directory An example shows below
         ###################################################################################
-        atlas_dir = os.path.expanduser(cachedir + '/../atlas')
+        atlas_dir = os.path.expanduser(default_cache + '/../atlas')
         index = header("CliMAF ATLAS of " + exp + " for " + period)
         index += open_table(title='variable/season', columns=['DJF', 'MAM', 'JJA'], spacing=5)
         index += fline(my_slice, 'tas', ['DJF', 'MAM', 'JJA'], title="Surface temperature",
@@ -154,4 +150,3 @@ def tearDown(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_find_files.py b/tests/test_find_files.py
new file mode 100644
index 00000000..8f1a79af
--- /dev/null
+++ b/tests/test_find_files.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Test the find files module.
+"""
+
+from __future__ import print_function, division, unicode_literals, absolute_import
+
+
+import unittest
+import os
+
+from env.environment import *
+from climaf.cache import setNewUniqueCache, craz
+from tests.tools_for_tests import remove_dir_and_content
+from climaf.find_files import selectGenericFiles, mysplit, build_facets_regexp, rreplace, store_wildcard_facet_values, \
+	my_glob, extract_period, glob_remote_data, post_process_wildcard_facets_values
+
+
+class selectGenericFilesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_selectGenericFiles(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class mysplitTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_mysplit(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class build_facets_regexpTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_build_facets_regexp(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class rreplaceTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_rreplace(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class store_wildcard_facet_valuesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_store_wildcard_facets_values(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class myglobTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_myglob(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class extract_periodTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_extract_period(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class glob_remote_dataTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_glob_remote_data(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+class post_process_wildcard_facets_valuesTest(unittest.TestCase):
+	@unittest.skipUnless(False, "The test is not written")
+	def test_post_process_wildcard_facets_values(self):
+		# TODO: Implement the tests
+		pass
+
+	def tearDown(self):
+		craz()
+
+
+if __name__ == '__main__':
+	# Jump into the test directory
+	tmp_directory = "/".join([os.environ["HOME"], "tmp", "tests", "test_find_files"])
+	remove_dir_and_content(tmp_directory)
+	if not os.path.isdir(tmp_directory):
+		os.makedirs(tmp_directory)
+	setNewUniqueCache(tmp_directory)
+	os.chdir(tmp_directory)
+	unittest.main()
diff --git a/tests/test_functions.py b/tests/test_functions.py
index a99e4e2b..b3ebdbe7 100644
--- a/tests/test_functions.py
+++ b/tests/test_functions.py
@@ -7,19 +7,17 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
-
-from climaf.cache import setNewUniqueCache
+from env.environment import *
+from climaf.cache import setNewUniqueCache, craz
 from climaf.driver import cfile
 from climaf.classes import ds
 from climaf.functions import cscalar, apply_scale_offset, fmul, fdiv, fadd, fsub, iplot, getLevs, vertical_average, \
     implot, diff_regrid, diff_regridn, tableau, annual_cycle, clim_average, clim_average_fast, summary, projects, \
     lonlatvert_interpolation, zonmean_interpolation, zonmean, diff_zonmean, convert_list_to_string,\
     ts_plot, iplot_members
-from env.environment import *
 
 
 class CscalarTests(unittest.TestCase):
@@ -28,6 +26,9 @@ class CscalarTests(unittest.TestCase):
     def test_cscalar(self):
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ApplyScaleOffsetTests(unittest.TestCase):
 
@@ -36,6 +37,9 @@ def test_apply_scale_offset(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FmulTests(unittest.TestCase):
 
@@ -44,6 +48,9 @@ def test_fmul(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FdivTests(unittest.TestCase):
 
@@ -52,6 +59,9 @@ def test_fdiv(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FaddTests(unittest.TestCase):
 
@@ -60,6 +70,9 @@ def test_fadd(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FsubTests(unittest.TestCase):
 
@@ -68,6 +81,9 @@ def test_fsub(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class IplotTests(unittest.TestCase):
 
@@ -76,6 +92,9 @@ def test_iplot(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class GetLevsTests(unittest.TestCase):
 
@@ -84,6 +103,9 @@ def test_getLevs(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class VerticalAverageTests(unittest.TestCase):
 
@@ -92,6 +114,9 @@ def test_vertical_average(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ImplotTests(unittest.TestCase):
 
@@ -100,6 +125,9 @@ def test_implot(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class DiffRegridTests(unittest.TestCase):
 
@@ -108,6 +136,9 @@ def test_diff_regrid(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class DiffRegridnTests(unittest.TestCase):
 
@@ -116,6 +147,9 @@ def test_diff_regridn(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class TableauTests(unittest.TestCase):
 
@@ -143,6 +177,9 @@ def test_tableau(self):
         my_table = tableau(8, 2)
         self.assertTrue(self.my_table_test_function(my_table, 2, 8))
 
+    def tearDown(self):
+        craz()
+
 
 class AnnualCycleTests(unittest.TestCase):
 
@@ -151,6 +188,9 @@ def test_annual_cycle(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ClimAverageTests(unittest.TestCase):
 
@@ -159,6 +199,9 @@ def test_clim_average(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ClimAverageFastTests(unittest.TestCase):
 
@@ -167,6 +210,9 @@ def test_clim_average_fast(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class SummaryTests(unittest.TestCase):
 
@@ -175,6 +221,9 @@ def test_summary(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ProjectsTests(unittest.TestCase):
 
@@ -183,6 +232,9 @@ def test_projects(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class lonlatvertInterpolationTests(unittest.TestCase):
 
@@ -191,6 +243,9 @@ def test_lonlatvert_interpolation(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ZonmeanInterpolationTests(unittest.TestCase):
 
@@ -199,6 +254,9 @@ def test_zonmean_interpolation(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ZonmeanTests(unittest.TestCase):
 
@@ -207,6 +265,9 @@ def test_zonmean(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class DiffZonmeanTests(unittest.TestCase):
 
@@ -215,6 +276,9 @@ def test_diff_zonmean(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class ConvertListToStringTests(unittest.TestCase):
 
@@ -242,6 +306,9 @@ def test_convert_list_to_string(self):
         my_list = ["toto", 1, dict(a_key="a_value", an_other_key=8), [("8", 6), "an_other_value"]]
         self.assertEqual(convert_list_to_string(my_list), self.my_conversion_function(my_list))
 
+    def tearDown(self):
+        craz()
+
 
 class TsPlotTests(unittest.TestCase):
 
@@ -250,6 +317,9 @@ def test_ts_plot(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class IplotMembersTests(unittest.TestCase):
 
@@ -258,6 +328,9 @@ def test_iplot_members(self):
         # TODO: Implement the test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -268,4 +341,3 @@ def test_iplot_members(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_html.py b/tests/test_html.py
index 7c4d6668..40a2c421 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -7,20 +7,19 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
 import unittest
 import string
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __file__ as climaf_based_file
 from climaf.classes import ds
 from climaf.driver import cfile
 from climaf.html import header, trailer, vspace, section, open_table, close_table, open_line, close_line, link,\
     link_on_its_own_line, cell, line, flines, fline, cinstantiate, compareCompanion, start_line, safe_mode_cfile_plot, \
     Climaf_Html_Error
-from env.environment import *
 
 
 class HeaderTests(unittest.TestCase):
@@ -57,12 +56,18 @@ def test_header(self):
         self.assertEqual(header("A header", style_file=cesmep_css_file),
                          template.safe_substitute(STYLE=cesmep_css_style, TITLE="A header"))
 
+    def tearDown(self):
+        craz()
+
 
 class TrailerTests(unittest.TestCase):
 
     def test_trailer(self):
         self.assertEqual(trailer(), "</body>\n")
 
+    def tearDown(self):
+        craz()
+
 
 class VspaceTests(unittest.TestCase):
 
@@ -71,6 +76,9 @@ def test_vspace(self):
         self.assertEqual(vspace(1), "<br>\n")
         self.assertEqual(vspace(5), "<br>\n<br>\n<br>\n<br>\n<br>\n")
 
+    def tearDown(self):
+        craz()
+
 
 class SectionTests(unittest.TestCase):
 
@@ -87,6 +95,9 @@ def test_section(self):
         self.assertEqual(section("A title", level=5, key="a key"),
                          template.safe_substitute(TITLE="A title", LEVEL=5, KEY='"a key"'))
 
+    def tearDown(self):
+        craz()
+
 
 class OpenTableTests(unittest.TestCase):
 
@@ -110,12 +121,18 @@ def test_open_table(self):
                                                   COLUMNS="".join([template_columns.safe_substitute(LABEL=lab)
                                                                    for lab in ["a column", "an other one"]])))
 
+    def tearDown(self):
+        craz()
+
 
 class CloseTableTests(unittest.TestCase):
 
     def test_close_table(self):
         self.assertEqual(close_table(), "</TABLE>\n")
 
+    def tearDown(self):
+        craz()
+
 
 class OpenLineTests(unittest.TestCase):
 
@@ -125,12 +142,18 @@ def test_open_line(self):
         self.assertEqual(open_line(), template.safe_substitute(TITLE=""))
         self.assertEqual(open_line("A title"), template.safe_substitute(TITLE="A title"))
 
+    def tearDown(self):
+        craz()
+
 
 class CloseLineTests(unittest.TestCase):
 
     def test_close_line(self):
         self.assertEqual(close_line(), ' </TR>\n')
 
+    def tearDown(self):
+        craz()
+
 
 class LinkTests(unittest.TestCase):
 
@@ -211,6 +234,9 @@ def test_link_thumbnail_None_hover_not_False(self):
         with self.assertRaises(Climaf_Html_Error):
             link(label="A label", filename="A filename", thumbnail=None, hover="200m300")
 
+    def tearDown(self):
+        craz()
+
 
 class LinkOnItsOwnLineTests(unittest.TestCase):
 
@@ -224,6 +250,9 @@ def test_link_on_its_own_line(self):
         self.assertEqual(link_on_its_own_line("A label", "A filename", thumbnail=150, hover=80),
                          open_line() + link("A label", "A filename", thumbnail=150, hover=80) + close_line())
 
+    def tearDown(self):
+        craz()
+
 
 class CellTests(unittest.TestCase):
 
@@ -263,6 +292,9 @@ def test_cell_dirname_not_None_filename_not_None(self):
                          self.test_template.safe_substitute(LINK=""))
         self.assertTrue(os.path.exists(self.test_dirname) and os.path.isdir(self.test_dirname))
 
+    def tearDown(self):
+        craz()
+
 
 class LineTests(unittest.TestCase):
 
@@ -276,6 +308,9 @@ def test_line(self):
               cell("some value", "with figure") + close_line()
         self.assertEqual(line(list_of_pairs, "A title"), rep)
 
+    def tearDown(self):
+        craz()
+
 
 class FlineTests(unittest.TestCase):
 
@@ -284,6 +319,9 @@ def test_fline(self):
         # TODO: Implement the tests
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class FlinesTests(unittest.TestCase):
 
@@ -292,6 +330,9 @@ def test_flines(self):
         # TODO: Implement the tests
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CinstantiateTests(unittest.TestCase):
 
@@ -300,6 +341,9 @@ def test_cinstantiate(self):
         # TODO: Implement the tests
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class CompareCompanionTests(unittest.TestCase):
 
@@ -310,6 +354,9 @@ def test_compareCompanion(self):
               'src="https://cdn.rawgit.com/PBrockmann/compareCompanion/master/compareCompanion.js"></script> \n'
         self.assertEqual(compareCompanion(), rep)
 
+    def tearDown(self):
+        craz()
+
 
 class StartLineTests(unittest.TestCase):
 
@@ -319,6 +366,9 @@ def test_start_line(self):
         self.assertEqual(start_line("A title"), open_table() + open_line("A title") + close_line() + close_table() +
                          open_table() + open_line())
 
+    def tearDown(self):
+        craz()
+
 
 class SafeModeCfilePlotTests(unittest.TestCase):
 
@@ -327,6 +377,9 @@ def test_safe_mode_cfile_plot(self):
         # TODO: Implement the tests
         pass
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -337,4 +390,3 @@ def test_safe_mode_cfile_plot(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_import.py b/tests/test_import.py
index 5962cefe..ad166be3 100644
--- a/tests/test_import.py
+++ b/tests/test_import.py
@@ -12,6 +12,7 @@
 import importlib
 
 from tests.tools_for_tests import remove_dir_and_content
+from climaf.cache import setNewUniqueCache
 
 
 testmodules = [
@@ -53,5 +54,5 @@ def test_imports(self):
     if not os.path.isdir(tmp_directory):
         os.makedirs(tmp_directory)
     os.chdir(tmp_directory)
+    setNewUniqueCache(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_mcdo.py b/tests/test_mcdo.py
index ceacedd1..28e885d7 100644
--- a/tests/test_mcdo.py
+++ b/tests/test_mcdo.py
@@ -12,7 +12,8 @@
 
 from tests.tools_for_tests import remove_dir_and_content, compare_netcdf_files, compare_text_files
 
-from climaf.cache import setNewUniqueCache
+from env.environment import *
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __path__ as rootpath
 
 from scripts.mcdo import main as mcdo_main
@@ -60,6 +61,9 @@ def test_2(self):
         compare_text_files(test_log, ref_log, _CLIMAF_PATH_=rootpath[0], _TEST_PATH_=tmp_directory)
         compare_netcdf_files(test_output_file, ref_output_file)
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -70,4 +74,3 @@ def test_2(self):
     os.chdir(tmp_directory)
     setNewUniqueCache(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_netcdfbasics.py b/tests/test_netcdfbasics.py
index 2209c0ed..f07d6462 100644
--- a/tests/test_netcdfbasics.py
+++ b/tests/test_netcdfbasics.py
@@ -7,17 +7,16 @@
 from __future__ import print_function, division, unicode_literals, absolute_import
 
 
-import os
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
 from climaf.netcdfbasics import varOfFile, varsOfFile, fileHasVar, fileHasDim, dimsOfFile, \
     model_id, timeLimits
 from climaf.period import init_period
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf import __path__ as rootpath
-from env.environment import *
 
 
 class VarsOfFileTests(unittest.TestCase):
@@ -29,6 +28,9 @@ def test_vars(self):
             [rootpath[0], "..", "examples", "data", "tas_Amon_CNRM-CM5_historical_r1i1p1_185001-185212.nc"])
         self.assertEqual(varsOfFile(my_file), ["tas", ])
 
+    def tearDown(self):
+        craz()
+
 
 class VarOfFileTests(unittest.TestCase):
 
@@ -39,6 +41,9 @@ def test_var(self):
             [rootpath[0], "..", "examples", "data", "tas_Amon_CNRM-CM5_historical_r1i1p1_185001-185212.nc"])
         self.assertEqual(varOfFile(my_file), "tas")
 
+    def tearDown(self):
+        craz()
+
 
 class FileHasVarTests(unittest.TestCase):
 
@@ -51,6 +56,9 @@ def test_var(self):
         self.assertFalse(fileHasVar(my_file, "ua"))
         self.assertTrue(fileHasVar(my_file, "tas"))
 
+    def tearDown(self):
+        craz()
+
 
 class DimsOfFileTests(unittest.TestCase):
 
@@ -61,6 +69,9 @@ def test_dims(self):
             [rootpath[0], "..", "examples", "data", "tas_Amon_CNRM-CM5_historical_r1i1p1_185001-185212.nc"])
         self.assertEqual(sorted(dimsOfFile(my_file)), ["lat", "lon", "nb2", "time"])
 
+    def tearDown(self):
+        craz()
+
 
 class FileHasDimTests(unittest.TestCase):
 
@@ -73,6 +84,9 @@ def test_dim(self):
         self.assertFalse(fileHasDim(my_file, "time_bounds"))
         self.assertTrue(fileHasDim(my_file, "lon"))
 
+    def tearDown(self):
+        craz()
+
 
 class ModelIdTests(unittest.TestCase):
 
@@ -83,6 +97,9 @@ def test_model_id(self):
             [rootpath[0], "..", "examples", "data", "tas_Amon_CNRM-CM5_historical_r1i1p1_185001-185212.nc"])
         self.assertEqual(model_id(my_file), "CNRM-CM5")
 
+    def tearDown(self):
+        craz()
+
 
 class TimeLimitesTests(unittest.TestCase):
 
@@ -93,6 +110,9 @@ def test_time_limites(self):
             [rootpath[0], "..", "examples", "data", "tas_Amon_CNRM-CM5_historical_r1i1p1_185001-185212.nc"])
         self.assertEqual(repr(timeLimits(my_file)), repr(init_period("1850-1852")))
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -103,4 +123,3 @@ def test_time_limites(self):
     os.chdir(tmp_directory)
     setNewUniqueCache(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_operators.py b/tests/test_operators.py
index 0149a41f..67764377 100644
--- a/tests/test_operators.py
+++ b/tests/test_operators.py
@@ -11,10 +11,10 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf.operators import scriptFlags, cscript, fixed_fields, coperator, Climaf_Operator_Error
-from env.environment import *
 
 
 class CscriptTest(unittest.TestCase):
@@ -94,6 +94,9 @@ def test_repr(self):
         my_script = cscript('mycdo', '(cdo ${operator} ${in} ${out})')
         self.assertEqual(repr(my_script), "CliMAF operator : mycdo")
 
+    def tearDown(self):
+        craz()
+
 
 class FixedFieldsTest(unittest.TestCase):
 
@@ -110,6 +113,9 @@ def test_fixed_fields(self):
                          (('coordinates.nc',
                            '/cnrm/ioga/Users/chevallier/chevalli/Partage/NEMO/eORCA_R025_coordinates_v1.0.nc'), ))
 
+    def tearDown(self):
+        craz()
+
 
 class CoperatorTest(unittest.TestCase):
 
@@ -118,6 +124,9 @@ def test_coperator(self):
         # TODO: Implement this test
         pass
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -128,4 +137,3 @@ def test_coperator(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_operators_derive.py b/tests/test_operators_derive.py
index e5fe0671..3b319538 100644
--- a/tests/test_operators_derive.py
+++ b/tests/test_operators_derive.py
@@ -11,11 +11,11 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf.operators import scriptFlags, cscript, fixed_fields, coperator
 from climaf.operators_derive import derive, is_derived_variable, derived_variable, Climaf_Operator_Error
-from env.environment import *
 
 
 class DeriveTest(unittest.TestCase):
@@ -40,6 +40,9 @@ def test_derive_from_sth_else(self):
         # TODO: Implement this test and modify CliMAF in this scope
         pass
 
+    def tearDown(self):
+        craz()
+
 
 class IsDerivedVariableTest(unittest.TestCase):
 
@@ -52,6 +55,9 @@ def test_is_derived_variable(self):
         self.assertTrue(is_derived_variable("rscre", "erai"))
         self.assertFalse(is_derived_variable("ta", "CMIP6"))
 
+    def tearDown(self):
+        craz()
+
 
 class DerivedVariableTest(unittest.TestCase):
 
@@ -66,6 +72,9 @@ def test_derived_variable(self):
         self.assertEqual(derived_variable("rscre", "erai"), ("minus", "rscre", ["rs", "rscs"], {}))
         self.assertEqual(derived_variable("ta", "erai"), ('rescale', 'ta', ['t'], {'scale': 1.0, 'offset': 0.0}))
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -76,4 +85,3 @@ def test_derived_variable(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_operators_scripts.py b/tests/test_operators_scripts.py
index a187b375..f74a8286 100644
--- a/tests/test_operators_scripts.py
+++ b/tests/test_operators_scripts.py
@@ -11,10 +11,10 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf.operators import scriptFlags
-from env.environment import *
 
 
 class ScriptFlagsTests(unittest.TestCase):
@@ -60,6 +60,9 @@ def test_equality(self):
         self.assertTrue(scriptFlags() == scriptFlags(canOpendap=False))
         self.assertFalse(scriptFlags() == scriptFlags(commuteWithEnsemble=False))
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -70,4 +73,3 @@ def test_equality(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_period.py b/tests/test_period.py
index 8a598b15..6b6a4ad6 100644
--- a/tests/test_period.py
+++ b/tests/test_period.py
@@ -12,12 +12,12 @@
 from datetime import datetime, timedelta
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
 from climaf.period import cperiod, Climaf_Period_Error, init_period, sort_periods_list, merge_periods, \
     intersect_periods_list, lastyears, firstyears
 
-from climaf.cache import setNewUniqueCache
-from env.environment import *
+from climaf.cache import setNewUniqueCache, craz
 
 
 class CreatePeriodDefinedTests(unittest.TestCase):
@@ -60,6 +60,8 @@ def test_order(self):
         with self.assertRaises(Climaf_Period_Error):
             cperiod(date_2, date_1)
 
+    def tearDown(self):
+        craz()
 
 class CreatePeriodFixedTests(unittest.TestCase):
 
@@ -106,6 +108,8 @@ def test_intersects(self):
         with self.assertRaises(Climaf_Period_Error):
             self.my_period.intersects(self.my_test_period)
 
+    def tearDown(self):
+        craz()
 
 class CreatePeriodGenericTests(unittest.TestCase):
 
@@ -189,6 +193,8 @@ def test_intersects(self):
         self.assertEqual(self.my_period_6.intersects(self.my_period), self.my_period)
         self.assertEqual(self.my_period_2.intersects(self.my_period), self.my_period_5)
 
+    def tearDown(self):
+        craz()
 
 class InitPeriodTests(unittest.TestCase):
 
@@ -296,6 +302,8 @@ def test_period(self):
         self.assertEqual(my_period.start, datetime(1850, 1, 2))
         self.assertEqual(my_period.end, datetime(1950, 5, 23))
 
+    def tearDown(self):
+        craz()
 
 class SortPeriodsListTests(unittest.TestCase):
 
@@ -315,6 +323,8 @@ def test_sort(self):
         unordered_list = [my_period_1, my_period_4, my_period_3, my_period_2]
         self.assertEqual(sort_periods_list(unordered_list), ordered_list)
 
+    def tearDown(self):
+        craz()
 
 class MergePeriodsTests(unittest.TestCase):
 
@@ -333,6 +343,8 @@ def test_merge(self):
                        cperiod(datetime(2000, 5, 6), datetime(2061, 4, 9))]
         self.assertEqual(merge_periods(unordered_list), result_list)
 
+    def tearDown(self):
+        craz()
 
 class IntersectPeriodsListsTests(unittest.TestCase):
 
@@ -353,6 +365,8 @@ def test_intersect(self):
         result_list = []
         self.assertEqual(intersect_periods_list(my_list_1, my_list_2), result_list)
 
+    def tearDown(self):
+        craz()
 
 class LastYearsTest(unittest.TestCase):
 
@@ -368,6 +382,8 @@ def test_last_years(self):
         self.assertEqual(lastyears(my_period, 1), repr(init_period("18580526-18590525")))
         self.assertEqual(lastyears(my_period, 15), repr(my_period))
 
+    def tearDown(self):
+        craz()
 
 class FirstYearsTest(unittest.TestCase):
 
@@ -383,6 +399,9 @@ def test_last_years(self):
         self.assertEqual(firstyears(my_period, 1), repr(init_period("18500623-18510622")))
         self.assertEqual(firstyears(my_period, 15), repr(my_period))
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -393,4 +412,3 @@ def test_last_years(self):
     os.chdir(tmp_directory)
     setNewUniqueCache(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/test_standard_operators.py b/tests/test_standard_operators.py
index 0d36247f..dbe75971 100644
--- a/tests/test_standard_operators.py
+++ b/tests/test_standard_operators.py
@@ -11,10 +11,10 @@
 import unittest
 
 from tests.tools_for_tests import remove_dir_and_content
+from env.environment import *
 
-from climaf.cache import setNewUniqueCache
+from climaf.cache import setNewUniqueCache, craz
 from climaf.standard_operators import load_standard_operators
-from env.environment import *
 
 
 class StandardOperatorsTests(unittest.TestCase):
@@ -27,6 +27,9 @@ def test_load_standard_operator(self):
             self.assertIn(script, globals())
             self.assertIn(script, list_of_objects_in_main)
 
+    def tearDown(self):
+        craz()
+
 
 if __name__ == '__main__':
     # Jump into the test directory
@@ -37,4 +40,3 @@ def test_load_standard_operator(self):
     setNewUniqueCache(tmp_directory)
     os.chdir(tmp_directory)
     unittest.main()
-    remove_dir_and_content(tmp_directory)
diff --git a/tests/tools_for_tests.py b/tests/tools_for_tests.py
index 08fe29e5..02b75387 100644
--- a/tests/tools_for_tests.py
+++ b/tests/tools_for_tests.py
@@ -7,16 +7,13 @@
 
 from __future__ import unicode_literals, absolute_import, print_function, division
 
-import os
-import subprocess
 import unittest
 import re
-import sys
 import six
+import shutil
 
-from climaf.api import cshow, ncview, cfile
-from climaf import xdg_bin
 from env.environment import *
+from climaf.api import cshow, ncview, cfile
 
 
 def skipUnless_CNRM_Lustre():
@@ -33,19 +30,10 @@ def skipUnless_Ciclad():
 
 def remove_dir_and_content(dirname):
     if os.path.isdir(dirname):
-        # Deal with files
-        listfiles = list()
-        listdirs = list()
-        for (d, subdirs, files) in os.walk(dirname):
-            for name in files:
-                listfiles.append(os.path.sep.join([d, name]))
-            for subd in subdirs:
-                listdirs.append(os.path.sep.join([d, subd]))
-        for f in listfiles:
-            os.remove(f)
-        # Deal with subdirectories
-        for d in sorted(listdirs, reverse=True):
-            os.rmdir(d)
+        shutil.rmtree(dirname)
+        print("Remove directory: %s" % dirname)
+    else:
+        print("Try to remove a non existing directory: %s" % dirname)
 
 
 def get_figures_and_content_from_html(html_file, regexp, patterns_to_exclude=list()):
@@ -142,8 +130,8 @@ def compare_picture_files(object_test, fic_ref, display=False, display_error=Tru
             files_format = files_format[0]
         if files_format not in ["png", "eps", "jpeg", "pdf"]:
             raise ValueError("Unknown format found %s" % files_format)
-        if xdg_bin and files_format in ["eps", "pdf"]:
-            display_cmd = "xdg-open {}"
+        if xdg_bin is not None and files_format in ["eps", "pdf"]:
+            display_cmd = "%s {}" % xdg_bin
         else:
             display_cmd = "display {}"
         diff_file = file_test + "_diff.{}".format(files_format)