Assigning cell values in discontinuous galerkin space

Hi,
first of all thanks for providing this project!
My question relates to the ordering of cells and vertices when creating a mesh. I’d like to load an external mesh with nodes and tetrahedra cell connections (cells) to construct a dolfinx mesh:

c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(nodes.shape[1],)))
mesh = create_mesh(MPI.COMM_WORLD, cells, nodes, c_el)

As far as I know, the ordering of the nodes and cells in the resulting mesh is now different to the ordering of nodes and cells.
So first, I tried to locate specific nodes that I want to consider as sink and source nodes. To do this, I access mesh.geometry.x and search for the coordinates of my nodes to get the correct index.

Then, I want to assign a conductivity value to all cells in the mesh, which is already predefined and aligned with the cells array.
So, I used mesh.topology.original_cell_index for a cells to mesh.topology.connectivity(3, 0).array.reshape(cells.shape) mapping (cell_map).
To assign the correct conductivities, I use the following code:

DG0 = fem.functionspace(mesh, ("DG", 0))
sigma = fem.Function(DG0)
sigma.x.array[:] = conductivities[cell_map]

I apply the sigma-function to the weak form:

V = fem.functionspace(mesh, ("CG", 1))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = ufl.dot(sigma *ufl.grad(u), ufl.grad(v)) * ufl.dx

The results, however, do not align with the expected results obtained from COMSOL.
I’m new to the topic and appreciate any help that I can get!

Thanks in advance! :slight_smile:

For this you should probably consider my answer at: How to apply a boundary condition to a range of DOFs? - #7 by dokken

Could you please provide all definitions here, as there is no way of telling what conductivities and cell_map is from your snippets.

