Affine transformation: Translation X-Axis of Function

Hello,

I would like to perform a geometric affine transformation of a Function u0, like a translation along the x-axis by a specific value b.

The FunctionSpace and the mesh are fixed, only the function (with unknown expression) would “translate on the FunctionSpace”. It would also be great if the part of the shifted function that lies outside the range of the mesh, could be mapped/“looped” at the beginning of the mesh:

Ideally, I would like to avoid solving a PDE as I plan on integrating this into an optimization with a large number of iterations.

Do some of you have any suggestions?

I tried something but don’t manage to make it work as desired… I’ve tried to map the coordinates of the translated Degrees Of Freedom (DOF) to the ones of the initial FunctionSpace.

from dolfin import *                                                             
from dolfin_adjoint import *  
import numpy as np                                                               
import matplotlib.pyplot as plt  

# Mesh information
n_elem = 16 
xmin, xmax = -0.5, 0.5
ymin, ymax = -0.25, 0.25
lx, ly = xmax-xmin , ymax-ymin

mesh = RectangleMesh(Point(xmin,ymin),Point(xmax,ymax),n_elem,n_elem)                                      
V = FunctionSpace(mesh, "CG", 2)   

# Initial Function (expression not known in practice)
expr_init = Expression("sin(pi*x[0])*cos(pi*x[1])",degree=3)                                    
u_0 = interpolate(expr_init, V)                                                          

n = V.dim()                                                                      
d = mesh.geometry().dim()                                                        

dof_V_coordinates = V.tabulate_dof_coordinates()               
dof_V_coordinates.resize((n, d)) 
dof_V_x = dof_V_coordinates[:, 0] 
dof_V_y = dof_V_coordinates[:, 1]

# Shift spatially along x the dofs
b = lx/4.0 #shift along x-axis
shifted_dofs = dof_V_x + b
idx_outside = np.where(shifted_dofs>xmax)[0]
shifted_dofs[idx_outside] -= lx # The dofs that are outside the bounds of the mesh are "looped" to its begining (xmax --> xmin) 

# Build the map of the dof from the inital field to the shifted one
dof_map_0to1 = np.array([])
idx_shifted_dofs = list(range(len(shifted_dofs)))
for idx in idx_shifted_dofs:
    idx_shiftedInMain = np.argmin(np.linalg.norm(dof_V_x - shifted_dofs[idx])) # Find the closest initial dof to the current shifted one 
    dof_map_0to1 = np.append(dof_map_0to1, idx_shiftedInMain)

u_1 = Function(V)
u_1.vector()[:] = u_0.vector()[dof_map_0to1]

# Plot the functions for comparison
pl, ax = plt.subplots(); fig = plt.gcf(); fig.set_size_inches(16, 4)
plt.subplot(1, 2, 1); p = plot(u_0,title='Initial Distribution',mode='color',vmin=-1.0,vmax=1.0); cbar = plt.colorbar(p);
plt.subplot(1, 2, 2); p = plot(u_1,title='Shifted Distribution',mode='color',vmin=-1.0,vmax=1.0); cbar = plt.colorbar(p);

This is what I obtained (which is obviously wrong):


And this is what I would like to achieve:

Thank you for your time.

You might be able to use a UserExpression, as follows:

class ShiftedExpr(UserExpression):
    def eval(self,values,x):
        x0_shift = x[0] - b
        if(x0_shift < xmin):
            x0_shift += lx
        x_shift = array([x0_shift,x[1]])
        values[0] = expr_init(x_shift)
    def value_shape(self):
        return ()
u_1 = interpolate(ShiftedExpr(),V)
1 Like

Thank you very much for your answer.
This works perfectly if I already know expr_init.

In my case, I just used it to create an initial Function. I would like to perform this affine transformation to an unknown Function (that results from some operations, I don’t know the Expression corresponding to it).

Do you have some suggestions if I want to apply this affine transformation to u_0 directly instead of expr_init ?

EDIT:
I’ve managed to find the errors in my previous code.

  • The norm representing the distances between the shifted dof and the initial ones were wrongly computed.
  • The created map should represent the indexes of the shifted dof in the referential of the initial one (the opposite was done before).

And I’ve also added a tolerance along the x-axis as the translation may result in non-perfectly aligned coordinates
Here is the full code corrected:

from dolfin import *                                                             
from dolfin_adjoint import *  
import numpy as np                                                               
import matplotlib.pyplot as plt  

# Mesh information
n_elem = 16
xmin, xmax = -0.5, 0.5
ymin, ymax = -0.25, 0.25
lx, ly = xmax-xmin , ymax-ymin

