Defining a fem.Function using a conditional ufl statement

I have a problem similar to Conditional statement in ufl form, where I am trying to define a conditional ufl function so that it can be used as a fem.Function. In this case, this is my analytic solution that I will compare the numerical solution to. I’m currently getting an error, and I would really appreciate some insight into what is going wrong, how I could make the code better, or if there is a different way of approaching this problem.

My conditional function is defined on the unit interval [0,1] and is

\begin{cases} \alpha_1\mathcal{I}_1(x/\gamma) & 0 \leq x \leq r_1 \\ 0.5 \mu_0 x\alpha_2 + \beta_2 / x & r_1\leq x\leq r_2 \\ \beta_3/x & r_2\leq x \end{cases}

where \alpha_1, \alpha_2, \beta_2, \beta_3 are predetermined constants, and \mathcal{I}_1 is a modified bessel function of the first kind.

Please see below for a minimum working example and the error. Many thanks in advance!

Katie
P.s. Please note that this performed using the complex dolfinx kernel.

import numpy as np
from dolfinx import mesh, fem
import ufl
from mpi4py import MPI


domain = mesh.create_unit_interval(MPI.COMM_WORLD, 100)
V = fem.FunctionSpace(domain, ("Lagrange", 1))

x = ufl.SpatialCoordinate(domain)
analytic_A = fem.Function(V)

def analytic_rz(x):   

    # Simplified parameters for the problem
    
    r1 = 0.1
    r2 = 0.2
    r3 = 0.3

    mu0 = 4 * np.pi * 1e-7

    C = 2e8
    I = 100

    gamma = np.sqrt(1j * C)

    alpha2 = I # constants that are already known
    beta1 = 0
    alpha_ext = 0

    alpha1 = (
        mu0
        * r2
        * alpha2
        / (ufl.bessel_I(1, r1) + r1 * gamma * 0.5 * ( ufl.bessel_I(0, r1) + ufl.bessel_I(1, r1)))
    )
    beta2 = r1 * (alpha1 * ufl.bessel_I(1, r1) - 0.5 * mu0 * r1 * alpha2)
    beta_ext = r2 * (beta2 / r2 - 0.5 * mu0 * r2 * alpha2)

    A = ufl.conditional(ufl.And(0 <= x[0], x[0] <= r1), alpha1 * ufl.bessel_I(1, x[0] / gamma ), \
                        ufl.conditional(ufl.And(r1 <= x[0], x[0] <= r2), 0.5 * mu0 * x[0] * alpha2 + beta2 / x[0], beta_ext / x[0]))
    return A

analytic_A.interpolate(analytic_rz)
TypeError                                 Traceback (most recent call last)
File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/fem/function.py:397, in Function.interpolate(self, u, cells, nmm_interpolation_data)
    395 try:
    396     # u is a Function or Expression (or pointer to one)
--> 397     _interpolate(u, cells)
    398 except TypeError:
    399     # u is callable

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/functools.py:889, in singledispatch.<locals>.wrapper(*args, **kw)
    886     raise TypeError(f'{funcname} requires at least '
    887                     '1 positional argument')
--> 889 return dispatch(args[0].__class__)(*args, **kw)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/fem/function.py:373, in Function.interpolate.<locals>._interpolate(u, cells)
    372 """Interpolate a cpp.fem.Function"""
--> 373 self._cpp_object.interpolate(u, cells, nmm_interpolation_data)

TypeError: interpolate(): incompatible function arguments. The following argument types are supported:
    1. (self: dolfinx.cpp.fem.Function_complex128, f: numpy.ndarray[numpy.complex128], cells: numpy.ndarray[numpy.int32]) -> None
    2. (self: dolfinx.cpp.fem.Function_complex128, u: dolfinx.cpp.fem.Function_complex128, cells: numpy.ndarray[numpy.int32], nmm_interpolation_data: Tuple[List[int], List[int], List[float], List[int]]) -> None
    3. (self: dolfinx.cpp.fem.Function_complex128, expr: dolfinx::fem::Expression<std::complex<double>, double>, cells: numpy.ndarray[numpy.int32]) -> None

