Pass entity index to integration kernel for cell-dependent custom data#4210
Open
sclaus2 wants to merge 20 commits into
Open
Pass entity index to integration kernel for cell-dependent custom data#4210sclaus2 wants to merge 20 commits into
sclaus2 wants to merge 20 commits into
Conversation
Replace void* with nullptr pattern with std::optional<void*> for custom_data in the Form class and assembly functions. This is the canonical modern C++ approach for representing optional values. Changes: - Form.h: integral_data::custom_data now uses std::optional<void*> - Form.h: custom_data() returns std::optional<void*> - Form.h: set_custom_data() accepts std::optional<void*> - assemble_*_impl.h: Function parameters use std::optional<void*> - assemble_*_impl.h: Kernel calls use .value_or(nullptr) - Python bindings: Handle std::optional with None support - Test: Update to expect None instead of 0 for unset custom_data Benefits: - Clearer intent: "optional value" vs "magic nullptr" - Type safety: Distinguishes "no value" from "null pointer value" - Pythonic: Returns None instead of 0 when not set - Zero-cost: .value_or(nullptr) compiles to same code
Address review comments to maintain Form immutability: - Remove set_custom_data method from Form class - Pass custom_data as 5th element of integrals tuple at Form creation - Integrals tuple format: (id, kernel_ptr, entities, coeffs, custom_data) - custom_data can be None (maps to std::nullopt) or a pointer (uintptr_t) Pass cell index through entity_local_index for cell integrals: - Cell integrals now receive &cell instead of nullptr for entity_local_index - Enables per-cell data lookup in custom kernels via custom_data - FFCx-generated kernels don't read entity_local_index for cells (return 0) Safety documentation: - Add warning about void pointer safety to Form.h and Python bindings - Document that users must keep data alive while Form is in use Update tests: - Update test_custom_jit_kernels.py to use 5-element tuples - Expand test_custom_data.py with per-cell material property example
…ata_to_assembler # Conflicts: # cpp/dolfinx/fem/assemble_matrix_impl.h # cpp/dolfinx/fem/assemble_vector_impl.h # python/dolfinx/wrappers/fem.cpp
Member
|
I'm not sure I agree with your LLMs reasoning here - best jump in a thread on #development before continuing. |
Contributor
Author
|
I am very open to suggestions. I wrote a short message in #development . |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pass assembly loop index to UFCx kernels for cell-dependent custom data
Motivation
This PR is to start a discussion about how to pass the current entity index to the integration kernel. This PR is an extension of PR #4013
Currently the entity index is not exposed to the integration kernels which makes the use of cell based
custom_dataimpossible. Cell basedcustom_datais for example essential in runtime quadrature, where the quadrature rule varies from entity to entity.I see three natural ways to provide this context:
data pointer for the current loop entry.
stores the current loop index.
entity_local_indexargument of the UFCx kernel.
This PR implements option 3. The implementation is intentionally small and keeps
the UFCx kernel signature unchanged, but the choice is not obvious. The rest of
this note compares the options.
Option 1: Per-entity callback
std::function<void*(std::int32_t loop_index)> custom_dataThe form could store a callable object, for example:
The assembler would call this function for each entity before invoking the UFCx
kernel:
The kernel continues to receive a plain
void*. The pointer can refer directlyto the data for the current entity, so the kernel does not need to know about the
global layout of the runtime data structure.
Advantages
code simple.
indexed by the assembly loop.
Disadvantages
decisions.
objects being involved.
Option 2: Context struct passed through
custom_dataThe assembler could wrap the user pointer in a small DOLFINx-owned context
object:
The assembler would update the index before each kernel call:
CustomDataContext ctx{user_custom_data, 0}; for (std::int32_t index = 0; index < num_entities; ++index) { ctx.loop_index = index; kernel(A.data(), coeffs, constants, coordinate_dofs, entity_local_index, quadrature_permutation, &ctx); }The kernel would cast
custom_datato this context type and then usectx->loop_indexto select the correct runtime data:Advantages
custom_databecomes a context containing both theuser data pointer and the current loop index.
call.
number of entities could be added later if DOLFINx defines a stable ABI for
the context.
Disadvantages
custom_data. Kernels would need to knowwhether they receive the raw user pointer or a DOLFINx context wrapper.
custom_datato their own typewould not automatically work with wrapped data unless wrapping is opt-in or
otherwise carefully designed.
fine for synchronous assembly, but the pointer must not escape the kernel
call.
void*.Option 3: Pass the loop index through
entity_local_indexThis PR implements the least invasive option: keep passing the user
custom_datapointer unchanged, and pass the current assembly loop index throughthe existing
entity_local_indexkernel argument.For cell integrals,
entity_local_indexis not otherwise needed by the kernel,so it can point directly to the current loop index:
For exterior facet, vertex, and ridge integrals, DOLFINx currently passes a
pointer to the local entity index. The loop index can be attached efficiently by
passing a small stack array that keeps the existing local entity index in the
first entry and adds the assembly loop index in the second entry:
For interior facets, DOLFINx currently passes the two local facet indices, one
for each adjacent cell. The loop index can be appended:
Advantages
The UFCx kernel signature is unchanged.
Existing
custom_datapointers are passed through unchanged.The implementation is small: the assembler only writes one additional integer
into data that is already passed to the kernel.
The runtime cost is negligible and comparable to option 2.
It is easy for a runtime quadrature kernel to use:
Disadvantages
entity_local_index.the object behind
custom_data.as cleanly as a dedicated context object.
This option is a pragmatic way to make cell-dependent
custom_datauseful now,with minimal changes to DOLFINx and no UFCx signature change.
Comparison
entity_local_indexcustom_datapointerWhy this PR implements option 3
Option 3 is implemented because it solves the immediate runtime quadrature
problem with the smallest API and ABI surface:
custom_datastorage remains valid.entity_local_indexto the kernel, so the changeis localized to how that array is populated.
This makes it a useful first implementation for discussion. It demonstrates that
cell-dependent runtime data can be selected inside the kernel without requiring
quadrature data to be globally constant across all cells.
Proposed discussion point
This PR chooses option 3 because it is the least invasive way to support
cell-dependent runtime quadrature data. However, option 2 may be semantically
cleaner if DOLFINx wants
custom_datato become a general runtime context and Option 1 is the most flexible but adds API complexity and an indirect call in the assembly loop.I would be grateful for your opinions and input as the cell based custom_data feature is essential for my work on runtime quadrature rules.