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:
- to a factor node, as the activation option
callbacksofReactiveMP.FactorNodeActivationOptions. The node reports its message rule calls,ReactiveMP.BeforeMessageRuleCallEventandReactiveMP.AfterMessageRuleCallEvent, its marginal rule calls, which compute the joint marginals of its clusters, and its term of the free energy,ReactiveMP.AfterFactorBoundFreeEnergyEvent. - to a variable, as the
callbacksof theReactiveMP.MessageProductContexts in itsRandomVariableActivationOptions. The variable reports the product and form constraint events, and the context of its marginal also reports the marginal events and the variable's term of the free energy,ReactiveMP.AfterVariableBoundEntropyEvent.
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.countsDict{Any, Int64} with 2 entries:
:μ => 3
:out => 1The 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 — Type
ReactiveMP.Event{E}The supertype of the events the engine reports to callbacks. E is the event's name, a Symbol such as :before_message_rule_call, and the event for the name :event_name is the struct EventNameEvent. An event carries what happened in its fields.
See also ReactiveMP.invoke_callback, ReactiveMP.handle_event.
ReactiveMP.event_name — Function
ReactiveMP.event_name(::Type{<:Event{E}}) -> Symbol
ReactiveMP.event_name(event::Event) -> SymbolThe name E of an event type or of an event.
Examples
julia> ReactiveMP.event_name(ReactiveMP.BeforeProductOfTwoMessagesEvent)
:before_product_of_two_messagesReactiveMP.invoke_callback — Function
ReactiveMP.invoke_callback(callbacks, event::Event) -> EventReport 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
NamedTupleor aDict{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_callbacksof several handlers, each invoked in order; - any other handler, for which
ReactiveMP.handle_eventis 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
2See also ReactiveMP.Event, ReactiveMP.handle_event.
ReactiveMP.listens — Function
ReactiveMP.listens(callbacks, ::Type{<:Event}) -> BoolWhether 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.
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
MethodErrorfor an event the handler has no method for; the error's hint says how to add one, and that aNamedTupleof 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: 1See also ReactiveMP.Event, ReactiveMP.merge_callbacks.
ReactiveMP.merge_callbacks — Function
ReactiveMP.merge_callbacks(callback_handlers...) -> MergedCallbacksSeveral 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: helloSee also ReactiveMP.handle_event.
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:
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.
ReactiveMP.generate_span_id — Function
ReactiveMP.generate_span_id(callbacks)The identifier shared by a "before" event and its "after" event, such as ReactiveMP.BeforeMessageRuleCallEvent and ReactiveMP.AfterMessageRuleCallEvent: a UUID unique within the session and random-looking across sessions, or nothing when callbacks is nothing. A handler of a custom type may add a method returning nothing to skip generating them; a ReactiveMP.MergedCallbacks always generates them.
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
mapping: theReactiveMP.MessageMapping, which holds the node, the target and the algorithm;messages: the inbound messages the rule reads, a tuple, ornothingfor none;marginals: the marginals the rule reads, a tuple, ornothingfor none;span_id: the identifier shared with theReactiveMP.AfterMessageRuleCallEvent(seeReactiveMP.generate_span_id).
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: theReactiveMP.MessageMapping, which holds the node, the target and the algorithm;messages: the inbound messages the rule read, a tuple, ornothingfor none;marginals: the marginals the rule read, a tuple, ornothingfor none;result: the message's data, what the rule or the fallback returned, ormissing;annotations: the message'sReactiveMP.AnnotationDict, the shared frozen one where nothing may write to it;logscale: the message's log scale (seegetlogscale), ornothingwhere log scales are not tracked;rule: what gave the message: the rule that ran, aRuleSpec; the node's rule fallback, where no rule matched (seeReactiveMP.FactorNodeActivationOptions); ornothing, when amissinginput skipped the rule;span_id: the identifier shared with theReactiveMP.BeforeMessageRuleCallEvent.
ReactiveMP.BeforeProductOfTwoMessagesEvent — Type
ReactiveMP.BeforeProductOfTwoMessagesEvent{V, C, L, R, S} <: Event{:before_product_of_two_messages}The event right before ReactiveMP.compute_product_of_two_messages multiplies two messages.
Fields
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;left,right: the twoMessages;span_id: the identifier shared with theReactiveMP.AfterProductOfTwoMessagesEvent.
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
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;left,right: the twoMessages;result: the product, aMessage;annotations: the product'sReactiveMP.AnnotationDict, the shared frozen one where nothing may write to it;span_id: the identifier shared with theReactiveMP.BeforeProductOfTwoMessagesEvent.
ReactiveMP.BeforeProductOfMessagesEvent — Type
ReactiveMP.BeforeProductOfMessagesEvent{V, C, Ms, S} <: Event{:before_product_of_messages}The event at the start of ReactiveMP.compute_product_of_messages, before a collection of messages is multiplied.
Fields
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;messages: the messages to multiply;span_id: the identifier shared with theReactiveMP.AfterProductOfMessagesEvent.
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
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;messages: the messages that were multiplied;result: the product, aMessage;span_id: the identifier shared with theReactiveMP.BeforeProductOfMessagesEvent.
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
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;strategy: the check strategy,FormConstraintCheckEach()orFormConstraintCheckLast();distribution: the distribution about to be constrained;span_id: the identifier shared with theReactiveMP.AfterFormConstraintAppliedEvent.
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
variable: theAbstractVariablethe product is for;context: theReactiveMP.MessageProductContext;strategy: the check strategy,FormConstraintCheckEach()orFormConstraintCheckLast();distribution: the distribution before the constraint;result: the distribution after it;span_id: the identifier shared with theReactiveMP.BeforeFormConstraintAppliedEvent.
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
variable: theRandomVariable;context: theReactiveMP.MessageProductContextof the marginal;messages: the inbound messages;span_id: the identifier shared with theReactiveMP.AfterMarginalComputationEvent.
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
variable: theRandomVariable;context: theReactiveMP.MessageProductContextof the marginal;messages: the inbound messages;result: the marginal, aMarginal;span_id: the identifier shared with theReactiveMP.BeforeMarginalComputationEvent.
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: theReactiveMP.MarginalMapping, which holds the node, the cluster and the algorithm;messages: the inbound messages the rule reads, a tuple, ornothingfor none;marginals: the marginals the rule reads, a tuple, ornothingfor none;span_id: the identifier shared with theReactiveMP.AfterMarginalRuleCallEvent.
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: theReactiveMP.MarginalMapping, which holds the node, the cluster and the algorithm;messages: the inbound messages the rule read, a tuple, ornothingfor none;marginals: the marginals the rule read, a tuple, ornothingfor none;result: the joint's data, what the rule returned, ormissing;rule: the rule that ran, aRuleSpec, ornothingwhen amissinginput skipped it;span_id: the identifier shared with theReactiveMP.BeforeMarginalRuleCallEvent.
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
node: the factor node;span_id: the identifier shared with theReactiveMP.AfterFactorBoundFreeEnergyEvent.
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 ofMarginals: 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, ornothingfor a deterministic node;entropy: the sum of the marginals' differential entropies;result: the contribution, aBayesBase.CountingReal;rule: the average energy that ran, aRuleSpec, ornothingfor a deterministic node;span_id: the identifier shared with theReactiveMP.BeforeFactorBoundFreeEnergyEvent.
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
variable: theRandomVariable;marginal: itsMarginal;span_id: the identifier shared with theReactiveMP.AfterVariableBoundEntropyEvent.
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
variable: theRandomVariable;marginal: itsMarginal;entropy: the marginal's differential entropy;scaling:d - 1for the variable'sReactiveMP.degreed, ordfor a point-mass marginal, whose entropy is not finite;result: the contribution, aBayesBase.CountingReal;span_id: the identifier shared with theReactiveMP.BeforeVariableBoundEntropyEvent.