Beginner struggels with object-oriented programming to compute a time-, space- and u-dependent source term

Dear community,

The minimum working code example is based on the ft03_heat.py tutorial, which describes a time dependent poisson equation and is governed by the following equations:

u’=Δu+f in the unit square
u=uD on the boundary (Dirichlet boundary conditions)
u=u0 at t=0

I tried to implement the time-, space and u-dependend source term f by the following code

# Define source term f
class MySourceConcentration(Expression):
    def __init__(self, t, u):
        self.t = None
        self.u = None
        
    def eval(self, value, t, x, u, area_old):
        # Calculate the source term f for t>0
        if t>0:
            if x[0] >= 0.0 and x[0] <= 0.1:
                # Calculate average of u for the domain x>=0 and x<=0.1
                u_avg = assemble(u*dx) / assemble(Constant(1.0)*dx)
            
                # Calculate the surface area of a particle (equation is made up for demonstrationen purposes without physical correctness) 
                area = area_0 - dt * sqrt(aera_old) * (1 - u_avg)
                
                # Calculate source term f
                value[0] = Constant1 * area * (Constant2 - u_avg)
            
                # set area to area old for next time step
                area_old = area
            # for the rest of the domain: source term f=0    
            else:
                value[0] = 0
                
            return value[0]
        
        # Set the source term f for t=0 to initial value f_0 (to provide a physical sound example, ... 
        # ...there has also to be a differentiation regarding the space, but to reduce the complexity of the example, I just set f=f_0 for the whole domain)
        else:
            value[0]=f_0
        return value[0]
    
    def value_shape(self):
        return ()

and called the source term f by the following code line

f = MySourceConcentration(t, u_n)

But no matter how I change the code, I get a error message similar to this one:

Traceback (most recent call last):
  File "20200416_ft03_heat_f_class.py", line 94, in <module>
    f = MySourceConcentration(t, u_n)
  File "20200416_ft03_heat_f_class.py", line 7, in __init__
    self.t = 0
  File "/usr/local/lib/python3.6/dist-packages/dolfin/function/expression.py", line 438, in __setattr__
    elif name in self._parameters:
  File "/usr/local/lib/python3.6/dist-packages/dolfin/function/expression.py", line 432, in __getattr__
    return self._parameters[name]
  File "/usr/local/lib/python3.6/dist-packages/dolfin/function/expression.py", line 432, in __getattr__
    return self._parameters[name]
  File "/usr/local/lib/python3.6/dist-packages/dolfin/function/expression.py", line 432, in __getattr__
    return self._parameters[name]
  [Previous line repeated 327 more times]
RecursionError: maximum recursion depth exceeded

After reading the Python documentation to classes and checking the Fenics book and not finding a solution, I thought it’s time to post a thread on the fenics discourse group.

On top of that, I´m not sure if

                # set area to area old for next time step
                area_old = area

of the eval function is appropriated to set area_old for the next time step or in other words, I´m not sure, if the eval function will remember area_old at the beginning of the new time step to calculate area by

area = area_0 - dt * sqrt(aera_old) * (1 - u_avg)

Thanks a lot!

Click to see my full MWCE.
from fenics import *
import numpy as np

# Define source term f
class MySourceConcentration(Expression):
    def __init__(self, t, u):
        self.t = None
        self.u = None
        
    def eval(self, value, t, x, u, area_old):
        # Calculate the source term f for t>0
        if t>0:
            if x[0] >= 0.0 and x[0] <= 0.1:
                # Calculate average of u for the domain x>=0 and x<=0.1
                u_avg = assemble(u*dx) / assemble(Constant(1.0)*dx)
            
                # Calculate the surface area of a particle (equation is made up for demonstrationen purposes without physical correctness) 
                area = area_0 - dt * sqrt(aera_old) * (1 - u_avg)
                
                # Calculate source term f
                value[0] = Constant1 * area * (Constant2 - u_avg)
            
                # set area to area old for next time step
                area_old = area
            # for the rest of the domain: source term f=0    
            else:
                value[0] = 0
                
            return value[0]
        
        # Set the source term f for t=0 to initial value f_0 (to provide a physical sound example, ... 
        # ...there has also to be a differentiation regarding the space, but to reduce the complexity of the example, I just set f=f_0 for the whole domain)
        else:
            value[0]=f_0
        return value[0]
    
    def value_shape(self):
        return ()

