diff --git a/examples/sediment_deposit/sediment_deposit.py b/examples/sediment_deposit/sediment_deposit.py
index 140251f5aef1a6b80d3200c04fb80ef1c11eeb96..364b3e56ea629aa09ecd56b0deb2422589d99200 100644
--- a/examples/sediment_deposit/sediment_deposit.py
+++ b/examples/sediment_deposit/sediment_deposit.py
@@ -249,7 +249,8 @@ def compute(args):
     g = 1.0
     Fext = SymbolicExternalForce(name='S', Fext=(0,-g*Ss),
                                    diffusion = {S: nu_S})
-    external_force = SpectralExternalForce(vorticity=vorti, dt=dt,
+    external_force = SpectralExternalForce(name='Fext', 
+                                   vorticity=vorti, dt=dt,
                                    Fext=Fext, Finf=True,
                                    implementation=impl,
                                    variables={vorti: npts, S: npts},
diff --git a/hysop/backend/device/opencl/operator/external_force.py b/hysop/backend/device/opencl/operator/external_force.py
index 64238517609fb787715242becdee0a8daadaaceb..b76699dde788ead88a38d997a955466648d25576 100644
--- a/hysop/backend/device/opencl/operator/external_force.py
+++ b/hysop/backend/device/opencl/operator/external_force.py
@@ -1,19 +1,28 @@
 
 import primefac, functools
+import sympy as sm
+
 from hysop.tools.numerics import float_to_complex_dtype
 from hysop.tools.numpywrappers import npw
 from hysop.tools.decorators  import debug
 from hysop.tools.warning import HysopWarning
-from hysop.operator.base.external_force import SpectralExternalForceOperatorBase
-from hysop.backend.device.opencl.opencl_symbolic import OpenClSymbolic
+from hysop.tools.types import first_not_None, to_tuple, check_instance
 from hysop.core.graph.graph import op_apply
 from hysop.core.memory.memory_request import MemoryRequest
+from hysop.fields.continuous_field import Field, ScalarField
+from hysop.parameters.tensor_parameter import TensorParameter
+from hysop.parameters.scalar_parameter import ScalarParameter
 from hysop.backend.device.opencl.opencl_fft import OpenClFFT
+from hysop.backend.device.opencl.opencl_symbolic import OpenClSymbolic
 from hysop.backend.device.codegen.base.variables import dtype_to_ctype
 from hysop.symbolic import local_indices_symbols
 from hysop.symbolic.relational import Assignment
 from hysop.symbolic.complex import ComplexMul
 from hysop.symbolic.misc import Select
+from hysop.symbolic.field import AppliedSymbolicField, SymbolicField, curl, laplacian
+from hysop.symbolic.parameter import SymbolicScalarParameter
+from hysop.symbolic.spectral import AppliedSpectralTransform
+from hysop.operator.base.external_force import ExternalForce, SpectralExternalForceOperatorBase
 
 class OpenClSpectralExternalForce(SpectralExternalForceOperatorBase, OpenClSymbolic):
     """
@@ -21,14 +30,290 @@ class OpenClSpectralExternalForce(SpectralExternalForceOperatorBase, OpenClSymbo
     """
     
     @debug
-    def __init__(self, **kwds):
-        super(OpenClSpectralExternalForce, self).__init__(**kwds)
+    def __init__(self, Fext, **kwds):
+        check_instance(Fext, SymbolicExternalForce)
+        super(OpenClSpectralExternalForce, self).__init__(Fext=Fext, **kwds)
 
-    @debug
-    def setup(self, work):
-        super(OpenClSpectralExternalForce, self).setup(work)
 
-    @op_apply
-    def apply(self, **kwds):
-        super(OpenClSpectralExternalForce, self).apply(**kwds)
+
+class SymbolicExternalForce(ExternalForce):
+    def __init__(self, name, Fext, diffusion=None, **kwds):
+        """
+        Specify an external force as a tuple of symbolic expressions.
+
+        2D ExternalForce example:  
+            1) Fext = -rho*g*e_y where rho is a field and g a constant
+                Fext = (0, -rho.s()*g)
+            2) Fext = (Rs*S+C)*e_y
+                Fext = (0, -Rs*S.s()+C.s())
+        """
+        Fext = to_tuple(Fext)
+        dim = len(Fext)
+
+        Fext = list(Fext)
+        for i,e in enumerate(Fext):
+            if isinstance(e, type(None)):
+                Fext[i] = 0 # curl(const) = 0
+            msg='Expression "{}" contains a SymbolicField, did you forget to apply it ?'
+            msg=msg.format(e)
+            if isinstance(e, sm.Basic):
+                assert not e.atoms(SymbolicField), msg
+        Fext = tuple(Fext)
+
+        super(SymbolicExternalForce, self).__init__(name=name, dim=dim, Fext=Fext, **kwds)
+        
+        diffusion = first_not_None(diffusion, {})
+        for (k,v) in diffusion.iteritems():
+            assert k in self.input_fields(), k.short_description()
+            assert isinstance(v, ScalarParameter)
+        self._diffusion = diffusion
+
+    def input_fields(self):
+        return set(map(lambda f: f.field, self._extract_objects(AppliedSymbolicField)))
+    def output_fields(self):
+        return set(self._diffusion.keys())
+    def input_params(self):
+        p0 = set(map(lambda p: p.parameter, self._extract_objects(SymbolicScalarParameter)))
+        p1 = set(self._diffusion.values())
+        return p0.union(p1)
+    def output_params(self):
+        return set()
+    
+    def initialize(self, op):
+        tg = op.new_transform_group()
+        fft_fields = tuple(self.input_fields())
+        forward_transforms  = {}
+        backward_transforms = {}
+        for Si in fft_fields:
+            Fi = tg.require_forward_transform(Si)
+            if (Si in self.diffusion):
+                Bi = tg.require_backward_transform(Si)
+            else:
+                Bi = None
+            forward_transforms[Si]  = Fi
+            backward_transforms[Si] = Bi
+
+        force_backward_transforms = {}
+        for Fi in op.force.fields:
+            force_backward_transforms[Fi] = tg.require_backward_transform(Fi,
+                    custom_input_buffer='B0')
+
+        frame = None
+        fft_expressions = ()
+        for e in self.Fext:
+            if isinstance(e, sm.Basic):
+                efields = tuple(e.atoms(AppliedSymbolicField))
+                for sf in efields:
+                    field = sf.field
+                    assert field in forward_transforms, field.name
+                    if field in self._diffusion:
+                        assert field in backward_transforms, field.name
+                replace = {sf:forward_transforms[sf.field].s for sf in efields}
+                frame = replace.values()[0].frame
+                e = e.xreplace(replace)
+            fft_expressions += (e,)
+        
+        if (frame is None):
+            msg='Could not extract frame from expressions.'
+            raise RuntimeError(frame)
+        fft_expressions = to_tuple(curl(fft_expressions, frame))
+        
+        self.tg = tg
+        self.forward_transforms  = forward_transforms
+        self.backward_transforms = backward_transforms
+        self.force_backward_transforms = force_backward_transforms
+        self.fft_expressions = fft_expressions
+        self.compute_required_kernels(op)
+
+    def compute_required_kernels(self, op):
+        dts  = op.dt.s
+        forces = op.force.s()
+        diffusion_kernels = {}
+        for (f, nu) in self.diffusion.iteritems():
+            nus = nu.s
+            kname = 'filter_diffusion_{}d_{}'.format(f.dim, f.name)
+            Ft = self.forward_transforms[f]
+            Fs = Ft.output_symbolic_array('{}_hat'.format(f.name))
+            E = 0
+            Wn = self.tg.push_expressions(laplacian(Ft.s, Ft.s.frame))
+            for Wi in Wn:
+                Wi = self.tg._indexed_wave_numbers[Wi]
+                E += Wi
+            expr = Assignment(Fs, Fs / (1 - nus*dts*E))
+            op.require_symbolic_kernel(kname, expr)
+            diffusion_kernels[f] = kname
+        
+        force_kernels = ()
+        vorticity_kernels = ()
+        names = ('dWx', 'dWy', 'dWz')
+        assert len(op.vorticity.fields)==len(op.force.fields)==len(self.fft_expressions)<=len(names)
+        for (Fi,Wi,name,e) in zip(
+                op.force.fields,
+                op.vorticity.fields, 
+                names, self.fft_expressions):
+            if (e==0):
+                force_kernels     += (None,)
+                vorticity_kernels += (None,)
+                continue
+            
+            Fis    = Fi.s()
+            Fi_hat = self.force_backward_transforms[Fi]
+            Fi_buf = Fi_hat.input_symbolic_array('{}_hat'.format(name))
+            Wn     = self.tg.push_expressions(Assignment(Fi_hat, e))
+            
+            msg='Could not extract transforms.'
+            try:
+                transforms = e.atoms(AppliedSpectralTransform)
+            except AttributeError:
+                raise RuntimeError(msg)
+            assert len(transforms)>=1, msg
+
+            fft_buffers = {Ft: Ft.output_symbolic_array('{}_hat'.format(Ft.field.name))}
+            wavenumbers = {Wi: self.tg._indexed_wave_numbers[Wi] for Wi in Wn}
+
+            replace = {}
+            replace.update(fft_buffers)
+            replace.update(wavenumbers)
+            expr = e.xreplace(replace)
+            expr = Assignment(Fi_buf, expr)
+            
+            kname = 'compute_{}'.format(name)
+            op.require_symbolic_kernel(kname, expr)
+            force_kernels += (kname,)
+
+            Wis = Wi.s()
+            expr = Assignment(Wis, Wis + dts*Fis)
+            kname = 'update_{}'.format(Wi.var_name)
+            op.require_symbolic_kernel(kname, expr)
+            vorticity_kernels += (kname,)
+
+        assert len(diffusion_kernels) == len(self.diffusion)
+        assert len(force_kernels) == op.vorticity.nb_components == len(vorticity_kernels)
+        self.diffusion_kernel_names = diffusion_kernels
+        self.force_kernel_names     = force_kernels
+        self.vorticity_kernel_names = vorticity_kernels
+
+    def discretize(self, op):
+        pass
+    
+    def get_mem_requests(self, op):
+        requests = {}
+        for (Ft, Bt) in zip(self.forward_transforms.values(), 
+                            self.backward_transforms.values()):
+            if (Bt is not None):
+                assert (Ft.backend is Bt.backend)
+                assert (Ft.output_dtype == Bt.input_dtype), (Ft.output_dtype, Bt.input_dtype)
+                assert (Ft.output_shape == Bt.input_shape), (Ft.output_shape, Bt.input_shape)
+            shape = Ft.output_shape
+            dtype = Ft.output_dtype
+            request = MemoryRequest(backend=self.tg.backend, dtype=dtype, 
+                                    shape=shape, nb_components=1,
+                                    alignment=op.min_fft_alignment)
+            name = '{}_hat'.format(Ft.field.name)
+            requests[name] = request
+        return requests
+    
+    def pre_setup(self, op, work):
+        for (Ft, Bt) in zip(self.forward_transforms.values(), 
+                            self.backward_transforms.values()):
+            dtmp, = work.get_buffer(op, '{}_hat'.format(Ft.field.name))
+            Ft.configure_output_buffer(dtmp)
+            if (Bt is not None):
+                Bt.configure_input_buffer(dtmp)
+
+    def post_setup(self, op, work):
+        diffusion_kernels = {}
+        force_kernels     = {}
+        vorticity_kernels = {}
+        ghost_exchangers  = {}
+
+        queue = self.tg.backend.cl_env.default_queue
+        def build_launcher(knl, update_params):
+            def kernel_launcher(knl=knl, update_params=update_params, queue=queue):
+                kwds = update_params()
+                return knl(queue=queue, **kwds)
+            return kernel_launcher
+        
+        for (field, kname) in self.diffusion_kernel_names.iteritems():
+            dfield = op.get_discrete_field(field)
+            knl, update_params = op.symbolic_kernels[kname]
+            diffusion_kernels[field] = build_launcher(knl, update_params)
+            ghost_exchangers[field] = dfield.build_ghost_exchanger(queue=queue)
+        
+        for i, (kname0, kname1) in enumerate(zip(
+            self.force_kernel_names, self.vorticity_kernel_names)):
+            if (kname0 is None):
+                assert (kname1 is None)
+                continue
+            Wi  = op.vorticity[i]
+            Fi  = op.force[i]
+            dWi = op.dW[i]
+            
+            knl, update_params = op.symbolic_kernels[kname0]
+            force_kernels[(Fi,Wi)]  = build_launcher(knl, update_params)
+            
+            knl, update_params    = op.symbolic_kernels[kname1]
+            vorticity_kernels[(Fi,Wi)] = build_launcher(knl, update_params)
+
+            ghost_exchangers[Wi] = dWi.build_ghost_exchanger(queue=queue)
+
+        def compute_statistics():
+            pass
+        self.compute_statistics = compute_statistics
+
+    def apply(self, op, **kwds):
+        for (field, Ft) in self.forward_transforms.iteritems():
+            evt = Ft()
+            if (field in self.backward_transforms):
+                evt = self.diffusion_kernels[field]()
+                evt = self.backward_transforms[field]()
+                evt = self.ghost_exchangers[field]()
+        
+        for (Fi,Wi) in self.force_kernels.keys():
+            evt = self.force_kernel[Wi]()
+            evt = self.force_backward_transforms[Fi]()
+            if op.compute_statistics:
+                evt = self.compute_statistics()
+            evt = self.vorticity_kernels[Wi]()
+            evt = self.ghost_exchangers[Wi]()
+
+    def _extract_objects(self, obj_type):
+        objs = set()
+        for e in self.Fext:
+            try:
+                objs.update(e.atoms(obj_type))
+            except AttributeError:
+                pass
+        return objs
+
+    def short_description(self):
+        return 'SymbolicExternalForce[name={}]'.format(self.name)
+    
+    def long_description(self):
+        sep = '\n      *'
+        expressions = sep + sep.join('F{} = {}'.format(x,e) for (x,e) in zip('xyz',self.Fext))
+        diffusion = sep + sep.join('{}: {}'.format(f.pretty_name, p.pretty_name) 
+                                        for (f,p) in self.diffusion.iteritems())
+        input_fields  = ', '.join(f.pretty_name for f in self.input_fields())
+        output_fields = ', '.join(f.pretty_name for f in self.output_fields())
+        input_params  = ', '.join(p.pretty_name for p in self.input_params())
+        output_params = ', '.join(p.pretty_name for p in self.output_params())
+        
+        ss = \
+        '''SymbolicExternalForce:
+    name:          {}
+    pretty_name:   {}
+    expressions:   {}
+    diffusion:     {}
+    -----------------
+    input_fields:  {}
+    output_fields: {}
+    input_params:  {}
+    output_params: {}
+        '''.format(self.name, self.pretty_name, 
+                expressions, diffusion, 
+                input_fields, output_fields,
+                input_params, output_params)
+        return ss
+
 
diff --git a/hysop/core/graph/computational_operator.py b/hysop/core/graph/computational_operator.py
index 5c3e192bfb7612fc6d6e8284b194e88b346ac01a..3adf180d71a44e4969a73475e17408f1e8b78d06 100644
--- a/hysop/core/graph/computational_operator.py
+++ b/hysop/core/graph/computational_operator.py
@@ -515,7 +515,10 @@ class ComputationalGraphOperator(ComputationalGraphNode):
 
         Automatically honour temporary field memory requests.
         """
+        self.allocate_tmp_fields(work)
         super(ComputationalGraphOperator, self).setup(work)
+
+    def allocate_tmp_fields(self, work):
         for dfield in self.discrete_fields:
             if dfield.is_tmp:
                 req_id = 'tmp_{}_{}'.format(dfield.name, dfield.tag)
diff --git a/hysop/operator/base/external_force.py b/hysop/operator/base/external_force.py
index 0fd15eaee75424528f6ce35d7bb06fa7b86b5115..f33c0d4b2b1868911cfaf09cd33e3a4c274c115a 100644
--- a/hysop/operator/base/external_force.py
+++ b/hysop/operator/base/external_force.py
@@ -1,21 +1,79 @@
 
-
-
-from abc import abstractmethod
+from abc import ABCMeta, abstractmethod
 from hysop.constants import SpectralTransformAction
 from hysop.tools.numpywrappers import npw
 from hysop.tools.types import check_instance, first_not_None, to_tuple
 from hysop.tools.decorators import debug
 from hysop.tools.numerics import float_to_complex_dtype
-from hysop.fields.continuous_field import Field
+from hysop.fields.continuous_field import Field, ScalarField
 from hysop.operator.base.spectral_operator import SpectralOperatorBase
 from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 from hysop.fields.continuous_field import Field
 from hysop.symbolic.relational import Assignment
 from hysop.core.memory.memory_request import MemoryRequest
-from hysop.operator.external_force import ExternalForce
 from hysop.parameters.tensor_parameter import TensorParameter
 from hysop.parameters.scalar_parameter import ScalarParameter
+from hysop.tools.interface import NamedObjectI
+
+class ExternalForce(NamedObjectI):
+    """Interface to implement a custom external force."""
+    __metaclass__ = ABCMeta
+
+    def __init__(self, name, dim, Fext, **kwds):
+        super(ExternalForce, self).__init__(name=name, **kwds)
+
+        check_instance(dim, int)
+        self._dim  = dim
+        self._Fext = Fext
+
+        for f in self.input_fields():
+            msg='Expected field dimension to be {} but got {} from field {}.'
+            msg=msg.format(dim, f.dim, f.short_description())
+            assert f.dim == dim, msg
+
+    
+    @property
+    def Fext(self):
+        return self._Fext
+    @property
+    def dim(self):
+        return self._dim
+    @property
+    def diffusion(self):
+        return self._diffusion
+
+    @abstractmethod
+    def input_fields(self):
+        pass
+    @abstractmethod
+    def output_fields(self):
+        pass
+    @abstractmethod
+    def input_params(self):
+        pass
+    @abstractmethod
+    def output_params(self):
+        pass
+
+    @abstractmethod
+    def initialize(self, op):
+        pass
+    @abstractmethod
+    def discretize(self, op):
+        pass
+    @abstractmethod
+    def get_mem_requests(self, op):
+        pass
+    @abstractmethod
+    def pre_setup(self, op, work):
+        pass
+    @abstractmethod
+    def post_setup(self, op, work):
+        pass
+    @abstractmethod
+    def apply(self, op, **kwds):
+        pass
+
 
 class SpectralExternalForceOperatorBase(SpectralOperatorBase):
     """
@@ -34,11 +92,11 @@ class SpectralExternalForceOperatorBase(SpectralOperatorBase):
         vorticity:    2          1        |    3          3
 
         What is computed:
-            tmp  = curl(Fext) by using a spectral backend
-            Fmin = min(tmp)
-            Fmax = max(tmp)
+            force  = curl(Fext) by using a spectral backend
+            Fmin = min(force)
+            Fmax = max(force)
             Finf = max(abs(Fmin), abs(Fmax))
-            W   += dt*tmp
+            W   += dt*force
         
         where Fext is computed from user given ExternalForce.
 
@@ -115,14 +173,20 @@ class SpectralExternalForceOperatorBase(SpectralOperatorBase):
             assert Fmin.shape==pshape, msg.format(Finf.shape, 'Finf')
             Finf = Finf.view((0,)) if (dim==2) else Finf
 
+        compute_statistics  = (Fmin is not None)
+        compute_statistics |= (Fmax is not None)
+        compute_statistics |= (Finf is not None)
+
         # input and output fields
         input_fields = Fext.input_fields()
+        check_instance(input_fields,  set, values=ScalarField)
         input_fields.add(vorticity)
 
         output_fields = Fext.output_fields()
+        check_instance(output_fields, set, values=ScalarField)
         output_fields.add(vorticity)
 
-        input_params  = Fext.input_params()
+        input_params = Fext.input_params()
         input_params.add(dt)
 
         output_params = Fext.output_params()
@@ -133,6 +197,12 @@ class SpectralExternalForceOperatorBase(SpectralOperatorBase):
         output_fields = {f: self.get_topo_descriptor(variables, f) for f in output_fields}
         input_params  = {p.name: p for p in input_params}
         output_params = {p.name: p for p in output_params}
+        
+        # TODO share tmp buffers for the whole tensor
+        force = vorticity.tmp_like(name='Fext', ghosts=0)
+        for (Fi, Wi) in zip(force.fields, vorticity.fields):
+            input_fields[Fi]  = self.get_topo_descriptor(variables, Wi)
+            output_fields[Fi] = self.get_topo_descriptor(variables, Wi)
 
         super(SpectralExternalForceOperatorBase, self).__init__(
                 input_fields=input_fields, output_fields=output_fields, 
@@ -140,18 +210,47 @@ class SpectralExternalForceOperatorBase(SpectralOperatorBase):
                 **kwds)
 
         self.vorticity = vorticity
-        self.Fext = Fext
+        self.Fext  = Fext
+        self.force = force
+        self.dt = dt
+
         self.dim = dim
         self.w_components = w_components
         self.f_components = f_components
+        
         self.Fmin = Fmin
         self.Fmax = Fmax
         self.Finf = Finf
+        self.compute_statistics  = compute_statistics
+        
+################### 
+# from now on, we delegate everything to the ExternalForce implementation
+###################
+    def initialize(self, **kwds):
+        if self.initialized:
+            return
+        self.Fext.initialize(self)
+        return super(SpectralExternalForceOperatorBase, self).initialize(**kwds)
         
     @debug
     def discretize(self):
         if self.discretized:
             return
         super(SpectralExternalForceOperatorBase, self).discretize()
-        self.dW = self.get_input_discrete_field(self.vorticity)
+        self.dW     = self.get_input_discrete_field(self.vorticity)
+        self.Fext.discretize(self)
+
+    def get_work_properties(self):
+        requests = super(SpectralExternalForceOperatorBase, self).get_work_properties()
+        for (name, request) in self.Fext.get_mem_requests(self).iteritems():
+            requests.push_mem_request(name, request)
+        return requests
+    
+    def setup(self, work):
+        self.Fext.pre_setup(self, work)
+        super(SpectralExternalForceOperatorBase, self).setup(work)
+        self.Fext.post_setup(self, work)
+    
+    def apply(self, **kwds):
+        self.Fext.apply(self, **kwds)
 
diff --git a/hysop/operator/base/spectral_operator.py b/hysop/operator/base/spectral_operator.py
index 4d7a1a666b68e51d5608c902c6a353d2036adffd..ba5a2782877075878ea2d0c5e38adfdebf3c7a5b 100644
--- a/hysop/operator/base/spectral_operator.py
+++ b/hysop/operator/base/spectral_operator.py
@@ -220,6 +220,7 @@ class SpectralOperatorBase(object):
 
 
     def setup(self, work):
+        self.allocate_tmp_fields(work)
         for tg in self.transform_groups.values():
             tg.setup(work=work)
         super(SpectralOperatorBase, self).setup(work=work)
@@ -1125,12 +1126,6 @@ class PlannedSpectralTransform(object):
 
         self._backend = dfield.backend
         
-        # bind field buffer to input or output
-        if is_forward:
-            self.configure_input_buffer(dfield.sbuffer[dfield.compute_slices])
-        else:
-            self.configure_output_buffer(dfield.sbuffer[dfield.compute_slices])
-        
         if self.DEBUG:
             def axis_format(info):
                 prefix='\n'+' '*4
@@ -1526,6 +1521,13 @@ class PlannedSpectralTransform(object):
             TMP, = work.get_buffer(op, TMP_tag, handle=True) 
         except ValueError:
             TMP = None
+        
+        # bind field buffer to input or output
+        dfield = self.dfield
+        if is_forward:
+            self.configure_input_buffer(dfield.sbuffer[dfield.compute_slices])
+        else:
+            self.configure_output_buffer(dfield.sbuffer[dfield.compute_slices])
 
         # bind group buffer to input or output if required.
         custom_input_buffer  = self._custom_input_buffer
diff --git a/hysop/operator/external_force.py b/hysop/operator/external_force.py
index bb77ad21c5bfa09be1ba8c136a8ae342c36d7c51..0709bbfbe17bb743a2cce46336f34a92358116b4 100644
--- a/hysop/operator/external_force.py
+++ b/hysop/operator/external_force.py
@@ -1,10 +1,6 @@
 
-import sympy as sm
-from abc import ABCMeta, abstractmethod
 from hysop.constants import Implementation
-from hysop.fields.continuous_field import Field
-from hysop.symbolic.field import AppliedSymbolicField, SymbolicField
-from hysop.symbolic.parameter import SymbolicScalarParameter
+from hysop.fields.continuous_field import Field, ScalarField
 from hysop.parameters.tensor_parameter import TensorParameter
 from hysop.parameters.scalar_parameter import ScalarParameter
 from hysop.operator.min_max import MinMaxFieldStatisticsBase
@@ -12,131 +8,8 @@ from hysop.core.graph.computational_node_frontend import ComputationalGraphNodeF
 from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 from hysop.tools.types import first_not_None, to_tuple, check_instance
 from hysop.tools.sympy_utils import nabla
-from hysop.tools.interface import NamedObjectI
 from hysop.tools.decorators import debug
 
-class ExternalForce(NamedObjectI):
-    __metaclass__ = ABCMeta
-
-    def __init__(self, name, dim, Fext, **kwds):
-        super(ExternalForce, self).__init__(name=name, **kwds)
-
-        check_instance(dim, int)
-        self._dim  = dim
-        self._Fext = Fext
-
-        for f in self.input_fields():
-            msg='Expected field dimension to be {} but got {} from field {}.'
-            msg=msg.format(dim, f.dim, f.short_description())
-            assert f.dim == dim, msg
-
-    
-    @property
-    def Fext(self):
-        return self._Fext
-    @property
-    def dim(self):
-        return self._dim
-    @property
-    def diffusion(self):
-        return self._diffusion
-
-    @abstractmethod
-    def input_fields(self):
-        pass
-    @abstractmethod
-    def output_fields(self):
-        pass
-    @abstractmethod
-    def input_params(self):
-        pass
-    @abstractmethod
-    def output_params(self):
-        pass
-
-
-class SymbolicExternalForce(ExternalForce):
-    def __init__(self, name, Fext, diffusion=None, **kwds):
-        """
-        Specify an external force as a tuple of symbolic expressions.
-
-        2D ExternalForce example:  
-            1) Fext = -rho*g*e_y where rho is a field and g a constant
-                Fext = (0, -rho.s()*g)
-            2) Fext = (Rs*S+C)*e_y
-                Fext = (0, -Rs*S.s()+C.s())
-        """
-        Fext = to_tuple(Fext)
-        dim = len(Fext)
-
-        Fext = list(Fext)
-        for i,e in enumerate(Fext):
-            if isinstance(e, (type(None), int, long, float)):
-                Fext[i] = 0 # curl(const) = 0
-            msg='Expression "{}" contains a SymbolicField, did you forget to apply it ?'
-            msg=msg.format(e)
-            if isinstance(e, sm.Basic):
-                assert not e.atoms(SymbolicField), msg
-        Fext = tuple(Fext)
-
-        super(SymbolicExternalForce, self).__init__(name=name, dim=dim, Fext=Fext, **kwds)
-        
-        diffusion = first_not_None(diffusion, {})
-        for (k,v) in diffusion.iteritems():
-            assert k in self.input_fields(), k.short_description()
-            assert isinstance(v, ScalarParameter)
-        self._diffusion = diffusion
-
-    def input_fields(self):
-        return set(map(lambda f: f.field, self._extract_objects(AppliedSymbolicField)))
-    def output_fields(self):
-        return set(self._diffusion.keys())
-    def input_params(self):
-        p0 = set(map(lambda p: p.parameter, self._extract_objects(SymbolicScalarParameter)))
-        p1 = set(self._diffusion.values())
-        return p0.union(p1)
-    def output_params(self):
-        return set()
-
-    def _extract_objects(self, obj_type):
-        objs = set()
-        for e in self.Fext:
-            try:
-                objs.update(e.atoms(obj_type))
-            except AttributeError:
-                pass
-        return objs
-
-    def short_description(self):
-        return 'SymbolicExternalForce[name={}]'.format(self.name)
-    
-    def long_description(self):
-        sep = '\n      *'
-        expressions = sep + sep.join('F{} = {}'.format(x,e) for (x,e) in zip('xyz',self.Fext))
-        diffusion = sep + sep.join('{}: {}'.format(f.pretty_name, p.pretty_name) 
-                                        for (f,p) in self.diffusion.iteritems())
-        input_fields  = ', '.join(f.pretty_name for f in self.input_fields())
-        output_fields = ', '.join(f.pretty_name for f in self.output_fields())
-        input_params  = ', '.join(p.pretty_name for p in self.input_params())
-        output_params = ', '.join(p.pretty_name for p in self.output_params())
-        
-        ss = \
-        '''SymbolicExternalForce:
-    name:          {}
-    pretty_name:   {}
-    expressions:   {}
-    diffusion:     {}
-    -----------------
-    input_fields:  {}
-    output_fields: {}
-    input_params:  {}
-    output_params: {}
-        '''.format(self.name, self.pretty_name, 
-                expressions, diffusion, 
-                input_fields, output_fields,
-                input_params, output_params)
-        return ss
-
 
 class SpectralExternalForce(ComputationalGraphNodeFrontend):
     """
@@ -217,6 +90,7 @@ class SpectralExternalForce(ComputationalGraphNodeFrontend):
             Fext has 3 components
             Expected parameters are TensorParameter of shape (3,)
         """
+        from hysop.operator.base.external_force import ExternalForce
         base_kwds = first_not_None(base_kwds, {})
         check_instance(vorticity, Field)
         check_instance(Fext, ExternalForce)
diff --git a/hysop/operators.py b/hysop/operators.py
index ba66000fe6664c7af26e9a90903dd32859a1db8a..a68cb3fe02ae2a862f0e03abd348e451f6b5eac4 100644
--- a/hysop/operators.py
+++ b/hysop/operators.py
@@ -32,7 +32,8 @@ from hysop.operator.min_max import MinMaxFieldStatistics,
 
 from hysop.operator.gradient import Gradient, MinMaxGradientStatistics
 from hysop.operator.curl     import Curl, SpectralCurl
-from hysop.operator.external_force import SymbolicExternalForce, SpectralExternalForce
+from hysop.operator.external_force import SpectralExternalForce
+from hysop.backend.device.opencl.operator.external_force import SymbolicExternalForce
 
 from hysop.numerics.splitting.strang import StrangSplitting
 from hysop.operator.directional.symbolic_dir   import DirectionalSymbolic