Thanks a lot for your quick response!
Sorry for the incomplete snippets. Here is the complete code:

    # create mesh and compute all connections
    c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(nodes.shape[1],)))
    mesh = create_mesh(MPI.COMM_WORLD, cells, nodes, c_el)
    mesh.topology.create_connectivity(mesh.topology.dim, 2)
    mesh.topology.create_connectivity(mesh.topology.dim-1, 1)
    mesh.topology.create_connectivity(mesh.topology.dim-2, 0)
    mesh.topology.create_connectivity(0, mesh.topology.dim-1)
    mesh.topology.create_connectivity(mesh.topology.dim-1, mesh.topology.dim)
    boundary_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
    node_map = get_original_index_mapping(mesh, nodes) # from dolfinx mesh to orignal; return idx of node in dolfinx mesh

    # define function space
    V = fem.functionspace(mesh, ("CG", 1))

    # define DG0 function space for conductivity
    DG0 = fem.functionspace(mesh, ("DG", 0))
    sigma = fem.Function(DG0)
    sigma.x.array[:] = conductivities[mesh.topology.original_cell_index]

    # find corresponding dof (source, sink, ref_nod and electrodes are just ids)
    source_dof = node_map[source]
    sink_dof = node_map[sink]
    ground_dof = node_map[ref_node]
    electrodes_dof = []
    for e in electrode_nodes:
        electrodes_dof.append(node_map[e])

    source_facet_indices = mesh.topology.connectivity(mesh.topology.dim - 1, 0).links(source_dof)
    sink_facet_indices = mesh.topology.connectivity(mesh.topology.dim - 1, 0).links(sink_dof)
    source_facet_indices = [i for i in source_facet_indices if i in boundary_facets]
    sink_facet_indices = [i for i in sink_facet_indices if i in boundary_facets]

    # define boundary conditions for potential at source, sink, and ground
    bc_source = fem.dirichletbc(np.array(curr, dtype=np.float64), np.asarray(source_dof.astype(np.int32)).reshape(1), V)
    bc_sink = fem.dirichletbc(np.array(-curr, dtype=np.float64), np.asarray(sink_dof.astype(np.int32)).reshape(1), V)
    bc_ground = fem.dirichletbc(np.array(0., dtype=np.float64), np.asarray(ground_dof.astype(np.int32)).reshape(1), V)

    # define the trial and test functions for the weak form
    u = ufl.TrialFunction(V)
    v = ufl.TestFunction(V)

    # define Poisson's equation weak form
    a = ufl.dot(sigma *ufl.grad(u), ufl.grad(v)) * ufl.dx
    f = fem.Constant(mesh, 0.)
    L = f * v * ufl.dx

    # use provided facet IDs for Neumann boundary condition
    facet_ids = np.concatenate([source_facet_indices, sink_facet_indices])
    facet_tags = np.concatenate([np.full(len(source_facet_indices), 1, dtype=np.int32), np.full(len(sink_facet_indices), 2, dtype=np.int32)])
    facet_tag = dolfinx.mesh.meshtags(mesh, mesh.topology.dim - 1, facet_ids, facet_tags)

    # create a measure for the boundary
    ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_tag)

    # add Neumann boundary condition to the weak form
    L += curr * v * ds(1) 
    L += -curr * v * ds(2)

    # solve the linear system
    problem = LinearProblem(a, L, bcs=[bc_sink, bc_source],
                            petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"})
    uh = problem.solve()

@dokken can you help me out here?

@dmnk1308
Your code is not complete, as it is missing:
-imports of packages

  • definitions of the mesh nodes and connectivity
  • Any error message you are currently getting.

Is also not defined.

I thought that this would overcomplicate the code. Also, I can’t provide the exact mesh as it involves sensitive data.

import basix
import pyvista as pv
import numpy as np
from dolfinx.mesh import create_mesh, exterior_facet_indices, meshtags
from dolfinx.fem import *
from dolfinx import fem
from mpi4py import MPI
from scipy.spatial import cKDTree
import ufl

def get_original_index_mapping(mesh, original_points):
    # get new coordinates of nodes from the DOLFINx mesh
    new_coordinates = mesh.geometry.x
    
    # build a KDTree for the new coordinates
    tree = cKDTree(new_coordinates)
    
    # query the KDTree to find the nearest points in new coordinates for each original point
    _, nearest_indices = tree.query(original_points)

    # create a mapping dictionary
    original_to_new_mapping = [nearest_indices[i].astype(int) for i in range(len(original_points))]
    
    return np.array(original_to_new_mapping)

def find_closest_node(points, target_node):
    # convert the target_node to a numpy array (if it's a list or tuple)
    target_node = np.array(target_node)
    
    # calculate Euclidean distances between the target node and all other points
    distances = np.linalg.norm(points - target_node, axis=1)
    
    # find the index of the minimum distance
    closest_node_index = np.argmin(distances)
    return closest_node_index

# read mesh with pyvista and get nodes and cells
mesh_init = pv.read('mesh.vtk')
nodes = np.asarray(mesh_init.points)
cells = np.array([mesh_init.get_cell(i).point_ids for i in range(mesh_init.n_cells)])
set_conductivities = np.array([0.0, 0.3, 1/10, 0.7, 1/50, 1/40])
conductivities = set_conductivities[mesh_init.cell_data['material']]

# read electrode coordinates and get corresponding nodes
electrodes = np.loadtxt('electrodes/electrodes.txt')
# last coordinates belong to reference electrode
ref_electrode = electrodes[-1]
electrodes = electrodes[:-1]

e_idx = []
for e in electrodes:
    idx = np.where(np.all(nodes==e, axis=1))[0]
    if idx.size==1:
        e_idx.append(int(idx))
    else:
        idx = find_closest_node(nodes, e)
        e_idx.append(idx)
electrode_nodes = np.array(e_idx).reshape(4,16)
ref_electrode_node = np.where(np.all(nodes==ref_electrode,axis=1))[0]
if len(ref_electrode_node)==0:
    for i, node in enumerate(nodes):
        if np.allclose(node, ref_electrode):
            ref_electrode_node = [i]
            break

# set source, sink, curr
ref_node = ref_electrode_node[0]
curr = 1.
sink = electrode_nodes[0,0]
source = electrode_nodes[0,1]
source_coord = nodes[source].reshape(1,3)
sink_coord = nodes[sink].reshape(1,3)
ground_coord = nodes[ref_node].reshape(1,3)

# create mesh and compute all connections
c_el = ufl.Mesh(basix.ufl.element("Lagrange", "tetrahedron", 1, shape=(nodes.shape[1],)))
mesh = create_mesh(MPI.COMM_WORLD, cells, nodes, c_el)
mesh.topology.create_connectivity(mesh.topology.dim, 2)
mesh.topology.create_connectivity(mesh.topology.dim-1, 1)
mesh.topology.create_connectivity(mesh.topology.dim-2, 0)
mesh.topology.create_connectivity(0, mesh.topology.dim-1)
mesh.topology.create_connectivity(mesh.topology.dim-1, mesh.topology.dim)
boundary_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
node_map = get_original_index_mapping(mesh, nodes) # from dolfinx mesh to orignal; return idx of node in dolfinx mesh

# define function space
V = fem.functionspace(mesh, ("CG", 1))

# define DG0 function space for conductivity
DG0 = fem.functionspace(mesh, ("DG", 0))
sigma = fem.Function(DG0)
sigma.x.array[:] = conductivities[mesh.topology.original_cell_index]

# find corresponding dof (source, sink, ref_nod and electrodes are just ids)
source_dof = node_map[source]
sink_dof = node_map[sink]
ground_dof = node_map[ref_node]
electrodes_dof = []
for e in electrode_nodes:
    electrodes_dof.append(node_map[e])

source_facet_indices = mesh.topology.connectivity(mesh.topology.dim - 1, 0).links(source_dof)
sink_facet_indices = mesh.topology.connectivity(mesh.topology.dim - 1, 0).links(sink_dof)
source_facet_indices = [i for i in source_facet_indices if i in boundary_facets]
sink_facet_indices = [i for i in sink_facet_indices if i in boundary_facets]

# define boundary conditions for potential at source, sink, and ground
bc_source = fem.dirichletbc(np.array(curr, dtype=np.float64), np.asarray(source_dof.astype(np.int32)).reshape(1), V)
bc_sink = fem.dirichletbc(np.array(-curr, dtype=np.float64), np.asarray(sink_dof.astype(np.int32)).reshape(1), V)
bc_ground = fem.dirichletbc(np.array(0., dtype=np.float64), np.asarray(ground_dof.astype(np.int32)).reshape(1), V)

# define the trial and test functions for the weak form
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)

# define Poisson's equation weak form
a = ufl.dot(sigma *ufl.grad(u), ufl.grad(v)) * ufl.dx
f = fem.Constant(mesh, 0.)
L = f * v * ufl.dx

# use provided facet IDs for Neumann boundary condition
facet_ids = np.concatenate([source_facet_indices, sink_facet_indices])
facet_tags = np.concatenate([np.full(len(source_facet_indices), 1, dtype=np.int32), np.full(len(sink_facet_indices), 2, dtype=np.int32)])
facet_tag = dolfinx.mesh.meshtags(mesh, mesh.topology.dim - 1, facet_ids, facet_tags)

# create a measure for the boundary
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_tag)

# add Neumann boundary condition to the weak form
L += curr * v * ds(1) 
L += -curr * v * ds(2)

# solve the linear system
problem = LinearProblem(a, L, bcs=[bc_sink, bc_source],
                        petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"})
uh = problem.solve()

I don’t get any error message, the values don’t match the excepted ones.

Point 1: of is quite hard to debug a code if one can’t run it
Point 2: have you checked that sigma looks ok? After assigning data to it, save it to file and check that it looks as expected