Apply Multipoint constraint in Mixedelement Functionspace

Hi,
I am performing frequency domain analysis of a piezoelectric cube model. The boundary conditions for the mechanical part: prescribed displacement at the left face,
electrical part: grounded or zero voltage at the bottom face, and floating potential(equal voltage at all nodes on this face). I am currently struggling to apply the multipoint constraint or the floating potential BC so that nodes in this region have equal voltage values. I have written the multipoint constraint part in the code below, but I doubt the implementation part to achieve what I wanted. can someone kindly go through the implementation and suggest to me the necessary changes.

import dolfinx
import numpy as np
from mpi4py import MPI

import dolfinx.fem as fem
from petsc4py import PETSc
from ufl import (Circumradius, FacetNormal, SpatialCoordinate, TrialFunction,
                 TestFunction, div, ds, grad, inner,dot, nabla_grad, nabla_div,Identity,dot, dx)
from dolfinx.cpp.mesh import CellType, locate_entities_boundary
from dolfinx.mesh import MeshTags, locate_entities
from dolfinx.mesh import locate_entities_boundary
from dolfinx.fem import (Constant, DirichletBC, Function,NonlinearProblem, LinearProblem, FunctionSpace, VectorFunctionSpace, 
                         locate_dofs_topological,assemble_scalar)
import ufl
from ufl import (Form, SpatialCoordinate, VectorElement, FiniteElement, TensorElement, MixedElement,as_tensor,as_vector,dot,TestFunction, TrialFunction,TestFunctions, TrialFunctions,dx, ds, grad, FacetNormal,inner, max_value,nabla_grad, nabla_div, Identity)
from scipy.optimize import root
from petsc4py.PETSc import ScalarType
import dolfinx.io
from petsc4py import PETSc
from dolfinx.fem import (assemble_matrix, assemble_vector, form, apply_lifting, locate_dofs_geometrical, set_bc)
import dolfinx_mpc
import dolfinx_mpc.utils
from dolfinx.generation import BoxMesh
from dolfinx.common import Timer, list_timings, TimingType

from dolfinx.io import XDMFFile
L, B, H = 1,1,1
mesh = BoxMesh(MPI.COMM_WORLD, [np.array([0,0,0]), np.array([L, B, H])], [1,1,1], cell_type=CellType.tetrahedron)

U = VectorElement("CG", mesh.ufl_cell(), 1)# # displacement vector element
V = FiniteElement("CG", mesh.ufl_cell(), 1) # voltage finite element
U1, V1 = FunctionSpace(mesh, U), FunctionSpace(mesh, V)

W = FunctionSpace(mesh, MixedElement([U, V]))
num_dofs_global = W.dofmap.index_map.size_global * W.dofmap.index_map_bs
print(f"Number of dofs global: {num_dofs_global}")

# Parameters of Anisotropic Piezomaterial (PIC-255)
c_11 = (1.23e11)
c_12 = (7.67e10)
c_13 = (7.023e10)
c_33 = (9.711e10)
c_44 = (2.226e10)

# Relative permitivity matrix parameters
eps_0 = (8.854e-12) # relative permitivity of free space
eps_11 = (1649 * eps_0)
eps_33 = (1750 * eps_0)

# Coupling matrix parameters
e_13 = (-7.15)
e_33 = (13.75)
e_15 = (11.91)

rho_Piezo = (7800) #in kg/m3

# Order of matrices similar to ANSYS notation: x, y, z, xy, yz,xz
# IEEE notation: x, y, z, yz, xz, xy
# Elasticity tensor c^E
#IEEE Notation
C = as_tensor([[c_11, c_12, c_13, 0., 0., 0.], 
               [c_12, c_11, c_13, 0., 0., 0.],
               [c_13, c_13, c_33, 0., 0., 0.],
               [0., 0., 0., c_44, 0., 0],
               [0., 0., 0., 0., c_44, 0.],
               [0., 0., 0., 0., 0., (c_11-c_12)/2]])

# piezoelectric coupling tensor e
et_piezo = as_tensor([[0.0, 0.0, 0.0, 0.0, e_15, 0.0],
                     [0.0, 0.0, 0.0, e_15, 0.0, 0.0],
                     [e_13, e_13, e_33, 0.0, 0.0, 0.0]])

# transpose form of piezo tensor
e_piezo = as_tensor([[0.0, 0.0, e_13],
                    [0.0, 0.0, e_13],
                    [0.0, 0.0, e_33],
                    [0.0, e_15, 0.0],
                    [e_15, 0.0, 0.0],
                    [0.0, 0.0, 0.0]])

# Permittivitats tensor  epsilon^S
eps_s = as_tensor([[eps_11, 0., 0.],
                    [0., eps_11, 0.],
                    [0., 0., eps_33]])

# rewrite the tensor into a vector using Voigt notation
def strain3voigt(ten):
    # FEniCS does not know anything about Voigt notation, 
    # so one need to access the components directly as eps[0, 0] etc.   
    return as_vector([ten[0,0],ten[1,1],ten[2,2],2*ten[1,2],2*ten[0,2],2*ten[0,1]])

# rewrite a vector into a tensor using Voigt notation
def voigt3stress(vec):
    return as_tensor([[vec[0], vec[5], vec[4]], 
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])

# first define the function for the strain tensor in fenics notation
def B(u):
    return 0.5*(nabla_grad(u) + nabla_grad(u).T)