Invoked with: <dolfinx.cpp.fem.Function_complex128 object at 0x7f872bd772b0>, <function analytic_rz at 0x7f872bb07880>, array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
       34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
       51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
       68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
       85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99],
      dtype=int32), ((), (), (), ())

Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,
<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic
conversions are optional and require extra headers to be included
when compiling your pybind11 module.

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
Cell In[2], line 46
     42     A = ufl.conditional(ufl.And(0 <= x[0], x[0] <= r1), alpha1 * ufl.bessel_I(1, x[0] / gamma ), \
     43                         ufl.conditional(ufl.And(r1 <= x[0], x[0] <= r2), 0.5 * mu0 * x[0] * alpha2 + beta2 / x[0], beta_ext / x[0]))
     44     return A
---> 46 analytic_A.interpolate(analytic_rz)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/fem/function.py:402, in Function.interpolate(self, u, cells, nmm_interpolation_data)
    400 assert callable(u)
    401 x = _cpp.fem.interpolation_coords(self._V.element, self._V.mesh.geometry, cells)
--> 402 self._cpp_object.interpolate(np.asarray(u(x), dtype=self.dtype), cells)

Cell In[2], line 42, in analytic_rz(x)
     39 beta2 = r1 * (alpha1 * ufl.bessel_I(1, r1) - 0.5 * mu0 * r1 * alpha2)
     41 beta_ext = r2 * (beta2 / r2 - 0.5 * mu0 * r2 * alpha2)
---> 42 A = ufl.conditional(ufl.And(0 <= x[0], x[0] <= r1), alpha1 * ufl.bessel_I(1, x[0] / gamma ), \
     43                     ufl.conditional(ufl.And(r1 <= x[0], x[0] <= r2), 0.5 * mu0 * x[0] * alpha2 + beta2 / x[0], beta_ext / x[0]))
     44 return A

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ufl/operators.py:500, in And(left, right)
    498 def And(left, right):
    499     """A boolean expression (left and right) for use with conditional."""
--> 500     return AndCondition(left, right)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ufl/conditional.py:209, in AndCondition.__init__(self, left, right)
    207 def __init__(self, left, right):
    208     """Initialise."""
--> 209     BinaryCondition.__init__(self, "&&", left, right)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ufl/conditional.py:50, in BinaryCondition.__init__(self, name, left, right)
     48 def __init__(self, name, left, right):
     49     """Initialise."""
---> 50     left = as_ufl(left)
     51     right = as_ufl(right)
     53     Condition.__init__(self, (left, right))

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ufl/constantvalue.py:501, in as_ufl(expression)
    499     return IntValue(expression)
    500 else:
--> 501     raise ValueError(
    502         f"Invalid type conversion: {expression} can not be converted to any UFL type.")

ValueError: Invalid type conversion: [ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True] can not be converted to any UFL type.

