Callbacks

Callbacks observe message passing. The engine reports an event before and after every rule call, every product of messages, every form constraint, every marginal of a random variable, every joint marginal a node computes, and every term of the free energy. A callback handler reacts to the events it is interested in. Callbacks serve debugging, tracing and monitoring, and they change nothing the engine computes. To transform the streams themselves, use stream postprocessors.

Attaching callbacks

You give callbacks in two places:

An event after a rule call names the rule that ran, its rule: the RuleSpec, which shows where the rule is defined, the node's rule fallback where no rule matched, or nothing where a missing input skipped the rule. The engine builds an event only for a handler that listens to it, so a graph activated without callbacks, or with callbacks for other events, runs as fast as one that reports nothing.

The examples use the Gaussian node of The example node. The simplest handler is a NamedTuple keyed by event names, each entry a function of the event. Mind the trailing comma of a one-entry NamedTuple, (name = f,).

x, y = randomvar(label = :x), datavar(label = :y)
prior = factornode(Gaussian, [(:out, x), (:μ, constvar(0.0)), (:v, constvar(10.0))])
likelihood = factornode(Gaussian, [(:out, y), (:μ, x), (:v, constvar(1.0))])

callbacks = (
    after_message_rule_call = (event) -> println("rule towards ", event.mapping.target, ": ", event.result),
    after_marginal_computation = (event) -> println("marginal: ", getdata(event.result)),
)

marginal_context = MessageProductContext(; callbacks)
activate!(x, RandomVariableActivationOptions(nothing, MessageProductContext(), marginal_context))
activate!(y, DataVariableActivationOptions())
foreach(node -> activate!(node, FactorNodeActivationOptions(; callbacks)), (prior, likelihood))

subscription = subscribe!(get_stream_of_marginals(x), (q) -> nothing)
new_observation!(y, 2.0)
unsubscribe!(subscription)
rule towards :out: ExponentialFamily.NormalMeanVariance{Float64}(μ=0.0, v=10.0)
rule towards :μ: ExponentialFamily.NormalMeanVariance{Float64}(μ=2.0, v=1.0)
marginal: ExponentialFamily.NormalWeightedMeanPrecision{Float64}(xi=2.0, w=1.1)

When the observation arrives, x reads the messages of both nodes, which runs their rules, and forms its marginal from them.

A handler of a type of its own implements ReactiveMP.handle_event for each event it reacts to. The engine builds an event only for a handler that ReactiveMP.listens to its type, so a handler that reacts to a few events declares which, and the others cost it nothing. The handler below counts the rule calls of each target:

struct RuleCounter
    counts::Dict{Any, Int}
end

ReactiveMP.listens(::RuleCounter, ::Type{<:ReactiveMP.Event}) = false
ReactiveMP.listens(::RuleCounter, ::Type{<:ReactiveMP.AfterMessageRuleCallEvent}) = true

function ReactiveMP.handle_event(counter::RuleCounter, event::ReactiveMP.AfterMessageRuleCallEvent)
    target = event.mapping.target
    counter.counts[target] = get(counter.counts, target, 0) + 1
    return nothing
end

counter = RuleCounter(Dict{Any, Int}())
x, y = randomvar(label = :x), datavar(label = :y)
prior = factornode(Gaussian, [(:out, x), (:μ, constvar(0.0)), (:v, constvar(10.0))])
likelihood = factornode(Gaussian, [(:out, y), (:μ, x), (:v, constvar(1.0))])
activate!(x, RandomVariableActivationOptions())
activate!(y, DataVariableActivationOptions())
foreach(node -> activate!(node, FactorNodeActivationOptions(; callbacks = counter)), (prior, likelihood))

subscription = subscribe!(get_stream_of_marginals(x), (q) -> nothing)
foreach(value -> new_observation!(y, value), (1.0, 2.0, 3.0))
unsubscribe!(subscription)
counter.counts
Dict{Any, Int64} with 2 entries:
  :μ   => 3
  :out => 1

The prior's message towards out depends on constants alone, so it is computed once. The likelihood's message towards μ is computed once per observation. ReactiveMP.merge_callbacks combines several handlers into one.