#define the electric field vector                 
def E_field_vector(Ei): 
    return as_vector([Ei[0], Ei[1], Ei[2]])
                  
#define the electric field in relation to potential
def E_field(v):
    return -grad(v)

def Sigma(u):
   return voigt3stress(dot(C, strain3voigt(B(u))))
#definition of the different components of my Stress tensor equation (sigma=C^E:Bu-e_piezo.E)
def sigma(u, v):
   return voigt3stress(dot(C, strain3voigt(B(u))) - e_piezo * E_field_vector(E_field(v)))

# the electric displacements vector
def disp_D(Di):
    return as_vector([Di[0], Di[1], Di[2]])

# definition of the different components of my electric displacements equation (D=e^s:Bu+eps^S.E)
# elect_disp_D for coupled linear elasicity and electrostatics
def D_field(u, v):
    D =   et_piezo * strain3voigt(B(u)) + eps_s * E_field(v)
    return disp_D(D)

# Mechanical: Prescribed Displacement Dirichlet BC
def clamped_boundary(x):
    return np.isclose(x[1], 0)
def f(x):
    vals = np.zeros(x.shape)
    vals[2] = 1e-3
    return vals

fdim = mesh.topology.dim - 1
boundary_facets = locate_entities_boundary(mesh, fdim, clamped_boundary)
u_D = Function(U1)
u_D.interpolate(f)

bc1 = DirichletBC(u_D, locate_dofs_topological((W.sub(0), U1), fdim, boundary_facets), W.sub(0))

# Electrical: Voltage Dirichlet BC
def Ground(x):
    return np.isclose(x[2], 1)

ground_facets = locate_entities_boundary(mesh, fdim, Ground)
v_D = Function(V1)
with v_D.vector.localForm() as loc:
     loc.set(0)
bc2 = DirichletBC(v_D, locate_dofs_topological((W.sub(1),V1), fdim, ground_facets), W.sub(1))
bcs = [bc1,bc2]

# Floating Potential
def Voltage(x):
    return np.isclose(x[2], 0)
v_facets = locate_entities_boundary(mesh, fdim, Voltage)
v_dofs = locate_dofs_topological(V1, mesh.topology.dim-1, v_facets)

def create_rigid_constraint(mpc, dofs, subspace=None):
    dof_coordinates = mpc.V.tabulate_dof_coordinates()[dofs, :]
    for i in range(len(dof_coordinates) - 1):
        mpc.create_general_constraint(
            {dof_coordinates[i + 1, :].tobytes(): {dof_coordinates[0, :].tobytes(): 1}},
            subspace,
            subspace,
        )

with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(V1)
    create_rigid_constraint(mpc, v_dofs)
    mpc.finalize()

# Trial and Test functions
(u, v) = TrialFunctions(W)
(wu, wv) = TestFunctions(W)

T = Constant(mesh, ScalarType((0,0,0)))
Tv = Constant(mesh, ScalarType((0)))

# The frequency domain is : [0,130] with interval of 10.
start = 10; d_freq = 10;  freq=40
omega=2*np.pi*freq

# Matrices
Muu = rho_Piezo*(inner(u,wu))*dx
Kuuv = (inner(sigma(u, v), B(wu)))*dx
Kvvu = (inner(D_field(u, v), grad(wv)))*dx
soln = Function(W)
a =  - omega**2*Muu + Kuuv + Kvvu 
L = inner(T, wu) * ds 

# Set up the PDE
problem = dolfinx_mpc.LinearProblem(a, L, mpc, bcs)
soln = problem.solve()
Ur = soln.sub(0).collapse()
Vr = soln.sub(1).collapse()





Please consider the thread: Rigidity constraint · Issue #9 · jorgensd/dolfinx_mpc · GitHub
with an example of how to do rigidity constraints efficiently.

Dear Sir,

I tried implementing the rigidity constraint referred to in the above link. But my code runs into a segmentation error which is attached below: It would be really appreciable if you help me out to understand the issue or can you kindly check my code which is also attached below. My goal is to constraint the nodes to have equal potential at the boundary defined as voltage in the code below.

