From b1edbf009a39c92e4a13bdb6a97a640b695799f9 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Thu, 1 Jun 2017 17:34:49 +0200
Subject: [PATCH] topology descriptors

---
 .../backend/device/opencl/opencl_operator.py  |  61 ++++---
 .../operator/directional/advection_dir.py     |   5 +-
 .../backend/host/fortran/operator/poisson.py  |   4 +-
 hysop/core/graph/computational_graph.py       |  61 +++++--
 hysop/core/graph/computational_node.py        |  92 +++++-----
 hysop/core/graph/computational_operator.py    |  88 ++++++---
 hysop/core/graph/continuous.py                |  52 ++----
 hysop/core/mpi/topology.py                    |  29 ++-
 hysop/core/mpi/topology_descriptor.py         | 158 ++++++++++++++--
 hysop/fields/field_requirements.py            | 168 ++++++++++--------
 hysop/numerics/splitting/strang.py            |   4 +-
 hysop/old/operator.old/computational.py       |   4 +-
 hysop/old/operator.old/continuous.py          |   2 +-
 hysop/old/operator.old/curlAndDiffusion.py    |   4 +-
 hysop/old/operator.old/redistribute.py        |   4 +-
 hysop/operator/diffusion.py                   |   4 +-
 hysop/operator/directional/advection_dir.py   |   4 +-
 hysop/operator/directional/stretching_dir.py  |   4 +-
 hysop/operator/poisson.py                     |   4 +-
 hysop/operator/redistribute.py                |   7 +-
 hysop/problem.py                              |   2 +
 hysop/tools/types.py                          |   6 +-
 22 files changed, 506 insertions(+), 261 deletions(-)

diff --git a/hysop/backend/device/opencl/opencl_operator.py b/hysop/backend/device/opencl/opencl_operator.py
index 2679c1f46..27506a5ae 100644
--- a/hysop/backend/device/opencl/opencl_operator.py
+++ b/hysop/backend/device/opencl/opencl_operator.py
@@ -7,14 +7,16 @@ discrete operators working on the OpenCl backend.
 """
 from abc import ABCMeta
 
+from hysop.constants import Precision, Backend
 from hysop.tools.decorators import debug
 from hysop.tools.types import check_instance, first_not_None
-from hysop.constants import Precision, Backend
-from hysop.core.graph.computational_operator import ComputationalGraphOperator
 from hysop.backend.device.codegen.structs.mesh_info import MeshInfoStruct
-from hysop.backend.device.opencl.opencl_tools         import get_or_create_opencl_env
 from hysop.backend.device.kernel_config import KernelConfig
+from hysop.backend.device.opencl.opencl_tools import get_or_create_opencl_env
+from hysop.backend.device.opencl.opencl_env   import OpenClEnvironment
 from hysop.core.mpi.topology import Topology
+from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.graph.computational_operator import ComputationalGraphOperator
 
 class OpenClOperator(ComputationalGraphOperator):
     """
@@ -42,10 +44,13 @@ class OpenClOperator(ComputationalGraphOperator):
             KernelConfig: user build options, defines, precision 
                                 and autotuner configuration
         """
+        
         super(OpenClOperator, self).__init__(**kwds)
+       
+        cl_env = cl_env or get_or_create_opencl_env()
+        check_instance(cl_env, OpenClEnvironment)
+        self.cl_env = cl_env
 
-        self.cl_env = cl_env or get_or_create_opencl_env()
-        
     def supported_backends(self):
         """
         return the backends that this operator's topologies can support.
@@ -75,16 +80,16 @@ class OpenClOperator(ComputationalGraphOperator):
         else:
             from hysop.backend.device.opencl.opencl_tools import convert_precision
             precision = convert_precision(precision)
-
+        
         self.precision = precision
         self.autotuner_config = autotuner_config
-    
-    @debug
-    def post_initialize(self):
-        super(OpenClOperator, self).post_initialize()
-        self._check_cl_env()
+        
         self._initialize_cl_build_options(kernel_config.user_build_options)
         self._initialize_cl_size_constants(kernel_config.user_size_constants)
+        
+    def check(self):
+        super(OpenClOperator, self).check()
+        self._check_cl_env()
     
     @debug
     def handle_field_requirements(self):
@@ -101,14 +106,30 @@ class OpenClOperator(ComputationalGraphOperator):
         Keys are continuous fields and values are of type
         hysop.fields.field_requirement.DiscreteFieldRequirements
 
-        Default is Backend.HOST, no min or max ghosts, Basis.CARTESIAN and no specific
+        Default is Backend.OPENCL, no min or max ghosts, Basis.CARTESIAN and no specific
         transposition state for each input and output variables.
         """
+
+        # by default we create OPENCL (gpu) TopologyDescriptors 
+        for field, topo_descriptor in self.input_vars.iteritems():
+            topo_descriptor = TopologyDescriptor.build_descriptor(
+                    backend=Backend.OPENCL,
+                    operator=self,
+                    field=field,
+                    handle=topo_descriptor,
+                    cl_env=self.cl_env)
+            self.input_vars[field] = topo_descriptor
+
+        for field, topo_descriptor in self.output_vars.iteritems():
+            topo_descriptor = TopologyDescriptor.build_descriptor(
+                    backend=Backend.OPENCL,
+                    operator=self,
+                    field=field,
+                    handle=topo_descriptor,
+                    cl_env=self.cl_env)
+            self.output_vars[field] = topo_descriptor
+
         super(OpenClOperator, self).handle_field_requirements()
-        for req in self.input_field_requirements.values():
-            req.set_backend(Backend.OPENCL)
-        for req in self.output_field_requirements.values():
-            req.set_backend(Backend.OPENCL)
 
     @debug
     def discretize(self):
@@ -136,20 +157,18 @@ class OpenClOperator(ComputationalGraphOperator):
     @debug
     def _check_cl_env(self):
         """
