Hi @dokken ! thanks for your answer ! The NaN was caused by  || used instead of and in boundary conditions definition. Then I didn’t get the fact that ds element uses a marker meshfunction of size n-1 instead of n, thus the code wasn’t finding the facets needed to apply boundary conditions. Took me a while to figure that out ! All sorted out now.
For information, this code works:
'''
this code simulates 2D heat transfer between flowing water and solid stainless steel (ss).
'''
from fenics import *
from mshr import *
import numpy as np
import matplotlib.pyplot as plt
T = 10.0           # final time
num_steps = 10    # number of time steps
dt = T / num_steps # time step size
#tensors needed for flow equation:
def epsilon(u):
    return sym(nabla_grad(u))
def sigma(u, p):
    return 2*mu*epsilon(u) - p*Identity(len(u))
#domain and subdomains definition as in demo_block-assembly-2D2D-nonlinear.py : https://bitbucket.org/fenics-project/dolfin/src/39253449ee544fe04eb8bf387eab115e094d325e/python/demo/undocumented/block-assembly-2D2D/demo_block-assembly-2D2D-nonlinear.py?at=cecile%2Fmixed-dimensional
domain = Rectangle(Point(0, 0), Point(16,8))
domain_water = Rectangle(Point(0, 0), Point(16,4))
domain_ss = domain-domain_water
domain.set_subdomain(1, domain_water)
domain.set_subdomain(2, domain_ss)
#mesh definition
mesh = generate_mesh(domain,16)
# subdomain markers, facet markers and integration measures
markers = MeshFunction('size_t', mesh, mesh.topology().dim(), mesh.domains())
plot(markers)
facetmarkers = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
dx = Measure("dx", domain=mesh, subdomain_data=markers)
ds = Measure("ds", domain=mesh, subdomain_data=facetmarkers)
print(markers.array(), min(markers.array()), max(markers.array()))
print(facetmarkers.array(), min(facetmarkers.array()), max(facetmarkers.array()))
#physical properties defined as in fenics tutorial ft11 updated : https://github.com/hplgit/fenics-tutorial/issues/65 :
#water viscosity
mu=1  #Pa.s
#Density :
class Density(UserExpression):
    def __init__(self, markers, **kwargs):
        super().__init__(**kwargs)
        self.markers = markers
    def eval_cell(self, values, x, cell):
        if self.markers[cell.index] == 1:
            values[0] = 1. #water, no units
        else:
            values[0] = 7.9 #stainless steel, no units
    def value_shape(self):
            return ()
rho = Density(markers, degree=1)
V = VectorFunctionSpace(mesh, 'P', 2)
Q = FunctionSpace(mesh, 'P', 1)
#DEFINITION OF BOUNDARY CONDITIONS USING SECOND MARKER FUNCTION---------------------------------
#source:https://fenicsproject.discourse.group/t/no-slip-boundary-condition-not-being-applied/3924/2
#inflow  = 'near(x[0], 0, 1E-10) and x[1]<=4+1E-10'
#outflow = 'near(x[0], 0, 1E-10) and x[1]<=4+1E-10'
#walls   = 'near(x[1], 0, 1E-10) || near(x[1], 4, 1E-10)'
# Sub domain for walls (mark whole boundary, inflow and outflow will overwrite)
class Walls(SubDomain):
    def __init__(self, markers, **kwargs):
        super().__init__(**kwargs)
        self.markers = markers
    def inside(self, x, on_boundary):
        return on_boundary or near(x[1], 4, 1E-10)
# Sub domain for inflow (right)
class Inflow(SubDomain):
    def __init__(self, markers, **kwargs):
        super().__init__(**kwargs)
        self.markers = markers
    def inside(self, x, on_boundary):
        return near(x[0], 0, 1E-10) and x[1]<=4+1E-14 and on_boundary
    
# Sub domain for outflow (left)
class Outflow(SubDomain):
    def __init__(self, markers, **kwargs):
        super().__init__(**kwargs)
        self.markers = markers
    def inside(self, x, on_boundary):
        return x[0] >16-1E-14 and x[1]<=4+1E-14 and on_boundary