Traceback (most recent call last):
  File "/root/coupling/piezocube.py", line 174, in <module>
    mpc.create_periodic_constraint_geometrical(W.sub(1), mt,3, mapping, bcs)
  File "/usr/local/lib/python3.9/dist-packages/dolfinx_mpc/multipointconstraint.py", line 171, in create_periodic_constraint_geometrical
    slaves, masters, coeffs, owners, offsets = create_periodic_condition_geometrical(
  File "/usr/local/lib/python3.9/dist-packages/dolfinx_mpc/periodic_condition.py", line 30, in create_periodic_condition_geometrical
    slave_blocks = dolfinx.fem.locate_dofs_geometrical(V, indicator)
  File "/usr/local/lib/python3.9/dist-packages/dolfinx/fem/dirichletbc.py", line 65, in locate_dofs_geometrical
    return cpp.fem.locate_dofs_geometrical(_V, marker)
TypeError: locate_dofs_geometrical(): incompatible function arguments. The following argument types are supported:
    1. (V: List[dolfinx::fem::FunctionSpace], marker: Callable[[numpy.ndarray[numpy.float64]], numpy.ndarray[bool]]) -> List[numpy.ndarray[2]]
    2. (V: dolfinx::fem::FunctionSpace, marker: Callable[[numpy.ndarray[numpy.float64]], numpy.ndarray[bool]]) -> numpy.ndarray[numpy.int32]

Invoked with: <dolfinx.cpp.fem.FunctionSpace object at 0x7f01285752b0>, <dolfinx.cpp.mesh.MeshTags_int32 object at 0x7f01291f45f0>
root@e895406ae63f:~/coupling# /usr/bin/python3 /root/coupling/piezocube.py
Number of dofs global: 32
[0]PETSC ERROR: ------------------------------------------------------------------------
[0]PETSC ERROR: Caught signal number 11 SEGV: Segmentation Violation, probably memory access out of range
[0]PETSC ERROR: Try option -start_in_debugger or -on_error_attach_debugger
[0]PETSC ERROR: or see https://petsc.org/release/faq/#valgrind
[0]PETSC ERROR: or try http://valgrind.org on GNU/linux and Apple Mac OS X to find memory corruption errors
[0]PETSC ERROR: configure using --with-debugging=yes, recompile, link, and run 
[0]PETSC ERROR: to get more information on the crash.
[0]PETSC ERROR: Run with -malloc_debug to check if memory corruption is causing the crash.
application called MPI_Abort(MPI_COMM_WORLD, 59) - process 0
[unset]: write_line error; fd=-1 buf=:cmd=abort exitcode=59
:
system msg for write_line failure : Bad file descriptor
from termios import VT1
import dolfinx
import numpy as np
from mpi4py import MPI

import dolfinx.fem as fem
from petsc4py import PETSc
from ufl import (Circumradius, FacetNormal, SpatialCoordinate, TrialFunction,
                 TestFunction, div, ds, grad, inner,dot, nabla_grad, nabla_div,Identity,dot, dx)
from dolfinx.cpp.mesh import CellType, locate_entities_boundary
from dolfinx.mesh import MeshTags, locate_entities
from dolfinx.mesh import locate_entities_boundary
from dolfinx.fem import (Constant, DirichletBC, Function,NonlinearProblem, LinearProblem, FunctionSpace, VectorFunctionSpace, 
                         locate_dofs_topological,assemble_scalar)
import ufl
from ufl import (Form, SpatialCoordinate, VectorElement, FiniteElement, TensorElement, MixedElement,as_tensor,as_vector,dot,TestFunction, TrialFunction,TestFunctions, TrialFunctions,dx, ds, grad, FacetNormal,inner, max_value,nabla_grad, nabla_div, Identity)
from scipy.optimize import root
from petsc4py.PETSc import ScalarType
import dolfinx.io
from petsc4py import PETSc
from dolfinx.fem import (assemble_matrix, assemble_vector, form, apply_lifting, locate_dofs_geometrical, set_bc)
import dolfinx_mpc
import dolfinx_mpc.utils
from dolfinx.generation import BoxMesh
from dolfinx.common import Timer, list_timings, TimingType
import dolfinx.cpp as _cpp
from dolfinx.io import XDMFFile
L, B, H = 1,1,1
mesh = BoxMesh(MPI.COMM_WORLD, [np.array([0,0,0]), np.array([L, B, H])], [1,1,1], cell_type=CellType.tetrahedron)

U = VectorElement("CG", mesh.ufl_cell(), 1)# # displacement vector element
V = FiniteElement("CG", mesh.ufl_cell(), 1) # voltage finite element
U1, V1 = FunctionSpace(mesh, U), FunctionSpace(mesh, V)

W = FunctionSpace(mesh, MixedElement([U, V]))
num_dofs_global = W.dofmap.index_map.size_global * W.dofmap.index_map_bs
print(f"Number of dofs global: {num_dofs_global}")

# Parameters of Anisotropic Piezomaterial (PIC-255)
c_11 = (1.23e11)
c_12 = (7.67e10)
c_13 = (7.023e10)
c_33 = (9.711e10)
c_44 = (2.226e10)

# Relative permitivity matrix parameters
eps_0 = (8.854e-12) # relative permitivity of free space
eps_11 = (1649 * eps_0)
eps_33 = (1750 * eps_0)

# Coupling matrix parameters
e_13 = (-7.15)
e_33 = (13.75)
e_15 = (11.91)

rho_Piezo = (7800) #in kg/m3

# Order of matrices similar to ANSYS notation: x, y, z, xy, yz,xz
# IEEE notation: x, y, z, yz, xz, xy
# Elasticity tensor c^E
#IEEE Notation
C = as_tensor([[c_11, c_12, c_13, 0., 0., 0.], 
               [c_12, c_11, c_13, 0., 0., 0.],
               [c_13, c_13, c_33, 0., 0., 0.],
               [0., 0., 0., c_44, 0., 0],
               [0., 0., 0., 0., c_44, 0.],
               [0., 0., 0., 0., 0., (c_11-c_12)/2]])

# piezoelectric coupling tensor e
et_piezo = as_tensor([[0.0, 0.0, 0.0, 0.0, e_15, 0.0],
                     [0.0, 0.0, 0.0, e_15, 0.0, 0.0],
                     [e_13, e_13, e_33, 0.0, 0.0, 0.0]])

# transpose form of piezo tensor
e_piezo = as_tensor([[0.0, 0.0, e_13],
                    [0.0, 0.0, e_13],
                    [0.0, 0.0, e_33],
                    [0.0, e_15, 0.0],
                    [e_15, 0.0, 0.0],
                    [0.0, 0.0, 0.0]])

# Permittivitats tensor  epsilon^S
eps_s = as_tensor([[eps_11, 0., 0.],
                    [0., eps_11, 0.],
                    [0., 0., eps_33]])

# rewrite the tensor into a vector using Voigt notation
def strain3voigt(ten):
    # FEniCS does not know anything about Voigt notation, 
    # so one need to access the components directly as eps[0, 0] etc.   
    return as_vector([ten[0,0],ten[1,1],ten[2,2],2*ten[1,2],2*ten[0,2],2*ten[0,1]])

# rewrite a vector into a tensor using Voigt notation
def voigt3stress(vec):
    return as_tensor([[vec[0], vec[5], vec[4]], 
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])

# first define the function for the strain tensor in fenics notation
def B(u):
    return 0.5*(nabla_grad(u) + nabla_grad(u).T)

#define the electric field vector                 
def E_field_vector(Ei): 
    return as_vector([Ei[0], Ei[1], Ei[2]])
                  
#define the electric field in relation to potential
def E_field(v):
    return -grad(v)

def Sigma(u):
   return voigt3stress(dot(C, strain3voigt(B(u))))
#definition of the different components of my Stress tensor equation (sigma=C^E:Bu-e_piezo.E)
def sigma(u, v):
   return voigt3stress(dot(C, strain3voigt(B(u))) - e_piezo * E_field_vector(E_field(v)))

# the electric displacements vector
def disp_D(Di):
    return as_vector([Di[0], Di[1], Di[2]])

# definition of the different components of my electric displacements equation (D=e^s:Bu+eps^S.E)
# elect_disp_D for coupled linear elasicity and electrostatics
def D_field(u, v):
    D =   et_piezo * strain3voigt(B(u)) + eps_s * E_field(v)
    return disp_D(D)

# Displacement Dirichlet BC
def clamped_boundary(x):
    return np.isclose(x[1], 0)
def f(x):
    vals = np.zeros(x.shape)
    vals[2] = 1e-3
    return vals

fdim = mesh.topology.dim - 1
boundary_facets = locate_entities_boundary(mesh, fdim, clamped_boundary)
u_D = Function(U1)
u_D.interpolate(f)

bc1 = DirichletBC(u_D, locate_dofs_topological((W.sub(0), U1), fdim, boundary_facets), W.sub(0))

# Voltage Dirichlet BC
def Ground(x):
    return np.isclose(x[2], 1)

ground_facets = locate_entities_boundary(mesh, fdim, Ground)
v_D = Function(V1)
with v_D.vector.localForm() as loc:
     loc.set(0)
bc2 = DirichletBC(v_D, locate_dofs_topological((W.sub(1),V1), fdim, ground_facets), W.sub(1))
bcs = [bc1,bc2]

# Floating Potential (Multi_point Constraint)
def Voltage(x):
   return np.isclose(x[2], 0)

def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = x[0]
    vals[1] = 0
    vals[2] = 0
    return vals


with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(W)
    mpc.create_periodic_constraint_geometrical(W.sub(1), Voltage, mapping, bcs)

    mpc.finalize()

# Trial and Test functions
(u, v) = TrialFunctions(W)
(wu, wv) = TestFunctions(W)

T = Constant(mesh, ScalarType((0,0,0)))
Tv = Constant(mesh, ScalarType((0)))

# The frequency domain is : [0,130] with interval of 10.
start = 10; d_freq = 10;  freq=40
omega=2*np.pi*freq

# Matrices
Muu = rho_Piezo*(inner(u,wu))*dx
Kuuv = (inner(sigma(u, v), B(wu)))*dx
Kvvu = (inner(D_field(u, v), grad(wv)))*dx
soln = Function(W)
a =  - omega**2*Muu + Kuuv + Kvvu 
L = inner(T, wu) * ds 

# Set up the PDE
petsc_options = {"ksp_type": "preonly", "pc_type": "lu", "pc_factor_solver_type": "mumps"}
problem = LinearProblem(a, L, bcs=bcs)
soln = problem.solve()
Ur = soln.sub(0).collapse()
Vr = soln.sub(1).collapse()
print(max(abs(Ur.x.array)))
print((Vr.x.array))




It seems like you are using a slightly outdated version of DOLFINx, not compatible with the latest release of dolfinx_mpc (which introduced such constraints). Please update DOLFINx to the one on the main branch, and similarly update dolfinx_mpc, and the following code should then run:

from termios import VT1

import dolfinx_mpc
import dolfinx_mpc.utils
import numpy as np
from dolfinx.common import Timer, TimingType
from dolfinx.fem import (Constant, Function, FunctionSpace, dirichletbc,
                         locate_dofs_topological)
from dolfinx.mesh import CellType, create_box, locate_entities_boundary
from mpi4py import MPI
from petsc4py.PETSc import ScalarType
from ufl import (FiniteElement, MixedElement, TestFunctions, TrialFunctions,
                 VectorElement, as_tensor, as_vector, dot, ds, dx, grad, inner,
                 nabla_grad)

L, B, H = 1, 1, 1
mesh = create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([L, B, H])], [
    1, 1, 1], cell_type=CellType.tetrahedron)

