How to build a custom basix element from hermite polynomials?

Here is the whole code:


import numpy as np
import basix
from basix import CellType, MapType, PolynomialType, PolysetType, SobolevSpace

# Initialize wcoeffs for translations (8, 64)
wcoeffs_translation = np.zeros((8, 64))

# Generate quadrature points for a hexahedron with quadrature order 1
pts, wts = basix.make_quadrature(CellType.hexahedron, 1)

# Compute Legendre polynomials of degree 1 at the quadrature points
poly = basix.tabulate_polynomials(PolynomialType.legendre, CellType.hexahedron, 1, pts)

# Define Hermite basis functions (placeholder: replace with actual Hermite expressions)
hermite_shapes = [
    lambda x, y, z: x * (1 - x) * y * (1 - y) * z * (1 - z),
    lambda x, y, z: (1 - x**2) * (1 - y**2) * (1 - z**2),
    lambda x, y, z: x * y * z,
    lambda x, y, z: (1 - x) * (1 - y) * (1 - z),
    lambda x, y, z: (1 + x) * (1 + y) * (1 + z),
    lambda x, y, z: x * (1 - y) * (1 - z),
    lambda x, y, z: y * (1 - x) * (1 - z),
    lambda x, y, z: z * (1 - x) * (1 - y)
]

# Compute wcoeffs for translations
num_polynomials = poly.shape[0]  # Number of basis polynomials
for i in range(8):  # Iterate over the 8 Hermite shape functions
    f = np.array([hermite_shapes[i](p[0], p[1], p[2]) for p in pts])  # Evaluate at quadrature points
    for j in range(num_polynomials):  # Iterate over the actual number of basis polynomials
        wcoeffs_translation[i, j] = sum(f * poly[j, :] * wts)

# Print the resulting wcoeffs_translation
print("wcoeffs_translation:", wcoeffs_translation)

# Define the DOF points for the custom hexahedron element
x = [[], [], [], []]

# Add the corner points of the hexahedron (8 vertices in total)
x[0].append(np.array([[0.0, 0.0, 0.0]]))  # Bottom-left-front corner
x[0].append(np.array([[1.0, 0.0, 0.0]]))  # Bottom-right-front corner
x[0].append(np.array([[0.0, 1.0, 0.0]]))  # Top-left-front corner
x[0].append(np.array([[1.0, 1.0, 0.0]]))  # Top-right-front corner
x[0].append(np.array([[0.0, 0.0, 1.0]]))  # Bottom-left-back corner
x[0].append(np.array([[1.0, 0.0, 1.0]]))  # Bottom-right-back corner
x[0].append(np.array([[0.0, 1.0, 1.0]]))  # Top-left-back corner
x[0].append(np.array([[1.0, 1.0, 1.0]]))  # Top-right-back corner

# Optionally, add internal points for higher-order elements (here, we leave them empty)
for _ in range(6):
    x[1].append(np.zeros((0, 3)))

# Define transformation matrices for the degrees of freedom (DOF transformations)
M = [[], [], [], []]

# Assign identity transformations for the vertices (no transformation needed)
for _ in range(8):
    M[0].append(np.array([[[[1.0]]]]))  # Identity matrix for each vertex DOF

# Add placeholders for higher-order edges and internal transformations (empty for now)
for _ in range(6):
    M[1].append(np.zeros((0, 1, 0, 1)))

# Create the custom hexahedron element using basix.create_custom_element
element = basix.create_custom_element(
    CellType.hexahedron,
    [],
    wcoeffs_translation,  # The computed weight coefficients for Hermite shapes
    x,                    # The DOF points
    M,                    # The transformation matrices
    0,                    # No entity transformations
    MapType.identity,      # Identity mapping type
    SobolevSpace.H1,       # Sobolev space
    False,                 # Not discontinuous
    1,                     # Degree of the element
    1,                     # Dimensionality of the basis functions
    PolysetType.standard   # Standard polynomial set type
)

print('Custom hexahedron element created successfully.')


