Calling rules
You can call any rule directly, without a graph. This is how rules are tested and explored. A call gives the inputs by name, resolves the rule as an engine would, runs it, and returns a RuleResult. Inspecting rules shows which rule a call would run, without running it.
The examples on this page use a small deterministic node, out = in + c, with a normal type of its own:
using MessagePassingRulesBase
struct Gauss # a normal, by its mean and variance
m::Float64
v::Float64
end
struct Shift end # out = in + c
@define_factor_node(node = Shift, type = Deterministic, interfaces = [:out, :in, :c])
@define_message_update_rule(
node = Shift, target = :out, args = (m[:in]::Gauss, m[:c]::Real), logscale = 0,
body = (args) -> Gauss(args.m[:in].m + args.m[:c], args.m[:in].v),
)
@define_message_update_rule(
node = Shift, target = :in, args = (m[:out]::Gauss, m[:c]::Real), logscale = 0,
body = (args) -> Gauss(args.m[:out].m - args.m[:c], args.m[:out].v),
)
result = @call_message_update_rule(node = Shift, target = :out, m = (in = Gauss(1.0, 2.0), c = 3.0))Result
| value | Gauss(4.0, 2.0) |
|---|---|
| type | Gauss |
| log scale | 0 (declared) |
Inputs
| edge | value |
|---|
Rule
| declared inputs | m[:in]::Gauss, m[:c]::Real |
|---|---|
| algorithm | DefaultAlgorithm() |
| log scale | 0 |
| defined | calling.md:27 |
| body | args->Gauss((args.m[:in]).m + args.m[:c], (args.m[:in]).v) |
Calling a rule
Each kind of rule has a function and a macro. The function takes the node and the target as positional arguments. The macro takes everything by name.
The inputs are three keywords:
m, the messages, aNamedTuplekeyed by interface name;q, the marginals, keyed the same way;clusters, the joint marginals.
The other keywords select the algorithm, supply the RuleContext and collect the annotations. A call does not check the context's services (see The rule context). The Keyword reference lists every keyword.
MessagePassingRulesBase.call_message_update_rule — Function
call_message_update_rule(node, target; m, q, clusters, logscale, algorithm, ctx, ann)Run the message rule of node towards target on the inputs given, as an engine would, and return a RuleResult: getresult is the message, getlogscale its log scale. For exploring a rule at the REPL, in a notebook or in a test; @call_message_update_rule is the same call written with keywords only.
Arguments
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.target: the interface the message goes to::out, or(:m, k)for memberkof the groupm, as(:in, 2).
Keywords
All optional.
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.logscale: the log scales that arrived with the messages, keyed likem:logscale = (μ = 0.0,). Needed only by a rule declared withreads_logscale = true, for which the call is an error without them. Default:nothing.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
MessagePassingRulesBase.call_marginal_update_rule — Function
call_marginal_update_rule(node, target; m, q, clusters, algorithm, ctx, ann)Run the marginal rule of node for the cluster target on the inputs given, as an engine would, and return a RuleResult: getresult is the joint marginal, and its log scale is nothing. @call_marginal_update_rule is the same call written with keywords only.
Arguments
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.target: the cluster whose joint marginal to compute, its members in interface order:(:out, :μ), or(:out, (:T, 1))with a group member.
Keywords
All optional. A marginal rule typically reads the messages on the cluster's members and the marginals of the node's other interfaces.
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
MessagePassingRulesBase.call_average_energy — Function
call_average_energy(node; q, clusters, m, algorithm, ctx, ann)Compute the average energy of node under the marginals given, as an engine's free energy would, and return a RuleResult: getresult is the energy, a number, and its log scale is nothing. @call_average_energy is the same call written with keywords only.
Arguments
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.
Keywords
All optional. An average energy reads marginals, q and clusters, one per cluster of the factorisation.
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
MessagePassingRulesBase.@call_message_update_rule — Macro
@call_message_update_rule(
node = ..., target = ..., m = (...), q = (...), clusters = (...),
logscale = (...), algorithm = ..., ctx = ..., ann = ...,
)Run the message rule of a node towards one of its interfaces on the inputs given, as an engine would, and return a RuleResult: getresult is the message, getlogscale its log scale. For exploring a rule at the REPL, in a notebook or in a test. The macro takes keyword arguments only; node and target are required, the rest optional. It is call_message_update_rule with node and target given by name.
Required keywords
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.target: the interface the message goes to::out, or(:m, k)for memberkof the groupm, as(:in, 2).
Optional keywords
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.logscale: the log scales that arrived with the messages, keyed likem:logscale = (μ = 0.0,). Needed only by a rule declared withreads_logscale = true, for which the call is an error without them. Default:nothing.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
The result
A RuleResult: getresult the message; getlogscale its log scale, a number, or an UndefinedLogScale when the rule declares none; getrule the RuleSpec that ran; getannotations what it annotated. In the terminal it shows a report of the call, one line per edge of the node, and in a notebook a card with the node drawn.
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
Example
julia> struct Shift end
julia> @define_factor_node(node = Shift, type = Deterministic, interfaces = [:out, :in])
julia> @define_message_update_rule(
node = Shift,
target = :out,
args = (m[:in]::Real,),
logscale = 0,
body = (args) -> args.m[:in] + 1,
)
julia> result = @call_message_update_rule(node = Shift, target = :out, m = (in = 1.0,));
julia> getresult(result), getlogscale(result)
(2.0, 0)MessagePassingRulesBase.@call_marginal_update_rule — Macro
@call_marginal_update_rule(
node = ..., target = (:y, :x), m = (...), q = (...), clusters = (...),
algorithm = ..., ctx = ..., ann = ...,
)Run the marginal rule of a node for one of its clusters on the inputs given, as an engine would, and return a RuleResult: getresult is the joint marginal. The macro takes keyword arguments only; node and target are required, the rest optional. It is call_marginal_update_rule with node and target given by name. A marginal has no log scale, so logscale is not accepted.
Required keywords
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.target: the cluster whose joint marginal to compute, its members in interface order:(:out, :μ), or(:out, (:T, 1))with a group member.
Optional keywords
A marginal rule typically reads the messages on the cluster's members and the marginals of the node's other interfaces.
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
The result
A RuleResult: getresult the joint marginal; getlogscale is nothing; getrule the RuleSpec that ran; getannotations what it annotated.
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
Example
result = @call_marginal_update_rule(
node = NormalMeanVariance,
target = (:out, :μ),
m = (out = NormalMeanVariance(1.0, 1.0), μ = NormalMeanVariance(0.0, 2.0)),
q = (v = PointMass(1.0),),
)
getresult(result)MessagePassingRulesBase.@call_average_energy — Macro
@call_average_energy(node = ..., q = (...), clusters = (...), m = (...), algorithm = ..., ctx = ..., ann = ...)Compute the average energy of a node under the marginals given, as an engine's free energy would, and return a RuleResult: getresult is the energy, a number. The macro takes keyword arguments only; node is required, the rest optional. It is call_average_energy with node given by name. An average energy has no target and no log scale, so target and logscale are not accepted.
Required keywords
node: the node, as declared with@define_factor_node: a type,NormalMeanVariance, or a function,+. A function node is the function itself: its type,typeof(+), is anArgumentErrorsaying so.
Optional keywords
An average energy reads marginals, q and clusters, one per cluster of the factorisation.
m: the inbound messages, aNamedTuplekeyed by the interfaces' declared names:m = (μ = NormalMeanVariance(0.0, 1.0), v = PointMass(1.0)). A group is a tuple of its members in order,m = (in = (m₁, m₂),); for a rule that selects some members,nothingmay stand for the others, as an engine gives them. Default: none.q: the marginals of single interfaces, keyed the same way:q = (v = Gamma(1.0, 1.0),). Default: none.clusters: the joint marginals of structural clusters, a tuple of pairs from the cluster's members, in interface order, to its joint:clusters = ((:y, :x) => q_yx,).(:in,) => qis the joint over the groupin. Default: none.algorithm: the algorithm value to run under, with its parameters:algorithm = ARVMP(Multivariate, 2, ARsafe()). Rule lookup selects the rules of its type, and for aDefaultAlgorithmExtensionthe default's as well; an inherited rule runs with the value it was written for (rule_algorithm). Default: the node's,default_algorithm(node).ctx: theRuleContextof services the rule reads asctx.name:ctx = MessagePassingRulesBase.RuleContext(rng = Xoshiro(1)). Default: an empty context. Its services are not checked: one the rule declares andctxdoes not supply reads asnothinginside the rule, so an unsetmatrix_correctiongives the rule's own default. An engine checks them when it resolves a rule; callMessagePassingRulesBase.check_services(getrule(result), ctx)for the same guarantee.ann: where the annotations go. Default:nothing, and what the rule annotates is dropped. AMessagePassingRulesBase.AnnotationStore()collects what the rule writes withannotate!, returned bygetannotations(result). AMessagePassingRulesBase.RuleAnnotations(m = …, q = …, out = AnnotationStore())also gives the annotations that arrived with the inputs, which the rule reads asann.m[:μ].
The result
A RuleResult: getresult the energy; getlogscale is nothing; getrule the RuleSpec that ran.
When no rule fits, the call throws a RuleNotFoundError, which lists the closest rules and, input by input, why each does not fit. An unknown keyword is an error. Test tooling counts a rule called this way as tested, for its rule-coverage gate.
Example
result = @call_average_energy(
node = NormalMeanVariance,
q = (out = NormalMeanVariance(1.0, 1.0), μ = NormalMeanVariance(0.0, 2.0), v = PointMass(1.0)),
)
getresult(result)Reading the result
A RuleResult holds the result together with everything that produced it. On these pages, and in a notebook, it shows itself as a card with the node drawn, as the result above does:
- the inputs are arrows in, solid for messages and dashed for marginals;
- the target is the arrow out;
- below the drawing are the result, its log scale, the rule that ran and the other rules for the same target.
In the terminal, it shows itself as a report with one line per edge of the node, giving what the edge carried into the rule. The accessors below read its parts:
getresult(result), getlogscale(result)(Main.Gauss(4.0, 2.0), 0)MessagePassingRulesBase.RuleResult — Type
RuleResultWhat a rule called by hand returns: the call_* functions (call_message_update_rule, call_marginal_update_rule, call_average_energy), their @call_* macros, and the message_passing_* functions (message_passing_rule and its siblings). It holds the rule's result together with everything that produced it. Read it with its getters:
getresult: the message, the marginal or the average energy;getlogscale: the log scale the rule declares for a message, a number or anUndefinedLogScale;nothingfor a marginal or an energy;getrule: theRuleSpecthat ran;getalgorithm,getcontext,getscratch: the algorithm value, theRuleContextand the working memory it ran with;getarguments: itsRuleArgs;gettarget: its target;getannotations: where it recorded its annotations.
It shows itself as a report in the terminal (text/plain), one line per edge of the node, and as a card with the node drawn in a notebook or in documentation (text/html): the inputs, the result and its log scale, the rule that ran and the other rules for the same target. The two-argument show is compact, RuleResult(2.0, logscale = 0). An engine runs rules through execute_rule without building one.
MessagePassingRulesBase.getresult — Function
getresult(r::RuleResult)The rule's result: the message, the joint marginal or the average energy. For an in-place rule, the buffer it wrote into.
MessagePassingRulesBase.getrule — Function
getrule(r::RuleResult) -> RuleSpecThe RuleSpec that ran, which shows its inputs, source, file and line.
MessagePassingRulesBase.getalgorithm — Function
getalgorithm(r::RuleResult)The algorithm value the rule ran with: the call's, or DefaultAlgorithm() for a rule a DefaultAlgorithmExtension inherited (rule_algorithm).
MessagePassingRulesBase.getcontext — Function
getcontext(r::RuleResult) -> RuleContextThe RuleContext the rule ran with.
MessagePassingRulesBase.getscratch — Function
getscratch(r::RuleResult)The working memory the rule ran with, as it left it; nothing for a rule that declares none.
MessagePassingRulesBase.getarguments — Function
getarguments(r::RuleResult) -> RuleArgsThe RuleArgs the rule ran on.
MessagePassingRulesBase.gettarget — Function
gettarget(r::RuleResult)The target the rule computed: an interface, a group member, a cluster, or nothing for an average energy.
MessagePassingRulesBase.getannotations — Function
getannotations(r::RuleResult)Where the rule recorded its annotations: the store the call was given as ann (the out of a RuleAnnotations), or a NoAnnotations when it was given none. Read it with getannotation. An engine adds methods for its messages and marginals.
A marginal rule whose cluster factorises returns a FactorizedCluster of its blocks. Each block is labelled with the members it covers, and an engine hands each block to its members.
MessagePassingRulesBase.FactorizedCluster — Type
FactorizedCluster(block => distribution, ...)The result of a marginal rule whose cluster factorises into independent blocks: FactorizedCluster((:out, :μ) => q_outμ, (:v,) => q_v) for q(out, μ, v) = q(out, μ) q(v).
It is a BayesBase FactorizedJoint of the blocks, labelled with the members each block covers. The joint is the distribution, and BayesBase supplies its entropy and float-type conversion. The labels are what an engine needs to hand each block on and to score it. Each label is the tuple of the block's members, in the cluster's order; a member of a group is written as in a cluster's key, (:T, 1). The labels are carried in the type, so a block is read as fc[(:out, :μ)] with no lookup at run time, and no names are joined together, so interface names may contain underscores. pairs(fc) gives block => distribution pairs, and BayesBase.components(fc) the distributions.
Throws
ArgumentErrorwhen given no block;KeyErrorwhen indexed by a block it does not have.
Examples
julia> fc = FactorizedCluster((:out, :μ) => 1.0, (:v,) => 2.0);
julia> fc[(:v,)]
2.0
julia> MessagePassingRulesBase.cluster_blocks(fc)
((:out, :μ), (:v,))See also cluster_blocks, check_factorized_cluster.
MessagePassingRulesBase.cluster_blocks — Function
cluster_blocks(fc::FactorizedCluster) -> TupleThe labels of the blocks of fc, in order: one tuple of members per block.
MessagePassingRulesBase.check_factorized_cluster — Function
check_factorized_cluster(target::ClusterTarget, fc::FactorizedCluster) -> FactorizedClusterCheck that the blocks of fc partition the members of target, every member in exactly one block and each block listing its members in the cluster's order, and return fc. For a marginal rule's tests; the blocks may come in any order.
Throws
ArgumentError naming the problem: a member outside the cluster, a member in two blocks, a block out of order, or a member no block covers.
When no rule fits
Every call throws a RuleNotFoundError when no rule fits. Its message diagnoses the call. It lists every rule for the node and target and says, slot by slot, why each one does not fit. Here c is given as a String:
try
@call_message_update_rule(node = Shift, target = :out, m = (in = Gauss(1.0, 2.0), c = "3"))
catch err
showerror(stdout, err)
endRuleNotFoundError: no message rule for Shift towards :out under DefaultAlgorithm() takes the inputs (m[:c]::String, m[:in]::Gauss)
a rule of this shape exists, but the input types do not fit (type mismatch)
what to try: the inputs arrive with other types than the rule takes. If a rule below takes such an input as a marginal `q`, a factorisation that separates it delivers one (in RxInfer, `@constraints`); a form constraint on a variable projects its marginal onto a family a rule takes; otherwise, define a rule for these types
near misses:
rule at calling.md:27
✓ algorithm DefaultAlgorithm
✓ m[:in]::Gauss got Gauss
✗ m[:c]::Real got StringMessagePassingRulesBase.RuleNotFoundError — Type
RuleNotFoundError(notfound::RuleNotFound)The error every call in this package throws when no rule fits: the call_* functions and macros, the which_* queries and the message_passing_* functions. It wraps the RuleNotFound resolution returned.
Its message reads, from the top:
- the call: the kind of rule, the node, the target, the algorithm, and the inputs given, each as
m[:name]::Typeorq[:name]::Type; - a diagnosis: no rule exists for this node and target under any algorithm; a rule of this shape exists, but the input types do not fit (a rule takes exactly these inputs, and some type differs); rules of this shape exist under another algorithm; or no rule consumes this set of inputs (the rules take other inputs);
- what to try for that diagnosis: loading the package that defines the rules, a form constraint, the node's algorithm, or another factorisation;
- the near misses, every rule for the node and target, by file and line, each with a line per slot:
✓or✗for the algorithm, then for each input the rule takes, what was given for it ornot provided, and each input given that the rule does not take,provided but not consumed.
For a node Shift with interfaces out and in and a single rule, towards out:
julia> @call_message_update_rule(node = Shift, target = :in, m = (out = 1.0,))
ERROR: RuleNotFoundError: no message rule for Shift towards :in under DefaultAlgorithm() takes the inputs (m[:out]::Float64)
no rule exists for this node and target under any algorithm
what to try: no loaded package defines this rule: load the package that defines the node's rules, or define the rule with `@define_message_update_rule`MessagePassingRulesBase.RuleNotFound — Type
RuleNotFoundWhat resolution (find_message_rule, find_marginal_rule, find_average_energy) returns when no rule matches; resolution never throws. Fields: kind (:message, :marginal or :average_energy), node, target (nothing for an average energy), algorithm, the value the call asked for, and args, the RuleArgs that found nothing.
A caller that needs a rule reports it by throwing a RuleNotFoundError, as every call in this package does; an engine may first consult a rule fallback.
MessagePassingRulesBase.rule_not_found_hint — Function
rule_not_found_hint(node, notfound::RuleNotFound) -> Union{String, Nothing}A sentence a node's package adds to the report of a RuleNotFoundError for node, after the generic hint, or nothing, the default. A package extends it for its node where it knows the usual cause, as the Delta node's does for a missing approximation method:
MessagePassingRulesBase.rule_not_found_hint(::Type{<:MyNode}, notfound) =
notfound.algorithm isa MyAlgorithm ? nothing : "MyNode runs under `MyAlgorithm(...)`"A rule that was found can still refuse its inputs, when they fail the check its definition declares with args_check (Checking inputs). The call then throws a RuleInputError, which names the rule and says why.
MessagePassingRulesBase.RuleInputError — Type
RuleInputError <: ExceptionA rule refused its inputs: their types matched its args, so it was selected, but they failed the check its definition declares with the args_check keyword. Its message names the rule, its node, target and algorithm, where it is defined, the inputs it was given, and why: the string the check returned, or, where it returned false, the check's own source.
A failed check is an error, not a reason to select another rule: resolution has already chosen this one. A rule whose inputs a check refuses would compute something wrong or fail inside its body; the error says so where it happens.
Fields: rule, the RuleSpec; reason, the string the check returned, or nothing where it returned false; args, the RuleArgs it was given.
Resolving without the interactive layer
Code that builds its own RuleArgs resolves a rule with the find_* functions. They never throw: they return a RuleNotFound when nothing fits. The code then runs the rule with the message_passing_* functions, which do throw. Julia's dispatch does the resolution, over every loaded package.
MessagePassingRulesBase.find_message_rule — Function
find_message_rule(node, target, algorithm, args) -> Union{RuleSpec, RuleNotFound}Resolve the message rule of node towards target under algorithm for the inputs args, without running it.
Every @define_message_update_rule adds a method, so resolution is Julia's dispatch over every loaded package; it never throws.
Arguments
node: the node, a type or a function, as declared;target: aTargetor anIndexedTarget;algorithm: the algorithm value, whose type selects the rules; for aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule is returned;args: the inputs, aRuleArgs; their keys and types select the rule.
Returns
The RuleSpec, or a RuleNotFound that names the algorithm the call asked for. Run the spec with rule_algorithm(spec, algorithm), which differs from algorithm for an inherited rule.
See also find_marginal_rule, find_average_energy, which_message_update_rule.
MessagePassingRulesBase.find_marginal_rule — Function
find_marginal_rule(node, cluster::ClusterTarget, algorithm, args) -> Union{RuleSpec, RuleNotFound}Resolve the marginal rule of node for the structural cluster under algorithm for the inputs args, a RuleArgs, without running it. Every @define_marginal_update_rule adds a method; it never throws. For a DefaultAlgorithmExtension without a rule of its own, the default's rule is returned.
Returns
The RuleSpec, or a RuleNotFound that names the algorithm the call asked for.
See also find_message_rule, which_marginal_update_rule.
MessagePassingRulesBase.find_average_energy — Function
find_average_energy(node, algorithm, args) -> Union{RuleSpec, RuleNotFound}Resolve the average energy of node under algorithm for the marginals in args, a RuleArgs, without computing it. Every @define_average_energy adds a method; it never throws. For a DefaultAlgorithmExtension without an energy of its own, the default's is returned.
Returns
The RuleSpec, or a RuleNotFound whose target is nothing.
See also find_message_rule, which_average_energy.
MessagePassingRulesBase.rule_algorithm — Function
rule_algorithm(spec::RuleSpec, algorithm) -> AbstractAlgorithmThe algorithm value to run spec with, for a call made under algorithm: the call's own, or DefaultAlgorithm() when spec was reached through a DefaultAlgorithmExtension's fallback to the default, so that a rule always receives the algorithm it was written for. An engine calls it before execute_rule, as the message_passing_* functions do.
MessagePassingRulesBase.message_passing_rule — Function
message_passing_rule(node, target, algorithm, args, ctx = RuleContext(), ann = NoAnnotations()) -> RuleResultResolve the message rule of node towards target and run it, allocating its result: the positional, non-interactive form of call_message_update_rule, for code that builds its RuleArgs itself. An engine resolves once and calls execute_rule instead.
Arguments
node: the node, as declared with@define_factor_node.target: aTargetor anIndexedTarget.algorithm: the algorithm value to run under. For aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule runs, withDefaultAlgorithm()(rule_algorithm).args: the inputs, aRuleArgs; their keys and types select the rule.ctx: theRuleContextthe rule runs with. Default: an empty context. Its services are not checked: a service the rule declares andctxdoes not supply reads asnothinginside the rule. Callcheck_services(getrule(result), ctx), or check the spec fromfind_message_rulefirst, for an engine's guarantee.ann: the rule'sann, aRuleAnnotations; or, for a rule that only writes annotations, anAnnotationStoreor aNoAnnotations. Default:NoAnnotations(), dropping what the rule annotates.
Returns
A RuleResult: getresult is the message, getlogscale its log scale.
Throws
RuleNotFoundErrorwhen no rule fits;ArgumentErrorwhen the rule reads log scales andargscarries none (check_reads_logscale);- whatever the rule throws, unchanged.
Examples
julia> using MessagePassingRulesBase: Target, RuleArgs
julia> struct Shift end
julia> @define_factor_node(node = Shift, type = Deterministic, interfaces = [:out, :in])
julia> @define_message_update_rule(node = Shift, target = :out, args = (m[:in]::Real,), body = (args) -> args.m[:in] + 1)
julia> getresult(message_passing_rule(Shift, Target(:out), DefaultAlgorithm(), RuleArgs(m = (in = 1.0,))))
2.0MessagePassingRulesBase.message_passing_rule! — Function
message_passing_rule!(output, node, target, algorithm, args, ctx = RuleContext(), ann = NoAnnotations()) -> RuleResultResolve the in-place message rule of node towards target and run it into the buffer output, which getresult then returns.
Arguments
output: the buffer the rule writes into, shaped as itspreallocatebuilds it.node: the node, as declared with@define_factor_node.target: aTargetor anIndexedTarget.algorithm: the algorithm value to run under. For aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule runs, withDefaultAlgorithm()(rule_algorithm).args: the inputs, aRuleArgs; their keys and types select the rule.ctx: theRuleContextthe rule runs with. Default: an empty context. Its services are not checked: a service the rule declares andctxdoes not supply reads asnothinginside the rule. Callcheck_services(getrule(result), ctx), or check the spec fromfind_message_rulefirst, for an engine's guarantee.ann: the rule'sann, aRuleAnnotations; or, for a rule that only writes annotations, anAnnotationStoreor aNoAnnotations. Default:NoAnnotations(), dropping what the rule annotates.
Returns
A RuleResult: getresult is the message, getlogscale its log scale.
Throws
ArgumentErrorwhen the rule has no in-place form;RuleNotFoundErrorwhen no rule fits;ArgumentErrorwhen the rule reads log scales andargscarries none (check_reads_logscale);- whatever the rule throws, unchanged.
See also message_passing_rule.
MessagePassingRulesBase.message_passing_marginalrule — Function
message_passing_marginalrule(node, cluster, algorithm, args, ctx = RuleContext(), ann = NoAnnotations()) -> RuleResultResolve the marginal rule of node for cluster and run it, allocating its result: the positional form of call_marginal_update_rule.
Arguments
node: the node, as declared with@define_factor_node.cluster: aClusterTarget.algorithm: the algorithm value to run under. For aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule runs, withDefaultAlgorithm()(rule_algorithm).args: the inputs, aRuleArgs; their keys and types select the rule.ctx: theRuleContextthe rule runs with. Default: an empty context. Its services are not checked: a service the rule declares andctxdoes not supply reads asnothinginside the rule. Callcheck_services(getrule(result), ctx), or check the spec fromfind_message_rulefirst, for an engine's guarantee.ann: the rule'sann, aRuleAnnotations; or, for a rule that only writes annotations, anAnnotationStoreor aNoAnnotations. Default:NoAnnotations(), dropping what the rule annotates.
Returns
A RuleResult: getresult is the joint marginal; its log scale is nothing.
Throws
RuleNotFoundErrorwhen no rule fits;ArgumentErrorwhen the rule reads log scales andargscarries none (check_reads_logscale);- whatever the rule throws, unchanged.
See also message_passing_marginalrule!.
MessagePassingRulesBase.message_passing_marginalrule! — Function
message_passing_marginalrule!(output, node, cluster, algorithm, args, ctx = RuleContext(), ann = NoAnnotations()) -> RuleResultResolve the in-place marginal rule of node for cluster and run it into the buffer output, which getresult then returns.
Arguments
output: the buffer the rule writes into, shaped as itspreallocatebuilds it.node: the node, as declared with@define_factor_node.cluster: aClusterTarget.algorithm: the algorithm value to run under. For aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule runs, withDefaultAlgorithm()(rule_algorithm).args: the inputs, aRuleArgs; their keys and types select the rule.ctx: theRuleContextthe rule runs with. Default: an empty context. Its services are not checked: a service the rule declares andctxdoes not supply reads asnothinginside the rule. Callcheck_services(getrule(result), ctx), or check the spec fromfind_message_rulefirst, for an engine's guarantee.ann: the rule'sann, aRuleAnnotations; or, for a rule that only writes annotations, anAnnotationStoreor aNoAnnotations. Default:NoAnnotations(), dropping what the rule annotates.
Returns
A RuleResult: getresult is the joint marginal; its log scale is nothing.
Throws
ArgumentErrorwhen the rule has no in-place form;RuleNotFoundErrorwhen no rule fits;ArgumentErrorwhen the rule reads log scales andargscarries none (check_reads_logscale);- whatever the rule throws, unchanged.
See also message_passing_marginalrule.
MessagePassingRulesBase.message_passing_average_energy — Function
message_passing_average_energy(node, algorithm, args, ctx = RuleContext(), ann = NoAnnotations()) -> RuleResultResolve the average energy of node and compute it: the positional form of call_average_energy.
Arguments
node: the node, as declared with@define_factor_node.algorithm: the algorithm value to run under. For aDefaultAlgorithmExtensionwithout a rule of its own, the default's rule runs, withDefaultAlgorithm()(rule_algorithm).args: the inputs, aRuleArgs; their keys and types select the rule.ctx: theRuleContextthe rule runs with. Default: an empty context. Its services are not checked: a service the rule declares andctxdoes not supply reads asnothinginside the rule. Callcheck_services(getrule(result), ctx), or check the spec fromfind_message_rulefirst, for an engine's guarantee.ann: the rule'sann, aRuleAnnotations; or, for a rule that only writes annotations, anAnnotationStoreor aNoAnnotations. Default:NoAnnotations(), dropping what the rule annotates.
Returns
A RuleResult: getresult is the energy, a number; its log scale and its target are nothing.
Throws
RuleNotFoundErrorwhen no rule fits;ArgumentErrorwhen the rule reads log scales andargscarries none (check_reads_logscale);- whatever the rule throws, unchanged.
For tools
A tool such as the test tooling calls rules the way the interactive functions do. It reads its inputs with the same functions, and it can observe which rule each call selects.
MessagePassingRulesBase.as_target — Function
as_target(target) -> Union{Target, IndexedTarget}The target of a message rule as an interactive call takes it, :out or (:m, 2), as the Target or IndexedTarget a lookup takes; a target already of that kind is returned as it is. For tools that take targets the way call_message_update_rule does.
MessagePassingRulesBase.as_cluster — Function
as_cluster(members) -> ClusterTargetThe target of a marginal rule as an interactive call takes it, a tuple of members such as (:out, :μ), as the ClusterTarget a lookup takes; a ClusterTarget is returned as it is. For tools that take targets the way call_marginal_update_rule does.
MessagePassingRulesBase.interactive_args — Function
interactive_args(m, q, clusters, logscale = nothing) -> RuleArgsThe RuleArgs an interactive call builds from its keywords: the messages m and marginals q as named tuples, the joint marginals clusters as a collection of members => marginal pairs, and the incoming log scales logscale, or nothing. For tools that take a rule's inputs the way call_message_update_rule does.
MessagePassingRulesBase.add_selection_observer! — Function
add_selection_observer!(f) -> nothingCall f(spec) with the RuleSpec of every rule an interactive call selects, a call_* or @call_* call, before the rule runs; a message_passing_* call is not observed. Test tooling registers one to count a rule a test calls by hand as tested. An engine resolves its rules itself, so its calls are not observed. Registering the same f twice registers it once.