ReactiveMP.event_name — Function
ReactiveMP.event_name(::Type{<:Event{E}}) -> Symbol
ReactiveMP.event_name(event::Event) -> Symbol

The name E of an event type or of an event.

Examples

julia> ReactiveMP.event_name(ReactiveMP.BeforeProductOfTwoMessagesEvent)
:before_product_of_two_messages
source
ReactiveMP.invoke_callback — Function
ReactiveMP.invoke_callback(callbacks, event::Event) -> Event

Report event to callbacks, and return the event. The engine calls it at every event, with the callbacks given at activation; callbacks is one of:

  • nothing: no callbacks, and nothing happens;
  • a NamedTuple or a Dict{Symbol} keyed by event names, whose entry for the event's name, if any, is called with the event: (before_message_rule_call = (event) -> …,);
  • a ReactiveMP.merge_callbacks of several handlers, each invoked in order;
  • any other handler, for which ReactiveMP.handle_event is called.

The return value of a callback is ignored.

Examples

julia> mutable struct CountEvent <: ReactiveMP.Event{:count_event}
           count::Int
       end;

julia> event = CountEvent(0);

julia> ReactiveMP.invoke_callback((count_event = (event) -> event.count += 1,), event);

julia> ReactiveMP.invoke_callback(Dict{Symbol, Any}(:count_event => (event) -> event.count += 1), event);

julia> event.count
2

See also ReactiveMP.Event, ReactiveMP.handle_event.

source
ReactiveMP.listens — Function
ReactiveMP.listens(callbacks, ::Type{<:Event}) -> Bool

Whether callbacks may react to events of the given type. ReactiveMP.@invoke_callback builds an event only when it returns true: false for nothing, by key for a NamedTuple (decided at compile time) or a Dict, true for any other handler. A handler of a custom type that reacts to only some events may add methods, false for the others, so that the engine does not build those; merged callbacks listen where any of theirs does.

source
ReactiveMP.handle_event — Function
ReactiveMP.handle_event(handler, event::Event)

What a callback handler of a custom type does with event: a handler adds a method for each event it reacts to, and ReactiveMP.invoke_callback calls it. Its return value is ignored.

Throws

  • MethodError for an event the handler has no method for; the error's hint says how to add one, and that a NamedTuple of callbacks needs its trailing comma.

Examples

julia> struct MyEvent <: ReactiveMP.Event{:my_event}
           value::Int
       end;

julia> struct MyHandler end;

julia> ReactiveMP.handle_event(::MyHandler, event::MyEvent) = println("value: ", event.value);

julia> ReactiveMP.invoke_callback(MyHandler(), MyEvent(1));
value: 1

See also ReactiveMP.Event, ReactiveMP.merge_callbacks.

source
ReactiveMP.merge_callbacks — Function
ReactiveMP.merge_callbacks(callback_handlers...) -> MergedCallbacks

Several callback handlers as one: each is invoked in turn, and each reacts to the events it handles. A handler is any value ReactiveMP.invoke_callback takes.

Examples

julia> struct PrintEvent <: ReactiveMP.Event{:print_event}
           label::String
       end;

julia> handler1 = (print_event = (event) -> println("first: ", event.label),);

julia> handler2 = (print_event = (event) -> println("second: ", event.label),);

julia> ReactiveMP.invoke_callback(ReactiveMP.merge_callbacks(handler1, handler2), PrintEvent("hello"));
first: hello
second: hello

See also ReactiveMP.handle_event.

source

Event names

Every event is a concrete subtype of ReactiveMP.Event{E}, where E is a Symbol that names it. The struct of the event :event_name is EventNameEvent:

SymbolStruct
:before_message_rule_callReactiveMP.BeforeMessageRuleCallEvent
:after_message_rule_callReactiveMP.AfterMessageRuleCallEvent
:before_product_of_two_messagesReactiveMP.BeforeProductOfTwoMessagesEvent
:after_product_of_two_messagesReactiveMP.AfterProductOfTwoMessagesEvent
:before_product_of_messagesReactiveMP.BeforeProductOfMessagesEvent
:after_product_of_messagesReactiveMP.AfterProductOfMessagesEvent
:before_form_constraint_appliedReactiveMP.BeforeFormConstraintAppliedEvent
:after_form_constraint_appliedReactiveMP.AfterFormConstraintAppliedEvent
:before_marginal_computationReactiveMP.BeforeMarginalComputationEvent
:after_marginal_computationReactiveMP.AfterMarginalComputationEvent
:before_marginal_rule_callReactiveMP.BeforeMarginalRuleCallEvent
:after_marginal_rule_callReactiveMP.AfterMarginalRuleCallEvent
:before_factor_bound_free_energyReactiveMP.BeforeFactorBoundFreeEnergyEvent
:after_factor_bound_free_energyReactiveMP.AfterFactorBoundFreeEnergyEvent
:before_variable_bound_entropyReactiveMP.BeforeVariableBoundEntropyEvent
:after_variable_bound_entropyReactiveMP.AfterVariableBoundEntropyEvent

Each event carries what happened in its fields, listed with it below.

Event spans

A "before" event and its "after" event share a span_id, from ReactiveMP.generate_span_id. A trace uses it to pair them, and to nest the products inside the marginal they form.

All events

ReactiveMP.BeforeMessageRuleCallEvent — Type
ReactiveMP.BeforeMessageRuleCallEvent{M, Ms, Mr, S} <: Event{:before_message_rule_call}

The event right before a message rule runs, at each call of a ReactiveMP.MessageMapping.

Fields

source
ReactiveMP.AfterMessageRuleCallEvent — Type
ReactiveMP.AfterMessageRuleCallEvent{M, Ms, Mr, R, A, L, U, S} <: Event{:after_message_rule_call}

The event right after a message rule ran, or its fallback, or after a missing input gave a missing message.

Fields

  • mapping: the ReactiveMP.MessageMapping, which holds the node, the target and the algorithm;
  • messages: the inbound messages the rule read, a tuple, or nothing for none;
  • marginals: the marginals the rule read, a tuple, or nothing for none;
  • result: the message's data, what the rule or the fallback returned, or missing;
  • annotations: the message's ReactiveMP.AnnotationDict, the shared frozen one where nothing may write to it;
  • logscale: the message's log scale (see getlogscale), or nothing where log scales are not tracked;
  • rule: what gave the message: the rule that ran, a RuleSpec; the node's rule fallback, where no rule matched (see ReactiveMP.FactorNodeActivationOptions); or nothing, when a missing input skipped the rule;
  • span_id: the identifier shared with the ReactiveMP.BeforeMessageRuleCallEvent.
source
ReactiveMP.AfterProductOfTwoMessagesEvent — Type
ReactiveMP.AfterProductOfTwoMessagesEvent{V, C, L, R, Rs, A, S} <: Event{:after_product_of_two_messages}

The event right after ReactiveMP.compute_product_of_two_messages multiplied two messages.

Fields

source
ReactiveMP.AfterProductOfMessagesEvent — Type
ReactiveMP.AfterProductOfMessagesEvent{V, C, Ms, R, S} <: Event{:after_product_of_messages}

The event at the end of ReactiveMP.compute_product_of_messages, after the fold and the form constraint.

Fields

source
ReactiveMP.BeforeFormConstraintAppliedEvent — Type
ReactiveMP.BeforeFormConstraintAppliedEvent{V, C, S, D, I} <: Event{:before_form_constraint_applied}

The event right before a product's form constraint applies, with constrain_form, under either strategy, FormConstraintCheckEach or FormConstraintCheckLast.

Fields

source
ReactiveMP.AfterFormConstraintAppliedEvent — Type
ReactiveMP.AfterFormConstraintAppliedEvent{V, C, S, D, R, I} <: Event{:after_form_constraint_applied}

The event right after a product's form constraint applied, with constrain_form, under either strategy, FormConstraintCheckEach or FormConstraintCheckLast.

Fields

source
ReactiveMP.BeforeMarginalComputationEvent — Type
ReactiveMP.BeforeMarginalComputationEvent{V, C, Ms, S} <: Event{:before_marginal_computation}

The event right before a RandomVariable computes its marginal from its inbound messages. Its callbacks are those of the variable's prod_context_for_marginal_computation (see RandomVariableActivationOptions).

Fields