T = 2.0            # final time
num_steps = 10     # number of time steps
dt = T / num_steps # time step size
alpha = 3          # parameter alpha
beta = 1.2         # parameter beta

Constant1 = 1
Constant2 = 10
C_S = 5
area_0 = 5
area_old = area_0
f_0 = 4

# Create mesh and define function space
nx = ny = 8
mesh = UnitSquareMesh(nx, ny)
space_dim = mesh.geometry().dim()
V = FunctionSpace(mesh, 'P', 1)

# Create classes for defining parts of the boundaries and the interior
# of the domain

class Obstacle_left(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.0, 0.1)))
    
# Initialize sub-domain instances
obstacle_left = Obstacle_left()

# Initialize mesh function for interior domains
domains = MeshFunction("size_t", mesh, space_dim-1)
obstacle_left.mark(domains, 1)
    
# Define new measures associated with the interior domains
dx = Measure('dx', domain=mesh, subdomain_data=domains)

vtkfile = File("20200416_ft03_heat_f_class/solution.pvd")

# Define boundary condition
u_D = Expression('1 + x[0]*x[0] + alpha*x[1]*x[1] + beta*t',
                 degree=2, alpha=alpha, beta=beta, t=0)

def boundary(x, on_boundary):
    return on_boundary

bc = DirichletBC(V, u_D, boundary)

# Define initial value
u_n = interpolate(u_D, V)

# Define variational problem
t=0
u = TrialFunction(V)
v = TestFunction(V)
f = MySourceConcentration(t, u_n)

F = u*v*dx + dt*dot(grad(u), grad(v))*dx - (u_n + dt*f[0])*v*dx
a, L = lhs(F), rhs(F)

# Time-stepping
u = Function(V)

for n in range(num_steps):

    # Update current time
    t += dt

    # Compute solution
    solve(a == L, u, bc)
    vtkfile << (u, t)

    # Update previous solution
    u_n.assign(u)

Your are missing a super init in the expression class, see:

1 Like

Dear Cou

assemble(u*dx) is integrating over the whole domain, not only between x\in [0,0.1]. I believe you want to declare a function, without any class, but for a part of the domain. Since the domain is 2-D, space_dim=2 so your domains is a list of lines not surfaces. Consider using

domains = MeshFunction("size_t", mesh, space_dim) 
dx = Measure('dx', domain=mesh, subdomain_data=domains)
obstacle_left.mark(domains, 1)

u_avg = assemble(u*dx(1)) / assemble(Constant(1.0)*dx(1))
area = area_0 - dt * sqrt(aera_old) * (1 - u_avg)
f = Constant1 * area * (Constant2 - u_avg)

Best, Emek
http://bilenemek.abali.org

Thanks a lot @dokken and @bilenemek!

I implemented the super init, but was not able to get the MWCE from the first post running. Hence, I simplified the example and was able to realize a time dependent UserExpression, but I´m struggeling to implement an additional dependence of space.

That’s my current class, I´m using:

# Define source term f
class MySourceConcentration(UserExpression):
    def __init__(self, t, x, **kwargs):
        super().__init__(**kwargs)
        self.t = t
        self.x = x
        
    def eval(self, values, t, x):
        # Set source term f
        if self.t >= 0 and self.t <= 1:
            if self.x[0]>0 and self.x[0]<=0.5:
                values[0] = 200
            else:
                values[0] = 20  
            
        elif self.t > 1 and self.t <= 2:
            if self.x[0]>0 and self.x[0]<=0.5:
                values[0] = 800
            else:
                values[0] = 20

Later on, I call MySourceConcentration by

x = SpatialCoordinate(mesh)
f = MySourceConcentration(t, x, degree=1)

and I get the following Error message

WARNING: user expression has not supplied value_shape method or an element. Assuming scalar element.
Solving linear variational problem.
Traceback (most recent call last):
  File "20200513_ft03_heat_f_class.py", line 92, in <module>
    solve(a == L, u, bc)
  File "/usr/local/lib/python3.6/dist-packages/dolfin/fem/solving.py", line 220, in solve
    _solve_varproblem(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/dolfin/fem/solving.py", line 247, in _solve_varproblem
    solver.solve()
  File "/usr/local/lib/python3.6/dist-packages/dolfin/function/expression.py", line 53, in wrapped_eval
    self.user_expression.eval(values, x)
TypeError: eval() missing 1 required positional argument: 'x'

I guess it has something to do with self.x[0], because I just want to use the x_1-axis values of x, but I was not able to sort out the right python expression.

Can you give my any advice?



@bilenemek:
Thanks for your advice. In order to create my simplified example, I defined the following classes

# Create classes for defining parts the interior of the domain
class Obstacle_left(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.0, 0.5)))
    
