Skip to content

Comments

compute_extrema function over domain for a UFL expression#187

Open
jorgensd wants to merge 7 commits intomainfrom
dokken/min-max
Open

compute_extrema function over domain for a UFL expression#187
jorgensd wants to merge 7 commits intomainfrom
dokken/min-max

Conversation

@jorgensd
Copy link
Member

Makes it possible to compute the min or max of a scalar valued ufl expression over a domain.

@jorgensd jorgensd requested a review from finsberg February 21, 2026 10:10
@jorgensd
Copy link
Member Author

jorgensd commented Feb 21, 2026

Resolves #186

Comment on lines 304 to 306
cache_dir = (
(Path.cwd() / f".scifem_extrema_cache_{mesh.comm.rank}_{u_sign}").absolute().as_posix()
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should hard code in the cace dir. I think have an environment variable SCIFEM_CACHE_DIR or something like that which can be used? And perhaps also make it possible to pass this as an argument to the function? Finally I think the default cache dir should be Path.home() / ".cache" / "scifem" instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have a global cache dir (as each rank needs its own cache dir, as described below).

return X_phys, sign * result.fun


def compute_LP_average(u: ufl.core.expr.Expr, p: int, domain: dolfinx.mesh.Mesh):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is missing a docstring.

Comment on lines 160 to 192
def eval_J(x_ref):
if isinstance(u, dolfinx.fem.Function):
_x_p[: mesh.geometry.dim] = mesh.geometry.cmap.push_forward(
x_ref.reshape(-1, mesh.topology.dim), mesh_nodes
)[0]
try:
u_eval = u.eval(_x_p, _cell)[0]
except RuntimeError: # NOTE: Nonlinear pullback might fail on low precision
u_expr = dolfinx.fem.Expression(
u,
x_ref,
comm=MPI.COMM_SELF,
jit_options=jit_options,
dtype=mesh.geometry.x.dtype,
)
u_eval = u_expr.eval(mesh, _cell)[0][0]
else:
u_expr = dolfinx.fem.Expression(
u, x_ref, comm=MPI.COMM_SELF, jit_options=jit_options, dtype=mesh.geometry.x.dtype
)
u_eval = u_expr.eval(mesh, _cell)[0][0]
return np.float64(sign * u_eval) # SLSQP only supports float64

def eval_dJ(x_ref):
u_grad_expr = dolfinx.fem.Expression(
ufl.Jacobian(mesh).T * ufl.grad(u),
x_ref,
comm=MPI.COMM_SELF,
jit_options=jit_options,
dtype=mesh.geometry.x.dtype,
)
u_grad_eval = u_grad_expr.eval(mesh, _cell)[0][0]
return (sign * u_grad_eval).astype(np.float64) # SLSQP only supports float64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder it is would be more clean to put these to methods as instance methods on a class instead. The class could potentially be useful in other contexts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure... We can talk about this tomorrow.

Comment on lines 307 to 313
if Path(cache_dir).exists():
raise RuntimeError(f"Cache directory for extrema exists at {cache_dir}")
jit_options = {
"cffi_extra_compile_args": ["-march=native", "-O3"],
"cffi_libraries": ["m"],
"cache_dir": cache_dir,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this specific cache dir in the first place? And why is it a problem if it already exist?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we are running dolfinx.fem.Expression on MPI.COMM_SELF, for all the cells (local to this process) that are part of the optimization process. To avoid racing conditions to the cache if all processes tries to generate code for the same expression at the same point, we need a specific cache dir for each of them.

Secondly, as the optimization method will greedly evaluate both expr(X_ref) and grad(expr)(X_ref) this will make a huge cache that will slow any other DOLFINx operation down.

for item in Path(cache_dir).iterdir():
# Check if the item is a file
if item.is_file():
item.unlink() # Delete the file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if comm.rank == 0 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each comm has its own cache dir.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants