FerriteOperators

A SciML compatible high performance parallel assembly system for Ferrite.jl.

Note

For an assembly framework in Ferrite.jl style we refer users for now to FerriteAssembly.jl.

Warning

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.

Note

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:

  1. Strategies decide how to partition work into items (sequential, per-color, element-assembly / matrix-free).
  2. Devices decide where to execute (e.g. sequential on the CPU, threaded via Polyester, or GPU via KernelAbstractions).
  3. Tasks encode what to execute on a device.
  4. 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
end

where 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.AbstractVolumetricElementCacheType

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)
source
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
source
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
source
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
source
Missing docstring.

Missing docstring for FerriteOperators.setup_interface_cache. Check Documenter's build log for details.

Missing docstring.

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.

Missing docstring for FerriteOperators.AbstractBilinearIntegrator. Check Documenter's build log for details.

Missing docstring.

Missing docstring for FerriteOperators.AbstractNonlinearIntegrator. Check Documenter's build log for details.

Missing docstring.

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_operatorFunction
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.

source
FerriteOperators.setup_nested_transfer_operatorFunction
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.

source
FerriteOperators.AbstractTransferIntegratorType
AbstractTransferIntegrator

Supertype 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.

source

The Setup Interface

The main entry point for users is the function

Missing docstring.

Missing docstring for setup_operator. Check Documenter's build log for details.

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

Strategies

Quadrature Data

FerriteOperators provides a unified system for working with data at quadrature points (QPs). The same infrastructure serves three purposes:

  1. Precomputed coefficients – pass per-QP material data into element formulations.
  2. Matrix-free precomputation – store evaluated quantities (e.g. stresses) at QPs during a separate pass for use in later matrix-free actions.
  3. 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.QVectorType
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 values
  • offsets: offsets[cellid] is the 1-based start index in data for cell cellid
  • npoints: npoints[cellid] is the number of quadrature points for cell cellid

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.

source
FerriteOperators.setup_qvectorFunction
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.

source
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.

source
FerriteOperators.get_range_for_cellFunction
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.

source

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.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 from u
  • qp — the current quadrature point
  • cellCellCache for the current cell
  • element_cache — element cache (user-provided subtype of AbstractVolumetricElementCache)
  • pe — element-local parameters derived from p
source
FerriteOperators.query_element_quadrature_dataFunction
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.

source

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.prepare_quadrature_queryFunction
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.

source
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.

source

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
end
FerriteOperators.VTKQuadratureFileType
VTKQuadratureFile

A 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")
end
source
FerriteOperators.write_quadrature_dataFunction
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.

source
write_quadrature_data(vtk::VTKQuadratureFile, q::QuadratureDataQuery, name)

Write the buffer inside q to the VTK point-data field name.

source