-        Check if all topologies are on OpenCL backend and check that all opencl environments match.
-        Sets attribute cl_env.
+        Check if all topologies are on OpenCL backend and check that all opencl environments 
+        match.
         """
         topo = (self.input_vars.values()+self.output_vars.values())[0]
         assert isinstance(topo, Topology)
         assert topo.backend.kind == Backend.OPENCL
-        ref_env = topo.cl_env
+        ref_env = self.cl_env
         
         for topo in set(self.input_vars.values()+self.output_vars.values()):
             assert isinstance(topo, Topology)
             assert topo.backend.kind == Backend.OPENCL
             assert topo.cl_env == ref_env
-
-        self.cl_env = ref_env
     
     @debug
     def _initialize_cl_build_options(self, user_options):
diff --git a/hysop/backend/device/opencl/operator/directional/advection_dir.py b/hysop/backend/device/opencl/operator/directional/advection_dir.py
index 14c13eead..c30376e23 100644
--- a/hysop/backend/device/opencl/operator/directional/advection_dir.py
+++ b/hysop/backend/device/opencl/operator/directional/advection_dir.py
@@ -1,9 +1,10 @@
 
-from hysop import Field, TopologyDescriptor
+from hysop import Field
 from hysop.tools.numpywrappers import npw
 from hysop.constants import BoundaryCondition
 from hysop.tools.decorators  import debug
 from hysop.tools.types import check_instance
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 from hysop.core.graph.graph import not_initialized, initialized, discretized, ready
 
 from hysop.backend.device.kernel_config import KernelConfig
@@ -71,7 +72,7 @@ class OpenClDirectionalAdvection(OpenClDirectionalOperator):
         check_instance(velocity, Field)
         check_instance(advected_fields, list, values=Field)
         check_instance(advected_fields_out, list, values=Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
         assert (len(advected_fields)==len(advected_fields_out))
 
         input_vars  = { velocity: variables[velocity] }
diff --git a/hysop/backend/host/fortran/operator/poisson.py b/hysop/backend/host/fortran/operator/poisson.py
index 2ba2eb7d5..a4439a9f9 100644
--- a/hysop/backend/host/fortran/operator/poisson.py
+++ b/hysop/backend/host/fortran/operator/poisson.py
@@ -3,7 +3,7 @@ from hysop.tools.types       import check_instance, InstanceOf
 from hysop.tools.decorators  import debug
 from hysop.tools.numpywrappers import npw
 from hysop.fields.continuous import Field
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 from hysop.constants         import FieldProjection
 from hysop.backend.host.fortran.operator.fortran_fftw import fftw2py, FortranFFTWOperator
 
@@ -40,7 +40,7 @@ class PoissonFFTW(FortranFFTWOperator):
         
         check_instance(velocity,  Field)
         check_instance(vorticity, Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
 
         assert velocity.domain is vorticity.domain, 'only one domain is supported'
         assert variables[velocity] is variables[vorticity], 'only one topology is supported'
diff --git a/hysop/core/graph/computational_graph.py b/hysop/core/graph/computational_graph.py
index 482972148..ad8ecf920 100644
--- a/hysop/core/graph/computational_graph.py
+++ b/hysop/core/graph/computational_graph.py
@@ -67,7 +67,7 @@ class ComputationalGraph(ComputationalGraphNode):
     def topology_report(self):
         def print_topo(topo):
             return 'topology_id={}, task_id={}, shape={}, ghosts={}'.format(
-                    topo.get_id(), topo.task_id(), topo.shape, topo.ghosts())
+                    topo.get_id(), topo.task_id(), topo.shape, topo.ghosts)
         
         ss = '== ComputationalGraph {} topology report =='.format(self.name)
         for (backend,topologies) in self.get_topologies().iteritems():
@@ -107,7 +107,6 @@ class ComputationalGraph(ComputationalGraphNode):
             return
 
         method = super(ComputationalGraph, self).initialize(topgraph_method)
-        
         self._build_topologies()
         self._build_graph(outputs_are_inputs=outputs_are_inputs)
         self._init_base(**self._kwds)
@@ -120,28 +119,60 @@ class ComputationalGraph(ComputationalGraphNode):
         input_field_requirements  = self.input_field_requirements
         output_field_requirements = self.output_field_requirements
         for node in self.nodes:
+            if __DEBUG__:
+                print
+                print '{} INITIALIZE'.format(node.name)
             node.initialize(self.method)
+            if __DEBUG__:
+                print 'NODE INPUT REQUIREMENTS: {}'
+                print '=========================='
+                print
 
             for ifield,ireqs in node.input_field_requirements.iteritems():
-                ireqs = ireqs if isinstance(ireqs, MultiFieldRequirements) \
-                              else MultiFieldRequirements(ireqs)
+                if not isinstance(ireqs, MultiFieldRequirements):
+                    _ireqs = ireqs
+                    ireqs = MultiFieldRequirements(ifield)
+                    ireqs.update(_ireqs)
                 if ifield in input_field_requirements:
+                    print 'ADD IN {}'.format(ireqs.nrequirements())
                     input_field_requirements[ifield].update(ireqs)
                 else:
+                    print 'FIRST IN'
                     input_field_requirements[ifield] = ireqs
+                print 'INPUT OPERATOR {}'.format(node.name), 
+                print input_field_requirements[ifield].nrequirements()
 
             for ofield,oreqs in node.output_field_requirements.iteritems():
-                oreqs = oreqs if isinstance(oreqs, MultiFieldRequirements) 
-                              else MultiFieldRequirements(oreqs)
+                if not isinstance(oreqs, MultiFieldRequirements):
+                    _oreqs = oreqs
+                    oreqs = MultiFieldRequirements(ofield)
+                    oreqs.update(_oreqs)
                 if ofield in output_field_requirements:
+                    print 'ADD OUT {}'.format(oreqs.nrequirements())
                     output_field_requirements[ofield].update(oreqs)
                 else:
+                    print 'FIRST OUT'
                     output_field_requirements[ofield] = oreqs
+                print 'OUTPUT OPERATOR {}'.format(node.name), 
+                print output_field_requirements[ofield].nrequirements()
 
     @debug
     def _build_topologies(self):
-        self.input_field_requirements.build_topologies()
-        self.output_field_requirements.build_topologies()
+        for field_reqs in self.input_field_requirements.values():
+            assert isinstance(field_reqs, MultiFieldRequirements)
+            print 'FIELD {}: building {} requirements.'.format(field_reqs.field.name, field_reqs.nrequirements())
+            field_reqs.build_topologies()
+        for field_reqs in self.output_field_requirements.values():
+            assert isinstance(field_reqs, MultiFieldRequirements)
+            field_reqs.build_topologies()
+        print
+        print 'AFTER BUILD'
+        for node in self.nodes:
+            if isinstance(node, ComputationalGraphNode):
+                print node.name
+                print node.input_vars.values()
+                print node.output_vars.values()
+                print
 
     @staticmethod
     def _op_info(op, jmp=False):
@@ -195,8 +226,10 @@ class ComputationalGraph(ComputationalGraphNode):
         field_write_nodes = {}
         field_read_nodes  = {}
         for field in continuous_variables:
-            field_write_nodes[field] = {} # dictionnary topology -> node that wrote this topo
-            field_read_nodes[field]  = {} # dictionnary topology -> list of nodes that reads field:topo
+            # dictionnary topology -> node that wrote this topology
+            field_write_nodes[field] = {} 
+            # dictionnary topology -> list of nodes that reads field:topo
+            field_read_nodes[field]  = {}
 
         for node_id,node in enumerate(self.nodes):
             node_ops = []
@@ -211,9 +244,9 @@ class ComputationalGraph(ComputationalGraphNode):
                 subgraph_ops    = subgraph.vertex_properties['operators']
                 node_properties = None
                 for nid in node_ordering:
-                    node = subgraph.vertex(nid)
-                    op = subgraph_ops[node]
-                    node_vertices.append(node)
+                    _node = subgraph.vertex(nid)
+                    op = subgraph_ops[_node]
+                    node_vertices.append(_node)
                     node_ops.append(op)
                 for prop_name,vp in subgraph.vertex_properties.iteritems():
                     if prop_name not in vertex_properties:
@@ -240,7 +273,7 @@ class ComputationalGraph(ComputationalGraphNode):
                 vertex_properties['operators'][opnode] = op
 
                 if from_subgraph:
-                    level = subgraph.vertex_properties['op_data'][oldvertex].current_level
+                    level = node.level + 1
                     for enp in extra_node_props:
                         vertex_properties[enp][opnode] = subgraph.vp[enp][oldvertex]
                 else:
diff --git a/hysop/core/graph/computational_node.py b/hysop/core/graph/computational_node.py
index b0ff354a9..34560069d 100644
--- a/hysop/core/graph/computational_node.py
+++ b/hysop/core/graph/computational_node.py
@@ -10,14 +10,14 @@ from hysop.tools.types           import InstanceOf, to_set, check_instance
 from hysop.fields.continuous     import Field
 from hysop.core.graph.graph      import not_implemented, wraps,\
                                         not_initialized, initialized, discretized, ready
-from hysop.core.graph.continuous import Operator
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.graph.continuous import OperatorBase
+from hysop.core.mpi.topology import Topology
 from hysop.tools.decorators import debug
 
 def base_initialized(f):
     assert callable(f)
     @wraps(f)
-    def check(*args,**kargs):
+    def _check(*args,**kargs):
         self = args[0]
         msg = 'Cannot call {}.{}() on node \'{}\' because {}'\
                 .format(self.__class__.__name__,f.__name__,self.name,'{}')
@@ -25,9 +25,9 @@ def base_initialized(f):
             reason='this self._init_base() has not been called yet.'
             raise RuntimeError(msg.format(reason))
         return f(*args,**kargs)
-    return check
+    return _check
 
-class ComputationalGraphNode(Operator):
+class ComputationalGraphNode(OperatorBase):
     """
     Interface of an abstract computational graph node.
     """
@@ -168,24 +168,12 @@ class ComputationalGraphNode(Operator):
         """
         Initialize base class and check everything.
         """
-        variables = {}
-        for io_vars in (self.input_vars, self.output_vars):
-            for field,topo in io_vars.iteritems():
-                if (field in variables):
-                    if (not topo is variables[field]):
-                        variables[field].add(topo)
-                else:
-                    print topo.__class__
-                    variables[field] = set([topo])
-
-        super(ComputationalGraphNode, self).__init__(
-            variables = variables,
-            **kwds)
+        ifields = set(self.input_vars.keys())
+        ofields = set(self.output_vars.keys())
+        fields = list(ifields.union(ofields))
+        super(ComputationalGraphNode, self).__init__(fields=fields, **kwds)
         self._base_initialized = True
-        self._check_variables()
-        self._check_topologies()
-        self._check_support()
-    
+
     @debug
     def _check_method(self, user_method):
         """
