Time dependent Dirichlet and Neumann BCs

Hello,
I am wondering how to apply time dependent multiple boundary conditions on dolfinx.

I used to define “Expressions” for both Dirichlet and Neumann time dependent boundary conditions in dolfin Legacy see MWE below:

from dolfin import *
import numpy as np

mesh = IntervalMesh(2, 0, 1)
V = FunctionSpace(mesh, FiniteElement('CG', mesh.ufl_cell(), degree=1))
u = Function(V, name = "displacement")

#Dirichlet BC
final_loading = 1.
loading = Expression("t", t=final_loading, degree=0)

def l_boundary(x, on_boundary):
    return near(x[0], 0.)

current_l_bc = Expression("0.1*t", t=loading, degree=0)
l_bc = DirichletBC(V, current_l_bc, l_boundary)

def r_boundary(x, on_boundary):
    return near(x[0], 1)

current_r_bc = Expression("0.2*t", t=loading, degree=0)
r_bc = DirichletBC(V, current_r_bc, r_boundary)

#Neumann BC
body_f_magnitude = Expression("0.3*t", t=loading, degree=0)
v = TestFunction(V)
L = body_f_magnitude * v * ds

load_steps = np.linspace(0, final_loading, 10)
for t in load_steps:
    loading.t = t
    l_bc.apply(u.vector())
    r_bc.apply(u.vector())
    print("Displacement vector", u.vector().get_local())
    print("Time dependant lhs", assemble(L).get_local())
    print("Loading parameter", loading.t)

So it was very easy to update all the BCs at once inside the time stepping loop writing loading.t = t.

On DolfinX I would also like to set my Dirichlet BC as well as the linear form using a single load parameter “t”. Whereas there is no issue with the lhs, it seems that it’s not possible to set a Dirichlet BC with a product of two Constants see the MWE:

from ufl import dot, TestFunction, inner, ds, FiniteElement
from dolfinx import fem
from dolfinx.fem import Function, assemble, form, Constant, FunctionSpace, locate_dofs_topological, set_bc, assemble_vector
from dolfinx.mesh import create_unit_interval, locate_entities_boundary
import numpy as np
from petsc4py.PETSc import ScalarType
from mpi4py import MPI

mesh = create_unit_interval(MPI.COMM_WORLD, 2)
V = FunctionSpace(mesh, ("CG", 1))
u = Function(V, name = "displacement")
fdim = mesh.topology.dim - 1

#Dirichlet BC
def l_boundary(x):
    return np.isclose(x[0], 0)

def r_boundary(x):
    return np.isclose(x[0], 1)

l_bc_facets = locate_entities_boundary(mesh, fdim, l_boundary)
r_bc_facets = locate_entities_boundary(mesh, fdim, r_boundary)

l_magnitude = Constant(mesh, ScalarType(0.1))
r_magnitude = Constant(mesh, ScalarType(0.2))

final_loading = 1
loading = Constant(mesh, ScalarType(final_loading))

current_l_bc = l_magnitude * loading
current_r_bc = r_magnitude * loading

#What i would like to do
l_bc = fem.dirichletbc(current_l_bc, locate_dofs_topological(V, fdim, l_bc_facets), V)
r_bc = fem.dirichletbc(current_r_bc, locate_dofs_topological(V, fdim, r_bc_facets), V)

#Neumann BC
body_f_magnitude = Constant(mesh, ScalarType(0.3))
v = TestFunction(V)
L = body_f_magnitude * loading * v * ds

load_steps = np.linspace(0, final_loading, 10)
for t in load_steps:
    #So that i would update my Dirichlet BC like this
    loading.value = t
    set_bc(u.vector, [l_bc, r_bc])
    print("Displacement vector", u.vector.array)
    print("Time dependant lhs", assemble_vector(fem.form(L)).array)
    print("Loading parameter", loading.value)

Which raise the following error:


  File "/home/bouteillerp/Bureau/Time_dependent_Bcs_Dolfinx.py", line 41, in <module>
    l_bc = fem.dirichletbc(current_l_bc, locate_dofs_topological(V, fdim, l_bc_facets), V)

  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/bcs.py", line 179, in dirichletbc
    raise AttributeError("Boundary condition value must have a dtype attribute.")

AttributeError: Boundary condition value must have a dtype attribute.

