Problem with UserExpression while updating code to DOLFINx

[EDITED BASED ON COMMENTS] I having some trouble updating some code from legacy Fenics to DOLFINx. In the code a temperature field is defined using a Karhunen-Loeve transformation via UserExpression. From reading other posts I understand that UserExpression is no longer supported in DOLFINx but I still haven’t found a way to reformulate the expression.

Here is an example of the problem using a mean temperature field.

from mpi4py import MPI
import numpy as np
import dolfinx
import ufl
import matplotlib.pyplot as plt
from petsc4py.PETSc import ScalarType

#WORKING FEA CODE
x_0 = 0
x_l = 10
Num_elements = 100

Bar_mesh = dolfinx.mesh.create_interval(comm=MPI.COMM_WORLD, nx=Num_elements, points=. [x_0, x_l])
V = dolfinx.fem.FunctionSpace(Bar_mesh, ("Lagrange", 1))
Bar_boundary_elements = dolfinx.mesh.locate_entities_boundary(Bar_mesh, dim=0, marker=lambda x:
      np.logical_or(np.isclose(x[0], x_0), np.isclose(x[0], x_l)))

boundary_dofs = dolfinx.fem.locate_dofs_topological(V=V, entity_dim=0, entities=Bar_boundary_elements)
bc = dolfinx.fem.dirichletbc(value=ScalarType(0), dofs=boundary_dofs, V=V)

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
x = ufl.SpatialCoordinate(Bar_mesh)

a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
T = .5 * 10 * (x[0] - 5) ** 2 + 200
L = 0.0032 * v.dx(0) * T * ufl.dx

problem = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
Solution = problem.solve()

x_loc = np.linspace(x_0, x_l, Num_elements+1)
t = .5 * 10 * (x_loc - 5) ** 2 + 200

fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 1, 1)
ax1.plot(x_loc, Solution.x.array.real, label='FE Solution')
ax1.legend(loc='lower left')
ax1.set_xlabel("Bar Location", fontsize=14)
ax1.set_ylabel("Displacement", fontsize=14)
ax11 = ax1.twinx()
ax11.plot(x_loc, t, color="red", label='Temperature Field')
ax11.legend(loc='lower right')
ax11.set_ylabel("Temperature", color="red", fontsize=14)
plt.show()

image

However for my problem I want to incorporate a random temperature field using a Karhunen-Loeve transformation, in place of T = .5 * 10 * (x[0] - 5) ** 2 + 200. In the legacy version of Fenics this was done as follows using a UserExpression.

fkls = np.zeros(self.nmesh)
    for j in range(0, self.ndim):
        fkls += np.sqrt(self.wlam[j]) * self.vhat[:, j] * self.yhat[isim, j]

class MyExpression0(UserExpression):
    def __init__(self, b, nmesh, **kwargs):
        super().__init__(**kwargs)
         self.A = b
         self.nmesh = nmesh

    def eval(self, value, x):
        lh = (xL - x0) / 2.
        i1 = np.min([np.floor(x[0] / deltax).astype(int), self.nmesh - 1])
        value[0] = .5 * 10 * (x[0] - lh) ** 2 + 200 + self.A * fkls[i1]

    def value_shape(self):
        return ()

T = MyExpression0(Acoeff, self.nmesh, degree=2)

However since UserExpression is depreciated in DOLFINx I’m unsure how to make the change. Any help would be appreciated!

ACCEPTED SOLUTION - with help from nate

#CREAT RANDOM TEMPURATURE FIELD
def inputUncertaintyLHS(coords, nmesh, ndim, Npt):
    x0 = coords[0]
    xL = coords[1]
    x = np.linspace(x0, xL, nmesh)
    k0 = .25
    R = np.exp(-k0 * x ** 2)
    fcov = np.zeros([nmesh, nmesh])
    flim = np.array([-3, 3])  # upper and lower bound for scaling Chebychev roots
    # to underlying input uncertainty
    for j in range(0, nmesh):
        for j1 in range(j, nmesh):
            ixi = j1 - j
            fcov[j, j1] = R[ixi]
     fcov = fcov + np.transpose(fcov) - np.eye(nmesh) * np.diag(fcov)
    (wlam, vhat) = np.linalg.eig(fcov)
    wlam = np.real(wlam)  # these should be real b/c fcov is SPD matrix but due to numerical
    vhat = np.real(vhat)  # errors there are very small imaginary values that i'm eliminating here
    mfac = np.zeros(nmesh)
    wsum = np.sum(wlam)
    for j in range(0, nmesh):
        mfac[j] = np.sum(wlam[0:j]) / wsum
    pts = np.random.rand(Npt, ndim)
    yhat = (flim[1] - flim[0]) * pts + flim[0]
    mux = 0.0
    sigx = 1.0
    yhat = ((yhat - mux) / sigx).astype(np.float32)
    vhat = vhat[:, 0:ndim]
    return (yhat, wlam, vhat)

Acoeff = 32
Num_dim = 4
Num_pt = 1

(yhat, wlam, vhat) = inputUncertaintyLHS([x_0, x_l], Num_elements, Num_dim, Num_pt)

fkls = np.zeros(Num_elements)
for n in range(0, 4):
    fkls += np.sqrt(wlam[n]) * vhat[:, n] * yhat[0, n]


def my_expression(x):
    lh = (x_l - x_0) / 2.
    deltax = (x_l - x_0) / Num_elements
    i1 = np.minimum(np.floor(x[0] / deltax).astype(int), Num_elements - 1).astype(int)
    return .5 * 10 * (x[0] - lh) ** 2 + 200 + Acoeff * fkls[i1]

T_new= dolfinx.fem.Function(V)
T_new.interpolate(my_expression)

L = 0.0032 * v.dx(0) * T_new * ufl.dx
problem = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly",    "pc_type": "lu"})
Solution = problem.solve()

x_loc = np.linspace(x_0, x_l, Num_elements+1)
fig1 = plt.figure()
ax1 = fig1.add_subplot(1, 1, 1)
ax1.plot(x_loc, Solution.x.array.real, label='FE Solution')
ax1.legend(loc='lower left')
ax1.set_xlabel("Bar Location", fontsize=14)
ax1.set_ylabel("Displacement", fontsize=14)
plt.show()

image

Can you provide a minimal working example? We don’t know what nmesh, deltax, Acoeff or fkls are. So I made it up…

Hopefully this gives you an idea of a way to approach your issue if nothing else.

from mpi4py import MPI
import numpy as np
import dolfinx


mesh = dolfinx.mesh.create_unit_interval(MPI.COMM_WORLD, 32)
V = dolfinx.fem.FunctionSpace(mesh, ("CG", 1))
u = dolfinx.fem.Function(V)

nmesh = 2.0
xL = 1.0
x0 = 0.5
deltax = 1.0
fkls = np.linspace(0, 100)
A = 1.0

def my_expression0(x):
    lh = (xL - x0) / 2.
    i1 = np.minimum(np.floor(x[0] / deltax).astype(int), nmesh - 1).astype(int)
    return .5 * 10 * (x[0] - lh) ** 2 + 200 + A * fkls[i1]

u.interpolate(my_expression0)

x = u.function_space.tabulate_dof_coordinates()
x_order = np.argsort(x[:,0])

import matplotlib.pyplot as plt
plt.plot(x[x_order, 0], u.x.array[x_order])
plt.show()

image

1 Like

Thanks for the help. Your answer was able to point me to a solution. I’ll edit my original post to include the minimum working work so it can serve as a better reference to others in the future.