Nodes implementation

In the message passing framework, one of the most important concepts is a factor node. A factor node represents a local function in a factorised representation of a generative model.

Note

To quickly check the list of all available factor nodes that can be used in the model specification language, call ?make_node or Base.doc(make_node).

Adding a custom node

ReactiveMP.jl exports the @node macro that allows for quick definition of a factor node with a fixed number of edges. The interface is the following:

struct MyNewCustomNode end

@node MyNewCustomNode   Stochastic         [ x, y, z ]
#     ^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^      ^^^^^^^^^^^
#     Node's tag/name   Node's type        A fixed set of edges
#                       Another possible   The very first edge (in this example `x`) is considered
#                       value is           to be the output of the node
#                       `Deterministic`

This expression registers a new node that can be used with the inference engine. Note howeve, that the @node macro does not generate any message passing update rules. These must be defined using the @rule macro.

Node types

We distinguish different types of factor nodes in order to have better control over Bethe Free Energy computation. Each factor node has either the Deterministic or Stochastic functional form type.

For example the + node has the Deterministic type:

plus_node = make_node(+)

println("Is `+` node deterministic: ", isdeterministic(plus_node))
println("Is `+` node stochastic: ", isstochastic(plus_node))
Is `+` node deterministic: true
Is `+` node stochastic: false

On the other hand, the Bernoulli node has the Stochastic type:

bernoulli_node = make_node(Bernoulli)

println("Is `Bernoulli` node deterministic: ", isdeterministic(bernoulli_node))
println("Is `Bernoulli` node stochastic: ", isstochastic(bernoulli_node))
Is `Bernoulli` node deterministic: false
Is `Bernoulli` node stochastic: true

To get an actual instance of the type object we use sdtype function:

println("sdtype() of `+` node is ", sdtype(plus_node))
println("sdtype() of `Bernoulli` node is ", sdtype(bernoulli_node))
sdtype() of `+` node is Deterministic()
sdtype() of `Bernoulli` node is Stochastic()

Node functional dependencies pipeline

The generic implementation of factor nodes in ReactiveMP supports custom functional dependency pipelines. Briefly, the functional dependencies pipeline defines what dependencies are need to compute a single message. As an example, consider the belief-propagation message update equation for a factor node $f$ with three edges: $x$, $y$ and $z$:

\[\mu(x) = \int \mu(y) \mu(z) f(x, y, z) \mathrm{d}y \mathrm{d}z\]

Here we see that in the standard setting for the belief-propagation message out of edge $x$, we need only messages from the edges $y$ and $z$. In contrast, consider the variational message update rule equation with mean-field assumption:

\[\mu(x) = \exp \int q(y) q(z) \log f(x, y, z) \mathrm{d}y \mathrm{d}z\]

We see that in this setting, we do not need messages $\mu(y)$ and $\mu(z)$, but only the marginals $q(y)$ and $q(z)$. The purpose of a functional dependencies pipeline is to determine functional dependencies (a set of messages or marginals) that are needed to compute a single message. By default, ReactiveMP.jl uses so-called DefaultFunctionalDependencies that correctly implements belief-propagation and variational message passing schemes (including both mean-field and structured factorisations). The full list of built-in pipelines is presented below:

ReactiveMP.RequireMessageFunctionalDependenciesType
RequireMessageFunctionalDependencies(indices::Tuple, start_with::Tuple)

The same as DefaultFunctionalDependencies, but in order to compute a message out of some edge also requires the inbound message on the this edge.

Arguments

  • indices::Tuple, tuple of integers, which indicates what edges should require inbound messages
  • start_with::Tuple, tuple of nothing or <:Distribution, which specifies the initial inbound messages for edges in indices

Note: start_with uses setmessage! mechanism, hence, it can be visible by other listeners on the same edge. Explicit call to setmessage! overwrites whatever has been passed in start_with.

@model macro accepts a simplified construction of this pipeline:

@model function some_model()
    # ...
    y ~ NormalMeanVariance(x, τ) where {
        pipeline = RequireMessage(x = vague(NormalMeanPrecision),     τ)
                                  # ^^^                               ^^^
                                  # request 'inbound' for 'x'         we may do the same for 'τ',
                                  # and initialise with `vague(...)`  but here we skip initialisation
    }
    # ...
end

Deprecation warning: RequireInboundFunctionalDependencies has been deprecated in favor of RequireMessageFunctionalDependencies.

See also: ReactiveMP.DefaultFunctionalDependencies, ReactiveMP.RequireMarginalFunctionalDependencies, ReactiveMP.RequireEverythingFunctionalDependencies

source
ReactiveMP.RequireMarginalFunctionalDependenciesType
RequireMarginalFunctionalDependencies(indices::Tuple, start_with::Tuple)

Similar to DefaultFunctionalDependencies, but in order to compute a message out of some edge also requires the posterior marginal on that edge.

Arguments

  • indices::Tuple, tuple of integers, which indicates what edges should require their own marginals
  • start_with::Tuple, tuple of nothing or <:Distribution, which specifies the initial marginal for edges in indices

Note: start_with uses the setmarginal! mechanism, hence it can be visible to other listeners on the same edge. Explicit calls to setmarginal! overwrites whatever has been passed in start_with.

@model macro accepts a simplified construction of this pipeline:

@model function some_model()
    # ...
    y ~ NormalMeanVariance(x, τ) where {
        pipeline = RequireMarginal(x = vague(NormalMeanPrecision),     τ)
                                   # ^^^                               ^^^
                                   # request 'marginal' for 'x'        we may do the same for 'τ',
                                   # and initialise with `vague(...)`  but here we skip initialisation
    }
    # ...
end

Note: The simplified construction in @model macro syntax is only available in GraphPPL.jl of version >2.2.0.

See also: ReactiveMP.DefaultFunctionalDependencies, ReactiveMP.RequireMessageFunctionalDependencies, ReactiveMP.RequireEverythingFunctionalDependencies

source
ReactiveMP.RequireEverythingFunctionalDependenciesType

RequireEverythingFunctionalDependencies

This pipeline specifies that in order to compute a message of some edge update rules request everything that is available locally. This includes all inbound messages (including on the same edge) and marginals over all local edge-clusters (this may or may not include marginals on single edges, depends on the local factorisation constraint).

See also: DefaultFunctionalDependencies, RequireMessageFunctionalDependencies, RequireMarginalFunctionalDependencies

source

Node traits

Each factor node has to define the ReactiveMP.as_node_functional_form trait function and to specify a ReactiveMP.ValidNodeFunctionalForm singleton as a return object. By default ReactiveMP.as_node_functional_form returns ReactiveMP.UndefinedNodeFunctionalForm. Objects that do not specify this property correctly cannot be used in model specification.

Note

@node macro does that automatically