U = VectorElement("CG", mesh.ufl_cell(), 1)  # displacement vector element
V = FiniteElement("CG", mesh.ufl_cell(), 1)  # voltage finite element
U1, V1 = FunctionSpace(mesh, U), FunctionSpace(mesh, V)

W = FunctionSpace(mesh, MixedElement([U, V]))
num_dofs_global = W.dofmap.index_map.size_global * W.dofmap.index_map_bs
print(f"Number of dofs global: {num_dofs_global}")

# Parameters of Anisotropic Piezomaterial (PIC-255)
c_11 = (1.23e11)
c_12 = (7.67e10)
c_13 = (7.023e10)
c_33 = (9.711e10)
c_44 = (2.226e10)

# Relative permitivity matrix parameters
eps_0 = (8.854e-12)  # relative permitivity of free space
eps_11 = (1649 * eps_0)
eps_33 = (1750 * eps_0)

# Coupling matrix parameters
e_13 = (-7.15)
e_33 = (13.75)
e_15 = (11.91)

rho_Piezo = (7800)  # in kg/m3

# Order of matrices similar to ANSYS notation: x, y, z, xy, yz,xz
# IEEE notation: x, y, z, yz, xz, xy
# Elasticity tensor c^E
# IEEE Notation
C = as_tensor([[c_11, c_12, c_13, 0., 0., 0.],
               [c_12, c_11, c_13, 0., 0., 0.],
               [c_13, c_13, c_33, 0., 0., 0.],
               [0., 0., 0., c_44, 0., 0],
               [0., 0., 0., 0., c_44, 0.],
               [0., 0., 0., 0., 0., (c_11 - c_12) / 2]])

