Create a custom mesh using dolfinx.mesh.create_mesh

I’m trying to obtain the elements (cells) and nodes (points) from a gmsh file to manipulate them and then create a dolfinx mesh. However, I’m having trouble with the dolfinx.mesh.create_mesh function, which I’ve seen has been subjected to changes (I’m using dolfinx version 0.8).
This is my simple code so far:

import numpy as np
from mpi4py import MPI
import ufl
import basix.ufl
import dolfinx
from dolfinx.io import XDMFFile
import meshio


def create_custom_mesh_3d(cells, x):
    # Ensure cells and x are numpy arrays with correct types
    cells = np.asarray(cells, dtype=np.int64, order='C')
    x = np.asarray(x, dtype=np.float64, order='C')
    
    # Debugging statements
    print(f"cells shape: {cells.shape}")
    print(f"x shape: {x.shape}")
    
    # Ensure x has the correct shape (N, 3)
    if x.ndim != 2 or x.shape[1] != 3:
        raise ValueError("The array x should be a 2D array with shape (N, 3).")
    
    # Ensure cells has the correct shape (num_cells, 4)
    if cells.ndim != 2 or cells.shape[1] != 4:
        raise ValueError("The array cells should be a 2D array with shape (num_cells, 4).")

    c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(x.shape[1],)))
    #c_el = dolfinx.fem.coordinate_element(dolfinx.mesh.CellType.tetrahedron, 1)
    custom_mesh = dolfinx.mesh.create_mesh(MPI.COMM_SELF, cells, c_el, x)
    return custom_mesh


def write_mesh_to_xdmf(mesh, filename):
    # Write the mesh to an XDMF file
    with XDMFFile(mesh.comm, filename, "w") as file:
        file.write_mesh(mesh)


mesh_file_name = "sphere"
# Read the .vtk file using meshio
meshio_mesh = meshio.read(mesh_file_name + ".msh", file_format="gmsh")
# Extract data 
cells = [cell.data for cell in meshio_mesh.cells if cell.type == "tetra"]
points = meshio_mesh.points
# TODO: MANIPULATE CELLS AND POINTS HERE WHEN CODE WORKS


# Convert cells to the required shape
if len(cells) > 0:
    cells = np.concatenate(cells)

# Create the mesh
custom_mesh = create_custom_mesh_3d(cells, points)
print(custom_mesh)  
write_mesh_to_xdmf(custom_mesh, "sphere_custom.xdmf")

When I run it, I’m getting this error:

cells shape: (683, 4)
x shape: (204, 3)
Traceback (most recent call last):
  File "/home/cborau/fenicsx_projects/test7.py", line 50, in <module>
    custom_mesh = create_custom_mesh_3d(cells, points)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cborau/fenicsx_projects/test7.py", line 29, in create_custom_mesh_3d
    custom_mesh = dolfinx.mesh.create_mesh(MPI.COMM_SELF, cells,c_el, x )
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cborau/miniconda3/envs/fenicsx-env/lib/python3.12/site-packages/dolfinx/mesh.py", line 401, in create_mesh
    gdim = x.shape[1]
           ~~~~~~~^^^
IndexError: tuple index out of range

And this is the code I use to create the sphere mesh:

from mpi4py import MPI
import gmsh
def gmsh_sphere(model: gmsh.model, name: str) -> gmsh.model:
    """Create a Gmsh model of a sphere.

    Args:
        model: Gmsh model to add the mesh to.
        name: Name (identifier) of the mesh to add.

    Returns:
        Gmsh model with a sphere mesh added.

    """
    model.add(name)
    model.setCurrent(name)
    sphere = model.occ.addSphere(0, 0, 0, 1, tag=1)

    # Synchronize OpenCascade representation with gmsh model
    model.occ.synchronize()

    # Add physical marker for cells. It is important to call this
    # function after OpenCascade synchronization
    model.add_physical_group(dim=3, tags=[sphere])

    # Generate the mesh
    model.mesh.generate(dim=3)
    return model


gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0)
model = gmsh.model()
model = gmsh_sphere(model, "Sphere")
model.setCurrent("Sphere")
gmsh.write('sphere.msh')

Thanks in advance!

Simply change the order to

    custom_mesh = dolfinx.mesh.create_mesh(MPI.COMM_SELF, cells, x, c_el)

