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:
- 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.
- Interoperability: Existing probabilistic model implementations, both in Julia and other languages, can be integrated by implementing the interface.
- Performance: Specialized model engines can be optimized for specific use cases without modifying Cortex.jl itself.
- 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.Variable
— TypeVariable
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
get_variable_name
: Retrieve the variable's nameget_variable_index
: Retrieve the variable's indexget_variable_marginal
: Retrieve the variable's marginal signalget_variable_linked_signals
: Retrieve the variable's linked signalslink_signal_to_variable!
: Link additional signals to the variable
Cortex.get_variable_name
— Functionget_variable_name(variable::Variable)
Retrieves the name of a variable.
Cortex.get_variable_index
— Functionget_variable_index(variable::Variable)
Retrieves the index of a variable.
Cortex.get_variable_marginal
— Functionget_variable_marginal(variable::Variable)
Retrieves the marginal of a variable.
Cortex.get_variable_linked_signals
— Functionget_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.
Cortex.link_signal_to_variable!
— Functionlink_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.
Factor
Cortex.Factor
— TypeFactor
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
get_factor_functional_form
: Retrieve the factor's functional formget_factor_local_marginals
: Retrieve the factor's local marginalsadd_local_marginal_to_factor!
: Add a local marginal to the factorVariable
: The variable data structure that factors connect
Cortex.get_factor_functional_form
— Functionget_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.
Cortex.get_factor_local_marginals
— Functionget_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.
Cortex.add_local_marginal_to_factor!
— Functionadd_local_marginal_to_factor!(factor::Factor, local_marginal::InferenceSignal)
Adds a local marginal signal to a factor's collection of local marginals.
Connection
Cortex.Connection
— TypeConnection
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.
Cortex.get_connection_label
— Functionget_connection_label(connection::Connection)
Retrieves the symbolic label of a connection.
Cortex.get_connection_index
— Functionget_connection_index(connection::Connection)
Retrieves the numeric index of a connection.
Cortex.get_connection_message_to_variable
— Functionget_connection_message_to_variable(connection::Connection)
Retrieves the reactive signal carrying messages from factor to variable.
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
.
Cortex.get_connection_message_to_factor
— Functionget_connection_message_to_factor(connection::Connection)
Retrieves the reactive signal carrying messages from variable to factor.
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
.
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_supported
— Functionis_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
Cortex.SupportedModelEngine
— TypeA trait object indicating a supported model engine. Use this as a return value of Cortex.is_engine_supported
.
Cortex.UnsupportedModelEngine
— TypeA trait object indicating an unsupported model engine. Used as a default return value of Cortex.is_engine_supported
.
If an unsupported data structure is used as a model engine, Cortex.jl will throw:
Cortex.UnsupportedModelEngineError
— TypeUnsupportedModelEngineError(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
Cortex.throw_if_engine_unsupported
— Functionthrow_if_engine_unsupported(engine::Any)
Throws an UnsupportedModelEngineError
if the engine is unsupported.
Required Model Engine Methods
After implementing the trait, model engines must implement the following methods.
Some methods are aliases for the Cortex.InferenceEngine
structure, those do not need to be implemented.
Cortex.get_variable
— Functionget_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
.
get_variable(engine::InferenceEngine, variable_id::Int)
Alias for get_variable(get_model_engine(engine), variable_id)
.
Cortex.get_factor
— Functionget_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
.
get_factor(engine::InferenceEngine, factor_id::Int)
Alias for get_factor(get_model_engine(engine), factor_id)
.
Cortex.get_variable_ids
— Functionget_variable_ids(model_engine)
Retrieves an iterator over all variable identifiers in the model engine. This function must be implemented by specific model engines.
get_variable_ids(engine::InferenceEngine)
Alias for get_variable_ids(get_model_engine(engine))
.
Cortex.get_factor_ids
— Functionget_factor_ids(model_engine)
Retrieves an iterator over all factor identifiers in the model engine. This function must be implemented by specific model engines.
get_factor_ids(engine::InferenceEngine)
Alias for get_factor_ids(get_model_engine(engine))
.
Cortex.get_connected_variable_ids
— Functionget_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.
get_connected_variable_ids(engine::InferenceEngine, factor_id::Int)
Alias for get_connected_variable_ids(get_model_engine(engine), factor_id)
.
Cortex.get_connected_factor_ids
— Functionget_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.
get_connected_factor_ids(engine::InferenceEngine, variable_id::Int)
Alias for get_connected_factor_ids(get_model_engine(engine), variable_id)
.
Cortex.get_connection
— Functionget_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
.
get_connection(engine::InferenceEngine, variable_id::Int, factor_id::Int)
Alias for get_connection(get_model_engine(engine), variable_id, factor_id)
.
These methods form a complete interface that allows Cortex.jl to:
- Access variables and factors in your model using standardized
Variable
andFactor
types - Navigate connections between variables and factors using the
Connection
type - Perform inference using your model engine