PageSourceSearch

https://docs.rxinfer.com/stable/search_index.js

js rxinfer.com collected 2026-10-03 22:11:29 UTC 531,401 bytes, 3 lines download raw bytes

1var documenterSearchIndex = {"docs":
2[{"location":"manuals/inference/nonconjugate/#inference-nonconjugate","page":"Non-conjugate inference","title":"Non-conjugate Inference","text":"The RxInfer package excels in scenarios where the model uses conjugate priors for hidden states. Conjugate priors allow Bayesian inference to utilize pre-computed analytical update rules, significantly speeding up the inference process. For instance, the conjugate prior for the parameter of a Bernoulli distribution is the Beta distribution. The conjugate prior for the mean parameter of a Normal distribution is another Normal distribution, and the conjugate prior for the precision parameter of a Normal distribution is the Gamma distribution.","category":"section"},{"location":"manuals/inference/nonconjugate/#Non-conjugate-Structures","page":"Non-conjugate inference","title":"Non-conjugate Structures","text":"However, models often contain non-conjugate structures, which prevent RxInfer from performing efficient inference. Non-conjugate priors occur when the prior and the likelihood do not result in a posterior that belongs to the same family as the prior. This complicates the inference process because it requires approximations or numerical methods instead of simple analytical updates.","category":"section"},{"location":"manuals/inference/nonconjugate/#Example-Scenario","page":"Non-conjugate inference","title":"Example Scenario","text":"Consider the scenario where we assign the Beta distribution as a prior for the mean parameter of a Normal distribution. Let's explore what happens in this case with an example.\n\nFirst, we generate some synthetic data:\n\nusing Distributions, ExponentialFamily, Plots, StableRNGs\n\n# The model will infer the hidden parameters from data\nhidden_mean         = 0.2\nhidden_precision    = 0.8\nhidden_distribution = NormalMeanPrecision(hidden_mean, hidden_precision)\n\nnumber_of_datapoints = 1000\ndata = rand(StableRNG(42), hidden_distribution, number_of_datapoints)\n\nhistogram(data; normalize = :pdf)\n\nNext, we specify the model. Suppose we believe the data follows a Normal distribution, and we are confident that the mean parameter is between 0 and 1. The Beta distribution is a logical choice for the prior of the mean parameter because it models a continuous variable in the range from 0 to 1. Similarly, we assign a Beta prior for the precision parameter, assuming it also lies between 0 and 1.\n\nusing RxInfer\n\n@model function non_conjugate_model(y)\n   m ~ Beta(1, 1)\n   p ~ Beta(1, 1)\n   y .~ Normal(mean = m, precision = p)\nend\n\nIf we attempt inference with this model, RxInfer will throw an error because the necessary computational rules for such a model are not available in closed form. This is due to the non-conjugate nature of the priors used.","category":"section"},{"location":"manuals/inference/nonconjugate/#Addressing-Non-conjugacy-with-ExponentialFamilyProjection","page":"Non-conjugate inference","title":"Addressing Non-conjugacy with ExponentialFamilyProjection","text":"To overcome this limitation, RxInfer integrates with the ExponentialFamilyProjection package. This package re-projects non-conjugate relationships back into a member of the exponential family at the cost of some accuracy.\n\nnote: Note\nRxInfer supports non-conjugate inference for completeness, but be aware that inference execution times may increase significantly. This is because non-conjugate models require more complex computations, often involving sampling-based approximations.","category":"section"},{"location":"manuals/inference/nonconjugate/#Specifying-Constraints","page":"Non-conjugate inference","title":"Specifying Constraints","text":"The projection constraint must be specified using the @constraints macro. For example:\n\nusing ExponentialFamilyProjection\n\n@constraints function non_conjugate_model_constraints()\n  # project variational posterior over `m` to `Beta`\n  q(m) :: ProjectedTo(Beta)\n  # project variational posterior over `p` to `Beta`\n  q(p) :: ProjectedTo(Beta)\n  # `m` and `p` are jointly independent\n  q(m, p) = q(m)q(p)\nend\n\nThese constraints specify that the posterior distribution for the hidden variable m must be re-projected to a Beta distribution to cover the region from 0 to 1. The same applies to the variable p.  \n\nnote: Note\nNote that the distribution specified in the @constraints does not need to match the distribution specified as a prior. For example, we could use a Gamma distribution as a prior and a Beta distribution as a posterior. The only requirement is that the support of the posterior distribution must be the same as or smaller than that of the prior.\n\nWe also assume that m and p are jointly independent with the q(m, p) = q(m)q(p) specification. Dropping the assumption of joint independence would require initializing messages for m and p without guarantees of convergence. Read more about factorization constraints in the Constraints Specification guide.\n\nnote: Note\nThe ProjectedTo structure is defined in the ExponentialFamilyProjection package. To fully explore its capabilities and hyper-parameters, we invite you to read the detailed documentation.","category":"section"},{"location":"manuals/inference/nonconjugate/#Initialization","page":"Non-conjugate inference","title":"Initialization","text":"We also need to initialize the inference procedure due to the factorization constraints. Read more about initialization in the corresponding section.\n\ninitialization = @initialization begin \n  q(m) = Beta(1, 1)\n  q(p) = Beta(1, 1)\nend","category":"section"},{"location":"manuals/inference/nonconjugate/#Running-the-Inference","page":"Non-conjugate inference","title":"Running the Inference","text":"With everything set up, we can run the inference procedure:\n\nresult = infer(\n  model = non_conjugate_model(),\n  data  = (y = data,),\n  constraints = non_conjugate_model_constraints(),\n  initialization = initialization,\n  iterations = 25,\n  free_energy = true\n)","category":"section"},{"location":"manuals/inference/nonconjugate/#Analyzing-the-Results","page":"Non-conjugate inference","title":"Analyzing the Results","text":"Let's analyze the results using the StatsPlots package to visualize the resulting posteriors over individual VMP iterations:\n\nusing StatsPlots\nusing Test #hide\n\n@test isapprox(mean(result.posteriors[:m][end]), hidden_mean, atol = 1e-1) #hide\n@test isapprox(mean(result.posteriors[:p][end]), hidden_precision, atol = 1e-1) #hide\n\n@gif for (i, q) in enumerate(zip(result.posteriors[:m], result.posteriors[:p]))\n  q_m = q[1]\n  q_p = q[2]\n\n  p1 = plot(q_m, label = \"Inferred `m`\", fill = 0, fillalpha = 0.2)\n  p1 = vline!(p1, [hidden_mean
2], label = \"Hidden `m`\")\n\n  p2 = plot(q_p, label = \"Inferred `p`\", fill = 0, fillalpha = 0.2)\n  p2 = vline!(p2, [hidden_precision], label = \"Hidden `p`\")\n\n  plot(p1, p2; title = \"Iteration $i\")\nend fps = 15\n\nAs we can see, the estimated posteriors are quite close to the actual hidden parameters used to generate our dataset. We can also verify the Bethe Free Energy values to ensure our result has converged:\n\n@test first(result.free_energy) > last(result.free_energy) #hide\nplot(result.free_energy, label = \"Bethe Free Energy (per iteration)\")\n\nThe convergence of the Bethe Free Energy indicates that the inference process has stabilized, and the model parameters have reached an optimal state.\n\nnote: Note\nThe projection method uses stochastic gradient computations, which may cause fluctuations in the estimates and Bethe Free Energy performance.\n\n","category":"section"},{"location":"manuals/inference/postprocess/#user-guide-inference-postprocess","page":"Inference results postprocessing","title":"Inference results postprocessing","text":"The infer function allows users to control how inference results are postprocessed  via the postprocess keyword argument.\n\nInternally, the inference engine operates on Marginal wrapper types that carry both the  underlying distribution and any associated annotation data. The postprocessing step determines whether these wrappers are preserved or stripped from the final result.","category":"section"},{"location":"manuals/inference/postprocess/#Default-behavior","page":"Inference results postprocessing","title":"Default behavior","text":"The default postprocessing strategy depends on whether annotations are enabled:\n\nWithout annotations (annotations = nothing, the default): the strategy is UnpackMarginalPostprocess.  Since no annotation data is attached, the Marginal wrapper is removed and the result  contains the underlying distribution directly (e.g., a Normal or Beta distribution).\nWith annotations (e.g., annotations = LogScaleAnnotations()): the strategy is NoopPostprocess.  The Marginal wrapper is preserved so that annotation data remains accessible via  ReactiveMP.getannotations.\n\nYou can always override the default by passing postprocess = ... explicitly to infer.","category":"section"},{"location":"manuals/inference/postprocess/#Available-strategies","page":"Inference results postprocessing","title":"Available strategies","text":"","category":"section"},{"location":"manuals/inference/postprocess/#user-guide-inference-postprocess-custom","page":"Inference results postprocessing","title":"Custom postprocessing step","text":"To implement a custom postprocessing strategy, define a new type and implement the  inference_postprocess method for it:\n\nusing RxInfer\n\nstruct CustomPostprocess end\n\n# For demonstration purposes our postprocessing step simply stringifies the result\nRxInfer.inference_postprocess(::CustomPostprocess, result::Marginal) = string(ReactiveMP.getdata(result))\n\nNow, we can use the postprocessing step in the infer function:\n\nusing Test #hide\n@model function beta_bernoulli(y)\n    θ ~ Beta(1, 1)\n    y ~ Bernoulli(θ)\nend\n\nresult = infer(\n    model = beta_bernoulli(),\n    data  = (y = 1.,),\n    postprocess = CustomPostprocess()\n)\n\n@test occursin(\"Beta{Float64}(α=2.0, β=1.0)\", result.posteriors[:θ]) #hide\nresult.posteriors[:θ] # should be a `String`\n\n@test result.posteriors[:θ] isa String #hide\nresult.posteriors[:θ] isa String\n\n","category":"section"},{"location":"manuals/inference/postprocess/#RxInfer.inference_postprocess","page":"Inference results postprocessing","title":"RxInfer.inference_postprocess","text":"inference_postprocess(strategy, result)\n\nPostprocesses the result of the inference procedure according to the given strategy. The result is typically a Marginal or a collection of Marginals.\n\nThe infer function selects a default strategy automatically based on whether annotations are enabled:\n\nWhen annotations = nothing (the default), the strategy is UnpackMarginalPostprocess, which strips the Marginal wrapper and returns the underlying distribution directly.\nWhen annotations are provided, the strategy is NoopPostprocess, which preserves the Marginal wrapper so that annotation data remains accessible via ReactiveMP.getannotations.\n\nYou can override this default by passing a custom postprocess argument to infer. See Inference results postprocessing for more details.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/postprocess/#RxInfer.UnpackMarginalPostprocess","page":"Inference results postprocessing","t
2itle":"RxInfer.UnpackMarginalPostprocess","text":"UnpackMarginalPostprocess\n\nA postprocessing strategy that removes the Marginal wrapper type from the inference result, returning the underlying distribution directly. This is the default strategy when no annotations are enabled, since the Marginal wrapper carries no extra information in that case.\n\nSee also: NoopPostprocess, inference_postprocess\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/postprocess/#RxInfer.NoopPostprocess","page":"Inference results postprocessing","title":"RxInfer.NoopPostprocess","text":"NoopPostprocess\n\nA postprocessing strategy that preserves the inference result as-is, keeping the Marginal wrapper intact. This is the default strategy when annotations are enabled, so that annotation data attached to each marginal remains accessible via ReactiveMP.getannotations.\n\nSee also: UnpackMarginalPostprocess, inference_postprocess\n\n\n\n\n\n","category":"type"},{"location":"manuals/sharpbits/usage-colon-equality/#usage-colon-equality","page":"Using = instead of := for deterministic nodes","title":"Using = instead of := for deterministic nodes","text":"When specifying probabilistic models in RxInfer, you might be tempted to use the = operator for deterministic relationships between variables. While this may seem natural from a programming perspective (especially if you're coming from other frameworks - see Comparison to other packages), it doesn't align with how Bayesian inference and factor graphs work. Let's explore why RxInfer uses a different approach and how it enables powerful probabilistic modeling.","category":"section"},{"location":"manuals/sharpbits/usage-colon-equality/#The-Problem","page":"Using = instead of := for deterministic nodes","title":"The Problem","text":"Consider this seemingly reasonable model specification:\n\n@model function wrong_model(θ)\n    x ~ MvNormal(mean = [ 0.0, 0.0 ], cov = [ 1.0 0.0; 0.0 1.0 ])\n    y = dot(x, θ)      # This won't work!\n    z ~ Normal(y, 1.0)\nend\n\nThis code will fail because:\n\nDuring model creation, x is not an actual vector of numbers - it's a reference to a node in the factor graph\nJulia's dot function expects a vector input, not a graph node\nThe = operator performs immediate assignment and executes the dot function, which isn't what we want for building factor graphs","category":"section"},{"location":"manuals/sharpbits/usage-colon-equality/#The-Solution","page":"Using = instead of := for deterministic nodes","title":"The Solution","text":"Use the := operator for deterministic relationships:\n\n@model function correct_model()\n    x ~ MvNormal(mean = [ 0.0, 0.0 ], cov = [ 1.0 0.0; 0.0 1.0 ])\n    y := dot(x, θ)     # This is correct!\n    z ~ Normal(y, 1.0)\nend\n\nThe := operator:\n\nCreates a deterministic node in the factor graph\nProperly tracks dependencies between variables\nAllows RxInfer to handle the computation during inference\n\ntip: Tip\nIf you're coming from other probabilistic programming frameworks like Turing.jl, remember that RxInfer uses := for deterministic relationships. While this might seem unusual at first, it's a deliberate design choice that enables powerful message-passing inference algorithms.","category":"section"},{"location":"manuals/sharpbits/usage-colon-equality/#Why-Not-?","page":"Using = instead of := for deterministic nodes","title":"Why Not =?","text":"RxInfer's design is based on factor graphs, which are probabilistic graphical models that represent the factorization of a joint probability distribution. In a factor graph:\n\nVariables are represented as nodes (vertices) in the graph\nFactor nodes connect variables and encode their relationships\nEdges represent the dependencies between variables and factors\nBoth probabilistic (~) and deterministic (:=) relationships create specific types of factor nodes\n\nWhen you specify a model, RxInfer constructs this graph structure where:\n\nEach ~ creates a factor node representing that probability distribution\nEach := creates a deterministic factor node representing that transformation\nVariables are automatically connected to their relevant factors\nThe graph captures the complete probabilistic model structure\n\nThis explicit graph-based design brings several key benefits:\n\nEfficient Message Passing: The graph structure enables localized belief propagation, where each node only needs to communicate with its immediate neighbors\nLazy Evaluation: Factor nodes compute messages only when needed during inference, avoiding unnecessary calculations\nFlexible Inference: The same graph structure can support different message-passing schedules and inference algorithms\nModular Updates: Changes in one part of the graph only affect the connected components\n\nUsing = would break this design because:\n\nIt executes computations immediately during model specification, before the graph is built\nIt prevents RxInfer from properly tracking the probabilistic dependencies\nIt makes message passing impossible since there's no graph structure to pass messages through","category":"section"},{"location":"manuals/sharpbits/usage-colon-equality/#Implementation-Details","page":"Using = instead of := for deterministic nodes","title":"Implementation Details","text":"When you write:\n\ny := dot(x, θ)\n\nRxInfer creates:\n\nA deterministic factor node representing the dot function with x and θ as arguments (edges)\nA node for y if it has not been created yet\nProper edges connecting x and θ to this node and this node to y\nMessage passing rules for propagating beliefs through this transformation\n\nThis structured approach enables efficie
2nt inference and maintains the mathematical rigor of the probabilistic model.\n\nFor more details about model specification, see the Model Specification guide, particularly the section on Deterministic relationships.\n\n","category":"section"},{"location":"manuals/inference/trace-callbacks/#manual-inference-trace-callbacks","page":"Trace callbacks","title":"Trace callbacks","text":"RxInfer provides a built-in callback structure called RxInferTraceCallbacks for recording all callback events during the inference procedure. Each event is stored as a TracedEvent containing the event name (as a Symbol) and the event object itself. This is useful for debugging, understanding the inference flow, and inspecting what happens at each stage. For general information about the callbacks system, see Callbacks.","category":"section"},{"location":"manuals/inference/trace-callbacks/#Basic-usage","page":"Trace callbacks","title":"Basic usage","text":"using RxInfer\nusing Test #hide\n\n@model function iid_normal(y)\n    μ  ~ Normal(mean = 0.0, variance = 100.0)\n    γ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = γ)\nend\n\ninit = @initialization begin\n    q(μ) = vague(NormalMeanVariance)\nend\n\n# Create a trace callbacks instance\ntrace = RxInferTraceCallbacks()\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(10),),\n    constraints = MeanField(),\n    iterations = 3,\n    initialization = init,\n    callbacks = trace,\n)\n\nevents = RxInfer.tracedevents(trace)\n@test !isempty(events) #hide\n@test all(e -> e isa TracedEvent, events) #hide\n\nprintln(\"Recorded $(length(events)) events\")\nfor i in 1:10\n    println(\"  \", events[i])\nend\nprintln(\"...\")","category":"section"},{"location":"manuals/inference/trace-callbacks/#Using-trace-true","page":"Trace callbacks","title":"Using trace = true","text":"Instead of creating a RxInferTraceCallbacks instance manually, you can use the trace = true keyword argument in the infer function. This automatically merges a RxInferTraceCallbacks instance with any user-provided callbacks and saves it to the model's metadata:\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(10),),\n    constraints = MeanField(),\n    iterations = 3,\n    initialization = init,\n    trace = true,\n)\n\n@test haskey(result.model.metadata, :trace) #hide\n@test result.model.metadata[:trace] isa RxInferTraceCallbacks #hide\n\ntrace = result.model.metadata[:trace]\nevents = RxInfer.tracedevents(trace)\nprintln(\"Recorded $(length(events)) events via trace = true\")","category":"section"},{"location":"manuals/inference/trace-callbacks/#Accessing-from-model-metadata","page":"Trace callbacks","title":"Accessing from model metadata","text":"After model creation, the trace callbacks instance is automatically saved into the model's metadata under the :trace key. This makes it accessible from the inference result without needing to hold onto the callbacks object separately:\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(10),),\n    constraints = MeanField(),\n    iterations = 3,\n    initialization = init,\n    callbacks = RxInferTraceCallbacks(),\n)\n\n@test haskey(result.model.metadata, :trace) #hide\n@test result.model.metadata[:trace] isa RxInferTraceCallbacks #hide\n\ntrace = result.model.metadata[:trace]\nevents = RxInfer.tracedevents(trace)\nprintln(\"Recorded $(length(events)) events via trace = true\")","category":"section"},{"location":"manuals/inference/trace-callbacks/#Inspecting-traced-events","page":"Trace callbacks","title":"Inspecting traced events","text":"Each TracedEvent has a single field:\n\nevent::ReactiveMP.Event — the original event object that was passed to the callback\n\nYou can retrieve the event name via ReactiveMP.event_name(typeof(traced_event.event)) and access event-specific fields directly on traced_event.event.\n\nusing RxInfer.ReactiveMP: event_name\nevents = RxInfer.tracedevents(trace)\n\n# Filter for specific events\niteration_events = filter(e -> event_name(typeof(e.event)) === :before_iteration, events)\n@test length(iteration_events) == 3 #hide\nprintln(\"Number of iterations: \", length(iteration_events))","category":"section"},{"location":"manuals/inference/trace-callbacks/#Combining-with-other-callbacks","page":"Trace callbacks","title":"Combining with other callbacks","text":"trace = true is compatible with other callbacks, including benchmark = true and custom callbacks:\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(10),),\n    constraints = MeanField(),\n    iterations = 3,\n    initialization = init,\n    trace = true,\n    benchmark = true,\n)\n\n@test haskey(result.model.metadata, :trace) #hide\n@test haskey(result.model.metadata, :benchmark) #hide\n\nprintln(\"Trace included: \", haskey(result.model.metadata, :trace))\nprintln(\"Benchmark included: \", haskey(result.model.metadata, :benchmark))","category":"section"},{"location":"manuals/inference/trace-callbacks/#Viewing-traces-in-Perfetto","page":"Trace callbacks","title":"Viewing traces in Perfetto","text":"A recorded trace can be inspected interactively with the Perfetto trace viewer. Use perfetto_view to embed the viewer inside a Pluto, VS Code or Jupyter notebook cell, or perfetto_open to open it in your default browser.\n\nInside Perfetto, you can navigate (zoom and pan) using the WASD keys. You can select with the mouse, and inspect individual events. Press ? for a quick help menu.\n\nresult = infer(model = iid_normal(), data = (y = randn(10),), iterations = 3, trace = true)\ntraces = RxInfer.tracedevents(result.model.metadata[:trace])\n\nperfetto_view(traces)   # show directly in your IDE (Pluto, VS Code, Jupyter)\nperfetto_open(traces)   # open in the browser\n\n(Image: screenshot of the perfetto viewer inspecting traces)\n\nIn the screenshot above, the first ProductOfMessages event is selected, showing the event details in the bottom panel. Here you see the duration (5ms), and the event arguments, including the result distribution.\n\n
2If you are interested in debugging the performance of your inference call, take note that runtimes can vary greatly between runs due to Julia features like GC and JIT compilation. Try running your inference multiple times to get a better picture. You can also try to use Julia's built-in profiler.\n\nhint: Experimental feature\nThe Perfetto functionality is still experimental, and we would value your feedback! Let us know if you encounter any issues or have suggestions for improvement.","category":"section"},{"location":"manuals/inference/trace-callbacks/#Exporting-to-TensorBoard","page":"Trace callbacks","title":"Exporting to TensorBoard","text":"When TensorBoardLogger.jl is loaded, the TensorBoardLoggerExt extension activates and provides RxInfer.convert_to_tensorboard, which converts a recorded trace into TensorFlow event files readable by TensorBoard.\n\nusing RxInfer\nusing TensorBoardLogger  # activates the extension\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(10),),\n    constraints = MeanField(),\n    iterations = 5,\n    initialization = init,\n    trace = true,\n)\n\ntrace = result.model.metadata[:trace]\n\nlog_dir = RxInfer.convert_to_tensorboard(trace; log_distributions = true)\n# Then run: tensorboard --logdir=\"<log_dir>\"","category":"section"},{"location":"manuals/inference/trace-callbacks/#What-gets-logged","page":"Trace callbacks","title":"What gets logged","text":"Output TensorBoard tab Condition\nPer-iteration wall-clock duration (iteration_time_ms) Scalars always\nParameterisation-aware scalar tags for each posterior (see table below) Scalars log_posteriors admits the variable (see Filtering posteriors)\nPer-iteration histogram of posterior samples (posteriors/<var>/distribution) Distributions / Histograms log_distributions = true and log_posteriors admits the variable\nEventCounts per-event-type table Text always\nSummary run-timing rollup (see Run summary) Text always\nPer-event narrative breadcrumbs (Events, before_iteration, …) Text log_text_events = true","category":"section"},{"location":"manuals/inference/trace-callbacks/#Posterior-scalar-tags-by-family","page":"Trace callbacks","title":"Posterior scalar tags by family","text":"Each posterior is logged under posteriors/<variable>/<tag> with one step per inference iteration. Specific dispatch is selected by the marginal's distribution type — the most-specific method wins, with the generic mean/var fallback catching anything not listed.\n\nDistribution family Emitted tags\nNormal (any of the UnivariateNormalDistributionsFamily aliases) mean, precision\nGamma (any of the GammaDistributionsFamily aliases) shape, rate\nBeta alpha, beta, mean\nBern
2oulli succprob\nBinomial ntrials, succprob\nInverseGamma (a.k.a. GammaInverse) shape, scale\nPoisson rate\nGeometric succprob\nNegativeBinomial r, succprob\nExponential rate\nVonMises location, concentration\nWeibull shape, scale\nLogNormal meanlog, stdlog\nErlang shape, scale\nLaplace location, scale\nPareto shape, scale\nRayleigh scale\nChisq dof\nany other UnivariateDistribution (fallback) mean, var\n\nMarginals that are not univariate (e.g. multivariate Normal, matrix-variate posteriors) are silently skipped on the scalar path and produce no posteriors/... scalar tags. The histogram path is also gated on <: UnivariateDistribution, so the same families above are the ones that contribute to the Distributions / Histograms tabs when log_distributions = true.","category":"section"},{"location":"manuals/inference/trace-callbacks/#tensorboard-run-summary","page":"Trace callbacks","title":"Run summary","text":"The Summary text tag is a one-shot snapshot of the run, written at step 1 alongside EventCounts. Each row is a key: value line; rows whose underlying measurement is missing (no matching Before*/After* event seen, or no iteration durations recorded) are silently skipped, so partial runs still produce a useful table instead of zero-valued placeholders.\n\nRow Meaning Source\nmodel_build Wall-clock between BeforeModelCreationEvent and AfterModelCreationEvent. Paired event timestamps.\ninference Wall-clock between BeforeInferenceEvent and AfterInferenceEvent. Paired event timestamps.\ntotal_wall First-to-last traced event timestamp — covers the whole infer call, including any time before model creation or after inference. First and last TracedEvent.time_ns.\nn_iterations Number of paired iteration spans observed. Length of the per-iteration duration map.\niter_total, iter_mean, iter_min, iter_max Aggregates over per-iteration wall-clock durations (sum, mean, min, max). Same per-iteration durations that drive the iteration_time_ms scalar.\n\nSummary complements rather than replaces existing outputs: per-iteration durations remain in the iteration_time_ms scalar series, and the per-event-type breakdown stays in EventCounts. The Summary tag is always emitted for any trace that contains at least one event — it is not gated by log_text_events, log_posteriors, or log_distributions.","category":"section"},{"location":"manuals/inference/trace-callbacks/#tensorboard-log-posteriors","page":"Trace callbacks","title":"Filtering posteriors","text":"The log_posteriors keyword controls which marginals reach the posteriors/<var>/* tags. It is independent of log_distributions, which controls what is emitted (scalars only vs. scalars + per-iteration histogram). Think of log_posteriors as the row filter and log_distributions as the column filter.\n\nlog_posteriors value Effect\ntrue (default) Log every marginal the model produces.\nfalse Suppress every posteriors/* tag (both scalars and histograms). Iteration timing, event counts, and event-text breadcrumbs are unaffected.\nVector{String} or Vector{Symbol} Log only marginals whose name appears in the list. Both [\"μ\", \"θ\"] and [:μ, :θ] are accepted. An empty list behaves like false.\n\ntrace = result.model.metadata[:trace]\n\n# Log only μ (the τ posterior is skipped on both scalar and histogram paths).\nRxInfer.convert_to_tensorboard(\n    trace;\n    log_posteriors    = [\"μ\"],\n    log_distributions = true,\n)\n\n# Suppress every posterior tag while keeping iteration timing visible.\nRxInfer.convert_to_tensorboard(trace; log_posteriors = false)\n\nWhen log_posteriors is an allow-list, the per-event text breadcrumb under on_marginal_update/<var> (gated by log_text_events) still fires for every variable — so you can keep visibility on which marginals updated without paying the scalar/histogram cost.","category":"section"},{"location":"manuals/inference/trace-callbacks/#API-Reference","page":"Trace callbacks","title":"API Reference","text":"","category":"section"},{"location":"manuals/inference/trace-callbacks/#RxInfer.perfetto_view","page":"Trace callbacks","title":"RxInfer.perfetto_view","text":"perfetto_view(traces::Vector{TracedEvent}; name = \"$(Time(now())) RxInfer trace\")\n\nConverts a vector of TracedEvents to an embedded Perfetto trace viewer. Returns a PerfettoDisplay that renders as an interactive trace when displayed in a Pluto, VS Code or Jupyter notebook cell.\n\nwarning: Experimental\nThis feature is experimental and its API may change in future releases.\n\nSee also: perfetto_open, RxInferTraceCallbacks.\n\nExample\n\nresult = infer(model = my_model(), data = my_data, trace = true)\ntraces = RxInfer.tracedevents(result.model.metadata[:trace])\nperfetto_view(traces)   # display in a notebook cell\n\nPluto tip: combine this with PlutoUI.WideCell for a bigger view, so `perfettoview(traces) |> WideCell`._\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/trace-callbacks/#RxInfer.perfetto_open","page":"Trace callbacks","title":"RxInfer.perfetto_open","text":"perfetto_open(traces::Vector{TracedEvent}; name = \"$(Time(now())) RxInfer trace\")\n\nConverts a vector of TracedEvents to Perfetto JSON and opens the result in your default web browser using the Perfetto trace viewer.\n\nReturns the path to the temporary HTML file that was opened.\n\nwarning: Experimental\nThis feature is experimental and its API may change in future releases.\n\nSee also: perfetto_view, RxInferTraceCallbacks.\n\nExample\n\nresult = infer(model = my_model(), data = my_data, trace = true)\ntraces = RxInfer.tracedevents(result.model.metadata[:trace])\nperfetto_open(traces)\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/trace-callbacks/#RxInfer.PerfettoDisplay","page":"Trace callbacks","title":"RxInfer.PerfettoDisplay","text":"PerfettoDisplay\n\nReturned by perfetto_view. Renders as an embedded Perfetto trace viewer when displayed in a Pluto, VS Code or Jupyter notebook cell.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/trace-callbacks/#RxInfer.convert_to_tensorboard","page":"Trace callbacks","title":"RxInfer.convert_to_tensorboard","text":"convert_to_tensorboard(trace::RxInferTraceCallbacks; output_file::Union{String, Nothing} = nothing,\n                       log_distributions::Bool = false, log_text_events::Bool = false,\n                       n_samples::Int = 1024, verbose
2::Bool = true)\n\nConvert trace events from inference to proper TensorFlow event files. Note that this function will not work  unless TensorBoardLogger.jl is loaded in the present Julia session.\n\nArguments\n\ntrace::RxInferTraceCallbacks: The trace callbacks object from inference results\noutput_file::Union{String, Nothing}: Optional directory path to write TensorBoard event logs. If not provided, writes to tensorboard_logs/ in the current working directory.\nlog_distributions::Bool: When true, log each univariate Normal and Gamma posterior as a per-iteration HistogramSummary so TensorBoard's Distributions tab renders a percentile-band view of the posterior across iterations. The same tag also appears in the Histograms tab as an offset ridgeline. Defaults to false.\nlog_text_events::Bool: When true, emit a per-event text breadcrumb (e.g. before_iteration, after_marginal_computation, and the Events step timeline) into the Text tab. The EventCounts summary is always written regardless of this flag. Scalar and histogram outputs are unaffected. Defaults to false.\nn_samples::Int: Number of samples drawn from each posterior to build the per-iteration histogram when log_distributions=true. Defaults to 1024.\nverbose: Whether to print useful information during export or not, defaults to true\n\nReturns\n\nString: Path to the directory containing the TensorBoard event log files\n\nDescription\n\nThis function processes all traced events and creates proper TensorFlow event files using TensorBoardLogger, which can be directly imported and visualized in TensorBoard. Outputs include:\n\nText summaries with event type information and counts\nScalar time-series for univariate Normal (mean, precision) and Gamma (shape, rate) posteriors\nScalar time-series for per-iteration wall-clock duration (iteration_time_ms)\nWhen log_distributions=true: per-iteration HistogramSummary under posteriors/<var>/distribution, rendered primarily in TensorBoard's Distributions tab\n\nThe output directory can be directly opened in TensorBoard's web interface for visualization and analysis.\n\nExample\n\nresults = infer(\n    model = my_model(),\n    data = my_data,\n    trace = true\n)\n\ntrace = results.model.metadata[:trace]\n\n# Create TensorBoard logs (writes to tensorboard_logs/ in the current directory)\nlog_dir = convert_to_tensorboard(trace; log_distributions = true)\n\n# Then run: tensorboard --logdir=$log_dir\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/trace-callbacks/#RxInfer.RxInferTraceCallbacks","page":"Trace callbacks","title":"RxInfer.RxInferTraceCallbacks","text":"RxInferTraceCallbacks()\n\nA callback structure that records (optionally filtered) callback events during the inference procedure. Eac
2h event is stored as a TracedEvent wrapping the original event object.\n\nWhen constructed with no arguments (or trace = true), all events are recorded. When constructed with a tuple of Symbols, only events whose names are in that tuple are recorded.\n\nAfter model creation, the trace callbacks instance is automatically saved into the model's metadata under the :trace key (i.e., model.metadata[:trace]), making it accessible from the inference result via result.model.metadata[:trace].\n\nUse RxInfer.tracedevents(callbacks) to retrieve the vector of traced events.\n\nExample\n\n# Create a trace callbacks instance that records all events\ntrace = RxInferTraceCallbacks()\n\n# Or record only specific events\ntrace = RxInferTraceCallbacks((:before_iteration, :after_iteration))\n\nresult = infer(\n    model = my_model(),\n    data = my_data,\n    callbacks = trace,\n)\n\n# Access the traced events\nevents = RxInfer.tracedevents(trace)\nfor event in events\n    println(event_name(event.event))\nend\n\n# Or access via model metadata\nresult.model.metadata[:trace] === trace # true\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/trace-callbacks/#RxInfer.TracedEvent","page":"Trace callbacks","title":"RxInfer.TracedEvent","text":"TracedEvent\n\nA single traced event recorded by RxInferTraceCallbacks. Wraps the original event object (a subtype of ReactiveMP.Event).\n\nFields\n\nevent::ReactiveMP.Event: the event object that was passed to the callback\ntime_ns::UInt64: the timestamp of the event in nanoseconds, uses time_ns() function from Julia\n\nUse ReactiveMP.event_name(traced_event.event) to retrieve the event name as a Symbol.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/trace-callbacks/#RxInfer.tracedevents","page":"Trace callbacks","title":"RxInfer.tracedevents","text":"tracedevents(callbacks::RxInferTraceCallbacks)\n\nReturns the vector of TracedEvent recorded by the trace callbacks.\n\nSee also: RxInferTraceCallbacks.\n\n\n\n\n\ntracedevents(event::Symbol, callbacks::RxInferTraceCallbacks)\n\nReturns the vector of TracedEvent recorded by the trace callbacks filtered by event.\n\nSee also: RxInferTraceCallbacks.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/trace-callbacks/#RxInfer.is_trace_event_included","page":"Trace callbacks","title":"RxInfer.is_trace_event_included","text":"is_trace_event_included(callbacks::RxInferTraceCallbacks, event_name::Symbol)\n\nChecks whether the specified event is not filtered and should be traced.\n\njulia> callbacks = RxInfer.RxInferTraceCallbacks((:event1, :event2));\n\njulia> RxInfer.is_trace_event_included(callbacks, :event1)\ntrue\n\njulia> RxInfer.is_trace_event_included(callbacks, :event2)\ntrue\n\njulia> RxInfer.is_trace_event_included(callbacks, :event3)\nfalse\n\n\n\n\n\n","category":"function"},{"location":"manuals/model-specification/#user-guide-model-specification","page":"Model specification","title":"Model Specification","text":"RxInfer largely depends on GraphPPL for model specification. Read extensive documentation regarding the model specification in the corresponding section of GraphPPL documentation. Here we outline only a small portion of model specification capabilities for beginners.","category":"section"},{"location":"manuals/model-specification/#@model-macro","page":"Model specification","title":"@model macro","text":"The RxInfer.jl package exports the @model macro for model specification. This @model macro accepts the model specification itself in a form of regular Julia function. \n\nFor example: \n\n@model function model_name(model_arguments...)\n    # model specification here\nend\n\nwhere model_arguments... may include both hyperparameters and data. \n\nnote: Note\nmodel_arguments are converted to keyword arguments. Positional arguments in the model specification are not supported.  Thus it is not possible to use Julia's multiple dispatch for the model arguments.\n\nThe @model macro returns a regular Julia function (in this example model_name()) which can be executed as usual. The only difference here is that all arguments of the model function are treated as keyword arguments. Upon calling, the model function returns a so-called model generator object, e.g:\n\nusing RxInfer #hide\n@model function my_model(observation, hyperparameter)\n    observations ~ Normal(mean = 0.0, var = hyperparameter)\nend\n\nmodel = my_model(hyperparameter = 3)\nnothing #hide\n\nThe model generator is not a real model (yet). For example, in the code above, we haven't specified anything for the observation.  The generator object allows us to iteratively add extra properties to the model, condition on data, and/or assign extra metadata information without actually materializing the entire graph structure. ","category":"section"},{"location":"manuals/model-specification/#A-state-space-model-example","page":"Model specification","title":"A state space model example","text":"Here we give an example of a probabilistic model before presenting the details of the model specification syntax. The model below is a simple state space model with latent random variables x and noisy observations y.\n\nusing RxInfer #hide\n\n@model function state_space_model(y, trend, variance)\n    x[1] ~ Normal(mean = 0.0, variance = 100.0)\n    y[1] ~ Normal(mean = x[1], variance = variance)\n    for i in 2:length(y)\n       x[i] ~ Normal(mean = x[i - 1] + trend, variance = 1.0)\n       y[i] ~ Normal(mean = x[i], variance = variance)\n    end\nend\n\nIn this model we assign a prior distribution over latent state x[1]. All subsequent states x[i] depend on x[i - 1] and trend and are modelled  as a simple Gaussian random walk. Observations y are modelled 
2with the Gaussian distribution as well with a  prespecified variance hyperparameter.\n\nnote: Note\nlength(y) can be called only if y has an associated data with it. This is not always the case, for example it is possible to instantiate the  model lazily before the data becomes available. In such situations, length(y) will throw an error.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-hyperparameters","page":"Model specification","title":"Hyperparameters","text":"Any constant passed to a model as a model argument will be automatically converted to a corresponding constant node in the model's graph.\n\nmodel = state_space_model(trend = 3.0, variance = 1.0)\nnothing #hide\n\nIn this example we instantiate a model generator with trend and variance parameters clamped to 3.0 and 1.0 respectively. That means  that no inference will be performed for those parameters and some of the expressions within the model structure might be simplified and compiled-out.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-conditioning","page":"Model specification","title":"Conditioning on data","text":"To fully complete model specification we need to specify y. In this example, y is playing a role of observations. RxInfer provides a convenient mechanism to pass data values to the model with the | operator.\n\nconditioned = model | (y = [ 0.0, 1.0, 2.0 ], )\n\nnote: Note\nThe conditioning on data is a feature of RxInfer, not GraphPPL. See Relation to GraphPPL for more details.\n\nIn the example above we conditioned on data in a form of the NamedTuple, but it is also possible to  condition on a dictionary where keys represent names of the corresponding model arguments:\n\ndata        = Dict(:y => [ 0.0, 1.0, 2.0 ])\nconditioned = model | data\n\nSometimes it might be useful to indicate that some arguments are data (thus condition on them) before the actual data becomes available. This situation may occur during reactive inference, when data becomes available after model creation. RxInfer provides a special structure called RxInfer.DeferredDataHandler, which can be used instead of the real data.\n\nFor the example above, however, we cannot simply do the following:\n\nmodel | (y = RxInfer.DeferredDataHandler(), )\n\nbecause we use length(y) in the model and this is only possible if y has an associated data.  We could adjust the model specification a bit, by adding the extra n parameter to the list of arguments:\n\n@model function state_space_model_with_n(y, n, trend, variance)\n    x[1] ~ Normal(mean = 0.0, variance = 100.0)\n    y[1] ~ Normal(mean = x[1], variance = variance)\n    for i in 2:n\n       x[i] ~ Normal(mean = x[i - 1] + trend, variance = 1.0)\n       y[i] ~ Normal(mean = x[i], variance = variance)\n    end\nend\n\nFor such model, we can safely condition on y without providing actual data for it, but using the RxInfer.DeferredDataHandler instead:\n\nstate_space_model_with_n(trend = 3.0, variance = 1.0, n = 10) | (\n    y = RxInfer.DeferredDataHandler(), \n)\n\nAfter the model has been conditioned it can be materialized with the RxInfer.create_model function. This function takes the RxInfer.ConditionedModelGenerator object and materializes it into a RxInfer.ProbabilisticModel.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-random-variables","page":"Model specification","title":"Latent variables","text":"Latent variables are being created with the ~ operator and can be read as is distributed as.  For example, to create a latent variable y which is modeled by a Normal distribution,  where its mean and variance are controlled by the random variables m and v respectively, we define\n\ny ~ Normal(mean = m, variance = v)\n\nIn the example above\n\nx[1] ~ Normal(mean = 0.0, variance = 100.0)\n\nindicates that x₁ is distributed as Normal distribution. \n\nnote: Note\nThe RxInfer.jl package uses the ~ operator for modelling both stochastic and deterministic relationships between random variables. However, GraphPPL.jl also allows to use the := operator for deterministic relationships.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation","page":"Model specification","title":"Relationships between variables","text":"In probabilistic models based on graphs, factor nodes are used to define a relationship between random variables and/or constants and data variables. A factor node defines a probability distribution over selected latent or data variables. The ~ operator not only creates a latent variable but also  defines a functional relationship of it with other variables and creates a factor node as a result.\n\nIn the example above\n\nx[1] ~ Normal(mean = 0.0, variance = 100.0)\n\nnot only creates a latent variable x₁ but also a factor node Normal.\n\nnote: Note\nGenerally it is not necessary to label all the arguments with their names, as mean = ... or variance = ... and many factor nodes  do not require it explicitly. However, for nodes, which have many different useful parametrizations (e.g. Normal) labeling the arguments  is a requirement that helps to avoid any possible confusion. Read more about Distributions compatibility here.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-deterministic","page":"Model specification","title":"Deterministic relationships","text":"Unlike other probabilistic programming languages in Julia, RxInfer does not allow use of the = operator for creating deterministic relationships between (latent) variables.  Instead, we can use the := operator for this purpose. For example:\n\nt ~ Normal(mean = 0.0, variance = 1.0)\nx := exp(t) # x is linked deterministically to t\ny ~ Normal(mean = x, variance = 1.0)\n\nUsing x = exp(t) directly would be incorrect and most likely would result in an MethodError because t does not have a definitive value at the model creation time  (remember that our models create a factor graph under the hood and latent states do not have a value until the inference is performed).\n\nSee Using = instead of := for deterministic nodes for a detailed explanation of this design choice.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-control-flow","page":"Model specification","title":"Control flow statements","text":"In general, it is possible to use any Julia code within model specification function, including control flow statements, such as for, while and if statements. However, it is not possible to use any latent states within such statements. This is due to the fact that it is necessary to know exactly the structure of the graph before the inference. Thus it is not possible to write statements like:\n\nc ~ Categorical([ 1/2, 1/2 ])\n# This is NOT possible in `RxInfer`'s model specification language\nif c > 1\n# ...\nend\n\nsince c must be statically known upon graph creation.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-anonymous","page":"Model specification","title":"Anonymous factor nodes and latent variables","text":"The @model macro automatically resolves any inner function calls into anonymous factor nodes and latent variables.  For example the following:\n\ny ~ Normal(\n    mean = Normal(mean = 0.0, variance = 1.0), \n    precision = Gamma(shape = 1.0, rate = 1.0)\n)\n\nis equivalent to\n\ntmp1 ~ Normal(mean = 0.0, variance = 1.0)\ntmp2 ~ Gamma(shape = 1.0, rate = 1.0)\ny    ~ Normal(mean = tmp1, precision = tmp2)\n\nThe inference backend still performs inference for anonymous latent variables; however, it does not provide an easy way to obtain posteriors for them. Note that the inference backend will try to optimize deterministic function calls in the case where all arguments are known in advance. For example:\n\ny ~ Normal(mean = 0.0, variance = inv(2.0))\n\nshould not create an extra factor node for the inv, since inv is a deterministic function and all arguments are known in advance. The same situation applies in case of complex initializations involving different types, as in:\n\ny ~ MvNormal(mean = zeros(3), covariance = Matrix(Diagonal(ones(3))))\n\nIn this case, the expression Matrix(Diagonal(ones(3))) can (and will) be precomputed upon model creation and does not require to perform probabilistic inference.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-indexing","page":"Model specification","title":"Indexing operations","text":"The ref expressions, such as x[i], are handled in a special way. Technically, in Julia, the x[i] call is translated to a function call getindex(x, i). Thus the @model macro should create a factor node for the getindex function, but this won't happen in practice because this case is treated separately. This means that the model parser will not create unnecessary nodes when only simple indexing is involved. That also means that all expressions inside x[...] list are left untouched during model parsing. \n\nwarning: Warning\nIt is not allowed to use latent variables within square brackets in the model specification or for control flow statements such as if, for or while.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-broadcasting","page":"Model specification","title":"Broadcasting syntax","text":"GraphPPL supports broadcasting for the ~ operator in the exact same way as Julia itself.  A user is free to write an expression of the following form:\n\nm  ~ Normal(mean = 0.0, precision = 0.0001)\nt  ~ Gamma(shape = 1.0, rate = 1.0)\ny .~ Normal(mean = m, precision = t)\n\nMore complex expressions are also allowed:\n\nw         ~ Wishart(3, diageye(2))\n
2x[1]      ~ MvNormal(mean = zeros(2), precision = diageye(2))\nx[2:end] .~ A .* x[1:end-1] # <- State-space model with transition matrix A\ny        .~ MvNormal(mean = x, precision = w) # <- Observations with unknown precision matrix\n\nNote, however, that shapes of all variables that take part in the broadcasting operation must be defined in advance. That means that it is not possible to  use broadcasting with deferred data. Read more about how broadcasting machinery works in Julia in the official documentation.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-distributions","page":"Model specification","title":"Distributions.jl compatibility","text":"For some factor nodes we rely on the syntax from Distributions.jl to make it easy to adopt RxInfer.jl for these users. These nodes include for example the Beta and Wishart distributions. These nodes can be created using the ~ syntax with the arguments as specified in Distributions.jl. Unfortunately, RxInfer.jl is not yet compatible with all possible distributions that can be used as factor nodes. If you feel that you would like to see another node implemented, please file an issue.\n\nnote: Note\nTo quickly check the list of all available factor nodes that can be used in the model specification language call ?ReactiveMP.is_predefined_node or Base.doc(ReactiveMP.is_predefined_node).\n\nSpecifically for the Gaussian/Normal case we have custom implementations that yield a higher computational efficiency and improved stability in comparison to Distributions.jl as these are optimized for sampling operations. Our aliases for these distributions therefore do not correspond to the implementations from Distributions.jl. However, our model specification language is compatible with syntax from Distributions.jl for normal distributions, which will be automatically converted. RxInfer has its own implementation because of the following 3 reasons:\n\nDistributions.jl constructs normal distributions by saving the corresponding covariance matrices in a PDMat object from PDMats.jl. This construction always computes the Cholesky decompositions of the covariance matrices, which is very convenient for sampling-based procedures. However, in RxInfer.jl we mostly base our computations on analytical expressions which do not always need to compute the Cholesky decomposition. In order to reduce the overhead that Distributions.jl introduces, we therefore have custom implementations.\nDepending on the update rules, we might favor different parameterizations of the normal distributions. ReactiveMP.jl has quite a variety of parameterizations that allow us to perform efficient computations where we convert between parameterizations as little as possible.\nIn certain situations we value stability a lot, especially when inverting matrices. PDMats.jl, and hence Distributions.jl, is not capable of fulfilling all the needs that we have here. Therefore we use PositiveFactorizations.jl to cope with the corner-cases.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-visualization","page":"Model specification","title":"Model structure visualisation","text":"Models specified using GraphPPL.jl in RxInfer.jl can be visualized in several ways to help understand their structure and relationships between variables. Let's create a simple model and visualize it.\n\nusing RxInfer\n\n@model function coin_toss(y)\n    t ~ Beta(1, 1)\n    for i in eachindex(y)\n        y[i] ~ Bernoulli(t)\n    end\nend\n\nmodel_generator = coin_toss() | (y = [ true, false, true ], )\nmodel_to_plot   = RxInfer.getmodel(RxInfer.create_model(model_generator))\nnothing #hide","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-visualization-graphviz","page":"Model specification","title":"GraphViz.jl","text":"It is possible to visualize the model structure after conditioning on data with the GraphViz.jl package. Note that this package is not included in the RxInfer package and must be installed separately.\n\nusing GraphViz\n\n# Call `load` function from `GraphViz` to visualise the structure of the graph\nGraphViz.load(model_to_plot, strategy = :simple)","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-visualization-cairo","page":"Model specification","title":"Cairo","text":"There is an alternative way to visualize the model structure with Cairo and GraphPlot Note, that those packages are also not included in the RxInfer package and must be installed separately.\n\nusing Cairo, GraphPlot\n\n# Call `gplot` function from `GraphPlot` to visualise the structure of the graph\nGraphPlot.gplot(model_to_plot)","category":"section"},{"location":"manuals/model-specification/#Node-Contraction","page":"Model specification","title":"Node Contraction","text":"RxInfer's model specification extension for GraphPPL supports a feature called node contraction. This feature allows you to contract (
2or replace) a submodel with a corresponding factor node. Node contraction can be useful in several scenarios:\n\nWhen running inference in a submodel is computationally expensive\nWhen a submodel contains many variables whose inference results are not of primary importance\nWhen specialized message passing update rules (see Understanding Rules) can be derived for variables in the Markov blanket of the submodel\n\nLet's illustrate this concept with a simple example. We'll first create a basic submodel and then allow the inference backend to replace it with a corresponding node that has well-defined message update rules.\n\nusing RxInfer, Plots\n\n@model function ShiftedNormal(data, mean, precision, shift)\n    shifted_mean := mean + shift\n    data ~ Normal(mean = shifted_mean, precision = precision)\nend\n\n@model function Model(data, precision, shift)\n    mean ~ Normal(mean = 15.0, var = 1.0)\n    data ~ ShiftedNormal(mean = mean, precision = precision, shift = shift)\nend\n\nresult = infer(\n    model = Model(precision = 1.0, shift = 1.0),\n    data  = (data = 10.0, )\n)\n\nplot(title = \"Inference results over `mean`\")\nplot!(0:0.1:20.0, (x) -> pdf(NormalMeanVariance(15.0, 1.0), x), label = \"prior\", fill = 0, fillalpha = 0.2)\nplot!(0:0.1:20.0, (x) -> pdf(result.posteriors[:mean], x), label = \"posterior\", fill = 0, fillalpha = 0.2)\nvline!([ 10.0 ], label = \"data point\")\n\nAs we can see, we can run inference on this model. We can also visualize the model's structure, as shown in the Model structure visualisation section.\n\nusing Cairo, GraphPlot\n\nGraphPlot.gplot(getmodel(result.model))\n\nNow, let's create an optimized version of the ShiftedNormal submodel as a standalone node with its own message passing update rules.\n\nnote: Note\nCreating correct message passing update rules is beyond the scope of this section. For more information about what rules are and how they work, see Understanding Rules. For details on implementing custom message passing update rules, refer to the Custom Node section.\n\n@node typeof(ShiftedNormal) Stochastic [ data, mean, precision, shift ]\n\n@rule typeof(ShiftedNormal)(:mean, Marginalisation) (q_data::PointMass, q_precision::PointMass, q_shift::PointMass, ) = begin \n    return @call_rule NormalMeanPrecision(:μ, Marginalisation) (q_out = PointMass(mean(q_data) - mean(q_shift)), q_τ = q_precision)\nend\n\nresult_with_contraction = infer(\n    model = Model(precision = 1.0, shift = 1.0),\n    data  = (data = 10.0, ),\n    allow_node_contraction = true\n)\nusing Test #hide\n@test result.posteriors[:mean] ≈ result_with_contraction.posteriors[:mean] #hide\n\nplot(title = \"Inference results over `mean` with node contraction\")\nplot!(0:0.1:20.0, (x) -> pdf(NormalMeanVariance(15.0, 1.0), x), label = \"prior\", fill = 0, fillalpha = 0.2)\nplot!(0:0.1:20.0, (x) -> pdf(result_with_contraction.posteriors[:mean], x), label = \"posterior\", fill = 0, fillalpha = 0.2)\nvline!([ 10.0 ], label = \"data point\")\n\nAs you can see, the inference result is identical to the previous case. However, the structure of the model is different:\n\nGraphPlot.gplot(getmodel(result_with_contraction.model))\n\nWith node contraction, we no longer have access to the variables defined inside the ShiftedNormal submodel, as it has been contracted to a single factor node. It's worth noting that this feature heavily relies on existing message passing update rules for the submodel. However, it can also be c
2ombined with another useful inference technique where no explicit message passing update rules are required.\n\nWe can also verify that node contraction indeed improves the performance of the inference:\n\nusing BenchmarkTools\n\nbenchmark_session = nothing #hide\n\nbenchmark_without_contraction = @benchmark infer(\n    model = Model(precision = 1.0, shift = 1.0),\n    data  = (data = 10.0, ),\n    session = benchmark_session #hide\n)\n\nbenchmark_with_contraction = @benchmark infer(\n    model = Model(precision = 1.0, shift = 1.0),\n    data  = (data = 10.0, ),\n    allow_node_contraction = true,\n    session = benchmark_session #hide\n)\n\nusing Test #hide\n@test benchmark_with_contraction.allocs < benchmark_without_contraction.allocs #hide\n@test mean(benchmark_with_contraction.times) < mean(benchmark_without_contraction.times) #hide\n@test median(benchmark_with_contraction.times) < median(benchmark_without_contraction.times) #hide\n@test minimum(benchmark_with_contraction.times) < minimum(benchmark_without_contraction.times) #hide\nnothing #hide\n\nLet's examine the benchmark results:\n\nbenchmark_without_contraction\n\nbenchmark_with_contraction\n\nAs we can see, the inference with node contraction runs faster due to the simplified model structure and optimized message update rules.  This performance improvement is reflected in reduced execution time and fewer memory allocations.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-node-creation-options","page":"Model specification","title":"Node creation options","text":"GraphPPL allows to pass optional arguments to the node creation constructor with the where { options...  } options specification syntax.\n\nExample:\n\ny ~ Normal(mean = y_mean, var = y_var) where { meta = ... }\n\nA list of the available options specific to the ReactiveMP inference engine is presented below.","category":"section"},{"location":"manuals/model-specification/#Metadata-option","page":"Model specification","title":"Metadata option","text":"It is possible to pass any extra metadata to a factor node with the meta option. Metadata can be later accessed in message computation rules.\n\nz ~ f(x, y) where { meta = Linearization() }\nd ~ g(a, b) where { meta = Unscented() }\n\nThis option might be useful to change message passing rules around a specific factor node. Read more about this feature in Meta Specification section.","category":"section"},{"location":"manuals/model-specification/#Dependencies-option","page":"Model specification","title":"Dependencies option","text":"A user can modify the default functional dependencies of a node with the dependencies option. Read more about the available policies in the ReactiveMP.jl documentation.\n\ny[k - 1] ~ Probit(x[k]) where {\n    # This specification indicates that in order to compute an outbound message from the `in` interface\n    # We need an inbound message from the same edge initialized to `NormalMeanPrecision(0.0, 1.0)`\n    dependencies = RequireMessageFunctionalDependencies(in = NormalMeanPrecision(0.0, 1.0))\n}","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-relation-to-graphppl","page":"Model specification","title":"Relation to GraphPPL","text":"Model creation in RxInfer largely depends on GraphPPL package. RxInfer re-exports the @model macro from GraphPPL and defines extra plugins and data structures on top of the default functionality.\n\nnote: Note\nThe model creation and construction were largely refactored in GraphPPL v4.  Read Migration Guide for more details.\n\nNote, that GraphPPL also implements @model macro, but does not export it by default. This was a deliberate choice to allow inference backends (such as RxInfer) to implement custom functionality on top of the default GraphPPL.@model macro. This is done with a custom backend for the GraphPPL.@model macro. Read more about backends in the corresponding section of GraphPPL documentation.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-pipelines","page":"Model specification","title":"Additional GraphPPL pipeline stages","text":"RxInfer implements several additional pipeline stages for default parsing stages in GraphPPL. A notable distinction of the RxInfer model specification language is the fact that RxInfer \"folds\"  some mathematical expressions and adds extra brackets to ensure the correct number of arguments for factor nodes. For example an expression x ~ x1 + x2 + x3 + x4 becomes x ~ ((x1 + x2) + x3) + x4 to ensure that the + function has exactly two arguments.","category":"section"},{"location":"manuals/model-specification/#user-guide-model-specification-internal-variable-access","page":"Model specification","title":"Getting access to internal variable data structures","text":"To get access to the internal ReactiveMP data structure of a variable in an RxInfer model, it is possible to return  a so-called label of the variable from the model macro, and access it later on as follows:\n\nusing RxInfer\nusing Test #hide\n\n@model function beta_bernoulli(y)\n    θ ~ Beta(1, 1)\n    y ~ Bernoulli(θ)\n    return θ\nend\n\nresult = infer(\n    model = beta_bernoulli(),\n    data  = (y = 0.0, )\n)\n\ngraph     = RxInfer.getmodel(result.model)\nreturnval = RxInfer.getreturnval(graph)\nθ         = returnval\nvariable  = RxInfer.getvariable(RxInfer.getvarref(graph, θ))\n@test variable isa ReactiveMP.RandomVariable #hide\nReactiveMP.israndom(variable)","category":"section"},{"location":"manuals/model-specification/#Read-also","page":"Model specification","title":"Read also","text":"Constraints specification\nMeta specification\nInference execution\nDebugging inference\n\n","category":"section"},{"location":"manuals/model-specification/#RxInfer.@model","page":"Model specification","title":"RxInfer.@model","text":"@model function model_name(model_arguments...)\n    # model description\nend\n\n@model macro generates a function that returns an equivalent graph-representation of the given probabilistic model description. See the documentation to GraphPPL.@model for more information.\n\nSupported aliases in the model specification specifically for RxInfer.jl and ReactiveMP.jl\n\na || b: alias for ReactiveMP.OR(a, b) node (operator precedence between ||, &&, -> and ! is the same as in Julia).\na && b: alias for ReactiveMP.AND(a, b) node (operator precedence ||, &&, -> and ! is the same as in Julia).\na -> b: alias for ReactiveMP.IMPLY(a, b) node (operator precedence ||, &&, -> and ! is the same as in Julia).\n¬a and !a: alias for ReactiveMP.NOT(
2a) node (Unicode \\neg, operator precedence ||, &&, -> and ! is the same as in Julia).\n\n\n\n\n\n","category":"macro"},{"location":"manuals/model-specification/#RxInfer.condition_on","page":"Model specification","title":"RxInfer.condition_on","text":"condition_on(generator::ModelGenerator; kwargs...)\n\nA function that creates a ConditionedModelGenerator object from GraphPPL.ModelGenerator. The | operator can be used as a shorthand for this function.\n\njulia> using RxInfer\n\njulia> @model function beta_bernoulli(y, a, b)\n           θ ~ Beta(a, b)\n           y .~ Bernoulli(θ)\n       end\n\njulia> conditioned_model = beta_bernoulli(a = 1.0, b = 2.0) | (y = [ 1.0, 0.0, 1.0 ], )\nbeta_bernoulli(a = 1.0, b = 2.0) conditioned on: \n  y = [1.0, 0.0, 1.0]\n\njulia> RxInfer.create_model(conditioned_model) isa RxInfer.ProbabilisticModel\ntrue\n\n\n\n\n\n","category":"function"},{"location":"manuals/model-specification/#Base.:|-Tuple{GraphPPL.ModelGenerator, Any}","page":"Model specification","title":"Base.:|","text":"An alias for RxInfer.condition_on.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.ConditionedModelGenerator","page":"Model specification","title":"RxInfer.ConditionedModelGenerator","text":"ConditionedModelGenerator(generator, conditioned_on)\n\nAccepts a model generator and data to condition on.  The generator must be GraphPPL.ModelGenerator object. The conditioned_on must be a named tuple or a dictionary with keys corresponding to the names of the input arguments in the model.\n\n\n\n\n\n","category":"type"},{"location":"manuals/model-specification/#RxInfer.DeferredDataHandler","page":"Model specification","title":"RxInfer.DeferredDataHandler","text":"An object that is used to condition on unknown data. This may be necessary to create a model from a ModelGenerator object for which the data is not known at the time of model creation.\n\n\n\n\n\n","category":"type"},{"location":"manuals/model-specification/#GraphPPL.create_model-Tuple{RxInfer.ConditionedModelGenerator}","page":"Model specification","title":"GraphPPL.create_model","text":"create_model(generator::ConditionedModelGenerator)\n\nMaterializes the model specification conditioned on some data into a corresponding factor graph representation. Returns ProbabilisticModel.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.ProbabilisticModel","page":"Model specification","title":"RxInfer.ProbabilisticModel","text":"A structure that holds the factor graph representation of a probabilistic model.\n\nFields\n\nmodel::M: The underlying factor graph model.\nmetadata::Dict{Any, Any}: A dictionary for storing arbitrary metadata during inference, e.g. from callbacks.\n\n\n\n\n\n","category":"type"},{"location":"manuals/model-specification/#GraphPPL.getmodel-Tuple{ProbabilisticModel}","page":"Model specification","title":"GraphPPL.getmodel","text":"Returns the underlying factor graph model.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getreturnval-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getreturnval","text":"Returns the value from the return ... operator inside the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getvardict-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getvardict","text":"Returns the (nested) dictionary of random variables from the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getrandomvars-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getrandomvars","text":"Returns the random variables from the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getdatavars-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getdatavars","text":"Returns the data variables from the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getconstantvars-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getconstantvars","text":"Returns the constant variables from the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.getfactornodes-Tuple{ProbabilisticModel}","page":"Model specification","title":"RxInfer.getfactornodes","text":"Returns the factor nodes from the model specification.\n\n\n\n\n\n","category":"method"},{"location":"manuals/model-specification/#RxInfer.ReactiveMPGraphPPLBackend","page":"Model specification","title":"RxInfer.ReactiveMPGraphPPLBackend","text":"A backend for GraphPPL that uses ReactiveMP for inference.\n\n\n\n\n\n","category":"type"},{"location":"manuals/model-specification/#RxInfer.error_datavar_constvar_randomvar","page":"Model specification","title":"RxInfer.error_datavar_constvar_randomvar","text":"error_datavar_constvar_randomvar(expr::Expr)\n\nAn additional pipeline stage for the @model macro from GraphPPL. Notifies the user that the datavar, constvar and randomvar syntax has been removed and is no longer supported in the current version.\n\n\n\n\n\n","category":"function"},{"location":"manuals/model-specification/#RxInfer.compose_simple_operators_with_brackets","page":"Model specification","title":"RxInfer.compose_simple_operators_with_brackets","text":"compose_simple_operators_with_brackets(expr::Expr)\n\nAn additional pipeline stage for the @model macro from GraphPPL.  This pipeline converts simple multi-argument operators to their corresponding bracketed expression.  E.g. the expression x ~ x1 + x2 + x3 + x4 becomes x ~ ((x1 + x2) + x3) + x4. The operators to compose are + and *.\n\n\n\n\n\n","category":"function"},{"location":"manuals/model-specification/#RxInfer.inject_tilderhs_aliases","page":"Model specification","title":"RxInfer.inject_tilderhs_aliases","text":"inject_tilderhs_aliases(e::Expr)\n\nA pipeline stage for the @model macro from GraphPPL. This pipeline applies the aliases defined in ReactiveMPNodeAliases to the expression.\n\n\n\n\n\n","category":"function"},{"location":"manuals/model-specification/#RxInfer.ReactiveMPNodeAliases","page":"Model specification","title":"RxInfer.ReactiveMPNodeAliases","text":"Syntactic sugar for ReactiveMP nodes. Replaces a || b with ReactiveMP.OR(a, b), a && b with ReactiveMP.AND(a, b), a ->
2 b with ReactiveMP.IMPLY(a, b) and ¬a with ReactiveMP.NOT(a).\n\n\n\n\n\n","category":"constant"},{"location":"examples/overview/#examples-overview","page":"Examples","title":"Examples","text":"All Julia examples for the RxInfer package are available at https://examples.rxinfer.com/. For Python examples, please visit the RxInferClient.py repository and its documentation.\n\nWe welcome community contributions! If you have an interesting example or application of RxInfer, please consider sharing it with the community. You can contribute:\n\nJulia examples through our examples repository\nPython examples through the RxInferClient.py repository\n\n","category":"section"},{"location":"contributing/exported-methods/#lib-using-methods","page":"Exported methods","title":"Using methods from RxInfer","text":"In the Julia programming language (in contrast to Python for example) the most common way of loading a module is:\n\nusing RxInfer\n\nA nice explanation about how modules/packages work in Julia can be found in the official documentation.\n\nIn a nutshell, Julia automatically resolves all name collisions and there is not a lot of benefit to importing specific names, e.g.:\n\nimport RxInfer: mean\n\nOne of the reasons for that is that Julia uses multiple-dispatch capabilities to merge names automatically and will indicate (with a warning) if something went wrong or names have unresolvable collisions on types. As a small example of this feature consider the following small import example:\n\nimport RxInfer: mean as mean_from_rxinfer\nimport Distributions: mean as mean_from_distributions\n\nmean_from_rxinfer === mean_from_distributions\n\nEven though we import the mean function from two different packages, they actually refer to the same object. It is worth noting that this is not always the case - Julia will print a warning in case it finds unresolvable conflicts and usage of such functions will be disallowed unless the user imports from them specifically. Read more about this in the section of the Julia's documentation.\n\n# It is easier to let Julia resolve names automatically\n# Julia will not overwrite `mean` that is coming from both packages\nusing RxInfer, Distributions \n\nmean(Normal(0.0, 1.0)) # `Normal` is an object from `Distributions.jl`\n\nmean(NormalMeanVariance(0.0, 1.0)) # `NormalMeanVariance` is an object from `RxInfer.jl`","category":"section"},{"location":"contributing/exported-methods/#lib-list-methods","page":"Exported methods","title":"List of available methods","text":"Below you can find a list of exported methods from RxInfer.jl. All methods (even private) can be always accessed with RxInfer. prefix, e.g RxInfer.mean.\n\nnote: Note\nSome exported names are (for legacy reasons) intended for private usage only. As a result some of these methods do not have a proper associated documentation with them. We constantly improve RxInfer.jl library and continue to add better documentation for many exported methods, but a small portion of these methods could be removed from this list in the future.\n\nusing RxInfer #hide\nforeach(println, names(RxInfer))\n\n","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference","page":"Variational Inference","title":"Variational Inference","text":"Variational inference (VI) is the approximation strategy that makes RxInfer scale. Exact Bayesian inference requires evaluating an integral that is almost always intractable; VI sidesteps the integral entirely by turning inference into an optimisation problem.\n\nThis page explains the idea at a high level. For the rigorous derivation — including the exact objective RxInfer minimises on a factor graph — see the Bethe Free Energy manual.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-idea","page":"Variational Inference","title":"The core idea","text":"Instead of computing the true posterior p(x mid haty) directly, pick a tractable family of distributions mathcalQ — the variational family — and search inside it for the member q^ast(x) closest to the true posterior:\n\nq^ast(x) = argmin_q in mathcalQ mathrmKLleftq(x)Vertp(x mid haty)right\
2n\nThe KL divergence measures how \"far\" one distribution is from another. Minimising it gives you the best in-family approximation of the posterior. Three things are worth noting:\n\nIf mathcalQ is rich enough to contain the true posterior, the optimum is exact.\nIf mathcalQ is too restrictive, you trade accuracy for tractability — a deliberate, controllable trade-off.\nThe KL above depends on the intractable normaliser of the posterior, so it is never minimised directly.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-free-energy","page":"Variational Inference","title":"Free energy: the tractable objective","text":"A little algebra rewrites the KL divergence into an equivalent objective that is computable — the Variational Free Energy (VFE):\n\nFq(haty) = mathbbE_q(x)leftlog fracq(x)p(x haty)right\n\nMinimising F is identical to minimising the KL, up to an additive constant equal to -log p(haty) — the log-evidence. So as a side-effect of variational inference, -F at the optimum gives you an approximation of the log model evidence, which is useful for model comparison and convergence diagnostics.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-bethe","page":"Variational Inference","title":"The Bethe approximation","text":"Minimising the VFE over arbitrary joint q(x) is itself intractable on a general model. RxInfer's central trick is to use the Bethe approximation, which factorises q according to the structure of the factor graph itself:\n\nq(x) triangleq fracprod_a q_a(x_a)prod_i q_i(x_i)^d_i - 1\n\nwhere q_a are factor-local beliefs, q_i are variable-local beliefs, and d_i is the degree of variable i. Substituting this into the VFE yields the Bethe Free Energy — an objective that decomposes over the graph and can therefore be minimised by local message passing updates.\n\nThis is the deep connection between variational inference and message passing: on a tree, minimising the Bethe Free Energy is exactly belief propagation; on a loopy graph, it is loopy BP; with extra factorisation constraints on q, it is variational message passing. RxInfer implements the unified view.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-family","page":"Variational Inference","title":"Choosing the variational family","text":"The variational family mathcalQ is under your control, through constraints specifications. Two common choices:\n\nMean-field — all latent variables are independent in q: q(mu tau) = q(mu) q(tau). Cheapest, but ignores posterior correlations.\nStructured — preserves dependencies that matter (e.g. q(mu tau) = q(mu mid tau) q(tau)). More faithful, more compute.\nNo extra constraints — fall back to plain belief propagation; exact on trees, approximate on loops.\n\nYou switch between these with a few lines of @constraints syntax — see the Constraints Specification page for how.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-results","page":"Variational Inference","title":"What you get back","text":"After the iterations converge, RxInfer returns, for each latent variable x_i:\n\nA posterior marginal q_i(x_i) — in a known distribution family (Gaussian, Gamma, Beta, ...).\nOptionally, the Bethe Free Energy trajectory per iteration, which you can monitor to diagnose convergence (see Convergence and Bethe Free Energy).\n\nresult = infer(\n    model          = my_model(),\n    data           = (y = observations,),\n    constraints    = my_constraints,\n    initialization = my_init,\n    iterations     = 20,\n    free_energy    = true,\n)\n\nposterior_μ = result.posteriors[:μ][end]\nbfe         = result.free_energy\n\nThe whole loop — factorised model, variational family, free energy objective, convergence — is covered end-to-end in Static Inference.","category":"section"},{"location":"concepts/variational-inference/#concepts-variational-inference-deeper","page":"Variational Inference","title":"For deeper understanding","text":"Bethe Free Energy in RxInfer — the full derivation of the objective RxInfer minimises.\nConstraints Specification — how to pick the variational family in practice.\nFunctional Form Constraints — shaping the distribution families of individual marginals.\nVariational Message Passing and Local Constraint Manipulation in Factor Graphs — Şenöz et al.\nVariational Inference: A Review for Statisticians — Blei, Kucukelbir and McAuliffe — an accessible introduction to VI.\nThe Bethe Free Energy Allows to Compute the Conditional Entropy of Graphical Code Instances — Vontobel's classical paper on the Bethe approximation.\n\n","category":"section"},{"location":"contributing/examples/#examples","page":"Contribute with examples","title":"Contribute with examples","text":"The examples for RxInfer.jl are now maintained in a dedicated repository at RxInferEx
2amples.jl. Please visit the examples repository to:\n\nBrowse existing examples\nLearn how to contribute your own examples\nGet started with RxInfer.jl","category":"section"},{"location":"contributing/examples/#Featured-Examples","page":"Contribute with examples","title":"Featured Examples","text":"Active Inference with RxInfer.jl - Dive into the realm of Active Inference guided by Kobus Esterhuysen at Learnable Loop.\nTutorial Series on RxInfer.jl - Explore a series of engaging tutorial videos on RxInfer.jl's functionalities, presented by @doggotodjl.\nPython Examples - Check out Python examples in the RxInferClient.py repository and its documentation.\n\nnote: Note\nIf you're interested in contributing an example, please visit the RxInferExamples.jl repository for Julia examples or the RxInferClient.py repository for Python examples. We welcome all contributions that help demonstrate the capabilities of RxInfer!\n\n","category":"section"},{"location":"manuals/telemetry/#manual-usage-telemetry","page":"Sharing sessions & telemetry","title":"RxInfer Usage Telemetry","text":"RxInfer has two separate telemetry features:\n\nA minimal package usage counter (enabled by default)\nOptional session sharing for better support and development insights\n\nnote: Note\nThe telemetry implementation in RxInfer has been designed with careful consideration of the community discussion about telemetry in Julia packages, particularly the discourse thread \"Pkg.jl telemetry should be opt-in\". We've aimed to strike a balance between gathering useful information for package development while respecting user privacy and control.\n\nThe following table compares the key properties of RxInfer's two telemetry features.\n\nProperty Package Usage Counter Session Sharing\nEntirely Anonymous Yes Yes\nShared Across Julia Sessions No No\nEnabled by default Yes (opt-out) No (opt-in)\nDeletion Request Support No (UUIDs not persistent, we cannot backtrace the session to a specific user) Yes* (only if users save their session ID and share it with us anonymously with the form below, otherwise we cannot backtrace the session to a specific user)\nCan Be Disabled for a Specific Julia Session Yes Yes (disabled by default)\nCan Be Disabled for All Julia Sessions Yes Yes (disabled by default)\nCan Be Disabled with Environment Variable Yes Yes (disabled by default)\nCustomizable Behavior Yes (with Preferences.jl, see Package Usage Counter manual) Yes (with Preferences.jl, see Session Sharing manual)\nCan Be Enabled for a Specific Julia Session Yes Yes\nCan Be Enabled for All Julia Sessions Yes Yes\nWhat Is Being Recorded Only timestamp and random UUID Session metadata, errors, and by default the model source code (constraints/meta); no observed data. See Source Code Sharing\nReal-Time Sharing of Recorded Data Yes (on package load) Optional (manual/automatic)\nLocal Access to Recorded Data N/A (No data is collected) Yes (via session inspection, see Session Summary manual)\nEnables Extra Support from Core Developers No Yes* (if users are willing to share their session ID when opening a GitHub issue or a discussion on the GitHub repository, otherwise we cannot backtrace the session to a specific user)\nPerformance Impact Negligible (only on package load) Minimal (async sharing)\nCI Environment Behavior Automatically disabled Automatically disabled\n\nUsers are welcome to join our regular online meetings where we analyze the collected data and discuss how it helps shape RxInfer's development.","category":"section"},{"location":"manuals/telemetry/#manual-package-usage-counter","page":"Sharing sessions & telemetry","title":"Package Usage Counter","text":"By default, RxInfer counts how many times the package is loaded via using RxInfer. This counter:\n\nOnly records a timestamp, a random UUID for deduplication, the RxInfer.jl version and the Julia version\nUUIDs are not persistent and are re-generated for each session\nDoes not collect any code or data\nIs completely anonymous\nHelps us understand how widely RxInfer is used","category":"section"},{"location":"manuals/telemetry/#manual-disabling-package-usage-counter","page":"Sharing sessions & telemetry","title":"Disabling Package Usage Counter","text":"You can disable the counter in several ways:\n\nUsing Julia functions:\nusing RxInfer\nRxInfer.disable_rxinfer_using_telemetry!() # Requires Julia restart\nUsing environment variables:\nexport LOG_USING_RXINFER=false\n\nThe counter is also automatically disabled in:\n\nCI environments (detected via CI=true environment variable)\nWhen telemetry is disabled via disable_rxinfer_using_telemetry!()\nWhen the telemetry endpoint is set to nothing","category":"section"},{"location":"manuals/telemetry/#manual-session-sharing","page":"Sharing sessions & telemetry","title":"Session Sharing","text":"RxInfer includes a built-in session tracking feature (detailed in Session Summary) that helps you monitor and debug your inference tasks. You can choose to share these sessions with core developers to:\n\nGet better support when encountering issues\nHelp improve RxInfer through real-world usage insights\nContribute to community-driven development\n\nRead more about what data is present in the session history in the Session Summary manual.","category":"section"},{"location":"manuals/telemetry/#How-to-Share-Sessions","page":"Sharing sessions & telemetry","title":"How to Share Sessions","text":"You can share your session data either manually or automatically.","category":"section"},{"location":"manuals/telemetry/#Manual-Sharing","page":"Sharing sessions & telemetry","title":"Manual Sharing","text":"Use the share_session_data function to manually share your session:","category":"section"},{"location":"manuals/telemetry/#Automatic-Sharing","page":"Sharing sessions & telemetry","title":"Automatic Sharing","text":"You can enable automatic session sharing after each session update:\n\nWhen automatic sharing is enabled:\n\nSession data is shared after each session update\nSharing is done asynchronously (won't block your code)\nNo progress bars or messages are shown\nFailed sharing attempts are silently ignored","category":"section"},{"location":"manuals/telemetry/#manual-source-code-sharing","page":"Sharing sessions & telemetry","title":"Source Code Sharing","text":"When you share a session, the payload for each labeled run includes the context that RxInfer captured for that infer call. By default this includes the model source code (obtained via GraphPPL.getsource), together with the constraints and meta source blocks. This is intentional — the model source is usually the single most useful piece of context when we help you debug an issue you have shared.\n\nIf your model source is proprietary or otherwise sensitive, you can prevent it from being transmitted. When source-code sharing is disabled, the model, constraints, and meta fields are replaced with a \"<redacted>\" marker in the shared payload, while everything else (timing, status, data shape, number of iterations, etc.) is still shared. This only affects what leaves your machine — your local session always keeps the full context for your own inspection (see the Session Summary manual).\n\nYou control this in two ways.\n\nGlobally, as a compile-time preference (default is to share):\n\nusing RxInfer\nRxInfer.disable_source_code_sharing!()  # do not share source code (requires Julia restart)\nRxInfer.enable_source_code_sharing!()   # share source code again (default)\n\nOr per call, via the share_source_code keyword of share_session_data, which overrides the preference for that call:\n\n# Never share source code for this particular call, regardless of the preference\nRxInfer.share_session_data(; share_source_code = false)\n\nshare_source_code = nothing (the default) follows the compile-time preference.","category":"section"},{"location":"manuals/telemetry/#Using-Session-IDs-in-Issues","page":"Sharing sessions & telemetry","title":"Using Session IDs in Issues","text":"When you share a session and then open a GitHub issue, include your session ID. This helps us:\n\nLink your issue to the shared session data\nUnderstand your usage context\nProvide more accurate and helpful support","category":"section"},{"location":"manuals/telemetry/#Deleting-Shared-Data","page":"Sharing sessions & telemetry","title":"Deleting Shared Data","text":"If you wish to delete previously shared session data, you can contact the core developers through GitHub issues at RxInfer.jl or anonymously with the following form.\n\nWhen requesting deleti
2on, you must provide the session UUID. Without this identifier, we cannot trace specific sessions back to individual users. See the Session Summary manual for details on how to obtain your session ID.","category":"section"},{"location":"manuals/telemetry/#Privacy-and-Control","page":"Sharing sessions & telemetry","title":"Privacy and Control","text":"Remember:\n\nSession sharing is completely optional\nAll statistics are anonymous, UUIDs are not persistent and are re-generated for each session\nNo observed data is shared, only meta information about it (e.g., type of data, number of observations)\nThe model source code (and constraints/meta) is shared by default; you can opt out globally or per call — see Source Code Sharing\nYou can inspect the sharing code in src/telemetry.jl\nWe only use this data to help improve RxInfer and provide better support\n\nWe appreciate your help in making RxInfer better! Whether you choose to enable telemetry or share sessions, your contribution helps us improve the package for everyone.","category":"section"},{"location":"manuals/telemetry/#Developers-Reference","page":"Sharing sessions & telemetry","title":"Developers Reference","text":"","category":"section"},{"location":"manuals/telemetry/#RxInfer.log_using_rxinfer","page":"Sharing sessions & telemetry","title":"RxInfer.log_using_rxinfer","text":"log_using_rxinfer()\n\nSend an anonymous usage statistics event to the telemetry endpoint on using RxInfer. This function makes an asynchronous HTTP POST request to the configured endpoint. See RxInfer.set_telemetry_endpoint! to configure the endpoint. If the telemetry endpoint is set to nothing, this function does nothing. The call sends a timestamp and a random UUID (re-generated for each usage), the RxInfer.jl version and the Julia version. The request is made asynchronously to avoid blocking the user's workflow. See RxInfer.disable_rxinfer_using_telemetry! to disable telemetry on using RxInfer. Alternatively, set the environment variable LOG_USING_RXINFER to false to disable logging.\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.disable_rxinfer_using_telemetry!","page":"Sharing sessions & telemetry","title":"RxInfer.disable_rxinfer_using_telemetry!","text":"disable_rxinfer_using_telemetry!()\n\nDisable telemetry collection on using RxInfer at compile time. The change requires a Julia session restart to take effect.\n\nSee also: set_telemetry_endpoint!, enable_rxinfer_using_telemetry!\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.enable_rxinfer_using_telemetry!","page":"Sharing sessions & telemetry","title":"RxInfer.enable_rxinfer_using_telemetry!","text":"enable_rxinfer_using_telemetry!()\n\nEnable telemetry collection on using RxInfer at compile time. The change requires a Julia session restart to take effect.\n\nSee also: set_telemetry_endpoint!, disable_rxinfer_using_telemetry!\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.set_telemetry_endpoint!","page":"Sharing sessions & telemetry","title":"RxInfer.set_telemetry_endpoint!","text":"set_telemetry_endpoint!(endpoint)\n\nSet the telemetry endpoint URL for RxInfer.jl at compile time. This endpoint is used for collecting anonymous usage statistics to help improve the package.\n\nThe change requires a Julia session restart to take effect.\n\nArguments\n\nendpoint: The URL of the telemetry endpoint as a String or nothing\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.share_session_data","page":"Sharing sessions & telemetry","title":"RxInfer.share_session_data","text":"share_session_data(session = RxInfer.default_session(); show_progress::Bool = true, share_source_code = nothing)\n\nShare your session data to help improve RxInfer.jl and its community. This data helps us:\n\nUnderstand how the package is used in practice\nIdentify areas for improvement\nMake informed decisions about future development\nShare aggregate usage patterns in our community meetings\n\nThe data is organized in a structured way:\n\nBasic session info (Julia version, OS, etc.)\nAnonymous statistics about different types of package usage\nInformation about individual labeled runs\n\nAll data is anonymous and only used to improve the package. We discuss aggregate statistics in our public community meetings to make the development process transparent and collaborative.\n\nnote: Note\nBy default the shared data includes the model source code 
2captured in each run's context (together with the constraints/meta source blocks). If your model source is proprietary, disable this either globally via disable_source_code_sharing! or per call via the share_source_code keyword below. The local session always keeps the full context regardless of this setting.\n\nArguments\n\nsession::Session: The session object containing data to share\nshow_progress::Bool = true: Whether to display progress bars during sharing\nshare_source_code::Union{Bool, Nothing} = nothing: Whether to include the model source code (and constraints/meta) in the shared payload. nothing (default) follows the preference_share_source_code compile-time preference (itself true by default); true/false overrides it for this call. When source code is not shared, those fields are replaced with a \"<redacted>\" marker.\n\nProgress Display\n\nWhen show_progress is true (default), the function displays:\n\nA blue progress bar for sharing session statistics\nA green progress bar for sharing labeled runs\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.enable_automatic_session_sharing!","page":"Sharing sessions & telemetry","title":"RxInfer.enable_automatic_session_sharing!","text":"enable_automatic_session_sharing!()\n\nEnable automatic session sharing after each inference call. The change requires a Julia session restart to take effect. When enabled, session data will be automatically shared after each successful inference call. This helps improve RxInfer by providing usage patterns and helps with debugging issues.\n\nSee also: disable_automatic_session_sharing!, share_session_data\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.disable_automatic_session_sharing!","page":"Sharing sessions & telemetry","title":"RxInfer.disable_automatic_session_sharing!","text":"disable_automatic_session_sharing!()\n\nDisable automatic session sharing after inference calls. The change requires a Julia session restart to take effect.\n\nSee also: enable_automatic_session_sharing!, share_session_data\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.enable_source_code_sharing!","page":"Sharing sessions & telemetry","title":"RxInfer.enable_source_code_sharing!","text":"enable_source_code_sharing!()\n\nEnable sharing of the model source code (and the constraints/meta source blocks) when session data is shared, at compile time. This is the default behaviour. The change requires a Julia session restart to take effect.\n\nSource code is only ever transmitted when you opt in to session sharing (manually via share_session_data or automatically via enable_automatic_session_sharing!); this preference controls whether that shared payload includes the source code. You can also override it per call with the share_source_code keyword of share_session_data.\n\nSee also: disable_source_code_sharing!, share_session_data\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.disable_source_code_sharing!","page":"Sharing sessions & telemetry","title":"RxInfer.disable_source_code_sharing!","text":"disable_source_code_sharing!()\n\nDisable sharing of the model source code (and the constraints/meta source blocks) when session data is shared, at compile time. When disabled, these fields are replaced with a \"<redacted>\" marker in the shared payload while all other metadata (timing, status, data shape, etc.) is still shared. The local session is unaffected and keeps the full context for your own inspection. The change requires a Julia session restart to take effect.\n\nYou can also override this per call with the share_source_code keyword of share_session_data.\n\nSee also: enable_source_code_sharing!, share_session_data\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.preference_share_source_code","page":"Sharing sessions & telemetry","title":"RxInfer.preference_share_source_code","text":"preference_share_source_code\n\nCompile-time preference controlling whether the model source code (and the constraints/meta source blocks) captured in the session invoke context is included when session data is shared. Defaults to true (source code is shared).\n\nThis only affects what leaves your machine during session sharing — the local session always keeps the full context for your own inspection. Flip it with enable_source_code_sharing! / disable_source_code_sharing!, or override it per call via the share_source_code keyword of share_session_data.\n\n\n\n\n\n","category":"constant"},{"location":"manuals/telemetry/#RxInfer.to_firestore_invoke","page":"Sharing sessions & telemetry","title":"RxInfer.to_firestore_invoke","text":"to_firestore_invoke(invoke::SessionInvoke, stats_id::UUID; share_source_code::Bool = true)\n\nConvert a SessionInvoke object to a Firestore-compatible document format. Includes a reference to the parent session stats.\n\nWhen share_source_code is false, the source-code fields of the invoke context (:model, :constraints, :meta) are replaced with a \"<redacted>\" marker before conversion, so they are not included in the shared document. All other context entr
2ies (timing, status, data shape, etc.) are preserved. The invoke object itself is left untouched.\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.to_firestore_value","page":"Sharing sessions & telemetry","title":"RxInfer.to_firestore_value","text":"to_firestore_value(value)\n\nConvert a Julia value to a Firestore-compatible value format. Returns a NamedTuple with the appropriate Firestore field type.\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.to_firestore_session","page":"Sharing sessions & telemetry","title":"RxInfer.to_firestore_session","text":"to_firestore_session(session::Session)\n\nConvert a Session object to a Firestore-compatible document format.\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.to_firestore_document","page":"Sharing sessions & telemetry","title":"RxInfer.to_firestore_document","text":"to_firestore_document(data::NamedTuple)\n\nConvert a Julia NamedTuple to a Firestore document format. Returns a NamedTuple with fields in Firestore format.\n\n\n\n\n\n","category":"function"},{"location":"manuals/telemetry/#RxInfer.to_firestore_session_stats","page":"Sharing sessions & telemetry","title":"RxInfer.to_firestore_session_stats","text":"to_firestore_session_stats(stats::SessionStats, session_id::UUID)\n\nConvert a SessionStats object to a Firestore-compatible document format. Includes a reference to the parent session.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#manual-session-summary","page":"Session summary","title":"Session Summary","text":"RxInfer provides a built-in session logging system that helps track and analyze various aspects of RxInfer usages. This feature is particularly useful for debugging, performance monitoring, and understanding the behavior of your inference models.\n\nnote: Note\nSessions are not shared and are intended for your personal use. However, you can opt-in to share your session data with the RxInfer team. See RxInfer Usage Telemetry for more details.","category":"section"},{"location":"manuals/session_summary/#Overview","page":"Session summary","title":"Overview","text":"Session logging in RxInfer automatically captures and maintains statistics for:\n\nModel source code and metadata\nInput data characteristics\nExecution timing and success rates\nError information (if any)\nEnvironment information (Julia version, OS, etc.)\nContext keys used across invocations","category":"section"},{"location":"manuals/session_summary/#Basic-Usage","page":"Session summary","title":"Basic Usage","text":"By default, RxInfer creates and maintains a global session that logs all inference invocations:\n\nusing RxInfer\n\n# Define a simple model\n@model function simple_model(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    y ~ Normal(mean = x, var = 1.0)\nend\n\n# Run inference with default session logging\nresult = infer(\n    model = simple_model(),\n    data = (y = 1.0,)\n)\n\n# Access the current session\nsession = RxInfer.default_session()\n\n# Get statistics for inference invocations\nstats = RxInfer.get_session_stats(session, :inference)\nprintln(\"Number of invokes: $(stats.total_invokes)\")\nprintln(\"Success rate: $(round(stats.success_rate * 100, digits=1))%\")\n\nnote: Note\nThe number of logged invocations may be different from the number of invocations in the example above since the session is created and logged at the start of the documentation build.","category":"section"},{"location":"manuals/session_summary/#Session-Capacity","page":"Session summary","title":"Session Capacity","text":"By default, RxInfer maintains a fixed-size history of the invocations.  When this limit is exceeded, the oldest invocations are automatically dropped. This prevents  memory growth while maintaining recent history. \n\nThis is particularly useful when:\n\nRunning benchmarks that might generate many invocations\nWorking with long-running applications\nManaging memory usage in resource-constrained environments\n\nnote: Note\nChanging the session stats capacity requires a Julia session restart to take effect. The change is persistent across Julia sessions until explicitly changed again.","category":"section"},{"location":"manuals/session_summary/#Custom-sessions","page":"Session summary","title":"Custom sessions","text":"You can also pass custom sessions to the infer function:\n\nusing RxInfer\n\n@model function simple_model(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    y ~ Normal(mean = x, var = 1.0)\nend\n\n# Create a custom session\nsession = RxInfer.create_session()\n\n# Ru
2n inference with custom session\nresult = infer(\n    model = simple_model(),\n    data = (y = 1.0,),\n    session = session\n)\n\nprintln(\"Session ID: $(session.id)\")\nprintln(\"Created at: $(session.created_at)\")\n\nor pass nothing to disable session logging:\n\nresult = infer(\n    model = simple_model(),\n    data = (y = 1.0,),\n    session = nothing # skips session logging for this invocation\n)\n\nSee Configuration for more details on how to manage sessions.","category":"section"},{"location":"manuals/session_summary/#Session-Reset","page":"Session summary","title":"Session Reset","text":"You can reset the session to its initial state with RxInfer.reset_session! function:","category":"section"},{"location":"manuals/session_summary/#Session-Statistics","page":"Session summary","title":"Session Statistics","text":"RxInfer maintains detailed statistics for each label in a session. Currently, only the :inference label is actively used, which collects information about inference invocations.","category":"section"},{"location":"manuals/session_summary/#What's-being-collected","page":"Session summary","title":"What's being collected","text":"For the :inference label, each invocation records:\n\nBasic Information:\nUnique identifier (UUID)\nStatus (:success or :error)\nExecution start and end timestamps\nModel Information:\nModel source code (via GraphPPL.getsource)\nModel name (via GraphPPL.getmodel)\nModel constraints (if specified)\nModel meta information (if specified)\nModel autoupdates specification (if specified)\nModel initialization specification (if specified)\nData Information:\nInput data characteristics (names, types, sizes) - no actual data is collected\nDatastream type (for streaming inference)\nReturn variables structure\nPredict variables structure (for batch inference)\nHistory variables structure (for streaming inference)\nHistory buffer size (if specified)\nInference Parameters:\nNumber of iterations\nFree energy computation flag\nNode contraction settings\nProgress display settings\nException catching settings\nAdditional Settings:\nCallbacks configuration\nAnnotations configuration\nOptions configuration\n\nnote: Note\nNo actual data is collected for the :inference label. Only metadata such as size and type is recorded.","category":"section"},{"location":"manuals/session_summary/#An-example-of-a-last-infer-call-in-the-session","page":"Session summary","title":"An example of a last infer call in the session","text":"The documentation build for RxInfer executes real code and maintains its own session. Let's look at an example of a last infer call in the session:\n\nusing RxInfer\n\nsession = RxInfer.default_session()\n\nstats = RxInfer.get_session_stats(session, :inference)\n\nlast_invoke = stats.invokes[end]\n\nlast_invoke.context","category":"section"},{"location":"manuals/session_summary/#Aggregated-statistics","page":"Session summary","title":"Aggregated statistics","text":"These individual invocations are then aggregated into real-time statistics:\n\nTotal number of invocations and success/failure counts\nSuccess rate (fraction of successful invokes)\nExecution timing statistics (min, max, total duration)\nSet of all context keys used across invocations\nFixed-size history of recent invocations (controlled by DEFAULT_SESSION_STATS_CAPACITY)\n\nYou can access these statistics using get_session_stats:\n\nusing RxInfer\n\nsession = RxInfer.default_session()\nstats = RxInfer.get_session_stats(session, :inference)\n\nprintln(\"Total invokes: $(stats.total_invokes)\")\nprintln(\"Success rate: $(round(stats.success_rate * 100, digits=1))%\")\nprintln(\"Failed invokes: $(stats.failed_count)\")\nprintln(\"Mean duration (ms): $(stats.total_invokes > 0 ? round(stats.total_duration_ms / stats.total_invokes, digits=2) : 0.0)\")","category":"section"},{"location":"manuals/session_summary/#Session-Structure","page":"Session summary","title":"Session Structure","text":"A session consists of the following components:","category":"section"},{"location":"manuals/session_summary/#Session-Fields","page":"Session summary","title":"Session Fields","text":"id::UUID: Unique identifier for the session\ncreated_at::DateTime: Session creation timestamp\nenvironment::Dict{Symbol, Any}: System and environment information\nstats::Dict{Symbol, SessionStats}: Statistics per label\nsemaphore::Base.Semaphore: Thread-safe semaphore for concurrent updates","category":"section"},{"location":"manuals/session_summary/#Environment-Information","page":"Session summary","title":"Environment Information","text":"The session automatically captures system information including:\n\nJulia version\nRxInfer version\nOperating system\nMachine architecture\nCPU threads\nSystem word size","category":"section"},{"location":"manuals/session_summary/#Statistics-Information","page":"Session summary","title":"Statistics Information","text":"Each label's statistics (SessionStats) captures:\n\nid::UUID: Unique identifier for these statistics\nlabel::Symbol: The label these statistics are for\ntotal_invokes::Int: Total number of invokes\nsuccess_count::Int: Number of successful invokes\nfailed_count::Int: Number of failed invokes\nsuccess_rate::Float64: Fraction of successful invokes\nmin_duration_ms::Float64: Minimum execution duration\nmax_duration_ms::Float64: Maximum execution duration\ntotal_duration_ms::Float64: Total execution duration\ncontext_keys::Set{Symbol}: Set of all context keys used\ninvokes::CircularBuffer{SessionInvoke}: Recent invocations history","category":"section"},{"location":"manuals/session_summary/#Accessing-Session-Data","page":"Session summary","title":"Accessing Session Data","text":"You can inspect session data to analyze inference behavior:\n\nusing RxInfer\n\n@model function simple_model(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    y ~ Normal(mean = x, var = 1.0)\nend\n\nsession = RxInfer.create_session()\nresult = infer(model = simple_model(), data = (y = 1.0,), session = session)\n\n# Get inference statistics\nstats = RxInfer.get_session_stats(session, :inference)\n\n# Get the latest invoke\nlatest_invoke = stats.invokes[end]\n\n# Check invocation status\nprintln(\"Status: $(latest_invoke.status)\")\n\n# Calculate execution duration\nduration = latest_invoke.execution_end - latest_invoke.execution_start\nprintln(\"Duration: $duration\")\n\n# Access model source\nprintln(\"Model name: $(latest_invoke.context[:model_name])\")\nprintln(\"Model source: $(latest_invoke.context[:model])\")\n\n# Examine data properties\nfor entry in latest_invoke.context[:data]\n    println(\"\\nData variable: $(entry.name)\")\n    println(\"Type: $(entry.type)\")\n    println(\"Size: $(entry.size)\")\nend","category":"section"},{"location":"manuals/session_summary/#session-configuration","page":"Session summary","title":"Configuration","text":"","category":"section"},{"location":"manuals/session_summary/#Default-Session","page":"Session summary","title":"Default Session","text":"The default session is created automatically when RxInfer is first imported. It is used for logging all inference invocations by default.","category":"section"},{"location":"manuals/session_summary/#Enabling/Disabling-Logging","page":"Session summary","title":"Enabling/Disabling Logging","text":"Session logging can be enabled or disabled globally","category":"section"},{"location":"manuals/session_summary/#Managing-Sessions","page":"Session summary","title":"Managing Sessions","text":"","category":"section"},{"location":"manuals/session_summary/#Best-Practices","page":"Session summary","title":"Best Practices","text":"Error Handling: Session logging automatically captures errors, making it easier to debug issues:\n\nusing RxInfer\n\n@model function problematic_model(y)\n    x ~ Normal(mean = 0.0, var = sqrt(-1.0)) # Invalid variance\n    y ~ Normal(mean = x, var = 1.0)\nend\n\ntry\n    result = infer(model = problematic_model(), data = (y = 1.0,))\ncatch e\n    # Check the latest invoke for error details\n    stats = RxInfer.get_session_stats(RxInfer.default_session(), :inference)\n    latest_invoke = stats.invokes[end]\n    println(\"Status: $(latest_invoke.status)\")\n    println(\"Error: $(latest_invoke.context[:error])\")\nend\n\nPerformance Monitoring: Use session statistics to monitor inference performance:\n\nusing RxInfer, Statistics\n\n@model function simple_model(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    y ~ Normal(mean = x, var = 1.0)\nend\n\nsession = RxInfer.create_session()\n\n# Ru
2n multiple inferences\nfor i in 1:5\n    infer(model = simple_model(), data = (y = randn(),), session = session)\nend\n\nstats = RxInfer.get_session_stats(session, :inference)\nprintln(\"Mean duration (ms): $(round(stats.total_duration_ms / stats.total_invokes, digits=2))\")\nprintln(\"Success rate: $(round(stats.success_rate * 100, digits=1))%\")\n\nnote: Note\nThe first invocation is typically slower due to Julia's JIT compilation.\n\nData Validation: Session logging helps track data characteristics:\n\nusing RxInfer\n\n@model function simple_model(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    y ~ Normal(mean = x, var = 1.0)\nend\n\nsession = RxInfer.create_session()\nresult = infer(model = simple_model(), data = (y = 1.0,), session = session)\n\nstats = RxInfer.get_session_stats(session, :inference)\nlatest_invoke = stats.invokes[end]\n\n# Check data properties\nfor entry in latest_invoke.context[:data]\n    println(\"Variable: $(entry.name)\")\n    println(\"Type: $(entry.type)\")\nend","category":"section"},{"location":"manuals/session_summary/#Session-Summary","page":"Session summary","title":"Session Summary","text":"You can view a tabular summary of these statistics at any time to understand how your inference tasks are performing:\n\nnote: Note\nSession statistics below are collected during the documentation build.\n\nThe main function for viewing session statistics is summarize_session:\n\nusing RxInfer #hide\n\n# Additionaly install `PrettyTables.jl` to see the nicely formatted output\nusing PrettyTables\n\nRxInfer.summarize_session(; n_last = 25)\n\nThe summary includes:\n\nTotal number of inference invocations and success rate\nExecution time statistics (mean, min, max)\nList of context keys present\nNumber of unique models used\nTable of most recent invocations showing:\nStatus (success/failed)\nDuration in milliseconds\nModel name\nData variables used","category":"section"},{"location":"manuals/session_summary/#Programmatic-Access","page":"Session summary","title":"Programmatic Access","text":"If you need to access the statistics programmatically, use get_session_stats:\n\nusing RxInfer #hide\nsession = RxInfer.default_session()\nstats = RxInfer.get_session_stats(session, :inference)","category":"section"},{"location":"manuals/session_summary/#Benchmarking-Considerations","page":"Session summary","title":"Benchmarking Considerations","text":"When benchmarking code that involves the infer function, it's important to be aware of session logging behavior:","category":"section"},{"location":"manuals/session_summary/#Why-Disable-Session-Logging-During-Benchmarking?","page":"Session summary","title":"Why Disable Session Logging During Benchmarking?","text":"Multiple Executions: Benchmarking tools like BenchmarkTools.jl execute the code multiple times to gather accurate performance metrics. Each execution is logged as a separate invoke in the session, which can quickly fill up the session buffer.\nSession Pollution: These benchmark runs can pollute your session history with test invocations, making it harder to track and analyze your actual inference calls.\nPerformance Impact: While minimal, session logging does add some overhead to each infer call, which could affect benchmark results.","category":"section"},{"location":"manuals/session_summary/#Best-Practices-2","page":"Session summary","title":"Best Practices","text":"To get accurate benchmarking results and maintain a clean session history:\n\n# DON'T: This will fill your session with benchmark invocations\n@benchmark infer(model = my_model, data = my_data)\n\n# DO: Explicitly disable session logging during benchmarking\n@benchmark infer(model = my_model, data = my_data, session = nothing)\n\nYou can also temporarily disable session logging globally:\n\n# Disable session logging\nprevious_session = RxInfer.default_session()\nRxInfer.set_default_session!(nothing)\n# Run your benchmarks\n# ...\n# Re-enable session logging reusing the previous session\nRxInfer.set_default_session!(previous_session)\n\nor disable it explicitly:\n\nRxInfer.disable_session_logging!() # works after Julia restart","category":"section"},{"location":"manuals/session_summary/#Developers-Reference","page":"Session summary","title":"Developers Reference","text":"","category":"section"},{"location":"manuals/session_summary/#RxInfer.DEFAULT_SESSION_STATS_CAPACITY","page":"Session summary","title":"RxInfer.DEFAULT_SESSION_STATS_CAPACITY","text":"DEFAULT_SESSION_STATS_CAPACITY\n\nThe default capacity for the circular buffer storing session invocations. This value determines how many past invocations are stored for each label's statistics. Can be modified at compile time using preferences:\n\nusing RxInfer\nset_session_stats_capacity!(100)\n\nThe change requires a Julia session restart to take effect. Default value is 1000. Must be a positive integer.\n\n\n\n\n\n","category":"constant"},{"location":"manuals/session_summary/#RxInfer.set_session_stats_capacity!","page":"Session summary","title":"RxInfer.set_session_stats_capacity!","text":"set_session_stats_capacity!(capacity::Int)\n\nSet the default capacity for session statistics at compile time. The change requires a Julia session restart to take effect.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.reset_session!","page":"Session summary","title":"RxInfer.reset_session!","text":"reset_session!(session, [ labels ])\n\nRemoves gathered statistics from the session. Optionally accepts a vector of labels to delete. If no labels specified deletes everything.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.SessionStats","page":"Session summary","title":"RxInfer.SessionStats","text":"SessionStats\n\nStatistics for a specific label in a session.\n\nFields\n\nid::UUID: Unique identifier for these statistics\nlabel::Symbol: The label these statistics are for\ntotal_invokes::Int: Total number of invokes with this label\nsuccess_count::Int: Number of successful invokes\nfailed_count::Int: Number of failed invokes\nsuccess_rate::Float64: Fraction of successful invokes (between 0 and 1)\nmin_duration_ms::Float64: Minimum execution duration in milliseconds\nmax_duration_ms::Float64: Maximum execution duration in milliseconds\ntotal_duration_ms::Float64: Total execution duration for mean calculation\ncontext_keys::Set{Symbol}: Set of all context keys used across invokes\ninvokes::CircularBuffer{SessionInvoke}: A series of invokes attached to the statistics\n\n\n\n\n\n","category":"type"},{"location":"manuals/session_summary/#RxInfer.SessionInvoke","page":"Session summary","title":"RxInfer.SessionInvoke","text":"SessionInvoke\n\nRepresents a single invocation of an inference operation.\n\nFields\n\nid::UUID: Unique identifier for this invocation\nstatus::Symbol: Status of the invocation (e.g. :success, :failure)\nexecution_start::DateTime: When the invocation started\nexecution_end::DateTime: When the invocation completed\ncontext::Dict{Symbol, Any}: Additional contextual information\n\n\n\n\n\n","category":"type"},{"location":"manuals/session_summary/#RxInfer.create_invoke","page":"Session summary","title":"RxInfer.create_invoke","text":"create_invoke()\n\nCreate a new session invoke with status set to :unknown.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.default_session","page":"Session summary","title":"RxInfer.default_session","text":"default_session()::Union{Nothing, Session}\n\nGet the current default session. If no session exists, returns nothing.\n\nReturns\n\nUnion{Nothing, Session}: The current default session or nothing if logging is disabled\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.disable_session_logging!","page":"Session summary","title":"RxInfer.disable_session_logging!","text":"Disables session logging for RxInfer globally at compile time and saves it in package preferences. Has effect after Julia restart.\n\nRestart Julia and verify it by isnothing(RxInfer.default_session()). \n\nNote that session logging still can be enabled manually for the current session if set_default_session! is called manually with appropriate Session object. \n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.enable_session_logging!","page":"Session summary","title":"RxInfer.enable_session_logging!","text":"Enables session logging for RxInfer globally at compile time and saves it in package preferences. Has effect after Julia restart.\n\nRestart Julia and verify it by !isnothing(RxInfer.default_session()).     \n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.create_session","page":"Session summary","title":"RxInfer.create_session","text":"create_session()\n\nCreate a new session with a unique identifier, environment info and current timestamp. The session maintains separate statistics for each label, with each label's statistics having its own circular buffer of invokes.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.set_default_session!","page":"Session summary","title":"RxInfer.set_default_session!","text":"set_default_session!(session::Union{Nothing, Session})\n\nSet the default session to a new session or disable logging by passing nothing. \n\nArguments\n\nsession::Union{Nothing, Session}: The new session to set as default, or nothing to disable logging\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.summarize_session","page":"Session summary","title":"RxInfer.summarize_session","text":"summarize_session([io::IO], session::Session, label::Symbol = :inference; n_last = 5)\n\nPrint a concise summary of session statistics for invokes with the specified label. The default label is :inference which gathers statistics of the infer function calls.\n\nnote: Note\nYou may want to additionally install PrettyTables.jl package to see the nicely formatted output of the summarize_session function. It is not bundled with RxInfer by default, but, if installed manually, it makes the output more readable.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.get_session_stats","page":"Session summary","title":"RxInfer.get_session_stats","text":"get_session_stats(session::Session, label::Symbol = :inference)\n\nGet statistics for invokes with the specified label. If the label doesn't exist in the session, returns a new empty SessionStats instance.\n\nArguments\n\nsession::Union{Nothing, Session}: The session to get statistics from, or nothing\nlabel::Symbol: The label to get statistics for, defaults to :inference\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.Session","page":"Session summary","title":"RxInfer.Session","text":"Session\n\nA structure that maintains a log of RxInfer usage. Each session has a unique identifier and saves when it was created together with its environment.  The session maintains a dictionary of labeled statistics, each tracking a series of invocations (SessionInvoke)  and computing real-time statistics.\n\nFields\n\nid::UUID: A unique identifier for the session\ncreated_at::DateTime: Timestamp when the session was created\nenvironment::Dict{Symbol, Any}: Information about the Julia & RxInfer versions and system when the session was created\nsemaphore::Base.Semaphore: Thread-safe semaphore for updating stats\nstats::Dict{Symbol, SessionStats}: Statistics per label\n\nThe session logging is transparent and only collects non-sensitive information about calls. Users can inspect the session at any time using get_current_session() and reset it using reset_session!().\n\n\n\n\n\n","category":"type"},{"location":"manuals/session_summary/#RxInfer.with_session","page":"Session summary","title":"RxInfer.with_session","text":"with_session(f::F, session, label::Symbol) where {F}\n\nExecute function f within a session context with the specified label. If session is provided, logs execution details including timing and errors, and updates the session statistics for the given label. If session is nothing, executes f without logging.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.append_invoke_context","page":"Session summary","title":"RxInfer.append_invoke_context","text":"append_invoke_context(f, invoke)\n\nAppend context information to a session invoke. If invoke is a SessionInvoke, executes function f with the invoke's context. If invoke is nothing, does nothing.\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.update_session!","page":"Session summary","title":"RxInfer.update_session!","text":"update_session!(session::Session, label::Symbol, invoke::SessionInvoke)\n\nThread-safely update session statistics for a given label with a new invoke. Uses a semaphore to ensure thread safety when multiple threads try to update statistics simultaneously.\n\nArguments\n\nsession::Session: The session to update\nlabel::Symbol: Label for the invoke\ninvoke::SessionInvoke: The invoke to add to statistics\n\n\n\n\n\n","category":"function"},{"location":"manuals/session_summary/#RxInfer.update_stats!","page":"Session summary","title":"RxInfer.update_stats!","text":"update_stats!(stats::SessionStats, invoke::SessionInvoke)\n\nUpdate session statistics with a new invoke.\n\n\n\n\n\n","category":"function"},{"location":"manuals/migration/v2-to-v3/#Migration-Guide-from-version-2.x-to-3.x","page":"Migration from v2 to v3","title":"Migration Guide from version 2.x to 3.x","text":"This guide is intended to help you migrate your project from version 2.x to 3.x of RxInfer. The main difference between these two versions is the redefinition of the model specification language. A detailed explanation of the new model definition language can be found in the GraphPPL documentation. Here, we will give an overview of the most important changes and introduce RxInfer specific changes.","category":"section"},{"location":"manuals/migration/v2-to-v3/#Model-specification","page":"Migration from v2 to v3","title":"Model specification","text":"Also read the Model specification guide.","category":"section"},{"location":"manuals/migration/v2-to-v3/#randomvar,-datavar-and-constvar-have-been-removed","page":"Migration from v2 to v3","title":"randomvar, datavar and constvar have been removed","text":"The most notable change in the model specification is the removal of the randomvar, datavar, and constvar functions. Now, the @model macro automatically determines whether to use randomvar or constvar based on their usage. Previously declared datavar variables must now be listed in the argument list of the model.\n\nThe following example is a simple model definition in previous version:\n\n@model function SSM(n, x0, A, B, Q, P) \n    x = randomvar(n) \n    y = datavar(Vector{Float64}, n) \n    x_prior ~ MvNormal(μ = mean(x0), Σ = cov(x0)) \n    x_prev = x_prior \n    for i in 1:n \n        x[i] ~ MvNormal(μ = A * x_prev, Σ = Q) \n        y[i] ~ MvNormal(μ = B * x[i], Σ = P) \n        x_prev = x[i] \n    end \nend \n\nThe equivalent model definition in the new version is as follows:\n\n@model function SSM(y, prior_x, A, B, Q, P) \n    x_prev ~ prior_x\n    for i in eachindex(y)\n        x[i] ~ MvNormal(μ = A * x_prev, Σ = Q) \n        y[i] ~ MvNormal(μ = B * x[i], Σ = P) \n        x_prev = x[i]\n    end\nend\n\nRead more about the change in the GraphPPL documentation and  in the updated Model specification guide.","category":"section"},{"location":"manuals/migration/v2-to-v3/#Positional-arguments-are-converted-to-keyword-arguments","page":"Migration from v2 to v3","title":"Positional arguments are converted to keyword arguments","text":"The changes in the model specification also have implications for the infer function. Since all interfaces to a model are now passed as arguments to the @model macro, the infer function needs additional information on model construction. Therefore, the model function definition converts all positional arguments to keyword arguments. Positional arguments are no longer supported in the model function definition. Below is an example of the new model definition:\n\nusing Test #hide\nusing RxInfer\n\n@model function coin_toss(prior, y)\n    θ ~ prior\n    y .~ Bernoulli(θ)\nend\n\n# Here, we pass a prior as a paramet
2er to the model, and the data `y` is passed as data. \n# Since we have to distinguish between what should be used as which argument, we have to pass the data as a keyword argument.\ninfer(\n    model = coin_toss(prior = Beta(1, 1)), \n    data  = (y = [1, 0, 1],) \n)","category":"section"},{"location":"manuals/migration/v2-to-v3/#Multiple-dispatch-is-no-longer-supported","page":"Migration from v2 to v3","title":"Multiple dispatch is no longer supported","text":"Due to the previous change, it is not possible to use multiple dispatch for model function definitions. In other words, type constraints for model arguments are ignored because Julia does not support multiple dispatch for keyword arguments.","category":"section"},{"location":"manuals/migration/v2-to-v3/#Return-value-from-the-model-function","page":"Migration from v2 to v3","title":"Return value from the model function","text":"Accessing the return value of the model function has changed. Previously, the return value was returned together with the model upon creation. Now, the return value is saved in the model's data structure, which can be accessed with the RxInfer.getreturnval function. To demonstrate the difference, previously we could do the following:\n\n@model function test_model(a, b)\n    y = datavar(Float64)\n    θ ~ Beta(1.0, 1.0)\n    y ~ Bernoulli(θ)\n    return \"Hello, world!\"\nend\nmodelgenerator = test_model(1.0, 1.0)\nmodel, returnval = RxInfer.create_model(modelgenerator)\nreturnval # \"Hello, world!\"\n\nThe new API is changed to:\n\n@model function test_model(y, a, b) #hide\n    θ ~ Beta(1.0, 1.0) #hide\n    y ~ Bernoulli(θ) #hide\n    return \"Hello, world!\" #hide\nend #hide\nmodelgenerator = test_model(a = 1.0, b = 1.0) | (y = 1, )\nmodel = RxInfer.create_model(modelgenerator)\n@test RxInfer.getreturnval(model) == \"Hello, world!\" #hide\nRxInfer.getreturnval(model)\n\nThe InferenceResult also no longer stores the returnval field. Instead, use the model field and the RxInfer.getreturnval function:\n\nresult = infer(\n    model = test_model(a = 1.0, b = 1.0),\n    data  = (y = 1, )\n)\n@test RxInfer.getreturnval(result.model) == \"Hello, world!\" #hide\nRxInfer.getreturnval(result.model)","category":"section"},{"location":"manuals/migration/v2-to-v3/#Returning-variables-from-the-model","page":"Migration from v2 to v3","title":"Returning variables from the model","text":"Similar to the previous version, you can still return latent variables from the model definition:\n\n@model function test_model(y, a, b)\n    θ ~ Beta(1.0, 1.0)\n    y ~ Bernoulli(θ)\n    return θ\nend\n\nHowever, their type has changed to internal data structures from the GraphPPL package. To access the ReactiveMP data structures (e.g., to retrieve the messages or marginals streams), use RxInfer.getvarref along with RxInfer.getvariable:\n\nusing RxInfer.ReactiveMP, Rocket\nresult = infer(\n    model = test_model(a = 1.0, b = 1.0),\n    data  = (y = 1, )\n)\n\nθlabel  = RxInfer.getreturnval(result.model)\nθvarref = RxInfer.getvarref(result.model, θlabel)\nθvar    = RxInfer.getvariable(θvarref)\n@test θvar isa RxInfer.ReactiveMP.RandomVariable #hide\nqθ_test = [] #hide\nsubscribe!(RxInfer.ReactiveMP.get_stream_of_marginals(θvar) |> take(1), (qθ) -> push!(qθ_test, qθ)) #hide\n@test length(qθ_test) === 1 #hide\n@test first(RxInfer.ReactiveMP.getdata(qθ_test)) == Beta(2.0, 1.0) #hide\n\n# `|> take(1)` ensures automatic unsubscription \nθmarginals_subscription = subscribe!(RxInfer.ReactiveMP.get_stream_of_marginals(θvar) |> take(1), (qθ) -> println(qθ))\nnothing #hide\n\nnote: Note\nPrior to ReactiveMP v6, ReactiveMP.get_stream_of_marginals was called ReactiveMP.getmarginal. This change is unrelated to breaking change in RxInfer from v2 to v3, but is reflected here in order for this example to work during the documentation build.","category":"section"},{"location":"manuals/migration/v2-to-v3/#Initialization","page":"Migration from v2 to v3","title":"Initialization","text":"Initialization of messages and marginals to kickstart the inference procedure was previously done with the initmessages and initmarginals keyword. With the introduction of a nested model specification in the @model macro, we now need a more specific way to initialize messages and marginals. This is done with the new @initialization macro.  Read more about the new syntax in the Initialization guide.\n\n","category":"section"},{"location":"manuals/comparison/#comparison","page":"RxInfer.jl vs. Others","title":"Comparison to other packages","text":"Nowadays there's plenty of probabilistic programming languages and packages available. Although all are based on Bayesian inference, their methodologies vary. This section compares RxInfer.jl against other renowned probabilistic programming languages and packages. The goal is to enlighten potential users about the nuances and guide them in choosing the package that best suits their requirements.\n\nwarning: Warning\nThis comparison is not exhaustive and mirrors the author's hands-on experience with the packages. Others may have undergone more rigorous testing. If you're an author of one of these packages and believe this comparison does not do justice, please reach out, and we will be more than willing to make corrections.\nThe comparison is more qualitative than quantitative, considering the intricacies of upkeeping benchmarking code for perpetually evolving packages.\n\nToolbox Universality Efficiency Expressiveness Debugging & Visualization Modularity Inference Engine Language Community & Ecosystem\nRxInfer.jl ~ ✓ ✓ ~ ✓ Message-passing Julia ✗\nForneyLab.jl ✗ ~ ✗ ~ ✗ Message-passing Julia ✗\nInfer.net ~ ✓ ✗ ✓ ✗ Message-passing C# ✗\nPGMax ✗ ✓ ✗ ✓ ✗ Message-passing Python ✗\nTuring.jl ✓ ✗ ✓ ~ ✗ Sampling Julia ✓\nPyMC ✓ ✗ ✓ ✓ ✗ Sampling Python ✓\nNumPyro ✓ ✓ ~ ✓ ✗ Sampling Python ✓\nTensorFlow Probability ✓ ✗ ~ ✓ ✗ Sampling Python ✓\nStan ✓ ✗ ✓ ✓ ✗ Sampling Stan ✓\n\n(Date of creation: 20/10/2023)\n\nLegend\n\n✓ : Full capability or feature is present.\n~ : Partial capability or feature is present.\n✗ : No capability or feature.\n\nNotes:\n\nUniversality: Denotes the capability to depict a vast array of probabilistic models.\nEfficiency: Highlights computational competence. A \"~\" in this context suggests perceived slowness.\nExpressiveness: Assesses the ability to concisely formulate intricate probabilistic models.\nDebugging & Visualization: Evaluates the suite of tools for model debugging and visualization.\nModularity: Reflects the potential to create models by integrating smaller models.\nInference Engine: Pinpoints the primary inference strategy employed by the toolbox.\nLanguage: Identifies the programming language integral to the toolbox.\nCommunity & Ecosystem: Signifies the vibrancy of the ecosystem, inclusive of tools, libraries, and community backing.\n\n","category":"section"},{"location":"manuals/comparison/#RxInfer.jl-breakdown","page":"RxInfer.jl vs. Others","title":"RxInfer.jl breakdown","text":"Universality: RxInfer.jl shines in formulating models derived from the exponential family of distributions. The package encompasses not only commonly used distributions such as Gaussian or Bernoulli, but also specialized stochastic no
2des that represent prevalent probabilistic models like Autoregressive models and Gamma Mixture models, among others. Furthermore, RxInfer.jl proficiently manages deterministic transformations of variables from the exponential family, see Delta node. Nevertheless, for models outside the exponential family, RxInfer.jl might not be a good choice. Such models would require the creation of novel nodes and corresponding rules (see Understanding Rules), as illustrated in this section.\nEfficiency: RxInfer.jl distinguishes itself with its inference engine rooted in reactive message passing. This approach is supremely efficient, facilitating real-time propagation of updates across the system, supporting parallelization, interruptibility, and more. \nModularity: Broadly, the toolboxes in the table aren't modular in the truest sense. They don't offer the fusion of models by integrating smaller models. RxInfer.jl on the other hand provides a way to compose different models:\n\nusing RxInfer #hide\n\n@model function inner_inner(τ, y, x)\n    y ~ Normal(mean = τ[1], var = τ[2] + x)\nend\n\n@model function inner(θ, α)\n    β ~ Normal(mean = 0.0, var = 1.0)\n    α ~ Gamma(shape = β, rate = 1.0)\n    α ~ inner_inner(τ = θ, x = 3)\nend\n\n@model function outer()\n    local w\n    for i = 1:5\n        w[i] ~ inner(θ = Gamma(shape = 1.0, rate = 1.0))\n    end\n    y ~ inner(θ = w[2:3])\nend\n\nExpressiveness: RxInfer.jl empowers users to elegantly and concisely craft models, closely mirroring probabilistic notation, thanks to Julia's macro capabilities.\n\nnote: Note\nRxInfer uses := for deterministic relationships (see Using = instead of := for deterministic nodes) which might differ from other frameworks but enables powerful message-passing capabilities.\n\nTo illustrate the expressiveness, let's consider the following model:\n\nbeginaligned\n x  sim mathrmNormal(00 10)\n w  sim mathrmInverseGamma(10 10)\n y  sim mathrmNormal(x w)\nendaligned\n\nThe model then is expressed in RxInfer.jl as follows:\n\nusing RxInfer #hide\n\n@model function example_model()\n    x ~ Normal(mean = 0.0, var = 1.0)\n    w ~ InverseGamma(α = 1, θ = 1)\n    y ~ Normal(mean = x, var = w)\nend\n\nDebugging & Visualization: RxInfer.jl does provide a mechanism to debug the inference procedure and visualise the graph structure, even though not as seamlessly as some other packages.\n\n","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs","page":"Factor Graphs","title":"Factor Graphs","text":"A factor graph is a picture of how a joint probability distribution factorises into smaller, local pieces. RxInfer compiles every @model you write into such a graph and performs Bayesian inference by exchanging messages along its edges. Understanding what a factor graph is — and how your code maps to one — is the single most useful mental model for using RxInfer productively.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-why","page":"Factor Graphs","title":"Why factorise?","text":"A generative model defines a joint distribution over all variables. For any non-trivial problem this joint is huge, and manipulating it directly is intractable. Fortunately, real models have structure: each local relationship (a prior, a likelihood, a transition, a deterministic equation) usually involves only a handful of variables. Writing the joint as a product of these local pieces,\n\np(x_1 x_2 dots x_n) = prod_a f_a(mathbfx_a)\n\nexposes that structure. Every factor f_a touches only a small subset mathbfx_a of variables, and that sparsity is what makes message passing — and therefore RxInfer — fast.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-anatomy","page":"Factor Graphs","title":"Anatomy of a factor graph","text":"A factor graph is bipartite: it has two kinds of nodes, and edges only run between the two kinds.\n\nVariable nodes bigcirc — one per random quantity in the model. They can be latent (to be inferred), observed (conditioned on data) or constant (fixed hyperparameters).\nFactor nodes blacksquare — one per local function in the factorisation. They can be stochastic (a probability distribution such as a Normal or Gamma) or deterministic (a functional relationship such as z = x + y).\n\nAn edge connects a factor to every variable it depends on. Messages — themselves probability distributions — flow along these edges during inference.\n\n          ┌───────── Beta ─────────┐\n          │                         │\n         (θ)───────[ Bernoulli ]──(y)\n\nThe tiny graph above corresponds to θ ~ Beta(1, 1); y ~ Bernoulli(θ): two factor nodes (Beta prior and Bernoulli likelihood) connected through the shared variable theta, with the observation y clamped to its data value.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-spec","page":"Factor Graphs","title":"From @model code to a factor graph","text":"RxInfer does not ask you to build this graph by hand. Instead, you describe the model in idiomatic Julia and GraphPPL.jl converts it into a factor graph for you. The specification language is intentionally small:\n\nusing RxInfer\n\n@model function state_space_model(y, variance)\n    x[1] ~ Normal(mean = 0.0, variance = 100.0)   # prior factor\n    y[1] ~ Normal(mean = x[1], variance = variance)\n    for i in 2:length(y)\n        x[i] ~ Normal(mean = x[i-1], variance = 1.0) # transition factor\n        y[i] ~ Normal(mean = x[i],   variance = variance) # likelihood factor\n    end\nend\n\nEach piece of syntax has a precise graph-level meaning:\n\nx ~ Distribution(...) introduces a stoch
2astic factor connected to x and to the variables appearing in its arguments.\nz := f(x, y) (or z ~ f(x, y) for random z) introduces a deterministic factor enforcing z = f(x, y). See := vs = for why this distinction matters.\nx .~ Distribution(...) broadcasts the factor across a collection — convenient for IID data or vectorised observations.\nRegular Julia control flow (for, if, comprehensions) is evaluated at graph-construction time and unrolls into multiple factor nodes.\nModel arguments can be turned into observations via the | conditioning operator, e.g. model | (y = data,).\n\nThe full reference — including indexing rules, anonymous nodes, broadcasting and graph visualisation — lives in the Model Specification manual. If you prefer to learn by example, the Getting started guide walks through a complete model from scratch.\n\ntip: Tip\nEvery ~ statement you write becomes one factor node. Keeping that correspondence in mind makes it much easier to reason about the resulting graph — and to understand why certain constraints or initialisations are required.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-topology","page":"Factor Graphs","title":"Trees, loops, and what they imply","text":"The topology of the graph determines what kind of inference is possible:\n\nTree-structured graphs (no cycles) admit exact inference via belief propagation — a single forward-backward sweep gives you the true marginal posteriors.\nGraphs with loops require approximate inference, typically via loopy belief propagation or variational message passing. RxInfer handles both automatically — see Message Passing and Variational Inference.\n\nYou do not choose the algorithm explicitly: RxInfer inspects the graph and dispatches to the appropriate local update rule on every edge.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-payoff","page":"Factor Graphs","title":"Why this representation pays off","text":"Representing a model as a factor graph buys you three things at once:\n\nScalability — local updates mean cost grows with the size of each factor, not with the size of the joint.\nModularity — adding or swapping a factor is a local change; the rest of the graph keeps working.\nAutomatic algorithm selection — conjugate pairs trigger exact rules, non-conjugate regions fall back to variational ones, and deterministic factors compose through the delta trick. You write the model; RxInfer picks the math.","category":"section"},{"location":"concepts/factor-graphs/#concepts-factor-graphs-deeper","page":"Factor Graphs","title":"For deeper understanding","text":"GraphPPL.jl — the model specification front-end and graph construction engine.\nReactiveMP.jl — the low-level factor graph and message passing back-end.\nThe Factor Graph Approach to Model-Based Signal Processing — Loeliger et al., the canonical introduction to Forney factor graphs.\nFactor Graphs and the Sum-Product Algorithm — Kschischang, Frey and Loeliger (2001).\nRxInfer Examples — gallery of models with visualised factor graphs.\n\n","category":"section"},{"location":"manuals/inference/overview/#user-guide-inference-execution","page":"Overview","title":"Inference execution","text":"The RxInfer inference API supports different types of message-passing algorithms (including hybrid algorithms combining several different types). While RxInfer implements several algorithms to cater to different computational needs and scenarios, the core message-passing algorithms that form the foundation of our inference capabilities are:\n\nBelief Propagation\nVariational Message Passing\n\nWhereas belief propagation computes exact inference for the random variables of interest, the variational message passing (VMP) is an approximation method that can be applied to a larger range of models.\n\nThe inference engine itself isn't aware of different algorithm types and simply does message passing between nodes. However, during the model specification stage user may specify different factorisation constraints around factor nodes with the help of the @constraints macro. Different factorisation constraints lead to different message passing update rules. See more documentation about constraints specification in the corresponding section.","category":"section"},{"location":"manuals/inference/overview/#user-guide-inference-execution-automatic-specification","page":"Overview","title":"Automatic inference specification","text":"RxInfer exports the infer function to quickly run and test your model with both static and asynchronous (real-time) datasets. See more information about the infer function on the separate documentation section:\n\nStatic Inference\nStreamlined Inference","category":"section"},{"location":"manuals/inference/overview/#Note-on-NamedTuples","page":"Overview","title":"Note on NamedTuples","text":"When passing NamedTuple as a value for some argument, make sure you use a trailing comma for NamedTuples with a single entry. The reason is that Julia treats returnvars = (x = KeepLast()) and returnvars = (x = KeepLast(), ) expressions differently. This first expression creates (or overwrites!) new local/global variable named x with contents KeepLast(). The second expression (note trailing comma) creates NamedTuple with x as a key and KeepLast() as a value assigned for this key.\n\nusing RxInfer #hide\n(x = KeepLast()) # defines a variable `x` with the value `KeepLast()`\n\n(x = KeepLast(), ) # defines a NamedTuple with `x` as one of the keys and value `KeepLast()`\n\nmodel\n\nAlso read the Model Specification section.\n\nThe model argument accepts a model specification as its input. The easiest way to create the model is to use the @model macro.  For example:\n\nusing RxInfer #hide\n\n@model function beta_bernoulli(y, a, b)\n    x  ~ Beta(a, b)\n    y .~ Bernoulli(x)\nend\n\nresult = infer(\n    model = beta_bernoulli(a = 1, b = 1),\n    data  = (y = [ true, false, false ], )\n)\n\nresult.posteriors[:x]\n\nnote: Note\nThe model keyword argument does not accept a ProbabilisticModel instance as a value, as it needs to inject constraints and meta during the inference procedure.\n\ndata\n\nEither data or datastream keyword argument are required.  Specifying both data and datastream is not supported and will result in an error. \n\nnote: Note\nThe behavior of the data keyword argument depends on the inference setting (batch or streamline).\n\nThe data keyword argument must be a NamedTuple (or Dict) where keys (of Symbol type) correspond to some arguments defined in the model specification.  For example, if a model defines y in its argument list \n\nusing RxInfer #hide\n@model function beta_bernoulli(y, a, b)\n    x  ~ Beta(a, b)\n    y .~ Bernoulli(x)\nend\n\nand you want to condition on this argument, then the data field must have an :y key (of Symbol type) which holds the data.  The values in the data must have the exact same shape as its corresponding variable container. E.g. in the example above y is being used in the broadcasting  operation, thus it must be a collection of values. The a and b arguments, however, could be just single numbers:\n\nresult = infer(\n    model = beta_bernoulli(),\n    data  = (y = [ true, false, false ], a = 1, b = 1)\n)\n\nresult.posteriors[:x]\n\ndatastream\n\nAlso read the Streamlined Inference section.\n\nThe datastream keyword argument must be an observable that supports subscribe! and unsubscribe! functions (e.g., streams from the Rocket.jl package). The elements of the observable must be of type NamedTuple where keys (of Symbol type) correspond to input arguments defined in the model specification, except for those which are listed in the @autoupdates specification.  For example, if a model defines y as its argument (which is not part of the @autoupdates specification) the named tuple from the observable must have an :y key (of Symbol type). The values in the named tuple must have the exact same shape as the corresponding variable container.\n\ninitialization\n\nAlso read the Initialization section.\n\nFor specific types of inference algorithms, such as variational message passing, it might be required to initialize (some of) the marginals before running the inference procedure in order to break the dependency loop. If this is not done, the inference algorithm will not be executed due to the lack of information and message and/or marginals will not be updated. In order to specify these initial marginals and messages, you can use the initialization argument in combination with the @initialization macro, such as\n\nusing RxInfer #hide\ninit = @initialization begin\n    # initialize the marginal distribution of x as a vague Normal distribution\n    # if x is a vector, then it simply uses the same value for all elements\n    # However, it is also possible to provide a vector of distributions to set each element individually \n    q(x) = vague(NormalMeanPrecision)\nend\n\nreturnvars\n\nreturnvars specifies latent variables of interest and their posterior updates. Its behavior depends on the inference type: streamline or batch.\n\nBatch inference:\n\nAccepts a NamedTuple or Dict of return variable specifications.\nTwo specifications available: KeepLast (saves the last update) and KeepEach (saves all updates).\nWhen iterations is set, returns every update for each iteration (equivalent to KeepEach()); if nothing, saves the last update (equivalent to KeepLast()).\nUse iterations = 1 to force KeepEach() for a single iteration or set returnvars = KeepEach() manually.\n\nresult = infer(\n    ...,\n    returnvars = (\n        x = KeepLast(),\n        τ = KeepEach()\n    )\n)\n\nShortcut for setting the same option for all variables:\n\nresult = infer(\n    ...,\n    returnvars = KeepLast()  # or KeepEach()\n)\n\nStreamline inference:\n\nFor each symbol in returnvars, infer creates an observable stream of posterior updates.\
2nAgents can subscribe to these updates using the Rocket.jl package.\n\nengine = infer(\n    ...,\n    autoupdates = my_autoupdates,\n    returnvars = (:x, :τ),\n    autostart  = false\n)\n\npredictvars\n\npredictvars specifies the variables which should be predicted. Similar to returnvars, predictvars accepts a NamedTuple or Dict. There are two specifications:\n\nKeepLast: saves the last update for a variable, ignoring any intermediate results during iterations\nKeepEach: saves all updates for a variable for all iterations\n\nresult = infer(\n    ...,\n    predictvars = (\n        o = KeepLast(),\n        τ = KeepEach()\n    )\n)\n\nnote: Note\nThe predictvars argument is exclusive for batch setting.\n\nhistoryvars\n\nAlso read the Keeping the history of posteriors.\n\nhistoryvars specifies the variables of interests and the amount of information to keep in history about the posterior updates when performing streamline inference. The specification is similar to the returnvars when applied in batch setting. The historyvars requires keephistory to be greater than zero.\n\nhistoryvars accepts a NamedTuple or Dict or return var specification. There are two specifications:\n\nKeepLast: saves the last update for a variable, ignoring any intermediate results during iterations\nKeepEach: saves all updates for a variable for all iterations\n\nresult = infer(\n    ...,\n    autoupdates = my_autoupdates,\n    historyvars = (\n        x = KeepLast(),\n        τ = KeepEach()\n    ),\n    keephistory = 10\n)\n\nIt is also possible to set either historyvars = KeepLast() or historyvars = KeepEach() that acts as an alias and sets the given option for all random variables in the model.\n\nresult = infer(\n    ...,\n    autoupdates = my_autoupdates,\n    historyvars = KeepLast(),\n    keephistory = 10\n)\n\nkeephistory\n\nSpecifies the buffer size for the updates history both for the historyvars and the free_energy buffers in streamline inference.\n\nnote: Note\nThe historyvars and keephistory arguments are exclusive for streamlined setting.\n\niterations\n\nSpecifies the number of variational (or loopy belief propagation) iterations. By default set to nothing, which is equivalent of doing 1 iteration. However, if set explicitly to 1 the default setting for returnvars changes from KeepLast to KeepEach.\n\nSee Early stopping for an opt-in callback example that implements early stopping criterion.\n\nfree_energy\n\nBatch inference:\n\nSpecifies if the infer function should return Bethe Free Energy (BFE) values.\n\nOptionally accepts a floating-point type (e.g., Float64) for improved BFE computation performance, but restricts the use of automatic differentiation packages.\n\nStreamline inference:\n\nSpecifies if the infer function should create an observable stream of Bethe Free Energy (BFE) values, computed at each VMP iteration.\n\nWhen free_energy = true and keephistory > 0, additional fields are exposed in the engine for accessing the history of BFE updates.\nengine.free_energy_history: Averaged BFE history over VMP iterations.\nengine.free_energy_final_only_history: BFE history of values computed in the last VMP iterations for each observation.\nengine.free_energy_raw_history: Raw BFE history.\n\nfree_energy_diagnostics\n\nThis settings specifies either a single or a tuple of diagnostic checks for Bethe Free Energy values stream. By default checks for NaNs and Infs.  See also RxInfer.ObjectiveDiagnosticCheckNaNs and RxInfer.ObjectiveDiagnosticCheckInfs. Pass nothing to disable any checks.\n\noptions\n\ncatch_exception\n\nThe catch_exception keyword argument specifies whether exceptions during the batch inference procedure should be caught in the error field of the  result. By default, if exception occurs during the inference procedure the result will be lost. Set catch_exception = true to obtain partial result  for the inference in case if an exception occurs. Use RxInfer.issuccess and RxInfer.iserror function to check if the inference completed successfully or failed. If an error occurs, the error field will store a tuple, where first element is the exception itself and the second element is the caught backtrace. Use the stacktrace function  with the backtrace as an argument to recover the stacktrace of the error. Use Base.showerror function to display the error.\n\ncallbacks\n\nThe inference function and underlying reactive message passing procedure both have their own lifecycle. The user is free to provide some (or none) of the callbacks to inject extra logic during the inference procedure. Callbacks can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. For example:\n\nresult = infer(\n    ...,\n    callbacks = (\n        on_marginal_update = (event) -> println(\"\\$(event.variable_name) has been updated: \\$(event.update)\"),\n        after_inference    = (event) -> println(\"Inference has been completed\")\n    )\n)\n\nFor the full list of available events, supported callback types, model metadata, and built-in callback handlers, see the Callbacks section. See also Early stopping and Benchmark callbacks.\n\nannotations\n\nSets a tuple of annotation processors that attach extra information to messages and marginals during inference. For example, annotations = LogScaleAnnotations() tracks log-scale normalization constants, which is useful for computing Bayes factors and model evidence in mixture models. When annotations are enabled, the inference results preserve the Marginal wrapper type so that annotation data remains accessible via ReactiveMP.getannotations. See ReactiveMP.jl documentation for available annotation types and how to implement custom annotation processors.\n\nAutomatically changes the default value of the postprocess argument to NoopPostprocess.\n\npostprocess\n\nAlso read the Inference results postprocessing section.\n\nThe postprocess keyword argument controls whether the inference results must be modified in some way before exiting the inference function. By default, the inference function removes the Marginal wrapper type from the results. Change this setting to NoopPostprocess if you would like to keep the Marginal wrapper type. If the annotations argument has been used, automatically changes the default strategy value to NoopPostprocess.\n\nError hints\n\nBy default, RxInfer provides helpful error hints when an error occurs during inference. This, for example, includes links to relevant documentation, common solutions and troubleshooting steps, information about where to get help, and suggestions for providing good bug reports.\n\nUse RxInfer.disable_inference_error_hint! to disable error hints or RxInfer.enable_inference_error_hint! to enable them. Note that the change requires a Julia session restart to take effect.","category":"section"},{"location":"manuals/inference/overview/#Where-to-go-next?","page":"Overview","title":"Where to go next?","text":"Read more explanation about the other keyword arguments in the Streamlined (online) inference section or check out the Static Inference section or check some more advanced examples.\n\n","category":"section"},{"location":"manuals/inference/overview/#RxInfer.infer","page":"Overview","title":"RxInfer.infer","text":"infer(\n    model; \n    data = nothing,\n    datastream = nothing,\n    autoupdates = nothing,\n    initialization = nothing,\n    constraints = nothing,\n    meta = nothing,\n    options = nothing,\n    returnvars = nothing, \n    predictvars = nothing, \n    historyvars = nothing,\n    keephistory = nothing,\n    iterations = nothing,\n    free_energy = false,\n    free_energy_diagnostics = DefaultObjectiveDiagnosticChecks,\n    showprogress = false,\n    callbacks = nothing,\n    annotations = nothing,\n    postprocess = nothing,\n    warn = true,\n    events = nothing,\n    uselock = false,\n    autostart = true,\n    catch_exception = false,\n    benchmark = false,\n    trace = false,\n    session = RxInfer.default_session()\n)\n\nThis function provides a generic way to perform probabilistic inference for batch/static and streamline/online scenarios. Returns either an InferenceResult (batch setting) or RxInferenceEngine (streamline setting) based on the parameters used.\n\nnote: Note\nBefore using this function, you may want to review common issues and s
2olutions in the Sharp bits of RxInfer section of the documentation.\n\nArguments\n\nCheck the official documentation for more information about some of the arguments. \n\nmodel: specifies a model generator, required\ndata: NamedTuple or Dict with data, required (or datastream or predictvars)\ndatastream: A stream of NamedTuple with data, required (or data)\nautoupdates = nothing: auto-updates specification, required for streamline inference, see @autoupdates\ninitialization = nothing: initialization specification object, optional, see @initialization\nconstraints = nothing: constraints specification object, or an alias such as MeanField, optional, see @constraints\nmeta  = nothing: meta specification object, optional, may be required for some models, see @meta\noptions = nothing: model creation options, optional, see ReactiveMPInferenceOptions\nreturnvars = nothing: return structure info, optional, defaults to return everything at each iteration\npredictvars = nothing: return structure info, optional (exclusive for batch inference)\nhistoryvars = nothing: history structure info, optional, defaults to no history (exclusive for streamline inference)\nkeephistory = nothing: history buffer size, defaults to empty buffer (exclusive for streamline inference)\niterations = nothing: number of iterations, optional, defaults to nothing, the inference engine does not distinguish between variational message passing, loopy belief propagation, or expectation propagation iterations. See Early stopping for an opt-in callback example, which implements early stopping.\nfree_energy = false: compute the Bethe free energy, optional, defaults to false. Can be passed a floating point type, e.g. Float64, for better efficiency, but disables automatic differentiation packages, such as ForwardDiff.jl\nfree_energy_diagnostics = DefaultObjectiveDiagnosticChecks: free energy diagnostic checks, optional, by default checks for possible NaNs and Infs. nothing disables all checks.\nshowprogress = false: show a progress bar, optional, defaults to false (exclusive for batch inference)\ncatch_exception: specifies whether exceptions during the inference procedure should be caught, optional, defaults to false (exclusive for batch inference)\ncallbacks = nothing: inference cycle callbacks, optional. Can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. See Callbacks for a comprehensive overview, Benchmark callbacks for performance analysis, Trace callbacks for event tracing, and Early stopping for an opt-in callback example.\nannotations = nothing: a tuple of annotation processors that attach extra information to messages and marginals during inference. For example, annotations = LogScaleAnnotations() tracks log-scale normalization constants, which is useful for computing Bayes factors and model evidence in mixture models. When annotations are enabled, the inference results preserve the Marginal wrapper type so that annotation data remains accessible via ReactiveMP.getannotations. See ReactiveMP.jl documentation for available annotation types and how to implement custom annotation processors.\npostprocess = nothing: inference results postprocessing step, optional. By default, uses UnpackMarginalPostprocess when annotations is nothing (strips the Marginal wrapper), and NoopPostprocess when annotations are enabled (preserves the wrapper). See Inference results postprocessing for details on implementing custom strategies.\nevents = nothing: inference cycle events, optional (exclusive for streamline inference)\nuselock = false: specifies whether to use a lock structure for the inference; if set to true, uses Base.Threads.SpinLock. Accepts a custom AbstractLock. (exclusive for streamline inference)\nautostart = true: specifies whether to call RxInfer.start on the created engine automatically or not (exclusive for streamline inference)\nwarn = true: enables/disables warnings\nbenchmark = false: when set to true, automatically merges a RxInferBenchmarkCallbacks instance with the user-provided callbacks. The benchmark results are accessible via result.model.metadata[:benchmark]. See Benchmark callbacks.\ntrace = false: when set to true, automatically merges a RxInferTraceCallbacks instance with the user-prov
2ided callbacks. Can also be a Tuple of Symbols to trace only specific event types (e.g., trace = (:before_iteration, :after_iteration)). The trace results are accessible via result.model.metadata[:trace]. See Trace callbacks.\nsession = RxInfer.default_session(): current logging session for the RxInfer invokes, see Session for more details, pass nothing to disable logging\n\nError hints\n\nBy default, RxInfer provides helpful error hints with documentation links, solutions, and troubleshooting guidance.\n\nUse RxInfer.disable_inference_error_hint!() to disable error hints or RxInfer.enable_inference_error_hint!() to enable them. Note that changes to error hint settings require a Julia session restart to take effect.\n\nSee also: RxInfer.disable_inference_error_hint!, RxInfer.enable_inference_error_hint!\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/overview/#RxInfer.KeepLast","page":"Overview","title":"RxInfer.KeepLast","text":"Instructs the inference engine to keep only the last marginal update and disregard intermediate updates.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/overview/#RxInfer.KeepEach","page":"Overview","title":"RxInfer.KeepEach","text":"Instructs the inference engine to keep each marginal update for all intermediate iterations.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/overview/#RxInfer.ReactiveMPInferenceOptions","page":"Overview","title":"RxInfer.ReactiveMPInferenceOptions","text":"ReactiveMPInferenceOptions(; kwargs...)\n\nCreates model inference options object. The list of available options is present below.\n\nOptions\n\nlimit_stack_depth: limits the stack depth for computing messages; helps with StackOverflowError for some huge models, but reduces the performance of the inference backend. Accepts an integer argument that specifies the maximum recursion depth. Lower is better for stack overflow errors, but worse for performance.\nwarn: (optional) flag to suppress warnings. Warnings are not displayed if set to false. Defaults to true.\nforce_marginal_computation: (optional) flag to force computation of marginals even when not explicitly requested. Defaults to false.\n\nAdvanced options\n\nstream_postprocessors: changes the postprocessor of reactive streams, see ReactiveMP.jl for more info, defaults to nothing, unless the limit_stack_depth option is set, in which case will be set to ReactiveMP.ScheduleOnStreamPostprocessor together with RxInfer.LimitStackScheduler.\nrulefallback: specifies a global message update rule fallback for cases when a specific message update rule is not available. Consult ReactiveMP documentation for the list of available callbacks.\n\nSee also: infer\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/overview/#RxInfer.issuccess","page":"Overview","title":"RxInfer.issuccess","text":"Checks if the InferenceResult object does not contain an error. \n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/overview/#RxInfer.iserror","page":"Overview","title":"RxInfer.iserror","text":"Checks if the InferenceResult object contains an error. \n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/overview/#RxInfer.disable_inference_error_hint!","page":"Overview","title":"RxInfer.disable_inference_error_hint!","text":"disable_inference_error_hint!()\n\nDisable error hints that are shown when an error occurs during inference.\n\nThe change requires a Julia session restart to take effect. When disabled, only the raw error will be shown without additional context or suggestions.\n\nSee also: enable_inference_error_hint!, infer\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/overview/#RxInfer.enable_inference_error_hint!","page":"Overview","title":"RxInfer.enable_inference_error_hint!","text":"enable_inference_error_hint!()\n\nEnable error hints that are shown when an error occurs during inference.\n\nThe change requires a Julia session restart to take effect. When enabled, errors during the inference call will include:\n\nLinks to relevant documentation\nCommon solutions and troubleshooting steps  \nInformation about where to get help\n\nSee also: disable_inference_error_hint!, infer\n\n\n\n\n\n","category":"function"},{"location":"concepts/message-passing/#concepts-message-passing","page":"Message Passing","t
2itle":"Message Passing","text":"Message passing is the algorithm RxInfer uses to turn a factor graph into concrete posterior distributions. Instead of ever touching the full joint, nodes exchange small local summaries — messages — along edges, and the posterior over each variable emerges from combining them.\n\nThe intuition is simple: every message from a factor towards a variable is a local answer to the question \"given everything I know about the rest of the graph, what do I think this variable looks like?\".","category":"section"},{"location":"concepts/message-passing/#concepts-message-passing-bp","page":"Message Passing","title":"Belief Propagation (BP)","text":"The classical message passing algorithm is belief propagation, also known as the sum-product algorithm. A message from factor f toward variable x integrates f against all other incoming messages:\n\nmu_f to x(x) = int f(x y z) mu_y to f(y) mu_z to f(z) mathrmdy mathrmdz\n\nThe message back from x collects beliefs from all other factors attached to x. The marginal posterior is then the normalised product of incoming messages at a variable node:\n\nq(x) = frac1Z prod_f in textnb(x) mu_f to x(x)\n\nOn tree-shaped graphs this procedure is exact: a single forward-backward sweep produces the true posterior marginals. On graphs with cycles, iterating the same updates — loopy BP — yields a principled approximation that is often excellent in practice.","category":"section"},{"location":"concepts/message-passing/#concepts-message-passing-vmp","page":"Message Passing","title":"Variational Message Passing (VMP)","text":"Once your model has loops, non-conjugate factors, or you deliberately impose simplifying assumptions, exact BP is no longer available. RxInfer's primary algorithm is variational message passing, which turns inference into minimisation of the Bethe Free Energy — the variational objective described in full on the Variational Inference page.\n\nUnder a mean-field-style factorisation, the factor-to-variable message becomes\n\nmu_f to x(x) = expleft( int q(y) q(z) log f(x y z) mathrmdy mathrmdz right)\n\nwhere q(y) and q(z) are the current marginal beliefs about the neighbouring variables. Two things make VMP attractive:\n\nBP is a special case: with no extra factorisation constraints, VMP reduces to ordinary belief propagation.\nLocality: each update still depends only on immediate neighbours, so the reactive execution model scales naturally.\n\nWhich factorisation you get — full mean-field, structured, or none at all — is controlled by constraints specifications.","category":"section"},{"location":"concepts/message-passing/#concepts-message-passing-automatic","page":"Message Passing","title":"Automatic rule selection","text":"You never choose a message update rule by hand. RxInfer uses Julia's multiple dispatch to pick the right rule for every edge based on:\n\nNode type — which factor you wrote (Normal, Gamma, a custom deterministic node, ...).\nOutgoing edge — which argument of the factor the message is heading towards.\nIncoming message types — the distribution families arriving on the other edges.\nFactorisation assumption — whether the surrounding constraints require a BP-style or VMP-style rule.\n\nWhen a conjugate pair is detected, the dispatched rule is a closed-form analytical update. Non-conjugate combinations fall back to numerical or approximate rules. The Understanding Rules manual explains exactly how this machinery works under the hood, and custom rules can be added without touching the core engine.\n\n# Conjugate — dispatches to an exact analytical Beta update\nθ ~ Beta(1.0, 1.0)\ny ~ Bernoulli(θ)\n\n# Non-conjugate — dispatches to a variational approximation\nλ ~ Normal(mean = 0.0, variance = 1.0)\ny ~ Poisson(exp(λ))","category":"section"},{"location":"concepts/message-passing/#concepts-message-passing-reactive","page":"Message Passing","title":"Reactive scheduling","text":"Traditional inference engines compile an explicit schedule (forward pass, backward pass, sweep order, ...) before inference begins. RxInfer does not. Every node owns a reactive stream of messages, and updates fire whenever the
2ir inputs change. The net effect:\n\nNew observations trigger only the messages that actually depend on them.\nThe graph is its own scheduler — no global plan to build or maintain.\nStreaming and real-time inference come for free.\n\nThe Reactive Programming concept page expands on this execution model.","category":"section"},{"location":"concepts/message-passing/#concepts-message-passing-deeper","page":"Message Passing","title":"For deeper understanding","text":"ReactiveMP.jl — the message passing engine and rule dispatch system.\nUnderstanding Rules — how RxInfer picks a rule for every edge.\nVariational Message Passing and Local Constraint Manipulation in Factor Graphs — Şenöz et al., the theoretical basis of RxInfer's VMP implementation.\nReactive Message Passing for Scalable Bayesian Inference — scaling message passing with reactive programming.\nFactor Graphs and the Sum-Product Algorithm — Kschischang, Frey and Loeliger (2001).\n\n","category":"section"},{"location":"manuals/inference/partial-data/#partial-data","page":"Partially-referenced (sparse) data","title":"Partially-referenced (sparse) data","text":"RxInfer materializes the nodes of a conditioned data array lazily: a data variable is created only for the indices that your model actually references inside a ~ statement. This means you can condition on a full, dense data array but only wire up the entries that are relevant for a particular run — the unreferenced entries are simply ignored.\n\nThis is useful whenever the structure of the graph depends on the data that happens to be available:\n\nMasked / missing observations — at each time step (or for each sensor) only some measurements are present, so only those should enter the likelihood.\nData-driven sub-graphs — a sub-model that branches on a mask and only connects the observed indices, leaving the rest of a conditioned tensor untouched.\nRagged observation sets — a different number of observations per group, padded into a rectangular array for convenience but only partially used.","category":"section"},{"location":"manuals/inference/partial-data/#Example","page":"Partially-referenced (sparse) data","title":"Example","text":"The model below conditions on a length-3 vector y, but only references y[1] and y[3]. The value supplied at y[2] is never used and has no effect on the result:\n\nusing RxInfer\n\n@model function partial(y)\n    x ~ NormalMeanVariance(0.0, 100.0)\n    y[1] ~ NormalMeanVariance(x, 1.0)\n    y[3] ~ NormalMeanVariance(x, 1.0)   # y[2] is never referenced\nend\n\n# y[2] = 999.0 is provided but ignored — only y[1] and y[3] inform the posterior.\nresult = infer(model = partial(), data = (y = [1.0, 999.0, 3.0],), iterations = 1)\n\nThe same applies to multi-dimensional tensors and to data tensors that are sliced and passed into sub-models, which is the typical pattern for masked, per-time-step observation blocks:\n\n@model function observe(y_, mask_, x)\n    for j in eachindex(mask_)\n        if mask_[j]\n            y_[j] ~ NormalMeanVariance(x, 1.0)\n        end\n    end\nend\n\n@model function masked_chain(y, mask)\n    J, T = size(mask)\n    x ~ NormalMeanVariance(0.0, 100.0)\n    for t in 1:T\n        # `observe` only references the observed sensors of the t-th column of `y`.\n        x ~ observe(y_ = y[1:J, t:t], mask_ = mask[1:J, t:t])\n    end\nend","category":"section"},{"location":"manuals/inference/partial-data/#How-the-data-is-fed","page":"Partially-referenced (sparse) data","title":"How the data is fed","text":"When a conditioned data array is only partially referenced, the corresponding array of data variables is sparse — it has the same bounding-box shape as the data, but only the referenced indices are materialized. During inference RxInfer feeds the provided data into those variables by index: the data variable at position I receives the value data[I], and entries of data at unreferenced positions are ignored. You therefore pass data with its natural, dense shape — no need to pre-mask or reshape it to match the referenced subset.\n\nThis applies to both batch inference (infer with data) and streaming inference (infer with datastream) — in the streaming case each tick's dense value is fed to the materialized variables by the same index alignment.","category":"section"},{"location":"manuals/inference/partial-data/#Non-standard-(offset)-data-indexing","page":"Partially-referenced (sparse) data","title":"Non-standard (offset) data indexing","text":"Conditioned data may use non-standard indexing, such as an OffsetArray whose axes start at 0 (or at a negative index). RxInfer presents such data to the model with standard 1-based axes — the values and their order are preserved, only the addressing is rebased. You therefore write your model with ordinary 1-based indexing (1:n, eachindex, axes, …) regardless of the data's native axes, and partial/sparse referencing works exactly as above:\n\nusing RxInfer, OffsetArrays\n\n@model function partial(y)\n    x ~ NormalMeanVariance(0.0, 100.0)\n    y[1] ~ NormalMeanVariance(x, 1.0)\n    y[3] ~ NormalMeanVariance(x, 1.0)\nend\n\n# A 0-based OffsetArray is accepted; the model still indexes 1-based.\nydata  = OffsetArray([1.0, 999.0, 3.0], 0:2)\nresult = infer(model = partial(), data = (y = ydata,), iterations = 1)\n\nStandard (already 1-based) arrays are passed through unchanged, with no copy. Note that indexing the model itself with a literal offset index (e.g. writing y[0] inside @model) is not supported — variable arrays are 1-based; only the data's indexing is rebased.\n\nnote: Rebasing incurs a copy\nRebasing offset data to 1-based indexing allocates a copy of the array (once per infer call for batch inference; once per tick for the affected variable in streaming inference). A warning is emitted when this happens, gated by the warn keyword of infer (set warn = false to silence it). To avoid the copy entirely, pass a 1-based array — e.g. collect(x) or OffsetArrays.no_offset_view(x). Standard 1-based data incurs no copy and no warning.","category":"section"},{"location":"manuals/inference/partial-data/#Notes-and-caveats","page":"Partially-referenced (sparse) data","title":"Notes and caveats","text":"The provided data array must be indexable at every referenced position, i.e. its bounding box must contain all the indices your model touches. Extra (unreferenced) entries are fine.\nThis applies to data inputs. Latent (random) variables are normally fully connected; a partially-referenced latent array would leave half-edges in the graph and is not a supported construction.\nBecause unreferenced indices never become part of the graph, they incur no message-passing or memory cost — only the referenced sub-graph is built and run.\n\n","category":"section"},{"location":"manuals/inference/undefinedrules/#inference-undefinedrules","page":"Undefined message update rules","title":"Inference without explicit message update rules","text":"RxInfer utilizes the ReactiveMP.jl package as its inference backend. Typically, running inference with ReactiveMP.jl requires users to define a factor node using the @node macro and specify corresponding message update rules with the @rule macro. For background on what rules are and how they work, see Understanding Rules. Detailed instructions on implementing rules can be found in this section of the documentation. However, in this tutorial, we will explore an alternative approach that allows inference with default message update rule for custom factor nodes by defining only BayesBase.logpdf and BayesBase.insupport for a factor node, without needing explicit @rule specifications.\n\nnote: Note\nIn the context of message-passing based Bayesian inference, custom message update rules enhance precision and efficie
2ncy. These rules leverage the specific mathematical properties of the model's distributions and relationships, leading to more accurate updates and faster convergence. By incorporating domain-specific knowledge, custom rules improve the robustness and reliability of the inference process, particularly in complex models where default rules may be inadequate or inefficient.","category":"section"},{"location":"manuals/inference/undefinedrules/#A-simple-prior-likelihood-model","page":"Undefined message update rules","title":"A simple prior-likelihood model","text":"We start a simple model with a hidden variable p and observations y. Later in the tutorial we explore more advanced use-cases. In this particular case we assume that p follows a prior distribution and y are drawn from a likelihood distribution. The model can be defined as follows:\n\nusing RxInfer\n\n@model function simple_model(y, prior, likelihood)\n    p ~ prior\n    y .~ likelihood(p)\nend","category":"section"},{"location":"manuals/inference/undefinedrules/#Node-specifications","page":"Undefined message update rules","title":"Node specifications","text":"Next, we define structures for both the prior and the likelihood. Let's start with the prior. Assume that the p parameter is best described by a Beta distribution. We can define it as follows:\n\nnote: Note\nThe Distributions.jl package already provides a fully-featured implementation of Beta and Bernoulli distributions, including functions like logpdf and support checks. The example below redefines the Beta distribution structure and related functions solely for illustrative purposes. In practice, you often won't need to define these distributions yourself, as many of them has already been included in Distributions.jl.\n\nusing Distributions, BayesBase\n\nstruct BetaDistribution{A, B} <: ContinuousUnivariateDistribution\n    a::A\n    b::B\nend\n\n# Reuse `logpdf` from `Distributions.jl` for illustrative purposes\nBayesBase.logpdf(d::BetaDistribution, x) = logpdf(Beta(d.a, d.b), x)\nBayesBase.insupport(d::BetaDistribution, x::Real) = 0 <= x <= 1\n\nNext, we assume that y is a discrete dataset of true and false values. The logical choice for the likelihood distribution is the Bernoulli distribution.\n\nstruct BernoulliDistribution{P} <: DiscreteUnivariateDistribution\n    p::P\nend\n\n# Reuse `logpdf` from `Distributions.jl` for illustrative purposes\nBayesBase.logpdf(d::BernoulliDistribution, x) = logpdf(Bernoulli(d.p), x)\nBayesBase.insupport(d::BernoulliDistribution, x) = x === true || x === false\n\nThe next step is to register these structures as valid factor nodes:\n\n@node BetaDistribution Stochastic [out, a, b]\n@node BernoulliDistribution Stochastic [out, p]\n\nWhen specifying a node for our custom distributions, we must follow a specific edge ordering. The first edge is always out, which represents a sample in the logpdf function. All remaining edges must match the parameters of the distribution in the exact same order. For example, for the BetaDistribution, the node function is defined as (out, a, b) -> logpdf(BetaDistribution(a, b), out). This ensures that the node specification and the logpdf function correctly maps the distribution parameters to the sample output.\n\nnote: Note\nAlthough Beta is a conjugate prior for the parameter of the Bernoulli distribution, ReactiveMP and RxInfer are unaware of this and cannot exploit this information. To utilize conjugacy, refer to the custom node creation section of the documentation.","category":"section"},{"location":"manuals/inference/undefinedrules/#Generating-a-synthetic-dataset","page":"Undefined message update rules","title":"Generating a synthetic dataset","text":"Previously, we assumed that our dataset consists of discrete values: true and false. We can generate a synthetic dataset with these values as follows:\n\nusing StableRNGs, Plots\n\nhidden_p    = 1 / 3.1415 # a value between `0` and `1`\nndatapoints = 1_000      # number of observations\ndataset     = rand(StableRNG(42), Bernoulli(hidden_p), ndatapoints)\n\nbar([\"true\", \"false\"], [ count(==(true), dataset), count(==(false), dataset) ], label = \"dataset\")","category":"section"},{"location":"manuals/inference/undefinedrules/#Inference-with-a-rule-fallback","page":"Undefined message update rules","title":"Inference with a rule fallback","text":"Now, we can run inference with RxInfer. Since explicit rules for our nodes have not been defined, we can instruct the ReactiveMP backend to use fallback message update rules. Refer to the ReactiveMP documentation for available fallbacks. In this example, we will use the NodeFunctionRuleFallback structure, which uses the logpdf of the stochastic no
2de to approximate messages.\n\nnote: Note\nNodeFunctionRuleFallback employs a simple approximation for outbound messages, which may significantly degrade inference accuracy. Whenever possible, it is recommended to define proper message update rules.\n\nTo complete the inference setup, we must define an approximation method for posteriors using the @constraints macro. We will utilize the ExponentialFamilyProjection library to project an arbitrary function onto a member of the exponential family. More information on ExponentialFamilyProjection can be found in the Non-conjugate Inference section and in its official documentation.\n\nusing ExponentialFamilyProjection\n\n@constraints function projection_constraints()\n    # Use `Beta` from `Distributions.jl` as it is compatible with the `ExponentialFamilyProjection` library\n    q(p) :: ProjectedTo(Beta) \nend\n\nWith all components ready, we can proceed with the inference procedure:\n\nresult = infer(\n    model = simple_model(prior = BetaDistribution(1, 1), likelihood = BernoulliDistribution),\n    data = (y = dataset, ),\n    constraints = projection_constraints(),\n    options = (\n        rulefallback = NodeFunctionRuleFallback(),\n    )\n)\n\nnote: Note\nFor rulefallback = NodeFunctionRuleFallback() to function correctly, the node must be defined as Stochastic and the underlying object must be a subtype of Distribution from Distributions.jl.","category":"section"},{"location":"manuals/inference/undefinedrules/#Result-analysis","page":"Undefined message update rules","title":"Result analysis","text":"We can perform a simple analysis and compare the inferred value with the hidden value used to generate the actual dataset:\n\nusing Plots, StatsPlots\nusing Test #hide\n@test isapprox(hidden_p, mean(result.posteriors[:p]), atol=1e-2) #hide\nplot(result.posteriors[:p], label = \"posterior of p\", fill = 0, fillalpha = 0.2)\nvline!([ hidden_p ], label = \"hidden p\")\n\nAs shown, the estimated posterior is quite close to the actual hidden value of p used during the inference procedure.","category":"section"},{"location":"manuals/inference/undefinedrules/#inference-undefinedrules-fusedelta","page":"Undefined message update rules","title":"Fusing deterministic transformations with stochastic nodes","text":"One of the limitations of the NodeFunctionRuleFallback implementation is that it does not support Deterministic or Delta no
2des. However, it is possible to combine a deterministic transformation with a stochastic node, such as Gaussian. For instance, consider a dataset drawn from the Normal distribution, where the mean parameter has been transformed by a known function, and the true hidden variable is h.\n\nusing ExponentialFamily, Distributions, Plots, StableRNGs\n\nhidden_h = 2.3\nhidden_t = 0.5\n\nknown_transformation(h) = exp(h)\n\nhidden_mean = known_transformation(hidden_h)\nndatapoints = 50\ndataset = rand(StableRNG(42), NormalMeanPrecision(hidden_mean, hidden_t), ndatapoints)\n\nhistogram(dataset; normalize = :pdf)\n\nThe model can be defined as follows:\n\nusing RxInfer \n\n@model function mymodel(y, prior_h, prior_t)\n    h ~ prior_h\n    t ~ prior_t\n    y .~ Normal(mean = known_transformation(h), precision = t)\nend\n\nInference in this model is challenging because the known_transformation function is explicitly used as a factor node, requiring special approximation rules. These rules are covered in a separate section. Here, we demonstrate a different approach that modifies the model structure to run inference without needing to approximate messages around a deterministic node.\n\nFirst, we define our custom transformed Normal distribution:\n\nusing BayesBase\n\nstruct TransformedNormalDistribution{H, T} <: ContinuousUnivariateDistribution\n    h::H\n    t::T\nend\n\n# We integrate the `known_transformation` within the `logpdf` function\n# This way, it won't be an explicit factor node but hidden within the `logpdf` of another node\nBayesBase.logpdf(dist::TransformedNormalDistribution, x) = logpdf(NormalMeanPrecision(known_transformation(dist.h), dist.t), x)\nBayesBase.insupport(dist::TransformedNormalDistribution, x) = true\n\n@node TransformedNormalDistribution Stochastic [out, h, t]\n\nNext, we tweak the model structure:\n\n@model function mymodel(y, prior_h, prior_t)\n    h ~ prior_h\n    t ~ prior_t\n    y .~ TransformedNormalDistribution(h, t)\nend\n\nWe use the following priors, constraints, and initialization:\n\nusing ExponentialFamilyProjection\n\nprior_h = LogNormal(0, 1)\nprior_t = Gamma(1, 1)\n\nconstraints = @constraints begin\n    q(h, t) = q(h)q(t)\n    q(h) :: ProjectedTo(LogNormal)\n    q(t) :: ProjectedTo(Gamma)\nend\n\ninitialization = @initialization begin\n    q(t) = Gamma(1, 1)\nend\n\nnote: Note\nThe ProjectedTo macro has a parameters field that allows for different hyperparameters, which may improve accuracy or convergence speed. Refer to the ExponentialFamilyProjection documentation for more information.","category":"section"},{"location":"manuals/inference/undefinedrules/#Inference-with-a-rule-fallback-2","page":"Undefined message update rules","title":"Inference with a rule fallback","text":"Now we are ready to run the inference procedure:\n\nresult = infer(\n    model = mymodel(prior_h = prior_h, prior_t = prior_t),\n    data = (y = dataset,),\n    constraints = constraints,\n    initialization = initialization,\n    iterations = 50,\n    options = (\n        rulefallback = NodeFunctionRuleFallback(),\n    )\n)","category":"section"},{"location":"manuals/inference/undefinedrules/#Result-analysis-2","page":"Undefined message update rules","title":"Result analysis","text":"Finally, let's plot the resulting posteriors for each VMP iteration:\n\nusing Test #hide\n@test isapprox(mean(result.posteriors[:h][end]), hidden_h, atol=0.1) #hide\n\n@gif for (i, q) in enumerate(zip(result.posteriors[:h], result.posteriors[:t]))\n    p1 = plot(1:0.01:3, q[1], label = \"q(h) iteration $i\", fill = 0, fillalpha = 0.2)\n    p1 = vline!([hidden_h], label = \"hidden h\")\n    \n    p2 = plot(0:0.01:1, q[2], label = \"q(t) iteration $i\", fill = 0, fillalpha = 0.2)\n    p2 = vline!([hidden_t], label = \"hidden t\")\n    \n    plot(p1, p2)\nend fps = 15\n\nWe can see that the inference results are able to recover the actual value of hidden h that has been used to generate the synthetic dataset. In conclusion, this example demonstrates that by integrating deterministic transformations within the logpdf function of a stochastic no
2de, we can bypass the limitations of NodeFunctionRuleFallback in handling deterministic nodes.\n\n","category":"section"},{"location":"contributing/guidelines/#contributing-guidelines","page":"Contribution guidelines","title":"Contribution Guidelines","text":"Welcome to the contribution guidelines for RxInfer.jl. Here you'll find detailed instructions on how to contribute effectively to the project.","category":"section"},{"location":"contributing/guidelines/#Reporting-bugs","page":"Contribution guidelines","title":"Reporting bugs","text":"If you encounter any bugs while using the software, please report them via GitHub issues. To ensure efficient bug resolution, please provide comprehensive and reproducible bug reports. Include details such as the versions of Julia and RxInfer you're using, along with a concise description of the issue. Additionally, attach relevant code snippets, test cases, screenshots, or any other pertinent information that could aid in replicating and addressing the problem.","category":"section"},{"location":"contributing/guidelines/#Nightly-Julia-status","page":"Contribution guidelines","title":"Nightly Julia status","text":"Check the badge below to see if RxInfer can be installed on a Julia nightly version. A failing badge may indicate issues with RxInfer or its dependencies. Click on the badge to access the latest evaluation report.\n\n(Image: PkgEval)","category":"section"},{"location":"contributing/guidelines/#Suggesting-features","page":"Contribution guidelines","title":"Suggesting features","text":"We encourage proposals for new features. Before submitting a feature request, consider the following:\n\nDetermine if the feature necessitates changes to the core RxInfer code. If not, such as adding a factor node for a specific application, consider local extensions in your script/notebook or creating a separate repository for your extensions.\nFor feature implementations requiring significant changes to the core RxInfer code, open a GitHub issue to discuss your proposal before proceeding with implementation. This allows for thorough deliberation and avoids investing time in features that may be challenging to integrate later on.","category":"section"},{"location":"contributing/guidelines/#Contributing-code","page":"Contribution guidelines","title":"Contributing code","text":"","category":"section"},{"location":"contributing/guidelines/#Installing-RxInfer","page":"Contribution guidelines","title":"Installing RxInfer","text":"For development purposes, it's recommended to use the dev command from the Julia package manager to install RxInfer. Use your fork's URL in the dev command to work on your forked version. For example:\n\n] dev [email protected]:your_username/RxInfer.jl.git\n\nThe dev command clones RxInfer to ~/.julia/dev/RxInfer. All local changes to RxInfer code will be reflected in imported code.\n\nnote: Note\nIt might also be useful to install Revise.jl package as it allows you to modify code and use the changes without restarting Julia.","category":"section"},{"location":"contributing/guidelines/#Core-dependencies","page":"Contribution guidelines","title":"Core dependencies","text":"RxInfer.jl depends heavily on the core packages ReactiveMP.jl, GraphPPL.jl, and Rocket.jl. Ensure RxInfer.jl is updated whenever any of these packages undergo major updates or API changes. While making changes to RxInfer.jl, developers are advised to use the dev command for these packages as well. Note that standard Julia testing utilities ignore the local development environment and test the package with the latest released versions of core dependencies. Refer to the Makefile section below to learn how to test RxInfer.jl with locally installed core dependencies.","category":"section"},{"location":"contributing/guidelines/#Committing-code","page":"Contribution guidelines","title":"Committing code","text":"We use the standard GitHub Flow workflow where all contributions are added through pull requests. To contribute:\n\nFork the repository\nCommit your contributions to your fork\nCreate a pull request on the main branch of the RxInfer repository.\n\nBefore opening a pull request, ensure all tests pass without errors.\n\nnote: Note\nUse make test and make docs commands to verify that all tests and documentation build correctly. See the Makefile section below for detailed command descriptions.","category":"section"},{"location":"contributing/guidelines/#Style-conventions","page":"Contribution guidelines","title":"Style conventions","text":"We use the default Julia style guide. There are a couple of important modifications to the Julia style guide to take into account:\n\nUse 4 spaces for indentation\nType names use UpperCamelCase. For example: AbstractFactorNode, RandomVariable, etc.\nFunction names are lowercase with underscores, when necessary. For example: activate!, randomvar, as_variable, etc.\nVariable names and function arguments use snake_case\nThe name of a method that modifies its argument(s) must end in !\n\nnote: Note\nThe RxInfer repository contains scripts to automatically format code according to our guidelines. Use the make format command to fix codestyle. This command overwrites files. Use make lint to run a linting procedure without overwriting the actual source files.","category":"section"},{"location":"contributing/guidelines/#Unit-tests","page":"Contribution guidelines","title":"Unit tests","text":"We use the test-driven development (TDD) methodology for RxInfer development. Aim for comprehensive test coverage, ensuring tests cover each piece of added code.\n\nAll unit tests are located in the /test/ directory. The /test/ directory follows the structure of the /src/ directory. Each test file should have the following filename format: *_tests.jl. Some tests are also present in jldoctest docs annotations directly in the source code. See Julia's documentation about doctests.\n\nThe tests can be evaluated by running the following command in the Julia REPL:\n\n] test RxInfer\n\nIn addition, tests can be evaluated by running the following command in the RxInfer root directory:\n\nmake test\n\nnote: Note\nUse make devtest to use local dev-ed versions of the core packages.","category":"section"},{"location":"contributing/guidelines/#Makefile","page":"Contribution guidelines","title":"Makefile","text":"RxInfer.jl uses Makefile for most common operations:\n\nmake help: Shows help snippet\nmake test: Run tests, supports extra arguments\nmake test test_args=\"distributions:normal_mean_variance\" would run tests only from distributions/test_normal_mean_variance.jl\nmake test test_args=\"distributions:normal_mean_variance models:lgssm\" would run tests both from distributions/test_normal_mean_variance.jl and models/test_lgssm.jl\nmake test dev=true would run tests while using dev-ed versions of core packages\nmake devtest: Alias for the make test dev=true ...\nmake docs: Compile documentation\nmake devdocs: Same as make docs, but uses dev-ed versions of core packages\nmake lint: Check codestyle\nmake format: Check and fix codestyle \n\nnote: Note\nCore packages include ReactiveMP.jl, GraphPPL.jl and Rocket.jl. When using any of the dev commands from the Makefile those packages must be present in the Pkg.devdir() directory.","category":"section"},{"location":"contributing/guidelines/#CHANGELOG.md","page":"Contribution guidelines","title":"CHANGELOG.md","text":"This project uses a CHANGELOG.md to document all user-visible changes. Regardless of the contribution type or size, please include a concise, descriptive entry for your changes in the CHANGELOG before merging. This ensures a clear upgrade path for users and helps maintainers track the evolution of features, fixes, and improvements in a consistent way.","category":"section"},{"location":"contributing/guidelines/#Opting-out","page":"Contribution guidelines","title":"Opting out","text":"Not all changes need a changelog entry; some are not user-visible, like changing CI or some internal refactoring. For those it is possible to disable the check.\n\nTo do so, apply the no changelog label to the PR. The
2n the changelog check will re-run and succeed automatically. This label can also be applied directly when opening the PR so the check will be automatically omitted.\n\n","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming","page":"Reactive Programming","title":"Reactive Programming","text":"RxInfer is built around a reactive programming paradigm: inference is not a batch procedure you invoke, but a graph of dependencies that continuously reacts to new information. This is what gives RxInfer its characteristic strengths — real-time updates, streaming data support, and schedule-free execution — and what most clearly sets it apart from sampling- or autodiff-based inference engines.\n\nIf you have used reactive frameworks such as RxJS, SwiftUI or Rocket.jl, the mental model will feel familiar. If not, the key shift is summarised on this page.","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming-streams","page":"Reactive Programming","title":"Streams, not values","text":"In a classical inference engine, a \"message\" or \"marginal\" is a value you compute at a specific moment. In RxInfer, it is a stream of values over time — an observable.\n\nClassical view               ┆  Reactive view\n────────────────────────────  ┆  ─────────────────────────────────────────\nmessage = compute(...)       ┆  message_stream : [m₀, m₁, m₂, m₃, ...]\n(one snapshot, recompute)    ┆  (keeps emitting as inputs change)\n\nEvery variable and factor node in the factor graph owns such a stream. When a stream emits a new value, all downstream nodes subscribed to it react — re-running the local message passing update and, in turn, emitting into their own streams. Posteriors update automatically as long as new observations keep arriving.","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming-execution","page":"Reactive Programming","title":"Dependency-driven execution","text":"Because the graph is a web of subscriptions, you do not schedule inference. The data flow decides what gets recomputed and when:\n\nA new observation is pushed into the graph.\nOnly the nodes whose inputs actually changed fire their update rules.\nChanges propagate outward, hop by hop, through subscribed neighbours.\nNodes unaffected by the update stay idle — zero wasted work.\n\nThe outcome is two-fold. You get incremental updates (no full recomputation when a single observation arrives) and schedule-free execution (no forward/backward pass to plan in advance). Both are essential for the real-time and streaming workloads RxInfer targets — see Streaming (online) inference for the streaming inference API.","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming-benefits","page":"Reactive Programming","title":"What reactivity enables","text":"Practical capabilities that come almost for free out of the reactive model:\n\nReal-time inference — sensor readings, signal processing, robotics control: observations flow in, posteriors flow out.\nOnline learning — long-running models that keep refining themselves as data arrives, without periodic re-fitting.\nSelective updates — only the part of the graph touched by new data recomputes, which matters a lot on large state-space models.\nNatural support for auto-updates — posteriors from one time step can be wired as priors for the next through autoupdates.","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming-rocket","page":"Reactive Programming","title":"Rocket.jl under the hood","text":"The reactive machinery comes from Rocket.jl, Julia's library for observables and streams. RxInfer and ReactiveMP wrap Rocket's primitives — observables, subscribers, operators — into the domain-specific streams used by inference:\n\nMessageObservable — a stream of messages flowing along one direction of an edge.\nMarginalObservable — a stream of posterior marginals at a variable node.\n\nYou rarely touch these directly, but they are what make the whole system tick. Browsing the Rocket documentation is the fastest way to build a deep intuition for how RxInfer actually executes.","category":"section"},{"location":"concepts/reactive-programming/#concepts-reactive-programming-deeper","page":"Reactive Programming","title":"For deeper understanding","text":"Rocket.jl — the reactive-programming foundation.\nReactiveMP.jl — how messages are scheduled via reactive streams.\nReactive Message Passing for Scalable Bayesian Inference — scalability and real-time properties of the approach.\nReactive Probabilistic Programming for Scalable Bayesian Inference — PhD dissertation outlining the core ideas behind RxInfer's reactive approach.\nStreaming (online) inference — user-facing API for streaming and real-time scenarios.\n\n","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-inference","page":"Static vs. Streamlined","title":"Static vs. Streaming vs. Batched Inference","text":"This guide explains the key differences between the three main inference approaches in RxInfer: Static Inference, Streaming (Online) Inference, and Batched Inference. We'll explore when to use each approach, their pros and cons, and demonstrate how to convert a model between these approaches using a Linear Gaussian State Space Model (LGSSM) as an example.\n\nAlso read about Static Inference and Streaming Inference for detailed information about each approach.","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-overview","page":"Static vs. Streamlined","title":"Overview of Inference Approaches","text":"","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-static","page":"Static vs. Streamlined","title":"Static Inference","text":"Static inference processes a complete dataset all at once. It's the traditional approach where you have all your data available upfront and want to compute posterior distributions for all latent variables simultaneously.\n\nKey characteristics:\n\nProcesses entire dataset in one go\nReturns InferenceResult with final posteriors\nBest for offline analysis and batch processing\nMemory usage scales with dataset size\nNo real-time updates\n\nWhen to use:\n\nYou have a complete dataset\nOffline analysis and exploration\nBatch processing workflows\nWhen you need all posteriors at once","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-streaming","page":"Static vs. Streamlined","title":"Streaming (Online) Inference","text":"Streaming inference processes data points one at a time as they arrive, continuously updating beliefs. It's designed for real-time applications where data comes in sequentially.\n\nKey characteristics:\n\nProcesses one observation at a time\n
2Returns RxInferenceEngine with reactive streams\nReal-time belief updates\nMemory usage can be controlled with history buffers\nSupports autoupdates for dynamic priors\n\nWhen to use:\n\nReal-time data streams\nOnline learning scenarios\nWhen you need immediate updates\nInteractive applications","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-batched","page":"Static vs. Streamlined","title":"Batched Inference","text":"Batched inference is a hybrid approach that processes data in chunks or batches, offering a middle ground between static and streaming approaches.\n\nKey characteristics:\n\nProcesses data in configurable batch sizes\nBalances memory usage and update frequency\nCan be more efficient than streaming for large datasets\nAllows for batch-level autoupdates\n\nWhen to use:\n\nLarge datasets that don't fit in memory\nWhen you want regular but not real-time updates\nBatch processing with some streaming benefits\nMemory-constrained environments","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-comparison","page":"Static vs. Streamlined","title":"Comparison Table","text":"Aspect Static Streaming Batched\nData Processing All at once One at a time In chunks\nMemory Usage High (scales with dataset) Low (controlled) Medium (configurable)\nUpdate Frequency Once (final result) Real-time Batch-level\nLatency High (wait for all data) Low (immediate) Medium (batch-dependent)\nUse Case Offline analysis Real-time systems Hybrid scenarios\nReturn Type InferenceResult RxInferenceEngine RxInferenceEngine\nAutoupdates No Yes Yes\nHistory Tracking No Yes Yes","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-example","page":"Static vs. Streamlined","title":"Practical Example: Linear Gaussian State Space Model","text":"Let's demonstrate the differences between these approaches using a Linear Gaussian State Space Model (LGSSM). This model is commonly used in time series analysis, signal processing, and robotics.","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-model","page":"Static vs. Streamlined","title":"Model Definition","text":"Our LGSSM has the following structure:\n\nState transition: xₜ ~ Normal(Axₜ₋₁, Q)\nObservation: yₜ ~ Normal(Cxₜ, R)\nInitial state: x₀ ~ Normal(μ₀, Σ₀)\n\nWhere:\n\nxₜ is the hidden state at time t\nyₜ is the observation at time t\nA is the state transition matrix\nQ is the state noise covariance\nC is the observation matrix\nR is the observation noise covariance\n\nusing RxInfer, Distributions, Plots, StableRNGs\n\n# Set random seed for reproducibility\nrng = StableRNG(42)\n\n# Model parameters\nn_states = 2\nn_obs = 1\nT = 256  # Number of time steps\n\n# State transition matrix (simple random walk + trend)\nA = [1.0 1.0; 0.0 0.9]\n# State noise covariance\nQ = [0.1 0.0; 0.0 0.1]\n# Observation matrix\nC = [1.0, 0.0]\n# Observation noise variance\nR = 5.0\n# Initial state distribution\nμ₀ = [0.0, 0.0]\nΣ₀ = [1.0 0.0; 0.0 1.0]\n\n# Generate synthetic data\nfunction generate_lgssm_data(rng, A, Q, C, R, μ₀, Σ₀, T)\n    x = Vector{Float64}[]\n    y = Float64[]\n\n    # Initial state\n    push!(x, rand(rng, MvNormal(μ₀, Σ₀)))\n\n    # Generate states and observations\n    for t in 1:T\n        if t > 1\n            push!(x, A * x[end] + rand(rng, MvNormal(zeros(n_states), Q)))\n        end\n        push!(y, dot(C, x[end]) + rand(rng, NormalMeanVariance(0, R)))\n    end\n\n    return x, y\nend\n\n# Generate data\ntrue_states, observations = generate_lgssm_data(rng, A, Q, C, R, μ₀, Σ₀, T)\n\np = plot(getindex.(true_states, 1), label=\"True state 1\")\nscatter!(getindex.(observations, 1), label=\"Observations\")","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-static-impl","page":"Static vs. Streamlined","title":"1. Static Inference Implementation","text":"First, let's implement the static version that processes all data at once:\n\n@model function lgssm_static(y, A, Q, C, R, μ₀, Σ₀)\n    # Initial state\n    x₀ ~ MvNormal(mean=μ₀, cov=Σ₀)\n\n    # State sequence\n    x[1] ~ MvNormal(mean=A * x₀, cov=Q)\n\n    # Observations\n    y[1] ~ Normal(mean=dot(C, x[1]), var=R)\n\n    # Subsequent states and observations\n    for t in 2:length(y)\n        x[t] ~ MvNormal(mean=A * x[t-1], cov=Q)\n        y[t] ~ Normal(mean=dot(C, x[t]), var=R)\n    end\nend\n\n# Ru
2n static inference\nstatic_results = infer(\n    model=lgssm_static(A=A, Q=Q, C=C, R=R, μ₀=μ₀, Σ₀=Σ₀),\n    data=(y=observations,)\n)\n\nplot(getindex.(true_states, 1), label=\"True state 1\")\nscatter!(observations, label=\"Observations\")\nplot!(getindex.(mean.(static_results.posteriors[:x]), 1), ribbon=3 .* getindex.(std.(static_results.posteriors[:x]), 1, 1), label=\"Static inference\")","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-streaming-impl","page":"Static vs. Streamlined","title":"2. Streaming Inference Implementation","text":"Now let's convert this to a streaming version that processes observations one at a time:\n\n@model function lgssm_streaming(y, x_prev, A, Q, C, R)\n    # State transition from previous state\n    x ~ MvNormal(mean=A * x_prev, cov=Q)\n\n    # Observation\n    y ~ Normal(mean=dot(C, x), var=R)\nend\n\n# Autoupdates for the previous state\nlgssm_autoupdates = @autoupdates begin\n    x_prev = mean(q(x))\nend\n\n# Initialization\nlgssm_init = @initialization begin\n    q(x) = MvNormal(mean = μ₀, cov = Σ₀)\nend\n\n# Run streaming inference\nstreaming_engine = infer(\n    model=lgssm_streaming(A=A, Q=Q, C=C, R=R),\n    data=(y=observations,),\n    autoupdates=lgssm_autoupdates,\n    initialization=lgssm_init,\n    keephistory=T,\n    autostart=true\n)\n\nplot(getindex.(true_states, 1), label=\"True state 1\")\nscatter!(observations, label=\"Observations\")\nplot!(getindex.(mean.(streaming_engine.history[:x]), 1), ribbon=3 .* getindex.(std.(streaming_engine.history[:x]), 1, 1), label=\"Streaming inference\")","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-batched-impl","page":"Static vs. Streamlined","title":"3. Batched Inference Implementation","text":"Finally, let's implement a batched version that processes data in chunks:\n\n# Define batch size\nbatch_size = 2\nn_batches = ceil(Int, T / batch_size)\n\n# Create batched datastream\nfunction create_batched_observations(data, batch_size)\n    batches = []\n    for i in 1:batch_size:length(data)\n        end_idx = min(i + batch_size - 1, length(data))\n        push!(batches, data[i:end_idx])\n    end\n    return batches\nend\n\nbatched_observations = create_batched_observations(observations, batch_size)\n\n# Batched model (processes a batch of observations)\n@model function lgssm_batched(y, x_prev, A, Q, C, R, batch_size)\n    # First state in batch\n    x[1] ~ MvNormal(mean=A * x_prev, cov=Q)\n    y[1] ~ Normal(mean=dot(C, x[1]), var=R)\n\n    # Subsequent states in batch\n    for t in 2:batch_size\n        x[t] ~ MvNormal(mean=A * x[t-1], cov=Q)\n        y[t] ~ Normal(mean=dot(C, x[t]), var=R)\n    end\nend\n\n# Autoupdates for batched processing\nlgssm_batched_autoupdates = @autoupdates begin\n    x_prev = mean(q(x[batch_size]))  # Use last state from previous batch\nend\n\n# Run batched inference\nprintln(\"Running batched inference...\")\nbatched_engine = infer(\n    model=lgssm_batched(A=A, Q=Q, C=C, R=R, batch_size=batch_size),\n    data=(y=batched_observations,),\n    autoupdates=lgssm_batched_autoupdates,\n    initialization=lgssm_init,\n    returnvars=(:x,),\n    keephistory=n_batches,\n    autostart=true,\n    free_energy=true\n)\n\n\nmerged_history = vcat(batched_engine.history[:x]...)\n\nplot(getindex.(true_states, 1), label=\"True state 1\")\nscatter!(observations, label=\"Observations\")\nplot!(getindex.(mean.(merged_history), 1), ribbon=3 .* getindex.(std.(merged_history), 1, 1), label=\"Batched inference\")","category":"section"},{"location":"manuals/inference/static-vs-streamlined/#manual-static-vs-streamlined-summary","page":"Static vs. Streamlined","title":"Summary","text":"The choice between static, streaming, and batched inference depends on your specific requirements:\n\nStatic inference is best for offline analysis with complete datasets\nStreaming inference excels at real-time applications with continuous data\nBatched inference provides a middle ground for large datasets with memory constraints\n\nThe Linear Gaussian State Space Model example demonstrates how the same underlying model can be adapted to different inference paradigms. All three approaches should produce similar results, but they differ significantly in their computational characteristics and use cases.\n\nRemember that you can always start with static inference to validate your model and then convert to streaming or batched approaches based on your deployment requirements.\n\n","category":"section"},{"location":"manuals/variational/constraints-specification/#user-guide-constraints-specification","page":"Constraints specification","title":"Constraints Specification","text":"RxInfer.jl uses a macro called @constraints from GraphPPL to add extra constraints during the inference process. For details on using the @constraints macro, you can check out the official documentation of GraphPPL.","category":"section"},{"location":"manuals/variational/constraints-specification/#user-guide-constraints-specification-background","page":"Constraints specification","title":"Backgroun
2d and example","text":"Here we briefly cover the mathematical aspects of constraints specification. For additional information and relevant links, please refer to the Bethe Free Energy section. In essence, RxInfer performs Variational Inference (via message passing) given specific constraints mathcalQ:\n\nq^* = argmin_q(s) in mathcalQFq(haty) = mathbbE_q(s)leftlog fracq(s)p(s y=haty) right\n\nThe @model macro specifies generative model p(s, y) where s is a set of random variables and y is a set of observations. In a nutshell the goal of probabilistic programming is to find p(s|y). RxInfer approximates p(s|y) with a proxy distribution q(s) using the KL divergence and the Bethe Free Energy optimisation procedure. By default there are no extra factorization constraints on q(s) and the optimal solution is q(s) = p(s|y).\n\nFor certain problems, it may be necessary to adjust the set of constraints mathcalQ (also known as the variational family of distributions) to either improve accuracy at the expense of computational resources or reduce accuracy to conserve computational resources. Sometimes, we are compelled to impose certain constraints because otherwise, the problem becomes too challenging to solve within a reasonable timeframe.\n\nFor instance, consider the following model:\n\nusing RxInfer\n\n@model function iid_normal(y)\n    μ  ~ Normal(mean = 0.0, variance = 1.0)\n    τ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = τ)\nend\n\nIn this model, we characterize all observations in a dataset y as a Normal distribution with mean μ and precision τ. It's reasonable to assume that the latent variables μ and τ are jointly independent, thereby rendering their joint posterior distribution as:\n\nq(μ τ) = q(μ)q(τ)\n\nIf we would write the variational family of distribution for such an assumption, it would be expressed as:\n\nmathcalQ = left q  q(μ τ) = q(μ)q(τ) right\n\nWe can express this constraint with the @constraints macro:\n\nconstraints = @constraints begin \n    q(μ, τ) = q(μ)q(τ)\nend\n\nand pass the created constraints object to the infer function:\n\n# We need to specify initial marginals, since with the constraints \n# the problem becomes inherently iterative (we could also specify initial for the `μ` instead)\ninit = @initialization begin \n    q(τ) = vague(Gamma)\nend\n\nresult = infer(\n    model       = iid_normal(),\n    # Sample data from mean `3.1415` and precision `2.7182`\n    data        = (y = rand(NormalMeanPrecision(3.1415, 2.7182), 1000), ),\n    constraints = constraints,\n    initialization = init,\n    iterations     = 25\n)\n\nprintln(\"Estimated mean of `μ` is \", mean(result.posteriors[:μ][end]), \" with standard deviation \", std(result.posteriors[:μ][end]))\nprintln(\"Estimated mean of `τ` is \", mean(result.posteriors[:τ][end]), \" with standard deviation \", std(result.posteriors[:τ][end]))\n\nWe observe that the estimates tend to slightly deviate from what the real values are.  This behavior is a known characteristic of inference with the aforementioned constraints, often referred to as Mean Field constraints.","category":"section"},{"location":"manuals/variational/constraints-specification/#General-syntax","page":"Constraints specification","title":"General syntax","text":"You can use the @constraints macro with either a regular Julia function or a single begin ... end block. Both ways are valid, as shown below:\n\nusing RxInfer #hide\n\n# `functional` style\n@constraints function create_my_constraints()\n    q(μ, τ) = q(μ)q(τ)\nend\n\n# `block` style\nmyconstraints = @constraints begin \n    q(μ, τ) = q(μ)q(τ)\nend\n\nnothing #hide\n\nThe function-based syntax can also take arguments, like this:\n\n@constraints function make_constraints(mean_field)\n    # Specify mean-field only if the flag is `true`\n    if mean_field\n        q(μ, τ) = q(μ)q(τ)\n    end\nend\n\nmyconstraints = make_constraints(true)\n\nnote: Note\nRxInfer exports MeanField and other prespecified constraints as convenient aliases that can be used directly in the constraints argument of infer. See Prespecified constraints below for more details.","category":"section"},{"location":"manuals/variational/constraints-specification/#Marginal-and-message-form-constraints","page":"Constraints specification","title":"Marginal and message form constraints","text":"To specify marginal or message form constraints, the @constraints macro uses the :: operator (in a somewhat similar way to how Julia uses it for multiple dispatch type specification). Read more about available functional form constraints in the Built-in Functional Forms section.\n\nAs an example, the following constraint:\n\n@constraints begin \n    q(x) :: PointMassFormConstraint()\nend\n\nindicates that the resulting marginal of the variable (or array of variables) named x must be approximated with a PointMass object. Message passing based algorithms compute posterior marginals as a normalized product of two colliding messages on corresponding edges of a factor graph. In a few words q(x)::PointMassFormConstraint reads as:\n\nmathrmapproximate q(x
2) = fracoverrightarrowmu(x)overleftarrowmu(x)int overrightarrowmu(x)overleftarrowmu(x) mathrmdxmathrmasPointMass\n\nSometimes it might be useful to set a functional form constraint on messages too, for example if it is essential to keep a specific Gaussian parametrisation or if some messages are intractable and need approximation. To set a message form constraint, the @constraints macro uses μ(...) instead of q(...):\n\n@constraints begin \n    q(x) :: PointMassFormConstraint()\n    μ(x) :: SampleListFormConstraint(1000)\n    # it is possible to assign different form constraints on the same variable \n    # both for the marginal and for the messages \nend\n\nThe @constraints macro understands \"stacked\" form constraints. For example, the following form constraint\n\n@constraints begin \n    q(x) :: SampleListFormConstraint(1000) :: PointMassFormConstraint()\nend\n\nindicates that the q(x) first must be approximated with a SampleList and in addition the result of this approximation should be approximated as a PointMass. \n\nnote: Note\nNot all combinations of \"stacked\" form constraints are compatible between each other.\n\nYou can find more information about built-in functional form constraints in the Built-in Functional Forms section. In addition, the ReactiveMP library documentation explains the functional form interfaces and shows how to build a custom functional form constraint that is compatible with RxInfer.jl and ReactiveMP.jl inference engine.","category":"section"},{"location":"manuals/variational/constraints-specification/#Factorization-constraints-on-posterior-distribution-q","page":"Constraints specification","title":"Factorization constraints on posterior distribution q","text":"As has been mentioned above, inference may be not tractable for every model without extra factorization constraints. To circumvent this, RxInfer.jl allows for extra factorization constraints, for example:\n\n@constraints begin \n    q(x, y) = q(x)q(y)\nend\n\nspecifies a so-called mean-field assumption on variables x and y in the model. Furthermore, if x is an array of variables in our model we may induce an extra mean-field assumption on x in the following way.\n\n@constraints begin \n    q(x) = q(x[begin])..q(x[end])\n    q(x, y) = q(x)q(y)\nend\n\nThese constraints specify a mean-field assumption between variables x and y (either a single variable or a collection of variables) and additionally specify a mean-field assumption on variables x_i.\n\nnote: Note\n@constraints macro does not support matrix-based collections of variables. E.g. it is not possible to write q(x[begin, begin])..q(x[end, end]). Use q(x[begin])..q(x[end]) instead.\n\nRead more about the @constraints macro in the official documentation of GraphPPL.","category":"section"},{"location":"manuals/variational/constraints-specification/#Constraints-in-submodels","page":"Constraints specification","title":"Constraints in submodels","text":"RxInfer allows you to define your generative model hierarchically, using previously defined @model definitions as submodels in larger models. Because of this, users need to specify their constraints hierarchically as well to avoid ambiguities. Consider the following example:\n\n@model function inner_inner(τ, y)\n    y ~ Normal(mean = τ[1], var = τ[2])\nend\n\n@model function inner(θ, α)\n    β ~ Normal(mean = 0.0, var = 1.0)\n    α ~ Gamma(shape = β, rate = 1.0)\n    α ~ inner_inner(τ = θ)\nend\n\n@model function outer()\n    local w\n    for i = 1:5\n        w[i] ~ inner(θ = Gamma(shape = 1.0, rate = 1.0))\n    end\n    y ~ inner(θ = w[2:3])\nend\n\nTo access the variables in the submodels, we use the for q in __submodel__ syntax, which will allow us to specify constraints over variables in the context of an inner submodel:\n\n@constraints begin\n    for q in inner\n        q(α) :: PointMassFormConstraint()\n        q(α, β) = q(α)q(β)\n    end\nend\n\nSimilarly, we can specify constraints over variables in the context of the innermost submodel by using the for q in __submodel__ syntax twice:\n\n@constraints begin\n    for q in inner\n        for q in inner_inner\n            q(y, τ) = q(y)q(τ[1])q(τ[2])\n        end\n        q(α) :: PointMassFormConstraint()\n        q(α, β) = q(α)q(β)\n    end\nend\n\nThe for q in __submodel__ applies the constraints specified in this code block to all instances of __submodel__ in the current context. If we want to apply constraints to a specific instance of a submodel, we can use the for q in (__submodel__, __identifier__) syntax, where __identifier__ is a counter integer. For example, if we want to specify constraints on the first instance of inner in our outer model, we can do so with the following syntax:\n\n@constraints begin\n    for q in (inner, 1)\n        q(α) :: PointMassFormConstraint()\n        q(α, β) = q(α)q(β)\n    end\nend\n\nFactorization constraints specified in a context propagate to their child submodels. This means that we can specify factorization constraints over variables where the factor node that c
2onnects the two is in a submodel, without having to specify the factorization constraint in the submodel itself. For example, if we want to specify a factorization constraint between w[2] and w[3] in our outer model, we can specify it in the context of outer, and RxInfer will recognize that these variables are connected through the Normal node in the inner_inner submodel:\n\n@constraints begin\n    q(w) = q(w[begin])..q(w[end])\nend","category":"section"},{"location":"manuals/variational/constraints-specification/#Inline-constraints-on-submodel-calls","page":"Constraints specification","title":"Inline constraints on submodel calls","text":"Constraints can also be specified inline at the call site of a submodel using the where keyword. This is useful when you want to attach constraints to a specific invocation without modifying the outer model's @constraints block. For example:\n\n@model function outer_toy_model(a, b, c)\n    a ~ toy_model(y = b, z = c) where {\n        constraints = @constraints begin\n            q(x, y, z) = q(x, y)q(z)\n            q(x) :: Normal\n        end\n    }\nend\n\nThe where { constraints = ... } syntax accepts any constraint set produced by the @constraints macro. Constraint sets defined with the @constraints function form can also be passed by reference:\n\n@constraints function my_constraints()\n    q(x, y, z) = q(x, y)q(z)\n    q(x) :: Normal\nend\n\n@model function outer_toy_model(a, b, c)\n    a ~ toy_model(y = b, z = c) where { constraints = my_constraints() }\nend\n\nInline constraints apply only to the specific submodel invocation they are attached to and propagate to any submodels nested within it. Their priority relative to other constraint sources, from highest to lowest, is:\n\nExternal constraints — passed at model creation via for q in submodel or for q in (submodel, index) blocks.\nInline constraints — specified with where { constraints = ... } at the call site.\nDefault constraints — defined via GraphPPL.default_constraints.\n\nThis means that if external constraints also target the same submodel, they will override the inline constraints.\n\nnote: Combining inline constraints and initialization\nWhen specifying both constraints and initialization inline on the same submodel call, each macro expression must be wrapped in parentheses so that Julia parses the where { ... } block correctly:@model function outer_toy_model(a, b, c)\n    a ~ toy_model(y = b, z = c) where {\n        constraints = (@constraints begin\n            q(x, y, z) = q(x, y)q(z)\n            q(x) :: Normal\n        end),\n        initialization = (@initialization begin\n            q(x) = vague(NormalMeanVariance)\n        end)\n    }\nendWithout the parentheses the parser will raise a syntax error.","category":"section"},{"location":"manuals/variational/constraints-specification/#Default-constraints","page":"Constraints specification","title":"Default constraints","text":"Sometimes, a submodel is used in multiple contexts, on multiple levels of hierarchy and in different submodels. In such cases, it becomes cumbersome to specify constraints for each instance of the submodel and track its usage throughout the model. To alleviate this, RxInfer allows users to specify default constraints for a submodel. These constraints will be applied to all instances of the submodel unless overridden by specific constraints. To specify default constraints for a submodel, override the GraphPPL.default_constraints function for the submodel:\n\nRxInfer.GraphPPL.default_constraints(::typeof(inner)) = @constraints begin\n    q(α) :: PointMassFormConstraint()\n    q(α, β) = q(α)q(β)\nend\n\nMore information can be found in the GraphPPL documentation.","category":"section"},{"location":"manuals/variational/constraints-specification/#Constraints-on-the-data","page":"Constraints specification","title":"Constraints on the data","text":"By default, RxInfer assumes that, since the data comes into the model as observed, the posterior marginal distribution of the data is independent from other marginals and is a Dirac-delta distribution. However, this assumption breaks when we pass missing data into our model. When the data is missing, we might have a joint dependency between the data and latent variables, as the missing data essentially behaves as a latent variable. In such cases, we can wrap the data in an UnfactorizedData. This will notify the inference engine that the data should not be factorized out, and we can specify a custom factorization constraint on these variables using the @constraints macro. \n\nunfactorized_example_constraints = @constraints begin\n    q(y[1:1000], μ, τ) = q(y[1:1000])q(μ)q(τ)\n    q(y[1001:1100], μ, τ) = q(y[1001:1100], μ)q(τ)\nend\nresult = infer(\n    model       = iid_normal(),\n    data        = (y = UnfactorizedData(vcat(rand(NormalMeanPrecision(3.1415, 2.7182), 1000), [missing for _ in 1:100])),),\n    constraints = unfactorized_example_constraints, \n    initialization = init,\n    iterations = 25\n)","category":"section"},{"location":"manuals/variational/constraints-specification/#prespecified-constraints","page":"Constraints specification","title":"Prespecified constraints","text":"GraphPPL exp
2orts some prespecified constraints that can be used in the @constraints macro, but these constraints can also be passed as top-level constraints in the infer function. For example, to specify a mean-field assumption on all variables in the model, we can use the MeanField constraint:\n\nresult = infer(\n    model       = iid_normal(),\n    data        = (y = rand(NormalMeanPrecision(3.1415, 2.7182), 1000), ),\n    constraints = MeanField(), # instead of using `@constraints` macro\n    initialization = init,\n    iterations  = 25\n)\n\n","category":"section"},{"location":"manuals/variational/constraints-specification/#RxInfer.UnfactorizedData","page":"Constraints specification","title":"RxInfer.UnfactorizedData","text":"UnfactorizedData{D}\n\nA wrapper struct to wrap data that should not be factorized out by default during inference. When performing Bayesian inference with message passing, every factor node contains a local factorization constraint on the variational posterior distribution. For data, we usually regard data as an independent component in the variational posterior distribution. However, in some cases, for example when we are predicting data, we do not want to factorize out the data. In such cases, we can wrap the data with UnfactorizedData struct to prevent the factorization and craft a custom node-local factorization with the @constraints macro.\n\n\n\n\n\n","category":"type"},{"location":"","page":"Home","title":"Home","text":"<div class=\"light-biglogo\">\n\n(Image: RxInfer Logo)\n\n</div>\n\n<div class=\"dark-biglogo\">\n\n(Image: RxInfer Logo)\n\n</div>\n\nJulia package for automatic Bayesian inference on a factor graph with reactive message passing.\n\nGiven a probabilistic model, RxInfer allows for efficient, message-passing based Bayesian inference. It uses the model structure to generate an algorithm consisting of a sequence of local computations on a factor graph representation of the model. RxInfer.jl is designed for efficient, scalable, and high-performance inference using reactive message passing.","category":"section"},{"location":"#Why-RxInfer?","page":"Home","title":"Why RxInfer?","text":"Many important AI applications, including audio processing, self-driving vehicles, weather forecasting, and extended-reality video processing require continually solving an inference task in sophisticated probabilistic models with a large number of latent variables. Often, the inference task in these applications must be performed continually and in real-time in response to new observations.\n\nPopular MC-based inference methods, such as the No U-Turn Sampler (NUTS) or Hamiltonian Monte Carlo (HMC) sampling, rely on computationally heavy sampling procedures that do not scale well to probabilistic models with thousands of latent states. Therefore, while MC-based inference is a very versatile tool, it is practically not suitable for real-time applications. While the alternative variational inference method (VI) promises to scale better to large models than sampling-based inference, VI requires the derivation of gradients of a \"Variational Free Energy\" cost function. For large models, manual derivation of these gradients might not be feasible, while automated \"black-box\" gradient methods do not scale either because they are not capable of taking advantage of sparsity or conjugate pairs in the model. Therefore, while Bayesian inference is known as the optimal data processing framework, in practice, real-time AI applications rely on much simpler, often ad hoc, data processing algorithms.\n\nRxInfer aims to remedy these issues by running efficient Bayesian inference in sophisticated probabilistic models, taking advantage of local conjugate relationships in probabilistic models, and focusing on real-time Bayesian inference in large state-space models with thousands of latent variables. In addition, RxInfer provides a straightforward way to extend its functionality with custom factor nodes and message passing update rules (see Understanding Rules). The engine is capable of running various Bayesian inference algorithms in different parts of the factor graph of a single probabilistic model. This makes it easier to explore different \"what-if\" scenarios and enables very efficie
2nt inference in specific cases.","category":"section"},{"location":"#Package-Features","page":"Home","title":"Package Features","text":"User friendly syntax for specification of probabilistic models, achieved with GraphPPL.\nSupport for hybrid models combining discrete and continuous latent variables.\nFactorization and functional form constraints specification.\nGraph visualisation and extensions with different custom plugins.\nSaving graph on a disk and re-loading it later on.\nAutomatic generation of message passing algorithms, achieved with ReactiveMP.\nSupport for hybrid distinct message passing inference algorithm under a unified paradigm.\nEvaluation of Bethe Free Energy as a model performance measure.\nSchedule-free reactive message passing API.\nScalability for large models with millions of parameters and observations.\nHigh performance.\nInference procedure is differentiable.\nEasy to extend with custom nodes and message update rules.\nCommunity-driven development and support\nRegular public meetings to discuss usage patterns and improvements\nActive community support through GitHub discussions\nOptional telemetry to help guide development (see Usage Telemetry)\nSession sharing for better debugging support (see Session Sharing)\nClient-Server infrastructure (third-party, see note below)\nRESTful API support through RxInferServer.jl\nOpenAPI-compliant endpoints for model management and inference\nPython and Julia SDKs for seamless integration\n\nnote: About `RxInferServer.jl`\nRxInferServer.jl and its associated Python client (RxInferClient.py) are developed and maintained by Lazy Dynamics — an external organisation separate from the core RxInfer team. These projects are distributed under a different license than RxInfer.jl itself and are not actively maintained by the ReactiveBayes / RxInfer core team. Please direct support questions and issues to the respective upstream repositories.\n\nCurious about how RxInfer compares to other tools you might be considering? We invite you to view a detailed comparison, where we put RxInfer head-to-head with other popular packages in the field.","category":"section"},{"location":"#How-to-get-started?","page":"Home","title":"How to get started?","text":"Head to the Getting started section to get up and running with RxInfer. ","category":"section"},{"location":"#Explore-examples","page":"Home","title":"Explore examples","text":"The RxInfer ecosystem offers two main documentation websites:\n\nDocumentation and Manual (this site): Contains all the guides, tutorials, and API documentation to help you learn and use RxInfer.  \nExamples Site: Features a wide variety of runnable, in-depth example notebooks and code, available at examples.rxinfer.com.\n\nExplore different examples in this documentation, and visit the RxInfer Examples Site for more hands-on tutorials and code.\n\n<div style=\"display: flex; justify-content: center; align-items: center; margin: 2rem 0;\">\n  <a href=\"https://examples.rxinfer.com/\" \n     style=\"\n         display:inline-block;\n         background:linear-gradient(90deg, #9558b2 0%, #4063d8 100%);\n         color:white;\n         font-weight:700;\n         font-size:1.25rem;\n         padding:1.1em 2.2em;\n         border-radius:0.5em;\n         text-decoration:none;\n         box-shadow:0 2px 8px rgba(0,0,0,0.10);\n         transition:background 0.15s, box-shadow 0.15s;\n         letter-spacing:0.02em;\n     \"\n  >\n    🚀 Explore Tutorials\n  </a>\n</div>","category":"section"},{"location":"#Table-of-Contents","page":"Home","title":"Table of Contents","text":"Pages = [\n  \"concepts/factor-graphs.md\",\n  \"concepts/message-passing.md\",\n  \"concepts/reactive-programming.md\",\n  \"manuals/comparison.md\",\n  \"manuals/getting-started.md\",\n  \"manuals/model-specification.md\",\n  \"manuals/variational/constraints-specification.md\",\n  \"manuals/inference/meta-specification.md\",\n  \"manuals/inference/overview.md\",\n  \"manuals/inference/create-node.md\",\n  \"manuals/performance-tips.md\",\n  \"manuals/faq.md\",\n  \"manuals/debugging.md\",\n  \"manuals
2/inference/delta-node.md\",\n  \"manuals/how-to-use-rxinfer-from-python.md\",\n  \"examples/overview.md\",\n  \"manuals/variational/functional-forms.md\",\n  \"manuals/variational/bethe-free-energy.md\",\n  \"contributing/exported-methods.md\",\n  \"contributing/guide.md\",\n  \"contributing/new-example.md\"\n]\nDepth = 2","category":"section"},{"location":"#Concepts-Overview","page":"Home","title":"Concepts Overview","text":"Before diving into practical usage, explore the foundational concepts underlying RxInfer:\n\nFactor Graphs — graphical representation of probabilistic models in terms of variable and factor nodes\nMessage Passing — VMP/BP algorithms for Bayesian inference on factor graphs\nReactive Programming Model — the streams mental model that enables real-time inference\n\nThese concept pages provide deeper theoretical understanding than the practical manuals. For quick-start usage, head to Getting started.","category":"section"},{"location":"#References","page":"Home","title":"References","text":"RxInfer: A Julia package for reactive real-time Bayesian inference - a reference paper for the RxInfer.jl framework.\nReactive Probabilistic Programming for Scalable Bayesian Inference - a PhD dissertation outlining core ideas and principles behind RxInfer (link2, link3).\nVariational Message Passing and Local Constraint Manipulation in Factor Graphs - describes theoretical aspects of the underlying Bayesian inference method.\nReactive Message Passing for Scalable Bayesian Inference - describes implementation aspects of the Bayesian inference engine and performs benchmarks and accuracy comparison on various models.\nA Julia package for reactive variational Bayesian inference - a reference paper for the ReactiveMP.jl package, the underlying inference engine.\nThe Factor Graph Approach to Model-Based Signal Processing - an introduction to message passing and FFGs.","category":"section"},{"location":"#Ecosystem","page":"Home","title":"Ecosystem","text":"RxInfer is part of the ReactiveBayes ecosystem, which unites 3 core packages into one powerful reactive message passing-based Bayesian inference framework:\n\nReactiveMP.jl - core package for efficient and scalable reactive message passing \nGraphPPL.jl - package for model and constraints specification\nRocket.jl - reactive programming tools\n\nnote: Note\nReactiveMP.jl engine is a successor of the ForneyLab package. It follows the same ideas and concepts for message-passing based inference, but uses new reactive and efficient message passing implementation under the hood. The API between two packages is different due to a better flexibility, performance and new reactive approach for solving inference problems.\n\nWhile these packages form the core, RxInfer relies on numerous other excellent open-source packages.  The developers of RxInfer express their deep appreciation to the entire open-source community for their tremendous efforts.\n\nWe are also very proud to be an affiliated project with NumFocus, a non-profit organization that supports the open-source scientific computing community.\n\n(Image: )","category":"section"},{"location":"#Index","page":"Home","title":"Index","text":"\n\n","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference","page":"Bayesian Inference","title":"Bayesian Inference","text":"Bayesian inference is the principled way of updating beliefs about unknown quantities when new data arrives. It is the problem RxInfer is built to solve — everything else in the documentation (factor graphs, message passing, constraints, reactive execution) is machinery in service of this single goal.\n\nThis page gives you the minimum intuition needed to read the rest of the concept pages. For a worked end-to-e
2nd example, jump to Getting started.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-recipe","page":"Bayesian Inference","title":"The Bayesian recipe","text":"You start with three ingredients:\n\nA prior p(x) — what you believe about the latent (unknown) variables before seeing any data.\nA likelihood p(y mid x) — how the observed data y depend on those latent variables.\nObserved data haty.\n\nBayes' rule tells you how to combine them into the posterior p(x mid haty) — your updated belief about x after seeing the data:\n\np(x mid haty) = fracp(haty mid x) p(x)p(haty) qquad\np(haty) = int p(haty mid x) p(x) mathrmdx\n\nRead out loud: \"posterior is proportional to likelihood times prior\". The denominator p(haty) — the evidence or marginal likelihood — is just a normalising constant that turns the numerator into a valid probability distribution.\n\nIn RxInfer you express this recipe once, inside a @model block:\n\n@model function coin(y)\n    θ ~ Beta(1.0, 1.0)       # prior over the coin bias\n    y .~ Bernoulli(θ)         # likelihood of the flips\nend\n\nThe infer function then computes (or approximates) the posterior p(theta mid haty) for you.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-hard","page":"Bayesian Inference","title":"Why it is hard","text":"The formula above looks innocent, but the evidence integral\n\np(haty) = int p(haty mid x) p(x) mathrmdx\n\nis almost always intractable once x is high-dimensional or the prior and likelihood are not conjugate. There are broadly three ways people attack this:\n\nExact inference — possible only in special cases (fully conjugate, discrete with small state space, or tree-structured graphs).\nSampl
2ing — Markov-chain Monte Carlo methods such as HMC / NUTS. Flexible but expensive; poor fit for real-time or streaming problems.\nVariational inference — turn the integral into an optimisation problem. This is what RxInfer does, and it is covered on the Variational Inference page.\n\nRxInfer's trick is to combine the speed and scalability of variational inference with exact analytical updates wherever conjugacy allows — giving you the best of both worlds. See the comparison page for how this plays out against other tools.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-generative","page":"Bayesian Inference","title":"Generative models and probabilistic programs","text":"A generative model is a joint distribution p(x y) describing how both latent variables and observations are produced. Factorising it via the product rule gives\n\np(x y) = p(y mid x) p(x)\n\nwhich is exactly the likelihood–prior pairing from above. A probabilistic program (like an RxInfer @model function) is just executable syntax for writing down p(x y) — each ~ statement declares one factor of the joint. This direct correspondence between code and distribution is what makes the factor graph representation possible.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-results","page":"Bayesian Inference","title":"What inference gives you","text":"When RxInfer \"runs inference\" it produces, for each latent variable:\n\nA posterior marginal q(x_i) approx p(x_i mid haty) — typically itself a familiar distribution such as a Gaussian, Gamma or Dirichlet.\nFrom that marginal you can read off point estimates (mean, mode), uncertainty (variance, credible intervals), or draw samples for downstream decisions.\n\nOptionally, RxInfer also reports the Bethe Free Energy — a model-quality score that approximates the (negative log) evidence and is useful for monitoring convergence and comparing models.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-next","page":"Bayesian Inference","title":"Where to go next","text":"Now that you know what Bayesian inference is, the rest of the concept pages explain how RxInfer performs it:\n\nFactor Graphs — how models are represented internally.\nMessage Passing — the core inference algorithm operating on the factor graph.\nVariational Inference — the optimisation view that underpins RxInfer.\nConstraints Specification — how you trade accuracy for tractability.\nReactive Programming — the execution model that enables real-time inference.","category":"section"},{"location":"concepts/bayesian-inference/#concepts-bayesian-inference-deeper","page":"Bayesian Inference","title":"For deeper understanding","text":"Pattern Recognition and Machine Learning — Christopher Bishop.\nBayesian Data Analysis — Andrew Gelman et al.\nAn Introduction to Probabilistic Programming — van de Meent, Paige, Yang, Wood.\nThe Factor Graph Approach to Model-Based Signal Processing — Loeliger et al.\n\n","category":"section"},{"location":"manuals/performance-tips/#user-guide-performance-tips","page":"Performance Tips","title":"Performance Tips","text":"This section provides practical advice and best practices for optimizing the performance of your RxInfer models. Following these guidelines can significantly improve inference speed and memory efficiency.\n\nnote: Note\nBefore diving into RxInfer-specific optimizations, we strongly recommend reading Julia's official Performance Tips guide. Many performance improvements come from following Julia's general best practices, such as avoiding global variables, using type stability, and minimizing allocations. The tips in this section build upon those fundamental principles.","category":"section"},{"location":"manuals/performance-tips/#Julia-Compilation-Latency","page":"Performance Tips","title":"Julia Compilation Latency","text":"Julia uses Just-In-Time (JIT) compilation. The first time you run a model and inference procedure, Julia compiles the specialized machine code. This can cause noticeable delays only once. Afterward, execution becomes much faster. This might be especially problematic for models and factor nodes that accept a dynamic number of arguments. Such n
2odes include mixture nodes (where the number of components is only known at compilation time) as well as deterministic nodes representing non-linear transformations (since those transformations can be arbitrary, their signature is only known at compilation time).\n\nTips: \n\nDon't worry about long first-run times during development — focus on steady-state performance.\nUse the @time macro from Julia to investigate the time spent on compilation and execution.","category":"section"},{"location":"manuals/performance-tips/#Model-Structure-Optimization","page":"Performance Tips","title":"Model Structure Optimization","text":"RxInfer is designed for fast inference on factor graphs and leverages the model structure to optimize the inference procedure. However, it is always possible to create a huge model with complex dependencies between variables and make inference slow with RxInfer. \n\nGeneral guidelines for model structure optimization:","category":"section"},{"location":"manuals/performance-tips/#Choose-Appropriate-Parametrization-for-Your-Nodes","page":"Performance Tips","title":"Choose Appropriate Parametrization for Your Nodes","text":"While confusing at first glance, the choice of parametrization for your nodes can have a significant impact on the performance of the inference procedure. For this reason, RxInfer allows you to choose, for example, between NormalMeanPrecision and NormalMeanVariance parametrizations for Normal nodes. Or, you can choose between MvNormalMeanPrecision and MvNormalMeanScalePrecision parametrizations for MvNormal nodes. The difference between these parametrizations is that the former needs to store the entire precision matrix, while the latter uses a single number to store the scale of the diagonal of the precision matrix.","category":"section"},{"location":"manuals/performance-tips/#Use-Conjugate-Pairs","page":"Performance Tips","title":"Use Conjugate Pairs","text":"Conjugate pairs enable analytical message updates. For example, a Gamma prior is appropriate for a NormalMeanPrecision node, but an InverseGamma is not. Conversely, an InverseGamma prior is appropriate for a NormalMeanVariance node, but a Gamma is not. Another example is that a Beta prior is appropriate for a Bernoulli node, but a Binomial is not. A Wishart prior is appropriate for an MvNormalMeanPrecision node, and an InverseWishart is appropriate for an MvNormalMeanCovariance node.  Note that the conjugacy also depends on the local factorization of your model. If you place priors on both the mean and the precision in MvNormalMeanPrecision, you must enforce independence (e.g., q(μ,Λ)=q(μ)q(Λ)) to make the model conditionally conjugate.","category":"section"},{"location":"manuals/performance-tips/#Be-Aware-of-the-Computational-Overhead-of-Deterministic-Nodes","page":"Performance Tips","title":"Be Aware of the Computational Overhead of Deterministic Nodes","text":"Each deterministic node adds computational overhead and requires approximation method specification. Read more about approximation methods in the Deterministic nodes section. In some situations, however, it is possible to use specialized factor nodes instead of deterministic nodes. For example, the SoftDot node is a specialized factor node for computing the dot product of two vectors where the result is passed to a Normal node. Using SoftDot directly instead of Normal(mean = dot(...), ...) can significantly improve both the performance and accuracy of the inference procedure. Similar applies to ContinuousTransition node.\n\nIf a specialized node is not available, you can either create one yourself (see also Understanding Rules for background on rules) or choose an appropriate approximation method for the deterministic node. For example, if all inputs to the non-linear transformation are known to be Gaussian, the fastest approximation method is probably Linearization. However, it requires the function to be differentiable and \"nice\" enough. More computationally expensive methods, such as Unscented or CVIProjection, are more robust and can be used in more general cases. We also suggest you to check Fusing deterministic transformations with stoch
2astic nodes example that provides additional tricks.","category":"section"},{"location":"manuals/performance-tips/#Smoothing-vs.-Filtering","page":"Performance Tips","title":"Smoothing vs. Filtering","text":"It might be appropriate to convert your model from operating on the whole dataset (smoothing) to operating on one observation at a time (filtering). Read more about smoothing in the Static Inference section and about filtering in the Online Inference section. It is also possible to combine both approaches and process data in batches.","category":"section"},{"location":"manuals/performance-tips/#Inference-Procedure-Optimization","page":"Performance Tips","title":"Inference Procedure Optimization","text":"The infer function is the main entry point for inference in RxInfer.jl. It is a wrapper around the inference procedure and allows you to specify the inference algorithm, the number of iterations, the initial values for the parameters, and more. The default parameters are chosen to be a good compromise between speed and accuracy. However, in some situations, it is possible to improve the performance of the inference procedure by tuning the parameters.","category":"section"},{"location":"manuals/performance-tips/#Use-free_energy-Float64-Instead-of-free_energy-true","page":"Performance Tips","title":"Use free_energy = Float64 Instead of free_energy = true","text":"By default, when computing free energy values, they are stored as an abstract type Real and are converted to Float64 only when they are returned. This can be a significant overhead (read Julia's Performance Tips), especially for large models. The reason for this choice is that in this case, the inference procedure can be auto-differentiated where free energy values serve as the objective function. If you do not plan to auto-differentiate the inference procedure, you can set free_energy = Float64 to avoid the overhead of type conversions.","category":"section"},{"location":"manuals/performance-tips/#Be-Aware-of-the-Computational-Overhead-of-the-limit_stack_depth-Option","page":"Performance Tips","title":"Be Aware of the Computational Overhead of the limit_stack_depth Option","text":"RxInfer provides a limit_stack_depth option to limit the depth of the stack of the inference procedure, which is explained in the Stack Overflow during inference section. This can be useful to avoid stack overflows, but it can also significantly degrade the performance of the inference procedure. The larger the value, the less the performance is degraded. You can tune the value based on the size of your model as well as your computer. The optimal value differs for different models and computers.","category":"section"},{"location":"manuals/performance-tips/#Getting-Help","page":"Performance Tips","title":"Getting Help","text":"If you encounter performance issues:\n\nCheck the documentation: Review relevant sections for optimization tips\nUse the community: Open discussions on GitHub for specific issues\nProfile your code: Use Julia's profiling tools to identify bottlenecks\nStart simple: Build complexity gradually to identify performance issues\n\n","category":"section"},{"location":"contributing/new-release/#contributing-new-release","page":"Publishing a new release","title":"Publishing a new release","text":"Please read first the general Contributing section. Also, please read the FAQ section in the Julia General registry.","category":"section"},{"location":"contributing/new-release/#Start-the-release-process","page":"Publishing a new release","title":"Start the release process","text":"In order to start the release process a person with the necessary permissions should:\n\nUpdate the version in the following files \nProject.toml (1 place, under version)\ncodemeta.json (2 places, under version and softwareVersion)\nEnsure that CHANGELOG.md has been properly maintained and up to date\nChange the Unreleased section to the new version, set the date\nAdd new Unreleased section\nUpdate the very bottom of the CHANGELOG with new diffs\nIf necessary make a new commit that will be registered and tagged as a new version\nOpen a commit page on GitHub\nWrite the @JuliaRegistrator register comment for the commit:\n\n(Image: Release comment)\n\nThe Julia Registrator bot should automatically register a request for the new release. Once all checks have passed on the Julia Registrator's side, the new release will be published and tagged automatically.\n\n","category":"section"},{"location":"manuals/inference/callbacks/#manual-inference-callbacks","page":"Callbacks","title":"Callbacks","text":"The infer function and the underlying reactive message passing engine both have their own lifecycle, consisting of multiple steps. By supplying c
2allbacks, users can inject custom logic at specific moments during the inference procedure — for example, for debugging, performance analysis, or early stopping.","category":"section"},{"location":"manuals/inference/callbacks/#Event-based-callback-system","page":"Callbacks","title":"Event-based callback system","text":"All callbacks in RxInfer use an event-based dispatch system built on ReactiveMP.Event{E}. Each callback event is a concrete struct that carries all relevant data as named fields. This makes callbacks self-documenting and extensible.\n\nFor example, an AfterIterationEvent has fields model and iteration:\n\n# NamedTuple callback — receives the event struct\ncallbacks = (\n    after_iteration = (event) -> println(\"Iteration \", event.iteration, \" done\"),\n)","category":"section"},{"location":"manuals/inference/callbacks/#Callback-types","page":"Callbacks","title":"Callback types","text":"The callbacks keyword argument of the infer function accepts three types of callback handlers:","category":"section"},{"location":"manuals/inference/callbacks/#NamedTuple","page":"Callbacks","title":"NamedTuple","text":"The simplest way to define callbacks is via a NamedTuple, where keys correspond to event names and values are functions that receive a single event object:\n\nusing RxInfer\nusing RxInfer.ReactiveMP\nusing Test #hide\n\n@model function coin_model(y)\n    θ ~ Beta(1, 1)\n    y .~ Bernoulli(θ)\nend\n\nbefore_inference_called = Ref(false) #hide\nafter_inference_called = Ref(false) #hide\n\nresult = infer(\n    model = coin_model(),\n    data  = (y = [1, 0, 1, 1, 0],),\n    callbacks = (\n        before_inference   = (event) -> begin\n            before_inference_called[] = true #hide\n            println(\"Starting inference on model: \", typeof(event.model))\n        end,\n        after_inference    = (event) -> begin\n            after_inference_called[] = true #hide\n            println(\"Inference completed\")\n        end,\n    )\n)\n\n@test before_inference_called[] #hide\n@test after_inference_called[] #hide\nnothing #hide\n\nwarning: Warning\nWhen defining a NamedTuple with a single entry, make sure to include a trailing comma. In Julia, (key = value) is parsed as a variable assignment, not a NamedTuple. Use (key = value,) (with trailing comma) instead. ReactiveMP will raise a helpful error if this mistake is detected.","category":"section"},{"location":"manuals/inference/callbacks/#Dict","page":"Callbacks","title":"Dict","text":"A Dict{Symbol} works the same way as a NamedTuple, but allows dynamic construction of callbacks:\n\nmy_callbacks = Dict(\n    :before_inference => (event) -> println(\"Starting inference\"),\n    :after_inference  => (event) -> println(\"Inference completed\"),\n)\n\nresult = infer(\n    model = coin_model(),\n    data  = (y = [1, 0, 1, 1, 0],),\n    callbacks = my_callbacks,\n)\n\n@test result.posteriors[:θ] isa Distribution #hide\nnothing #hide","category":"section"},{"location":"manuals/inference/callbacks/#Custom-callback-structures","page":"Callbacks","title":"Custom callback structures","text":"For more advanced use cases, you can pass any custom structure as a callback handler. The structure must implement ReactiveMP.handle_event methods for the event types it wants to handle:\n\nstruct MyCallbackHandler\n    log::Vector{String}\nend\n\n# Catch-all: ignore events you don't care about\nReactiveMP.handle_event(::MyCallbackHandler, ::ReactiveMP.Event) = nothing\n\n# Handle specific events by dispatching on the concrete event type\nfunction ReactiveMP.handle_event(handler::MyCallbackHandler, event::BeforeInferenceEvent)\n    push!(handler.log, \"inference started\")\nend\n\nfunction ReactiveMP.handle_event(handler::MyCallbackHandler, event::AfterInferenceEvent)\n    push!(handler.log, \"inference completed\")\nend\n\nhandler = MyCallbackHandler(String[])\n\nresult = infer(\n    model = coin_model(),\n    data  = (y = [1, 0, 1, 1, 0],),\n    callbacks = handler,\n)\n\n@test length(handler.log) == 2 #hide\n@test handler.log[1] == \"inference started\" #hide\n@test handler.log[2] == \"inference completed\" #hide\nprintln(handler.log)","category":"section"},{"location":"manuals/inference/callbacks/#Dispatching-by-event-name-with-Event{:name}","page":"Callbacks","title":"Dispatching by event name with Event{:name}","text":"Since every event struct is a subtype of ReactiveMP.Event{E}
2 where E is a Symbol, you can also dispatch on ReactiveMP.Event{:event_name} instead of the concrete type name. This is equivalent — you still have access to all the same fields — and can be more convenient when the concrete type is not exported. For example, ReactiveMP-level events like BeforeMessageRuleCallEvent are not exported by default, but you can always dispatch on ReactiveMP.Event{:before_message_rule_call}:\n\nstruct MyEventNameHandler\n    log::Vector{String}\nend\n\n# Catch-all\nReactiveMP.handle_event(::MyEventNameHandler, ::ReactiveMP.Event) = nothing\n\n# Dispatch using Event{:name} — no need to know the concrete struct name\nfunction ReactiveMP.handle_event(handler::MyEventNameHandler, event::ReactiveMP.Event{:before_inference})\n    push!(handler.log, \"inference started (via Event name)\")\nend\n\nfunction ReactiveMP.handle_event(handler::MyEventNameHandler, event::ReactiveMP.Event{:after_inference})\n    push!(handler.log, \"inference completed (via Event name)\")\nend\n\nhandler_by_name = MyEventNameHandler(String[])\n\nresult = infer(\n    model = coin_model(),\n    data  = (y = [1, 0, 1, 1, 0],),\n    callbacks = handler_by_name,\n)\n\n@test length(handler_by_name.log) == 2 #hide\n@test handler_by_name.log[1] == \"inference started (via Event name)\" #hide\n@test handler_by_name.log[2] == \"inference completed (via Event name)\" #hide\nprintln(handler_by_name.log)\n\nBoth approaches are fully interchangeable — use whichever is more convenient for your use case.\n\nCustom callback structures are useful when you need to:\n\nMaintain state across events (e.g., collecting timing information)\nImplement complex logic that spans multiple events\nStore information in the model's metadata dictionary for later access\n\nRxInfer provides built-in callback structures such as RxInferBenchmarkCallbacks and StopEarlyIterationStrategy as examples of this pattern.","category":"section"},{"location":"manuals/inference/callbacks/#Model-metadata","page":"Callbacks","title":"Model metadata","text":"The ProbabilisticModel structure contains a metadata dictionary (Dict{Any, Any}) that callbacks can use to store arbitrary information during inference. This is accessible from the inference result via result.model.metadata.\n\nFor example, you can track the history of marginal updates during variational inference:\n\n@model function gaussian_model(y)\n    μ ~ Normal(mean = 0.0, variance = 100.0)\n    τ ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = τ)\nend\n\nstruct MarginalHistoryCollector end\n\nReactiveMP.handle_event(::MarginalHistoryCollector, ::ReactiveMP.Event) = nothing\n\nfunction ReactiveMP.handle_event(::MarginalHistoryCollector, event::OnMarginalUpdateEvent)\n    history = get!(() -> [], event.model.metadata, :marginal_history)\n    push!(history, (iteration_variable = event.variable_name, value = event.update))\nend\n\nresult = infer(\n    model = gaussian_model(),\n    data  = (y = randn(50),),\n    constraints = MeanField(),\n    initialization = @initialization(begin\n        q(μ) = vague(NormalMeanVariance)\n        q(τ) = vague(GammaShapeRate)\n    end),\n    iterations = 5,\n    callbacks = MarginalHistoryCollector(),\n)\n\n@test haskey(result.model.metadata, :marginal_history) #hide\n@test length(result.model.metadata[:marginal_history]) > 0 #hide\n\n# Access the collected marginal history\nhistory = result.model.metadata[:marginal_history]\nprintln(\"Collected \", length(history), \" marginal updates across all iterations\")\nprintln(\"Variables updated: \", unique(map(h -> h.iteration_variable, history)))","category":"section"},{"location":"manuals/inference/callbacks/#Available-events","page":"Callbacks","title":"Available events","text":"Callbacks can listen to events from two layers: RxInfer-level events from the inference lifecycle, and ReactiveMP-level events from the message passing engine itself.\n\nusing RxInfer, Test\n# Verify that the events documented below match the actual available callbacks\n@test RxInfer.available_callbacks(RxInfer.batch_inference) === Val((\n    :on_marginal_update,\n    :before_model_creation,\n    :after_model_creation,\n    :before_inference,\n    :before_iteration,\n    :before_data_update,\n    :after_data_update,\n    :after_iteration,\n    :after_inference,\n    :before_message_rule_call,\n    :after_message_rule_call,\n    :before_product_of_two_messages,\n    :after_product_of_two_messages,\n    :before_product_of_messages,\n    :after_product_of_messages,\n    :before_form_constraint_applied,\n    :after_form_constraint_applied,\n    :before_marginal_computation,\n    :after_marginal_computation\n))\n@test RxInfer.available_callbacks(RxInfer.streaming_inference) === Val((\n    :before_model_creation,\n    :after_model_creation,\n    :before_autostart,\n    :after_autostart,\n    :before_message_rule_call,\n    :after_message_rule_call,\n    :before_product_of_two_messages,\n    :after_product_of_two_messages,\n    :before_product_of_messages,\n    :after_product_of_messages,\n    :before_form_constraint_applied,\n    :after_form_constraint_applied,\n    :before_marginal_computation,\n    :after_marginal_computation\n))\nnothing","category":"section"},{"location":"manuals/inference/callbacks/#RxInfer-events","page":"Callbacks","title":"RxInfer events","text":"These events are fired by the infer function during the inference lifecycle. Each event is a concrete struct subtyping ReactiveMP.Event{E} with named fields.","category":"section"},{"location":"manuals/inference/callbacks/#Common-to-batch-and-streamline-inference","page":"Callbacks","title":"Common to batch and streamline inference","text":"","category":"section"},{"location":"manuals/inference/callbacks/#Batch-inference-only","page":"Callbacks","title":"Batch inference only","text":"For more details on batch inference, see Static inference.\n\nnote: Note\nbefore_iteration and after_iteration events carry a mutable stop_iteration::Bool field (default false). Set event.stop_iteration = true from a callback to halt iterations early. See Early stopping for an example.","category":"section"},{"location":"manuals/inference/callbacks/#Streamline-inference-only","page":"Callbacks","title":"Streamline inference only","text":"For more details on streamline inference, see Streamline inference.","category":"section"},{"location":"manuals/inference/callbacks/#ReactiveMP-events","page":"Callbacks","title":"ReactiveMP events","text":"These lower-level events are fired by the ReactiveMP message passing engine during inference. They are available in both batch and streamline inference modes. Each event is a concrete struct subtyping ReactiveMP.Event{E} with named fields — refer to the ReactiveMP documentation for field details.\n\nBeforeMessageRuleCallEvent / AfterMessageRuleCallEvent — fired around message rule computations\nBeforeProductOfTwoMessagesEvent / AfterProductOfTwoMessagesEvent — fired around pairwise message products\nBeforeProductOfMessagesEvent / AfterProductOfMessagesEvent — fired around folded message products\nBeforeFormConstraintAppliedEvent / AfterForm
2ConstraintAppliedEvent — fired around form constraint application\nBeforeMarginalComputationEvent / AfterMarginalComputationEvent — fired around marginal computations\n\nFor detailed descriptions of these events and their fields, refer to the official documentation of ReactiveMP.","category":"section"},{"location":"manuals/inference/callbacks/#Migration-from-positional-argument-callbacks","page":"Callbacks","title":"Migration from positional-argument callbacks","text":"warning: Breaking change\nPrevious versions of RxInfer passed callback arguments positionally:# OLD (no longer works)\ncallbacks = (\n    after_iteration = (model, iteration) -> println(iteration),\n    on_marginal_update = (model, name, update) -> println(name),\n)The new system uses event structs with named fields. Each callback now receives a single event object:# NEW\ncallbacks = (\n    after_iteration = (event) -> println(event.iteration),\n    on_marginal_update = (event) -> println(event.variable_name),\n)For custom callback structures, implement handle_event methods that dispatch on the concrete event type:# Dispatch on specific events\nReactiveMP.handle_event(::MyHandler, event::BeforeInferenceEvent) = ... # event.model\n# Catch-all for events you don't care about\nReactiveMP.handle_event(::MyHandler, ::ReactiveMP.Event) = nothingThe migration is straightforward: replace positional arguments with named field access on the event object. The event structs are fully documented with their field names — see the sections above.","category":"section"},{"location":"manuals/inference/callbacks/#Built-in-callback-handlers","page":"Callbacks","title":"Built-in callback handlers","text":"RxInfer provides the following built-in callback structures:\n\nRxInferBenchmarkCallbacks — collects timing statistics across inference runs. See Benchmark callbacks.\nRxInferTraceCallbacks — records all callback events for debugging and inspection. See Trace callbacks.\nStopEarlyIterationStrategy — stops variational iterations early based on free energy convergence. See Early stopping.\n\n","category":"section"},{"location":"manuals/inference/callbacks/#RxInfer.BeforeModelCreationEvent","page":"Callbacks","title":"RxInfer.BeforeModelCreationEvent","text":"BeforeModelCreationEvent{S} <: ReactiveMP.Event{:before_model_creation}\n\nFires right before the probabilistic model is created in the infer function.\n\nFields\n\nspan_id: an identifier shared with the corresponding AfterModelCreationEvent\n\nSee also: AfterModelCreationEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.AfterModelCreationEvent","page":"Callbacks","title":"RxInfer.AfterModelCreationEvent","text":"AfterModelCreationEvent{M, S} <: ReactiveMP.Event{:after_model_creation}\n\nFires right after the probabilistic model is created in the infer function.\n\nFields\n\nmodel::M: the created ProbabilisticModel instance\nspan_id: an identifier shared with the corresponding BeforeModelCreationEvent\n\nSee also: BeforeModelCreationEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.OnMarginalUpdateEvent","page":"Callbacks","title":"RxInfer.OnMarginalUpdateEvent","text":"OnMarginalUpdateEvent{M, U} <: ReactiveMP.Event{:on_marginal_update}\n\nFires each time a marginal posterior for a variable is updated during inference.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\nvariable_name::Symbol: the name of the variable whose marginal was updated\nupdate::U: the updated marginal value\n\nSee also: Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.BeforeInferenceEvent","page":"Callbacks","title":"RxInfer.BeforeInferenceEvent","text":"BeforeInferenceEvent{M, S} <: ReactiveMP.Event{:before_inference}\n\nFires right before the inference procedure starts (after model creation and subscription setup).\n\nFields\n\nmodel::M: the ProbabilisticModel instance\nspan_id: an identifier shared with the corresponding AfterInferenceEvent\n\nSee also: AfterInferenceEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.AfterInferenceEvent","page":"Callbacks","title":"RxInfer.AfterInferenceEvent","text":"AfterInferenceEvent{M, S} <: ReactiveMP.Event{:after_inference}\n\nFires right after the inference procedure completes.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\nspan_id: an i
2dentifier shared with the corresponding BeforeInferenceEvent\n\nSee also: BeforeInferenceEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.BeforeIterationEvent","page":"Callbacks","title":"RxInfer.BeforeIterationEvent","text":"BeforeIterationEvent{M, S} <: ReactiveMP.Event{:before_iteration}\n\nFires right before each variational iteration.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\niteration::Int: the current iteration number\nstop_iteration::Bool: set to true from a callback to halt iterations early (default: false)\nspan_id: an identifier shared with the corresponding AfterIterationEvent\n\nSee also: AfterIterationEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.AfterIterationEvent","page":"Callbacks","title":"RxInfer.AfterIterationEvent","text":"AfterIterationEvent{M, S} <: ReactiveMP.Event{:after_iteration}\n\nFires right after each variational iteration.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\niteration::Int: the current iteration number\nstop_iteration::Bool: set to true from a callback to halt iterations early (default: false)\nspan_id: an identifier shared with the corresponding BeforeIterationEvent\n\nSee also: BeforeIterationEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.BeforeDataUpdateEvent","page":"Callbacks","title":"RxInfer.BeforeDataUpdateEvent","text":"BeforeDataUpdateEvent{M, D, S} <: ReactiveMP.Event{:before_data_update}\n\nFires right before updating data variables in each iteration.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\ndata::D: the data being used for the update\nspan_id: an identifier shared with the corresponding AfterDataUpdateEvent\n\nSee also: AfterDataUpdateEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.AfterDataUpdateEvent","page":"Callbacks","title":"RxInfer.AfterDataUpdateEvent","text":"AfterDataUpdateEvent{M, D, S} <: ReactiveMP.Event{:after_data_update}\n\nFires right after updating data variables in each iteration.\n\nFields\n\nmodel::M: the ProbabilisticModel instance\ndata::D: the data that was used for the update\nspan_id: an identifier shared with the corresponding BeforeDataUpdateEvent\n\nSee also: BeforeDataUpdateEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.BeforeAutostartEvent","page":"Callbacks","title":"RxInfer.BeforeAutostartEvent","text":"BeforeAutostartEvent{E, S} <: ReactiveMP.Event{:before_autostart}\n\nFires right before RxInfer.start() is called on the streaming inference engine (when autostart = true).\n\nFields\n\nengine::E: the RxInferenceEngine instance\nspan_id: an identifier shared with the corresponding AfterAutostartEvent\n\nSee also: AfterAutostartEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/callbacks/#RxInfer.AfterAutostartEvent","page":"Callbacks","title":"RxInfer.AfterAutostartEvent","text":"AfterAutostartEvent{E, S} <: ReactiveMP.Event{:after_autostart}\n\nFires right after RxInfer.start() is called on the streaming inference engine (when autostart = true).\n\nFields\n\nengine::E: the RxInferenceEngine instance\nspan_id: an identifier shared with the corresponding BeforeAutostartEvent\n\nSee also: BeforeAutostartEvent, Callbacks\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/early-stopping/#manual-inference-early-stopping","page":"Early stopping","title":"Early stopping","text":"RxInfer supports early stopping as an opt-in callback via StopEarlyIterationStrategy. For a general overview of the callbacks system, see the Callbacks section.","category":"section"},{"location":"manuals/inference/early-stopping/#Constructors","page":"Early stopping","title":"Constructors","text":"StopEarlyIterationStrategy(rtol): sets atol = 0.0, uses the given relative tolerance.\nStopEarlyIterationStrategy(atol, rtol): sets both absolute and relative tolerances.\nBoth constructors use start_fe_value = Inf for the initial comparison value.","category":"section"},{"location":"manuals/inference/early-stopping/#Early-stopping-mechanism","page":"Early stopping","title":"Early stopping mechanism","text":"The BeforeIterationEvent and AfterIterationEvent carry a mutable stop_iteration::Bool field (default false). Any callback can set event.stop_iteration = true to signal the inference engine to stop iterating. The StopEarlyIterationStrategy uses this mechanism internally — when the free energy has converged, it sets event.stop_iteration = true.\n\nCheck out more about callbacks for static inference here.\n\nNote that in this case we still have to specify the iterations, which in the case of early stopping specifies maximum number of iterations.","category":"section"},{"location":"manuals/inference/early-stopping/#Example","page":"Early stopping","title":"Example","text":"using RxInfer\nusing Test #hide\n\n@model function iid_normal(y)\n    m ~ Normal(mean = 0.0, variance = 1.0)\n    tau ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = m, precision = tau)\nend\n\ndata = (y = randn(100),)\nmax_iterations = 50\ninitialization = @initialization begin\n    q(m) = NormalMeanVariance(0.0, 1.0)\n    q(tau) = GammaShapeRate(1.0, 1.0)\nend\n\nresult = infer(\n    model = iid_normal(),\n    data = data,\n    constraints = MeanField(),\n    initialization = initialization,\n    free_energy = true,\n    iterations = max_iterations,\n    callbacks = (\n        after_iteration = StopEarlyIterationStrategy(1e-10, 1e-3),\n    )\n)\n\n@test length(result.free_energy) < max_iterations #hide\nlength(result.free_energy)\n\nAs you can see the total number of free_energy evaluations is less than max_iterations.\n\n","category":"section"},{"location":"manuals/inference/early-stopping/#RxInfer.StopEarlyIterationStrategy","page":"Early stopping","title":"RxInfer.StopEarlyIterationStrategy","text":"StopEarlyIterationStrategy\n\nEarly-stopping criterion based on consecutive Bethe free energy (FE) values.\
2n\nFields\n\natol::Float64: Absolute tolerance.\nrtol::Float64: Relative tolerance.\nstart_fe_value::Float64: Initial FE reference used before the first iteration.\nfe_values::Vector{Float64}: History of observed FE values (most recent is last).\n\nConstructors\n\nStopEarlyIterationStrategy(rtol): uses atol = 0.0, custom rtol.\nStopEarlyIterationStrategy(atol, rtol): custom absolute and relative tolerances.\n\nBoth constructors use start_fe_value = Inf by default to avoid immediate stopping on the first iteration.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/early-stopping/#RxInfer.StopEarlyIterationStrategy-Tuple{Real}","page":"Early stopping","title":"RxInfer.StopEarlyIterationStrategy","text":"StopEarlyIterationStrategy(rtol::Real)\n\nCreate an early-stopping strategy with atol = 0.0 and the given rtol. Uses start_fe_value = Inf by default.\n\n\n\n\n\n","category":"method"},{"location":"manuals/inference/early-stopping/#RxInfer.StopEarlyIterationStrategy-Tuple{Real, Real}","page":"Early stopping","title":"RxInfer.StopEarlyIterationStrategy","text":"StopEarlyIterationStrategy(atol::Real, rtol::Real)\n\nCreate an early-stopping strategy with explicit absolute (atol) and relative (rtol) tolerances. Uses start_fe_value = Inf by default.\n\n\n\n\n\n","category":"method"},{"location":"contributing/new-documentation/#guide-docs-contributing","page":"Contributing to the documentation","title":"Contributing to the documentation","text":"Contributing to our documentation is a valuable way to enhance the RxInfer ecosystem. To get started, you can follow these steps:\n\nFamiliarize Yourself: First, take some time to explore our existing documentation. Understand the structure, style, and content to align your contributions with our standards.\nIdentify Needs: Identify areas that require improvement, clarification, or expansion. These could be missing explanations, code examples, or outdated information.\nFork the Repository: Fork our documentation repository on GitHub to create your own copy. This allows you to work on your changes independently.\nMake Your Edits: Create or modify content in your forked repository. Ensure your contributions are clear, concise, and well-structured.\nSubmit a Pull Request: When you're satisfied with your changes, submit a pull request (PR) to our main repository. Describe your changes in detail in the PR description.\nReview and Feedback: Our documentation maintainers will review your PR. They may provide feedback or request adjustments. Be responsive to this feedback to facilitate the merging process.\nMerging: Once your changes align with our documentation standards, they will be merged into the main documentation. Congratulations, you've successfully contributed to the RxInfer ecosystem!\n\nBy following these steps, you can play an essential role in improving and expanding our documentation, making it more accessible and valuable to the RxInfer community.","category":"section"},{"location":"contributing/new-documentation/#Use-[LiveServer.jl](https://github.com/tlienart/LiveServer.jl)","page":"Contributing to the documentation","title":"Use LiveServer.jl","text":"LiveServer.jl is a simple and lightweight web server developed in Julia. It features live-reload capabilities, making it a valuable tool for automatically refreshing the documentation of a package while you work on its content.\n\nTo use LiveServer.jl, simply follow these steps:[1]\n\n[1]: Make sure to install the LiveServer and Documenter in your current working environment.\n\nMake sure to import the required packages \n\njulia> using LiveServer, Documenter\n\nAfter importing the required packages, you can start the live server with the following command:\n\njulia> servedocs()\n\n","category":"section"},{"location":"manuals/rules_and_nodes/#what-is-a-rule","page":"Understanding Rules","title":"Understanding Rules: The Mechanics of Message Passing","text":"In RxInfer, a Rule is a computational unit that defines how information flows across a specific edge of your factor graph.\n\nUsers often ask: \"Why do some rules take m_x as input, while others take q_x? And what about q_xy?\"\n\nThe answer lies in the variational constraints you place on your model. Depending on whether you are running unconstrained Belief Propagation or Mean-Field Variational Inference, the nodes in your graph need to perform different mathematical operations, which requires different types of input data.","category":"section"},{"location":"manuals/rules_and_nodes/#Visualizing-a-Rule","page":"Understanding Rules","title":"Visualizing a Rule","text":"Consider a simple factor node f connecting an input variable y to an output variable x. When the node needs to send a message to x, the Rule defines how to transform the incoming information from y into that outgoing message.\n\ngraph LR\n    subgraph \"The Local Neighborhood\"\n    y((y)) -- \"Incoming Information\" --> factor[Factor Node f]\n    factor -- \"Outgoing Message\" --> x((x))\n    end\n\n    style factor fill:#f9f,stroke:#333,stroke-width:2px\n    style y fill:#fff,stroke:#333\n    style x fill:#fff,stroke:#333\n\nThe \"Rule\" is the logic inside the pink box. It answers the question: \"Given what I know about y, what should I tell x?\"","category":"section"},{"location":"manuals/rules_and_nodes/#The-Analogy:-Telephone-vs.-Surveys","page":"Understanding Rules","title":"The Analogy: Telephone vs. Surveys","text":"To understand which rule RxInfer needs, imagine the nodes in your graph are agents trying to learn about the world. They use different \"protocols\" to communicate based on the constraints you apply.","category":"section"},{"location":"manuals/rules_and_nodes/#1.-The-\"Telephone\"-Protocol-(Belief-Propagation)","page":"Understanding Rules","title":"1. The \"Telephone\" Protocol (Belief Propagation)","text":"Context: No constraints are applied (or the graph is a tree).\n\nInput Prefix: m_ (Incoming Message)\nThe Logic: \"I don't know the final truth, but I will pass on the specific message I just received from my neighbor.\"\nThe Math (Sum-Product Integral): In exact inference, a factor node f(x y) calculates a message towards x by integrating the factor with the incoming message from y (vecmu(y)):\n\nvecmu(x) propto int f(x y) cdot vecmu(y)  dy\n\nWhy m_? To solve this integral, the node only needs the \"incoming opinion\" (vecmu(y)) from the neighbor branch. It does not need the neighbor's final marginal belief.","category":"section"},{"location":"manuals/rules_and_nodes/#2.-The-\"Survey\"-Protocol-(Variational-Message-Passing)","page":"Understanding Rules","title":"2. The \"Survey\" Protocol (Variational Message Passing)","text":"Context: Factorization constraints are applied (e.g., Mean-Field q(xy) = q(x)q(y)).\n\nInput Prefix: q_ (Marginal Distribution)\nThe Logic: \"To minimize the global error (Free Energy), I need to calculate the average opinion (expectation) of my neighbors.\"\nThe Math (Variational Expectation): In Variational Message Passing (VMP), the update minimizes the KL-divergence. This results in an update equation based on the expectation of the log-factor:\n\nln vecmu(x) propto mathbbE_q(y) left ln f(x y) right\n\nWhy q_? To calculate an expectation mathbbE_q(y), the node must know the marginal distribution q(y) of the neighbor. A simple message is not enough; it needs the full summary of the neighbor's belief.","category":"section"},{"location":"manuals/rules_and_nodes/#3.-The-\"Team-Report\"-Protocol-(Structured-Inference)","page":"Understanding Rules","title":"3. The \"Team Report\" Protocol (Structured Inference)","text":"Context: Structured constraints (variables are coupled in blocks).\n\nInput Prefix: q_yz (Joint Marginal)\nThe Logic: \"Variables A and B are strictly coupled. I cannot look at them separately; I need a joint report on how they relate to each other.\"\nThe Math: Similar to VMP, but the expectation is taken over the joint distribution of the coupled variables:\n\nln vecmu(x) propto mathbbE_q(y z) left ln f(x y z) right\n\nWhy q_yz? If y and z are correlated, mathbbEy cdot z neq mathbbEymathbbEz. The rule needs the joint distribution to capture these correlations.","category":"section"},{"location":"manuals/rules_and_nodes/#implementing-a-custom-node","page":"Understanding Rules","title":"Implementing a Custom Node","text":"Sometimes you may 
2need to implement a custom node and its corresponding rule. This is a common task when you need to model a new distribution or a new relationship between variables. To read more about how to implement a custom node and its corresponding rule, see Creating your own custom nodes section.\n\n","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms","page":"Functional form constraints","title":"Built-in Functional Forms","text":"This section describes built-in functional forms that can be used for posterior marginal and/or messages form constraints specification. Read more information about constraints specification syntax in the Constraints Specification section.","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-background","page":"Functional form constraints","title":"Background","text":"In the message passing framework, in order to compute a posterior over some latent state q(x), it is necessary to compute a normalized product of two messages q(x) = fracmu_1(x) mu_2(x)int mu_1(x) mu_2(x) mathrmdx In some situations, when functional forms of mu_1(x) and mu_2(x) are known in advance, it is possible to compute the normalized product efficiently and analytically. It is, however, not always the case, since the messages can have arbitrary functional form and it is not always easy to compute the normalization factor.\n\nFunctional forms help to circumvent this. They implement a custom callback on the product of two messages, which cannot  be computed analytically. Essentially, a functional form constraint defines a functional F, such that  q(x) = Fmu_1 mu_2 approx fracmu_1(x) mu_2(x)int mu_1(x) mu_2(x) mathrmdx\n\nSee also Bethe Free Energy section for more information on variational inference and posterior computation.","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-unspecified-constraint","page":"Functional form constraints","title":"UnspecifiedFormConstraint","text":"Unspecified functional form constraint is used by default and uses only analytical update rules for computing posterior marginals. Throws an error if a product of two colliding messages cannot be computed analytically.\n\nusing RxInfer, Distributions #hide\n@constraints begin \n    # This is the default setting for all latent variables\n    q(x) :: UnspecifiedFormConstraint() \nend\nnothing #hide","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-point-mass-constraint","page":"Functional form constraints","title":"PointMassFormConstraint","text":"The most basic form of posterior marginal approximation is the PointMass function. In a few words PointMass represents the delta function. In the context of functional form constraints PointMass approximation corresponds to the MAP estimate. For a given distribution d, the PointMass functional form simply finds the argmax of the logpdf of q(x), thus q(x) = Fmu_1 mu_2 = delta(x - argmax_x mu_1(x) mu_2(x)). This is especially useful when the exact functional form of q(x) is not available or cannot be parametrized efficiently. \n\n@constraints begin \n    q(x) :: PointMassFormConstraint()\nend\n\n@constraints begin \n    q(x) :: PointMassFormConstraint(starting_point = (args...) -> 1.0)\nend\nnothing #hide","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-sample-list-constraint","page":"Functional form constraints","title":"SampleListFormConstraint","text":"SampleListFormConstraint approximates the resulting posterior marginal (product of two colliding messages) as a list of weighted samples. Hence, it requires one of the arguments to be a proper distribution (or at least the inference backend should be able to sample from it). This setting is controlled with LeftProposal(), RightProposal() or AutoProposal() objects. It also accepts an optional method object, but the only sampling method currently available is BayesBase.BootstrapImportanceSampling.\n\n@constraints begin \n    q(x) :: SampleListFormConstraint(1000)\n    # or \n    q(y) :: SampleListFormConstraint(1000, LeftProposal())\nend\nnothing #hide","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-fixed-marginal-constraint","page":"Functional form constraints","title":"FixedMarginalFormConstraint","text":"Fixed marginal form constraint replaces the resulting posterior marginal obtained during the inference procedure with the prespecified one. Note that the inference backend still tries to compute the real posterior marginal and may fail during this process. This may be useful for debugging. If nothing is passed then the computed posterior marginal is returned (see also UnspecifiedFormConstraint).\n\n@constraints function block_updates(x_posterior = nothing) \n    # `nothing` returns the computed posterior marginal\n    q(x) :: FixedMarginalFormConstraint(x_posterior)\nend\nnothing #hide\n\nIt is also possible to control the constraint manually, e.g:\n\nform_constraint = FixedMarginalFormConstraint(nothing)\n\nconstraints_specification = @constraints function manual_block_updates(form_constraint) \n    q(x) :: form_constraint\nend\n\n# later on ...\nform_constraint.fixed_value = Gamma(1.0, 1.0)","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-composite-constraint","page":"Functional form constraints","title":"CompositeFormConstraint","text":"It is possible to create a composite functional form by stacking operators, e.g:\n\n@constraints begin \n    q(x) :: SampleListFormConstraint(1000) :: PointMassFormConstraint()\nend","category":"section"},{"location":"manuals/variational/functional-forms/#lib-forms-custom-constraints","page":"Functional form constraints","title":"Custom functional forms","text":"See the ReactiveMP.jl library documentation for more information about defining novel custom functional forms that are compatible with ReactiveMP inference backend.\n\n","category":"section"},{"location":"manuals/variational/functional-forms/#RxInfer.PointMassFormConstraint","page":"Functional form constraints","title":"RxInfer.PointMassFormConstraint","text":"PointMassFormConstraint\n\nOne of the form constraint objects. Constrains a message to be in the form of a Dirac delta point mass. By default uses the Optim.jl package to find the argmin of -logpdf(x). Accepts a custom optimizer callback which can be used to customise the optimisation procedure with different packages or different arguments for the Optim.jl package.\n\nKeyword arguments\n\noptimizer: specifies a callback function for logpdf optimisation. See also: RxInfer.default_point_mass_form_constraint_optimizer\nstarting_point: specifies a callback function for the initial optimisation point. See also: RxInfer.default_point_mass_form_constraint_starting_point\nboundaries: specifies a callback function for determining optimisation boundaries. See also: RxInfer.default_point_mass_form_constraint_boundaries\n\nCustom optimizer callback interface\n\n# This is an example of the `custom_optimizer` interface\nfunction custom_optimizer(::Type{ Univariate }, ::Type{ Continuous }, constraint::PointMassFormConstraint, distribution)\n    # should return argmin of the -logpdf(distribution)\nend\n\nCustom starting point callback interface\n\n# This is an example of the `custom_starting_point` interface\nfunction custom_starting_point(::Type{ Univariate }, ::Type{ Continuous }, constraint::PointMassFormConstraint, distribution)\n    # built-in optimizer expects an array, even for a univariate distribution\n    return [ 0.0 ] \nend\n\nCustom boundaries callback interface\n\n# This is an example of the `custom_boundaries` interface\nfunction custom_boundaries(::Type{ Univariate }, ::Type{ Continuous }, constraint::PointMassFormConstraint, distribution)\n    # returns a tuple of `lower` and `upper` boundaries\n    return (-Inf, Inf)\nend\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/functional-forms/#RxInfer.default_point_mass_form_constraint_optimizer","page":"Functional form constraints","title":"RxInfer.default_point_mass_form_constraint_optimizer","text":"default_point_mass_form_constraint_optimizer(::Type{<:VariateType}, ::Type{<:ValueSupport}, constraint::PointMassFormConstraint, distribution)\n\nDefines a default optimisation procedure for the PointMassFormConstraint. By default uses Optim.jl package to find argmin of -logpdf(x). Uses the starting_point and boundaries callbacks to determine the starting point and boundaries for the optimisation procedure.\n\n\n\n\n\n","category":"function"},{"location":"manuals/variational/functional-forms/#RxInfer.default_point_mass_form_constraint_starting_point","page":"Functional form constraints","title":"RxInfer.default_point_mass_form_constraint_starting_point","text":"default_point_mass_form_constraint_starting_point(::Type{<:VariateType}, ::Type{<:ValueSupport}, constraint::PointMassFormConstraint, distribution)\n\nDefines the default starting point for the PointMassFormConstraint. By default uses the support of the distribution. If the support is unbounded, returns a zero point. Otherwise throws an error.\n\n\n\n\n\n","category":"function"},{"location":"manuals/variational/functional-forms/#RxInfer.default_point_mass_form_constraint_boundaries","page":"Functional form constraints","title":"RxInfer.default_point_mass_form_constraint_boundaries","text":"default_point_mass_form_constraint_boundaries(::Type{<:VariateType}, ::Type{<:ValueSupport}, constraint::PointMassFormConstraint, distribution)\n\nDefines the default boundaries for the PointMassFormConstraint. By default simply uses the support of the distribution.\n\n\n\n\n\n","category":"function"},{"location":"manuals/variational/functional-forms/#RxInfer.SampleListFormConstraint","page":"Functional form constraints","title":"RxInfer.SampleListFormConstraint","text":"SampleListFormConstraint([rng], nsamples, strategy, method)\n\nOne of the form constraint objects. Approximates ProductOf with a SampleList object.\n\nArguments\n\nrng: (optional) random number generator used to draw the samples. Defaults to Random.default_rng(). Pass an explicit, seeded generator (e.g. MersenneTwister(42) or a StableRNG) to obtain reproducible sample-list approximations.\nnsamples: number of samples in the resulting SampleList.\nstrategy: proposal selection strategy, one of AutoProposal, LeftProposal or RightProposal. Defaults to AutoProposal().\nmethod: sampling method. Defaults to BootstrapImportanceSampling().\n\nnote: Note\nWhen no rng is provided the constraint uses Random.default_rng(), so the drawn samples depend on the global RNG state. For reproducible inference either seed the global RNG with Random.seed! or pass an explicit rng as the first argument.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/functional-forms/#RxInfer.AutoProposal","page":"Functional form constraints","title":"RxInfer.AutoProposal","text":"Tries to determine the proposal distribution in the SampleList approximation automatically.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/functional-forms/#RxInfer.LeftProposal","page":"Functional form constraints","title":"RxInfer.LeftProposal","text":"Uses the left argument in the prod call as the proposal distribution in the SampleList approximation.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/functional-forms/#RxInfer.RightProposal","page":"Functional form constraints","title":"RxInfer.RightProposal","text":"Uses the right argument in the prod call as the proposal distribution in the SampleList approximation.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/functional-forms/#RxInfer.FixedMarginalFormConstraint","page":"Functional form constraints","title":"RxInfer.FixedMarginalFormConstraint","text":"FixedMarginalFormConstraint\n\nOne of the form constraint objects. Provides a constraint on the marginal distribution such that it remains fixed during inference.  Can be viewed as blocking of updates of a specific edge associated with the marginal. If nothing is passed then the computed posterior marginal is returned. Use .fixed_value field to update the value of the constraint.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/initialization/#initialization","page":"Initialization","title":"Understanding why we need to initialize posteriors or messages in RxInfer","text":"In certain models, after completing the model specification step and moving on to execute the inference procedure, you may encounter the error prompting you to initialize required marginals and messages. \n\nIn RxInfer, initialization is essential because both loopy belief propagation and variational message passing with factorization constraints require well‑defined starting messages and/or marginals. The exact requirements depend on how the model is specified. For this reason, whenever an error indicates missing initialization, the user should carefully review their model structure and intended factorization, and only then define the necessary initial messages and marginals. This tutorial provides intuition for why initialization is needed and demonstrates these ideas through a practical example.\n\nThe syntax for the @initialization macro is similar to the @constraints and @meta macro. An example is shown below:\n\nusing RxInfer\n\n@model function submodel(z, x) \n    t := x + 1\n    z ~ Normal(mean = t, var = 1.0)\nend \n\n@model function mymodel(y)\n    x ~ Normal(mean = 0.0, var = 1.0)\n    z ~ submodel(x = x)\n    y ~ Normal(mean = z, var = 1.0)\nend\n\n@initialization begin\n    # Initialize the marginal for the variable x\n    q(x) = vague(NormalMeanVariance)\n\n    # Initialize the message for the variable z\n    μ(z) = vague(NormalMeanVariance)\n\n    # Specify the initialization for a submodel of type `submodel`\n    for init in submodel\n        q(t) = vague(NormalMeanVariance)\n    end\n\n    # Specify the initialization for a submodel of type `submodel` with a specific index\n    for init in (submodel, 1)\n        q(t) = vague(NormalMeanVariance)\n    end\nend\n\nSimilar to the @constraints macro, the @initialization macro also supports function definitions:\n\n@initialization function my_init()\n    # Initialize the marginal for the variable x\n    q(x) = vague(NormalMeanVariance)\n\n    # Initialize the message for the variable z\n    μ(z) = vague(NormalMeanVariance)\n\n    # Specify the initialization for a submodel of type `submodel`\n    for init in submodel\n        q(t) = vague(NormalMeanVariance)\n    end\n\n    # Specify the initialization for a submodel of type `submodel` with a specific index\n    for init in (submodel, 1)\n        q(t) = vague(NormalMeanVariance)\n    end\nend\n\nThe result of the initialization macro can be passed to the infer function with a keyword argument called initialization.","category":"section"},{"location":"manuals/inference/initialization/#Inline-initialization-on-submodel-calls","page":"Initialization","title":"Inline initialization on submodel calls","text":"Initialization can also be specified inline at the call site of a submodel using the where keyword. This is useful when you want to attach initialization to a specific invocation without defining a separate @initialization block at the outer level. For example:\n\n@model function outer_model(a, b)\n    a ~ inner_model(y = b) where {\n        initialization = @initialization begin\n            q(x) = vague(NormalMeanVariance)\n        end\n    }\nend\n\nInitialization objects created with the @initialization function form can also be passed by reference:\n\n@initialization function my_init()\n    q(x) = vague(NormalMeanVariance)\nend\n\n@model function outer_model(a, b)\n    a ~ inner_model(y = b) where { initialization = my_init() }\nend\n\nInline initialization applies only to the specific submodel invocation it is attached to. If external initialization also targets the same submodel (via for init in submodel blocks passed to infer), the external initialization takes priority and the inline value is ignored.\n\nnote: Combining inline constraints and initialization\nWhen specifying both constraints and initialization inline on the same submodel call, each macro expression must be wrapped in parentheses so that Julia parses the where { ... } block correctly:@mode
2l function outer_model(a, b, c)\n    a ~ inner_model(y = b, z = c) where {\n        constraints = (@constraints begin\n            q(x, y, z) = q(x, y)q(z)\n        end),\n        initialization = (@initialization begin\n            q(x) = vague(NormalMeanVariance)\n        end)\n    }\nendWithout the parentheses the parser will raise a syntax error.","category":"section"},{"location":"manuals/inference/initialization/#Part-1.-Framing-the-problem","page":"Initialization","title":"Part 1. Framing the problem","text":"John has recently acquired a new car and is keenly interested in its fuel consumption rate. He holds the belief that this rate follows a linear relationship with the variable speed. To validate this hypothesis, he plans to conduct tests by driving his car on the urban roads close to his home, recording both the fuel consumption and speed data. To ascertain the fuel consumption rate, John has opted for Bayesian linear regression as his analytical method.\n\nusing Random, Plots, StableRNGs\n\nfunction generate_data(a, b, v, nr_samples; rng = StableRNG(1234))\n    x = float.(collect(1:nr_samples))\n    y = a .* x .+ b .+ randn(rng, nr_samples) .* sqrt(v)\n    return x, y\nend;\n\n# For demonstration purposes we generate some fake data \nx_data, y_data = generate_data(0.5, 25.0, 1.0, 250)\n\nscatter(x_data, y_data, title = \"Dataset (City road)\", legend=false)\nxlabel!(\"Speed\")\nylabel!(\"Fuel consumption\")","category":"section"},{"location":"manuals/inference/initialization/#Univariate-regression-with-known-noise","page":"Initialization","title":"Univariate regression with known noise","text":"First, he drives the car on an urban road. John enjoys driving on the well-built, wide, and flat urban roads. Urban roads also offer the advantage of precise fuel consumption measurement with minimal noise. Therefore John models the fuel consumption y_ninmathbbR as a normal distribution and treats x_n as a fixed hyperparameter:\n\nbeginaligned\np(y_n mid a b) = mathcalN(y_n mid a x_n + b  1)\nendaligned\n\nThe recorded speed is denoted as x_n in mathbbR and the recorded fuel consumption as y_n in mathbbR. Prior beliefs on a and b are informed by the vehicle manual.\n\nbeginaligned\n    p(a) = mathcalN(a mid m_a v_a) \n    p(b) = mathcalN(b mid m_b v_b) \nendaligned\n\nTogether they form the probabilistic model p(y a b) = p(a)p(b) prod_n=1^N p(y_n mid a b) where the goal is to infer the posterior distributions p(a mid y) and p(bmid y).\n\nIn order to estimate the two parameters with the recorded data, he uses RxInfer.jl to create the model described above.\n\nusing RxInfer\n\n@model function linear_regression(y, x)\n    a  ~ Normal(mean = 0.0, variance = 1.0)\n    b  ~ Normal(mean = 0.0, variance = 100.0)\n    y .~ Normal(mean = a .* x .+ b, variance = 1.0)\nend\n\nDelighted with the convenience offered by the package's inference function (infer), he appreciates the time saved from building everything from the ground up. This feature allows him to effortlessly obtain the desired results for his specific road. Upon consulting the documentation, he proceeds to run the inference function.\n\nresults = infer(\n    model        = linear_regression(), \n    data         = (y = y_data, x = x_data), \n    returnvars   = (a = KeepLast(), b = KeepLast()),\n    iterations   = 20,\n    free_energy  = true\n)\n\nOops! Exception?\n\nexception =\n│    Variables [ a, b ] have not been updated after an update event. \n│    Therefore, make sure to initialize all required marginals and messages. See `initialization` keyword argument for the inference function. \n│    See the function documentation for detailed information regarding the initialization.\n\nAfter running the inference procedure an error appears, which prompts him to initialize all required messages and marginals. Now, John is left pondering the reason behind this requirement. Why is it necessary? Should he indeed initialize all messages and marginals? And if so, how might this impact the inference procedure?","category":"section"},{"location":"manuals/inference/initialization/#Part-2.-Why-and-What-to-initialize","page":"Initialization","title":"Part 2. Why and What to initialize","text":"Before delving too deeply into the details, it's important to understand that RxInfer constructs a factorized representation of your model using a factor graph. In this structure, inference is executed through message passing.\n\nA challenge arises when RxInfer generates the FFG representation with structural loops in certain parts of the graph. These loops indicate that a message or marginal within the loop depends not only on its prior but also on itself. Consequently, proper initialization is crucial for initiating the inference process. Two general rules of thumb guide this initialization, although the intricate details are beyond the scope of this tutorial:\n\n1.\tInitiate as few messages/marginals as possible when dealing with a loop structure, it will be more efficient and accurate. 2.\tPrioritize initializing marginals over messages.\n\nHow to i
2dentify and handle the loops?\n\nIdentifying loops is currently a manual process, as the current version of RxInfer doesn't support a graphical representation of the created graph. As such, the manual process involves:\n\n1.\tDeriving the graphical representation of the model, 2.\tIdentifying loops and the messages or marginals that need to be initialized within the loop.\n\nHowever, once you receive the message Variables [x, y, z] have not been updated after an update event, it is a good indication that there is a loop in your model. If you see this message, you should check your model for loops and try to initialize the messages and/or marginals that are part of the loop.","category":"section"},{"location":"manuals/inference/initialization/#Deriving-factor-graph-and-identifying-the-loops","page":"Initialization","title":"Deriving factor graph and identifying the loops","text":"John proceeds to derive the factor graph for his problem where he identifies where the loops are:\n\n(Image: linear_regression_model)\n\nHe does note that there is a loop in his model, namely all a and b variables are connected over all observations, therefore he needs to initialize one of the messages and run multiple iterations for the loopy belief propagation algorithm. Note that loopy belief propagation is not guaranteed to converge in general and might be highly influenced by the choice of the initial messages in the initialization argument. He is going to evaluate the convergence performance of the algorithm with the free_energy = true option:\n\ninit = @initialization begin\n    μ(b) = NormalMeanVariance(0.0, 100.0)\nend\n\nresults = infer(\n    model           = linear_regression(), \n    data            = (y = y_data, x = x_data), \n    initialization  = init, \n    returnvars      = (a = KeepLast(), b = KeepLast()),\n    iterations      = 20,\n    free_energy     = true\n)\n\n# drop first iteration, which is influenced by the `initmessages`\nplot(2:20, results.free_energy[2:end], title=\"Free energy\", xlabel=\"Iteration\", ylabel=\"Free energy [nats]\", legend=false)\n\nNow the inference runs without the error! 🎉\n\nas = rand(results.posteriors[:a], 100)\nbs = rand(results.posteriors[:b], 100)\np = scatter(x_data, y_data, title = \"Linear regression with more noise\", legend=false)\nxlabel!(\"Speed\")\nylabel!(\"Fuel consumption\")\nfor (a, b) in zip(as, bs)\n    global p = plot!(p, x_data, a .* x_data .+ b, alpha = 0.05, color = :red)\nend\nplot(p, size = (900, 400))","category":"section"},{"location":"manuals/inference/initialization/#Implementation-details","page":"Initialization","title":"Implementation details","text":"","category":"section"},{"location":"manuals/inference/initialization/#RxInfer.@initialization","page":"Initialization","title":"RxInfer.@initialization","text":"@initialization begin ... end \n    or\n@initialization function f_name() ... end\n\nMacro for specifying the initialization state of a model: initial marginals and messages.  Accepts either a function or a block of code and is applied to a model through the infer function.\n\nExamples:     @initialization begin         # Initialize the marginal for the variable x         q(x) = vague(NormalMeanVariance)\n\n    # Initialize the message for the variable z\n    μ(z) = vague(NormalMeanVariance)\n\n    # Specify the initialization for a submodel of type `submodel`\n    for init in submodel\n        q(t) = vague(NormalMeanVariance)\n    end \nend\n\nSee the full guide: https://docs.rxinfer.com/stable/manuals/inference/initialization/ \n\n\n\n\n\n","category":"macro"},{"location":"manuals/inference/initialization/#RxInfer.InitializationPlugin","page":"Initialization","title":"RxInfer.InitializationPlugin","text":"InitializationPlugin(init)\n\nA plugin that adds initialization information to the factor nodes of the model.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/initialization/#RxInfer.convert_init_variables","page":"Initialization","title":"RxInfer.convert_init_variables","text":"convert_init_variables(e::Expr)\n\nConverts all variable references on the left-hand side of an init specification to IndexedVariable c
2alls.\n\nArguments\n\ne::Expr: The expression to convert.\n\nReturns\n\nExpr: The resulting expression with all variable references converted to IndexedVariable calls.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/initialization/#RxInfer.convert_init_object","page":"Initialization","title":"RxInfer.convert_init_object","text":"convert_init_object(e::Expr)\n\nConverts a variable init or a factor init call on the left-hand side of an init specification to an RxInfer.InitObject.\n\nArguments\n\ne::Expr: The expression to convert.\n\nReturns\n\nExpr: The resulting expression with the variable reference or factor function call converted to an RxInfer.InitObject.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/initialization/#RxInfer.convert_init_fform","page":"Initialization","title":"RxInfer.convert_init_fform","text":"convert_init_fform(init_obj::Expr)\n\nConverts distribution constructor calls with (kw)args to use RxInfer.convert_fform. Skips conversion for special functions like vague() and huge().\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#autoupdates-guide","page":"Auto-updates","title":"Autoupdates specification","text":"RxInfer supports streaming inference on infinite datastreams, wherein posterior beliefs over latent states update automatically as soon as new observations are available. However, we also aim to update our priors given updated beliefs. Let's begin with a simple example:\n\nusing RxInfer\nusing Test #hide\n\n@model function streaming_beta_bernoulli(a, b, y)\n    θ ~ Beta(a, b)\n    y ~ Bernoulli(θ)\nend\n\nFor this model, the RxInfer engine will update the posterior belief over the variable θ every time we receive a new observation y. However, we also wish to update our prior belief by adjusting the arguments a and b as soon as we have a new belief for the variable θ. The @autoupdates macro automates this process, simplifying the task of writing automatic updates for certain model arguments based on new beliefs within the model. Here's how it could look:\n\nautoupdates = @autoupdates begin \n    a, b = params(q(θ))\nend\n\nThis specification directs the RxInfer inference engine to update a and b by invoking the params function on the posterior q of θ. The params function, defined in the Distributions.jl package, extracts the parameters (a and b in this case) in the form of a tuple of the resulting posterior (Beta) distribution.\n\n# Change the text above if this test is failing\nusing RxInfer, Test, Distributions\nautoupdates = @autoupdates begin \n    a, b = params(q(θ))\nend\n@test RxInfer.getmappingfn(RxInfer.getmapping(RxInfer.getautoupdate(autoupdates, 1))) === Distributions.params\nnothing\n\nNow, we can use the autoupdates structure in the infer function as following:\n\n# The streaming inference supports static datasets as well\ndata = (y = [ 1, 0, 1 ], )\n\nresult = infer(\n    model          = streaming_beta_bernoulli(),\n    autoupdates    = autoupdates,\n    data           = data,\n    keephistory    = 3,\n    initialization = @initialization(q(θ) = Beta(1, 1))\n)\n@test result.history[:θ] == [ Beta(2.0, 1.0), Beta(2.0, 2.0), Beta(3.0, 2.0) ] #hide\n\nresult.history[:θ]\n\nIn this example, we also used the initialization keyword argument.  This is required for latent states, which are used in the @autoupdates specification together with streaming inference.\n\nConsider another example with the following model and auto-update specification:\n\n@model function kalman_filter(y, x_current_mean, x_current_var)\n    x_current ~ Normal(mean = x_current_mean, var = x_current_var)\n    x_next    ~ Normal(mean = x_current, var = 1.0)\n    y         ~ Normal(mean = x_next, var = 1.0)\nend\n\nThis model comprises two arguments representing our prior knowledge of the x_current state of the system.  The latent state x_next represents the subsequent state of the system, linked to the observed variable y.  An auto-update specification could resemble the following:\n\nautoupdates = @autoupdates begin\n    x_current_mean = mean(q(x_next))\n    x_current_var  = var(q(x_next))\nend\n\nThis structure dictates updating our prior immediately upon obtaining a new posterior q(x_next).  It then applies the mean and var functions to the updated posteriors, thereby automatically updating x_current_mean and x_current_var.\n\nresult = infer(\n    model = kalman_filter(),\n    data  = (y = rand(3), ),\n    autoupdates = autoupdates,\n    initialization = @initialization(q(x_next) = NormalMeanVariance(0, 1)),\n    keephistory = 3,\n)\nresult.history[:x_next]\n\nRead more about streaming inference in the Streaming (online) inference section.","category":"section"},{"location":"manuals/inference/autoupdates/#General-syntax","page":"Auto-updates","title":"General syntax","text":"The @autoupdates macro accepts either a block of code or a full function definition. It detects and transforms lines structured as follows:\n\n(model_arguments...) = some_function(model_variables...)\n\nThese lines are referred to as individual autoupdate specifications. Other expressions remain unchanged.  The result of the macro execution is the RxInfer.AutoUpdateSpecification structure that holds the collection  of RxInfer.IndividualAutoUpdateSpecification.\n\nThe @autoupdates macro identifies an individual autoupdate specification if the model_variables... contains:\n\nq(s), which monitors updates from marginal posteriors of an individual variable s or a collection of variables s.\nq(s[i]), which monitors updates from marginal posteriors of the collection of variables s at index i.\n\nExpressions not meeting the above criteria remain unmodified. For instance, an expression like a = f(1) is not considered an individual autoupdate. Therefore, the @autoupdates macro can contain arbitrary expressions and allows for the definition of temporary variables or even functions. Additionally, within an individual autoupdate specification, it is possible to use any intermediate constants, such as a, b = some_function(q(s), a_constant).\n\nThe model_arguments... can either be a single model argument or a tuple of model arguments, as defined within the @model macro. However, it's important to note that if model_arguments... is a tuple, for example in a, b = some_function(q(s)), then some_function must also return a tuple of the same length (of length 2 in this example).\n\n
2Individual autoupdate specifications can involve somewhat complex expressions, as demonstrated below:\n\n@autoupdates begin \n    a = mean(q(θ)) / 2\n    b = 2 * (mean(q(θ)) + 1)\nend\n\nor\n\n@autoupdates begin \n    x = clamp(mean(q(z)), 0, 1)\nend\n\nwarning: Warning\nq(θ)[i] or f(q(θ))[i] syntax is not supported, use getindex(q(θ), i) or getindex(f(q(θ)), i) instead.\n\nThe @autoupdates macro does also support the broadcasting:\n\n@autoupdates begin \n    x = clamp.(mean.(q(z)), 0, 1)\nend\n\nRead more about broadcasting in the official Julia documentation.\n\nAn individual autoupdate can also simultaneously depend on multiple latent states, e.g:\n\nf(args...) = nothing #hide\ng(args...) = nothing #hide\n@autoupdates begin \n    a = f(q(μ), q(s), q(τ))\n    b = g(q(θ))\nend\n\nAs mentioned before, the @autoupdates accepts a full function definition, which can also accepts arbitrary arguments:\n\n@autoupdates function generate_autoupdates(f, condition)\n    if condition \n        a = f(q(θ))\n    else\n        a = f(q(s))\n    end\nend\n\nautoupdates = generate_autoupdates(mean, true)","category":"section"},{"location":"manuals/inference/autoupdates/#The-options-block","page":"Auto-updates","title":"The options block","text":"Optionally, the @autoupdates macro accepts a set of [ options... ] before the main block or the full function definition. The available options are:\n\nwarn = true/false: Enables or disables warnings for an incompatible model. Set to true by default.\nstrict = true/false: Turns warnings into errors. Set to false by default.\n\nautoupdates = @autoupdates [ strict = true ] begin \n    a, b = params(q(θ))\nend\n\nor \n\n@autoupdates [ strict = true ] function generate_autoupdates()\n    a, b = params(q(θ))\nend\nautoupdates = generate_autoupdates()","category":"section"},{"location":"manuals/inference/autoupdates/#Empty-autoupdates","page":"Auto-updates","title":"Empty autoupdates","text":"To specify empty updates:","category":"section"},{"location":"manuals/inference/autoupdates/#Internal-data-structures","page":"Auto-updates","title":"Internal data structures","text":"","category":"section"},{"location":"manuals/inference/autoupdates/#RxInfer.@autoupdates","page":"Auto-updates","title":"RxInfer.@autoupdates","text":"@autoupdates [ options... ] begin \n    argument_to_update = some_function(q(some_variable_from_the_model))\nend\n\nCreates the auto-updates specification for the infer function for the online-streaming Bayesian inference procedure, where  it is important to update prior states based on the new updated posteriors. Read more information about the  @autoupdates syntax in the official documentation.\n\n\n\n\n\n","category":"macro"},{"location":"manuals/inference/autoupdates/#RxInfer.EmptyAutoUpdateSpecification","page":"Auto-updates","title":"RxInfer.EmptyAutoUpdateSpecification","text":"const EmptyAutoUpdateSpecification::AutoUpdateSpecification\n\nAn autoupdates specification that does not update any variables. Use this if you want to do streaming inference without updating the model structure.\n\ninfer(\n    ...\n    autoupdates = RxInfer.EmptyAutoUpdateSpecification,\n    ...\n)\n\n\n\n\n\n","category":"constant"},{"location":"manuals/inference/autoupdates/#RxInfer.AutoUpdateSpecification","page":"Auto-updates","title":"RxInfer.AutoUpdateSpecification","text":"AutoUpdateSpecification(specifications)\n\nA structure that holds a collection of individual auto-update specifications.  Each specification defines how to update the model's arguments  based on the new posterior/messages updates. \n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.parse_autoupdates","page":"Auto-updates","title":"RxInfer.parse_autoupdates","text":"parse_autoupdates(options, expression)\n\nParses the internals of the expression passed to the @autoupdates macro and returns the RxInfer.AutoUpdateSpecification structure.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.autoupdate_check_reserved_expressions","page":"Auto-updates","title":"RxInfer.autoupdate_check_reserved_expressions","text":"autoupdate_check_reserved_expressions(block)\n\nThis function checks if the expression is a valid autoupdate specification some expressions are forbidden within the autoupdate specification.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.numautoupdates","page":"Auto-updates","title":"RxInfer.numautoupdates","text":"Returns the number of auto-updates in the specification\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#Base.isempty-Tuple{RxInfer.AutoUpdateSpecification}","page":"Auto-updates","title":"Base.isempty","text":"Returns true if the auto-update specification is empty\n\n\n\n\n\n","category":"method"},{"location":"manuals/inference/autoupdates/#RxInfer.getautoupdate","page":"Auto-updates","title":"RxInfer.getautoupdate","text":"Returns the individual auto-update specification at the given index\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.addspecification","page":"Auto-updates","title":"RxInfer.addspecification","text":"Appends the individual auto-update specification to the existing specification\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.getvarlabels","page":"Auto-updates","title":"RxInfer.getvarlabels","text":"Returns the labels of the auto-update specification, which are the names of the variables to update\n\n\n\n\n\nReturns the labels of the auto-update specification, which are the names of the variables to update\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.IndividualAutoUpdateSpecification","page":"Auto-updates","title":"RxInfer.IndividualAutoUpdateSpecification","text":"IndividualAutoUpdateSpecification(varlabels, mapping)\n\nA structure that defines how to update a single variable in the model. It consists of the variable labels and the mapping function.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.getmapping","page":"Auto-updates","title":"RxInfer.getmapping","text":"Returns the mapping function of the auto-update specification, which defines how to update the variable\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/autoupdates/#RxInfer.AutoUpdateVariableLabel","page":"Auto-updates","title":"RxInfer.AutoUpdateVariableLabel","text":"AutoUpdateVariableLabel{L, I}(label, [ index = nothing ])\n\nA structure that holds the label of the variable to update and its index. By default, the index is set to nothing.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.AutoUpdateMapping","page":"Auto-updates","title":"RxInfer.AutoUpdateMapping","text":"AutoUpdateMapping(mappingFn, arguments)\n\nA structure that holds the mapping function and the arguments for the individual auto-update specification.\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.AutoUpdateFetchMarginalArgument","page":"Auto-updates","title":"RxInfer.AutoUpdateFetchMarginalArgument","text":"This autoupdate would fetch updates from the marginal of a variable\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.AutoUpdateFetchMessageArgument","page":"Auto-updates","title":"RxInfer.AutoUpdateFetchMessageArgument","text":"This autoupdate would fetch updates from the last message (in the array of messages) of a variable\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/autoupdates/#RxInfer.prepare_autoupdates_for_model","page":"Auto-updates","title":"RxInfer.prepare_autoupdates_for_model","text":"prepare_autoupdates_for_model(autoupdates, model)\n\nThis function extracts the variables saved in the autoupdates from the model. Replaces AutoUpdateFetchMarginalArgument and AutoUpdateFetchMessageArgument with actual streams.\n\n\n\n\n\n","category":"function"},{"location":"contributing/guide/#contributing-overview","page":"Contribution guide","title":"Contributing","text":"Welcome to the contribution guide for RxInfer.jl. Here you'll find information on the RxInfer project structure, and how to get started with contributing to the project. For more practical instructions and guidelines, refer to the contribution guidelines.","category":"section"},{"location":"contributing/guide/#Project-structure","page":"Contribution guide","title":"Project structure","text":"RxInfer.jl is a Julia package that provides a high-level interface for probabilistic programming. It is composed of three major core dependencies:\n\nRocket.jl: A package for reactive programming, allowing asynchronous data processing\nGraphPPL.jl: A domain-specific language for probabilistic programming, facilitating the @model macro and other crucial user-facing features.\nReactiveMP.jl: Reactive message passing engine, using Rocket.jl to pass messages between nodes in a probabilistic model defined with GraphPPL.jl.\n\nIn general, non-inference related functionality is implemented in Rocket.jl and GraphPPL.jl, while inference-related functionality is implemented in ReactiveMP.jl. For example, all factor nodes and inference rules for messages are implemented in ReactiveMP.jl.","category":"section"},{"location":"contributing/guide/#Getting-started","page":"Contribution guide","title":"Getting started","text":"To familiarize yourself with development in RxInfer, we recommend the following steps:\n\nFamiliarize yourself with the collaborative tools used in the project. RxInfer uses GitHub for version control, issue tracking, and pull requests. We aim to maintain the good first issue label on issues that are suitable for new contributors. Furthermore, the core development team tracks the project's progress and development tasks on the project board. Because the project board can be over
2whelming, we recommend focusing first on issues labeled with the good first issue label.\nRead the contribution guidelines to understand the contribution process and best practices for contributing to RxInfer, as well as coding practices and testing procedures.\nFamiliarize yourself with the RxInfer codebase and its core dependencies. While most information can be found on the RxInfer documentation page, it is also recommended to read the documentation for Rocket.jl, GraphPPL.jl, and ReactiveMP.jl to understand the core functionality and design principles of the project.\nPick an issue to work on. We recommend starting with a good first issue to familiarize yourself with the contribution process. Once you're comfortable with the process, you can move on to more complex issues.","category":"section"},{"location":"contributing/guide/#Contribution-guidelines","page":"Contribution guide","title":"Contribution guidelines","text":"The contribution guidelines provide detailed instructions on how to contribute effectively to the project. They cover reporting bugs, suggesting features, and contributing code. For more information, refer to the contribution guidelines.\n\n","category":"section"},{"location":"manuals/inference/meta-specification/#user-guide-meta-specification","page":"Meta specification","title":"Meta Specification","text":"RxInfer.jl utilizes the GraphPPL.jl package to construct a factor graph representing a probabilistic model, and then employs the ReactiveMP.jl package to conduct variational inference through message passing on this factor graph. Some factor nodes within the ReactiveMP.jl inference engine require an additional structure, known as meta-information. This meta-information can serve various purposes such as providing extra details to nodes, customizing the inference process, or adjusting how nodes compute outgoing messages. For example, the AR node, which models Auto-Regressive processes, needs to know the order of the AR process. Similarly, the GCV node (Gaussian Controlled Variance) requires an approximation method to handle non-conjugate relationships between its variables. To address these needs, RxInfer.jl utilizes the @meta macro from the GraphPPL.jl package to specify node-specific meta-information and contextual details.\n\nHere, we only touch upon the basics of the @meta macro. For further details, please consult the official documentation of the GraphPPL.jl package.","category":"section"},{"location":"manuals/inference/meta-specification/#General-syntax","page":"Meta specification","title":"General syntax","text":"The @meta macro accepts either a regular Julia function or a single begin ... end block:\n\nusing RxInfer\n\nstruct MetaObject\n    arg1\n    arg2\nend\n\n@meta function create_meta(arg1, arg2)\n    Normal(y, x) -> MetaObject(arg1, arg2)\nend\n\nmy_meta = @meta begin \n    Normal(y, x) -> MetaObject(1, 2)\nend\n\nnothing #hide\n\nIn the first case, it returns a function that produces an object c
2ontaining metadata when called. For instance, to specify meta for an AR node with an order of 5, you can do the following:\n\n@meta function ARmodel_meta(num_order)\n    AR() -> ARMeta(Multivariate, num_order, ARsafe())\nend\n\nmy_meta = ARmodel_meta(5)\nnothing #hide\n\nIn the second case, it directly provides the meta object. The same meta for the AR node can also be defined as follows:\n\nnum_order = 5\n\nmy_meta = @meta begin \n    AR() -> ARMeta(Multivariate, num_order, ARsafe())\nend\nnothing #hide\n\nBoth syntax variations provide the same meta specification and there is no preference given to one over the other. \n\nAnother example:\n\nmy_meta = @meta begin \n    GCV(x, k, w) -> GCVMetadata(GaussHermiteCubature(20))\nend\nnothing #hide\n\nThis meta specification indicates that for every GCV node in the model with x, k and w as connected variables should use the GCVMetadata(GaussHermiteCubature(20)) meta object.\n\nYou can have a list of as many meta specification entries as possible for different nodes:\n\nmy_meta = @meta begin \n    GCV(x1, k1, w1) -> GCVMetadata(GaussHermiteCubature(20))\n    AR() -> ARMeta(Multivariate, 5, ARsafe())\nend\nnothing #hide\n\nThe meta-information object can be used in the infer function that accepts meta keyword argument:\n\ninferred_result = infer(\n    model = my_model(arguments...),\n    data  = ...,\n    meta  = my_meta,\n    ...\n)\n\nUsers can also specify metadata for nodes directly inside @model, without the need to use @meta. For example:\n\n@model function my_model()\n    ...\n\n    y ~ AR(x, θ, γ) where { meta = ARMeta(Multivariate, 5, ARsafe()) }\n\n    ...\nend\n\nIf you add node-specific meta to your model this way, you do not need to use the meta keyword argument in the infer function.","category":"section"},{"location":"manuals/inference/meta-specification/#Create-your-own-meta","page":"Meta specification","title":"Create your own meta","text":"Although some nodes in RxInfer.jl already come with their own meta structure, users have the flexibility to define different meta structures for those nodes and also for custom ones. A meta structure is created by using the struct statement in Julia. For example, the following snippet of code illustrates how you can create your own meta structures for your custom node. This section provides a concrete example of how to create and use meta in RxInfer.jl. Suppose that we have the following Gaussian model:\n\nbeginaligned\n x  sim mathrmNormal(25 05)\n y  sim mathrmNormal(2*x 20)\nendaligned\n\nwhere y is observable data and x is a latent variable. In RxInfer.jl, the inference procedure for this model is well defined without the need of specifying any meta data for the Normal node.\n\nusing RxInfer\n\n#create data\ny_data = 4.0 \n\n#make model\n@model function gaussian_model(y)\n    x ~ NormalMeanVariance(2.5, 0.5)\n    y ~ NormalMeanVariance(2*x, 2.)\nend\n\n#do inference\ninference_result = infer(\n    model = gaussian_model(),\n    data = (y = y_data,)\n)\n\nHowever, let's say we would like to experiment with message update rules and define a new inference procedure by introducing a meta structure to the Normal node that always yields a message equal to Normal distribution with mean m clamped between lower_limit and upper_limit for the outbound messages of the node. For background on what rules are and how they work, see Understanding Rules. This is done as follows:\n\n#create your new meta structure for Normal node\nstruct MetaConstrainedMeanNormal{T}\n    lower_limit :: T\n    upper_limit :: T\nend\n\n#define rules with meta for the Normal node\n@rule NormalMeanVariance(:out, Marginalisation) (q_μ::Any, q_v::Any, meta::MetaConstrainedMeanNormal) = begin\n    return NormalMeanVariance(clamp(mean(q_μ), meta.lower_limit, meta.upper_limit), mean(q_v))\nend\n\n@rule NormalMeanVariance(:μ, Marginalisation) (q_out::Any, q_v::Any, meta::MetaConstrainedMeanNormal) = begin\n    return NormalMeanVariance(clamp(mean(q_out), meta.lower_limit, meta.upper_limit), mean(q_v))\nend\n\n#make model\n@model function gaussian_model_with_meta(y)\n    x ~ NormalMeanVariance(2.5, 0.5)\n    y ~ NormalMeanVariance(2*x, 2.)\nend\n\ncustom_meta = @meta begin\n    NormalMeanVariance(y) -> MetaConstrainedMeanNormal(-2, 2)\nend\n\n#do inference\ninference_result = infer(\n    model = gaussian_model(),\n    data = (y = y_data,),\n    meta = custom_meta\n)\n\nprintln(\"Estimated mean for latent state `x` is \", mean(inference_result.posteriors[:x]), \" with standard deviation \", std(inference_result.posteriors[:x]))\n\nwarning: Warning\nThe above example is not mathematically correct. It is only used to show how we 
2can work with @meta as well as how to create a meta structure for a node in RxInfer.jl.\n\nRead more about the @meta macro in the official documentation of GraphPPL.","category":"section"},{"location":"manuals/inference/meta-specification/#Adding-metadata-to-nodes-in-submodels","page":"Meta specification","title":"Adding metadata to nodes in submodels","text":"Similarly to the @constraints macro, the @meta macro exposes syntax to push metadata to nodes in submodels. With the for meta in submodel syntax we can apply metadata to nodes in submodels. For example, if we use the gaussian_model_with_meta model in a larger model, we can write:\n\ncustom_meta = @meta begin\n    for meta in gaussian_model_with_meta\n        NormalMeanVariance(y) -> MetaConstrainedMeanNormal(-2, 2)\n    end\nend\n\n","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#rule-not-found","page":"Rule Not Found Error","title":"Rule Not Found Error","text":"When using RxInfer, you might encounter a RuleNotFoundError. This error occurs during message-passing inference when the system cannot find appropriate update rules for computing messages between nodes in your factor graph. Let's understand why this happens and how to resolve it.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#Why-does-this-happen?","page":"Rule Not Found Error","title":"Why does this happen?","text":"Message-passing inference works by exchanging messages between nodes in a factor graph. Each message represents a probability distribution, and the rules for computing these messages depend on:\n\nThe type of the factor node (e.g., Normal, Gamma, etc.)\nThe types of incoming messages (e.g., Normal, PointMass, etc.) \nThe interface through which the message is being computed\nThe inference method being used (Belief Propagation or Variational Message Passing)\n\nThe last point is particularly important - some message update rules may exist for Variational Message Passing (VMP) but not for Belief Propagation (BP), or vice versa. This is because BP aims to compute exact posterior distributions through message passing (when possible), while VMP approximates the posterior using the Bethe approximation. For a detailed mathematical treatment of these differences, see our Bethe Free Energy implementation guide.\n\nFor example, consider this simple model:\n\n@model function problematic_model()\n    μ ~ Normal(mean = 0.0, variance = 1.0)\n    τ ~ Gamma(shape = 1.0, rate = 1.0)\n    y ~ Normal(mean = μ, precision = τ)\nend\n\nThis model will fail with a RuleNotFoundError because there are no belief propagation message passing update rules available for this combination of distributions - only variational message passing rules exist. Even though the model looks simple, the message passing rules needed for exact inference do not exist in closed form.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#Common-scenarios","page":"Rule Not Found Error","title":"Common scenarios","text":"You're likely to encounter this error when:\n\nUsing non-conjugate pairs of distributions (e.g., Beta prior with Normal likelihood with precision parameterization)\nWorking with custom distributions or factor nodes without defining all necessary update rules\nUsing complex transformations between variables that don't have defined message computations\nMixing different types of distributions in ways that don't have analytical solutions","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#Design-Philosophy","page":"Rule Not Found Error","title":"Design Philosophy","text":"RxInfer prioritizes performance over generality in its message-passing implementation. By default, it only uses analytically derived message update rules, even in cases where numerical approximations might be possible. This design choice:\n\nEnsures fast and reliable inference when rules exist\nAvoids potential numerical instabilities from approximations\nThrows an error when analytical solutions don't exist\n\nThis means you may encounter RuleNotFoundError even in cases where approximate solutions could theoretically work. This is intentional - RxInfer will tell you explicitly when you need to consider alternative approaches rather than silently falling back to potentially slower or less reliable approximations. See the Solutions section below for more details.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#Visualizing-the-message-passing-gra
2ph","page":"Rule Not Found Error","title":"Visualizing the message passing graph","text":"To better understand where message passing rules are needed, let's look at a simple factor graph visualization:\n\ngraph LR\n    %% Other parts of the graph\n    g1[g] -.-> x\n    h1[h] -.-> z\n    y -.-> g2[p]\n    \n    %% Main focus area\n    x((x)) -.- m1[[\"μ<sub>x→f</sub>\"]] --> f[f]\n    f --> m2[[\"μ<sub>f→y</sub>\"]] -.- y((y))\n    z((z)) -.- m3[[\"μ<sub>z→f</sub>\"]] --> f\n\n    %% Styling\n    classDef variable fill:#b3e0ff,stroke:#333,stroke-width:2px;\n    classDef factor fill:#ff9999,stroke:#333,stroke-width:2px,shape:square;\n    classDef otherFactor fill:#ff9999,stroke:#333,stroke-width:2px,opacity:0.3;\n    classDef message fill:none,stroke:none;\n    class x,y,z variable;\n    class f factor;\n    class g1,g2,h1 otherFactor;\n    class m1,m2,m3 message;\n\nIn this example:\n\nVariables (x, y, z) are represented as circles\nThe factor node (f) is represented as a square\nMessages (μ) flow along the edges between variables and factors, with subscripts indicating direction (e.g., x→f flows from x to f)\nFaded nodes (g, h) represent other parts of the factor graph that aren't relevant for this local message computation\n\nTo compute the outgoing message f→y, RxInfer needs:\n\nRules for how to process incoming messages x→f and z→f\nRules for combining these messages based on the factor f's type\nRules for producing the outgoing message type that y expects\n\nA RuleNotFoundError occurs when any of these rules are missing. For example, if x sends a Normal message but f doesn't know how to process Normal inputs, or if f can't produce the type of message that y expects.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#rule-not-found-solutions","page":"Rule Not Found Error","title":"Solutions","text":"","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#1.-Convert-to-conjugate-pairs","page":"Rule Not Found Error","title":"1. Convert to conjugate pairs","text":"First, try to reformulate your model using conjugate prior-likelihood pairs. Conjugate pairs have analytical solutions for message passing and are well-supported in RxInfer. For example, instead of using a Normal likelihood with Beta prior on its precision, use a Normal-Gamma conjugate pair. See Conjugate prior - Wikipedia for a comprehensive list of conjugate distributions.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#2.-Check-available-rules","page":"Rule Not Found Error","title":"2. Check available rules","text":"If conjugate pairs aren't suitable, verify if your combination of distributions and message types is supported. RxInfer provides many predefined rules, but not all combinations are possible. A good starting point is to check the List of available nodes section in the documentation of ReactiveMP.jl.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#3.-Create-custom-update-rules","page":"Rule Not Found Error","title":"3. Create custom update rules","text":"If you need specific message computations, you can define your own update rules. See Creating your own custom nodes for a detailed guide on implementing custom nodes and their update rules.","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#4.-Use-approximations","page":"Rule Not Found Error","title":"4. Use approximations","text":"When exact message updates aren't available, consider:\n\nUsing simpler distribution pairs that have defined rules\nEmploying approximation techniques like moment matching or the methods described in Meta Specification and Deterministic nodes","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#5.-Use-variational-inference","page":"Rule Not Found Error","title":"5. Use variational inference","text":"Sometimes, adding appropriate factorization constraints can help avoid problematic message computations:\n\nconstraints = @constraints begin\n    q(μ, τ) = q(μ)q(τ)  # Mean-field assumption\nend\n\nresult = infer(\n    model = problematic_model(),\n    constraints = constraints,\n)\n\nnote: Note\nWhen using variational constraints, you will likely need to initialize certain messages or marginals to handle loops in the factor graph. See Initialization for details on how to properly initialize your model.\n\nFor more details on constraints and variational inference, see:\n\nConstraints Specification for a complete guide on using constraints\nBethe Free Energy for the mathematical background on variational inference and message passing","category":"section"},{"location":"manuals/sharpbits/rule-not-found/#Implementation-detail
2s","page":"Rule Not Found Error","title":"Implementation details","text":"When RxInfer encounters a missing rule, it means one of these is missing:\n\nA @rule definition for the specific message direction and types\nA @marginalrule for computing joint marginals\nAn @average_energy implementation for free energy computation\n\nFor an explanation of what rules are and how they work, see Understanding Rules. You can add these using the methods described in Creating your own custom nodes.\n\nnote: Note\nNot all message-passing rules have analytical solutions. In such cases, you might need to use numerical approximations or choose different model structures.\n\n","category":"section"},{"location":"manuals/inference/create-node/#create-node","page":"Creating your own custom nodes","title":"Creating your own custom nodes","text":"Welcome to the RxInfer documentation on creating custom factor graph nodes. In RxInfer, factor nodes represent functional relationships between variables, also known as factors. Together, these factors define your probabilistic model. Quite often these factors represent distributions, denoting how a certain parameter affects another. However, other factors are also possible, such as ones specifying linear or non-linear relationships. RxInfer already supports a lot of factor nodes, however, depending on the problem that you are trying to solve, you may need to create a custom node that better fits the specific requirements of your model. This tutorial will guide you through the process of defining a custom node in RxInfer, step by step. By the end of this tutorial, you will be able to create your own custom node and integrate it into your model.\n\nnote: Note\nBefore we dive into the details of how to implement a custom node and its corresponding rule, read what a rule is and how it works in the Understanding Rules section. In addition, read another section on a different way of running inference with custom stochastic nodes without explicit rule specification here.\n\n\n\nTo create a custom node in RxInfer, 4 steps are required:\n\nCreate your custom node in RxInfer using the @node macro.\nDefine the corresponding message passing update rules with the @rule macro. These rules specify how the node processes information in the form of messages, and how it communicates the results to adjacent parts of the model.\nSpecify computations for marginal distributions of the relevant variables with the @marginalrule macro.\nImplement the computation of the Free Energy in a node with the @average_energy macro.\n\nThroughout this tutorial, we will create a node for the Bernoulli distribution. The Bernoulli distribution is a commonly used distribution in statistical modeling that is often used to model a binary outcome, such as a coin flip. By recreating this node, we will be able to demonstrate the process of creating a custom node, from notifying RxInfer of the nodes existence to implementing the required methods. While this tutorial focuses on the Bernoulli distribution, the principles can be applied to creating custom nodes for other distributions as well. So let's get started!","category":"section"},{"location":"manuals/inference/create-node/#Problem-statement","page":"Creating your own custom nodes","title":"Problem statement","text":"Jane wants to determine whether a coin is a fair coin, meaning that is equally likely to land on heads or tails. In order to determine this, she will throw the coin K=20 times and write down how often it lands on heads and tails. The result of this experiment is a realization of the underlying stochastic process. Jane models the outcome of the experiment x_kin01 using the Bernoulli distribution as\n\np(x_k mid pi) = mathrmBer(x_k mid pi) = pi^x_k (1-pi)^1-x_k\n\nwhere pi in01 denotes the probability that she throws heads, also known as the success probability. Jane also has a prior belief (initial guess) about the value of pi which she models using the Beta distribution as\n\np(pi) = mathrmBeta(pi mid 4 8)\n\nWith this prior belief, the total probabilistic model that she has for this experiment is given by\n\np(x_1K pi) = p(pi) prod_k=1^K p(x_k mid pi)\n\nJane is interested in determining the fairness of the coin. Therefore she aims to infer 
2(calculate) the posterior belief of pi, p(pi mid x_1K), denoting how pi is distributed after we have seen the data.\n\n","category":"section"},{"location":"manuals/inference/create-node/#Step-1:-Creating-the-custom-node","page":"Creating your own custom nodes","title":"Step 1: Creating the custom node","text":"note: Note\nIn this example we will assume that the Bernoulli node and distribution do not yet exist. The RxInfer already defines the node for the Bernoulli distribution from the Distributions.jl package.\n\nFirst things first, let's import RxInfer:\n\nusing RxInfer\n\nIn order to define a custom node using the @node macro from ReactiveMP, we need the following three arguments:\n\nThe name of the node.\nWhether the node is Deterministic or Stochastic.\nThe interfaces of the node and any potential aliases.\n\nFor the name of the node we wish to use MyBernoulli in this tutorial (Bernoulli already exists). However, the corresponding distribution does not yet exist. Therefore we need to specify it first as\n\n# struct for Bernoulli distribution with success probability π\nstruct MyBernoulli{T <: Real} <: ContinuousUnivariateDistribution\n    π :: T\nend\n\n# for simplicity, let's also specify the mean of the distribution\nDistributions.mean(d::MyBernoulli) = d.π\n\nnothing # hide\n\nnote: Note\nYou can use regular functions, e.g + as a node type. Their Julia type, however, is written with the typeof(_) specification, e.g. typeof(+)\n\nFor our node we are dealing with a stochastic node, because the node forms a probabilistic relationship. This means that for a given value of pi, we do know the corresponding value of the output, but we do have some belief about this. Deterministic nodes include for example linear and non-linear transformation, such as + or *.\n\nThe interfaces specify what variables are connected to the node. The first argument is its output by convention. The ordering is important for both the model specification as the rule definition. As an example consider the NormalMeanVariance factor node. This factor node has interfaces [out, μ, v] and can be called in the model specification language as x ~ NormalMeanVariance(μ, v). It is also possible to use aliases for the interfaces, which can be specified in a tuple as you will see below.\n\nConcluding, we can create the MyBernoulli factor node as\n\n@node MyBernoulli Stochastic [out, (π, aliases = [p])]\n\nCool! Step 1 is done, we have created a custom node.\n\n","category":"section"},{"location":"manuals/inference/create-node/#Step-2:-Defining-rules-for-our-node","page":"Creating your own custom nodes","title":"Step 2: Defining rules for our node","text":"In order for RxInfer to perform probabilistic inference and compute posterior distributions, such as p(pimid x_1K), we need to tell it how to perform inference locally around our node. This localization is what makes RxInfer achieve high performance. In our message passing-based paradigm, we need to describe how the node processes incoming information in the form of messages (or marginals). Here we will highlight two different message passing strategies: sum-product message passing and variational message passing.","category":"section"},{"location":"manuals/inference/create-node/#Sum-product-message-passing-update-rules","page":"Creating your own custom nodes","title":"Sum-product message passing update rules","text":"In sum-product message passing we compute outgoing messages to our node as\n\nvecmu(x) propto int mathrmBer(xmid pi) vecmu(pi) mathrmdx\n\noverleftarrowmu(pi) propto sum_x in 01 mathrmBer(xmid pi) overleftarrowmu(x)\n\nThis integral does not always have nice tractable solutions. However, for some forms of the incoming messages, it does yield a tractable solution.\n\nFor the case of a Beta message coming into our node, the outgoing message will be the predictive posterior of the Bernoulli distribution with a Beta prior. Here we obtain pi = fracalphaalpha + beta, which coincides with the mean of the Beta distribution. Hence, we can write down the first update rule using the @rule macro as\n\n@rule MyBernoulli(:out, Marginalisation) (m_π :: Beta,) = MyBernoulli(mean(m_π))\n\nHere, :out refers to the interface of the outgoing message. The second argument denotes the incoming messages (which can be typed) as a tuple. Therefore make sure that it has a trailing , when there is a single message coming in. m_π is shorthand for the incoming message on interface π. As we will see later, the structured approximation update rule for incoming message from π will have q_π as parameter. For more details on why some rules use m_ prefixes while others use q_ prefixes, see Understanding Rules.\n\nThe second rule is also straightforward; if π is a PointMass and therefore fixed, the outgoing message will be MyBernoulli(π):\n\n@rule MyBernoulli(:out, Marginalisation) (m_π :: PointMass,) = MyBernoulli(mean(m_π))\n\nContinuing with the sum-product update rules, we now have to define the update rules towards the π interface. We can only do exact inference if the incoming message is known, which in the case of the Bernoulli distribution, means that the out message is a PointMass distribution that is either 0 or 1. The updated Beta distribution for π will be:\n\noverleftarrowmu(π) propto mathrmBeta(1 + x 2 - x)\n\nWhich gives us the following update rule:\n\n@rule MyBernoulli(:π, Marginalisation) (m_out :: PointMass,) = begin\n    p = mean(m_out)\n    return Beta(one(p) + p, 2one(p) - p)\nend","category":"section"},{"location":"manuals/inference/create-node/#Variational-message-passing-update-rules","page":"Creating your own custom nodes","title":"Variational message passing update rules","text":"We will now cover our second set of update rules. The sum-product messages are not always tractable and therefore we may 
2need to resort to approximations. Here we highlight the variational approximation. In variational message passing we compute outgoing messages to our node as\n\nvecnu(x) propto exp int q(pi) ln mathrmBer(xmid pi) mathrmdx\n\noverleftarrownu(pi) propto exp sum_x in 01 q(x) ln mathrmBer(xmid pi)\n\nThese messages depend on the marginals on the adjacent edges and not on the incoming messages as was the case with sum-product message passing. Update rules that operate on the marginals instead of the incoming messages are specified with the q_{interface} argument names. With these update rules, we can often support a wider family of distributions. For a detailed explanation of why variational message passing requires q_ prefixes instead of m_ prefixes, see Understanding Rules. Below we directly give the variational update rules. Deriving them yourself will be a nice challenge.\n\n#rules towards out\n@rule MyBernoulli(:out, Marginalisation) (q_π :: PointMass,) = MyBernoulli(mean(q_π))\n\n@rule MyBernoulli(:out, Marginalisation) (q_π::Any,) = begin\n    rho_1 = mean(log, q_π)          # E[ln(x)]\n    rho_2 = mean(mirrorlog, q_π)    # E[log(1-x)]\n    m = max(rho_1, rho_2)\n    tmp = exp(rho_1 - m)\n    p = clamp(tmp / (tmp + exp(rho_2 - m)), tiny, one(m))\n    return Bernoulli(p)\nend\n\n#rules towards π\n@rule MyBernoulli(:π, Marginalisation) (q_out :: Any,) = begin\n    p = mean(q_out)\n    return Beta(one(p) + p, 2one(p) - p)\nend\n\nnote: Note\nTypically, the type of the variational distributions q_ does not matter in the real computations, but only their statistics, e.g mean or var. Thus, in this case, we may safely use ::Any.\n\nIn the example that we will show later on, we solely use sum-product message passing. Variational message passing requires us to set the local constraints in our model, something which is out of scope of this tutorial.\n\n","category":"section"},{"location":"manuals/inference/create-node/#Step-3:-Defining-joint-marginals-for-our-node","page":"Creating your own custom nodes","title":"Step 3: Defining joint marginals for our node","text":"The entire probabilistic model can be scored using the Bethe free energy, which bounds the log-evidence for acyclic graphs. This Bethe free energy consists out of the sum of node-local entropies, negative node-local average energies and edge specific entropies. Formally we can denote this by\n\nFqf = - sum_ainmathcalV mathrmHq_a(s_a) - sum_ainmathcalVmathrmE_q_a(s_a)ln f_a(s_a) + sum_iinmathcalEmathrmHq_i(s_i)\n\nHere we call q_a(s_a) the joint marginals around a node and -mathrmE_q_a(s_a)ln f_a(s_a) we term the average energy.\n\nIn order to be able to compute the Bethe free energy, we need to first describe how to compute q_a(s_a), defined in our case as \n\nq(x_k pi) = vecmu(pi) overleftarrowmu(x_k) mathrmBer(x_k mid pi)\n\nTo calculate the updated posterior marginal for our custom distribution, we need to return joint posterior marginals for the interfaces of our node. In our case, the posterior marginal for the observation is still the same PointMass distribution. However, to calculate the posterior marginal over π, we use RxInfer's built-in prod functionality to multiply the Beta prior with the Beta likelihood. This gives us the updated posterior distribution, which is also a Beta distribution. We use PreserveTypeProd(Distribution) parameter to ensure that we multiply the two distributions analytically. This is done as follows:\n\n@marginalrule MyBernoulli(:out_π) (m_out::PointMass, m_π::Beta) = begin\n    r = mean(m_out)\n    p = prod(PreserveTypeProd(Distribution), Beta(one(r) + r, 2one(r) - r), m_π)\n    return (out = m_out, p = p)\nend\n\nIn this code :out_π describes the arguments of the joint marginal distribution. The second argument contains the incoming messages. Here we know from the model specification that we observe out and therefore this has to be a PointMass. Because it is a PointMass, the joint marginal automatically factorizes as q(x_k pi) = q(x_k)q(pi). These are the distributions that we return in a form of the NamedTuple. NamedTuple is used only in cases where we know that the joint marginal factorizes further, but typically it should be a full distribution. For computing q(pi) we need to compute the product vecmu(pi)overleftarrowmu(pi). We already know how overleftarrowmu(pi) looks like from the previous step, so we can just use the prod function.\n\n","category":"section"},{"location":"manuals/inference/create-node/#Step-4:-Defining-the-average-energy-for-our-node","page":"Creating your own custom nodes","title":"Step 4: Defining the average energy for our node","text":"To complete the computation of the Bethe free energy, we also need to compute the average energy term. The average energy in our MyBernoulli example can be computed as -mathrmE_q(x_k pi)ln p(x_k mid pi), however, because we know that we observe x_k and therefore q(x_k pi) factorizes, we can instead compute beginaligned -mathrmE_q(x_k)q(pi)ln p(x_k mid pi) = -mathrmE_q(x_k)q(pi) ln (pi^x_k (1-pi)^1 - x_k) \n= -mathrmE_q(x_k)q(pi) x_k ln(pi) + (1-x_k) ln(1-pi) \n= -mathrmE_q(x_k)x_k mathrmE_q(pi) ln(pi) - (1-mathrmE_q(x_k)x_k) mathrmE_q(pi)ln(1-pi) endaligned\n\nWhich is what we implemented below. Note that mean(mirrorlog, q(x)) is equal to mathrmE_q(x)log(1-x).\n\n@average_energy MyBernoulli (q_out::Any, q_π::Any) = -mean(q_out) * mean(log, q_π) - (1.0 - mean(q_out)) * mean(mirrorlog, q_π)\n\nIn the case that the interfaces do not factorize, we would get something like @average_energy MyBernoulli (q_out_π::Any,) = begin ... end.","category":"section"}
2,{"location":"manuals/inference/create-node/#Using-our-node-in-a-model","page":"Creating your own custom nodes","title":"Using our node in a model","text":"With all the necessary functions defined, we can proceed to test our custom node in an experiment. For this experiment, we will generate a dataset from a Bernoulli distribution with a fixed success probability of 0.75. Next, we will define a probabilistic model that has a Beta prior and a MyBernoulli likelihood. The Beta prior will be used to model our prior belief about the probability of success. The MyBernoulli likelihood will be used to model the generative process of the observed data. We start by generating the dataset:\n\nusing Random\n\nrng = MersenneTwister(42)\nn = 500\nπ_real = 0.75\ndistribution = Bernoulli(π_real)\n\ndataset = float.(rand(rng, distribution, n))\n\nnothing # hide\n\nNext, we define our model. Note that we use the MyBernoulli node in the model. The model consists of a single latent variable π, which has a Beta prior and is the parameter of the MyBernoulli likelihood. The MyBernoulli node takes the value of π as its parameter and returns a binary observation. We set the hyperparameters of the Beta prior to be 4 and 8, respectively, which correspond to a distribution slightly biased towards higher values of π. The model is defined as follows:\n\n@model function coin_model_mybernoulli(y)\n    # We endow θ parameter of our model with some prior\n    π ~ Beta(4.0, 8.0)\n    # We assume that outcome of each coin flip is governed by the MyBernoulli distribution\n    for i in eachindex(y)\n        y[i] ~ MyBernoulli(π)\n    end\nend\n\nFinally, we can run inference with this model and the generated dataset:\n\nresult_mybernoulli = infer(\n    model = coin_model_mybernoulli(), \n    data  = (y = dataset, ),\n)\n\nWe have now completed our experiment and obtained the posterior marginal distribution for p through inference. To evaluate the performance of our inference, we can compare the estimated posterior to the true value. In our experiment, the true value for p is 0.75, and we can see that the estimated posterior has a mean close to this value, which shows that our custom node was able to successfully pass messages towards the π variable in order to learn the true value of the parameter.\n\nusing Plots\n\nrθ = range(0, 1, length = 1000)\n\np = plot(title = \"Inference results\")\n\nplot!(rθ, (x) -> pdf(result_mybernoulli.posteriors[:π], x), fillalpha=0.3, fillrange = 0, label=\"p(π|x)\", c=3)\nvline!([π_real], label=\"Real π\")\n\nAs a sanity check, we can create the same model with the RxInfer built-in node Bernoulli and compare the resulting posterior distribution with the one obtained using our custom MyBernoulli node. This will give us confidence that our custom node is working correctly. We use the Bernoulli node with the same Beta prior and the observed data, and then run inference. We can compare the two posterior distributions and observe that they are exactly the same, which indicates that our custom node is performing as expected.\n\n@model function coin_model(y)\n    p ~ Beta(4.0, 8.0)\n    for i in eachindex(y)\n        y[i] ~ Bernoulli(p)\n    end\nend\n\nresult_bernoulli = infer(\n    model = coin_model(), \n    data  = (y = dataset, ),\n)\n\nif !(result_bernoulli.posteriors[:p] == result_mybernoulli.posteriors[:π])\n    error(\"Results are not identical\")\nelse \n    println(\"Results are identical 🎉🎉🎉\")\nend\n\nnothing # hide\n\nCongratulations! You have successfully implemented your own custom node in RxInfer. We went through the definition of a node to the implementation of the update rules and marginal posterior calculations. Finally we tested our custom node in a model and checked if we implemented everything correctly.","category":"section"},{"location":"manuals/inference/create-node/#custom-node-experimental","page":"Creating your own custom nodes","title":"Custom node experimental functionality","text":"warning: Experimental features\nThe functionality described below is experimental and subject to change in future releases. Use it with caution in production code.","category":"section"},{"location":"manuals/inference/create-node/#inference-ruleswithnode","page":"Creating your own custom nodes","title":"Rules that require a reference to a node object","text":"In some advanced scenarios, you might need access to the node object itself within a message passing rule. This can be useful when:\n\nYou need to inspect the current state of other variables in the model\nYou want to implement complex message passing schemes that depend on the global model state\nYou're experimenting with custom inference algorithms that require access to the factor graph structure\n\nHere's how to implement a rule with node access. First we define a custom node and a simple model that uses this node:\n\nusing RxInfer\n\nstruct MyExperimentalNode end\n\n@node MyExperimentalNode Stochastic [ out, θ ]\n\n@model function my_experimental_model(y)\n    θ ~ Normal(mean = 0.0, variance = 1.0)\n    y ~ MyExperimentalNode(θ)\nend\n\nSecond, we enable instruction to the inference backend to pass node reference to the rule.\n\n# Enable node reference passing for this node type\nReactiveMP.call_rule_is_node_required(::Type{<:MyExperimentalNode}) = ReactiveMP.CallRuleNodeRequired()\n\nnote: Performance Impact\nEnabling node reference passing can negatively impact performance as it requires additional bookkeeping during inference.\n\ndanger: Global State\nSetting call_rule_is_node_required for existing nodes (like NormalMeanVariance) affects all models globally and will affect code that depends on your package. Only safe to use this for your custom nodes.\n\nThe call_rule_is_node_required function is used to instruct the inference backend to pass the node object to the rule. After this is set, we can use the getnode() function to access the node object within the rule.\n\n@rule MyExperimentalNode(:θ, Marginalisation) (q_out::Any, ) = begin \n    node = getnode()\n    # Access interface index\n    ii = ReactiveMP.interfaceindex(node, :θ)\n    # Get interface object\n    θi = ReactiveMP.getinterfaces(node)[ii]\n    # Get variable object\n    θv = ReactiveMP.getvariable(θi)\n    \n    # By default, `getmarginal` ignores marginals set in the @initialization block\n    # `IncludeAll` overrides this behavior and includes all marginals\n    qθ = Rocket.getrecent(ReactiveMP.get_stream_of_marginals(θv))\n\n    # This is a simple rule that returns a NormalMeanVariance distribution\n    # It could be replaced with any other rule that returns a distribution\n    return NormalMeanVariance(mean(qθ) + mean(q_out), var(qθ))\nend","category":"section"},{"location":"manuals/inference/create-node/#Running-inference-with-the-custom-node-and-rule","page":"Creating your own custom nodes","title":"Running inference with the custom node and rule","text":"Here's a full example showing how to use this functionality:\n\ninitialization = @initialization begin\n    q(θ) = NormalMeanVariance(3.14, 2.71)\nend\n\nresult = infer(\n    model = my_experimental_model(),\n    data = (y = 1.0, ),\n    initialization = initialization\n)\nnothing #hide\n\nThe inference runs successfully, which means that the rule was able to access the node object through getnode() and that node reference passing is working as expected. This feature opens up possibilities for advanced inference scenarios, but should be used judiciously. Consider whether your use case truly requires access to the node object, as simpler solutions using standard message passing rules are often sufficient and more maintainable.\n\n","category":"section"},{"location":"manuals/sharpbits/overview/#Sharp-bits-of-RxInfer","page":"Overview","title":"Sharp bits of RxInfer","text":"This page serves as a collection of sharp bits - potential pitfalls and common issues you might encounter while using RxInfer. While RxInfer is designed to be user-friendly, there are certain scenarios where you might encounter unexpected behavior or errors. Understanding these can help you avoid common problems and debug your co
2de more effectively.\n\nnote: Note\nSee Session Data and Telemetry for more information on how to share sessions for better support.\n\nRule Not Found Error\nWhat causes it\nHow to diagnose and fix it\nCommon scenarios\nStack Overflow during inference\nUnderstanding the potential cause\nPrevention strategies\nUsing = instead of := for deterministic nodes\nWhy not =?\n\nnote: Note\nThis is a community document that will be updated as we identify more common issues and their solutions. If you encounter a problem that isn't covered here, please consider opening an issue/discussion or contributing to this guide.","category":"section"},{"location":"manuals/sharpbits/overview/#getting-help","page":"Overview","title":"Getting Help with Issues","text":"When you encounter issues with RxInfer, we want to help you as effectively as possible. Here's how you can help us help you:","category":"section"},{"location":"manuals/sharpbits/overview/#sharpbits-telemetry-note","page":"Overview","title":"Session Data and Telemetry","text":"RxInfer includes optional telemetry and session sharing features to help us provide better support and improve the package. When enabled, these features help us:\n\nUnderstand how the package is used in practice\nIdentify and debug issues more effectively \nMake informed decisions about improvements\nShare aggregate usage patterns in community meetings\n\nFor details on enabling telemetry and sharing session data, see:\n\nUsage Telemetry - Learn about anonymous usage statistics\nSession Sharing - Share session data for debugging\n\nAll data sharing is optional and privacy-focused. When opening issues, including your session ID helps us provide more targeted support by understanding your usage context.","category":"section"},{"location":"manuals/sharpbits/overview/#How-to-contribute","page":"Overview","title":"How to contribute","text":"If you have a sharp bit to share, please consider opening an issue/discussion or contributing to this guide. To write a new section, create a new file in the docs/src/manuals/sharpbits directory. Use @id to specify the ID of the section and @ref to reference it later.\n\n# [New section](@id new-section)\n\nThis is a new section.\n\nThen add a new entry to the pages array in the docs/make.jl file.\n\n\"Sharp bits of RxInfer\" => [\n    \"Overview\" => \"manuals/sharpbits/overview.md\",\n    \"Rule Not Found Error\" => \"manuals/sharpbits/rule-not-found.md\",\n    \"Stack Overflow in Message Computations\" => \"manuals/sharpbits/stack-overflow-inference.md\",\n    \"Using `=` instead of `:=` for deterministic nodes\" => \"manuals/sharpbits/usage-colon-equality.md\",\n    # ...\n    \"New section\" => \"manuals/sharpbits/new-section.md\",\n]\n\nIn the overview.md file, add a new section with the title and the ID of the section. Use the @ref macro to reference the ID.\n\n- [New section](@ref new-section)\n    - What this section is about\n    - ...\n\n","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions","page":"Probability Distributions","title":"Probability Distributions","text":"Probability distributions are the vocabulary of Bayesian inference. Every variable in an RxInfer model — observations, latent states, parameters, priors — is described by a distribution, and the job of Bayesian inference is to reshape these distributions in light of data.\n\nThis page is a short refresher. If you have already worked with probabilistic programming, feel free to skim it and move on to Bayesian Inference and Factor Graphs.","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-basics","page":"Probability Distributions","title":"Random variables and distributions","text":"A random variable x is a quantity whose value is uncertain. A probability distribution p(x) assigns plausibilities to the values x can take. For continuous variables we work with densities; for discrete variables with probability mass functions. Either way the total probability integrates (or sums) to one:\n\nint p(x) mathrmdx = 1\n\nA joint distribution p(x 
2y) describes two or more variables together. The conditional distribution p(x mid y) describes x once y is known. These two objects are tied together by the product rule:\n\np(x y) = p(x mid y) p(y) = p(y mid x) p(x)\n\nThat simple identity is the seed of Bayes' rule.","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-families","page":"Probability Distributions","title":"Families you will see most often","text":"In practice you will keep running into a handful of distributions. RxInfer supports all standard distributions from Distributions.jl and many additional specialized forms. The most common ones are:\n\nGaussian / Normal mathcalN(mu sigma^2) — the default for continuous real-valued variables; closed under linear operations, which is why it dominates state-space models and signal processing.\nGamma / Inverse-Gamma — standard priors on positive quantities such as precisions and variances.\nBeta — priors on probabilities and rates, bounded on 0 1.\nBernoulli / Categorical — single binary or discrete outcomes.\nDirichlet — priors on categorical probability vectors.\nWishart / Inverse-Wishart — priors on covariance and precision matrices.\nPoisson — counts of events per unit time.\n\nEach family comes with its own parameters (mean, variance, shape, rate, ...), and RxInfer lets you use whichever parameterisation is natural for your problem (see Distributions.jl compatibility).","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-conjugate","page":"Probability Distributions","title":"Conjugate pairs","text":"Two distributions form a conjugate pair when a likelihood from one family, multiplied by a prior from a matching family, yields a posterior in the same family as the prior. In that case the posterior update has a closed-form analytical expression — no numerical integration, no sampling, no gradient descent.\n\nClassic examples:\n\nLikelihood Conjugate prior Resulting posterior\nBernoulli Beta Beta\nCategorical Dirichlet Dirichlet\nGaussian (known variance) Gaussian Gaussian\nGaussian (known mean) Gamma Gamma\nPoisson Gamma Gamma\n\nRxInfer leans heavily on conjugacy: whenever a factor graph contains conjugate pairs, message passing dispatches to exact analytical update rules automatically. This is one of the main reasons inference in RxInfer is fast and accurate. See the Wikipedia overview of conjugate priors for more examples.","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-exponential-family","page":"Probability Distributions","title":"The exponential family","text":"Most of the distributions above belong to the exponential family, which has the general form\n\np(x mid theta) = h(x) expleft( eta(theta)^top T(x) - A(theta) right)\n\nwith sufficient statistics T(x), natural parameters eta(theta) and log-partition A(theta). Exponential-family distributions are convenient because:\n\nmessages stay within the family under conjugate updates,\nnatural parameters add when you multiply two densities,\nmany variational update rules reduce to simple updates of natural parameters.\n\nYou rarely need to write things in this form yourself, but it is the machinery that makes RxInfer's variational message passing rules compact and efficient.","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-rxinfer","page":"Probability Distributions","title":"Where distributions live in RxInfer","text":"Inside a model specification, distributions appear on the right-hand side of the ~ operator:\n\nμ ~ Normal(mean = 0.0, variance = 100.0)\nτ ~ Gamma(shape = 1.0, rate = 1.0)\ny ~ Normal(mean = μ, precision = τ)\n\nEach ~ statement introduces a factor node in the underlying factor graph. During inference, messages flowing over the edges of that graph are themselves distributions from these same families — Gaussians, Gammas, Betas, and so on.","category":"section"},{"location":"concepts/probability-distributions/#concepts-probability-distributions-deeper","page":"Probability Distributions","title":"For deeper understanding","text":"Distributions.jl — the Julia ecosystem's reference catalogue of probability distributions.\nExponentialFamily.jl — the exponential-family backbone used by RxInfer for efficient message representations.\nConjugate prior (Wikipedia) — table of conjugate likelihood/prior pairs.\nPattern Recognition and Machine Learning by Christopher Bishop — classic textbook treatment of distributions, exponential families and conjugacy.\n\n","category":"section"},{"location":"manuals/faq/#user-guide-faq","page":"FAQ","title":"Frequently Asked Questions (FAQ)","text":"This section addresses common questions and issues that users encounter when working with RxInfer. The FAQ is a living document that grows based on community feedback and common usage patterns.","category":"section"},{"location":"manuals/faq/#General-Questions","page":"FAQ","title":"General Questions","text":"","category":"section"},{"location":"manuals/faq/#What-is-RxInfer?","page":"FAQ","title":"What is RxInfer?","text":"RxInfer is a Julia package for automated Bayesian inference on factor graphs using reactive message passing. It provides an efficie
2nt, scalable framework for probabilistic programming with a focus on streaming data.","category":"section"},{"location":"manuals/faq/#How-does-RxInfer-compare-to-other-probabilistic-programming-packages?","page":"FAQ","title":"How does RxInfer compare to other probabilistic programming packages?","text":"See our detailed comparison guide for a comprehensive analysis of RxInfer vs. other tools like Turing.jl, Stan, and PyMC.","category":"section"},{"location":"manuals/faq/#Is-RxInfer-suitable-for-beginners?","page":"FAQ","title":"Is RxInfer suitable for beginners?","text":"Yes! RxInfer provides a user-friendly syntax through GraphPPL and comprehensive documentation. Start with the Getting Started guide and work through examples.","category":"section"},{"location":"manuals/faq/#Installation-and-Setup","page":"FAQ","title":"Installation and Setup","text":"See Installation for details.","category":"section"},{"location":"manuals/faq/#I'm-getting-dependency-conflicts.-What-should-I-do?","page":"FAQ","title":"I'm getting dependency conflicts. What should I do?","text":"Try Pkg.resolve() to resolve conflicts. See Pkg.jl for more details.","category":"section"},{"location":"manuals/faq/#Can-I-use-RxInfer-from-Python?","page":"FAQ","title":"Can I use RxInfer from Python?","text":"Yes! See our guide on Using RxInfer from Python.","category":"section"},{"location":"manuals/faq/#Model-Specification","page":"FAQ","title":"Model Specification","text":"","category":"section"},{"location":"manuals/faq/#What's-the-difference-between-and-:-in-model-specification?","page":"FAQ","title":"What's the difference between = and := in model specification?","text":"= is a regular Julia assignment operator, use it only for regular Julia variables\n:= creates a deterministic node, use it to define deterministic relationships between latent variables in your model\n\nSee Sharp Bits: Using = instead of := for details.","category":"section"},{"location":"manuals/faq/#How-do-I-handle-missing/incomplete-data?","page":"FAQ","title":"How do I handle missing/incomplete data?","text":"RxInfer supports missing data through the missing value in Julia. The inference engine will automatically handle missing observations. See Missing Data for details.","category":"section"},{"location":"manuals/faq/#How-do-I-create-custom-nodes-and-message-update-rules?","page":"FAQ","title":"How do I create custom nodes and message update rules?","text":"See Understanding Rules for an explanation of what rules are and why they use different input prefixes (m_ vs q_). See Custom Node and Rules for detailed guidance on extending RxInfer.","category":"section"},{"location":"manuals/faq/#Inference-Issues","page":"FAQ","title":"Inference Issues","text":"","category":"section"},{"location":"manuals/faq/#I'm-getting-\"Rule-not-found\"-errors.-What-does-this-mean?","page":"FAQ","title":"I'm getting \"Rule not found\" errors. What does this mean?","text":"This error occurs when RxInfer can't find appropriate message update rules for your model. See Rule Not Found Error for solutions.","category":"section"},{"location":"manuals/faq/#\"Stack-overflow-in-inference\"","page":"FAQ","title":"\"Stack overflow in inference\"","text":"See Stack Overflow during inference for more details.","category":"section"},{"location":"manuals/faq/#My-inference-is-running-very-slowly.-How-can-I-improve-performance?","page":"FAQ","title":"My inference is running very slowly. How can I improve performance?","text":"Check our Performance Tips section for optimization strategies.","category":"section"},{"location":"manuals/faq/#How-do-I-debug-inference-problems?","page":"FAQ","title":"How do I debug inference problems?","text":"Check out the Debugging guide.","category":"section"},{"location":"manuals/faq/#Performance-and-Scaling","page":"FAQ","title":"Performance and Scaling","text":"","category":"section"},{"location":"manuals/faq/#How-large-can-my-models-be?","page":"FAQ","title":"How large can my models be?","text":"RxInfer can handle models with millions of latent variables. Performance depends on:\n\nModel complexity (e.g. simple models with conjugate pairs of distributions are the fastest)\nAvailable memory (large models require more memory)\nComputational resources (more cores, more memory, faster CPU, etc.)\nOptimization techniques used (see Performance Tips)","category":"section"},{"location":"manuals/faq/#Can-I-use-RxInfer-for-real-time-applications?","page":"FAQ","title":"Can I use RxInfer for real-time applications?","text":"Yes! RxInfer is designed for real-time inference with reactive message passing. See our streaming inference documentation.","category":"section"},{"location":"manuals/faq/#Community-and-Support","page":"FAQ","title":"Community and Support","text":"","category":"section"},{"location":"manuals/faq/#Where-can-I-get-help?","page":"FAQ","title":"Where can I get help?","text":"Documentation: Start with the relevant sections in the User Guide\nGitHub Discussions: Ask questions and share experiences\nIssues: Report bugs and request features\nCommunity Meetings: Join regular public discu
2ssions, more info here","category":"section"},{"location":"manuals/faq/#How-can-I-contribute?","page":"FAQ","title":"How can I contribute?","text":"See our Contributing Guide for ways to help. Any help is welcome!","category":"section"},{"location":"manuals/faq/#Contributing-to-the-FAQ","page":"FAQ","title":"Contributing to the FAQ","text":"This FAQ grows through community contributions! If you have questions that aren't covered here:\n\nCheck existing discussions on GitHub\nAsk your question in GitHub Discussions\nConsider contributing the answer back to this FAQ\nOpen an issue if you find a documentation gap","category":"section"},{"location":"manuals/faq/#How-to-add-questions-to-the-FAQ","page":"FAQ","title":"How to add questions to the FAQ","text":"Open a discussion or issue with your question\nIf it's a common question, consider adding it here\nFollow the Contributing to Documentation guide\nUse clear, concise language and include code examples when helpful\n\n\n\nNote: This FAQ is maintained by the community. For the most up-to-date information, check GitHub discussions and issues. If you find outdated information, please help us keep it current!\n\n","category":"section"},{"location":"manuals/inference/static/#manual-static-inference","page":"Static inference","title":"Static Inference","text":"This guide explains how to use the infer function for static datasets. We'll show how RxInfer can estimate posterior beliefs given a set of observations. We'll use a simple Beta-Bernoulli model as an example, which has been covered in the Getting Started section, but keep in mind that these techniques can apply to any model.\n\nAlso read about Streaming Inference or checkout more complex examples.","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-model-spec","page":"Static inference","title":"Model specification","text":"Also read the Model Specification section.\n\nIn static inference, we want to update our prior beliefs about certain hidden states given some dataset. To achieve this, we include data as an argument in our model specification:\n\nusing RxInfer\nusing Test #hide\n\n@model function beta_bernoulli(y, a, b)\n    θ ~ Beta(a, b)  \n    for i in 1:length(y)\n        y[i] ~ Bernoulli(θ)\n    end\nend\n\nIn this model, we assume that y is a collection of data points, and a and b are just numbers. To run inference in this model, we have to call the infer function with the data argument provided.","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-dataset","page":"Static inference","title":"Dataset of observations","text":"For demonstration purposes, we will use a hand-crafted dataset:\n\nusing Distributions, StableRNGs\n\nhidden_θ       = 1 / 3.1415\ndistribution   = Bernoulli(hidden_θ)\nrng            = StableRNG(43)\nn_observations = 1_000\ndataset        = rand(rng, distribution, n_observations)\nnothing #hide","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-infer","page":"Static inference","title":"Calling the inference procedure","text":"Everything is ready to run inference in our simple model. In order to run inference with static dataset using the infer function, we need to use the data argument. The data argument  expects a NamedTuple where keys correspond to the names of the model arguments. In our case the model arguments were a, b and y. We treat a and b as hyperparameters and pass them directly to the model constructor and we treat y as our observations, thus we pass it to the data argument as follows:\n\nresults = infer(\n    model = beta_bernoulli(a = 1.0, b = 1.0),\n    data  = (y = dataset, )\n)\n@test results isa InferenceResult #hide\nresults #hide\n\nnote: Note\ny inside the @model specification is not the same data collection as provided in the data argument. Inside the @model, y is a collection of nodes in the corresponding factor graph, but it will have exactly the same shape as the collection provided in the data argument, hence we can use some basic Julia function, e.g. length. \n\nNote, that we could also pass a and b as data:\n\nresults = infer(\n    model = beta_bernoulli(),\n    data  = (y = dataset, a = 1.0, b = 1.0)\n)\n@test results isa InferenceResult #hide\nresults #hide\n\nThe infer function, however, requires at least one data argument to be present in the supplied data. The difference between hyperparameters and observations is purely semantic and should not have real influence on the result of the inference procedure. \n\nnote: Note\nThe inference procedure uses reactive message passing protocol and may decide to optimize and precompute certain messages that use fixed hyperparameters, hence changing the order of computed messages. The order of computations may change the convergence properties for some complex models.\n\nIn case of inference with static datasets, the infer function will return the InferenceResult structure. This structure has the .posteriors field, which is a Dict like structure that maps names of latent states to their corresponding posteriors. For example:\n\n@test results.posteriors[:θ] isa Beta #hide\nresults.posteriors[:θ]\n\nWe can also visualize our posterior results with the Plots.jl package. We  used Beta(a = 1.0, b = 1.0) as a prior, lets compare our prior and posterior beliefs:\n\nusing Plots\n\nrθ = range(0, 1, length = 1000)\n\np = plot()\np = plot!(p, rθ, (x) ->
2 pdf(Beta(1.0, 1.0), x), title=\"Prior\", fillalpha=0.3, fillrange = 0, label=\"P(θ)\", c=1,)\np = plot!(p, rθ, (x) -> pdf(results.posteriors[:θ], x), title=\"Posterior\", fillalpha=0.3, fillrange = 0, label=\"P(θ|y)\", c=3)\np = vline!(p, [ hidden_θ ], label = \"Real (hidden) θ\")","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-missing-data","page":"Static inference","title":"Missing data points and predictions","text":"result = infer(\n    model = beta_bernoulli(a = 1.0, b = 1.0),\n    data  = (y = [ true, false, missing, true, false ], )\n)\n\nIn principle, the entire dataset may consist of missing entries:\n\nresult = infer(\n    model = beta_bernoulli(a = 1.0, b = 1.0),\n    data  = (y = [ missing, missing, missing, missing, missing ], )\n)\n\nIn this case, the resulting posterior is simply equal to the prior (as expected, since no extra information can be extracted from the observations):\n\n@test result.posteriors[:θ] == Beta(1.0, 1.0) #hide\nresult.posteriors[:θ]\n\nIn addition, in the presence of missing data points RxInfer will also attempt to compute predictive distributions:\n\n@test all(v -> v == Bernoulli(0.5), result.predictions[:y]) #hide\nresult.predictions[:y]\n\n# Sample y₃\nrand(result.predictions[:y][3])","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-variational-inference","page":"Static inference","title":"Variational Inference with static datasets","text":"The example above is quite simple and performs exact Bayesian inference. However, for more complex models, we may need to specify variational constraints and perform variational inference. To demonstrate this, we will use a slightly more complex model, where we need to estimate mean and the precision of IID samples drawn from the Normal distribution:\n\n@model function iid_estimation(y)\n    μ  ~ Normal(mean = 0.0, precision = 0.1)\n    τ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = τ)\nend\n\nIn this model, we have two latent variables μ and τ and a set of observations y. Note  that we used the broadcasting syntax, which is roughly equivalent to the manual for loop shown in the previous example. Let's try to run the inference in this model, but first, we need to create our observations:\n\n# `ExponentialFamily` package exports different parametrizations \n# for the Normal distribution\nusing ExponentialFamily\n\nhidden_μ       = 3.1415\nhidden_τ       = 2.7182\ndistribution   = NormalMeanPrecision(hidden_μ, hidden_τ)\nrng            = StableRNG(42)\nn_observations = 1_000\ndataset        = rand(rng, distribution, n_observations)\nnothing #hide\n\nAnd finally we run the inference procedure:\n\nresults = infer(\n    model = iid_estimation(),\n    data  = (y = dataset, )\n)\n\nERROR: Variables [ μ, τ ] have not been updated after an update event. \nTherefore, make sure to initialize all required marginals and messages. See `initialization` keyword argument for the inference function. \nSee the official documentation for detailed information regarding the initialization.\n\nStacktrace:\n [1] error(s::String)\n   @ Base ./error.jl:35\n\n# This is just a test to ensure that the example above does\n# indeed fail with the exact same error\nusing RxInfer, Test \n@model function iid_estimation(y)\n    μ  ~ Normal(mean = 0.0, precision = 0.1)\n    τ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = τ)\nend\n@test_throws \"Variables [ μ, τ ] have not been updated after an update event.\" infer(\n    model = iid_estimation(),\n    data  = (y = rand(10), ),\n    disable_inference_error_hint = true #hide\n)\nnothing\n\nHuh? We get an error saying that the inference could not update the latent variables. This happened because our model contains loops in its structure, therefore it requires initialization. Read more about the initialization in the corresponding section in the documentation.\n\nWe have two options here, either we initialize the messages and perform Loopy Belief Propagation in this model or we break the loops with variational constraints and perform variational inference. In this tutorial, we will choose the second option. For this we need to specify factorization constraints with the @constraints macro.\n\n# Specify mean-field constraint over the joint variational posterior\nconstraints = @constraints begin \n    q(μ, τ) = q(μ)q(τ)\nend\n# Specify initial posteriors for variational iterations\ninitialization = @initialization begin \n    q(μ) = vague(NormalMeanPrecision)\n    q(τ) = vague(GammaShapeRate)\nend\nnothing #hide\n\nWith this, we can use the constraints and initialization keyword arguments in the infer function. We also specify the number of variational iterations with the iterations keyword argument:\n\nresults = infer(\n    model          = iid_estimation(),\n    data           = (y = dataset, ),\n    constraints    = constraints,\n    iterations     = 100,\n    initialization = initialization\n)\n\nNice! Now, we have some result. Let's for example inspect the posterior results for μ.\n\nresults.posteriors[:μ]\n\nIn contrast to the previous example, now we have an array of posteriors for μ, not just a single value. Each posterior in the collection corresponds to the intermediate variational update for each variational iteration. Let's visualize how our posterior over μ has been changing during the variational optimization:\n\n@gif for (i, intermediate_posterior) in enumerate(results.posteriors[:μ])\n    rμ = range(0, 5, length = 1000)\n    plot(rμ, (x) -> pdf(intermediate_posterior, x), title=\"Posterior on iteration $(i)\", fillalpha=0.3, fillrange = 0, label=\"P(μ|y)\", c=3)\n    vline!([hidden_μ], label = \"Real (hidden) μ\")\nend\n\nIt seems that the posterior has converged to a stable distribution pretty fast.  We are going to verify the convergence in the next section. If, for example, we are not interested in intermediate updates, but just in the final posterior, we could use the returnvars option in the infer function and use the KeepLast option for μ:\n\nresults_keep_last = infer(\n    model          = iid_estimation(),\n    data           = (y = dataset, ),\n    constraints    = constraints,\n    iterations     = 100,\n    returnvars     = (μ = KeepLast(), ),\n    initialization = initialization\n)\n\nWe can also verify that we got exactly the same result:\n\n@test results_keep_last.posteriors[:μ] == last(results.posteriors[:μ]) #hide\nresults_keep_last.posteriors[:μ] == last(results.posteriors[:μ])\n\nLet's also verify that the posteriors are consistent with the real hidden values used in the dataset generation:\n\nprintln(\"Real (hidden) μ was \", hidden_μ)\nprintln(\"Inferred mean for μ is \", mean(last(results.posteriors[:μ])), \" with standard deviation \", std(last(results.posteriors[:μ])))\n\nprintln(\"Real (hidden) τ was \", hidden_τ)\nprintln(\"Inferred mean for τ is \", mean(last(results.posteriors[:τ])), \" with standard deviation \", std(last(results.posteriors[:τ])))\n\n@test abs(mean(last(results.posteriors[:μ])) - hidden_μ) < 3std(last(results.posteriors[:μ])) #hide\n@test abs(mean(last(results.posteriors[:τ])) - hidden_τ) < 3std(last(results.posteriors[:τ])) #hide\nnothing #hide\n\nrμ = range(2, 4, length = 1000)\npμ = plot(rμ, (x) -> pdf(last(results.posteriors[:μ]), x), title=\"Posterior for μ\", fillalpha=0.3, fillrange = 0, label=\"P(μ|y)\", c=3)\npμ = vline!(pμ, [ hidden_μ ], label = \"Real (hidden) μ\")\n\nrτ = range(2, 4, length = 1000)\npτ = plot(rτ, (x) -> pdf(last(results.posteriors[:τ]), x), title=\"Posterior for τ\", fillalpha=0.3, fillrange = 0, label=\"P(τ|y)\", c=3)\npτ = vline!(pτ, [ hidden_τ ], label = \"Real (hidden) τ\")\n\nplot(pμ, pτ)\n\nNice result! Our posteriors are pretty close to the actual values of the parameters used for dataset generation.","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-bfe","page":"Static inference","title":"Convergence and Bethe Free Energy","text":"Read also the Bethe Free Energy section.\n\n
2In contrast to Loopy Belief Propagation, the variational inference is set to converge to a stable point during variational inference. In order to verify the convergence for this particular model, we can check the convergence of the Bethe Free Energy values. By default, infer function does not compute the Bethe Free Energy values. In order to compute those, we must set the free_energy flag explicitly to true:\n\nresults = infer(\n    model          = iid_estimation(),\n    data           = (y = dataset, ),\n    constraints    = constraints,\n    iterations     = 100,\n    initialization = initialization,\n    free_energy    = true\n)\n\nNow, we can access the free_energy field of the results and verify if the inference procedure has converged or not:\n\nplot(results.free_energy, label = \"Bethe Free Energy\")\n\nWell, it seems that 100 iterations were too many for this simple problem and we could do far fewer iterations in order to converge to a stable point. The animation above also suggested that the posterior for μ has converged pretty fast to a stable point.\n\n# Let's try to use only 5 iterations\nresults = infer(\n    model          = iid_estimation(),\n    data           = (y = dataset, ),\n    constraints    = constraints,\n    iterations     = 5,\n    initialization = initialization,\n    free_energy    = true\n)\n\nplot(results.free_energy, label = \"Bethe Free Energy\")","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-callbacks","page":"Static inference","title":"Callbacks","text":"The infer function has its own lifecycle, consisting of multiple steps. A user is free to inject some extra logic during the inference procedure, e.g. for debugging purposes or performance analysis. By supplying callbacks, users can inject custom logic on specific moments during the inference procedure. Callbacks can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. For a comprehensive overview of the callbacks system, including custom callback structures and model metadata, see the Callbacks section. Here are available callbacks that can be used together with the static datasets:\n\nusing RxInfer, Test, Markdown\n# Update the documentation below if this test does not pass\n@test RxInfer.available_callbacks(RxInfer.batch_inference) === Val((\n    :on_marginal_update,\n    :before_model_creation,\n    :after_model_creation,\n    :before_inference,\n    :before_iteration,\n    :before_data_update,\n    :after_data_update,\n    :after_iteration,\n    :after_inference,\n    :before_message_rule_call,\n    :after_message_rule_call,\n    :before_product_of_two_messages,\n    :after_product_of_two_messages,\n    :before_product_of_messages,\n    :after_product_of_messages,\n    :before_form_constraint_applied,\n    :after_form_constraint_applied,\n    :before_marginal_computation,\n    :after_marginal_computation\n)) \nnothing\n\n\n\nBelow we list RxInfer specific callbacks. In addition to these, ReactiveMP provides lower-level callbacks for the message passing procedure itself, such as before_message_rule_call, after_message_rule_call, before_product_of_messages, after_product_of_messages, before_marginal_computation, after_marginal_computation, and others. For a full list and detailed descriptions of these callbacks, refer to the official documentation of ReactiveMP.\n\nbefore_model_creation()\n\nCalls before the model is going to be created, does not accept any arguments.\n\nafter_model_creation(model::ProbabilisticModel)\n\nCalls right after the model has been created, accepts a single argument, the model.\n\nbefore_inference(model::ProbabilisticModel)\n\nCalls before the inference procedure starts, accepts a single argument, the model.\n\nafter_inference(model::ProbabilisticModel)\n\nCalls after the inference procedure ends, accepts a single argument, the model.\n\nbefore_iteration(model::ProbabilisticModel, iteration::Int)\n\nCalls before each iteration, accepts two arguments: the model and the current iteration number.\n\nafter_iteration(model::ProbabilisticModel, iteration::Int)\n\nCalls after each iteration, accepts two arguments: the model and the current iteration number.\n\nbefore_data_update(model::ProbabilisticModel, data)\n\nCalls before each data update, accepts two arguments: the model and the updated data.\n\nafter_data_update(model::ProbabilisticModel, data)\n\nCalls after each data update, accepts two arguments: the model and the updated data.\n\non_marginal_update(model::ProbabilisticModel, name, update)\n\nCalls after each marginal update, accepts three arguments: the model, the name of the updated marginal, and the updated marginal itself.\n\n\n\nHere is an example usage of the outlined callbacks:\n\nbefore_model_creation_called = Ref(false) #hide\nafter_model_creation_called = Ref(false) #hide\nbefore_inference_called = Ref(false) #hide\nafter_inference_called = Ref(false) #hide\nbefore_iteration_called = Ref(false) #hide\nafter_iteration_called = Ref(false) #hide\nbefore_data_update_called = Ref(false) #hide\nafter_data_update_called = Ref(false) #hide\non_marginal_update_called = Ref(false) #hide\n\nfunction before_model_creation(event::BeforeModelCreationEvent)\n    before_model_creation_called[] 
2= true #hide\n    println(\"The model is about to be created\")\nend\n\nfunction after_model_creation(event::AfterModelCreationEvent)\n    after_model_creation_called[] = true #hide\n    model = event.model\n    println(\"The model has been created\")\n    println(\"  The number of factor nodes is: \", length(RxInfer.getfactornodes(model)))\n    println(\"  The number of latent states is: \", length(RxInfer.getrandomvars(model)))\n    println(\"  The number of data points is: \", length(RxInfer.getdatavars(model)))\n    println(\"  The number of constants is: \", length(RxInfer.getconstantvars(model)))\nend\n\nfunction before_inference(event::BeforeInferenceEvent)\n    before_inference_called[] = true #hide\n    println(\"The inference procedure is about to start\")\nend\n\nfunction after_inference(event::AfterInferenceEvent)\n    after_inference_called[] = true #hide\n    println(\"The inference procedure has ended\")\nend\n\nfunction before_iteration(event::BeforeIterationEvent)\n    before_iteration_called[] = true #hide\n    println(\"The iteration \", event.iteration, \" is about to start\")\nend\n\nfunction after_iteration(event::AfterIterationEvent)\n    after_iteration_called[] = true #hide\n    println(\"The iteration \", event.iteration, \" has ended\")\nend\n\nfunction before_data_update(event::BeforeDataUpdateEvent)\n    before_data_update_called[] = true #hide\n    println(\"The data is about to be processed\")\nend\n\nfunction after_data_update(event::AfterDataUpdateEvent)\n    after_data_update_called[] = true #hide\n    println(\"The data has been processed\")\nend\n\nfunction on_marginal_update(event::OnMarginalUpdateEvent)\n    on_marginal_update_called[] = true #hide\n    println(\"New marginal update for \", event.variable_name, \" \", event.update)\nend\n\nresults = infer(\n    model          = iid_estimation(),\n    data           = (y = dataset, ),\n    constraints    = constraints,\n    iterations     = 5,\n    initialization = initialization,\n    free_energy    = true,\n    callbacks      = (\n        before_model_creation = before_model_creation,\n        after_model_creation = after_model_creation,\n        before_inference = before_inference,\n        after_inference = after_inference,\n        before_iteration = before_iteration,\n        after_iteration = after_iteration,\n        before_data_update = before_data_update,\n        after_data_update = after_data_update,\n        on_marginal_update = on_marginal_update\n    )\n)\n\n@test before_model_creation_called[] #hide\n@test after_model_creation_called[] #hide\n@test before_inference_called[] #hide\n@test after_inference_called[] #hide\n@test before_iteration_called[] #hide\n@test after_iteration_called[] #hide\n@test before_data_update_called[] #hide\n@test after_data_update_called[] #hide\n@test on_marginal_update_called[] #hide\n\nnothing #hide","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-force-marginal-computation","page":"Static inference","title":"Force marginal computation","text":"RxInfer uses message passing to perform inference. During this process, joint marginal distributions around nodes are not always necessary for the inference procedure to work correctly. The inference engine optimizes performance by only computing the distributions that are essential for message passing.\n\nHowever, in some cases we may want to explicitly request these marginal distributions to be computed - for example, to analyze intermediate results or debug the inference process. We can force the computation of all marginal distributions by setting force_marginal_computation=true in the options when calling the infer function:\n\n@model function force_joint_marginal_computation(y)\n   m ~ NormalMeanVariance(0, 1)\n   x ~ NormalMeanVariance(m, 1) # Joint marginal q(x, m) around this node will be computed, even though it is not necessary for the inference.\n   y ~ NormalMeanVariance(x, 1)\nend\n\nresults = infer(\n    model = force_joint_marginal_computation(),\n    data = (y = 1.0, ),\n    options = (force_marginal_computation = true,)\n)","category":"section"},{"location":"manuals/inference/static/#manual-static-inference-where-to-go","page":"Static inference","title":"Where to go next?","text":"This guide covered some fundamental usages of the infer function in the context of inference with static datasets,  but did not cover all the available keyword arguments of the function. Read more explanation about the other keyword arguments  in the Overview section or check out the Streaming Inference section. Also check out more complex examples.\n\n","category":"section"},{"location":"manuals/inference/static/#RxInfer.InferenceResult","page":"Static inference","title":"RxInfer.InferenceResult","text":"InferenceResult\n\nThis structure is used as a return value from the infer function for static datasets. \n\nPublic Fields\n\nposteriors: Dict or NamedTuple of 'random variable' - 'posterior' pairs. See the returnvars argument for infer.\npredictions: (optional) Dict or NamedTuple of 'data variable' - 'prediction' pairs. See the predictvars argument for infer.\nfree_energy: (optional) An array of Bethe Free Energy values per VMP iteration. See the free_energy argument for infer.\nmodel: FactorGraphModel object reference.\nerror: (optional) A reference to an exception, that might have occurred during the inference. See the cat
2ch_exception argument for infer.\n\nSee also: infer\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/static/#RxInfer.ReactiveMPForceMarginalComputationPlugin","page":"Static inference","title":"RxInfer.ReactiveMPForceMarginalComputationPlugin","text":"A plugin for GraphPPL graph engine that forces the computation of marginal distributions for every node in the graph.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-free-energy","page":"Bethe Free Energy","title":"Bethe Free Energy implementation in RxInfer","text":"The following text introduces the Bethe Free Energy. We start by defining a factorized model and move from the Variational Free Energy to a definition of the Bethe Free Energy.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-factorized-model","page":"Bethe Free Energy","title":"Factorized model","text":"Before we can define a model, we must identify all variables that are relevant to the problem at hand. We distinguish between variables that can be directly observed, y = (y_1 dots y_j dots y_m) and variables that can not be observed directly, also known as latent variables, x = (x_1 dots x_i dots x_n) We then define a model that factorizes over constituent smaller factors (functions), as f(yx) = prod_a f_a(y_ax_a)\n\nIndividual factors may represent stochastic functions, such as conditional or prior distributions, but also potential functions or deterministic relationships. A factor may depend on multiple observed and/or latent variables (or none).","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-vfe","page":"Bethe Free Energy","title":"Variational Free Energy","text":"The Variational Free Energy (VFE) then defines a functional objective that includes the model and a variational distribution over the latent variables, Fq(haty) = mathbbE_q(x)leftlog fracq(x)f(y=haty x) right A functional defines a function of a function that returns a scalar. Here, the VFE is a function of the variational distribution (as indicated by square brackets) and returns a number.\n\nThe VFE is also a function of the observed data, as indicated by round brackets, where the data are substituted in the factorized model.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-variational-inference","page":"Bethe Free Energy","title":"Variational inference","text":"The goal of variational inference is to find a variational distribution that minimizes the VFE, q^*(x) = argmin_qinmathcalQ Fq(haty) This objective can be optimized (under specific constraints) with the use of variational calculus. Constraints are implied by the domain over which the variational distribution is optimized, and can be enforced by Lagrange multipliers.\n\nFor the VFE, constraints enforce e.g. the normalization of the variational distribution. The variational distribution that minimizes the VFE then approximates the true (but often unobtainable) posterior distribution.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-approximation","page":"Bethe Free Energy","title":"Bethe approximation","text":"Optimization of the VFE is still a daunting task, because the variational distribution is a joint distribution over possibly many latent variables. Instead of optimizing the joint variational distribution directly, a factorized variational distribution is often chosen. The factorized variational distribution is then optimized for its constituent factors.\n\nA popular choice of factorization is the Bethe approximation, which is constructed from the factorization of the model itself, q(x) triangleq fracprod_a q_a(x_a)prod_i q_i(x_i)^d_i - 1 The numerator iterates over the factors in the model, and carves the joint variational distribution in smaller variational distributions that are more manageable to optimize.\n\nThe denominator of the Bethe approximation iterates over all individual latent variables and discounts them. The discounting factor is chosen as the degree of the variable minus one, where the degree counts the number of factors in which the variable appears.\n\nThe Bethe approximation thus constrains the variational distribution to a factorized form. However, the true posterior distribution might not factorize in this way, e.g. if the graphical representation of the model contains cycles. In these cases the Bethe approximation trades the exact solution for computational tractability.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-bfe","page":"Bethe Free Energy","title":"Bethe Free Energy","text":"The Bethe Free Energy (BFE) substitutes the Bethe approximation in the VFE, which then fragments over factors and variables, as F_Bq(haty) = sum_a U_aq_a(haty_a) - sum_a Hq_a + sum_i (d_i - 1) Hq_i The first term of the BFE specifies an average energy,  U_aq_a(haty_a) = -mathbbE_q_a(x_a)leftlog f_a(y_a=haty_a x_a)right which internalizes the factors of the model. The last two terms specify entro
2pies.\n\nCrucially, the BFE can be iteratively optimized for each individual variational distribution in turn. Optimization of the BFE is thus more manageable than direct optimization of the VFE.\n\nFor iterative optimization of the BFE, the variational distributions must first be initialized. The infer function uses the initialization keyword argument to initialize the variational distributions of the BFE.\n\nFor disambiguation, note that the initialization of the variational distribution is a different design consideration than the choice of priors. A prior specifies a factor in the model definition, while initialization concerns factors in the variational distribution.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#lib-bethe-further-reading","page":"Bethe Free Energy","title":"Further reading","text":"Pearl (1986) on the original foundations of Bayesian networks and belief propagation;\nYedidia et al. (2005) on the connections between belief propagation and regional approximations to the VFE;\nDauwels (2007) on variational message passing on Forney-style factor graphs (FFGs);\nSenoz et al. (2021) on constraint manipulation and message passing on FFGs.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#Implementation-details","page":"Bethe Free Energy","title":"Implementation details","text":"RxInfer implements Bethe Free Energy optimization in an implicit way via the message passing technique. That means that the inference engine does not compute BFE values explicitly,  unless specified explicitly. The infer function has a free_energy flag, which indicates whether BFE values must be computed explicitly or not. Note, however, that due to the reactive nature of the message passing implementation in RxInfer the computed BFE value may not represent its actual state. This may happen when updates for certain posteriors arrive more often than updates for other posteriors, and usually tends to happen in models with loops in their structure. To circumvent this, instead of checking if the BFE value is being minimized it is advised to check if it converges.","category":"section"},{"location":"manuals/variational/bethe-free-energy/#Extra-diagnostic-checks","page":"Bethe Free Energy","title":"Extra diagnostic checks","text":"RxInfer verifies intermediate computations of BFE on each iteration. By default, RxInfer will throw an exception, if local factor node or variable node computations result in either NaN or Inf. Note that the verification happens only if the computation of BFE has been requested explicitly.\n\n","category":"section"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.BetheFreeEnergy","page":"Bethe Free Energy","title":"RxInfer.BetheFreeEnergy","text":"BetheFreeEnergy(T)\n\nImplements a reactive stream for Bethe Free Energy values.  Must be used in combination with the score function of ReactiveMP.jl. \n\nArguments\n\n::Type{T}: a type of the counting real number, e.g. Float64. Set to Real by default, otherwise the inference procedure is not automatically differentiable.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.ReactiveMPFreeEnergyPlugin","page":"Bethe Free Energy","title":"RxInfer.ReactiveMPFreeEnergyPlugin","text":"A plugin for GraphPPL graph engine that adds the Bethe Free Energy objective computation to the nodes of the model.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.apply_diagnostic_check","page":"Bethe Free Energy","title":"RxInfer.apply_diagnostic_check","text":"apply_diagnostic_check(check, stream)\n\nThis function applies a check to the stream. Does nothing if check is of type Nothing. \n\n\n\n\n\n","category":"function"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.ObjectiveDiagnosticCheckNaNs","page":"Bethe Free Energy","title":"RxInfer.ObjectiveDiagnosticCheckNaNs","text":"ObjectiveDiagnosticCheckNaNs\n\nIf enabled, checks that both variable and factor bound score functions in the objective computation do n
2ot return NaNs. Throws an error if it finds a NaN.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.ObjectiveDiagnosticCheckInfs","page":"Bethe Free Energy","title":"RxInfer.ObjectiveDiagnosticCheckInfs","text":"ObjectiveDiagnosticCheckInfs\n\nIf enabled, checks that both variable and factor bound score functions in the objective computation do not return Infs. Throws an error if it finds an Inf.\n\n\n\n\n\n","category":"type"},{"location":"manuals/variational/bethe-free-energy/#RxInfer.DefaultObjectiveDiagnosticChecks","page":"Bethe Free Energy","title":"RxInfer.DefaultObjectiveDiagnosticChecks","text":"const DefaultObjectiveDiagnosticChecks = (ObjectiveDiagnosticCheckNaNs(), ObjectiveDiagnosticCheckInfs())\n\nA constant that defines the default objective diagnostic checks.\n\n\n\n\n\n","category":"constant"},{"location":"contributing/new-example/#contributing-new-example","page":"Contributing to the examples","title":"Contributing to the examples","text":"The examples have been moved to a dedicated repository at RxInferExamples.jl.\n\nTo contribute new examples, please visit the examples repository and follow the contribution guidelines there. We welcome all contributions that help demonstrate the capabilities of RxInfer.jl!\n\n","category":"section"},{"location":"manuals/getting-started/#user-guide-getting-started","page":"Getting started","title":"Getting started","text":"RxInfer.jl is a Julia package for Bayesian Inference on Factor Graphs by Message Passing.  It supports both exact and variational inference algorithms and forms an ecosystem around three main packages: \n\nReactiveMP.jl - the underlying message passing-based inference engine\nGraphPPL.jl - model and constraints specification package\nRocket.jl - reactive extensions package for Julia \n\nThis page provides the necessary information you need to get started with RxInfer. We will show the general approach to solving inference problems with RxInfer by means of a running example: inferring the bias of a coin using a simple Beta-Bernoulli model.","category":"section"},{"location":"manuals/getting-started/#user-guide-getting-started-installation","page":"Getting started","title":"Installation","text":"RxInfer is an officially registered Julia package. Install RxInfer through the Julia package manager by using the following command from the package manager mode:\n\n] add RxInfer\n\nAlternatively:\n\njulia> import Pkg\n\njulia> Pkg.add(\"RxInfer\")","category":"section"},{"location":"manuals/getting-started/#Importing-RxInfer","page":"Getting started","title":"Importing RxInfer","text":"To add RxInfer package (and all associated packages) into a running Julia session simply run:\n\nusing RxInfer\n\nRead more about using in the Using methods from RxInfer section of the documentation.","category":"section"},{"location":"manuals/getting-started/#Example:-Inferring-the-bias-of-a-coin","page":"Getting started","title":"Example: Inferring the bias of a coin","text":"The RxInfer approach to solving inference problems consists of three phases:\n\nModel specification: RxInfer uses GraphPPL package for model specification part. It offers a domain-specific language to specify your probabilistic model.\nInference specification: RxInfer inference API uses ReactiveMP inference engine under the hood and has been designed to be as flexible as possible. It is compatible both with asynchronous infinite data streams and with static datasets. For most of the use cases it consists of the same simple building blocks. In this example we will show one of the many possible ways to infer your quantities of interest.\nInference execution: Given a model specification and an inference procedure, it is pretty straightforward to use the package's API to pass data to the inference backend and to run the actual inference.","category":"section"},{"location":"manuals/getting-started/#user-guide-getting-started-coin-flip-simulation","page":"Getting started","title":"Coin flip simulation","text":"Let's start by creating some dataset. One approach could be flipping a coin N times and recording each outcome. For simplicity, in this example we will use a static pre-generated dataset. Each sample can be thought of as the outcome of a single flip, which is either heads or tails (1 or 0). We will assume that our virtual coin is biased, and lands heads up on 75% of the trials (on average).\n\nFirst let's set up our environment by importing all needed packages:\n\nusing Test #hide\nusing RxInfer, Distributions, Random\n\nNext, let's define our dataset:\n\n# Random number generator for reproducibility\nrng            = MersenneTwister(42)\n# Number of coin flips (observations)\nn_observations = 10\n# The bias of a coin used in the demonstration\ncoin_bias      = 0.75\n# We assume that the outcome of each coin flip is \n# distributed as the `Bernoulli` distribution\ndistribution   = Bernoulli(coin_bias)\n# Simulated coin flips\ndataset        = rand(rng, distribution, n_observations)","category":"section"},{"location":"manuals/getting-started/#getting-started-model-specification","page":"Getting started","title":"Model specification","text":"In a Bayesian setting, the next step is to specify our probabilistic model. This amounts to specifying the joint probability of the random variables of the system.","category":"section"},{"location":"manuals/getting-started/#Likelihood","page":"Getting started","title":"Likelihood","text":"We've made an assumption that the outcome of each coin flip is governed by the Bernoulli distribution, i.e.\n\ny_i sim mathrmBernoulli(theta)\n\nwhere y_i = 1 represents \"heads\", y_i = 0 represents \"tails\". The underlying probability of the coin landing heads up for a single coin flip is theta in 01.","category":"section"},{"location":"manuals/getting-started/#Prior","page":"Getting started","title":"Prior","text":"We will choose the conjugate prior of the Bernoulli likelihood function defined above, namely the Beta distribution, i.e.\n\ntheta sim Beta(a b)\n\nwhere a and b are the hyperparameters that encode our prior beliefs about the possible values of theta. We will assign values to the hyperparameters in a later step.   ","category":"section"},{"location":"manuals/getting-started/#Joint-probability","page":"Getting started","title":"Joint probability","text":"The joint probability is given by the multiplication of the likelihood and the prior, i.e.\n\nP(y_1N θ) = P(θ) prod_i=1^N P(y_i  θ)\n\nNow let'
2s see how to specify this model using GraphPPL's package syntax.\n\n# GraphPPL.jl exports the `@model` macro for model specification\n# It accepts a regular Julia function and builds an FFG under the hood\n@model function coin_model(y, a, b)\n    # We endow θ parameter of our model with some prior\n    θ ~ Beta(a, b)\n    # or, in this particular case, the `Uniform(0.0, 1.0)` prior also works:\n    # θ ~ Uniform(0.0, 1.0)\n\n    # We assume that outcome of each coin flip is governed by the Bernoulli distribution\n    for i in eachindex(y)\n        y[i] ~ Bernoulli(θ)\n    end\nend\n\nAs you can see, RxInfer offers a model specification syntax that closely resembles the mathematical equations defined above. Alternatively, we could use a broadcasting syntax:\n\n@model function coin_model(y, a, b) \n    θ  ~ Beta(a, b)\n    y .~ Bernoulli(θ) \nend\n\nnote: Note\nTo quickly check the list of all available factor nodes that can be used in the model specification language call ?ReactiveMP.is_predefined_node or Base.doc(ReactiveMP.is_predefined_node).","category":"section"},{"location":"manuals/getting-started/#getting-started-conditioning","page":"Getting started","title":"Conditioning on data and inspecting the model structure","text":"Given the model specification we can construct an actual model graph and visualize it. In order to do that we can use the | operator to condition on data and the RxInfer.create_model function to create the graph. Read more about conditioning in the corresponding section of the documentation.\n\nconditioned = coin_model(a = 2.0, b = 7.0) | (y = [ true, false, true ], )\n\nWe can use GraphPPL.jl visualisation capabilities to show the structure of the resulting graph. For that we need two extra packages installed: Cairo and GraphPlot. Note, that those packages are not included in the RxInfer package and must be installed separately.\n\nusing Cairo, GraphPlot\n\n# Create the actual graph of the model conditioned on the data\nmodel = RxInfer.create_model(conditioned)\n\n# Call `gplot` function from `GraphPlot` to visualise the structure of the graph\nGraphPlot.gplot(RxInfer.getmodel(model))\n\nIn addition, we can also programmatically query the structure of the graph:\n\nRxInfer.getrandomvars(model)\n\nRxInfer.getdatavars(model)\n\nRxInfer.getconstantvars(model)\n\nRxInfer.getfactornodes(model)","category":"section"},{"location":"manuals/getting-started/#Conditioning-on-data-that-is-not-available-at-model-creation-time","page":"Getting started","title":"Conditioning on data that is not available at model creation time","text":"Sometimes the data is not known at model creation time, for example, during reactive inference. For that purpose RxInfer uses RxInfer.DeferredDataHandler structure.\n\n# The only difference here is that we do not specify `a` and `b` as hyperparameters \n# But rather indicate that the data for them will be available later during the inference\nconditioned_with_deferred_data = coin_model() | (\n    y = [ true, false, true ], \n    a = RxInfer.DeferredDataHandler(), \n    b = RxInfer.DeferredDataHandler()\n)\n\n# The graph creation API does not change\nmodel_with_deferred_data = RxInfer.create_model(conditioned_with_deferred_data)\n\n# We can visualise the graph with missing data handles as well\nGraphPlot.gplot(RxInfer.getmodel(model_with_deferred_data))\n\nFrom the model structure visualisation we can see now that both a and b are no longer indicated as constants. Read more about the structure of the graph in GraphPPL documentation.","category":"section"},{"location":"manuals/getting-started/#getting-started-inference-specification","page":"Getting started","title":"Inference specification","text":"","category":"section"},{"location":"manuals/getting-started/#Automatic-inference-specification","page":"Getting started","title":"Automatic inference specification","text":"Once we have defined our model, the next step is to use the RxInfer API to infer quantities of interest. To do this we can use the generic infer function that supports static datasets. Read more information about the infer function in the Inference Execution documentation section.\n\nresult = infer(\n    model = coin_model(a = 2.0, b = 7.0),\n    data  = (y = dataset, )\n)\n\nAs you can see we don't need to condition on the data manually; the infer function will do it automatically. After the inference is complete we can fetch the results from the .posteriors field with the name of the latent state:\n\nθestimated = result.posteriors[:θ]\n\nWe can also compute some statistical properties of the result:\n\nprintln(\"Real bias is \", coin_bias)\nprintln(\"Estimated bias is \", mean(θestimated))\nprintln(\"Standard deviation \", std(θestimated))\nnothing #hide\n\nLet's also visualize the resulting posteriors:\n\nusing Plots\n\nrθ = range(0, 1, length = 1000)\n\np1 = plot(rθ, (x) ->
2 pdf(Beta(2.0, 7.0), x), title=\"Prior\", fillalpha=0.3, fillrange = 0, label=\"P(θ)\", c=1,)\np2 = plot(rθ, (x) -> pdf(θestimated, x), title=\"Posterior\", fillalpha=0.3, fillrange = 0, label=\"P(θ|y)\", c=3)\n\nplot(p1, p2, layout = @layout([ a; b ]))\n\nIn our dataset we used 10 coin flips and a skewed prior to estimate the bias of a coin.  This resulted in a vague posterior distribution; however, RxInfer scales very well for large models and factor graphs.  We may use more coin flips in our dataset for better posterior distribution estimates:\n\ndataset_100   = rand(rng, Bernoulli(coin_bias), 100)\ndataset_1000  = rand(rng, Bernoulli(coin_bias), 1000)\ndataset_10000 = rand(rng, Bernoulli(coin_bias), 10000)\nnothing # hide\n\nθestimated_100   = infer(model = coin_model(a = 2.0, b = 7.0), data  = (y = dataset_100, ))\nθestimated_1000  = infer(model = coin_model(a = 2.0, b = 7.0), data  = (y = dataset_1000, ))\nθestimated_10000 = infer(model = coin_model(a = 2.0, b = 7.0), data  = (y = dataset_10000, ))\nnothing #hide\n\nLet's investigate how the number of observations affects the estimated posterior:\n\np3 = plot(title = \"Posterior\", legend = :topleft)\np3 = plot!(p3, rθ, (x) -> pdf(θestimated_100.posteriors[:θ], x), fillalpha = 0.3, fillrange = 0, label = \"P(θ|y_100)\", c = 4)\np3 = plot!(p3, rθ, (x) -> pdf(θestimated_1000.posteriors[:θ], x), fillalpha = 0.3, fillrange = 0, label = \"P(θ|y_1000)\", c = 5)\np3 = plot!(p3, rθ, (x) -> pdf(θestimated_10000.posteriors[:θ], x), fillalpha = 0.3, fillrange = 0, label = \"P(θ|y_10000)\", c = 6)\n\nplot(p1, p3, layout = @layout([ a; b ]))\n\nWe can see that with a larger dataset our posterior marginal estimate becomes more and more accurate and represents the real value of the bias of a coin.\n\nprintln(\"Real bias is \", coin_bias)\nprintln(\"Estimated bias is \", mean(θestimated_10000.posteriors[:θ]))\nprintln(\"Standard deviation \", std(θestimated_10000.posteriors[:θ]))\nnothing #hide","category":"section"},{"location":"manuals/getting-started/#Missing-data-points","page":"Getting started","title":"Missing data points","text":"RxInfer inference engine understands missing data points in the dataset, for example:\n\nresult = infer(\n    model = coin_model(a = 2.0, b = 7.0),\n    data  = (y = [ true, false, missing, true, false ], )\n)\nresult.posteriors[:θ]\n\nIn principle, the entire dataset may consist of missing entries:\n\nresult = infer(\n    model = coin_model(a = 2.0, b = 7.0),\n    data  = (y = [ missing, missing, missing, missing, missing ], )\n)\n\nIn this case, the resulting posterior is simply equal to the prior (as expected, since no extra information can be extracted from the observations):\n\n@test result.posteriors[:θ] == Beta(2.0, 7.0) #hide\nresult.posteriors[:θ]","category":"section"},{"location":"manuals/getting-started/#Where-to-go-next?","page":"Getting started","title":"Where to go next?","text":"There are a set of examples available in the RxInfer repository that demonstrate the more advanced features of the package for various problems. Alternatively, you can head to the Model specification section, which provides more detailed information on how to use RxInfer to specify probabilistic models. The Inference execution section provides documentation about the RxInfer API for running reactive Bayesian inference. Also read the Comparison to compare RxInfer with other probabilistic programming libraries. For advanced use cases refer to the Non-conjugate inference tutorial and inference without defining the message update rules explicitly.\n\n","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#manual-inference-benchmark-callbacks","page":"Benchmark callbacks","title":"Benchmark callbacks","text":"RxInfer provides a built-in callback structure called RxInferBenchmarkCallbacks for collecting timing information during the inference procedure. This structure aggregates timestamps across multiple inference runs, allowing you to track performance statistics (min/max/average/etc.) of your model's creation and inference procedure. For general information about the callbacks system, see Callbacks.","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Basic-usage","page":"Benchmark callbacks","title":"Basic usage","text":"using RxInfer\nusing Test #hide\n\n@model function iid_normal(y)\n    μ  ~ Normal(mean = 0.0, variance = 100.0)\n    γ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = γ)\nend\n\ninit = @initialization begin\n    q(μ) = vague(NormalMeanVariance)\nend\n\n# Warm up to avoid measuring compilation time\ninfer(model = iid_normal(), data = (y = randn(100),), constraints = MeanField(), iterations = 5, initialization = init, callbacks = RxInferBenchmarkCallbacks()) #hide\n\n# Create a benchmark callbacks instance to track performance\nbenchmark_callbacks = RxInferBenchmarkCallbacks()\n\n# Ru
2n inference multiple times to gather statistics\nfor i in 1:5\n    infer(\n        model = iid_normal(),\n        data = (y = randn(100),),\n        constraints = MeanField(),\n        iterations = 5,\n        initialization = init,\n        callbacks = benchmark_callbacks,\n    )\nend\n\n@test length(benchmark_callbacks.before_model_creation_ts) == 5 #hide\n@test length(benchmark_callbacks.after_model_creation_ts) == 5 #hide\n@test length(benchmark_callbacks.before_inference_ts) == 5 #hide\n@test length(benchmark_callbacks.after_inference_ts) == 5 #hide\nnothing #hide\n\nThe benchmark callbacks instance accumulates timestamps across multiple calls to infer, making it easy to collect performance statistics over many runs.","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Displaying-results","page":"Benchmark callbacks","title":"Displaying results","text":"Install PrettyTables.jl to display the collected statistics in a nicely formatted table:\n\nusing PrettyTables\n\nPrettyTables.pretty_table(benchmark_callbacks)","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Accessing-from-model-metadata","page":"Benchmark callbacks","title":"Accessing from model metadata","text":"After model creation, the benchmark callbacks instance is automatically saved into the model's metadata under the :benchmark key. This makes it accessible from the inference result without needing to hold onto the callbacks object separately:\n\nresult = infer(\n    model = iid_normal(),\n    data = (y = randn(100),),\n    constraints = MeanField(),\n    iterations = 5,\n    initialization = init,\n    callbacks = RxInferBenchmarkCallbacks(),\n)\n\n@test haskey(result.model.metadata, :benchmark) #hide\n@test result.model.metadata[:benchmark] isa RxInferBenchmarkCallbacks #hide\n\nbenchmark = result.model.metadata[:benchmark]\nprintln(benchmark)","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Tracked-events","page":"Benchmark callbacks","title":"Tracked events","text":"The RxInferBenchmarkCallbacks structure collects timestamps at the following stages:\n\nEvent Batch inference Streamline inference\nModel creation (before/after) yes yes\nInference (before/after) yes —\nEach iteration (before/after) yes —\nAutostart (before/after) — yes","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Buffer-capacity","page":"Benchmark callbacks","title":"Buffer capacity","text":"By default, the structure uses circular buffers with a capacity of RxInfer.DEFAULT_BENCHMARK_CALLBACKS_BUFFER_CAPACITY entries. This limits memory usage in long-running applications. You can change the capacity:\n\n# Store up to 10000 benchmark entries\nlarge_buffer_callbacks = RxInferBenchmarkCallbacks(capacity = 10_000)\nnothing #hide","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#Programmatic-access-to-statistics","page":"Benchmark callbacks","title":"Programmatic access to statistics","text":"Use RxInfer.get_benchmark_stats to retrieve the raw statistics matrix:\n\n# Use the previously populated benchmark_callbacks\nstats = RxInfer.get_benchmark_stats(benchmark_callbacks)\n\n@test size(stats, 2) == 6 #hide\n@test size(stats, 1) >= 1 #hide\n\nfor row in eachrow(stats)\n    println(row[1], \": min=\", round(row[2] / 1e6, digits=2), \"ms, mean=\", round(row[4] / 1e6, digits=2), \"ms\")\nend\n\nThe matrix contains the following columns:\n\nOperation name (String)\nMinimum time (Float64, nanoseconds)\nMaximum time (Float64, nanoseconds)\nMean time (Float64, nanoseconds)\nMedian time (Float64, nanoseconds)\nStandard deviation (Float64, nanoseconds)","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#API-Reference","page":"Benchmark callbacks","title":"API Reference","text":"note: Note\nThe timing measurements include all overhead from the Julia runtime and may vary between runs. For more precise benchmarking of specific code sections, consider using the BenchmarkTools.jl package.\n\n","category":"section"},{"location":"manuals/inference/benchmark-callbacks/#RxInfer.RxInferBenchmarkCallbacks","page":"Benchmark callbacks","title":"RxInfer.RxInferBenchmarkCallbacks","text":"RxInferBenchmarkCallbacks(; capacity = RxInfer.DEFAULT_BENCHMARK_CALLBACKS_BUFFER_CAPACITY)\n\nA callback structure for collecting timing information during the inference procedure. This structure collects timestamps for various stages of the inference process and aggregates them across multiple runs, allowing you to track performance statistics (min/max/average/etc.) of your model's creation and inference procedure. The structure supports pretty printing b
2y default, displaying timing statistics in a human-readable format.\n\nThe structure uses circular buffers with a default capacity of 1000 entries to store timestamps, which helps to limit memory usage in long-running applications. Use RxInferBenchmarkCallbacks(; capacity = N) to change the buffer capacity. See also RxInfer.get_benchmark_stats(callbacks).\n\nAfter model creation, the benchmark callbacks instance is automatically saved into the model's metadata under the :benchmark key (i.e., model.metadata[:benchmark]), making it accessible from the inference result via result.model.metadata[:benchmark].\n\nFields\n\nbefore_model_creation_ts: CircularBuffer of timestamps before model creation\nafter_model_creation_ts: CircularBuffer of timestamps after model creation\nbefore_inference_ts: CircularBuffer of timestamps before inference starts\nafter_inference_ts: CircularBuffer of timestamps after inference ends\nbefore_iteration_ts: CircularBuffer of vectors of timestamps before each iteration\nafter_iteration_ts: CircularBuffer of vectors of timestamps after each iteration\nbefore_autostart_ts: CircularBuffer of timestamps before autostart\nafter_autostart_ts: CircularBuffer of timestamps after autostart\n\nExample\n\n# Create a callbacks instance to track performance\ncallbacks = RxInferBenchmarkCallbacks()\n\n# Run inference multiple times to gather statistics\nfor _ in 1:10\n    result = infer(\n        model = my_model(),\n        data = my_data,\n        callbacks = callbacks\n    )\nend\n\n# Access the benchmark callbacks from the inference result\nresult.model.metadata[:benchmark] === callbacks # true\n\n# Display the timing statistics (you need to install `PrettyTables.jl` to use `pretty_table` function)\nusing PrettyTables\n\nPrettyTables.pretty_table(callbacks)\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/benchmark-callbacks/#RxInfer.get_benchmark_stats","page":"Benchmark callbacks","title":"RxInfer.get_benchmark_stats","text":"get_benchmark_stats(callbacks::RxInferBenchmarkCallbacks)\n\nReturns a matrix containing benchmark statistics for different operations in the inference process. The matrix contains the following columns:\n\nOperation name (String)\nMinimum time (Float64)\nMaximum time (Float64)\nMean time (Float64)\nMedian time (Float64)\nStandard deviation (Float64)\n\nEach row represents a different operation (model creation, inference, iteration, autostart). Times are in nanoseconds.\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/benchmark-callbacks/#RxInfer.DEFAULT_BENCHMARK_CALLBACKS_BUFFER_CAPACITY","page":"Benchmark callbacks","title":"RxInfer.DEFAULT_BENCHMARK_CALLBACKS_BUFFER_CAPACITY","text":"DEFAULT_BENCHMARK_CALLBACKS_BUFFER_CAPACITY\n\nThe default capacity of the circular buffers used to store timestamps in the RxInferBenchmarkCallbacks structure.\n\n\n\n\n\n","category":"constant"},{"location":"manuals/debugging/#user-guide-debugging","page":"Debugging","title":"Debugging","text":"Debugging inference in RxInfer can be challenging due to the reactive nature of the inference, undefined order of computations, the use of observables, and generally hard-to-read stack traces in Julia. This page covers several techniques to help you find and fix problems in your model.\n\ntip: Quick reference\nTrace inference events — use RxInferTraceCallbacks or trace = true to record every callback event. See Trace callbacks.\nBenchmark performance — use RxInferBenchmarkCallbacks or benchmark = true to collect timing statistics. See Benchmark callbacks.\nCustom callbacks — use the Callbacks system to inject arbitrary logic at any point during inference.","category":"section"},{"location":"manuals/debugging/#Getting-Help-from-the-Community","page":"Debugging","title":"Getting Help from the Community","text":"When you encounter issues that are difficult to debug, the RxInfer community is here to help:\n\nShare Session Data: For complex issues, you can share your session data to help us understand exactly what's happening in your model. See Session Sharing to learn how.\nJoin Community Meetings: We discuss common issues and s
2olutions in our regular community meetings. See Getting Help with Issues for more information.","category":"section"},{"location":"manuals/debugging/#user-guide-debugging-callbacks","page":"Debugging","title":"Using callbacks to inspect the inference procedure","text":"The Callbacks system lets you inject custom logic at specific moments during inference — for example, to print intermediate posteriors, track the order of updates, or collect diagnostics.\n\nBelow we show an application of callbacks to a model containing both scalar and vectorized latent variables.\n\nusing RxInfer\n\n@model function vectorized_model(y)\n    local u\n    θ ~ Normal(mean= 1.0, var = 1.0)\n    for i in 1:2\n        u[i] ~ Normal(mean = θ, var = 1.0)\n    end\n    y .~ Normal(mean = u, var = 1.0)\nend\n\nNext, let us define a synthetic dataset:\n\ndataset = [1.0, 2.0]\nnothing #hide\n\nNow we define callbacks that track the order of posterior updates and their intermediate values for each variational iteration.\n\nnote: Note on Dispatch\nSince RxInfer passes a collection of posteriors when updating vectorized variables, we branch on posterior isa AbstractVector to handle vectorized updates (e.g., u) using broadcasted operations like mean.().\n\n# A callback that will be called every time before a variational iteration starts\nfunction before_iteration_callback(event)\n    println(\"--- Starting iteration \", event.iteration, \" ---\")\nend\n\n# A callback that will be called every time after a variational iteration finishes\nfunction after_iteration_callback(event)\n    println(\"--- Iteration \", event.iteration, \" has been finished ---\")\nend\n\n# A callback that will be called every time a posterior is updated\nfunction on_marginal_update_callback(event)\n    variable_name = event.variable_name\n    posterior = event.update\n    if posterior isa AbstractVector\n        println(\"Latent variable \", variable_name, \" has been updated. Estimated mean is \", mean.(posterior), \" with standard deviation \", std.(posterior))\n    else\n        println(\"Latent variable \", variable_name, \" has been updated. Estimated mean is \", mean(posterior), \" with standard deviation \", std(posterior))\n    end\nend\n\nAfter defining the callbacks, pass them to infer as a named tuple:\n\nresult = infer(\n    model = vectorized_model(),\n    data  = (y = dataset, ),\n    iterations = 3,\n    initialization = @initialization(q(θ) = Uniform(0, 1)),\n    returnvars = KeepLast(),\n    callbacks = (\n        on_marginal_update = on_marginal_update_callback,\n        before_iteration   = before_iteration_callback,\n        after_iteration    = after_iteration_callback\n    )\n)\nnothing #hide\n\nWe can see that the callback has been correctly executed for each intermediate variational iteration, correctly handling both the scalar θ and the vector u.\n\nprintln(\"Estimated mean u[1]: \", mean(result.posteriors[:u][1]))\nprintln(\"Estimated mean u[2]: \", mean(result.posteriors[:u][2]))\nprintln(\"Estimated mean θ: \", mean(result.posteriors[:θ]))\nnothing #hide","category":"section"},{"location":"manuals/debugging/#user-guide-debugging-trace-callbacks","page":"Debugging","title":"Tracing callback events with RxInferTraceCallbacks","text":"tip: Tip\nFor the full API reference, see the dedicated Trace callbacks page.\n\nFor a quick overview of which events fired and in what order, use RxInferTraceCallbacks (or simply pass trace = true to infer). This records every callback event — both RxInfer-level and ReactiveMP-level — as a TracedEvent, making it easy to inspect the full inference lifecycle after the fact.\n\nusing RxInfer\nusing RxInfer.ReactiveMP: event_name\n\nresult = infer(\n    model = vectorized_model(),\n    data  = (y = dataset, ),\n    iterations = 3,\n    initialization = @initialization(q(θ) = Uniform(0, 1)),\n    returnvars = KeepLast(),\n    trace = true,\n)\n\n# Access the trace from model metadata\ntrace = result.model.metadata[:trace]\n\n# Show all recorded event names\nevent_names = [event_name(e.event) for e in RxInfer.tracedevents(trace)]\nprintln(\"Recorded \", length(event_names), \" events\")\nprintln(\"Unique event types: \", unique(event_names))\n\nYou can also filter for specific events:\n\n# How many iteration events were recorded?\nbefore_iters = RxInfer.tracedevents(:before_iteration, trace)\nprintln(\"Number of before_iteration events: \", length(before_iters))","category":"section"},{"location":"manuals/debugging/#user-guide-debugging-message-computations","page":"Debugging","title":"Tracing individual message computations","text":"The on_marginal_update callback shown above reports posteriors as they become available. To trace finer-grained events — every individual rule invocation, message product, or marginal computation — use the lower-level message-passing callbacks such as before_message_rule_call and after_message_rule_call. See the Callbacks page for the full list of available events and their fields.\n\nFor a drop-in solution that records every event (iteration boundaries, rule calls, marginal updates, ...) into a structured log you can filter and inspect after inference, use RxInferTraceCallbacks or pass trace = true to infer. See Trace callbacks for details.\n\nnote: Note\nEarlier versions of RxInfer exposed a LoggerPipelineStage attached via the where { pipeline = ... }
2 node clause. That API was removed together with ReactiveMP's AbstractPipelineStage hierarchy in v6; the callback mechanism above subsumes its functionality without subscribing to the reactive streams.","category":"section"},{"location":"manuals/debugging/#user-guide-debugging-benchmark-callbacks","page":"Debugging","title":"Using RxInferBenchmarkCallbacks for performance analysis","text":"tip: Tip\nFor the full API reference, model metadata integration, and programmatic access to statistics, see the dedicated Benchmark callbacks page.\n\nRxInferBenchmarkCallbacks collects timing information during the inference procedure. It aggregates timestamps across multiple runs, allowing you to track performance statistics (min/max/average/etc.) of your model's creation and inference procedure. You can either pass it directly as a callbacks argument, or simply use benchmark = true in the infer function.\n\nusing RxInfer\n\n@model function iid_normal(y)\n    μ  ~ Normal(mean = 0.0, variance = 100.0)\n    γ  ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = γ)\nend\n\ninit = @initialization begin\n    q(μ) = vague(NormalMeanVariance)\nend\n\ninfer(model = iid_normal(), data = (y = dataset, ), constraints = MeanField(), iterations = 5, initialization = init, callbacks = RxInferBenchmarkCallbacks()) #hide\n\n# Create a benchmark callbacks instance to track performance\nbenchmark_callbacks = RxInferBenchmarkCallbacks()\n\n# Run inference multiple times to gather statistics\nfor i in 1:3  # Usually you'd want more runs for better statistics\n    infer(\n        model = iid_normal(),\n        data = (y = dataset, ),\n        constraints = MeanField(),\n        iterations = 5,\n        initialization = init,\n        callbacks = benchmark_callbacks\n    )\nend\n\nTo nicely display the statistics, install the PrettyTables.jl package. It is not bundled with RxInfer by default, but if installed, it makes the output more readable.\n\nusing PrettyTables\n\n# Display the benchmark statistics in a nicely formatted table\nPrettyTables.pretty_table(benchmark_callbacks)\n\nThe RxInferBenchmarkCallbacks structure collects timestamps at various stages of the inference process:\n\nBefore and after model creation\nBefore and after inference starts/ends\nBefore and after each iteration\nBefore and after autostart (for streaming inference)\n\nFor the full API reference, programmatic access to statistics, and model metadata integration, see the dedicated Benchmark callbacks page.","category":"section"},{"location":"manuals/debugging/#user-guide-debugging-memory-addon","page":"Debugging","title":"Legacy: Tracing message computations with InputArgumentsAnnotations","text":"warning: Legacy feature\nThe InputArgumentsAnnotations system is a legacy feature from ReactiveMP and may be removed in a future release. For most debugging and inspection use cases, the Trace callbacks system is more powerful and easier to use — it records every event (including message rule calls, product computations, form constraint applications, and marginal computations) from both RxInfer and ReactiveMP.\n\nRxInfer provides a way to save the history of the computations leading up to the computed messages and marginals. This history is added on top of messages and marginals and is referred to as an Input Arguments Annotation.\n\nnote: Note\nAnnotations are a feature of ReactiveMP. Read more about implementing custom annotations in the corresponding section of the ReactiveMP package.\n\nWe demonstrate the Input Arguments Annotation on the coin toss example from earlier in the documentation. We model the binary outcome x (heads or tails) using a Bernoulli distribution, with a parameter theta that represents the probability of landing on heads. We have a Beta prior distribution for the theta parameter, with a known shape alpha and rate beta parameter.\n\ntheta sim mathrmBeta(a b)\n\nx_i sim mathrmBernoulli(theta)\n\nwhere x_i in 0 1 are the binary observations (heads = 1, tails = 0). This is the corresponding RxInfer model:\n\nusing RxInfer, Random, Plots\n\nn = 4\nθ_real = 0.3\ndataset = float.(rand(Bernoulli(θ_real), n))\n\n@model function coin_model(x)\n    θ  ~ Beta(4, huge)\n    x .~ Bernoulli(θ)\nend\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = dataset, ),\n)\n\nThe model runs without errors. But when we plot the posterior distribution for theta, something's wrong — the posterior seems to be a flat distribution:\n\n\nrθ = range(0, 1, length = 1000)\n\nplot(rθ, (rvar) -> pdf(result.posteriors[:θ], rvar), label=\"Inferred posterior\")\nvline!([θ_real], label=\"Real θ\", title = \"Inference results\")\n\nWe can figure out what's wrong by tracing the computation of the posterior with the Input Arguments Annotation. To obtain the trace, add annotations = (InputArgumentsAnnotations(),) as an argument to the infer function. Note that the argument to the annotations keyword argument must be a tuple, because multiple annotations can be activated at the same time.\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = dataset, ),\n    annotations = (InputArgumentsAnnotations(),)\n)\n\nNow we have access to the messages that led to the marginal posterior:\n\nRxInfer.ReactiveMP.getannotations(result.posteriors[:θ])\n\n(Image: messages_annotated_with_input_arguments)\n\nThe messages in the factor graph are marked in color. If you're interested in the mathematics behind these results, consider verifying them manually using the general equation for sum-product messages:\n\nunderbraceoverrightarrowmu_θ(θ)_substack textoutgoing textmessage = sum_x_1ldotsx_n underbraceoverrightarrowmu_X_1(x_1)cdots overrightarrowmu_X_n(x_n)_substacktextincoming  textmessages cdot underbracef(θx_1ldotsx_n)_substacktextnode textfunction\n\n(Image: Graph)\n\nNote that the posterior (yellow) has a rate parameter on the order of 1e12. Our plot failed because a Beta distribution with such a rate parameter cannot be accurately depicted using the range of theta we used in the code block above. So why does the posterior have this rate parameter?\n\nAll the observations (purple, green, pink, blue) have much smaller rate parameters. It seems the prior distribution (red) has an unusual rate parameter, namely 1e12. If we look back at the model, the parameter was set to huge (which is a reserved keyword meaning 1e12). Reducing the prior rate parameter will ensure the posterior has a reasonable rate parameter as well.\n\n@model function coin_model(x)\n    θ  ~ Beta(4, 100)\n    x .~ Bernoulli(θ)\nend\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = dataset, ),\n)\n\nrθ = range(0, 1, length = 1000)\n\nplot(rθ, (rvar) -> pdf(result.posteriors[:θ], rvar), fillalpha = 0.4, fill = 0, label=\"Inferred posterior\")\nvline!([θ_real], label=\"Real θ\", title = \"Inference results\")\n\nNow the posterior has a much more sensible shape, confirming that we have identified the original issue correctly. We can run the model with more observations to get an even better posterior:\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = float.(rand(Bernoulli(θ_real), 1000)), ),\n)\n\nrθ = range(0, 1, length = 1000)\nplot(rθ, (rvar) -> pdf(result.posteriors[:θ], rvar), fillalpha = 0.4, fill = 0, label=\"Inferred posterior (1000 observations)\")\nvline!([θ_real], label=\"Real θ\", title = \"Inference results\")\n\n","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference","page":"Streamline inference","title":"Streaming (online) inference","text":"This guide explains how to use the infer function for dynamic datasets. We show how RxInfer can continuously update beliefs asynchronously whenever a new observation arrives. We use a simple Beta-Bernoulli model as an example, which has been covered in the Getting Started section,  however, these techniques can be applied to any model.\n\nAlso read about Static Inference or checkout more complex examples.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-model-spec","page":"Streamline inference","title":"Model specification","text":"Also read the Model Specification section.\n\n
2In online inference, we want to continuously update our prior beliefs about certain hidden states.  To achieve this, we include extra arguments in our model specification to allow for dynamic prior changes:\n\nusing RxInfer\nusing Test #hide\n\n@model function beta_bernoulli_online(y, a, b)\n    θ ~ Beta(a, b)  \n    y ~ Bernoulli(θ)\nend\n\nIn this model, we assume we only have one observation y at a time, and the a and b parameters are not fixed to specific values but rather are arguments of the model itself.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-autoupdates","page":"Streamline inference","title":"Automatic prior update","text":"Next, we want to enable RxInfer to automatically update the a and b parameters as soon as a new posterior for θ is available. To accomplish this, we utilize the @autoupdates macro.\n\nbeta_bernoulli_autoupdates = @autoupdates begin \n    # We want to update `a` and `b` to be equal to the parameters \n    # of the current posterior for `θ`\n    a, b = params(q(θ))\nend\n\nThis specification instructs RxInfer to update a and b parameters automatically as soon as a new posterior for θ is available. Read more about @autoupdates in the Autoupdates guide.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-async-datastream","page":"Streamline inference","title":"Asynchronous data stream of observations","text":"For demonstration purposes, we use a handcrafted stream of observations with the Rocket.jl library\n\nusing Rocket, Distributions, StableRNGs\n\nhidden_θ     = 1 / 3.1415\ndistribution = Bernoulli(hidden_θ)\nrng          = StableRNG(43)\ndatastream   = RecentSubject(Bool)\n\nThe infer function expects the datastream to emit values in the form of the NamedTuples. To simplify this process, Rocket.jl exports labeled function. We also use the combineLatest function to convert a stream of Bools to a stream of Tuple{Bool}s. Read more about these functions in the documentation to Rocket.jl.\n\nobservations = labeled(Val((:y, )), combineLatest(datastream))\n\nLet's verify that our datastream does indeed produce NamedTuples\n\ntest_values = [] #hide\ntest_subscription = subscribe!(observations, (new_observation) -> push!(test_values, new_observation)) #hide\nsubscription = subscribe!(observations, \n    (new_observation) -> println(\"Got new observation \", new_observation, \" 🎉\")\n)\nnothing #hide\n\nfor i in 1:5\n    next!(datastream, rand(rng, distribution))\nend\n@test length(test_values) === 5 #hide\n@test all(value -> haskey(value, :y) && (isone(value[:y]) || iszero(value[:y])), test_values) #hide \nnothing #hide\n\nNice! Our data stream produces events in a form of the NamedTuples, which is compatible with the infer function.\n\nunsubscribe!(test_subscription) #hide\n# It is important to keep track of the existing subscriptions\n# and unsubscribe to reduce the usage of computational resources\nunsubscribe!(subscription)","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-inst-reactive-engine","page":"Streamline inference","title":"Instantiating the reactive inference engine","text":"Now, we have everything ready to start running the inference with RxInfer on dynamic datasets with the infer function:\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    datastream     = observations,\n    autoupdates    = beta_bernoulli_autoupdates,\n    returnvars     = (:θ, ),\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    autostart      = false\n)\n\nIn the code above, there are several notable differences compared to running inference for static datasets. Firstly, we utilized the autoupdates argument as discussed previously. Secondly, we employed the @initialization macro to initialize the posterior over θ. This is necessary for the @autoupdates macro, as it needs to initialize the a and b parameters before the data becomes available. Thirdly, we set autostart = false to indicate that we do not want to immediately subscribe to the datastream, but rather do so manually later using the RxInfer.start function. The returnvars specification differs a little from Static Inference. In reactive inference, the returnvars = (:θ, ) must be a tuple of Symbols and specifies that we would be interested to get a stream of posteriors update for θ. The returnvars specification is optional and the inference engine will create reactive streams for all latent states if omitted.\n\nGiven the engine, we now can subscribe on the posterior updates:\n\nθ_updates_for_testing_the_example  = [] #hide\nθ_updates_for_testing_subscription = subscribe!(engine.posteriors[:θ], (new_posterior_for_θ) -> push!(θ_updates_for_testing_the_example, new_posterior_for_θ)) #hide\nθ_updates_subscription = subscribe!(engine.posteriors[:θ], \n    (new_posterior_for_θ) -> println(\"A new posterior for θ is \", new_posterior_for_θ, \" 🤩\")\n)\nnothing #hide\n\nIn this setting, we should get a message every time a new posterior is available for θ. Let's try to generate a new observation!\n\nnext!(datastream, rand(rng, distribution))\n@test isempty(θ_updates_for_testing_the_example) #hide\nnothing #hide\n\nHmm, nothing happened...? Oh, we forgot to start the engine with the RxInfer.start function. Let's do that now:\n\nRxInfer.start(engine)\n@test length(θ_updates_for_testing_the_example) === 1 #hide\nnothing #hide\n\nAh, as soon as we start our engine, we receive the posterior for θ. This occurred because we initialized our stream as RecentSubject, which retains the most recent value and emits it upon subscription. Our engine automatically subscribed to the observations and obtained the most recent value, initiating inference. Let's see if we can add more observations:\n\nnext!(datastream, rand(rng, distribution))\n@test length(θ_updates_for_testing_the_example) === 2 #hide\nnothing #hide\n\nGreat! We got another posterior! Let's try a few more observations:\n\nfor i in 1:5\n    next!(datastream, rand(rng, distribution))\nend\n@test length(θ_updates_for_testing_the_example) === 7 #hide\nnothing #hide\n\nAs demonstrated, the reactive engine reacts to new observations and performs inference as soon as a new observation is available. But what if we want to maintain a history of posteriors? The infer function supports the historyvars and keephistory arguments precisely for that purpose. In the next section we reinstantiate our engine, with the keephistory argument enabled, but first, we must shutdown the previous engine and unsubscribe from its posteriors:\n\nRxInfer.stop(engine)\nunsubscribe!(θ_updates_subscription)\nunsubscribe!(θ_updates_for_testing_subscription) #hide\nnothing #hide","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-history","page":"Streamline inference","title":"Keeping the history of posteriors","text":"To retain the history of posteriors within the engine, we can utilize the keephistory and historyvars arguments. The keephistory parameter specifies the length of the circular buffer for storing the history of posterior updates, while historyvars determines what variables to save in the history and how often to save them (e.g., every iteration or only at the end of iterations).\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    datastream     = observations,\n    autoupdates    = beta_bernoulli_autoupdates,\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    keephistory    = 100,\n    historyvars    = (θ = KeepLast(), ),\n    autostart      = true\n)\n\nIn the example above, we specified that we want to store at most 100 posteriors for θ, and KeepLast() indicates that we are only interested in the final value of θ and not in intermediate values during variational iterations. We also specified the autostart = true to start the engine automatically without need for RxInfer.start and RxInfer.stop.\n\nnote: Note\nIn this model, we do not utilize the iterations argument, indicating that we perform a single VMP iteration. If multiple iterations were employed, engine.posteriors[:θ] would emit every intermediate value.\n\nNow, we can feed some more observations to the datastream:\n\nfor i in 1:5\n    next!(datastream, rand(rng, distribution))\nend\n\nAnd inspect the engine.history[:θ] buffer:\n\n@test length(engine.history[:θ]) === 6 #hide\nengine.history[:θ]\n\nAs we can see the buffer correctly saved the posteriors in the .history buffer.\n\nnote: Note\nWe have 6 entries, despite having only 5 new observations. As mentioned earlier, this occurs because we initialized our datastream as a RecentSubject, which retains the most recent observation and emits it each time a new subscription occurs.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-history-visualization","page":"Streamline inference","title":"Visualizing the history of posterior estimation","text":"Let's feed more observation and visualize how the posterior changes over time:\n\nfor i in 1:94\n    next!(datastream, rand(rng, distribution))\nend\n@test length(engine.history[:θ]) === 100 #hide\nnothing #hide\n\nTo visualize the history of posteriors we use the @gif macro from the Plots package:\n\nusing Plots\n\n@gif for posterior in engine.history[:θ]\n    rθ = range(0, 1, length = 1000)\n    pθ = plot(rθ, (x) -> pdf(posterior, x), fillalpha=0.3, fillrange = 0, label=\"P(θ|y)\", c=3)\n    pθ = vline!(pθ, [ hidden_θ ], label = \"Real value of θ\")\n\n    plot(pθ)\nend\n\nWe can keep feeding data to our datastream, but only last 100 posteriors will be saved in the history buffer:\n\nfor i in 1:200\n    next!(datastream, rand(rng, distribution))\nend\n\n@test length(engine.history[:θ]) === 100 #hide\n@gif for posterior in engine.history[:θ]\n    rθ = range(0, 1, length = 1000)\n    pθ = plot(rθ, (x) -> pdf(posterior, x), fillalpha=0.3, fillrange = 0, label=\"P(θ|y)\", c=3)\n    pθ = vline!(pθ, [ hidden_θ ], label = \"Real value of θ\")\n\n    plot(pθ)\nend\n\nnote: Note\nIt is also possible to visualize the inference estimation continuously with manual subscription to engine.posteriors[:θ].\n\nAs previously it is important to shutdown the inference engine when it becomes unnecessary:\n\nRxInfer.stop(engine)","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-free-energy","page":"Streamline inference","title":"Subscribing on the stream of free energy","text":"To obtain a continuous stream of updates for the Bethe Free Energy, we need to initialize the engine with the free_energy argument set to true:\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    datastream     = observations,\n    autoupdates    = beta_bernoulli_autoupdates,\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    keephistory    = 5,\n    autostart      = true,\n    free_energy    = true\n)\n\nnote: Note\nIt's important to use the keephistory argument alongside the free_energy argument because setting free_energy = true also maintains an internal 
2circular buffer to track its previous updates.\n\nfree_energy_for_testing = [] #hide\nfree_energy_for_testing_subscription = subscribe!(engine.free_energy, (v) -> push!(free_energy_for_testing, v)) #hide\nfree_energy_subscription = subscribe!(engine.free_energy, \n    (bfe_value) -> println(\"New value of Bethe Free Energy has been computed \", bfe_value, \" 👩‍🔬\")\n)\n@test length(free_energy_for_testing) === 1 #hide\nnothing #hide\n\nLet's emit more observations:\n\nfor i in 1:5\n    next!(datastream, rand(rng, distribution))\nend\n@test length(free_energy_for_testing) === 6 #hide\nnothing #hide\n\nIn this particular example, we do not perform any variational iterations and do not use any variational constraints, hence, the inference is exact. In this case the BFE values are equal to the minus log-evidence of the model given new observation.  We can also track history of Bethe Free Energy values with the following fields of the engine:\n\nfree_energy_history: free energy history, averaged across variational iterations value for all observations  \nfree_energy_raw_history: free energy history, returns computed values of all variational iterations for each data event (if available)\nfree_energy_final_only_history: free energy history, returns computed values of final variational iteration for each data event (if available)\n\n@test length(engine.free_energy_history) === 1 #hide\nengine.free_energy_history\n\n@test length(engine.free_energy_raw_history) === 5 #hide\nengine.free_energy_raw_history\n\n@test length(engine.free_energy_final_only_history) === 5 #hide\nengine.free_energy_final_only_history\n\nunsubscribe!(free_energy_for_testing_subscription) #hide\n# Stop the engine when not needed as usual\nRxInfer.stop(engine)\nunsubscribe!(free_energy_subscription)\n\nAs has been mentioned, in this particular example we do not perform variational iterations, hence, there is little difference between the different representations of the BFE history buffers. However, when performing variational inference with the iterations argument, those buffers will be different. To demonstrate this difference let's build a slightly more complex model with variational constraints:\n\n@model function iid_normal(y, mean_μ, var_μ, shape_τ, rate_τ)\n    μ ~ Normal(mean = mean_μ, var = var_μ)\n    τ ~ Gamma(shape = shape_τ, rate = rate_τ)\n    y ~ Normal(mean = μ, precision = τ)\nend\n\niid_normal_constraints = @constraints begin\n    q(μ, τ) = q(μ)q(τ)\nend\n\niid_normal_autoupdates = @autoupdates begin \n    mean_μ  = mean(q(μ))\n    var_μ   = var(q(μ))\n    shape_τ = shape(q(τ))\n    rate_τ  = rate(q(τ))\nend\n\niid_normal_hidden_μ       = 3.1415\niid_normal_hidden_τ       = 0.0271\niid_normal_distribution   = NormalMeanPrecision(iid_normal_hidden_μ, iid_normal_hidden_τ)\niid_normal_rng            = StableRNG(123)\niid_normal_datastream     = RecentSubject(Float64)\niid_normal_observations   = labeled(Val((:y, )), combineLatest(iid_normal_datastream))\niid_normal_initialization = @initialization begin \n    q(μ) = NormalMeanPrecision(0.0, 0.001)\n    q(τ) = GammaShapeRate(10.0, 10.0)\nend\n\niid_normal_engine  = infer(\n    model          = iid_normal(),\n    datastream     = iid_normal_observations,\n    autoupdates    = iid_normal_autoupdates,\n    constraints    = iid_normal_constraints,\n    initialization = iid_normal_initialization,\n    historyvars    = (\n        μ = KeepLast(),\n        τ = KeepLast(),\n    ),\n    keephistory    = 100,\n    iterations     = 10,\n    free_energy    = true,\n    autostart      = true\n)\n\nThe notable differences with the previous example is the use of the constraints and iterations arguments. Read more about constraints in the Constraints Specification section of the documentation. We have also indicated in the historyvars that we want to keep track of posteriors only from the last variational iteration in the history buffer.\n\nNow we can feed some observations to the datastream:\n\nfor i in 1:100\n    next!(iid_normal_datastream, rand(iid_normal_rng, iid_normal_distribution))\nend\n\nLet's inspect the differences in the free_energy buffers:\n\n@test all(v -> v <= 0.0, diff(iid_normal_engine.free_energy_history)) #hide\n@test length(iid_n
2ormal_engine.free_energy_history) === 10 #hide\niid_normal_engine.free_energy_history\n\n@test length(iid_normal_engine.free_energy_raw_history) === 1000 #hide\niid_normal_engine.free_energy_raw_history\n\n@test length(iid_normal_engine.free_energy_final_only_history) === 100 #hide\niid_normal_engine.free_energy_final_only_history\n\nWe can also visualize different representations:\n\nplot(iid_normal_engine.free_energy_history, label = \"Bethe Free Energy (averaged)\")\n\nnote: Note\nIn general, the averaged Bethe Free Energy values must decrease and converge to a stable point.\n\nplot(iid_normal_engine.free_energy_raw_history, label = \"Bethe Free Energy (raw)\")\n\nplot(iid_normal_engine.free_energy_final_only_history, label = \"Bethe Free Energy (last per observation)\")\n\nAs we can see, in the case of the variational iterations those buffers are quite different and represent different representations of the same Bethe Free Energy stream (which corresponds to the .free_energy_raw_history). As a sanity check, we could also visualize the history of our posterior estimations in the same way  as we did for a simpler previous example:\n\n@test length(iid_normal_engine.history[:μ]) === 100 #hide\n@test length(iid_normal_engine.history[:τ]) === 100 #hide\n@gif for (μ_posterior, τ_posterior) in zip(iid_normal_engine.history[:μ], iid_normal_engine.history[:τ])\n    rμ = range(0, 10, length = 1000)\n    rτ = range(0, 1, length = 1000)\n\n    pμ = plot(rμ, (x) -> pdf(μ_posterior, x), fillalpha=0.3, fillrange = 0, label=\"P(μ|y)\", c=3)\n    pμ = vline!(pμ, [ iid_normal_hidden_μ ], label = \"Real value of μ\")\n\n    pτ = plot(rτ, (x) -> pdf(τ_posterior, x), fillalpha=0.3, fillrange = 0, label=\"P(τ|y)\", c=3)\n    pτ = vline!(pτ, [ iid_normal_hidden_τ ], label = \"Real value of τ\")\n\n    plot(pμ, pτ, layout = @layout([ a; b ]))\nend\n\nNice, the history of the estimated posteriors aligns well with the real (hidden) values of the underlying parameters.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-callbacks","page":"Streamline inference","title":"Callbacks","text":"The RxInferenceEngine has its own lifecycle. The callbacks differ a little bit from Using callbacks with Static Inference. Callbacks can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. For a comprehensive overview of the callbacks system, including custom callback structures and model metadata, see the Callbacks section. Here are available callbacks that can be used together with the streaming inference:\n\nusing RxInfer, Test, Markdown\n# Update the documentation below if this test does not pass\n@test RxInfer.available_callbacks(RxInfer.streaming_inference) === Val((\n    :before_model_creation,\n    :after_model_creation,\n    :before_autostart,\n    :after_autostart,\n    :before_message_rule_call,\n    :after_message_rule_call,\n    :before_product_of_two_messages,\n    :after_product_of_two_messages,\n    :before_product_of_messages,\n    :after_product_of_messages,\n    :before_form_constraint_applied,\n    :after_form_constraint_applied,\n    :before_marginal_computation,\n    :after_marginal_computation\n))\nnothing\n\n\n\nBelow we list RxInfer specific callbacks. In addition to these, ReactiveMP provides lower-level callbacks for the message passing procedure itself, such as before_message_rule_call, after_message_rule_call, before_product_of_messages, after_product_of_messages, before_marginal_computation, after_marginal_computation, and others. For a full list and detailed descriptions of these callbacks, refer to the official documentation of ReactiveMP.\n\nbefore_model_creation(event::BeforeModelCreationEvent)\n\nCalls before the model is going to be created. The event has no fields.\n\nafter_model_creation(event::AfterModelCreationEvent)\n\nCalls right after the model has been created. Access the model via event.model.\n\nbefore_autostart(event::BeforeAutostartEvent)\n\nCalls before the RxInfer.start() function, if autostart is set to true. Access the engine via event.engine.\n\nafter_autostart(event::AfterAutostartEvent)\n\nCalls after the RxInfer.start() function, if autostart is set to true. Access the engine via event.engine.\n\n\n\nHere is an example usage of the outlined callbacks:\n\nbefore_model_creation_called = Ref(false) #hide\nafter_model_creation_called = Ref(false) #hide\nbefore_autostart_called = Ref(false) #hide\nafter_autostart_called = Ref(false) #hide\n\nfunction before_model_creation(event::BeforeModelCreationEvent)\n    before_model_creation_called[] 
2= true #hide\n    println(\"The model is about to be created\")\nend\n\nfunction after_model_creation(event::AfterModelCreationEvent)\n    after_model_creation_called[] = true #hide\n    println(\"The model has been created\")\n    println(\"  The number of factor nodes is: \", length(RxInfer.getfactornodes(event.model)))\n    println(\"  The number of latent states is: \", length(RxInfer.getrandomvars(event.model)))\n    println(\"  The number of data points is: \", length(RxInfer.getdatavars(event.model)))\n    println(\"  The number of constants is: \", length(RxInfer.getconstantvars(event.model)))\nend\n\nfunction before_autostart(event::BeforeAutostartEvent)\n    before_autostart_called[] = true #hide\n    println(\"The reactive inference engine is about to start\")\nend\n\nfunction after_autostart(event::AfterAutostartEvent)\n    after_autostart_called[] = true #hide\n    println(\"The reactive inference engine has been started\")\nend\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    datastream     = observations,\n    autoupdates    = beta_bernoulli_autoupdates,\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    keephistory    = 5,\n    autostart      = true,\n    free_energy    = true,\n    callbacks      = (\n        before_model_creation = before_model_creation,\n        after_model_creation  = after_model_creation,\n        before_autostart      = before_autostart,\n        after_autostart       = after_autostart\n    )\n)\n\n@test before_model_creation_called[] #hide\n@test after_model_creation_called[] #hide\n@test before_autostart_called[] #hide\n@test after_autostart_called[] #hide\n\nRxInfer.stop(engine) #hide\nnothing #hide","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-event-loop","page":"Streamline inference","title":"Event loop","text":"In contrast to Static Inference, the streaming version of the infer function  does not provide callbacks such as on_marginal_update, since it is possible to subscribe directly on those updates with the  engine.posteriors field. However, the reactive inference engine provides an ability to listen to its internal event loop, that also includes \"pre\" and \"post\" events for posterior updates.\n\nLet's build a simple example by implementing our own event listener that does not do anything complex but simply prints some debugging information.\n\nusing RxInfer, Test, Markdown\n# Update the documentation below if this test does not pass\n@test RxInfer.available_events(RxInfer.streaming_inference) === Val((\n    :before_start,\n    :after_start,\n    :before_stop,\n    :after_stop,\n    :on_new_data,\n    :before_iteration,\n    :before_auto_update,\n    :after_auto_update,\n    :before_data_update,\n    :after_data_update,\n    :after_iteration,\n    :before_history_save,\n    :after_history_save,\n    :on_tick,\n    :on_error,\n    :on_complete\n))\nnothing\n\nstruct MyEventListener <: Rocket.Actor{RxInferenceEvent}\n    # ... extra fields\nend\n\nThe available events are\n\n:before_start\n\nEmits right before starting the engine with the RxInfer.start function. The data is (engine::RxInferenceEngine, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_start })\n    (engine, ) = event\n    @test engine isa RxInferenceEngine #hide\n    println(\"The engine is about to start.\")\nend\n\n:after_start\n\nEmits right after starting the engine with the RxInfer.start function. The data is (engine::RxInferenceEngine, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_start })\n    (engine, ) = event\n    @test engine isa RxInferenceEngine #hide\n    println(\"The engine has been started.\")\nend\n\n:before_stop\n\nEmits right before stopping the engine with the RxInfer.stop function. The data is (engine::RxInferenceEngine, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_stop })\n    (engine, ) = event\n    @test engine isa RxInferenceEngine #hide\n    println(\"The engine is about to be stopped.\")\nend\n\n:after_stop\n\nEmits right after stopping the engine with the RxInfer.stop function. The data is (engine::RxInferenceEngine, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_stop })\n    (engine, ) = event\n    @test engine isa RxInferenceEngine #hide\n    println(\"The engine has been stopped.\")\nend\n\n:on_new_data\n\nEmits right before processing new data point. The data is (model::ProbabilisticModel, data)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :on_new_data })\n    (model, data) = event\n    @test model isa ProbabilisticModel #hide\n    @test data isa NamedTuple #hide\n    @test haskey(data, :y) #hide\n    @test iszero(data[:y]) || isone(data[:y]) #hide\n    println(\"The new data point has been received: \", data)\nend\n\n:before_iteration\n\nEmits right before starting new variational iteration. The data is (model::ProbabilisticModel, iteration::Int)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_iteration })\n    (model, iteration) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    println(\"Starting new variational iteration #\", iteration)\nend\n\n:before_auto_update\n\nEmits right before executing the @autoupdates. The data is (model::ProbabilisticModel, iteration::Int, autoupdates)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_auto_update })\n    (model, iteration, autoupdates) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    @test RxInfer.numautoupdates(autoupdates) === 1 #hide\n    println(\"Before processing autoupdates\")\nend\n\n:after_auto_update\n\nEmits right after executing the @autoupdates. The data is (model::ProbabilisticModel, iteration::Int, autoupdates)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_auto_update })\n    (model, iteration, autoupdates) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    @test RxInfer.numautoupdates(autoupdates) === 1 #hide\n    println(\"After processing autoupdates\")\nend\n\n:before_data_update\n\nEmits right before feeding the model with the new data. The data is (model::ProbabilisticModel, iteration::Int, data)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_data_update })\n    (model, iteration, data) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    println(\"Before processing new data \", data)\nend\n\n:after_data_update\n\nEmits right after feeding the model with the new data. The data is (model::ProbabilisticModel, iteration::Int, data)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_data_update })\n    (model, iteration, data) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    println(\"After processing new data \", data)\nend\n\n:after_iteration\n\nEmits right after finishing a variational iteration. The data is (model::ProbabilisticModel, iteration::Int)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_iteration })\n    (model, iteration) = event\n    @test model isa ProbabilisticModel #hide\n    @test iteration isa Int #hide\n    println(\"Finishing the variational iteration #\", iteration)\nend\n\n:before_history_save\n\nEmits right before saving the history (if requested). The data is (model::ProbabilisticModel, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :before_history_save })\n    (model, ) = event\n    @test model isa ProbabilisticModel #hide\n    println(\"Before saving the history\")\nend\n\n:after_history_save\n\nEmits right after saving the history (if requested). The data is (model::ProbabilisticModel, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_history_save })\n    (model, ) = event\n    @test model isa ProbabilisticModel #hide\n    println(\"After saving the history\")\nend\n\n:on_tick\n\nEmits right after finishing processing the new observations and completing the inference step. The data is (model::ProbabilisticModel, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :on_tick })\n    (model, ) = event\n    @test model isa ProbabilisticModel #hide\n    println(\"Finishing the inference for the new observations\")\nend\n\n:on_error\n\nEmits if an error occurs in the inference engine. The data is (model::ProbabilisticModel, err::Any)\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :on_error })\n    (model, err) = event\n    @test model isa ProbabilisticModel #hide\n    println(\"An error occurred during the inference procedure: \", err)\nend\n\n:on_complete\n\nEmits when the datastream completes. The data is (model::ProbabilisticModel, )\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :on_complete })\n    (model, ) = event\n    @test model isa ProbabilisticModel #hide\n    println(\"The data stream completed. The inference has been finished.\")\nend\n\nLet's use our event listener with the infer function:\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    datastream     = observations,\n    autoupdates    = beta_bernoulli_autoupdates,\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    keephistory    = 5,\n    iterations     = 2,\n    autostart      = false,\n    free_energy    = true,\n    events         = Val((\n        :before_start,\n        :after_start,\n        :before_stop,\n        :after_stop,\n        :on_new_data,\n        :before_iteration,\n        :before_auto_update,\n        :after_auto_update,\n        :before_data_update,\n        :after_data_update,\n        :after_iteration,\n        :before_history_save,\n        :after_history_save,\n        :on_tick,\n        :on_error,\n        :on_complete\n    ))\n)\n\nAfter we have created the engine, we can subscribe on events and RxInfer.start the engine:\n\nevents_subscription = subscribe!(engine.events, MyEventListener())\n\nRxInfer.start(engine)\nnothing #hide\n\nThe event loop stays idle without new observation and runs again when a new observation becomes available:\n\nnext!(datastream, rand(rng, distribution))\n\nLet's complete the datastream \n\ncomplete!(datastream)\n\nIn this case, it is not necessary to RxInfer.stop the engine, because  it will be stopped automatically.\n\n@test_logs (:warn, r\"The engine has been completed.*\") RxInfer.stop(engine) #hide\nRxInfer.stop(engine)\nnothing #hide\n\nnote: Note\nThe :before_stop and :after_stop events are not emitted in case of the datastream completion. Use the :on_complete instead.","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-data","page":"Streamline inference","title":"Using data keyword argument with streaming inference","text":"The streaming version does support static datasets as well.  Internally, it converts it to a datastream that emits all observations in sequential order without any delay. As an example:\n\nstaticdata = rand(rng, distribution, 1_000)\n\nUse the data keyword argument instead of the datastream to pass the static data.\n\nengine = infer(\n    model          = beta_bernoulli_online(),\n    data           = (y = staticdata, ),\n    autoupdates    = beta_bernoulli_autoupdates,\n    initialization = @initialization(q(θ) = Beta(1, 1)),\n    keephistory    = 1000,\n    autostart      = true,\n    free_energy    = true,\n)\n\nengine.history[:θ]\n\n@gif for posterior in engine.history[:θ]\n    rθ = range(0, 1, length = 1000)\n    pθ = plot(rθ, (x) -> pdf(posterior, x), fillalpha=0.3, fillrange = 0, label=\"P(θ|y)\", c=3)\n    pθ = vline!(pθ, [ hidden_θ ], label = \"Real value of θ\")\n\n    plot(pθ)\nend","category":"section"},{"location":"manuals/inference/streamlined/#manual-online-inference-where-to-go","page":"Streamline inference","title":"Where to go next?","text":"This guide covered some fundamental usages of the infer function in the context of streamline inference,  but did not cover all the available keyword arguments of the function. Read more explanation about the other keyword arguments  in the Overview section or check out the Static Inference section. Also check out more complex examples.\n\n","category":"section"},{"location":"manuals/inference/streamlined/#RxInfer.RxInferenceEngine","page":"Streamline inference","title":"RxInfer.RxInferenceEngine","text":"RxInferenceEngine\n\nThe return value of the infer function in case of streamlined inference. \n\nPublic fields\n\nposteriors: Dict or NamedTuple of 'random variable' - 'posterior stream' pairs. See the returnvars argument for the infer.\nfree_energy: (optional) A stream of Bethe Free Energy values per VMP iteration. See the free_energy argument for the infer.\nhistory: (optional) Saves history of previous marginal updates. See the historyvars and keephistory arguments for the infer.\nfree_energy_history: (optional) Free energy history, averaged across variational iterations for all observations\nfree_energy_raw_history: (optional) Free energy history, returns the computed values of all variational iterations for each data event (if available)\nfree_energy_final_only_history: (optional) Free energy history, returns the computed values of the final variational iteration for each data event (if available)\nevents: (optional) A stream of events sent by the inference engine. See the events argument for the infer.\nmodel: ProbabilisticModel object reference.\n\nUse the RxInfer.start(engine) function to subscribe on the datastream source and start the inference procedure.  Use RxInfer.stop(engine) to unsubscribe from the datastream source and stop the inference procedure.  Note, that it is not always possible to start/stop the inference procedure.\n\nSee also: infer, RxInferenceEvent, RxInfer.start, RxInfer.stop\n\n\n\n\n\n","category":"type"},{"location":"manuals/inference/streamlined/#RxInfer.start","page":"Streamline inference","title":"RxInfer.start","text":"start(engine::RxInferenceEngine)\n\nStarts the RxInferenceEngine by subscribing to the data source, instantiating free energy (if enabled) and starting the event loop. Use RxInfer.stop to stop the RxInferenceEngine. Note that it is not always possible to stop/restart the engine and this depends on the data source type.\n\nSee also: RxInfer.stop\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/streamlined/#RxInfer.stop","page":"Streamline inference","title":"RxInfer.stop","text":"stop(engine::RxInferenceEngine)\n\nStops the RxInferenceEngine by unsubscribing to the data source, free energy (if enabled) and stopping the event loop. Use RxInfer.start to start the RxInferenceEngine again. Note that it is not always possible to stop/restart the engine and this depends on the data source type.\n\nSee also: RxInfer.start\n\n\n\n\n\n","category":"function"},{"location":"manuals/inference/streamlined/#RxInfer.RxInferenceEvent","page":"Streamline inference","title":"RxInfer.RxInferenceEvent","text":"RxInferenceEvent{T, D}\n\nThe RxInferenceEngine sends events in the form of the RxInferenceEvent structure. T represents the type of an event, D represents the type of the data associated with the event. The type of data depends on the type of an event, but usually represents a tuple, which can be unrolled automatically with Julia's splatting syntax, e.g. model, iteration = event. See the documentation of the infer function for possible event types and their associated data types.\n\nThe events system itself uses the Rocket.jl library API. For example, one may create a custom event listener in the following way:\n\nusing Rocket\n\nstruct MyEventListener <: Rocket.Actor{RxInferenceEvent}\n    # ... extra fields\nend\n\nfunction Rocket.on_next!(listener::MyEventListener, event::RxInferenceEvent{ :after_iteration })\n    model, iteration = event\n    println(\"Iteration $(iteration) has been finished.\")\nend\n\nfunction Rocket.on_error!(listener::MyEventListener, err)\n    # ...\nend\n\nfunction Rocket.o
2n_complete!(listener::MyEventListener)\n    # ...\nend\n\n\nand later on:\n\nengine = infer(events = Val((:after_iteration, )), ...)\n\nsubscription = subscribe!(engine.events, MyEventListener(...))\n\nSee also: infer, RxInferenceEngine\n\n\n\n\n\n","category":"type"},{"location":"manuals/how-to-use-rxinfer-from-python/#python-usage","page":"Using RxInfer from Python","title":"Using RxInfer from Python","text":"warning: Third-party, unmaintained\nRxInferServer.jl and the RxInferClient.py Python SDK are developed by Lazy Dynamics — an external organisation that is separate from the core RxInfer team. These projects are distributed under a different license than RxInfer.jl and are not actively maintained by the ReactiveBayes / RxInfer core team. The links on this page are provided for reference only; some of them may be outdated or unavailable. Please direct support requests to the respective upstream repositories rather than to the RxInfer issue tracker.\n\nRxInfer can be used from Python through the RxInferServer.jl RESTful API and the RxInferClient.py Python SDK.\n\nFor detailed documentation on how to use RxInfer from Python, please refer to:\n\nRxInferServer Documentation - Complete documentation of the RESTful API\nPython SDK Documentation - Documentation for the Python client\nRxInferServer Repository - Source code and examples for the server\nPython SDK Repository - Source code and examples for the Python client\n\nFor support and issues, please use:\n\nRxInferServer Issues\nPython SDK Issues\nCommunity Discussions\n\n","category":"section"},{"location":"manuals/migration/v4-to-v5/#Migration-Guide-from-version-4.x-to-5.x","page":"Migration from v4 to v5","title":"Migration Guide from version 4.x to 5.x","text":"This guide explains how to migrate your code from RxInfer version 4.x to 5.x. The main breaking changes in version 5.x are:\n\nAddons have been renamed to annotations (propagated from the new ReactiveMP v6 release).\nThe callback system has been refactored to use event structs instead of dispatch on positional arguments.\nThe default postprocessing strategy has been simplified.\nPipeline stages and the per-node scheduler argument have been replaced by a unified stream postprocessor API (propagated from ReactiveMP v6).","category":"section"},{"location":"manuals/migration/v4-to-v5/#Addons-have-been-renamed-to-annotations","page":"Migration from v4 to v5","title":"Addons have been renamed to annotations","text":"In ReactiveMP v6, the \"addon\" system was redesigned and renamed to \"annotations\". RxInfer has been updated to match this new API. All user-facing references to addons have been renamed to annotations.","category":"section"},{"location":"manuals/migration/v4-to-v5/#The-addons-keyword-in-infer-is-now-annotations","page":"Migration from v4 to v5","title":"The addons keyword in infer is now annotations","text":"The most visible change is the keyword argument in the infer function:\n\nBefore (v4.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    addons = AddonLogScale(),\n)\n\nAfter (v5.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    annotations = LogScaleAnnotations(),\n)\n\nThe same rename applies when passing the option through the options NamedTuple:\n\nBefore (v4.x):\n\ninfer(model = my_model(), data = ..., options = (addons = AddonLogScale(),))\n\nAfter (v5.x):\n\ninfer(model = my_model(), data = ..., options = (annotations = LogScaleAnnotations(),))","category":"section"},{"location":"manuals/migration/v4-to-v5/#Addon-types-have-been-renamed","page":"Migration from v4 to v5","title":"Addon types have been renamed","text":"The two built-in addon types from ReactiveMP have been renamed:\n\nOld name (v4.x) New name (v5.x)\nAddonLogScale LogScaleAnnotations\nAddonMemory InputArgumentsAnnotations\n\nFor example, to trace input arguments to message rules (previously the \"Memory addon\"), use:\n\nBefore (v4.x):\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = dataset,),\n    addons = (AddonMemory(),),\n)\nRxInfer.ReactiveMP.getaddons(result.posteriors[:θ])\n\nAfter (v5.x):\n\nresult = infer(\n    model = coin_model(),\n    data  = (x = dataset,),\n    annotations = (InputArgumentsAnnotations(),),\n)\nRxInfer.ReactiveMP.getannotations(result.posteriors[:θ])","category":"section"},{"location":"manuals/migration/v4-to-v5/#getaddons-/-setaddons-have-been-renamed","page":"Migration from v4 to v5","title":"getaddons / setaddons have been renamed","text":"If you used RxInfer.getadd
2ons or RxInfer.setaddons on ReactiveMPInferenceOptions directly, rename them to getannotations / setannotations. The same applies to ReactiveMP.getaddons / ReactiveMP.AbstractAddon, which are now ReactiveMP.getannotations / ReactiveMP.AbstractAnnotations.","category":"section"},{"location":"manuals/migration/v4-to-v5/#The-Marginal-type-and-constructor-have-changed","page":"Migration from v4 to v5","title":"The Marginal type and constructor have changed","text":"In ReactiveMP v6, the Marginal type no longer carries addons as a type parameter. The new signature is Marginal{D} instead of Marginal{D, A}, and annotations are always stored in a ReactiveMP.AnnotationDict (which is empty when no annotations are enabled).\n\nConstructor changes:\n\nNo annotations: use the 3-argument form Marginal(data, is_point, is_clamped) instead of the previous Marginal(data, is_point, is_clamped, nothing).\nWith annotations: use Marginal(data, is_point, is_clamped, annotation_dict) where annotation_dict::ReactiveMP.AnnotationDict.\n\nThe AnnotationDict itself only supports an empty constructor. To populate it, use ReactiveMP.annotate!:\n\nann = ReactiveMP.AnnotationDict()\nReactiveMP.annotate!(ann, :my_key, my_value)\nm   = Marginal(data, false, false, ann)\n\nThis is mostly relevant for advanced users who construct Marginal instances directly (e.g. in custom postprocessing tests). Most users will not encounter this change.","category":"section"},{"location":"manuals/migration/v4-to-v5/#Callbacks-now-receive-event-structs","page":"Migration from v4 to v5","title":"Callbacks now receive event structs","text":"The callback system has been completely refactored. In v4.x, callbacks were dispatched with positional arguments (e.g. (model, iteration) -> ...). In v5.x, every callback receives a single event object — a concrete struct subtyping ReactiveMP.Event{E} with named fields.\n\nThis change unifies how RxInfer-level and ReactiveMP-level callbacks are handled, makes the API extensible, and allows passing custom callback handlers as structs.","category":"section"},{"location":"manuals/migration/v4-to-v5/#NamedTuple/Dict-callbacks","page":"Migration from v4 to v5","title":"NamedTuple/Dict callbacks","text":"If you used a NamedTuple or Dict to register callbacks by name, simply replace positional arguments with named field access on the event object.\n\nBefore (v4.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    callbacks = (\n        before_iteration = (model, iteration) -> println(\"Starting iteration \", iteration),\n        after_iteration  = (model, iteration) -> println(\"Finished iteration \", iteration),\n    ),\n)\n\nAfter (v5.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    callbacks = (\n        before_iteration = (event) -> println(\"Starting iteration \", event.iteration),\n        after_iteration  = (event) -> println(\"Finished iteration \", event.iteration),\n    ),\n)","category":"section"},{"location":"manuals/migration/v4-to-v5/#New-event-types","page":"Migration from v4 to v5","title":"New event types","text":"The following RxInfer-level event types are available in v5.x:\n\nBeforeModelCreationEvent, AfterModelCreationEvent\nBeforeInferenceEvent, AfterInferenceEvent\nBeforeIterationEvent, AfterIterationEvent\nBeforeDataUpdateEvent, AfterDataUpdateEvent\nOnMarginalUpdateEvent\nBeforeAutostartEvent, AfterAutostartEvent\n\nIn addition, RxInfer now exposes ReactiveMP-level callbacks such as before_message_rule_call, after_message_rule_call, before_product_of_messages, after_product_of_messages, before_marginal_computation, after_marginal_computation, and others. See the Callbacks section for the full list and event field definitions.","category":"section"},{"location":"manuals/migration/v4-to-v5/#Custom-callback-handlers","page":"Migration from v4 to v5","title":"Custom callback handlers","text":"The callbacks field of infer now also accepts any custom struct that implements ReactiveMP.handle_event. This is useful if you want to share state across callbacks or implement complex logic in a single, type-stable handler:\n\nstruct MyCallbackHandler\n    log::Vector{String}\nend\n\nfunction ReactiveMP.handle_event(handler::MyCallbackHandler, event::AfterIterationEvent)\n    push!(handler.log, \"iteration $(event.iteration) finished\")\nend\n\nhandler = MyCallbackHandler(String[])\nresult  = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    callbacks = handler,\n)","category":"section"},{"location":"manuals/migration/v4-to-v5/#Early-stopping-with-stop_iteration","page":"Migration from v4 to v5","title":"Early stopping with stop_iteration","text":"In v4.x, the before_iteration / after_iteration callbacks could halt iterations by returning true. In v5.x, this is now done by setting a mutable stop_iteration::Bool field on the event object (default false).\n\nBefore (v4.x):\n\ncallbacks = (\n    after_iteration = (model, iteration) -> begin\n        return iteration >= 5  # stop after iteration 5\n    end,\n)\n\nAfter (v5.x):\n\ncallbacks = (\n    after_iteration = (event) -> begin\n        if event.iteration >= 5\n            event.stop_iteration = true\n        end\n    end,\n)\n\nThe built-in StopEarlyIterationStrategy has been updated accordingly — it now receives an AfterIterationEvent instead of (model, iteration). If you used StopEarlyIterationStrategy directly, no changes are required.\n\nSee Early stopping for a complete example.","category":"section"},{"location":"manuals/migration/v4-to-v5/#DefaultPostprocess-has-been-removed","page":"Migration from v4 to v5","title":"DefaultPostprocess has been removed","text":"In v4.x, the postprocess keyword in infer defaulted to DefaultPostprocess(), which inspected each result at runtime to decide whether to unpack the Marginal wrapper. In v5.x this logic has been simplified:\n\nThe postprocess keyword now defaults to nothing.\nWhen postprocess === nothing, infer automatically picks a strategy based on the annotations keyword:\nIf annotations === nothing (the default), UnpackMarginalPostprocess is used — the Marginal wrapper is stripped from results.\nIf annotations are enabled, NoopPostprocess is used — the wrapper is preserved so that annotation data remains accessible via ReactiveMP.getannotations.\nIf you pass postprocess = ... explicitly, your choice is always respected.","category":"section"},{"location":"manuals/migration/v4-to-v5/#Migration","page":"Migration from v4 to v5","title":"Migration","text":"If you were relying on the default behavior, no changes are needed — the new automatic selection produces the same results in both the no-annotations and with-annotations cases.\n\nIf you explicitly passed DefaultPostprocess(), simply remove it (or replace with nothing):\n\nBefore (v4.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    postprocess = DefaultPostprocess(),\n)\n\nAfter (v5.x):\n\nresult = infer(\n    model = my_model(),\n    data  = (y = observations,),\n    # postprocess defaults to `nothing`, which selects the right strategy automatically\n)\n\nCustom postprocessing strategies (any user-defined type that implements inference_postprocess) continue to work unchanged.\n\nSee Inference results postprocessing for more details on the postprocessing system.","category":"section"},{"location":"manuals/migration/v4-to-v5/#Pipeline-stages-and-the-node-level-scheduler-have-been-replaced-by-stream-postprocessors","page":"Migration from v4 to v5","title":"Pipeline stages and the node-level scheduler have been replaced by stream postprocessors","text":"In ReactiveMP v6, the AbstractPipelineStage hierarchy and the per-node scheduler argument have been unified into a single ReactiveMP.AbstractStreamPostprocessor abstraction that postprocesses outbound message streams, marginal streams, and score streams uniformly. RxInfer exposes this through the stream_postprocessors option instead of the old pipeline / scheduler options.","category":"section"},{"location":"manuals/migration/v4-to-v5/#The-where-{-pipeline-...-}-node-clause-has-been-removed","page":"Migration from v4 to v5","title":"The where { pipeline = ... }
2 node clause has been removed","text":"Attaching pipeline stages per node via the where { pipeline = ... } clause is no longer supported — the API it depended on (AbstractPipelineStage, LoggerPipelineStage, AsyncPipelineStage, DiscontinuePipelineStage, ScheduleOnPipelineStage, EmptyPipelineStage, CompositePipelineStage, apply_pipeline_stage, schedule_updates, ...) was removed in ReactiveMP v6.\n\nBefore (v4.x):\n\n@model function my_model(y)\n    μ ~ Normal(mean = 0.0, variance = 100.0)\n    γ ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = γ) where { pipeline = LoggerPipelineStage() }\nend\n\nAfter (v5.x):\n\nLoggerPipelineStage has no direct replacement — use the new Callbacks system (e.g. before_message_rule_call / after_message_rule_call) to observe every message computation without subscribing to the reactive streams. See Tracing individual message computations and Trace callbacks.","category":"section"},{"location":"manuals/migration/v4-to-v5/#The-options-(scheduler-...,)-keyword-is-now-stream_postprocessors","page":"Migration from v4 to v5","title":"The options = (scheduler = ...,) keyword is now stream_postprocessors","text":"The scheduler option under infer(..., options = ...) has been renamed to stream_postprocessors and now expects a ReactiveMP.AbstractStreamPostprocessor (or nothing) rather than a Rocket.jl scheduler. To reproduce the old schedule_on(scheduler) behaviour, wrap the scheduler in a ReactiveMP.ScheduleOnStreamPostprocessor:\n\nBefore (v4.x):\n\ninfer(\n    model = my_model(),\n    data  = (y = dataset,),\n    options = (scheduler = PendingScheduler(),),\n)\n\nAfter (v5.x):\n\ninfer(\n    model = my_model(),\n    data  = (y = dataset,),\n    options = (stream_postprocessors = ReactiveMP.ScheduleOnStreamPostprocessor(PendingScheduler()),),\n)\n\nThe default is now nothing (no-op pass-through) instead of AsapScheduler(). This matches the RandomVariableActivationOptions / FactorNodeActivationOptions default in ReactiveMP v6.","category":"section"},{"location":"manuals/migration/v4-to-v5/#The-limit_stack_depth-option-is-unchanged","page":"Migration from v4 to v5","title":"The limit_stack_depth option is unchanged","text":"options = (limit_stack_depth = N,) still works exactly as before. Internally it is now expanded to ReactiveMP.ScheduleOnStreamPostprocessor(RxInfer.LimitStackScheduler(N)) instead of being passed through as a scheduler directly. The behaviour is identical; you only need to change your code if you combined limit_stack_depth with an explicit scheduler (replace the latter with stream_postprocessors).","category":"section"},{"location":"manuals/migration/v4-to-v5/#Pipeline-stage-replacements","page":"Migration from v4 to v5","title":"Pipeline stage replacements","text":"v4.x v5.x\nLoggerPipelineStage Callbacks (before_message_rule_call / after_message_rule_call)\nAsyncPipelineStage ReactiveMP.ScheduleOnStreamPostprocessor(AsyncScheduler())\nScheduleOnPipelineStage(sched) ReactiveMP.ScheduleOnStreamPostprocessor(sched)\nDiscontinuePipelineStage Removed (was unused); implement a custom AbstractStreamPostprocessor if needed\nEmptyPipelineStage() nothing\nCompositePipelineStage(stages) ReactiveMP.CompositeStreamPostprocessor(stages)\nschedule_updates(vars; pipeline_stage = ...) Pass a ReactiveMP.ScheduleOnStreamPostprocessor through the activation options instead\n\nSee the ReactiveMP v5→v6 migration guide and the Stream postprocessors page in the ReactiveMP documentation for the full low-level API.","category":"section"},{"location":"manuals/migration/v4-to-v5/#Summary-of-breaking-changes","page":"Migration from v4 to v5","title":"Summary of breaking changes","text":"Annotations:\n\naddons keyword argument → annotations (in infer, batch_inference, streaming_inference, and options NamedTuple)\nAddonLogScale → LogScaleAnnotations\nAddonMemory → InputArgumentsAnnotations\ngetaddons / setaddons → getannotations / setannotations\nReactiveMP.AbstractAddon → ReactiveMP.AbstractAnnotations\nMarginal{D, A} → Marginal{D} (annotations stored in an AnnotationDict)\n\nCallbacks:\n\nCallbacks receive a single event object instead of positional arguments\nNew event types: BeforeModelCreationEvent, AfterModelCreationEvent, BeforeInferenceEvent, AfterInferenceEvent, BeforeIterationEvent, AfterIterationEvent, BeforeDataUpdateEvent, AfterDataUpdateEvent, OnMarginalUpdateEvent, BeforeAutostartEvent, AfterAutostartEvent\nEarly stopping uses event.stop_iteration = true instead of returning true\ncallbacks field also accepts custom structs implementing ReactiveMP.handle_event\n\nPostprocessing:\n\nDefaultPostprocess removed; postprocess defaults to nothing and is auto-selected based on annotations\n\nPipeline stages and scheduler:\n\nwhere { pipeline = ... }
2 node clause removed; pipeline-stage types (LoggerPipelineStage, AsyncPipelineStage, ScheduleOnPipelineStage, DiscontinuePipelineStage, EmptyPipelineStage, CompositePipelineStage, AbstractPipelineStage) no longer exist\noptions = (scheduler = ...,) → options = (stream_postprocessors = ReactiveMP.ScheduleOnStreamPostprocessor(...),); default changed from AsapScheduler() to nothing\nLoggerPipelineStage has no direct replacement — use before_message_rule_call / after_message_rule_call callbacks instead\nlimit_stack_depth continues to work unchanged\n\nFor a deeper dive into the new annotation processor system and how to implement custom annotations, see the ReactiveMP.jl documentation.\n\n","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#stack-overflow-inference","page":"Stack Overflow in Message Computations","title":"Stack Overflow during inference","text":"When working with large probabilistic models in RxInfer, you might encounter a StackOverflowError. This section explains why this happens and how to prevent it.","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#The-Problem","page":"Stack Overflow in Message Computations","title":"The Problem","text":"RxInfer uses reactive streams to compute messages between nodes in the factor graph. The subscription to these streams happens recursively, which means:\n\nEach node subscribes to its input messages or posteriors\nThose input messages may need to subscribe to their own inputs\nThis continues until all dependencies are resolved\n\nFor large models, this recursive subscription process can consume the entire stack space, resulting in a StackOverflowError.","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#Example-Error","page":"Stack Overflow in Message Computations","title":"Example Error","text":"When this occurs, you'll see an error message that looks something like this:\n\nERROR: Stack overflow error occurred during the inference procedure. ","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#Solution:-Limiting-Stack-Depth","page":"Stack Overflow in Message Computations","title":"Solution: Limiting Stack Depth","text":"RxInfer provides a solution through the limit_stack_depth option in the inference options. This option limits the recursion depth at the cost of some performance overhead.","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#How-to-Use","page":"Stack Overflow in Message Computations","title":"How to Use","text":"You can enable stack depth limiting by passing it through the options parameter to the infer function:\n\nusing RxInfer\n\n@model function long_state_space_model(y)\n    x[1] ~ Normal(mean = 0.0, var = 1.0)\n    y[1] ~ Normal(mean = x[1], var = 1.0)\n    for i in 2:length(y)\n        x[i] ~ Normal(mean = x[i - 1], var = 1.0)\n        y[i] ~ Normal(mean = x[i], var = 1.0)\n    end\nend\n\ndata = (y = randn(10000), )\n\nresults = infer(\n    model = long_state_space_model(),\n    data = data,\n    options = (\n        limit_stack_depth = 100, # note the comma\n    )\n)\n\nnote: Note\nNote the comma after limit_stack_depth = 100. This is important because it tells Julia that the option is placed in the named tuple options.\n\nWithout limit_stack_depth enabled, the inference will fail with a StackOverflowError:\n\nresults = infer(\n    model = long_state_space_model(),\n    data = data\n)\n\nERROR: Stack overflow error occurred during the inference procedure. ","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#Performance-Considerations","page":"Stack Overflow in Message Computations","title":"Performance Considerations","text":"When limit_stack_depth is enabled:\n\nThe recursive subscription process is split into multiple steps\nThis prevents stack overflow but introduces performance overhead (you should verify this in your use case)\nFor very large models, this option might be essential for successful execution","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#When-to-Use","page":"Stack Overflow in Message Computations","title":"When to Use","text":"Consider using limit_stack_depth when:\n\nWorking with large models (many nodes/variables)\nEncountering StackOverflowError\nProcessing deep hierarchical models\nDealing with long sequences or time series\n\ntip: Tip\nIf you're not sure whether you need this option, try running your model without it first. Only enable limit_stack_depth if you encounter stack overflow issues.","category":"section"},{"location":"manuals/sharpbits/stack-overflow-inference/#Further-Reading","page":"Stack Overflow in Message Computations","title":"Further Reading","text":"For more details about inference options and execution, see:\n\nStatic Inference documentation\nThe options parameter in the infer function documentation\n\n","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification","page":"Constraints Specification","title":"Constraints Specification","text":"Variational inference solves an optimisation problem over a variational family mathcalQ. Constraints specifications are how you tell RxInfer which family to use. They are the main knob by which you trade accuracy for tractability — and the same knob that unlocks inference in models that would otherwise be intractable.\n\nThis page covers the intuition. For the full @constraints syntax, macros and options, see the Constraints Specification manual.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-why","page":"Constraints Specification","title":"Why we need constraints","text":"Formally, RxInfer looks for\n\nq^ast = argmin_q(x) in mathcalQ Fq(haty)\n\nwhere F is the Bethe Free Energy. Without any constraints, mathcalQ is the set of all distributions, and — on a tree-structured factor graph — the optimum is the exact posterior. Once the graph is loopy, the likelihood is non-conjugate, or the joint posterior simply doesn't admit a closed form, you have two choices:\n\nAccept intractability and run expensive sampling (e.g. HMC).\nNarrow the family mathcalQ so that the optimum has a closed form, and optimise inside that narrower family with message passing.\n\nRxInfer is built around option 2. Constraints specifications are how you narrow mathcalQ.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-flavours","page":"Constraints Specification","title":"Two flavours of constraints","text":"There are two kinds of assumptions you can impose on q, and RxInfer supports both.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-factorisation","page":"Constraints Specification","title":"Factorisation constraints","text":"Factorisation constraints say which groups of variables are allowed to carry depen
2dencies in the approximation. The extremes:\n\nMean-field — every latent variable is independent:\nq(mu tau x) = q(mu) q(tau) q(x)\nCheapest, fastest, but ignores all posterior correlations.\nStructured — preserve dependencies between groups of variables that are known (or suspected) to correlate strongly:\nq(mu tau x) = q(mu tau) q(x)\nMore accurate, slightly more expensive, and often the right default for the noise-plus-location style models common in signal processing.\nNo factorisation — pure belief propagation; exact on trees.\n\nIn RxInfer these read almost literally like the math:\n\nusing RxInfer\n\nconstraints = @constraints begin\n    q(μ, τ) = q(μ)q(τ)           # mean-field between μ and τ\nend","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-form","page":"Constraints Specification","title":"Functional form constraints","text":"Functional form constraints pin down which distribution family a marginal is allowed to live in — for example forcing q(x) to be a PointMass (MAP-style estimation), a Gaussian (moment matching), or a collection of samples. These are covered in detail in Built-in Functional Forms.\n\nusing RxInfer\n\nconstraints = @constraints begin\n    q(x) :: PointMassFormConstraint()   # collapse q(x) to a point estimate\nend\n\nTogether, factorisation and functional-form constraints let you shape the variational family precisely — from fully Bayesian to point-estimate and everything in between — in a single declarative block.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-example","page":"Constraints Specification","title":"A worked intuition","text":"Consider the canonical IID Normal example — we want to estimate both a mean μ and a precision τ:\n\n@model function iid_normal(y)\n    μ ~ Normal(mean = 0.0, variance = 1.0)\n    τ ~ Gamma(shape = 1.0, rate = 1.0)\n    y .~ Normal(mean = μ, precision = τ)\nend\n\nHere the Normal likelihood is not conjugate when both μ and τ are unknown — the exact posterior has no closed form. The pragmatic fix is a mean-field factorisation between μ and τ:\n\nconstraints = @constraints begin\n    q(μ, τ) = q(μ)q(τ)\nend\n\nThis assumption makes each individual update analytic: given q(τ), the update for q(μ) is a Gaussian; given q(μ), the update for q(τ) is a Gamma. RxInfer alternates between them until the Bethe Free Energy converges. You gain tractability at the cost of ignoring the (typically weak) posterior correlation between mean and precision — an excellent trade in practice.\n\nSee the full example for the runnable version, including initialisation and diagnostic output.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-guidelines","page":"Constraints Specification","title":"Guidelines for choosing constraints","text":"A few rules of thumb when deciding how aggressively to factorise:\n\nStart lenient, tighten as needed. No factorisation on trees; minimal mean-field assumptions on loopy or non-conjugate portions only.\nFactorise across variable groups, not within them. If two variables are naturally coupled (location–location, scale–scale in the same layer) keep them together.\nWatch the Bethe Free Energy. It is your convergence and sanity diagnostic — a BFE that fails to decrease, or that oscillates, often signals over- or under-constrained mathcalQ.\nConstraints imply initialisation. A factorised q makes inference iterative, so you need an initial marginal somewhere in the loop. See Initialization and the examples in the manual.","category":"section"},{"location":"concepts/constraints-specification/#concepts-constraints-specification-deeper","page":"Constraints Specification","title":"For deeper understanding","text":"Constraints Specification manual — the full @constraints macro reference.\nFunctional Form Constraints — PointMass, SampleList, FixedMarginal, custom forms.\nBethe Free Energy — the objective constraints reshape.\nVariational Message Passing and Local Constraint Manipulation in Factor Graphs — the theoretical grounding for local constraint manipulation in RxInfer.\n\n","category":"section"},{"location":"manuals/inference/delta-node/#delta-node-manual","page":"Deterministic nodes","title":"Deterministic nodes","text":"RxInfer.jl offers a comprehensive set of stochastic no
2des, primarily emphasizing distributions from the exponential family and related compositions, such as Gaussian with controlled variance (GCV) or autoregressive (AR) nodes. The DeltaNode stands out in this package, representing a deterministic transformation of either a single random variable or a group of them. This guide provides insights into the DeltaNode and its functionalities.","category":"section"},{"location":"manuals/inference/delta-node/#Features-and-Supported-Inference-Scenarios","page":"Deterministic nodes","title":"Features and Supported Inference Scenarios","text":"The delta node supports several approximation methods for probabilistic inference. The desired approximation method depends on the nodes connected to the delta node. We differentiate the following deterministic transformation scenarios:\n\nGaussian Nodes: For delta nodes linked to strictly multivariate or univariate Gaussian distributions, the recommended methods are Linearization or Unscented transforms.\nExponential Family Nodes: For the delta node connected to nodes from the exponential family, the CVIProjection (Conjugate Variational Inference) is the method of choice.\nStacking Delta Nodes: For scenarios where delta nodes are stacked, either Linearization, Unscented or CVIProjection are suitable.\nSupport for Inverse Functions: For scenarios where an inverse function is available.\n\nThe table below summarizes the features of the delta node in RxInfer.jl, categorized by the approximation method:\n\nMethods Gaussian Nodes Exponential Family Nodes Stacking Delta Nodes Inverse functions\nLinearization ✓ ✗ ✓ ✓\nUnscented ✓ ✗ ✓ ✓\nCVI (deprecated) ✓ ✓ ✗ ✗\nCVI Projection ✓ ✓ ✓ ✗","category":"section"},{"location":"manuals/inference/delta-node/#Gaussian-Case","page":"Deterministic nodes","title":"Gaussian Case","text":"In the context of Gaussian distributions, we recommend either the Linearization or Unscented method for delta node approximation. The Linearization method provides a first-order approximation, while the Unscented method delivers a more precise second-order approximation. It's worth noting that while the Unscented method is more accurate, it may require hyperparameters tuning. In addition, both methods are working well when the function is differentiable. The results may not be accurate if the function is not differentiable.\n\nFor clarity, consider the following example:\n\nusing RxInfer\n\n@model function delta_node_example(z)\n    x  ~ Normal(mean = 0.0, var = 1.0)\n    y := tanh(x)\n    z  ~ Normal(mean = y, var = 1.0)\nend\n\nnote: Note\nWhile not strictly required, it is advised to use := to define a deterministic relationship within the @model macro.\n\nTo perform inference on this model, designate the approximation method for the delta node (here, the tanh function) using the @meta specification:\n\ndelta_meta = @meta begin \n    tanh() -> Linearization()\nend\n\nor\n\ndelta_meta = @meta begin \n    tanh() -> Unscented()\nend\n\nFor a deeper understanding of the Unscented method and its parameters, consult the docstrings.\n\nGiven the invertibility of tanh, indicating its inverse function can optimize the inference procedure:\n\ndelta_meta = @meta begin \n    tanh() -> DeltaMeta(method = Linearization(), inverse = atanh)\nend\n\nTo execute the inference procedure:\n\nresult = infer(\n    model = delta_node_example(), \n    meta  = delta_meta, \n    data = (z = 1.0,)\n)\n\nThis methodology is consistent even when the delta node is associated with multiple inputs. For instance:\n\nf(x, g) = x*tanh(g)\n\n@model function delta_node_example(z)\n    x ~ Normal(mean = 1.0, var = 1.0)\n    g ~ Normal(mean = 1.0, var = 1.0)\n    y := f(x, g)\n    z ~ Normal(mean = y, var = 0.1)\nend\n\nThe corresponding meta specification is:\n\ndelta_meta = @meta begin \n    f() -> DeltaMeta(method = Linearization())\nend\n\nor simply\n\ndelta_meta = @meta begin \n    f() -> Linearization()\nend\n\nIf specific functions outline the backward relation of variables within the f function, you can provide a tuple of inverse functions in the order of the variables:\n\nf_back_x(out, g) = out/tanh(g)\nf_back_g(out, x) = atanh(out/x)\n\ndelta_meta = @meta begin \n    f() -> DeltaMeta(method = Linearization(), inverse=(f_back_x, f_back_g))\nend","category":"section"},{"location":"manuals/inference/delta-node/#Exponential-Family-Case","page":"Deterministic nodes","title":"Exponential Family Case","text":"When the delta no
2de is associated with nodes from the exponential family (excluding Gaussians), the Linearization and Unscented methods are not applicable. In such cases, the CVI (Conjugate Variational Inference) is available. Here's a modified example:\n\nnote: Note\nThe CVIProjection method is available only if ExponentialFamilyProjection package is installed in the current environment.\n\nusing RxInfer, ExponentialFamilyProjection\n\n@model function delta_node_example1(z)\n    x ~ Gamma(shape = 1.0, rate = 1.0)\n    y := tanh(x)\n    z ~ Bernoulli(y)\nend\n\nThe corresponding meta specification can be represented as:\n\ndelta_meta = @meta begin \n    tanh() -> CVIProjection()\nend\n\nConsult the CVIProjection docstrings for a detailed explanation of its hyper-parameters. Additionally, read the Non-conjugate Inference section.\n\nnote: Note\nThe CVIProjection method is an improved version of the now-deprecated CVI method. This new implementation features different hyperparameters, better accuracy, and improved stability.","category":"section"},{"location":"manuals/inference/delta-node/#Fuse-deterministic-nodes-with-stochastic-nodes","page":"Deterministic nodes","title":"Fuse deterministic nodes with stochastic nodes","text":"Read how to circumvent the need to define the meta structure and, instead, fuse the deterministic relation with a neighboring stochastic factor node in this section.\n\n","category":"section"}]
3}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.