# piezoelectric coupling tensor e
et_piezo = as_tensor([[0.0, 0.0, 0.0, 0.0, e_15, 0.0],
                     [0.0, 0.0, 0.0, e_15, 0.0, 0.0],
                     [e_13, e_13, e_33, 0.0, 0.0, 0.0]])

# transpose form of piezo tensor
e_piezo = as_tensor([[0.0, 0.0, e_13],
                    [0.0, 0.0, e_13],
                    [0.0, 0.0, e_33],
                    [0.0, e_15, 0.0],
                    [e_15, 0.0, 0.0],
                    [0.0, 0.0, 0.0]])

# Permittivitats tensor  epsilon^S
eps_s = as_tensor([[eps_11, 0., 0.],
                   [0., eps_11, 0.],
                   [0., 0., eps_33]])

# rewrite the tensor into a vector using Voigt notation


def strain3voigt(ten):
    # FEniCS does not know anything about Voigt notation,
    # so one need to access the components directly as eps[0, 0] etc.
    return as_vector([ten[0, 0], ten[1, 1], ten[2, 2], 2 * ten[1, 2], 2 * ten[0, 2], 2 * ten[0, 1]])

# rewrite a vector into a tensor using Voigt notation


def voigt3stress(vec):
    return as_tensor([[vec[0], vec[5], vec[4]],
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])

# first define the function for the strain tensor in fenics notation


def B(u):
    return 0.5 * (nabla_grad(u) + nabla_grad(u).T)

# define the electric field vector


def E_field_vector(Ei):
    return as_vector([Ei[0], Ei[1], Ei[2]])

# define the electric field in relation to potential


def E_field(v):
    return -grad(v)


def Sigma(u):
    return voigt3stress(dot(C, strain3voigt(B(u))))
# definition of the different components of my Stress tensor equation (sigma=C^E:Bu-e_piezo.E)


def sigma(u, v):
    return voigt3stress(dot(C, strain3voigt(B(u))) - e_piezo * E_field_vector(E_field(v)))

# the electric displacements vector


def disp_D(Di):
    return as_vector([Di[0], Di[1], Di[2]])

# definition of the different components of my electric displacements equation (D=e^s:Bu+eps^S.E)
# elect_disp_D for coupled linear elasicity and electrostatics


def D_field(u, v):
    D = et_piezo * strain3voigt(B(u)) + eps_s * E_field(v)
    return disp_D(D)

# Displacement Dirichlet BC


def clamped_boundary(x):
    return np.isclose(x[1], 0)


def f(x):
    vals = np.zeros(x.shape)
    vals[2] = 1e-3
    return vals


fdim = mesh.topology.dim - 1
boundary_facets = locate_entities_boundary(mesh, fdim, clamped_boundary)
u_D = Function(U1)
u_D.interpolate(f)

bc1 = dirichletbc(u_D, locate_dofs_topological((W.sub(0), U1), fdim, boundary_facets), W.sub(0))

# Voltage Dirichlet BC


def Ground(x):
    return np.isclose(x[2], 1)


ground_facets = locate_entities_boundary(mesh, fdim, Ground)
v_D = Function(V1)
with v_D.vector.localForm() as loc:
    loc.set(0)
bc2 = dirichletbc(v_D, locate_dofs_topological((W.sub(1), V1), fdim, ground_facets), W.sub(1))
bcs = [bc1, bc2]

# Floating Potential (Multi_point Constraint)


def Voltage(x):
    return np.isclose(x[2], 0)


def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = x[0]
    vals[1] = 0
    vals[2] = 0
    return vals


with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(W)
    mpc.create_periodic_constraint_geometrical(W.sub(1), Voltage, mapping, bcs)

    mpc.finalize()