It is worthwhile looking at the signature of the function:
https://docs.fenicsproject.org/dolfinx/v0.8.0/python/generated/dolfinx.mesh.html#dolfinx.mesh.create_mesh

Thanks for your fast response! I swear I had the code like that before but I was getting a positional error. Anyway, it works now.
However, if I may continue the topic, I’m facing a new problem now. What I was trying to do with this code is to create a fully overlapping mesh: a mesh that has every element duplicated, but shares the same nodes with its “twin”. That is, the nodes are the same, but the cells are duplicated (I need this to solve a mechanical problem in which two phases of a biological material suffer a different growth and they “compete” mechanically)
If I introduce the duplicated cells, the code crashes (see error below). Is there a way to achieve what I want or am I facing an unpassable obstacle with fenicsx?

This is the error:

cells shape: (1366, 4)
x shape: (204, 3)
[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 and https://petsc.org/release/faq/
[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.
Abort(59) on node 0 (rank 0 in comm 0): application called MPI_Abort(MPI_COMM_WORLD, 59) - process 0

This is the new code duplicating cells:

import numpy as np
from mpi4py import MPI
import ufl
import basix.ufl
import dolfinx
from dolfinx.io import XDMFFile
import meshio


def create_custom_mesh_3d(cells, x):
    # Ensure cells and x are numpy arrays with correct types
    cells = np.asarray(cells, dtype=np.int64, order='C')
    x = np.asarray(x, dtype=np.float64, order='C')
    
    # Debugging statements
    print(f"cells shape: {cells.shape}")
    print(f"x shape: {x.shape}")
    
    # Ensure x has the correct shape (N, 3)
    if x.ndim != 2 or x.shape[1] != 3:
        raise ValueError("The array x should be a 2D array with shape (N, 3).")
    
    # Ensure cells has the correct shape (num_cells, 4)
    if cells.ndim != 2 or cells.shape[1] != 4:
        raise ValueError("The array cells should be a 2D array with shape (num_cells, 4).")

    c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(x.shape[1],)))
    #c_el = dolfinx.fem.coordinate_element(dolfinx.mesh.CellType.tetrahedron, 1)
    custom_mesh = dolfinx.mesh.create_mesh(MPI.COMM_SELF, cells, x, c_el)
    return custom_mesh


def write_mesh_to_xdmf(mesh, filename):
    # Write the mesh to an XDMF file
    with XDMFFile(mesh.comm, filename, "w") as file:
        file.write_mesh(mesh)


mesh_file_name = "sphere"
# Read the .vtk file using meshio
meshio_mesh = meshio.read(mesh_file_name + ".msh", file_format="gmsh")
# Extract data 
cells = [cell.data for cell in meshio_mesh.cells if cell.type == "tetra"]
points = meshio_mesh.points

# Convert cells to the required shape
if len(cells) > 0:
    cells = np.concatenate(cells)

# Duplicate the elements (overlapping mesh)    
cells = np.tile(cells, (2, 1))

# Create the mesh
custom_mesh = create_custom_mesh_3d(cells, points)
print(custom_mesh)  
write_mesh_to_xdmf(custom_mesh, "sphere_custom.xdmf")

Will the cells actually be moved in physical space, or are you talking about have two different species that has different physical laws?
If you really want exact duplicate cells, I would create the normal mesh,and then in turn use dolfinx.mesh.create_submesh, but send in all cells of your mesh.

Yes, I’m talking about two different species with their own physical laws within the same finite element.
Sorry for my ignorance (first time using fenicsx), but once I do this:
dup_mesh = dolfinx.mesh.create_submesh(custom_mesh, dim=3, entities=np.arange(cells.shape[0], dtype=np.int32))

What should I do now? I see that dup_mesh[0] contains a mesh object, dup_mesh[1] an array with the cell index (not duplicated), dup_mesh[2] and dup_mesh[3] an array with the node index.

How do I combine that with my previous mesh to create a domain that I can use to solve the mechanical problem as in:

V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim, )))

Then you should use a mixed_element, as this is way simpler than duplicating the mesh. See for instance:
https://docs.fenicsproject.org/dolfinx/v0.8.0/python/demos/demo_stokes.html
or any of
https://docs.fenicsproject.org/dolfinx/v0.8.0/python/search.html?q=mixed_element&check_keywords=yes&area=default

Thanks for the links, I’ve checked them and more questions arise, so I’m going to move it to a separate post. Cheers