AttributeError: Boundary condition value must have a dtype attribute.

Is there any elegant way to define multiple boundary conditions, parameterized by a single scalar t, without updating all of them manually ?

Thank you

The input here should be a single constant, not a product of Constant. If you want to use a product of constants, I would use a dolfinx.fem.Expression, i.e.

bc_expr = dolfinx.fem.Expression(l_magnitude*loading, points=V.element.interpolation_points())
u_bc.interpolate(bc_expr)
l_bc = fem.dirichletbc(u_bc, locate_dofs_topological(...))

for t in load_steps:
    loading.value = t
    uc_b.interpolate(bc_expr)
    # ...

Thank you for your quick response! Unfortunately, using fem.Expression seems to trigger an error:


  File "/home/bouteillerp/Bureau/Time_dependent_Bcs_Dolfinx.py", line 35, in <module>
    bc_expr =fem.Expression(l_magnitude, loading, V.element.interpolation_points())

  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/function.py", line 101, in __init__
    assert X.ndim < 3

AttributeError: 'Constant' object has no attribute 'ndim'

Associated with the code:

from ufl import dot, TestFunction, inner, ds, FiniteElement
from dolfinx import fem
from dolfinx.fem import Function, assemble, form, Constant, FunctionSpace, locate_dofs_topological, set_bc, assemble_vector
from dolfinx.mesh import create_unit_interval, locate_entities_boundary
import numpy as np
from petsc4py.PETSc import ScalarType
from mpi4py import MPI

mesh = create_unit_interval(MPI.COMM_WORLD, 2)
V = FunctionSpace(mesh, ("CG", 1))
u = Function(V, name = "displacement")
fdim = mesh.topology.dim - 1

#Dirichlet BC
def l_boundary(x):
    return np.isclose(x[0], 0)

def r_boundary(x):
    return np.isclose(x[0], 1)

final_loading = 1
loading = Constant(mesh, ScalarType(final_loading))

l_bc_facets = locate_entities_boundary(mesh, fdim, l_boundary)
l_magnitude = Constant(mesh, ScalarType(0.1))
current_l_bc = l_magnitude * loading

bc_expr =fem.Expression(l_magnitude, loading, V.element.interpolation_points())
u_bc = Constant(mesh, ScalarType(0))
u_bc.interpolate(bc_expr)
l_bc = fem.dirichletbc(u_bc, locate_dofs_topological(V, fdim, l_bc_facets), V)

#What i would like to do
# l_bc = fem.dirichletbc(current_l_bc, locate_dofs_topological(V, fdim, l_bc_facets), V)

load_steps = np.linspace(0, final_loading, 10)
for t in load_steps:
    #So that i would update my Dirichlet BC like this
    loading.value = t
    l_bc.interpolate(bc_expr)
    set_bc(u.vector, l_bc)
    print("Displacement vector", u.vector.array)
    print("Loading parameter", loading.value)

(Also the named parameter “points” seems not to be known?)

I’ve edited my post above, as I had a typo. It now states

