FerriteOperators
A SciML compatible high performance parallel assembly system for Ferrite.jl.
For an assembly framework in Ferrite.jl style we refer users for now to FerriteAssembly.jl.
This package is under heavy development. Expect regular breaking changes for now. If you are interested in joining development, then either comment an issue or reach out via julialang.zulipchat.com, via mail or via julialang.slack.com. Alternatively open a discussion if you have something specific in mind.
If you are interested in using this package, then I am also happy to to get some constructive feedback, especially if things don't work out in the current design. This can be done via julialang.slack.com, julialang.zulipchat.com or via mail.
Architecture Overview
FerriteOperators sits between Ferrite modeling code and solver code. It provides a flexible job system to define generic finite element operators. These typically assemble sparse matrices, residual vectors, or apply matrix-free actions with user-defined element formulations. The task system allows to execute these actions either sequentially or in parallel and on different devices (CPU threads, GPUs, ...).
The assembly pipeline is built around four layers:
- Strategies decide how to partition work into items (sequential, per-color, element-assembly / matrix-free).
- Devices decide where to execute (e.g. sequential on the CPU, threaded via Polyester, or GPU via KernelAbstractions).
- Tasks encode what to execute on a device.
- Workspaces hold the pre-allocated per-worker scratch data (e.g. element cache, cell cache, local matrices/vectors, ...) allowing them to execute their assigned tasks independently.
These layers compose into a single generic device loop shared by all operator types, implemented as FerriteOperators.execute_on_device!:
for chunk in partitions
parfor taskid in chunk
reinit!(workspace, taskid)
execute_single_task!(task, workspace)
end
endwhere the partition is computed at setup time by compute_partition(strategy, sdh) and encodes the work distribution (single batch for sequential, color groups for per-color, etc.). The device cache contains the workspace(s) for each parallel worker, constructed by setup_device_instances(device, obj, n_workers), which creates independent copies of obj via duplicate_for_device(device, obj) for parallel execution. The function works on any duplicable object, not only workspaces.
Square operators (bilinear, nonlinear, linear) use an AssemblyWorkspace that holds the local element matrix Ke, unknown vector ue, residual vector re, geometry cache, internal variable handler, and element cache.
Transfer operators (prolongation/restriction) use a TransferWorkspace with the rectangular element matrix Pe, a transfer cell cache, and the transfer element cache.
Adding a new operator type typically only requires defining a new task type and implementing execute_single_task! for the appropriate workspace - the device loop, strategy infrastructure, and parallel duplication remain unchanged.
The Element Interface
For users the most important piece is the element interface. Users need to provide some structs and corresponding dispatches to work with FerriteOperators.jl.
Essentially there are three super-types for elements
FerriteOperators.AbstractVolumetricElementCache — Type
Supertype for all caches to integrate over volumes.
General Interface:
setup_element_cache(integrator, sdh)Specialized Interface for Condensed Problems:
get_number_of_internal_dofs_per_element(model, element_cache, sdh)FerriteOperators.AbstractSurfaceElementCache — Type
Supertype for all caches to integrate over surfaces.
Interface:
setup_boundary_cache(integrator, sdh)FerriteOperators.AbstractInterfaceElementCache — Type
Supertype for all caches to integrate over interfaces.
Interface:
setup_interface_cache(integrator, sdh)FerriteOperators.assemble_element! — Function
assemble_element!(Kₑ::AbstractMatrix, cell::CellCache, element_cache::AbstractVolumetricElementCache, time)Main entry point for bilinear operators
assemble_element!(Kₑ::AbstractMatrix, uₑ::AbstractVector, cell::CellCache, element_cache::AbstractVolumetricElementCache, time)Update element matrix in nonlinear operators
assemble_element!(Kₑ::AbstractMatrix, residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, element_cache::AbstractVolumetricElementCache, time)Update element matrix and residual in nonlinear operators
assemble_element!(residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, element_cache::AbstractVolumetricElementCache, time)Update residual in nonlinear operators
The notation is as follows.
\[K_e\]
the element stiffness matrix\[u_e\]
the element unknowns\[residual_e\]
the element residual
FerriteOperators.assemble_facet! — Function
assemble_facet!(Kₑ::AbstractMatrix, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Main entry point for bilinear operators
assemble_facet!(Kₑ::AbstractMatrix, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update face matrix in nonlinear operators
assemble_facet!(Kₑ::AbstractMatrix, residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update face matrix and residual in nonlinear operators
assemble_facet!(residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update residual in nonlinear operators
The notation is as follows.
\[K_e\]
the element stiffness matrix\[u_e\]
the element unknowns\[residual_e\]
the element residual
FerriteOperators.assemble_interface! — Function
assemble_interface!(Kₑ::AbstractMatrix, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Main entry point for bilinear operators
assemble_interface!(Kₑ::AbstractMatrix, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update face matrix in nonlinear operators
assemble_interface!(Kₑ::AbstractMatrix, residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update face matrix and residual in nonlinear operators
assemble_interface!(residualₑ::AbstractVector, uₑ::AbstractVector, cell::CellCache, face_cache::AbstractSurfaceElementCache, time)Update residual in nonlinear operators
The notation is as follows.
\[K_e\]
the element pair stiffness matrix\[u_e\]
the element pair unknowns\[residual_e\]
the element pair residual
FerriteOperators.setup_element_cache — Function
setup_element_cache(integrator, sdh)Setup the element on a given subdofhandler.
FerriteOperators.setup_boundary_cache — Function
setup_boundary_cache(integrator, sdh)Setup the boundary element on a given subdofhandler.
Missing docstring for FerriteOperators.setup_interface_cache. Check Documenter's build log for details.
Missing docstring for FerriteOperators.load_element_unknowns!. Check Documenter's build log for details.
Only FerriteOperators.AbstractVolumetricElementCache is implemented for now and it covers already all typical use-cases.
Furthermore, each element formulation is derived from an integrator. Integrators are the bridge between elements and materials. Right now, these types of integrators are provided
Missing docstring for FerriteOperators.AbstractBilinearIntegrator. Check Documenter's build log for details.
Missing docstring for FerriteOperators.AbstractNonlinearIntegrator. Check Documenter's build log for details.
Missing docstring for FerriteOperators.AbstractLinearIntegrator. Check Documenter's build log for details.
Transfer Operators
Transfer operators assemble rectangular sparse matrices for prolongation and restriction between two DofHandlers.
FerriteOperators.setup_transfer_operator — Function
setup_transfer_operator(strategy, integrator, dh_row, dh_col)Set up a TransferFerriteOperator for assembling a rectangular sparse matrix of size (ndofs(dh_row) × ndofs(dh_col)).
dh_row and dh_col must live on the same grid and their subdomain lists must correspond 1-to-1 (same length, same cellsets at each index).
integrator must be an AbstractTransferIntegrator; its setup_transfer_element_cache(integrator, sdh_row, sdh_col) method is called once per subdomain pair.
FerriteOperators.setup_nested_transfer_operator — Function
setup_nested_transfer_operator(strategy, integrator, dh_fine, dh_coarse, fine2coarse, child_ref_coords)Set up a NestedTransferFerriteOperator for assembling a rectangular sparse matrix of size (ndofs(dh_fine) × ndofs(dh_coarse)).
dh_fine and dh_coarse must live on different grids where every fine cell is a child of exactly one coarse cell, as encoded by fine2coarse and child_ref_coords.
FerriteOperators.AbstractTransferIntegrator — Type
AbstractTransferIntegratorSupertype for integrators that produce element-local rectangular matrices, i.e. contributions to a transfer (prolongation / restriction) operator between two DofHandlers.
Required methods:
setup_transfer_element_cache(integrator, sdh_row::SubDofHandler, sdh_col::SubDofHandler)
The returned cache must be a subtype of AbstractTransferElementCache.
FerriteOperators.AbstractTransferElementCache — Type
AbstractTransferElementCacheSupertype for element caches used in transfer-operator assembly.
Required method:
assemble_transfer_element!(Pe, tc, element_cache, p)where tc is a SameGridCellCache or a NestedGridCellCache and Pe is the pre-allocated rectangular element matrix of size (nrdofs_per_cell × ncdofs_per_cell).
The Setup Interface
The main entry point for users is the function
which takes a strategy, the integrator and a matching dof handler. Here the strategy controls the type of parallelism, the used device (e.g. threaded CPU or GPU) and the integrator is the hub controlling what exactly will be assembled.
Devices
FerriteOperators.SequentialCPUDevice — Type
SequentialCPUDevice()Sequential algorithms on CPU.
FerriteOperators.PolyesterDevice — Type
PolyesterDevice(chunksize)Threaded algorithms via Polyester.jl. Load Polyester.jl to activate this device.
Strategies
FerriteOperators.SequentialAssemblyStrategy — Type
SequentialAssemblyStrategy()FerriteOperators.PerColorAssemblyStrategy — Type
PerColorAssemblyStrategy(chunksize, coloralg)FerriteOperators.ElementAssemblyStrategy — Type
ElementAssemblyStrategyQuadrature Data
FerriteOperators provides a unified system for working with data at quadrature points (QPs). The same infrastructure serves three purposes:
- Precomputed coefficients – pass per-QP material data into element formulations.
- Matrix-free precomputation – store evaluated quantities (e.g. stresses) at QPs during a separate pass for use in later matrix-free actions.
- Post-processing / visualization – query QP data for export to VTK or other downstream processing.
Storage: QVector
QVector is the flat, cell-indexed storage type. Each cell owns a contiguous slice of the underlying data vector; slices can have different lengths, enabling p-adaptivity and mixed meshes.
FerriteOperators.QVector — Type
QVector{T, VT, OT, NT} <: AbstractVector{T}A flat storage vector for quadrature-point data across all cells, with per-cell random-access via get_range_for_cell.
Fields:
data: flat storage (AbstractVector{T}) holding all quadrature valuesoffsets:offsets[cellid]is the 1-based start index indatafor cellcellidnpoints:npoints[cellid]is the number of quadrature points for cellcellid
Use setup_qvector to build a QVector from a DofHandler or an assembled operator. Use get_range_for_cell to obtain a mutable view into the slice owned by a particular cell.
FerriteOperators.setup_qvector — Function
setup_qvector(::Type{T}, dh::AbstractDofHandler, qrc) -> QVector{T}Build a QVector with element type T whose layout matches the quadrature structure defined by qrc over all cells in dh.
For each SubDofHandler in dh, the number of quadrature points per cell is determined by getnquadpoints(getquadraturerule(qrc, sdh)). Cells not belonging to any subdomain receive zero quadrature points.
setup_qvector(::Type{T}, operator) -> QVector{T}Build a QVector whose layout matches the quadrature structure of operator.
The number of quadrature points per cell is determined from the element caches stored in the operator's subdomain caches via getnquadpoints.
FerriteOperators.get_range_for_cell — Function
get_range_for_cell(q::QVector, cellid::Integer)Return a mutable view into the slice of q that belongs to cell cellid. The view has length following q.npoints.
Evaluation: FerriteQuadratureOperator
FerriteQuadratureOperator is a lightweight operator that drives a user-supplied function over all quadrature points and writes the results into a QVector.
FerriteOperators.FerriteQuadratureOperator — Type
FerriteQuadratureOperatorAn operator for evaluating user-defined functions at quadrature points and storing the results in a QVector.
Build with setup_quadrature_operator and execute with evaluate_quadrature!.
FerriteOperators.setup_quadrature_operator — Function
setup_quadrature_operator(strategy, integrator, dh) -> FerriteQuadratureOperatorSet up a FerriteQuadratureOperator that can be used with evaluate_quadrature! to evaluate a function at all quadrature points.
FerriteOperators.evaluate_quadrature! — Function
evaluate_quadrature!(q::QVector, op, u, p, f, [set = nothing])Evaluate f(ue, qp, cell, element_cache, pe, set) at every quadrature point and return the result.
ue— element-local unknowns loaded fromuqp— the current quadrature pointcell—CellCachefor the current cellelement_cache— element cache (user-provided subtype ofAbstractVolumetricElementCache)pe— element-local parameters derived fromp
FerriteOperators.query_element_quadrature_data — Function
query_element_quadrature_data(element_cache, cell, ivh, q::QVector)Return the per-element quadrature data buffer for the current cell.
The default implementation returns a mutable view into q for cellid(cell), so the user function f(qe, ue, cell, element_cache, pe) writes directly to the global QVector with no extra copy. Override this method together with store_quadrature_data! if your element needs a local staging buffer.
FerriteOperators.store_quadrature_data! — Function
store_quadrature_data!(q::QVector, qe, cell, ivh, element_cache)Copy the element-local quadrature results qe back into the global QVector.
The default implementation is a no-op because qe is already a view into q (see query_element_quadrature_data). Override if your element uses a local staging buffer.
Post-processing: QuadratureDataQuery
QuadratureDataQuery bundles a QVector output buffer with an optional cell-ID filter. Build one with prepare_quadrature_query, run it with process_query!, then inspect query.buffer or write to VTK.
FerriteOperators.QuadratureDataQuery — Type
QuadratureDataQuery{T, QV <: QVector{T}}A post-processing query buffer that holds quadrature-point results for all (or a filtered subset of) cells.
Fields:
buffer—QVector{T}storing the result at every quadrature pointset— optional set of cell IDs to evaluate;nothingmeans all cells
Build with prepare_quadrature_query and execute with process_query!.
FerriteOperators.QuadratureDataMultiQuery — Type
QuadratureDataMultiQuery{Q <: QuadratureDataQuery}A bundle of QuadratureDataQuery objects that are executed together (one pass per query) via process_query!.
FerriteOperators.prepare_quadrature_query — Function
prepare_quadrature_query(::Type{T}, op; set = nothing)
prepare_quadrature_query(::Type{T}, prototype::QuadratureDataQuery)Build a QuadratureDataQuery{T}.
The first form allocates a fresh QVector{T} whose layout matches op. The optional set keyword restricts evaluation to the given cell IDs.
The second form reuses the offset/npoints layout of an existing prototype query, useful for building multiple queries over the same mesh without recomputing the layout.
FerriteOperators.process_query! — Function
process_query!(query::QuadratureDataQuery, op, u, p, f)
process_query!(multi::QuadratureDataMultiQuery, op, u, p, fs)Evaluate f(qe, ue, cell, element_cache, pe) at every quadrature point and store results in query.buffer. If query.set is set, only cells whose ID is in that set are evaluated; all other cells retain their current (typically zero) values.
The multi-query form calls process_query! once per (query, f) pair.
VTK export: VTKQuadratureFile
QP data can be exported to VTK for visualization in e.g. ParaView. The workflow mirrors Ferrite.VTKGridFile:
qgrid = VTKQuadratureGrid(dh, qrc)
VTKQuadratureFile("stress_output", qgrid) do vtk
write_quadrature_data(vtk, σ, "stress")
write_quadrature_data(vtk, query, "plastic_strain") # QuadratureDataQuery
endFerriteOperators.VTKQuadratureGrid — Type
VTKQuadratureGrid{T, sdim}A VTK-compatible "grid" where every node is a quadrature point and every cell is a single-node VTK_VERTEX. Used as the mesh backing a VTKQuadratureFile.
Build with VTKQuadratureGrid(dh, qrc).
FerriteOperators.VTKQuadratureFile — Type
VTKQuadratureFileA VTK file handler for quadrature-point data, analogous to Ferrite.VTKGridFile but backed by a VTKQuadratureGrid.
Use the do-block syntax (which calls close automatically):
qgrid = VTKQuadratureGrid(dh, qrc)
VTKQuadratureFile("output", qgrid) do vtk
write_quadrature_data(vtk, σ, "stress")
endFerriteOperators.write_quadrature_data — Function
write_quadrature_data(vtk::VTKQuadratureFile, q::QVector, name)Write quadrature-point data from q to the VTK point-data field name. Supports both scalar (QVector{<:Real}) and vector (QVector{Vec{dim,T}}) data.
write_quadrature_data(vtk::VTKQuadratureFile, q::QuadratureDataQuery, name)Write the buffer inside q to the VTK point-data field name.