ArityMismatch | Acoustic-Elastic interaction problem

Hi everybody,

I’m trying to implement a simulation for the acoustic-elastic interaction in 2D. Thanks to this community and some articles on the subject, I was able to pose mathematically a simplified version of the problem I want to solve. Unfortunately, I’m still encountering some problems in order to run it on FEniCS. For a start, let us consider a solid domain \Omega_s inside a bounded fluid domain \Omega_a. The domains, boundaries and surface-normal vector are represented in this figure.
untitled(1)

The problem is: given p on \partial B_{left}, find the pressure p and the displacement \mathbf{u} such that
\cases{ \Delta p + \kappa^2 p = 0 \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \text{in } \Omega_a\\ \nabla \mathbf{\sigma(u)} + \omega^2 \mathbf{u} = 0 \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \text{ in } \Omega_s\\ \partial _{\mathbf{n}_1} p = \rho_a \omega^2 \mathbf{n}_1 \cdot \mathbf{u} \text{ and} -p \mathbf{n}_1 = \mathbf{\sigma(u)} \cdot \mathbf{n}_1 \quad \space \space \text{on } \Gamma_s\\ p = 0 \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \space \space \text{on } \partial B \neq \partial B_{left}}

where \kappa is the wave number, \omega the angular frequency, \rho_a the fluid density, \mu and \lambda the Lamè parameters for which \mathbf{\sigma(u)} = 2 \mu \mathbf{\epsilon(u)} + \lambda tr(\mathbf{\epsilon(u)})I and \mathbf{\epsilon(u)} = \frac{1}{2} (\nabla \mathbf{u} + \nabla \mathbf{u} ^T)

The complete weak form should be
\int_{\Omega_a}(\nabla p \cdot \nabla \overline{q} - \kappa^2 p \overline{q})d \mathbf{x} + \int_{\Omega_s}(\mathbf{\sigma(u)} : \nabla \overline{\mathbf{v}} - \omega^2 \mathbf{u} \cdot \overline{\mathbf{v}})d \mathbf{x}
+ \int_{\Gamma_s} \rho_a \omega^2 (\mathbf{n}_1 \cdot \mathbf{u}) \overline{q} ds + \int_{\Gamma_s}(p \mathbf{n}_1) \cdot \overline{\mathbf{v}}ds = 0

Now in FEniCS:

1. Import libraries and define geometrical and material properties

from fenics import *
import matplotlib.pyplot as plt

x_1, y_1 = [-2, -1]
x_2, y_2 = [2, 1]
nx = 200
ny = 200

E, nu = Constant(1e5), Constant(0.)
mu = E/2./(1+nu)
lmbda = E*nu/(1+nu)/(1-2*nu)
rho = Constant(1.2)
omega = 1
k = 15

2. Create the mesh and the appropriate function spaces

mesh = RectangleMesh(Point(x_1, y_1), Point(x_2, y_2), nx, ny, "crossed")
V = FiniteElement("Lagrange", mesh.ufl_cell(), 1)
Vv = VectorElement("Lagrange", mesh.ufl_cell(), 2)
W = FunctionSpace(mesh, V*Vv)

TRF = TrialFunction(W)
TTF = TestFunction(W)
(p, u) = split(TRF)
(q, v) = split(TTF)

3. Define a square subdomain for the solid obstacle and mark the domains and the boundaries

class Obstacle(SubDomain):
    def inside(self, x, on_boundary):
        return (between(x[1], (-0.75, 0.75)) and between(x[0], (0.5, 0.6)))

obstacle = Obstacle()

domains = MeshFunction("size_t", mesh, mesh.topology().dim(), 0)
domains.set_all(0)
obstacle.mark(domains, 1)

boundaries = MeshFunction("size_t", mesh, mesh.topology().dim()-1, 0)
boundaries.set_all(0)
obstacle.mark(boundaries, 1)

4. Define boundary conditions: p |_{\partial B_{left}} = cos(\kappa x_1)

tol = 1E-14
def LEFT(x, on_boundary):
    return on_boundary and abs(x[0] - x_1) < tol

bound_x = Expression(("cos(k*x[0])"), degree=1, k=k)

bcs = [DirichletBC(W.sub(0), Constant(0.0), "on_boundary"),
       DirichletBC(W.sub(0), bound_x, LEFT)]

5. Define the variational problem, solve and plot