class Obstacle_right(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.5, 1.0)))
    
# Initialize sub-domain instances
obstacle_left = Obstacle_left()
obstacle_right = Obstacle_right()

# Initialize mesh function for interior domains
domains = MeshFunction("size_t", mesh, space_dim-1)
obstacle_left.mark(domains, 1)
obstacle_right.mark(domains, 3)
    
# Define new measures associated with the interior domains
dx = Measure('dx', domain=mesh, subdomain_data=domains)

and extended the weak formulation to

F = u*v*dx + dt*dot(grad(u), grad(v))*dx - u_n*v*dx - dt*f*v*dx(1) - dt*f*v*dx(3) 




Thanks,
Cou

Click to see my simplified MWCE
from fenics import *
import numpy as np

# Define source term f
class MySourceConcentration(UserExpression):
    def __init__(self, t, x, **kwargs):
        super().__init__(**kwargs)
        self.t = t
        self.x = x
        
    def eval(self, values, t, x):
        # Set source term f
        if self.t >= 0 and self.t <= 1:
            if self.x[0]>0 and self.x[0]<=0.5:
                values[0] = 200
            else:
                values[0] = 20  
            
        elif self.t > 1 and self.t <= 2:
            if self.x[0]>0 and self.x[0]<=0.5:
                values[0] = 800
            else:
                values[0] = 20
            
    
T = 2.0            # final time
num_steps = 10     # number of time steps
dt = T / num_steps # time step size

# Create mesh and define function space
nx = ny = 16
mesh = UnitSquareMesh(nx, ny)
space_dim = mesh.geometry().dim()
V = FunctionSpace(mesh, 'P', 1)

# Create classes for defining parts the interior of the domain
class Obstacle_left(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.0, 0.5)))
    
class Obstacle_right(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.5, 1.0)))
    
# Initialize sub-domain instances
obstacle_left = Obstacle_left()
obstacle_right = Obstacle_right()

# Initialize mesh function for interior domains
domains = MeshFunction("size_t", mesh, space_dim-1)
obstacle_left.mark(domains, 1)
obstacle_right.mark(domains, 3)
    
# Define new measures associated with the interior domains
dx = Measure('dx', domain=mesh, subdomain_data=domains)

vtkfile = File("20200513_ft03_heat_f_class/solution.pvd")

# Define boundary condition
u_D = Constant(1)

def boundary(x, on_boundary):
    return on_boundary

bc = DirichletBC(V, u_D, boundary)

# Define initial value
u_n = interpolate(u_D, V)

# Define variational problem
t = 0
u = TrialFunction(V)
v = TestFunction(V)

x = SpatialCoordinate(mesh)
f = MySourceConcentration(t, x, degree=1)

F = u*v*dx + dt*dot(grad(u), grad(v))*dx - u_n*v*dx - dt*f*v*dx(1) - dt*f*v*dx(3) 
a, L = lhs(F), rhs(F)

# Time-stepping
u = Function(V)

for n in range(num_steps):

    # Update current time
    t += dt
    
    # Compute solution
    solve(a == L, u, bc)
    vtkfile << (u, t)
    
    # u_avg for fast post processing
    vertex_values_u = u.compute_vertex_values(mesh)
    u_avg = np.average(vertex_values_u)
    print('t = %.2f: u_avg = %.7f' % (t, u_avg))
    
    # Update previous solution
    u_n.assign(u)

The issue is that you have now mixed the methods eval and eval_cell, as well as adding the t argument to the eval_cell input, which is stored by the class.
The following works:

from dolfin import *