Is there anything wrong with my Dolfinx version ? When i try to implement your Expression it throws another error:

  File "/home/bouteillerp/Bureau/Time_dependent_Bcs_Dolfinx.py", line 22, in <module>
    bc_expr = fem.Expression(l_magnitude * loading, V.element.interpolation_points())

  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/fem/function.py", line 117, in __init__
    self._ufcx_expression, module, self._code = jit.ffcx_jit(mesh.comm, (ufl_expression, _X),

  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/jit.py", line 56, in mpi_jit
    return local_jit(*args, **kwargs)

  File "/usr/lib/petsc/lib/python3/dist-packages/dolfinx/jit.py", line 211, in ffcx_jit
    r = ffcx.codegeneration.jit.compile_expressions([ufl_object], parameters=p_ffcx, **p_jit)

  File "/usr/lib/python3/dist-packages/ffcx/codegeneration/jit.py", line 193, in compile_expressions
    ffcx.naming.compute_signature(expressions, _compute_parameter_signature(p)

  File "/usr/lib/python3/dist-packages/ffcx/naming.py", line 62, in compute_signature
    signature = ufl.algorithms.signature.compute_expression_signature(expr, rn)

  File "/usr/lib/python3/dist-packages/ufl/algorithms/signature.py", line 118, in compute_expression_signature
    terminal_hashdata = compute_terminal_hashdata([expr], renumbering)

  File "/usr/lib/python3/dist-packages/ufl/algorithms/signature.py", line 64, in compute_terminal_hashdata
    data = expr._ufl_signature_data_(renumbering)

  File "/usr/lib/python3/dist-packages/ufl/constant.py", line 72, in _ufl_signature_data_
    self._ufl_domain._ufl_signature_data_(renumbering), repr(self._ufl_shape),

  File "/usr/lib/python3/dist-packages/ufl/domain.py", line 121, in _ufl_signature_data_
    return ("Mesh", renumbering[self], self._ufl_coordinate_element)

KeyError: Mesh(VectorElement(FiniteElement('Lagrange', interval, 1), dim=1), 30)
from dolfinx import fem
from dolfinx.fem import Function, assemble, form, Constant, FunctionSpace
from dolfinx.mesh import create_unit_interval
import numpy as np
from petsc4py.PETSc import ScalarType
from mpi4py import MPI

mesh = create_unit_interval(MPI.COMM_WORLD, 2)
V = FunctionSpace(mesh, ("CG", 1))
u = Function(V, name = "displacement")
fdim = mesh.topology.dim - 1

loading = Constant(mesh, ScalarType(1))
l_magnitude = Constant(mesh, ScalarType(0.1))
bc_expr = fem.Expression(l_magnitude * loading, V.element.interpolation_points())
u_bc = Constant(mesh, ScalarType(0))
u_bc.interpolate(bc_expr)

This was fixed in: Error interpolating on sub of vector function - #4 by dokken

Thank you very much, it however seems that the ppa still contains the 0.5.2 version, is it to get updated soon ?

@dparsons would be the one knowing this.

There is a regression in ufl 2023.1 used by dolfinx 0.6. This regression needs to be resolved before 0.6 can be provided on the PPA.

That regression has been resolved?
Ref: Regression in time required for code generation · Issue #552 · FEniCS/ffcx · GitHub

No, the regression breaks old dolfin, not ffcx.

Is there an issue tracking this? Old dolfin does not support ufl past 2021.1.0?

It was discussed on the fenicsx slack channel, but I’ve filed the bug now at ufl 2023.1 breaks old dolfin · Issue #151 · FEniCS/ufl · GitHub

dolfin was able to work with ufl 2022.2.0 with some simple patching (applied to the bitbucket repo). But the 2023 changes to set_cell_domains() mean that patching is not so straightforward now.

I’ve applied the PR#532 patch to ffcx 0.5 in the PPA in any case, which hopefully fixes the problem with Constant expressions reported here.

I also need to use the time-dependent Dirichlet boundary condition. I am using the Dirichlet boundary condition bcs in this standard way NonlinearProblem(F, u, bcs). As I understand it in your provided example, when u_bc is updated, the Dirichlet boundary condition is automatically updated every time the solver calls it, because u_bc is passed by reference in Python. Is this correct? Also, if I already set the Dirichlet boundary condition in the standard way, do I need to call set_bc at every time step as done in this original post? Thank you very much for help.

Yes

Please provide an example, as I dont know what you mean by standard way here

By standard way I just meant setting Dirichlet boundary condition bcs via creating the nonlinear problem, NonlinearProblem(F, u, bcs). The problem I encountered with this standard way is that, say the Dirichlet boundary condition value jumps at a given time, the boundary value of the solution u does not exhibit the jump unless I specifically use set_bc(u, bcs). So I am wondering whether set_bc is required even if bcs has already been set in the NonlinearProblem. This is also the case when the initial condition does not match the Dirichlet boundary condition, in which case the solution’s boundary value will not match the Dirichlet boundary condition value long after the initial time. I don’t understand this behavior because I thought dirichletbc would restrict the solution’s boundary value to the prescribed one no matter what the current solution is.

Without having an example at hand its hard to tell what goes wrong kn your case.

Sorry, I just solved my issue. The problem was that the boundary value of the solution appeared not to match the Dirichlet boundary condition value after the Dirichlet boundary condition value jumps at a given time. I just found that this is due to some solver divergence induced by the abrupt jump of the Dirichlet boundary condition value. I have changed the abrupt jump to a smooth transition, and the solution appeared normal and correct. Thank you!