Custom Addons

Standard message passing schemes only pass along distributions to other nodes. However, for more advanced usage, there might be a need for passing along additional information in messages and/or marginals. One can for example think of passing along the scaling of the distribution or some information that specifies how the message or marginal was computed, i.e. which messages were used for its computation and which node was preceding it. Another use cases is saving extra debugging information inside messages themselves, e.g. what arguments have been used to compute a message.

Addons provide a solution here. Basically, addons are structures that contain extra information that are passed along the graph with messages and marginals in a tuple. These addons can be extracted using the getaddons(message/marginal) function. Its usage and operations can differ significantly for each application, yet below gives a concise overview on how to implement them on your own.

Example

Suppose that we wish to create an addon that counts the number of computations that preceded some message or marginal. This addon can be created by adding the file src/addons/count.jl and by including it in the ReactiveMP.jl file.

Step 1: Creating the addon structure

Let's start by defining our new addon structure. This might seem daunting, but basically only requires us to specify the information that we would like to collect. Just make sure that it is specified as a subtype of AbstractAddon. In our example this becomes:

struct AddonCount{T} <: AbstractAddon
    count :: T
end

You can add additional fields or functions for improved handling, such as get_count() or show() functions.

Step 2: Compute addon value after computing a message

As a second step we need to specify how the addon behaves when a new message is computed in a factor node. For this purpose we need to implement a specialized version of the message_mapping_addon() function. This function accepts the mapping variables of the factor node and updates the addons by extending the tuple.

In our example we could write

# This specification assumes that the default value for addon is `AddonCount(nothing)`
function message_mapping_addon(::AddonCount{Nothing}, mapping, messages, marginals, result, addons)

    # get number of operations of messages
    message_count = 0
    for message in messages
        message_count += getcount(message)
    end

    # get number of operations of marginals
    marginal_count = 0
    for marginal in marginals
        marginal_count += getcount(marginal)
    end

    # extend addons with AddonCount() structure
    return AddonCount(message_count + marginal_count + 1)
end

Step 3: Computing products

The goal is to update the AddonCount structure when we multiply 2 messages. As a result, we need to write a function that allows us to define this behaviour. This function is called multiply_addons and accepts 5 arguments. In our example this becomes

function multiply_addons(left_addon::AddonCount, right_addon::AddonCount, new_dist, left_dist, right_dist)
    return AddonCount(left_addon.count + right_addon.count + 1)
end

here we add the number of operations from the addons that are being multiplied and we add one (for the current operation). we are aware that this is likely not valid for iterative message passing schemes, but it still serves as a nice example. the left_addon and right_addon argument specify the addoncount objects that are being multiplied. corresponding to these addons, there are the distributions left_dist and right_dist, which might contain information for computing the product. the new distribution new_dist ∝ left_dist * right_dist is also passed along for potentially reusing the result of earlier computations.

More information

For more advanced information check the implementation of the log-scale or memory addons.

Built-in addons

ReactiveMP.AddonDebugType
AddonDebug(f :: Function)

This addon calls the function f over the output of the message mapping and products. The result is expected to be boolean and when returning true, it will throw an error with the debug information. Common applications of this addon are to check for NaNs and Infs in the messages and marginals.

Example

checkfornans(x) = isnan(x)
checkfornans(x::AbstractArray) = any(checkfornans.(x))
checkfornans(x::Tuple) = any(checkfornans.(x))

addons = (AddonDebug(dist -> checkfornans(params(dist))),)
source