### Describe new/missing feature
In C++, we use pure ufl/basix python files to …compile forms.
I would like the same user-interface from Python.
This is for instance neat when one uses multiple meshes in the same code, or want to make a library containing various forms, that could be used with either the Python or C++ interface.
A minimal working product is described below.
Here I use `get_integration_domains`, current a hidden function in DOLFINx python interface to compute the integration entities.
The only change to make this work is to expose `dolfinx::fem::create_form` to the Python interface:
```c++
m.def(
pymethod_create_form.c_str(),
[](std::uintptr_t form,
const std::vector<
std::shared_ptr<const dolfinx::fem::FunctionSpace<U>>>& spaces,
const std::map<std::string,
std::shared_ptr<const dolfinx::fem::Function<T, U>>>&
coefficients,
const std::map<std::string,
std::shared_ptr<const dolfinx::fem::Constant<T>>>&
constants,
const std::map<dolfinx::fem::IntegralType,
std::vector<std::pair<std::int32_t,
std::span<const std::int32_t>>>>&
subdomains,
std::shared_ptr<const dolfinx::mesh::Mesh<U>> mesh = nullptr)
{
std::map<
dolfinx::fem::IntegralType,
std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>>>
sd;
for (auto& [itg, data] : subdomains)
{
std::vector<std::pair<std::int32_t, std::span<const std::int32_t>>> x;
for (auto& [id, idx] : data)
x.emplace_back(id, std::span(idx.data(), idx.size()));
sd.insert({itg, std::move(x)});
}
ufcx_form* p = reinterpret_cast<ufcx_form*>(form);
return dolfinx::fem::create_form<T, U>(*p, spaces, coefficients,
constants, sd, mesh);
},
nb::arg("form"), nb::arg("spaces"), nb::arg("coefficients"),
nb::arg("constants"), nb::arg("subdomains"), nb::arg("mesh"),
"Create Form from a pointer to ufcx_form.");
```
**Things to improve**
- Currently one has to use the `ufl` - coeffs/constants to map to the dolfinx coeffs/constants. This is due to the fact that we cannot supply names to dolfinx.jit.ffcx_jit or ufl, meaning that the coefficients are named `w0, w1,..` and constants `c0, c1, ...`.
### Suggested user interface
```python3
import ufl
import basix.ufl
import numpy as np
from mpi4py import MPI
import dolfinx
import numpy.typing as npt
import typing
from dataclasses import dataclass
@dataclass
class GeneralForm:
ufl_form: ufl.Form
ufcx_form: typing.Any
module: typing.Any
code: str
def compile_form(form: ufl.Form, comm: MPI.Intracomm) -> GeneralForm:
compiled_form = dolfinx.jit.ffcx_jit(comm, J)
return GeneralForm(form, *compiled_form)
def get_integration_domains(integral_type, subdomain):
"""Get integration domains from subdomain data"""
if subdomain is None:
return []
else:
try:
if integral_type in (
dolfinx.fem.IntegralType.exterior_facet,
dolfinx.fem.IntegralType.interior_facet,
):
tdim = subdomain.topology.dim
subdomain._cpp_object.topology.create_connectivity(tdim - 1, tdim)
subdomain._cpp_object.topology.create_connectivity(tdim, tdim - 1)
domains = dolfinx.cpp.fem.compute_integration_domains(
integral_type, subdomain._cpp_object
)
return [(s[0], np.array(s[1])) for s in domains]
except AttributeError:
return [(s[0], np.array(s[1])) for s in subdomain]
def form_cpp_creator(
dtype: npt.DTypeLike,
) -> typing.Union[
dolfinx.cpp.fem.Form_float32,
dolfinx.cpp.fem.Form_float64,
dolfinx.cpp.fem.Form_complex64,
dolfinx.cpp.fem.Form_complex128,
]:
"""Return the wrapped C++ class of a variational form of a specific scalar type.
Args:
dtype: Scalar type of the required form class.
Returns:
Wrapped C++ form class of the requested type.
Note:
This function is for advanced usage, typically when writing
custom kernels using Numba or C.
"""
if np.issubdtype(dtype, np.float32):
return dolfinx.cpp.fem.create_form_float32
elif np.issubdtype(dtype, np.float64):
return dolfinx.cpp.fem.create_form_float64
elif np.issubdtype(dtype, np.complex64):
return dolfinx.cpp.fem.create_form_complex64
elif np.issubdtype(dtype, np.complex128):
return dolfinx.cpp.fem.create_form_complex128
else:
raise NotImplementedError(f"Type {dtype} not supported.")
def create_form(
form: GeneralForm,
mesh: dolfinx.mesh.Mesh,
coefficient_map: typing.Dict[str, dolfinx.fem.Function],
constant_map: typing.Dict[str, dolfinx.fem.Constant],
):
sd = form.ufl_form.subdomain_data()
(domain,) = list(sd.keys()) # Assuming single domain
# Subdomain markers (possibly empty list for some integral types)
subdomains = {
dolfinx.fem.forms._ufl_to_dolfinx_domain[key]: get_integration_domains(
dolfinx.fem.forms._ufl_to_dolfinx_domain[key], subdomain_data[0]
)
for (key, subdomain_data) in sd.get(domain).items()
}
coefficients = {
f"w{u.count()}": uh._cpp_object for (u, uh) in coefficient_map.items()
}
constants = {f"c{c.count()}": ch._cpp_object for (c, ch) in constant_map.items()}
ftype = form_cpp_creator(dolfinx.default_scalar_type)
f = ftype(
form.module.ffi.cast("uintptr_t", form.module.ffi.addressof(form.ufcx_form)),
[],
coefficients,
constants,
subdomains,
mesh._cpp_object,
)
return dolfinx.fem.Form(f, form.ufcx_form, form.code)
# Create ufl form exclusively with basix.ufl and UFL
c_el = basix.ufl.element("Lagrange", "triangle", 1, shape=(2,))
domain = ufl.Mesh(c_el)
el = basix.ufl.element("Lagrange", "triangle", 2)
V = ufl.FunctionSpace(domain, el)
u = ufl.Coefficient(V)
w = ufl.Coefficient(V)
c = ufl.Constant(domain)
e = ufl.Constant(domain)
J = c * e * u * w * ufl.dx(domain=domain)
# Compile form using dolfinx.jit.ffcx_jit
compiled_form = compile_form(J, MPI.COMM_WORLD)
def create_and_integrate(N, compiled_form):
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, N, N)
el_2 = basix.ufl.element("Lagrange", "triangle", 2)
Vh = dolfinx.fem.functionspace(mesh, el_2)
uh = dolfinx.fem.Function(Vh)
uh.interpolate(lambda x: x[0])
wh = dolfinx.fem.Function(Vh)
wh.interpolate(lambda x: x[1])
eh = dolfinx.fem.Constant(mesh, 3.0)
ch = dolfinx.fem.Constant(mesh, 2.0)
form = create_form(compiled_form, mesh, {u: uh, w: wh}, {c: ch, e: eh})
print(mesh.comm.allreduce(dolfinx.fem.assemble_scalar(form), op=MPI.SUM))
# Create various meshes, that all uses this compiled form with a map from ufl to dolfinx functions and constants
for i in range(1, 4):
create_and_integrate(i, compiled_form)
```