Probabilistic Model Engine

Overview

Cortex.jl is designed to allow it to work with various probabilistic model representations. Instead of implementing a specific model engine, Cortex.jl defines an interface that any model engine can implement to be used with the inference engine.

This design choice offers several advantages:

  1. Flexibility: Users can choose or implement model engines that best suit their needs, whether it's a simple in-memory graph or a distributed database.
  2. Interoperability: Existing probabilistic model implementations, both in Julia and other languages, can be integrated by implementing the interface.
  3. Performance: Specialized model engines can be optimized for specific use cases without modifying Cortex.jl itself.
  4. Extensibility: New model engines can be added without changing the core Cortex.jl codebase.

Core Data Structures

Model engines work with three core data structures:

  • Variable - Represents a random variable in the model.
  • Factor - Represents a factor in the model.
  • Connection - Represents a connection between a variable and a factor.

These structures provide a standardized interface for representing probabilistic models while allowing flexibility in the underlying implementation.

Variable

Cortex.VariableType
Variable

A data structure representing a probabilistic variable in a model.

A Variable encapsulates the essential components of a probabilistic variable, including its identity, current marginal belief (as a reactive signal), and any signals that should be automatically updated when the variable's marginal changes.

Fields

  • name::Symbol: The symbolic name of the variable, used for identification and display.
  • index::Any = nothing: An optional index for the variable, useful for indexed variable families (e.g., x[1], x[2], etc.). Can be any type that makes sense for the specific use case.
  • marginal::InferenceSignal = create_inference_signal(): A reactive signal representing the current marginal belief over this variable. Updated automatically during inference.
  • linked_signals::Vector{InferenceSignal} = InferenceSignal[]: A collection of signals that should be automatically updated when this variable's marginal changes. These might represent joint marginals around factors, or any other derived quantities that depend on this variable.

See Also

source
Cortex.get_variable_linked_signalsFunction
get_variable_linked_signals(variable::Variable)

Retrieves the linked signals of a variable. The linked signals are the signals that should be updated when the marginal (which is a signal) of the variable is updated. Those may, for example, represent joint marginals around certain factors, but any other signal can be linked as well.

source
Cortex.link_signal_to_variable!Function
link_signal_to_variable!(variable::Variable, signal::InferenceSignal)

Links a signal to a variable. The linked signal will be updated automatically when the marginal of the variable is updated.

source

Factor

Cortex.FactorType
Factor

A data structure representing a probabilistic factor in a graphical model.

A Factor encapsulates the functional form of a probabilistic relationship between variables and maintains references to local marginal beliefs that are relevant to this factor's computation and inference updates.

Fields

  • functional_form::Any: The mathematical or computational representation of the factor. This could be a function, a probability distribution, a constraint, or any other object that defines the probabilistic relationship encoded by this factor.
  • local_marginals::Vector{InferenceSignal} = InferenceSignal[]: A collection of reactive signals representing local marginal beliefs associated with this factor. These signals are typically updated during message-passing inference and may represent beliefs about individual variables or joint beliefs over subsets of variables connected to this factor.

See Also

source
Cortex.get_factor_functional_formFunction
get_factor_functional_form(factor::Factor)

Retrieves the functional form of a factor. The functional form represents the mathematical or computational definition of the probabilistic relationship encoded by the factor.

source
Cortex.get_factor_local_marginalsFunction
get_factor_local_marginals(factor::Factor)

Retrieves the local marginals associated with a factor. Local marginals are reactive signals that represent beliefs about variables or variable subsets that are relevant to this factor's inference computations.

source

Connection

Cortex.ConnectionType
Connection

A data structure representing a connection between a variable and a factor in a probabilistic graphical model.

A Connection encapsulates the communication interface between variables and factors during message-passing inference. It maintains the bidirectional message signals and provides identification through labels and indices.

Fields

  • label::Symbol: A symbolic label identifying the role or type of this connection (e.g., :observation, :prior, :likelihood). Used for semantic identification of the connection's purpose in the model.
  • index::Int = 0: A numeric index for the connection, useful when multiple connections of the same type exist between a variable-factor pair, or for ordering connections in message-passing algorithms.
  • message_to_variable::InferenceSignal = create_inference_signal(): A reactive signal carrying messages sent from the factor to the variable. Updated during factor-to-variable message passing.
  • message_to_factor::InferenceSignal = create_inference_signal(): A reactive signal carrying messages sent from the variable to the factor. Updated during variable-to-factor message passing.
source
Cortex.get_connection_message_to_variableFunction
get_connection_message_to_variable(connection::Connection)

Retrieves the reactive signal carrying messages from factor to variable.

source
get_connection_message_to_variable(engine::InferenceEngine, variable_id::Int, factor_id::Int)

Alias for get_connection_message_to_variable(get_connection(engine, variable_id, factor_id)::Connection)::InferenceSignal.

source
Cortex.get_connection_message_to_factorFunction
get_connection_message_to_factor(connection::Connection)

Retrieves the reactive signal carrying messages from variable to factor.

source
get_connection_message_to_factor(engine::InferenceEngine, variable_id::Int, factor_id::Int)

