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_ad
— Functionunwrap_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
element_routine!
is not implemented form.material
, but automatic differentiation is used.AutoDiffCellBuffer
is used to speed up the automatic differentiation
Please note that
- The material is not unwrapped automatically when calling
element_routine_ad!
. This behavior avoids user definingunwrap_material_for_ad
for their wrapper, and then unintentionally unwrap before reaching theelement_residual
call. - To support wrapped wrappers, overload as
unwrap_material_for_ad(m::MyWrapper) = unwrap_material_for_ad(m.material)
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.TaskLocals
— TypeTaskLocals(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)
FerriteAssembly.create_local
— Functioncreate_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)
FerriteAssembly.scatter!
— Functionscatter!(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)
FerriteAssembly.gather!
— Functiongather!(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
.
FerriteAssembly.get_local
— Functionget_local(tl::TaskLocals, i::Int)
Get the i
th local
variable from tl
FerriteAssembly.get_locals
— Functionget_locals(tl::TaskLocals)
Get the vector of locals in tl
FerriteAssembly.get_base
— Functionget_base(tl::TaskLocals)
get_base(x::Any)
Get the base
variable from tl
, or, for an x
that is not a TaskLocals
, return itself
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, iffalse
, sequential assembly will run even if the aThreadedDomainBuffer
is used. If not defined, defaults tofalse
.skip_this_domain(assembler, name::String)::Bool
: When running multiple domains, should the domain with keyname
be skipped? Defaults tofalse
.
Custom scaling
In general, a scaling must, in addition to the TaskLocals
API, support the following functions
FerriteAssembly.update_scaling!
— Functionupdate_scaling!(scaling, re, cellbuffer)
This function should add the contribution from the element residual vector re
to the scaling factors in scaling
.
FerriteAssembly.reset_scaling!
— Functionreset_scaling!(scaling)
This function should reset the scaling factors, such that the values don't accumulate if used in multiple iterations/time steps.