diff --git a/src/SemiSmoothNewton.jl b/src/SemiSmoothNewton.jl
index 47b41372fbecc66049c15a2184f0bfc212fa6425..eae1c6550f9cb6f6be892a1b22f6253291577616 100644
--- a/src/SemiSmoothNewton.jl
+++ b/src/SemiSmoothNewton.jl
@@ -5,6 +5,8 @@ include("mesh.jl")
 include("function.jl")
 include("operator.jl")
 
+include("run.jl")
+
 function clip_line(r0, r1, l0, l1)
     d_l = -(l1[1] - l0[1])
     d_r = +(l1[1] - l0[1])
diff --git a/src/function.jl b/src/function.jl
index fdfa40de53f4480be323e504c60ad11ff7d09178..8a9295cdd1cca866f613ab947f3163a631f862b9 100644
--- a/src/function.jl
+++ b/src/function.jl
@@ -1,6 +1,6 @@
 using Statistics: mean
 export FeSpace, Mapper, FeFunction, P1, DP0, DP1
-export interpolate!, sample, bind!, evaluate
+export interpolate!, sample, bind!, evaluate, nabla
 
 # Finite Elements
 struct P1 end
@@ -10,7 +10,7 @@ struct DP1 end
 # FIXME: should be static vectors
 # evaluate all (1-dim) local basis functions against x
 evaluate_basis(::P1, x) = [1 - x[1] - x[2], x[1], x[2]]
-evaluate_basis(::DP0, x) = [x]
+evaluate_basis(::DP0, x) = [1]
 evaluate_basis(::DP1, x) = evaluate_basis(P1(), x)
 
 # non-mixed
@@ -36,7 +36,7 @@ function FeSpace(mesh, el::P1, size_=(1,))
 end
 
 function FeSpace(mesh, el::DP0, size_=(1,))
-    # special case for P1: dofs correspond to vertices
+    # special case for DP1: dofs correspond to cells
     rdims = prod(size_)
     ncells = size(mesh.cells, 2)
     dofmap = Array{Int, 3}(undef, rdims, 1, ncells)
@@ -46,6 +46,17 @@ function FeSpace(mesh, el::DP0, size_=(1,))
     return FeSpace(mesh, el, dofmap, rdims * ncells, size_)
 end
 
+function FeSpace(mesh, el::DP1, size_=(1,))
+    # special case for DP1: dofs correspond to vertices
+    rdims = prod(size_)
+    ncells = size(mesh.cells, 2)
+    dofmap = Array{Int, 3}(undef, rdims, 3, ncells)
+    for i in LinearIndices((rdims, 3, ncells))
+	dofmap[i] = i
+    end
+    return FeSpace(mesh, el, dofmap, rdims * 3 * ncells, size_)
+end
+
 Base.show(io::IO, ::MIME"text/plain", x::FeSpace) =
     print("$(nameof(typeof(x))), $(nameof(typeof(x.element))) elements, size $(x.size), $(x.ndofs) dofs")
 
@@ -78,38 +89,54 @@ end
 Base.show(io::IO, ::MIME"text/plain", f::FeFunction) =
     print("$(nameof(typeof(f))), size $(f.space.size) with $(length(f.data)) dofs")
 
-interpolate!(dst::FeFunction, src::Function) = interpolate!(dst, dst.space.element, src)
+interpolate!(dst::FeFunction, expr::Function; params...) = interpolate!(dst, dst.space.element, expr; params...)
 
 
 myvec(x) = vec(x)
 myvec(x::Number) = x
 
-function interpolate!(dst::FeFunction, ::P1, src::Function)
+function interpolate!(dst::FeFunction, ::P1, expr::Function; params...)
+    params = NamedTuple(params)
     space = dst.space
     mesh = space.mesh
     for cell in axes(mesh.cells, 2)
+	for f in params
+	    bind!(f, cell)
+	end
 	for eldof in axes(mesh.cells, 1)
 	    xid = mesh.cells[eldof, cell]
 	    x = mesh.vertices[:, xid]
+	    xloc = [0. 1. 0.; 0. 0. 1.][:, eldof]
+
+	    opvalues = map(f -> evaluate(f, xloc), params)
 
 	    gdofs = space.dofmap[:, eldof, cell]
-	    dst.data[gdofs] .= myvec(src(x))
+	    dst.data[gdofs] .= myvec(expr(x; opvalues...))
 	end
     end
 end
 
-function interpolate!(dst::FeFunction, ::DP0, src::Function)
+function interpolate!(dst::FeFunction, ::DP0, expr::Function; params...)
+    params = NamedTuple(params)
     space = dst.space
     mesh = space.mesh
     for cell in axes(mesh.cells, 2)
