CEED / libCEED

CEED Library: Code for Efficient Extensible Discretizations
https://libceed.org
BSD 2-Clause "Simplified" License
198 stars 47 forks source link

Rust: Creation of QFunctions from C source #1677

Open eliasboegel opened 8 hours ago

eliasboegel commented 8 hours ago

Hi,

In #1621, it was mentioned that functionality to create QFunctions from C source through the Rust bindings would be reasonably straightforward to implement. This would allow usage of the JIT backends when using the Rust bindings from what I can tell. (With this type of QFunction creation, is it still possible to run non-JIT on CPU?)

First, I would like to hear some general opinions on including this as a feature next to QFunctions from the gallery and QFunctions from closures.

Secondly, given that this probably not high on your priority list, I'm happy to contribute to this (with some guidance) if that would be welcome/helpful.

jeremylt commented 8 hours ago

I think this would be a good add. Past deciding upon little interface details, I think the bulk of the work would be 1) Make Rust compile a stand alone C file 2) Pass the function pointer and filepath back to the libCEED C interface 3) Decide how to handle context data

eliasboegel commented 8 hours ago

Good to hear!

Questions to your points:

  1. This would be necessary only if one wants to run a C source QFunction on a non-JIT backend, correct?
  2. I am not familiar with how libCEED in general handles QFunctions on the C side. Do libCEED's JIT backends care whether the C source is supplied as a file in its final form at compile time, or whether it is supplied as just a string at runtime? Context: I'm interested in doing some specialization of constants of the C source at runtime for the JIT backends when creating an operator.
jeremylt commented 7 hours ago

1) I think it would make the most sense for the interface to take both the function pointer and the path so it's easy to swap back and forth between backends. We could even internally create the function pointer from the path.

2) The GPU backends want a filepath to a file it can open and read, such as https://github.com/CEED/libCEED/blob/main/include/ceed/jit-source/gallery/ceed-poisson3dapply.h The backend already does compile time specification of a lot of the pieces. What did you want to specialize upon in the source file? We usually use the context data (automatically captured by the closure currently) to specialize on details in the user physics.

eliasboegel commented 5 hours ago
  1. I agree that it should take both. My question was more about which compilation path are used in which cases.
  2. I was thinking mainly about the number of quadrature points, as well as the number of components, since those may not be known at compile time. I see that e.g. in VectorMassApply, num_components is simply a fixed constant, but Q is a parameter. I suppose parameters can work for this. Another use I was thinking of was implementing an operator for projecting an arbitrary function onto an FE space that takes in a C code snippet, essentially to set user-defined initial conditions as a function of the mesh coordinate. I don't see any other way than inserting a string of C code into a QFunction skeleton if I want to use the libCEED infrastructure to do the projection.
jeremylt commented 5 hours ago

The JiT backends know Q and that is a compile time constant for them. The number of components is compile time constant from the basis.

I'm not sure I follow why you wouldn't be able to write the functions you would possibly want to use ahead of time? If it's not possible to do that for some reason, you could make a Rust function to write brand new QFunction source files I suppose?

jrwrigh commented 4 hours ago

I see that e.g. in VectorMassApply, num_components is simply a fixed constant, but Q is a parameter.

May not be helpful, but the way we get around this in HONEE and the fluids example is to have qfunction helper that's flexible and then have a series of qfunctions that set the number of components as a compile-time constant. Then we have a function that will return the appropriate qfunction for the number of components desired.

eliasboegel commented 1 hour ago

The JiT backends know Q and that is a compile time constant for them. The number of components is compile time constant from the basis.

You're right, in hindsight I don't see a problem for these kinds of constants. My concern came primarily from the way it is done with the Rust closures.

I'm not sure I follow why you wouldn't be able to write the functions you would possibly want to use ahead of time? If it's not possible to do that for some reason, you could make a Rust function to write brand new QFunction source files I suppose?

The only (but realistic) example I have at this time would be specifying a function f(x,y,z) to project on the finite element space as part of a configuration file that is read at runtime. Without touching the QFunction source at runtime in one way or another I don't see a way to inject a custom function at runtime. Emitting or modifying a C source file at runtime would be totally fine for that too - I was just curious as to whether the libCEED backends normally expect a file or just a string that holds the C source code.

May not be helpful, but the way we get around this in HONEE and the fluids example is to have qfunction helper that's flexible and then have a series of qfunctions that set the number of components as a compile-time constant. Then we have a function that will return the appropriate qfunction for the number of components desired.

Interesting to see, thanks for this!