dolfinx icon indicating copy to clipboard operation
dolfinx copied to clipboard

Restructure `FunctionSpace` and `Form` for mixed-topology meshes

Open jpdean opened this issue 11 months ago • 7 comments

Currently, for mixed-topology meshes we have to create a FunctionSpace and Form for each cell type. This isn't very natural and leads to objects with almost the same data; for example, the main difference between the forms is just the kernel.

I think we should restructure this so we create a single FunctionSpace (with possibly multiple element types) and a single Form (with possibly multiple kernels).

This should hopefully simplify both the user interface and the assembler implementation, since we can just loop over all kernels in the assembler rather than having to reverse engineer the cell type from the form.

jpdean avatar Dec 19 '24 13:12 jpdean

Proposed Restructuring: Single FunctionSpace: Create a single FunctionSpace that can handle multiple element types. This could involve defining the elements in a flexible way (e.g., using a list or dictionary to store different elements that can be applied to different cell types).

Single Form: Similarly, create a single Form that can handle multiple kernels. The Form would contain a list of kernels, and the assembler could loop over all kernels rather than needing to reverse engineer the form's cell type.

Simplification of Data Management: This will reduce the need for maintaining separate structures for each cell type and make the codebase more maintainable. The function space and form will be more general-purpose and less specific to individual cell types, which can simplify the user interface for users.

Detailed Steps for Implementation:

Define a General FunctionSpace: The FunctionSpace should be able to store multiple elements (which might correspond to different cell types). Example: FunctionSpace fs; fs.add_element(element1); // e.g., P1 element for triangular cells fs.add_element(element2); // e.g., P2 element for quadrilateral cells

Define a General Form: The Form can store multiple kernels, each corresponding to a different cell type or operation. Example: Form f; f.add_kernel(kernel1); // e.g., stiffness matrix kernel for triangular elements f.add_kernel(kernel2); // e.g., mass matrix kernel for quadrilateral elements

Assembler Looping over Kernels: The assembler should loop over all kernels in the form and apply the appropriate kernel to the corresponding cells. This way, we avoid having to reverse-engineer the cell type. Example: for (auto& kernel : form.get_kernels()) { assembler.apply_kernel(kernel); }

Digital365Staking avatar Dec 20 '24 22:12 Digital365Staking

FunctionSpace fs; fs.add_element(element1); // e.g., P1 element for triangular cells fs.add_element(element2); // e.g., P2 element for quadrilateral cells

This kind of design goes against how rest of DOLFINx is built, as for each add there would need to be several recomputations(dofmap update->function update->recompile form-> update matrix/vector)

A constructor that takes a list would be more appropriate.

jorgensd avatar Dec 21 '24 05:12 jorgensd

Yes. A constructor-based approach that takes a list of elements at once would be a more efficient design.

Here’s an example of how such a constructor might look:

class FunctionSpace { public: // Constructor taking a list of elements FunctionSpace(const std::vector<Element>& elements) { for (const auto& element : elements) { add_element_internal(element); } finalize_initialization(); }

private: // Internal method to add an element void add_element_internal(const Element& element) { // Logic for adding a single element elements_.push_back(element); // Update internal state based on the new element }

// Finalize initialization after all elements are added
void finalize_initialization() {
    update_dofmap();
    recompile_forms();
    update_matrices_and_vectors();
}

void update_dofmap() { /*...*/ }
void recompile_forms() { /*...*/ }
void update_matrices_and_vectors() { /*...*/ }

std::vector<Element> elements_;

};

Key Improvements: Batch Processing: All elements are processed in one go, minimizing redundant computations. Consistency with DOLFINx: Aligns with DOLFINx's design principles of efficiency and performance. Simplified Interface: The API is cleaner and encourages users to provide all elements upfront.

Usage Example:

std::vector<Element> elements = {element1, element2}; FunctionSpace fs(elements); This ensures that all costly updates (dofmap, form recompilation, etc.) occur only once, after all elements are added. It also encourages better practices by users, making them think about the entire configuration of the FunctionSpace in advance.

Digital365Staking avatar Dec 21 '24 10:12 Digital365Staking

@Digital365Staking Im not against you using LLMs to scope out ideas and understand what's going on, but could you not write the output here? It's clutter.

jhale avatar Dec 21 '24 11:12 jhale

@Digital365Staking Im not against you using LLMs to scope out ideas and understand what's going on, but could you not write the output here? It's clutter.

Hello, my question is : how much is the payment for collaborating on the project ? I have no intention of working for free. Thanks.

Digital365Staking avatar Dec 21 '24 12:12 Digital365Staking

@Digital365Staking Im not against you using LLMs to scope out ideas and understand what's going on, but could you not write the output here? It's clutter.

Hello, my question is : how much is the payment for collaborating on the project ? I have no intention of working for free. Thanks.

This is an open source project. Hence there is no payment involved in contributing.

jorgensd avatar Dec 21 '24 12:12 jorgensd

Hello, my question is : how much is the payment for collaborating on the project ? I have no intention of working for free. Thanks.

No worries @Digital365Staking, nobody will ever ask you for anything more on this project: you have now been banned from the organization.

francesco-ballarin avatar Dec 21 '24 13:12 francesco-ballarin