From c5609f04bbe0f4eb107385d9a051c2dcd6fb558d Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Tue, 14 Aug 2018 19:32:31 +0200
Subject: [PATCH] fixed varname issues in codegeb

---
 examples/taylor_green/taylor_green.py         |  2 +-
 .../device/codegen/kernels/custom_symbolic.py | 14 +--
 .../functions/custom_symbolic_function.py     |  2 +-
 .../autotunable_kernels/custom_symbolic.py    | 12 +--
 hysop/core/memory/memory_request.py           |  4 +-
 hysop/fields/cartesian_discrete_field.py      | 20 +++-
 hysop/fields/continuous_field.py              | 99 +++++++++++++------
 hysop/fields/discrete_field.py                | 65 ++++++++----
 hysop/fields/field_requirements.py            |  9 +-
 hysop/mesh/cartesian_mesh.py                  | 28 +++---
 hysop/operator/base/derivative.py             |  2 +-
 hysop/symbolic/field.py                       | 19 ++--
 hysop/tools/parameters.py                     | 14 +++
 hysop/topology/cartesian_descriptor.py        | 23 +++--
 hysop/topology/cartesian_topology.py          | 29 +++---
 15 files changed, 233 insertions(+), 109 deletions(-)

diff --git a/examples/taylor_green/taylor_green.py b/examples/taylor_green/taylor_green.py
index 5d7731835..21115b59d 100644
--- a/examples/taylor_green/taylor_green.py
+++ b/examples/taylor_green/taylor_green.py
@@ -84,7 +84,7 @@ def compute(args):
     # Define parameters and field (time, timestep, velocity, vorticity, enstrophy)
     t, dt = TimeParameters(dtype=args.dtype)
     velo  = VelocityField(domain=box, dtype=args.dtype)
-    vorti = VorticityField(domain=box, dtype=args.dtype)
+    vorti = VorticityField(velocity=velo)
     enstrophy = EnstrophyParameter(dtype=args.dtype)
     
     ### Build the directional operators
diff --git a/hysop/backend/device/codegen/kernels/custom_symbolic.py b/hysop/backend/device/codegen/kernels/custom_symbolic.py
index 3fcd9ae28..e20fe6851 100644
--- a/hysop/backend/device/codegen/kernels/custom_symbolic.py
+++ b/hysop/backend/device/codegen/kernels/custom_symbolic.py
@@ -242,8 +242,8 @@ class SymbolicCodegenContext(object):
         for dfield in dfields:
             field = dfield._field
             ctype = dfield.ctype
-            name = dfield.name.lower()
-            if (name == dfield.name):
+            name = dfield.var_name.lower()
+            if (name == dfield.var_name):
                 name = '_'+name
             name = '{}_{{}}'.format(name)
             reads  = read_counter.get(dfield, None)
@@ -533,7 +533,7 @@ class CustomSymbolicKernelGenerator(KernelCodeGenerator):
                 args    = array_args.setdefault(obj, {})
                 strides = array_strides.setdefault(obj, {})
 
-                mesh_info_name = '{}_mesh_info'.format(dfield.name)
+                mesh_info_name = '{}_mesh_info'.format(dfield.var_name)
                 mesh_info = kernel_reqs['MeshInfoStruct'].build_codegen_variable(
                         const=True, name=mesh_info_name)
                 assert dfield not in mesh_infos