# Trial and Test functions
(u, v) = TrialFunctions(W)
(wu, wv) = TestFunctions(W)

T = Constant(mesh, ScalarType((0, 0, 0)))
Tv = Constant(mesh, ScalarType((0)))

# The frequency domain is : [0,130] with interval of 10.
start = 10
d_freq = 10
freq = 40
omega = 2 * np.pi * freq

# Matrices
Muu = rho_Piezo * (inner(u, wu)) * dx
Kuuv = (inner(sigma(u, v), B(wu))) * dx
Kvvu = (inner(D_field(u, v), grad(wv))) * dx
soln = Function(W)
a = - omega**2 * Muu + Kuuv + Kvvu
L = inner(T, wu) * ds

# Set up the PDE
petsc_options = {"ksp_type": "preonly", "pc_type": "lu", "pc_factor_solver_type": "mumps"}
problem = dolfinx_mpc.LinearProblem(a, L, mpc, bcs=bcs)
soln = problem.solve()
Ur = soln.sub(0).collapse()
Vr = soln.sub(1).collapse()
print(max(abs(Ur.x.array)))
print((Vr.x.array))

Dear Sir,

I notice different results when I try to constraint the nodes using periodic constraints. I have set up a minimal code that imports external 2D mesh with temperature as unknown.(I have attached the link for mesh details:Mesh Details - Google Drive)
Basically, it is a square with one tetrahedral element. A left boundary has a coupling effect where I want the nodes to have equal temperature distribution and the right boundary has a convection effect. I have set up 2 cases for applying the multipoint.
in case1: I have used rigidity constraint where I get the correct results at the nodes which are [50,50,50,50]. In case2 I have used periodic constraints. Am not sure whether both the implementation have the same effects because the nodal results are different in case 2. Does both the implementation mean the same or does the periodic constraint have a different meaning?

import dolfinx
from dolfinx.fem import (Constant,dirichletbc, Function, FunctionSpace,assemble_scalar, locate_dofs_geometrical, locate_dofs_topological)
from dolfinx.fem.petsc import (apply_lifting, apply_lifting_nest,
                               assemble_matrix, assemble_matrix_block,
                               assemble_matrix_nest, assemble_vector,
                               assemble_vector_block, assemble_vector_nest,
                               create_matrix, create_matrix_block,
                               create_matrix_nest, create_vector,
                               create_vector_block, create_vector_nest, set_bc,
                               set_bc_nest)
import ufl
from dolfinx import fem
from ufl import (SpatialCoordinate, TestFunction, TrialFunction,dx, ds, grad, inner)
from mpi4py import MPI
import dolfinx.io
from dolfinx.common import Timer, list_timings, TimingType
import numpy as np
from petsc4py import PETSc
from petsc4py.PETSc import ScalarType
import dolfinx_mpc
import dolfinx_mpc.utils
from dolfinx.io import XDMFFile

with dolfinx.io.XDMFFile(MPI.COMM_WORLD, "Mesh.xdmf", "r") as xdmf:
    mesh = xdmf.read_mesh(name="Grid")
    mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim)
    m_subdomains = xdmf.read_meshtags(mesh, name="Grid")

mesh.topology.create_connectivity(mesh.topology.dim-1, mesh.topology.dim)
with dolfinx.io.XDMFFile(MPI.COMM_WORLD, "mt.xdmf", "r") as xdmf:
    m_boundaries = xdmf.read_meshtags(mesh, name="Grid")

# function space
V = FunctionSpace(mesh, ("CG", 1))
num_dofs_global = V.dofmap.index_map.size_global * V.dofmap.index_map_bs
print(f"Number of dofs global: {num_dofs_global}")
dx = ufl.Measure("dx", domain=mesh, subdomain_data=m_subdomains)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=m_boundaries)

# Trial and Test functions
T = TrialFunction(V)  
v = TestFunction(V)

# parameters
T_amb = Constant(mesh, 50.0)  # ambient temperature
kappa = Constant(mesh, 52.0)  # thermal conductivity [W/(m*K)]
h = Constant(mesh, 5.0)   # heat Transfer Co-efficient [W/(m^2 deg C)]

# Robin boundary conditions
r1 = h
Integral_Ra = []
Integral_RL = []
Integral_Ra.append(r1 * T * v * ds(2))
Integral_RL.append(r1 * T_amb * v * ds(2))

# Reformulate for steady state
a = kappa * inner(grad(T),grad(v)) * dx + sum(Integral_Ra)
L = sum(Integral_RL) 

# case1: Create multipoint constraint
left_facets = m_boundaries.indices[m_boundaries.values==3]
left_dofs = locate_dofs_topological(V, mesh.topology.dim-1, left_facets)

def create_rigid_constraint(mpc, dofs, subspace=None):
    dof_coordinates = mpc.V.tabulate_dof_coordinates()[dofs, :]
    for i in range(len(dof_coordinates) - 1):
        mpc.create_general_constraint(
            {dof_coordinates[i + 1, :].tobytes(): {dof_coordinates[0, :].tobytes(): 1}},
            subspace,
            subspace,
        )

with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(V)
    create_rigid_constraint(mpc, left_dofs)
    mpc.finalize()

"""
# case2: Create multipoint constraint
def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = x[0]
    return vals

print (mapping)
with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(V)
    mpc.create_periodic_constraint_topological(V, m_boundaries,3, mapping,bcs =[])
    mpc.finalize()

"""
problem = dolfinx_mpc.LinearProblem(a, L, mpc)
u_h = problem.solve()
print(u_h.x.array)