dX = Measure('dx', domain=mesh, subdomain_data=domains)
dS = Measure('ds', domain=mesh, subdomain_data=boundaries)

sigma = 2.0*mu*sym(grad(u)) + lmbda*tr(sym(grad(u)))*Identity(u.geometric_dimension())
n = FacetNormal(mesh)

#Fluid domain
g = Constant(0.0)
a_f = (inner(grad(p),grad(q)) - k**2 * p*q)*dX(0)
L_f = inner(g,q)*dS(0)

#Solid domain
T = Constant((0.0, 0.0))
a_s = (inner(sigma, grad(v)) - rho*omega**2*inner(u,v))*dX(1)
L_s = inner(T,v)*dS(1)

#Interface fluid-solid
a_i = (rho*omega**2 * inner(n('+'), u('+'))*q('+') + p*inner(n('+'), v('+')))*dS(1)
L_i = Constant(0.0)*dS(1)

#Weak form
a = a_f + a_s + a_i
L = L_f + L_s + L_i

#Solve
s = Function(W)
solve(a == L, s, bcs)

#Plot
plot(s)
plt.show()

This code produces the following error on solve(a == L, s, bcs)

ArityMismatch: Adding expressions with non-matching form arguments ('v_0',) vs ().

I believe the problem is in the #Interface fluid-solid definition, however I’d need someone more experienced to help me sort it out.
Many thanks for your time!

Hi,
your L_i is missing a TestFunction. Consider changing it to L_i = Constant(0.0)*q*dS(1) for instance.

1 Like

Hi @bleyerj, thank you for taking the time to read my question.

I did as you suggested, but then I got AssertionError: and no message at all. Do you see something else wrong in the code or could it be some hardware limitation?

