How to apply time varying boundary conditions coming from a function?

I am wondering how I could apply Dirichlet boundary conditions if they come from a complicated function, for example something that interpolates some experimental random data. There is no explicit form for this function (cannot be written as a sin(t)). Instead, when we call it, it yields the desired value.

So, say we have

def complicated_func(t):
    do some things like interpolate linearly random data between different points.
    return desired_value_at_time_t

Then somewhere in the code we would do:

u_D = Expression('complicated_func(t)', complicated_func(t), t=0, degree = 2)
bc = DirichletBC(V, u_D, Top_region())

And in the time loop we would update the function like so:
u_D.t = t

But the problem is that it doesn’t work. I haven’t been able to make it work unless I explicitly write down the time varying expression, like sin(t) for example. Is there a way to make it work for a user-defined function like above? I am guessing yes, but I haven’t found any working example so far.

Edit: After a discussion below, I add a MWE:

from dolfin import *
import numpy as np
import mshr
from scipy.interpolate import InterpolatedUnivariateSpline

def complicated_func(t):
    return t 

cylinder = mshr.Cylinder(Point(0, 0, 0), Point(0, 0, -9), 8.0, 8.0)
domain = cylinder
mesh = mshr.generate_mesh(domain, 20)
V = FunctionSpace(mesh, 'CG', 1)

# Define boundary conditions
tol = 1E-14
class Top(SubDomain):
    def inside(self, x , on_boundary):
       return on_boundary and near(x[2], 0.0, tol)

# Define subdomains. 
# top
class Omega_0(SubDomain):
    def inside(self, x, on_boundary):
        return x[2] >= -6 - tol

# Other region.
class Omega_1(SubDomain):
    def inside(self, x, on_boundary):
        return x[2] <= -6 + tol

# Define the mesh
region = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)

# Use the subdomains to mark the cells belonging to each subdomain
subdomain_0 = Omega_0()
subdomain_1 = Omega_1()
subdomain_0.mark(region, 0)
subdomain_1.mark(region, 1)

# k values of the layers
k_0 = 75
k_1 = 7

# Define the values of kappa in each subdomain
class K(UserExpression):
    def __init__(self, region, k_0, k_1, **kwargs):
        super().__init__(**kwargs)
        self.region = region
        self.k_0 = k_0
        self.k_1 = k_1
    def eval_cell(self, values, x, cell):
        if self.region[cell.index] == 0:
            values[0] = self.k_0
        else:
            values[0] = self.k_1
    def value_shape(self):
        return ()

kappa = K(region, k_0, k_1)

# Apply boundary conditions
u_D0 = Constant(0)
bc0 = [DirichletBC(V, u_D0, Top())]

# Define initial value
u_0 = Constant(5.0)
u_n = interpolate(u_0, V)

# Define trial and test functions
u = TrialFunction(V)
v = TestFunction(V)

F = u*v*dx + kappa*dot(grad(u), grad(v))*dx - u_n*v*dx
a, L = lhs(F), rhs(F)
u = Function(V)

num_steps= 10
t=0
dt=0.1
for n in range(num_steps):
    # Update boundary condition (because it's time-dependent)
    u_D0.assign = complicated_func(t)
    #bc0 = DirichletBC(V, u_D0, Top())
    #print('this line', complicated_func(t))
    print('this line', u_D0.values(), t) # Debug.
    # Update current time
    t += dt

    # Compute solution
    solve(a == L, u, bcs = bc0)
    #print(t, u.vector()[0,0,0] - u.vector()[0,0,-2.0])
    #print('the value at the origin is ', u.vector()[0,0,0])
    #print(u.vector()[:])        
    # Update previous solution
    u_n.assign(u)

So complicated_func(t) returns a FEniCS Function?

In that case you can do u_D.assign(complicated_func(t)) at every time step before applying the BC.

I am not sure what you mean by whether the function returns a FEniCS function. It is simply a Python function, such as an interpolation (with Scipy) of random or almost random datapoints. When you call it, it returns a value, a float for example.

Regarding your last comment, I cannot do that because I am precisely unable to apply the BC!

I see, I thought by “interpolate” you meant that your function included a call to interpolate and returned the resulting function. In my example u_D was a function, sorry.

If complicated_func(t) is just a number, this is easy!

  1. Constant:
# initialize u_D as a Constant, e.g.
u_D = Constant(0)
# in the time loop:
u_D.assign(complicated_func(t))
  1. using Expression:
# init:
u_D = Expression('c', c=complicated_func(0), degree = 2)  # why degree=2 if this is just constant?

# time loop:
u_D.c = complicated_func(t)

Or am I missing something?

1 Like

Thanks for the reply!
Hmm, now at least I get no error, but the problem is that the boundary condition is not updated. It has always the initial value (the one with t=0). When I print(u_D) I always get the same object f_11 in the time loop, over and over.

Do you have a MWE?

f_11 is like the id of the Constant/Expression and shouldn’t change. To print the value of a Constant, do print(u_D.values()).

1 Like

I didn’t make the MWE so far, I might do one if we go into circles.
u_D.values() yields an AttributeError.
The type() of u_D is: <class ‘dolfin.function.expression.Expression’>

Edit: This is when I use the Expression() rather than Constant(). When I use Constant() I get 21.2 over and over which correspond to what I observed, i.e. that it gets the value of the complicated function at t=0 and does not get updated.

Sure, that’s why I said “to print the value of a Constant”. Why are you using the more complicated Expression if a Constant will do in your case? To see the resulting value of the Expression (of course, print(u_D.c) prints the value of the parameter), you’ll have to interpolate it to a function space and print the value of that function (in terms of the dof values or a norm of the function)…

I see, I noticed this a bit late, I edited my previous post. In short, now I see the value of that constant stuck to 21.2 while complicated_func(t) yields different value at each time iteration.

I added a MWE in my original (1st) post.

I didn’t try to run your example, but the line
u_D0.assign = complicated_func(t)
is wrong. You have to to u_D0.assign(complicated_func(t))

1 Like

Hi, I have the exact same problem.
I am trying to solve the heat equation on a 2D problem and I have some experimental data for a boundary T(t).

How can I implement this? My idea is to interpolate the experimental points and then use this as a Dirichlet BC, but im not sure how to do it.

Did you solve this?

As indicated below the original question, yes I have solved the problem following the given tip.

Hi there!

I obtain my 1D boundary conditions for a 2D problem from a formula that gives me a theoretical solution, but I cannot cast this solution in an “Expression” unless I make a fit to obtain an expression in terms of the coordinates, which is cumbersome. Is there no way in Fenics to convert values from space functions directly into boundary-condition values?