Customizations

The functions described on this page are somewhere inbetween regular API and internal API, and are intended for making custom solutions, but with a higher risk of breaking changes.

Modify the material

Some advanced or experimental features are documented here, these are typically used in research settings. Their API may be less stable than other parts of the library.

FerriteAssembly.unwrap_material_for_adFunction
unwrap_material_for_ad(m)

Experimental feature: Unwrap a material that has been wrapped in another struct before using for automatic differentiation. This function is called when setting up the AutoDiffCellBuffer's ForwardDiff.JacobianConfig, and all calls to element_routine_ad! should use the unwrapped material from the cellbuffer.

This feature is used to customize the behavior of a material, by performing additional calculations before or after the regular element routine. One example is to modify the stiffness, for example

struct ModifiedStiffness{M,T}
    material::M
    factor::T
end
function FerriteAssembly.unwrap_material_for_ad(m::ModifiedStiffness)
    return FerriteAssembly.unwrap_material_for_ad(m.material)
end
function FerriteAssembly.element_routine!(Ke, re, state, ae, m::ModifiedStiffness, args...)
    FerriteAssembly.element_routine!(Ke, re, state, ae, m.material, args...)
    Ke .+= LinearAlgebra.I*m.factor # Add m.factor to Ke's diagonal
end

Defining unwrap_material_for_ad is then necessary if both the following holds

  1. element_routine! is not implemented for m.material, but automatic differentiation is used.
  2. AutoDiffCellBuffer is used to speed up the automatic differentiation

Please note that

  1. The material is not unwrapped automatically when calling element_routine_ad!. This behavior avoids user defining unwrap_material_for_ad for their wrapper, and then unintentionally unwrap before reaching the element_residual call.
  2. To support wrapped wrappers, overload as unwrap_material_for_ad(m::MyWrapper) = unwrap_material_for_ad(m.material)
source

TaskLocals API (task local storage)

During multithreaded assembly, each task needs its own storage with values that it can change. This can be both cache variables (whose values don't matter after the task completes) or other values that are part of the result from the assembly procedure. To do this in a structured way, the TaskLocals type and associated interface is used for all these cases in the package, but the user should never "see" this type directly (but it can be embedded in types seen by the user, such as ThreadedDomainBuffer). Specifically, a so-called scatter-gather approach is emulated, even though the memory is shared, allowing this to be simplified.

FerriteAssembly.TaskLocalsType
TaskLocals(base; num_tasks=Threads.nthreads())

TaskLocals is used to store a base value and a vector, locals. Items in locals are for each task and can be obtained with get_local. base can be obtained with get_base.

A base-type that supports the TaskLocals API should define the following functions

  • create_local
  • scatter!(local, base)
  • gather!(base, local)
source
FerriteAssembly.create_localFunction
create_local(base::TB)

Create a task local variable, local::TL. In many cases, TL=TB. local's state should match that after calling gather!(base, local) (reset local) followed by scatter!(local, base) (add info from base) (This is important, because it allows create_local to be called while working with tasks, if more locals are needed)

source
FerriteAssembly.scatter!Function
scatter!(local, base)

Write any information from base required to be forwarded to the local::typeof(create_local(base)). (typically if something in base have changed since its creation)

source
FerriteAssembly.gather!Function
gather!(base, local)

Take any information from local::typeof(create_local(base)) that should be added to base after an assembly. In addition, any accumulative information in local should be reset, such that running scatter! -> "do work" -> gather! should only affect the values in base.

source
FerriteAssembly.get_baseFunction
get_base(tl::TaskLocals)
get_base(x::Any)

Get the base variable from tl, or, for an x that is not a TaskLocals, return itself

source

Assembler interface

Different types of assemblers can be created in addition to those already defined by the package. The interface for creating an assembler is that the assembler must support the TaskLocals API, as well as the methods,

  • FerriteAssembly.work_single_cell!(assembler, buffer::AbstractCellBuffer)
  • FerriteAssembly.work_single_facet!(assembler, buffer::FacetBuffer)

The internal methods for builtin assemblers can be used as examples.

In addition, the following functions are used to determine properties/requirements for the assembler.

  • can_thread(assembler)::Bool: Is multithreading supported, if false, sequential assembly will run even if the a ThreadedDomainBuffer is used. If not defined, defaults to false.
  • skip_this_domain(assembler, name::String)::Bool: When running multiple domains, should the domain with key name be skipped? Defaults to false.

Custom scaling

In general, a scaling must, in addition to the TaskLocals API, support the following functions

FerriteAssembly.update_scaling!Function
update_scaling!(scaling, re, cellbuffer)

This function should add the contribution from the element residual vector re to the scaling factors in scaling.

source
FerriteAssembly.reset_scaling!Function
reset_scaling!(scaling)

This function should reset the scaling factors, such that the values don't accumulate if used in multiple iterations/time steps.

source