mesh = UnitSquareMesh(10,10)
class MySourceConcentration(UserExpression):
    def __init__(self, t, **kwargs):
        super().__init__(**kwargs)
        self.t = t


    def eval(self, values, x):
        # Set source term f
        if self.t >= 0 and self.t <= 1:
            if x[0]>0 and x[0]<=0.5:
                values[0] = 200
            else:
                values[0] = 20

        elif self.t > 1 and self.t <= 2:
            if x[0]>0 and x[0]<=0.5:
                values[0] = 800
            else:
                values[0] = 20

    def value_shape(self):
        return ()
t = 0.1
f = MySourceConcentration(t, degree=1)
V = FunctionSpace(mesh, "CG", 1)
v = project(f,V)
1 Like

Thanks @dokken for your ongoing support. Without your help, this wouldn´t be possible.

I was able to get the MWCE of the first post running, although I had to use v = TestFunction(V) instead of your suggestion to use v = project(f,V), because this resulted in an error message. Do you see any problem with using v = TestFunction(V) ?

There is one final question. How do persuade :wink: Python to use area_old calculated by the eval method after t=0 to calculate area for the next time step instead of using area_old from line 7 (btw: How is it possible to add line numbers in Discourse)?

from fenics import *
import numpy as np

Constant1 = 1
Constant2 = 10
area_0 = 5
area_old = area_0
f_0 = 4

# Define source term f
class MySourceConcentration(UserExpression):
    def __init__(self, t, u, area_old, **kwargs):
        super().__init__(**kwargs)
        self.t = t
        self.u = u
        self.area_old = area_old
        
    def eval(self, values, x):
        
        # Calculate the source term f for t>0
        if self.t >0:
            if x[0]>0 and x[0]<=0.5:
                # Calculate average of u for the domain x>=0 and x<=0.5
                u_avg = assemble(self.u*dx(1)) / assemble(Constant(1.0)*dx(1))
            
                # Calculate the surface area of a particle (equation is made up for demonstrationen purposes without physical correctness) 
                area = area_0 - dt * sqrt(self.area_old) * (1 - u_avg)
                
                # Calculate source term f
                values[0] = Constant1 * area * (Constant2 - u_avg)
                # set area to area old for next time step
                self.area_old = area
                
            # for the rest of the domain: source term f=0     
            else:
                values[0] = 0  
                
        # Set the source term f for t=0
        else:
            if x[0]>0 and x[0]<=0.5:
                values[0] = f_0
            else:
                values[0] = 0

    def value_shape(self):
        return ()            
    
T = 2.0            # final time
num_steps = 10     # number of time steps
dt = T / num_steps # time step size

# Create mesh and define function space
nx = ny = 16
mesh = UnitSquareMesh(nx, ny)
space_dim = mesh.geometry().dim()
V = FunctionSpace(mesh, 'CG', 1)

# Create classes for defining parts the interior of the domain
class Obstacle_left(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.0, 0.5)))
    
class Obstacle_right(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[0], (0.5, 1.0)))
    
# Initialize sub-domain instances
obstacle_left = Obstacle_left()
obstacle_right = Obstacle_right()

# Initialize mesh function for interior domains
domains = MeshFunction("size_t", mesh, space_dim-1)
obstacle_left.mark(domains, 1)
obstacle_right.mark(domains, 3)
    
# Define new measures associated with the interior domains
dx = Measure('dx', domain=mesh, subdomain_data=domains)

vtkfile = File("20200515_ft03_heat_f_class2/solution.pvd")

# Define boundary condition
u_D = Constant(1)

def boundary(x, on_boundary):
    return on_boundary

bc = DirichletBC(V, u_D, boundary)

# Define initial value
u_n = interpolate(u_D, V)

# Define variational problem
t = 0
u = TrialFunction(V)
v = TestFunction(V)

f = MySourceConcentration(t, u_n, area_old, degree=1)

F = u*v*dx + dt*dot(grad(u), grad(v))*dx - u_n*v*dx - dt*f*v*dx(1) - dt*f*v*dx(3) 
a, L = lhs(F), rhs(F)

# Time-stepping
u = Function(V)

for n in range(num_steps):

    # Update current time
    t += dt
    
    # Compute solution
    solve(a == L, u, bc)
    vtkfile << (u, t)
    
    # u_avg for fast post processing
    vertex_values_u = u.compute_vertex_values(mesh)
    u_avg = np.average(vertex_values_u)
    print('t = %.2f: u_avg = %.7f' % (t, u_avg))
    
    # Update previous solution
    u_n.assign(u)

Thanks a lot!

Best,
Cou