# Matrices with MPC
A = dolfinx_mpc.assemble_matrix(fem.form(a), mpc)
A.assemble()
Ad = A.convert("dense")
print(Ad.getDenseArray())

b = dolfinx_mpc.assemble_vector(fem.form(L), mpc)
print(b.getArray())

The problem here is that you’re input surface (the slave surface) is part of the surface that you want to constrain it by (the master) surface. This is why I’ve used

in the rigidity constraint.

Similarly. you can use:

def right_boundary(x):
    return np.logical_and(np.isclose(x[0], 0.5), x[1] > -0.5 + 1e-14)

def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = x[0]
    vals[1] = -0.5
    return vals


with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(V)
    mpc.create_periodic_constraint_geometrical(V, right_boundary, mapping, bcs=[])
    mpc.finalize()

To solve your problem.

1 Like

Dear Sir,

As per the suggestion, I have set up the slave and master nodes on different surfaces in the code above. But I notice that the voltage distribution is not equal in the solution vector where slave nodes are defined. Considering the mixed-function space, am still not able to achieve what I wanted. I have shared the implementation below:

import dolfinx
import numpy as np
from mpi4py import MPI

import dolfinx_mpc
import dolfinx_mpc.utils
from dolfinx.common import Timer, TimingType
import dolfinx.fem as fem
from petsc4py import PETSc
from dolfinx.fem.petsc import (apply_lifting, apply_lifting_nest,
                               assemble_matrix, assemble_matrix_block,
                               assemble_matrix_nest, assemble_vector,
                               assemble_vector_block, assemble_vector_nest,
                               create_matrix, create_matrix_block,
                               create_matrix_nest, create_vector,
                               create_vector_block, create_vector_nest, set_bc,
                               set_bc_nest)
from ufl import (Circumradius, FacetNormal, SpatialCoordinate, TrialFunction,
                 TestFunction, div, ds, grad, inner,dot, nabla_grad, nabla_div,Identity,dot, dx)
from dolfinx.mesh import CellType, create_box, locate_entities_boundary
from dolfinx.mesh import locate_entities
from dolfinx.mesh import locate_entities_boundary
from dolfinx.fem import (Constant, dirichletbc, Function, FunctionSpace, VectorFunctionSpace, 
                         locate_dofs_topological,assemble_scalar,form)
import ufl
from ufl import (Form, SpatialCoordinate, VectorElement, FiniteElement, TensorElement, MixedElement,as_tensor,as_vector,dot,TestFunction, TrialFunction,TestFunctions, TrialFunctions,dx, ds, grad, FacetNormal,inner, max_value,nabla_grad, nabla_div, Identity)
from scipy.optimize import root
from petsc4py.PETSc import ScalarType
import dolfinx.io
from petsc4py import PETSc
from dolfinx.fem import (apply_lifting, locate_dofs_geometrical, set_bc)


from dolfinx.io import XDMFFile
L, B, H = 1,1,1
mesh = dolfinx.mesh.create_box(MPI.COMM_WORLD, [np.array([0,0,0]), np.array([L, B, H])], [1,1,1], cell_type=CellType.tetrahedron)

U = VectorElement("CG", mesh.ufl_cell(), 1)# # displacement vector element
V = FiniteElement("CG", mesh.ufl_cell(), 1) # voltage finite element
U1, V1 = FunctionSpace(mesh, U), FunctionSpace(mesh, V)

W = FunctionSpace(mesh, MixedElement([U, V]))
num_dofs_global = W.dofmap.index_map.size_global * W.dofmap.index_map_bs
print(f"Number of dofs global: {num_dofs_global}")

# Parameters of Anisotropic Piezomaterial (PIC-255)
c_11 = (1.23e11)
c_12 = (7.67e10)
c_13 = (7.023e10)
c_33 = (9.711e10)
c_44 = (2.226e10)

# Relative permitivity matrix parameters
eps_0 = (8.854e-12) # relative permitivity of free space
eps_11 = (1649 * eps_0)
eps_33 = (1750 * eps_0)

# Coupling matrix parameters
e_13 = (-7.15)
e_33 = (13.75)
e_15 = (11.91)

rho_Piezo = (7800) #in kg/m3

# Order of matrices similar to ANSYS notation: x, y, z, xy, yz,xz
# IEEE notation: x, y, z, yz, xz, xy
# Elasticity tensor c^E
#IEEE Notation
C = as_tensor([[c_11, c_12, c_13, 0., 0., 0.], 
               [c_12, c_11, c_13, 0., 0., 0.],
               [c_13, c_13, c_33, 0., 0., 0.],
               [0., 0., 0., c_44, 0., 0],
               [0., 0., 0., 0., c_44, 0.],
               [0., 0., 0., 0., 0., (c_11-c_12)/2]])

# piezoelectric coupling tensor e
et_piezo = as_tensor([[0.0, 0.0, 0.0, 0.0, e_15, 0.0],
                     [0.0, 0.0, 0.0, e_15, 0.0, 0.0],
                     [e_13, e_13, e_33, 0.0, 0.0, 0.0]])