@@ -544,7 +544,7 @@ class CustomSymbolicKernelGenerator(KernelCodeGenerator):
                         continue
                     if (dfield in di.write_counter) and di.write_counter[dfield][i]>0:
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     (arg, stride) = OpenClArrayBackend.build_codegen_arguments(kargs, name=vname,
                                 known_vars=csc.known_vars, symbolic_mode=csc.symbolic_mode,
                                 storage=self._global, ctype=dfield.ctype, 
@@ -585,7 +585,7 @@ class CustomSymbolicKernelGenerator(KernelCodeGenerator):
                 args    = array_args.setdefault(dfield, {})
                 strides = array_strides.setdefault(dfield, {})
                 if (dfield not in mesh_infos):
-                    mesh_info_name = '{}_mesh_info'.format(dfield.name)
+                    mesh_info_name = '{}_mesh_info'.format(dfield.var_name)
                     mesh_info = kernel_reqs['MeshInfoStruct'].build_codegen_variable(
                             const=True, name=mesh_info_name)
                     mesh_infos[dfield] = mesh_info_name
@@ -593,7 +593,7 @@ class CustomSymbolicKernelGenerator(KernelCodeGenerator):
                 for (i, count) in enumerate(counts):
                     if (count==0):
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     arg, arg_strides = OpenClArrayBackend.build_codegen_arguments(kargs, 
                                 name=vname,
                                 known_vars=csc.known_vars, symbolic_mode=csc.symbolic_mode,
@@ -750,6 +750,8 @@ class CustomSymbolicKernelGenerator(KernelCodeGenerator):
         for (array, array_data) in array_args.iteritems():
             if isinstance(array, OpenClSymbolicArray):
                 name = array.varname
+            elif isinstance(array, DiscreteScalarFieldView):
+                name = array.var_name
             else:
                 name = array.name
             vindex = CodegenVectorClBuiltin(name+'_vid', itype, varray_dim, typegen=tg)
diff --git a/hysop/backend/device/codegen/symbolic/functions/custom_symbolic_function.py b/hysop/backend/device/codegen/symbolic/functions/custom_symbolic_function.py
index e0fb4712f..858b13992 100644
--- a/hysop/backend/device/codegen/symbolic/functions/custom_symbolic_function.py
+++ b/hysop/backend/device/codegen/symbolic/functions/custom_symbolic_function.py
@@ -35,7 +35,7 @@ class CustomSymbolicFunction(OpenClFunctionCodeGenerator):
 
     @classmethod
     def field_name(cls, field, index):
-        return cls.varname('{}_{}'.format(field.name, index))
+        return cls.varname('{}_{}'.format(field.var_name, index))
     @classmethod
     def array_name(cls, array):
         return cls.varname(array.varname)
diff --git a/hysop/backend/device/opencl/autotunable_kernels/custom_symbolic.py b/hysop/backend/device/opencl/autotunable_kernels/custom_symbolic.py
index 395f5887a..1f2f14495 100644
--- a/hysop/backend/device/opencl/autotunable_kernels/custom_symbolic.py
+++ b/hysop/backend/device/opencl/autotunable_kernels/custom_symbolic.py
@@ -120,14 +120,14 @@ class OpenClAutotunableCustomSymbolicKernel(OpenClAutotunableKernel):
             if isinstance(obj, di.IndexedCounterTypes):
                 assert isinstance(obj, DiscreteScalarFieldView)
                 dfield = expr_info.input_dfields[obj._field]
-                mesh_info_name = '{}_mesh_info'.format(dfield.name)
+                mesh_info_name = '{}_mesh_info'.format(dfield.var_name)
                 mesh_info_vars[mesh_info_name] = self.mesh_info(mesh_info_name, dfield.mesh)
                 for (i, count) in enumerate(counts):
                     if (count==0):
                         continue
                     if (dfield in di.write_counter) and (di.write_counter[dfield][i]>0):
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     kernel_args[vname+'_base']    = dfield.data[i].base_data
                     target_stride_args[vname+'_strides'] = make_strides(dfield.data[i].strides, dfield.dtype)
                     target_offset_args[vname+'_offset']  = make_offset(dfield.data[i].offset, dfield.dtype)
@@ -154,12 +154,12 @@ class OpenClAutotunableCustomSymbolicKernel(OpenClAutotunableKernel):
             if isinstance(obj, di.IndexedCounterTypes):
                 assert isinstance(obj, DiscreteScalarFieldView)
                 dfield = expr_info.output_dfields[obj._field]
-                mesh_info_name = '{}_mesh_info'.format(dfield.name)
+                mesh_info_name = '{}_mesh_info'.format(dfield.var_name)
                 mesh_info_vars[mesh_info_name] = self.mesh_info(mesh_info_name, dfield.mesh)
                 for (i, count) in enumerate(counts):
                     if (count==0):
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     kernel_args[vname+'_base']    = dfield.data[i].base_data
                     target_stride_args[vname+'_strides'] = make_strides(dfield.data[i].strides, dfield.dtype)
                     target_offset_args[vname+'_offset']  = make_offset(dfield.data[i].offset, dfield.dtype)
@@ -252,7 +252,7 @@ class OpenClAutotunableCustomSymbolicKernel(OpenClAutotunableKernel):
                         continue
                     if (dfield in di.write_counter) and (di.write_counter[dfield][i]>0):
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     args_mapping[vname+'_base'] = (arg_index, cl.MemoryObjectHolder)
                     arg_index += 1
                     if (not hardcode_arrays):
@@ -286,7 +286,7 @@ class OpenClAutotunableCustomSymbolicKernel(OpenClAutotunableKernel):
                 for (i, count) in enumerate(counts):
                     if (count==0):
                         continue
-                    vname = dfield.name + '_' + str(i)
+                    vname = dfield.var_name + '_' + str(i)
                     args_mapping[vname+'_base'] = (arg_index, cl.MemoryObjectHolder)
                     arg_index += 1
                     if (not hardcode_arrays):
diff --git a/hysop/core/memory/memory_request.py b/hysop/core/memory/memory_request.py
index 9b564f28d..ee0815c05 100644
--- a/hysop/core/memory/memory_request.py
+++ b/hysop/core/memory/memory_request.py
@@ -145,7 +145,7 @@ class MemoryRequest(object):
     @classmethod
     def cartesian_dfield_like(cls, name, dfield, 
             nb_components=None, initial_values=None, dtype=None, 
-            global_resolution=None, ghosts=None,
+            grid_resolution=None, ghosts=None,
             backend=None, is_read_only=None):
         from hysop.fields.cartesian_discrete_field import CartesianDiscreteScalarFieldView
         check_instance(dfield,  CartesianDiscreteScalarFieldView)
@@ -160,7 +160,7 @@ class MemoryRequest(object):
 
         (dfield, request, request_id) = dfield.tmp_dfield_like(name=name, backend=backend,
                 nb_components=nb_components, initial_values=initial_values, dtype=dtype,
-                global_resolution=global_resolution, ghosts=ghosts, is_read_only=is_read_only)
+                grid_resolution=grid_resolution, ghosts=ghosts, is_read_only=is_read_only)
 
         return (dfield, request, request_id)
 
diff --git a/hysop/fields/cartesian_discrete_field.py b/hysop/fields/cartesian_discrete_field.py
index b1f6bf0c1..858b90f9d 100644
--- a/hysop/fields/cartesian_discrete_field.py
+++ b/hysop/fields/cartesian_discrete_field.py
@@ -864,7 +864,8 @@ class CartesianDiscreteScalarFieldView(CartesianDiscreteScalarFieldViewContainer
         return s
 
 
-    def clone(self, name=None, pretty_name=None, tstate=None):
+    def clone(self, name=None, pretty_name=None, 
+                    var_name=None, latex_name=None, tstate=None):
         """
         Create a new temporary DiscreteScalarField and allocate it 
         like the current object, possibly on a different backend. 
@@ -877,14 +878,20 @@ class CartesianDiscreteScalarFieldView(CartesianDiscreteScalarFieldViewContainer
         default_name='{}__{}'.format(self.name, self._dfield._clone_id)
         default_pname='{}__{}'.format(self.pretty_name, 
                 subscript(self._dfield._clone_id).encode('utf-8'))
+        default_vname='{}__{}'.format(self.var_name, self._dfield._clone_id)
+        default_lname='{}__{}'.format(self.latex_name, self._dfield._clone_id)
         self._dfield._clone_id += 1
 
         tstate = first_not_None(tstate, self.topology_state)
-        name = first_not_None(name, default_name)
-        pretty_name = first_not_None(pretty_name, default_pname)
+        pretty_name = first_not_None(pretty_name, name, default_pname)
+        var_name    = first_not_None(var_name, name, default_vname)
+        latex_name  = first_not_None(latex_name, name, default_lname)
+        name        = first_not_None(name, default_name)
         
         dfield = CartesianDiscreteScalarField(name=name, 
                 pretty_name=pretty_name,
+                latex_name=latex_name, 
+                var_name=var_name,
                 field=self._dfield._field, 
                 topology=self._dfield._topology, 
                 init_topology_state=tstate,
@@ -894,9 +901,10 @@ class CartesianDiscreteScalarFieldView(CartesianDiscreteScalarFieldViewContainer
         return dfield
 
     def tmp_dfield_like(self, name, pretty_name=None,
+            var_name=None, latex_name=None,
             backend=None, is_read_only=None,
             initial_values=None, dtype=None,
-            global_resolution=None, ghosts=None, tstate=None, 
+            grid_resolution=None, ghosts=None, tstate=None, 
             lboundaries=None, rboundaries=None,
             register_discrete_field=False, **kwds):
         """
@@ -904,6 +912,7 @@ class CartesianDiscreteScalarFieldView(CartesianDiscreteScalarFieldViewContainer
         like the current object, possibly on a different backend. 
         /!\ The returned discrete field is not allocated.
         """
+        assert ('global_resolution' not in kwds), 'Specify grid_resolution instead.'
         tstate = first_not_None(tstate, self._topology_state)
         if (is_read_only is not None):
             tstate._is_read_only = is_read_only
@@ -912,12 +921,13 @@ class CartesianDiscreteScalarFieldView(CartesianDiscreteScalarFieldViewContainer
         btopo  = self._dfield._topology
 
         field = bfield.field_like(name=name, pretty_name=pretty_name,
+                                   latex_name=latex_name, var_name=var_name,
                                    initial_values=initial_values, dtype=dtype,
                                    lboundaries=lboundaries, rboundaries=rboundaries,
                                    register_object=register_discrete_field)
 
         topology = btopo.topology_like(backend=backend,
-                global_resolution=global_resolution, ghosts=ghosts,
+                grid_resolution=grid_resolution, ghosts=ghosts,
                 lboundaries=lboundaries, rboundaries=rboundaries)
 
         dfield = TmpCartesianDiscreteScalarField(field=field, topology=topology, 
diff --git a/hysop/fields/continuous_field.py b/hysop/fields/continuous_field.py
index 7ba53d130..2d3da93b0 100644
--- a/hysop/fields/continuous_field.py
+++ b/hysop/fields/continuous_field.py
@@ -41,7 +41,7 @@ class SymbolContainerI(object):
 class NamedObjectI(object):
     __metaclass__ = ABCMeta
     
-    def __new__(cls, name, pretty_name=None, **kwds):
+    def __new__(cls, name, pretty_name=None, latex_name=None, var_name=None, **kwds):
         """
         Create an abstract named object that contains a symbolic value.
         name : string
@@ -52,59 +52,86 @@ class NamedObjectI(object):
         kwds: dict
             Keywords arguments for base class.
         """
+        
+        obj = super(NamedObjectI, cls).__new__(cls, **kwds)
+        obj.rename(name=name, pretty_name=pretty_name, 
+                    latex_name=latex_name, var_name=var_name)
+        return obj
+    
+    def rename(self, name, pretty_name=None, latex_name=None):
+        """Change the names of this object."""
         check_instance(name, str)
         check_instance(pretty_name, (str,unicode), allow_none=True)
-        
-        pretty_name = first_not_None(pretty_name, name)
+        check_instance(latex_name, str, allow_none=True)
         if isinstance(pretty_name, unicode):
             pretty_name = pretty_name.encode('utf-8')
         check_instance(pretty_name, str)
         
-        obj = super(NamedObjectI, cls).__new__(cls, **kwds)
-        obj._name   = name
-        obj._pretty_name = pretty_name
-        return obj
-        
-    @abstractmethod
-    def short_description(self):
-        """Short description of this field as a string."""
-        pass
-
-    @abstractmethod
-    def long_description(self):
-        """Long description of this field as a string."""
-        pass
+        pretty_name = first_not_None(pretty_name, name)
+        latex_name  = first_not_None(latex_name, name)
 
+        self._name   = name
+        self._pretty_name = pretty_name
+        self._latex_name = latex_name
+        
     def _get_name(self):
         """Return the name of this field."""
         return self._name
     def _get_pretty_name(self):
         """Return the pretty name of this field."""
         return self._pretty_name
+    def _get_latex_name(self):
+        """Return the latex name of this field."""
+        return self._latex_name
     
     def __str__(self):
         return self.long_description()
     
-    def rename(self, name, pretty_name=None):
-        """Change the name or pretty name of this object."""
-        pretty_name = first_not_None(pretty_name, name)
-        check_instance(name, str)
-        self._name = name
-        if isinstance(pretty_name, unicode):
-            pretty_name = pretty_name.encode('utf-8')
-        check_instance(pretty_name, str)
-        self._pretty_name = pretty_name
-        return self
-   
+    @abstractmethod
+    def short_description(self):
+        """Short description of this field as a string."""
+        pass
+
+    @abstractmethod
+    def long_description(self):
+        """Long description of this field as a string."""
+        pass
+
     name = property(_get_name)
     pretty_name = property(_get_pretty_name)
+    latex_name = property(_get_latex_name)
 
 
 class NamedScalarContainerI(NamedObjectI, SymbolContainerI):
+
     @property
     def ndim(self):
         """Number of dimensions of this this tensor."""
         return 0
+    
+    def _get_var_name(self):
+        """Return the variable name of this field."""
+        return self._var_name
+    
+    def rename(self, name, pretty_name=None, 
+                latex_name=None, var_name=None):
+        """Change the names of this object."""
+        super(NamedScalarContainerI, self).rename(name=name, 
+                pretty_name=pretty_name, latex_name=latex_name)
+        self.check_and_set_varname(first_not_None(var_name, self._name))
+    
+    def check_and_set_varname(self, var_name):
+        check_instance(var_name, str, allow_none=True)
+
+        msg='Invalid variable name {}.'.format(var_name)
+        if var_name[0] in tuple(str(x) for x in range(10)):
+            raise RuntimeError(msg)
+        for c in '/*+-=|&()[]{}-!?:;,\'"#$^%<>@':
+            if c in var_name:
+                raise RuntimeError(msg)
+        self._var_name = var_name
+    
+    var_name = property(_get_var_name)
 
 
 class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
@@ -115,6 +142,13 @@ class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
         obj._contained_objects = contained_objects
         return obj
     
+    def rename(self, name, pretty_name=None, 
+                latex_name=None, var_name=None):
+        """Change the names of this object."""
+        assert (var_name is None), 'Tensor do not have variable names.'
+        super(NamedTensorContainerI, self).rename(name=name, 
+                pretty_name=pretty_name, latex_name=latex_name)
+    
     @property
     def size(self):
         """Full size of this container as if it was a 1D tensor."""
@@ -670,6 +704,7 @@ class ScalarField(NamedScalarContainerI, FieldContainerI):
         
         obj = super(ScalarField, cls).__new__(cls, domain=domain,
                 name=name, pretty_name=pretty_name, 
+                var_name=var_name, latex_name=latex_name,
                 tag_prefix='f', tagged_cls=ScalarField, **kwds)
         obj._dtype  = dtype
         obj._initial_values = initial_values
@@ -680,9 +715,7 @@ class ScalarField(NamedScalarContainerI, FieldContainerI):
     
         # Symbolic representation of this field
         from hysop.symbolic.field import SymbolicField
-        obj._symbol = SymbolicField(field=obj, 
-                                    var_name=var_name, 
-                                    latex_name=latex_name)
+        obj._symbol = SymbolicField(field=obj)
 
         # Dictionnary of all the discretizations of this field.
         # keys are hysop.topology.topology.Topology,
@@ -732,6 +765,7 @@ class ScalarField(NamedScalarContainerI, FieldContainerI):
         check_instance(obj.is_tmp, bool)
 
     def field_like(self, name, pretty_name=None,
+            latex_name=None, var_name=None,
             domain=None, dtype=None, is_tmp=None, 
             lboundaries=None, rboundaries=None,
             initial_values=None, **kwds):
@@ -744,6 +778,7 @@ class ScalarField(NamedScalarContainerI, FieldContainerI):
         rboundaries    = first_not_None(rboundaries, self.rboundaries)
         initial_values = first_not_None(initial_values, self.initial_values)
         return ScalarField(name=name, pretty_name=pretty_name,
+                var_name=var_name, latex_name=latex_name,
                 domain=domain, dtype=dtype, is_tmp=is_tmp, 
                 lboundaries=lboundaries, rboundaries=rboundaries, 
                 initial_values=initial_values, **kwds)
@@ -782,7 +817,7 @@ class ScalarField(NamedScalarContainerI, FieldContainerI):
           *topology tags:  [{}]
         ''').format(self.full_tag,
                 self.name, self.pretty_name, 
-                self.symbol._var_name, self.symbol._latex_name,
+                self.var_name, self.latex_name,
                 self.dim, self.dtype,
                 self.lboundaries.tolist(), self.rboundaries.tolist(), 
                 self.initial_values,
diff --git a/hysop/fields/discrete_field.py b/hysop/fields/discrete_field.py
index 3205e2b35..4b026955f 100644
--- a/hysop/fields/discrete_field.py
+++ b/hysop/fields/discrete_field.py
@@ -453,6 +453,12 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
     def _get_pretty_name(self):
         """Get the name of the discrete field."""
         return self._dfield._pretty_name
+    def _get_latex_name(self):
+        """Get the latex name of the discrete field."""
+        return self._dfield._latex_name
+    def _get_var_name(self):
+        """Get the latex name of the discrete field."""
+        return self._dfield._var_name
     
     def _get_dtype(self):
         """Get the data type of the discrete field."""
@@ -512,14 +518,6 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
         h ^= hash(self._topology_state)
         return h
 
-    @property
-    def name(self):
-        return self._dfield._name
-
-    @property
-    def pretty_name(self):
-        return self._dfield._pretty_name
-
     @property
     def symbol(self):
         return self._dfield._symbol
@@ -534,6 +532,8 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
     
     name  = property(_get_name)
     pretty_name  = property(_get_pretty_name)
+    latex_name = property(_get_latex_name)
+    var_name = property(_get_var_name)
     
     dtype = property(_get_dtype)
     initial_values = property(_get_initial_values)
@@ -547,6 +547,7 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
     memory_request = property(_get_memory_request)
     memory_request_id = property(_get_memory_request_id)
 
+
 class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
     """
     Discrete representation of scalar or vector fields,
@@ -569,7 +570,9 @@ class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
 
     @debug
     def __new__(cls, field, topology, register_discrete_field=True, 
-                name=None, pretty_name=None, **kwds):
+                name=None, pretty_name=None, 
+                var_name=None, latex_name=None,
+                **kwds):
         """
         Creates a discrete field for a given continuous field and topology.
 
@@ -579,6 +582,17 @@ class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
             The continuous field that is dicrerized.
         topology: :class:`~hysop.topology.topology.Topology`
             The topology where to allocate the discrete field.
+        name : string, optional
+            A name for the field.
+        pretty_name: string or unicode, optional.
+            A pretty name used for display whenever possible.
+            Defaults to name.
+        var_name: string, optional.
+            A variable name used for code generation.
+            This will be passed to the symbolic representation of this discrete field.
+        latex_name: string, optional.
+            A variable name used for latex generation.
+            This will be passed to the symbolic representation of this discrete field.
         kwds: dict
             Base class arguments.
         """
@@ -587,12 +601,20 @@ class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
         check_instance(name, str, allow_none=True)
         check_instance(pretty_name, (str,unicode), allow_none=True)
 
-        _name, _pretty_name = cls.format_discrete_names(field.name, 
-                                                        field.pretty_name, topology)
+        _name, _pretty_name, _var_name, _latex_name = \
+                cls.format_discrete_names(field.name, 
+                                          field.pretty_name, 
+                                          field.s._var_name,
+                                          field.s._latex_name,
+                                          topology)
+        
         pretty_name = first_not_None(pretty_name, name, _pretty_name)
+        var_name    = first_not_None(var_name,    name, _var_name)
+        latex_name  = first_not_None(latex_name,  name, _latex_name)
         name        = first_not_None(name, _name)
 
         obj = super(DiscreteScalarField, cls).__new__(cls, name=name, pretty_name=pretty_name,
+                                                     var_name=var_name, latex_name=latex_name,
                                                      tag_prefix='df', **kwds)
         assert isinstance(obj, DiscreteScalarFieldView), 'DiscreteScalarFieldView not inherited.'
 
@@ -615,19 +637,23 @@ class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
         return obj
 
     @classmethod
-    def format_discrete_names(cls, name, pretty_name, topology):
+    def format_discrete_names(cls, name, pretty_name, var_name, latex_name, topology):
         from hysop.tools.sympy_utils import subscript
         if (topology is None):
             # Tensor discrete field names (topology is not unique)
             name        = '{}*'.format(name)
             pretty_name = '{}*'.format(pretty_name)
+            latex_name  = '{}'.format(latex_name)
+            var_name    = None
         else:
             # Scalar discrete field names
             name        = '{}_t{}'.format(name, topology.id)
-            pretty_name = '{}_{}{}'.format(pretty_name,
+            pretty_name = '{}.{}{}'.format(pretty_name,
                                             u'\u209c'.encode('utf-8'),
                                             subscript(topology.id).encode('utf-8'))
-        return (name, pretty_name)
+            var_name    = var_name + '_t{}'.format(topology.id)
+            latex_name  = latex_name + '.t_{{{}}}'.format(0)
+        return (name, pretty_name, var_name, latex_name)
 
 
 class DiscreteTensorField(NamedTensorContainerI, DiscreteScalarFieldViewContainerI, TaggedObject):
@@ -649,7 +675,8 @@ class DiscreteTensorField(NamedTensorContainerI, DiscreteScalarFieldViewContaine
     discrete fields may be defined on different topologies.
     """
 
-    def __new__(cls, field, dfields, name=None, pretty_name=None, **kwds):
+    def __new__(cls, field, dfields, name=None, 
+                pretty_name=None, latex_name=None, **kwds):
         check_instance(field, TensorField)
         check_instance(dfields, npw.ndarray, dtype=object, values=DiscreteScalarFieldView)
         assert npw.array_equal(field.shape, dfields.shape)
@@ -661,12 +688,14 @@ class DiscreteTensorField(NamedTensorContainerI, DiscreteScalarFieldViewContaine
                                       name=name, pretty_name=pretty_name,
                                       **kwds)
 
-        _name, _pretty_name = DiscreteScalarField.format_discrete_names(field.name,
-                field.pretty_name, None)
+        _name, _pretty_name, _, _latex_name = DiscreteScalarField.format_discrete_names(
+                field.name, field.pretty_name, None, field.latex_name, None)
         name        = first_not_None(name, _name)
         pretty_name = first_not_None(pretty_name, _pretty_name)
+        latex_name  = first_not_None(latex_name, _latex_name)
 
-        obj = super(DiscreteTensorField, cls).__new__(cls, name=name, pretty_name=pretty_name,
+        obj = super(DiscreteTensorField, cls).__new__(cls, name=name, 
+                pretty_name=pretty_name, latex_name=latex_name,
                 tag_prefix='tdf', tagged_cls=DiscreteTensorField, 
                 contained_objects=dfields, **kwds)
         obj._field   = field
diff --git a/hysop/fields/field_requirements.py b/hysop/fields/field_requirements.py
index 18af537ab..176c3dff2 100644
--- a/hysop/fields/field_requirements.py
+++ b/hysop/fields/field_requirements.py
@@ -233,8 +233,15 @@ class DiscreteFieldRequirements(object):
             msg='{} Dimension mismatch between field and topology.\n field={}d, topology={}d.'
             msg=msg.format(self._header, self.field.dim, topology.domain.dim)
             raise RuntimeError(msg)
+        if (topology.grid_resolution != self.topology_descriptor.grid_resolution).any():
+            msg='{} Grid resolution mismatch between requirement and topology.\n '
+            msg+=' requirement={}\n topology={}'
+            msg=msg.format(self._header,
+                    self.topology_descriptor.grid_resolution,
+                    topology.grid_resolution)
+            raise RuntimeError(msg)
         if (topology.global_resolution != self.topology_descriptor.global_resolution).any():
-            msg='{} Discretisation mismatch between requirement and topology.\n '
+            msg='{} Global resolution mismatch between requirement and topology.\n '
             msg+=' requirement={}\n topology={}'
             msg=msg.format(self._header,
                     self.topology_descriptor.global_resolution,
diff --git a/hysop/mesh/cartesian_mesh.py b/hysop/mesh/cartesian_mesh.py
index b0a383b69..ca8baaf19 100644
--- a/hysop/mesh/cartesian_mesh.py
+++ b/hysop/mesh/cartesian_mesh.py
@@ -81,7 +81,7 @@ class CartesianMeshView(MeshView):
     def _get_grid_npoints(self):
         """
         Effective size of the global mesh.
-        Corresponds to np.prod(self.global_resolution - topology.is_periodic).
+        Corresponds to np.prod(self.grid_resolution)
         """
         return prod(self._mesh._grid_resolution)
 
@@ -944,21 +944,25 @@ class CartesianMesh(CartesianMeshView, Mesh):
         dim = domain.dim
 
         discretization = topology._topology._discretization
-        assert (discretization.resolution>=1).all()
+        assert (discretization.grid_resolution>=1).all()
         assert (discretization.ghosts>=0).all()
-        ghosts     = np.asintegerarray(discretization.ghosts).copy()
-        resolution = np.asintegerarray(discretization.resolution).copy()
-        space_step = npw.asrealarray(domain.length / (resolution - 1))
-        del resolution
-
-        global_resolution = npw.asintegerarray(topology.global_resolution).copy()
-        global_start      = npw.asintegerarray(global_start).copy()
+        ghosts            = np.asintegerarray(discretization.ghosts).copy()
+        grid_resolution   = np.asintegerarray(discretization.grid_resolution).copy()
+        periodicity       = np.asintegerarray(discretization.periodicity).copy()
+        global_resolution = np.asintegerarray(discretization.global_resolution).copy()
+        # /!\ now we add one point on each periodic axes because the user give grid_resolution
+        #     and not global resolution as it used to be.
+        assert all(global_resolution == grid_resolution + periodicity)
+        space_step = npw.asrealarray(domain.length / (grid_resolution + periodicity - 1))
+
+        topo_global_resolution = npw.asintegerarray(topology.global_resolution).copy()
+        topo_grid_resolution = npw.asintegerarray(topology.grid_resolution).copy()
+        global_start = npw.asintegerarray(global_start).copy()
+        assert all(topo_global_resolution == global_resolution)
+        assert all(topo_grid_resolution   == grid_resolution)
         assert global_start.size == dim
         assert (global_start>=0).all()
 
-        # Remove 1 point on each periodic axe because of periodicity
-        grid_resolution = global_resolution - discretization.periodicity
-
         # global boundaries and local boundaries
         (global_lboundaries, global_rboundaries) = discretization.boundaries
         del discretization
diff --git a/hysop/operator/base/derivative.py b/hysop/operator/base/derivative.py
index a603cabe3..769247c03 100644
--- a/hysop/operator/base/derivative.py
+++ b/hysop/operator/base/derivative.py
@@ -86,7 +86,7 @@ class SpaceDerivativeBase(object):
         check_instance(derivative, int, minval=1)
         check_instance(direction, int, minval=0, maxval=F.dim-1)
         check_instance(variables, dict, keys=Field, values=CartesianTopologyDescriptors)
-
+        
         assert 0 <= direction < F.dim, direction
         assert 1 <= derivative, derivative
 
diff --git a/hysop/symbolic/field.py b/hysop/symbolic/field.py
index 5475b1ee3..2ce88e9f0 100644
--- a/hysop/symbolic/field.py
+++ b/hysop/symbolic/field.py
@@ -200,14 +200,22 @@ class FieldExpressionBuilder(object):
         
 
 class FieldBase(FunctionBase):
-    def __new__(cls, field, idx=None, name=None, pretty_name=None, **kwds):
+    def __new__(cls, field, idx=None, 
+                    **kwds):
+        assert 'name' not in kwds
+        assert 'pretty_name' not in kwds
+        assert 'latex_name' not in kwds
+        assert 'var_name' not in kwds
         check_instance(field, (Field, DiscreteField))
         assert (field.nb_components == 1) or (idx is not None), (field.nb_components, idx)
         index = first_not_None(idx, [0])[0]
-        name = first_not_None(name, field.name)
-        pretty_name = first_not_None(pretty_name, field.pretty_name)
+        name        = field.name
+        pretty_name = field.pretty_name
+        var_name    = field.var_name
+        latex_name  = field.latex_name
         assert (0<=index<field.nb_components), index
-        obj = super(FieldBase, cls).__new__(cls, name=name, pretty_name=pretty_name, **kwds)
+        obj = super(FieldBase, cls).__new__(cls, name=name, pretty_name=pretty_name, 
+                                                 var_name=var_name, latex_name=latex_name, **kwds)
         obj._field = field
         obj._index = index
         return obj
@@ -241,9 +249,8 @@ class SymbolicDiscreteField(FieldBase, Symbol):
     """
     def __new__(cls, field, name=None, fn=None, **kwds):
         check_instance(field, DiscreteField)
-        name  = first_not_None(name, field.name)
         return super(SymbolicDiscreteField, cls).__new__(cls, field=field, 
-                name=name, fn=fn, **kwds)
+                fn=fn, **kwds)
 
     @classmethod
     def from_field(cls, field):
diff --git a/hysop/tools/parameters.py b/hysop/tools/parameters.py
index a208e73d8..c00400c3b 100755
--- a/hysop/tools/parameters.py
+++ b/hysop/tools/parameters.py
@@ -76,6 +76,7 @@ class CartesianDiscretization(namedtuple("CartesianDiscretization",
     """
     A struct to handle discretization parameters:
     - a resolution (either a list of int or a numpy array of int)
+      resolution is GRID_RESOLUTION. GLOBAL_RESOLUTION is GRID_RESOLUTION + PERIODICITY.
     - number of points in the ghost-layer. One value per direction, list
         or array. Default = None (ie. no ghosts).
     - global boundary conditions that should be prescribed on the left and the
@@ -135,6 +136,19 @@ class CartesianDiscretization(namedtuple("CartesianDiscretization",
         else:
             return (self.lboundaries == BoundaryCondition.PERIODIC)
 
+    @property
+    def grid_resolution(self):
+        """Effective grid resolution given by user."""
+        return self.resolution
+
+    @property
+    def global_resolution(self):
+        """
+        Logical grid resolution (grid_resolution + periodicity).
+        Can only be fetched if boundaries have been specified.
+        """
+        return self.grid_resolution + self.periodicity
+
     def __eq__(self, other):
         if self.__class__ != other.__class__:
             return NotImplemented
diff --git a/hysop/topology/cartesian_descriptor.py b/hysop/topology/cartesian_descriptor.py
index 96e513a13..80a3315d3 100644
--- a/hysop/topology/cartesian_descriptor.py
+++ b/hysop/topology/cartesian_descriptor.py
@@ -35,10 +35,12 @@ class CartesianTopologyDescriptor(TopologyDescriptor):
             msg='No ghost allowed for a topology descriptor.'
             raise ValueError(msg)
         
-        global_resolution  = cartesian_discretization.resolution
+        global_resolution  = cartesian_discretization.global_resolution
+        grid_resolution    = cartesian_discretization.grid_resolution
         lboundaries        = cartesian_discretization.lboundaries
         rboundaries        = cartesian_discretization.rboundaries
         
+        check_instance(grid_resolution, np.ndarray, size=domain.dim, minval=2)
         check_instance(global_resolution, np.ndarray, size=domain.dim, minval=2)
         check_instance(lboundaries, npw.ndarray, dtype=object, 
                 size=domain.dim, values=BoundaryCondition,
@@ -50,10 +52,12 @@ class CartesianTopologyDescriptor(TopologyDescriptor):
         is_lperiodic = (lboundaries==BoundaryCondition.PERIODIC) 
         is_rperiodic = (rboundaries==BoundaryCondition.PERIODIC) 
 
+        assert all((grid_resolution + is_lperiodic) == global_resolution)
+
         msg='Invalid boundary conditions {} vs {}.'
         msg=msg.format(lboundaries, rboundaries)
         assert not (is_lperiodic ^ is_rperiodic).any(), msg
-        
+
         # compute space step
         space_step = npw.asrealarray(domain.length / (global_resolution - 1))
         npw.set_readonly(space_step)
@@ -63,8 +67,13 @@ class CartesianTopologyDescriptor(TopologyDescriptor):
     
     @property
     def global_resolution(self):
-        """Get the global global_resolution of the discretization."""
-        return self._cartesian_discretization.resolution
+        """Get the global global_resolution of the discretization (logical grid_size)."""
+        return self._cartesian_discretization.global_resolution
+    
+    @property
+    def grid_resolution(self):
+        """Get the global grid resolution of the discretization (effective grid size)."""
+        return self._cartesian_discretization.grid_resolution
 
     @property
     def lboundaries(self):
@@ -110,9 +119,9 @@ class CartesianTopologyDescriptor(TopologyDescriptor):
         return h
 
     def __str__(self):
-        return ':CartesianTopologyDescriptor: backend={}, domain={}, resolution={}, bc=[{}]'.format(
+        return ':CartesianTopologyDescriptor: backend={}, domain={}, grid_resolution={}, bc=[{}]'.format(
                     self.backend, self.domain.full_tag,
-                    self.global_resolution, 
+                    self.grid_resolution, 
                      ','.join(('{}/{}'.format(
                          str(lb).replace('HOMOGENEOUS_','')[:3],
                          str(rb).replace('HOMOGENEOUS_','')[:3]) 
@@ -181,7 +190,7 @@ class CartesianTopologyDescriptor(TopologyDescriptor):
         by operators on variables and solved during operator's
         method get_field_requirements().
         """
-        discretization = CartesianDiscretization(resolution=self.global_resolution, 
+        discretization = CartesianDiscretization(resolution=self.grid_resolution, 
                                                  lboundaries=self.lboundaries, 
                                                  rboundaries=self.rboundaries,
                                                  ghosts=ghosts)
diff --git a/hysop/topology/cartesian_topology.py b/hysop/topology/cartesian_topology.py
index 0c0ea7c13..6a64a61bc 100644
--- a/hysop/topology/cartesian_topology.py
+++ b/hysop/topology/cartesian_topology.py
@@ -237,8 +237,11 @@ class CartesianTopologyView(TopologyView):
     
     # ATTRIBUTE GETTERS
     def _get_global_resolution(self):
-        """Returns global resolution of the discretization."""
-        return self._proc_transposed(self._topology._discretization.resolution)
+        """Returns global resolution of the discretization (logical grid size)."""
+        return self._proc_transposed(self._topology._discretization.global_resolution)
+    def _get_grid_resolution(self):
+        """Returns grid resolution of the discretization (effective grid size)."""
+        return self._proc_transposed(self._topology._discretization.grid_resolution)
     def _get_ghosts(self):
         """Returns ghosts of the discretization."""
         return self._proc_transposed(self._topology._discretization.ghosts)
@@ -335,6 +338,7 @@ class CartesianTopologyView(TopologyView):
         return np.where(self._get_is_periodic() == True)[0].astype(np.int32)
     
     global_resolution = property(_get_global_resolution)
+    grid_resolution = property(_get_grid_resolution)
     ghosts = property(_get_ghosts)
 
     comm = property(_get_comm)
@@ -372,13 +376,13 @@ class CartesianTopologyView(TopologyView):
         Short version of long_description().
         """
         s='{}[domain={}, backend={}, pcoords={}, pshape={}, '
-        s+='resolution={}, ghosts={}, bc=({})]'
+        s+='grid_resolution={}, ghosts={}, bc=({})]'
         s = s.format(
                 self.full_tag, 
                 self.domain.domain.full_tag, 
                 self.backend.kind,
                 self.proc_coords, self.proc_shape, 
-                '[{}]'.format(','.join(str(s) for s in self.global_resolution)),
+                '[{}]'.format(','.join(str(s) for s in self.grid_resolution)),
                 '[{}]'.format(','.join(str(g) for g in self.ghosts)),
                  ','.join(('{}/{}'.format(
                      str(lb).replace('HOMOGENEOUS_','')[:3],
@@ -685,16 +689,17 @@ class CartesianTopology(CartesianTopologyView, Topology):
         check_instance(self.domain, BoxView)
         check_instance(self.mesh, CartesianMeshView)
 
-    def topology_like(self, backend=None, global_resolution=None, ghosts=None, 
+    def topology_like(self, backend=None, grid_resolution=None, ghosts=None, 
             lboundaries=None, rboundaries=None, mpi_params=None, 
             cart_shape=None, **kwds):
         """Return a topology like this object, possibly altered."""
-        global_resolution = first_not_None(global_resolution, self._discretization.resolution)
+        assert ('global_resolution' not in kwds), 'Specify grid_resolution instead.'
+        grid_resolution   = first_not_None(grid_resolution,   self._discretization.grid_resolution)
         ghosts            = first_not_None(ghosts,            self._discretization.ghosts)
         lboundaries       = first_not_None(lboundaries,       self._discretization.lboundaries)
         rboundaries       = first_not_None(rboundaries,       self._discretization.rboundaries)
         backend           = first_not_None(backend,           self._backend)
-        discretization    = CartesianDiscretization(resolution=global_resolution, ghosts=ghosts,
+        discretization    = CartesianDiscretization(resolution=grid_resolution, ghosts=ghosts,
                                                     lboundaries=lboundaries, rboundaries=rboundaries)
 
         # find out the target mpi_params
@@ -706,7 +711,7 @@ class CartesianTopology(CartesianTopologyView, Topology):
             mpi_params = backend.cl_env.mpi_params
         mpi_params = first_not_None(mpi_params, self.mpi_params)
 
-        if (self.domain.dim == global_resolution.size):
+        if (self.domain.dim == grid_resolution.size):
             cart_shape = first_not_None(cart_shape, self.proc_shape)
 
         return CartesianTopology(domain=self._domain, mpi_params=mpi_params,
@@ -919,7 +924,7 @@ class CartesianTopology(CartesianTopologyView, Topology):
     def _compute_mesh(self, domain, discretization):
         assert isinstance(domain, Box)
         assert isinstance(discretization, CartesianDiscretization)
-        assert (self.domain_dim == discretization.resolution.size), \
+        assert (self.domain_dim == discretization.grid_resolution.size), \
             'The resolution size differs from the domain dimension.'
 
         proc_coords = self._proc_coords
@@ -929,8 +934,10 @@ class CartesianTopology(CartesianTopologyView, Topology):
         domain_dim  = domain.dim
         periodicity = discretization.periodicity
         
-        # Remove 1 point on each periodic axe because of periodicity
-        computational_grid_resolution = discretization.resolution - periodicity
+        # /!\ Now we assume that the user gives us the grid resolutionn
+        #     and not the global_resolution as it used to be.
+        #     We do not remove 1 point on each periodic axe because of periodicity
+        computational_grid_resolution = discretization.grid_resolution
 
         # Number of "computed" points (i.e. excluding ghosts).
         pts_noghost    = npw.dim_zeros((domain_dim))
-- 
GitLab