Your dS measure should be an interior measure "dS" and not “ds", then you need to use appropriate average values using avgor left/right values using(”+")or("-")` for the quantities involved in the external measures.
Also note that with your approach, the system will be ill posed since p is undefined in the interior domain and u in the exterior domain.
For this kind of approach coupling multiphysics problems on subdomains, you should use the mixed dimensional branch or the multiphenics package.

Thanks, I’ll take a closer look at the alternatives you mentioned. I’d have a couple more questions:

I should use "dS" whenever I’m integrating over the interior boundaries and "ds" for the exterior ones. That is, in my case, dS(1) for \Gamma_s and ds(0) for \partial B, right?

When you say

you mean p, \overline{q} and \mathbf{n}_1?
That is:
a_i = (rho*omega**2 * inner(avg(n), u)*avg(q) + p*inner(avg(n), v))*dS(1) ?

Hi Bleyerj,

… ill posed since p is undefined in the interior domain and u in the exterior domain. … use the mixed dimensional branch or the multiphenics package

would a.ident_zeros() also work - in normal Fenics?

regards

Thomas

Hi Luc

"dS" … over the interior boundaries and "ds" for the exterior ones. …dS(1) for Γs and ds(0) for ∂B

yes from my (limited) experience that always works as expected

I think you have a mistake in the way you define Γ_s , don’t you mean this?

class ObstacleEdge(SubDomain):
    def inside(self, x, on_boundary):
        return between(x[1], (-0.75, 0.75)) and between(x[0], (0.5-eps, 0.5+eps)) or between(x[1], (-0.75, 0.75)) and between(x[0], (0.6-eps, 0.6+eps))     or between(x[1], (-0.75-eps, -0.75+eps)) and between(x[0], (0.5-eps, 0.6+eps)) or between(x[1], (0.75-eps, 0.75+eps)) and between(x[0], (0.5-eps, 0.6+eps))
obstacleEdge = ObstacleEdge()
interface = MeshFunction("size_t", mesh, mesh.topology().dim()-1, 0)
interface.set_all(0)
obstacleEdge.mark(interface, 1)

dS = Measure('dS', domain=mesh, subdomain_data=interface)

For info I got your code to run in “normal” fenics by using the above change to the boundary and a.ident_zeros() and adding some (‘+’) and (‘-’) in the parts of the linear form which involved dS.

This is how I use a.ident_zeros(), I have successfully used this before on a similar FSI problem but I would wait for other advice before jumping into this approach - I am just a beginner.

            A=assemble(a, keep_diagonal=True)
            b=assemble(L)
            for bc in bcs: bc.apply(A, b)
            A.ident_zeros()
            s = Function(W)
            solve(A, s.vector(), b)

Is this this the sort of distribution you expect from the code? I don’t understand how you have coupled the two domains in your weak form so I don’t know what to expect. - If you have time it would help me if you could explain how your weak form works at the interface.

test1

regards

Thomas

2 Likes

Hi @Thomas2, thank you for your contribution! It’s always good to see different approaches to the same problem.

Ok, now I got it :+1:

Well, according to what I understood, you’ve defined only the boundary \Gamma_s, and not the domain \Omega_s. I need both as I’m trying to simulate the acoustic-elastic interaction. Please, refer to this video for some insights. Anyway, I adapted my code from here.

More precisely, I need to solve the time-harmonic acoustic wave in \Omega_a, the time-harmonic elastic wave in \Omega_s and the coupling between the two on \Gamma_s. That is, on \Gamma_s, the incoming p should be mapped into \mathbf{u} and vice-versa. I believe this is the issue mentioned by @bleyerj when he says

I thought FEniCS was able to deal with multiphysic couplings natively at first. Now I am reading some tutorials on how to use multiphenics to that end. I should share my code when I get it running properly (which may take some time).

Lastly, the equations I wrote are a simplified version of equations 2.9 and 2.10 from here. I too am a beginner in numerical simulations, so if you or somebody else see any issue in my approach, please let me know.

Hi Luc

Well, according to what I understood, you’ve defined only the boundary Γ_s

Yes, you still need to keep your original definition of the fluid and structure domain - (the one you defined with a MeshFunction of the same dimension as the topology), but I think you intended your definition of the interface ((the one you defined with a MeshFunction of dimension = topology -1 ) to be just the interface; what you did with obstacle.mark(boundaries, 1) was set the marker to be 1 for all edges in the fluid domain - but I think you intended just the edges at the interface.

I need to solve the time-harmonic

your code is solving a static problem, you can extend your approach with a mixed function space (one set of spaces for real and another for imaginary) to get a complex solution.

I thought FEniCS was able to deal with multiphysic couplings natively

It is - as long as both physical phenomena have complete cover of the domain, if they don’t - as in your case - you can either use ident_zero() (works but wastes memory), or one of many alternatives. The mixed dimensional branch is quite easy to pick up but there are many other ways as well.

thanks for the reference to the paper it is useful

Regards, Thomas

1 Like

Thank you for the explanation about the meshing on the interface and ident_zero!

Sorry if it is an elementary question, but why do I need to use a complex space in order to solve my problem? Since I’m only interested in the real part of the solution, doesn’t it work with bound_x = Expression(("cos(k*x[0])")) on the left boundary?

If that’s not the case, I believe I should change my code to

mesh = RectangleMesh(Point(x_1, y_1), Point(x_2, y_2), nx, ny, "crossed")
P1 = FiniteElement("Lagrange", mesh.ufl_cell(), 1)
P2 = VectorElement("Lagrange", mesh.ufl_cell(), 2)
W = FunctionSpace(mesh, P1*P1*P2*P2)

(p_re, p_im, u_re, u_im) = TrialFunction(W)
(q_re, q_im, u_re, u_im) = TestFunction(W)

bound_x_re = Expression((" cos(k*x[0])"), degree=1, k=k)
bound_x_im = Expression(("-sin(k*x[0])"), degree=1, k=k)

bcs = [DirichletBC(W.sub(0), bound_x_re, LEFT),
       DirichletBC(W.sub(1), bound_x_im, LEFT)]

However, doing so would imply changing the whole weak form as to account for p_{re}, p_{im}, \mathbf{u}_{re} and \mathbf{u}_{im}, wouldn’t it?

Thank you for your help.

Hi Luc,

Actually you might be OK with just the real part for that sample problem you showed - as long as you don’t have any damping or other means to dissipate energy (like a gravity wave), however the reference you gave earlier had a Sommerfield boundary condition (via PML), won’t that give you in phase and out of phase parts to p and u?

Sorry I can’t be more helpful, I am not an expert on this and I don’t want to set you off in the wrong direction.

regards

Thomas

1 Like

Hey I know it’s been two years, but would you be able to provide this full working code? I’m not quite able to connect the dots using your comments about " some (’+’) and (’-’) ".

Brad

Hi Brad, I will try to look at it what I did a couple of weeks time, give me a nudge if I forget.

1 Like