# transpose form of piezo tensor
e_piezo = as_tensor([[0.0, 0.0, e_13],
                    [0.0, 0.0, e_13],
                    [0.0, 0.0, e_33],
                    [0.0, e_15, 0.0],
                    [e_15, 0.0, 0.0],
                    [0.0, 0.0, 0.0]])

# Permittivitats tensor  epsilon^S
eps_s = as_tensor([[eps_11, 0., 0.],
                    [0., eps_11, 0.],
                    [0., 0., eps_33]])

# rewrite the tensor into a vector using Voigt notation
def strain3voigt(ten):
    # FEniCS does not know anything about Voigt notation, 
    # so one need to access the components directly as eps[0, 0] etc.   
    return as_vector([ten[0,0],ten[1,1],ten[2,2],2*ten[1,2],2*ten[0,2],2*ten[0,1]])

# rewrite a vector into a tensor using Voigt notation
def voigt3stress(vec):
    return as_tensor([[vec[0], vec[5], vec[4]], 
                      [vec[5], vec[1], vec[3]],
                      [vec[4], vec[3], vec[2]]])

# first define the function for the strain tensor in fenics notation
def B(u):
    return 0.5*(nabla_grad(u) + nabla_grad(u).T)

#define the electric field vector                 
def E_field_vector(Ei): 
    return as_vector([Ei[0], Ei[1], Ei[2]])
                  
#define the electric field in relation to potential
def E_field(v):
    return -grad(v)

def Sigma(u):
   return voigt3stress(dot(C, strain3voigt(B(u))))
#definition of the different components of my Stress tensor equation (sigma=C^E:Bu-e_piezo.E)
def sigma(u, v):
   return voigt3stress(dot(C, strain3voigt(B(u))) - e_piezo * E_field_vector(E_field(v)))

# the electric displacements vector
def disp_D(Di):
    return as_vector([Di[0], Di[1], Di[2]])

# definition of the different components of my electric displacements equation (D=e^s:Bu+eps^S.E)
# elect_disp_D for coupled linear elasicity and electrostatics
def D_field(u, v):
    D =   et_piezo * strain3voigt(B(u)) + eps_s * E_field(v)
    return disp_D(D)

# Displacement Dirichlet BC
def clamped_boundary(x):
    return np.isclose(x[1], 0)
def f(x):
    vals = np.zeros(x.shape)
    vals[2] = 1e-3
    return vals

fdim = mesh.topology.dim - 1
boundary_facets = locate_entities_boundary(mesh, fdim, clamped_boundary)
u_D = Function(U1)
u_D.interpolate(f)

bc1 = dirichletbc(u_D, locate_dofs_topological((W.sub(0), U1), fdim, boundary_facets), W.sub(0))

# Voltage Dirichlet BC
def Ground(x):
    return np.isclose(x[2], 1)

ground_facets = locate_entities_boundary(mesh, fdim, Ground)
v_D = Function(V1)
with v_D.vector.localForm() as loc:
     loc.set(0)
bc2 = dirichletbc(v_D, locate_dofs_topological((W.sub(1),V1), fdim, ground_facets), W.sub(1))

bcs =[bc1,bc2]

# Trial and Test functions
(u, v) = TrialFunctions(W)
(wu, wv) = TestFunctions(W)

T = Constant(mesh, ScalarType((0,0,0)))
Tv = Constant(mesh, ScalarType((0)))

# Multi point Constraint
def Voltage(x):
    return np.logical_and(np.isclose(x[2], 0), x[1] > 1e-10, x[0] > 1e-10)  # slave_nodes

def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = 0
    vals[1] = 0
    vals[2] = x[2]
    return vals

with Timer("~RIGID: Initialize MPC"):
    mpc = dolfinx_mpc.MultiPointConstraint(W)
    mpc.create_periodic_constraint_geometrical(W.sub(1), Voltage, mapping, bcs)

    mpc.finalize()

# The frequency domain is 
freq = 40
omega = 2 * np.pi * freq

# Matrices
Muu = rho_Piezo * (inner(u, wu)) * dx
Kuuv = (inner(sigma(u, v), B(wu))) * dx
Kvvu = (inner(D_field(u, v), grad(wv))) * dx
soln = Function(W)
a = - omega**2 * Muu + Kuuv + Kvvu
L = inner(T, wu) * ds

# Set up the PDE
petsc_options = {"ksp_type": "preonly", "pc_type": "lu", "pc_factor_solver_type": "mumps"}
problem = dolfinx_mpc.LinearProblem(a, L, mpc, bcs=bcs)
soln = problem.solve()
Ur = soln.sub(0).collapse()
Vr = soln.sub(1).collapse()
print(max(abs(Ur.x.array)))
print((Vr.x.array))

I am trying to understant the Rigidity constraint · Issue #9 · jorgensd/dolfinx_mpc · GitHub example.
Can anyone please explain to me why:

  • The mapping has been defined in the following way?
def mapping(x):
    vals = np.zeros(x.shape)
    vals[0] = x[0]
    vals[1] = 0
    vals[2] = 0
    return vals
  • What does the following line do in the code?
mpc.create_periodic_constraint_geometrical(V.sub(1), right_boundary, mapping, bcs)

Does it mean u[1] on the right_boundary must be equal to the value of a point with same x[0] but x[1]=0?

Thanks

It is mapping every (x,y, z) coordinate to the corner (x,0,0) of the given surface.

Yes,

It means that u_y at right_boundary is equal to (x,0,0).