source
ReactiveMP.AfterMarginalComputationEvent — Type
ReactiveMP.AfterMarginalComputationEvent{V, C, Ms, R, S} <: Event{:after_marginal_computation}

The event right after a RandomVariable computed its marginal from its inbound messages. Its callbacks are those of the variable's prod_context_for_marginal_computation.

Fields

source
ReactiveMP.BeforeMarginalRuleCallEvent — Type
ReactiveMP.BeforeMarginalRuleCallEvent{M, Ms, Mr, S} <: Event{:before_marginal_rule_call}

The event right before a factor node computes the joint marginal of one of its clusters with its marginal rule, at each call of a ReactiveMP.MarginalMapping: for a cluster as its inputs update, and for the joint a deterministic node's free energy reads. Its callbacks are the node's (see ReactiveMP.FactorNodeActivationOptions).

Fields

  • mapping: the ReactiveMP.MarginalMapping, which holds the node, the cluster and the algorithm;
  • messages: the inbound messages the rule reads, a tuple, or nothing for none;
  • marginals: the marginals the rule reads, a tuple, or nothing for none;
  • span_id: the identifier shared with the ReactiveMP.AfterMarginalRuleCallEvent.
source
ReactiveMP.AfterMarginalRuleCallEvent — Type
ReactiveMP.AfterMarginalRuleCallEvent{M, Ms, Mr, R, U, S} <: Event{:after_marginal_rule_call}

The event right after a factor node's marginal rule ran, or after a missing input gave a missing joint.

Fields

  • mapping: the ReactiveMP.MarginalMapping, which holds the node, the cluster and the algorithm;
  • messages: the inbound messages the rule read, a tuple, or nothing for none;
  • marginals: the marginals the rule read, a tuple, or nothing for none;
  • result: the joint's data, what the rule returned, or missing;
  • rule: the rule that ran, a RuleSpec, or nothing when a missing input skipped it;
  • span_id: the identifier shared with the ReactiveMP.BeforeMarginalRuleCallEvent.
source
ReactiveMP.BeforeFactorBoundFreeEnergyEvent — Type
ReactiveMP.BeforeFactorBoundFreeEnergyEvent{N, S} <: Event{:before_factor_bound_free_energy}

The event right before a factor node's contribution to the Bethe free energy is computed, each time its stream (FactorBoundFreeEnergy) computes a value. Its callbacks are the node's (see ReactiveMP.FactorNodeActivationOptions); a node not activated reports none.

Fields

source
ReactiveMP.AfterFactorBoundFreeEnergyEvent — Type
ReactiveMP.AfterFactorBoundFreeEnergyEvent{N, Ms, E, H, R, U, S} <: Event{:after_factor_bound_free_energy}

The event right after a factor node's contribution to the Bethe free energy was computed: result = energy - entropy for a stochastic node, result = -entropy for a deterministic one. A deterministic node whose joint is computed for its term reports that marginal rule call, inside this event's span.

Fields

  • node: the factor node;
  • marginals: the local marginals the term is for, a tuple of Marginals: every cluster's for a stochastic node, the joint over the inputs (or the single input's marginal) for a deterministic one;
  • energy: the average energy, or nothing for a deterministic node;
  • entropy: the sum of the marginals' differential entropies;
  • result: the contribution, a BayesBase.CountingReal;
  • rule: the average energy that ran, a RuleSpec, or nothing for a deterministic node;
  • span_id: the identifier shared with the ReactiveMP.BeforeFactorBoundFreeEnergyEvent.
source
ReactiveMP.BeforeVariableBoundEntropyEvent — Type
ReactiveMP.BeforeVariableBoundEntropyEvent{V, M, S} <: Event{:before_variable_bound_entropy}

The event right before a random variable's contribution to the Bethe free energy is computed, each time its stream (VariableBoundEntropy) computes a value. Its callbacks are those of the variable's prod_context_for_marginal_computation (see RandomVariableActivationOptions), the ones its marginal events go to.

Fields

source
ReactiveMP.AfterVariableBoundEntropyEvent — Type
ReactiveMP.AfterVariableBoundEntropyEvent{V, M, H, R, S} <: Event{:after_variable_bound_entropy}

The event right after a random variable's contribution to the Bethe free energy was computed, result = scaling * entropy.

Fields

source