+	for f in params
+	    bind!(f, cell)
+	end
 	vertices = mesh.vertices[:, mesh.cells[:, cell]]
 	centroid = mean(vertices, dims = 2)
+	lcentroid = [1/3, 1/3]
+
+	opvalues = map(f -> evaluate(f, lcentroid), params)
 
 	gdofs = space.dofmap[:, 1, cell]
-	dst.data[gdofs] .= myvec(src(centroid))
+	dst.data[gdofs] .= myvec(expr(centroid; opvalues...))
     end
 end
 
+interpolate!(dst::FeFunction, ::DP1, expr::Function; params...) =
+    interpolate!(dst, P1(), expr; params...)
 
 function bind!(f::FeFunction, cell)
     f.ldata .= vec(f.data[f.space.dofmap[:, :, cell]])
@@ -119,11 +146,22 @@ end
 # evaluate at local point (needs bind! call before)
 evaluate(f::FeFunction, x) = evaluate(f.space, f.ldata, x)
 
-struct CellFunction
-    f::FeFunction
-    dofs::Vector{Float64}
+struct Derivative{F}
+    f::F
+end
+
+Base.show(io::IO, ::MIME"text/plain", x::Derivative) =
+    print("$(nameof(typeof(x))) of $(typeof(x.f))")
+
+nabla(f) = Derivative(f)
+
+bind!(df::Derivative, cell) = bind!(df.f, cell)
+function evaluate(df::Derivative, x)
+    jac = jacobian(x -> evaluate(df.f.space, df.f.ldata, x), x)
+    return reshape(jac, df.f.space.size..., :)
 end
 
+
 function sample(f::FeFunction)
     mesh = f.mapper.mesh
     for cell in axes(mesh.cells, 2)
@@ -143,6 +181,7 @@ function sample(f::FeFunction)
 end
 
 
+
 append_data!(vtkfile, f::FeFunction) = append_data!(vtkfile, f, f.space.element)
 
 function append_data!(vtkfile, f::FeFunction, ::P1)
diff --git a/src/run.jl b/src/run.jl
new file mode 100644
index 0000000000000000000000000000000000000000..54f6e0b5272cdd5dee746e85b034a10dbf86e57d
--- /dev/null
+++ b/src/run.jl
@@ -0,0 +1,66 @@
+export myrun
+
+using LinearAlgebra: norm
+
+
+
+function myrun()
+    mesh = init_grid(50)
+    d = 2
+    m = 1
+
+    Vg = FeSpace(mesh, DP0(), (1,))
+    Vu = FeSpace(mesh, P1(), (m,))
+    Vp1 = FeSpace(mesh, DP0(), (1,))
+    Vp2 = FeSpace(mesh, DP1(), (m, d))
+
+    g = FeFunction(Vu)
+    u = FeFunction(Vu)
+    p1 = FeFunction(Vp1)
+    p2 = FeFunction(Vp2)
+    du = FeFunction(Vu)
+    dp1 = FeFunction(Vp1)
+    dp2 = FeFunction(Vp2)
+    nablau = nabla(u)
+    nabladu = nabla(du)
+
+    g.data .= 1
+    u.data .= 0
+    p1.data .= -1.
+    p2.data .= 0
+    du.data .= 0
+    dp1.data .= 0
+    dp2.data .= 0
+
+    alpha1 = 1.
+    alpha2 = 1.
+    beta = 1e-3
+    lambda = 1.
+
+    gamma1 = 1e-3
+    gamma2 = 1e-3
+
+    T(u) = u
+
+    function dp1_update(xloc; g, u, p1, du)
+	m1 = max(gamma1, norm(T(u) - g))
+	cond = norm(T(u) - g) >= gamma1 ?
+	    dot(T(u) - g, T(du)) / norm(T(u) - g)^2 * p1 :
+	    zeros(size(p1))
+	return -p1 + alpha1 / m1 * (T(u + du) - g) - cond
+    end
+
+    interpolate!(p1, dp1_update; g, u, p1, du)
+
+    function dp2_update(xloc; u, nablau, p2, du, nabladu)
+	m2 = max(gamma2, norm(nablau))
+	cond = norm(nablau) >= gamma2 ?
+	    dot(nablau, nabladu) / norm(nablau)^2 * p2 :
+	    zeros(size(p2))
+	return -p2 + lambda / m2 * (nablau + nabladu) - cond
+    end
+
+    interpolate!(p2, dp2_update; u, nablau, p2, du, nabladu)
+
+    return p1, p2
+end