Traceback (most recent call last):
  File "/home/prusso/dolfinx-basix-demos/new2.py", line 65, in <module>
    element = basix.create_custom_element(
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/basix/finite_element.py", line 665, in create_custom_element
    _create_custom_element(
RuntimeError: wcoeffs has the wrong number of columns

So there is a difficulty with the shape so far of wcoeffs_translation, how come?

So I guess so far that::


PolynomialType.legendre

is going to be incompatible here, since that a hermite is -inf, inf based and a legendre is -1,1 based.

So far it is possible to use an 8,8 shape for the coeffs, and use this way:


# Compute wcoeffs for translations
num_polynomials = poly.shape[0]  # Number of basis polynomials
for i in range(8):  # Iterate over the 8 Hermite shape functions
    f = np.array([hermite_shapes[i](p[0], p[1], p[2]) for p in pts])  # Evaluate at quadrature points
    wcoeffs_translation[7, i] = sum(f * poly[i, :] * wts)

however that leads to this:


Traceback (most recent call last):
  File "/home/prusso/dolfinx-basix-demos/new2.py", line 64, in <module>
    element = basix.create_custom_element(
  File "/home/prusso/spack/var/spack/environments/fenicsx-env/.spack-env/view/lib/python3.10/site-packages/basix/finite_element.py", line 665, in create_custom_element
    _create_custom_element(
RuntimeError: Cannot orthogonalise the rows of a matrix with incomplete row rank

OK…So far not sure what to do about that. So far the system I have in mind is one where a nodal matrix gets built:


$$ \begin{bmatrix} \text{NH}{dx} & 0 & 0 & \text{RH}{dx} & 0 & 0 \\ 0 & \text{NH}{dy} & 0 & 0 & \text{RH}{dy} & 0 \\ 0 & 0 & \text{NH}{dz} & 0 & 0 & \text{NH}{dz} \\ \text{NH}{dy} & \text{NH}{dx} & 0 & \text{RH}{dy} & \text{RH}{dx} & 0 \\ 0 & \text{NH}{dz} & \text{NH}{dy} & 0 & \text{RH}{dz} & \text{RH}{dy} \\ \text{NH}{dz} & 0 & \text{NH}{dx} & \text{RH}{dz} & 0 & \text{RH}{dx} \end{bmatrix} $$

so with the matrix it should be possible to K with it:


                for m in range(len(nodes_in_domain)):
                    for n in range(len(nodes_in_domain)):
                        Bm = hermite_matrix(m + 1)
                        Bn = hermite_matrix(n + 1)
                        Bm_sub = Bm.subs({ξ: ξ_val, η: η_val, ζ: ζ_val})
                        Bn_sub = Bn.subs({ξ: ξ_val, η: η_val, ζ: ζ_val})
                        K_e[m*6:(m+1)*6, n*6:(n+1)*6] += weight * (Bm_sub.T * D * Bn_sub) * J_det_val

So far there is a page about hermite elements:

it looks like there is no documentation about the abbreviated names for one at this time. I guess that may mean that those are maybe under construction at this time in basix 0.10.0?

Is it possible to set this up somehow with the dolfinx environment, or will it maybe be possible at a later time?

Just for informational purposes. These are the RH’s I have. I had GPT process all of this, which is for the time being the best that I could do, so I can’t vouch for there accuracy 100% but they look good so far. I wouldn’t want to leave this out and leave anyone grasping at straws here:

So it sounds like once that hermite elementts are available that a mixed formulation should be able to be developed and ufl can maybe be used to build the matrix in a similar fashion to the k_form used by Bleyer, j…I get the feeling so far the possibility to do so is either there or will exist in the future…


# Define the shape functions for rotation (vector) in terms of x, y, z
RH1 = -(1/4) * x**3 + (3/4) * x + 1/2 * -(1/4) * y**3 + (3/4) * y + 1/2 * -(1/4) * z**3 + (3/4) * z + 1/2
RH2 = (1/4) * x**3 - (1/4) * x**2 - (1/4) * x + 1/4 * -(1/4) * y**3 + (3/4) * y + 1/2 * -(1/4) * z**3 + (3/4) * z + 1/2
RH3 = (1/4) * x**3 - (1/4) * x**2 - (1/4) * x + 1/4 * (-(1/4) * y**3 + (3/4) * y + 1/2) * -(1/4) * z**3 + (3/4) * z + 1/2
RH4 = -(1/4) * x**3 + (3/4) * x + 1/2 * (-(1/4) * y**3 + (3/4) * y + 1/2) * -(1/4) * z**3 + (3/4) * z + 1/2
RH5 = -(1/4) * x**3 + (3/4) * x + 1/2 * -(1/4) * y**3 + (3/4) * y + 1/2 * (1/4) * z**3 - (1/4) * z**2 - (1/4) * z + 1/4
RH6 = (1/4) * x**3 - (1/4) * x**2 - (1/4) * x + 1/4 * -(1/4) * y**3 + (3/4) * y + 1/2 * (1/4) * z**3 - (1/4) * z**2 - (1/4) * z + 1/4
RH7 = (1/4) * x**3 - (1/4) * x**2 - (1/4) * x + 1/4 * (-(1/4) * y**3 + (3/4) * y + 1/2) * (1/4) * z**3 - (1/4) * z**2 - (1/4) * z + 1/4
RH8 = -(1/4) * x**3 + (3/4) * x + 1/2 * (1/4) * y**3 - (1/4) * y**2 - (1/4) * y + 1/4 * (1/4) * z**3 - (1/4) * z**2 - (1/4) * z + 1/4

There is a template that I think is good to use in terms of setting this up when hermite elements become available:

Here there is a way to build a beam mesh, and also it describes that doing so almost always leads to a singularity as far as I know and what to do about that. It also states why doing things with Bleyer J for a square beam of this scenario was a “square peg in round hole” so to speak. Mostly what is here is how to set up a function for the moments and then start integrating those:

So what GPT and I built doesn’t work yet it looks like because of the lack of nodal matrix as described above. I havn’t tried at this point if this type of system will work with Legendre’s polynomials. Mostly I am trying to avoid spinning my wheels too much over this…

Here is a gist which is a mash-up between those two templates.

It doesn’t yet work but this is just to show an idea of the type of format I would like to try to get working if this can work with the currently supported elements:

( xi, eta, zeta) implies the local co-ordinates on the x,y,z axis respectively…


import numpy as np
import gmsh
import ufl
from mpi4py import MPI
from dolfinx import mesh, fem, io
import basix
import dolfinx.fem.petsc
import sympy as sp
from sympy import Matrix

# Initialize gmsh and load your model
gmsh.initialize()
gmsh.open("beam_mesh.msh")

# Create the DOLFIN-X mesh using gmshio.model_to_mesh
domain, _, _ = io.gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, 0)

# Finalize gmsh
gmsh.finalize()

gdim = domain.geometry.dim
tdim = domain.topology.dim

# Compute the first column of the Jacobian matrix for the domain
dx_dX = ufl.Jacobian(domain)[:, 0]
t = dx_dX / ufl.sqrt(ufl.inner(dx_dX, dx_dX))  # Unit vector along the beam

# Define material properties and constants
E = fem.Constant(domain, 70e3)
nu = fem.Constant(domain, 0.3)
rho = fem.Constant(domain, 2.7e-3)
g = fem.Constant(domain, 9.81)

# Define function spaces with 6-DOF per node (translations + rotations)
Ue = basix.ufl.element("P", domain.basix_cell(), 1, shape=(gdim,))
W = fem.functionspace(domain, basix.ufl.mixed_element([Ue, Ue]))
V0 = W.sub(0)  # Translation
V1 = W.sub(1)  # Rotation
u_, theta_ = ufl.TestFunctions(W)
du, dtheta = ufl.TrialFunctions(W)

'''
This is right now just a placeholder for this:
$$ \begin{bmatrix} \text{NH}{dx} & 0 & 0 & \text{RH}{dx} & 0 & 0 \\ 0 & \text{NH}{dy} & 0 & 0 & \text{RH}{dy} & 0 \\ 0 & 0 & \text{NH}{dz} & 0 & 0 & \text{NH}{dz} \\ \text{NH}{dy} & \text{NH}{dx} & 0 & \text{RH}{dy} & \text{RH}{dx} & 0 \\ 0 & \text{NH}{dz} & \text{NH}{dy} & 0 & \text{RH}{dz} & \text{RH}{dy} \\ \text{NH}{dz} & 0 & \text{NH}{dx} & \text{RH}{dz} & 0 & \text{RH}{dx} \end{bmatrix} $$
'''
# Define Hermite shape functions for each node
def hermite_matrix(m, xi, eta, zeta):
    # Example of constructing Hermite functions (you will expand this function)
    # Replace with real Hermite functions for bending/torsion
    return np.array([[xi, 0, 0], [0, eta, 0], [0, 0, zeta]])

# Material property matrix for wood
def D_matrix(mu, lamb):
    D = Matrix([
        [2*mu+lamb, lamb, lamb, 0, 0, 0],
        [lamb, 2*mu+lamb, lamb, 0, 0, 0],
        [lamb, lamb, 2*mu+lamb, 0, 0, 0],
        [0, 0, 0, mu, 0, 0],
        [0, 0, 0, 0, mu, 0],
        [0, 0, 0, 0, 0, mu]
    ])
    return D

# Use Sympy to create the D matrix
mu_val = 70e3 / (2 * (1 + 0.3))
lamb_val = 70e3 * 0.3 / ((1 + 0.3) * (1 - 2 * 0.3))
D = D_matrix(mu_val, lamb_val)


# 
# So there should maybe be some way to modify this k_form onto this new type of system where forces
# can be applied in fx,fy,fz,mx,my,mz format... 
# k_form = sum([hermite_matrix(du, dtheta, t)[i] * hermite_matrix(u_, theta_, t)[i] * ufl.dx for i in range(6)])


'''
So there must be some way of expressing this in dolfinx speak...
So far there is a reference to a Jacobian here that Jeremy uses and I can't
really say I understand it...There is one that I know of for a hexahedron:

    J = Matrix([ # of a hexahedron
        [diff(x1, ξ), diff(x1, η), diff(x1, ζ)],
        [diff(x2, ξ), diff(x2, η), diff(x2, ζ)],
        [diff(x3, ξ), diff(x3, η), diff(x3, ζ)]
    ])

however this is a tetrahedral case I would really like to get working since that gmsh doesn't really have the best capabilities there and tet's work best...

The reason I am expressing things like this is because I didn't get the UFL working yet... I guess this could maybe be express with some type of UFL integration maybe against the bi-linear form...
'''                                                                                                                    
# Quadrature and assembly
# Create stiffness matrix K using Hermite shape functions and D matrix
k_form = 0
for m in range(6):
    for n in range(6):
        Bm_sub = hermite_matrix(m, u_[0], u_[1], u_[2])
        Bn_sub = hermite_matrix(n, du[0], du[1], du[2])
        weight = 1  # Define appropriate weight based on quadrature
        J_det_val = ... # compute the determinant of the appropriate Jacobian of the element such as tet, hexa, etc...
        k_form += weight * (ufl.dot(Bm_sub.T * D, Bn_sub)) * ufl.dx

# Apply boundary conditions and solve
# Create a linear problem and solve for displacements and rotations


So there must be some way of expressing this in dolfinx speak…
So far there is a reference to a Jacobian here that Jeremy uses and I can’t
really say I understand it…There is one that I know of for a hexahedron:


    J = Matrix([ # of a hexahedron
        [diff(x1, ξ), diff(x1, η), diff(x1, ζ)],
        [diff(x2, ξ), diff(x2, η), diff(x2, ζ)],
        [diff(x3, ξ), diff(x3, η), diff(x3, ζ)]
    ])

however this is a tetrahedral case I would really like to get working since that gmsh doesn’t really have the best capabilities there and tet’s work best…

Here in the gist hermite_matrix is really just a placeholder function and a place to implement:

(This works for a hexahedron and is the shape for a hexahdral element where each node has 6 d.o.f.s., So if this can be modified for a tetrahedron that would be super! If not I’m OK with just using hexahedron for everything but people will lose the ability to GMSH really great…:slight_smile:


$$ \begin{bmatrix} \text{NH}{dx} & 0 & 0 & \text{RH}{dx} & 0 & 0 \\ 0 & \text{NH}{dy} & 0 & 0 & \text{RH}{dy} & 0 \\ 0 & 0 & \text{NH}{dz} & 0 & 0 & \text{NH}{dz} \\ \text{NH}{dy} & \text{NH}{dx} & 0 & \text{RH}{dy} & \text{RH}{dx} & 0 \\ 0 & \text{NH}{dz} & \text{NH}{dy} & 0 & \text{RH}{dz} & \text{RH}{dy} \\ \text{NH}{dz} & 0 & \text{NH}{dx} & \text{RH}{dz} & 0 & \text{RH}{dx} \end{bmatrix} $$

The reason I am expressing things like this is because I didn’t get the UFL working yet… I guess this could maybe be express with some type of UFL integration maybe against the bi-linear form…

Things are a little disheveled there in my last reply. This is a much more adequate, Step 1… to developing the scenario I have in mind. So this will run and attempt so far to build the matrix:


$$ \begin{bmatrix} \text{NH}{dx} & 0 & 0 & \text{RH}{dx} & 0 & 0 \\ 0 & \text{NH}{dy} & 0 & 0 & \text{RH}{dy} & 0 \\ 0 & 0 & \text{NH}{dz} & 0 & 0 & \text{NH}{dz} \\ \text{NH}{dy} & \text{NH}{dx} & 0 & \text{RH}{dy} & \text{RH}{dx} & 0 \\ 0 & \text{NH}{dz} & \text{NH}{dy} & 0 & \text{RH}{dz} & \text{RH}{dy} \\ \text{NH}{dz} & 0 & \text{NH}{dx} & \text{RH}{dz} & 0 & \text{RH}{dx} \end{bmatrix} $$

Is there a more polite way of saying this in dolfinx speak by using the dx operater maybe? So far if this can accomplish the goal for the mixed element situation I would like to set up a mock-up to solve this. The boundary conditions and the rest of the boiler plate can be taken from the templates I linked to… So far this is the best that I could to…


import dolfinx
from dolfinx import mesh, fem, plot
from mpi4py import MPI
import basix
import numpy as np
from petsc4py import PETSc
import ufl
from dolfinx.fem.petsc import LinearProblem
from dolfinx.fem import dirichletbc, locate_dofs_topological
from ufl import Measure, SpatialCoordinate, TestFunctions, TrialFunctions, div, inner, dot
import pyvista as pv
import sympy as sp

# Material property matrix for wood
def D_matrix(mu, lamb):
    D = sp.Matrix([
        [2*mu+lamb, lamb, lamb, 0, 0, 0],
        [lamb, 2*mu+lamb, lamb, 0, 0, 0],
        [lamb, lamb, 2*mu+lamb, 0, 0, 0],
        [0, 0, 0, mu, 0, 0],
        [0, 0, 0, 0, mu, 0],
        [0, 0, 0, 0, 0, mu]
    ])
    return D

# Material properties
E = 2000000.0  # Young's modulus in PSI (for structural wood)
nu = 0.299999  # Poisson's ratio (for structural wood)
L = 72  # Beam length in inches
W = 4.0  # Beam width in inches
w = 100.0  # Distributed load in lbs/inch

# Define the mesh with tetrahedral cells
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([L, W, W])],
                         [20, 6, 6], cell_type=mesh.CellType.tetrahedron)

# Define elements for displacements (3 DOF) and rotations (3 DOF)
elem_disp = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_rot = basix.ufl.element("Lagrange", domain.topology.cell_name(), 1, shape=(3,))
elem_mixed = basix.ufl.mixed_element([elem_disp, elem_rot])

V = fem.functionspace(domain, elem_mixed)


# Not in use... Strain tensor as a symmetric gradient of the displacement field
def epsilon(u):
    return 0.5 * (ufl.grad(u) + ufl.grad(u).T)

# Not in use... Stress tensor derived from Hooke's law
def sigma(u):
    lambda_ = E * nu / ((1 + nu) * (1 - 2 * nu))  # First Lamé parameter
    mu = E / (2 * (1 + nu))  # Shear modulus (second Lamé parameter)
    return lambda_ * ufl.tr(epsilon(u)) * ufl.Identity(3) + 2 * mu * epsilon(u)

# Define trial and test functions for displacements (u) and rotations (theta)
(u, theta) = TrialFunctions(V)
(v, tau) = TestFunctions(V)

# Apply boundary conditions and identify subspaces
V0 = V.sub(0)  # Displacement space
V1 = V.sub(1)  # Rotation space
Q0, _ = V0.collapse()
Q1, _ = V1.collapse()
fdim = domain.topology.dim - 1

# Define the trial and test functions for the displacement part (translations only)
du = ufl.TrialFunction(V0)  # Trial function for translations
u_ = ufl.TestFunction(V0)   # Test function for translations

# Define the bilinear form for translations (displacement part)
a_translations = ufl.inner(ufl.grad(du), ufl.grad(u_)) * ufl.dx

# Assemble the bilinear form for translations
A_translations = fem.petsc.assemble_matrix(fem.form(a_translations))
A_translations.assemble()

# Define the trial and test functions for rotations (theta)
dtheta = ufl.TrialFunction(V1)  # Trial function for rotations
theta_ = ufl.TestFunction(V1)   # Test function for rotations

# Define the bilinear form for rotations (using gradient of rotation)
a_rotations = ufl.inner(ufl.grad(dtheta), ufl.grad(theta_)) * ufl.dx

# Assemble the bilinear form for rotations
A_rotations = fem.petsc.assemble_matrix(fem.form(a_rotations))
A_rotations.assemble()

# Print matrix sizes
print(f"Matrix size for translations: {A_translations.size[0]} x {A_translations.size[1]}")
print(f"Matrix size for rotations: {A_rotations.size[0]} x {A_rotations.size[1]}")

# Assuming A_rotations is already defined as a PETSc matrix
#attributes = [attr for attr in dir(A_rotations) if not attr.startswith('_')]
#print(attributes)

# Define the total number of DOF (degrees of freedom)
total_dof = 6174

# Create empty matrix of the required size (6174x6174)
joined_components_K = np.zeros((total_dof, total_dof))

# You are looping through the matrix to populate the 3x3 blocks
# Iterate over blocks as you did previously, but now update the larger matrix
for i in range(0, A_translations.size[0] - 12, 12):
    start_i = i
    end_i = start_i + 12
    trans_block = A_translations[start_i:end_i, start_i:end_i]
    rot_block = A_rotations[start_i:end_i, start_i:end_i]

    # Loop through the diagonal blocks (each 3x3) for both translation and rotation
    for j in range(0, 12, 3):  # j will be the starting index for each 3x3 block
        # Extract the 3x3 blocks for translation and rotation
        NH_block = trans_block[j:j+3, j:j+3]  # 3x3 translation block
        RH_block = rot_block[j:j+3, j:j+3]   # 3x3 rotation block

        # Extract the individual components from NH_block
        NHdx = NH_block[0, 0]  # Translation in x-direction (Node 1)
        NHdy = NH_block[1, 1]  # Translation in y-direction (Node 1)
        NHdz = NH_block[2, 2]  # Translation in z-direction (Node 1)

        # Extract the individual components from RH_block
        RHdx = RH_block[0, 0]  # Rotation about x-axis (Node 1)
        RHdy = RH_block[1, 1]  # Rotation about y-axis (Node 1)
        RHdz = RH_block[2, 2]  # Rotation about z-axis (Node 1)

        # Create the 6x6 matrix from the blocks (NH and RH)
        joined_components_Mat = np.array([
            [NHdx, 0, 0, RHdx, 0, 0],
            [0, NHdy, 0, 0, RHdy, 0],
            [0, 0, NHdz, 0, 0, NHdz],
            [NHdy, NHdx, 0, RHdy, RHdx, 0],
            [0, NHdz, NHdy, 0, RHdz, RHdy],
            [NHdz, 0, NHdx, RHdz, 0, RHdx]
        ])

        # Calculate the global indices (map the local 6x6 block to the larger matrix)
        global_i = start_i + j
        global_j = start_i + j

        # Insert the 6x6 block into the larger matrix (block_matrix_6174x6174)
        # I guess this should really be called B, since it is a network of B's
        # that can be looped over in the fashion of K building in the previous
       # posts, where the transpose of B is used to multiply by D and the det_J....
        joined_components_K[global_i:global_i+6, global_j:global_j+6] = joined_components_Mat

# Now you have a block matrix of dimensions 6174x6174
# You can print or process the matrix as needed
print(joined_components_K.shape)

So essentially Ku=F with 6 dofs per node can now be set up manually either by converting to NumPy or some other acceptible way. I would guess so far that there is a way to have dolfinx apply boundary conditions to the bi-linear form to make that more simple. Mostly how to set this up with UFL and how to solve it without doing all of this so verbosely in the manual sense is a source of confusion for me still…

So the B matrix network now needs to be looped over in the fashion mentioned in its comment. So there will be different types of elements and also Guassian schemes… Is there some facet or function of dolfinx or UFL, that can help to ease the pain and over-verbosity of doing this"?

So the joined K is now a system of B matrices… So about the use of the Jacobian there… When dolfinx builds the bi-linear form does it already apply the determinant of the Jacobian or does that have to be done manually? So far I take things as that the bi-linear form that dolfinx builds is the gradients of the shape functions evaluated at positions which canverts to a scalar in local space? How does that differ from the gradients of the shape functions evaluated at positions in which converts into a scalar multiplied by the determinant of the Jacobian? In other words is the multiplications by the Jacobian determinant built into dolfinx’s building of bi-linear form or should it be added into a potential UFL statement?

The reason why I ask is that I don’t know what dolfinx does exactly and its beyond my bandwidth level to start at this time to go through all of that…

Also its likely to end up a singularity so I’m looking for any comments here on how to resolve that…

So the usage here may be misgnomer… The LaTeX matrix should be built from the gradient (or differntial) of the scalar from the evaluated shape functions…