# Mark all facets as facetmarkers sub domain 3
facetmarkers.set_all(3)
# Mark no-slip facets as facetmarkers sub domain 0,
walls = Walls(markers)
walls.mark(facetmarkers, 0)
# Mark inflow as facetmarkers sub domain 1,
inflow = Inflow(markers)
inflow.mark(facetmarkers, 1)
# Mark outflow as facetmarkers sub domain 2
outflow = Outflow(markers)
outflow.mark(facetmarkers, 2)
print(facetmarkers.array(), min(facetmarkers.array()), max(facetmarkers.array()))
#inflow  = 'near(x[0], 0, 1E-10) and x[1]<=4+1E-10'
#outflow = 'near(x[0], 0, 1E-10) and x[1]<=4+1E-10'
#walls   = 'near(x[1], 0, 1E-10) and near(x[1], 4, 1E-10)'
bcu_noslip  = DirichletBC(V, Constant((0, 0)), walls)
bcp_inflow  = DirichletBC(Q, Constant(8), inflow)
bcp_outflow = DirichletBC(Q, Constant(0), outflow)
bcu = [bcu_noslip]
bcp = [bcp_inflow, bcp_outflow]
#FUNCTIONS DEFINITION-----------------------------------------------------------------------
u = TrialFunction(V)
v = TestFunction(V)
p = TrialFunction(Q)
q = TestFunction(Q)
u_n = Function(V)
u_  = Function(V)
p_n = Function(Q)
p_  = Function(Q)
U   = 0.5 * (u_n + u)
n   = FacetNormal(mesh)
f   = Constant((0, 0))
k   = Constant(dt)
mu  = Constant(mu)
#IPCS IMPLEMENTATION---------------------------------------------------------------------------
#Source :fenics tutorial demo ft07: https://github.com/gdmcbain/fenics-tuto-in-skfem/commit/3d4803e2fd61a7672cd7d1a4fb4f4ccfedde6084
# Define variational problem for step 1
F1 =rho*dot((u - u_n) / k, v)*dx(1) + \
   rho*dot(dot(u_n, nabla_grad(u_n)), v)*dx(1) \
    + inner(sigma(U, p_n), epsilon(v))*dx(1) \
   + dot(p_n*n, v)*ds - dot(mu*nabla_grad(U)*n, v)*ds \
   - dot(f, v)*dx(1)
a1 = lhs(F1)
L1 = rhs(F1)
a2 = dot(nabla_grad(p), nabla_grad(q))*dx(1)
L2 =dot(nabla_grad(p_n), nabla_grad(q))*dx(1) - (1/k)*div(u_)*q*dx(1)
a3 = dot(u, v)*dx(1)
L3 = dot(u_, v)*dx (1)- k*dot(nabla_grad(p_ - p_n), v)*dx(1)
A1 = assemble(a1, keep_diagonal=True)
A1.ident_zeros()
A2 = assemble(a2, keep_diagonal=True)
A2.ident_zeros()
A3 = assemble(a3, keep_diagonal=True)
A3.ident_zeros()
[bc.apply(A1) for bc in bcu]
[bc.apply(A2) for bc in bcp]
xdmffile_u = XDMFFile('example/navstok.xdmf')
t = 0
for n in range(num_steps):
    t += dt
    b1 = assemble(L1)           # Step 1: Tentative velocity step
    [bc.apply(b1) for bc in bcu]
    solve(A1, u_.vector(), b1)
    print(min(u_.vector().get_local()), max(u_.vector().get_local()))
    
    b2 = assemble(L2)           # Step 2: Pressure correction step
    [bc.apply(b2) for bc in bcp]
    solve(A2, p_.vector(), b2)
    print(min(u_.vector().get_local()), max(u_.vector().get_local()))
    
    b3 = assemble(L3)           # Step 3: Velocity correction step
    solve(A3, u_.vector(), b3)
    print(min(u_.vector().get_local()), max(u_.vector().get_local()))
    
    u_n.assign(u_)
    p_n.assign(p_)
 
    xdmffile_u.write(u_, t)
plot(u_)
plt.show()
link to the Github