@@ -218,27 +206,35 @@ class ComputationalGraphNode(Operator):
                 msg+='\n possible values are {}.'.format(available_methods[k])
                 raise ValueError(msg)
         return method
-
+    
+    @debug
+    @base_initialized
+    def check(self):
+        """
+        Check if node was correctly initialized.
+        By default this checks variables, topologies and support.
+        """
+        self._check_variables()
+        self._check_topologies()
+        self._check_support()
+    
     @debug
     @base_initialized
     def _check_variables(self):
         """
         Check input and output variables.
-        Called automatically in ComputationalGraphNode.post_initialize()
-        """
-        variables = self.variables
-        for (k,v) in variables.iteritems():
-            if not isinstance(k,Field):
-                msg = 'Given key is not a continuous Field (got a {}).' 
-                raise ValueError(msg.format(k.__class__))
-            if not isinstance(v, set):
-                msg = 'Given value is not a set of topologies (got a {}).' 
-                raise ValueError(msg.format(v.__class__))
-            else:
-                for t in v:
-                    if not isinstance(t,TopologyDescriptor):
-                        msg = 'Given value is not a topology (got a {}).' 
-                        raise ValueError(msg.format(t.__class__))
+        Called automatically in ComputationalGraphNode.check()
+        """
+        for variables in [self.input_vars, self.output_vars]:
+            for (k,v) in variables.iteritems():
+                if not isinstance(k,Field):
+                    msg = 'Given key is not a continuous Field (got a {}).' 
+                    raise TypeError(msg.format(k.__class__))
+                if not isinstance(v, Topology):
+                    msg='Expected a Topology instance but got a {}.'.format(topo.__class__)
+                    msg+='\nAll topologies are expected to be set after '
+                    msg+='ComputationalGraph.handle_field_requirements() has been called.'
+                    raise TypeError(msg)
 
     @debug
     @base_initialized
@@ -250,23 +246,25 @@ class ComputationalGraphNode(Operator):
             _has_multiple_field_topologies
         Sets the following attributes:
             _multi_topo_fields (list of field that have at least two different topologies)
-        Called automatically in ComputationalGraphNode.post_initialize()
+        Called automatically in ComputationalGraphNode.check()
         """
         is_distributed                = self.mpi_params.size > 1
         has_multiple_topologies       = False
         has_multiple_field_topologies = False
         multi_topo_fields = set()
         
-        assert self.variables, 'no variables in {}'.format(self.__class__)
-        topo_ref = list(self.variables.values()[0])[0]
-        for field,topologies in self.variables.iteritems():
-            if len(topologies)>1:
-                multi_topo_fields.add(field)
-                has_multiple_field_topologies = True
-            for topo in topologies: 
+        topo_ref = (self.input_vars.values()+self.output_vars.values())[0]
+        for variables in [self.input_vars, self.output_vars]:
+            for field, topo in variables.iteritems():
                 if topo != topo_ref:
                     has_multiple_topologies = True
 
+        for ifield in self.input_vars:
+            if ifield in self.output_vars and \
+                    self.input_vars[ifield] != self.output_vars[ifield]:
+                multi_topo_fields.add(ifield)
+                has_multiple_field_topologies = True
+
         self._is_distributed                = is_distributed
         self._has_multiple_topologies       = has_multiple_topologies
         self._has_multiple_field_topologies = has_multiple_field_topologies
@@ -280,7 +278,7 @@ class ComputationalGraphNode(Operator):
         See ComputationalGraphNode.supports_multiple_topologies()
             ComputationalGraphNode.supports_multiple_field_topologies()
             ComputationalGraphNode.supports_mpi()
-        Called automatically in ComputationalGraphNode.post_initialize()
+        Called automatically in ComputationalGraphNode.check()
         """
         cls = self.__class__
         if (self._has_multiple_field_topologies) and \
diff --git a/hysop/core/graph/computational_operator.py b/hysop/core/graph/computational_operator.py
index 5eb4f6019..543fbe10f 100644
--- a/hysop/core/graph/computational_operator.py
+++ b/hysop/core/graph/computational_operator.py
@@ -4,6 +4,7 @@ from hysop.tools.decorators  import debug
 from hysop.core.graph.computational_node import ComputationalGraphNode
 from hysop.core.graph.graph import initialized, discretized
 from hysop.core.memory.memory_request import OperatorMemoryRequests
+from hysop.core.mpi.topology_descriptor import TopologyDescriptor
 from hysop.fields.field_requirements import DiscreteFieldRequirements
 from abc import ABCMeta
 
@@ -82,6 +83,7 @@ class ComputationalGraphOperator(ComputationalGraphNode):
             handle_field_requirements() -> self.input_field_requirements it set
             self._initialized is set
             post_intialize() 
+        check() (requires self.initialized to be set)
         discretize() (requires self.initialized to be set)
             self.discretized is set
         get_work_properties() (requires self.discretized to be set)
@@ -133,7 +135,7 @@ class ComputationalGraphOperator(ComputationalGraphNode):
         self.output_discrete_fields = {}
         self.input_field_requirements  = {}
         self.output_field_requirements = {}
-   
+
     @debug
     def handle_field_requirements(self):
         """
@@ -142,6 +144,7 @@ class ComputationalGraphOperator(ComputationalGraphNode):
             1) min and max ghosts for each input and output variables
             2) allowed splitting directions for cartesian topologies
             3) required local and global transposition state, if any. 
+            4) required space (fourier, physical, ...)
             and more
         They are stored in self.input_field_requirements and
         self.output_field_requirements.
