The rule context
A rule reads the services its caller supplies from its ctx slot, a RuleContext. A service is a value the rule needs from whoever runs it: the random number generator, the engine's node, a matrix correction strategy, or a service of the rule's own. The context never takes part in dispatch.
A rule declares the services it reads with the ctx keyword, as ctx = (:rng,), and reads each one as ctx.name. A service the context does not supply reads as nothing.
using MessagePassingRulesBase
struct Scaled end # out = scale · in, with scale a service
@define_factor_node(node = Scaled, type = Deterministic, interfaces = [:out, :in])
@define_message_update_rule(
node = Scaled, target = :out, args = (m[:in]::Real,),
ctx = (:scale,),
body = (ctx, args) -> ctx.scale * args.m[:in],
)
ctx = MessagePassingRulesBase.RuleContext(scale = 3.0)
@call_message_update_rule(node = Scaled, target = :out, m = (in = 2.0,), ctx = ctx)Result
| value | 6.0 |
|---|---|
| type | Float64 |
| log scale | undefined: the message rule for Scaled towards :out under DefaultAlgorithm declares no `logscale` |
Inputs
| edge | value |
|---|
Rule
| declared inputs | m[:in]::Real |
|---|---|
| algorithm | DefaultAlgorithm() |
| services | scale |
| log scale | none |
| defined | context.md:22 |
| body | (ctx, args)->ctx.scale * args.m[:in] |
The rule reads scale from the context it is given, and returns 6.0.
MessagePassingRulesBase.RuleContext — Type
RuleContext(; services...)
RuleContext(services::NamedTuple)The context a rule receives as ctx: the services its caller supplies, read as ctx.name. It never takes part in dispatch. A rule declares the services it reads, ctx = (:rng,); any name is allowed, so a rule may need a service of its own. A service the caller does not supply reads as nothing, and missing_services lists them; an engine refuses to run such a rule, through check_services, while an interactive call runs it with what it is given.
The services an engine supplies by default, DEFAULT_CONTEXT_SERVICES:
node: the factor node the rule belongs to, e.g. forgetnodefn(ctx.node, target);rng: the random number generator, owned by the caller;matrix_correction: how a rule corrects a matrix it builds before using it, such as a precision that must stay positive definite: a strategy from MatrixCorrectionTools, ornothing, and each rule then applies its own default; a rule reads it throughmatrix_correction(ctx, default). MatrixCorrectionTools'NoCorrection()is an explicit identity.
A mutable object holding the services as a typed NamedTuple: a caller builds one and passes it to every call by reference, and reading a service is inferred. propertynames(ctx) gives the services it supplies, and merge(ctx, services::NamedTuple) returns a context with services added or overridden, as a caller layers its own over the defaults.
julia> using MessagePassingRulesBase: RuleContext
julia> ctx = merge(RuleContext(rng = 1), (scale = 2.0,));
julia> ctx.rng, ctx.scale, ctx.node
(1, 2.0, nothing)
julia> propertynames(ctx)
(:rng, :scale)See also check_services, matrix_correction.
MessagePassingRulesBase.DEFAULT_CONTEXT_SERVICES — Constant
DEFAULT_CONTEXT_SERVICESThe context services an engine supplies by default: node, rng and matrix_correction (see RuleContext). A rule may declare others.
Matrix correction
Some rules build a matrix they must invert or factorise, such as a precision matrix that must stay positive definite. Such a rule corrects the matrix first.
The caller chooses the correction. It supplies a MatrixCorrectionTools strategy as ctx.matrix_correction. Each rule has its own default for when none is set, and matrix_correction chooses between the two.
MessagePassingRulesBase.matrix_correction — Function
matrix_correction(ctx::RuleContext, default)The matrix correction a rule applies: ctx.matrix_correction when it is set, and the rule's own default when it is nothing or not supplied. A rule that corrects nothing by default passes nothing. The rule declares ctx = (:matrix_correction,), and applies the strategy with MatrixCorrectionTools' correction!.
W = correction!(matrix_correction(ctx, ReplaceZeroDiagonalEntries(tiny)), A * w * A')Who checks the services
An engine checks the services. A call by hand does not.
When an engine resolves a rule for a node in a graph, it calls check_services once, before the rule ever runs. A service that nobody supplies is then an error at setup, and the error names the rule and the service.
The calls by hand are the call_* functions and macros and the message_passing_* functions. They run the rule with whatever context they are given, which is empty by default. A declared service that the context lacks reads as nothing inside the rule. A test can therefore call a rule that reads ctx.matrix_correction without supplying one, and the rule applies its own default.
To get the engine's guarantee in a call by hand, check the rule first. The rule's RuleSpec lists the services it declares:
spec = which_message_update_rule(Scaled, :out; m = (in = 2.0,))| inputs | m[:in]::Real |
|---|---|
| in-place | no |
| scratch | no |
| pure | yes |
| services | scale |
| log scale | none |
| defined | context.md:22 |
| body | (ctx, args)->ctx.scale * args.m[:in] |
julia> MessagePassingRulesBase.missing_services(spec, MessagePassingRulesBase.RuleContext())(:scale,)julia> MessagePassingRulesBase.check_services(spec, ctx)
An empty context lacks scale. The context ctx above supplies it, so check_services returns nothing. For the empty context, it throws the error an engine reports at setup:
try
MessagePassingRulesBase.check_services(spec, MessagePassingRulesBase.RuleContext())
catch err
showerror(stdout, err)
endArgumentError: the message rule for Scaled towards :out under DefaultAlgorithm needs the context service :scale, which its context does not supply; supply it in the context the rule is called with: ReactiveMP's activation option `context = (name = value, ...)`, RxInfer's `infer(...; options = (context = (name = value, ...),))`, or a call's `ctx = (name = value, ...)`MessagePassingRulesBase.check_services — Function
check_services(spec::RuleSpec, ctx::RuleContext) -> nothingCheck that ctx supplies every context service spec declares. An engine calls it when it resolves a rule, before running it, so a service nobody supplies is an error there rather than a nothing inside the rule. It allocates nothing when every service is supplied.
Throws
ArgumentError naming the rule and the services missing_services lists.
The interactive calls (call_message_update_rule and its siblings, the @call_* macros) and the message_passing_* calls do not check: they run with whatever context the caller passes, empty by default, and a service it lacks reads as nothing. Call check_services first for the engine's guarantee.
MessagePassingRulesBase.missing_services — Function
missing_services(spec::RuleSpec, ctx::RuleContext) -> Tuple{Vararg{Symbol}}The context services spec declares that ctx does not supply: the names ctx has no entry for, in the order the rule declares them. An entry whose value is nothing, such as an unset matrix_correction, is supplied. Meant to be checked once, when a node is set up, rather than on every call.
julia> using MessagePassingRulesBase: RuleContext, missing_services
julia> struct Noisy end
julia> @define_factor_node(node = Noisy, type = Stochastic, interfaces = [:out, :in])
julia> @define_message_update_rule(node = Noisy, target = :out, args = (m[:in]::Real,), ctx = (:rng, :scale), body = (ctx, args) -> args.m[:in])
julia> spec = which_message_update_rule(Noisy, :out; m = (in = 1.0,));
julia> missing_services(spec, RuleContext(rng = nothing))
(:scale,)