You are currently mixing methods.
If you want to use ufl-operators you need to wrap analytic_rz in a dolfinx.fem.Expression and use that in interpolate (where x = ufl.SpatialCoordinate(mesh).

If you use numpy arrays for the evalutation, analytic_rz should take in a (3, num_points) numpy array and return an array of shape (num_points, )

1 Like

Hi Dokken,

Thank you for the quick reply!

I still get an error when using fem.Expression, although it is a different one.

Are using Bessel functions allowed for Expressions?

Many thanks,
Katie

import numpy as np
from dolfinx import mesh, fem
import ufl
from mpi4py import MPI


domain = mesh.create_unit_interval(MPI.COMM_WORLD, 100)
V = fem.FunctionSpace(domain, ("Lagrange", 1))

def analytic_rz(x):   

    # Simplified parameters for the problem
    
    r1 = 0.1
    r2 = 0.2
    r3 = 0.3

    mu0 = 4 * np.pi * 1e-7

    C = 2e8
    I = 100

    gamma = np.sqrt(1j * C)

    alpha2 = I # constants that are already known
    beta1 = 0
    alpha_ext = 0

    alpha1 = (
        mu0
        * r2
        * alpha2
        / (ufl.bessel_I(1, r1) + r1 * gamma * 0.5 * ( ufl.bessel_I(0, r1) + ufl.bessel_I(1, r1)))
    )
    beta2 = r1 * (alpha1 * ufl.bessel_I(1, r1) - 0.5 * mu0 * r1 * alpha2)
    beta_ext = r2 * (beta2 / r2 - 0.5 * mu0 * r2 * alpha2)

    A = ufl.conditional(ufl.And(0 <= x[0], x[0] <= r1), alpha1 * ufl.bessel_I(1, x[0] / gamma ), \
                        ufl.conditional(ufl.And(r1 <= x[0], x[0] <= r2), 0.5 * mu0 * x[0] * alpha2 + beta2 / x[0], beta_ext / x[0]))
    return A


x = ufl.SpatialCoordinate(domain)
analytic_A = fem.Function(V)
analytic_expr = fem.Expression(analytic_rz(x), V.element.interpolation_points())
analytic_A.interpolate(analytic_expr)
RuntimeError                              Traceback (most recent call last)
Cell In[2], line 45
     43 x = ufl.SpatialCoordinate(domain)
     44 analytic_A = fem.Function(V)
---> 45 analytic_expr = fem.Expression(analytic_rz(x), V.element.interpolation_points())
     46 analytic_A.interpolate(analytic_expr)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/fem/function.py:149, in Expression.__init__(self, e, X, comm, form_compiler_options, jit_options, dtype)
    146 else:
    147     raise RuntimeError(f"Unsupported scalar type {dtype} for Expression.")
--> 149 self._ufcx_expression, module, self._code = jit.ffcx_jit(comm, (e, _X),
    150                                                          form_compiler_options=form_compiler_options,
    151                                                          jit_options=jit_options)
    152 self._ufl_expression = e
    154 # Prepare coefficients data. For every coefficient in expression
    155 # take its C++ object.

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/jit.py:56, in mpi_jit_decorator.<locals>.mpi_jit(comm, *args, **kwargs)
     51 @functools.wraps(local_jit)
     52 def mpi_jit(comm, *args, **kwargs):
     53 
     54     # Just call JIT compiler when running in serial
     55     if comm.size == 1:
---> 56         return local_jit(*args, **kwargs)
     58     # Default status (0 == ok, 1 == fail)
     59     status = 0

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/dolfinx/jit.py:210, in ffcx_jit(ufl_object, form_compiler_options, jit_options)
    208     r = ffcx.codegeneration.jit.compile_coordinate_maps([ufl_object], options=p_ffcx, **p_jit)
    209 elif isinstance(ufl_object, tuple) and isinstance(ufl_object[0], ufl.core.expr.Expr):
--> 210     r = ffcx.codegeneration.jit.compile_expressions([ufl_object], options=p_ffcx, **p_jit)
    211 else:
    212     raise TypeError(type(ufl_object))

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/jit.py:247, in compile_expressions(expressions, options, cache_dir, timeout, cffi_extra_compile_args, cffi_verbose, cffi_debug, cffi_libraries)
    245     except Exception:
    246         pass
--> 247     raise e
    249 obj, module = _load_objects(cache_dir, module_name, expr_names)
    250 return obj, module, (decl, impl)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/jit.py:238, in compile_expressions(expressions, options, cache_dir, timeout, cffi_extra_compile_args, cffi_verbose, cffi_debug, cffi_libraries)
    235     for name in expr_names:
    236         decl += expression_template.format(name=name)
--> 238     impl = _compile_objects(decl, expressions, expr_names, module_name, p, cache_dir,
    239                             cffi_extra_compile_args, cffi_verbose, cffi_debug, cffi_libraries)
    240 except Exception as e:
    241     try:
    242         # remove c file so that it will not timeout next time

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/jit.py:260, in _compile_objects(decl, ufl_objects, object_names, module_name, options, cache_dir, cffi_extra_compile_args, cffi_verbose, cffi_debug, cffi_libraries)
    256 import ffcx.compiler
    258 # JIT uses module_name as prefix, which is needed to make names of all struct/function
    259 # unique across modules
--> 260 _, code_body = ffcx.compiler.compile_ufl_objects(ufl_objects, prefix=module_name, options=options)
    262 ffibuilder = cffi.FFI()
    263 ffibuilder.set_source(module_name, code_body, include_dirs=[ffcx.codegeneration.get_include_path()],
    264                       extra_compile_args=cffi_extra_compile_args, libraries=cffi_libraries)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/compiler.py:107, in compile_ufl_objects(ufl_objects, object_names, prefix, options, visualise)
    105 # Stage 3: code generation
    106 cpu_time = time()
--> 107 code = generate_code(ir, options)
    108 _print_timing(3, time() - cpu_time)
    110 # Stage 4: format code

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/codegeneration.py:56, in generate_code(ir, options)
     54 code_integrals = [integral_generator(integral_ir, options) for integral_ir in ir.integrals]
     55 code_forms = [form_generator(form_ir, options) for form_ir in ir.forms]
---> 56 code_expressions = [expression_generator(expression_ir, options) for expression_ir in ir.expressions]
     57 code_file_pre, code_file_post = file_generator(options)
     58 return CodeBlocks(file_pre=[code_file_pre], elements=code_finite_elements, dofmaps=code_dofmaps,
     59                   integrals=code_integrals, forms=code_forms, expressions=code_expressions,
     60                   file_post=[code_file_post])

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/codegeneration.py:56, in <listcomp>(.0)
     54 code_integrals = [integral_generator(integral_ir, options) for integral_ir in ir.integrals]
     55 code_forms = [form_generator(form_ir, options) for form_ir in ir.forms]
---> 56 code_expressions = [expression_generator(expression_ir, options) for expression_ir in ir.expressions]
     57 code_file_pre, code_file_post = file_generator(options)
     58 return CodeBlocks(file_pre=[code_file_pre], elements=code_finite_elements, dofmaps=code_dofmaps,
     59                   integrals=code_integrals, forms=code_forms, expressions=code_expressions,
     60                   file_post=[code_file_post])

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/C/expressions.py:37, in generator(ir, options)
     34 d["name_from_uflfile"] = ir.name_from_uflfile
     35 d["factory_name"] = ir.name
---> 37 parts = eg.generate()
     39 CF = CFormatter(options["scalar_type"])
     40 d["tabulate_expression"] = CF.c_format(parts)

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/expression_generator.py:41, in ExpressionGenerator.generate(self)
     39 # Generate the tables of geometry data that are needed
     40 parts += self.generate_geometry_tables()
---> 41 parts += self.generate_piecewise_partition()
     43 all_preparts = []
     44 all_quadparts = []

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/expression_generator.py:145, in ExpressionGenerator.generate_piecewise_partition(self)
    142 F = self.ir.integrand[self.quadrature_rule]["factorization"]
    144 arraysymbol = L.Symbol("sp", dtype=L.DataType.SCALAR)
--> 145 parts = self.generate_partition(arraysymbol, F, "piecewise")
    146 parts = L.commented_code_list(parts, "Unstructured piecewise computations")
    147 return parts

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/expression_generator.py:348, in ExpressionGenerator.generate_partition(self, symbol, F, mode)
    346 # Mapping UFL operator to target language
    347 self._ufl_names.add(v._ufl_handler_name_)
--> 348 vexpr = L.ufl_to_lnodes(v, *vops)
    350 # Create a new intermediate for each subexpression
    351 # except boolean conditions and its childs
    352 if isinstance(parent_exp, ufl.classes.Condition):
    353     # Skip intermediates for 'x' and 'y' in x<y
    354     # Avoid the creation of complex valued intermediates

File ~/anaconda3/envs/env3-10-complex/lib/python3.10/site-packages/ffcx/codegeneration/lnodes.py:920, in ufl_to_lnodes(operator, *args)
    918     return _ufl_call_lookup[optype](operator, *args)
    919 else:
--> 920     raise RuntimeError(f"Missing lookup for expr type {optype}.")

RuntimeError: Missing lookup for expr type <class 'ufl.mathfunctions.BesselI'>.

Seems like bessel functions are currently not supported in ffcx. I believe this is a mistake, and that they should be supported. I’ve added an issue at: Missing bessel functions · Issue #673 · FEniCS/ffcx · GitHub

1 Like

I would actually use numpy directly to create these objects, as there are some inherent issues with the bessel_i and bessel_k functions in C (they don’t support complex numbers), See the aforementioned issue for context.

As you are not really using anything ufl-specific (as a conditional based on floats and spatial coordinates can easily be implemented in numpy as well) working directly with dof coordinates will be easier.

1 Like