@@ -152,14 +155,38 @@ class ComputationalGraphOperator(ComputationalGraphNode):
         Default is Backend.HOST, no min or max ghosts, Basis.CARTESIAN and no specific
         transposition state for each input and output variables.
         """
-        for field,topology_descriptor in self.input_vars.iteritems():
-            req = DiscreteFieldRequirements(self, self.input_vars, field,
-                    backend=Backend.HOST)
+        
+        # by default we create HOST (cpu) TopologyDescriptors 
+        for field,topo_descriptor in self.input_vars.iteritems():
+            topo_descriptor = TopologyDescriptor.build_descriptor(
+                    backend=Backend.HOST,
+                    operator=self,
+                    field=field,
+                    handle=topo_descriptor)
+            self.input_vars[field] = topo_descriptor
+
+        for field,topo_descriptor in self.output_vars.iteritems():
+            topo_descriptor = TopologyDescriptor.build_descriptor(
+                    backend=Backend.HOST,
+                    operator=self,
+                    field=field,
+                    handle=topo_descriptor)
+            self.output_vars[field] = topo_descriptor
+        
+        # and we use default DiscreteFieldRequirements (ie. no min ghosts, no max ghosts,
+        # can_split set to True in all directions, Basis.CARTESIAN, TranspositionState.XYZ).
+        for field, topo_descriptor in self.input_vars.iteritems():
+            req = DiscreteFieldRequirements(self, self.input_vars, field)
             self.input_field_requirements[field] = req
-        for field,topology_descriptor in self.output_vars.iteritems():
-            req = DiscreteFieldRequirements(self, self.output_vars, field,
-                    backend=Backend.HOST)
+
+        for field, topo_descriptor in self.output_vars.iteritems():
+            req = DiscreteFieldRequirements(self, self.output_vars, field)
             self.output_field_requirements[field] = req
+
+        print '{} HANDLE FIELD REQUIREMENT'.format(self.name)
+        print len(self.input_field_requirements.values())
+        print len(self.output_field_requirements.values())
+        print
     
     @debug
     @initialized
@@ -192,28 +219,39 @@ class ComputationalGraphOperator(ComputationalGraphNode):
         no extra buffers.
         """
         return OperatorMemoryRequests(self)
-
+    
     def supported_backends(self):
         """
         Return the backends that this operator's topologies can support as a set.
         By default all operators support only Backend.HOST.
         """
         return set([Backend.HOST])
+    
+    @debug
+    @initialized
+    def check(self):
+        """
+        Check if node was correctly initialized.
+        This calls ComputationalGraphNode.check() and then checks backend
+        by comparing all variables topology backend to set.supported_backends().
+        """
+        super(ComputationalGraphOperator, self).check()
+        self._check_backend()
+
+    def _check_backend(self):
+        """
+        Checks backend support and topologies.
+        """
+        topologies_per_backend = self.get_topologies()
+        supported_backends = self.supported_backends()
+        for (backend, topologies) in topologies_per_backend.iteritems():
+            if backend.kind not in supported_backends:
+                msg='\n\nOperator {} topology backend mismatch:'.format(self.name)
+                msg+='\n -> got topologies defined on backend {}.'.format(backend)
+                msg+='\n     *bad topology ids were {}'.format([t.get_id() for t in topologies])
+                msg+='\n -> this operator only supports the following backends:'
+                msg+='\n     *'+'\n     *'.join([str(b) for b in supported_backends])
+                for t in topologies:
+                    print '\n',t
+                raise RuntimeError(msg)
 
-    # def _check_support(self):
-        # """
-        # Calls ComputationalGraphNode._check_support() and checks backend support.
-        # """
-        # super(ComputationalGraphOperator,self)._check_support()
-        # topologies_per_backend = self.get_topologies()
-        # supported_backends = self.supported_backends()
-        # for (backend, topologies) in topologies_per_backend.iteritems():
-            # if backend.kind not in supported_backends:
-                # msg='\n\nOperator {} topology backend mismatch:'.format(self.name)
-                # msg+='\n -> got topologies defined on backend {}.'.format(backend)
-                # msg+='\n     *bad topology ids were {}'.format([t.get_id() for t in topologies])
-                # msg+='\n -> this operator only supports the following backends:'
-                # msg+='\n     *'+'\n     *'.join([str(b) for b in supported_backends])
-                # for t in topologies:
-                    # print '\n',t
-                # raise RuntimeError(msg)
diff --git a/hysop/core/graph/continuous.py b/hysop/core/graph/continuous.py
index 562b6553f..4422f5d72 100755
--- a/hysop/core/graph/continuous.py
+++ b/hysop/core/graph/continuous.py
@@ -8,60 +8,42 @@ from hysop.tools.parameters import MPIParams
 from hysop.tools.decorators import debug
 from hysop.tools.types import check_instance
 from hysop.fields.continuous import Field
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
 
 import hysop.tools.io_utils as io
 
-class Operator(object):
+class OperatorBase(object):
     """Abstract interface to continuous operators.
     """
     __metaclass__ = ABCMeta
 
     @debug
