I am currently working on a problem where I need to find the maximum value of a function in a triangular cell. My current approach involves sampling points (defined by the coordinates of the three vertices) and evaluating the function at these sampled points to determine the maximum value.
However, I am concerned that this method may miss the true maximum value due to the limited number of sample points, especially in regions where the function might have sharp peaks or variations.
Is there a more robust method within FEniCSx to accurately find the maximum value of a function over each mesh cell?
This will depend on the function space you are using, as you can probably exploit the structure of the basis functions of each cell to give you a good guess as to where the maximum of the function will be.
So the question is then, what is the function space you are using?
Since it is a linear space, the max value is definitely at a vertex. Thus you can use dofs = V.dofmap.cell_dofs(i) to get the indices of the vertices in a given cell (index i), and then access the appropriate u.x.array[dofs] (remember to unroll for block size if using a vector space).
Thanks for your affirmation. I think I can well access the value at vertex. However, what about lagrange 2-oder functionspace? The max value may be not at the vertex. Is there a method to get it accurately? Furthermore, is there a general method to find the max value of any type function in each cell?
where F and \mathcal{F} is described in DefElement: How to define a finite element, c_i are the local coefficients on the given cell, \hat x is the coordinates on the reference cell.
This function is also not correct, as you are mixing topology and geometry indices.
Here is what I would do in the case of P1:
from mpi4py import MPI
import dolfinx
import numpy as np
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10, dolfinx.cpp.mesh.CellType.triangle)
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: x[0] + 2*x[1] + np.cos(x[0]))
dofmap = V.dofmap
bs = dofmap.bs
dof_coords = V.tabulate_dof_coordinates()
num_cells_local = mesh.topology.index_map(mesh.topology.dim).size_local
for c in range(num_cells_local):
cell_dofs = dofmap.cell_dofs(c)
# Max per component in space
cell_vals = u.x.array[cell_dofs]
pos = np.argmax(cell_vals)
print(f"Max value in cell {c} is {cell_vals[pos]} at {dof_coords[cell_dofs[pos]]} (other options: {cell_vals}, {dof_coords[cell_dofs]})")
My idea is to obtain the coordinates of the three vertices of the cell, not the degrees of freedom, because only in this way can we truly locate the range of the triangle. I think this function is correct if the search range is limited to sampling points.
The test is as follows:
from mpi4py import MPI
import dolfinx
from dolfinx import fem, mesh
import numpy
def max_value_in_cell(function: fem.Function, cell_index: int, mesh) -> float:
cell_vertices = mesh.topology.connectivity(mesh.topology.dim, 0).links(cell_index)
points = mesh.geometry.x[cell_vertices]
num_samples = 10000
sample_points = numpy.random.rand(num_samples, 2)
u = sample_points[:, 0]
v = sample_points[:, 1]
mask = u + v > 1
u[mask] = 1 - u[mask]
v[mask] = 1 - v[mask]
coords = (1 - u - v)[:, numpy.newaxis] * points[0] + \
u[:, numpy.newaxis] * points[1] + \
v[:, numpy.newaxis] * points[2]
values = function.eval(coords, numpy.full(num_samples, cell_index))
max_pos = numpy.argmax(values)
return values[max_pos], coords[max_pos], points
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10, dolfinx.cpp.mesh.CellType.triangle)
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: x[0] + 2*x[1])
num_cells_local = mesh.topology.index_map(mesh.topology.dim).size_local
for c in range(num_cells_local):
value, coord, vertices_coords = max_value_in_cell(u, c, mesh)
print(f"Max value in cell {c} is {value} at {coord}\n",
f"the vertices coordinates in cell {c} are \n{vertices_coords}")
What do you mean by this? If you use a P1 function space, the dofs are located at the vertices, whatever those vertices are are not affected by V.tabulate_dof_coordinates() which I just used to illustrate to you want the coordinates are.
The true value at the vertex is the value that is in u.x.array, u.eval will be affected by floating point accuracy in the push forward.
Okay. What I mean is, if the function space is not āLagrange 1ā and the degrees of freedom are not located at the vertices of each cell, is there aother method to obtain the coordinates of the cell vertices?
Thank you for reminding me again. Is it correct to use the compute_incident_entities like this:
Apologies for getting back to this so late! Iāve been busy with other commitments recently, but now Iām finally revisiting this approach and trying to implement it properly. However, Iāve hit a bit of a roadblock and was hoping to ask for some additional guidance.
If I understand correctly, the idea is to first obtain the expression of the function on each cell in FEniCSx and then use a numerical optimization tool in Python to find the maximum value. Regarding obtaining the function expression, you previously shared a link about defining finite elements, which I believe is closely related to this process. However, the link now seems to be broken. Could you kindly share an updated link or point me to any other resources that might help?
Thank you for sharing the updated link! Itās really helpful for understanding the finite element definitions in more detail.
Iāve been thinking about this and trying to start the implementation, but I find it really difficult to even begin the processāI feel stuck at the very first step. From my understanding, the first step involves combining the coefficients with the basis functions on the reference element to form the target function. Is that correct? I also tried searching the community for similar issues and came across some potentially helpful posts, like Accessing the basis function of a Finite Element, but Iām not sure if they are relevant or necessary for this optimization problem.
Could I ask for your further guidance on this? Thanks again for your patience and supportāI really appreciate it.
I am looking for the function values on the individual cells, in the similar manner as you shown above but on the vectorial function space V = dolfinx.fem.functionspace(mesh, ("CG", 1, (mesh.geometry.dim,)))
I can manage to do this , if I need the individual components of the vectorial space seperately
Dear @dokken, I have a possibly naive follow-up: is there a more efficient or vectorized way to compute the maximum value of the DOFs per cell across the entire mesh?
As far as I can tell, dofmap.cell_dofs() only accepts a single cell index at a time, which means I have to loop over all cells in Python. This works, but becomes a bottleneck for large meshes. Just wondering if thereās a faster alternative ā maybe a way to get all cell DOFs in one shot, or use lower-level access?
dofmap.list will give you an adjacency list that you can access the raw data from (through .array and .offsets:
If you check that the offsets for each cell is constant, which is a 99% certainty, you can reshape dofmap.list.array as dofmap.list.array.reshape(num_cells_local+num_ghost_cells, -1) to get them all at once.
In general, finding the maximum value within a cell is a quite expensive operation (when the basis function is not linear). However, it can be done, I just havenāt had the bandwidth to sit down and implement it (as it involves a Newton solver for each individual cell).
Based on the suggestion from leader @dokken, I implemented a function that computes the maximum value of all DoFs in each cell. I tried both a vectorized version and a loop-based version of the function. To my surprise, I found that vectorization had almost no impact on the actual computation speed. Iām sharing both versions of the code here, in case others are interested in exploring this further or spotting potential improvements.(FEniCSx version: 0.7.2)
Hi @dokken, I just tested this on DOLFINx 0.9.0, and it seems that dofmap.list itself is already a 2D NumPy array with shape (num_cells_local + num_ghost_cells, ...), and it doesnāt have an .array attribute. So it looks like thereās no need to access .array or .offsets, and also no need to reshape ā itās already structured for per-cell DoF access.
I wonder if there might have been some mix-up between the Python and C++ layer APIs, or perhaps a change from earlier versions?
Could you confirm if thatās the case? Just wanted to make sure Iām not missing anything.