Alias for get_connection_message_to_factor(get_connection(engine, variable_id, factor_id)::Connection)::InferenceSignal.

source

Currently Supported Model Engines

At present, Cortex.jl officially supports the BipartiteFactorGraph type from the BipartiteFactorGraphs.jl package. To use it:

using BipartiteFactorGraphs
using Cortex

# Create a bipartite factor graph where
# - the type of variables is `Cortex.Variable`
# - the type of factors is `Cortex.Factor`
# - the type of connections is `Cortex.Connection`
graph = BipartiteFactorGraph(Cortex.Variable, Cortex.Factor, Cortex.Connection)
# ... add variables and factors to the graph ...

# Use it with Cortex's inference engine
engine = Cortex.InferenceEngine(model_engine = graph)

You might be interested in the GraphPPL.jl package which provides a more user-friendly interface for creating and manipulating probabilistic models in a form of a BipartiteFactorGraph.

Implementing a New Model Engine

To make a new data structure work as a model engine, first you need to implement the trait that indicates support:

Cortex.is_engine_supportedFunction
is_engine_supported(engine::Any) -> Union{SupportedModelEngine, UnsupportedModelEngine}

Checks if a given engine is supported by the InferenceEngine.

This function should be extended by specific engine implementations.

Arguments

  • engine::Any: The model engine instance to check.

Returns

  • SupportedModelEngine() if the engine is supported.
  • UnsupportedModelEngine() otherwise.

Example

julia> struct CustomModelEngine end

julia> Cortex.is_engine_supported(::CustomModelEngine) = Cortex.SupportedModelEngine();
julia> struct SomeOtherDataStructure end

julia> Cortex.is_engine_supported(::SomeOtherDataStructure) = Cortex.UnsupportedModelEngine();

See Also

source

If an unsupported data structure is used as a model engine, Cortex.jl will throw:

Cortex.UnsupportedModelEngineErrorType
UnsupportedModelEngineError(engine, [ missing_function ])

An error thrown when attempting to use an unsupported model engine.

This error is typically thrown by throw_if_engine_unsupported when is_engine_supported returns UnsupportedModelEngine. Additionaly, accepts a function name that was missing from the model engine. In this case, the error message will include the missing function name.

Fields

  • model_engine: The unsupported model engine instance.
  • missing_function: The name of the function that was missing from the model engine.

See Also

source

Required Model Engine Methods

After implementing the trait, model engines must implement the following methods.

Note

Some methods are aliases for the Cortex.InferenceEngine structure, those do not need to be implemented.

Cortex.get_variableFunction
get_variable(model_engine, variable_id::Int)::Variable

Retrieves the data structure representing a specific variable from the model engine. This function must be implemented by specific model engines. The returned object be Cortex.Variable.

source
get_variable(engine::InferenceEngine, variable_id::Int)

Alias for get_variable(get_model_engine(engine), variable_id).

source
Cortex.get_factorFunction
get_factor(model_engine, factor_id::Int)::Factor

Retrieves the data structure representing a specific factor from the model engine. This function must be implemented by specific model engines. The returned object be Cortex.Factor.

source
get_factor(engine::InferenceEngine, factor_id::Int)

Alias for get_factor(get_model_engine(engine), factor_id).

source
Cortex.get_variable_idsFunction
get_variable_ids(model_engine)

Retrieves an iterator over all variable identifiers in the model engine. This function must be implemented by specific model engines.

source
get_variable_ids(engine::InferenceEngine)

Alias for get_variable_ids(get_model_engine(engine)).

source
Cortex.get_factor_idsFunction
get_factor_ids(model_engine)

Retrieves an iterator over all factor identifiers in the model engine. This function must be implemented by specific model engines.

source
get_factor_ids(engine::InferenceEngine)

Alias for get_factor_ids(get_model_engine(engine)).

source
Cortex.get_connected_variable_idsFunction
get_connected_variable_ids(model_engine, factor_id::Int)

Retrieves an iterator over the identifiers of variables connected to a given factor. This function must be implemented by specific model engines.

source
get_connected_variable_ids(engine::InferenceEngine, factor_id::Int)

Alias for get_connected_variable_ids(get_model_engine(engine), factor_id).

source
Cortex.get_connected_factor_idsFunction
get_connected_factor_ids(model_engine, variable_id::Int)

Retrieves an iterator over the identifiers of factors connected to a given variable. This function must be implemented by specific model engines.

source
get_connected_factor_ids(engine::InferenceEngine, variable_id::Int)

Alias for get_connected_factor_ids(get_model_engine(engine), variable_id).

source
Cortex.get_connectionFunction
get_connection(model_engine, variable_id::Int, factor_id::Int)::Connection

Retrieves the data structure representing the connection between a specified variable and factor. The returned object must be Cortex.Connection.

source
get_connection(engine::InferenceEngine, variable_id::Int, factor_id::Int)

Alias for get_connection(get_model_engine(engine), variable_id, factor_id).

source

These methods form a complete interface that allows Cortex.jl to:

  • Access variables and factors in your model using standardized Variable and Factor types
  • Navigate connections between variables and factors using the Connection type
  • Perform inference using your model engine