mesh = RectangleMesh(Point(xmin,ymin),Point(xmax,ymax),n_elem,n_elem)                                      
V = FunctionSpace(mesh, "CG", 2)   

# Initial Function (expression not known in practice)
expr_init = Expression("sin(pi*x[0])*cos(pi*x[1])",degree=3)                                    
u_0 = interpolate(expr_init, V)                                                          

n = V.dim()                                                                      
d = mesh.geometry().dim()                                                        

dof_V_coordinates = V.tabulate_dof_coordinates()               
dof_V_coordinates.resize((n, d)) 
dof_V_x = dof_V_coordinates[:, 0] 
dof_V_y = dof_V_coordinates[:, 1]

# Determine the minimal distance along x between 2 different dof (that are not on the same x) 
# It is used to create a "tolerance" that is used when comparing distances between points
# For CG elements of degree 1 it is simply: lx_e = lx / (n_elem)
x_diff = np.absolute(dof_V_x-dof_V_x[0]) # idx 0 chosen arbitrarily 
dofs_on_different_x = np.nonzero(x_diff) 
lx_e = np.amin(x_diff[dofs_on_different_x])
x_tol = 1/2*lx_e # tolerance along the x-axis in case the shifted dofs are not perfectly aligned with the initial one

# Shift spatially along x the dofs
b = lx/4.0 #shift along x-axis
x_shifted = dof_V_x + b

idx_shift_outside = np.where(x_shifted>(xmax+x_tol))[0]
x_shifted[idx_shift_outside] -= (lx+lx_e)

shifted_dof_coordinates = np.copy(dof_V_coordinates)
shifted_dof_coordinates[:,0] = x_shifted

# Build the map of the dof from the inital field to the shifted one
dof_map_1in0 = np.array([])
idx_shifted_dofs = list(range(len(x_shifted)))
for idx in idx_shifted_dofs:
    idx_shiftedInMain = np.argmin(np.linalg.norm(dof_V_coordinates[idx,:] - shifted_dof_coordinates, axis=1))
    dof_map_1in0 = np.append(dof_map_1in0, idx_shiftedInMain)

u_1 = Function(V)
u_1.vector()[:] = u_0.vector()[dof_map_1in0]

# Plot the functions for comparison
pl, ax = plt.subplots(); fig = plt.gcf(); fig.set_size_inches(16, 4)
plt.subplot(1, 2, 1); p = plot(u_0,title='Initial Distribution',mode='color',vmin=-1.0,vmax=1.0); cbar = plt.colorbar(p);
plt.subplot(1, 2, 2); p = plot(u_1,title='Shifted Distribution',mode='color',vmin=-1.0,vmax=1.0); cbar = plt.colorbar(p);

Which results in:

1 Like

Sounds like it’s not needed at this point, but I’ll mention that you can directly evaluate Functions at points in the same way as Expressions.

Actually, as the method I use requires to store the map for the dof’s indexes, this may become quite large depending on the application …
So I would be really interested if you could provide a minimal example similar to your previous one, but directly by evaluating the function (and not the expression).

Thank you for your time.

You can make the following replacement in my earlier example:

        #values[0] = expr_init(x_shift)
        values[0] = u_0(x_shift)
2 Likes

Note that @kamensky’s suggestion also works on unstructured meshes and with non-mesh matching shifts, as opposed to working directly with a dof-to-dof mapping

1 Like

Thanks a lot, this is what I was expecting!

In my application, I’m performing an optimization with this affine transformation performed at each iteration. The mesh is regular and the translation-value stays constant, so using the interpolate method at each iteration may significantly increase the computational time…
I think the dof mapping will perform better in this specific case, but I’ll keep in mind your answer if I have to perform this on unstructured meshes or with varying value for the translation.

Thank you again for your help!

How can this operation be accounted for while computing the derivative with dolfin_adjoint?

I get the following error: AttributeError: ‘float’ object has no attribute ‘_cpp_object’
I tried to see how it is commonly done when defining UserExpression as shown in here, but cannot manage to adapt it to my problem …

Here follows a minimal non-working example to show what is desired. You can comment the lines after the plots to have it running without error:

from dolfin import * 
from dolfin_adjoint import *

import matplotlib.pyplot as plt
import numpy as np

lx = 1.0
xmin=0.0
xmax=1.0

mesh = UnitSquareMesh(16,16)
V = FunctionSpace(mesh,'CG', 1)

u_0 = interpolate(Expression("x[0]",degree=3), V)

