Exterior trace on an internal material interface using dx(skeleton=True)

Hi,

I have a mesh containing two material subdomains, which I will call mechanical and exterior. The displacement test function is only defined on the mechanical subdomain.

I want to assemble

\int_{\partial\Omega_{c}} \left(\boldsymbol{\sigma}_{\mathrm{ext}}\boldsymbol{n}_{\mathrm{mech}}\right) \cdot \boldsymbol{v}_{\mathrm{mech}}\,\mathrm{d}s,

where:

  • \partial\Omega_{c} is the internal interface between the two materials
  • \boldsymbol{\sigma}_{\mathrm{ext}} is evaluated on the exterior side
  • \boldsymbol{n}_{\mathrm{mech}} points outwards from the mechanical subdomain
  • \boldsymbol{v}_{\mathrm{mech}} is the mechanical test-function

The stress is a material-wise discontinuous coefficient function. The relevant part of my current implementation is:

mechanical_other = is_mechanical.Other()

normal = (is_mechanical - mechanical_other) * specialcf.normal(mesh.dim)

transmission_pair = (
    is_mechanical * is_exterior.Other()
    + mechanical_other * is_exterior
)

exterior_stress = (
    is_mechanical * stress.Other()
    + mechanical_other * stress
)

test_on_mechanical = (
    is_mechanical * v
    + mechanical_other * v.Other()
)

rhs = LinearForm(fes)

rhs += (
    transmission_pair
    * InnerProduct(exterior_stress * normal, test_on_mechanical)
    * dx(skeleton=True, bonus_intorder=6)
)

rhs.Assemble()

Here, is_mechanical and is_exterior are material-wise cutoff coefficient functions, and fes is defined only on the mechanical subdomain.

As a check, I created a separate mesh containing only the mechanical domain. The interface then became a genuine external boundary, so I could impose the known traction directly using an ordinary boundary integral. The displacement obtained from that reference problem was very close to the result from the skeleton expression above.

I also compared the direct dx(skeleton=True) expression with:

rhs += SymbolicLFI(
    transmission_pair
    * InnerProduct(exterior_stress * normal, test_on_mechanical),
    VOL,
    skeleton=True,
    bonus_intorder=6,
)

The two expressions produced identical assembled right-hand-side vectors.

Could someone confirm whether the first expression is the recommended way to combine the exterior stress trace, outward mechanical normal and mechanical test-function trace?

In particular, does including both facet orientations in transmission_pair correctly select the required traces, or does it count the interface twice?

Thanks,
Yash

I think yes (its a bit hard from reading code and not testing it :wink: )

I prefer this way:

interface = mesh.Materials("exterior").Boundaries() * mesh.Materials("mechanical").Boundaries()
LinearForm(BoundaryFromVolumeCF(mesh.MaterialCF({"exterior" : stress})) * specialcf.normal(mesh.Materials("mechanical")) * v * ds(interface)).Assemble()

Stress is a gridfunction of another solution or given function right?
specialcf.normal(domain) gives you outward pointing normal of domain
with BoundaryFromVolume you get (a random) volume side. With the inner MaterialCF you defiine the stress only on one side and then the chosen one is the one where it is definedon.
Best
Christopher

Thanks Christopher, that is very helpful!

Best,
Yash

Related to Yash’s query, I have a problem involving an interface between two materials and an integral on the interface involving the normal component of a H(curl) conforming grid function from the object side (output is an eigensolution of a eigenvalue problem). I though it is possible to get the same result by following what Christopher has suggested, assembling a Linear form and then taking the inner product with the grid function as computing the integral on the interface directly:

interface=mesh.Materials(“air”).Boundaries().mesh.Materials(“object”).Boundaries()
vonint=BoundaryFromVolumeCF(mesh.MaterialCF({“object”:v}))
L = LinearForm(fes)
L += SymbolicLFI(InnerProduct(vonint,specialcf.normal(mesh.Materials(“object”))),definedon=interface)
gfu = GridFunction(fes)
with TaskManager():
L.Assemble()
for k in range(len(evals)):
gfu.vec.data = evecs[k]
gonint=BoundaryFromVolumeCF(mesh.MaterialCF({“object”:gfu}))
I1=InnerProduct(L.vec, gfu.vec)
I2=Integrate(InnerProduct(gonint,specialcf.normal(mesh.Materials(“object”))),mesh, definedon=interface,order=Order+1)
print(k,evals[k],I1,I2)

But I different results for I1 and I2, I get I1 is always 0 which I don’t think is correct (this is for a mesh just of tetrahedra, the object is a unit sphere in a big box). Ultimately I want a constraint so the I1 form is useful.

e.g.

0 -1.0030354274466133e-07 0.0 5.586047458635731e-06
1 -9.02473977591743e-08 0.0 -3.158150848210359e-05
2 -4.20618434141008e-08 0.0 0.0001740815373588658

Is there something specific to be aware of in this case?

BoundaryFromVolume does not work with test/trial functions inside a symbolic form. only with GridFunctions/CFs. Reason is that this would need to change which dofs act in the integrator and so on. But it should be possible to implement such a thing using skeleton/element boundary forms with facet indicators if you really need a form.
I think this is what you mean, but if not can you maybe post a MWE of your problem?

best

Thanks Christopher. Yes, I was able to create a skelton/element form with facet indicators that produce a linear form, which, when combined with the grid function through an inner product produces the same result as the Integrate command.