-    def __new__(cls, *args, **kw):
-        return object.__new__(cls, *args, **kw)
-
-    @debug
-    def __init__(self, variables=None, 
+    def __init__(self, fields,
                        mpi_params=None, 
                        io_params=None, 
                        **kwds):
         """
         Parameters
         ----------
-        variables : dictionnary. See Notes for details.
+        field: list of Field
+            fields on which this operator operates
         mpi_params : :class:`hysop.tools.parameters.MPIParams`
             mpi config for the operator (comm, task ...)
         io_params : :class:`hysop.tools.io_utils.IOParams`
             file i/o config (filename, format ...)
 
-        Notes
-        -----
-        Variables is a dictionnary.
-        The elements of the list or the keys of the dict
-        are :class:`hysop.fields.continuous.Fields`.
-
-        The values of the dict can be either
-        :class:`hysop.core.mpi.topology.Topology`
-        or a set of topologies.
-
         Attributes
         ----------
-        variables: dict
-            :class:`~hysop.fields.continuous.Continuous` with their topologies as a set.
-            One field can expose multiple topologies as a set.
+        fields: list of Field
         io_params: IOParams
         mpi_params: MPIParams
         """
-        super(Operator,self).__init__(**kwds)
-
+        super(OperatorBase,self).__init__(**kwds)
+        
+        check_instance(fields, list, values=Field)
         check_instance(io_params,  IOParams,  allow_none=True)
         check_instance(mpi_params, MPIParams, allow_none=True)
-        check_instance(variables, dict, keys=Field)
-        for var in variables.values():
-            check_instance(var, set, values=TopologyDescriptor)
-
-        self.variables  = variables
+        
+        self.fields     = fields
         self.io_params  = io_params
         self.mpi_params = mpi_params
 
@@ -101,7 +83,7 @@ class Operator(object):
 
     def _set_domain_and_tasks(self):
         """
-        Initialize the mpi context, depending on local variables, domain
+        Initialize the mpi context, depending on local fields, domain
         and so on.
         
         Notes
@@ -111,13 +93,13 @@ class Operator(object):
         during construction (__init__).
         """
         # When this function is called, the operator must at least
-        # have one variable.
-        assert len(self.variables) > 0
-        self.domain = self.variables.keys()[0].domain
+        # have one field.
+        assert len(self.fields) > 0
+        self.domain = self.fields[0].domain
 
-        # Check if all variables have the same domain
-        for v in self.variables:
-            assert v.domain is self.domain, 'All variables of the operator\
+        # Check if all fields have the same domain
+        for field in self.fields:
+            assert field.domain is self.domain, 'All fields of the operator\
             must be defined on the same domain.'
         
         # Set/check mpi context
diff --git a/hysop/core/mpi/topology.py b/hysop/core/mpi/topology.py
index a3524aba7..cd2ac5d1a 100644
--- a/hysop/core/mpi/topology.py
+++ b/hysop/core/mpi/topology.py
@@ -25,6 +25,12 @@ class Topology(object):
     
     # Counter of topology.Topology instances to set a unique id for each topology.
     __ids = count(0)
+    
+    @debug
+    def __new__(cls, mpi_params, domain, backend, **kwds):
+        obj = object.__new__(cls, **kwds)
+        obj.__id = cls.__ids.next()
+        return obj
 
     def __init__(self, mpi_params, domain, backend=Backend.HOST , **kargs):
         super(Topology,self).__init__(**kargs)
@@ -52,13 +58,13 @@ class Topology(object):
         elif backend == Backend.OPENCL:
             from hysop.backend.device.opencl.opencl_tools import get_or_create_opencl_env
             from hysop.core.arrays.all import OpenClArrayBackend
-            cl_env    = kargs.pop('cl_env',None) \
-                        or get_or_create_opencl_env(mpi_params.comm)
+            cl_env = kargs.pop('cl_env',None) \
+                     or get_or_create_opencl_env(mpi_params.comm)
             self.cl_env = cl_env
             assert cl_env.comm == mpi_params.comm
 
-            queue     = kargs.pop('queue',None)
-            allocator = kargs.pop('allocator',None)
+            queue     = kargs.pop('queue', None)
+            allocator = kargs.pop('allocator', None)
             backend   = OpenClArrayBackend(cl_env=cl_env,queue=queue,allocator=allocator)
         else:
             msg = 'Unsupported backend {}.'.format(backend)
@@ -107,8 +113,11 @@ class Cartesian(Topology):
     """
 
     @debug
-    def __new__(cls, *args, **kw):
-        return object.__new__(cls, *args, **kw)
+    def __new__(cls, domain, discretization, dim=None, mpi_params=None,
+                 isperiodic=None, cutdir=None, shape=None, mesh=None,
+                 cartesian_topology=None, **kargs):
+        obj = super(Cartesian,cls).__new__(cls, *args, **kw)
+        return obj
 
     @debug
     def __init__(self, domain, discretization, dim=None, mpi_params=None,
@@ -175,7 +184,8 @@ class Cartesian(Topology):
         self.cutdir = None
         # mpi communicator (cartesian topo)
         self.comm = None
-
+        # discretization used to build this topology
+        self.discretization = discretization
         if cartesian_topology is None:
             self._build_mpi_topo(shape, cutdir, dim, isperiodic)
         else:
@@ -225,7 +235,7 @@ class Cartesian(Topology):
 
         # ===== 5 - Final setup ====
 
-        # The topology is register into domain list.
+        # The topology is registered into domain list.
         # If it already exists (in the sense of the comparison operator
         # of the present class) then isNew is turned to false
         # and the present instance will be destroyed.
@@ -406,10 +416,11 @@ class Cartesian(Topology):
         """
         return self.mpi_params.comm
 
-    def ghosts(self):
+    def get_ghosts(self):
         """returns ghost layer width.
         """
         return self.mesh.discretization.ghosts
+    ghosts = property(get_ghosts)
 
     def task_id(self):
         """returns id of the task that owns this topology
diff --git a/hysop/core/mpi/topology_descriptor.py b/hysop/core/mpi/topology_descriptor.py
index acc624d11..5edf49f55 100644
--- a/hysop/core/mpi/topology_descriptor.py
+++ b/hysop/core/mpi/topology_descriptor.py
@@ -1,15 +1,147 @@
 
+from abc import ABCMeta, abstractmethod
+from hysop.deps import np
+from hysop.tools.types import check_instance
+from hysop.tools.numpywrappers import npw
+from hysop.constants import Backend, BoundaryCondition
 from hysop.tools.parameters import Discretization
-from hysop.core.mpi.topology import Topology
-
-TopologyDescriptor = (Topology, Discretization,)
-        
-def get_discretization(self, topology_descriptor):    
-    if isinstance(topology_descriptor, Topology):
-        return self.topology_descriptor.mesh.discretization
-    elif isinstance(topology_descriptor, Discretization):
-        return self.topology_descriptor
-    else:
-        msg='{} Could not extract discretization from topology descriptor of class {}.'
-        msg=msg.format(self.header, self.topology_descriptor.__class__)
-        raise TypeError(msg)
+from hysop.core.mpi.topology import Topology, Cartesian
+from hysop.fields.continuous import Field
+
+class TopologyDescriptor(object):
+    """
+    Describes how a topology should be built.
+    kargs allows for backend specific variables.
+    """
+    __metaclass__ = ABCMeta
+
+    def __init__(self, mpi_params, domain, backend, **kargs):
+        super(TopologyDescriptor, self).__init__()
+        self.mpi_params = mpi_params
+        self.domain = domain
+        self.backend=backend
+        self.extra_kargs = kargs
+    
+    @staticmethod
+    def build_descriptor(backend, operator, field, handle, **kargs):
+        if isinstance(handle, Topology):
+            # handle is already a Topology, so we return it.
+            return handle
+        elif isinstance(handle, TopologyDescriptor):
+            # handle is already a TopologyDescriptor, so we return it.
+            return handle
+        elif isinstance(handle, CartesianDescriptors):
+            return CartesianDescriptor.build_descriptor(backend, operator, 
+                    field, handle, **kargs)
+        else:
+            msg='Unknown handle of class {} to build a TopologyDescriptor.'
+            msg=msg.format(handle.__class__)
+            raise TypeError(msg)
+    
+    def choose_or_create_topology(self, known_topologies, **kargs):
+        """
+        Returns a topology that is either taken from known_topologies, a set
+        of user specified topologies which are ensured to be compatible
+        with the current TopologyDescriptor, or created from the descriptor
+        if choose_topology() returns None.
+        """
+        check_instance(known_topologies, list, values=Topology)
+        topo = self.choose_topology(known_topologies, **kargs) or \
+               self.create_topology(**kargs)
+        return topo
+    
+    @abstractmethod
+    def choose_topology(self, known_topologies, **kargs):
+        pass
+
+    @abstractmethod
+    def create_topology(self, **kargs):
+        pass
+
+    def dim(self):
+        return self.domain.dimension
+
+    def is_periodic(self):
+        is_lperiodic = [lboundary==BoundaryCondition.PERIODIC 
+                for lboundary in self.domain.boundaries[0]]
+        is_rperiodic = [rboundary==BoundaryCondition.PERIODIC 
+                for rboundary in self.domain.boundaries[1]]
+        assert len(is_lperiodic)==self.dim()
+        assert len(is_rperiodic)==self.dim()
+        assert is_lperiodic == is_rperiodic
+        return npw.asintegerarray(is_lperiodic)
+
+
+class CartesianDescriptor(TopologyDescriptor):
+    """
+    Describes how a Cartesian topology should be built.
+    kargs allows for backend specific variables.
+    """
+    def __init__(self, mpi_params, domain, backend, discretization, **kargs):
+        super(CartesianDescriptor, self).__init__(mpi_params=mpi_params, 
+                domain=domain, backend=backend, **kargs)
+        # boundaries are stored in domain
+        self.discretization = discretization
+
+    @staticmethod
+    def build_descriptor(backend, operator, field, handle, **kargs):
+        from hysop.core.graph.computational_operator import ComputationalGraphOperator
+        check_instance(backend, Backend)
+        check_instance(operator, ComputationalGraphOperator)
+        check_instance(field, Field)
+        check_instance(handle, CartesianDescriptors)
+        msg='A Cartesian topology descriptor should not contain any ghosts, '
+        msg+='they will be determined during the handle_field_requirements() in the '
+        msg+=' operator initialization step to minimize the number of topologies created.'
+        msg+='\nIf you want to impose a specific topology, you can directly pass a '
+        msg+='Cartesian instance into operator\'s input or output variables dictionnary instead.'
+        if isinstance(handle, Discretization):
+            if handle.ghosts.sum() > 0:
+                raise ValueError(msg)
+            msg='mpi_params has not been set in operator {}.'.format(operator.name)
+            if not hasattr(operator, 'mpi_params'):
+                raise RuntimeError(msg)
+            return CartesianDescriptor(
+                    backend=backend, 
+                    domain=field.domain, 
+                    mpi_params=operator.mpi_params,
+                    discretization=handle,
+                    **kargs)
+        elif isinstance(handle, CartesianDescriptor):
+            if handle.discretization.ghosts.sum() > 0:
+                raise ValueError(msg)
+            return handle
+        else:
+            # handle is a Cartesian instance, ghosts can be imposed by user here
+            return handle
+    
+    def choose_topology(self, known_topologies, **kargs):
+        """
+        Find optimal topology parameters from known_topologies.
+        If None is returned, create_topology will be called instead.
+        """
+        pass
+
+    def create_topology(self, cutdir, ghosts):
+        """
+        Build a topology with the current TopologyDescriptor.
+        Free parameters are cutdir and ghosts which are imposed
+        by operators on variables and solved during operator's
+        method handle_field_requirements().
+        """
+        is_periodic = self.is_periodic()
+        if self.mpi_params.size==1:
+            #local periodic computation, we remove ghosts.
+            ghosts *= (~is_periodic)
+        discretization = Discretization(self.discretization.resolution, ghosts)
+        return Cartesian(domain=self.domain, discretization=discretization,
+                mpi_params=self.mpi_params, isperiodic=is_periodic, 
+                cutdir=cutdir)
+
+
+CartesianDescriptors = (Cartesian, CartesianDescriptor, Discretization)
+"""
+Instance of those types can be used to create a CartesianDescriptor.
+Thus they can be passed in the variables of each operator supporting
+Cartesian topologies.
+"""
diff --git a/hysop/fields/field_requirements.py b/hysop/fields/field_requirements.py
index e443ab5a1..24f319279 100644
--- a/hysop/fields/field_requirements.py
+++ b/hysop/fields/field_requirements.py
@@ -1,15 +1,23 @@
 
 from hysop.deps import np, it
+from hysop.constants import Basis, transposition_states
 from hysop.tools.types import to_list, check_instance
-from hysop.constants import Backend, Basis, transposition_states
+from hysop.tools.numpywrappers import npw
 from hysop.fields.continuous import Field
 from hysop.fields.discrete import DiscreteField
+from hysop.core.mpi.topology import Topology
 from hysop.core.mpi.topology_descriptor import TopologyDescriptor
 from hysop.core.graph.computational_node import ComputationalGraphNode
+    
+def can_convert_basis_inplace(lbasis, rbasis):
+    return False
+
+def can_transpose_inplace(ltranspose, rtranspose):
+    return False
+
 
 class DiscreteFieldRequirements(object):
-    def __init__(self, operator, variables, 
-            field, backend,
+    def __init__(self, operator, variables, field, 
             min_ghosts=None, 
             max_ghosts=None,
             can_split=None, 
@@ -21,27 +29,20 @@ class DiscreteFieldRequirements(object):
         
         check_instance(operator, ComputationalGraphNode)
         check_instance(field, Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=(Topology,TopologyDescriptor))
         
         self._operator  = operator
         self._field = field
         self._variables = variables
         self._topology_descriptor = variables[field]
-        self._header = '::{}[{}] requirements::\n'.format(operator.name, field.name)
+        self.header = '::{}[{}] requirements::\n'.format(operator.name, field.name)
         
-        self.backend = backend
         self.min_ghosts = min_ghosts
         self.max_ghosts = max_ghosts
         self.can_split = can_split
         self.required_basis = required_basis
         self.required_transposition_state = required_transposition_state
         
-    def get_backend(self):
-        return self._backend
-    def set_backend(self, backend):
-        check_instance(backend, Backend)
-        self._backend = backend
-
     def get_required_transposition_state(self):
         return self._required_transposition_state
     def set_required_transposition_state(self, tstate):
@@ -76,78 +77,73 @@ class DiscreteFieldRequirements(object):
         assert self.can_split.size  == self.workdim
 
     def get_work_dim(self):
-        return self._field.dimension
+        return self._topology_descriptor.domain.dimension
+    def get_operator(self):
+        return self._operator
+    def get_field(self):
+        return self._field
+    def get_variables(self):
+        return self._variables
+    def get_topology_descriptor(self):
+        return self._topology_descriptor
 
-    backend = property(get_backend, set_backend)
     can_split  = property(get_can_split, set_can_split)
     min_ghosts = property(get_min_ghosts, set_min_ghosts)
     max_ghosts = property(get_max_ghosts, set_max_ghosts)
     required_basis = property(get_required_basis, set_required_basis)
     required_transposition_state = property(get_required_transposition_state, 
                                             set_required_transposition_state)
-    workdim = property(get_work_dim)
+    workdim   = property(get_work_dim)
+    operator  = property(get_operator)
+    field     = property(get_field)
+    variables = property(get_variables)
+    topology_descriptor = property(get_topology_descriptor)
     
-    def is_compatible_with(self, other, check_backend=True):
+    def is_compatible_with(self, other):
         assert self.field == other.field, 'field mismatch.'
-        assert self.workdim == other.workdim, 'workdim mismatch.'
-        assert self.discretization() == other.discretization(), 'discretization mismatch.'
-        if isinstance(other, FieldRequirements):
+        if isinstance(other, DiscreteFieldRequirements):
             others=[other]
         elif isinstance(other, MultiFieldRequirements):
-            if self.discretization() in other.requirements.keys():
-                others=other.requirements[self.discretization()]
+            if self.topology_descriptor in other.requirements.keys():
+                others=other.requirements[self.topology_descriptor]
             else:
                 return True
         else:
             msg='Unknown type {}.'.format(other.__class__)
 
         for other in others:
-            if check_backend and (other.backend != self.backend):
+            assert self.workdim == other.workdim, 'workdim mismatch.'
+            assert self.topology_descriptor == other.topology_descriptor, \
+                    'topology_descriptor mismatch.'
+            if (other.max_ghosts < self.min_ghosts).any():
                 return False
-            if other.max_ghosts < self.min_ghosts:
+            if (other.min_ghosts > self.max_ghosts).any():
                 return False
-            if other.min_ghosts > self.max_ghosts:
+            if not (other.can_split * self.can_split).any():
                 return False
             if (other.required_basis != self.required_basis) and \
-                    not self.can_convert_basis_inplace(
+                    not can_convert_basis_inplace(
                             other.required_basis, self.required_basis):
                 return False
             if (other.required_transposition_state != self.required_transposition_state) and \
-                    not self.can_transpose_inplace(
+                    not can_transpose_inplace(
                             other.required_transposition_state, 
                             self.required_transposition_state):
                 return False
         return True
     
-    def discretization(self):
-        return get_discretization(self.topology_descriptor)
-    def can_convert_basis_inplace(self):
-        return False
-    def can_transpose_inplace(self):
-        return False
-
-    def choose_or_create_topology(self, known_topologies, ghosts):
-        assert not isinstance(self.variables[self.field], Topology)
-        return Cartesian(domain, self.discretization, 
-
-        
     def check_topology(self, topology=None):
         topology = topology or self.variables[self.field]
         check_instance(topology, Topology)
-        sid = self.requirement_identifier
-        if topology.dimension != self.field.dimension: 
+        if topology.domain.dimension != self.field.dimension: 
             msg='{} Dimension mismatch between field and topology.\n field={}d, topology={}d.'
             msg=msg.format(self.header, self.field.dimension, topology.dimension)
             raise RuntimeError(msg)
-        if topology.mesh.discretization != self.discretization():
+        if topology.mesh.discretization != self.topology_descriptor.discretization:
             msg='{} Discretisation mismatch between requirement and topology.\n '
             msg+='requirement={}, topology={}.'
-            msg=msg.format(self.header, self.discretization(), topology.mesh.discretization)
-            raise RuntimeError(msg)
-        if (topology.backend != self.Backend):
-            msg='{} Backend mismatch between requirement and topology.\n '
-            msg+='requirement={}, topology={}.'
-            msg=msg.format(self.header, self.backend, topology.backend)
+            msg=msg.format(self.header, self.topology_descriptor, 
+                    topology.mesh.topology_descriptor)
             raise RuntimeError(msg)
         if (topology.ghosts < self.min_ghosts).any():
             msg='{} min ghosts constraint was not met.\n min={}, actual={}.'
@@ -158,6 +154,17 @@ class DiscreteFieldRequirements(object):
             msg=msg.format(self.header, self.max_ghosts, topology.ghosts)
             raise RuntimeError(msg)
 
+    def set_and_check_topology(self, topology):
+        """
+        Check topology and replace a TopologyDescriptor by a Topology instance in
+        self.variables[self.field].
+        """
+        assert isinstance(topology, Topology)
+        assert not isinstance(self.variables[self.field], Topology) \
+                or (self.variables[self.field] == topology)
+        self.check_topology(topology)
+        self.variables[self.field] = topology
+
     def check_state(self, dfield):
         check_instance(dfield, DiscreteField)
         self.check_topology(dfield.topology)
@@ -176,47 +183,62 @@ class MultiFieldRequirements(object):
     def __init__(self, field):
         self.field = field
         self.requirements = {}
-    
-    def update(self, *reqs):
-        for lreq in reqs:
-            if not isinstance(lreq, MultiFieldRequirements):
-                lreq = [lreq]
-            for req in lreq:
-                check_instance(req, FieldRequirements)
-                assert req.field == self.field
-                self.requirements.setdefault(req.discretization(), []).append(req)
+        self.built = False
+
+    def nrequirements(self):
+        return sum(len(lreqs) for lreqs in self.requirements.values())
+
+    def update(self, *update_reqs):
+        for reqs in update_reqs:
+            if isinstance(reqs, MultiFieldRequirements):
+                reqs = reqs.requirements.values()
+            else:
+                reqs = [[reqs]]
+            for lreq in reqs:
+                for req in lreq:
+                    check_instance(req, DiscreteFieldRequirements)
+                    assert req.field == self.field
+                    td_reqs = self.requirements.setdefault(req.topology_descriptor, [])
+                    td_reqs.append(req)
     
     def build_topologies(self):
-        for compatible_reqs in self.split():
+        if self.built:
+            return
+        for compatible_reqs in self._split():
             compatible_reqs._build_compatible_topologies()
+        self.built = True
     
     def all_compatible(self):
-        for discretization in self.requirements.keys():
-            requirements = self.requirements[discretization]
+        for topology_descriptor in self.requirements.keys():
+            requirements = self.requirements[topology_descriptor]
             assert len(requirements)>0
-            for req0, req1 in it.combinations(requirements, repeat=2):
+            for req0, req1 in it.combinations(requirements, 2):
                 if not req0.is_compatible_with(req1):
                     return False
             return True
 
-    def _split(self, **kargs):
+    def _split(self):
         sub_field_requirements = []
-        for lreq in self.reqs.values():
+        for lreq in self.requirements.values():
             for req in lreq:
-                for multi_reqs in sub_field_requirement:
-                    if req.is_compatible_with(multi_reqs, 
-                            no_backend_check=True):    
+                ok = False
+                for multi_reqs in sub_field_requirements:
+                    if req.is_compatible_with(multi_reqs):
                         multi_reqs.update(req)
-                else:
+                        ok=True
+                        break
+                if not ok:
                     new_group = MultiFieldRequirements(self.field)
                     new_group.update(req)
                     sub_field_requirements.append(new_group)
+        assert self.nrequirements() == sum(sf.nrequirements() for sf in sub_field_requirements)
         return sub_field_requirements
 
     def _build_compatible_topologies(self):
         assert self.all_compatible()
-        for discretization, reqs in self.requirements.iteritems():
-            ghosts = np.asarray([0,0,0])
+        for topology_descriptor, reqs in self.requirements.iteritems():
+            ghosts    = npw.integer_zeros(shape=(topology_descriptor.dim(),))
+            can_split = npw.integer_ones(shape=(topology_descriptor.dim(),))
             known_topologies = []
             unknown_topologies = []
 
@@ -226,8 +248,14 @@ class MultiFieldRequirements(object):
                     known_topologies.append(topology)
                 else:
                     ghosts = np.maximum(ghosts, req.min_ghosts)
+                    can_split *= req.can_split
                     unknown_topologies.append(req)
 
+            assert can_split.any()
+
             for req in unknown_topologies:
-                req.choose_or_create_topology(known_topologies, ghosts)
-                req.check_topology()
+                topo = req.topology_descriptor.choose_or_create_topology(known_topologies, 
+                        ghosts=ghosts, cutdir=can_split)
+                known_topologies.append(topo)
+                req.set_and_check_topology(topo)
+        
diff --git a/hysop/numerics/splitting/strang.py b/hysop/numerics/splitting/strang.py
index e17c3945f..c4e442002 100644
--- a/hysop/numerics/splitting/strang.py
+++ b/hysop/numerics/splitting/strang.py
@@ -40,8 +40,8 @@ class StrangSplitting(DirectionalSplitting):
         splitting_dim = self.splitting_dim
         for op in self.directional_operators:
             op.generate(splitting_dim=splitting_dim)
-        for dir in self.directions:
+        for dir_ in self.directions:
             for op in self.directional_operators:
-                operators = op.get_direction(dir)
+                operators = op.get_direction(dir_)
                 self.push_nodes(operators)
 
diff --git a/hysop/old/operator.old/computational.py b/hysop/old/operator.old/computational.py
index a76ca7537..3c798ffa5 100755
--- a/hysop/old/operator.old/computational.py
+++ b/hysop/old/operator.old/computational.py
@@ -3,14 +3,14 @@
 """
 from abc import ABCMeta, abstractmethod
 from hysop.constants import debug
-from hysop.operator.continuous import Operator, opapply
+from hysop.operator.continuous import OperatorBase, opapply
 from hysop.core.mpi.topology import Cartesian
 from hysop.tools.parameters import Discretization
 from hysop.tools.numpywrappers import npw
 from hysop import __FFTW_ENABLED__
 
 
-class Computational(Operator):
+class Computational(OperatorBase):
     """
     Abstract base class for computational operators.
 
diff --git a/hysop/old/operator.old/continuous.py b/hysop/old/operator.old/continuous.py
index 0a9205a5d..e6a3b3701 100755
--- a/hysop/old/operator.old/continuous.py
+++ b/hysop/old/operator.old/continuous.py
@@ -59,7 +59,7 @@ class Operator(object):
         the geometry on which this operator applies
 
         """
-        super(Operator,self).__init__(**kwds)
+        super(OperatorBase,self).__init__(**kwds)
 
         # 1 ---- Variables setup ----
         # List of hysop.continuous.Fields involved in the operator.
diff --git a/hysop/old/operator.old/curlAndDiffusion.py b/hysop/old/operator.old/curlAndDiffusion.py
index fe7172abf..6b9ffc207 100644
--- a/hysop/old/operator.old/curlAndDiffusion.py
+++ b/hysop/old/operator.old/curlAndDiffusion.py
@@ -5,7 +5,7 @@
 Operator for diffusion problem.
 
 """
-from hysop.operator.continuous import Operator
+from hysop.operator.continuous import OperatorBase
 try:
     from hysop.f2hysop import fftw2py
 except ImportError:
@@ -15,7 +15,7 @@ from hysop.constants import debug
 from hysop.operator.continuous import opsetup
 
 
-class CurlDiffusion(Operator):
+class CurlDiffusion(OperatorBase):
     """
     Diffusion operator
     \f{eqnarray*}
diff --git a/hysop/old/operator.old/redistribute.py b/hysop/old/operator.old/redistribute.py
index c81a2e859..2e5de956f 100644
--- a/hysop/old/operator.old/redistribute.py
+++ b/hysop/old/operator.old/redistribute.py
@@ -13,7 +13,7 @@
 
 """
 
-from hysop.operator.continuous import Operator
+from hysop.operator.continuous import OperatorBase
 from abc import ABCMeta, abstractmethod
 from hysop.core.mpi.topology import Cartesian
 from hysop.operator.computational import Computational
@@ -22,7 +22,7 @@ from hysop.core.mpi.bridge import Bridge, BridgeOverlap, BridgeInter
 from hysop.constants import DirectionLabels, debug
 
 
-class Redistribute(Operator):
+class Redistribute(OperatorBase):
     """Abstract interface to redistribute operators
     """
 
diff --git a/hysop/operator/diffusion.py b/hysop/operator/diffusion.py
index e38ea6567..b918b81fc 100644
--- a/hysop/operator/diffusion.py
+++ b/hysop/operator/diffusion.py
@@ -8,7 +8,7 @@ from hysop.tools.types       import check_instance
 from hysop.tools.enum        import EnumFactory
 from hysop.tools.decorators  import debug
 from hysop.fields.continuous import Field
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 from hysop.core.graph.computational_node_frontend import ComputationalGraphNodeFrontend
 
 from hysop.backend.host.fortran.operator.diffusion import DiffusionFFTW
@@ -70,7 +70,7 @@ class Diffusion(ComputationalGraphNodeFrontend):
 
         check_instance(input_field,  Field)
         check_instance(output_field, Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
         check_instance(base_kwds, dict, keys=str)
 
         super(Diffusion, self).__init__(input_field=input_field, output_field=output_field,
diff --git a/hysop/operator/directional/advection_dir.py b/hysop/operator/directional/advection_dir.py
index 7e078d718..7c15bd403 100644
--- a/hysop/operator/directional/advection_dir.py
+++ b/hysop/operator/directional/advection_dir.py
@@ -12,7 +12,7 @@ from hysop.core.mpi.topology import Topology
 from hysop.operator.directional.directional import DirectionalOperatorFrontend
 
 from hysop.backend.device.opencl.operators import OpenClDirectionalAdvection
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 
 
 class DirectionalAdvection(DirectionalOperatorFrontend):
@@ -90,7 +90,7 @@ class DirectionalAdvection(DirectionalOperatorFrontend):
         check_instance(velocity, Field)
         check_instance(advected_fields,     list, values=Field)
         check_instance(advected_fields_out, list, values=Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
         check_instance(base_kwds, dict, keys=str)
     
         super(DirectionalAdvection, self).__init__(velocity=velocity,
diff --git a/hysop/operator/directional/stretching_dir.py b/hysop/operator/directional/stretching_dir.py
index 7e808a476..070dc1540 100644
--- a/hysop/operator/directional/stretching_dir.py
+++ b/hysop/operator/directional/stretching_dir.py
@@ -8,7 +8,7 @@ from hysop.constants         import DirectionLabels, Implementation
 from hysop.tools.types       import check_instance, to_tuple
 from hysop.tools.decorators  import debug
 from hysop.fields.continuous import Field
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 from hysop.operator.directional.directional import DirectionalOperatorFrontend
 
 from hysop.backend.device.opencl.operators import OpenClDirectionalStretching
@@ -72,7 +72,7 @@ class DirectionalStretching(DirectionalOperatorFrontend):
         check_instance(velocity,      Field)
         check_instance(vorticity,     Field)
         check_instance(vorticity_out, Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
 
         assert vorticity.domain.dimension==3
         assert vorticity_out.domain.dimension==3
diff --git a/hysop/operator/poisson.py b/hysop/operator/poisson.py
index c8ab0934e..ee12c8b1f 100644
--- a/hysop/operator/poisson.py
+++ b/hysop/operator/poisson.py
@@ -8,7 +8,7 @@ from hysop.tools.types       import check_instance
 from hysop.tools.enum        import EnumFactory
 from hysop.tools.decorators  import debug
 from hysop.fields.continuous import Field
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
+from hysop.core.mpi.topology_descriptor import CartesianDescriptors
 from hysop.core.graph.computational_node_frontend import ComputationalGraphNodeFrontend
 
 from hysop.backend.host.fortran.operator.poisson import PoissonFFTW
@@ -76,7 +76,7 @@ class Poisson(ComputationalGraphNodeFrontend):
 
         check_instance(velocity,  Field)
         check_instance(vorticity, Field)
-        check_instance(variables, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(variables, dict, keys=Field, values=CartesianDescriptors)
         check_instance(base_kwds, dict, keys=str)
         
         dim   = velocity.domain.dimension
diff --git a/hysop/operator/redistribute.py b/hysop/operator/redistribute.py
index 1e57b2c3a..e63fecd76 100644
--- a/hysop/operator/redistribute.py
+++ b/hysop/operator/redistribute.py
@@ -12,7 +12,6 @@ from hysop.tools.types import check_instance, to_set
 from hysop.tools.decorators import debug
 from hysop.fields.continuous import Field
 from hysop.core.mpi.topology import Topology, Cartesian
-from hysop.core.mpi.topology_descriptor import TopologyDescriptor
 from hysop.core.mpi.redistribute import RedistributeOperator, RedistributeIntra
 from hysop.core.graph.node_generator import ComputationalGraphNodeGenerator
 
@@ -70,13 +69,13 @@ class Redistribute(ComputationalGraphNodeGenerator):
 
         check_instance(source_topos, dict, keys=Field, values=set)
         for v in source_topos.values():
-            check_instance(v, set, values=TopologyDescriptor)
+            check_instance(v, set, values=Topology)
         
         # format target_topo to a dict(Field, Topology)
         if not isinstance(target_topo, dict):
-            check_instance(target_topo, TopologyDescriptor)
+            check_instance(target_topo, Topology)
             target_topo = dict(zip(variables, (target_topo,)*len(variables)))
-        check_instance(target_topo, dict, keys=Field, values=TopologyDescriptor)
+        check_instance(target_topo, dict, keys=Field, values=Topology)
 
         # format components to a dict(Field, set(int)|None)
         if not isinstance(components, dict):
diff --git a/hysop/problem.py b/hysop/problem.py
index 745b13316..8de18fda4 100644
--- a/hysop/problem.py
+++ b/hysop/problem.py
@@ -16,6 +16,8 @@ class Problem(ComputationalGraph):
     def build(self):
         vprint('\nInitializing problem...')
         self.initialize(outputs_are_inputs=True, topgraph_method=None)
+        vprint('\nChecking problem...')
+        self.check()
         vprint('\nDiscretizing problem...')
         self.discretize()
         vprint('\nGetting work properties...')
diff --git a/hysop/tools/types.py b/hysop/tools/types.py
index 05fff7c71..3d8ef1e69 100644
--- a/hysop/tools/types.py
+++ b/hysop/tools/types.py
@@ -46,8 +46,10 @@ def check_instance(val, cls, allow_none=False, **kargs):
             val_cls = kargs.pop('values')
             for k,v in val.iteritems():
                 if not isinstance(v,val_cls):
-                    msg='Value contained in {} at key {} is not an instance of {}, got {}.'
-                    msg=msg.format(cls.__name__, k, val_cls.__name__, v.__class__)
+                    if hasattr(k, 'name'):
+                        key=k.name
+                    msg='Value contained in {} at key \'{}\' is not an instance of {}, got {}.'
+                    msg=msg.format(cls.__name__, key, val_cls, v.__class__)
                     raise TypeError(msg)
     if kargs:
         raise RuntimeError('Some arguments were not used ({}).'.format(kargs))
-- 
GitLab