# Shift spatially along x the dofs
b = lx/2.0 #shift along x-axis

class ShiftedExpr(UserExpression):
    def __init__(self,func,**kwargs):
        super().__init__(**kwargs)
        self.func = func
    def eval(self,values,x):
        x0_shift = x[0] - b
        if(x0_shift < xmin):
            x0_shift += lx
        x_shift = np.array([x0_shift,x[1]])
        values[0] = self.func(x_shift)
    def value_shape(self):
        return ()
    
u_1 = project(ShiftedExpr(u_0),V)

pl, ax = plt.subplots();
fig = plt.gcf()
fig.set_size_inches(16, 4)
plt.subplot(1, 2, 1); p = plot(u_0, title='Initial distribution', mode='color',vmin=0.0,vmax=1.0)
p.set_cmap("viridis"); cbar = plt.colorbar(p); # add a colorbar
plt.subplot(1, 2, 2); p = plot(u_1, title='Shifted distribution', mode='color',vmin=0.0,vmax=1.0)
p.set_cmap("viridis"); cbar = plt.colorbar(p); # add a colorbar

# Comment the following to have it running without error
m0 = Control(u_0)
J = -assemble((u_1**2) *dx)**(1/2) # Random Functional to serve as objective function
Jhat = ReducedFunctional(J,m0)
dJdmh0 = Jhat.derivative()

# Taylor remainder test to verify the computation of the gradient
h = Function(V)
h.vector()[:] = 0.01*(1.0- 0.0)
conv_rate = taylor_test(Jhat,u_0,h) #Uses the previously setted 

Thank you for your help.

Since you are using the expression class, you Need to add the derivative of the operation, see:
http://www.dolfin-adjoint.org/en/release/documentation/time-dependent-wave/time-dependent-wave.html

Yes, this is the example I said I already investigated in my previous message.
But for the expression that performs a spatial shift along the x-axis, it is not as explicit as in the example.

In our case, the derivation should be performed with respect to a Function and not a Constant. Does it mean that a full Jacobian should be provided?

And I can’t seem to figure how to derive mathematically the spatial shift of a function with respect to this same function …

As your expression uses function evaluation inside a custom expression, it requires quite a lot of additional work.

Function evaluation itself is overloaded in dolfin-adjoint. Consider:

from dolfin_adjoint import *

import matplotlib.pyplot as plt
import numpy as np

lx = 1.0
xmin=0.0
xmax=1.0

mesh = UnitSquareMesh(16,16)
V = FunctionSpace(mesh,'CG', 1)

u_0 = interpolate(Expression("x[0]",degree=3), V)
u_0.rename("u_0", "")
# Shift spatially along x the dofs
b = lx/2.0 #shift along x-axis


x = np.array([0.1,0.1])
j = u_0(x)**2
jhat = ReducedFunctional(j, Control(u_0))
h = interpolate(Expression("x[0]*x[1]", degree=3), V)
results = taylor_test(jhat, u_0, h)
print(results)

What is a meaningful functional in your case? Do you need the shifting operation for anything except visualization? (Couldn’t you shift a target function in the opposite direction?)

It may not be explicit in the minimal example, but the desired functional depends on a combination of the initial Function and the shifted one. And the control variable is the initial function.

# u0 a Function, and u0_shifted a spatially shifted version of this function
u_combined = u0 + u0_shifted - 2*u0*u0_shifted
J = assemble((u_combined**2) *dx) 
Jhat = ReducedFunctional(J,Control(u0))

I used a CustomExpression as it was suggested earlier in this topic, but I could change it if there are other ways to create the shifted function based on the initial one (without knowing it beforehand, this initial function is the result of a PDE).

A big question is:
Do you need to assemble the functions, or is it sufficient to do something like this:

J = 0
coords = V.tabulate_dof_coordinates()
for coord in coords:
    x0_shift = coord[0] - b
    if x0_shift < xmin:
        x0_shift += lx
    x_shift = np.array([x0_shift,x[1]])
    J += (u(coord)-u(x_shift))**2

This would be equivalent to computing a the l^2-norm, instead of the L^2 norm of the difference between the original and shifted function.

Yes I need to assemble.
The functional is more complex than in this case. It is composed of various combinations with other variables as well.

I restrained the example to the shift operation only.

And as I am integrating this into an optimization, I try to avoid for loops. The mesh and the shift value do not change between iterations but the function does.

As of now, dolfin-adjoint do not support function evaluation inside an expression.

Alright, thank you for taking time to answer my question