Visualizing Forney Factor Graphs when using RxInfer (Part 5)
Creating a separate FFGViz module
Bayesian Inference
Active Inference
TikZ
RxInfer
Julia
Author
Kobus Esterhuysen
Published
August 16, 2024
Modified
September 6, 2024
In this part (Part 5) we:
move the visualization code to a separate module (FFGViz.jl)
to remove confusion surrounding the naming of layers, we rename:
stateM2 -> stateU2 (state up 2)
stateM1 -> stateU1 (state up 1)
state0 -> state
stateP1 -> stateD1 (state down 1)
stateP2 -> stateD2 (state down 2)
rename layers2 -> layers
One of the distinguishing features of the RxInfer Julia package is that it uses reactive message passing (RMP). This means node updates in the factor graph happens asynchronously. A user usually tends to gloss over these details due to the increased complexity. When node updates happen in the more traditional way, i.e. synchronously (for example as with the PyMDP Python package), users tend to have the option of being aware of which node is currently being udpated. It is quite beneficial to conceptualize the workings of factor graphs in general and the visualization of the factor graph itself is often helpful. The existing visualization code is rather limited and that is the reason this project was undertaken. The ultimate goal is reach a visualization which conforms to the Constrained Forney Factor Graph (CFFG) as defined in
GraphPPL.jl exports the @model macro for model specification allowing the acceptance of a regular Julia function and then builds a Forney Factor Graph (FFG) under the hood. This graph is bipartite where nodes belong to one of two sets: one for factors and one for variables. Note that we use the terms node and link rather than vertex and edge to align with RxInfer terminology.
Node Taxonomy
nodes
facs (FFG/RxInfer factor nodes)
vars (FFG/RxInfer variable nodes)
parvars (parameter variables, e.g. \(\alpha, a, A_t, B, \gamma_t, \mu\))
sysvars (system variables, e.g. \(x_t, s_t, u_t\))
Node Visualization
a fac node is visualized by a
square (style fac) if it is a factor
small black square (style dlt) if it is a delta (\(\delta\)) factor
delta factors connect to their associated parvars and sysvars
a var node is visualized by a
point (style var) if connected to a single fac
equals-square (style eql) if connected to multiple facs
rather than converting the bipartite graph from RxInfer to a graph that only contains a single kind of node, we keep the bipartite graph and visualize the nodes differently
For each of the examples in this project the following steps happen:
A @model is created
The @model is conditioned on some data
ususally a few points so that the visualization does not become unwieldy
A RxInfer model is created (rxi_model)
A GraphPPL model is obtained (gppl_model)
A meta graph is obtained (meta_graph)
The graph is rendered by the existing implementation (GraphPlot.gplot())
Some vertex labels are inspected for the sake of interest
Setup global parameters (indicated by a leading underscore character) to influence the behavior of generate_tikz()
_san_node_ids is provided for node names that needs to be sanitized
_lup_enabled is set to true/false to enable lookup of math names
_lup_agc is set to true/false to enable appending of global_counter values
_raw_links_enabled is set to true/false to enable the drawing of raw links for reference
_CFFG is set to true/false to enable the drawing of beads to turn a FFG into a CFFG
_deltas_enabled is set to true/false to enable the drawing of delta decorations
_lup_dict is provided for all variable names needing math equivalents
_tsep specifies the time delta separation of nodes
_lup_dict specifies the lookup of canonical latex names for nodes
when _lup_agc is true, global_counter values are appended
when _lup_agc is false, global_counter values are not appended
The TikZ string is generated (generate_tikz())
heading of the FFG
GraphPPL model
layers for the FFG
layer names (if applicable)
cntrl (control layer)
paramB1 (parameter B1 layer)
paramB2 (parameter B2 layer)
stateU2 (state up 2 layer, i.e. 2 layers above state layer)
stateU1 (state up 1 layer, i.e. 1 layer above state layer)
state (state layer, i.e. where state transitions happen)
stateD1 (state down 1 layer, i.e. 1 layer below state layer)
stateD2 (state down 2 layer, i.e. 2 layers below state layer)
obser (observation layer)
prefr (preference layer, for setpoints)
paramA (parameter A layer)
The TikZ string is printed
The FFG is saved to a pdf file (show_tikz())
Setup notes
If additional Ubuntu packages are needed for a Julia package
sudo apt-get install package
If you want to run bash on the devcontainer in a local terminal; kobus@Kobuss-Mac-mini ~ %
Activating project at `~/.julia/environments/v1.10`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
### # If your PDF contains a TikZ picture (a vector graphic created using LaTeX), # # you can still use the method I mentioned earlier to display it in a Julia notebook. # # Here’s the method again for your reference:# import Pkg; Pkg.add("PGFPlotsX")# using PGFPlotsX# # Create a struct that represents a PDF file# struct PDF# file::String# end# # Define a function to show the PDF as inline SVG# function Base.show(io::IO, ::MIME"image/svg+xml", pdf::PDF)# svg = first(splitext(pdf.file)) * ".svg"# PGFPlotsX.convert_pdf_to_svg(pdf.file, svg)# write(io, read(svg))# try; rm(svg; force=true); catch e; end# return nothing# end# # Now you can display a PDF file like this:# display(PDF("myoutput.pdf"))# # Internal Error: cairo context error: invalid matrix (not invertible)<0a># # cairo error: invalid matrix (not invertible)
_ytop =30#18 ## grid y-value at the top of picture_show_grid =false## displays a grid with the FFG if true_node_size ="10mm"#"15mm" ## the length of the sides of factor node boxes (in millimeters)## _show_var_points = false ## draws the var circles if true_xsep =2# _xsep = 2.5_tsep =6*_xsep# _tsep = 7*_xsep# _tsep = 7.5*_xsep# _tsep = 8*_xsep# _tsep = 9*_xsep_ysep =3_divn =setup_divn(15)
where \(y_i \in \{0, 1\}\) is a binary observation induced by Bernoulli likelihood while \(\theta\) is a Beta prior distribution on the parameter of Bernoulli. We are interested in inferring the posterior distribution of \(\theta\).
See Fig 4.1 in 2023_Bagaev_RPPfSBI_Thesis. So, \(N\)\(\delta\) factors, 1 Beta factor, \(N\) Ber factors.
@modelfunctioncoin_model(y, a, b)## We endow θ parameter of our model with some prior θ ~Beta(a, b)## We assume that outcome of each coin flip is governed by the Bernoulli distributionfor i ineachindex(y) y[i] ~Bernoulli(θ)endend
meta_graph = coin_meta_graph## Shorten some labels to make graph more readable# str_labels = [string(lab) for lab in labels(meta_graph)]# replacements = # Pair("MvNormalMeanCovariance", "Nmc"), # Pair("MvNormalMeanPrecision", "Nmp"), # Pair("constvar", "cv"),# Pair("x", "XXXXX") ## make more obvious# short_labels = [replace(s, replacements...) for s in str_labels]GraphPlot.gplot( ## existing plotting functionality meta_graph, layout=spring_layout, nodelabel=collect(labels(meta_graph)),## nodelabel=short_labels, nodelabelsize=0.1, NODESIZE=0.02, ## diameter of the nodes NODELABELSIZE=1.5,# nodelabelc="white", nodelabelc="green", nodelabeldist=0.0, nodefillc=nothing, ## "cyan" edgestrokec="red",## ImageSize = (800, 800) ##- does not work)
obser_var_label_strs: ["y_5", "y_7", "y_9"]
obser_var_label_strs[i]: y_5
var_neighbours[j]: Bernoulli_6
in_neighbor_label: θ_1
out_neighbor_label: y_5
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(y_5, out, NodeData in context with properties name = y, index = 1), (θ_1, p, NodeData in context with properties name = θ, index = nothing)]
obser_var_label_strs[i]: y_7
var_neighbours[j]: Bernoulli_8
in_neighbor_label: θ_1
out_neighbor_label: y_7
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(y_7, out, NodeData in context with properties name = y, index = 2), (θ_1, p, NodeData in context with properties name = θ, index = nothing)]
obser_var_label_strs[i]: y_9
var_neighbours[j]: Bernoulli_10
in_neighbor_label: θ_1
out_neighbor_label: y_9
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(y_9, out, NodeData in context with properties name = y, index = 3), (θ_1, p, NodeData in context with properties name = θ, index = nothing)]
var_neighbours[1]: Beta_4
in_neighbor_label: constvar_2
out_neighbor_label: θ_1
inifac_out_neighbor_str: θ_1
inifac_str: Beta_4
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(θ_1, out, NodeData in context with properties name = θ, index = nothing), (constvar_2, a, NodeData in context with properties name = constvar, index = nothing), (constvar_3, b, NodeData in context with properties name = constvar, index = nothing)]
out_var: θ_1
coin_tikz =generate_tikz( heading ="Coin Toss", Model = coin_gppl_model, layers = layers) ## fac layers, i.e. not var (vars are handled with assoced layer)## print(coin_tikz)show_tikz(coin_tikz); ## write to file rather than show in notebook
@modelfunctionhidden_markov_model(x) B ~MatrixDirichlet(ones(3, 3)) A ~MatrixDirichlet([10.01.01.0; 1.010.01.0; 1.01.010.0 ]) d =fill(1.0/3.0, 3) s₀ ~Categorical(d) sₖ₋₁ = s₀for k ineachindex(x) s[k] ~Transition(sₖ₋₁, B) x[k] ~Transition(s[k], A) sₖ₋₁ = s[k]endend
meta_graph = hmm_meta_graph## Shorten some labels to make graph more readable# str_labels = [string(lab) for lab in labels(meta_graph)]# replacements = # Pair("MvNormalMeanCovariance", "Nmc"), # Pair("MvNormalMeanPrecision", "Nmp"), # Pair("constvar", "cv"),# Pair("x", "XXXXX") ## make more obvious# short_labels = [replace(s, replacements...) for s in str_labels]GraphPlot.gplot( ## existing plotting functionality meta_graph, layout=spring_layout, nodelabel=collect(labels(meta_graph)),## nodelabel=short_labels, nodelabelsize=0.1, NODESIZE=0.02, ## diameter of the nodes NODELABELSIZE=1.5,# nodelabelc="white", nodelabelc="green", nodelabeldist=0.0, nodefillc=nothing, ## "cyan" edgestrokec="red",## ImageSize = (800, 800) ##- does not work)
var_neighbours[1]: MatrixDirichlet_3
in_neighbor_label: constvar_2
out_neighbor_label: B_1
inifac_out_neighbor_str: B_1
inifac_str: MatrixDirichlet_3
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(B_1, out, NodeData in context with properties name = B, index = nothing), (constvar_2, a, NodeData in context with properties name = constvar, index = nothing)]
out_var: B_1
var_neighbours[1]: Categorical{P} where P<:Real_9
in_neighbor_label: constvar_8
out_neighbor_label: s₀_7
inifac_out_neighbor_str: s₀_7
inifac_str: Categorical{P} where P<:Real_9
var: (s₀_7, out, NodeData in context with properties name = s₀, index = nothing)
=== NEXT var node is: s₀_7
var_label: s₀_7
var_index: 0
var_neighbours: Any["Categorical{P} where P<:Real_9", "Transition_11"]
var_neighbours[1]: Categorical{P} where P<:Real_9
in_neighbor_label: constvar_8
out_neighbor_label: s₀_7
var_neighbours[2]: Transition_11
in_neighbor_label: s₀_7
out_neighbor_label: s_10
=== NEXT fac node is: Transition_11
var: (s_10, out, NodeData in context with properties name = s, index = 1)
=== NEXT var node is: s_10
var_label: s_10
var_index: 1
var_neighbours: Any["Transition_11", "Transition_13", "Transition_15"]
var_neighbours[1]: Transition_11
in_neighbor_label: s₀_7
out_neighbor_label: s_10
var_neighbours[2]: Transition_13
in_neighbor_label: s_10
out_neighbor_label: x_12
var_neighbours[3]: Transition_15
in_neighbor_label: s_10
out_neighbor_label: s_14
=== NEXT fac node is: Transition_15
var: (s_14, out, NodeData in context with properties name = s, index = 2)
=== NEXT var node is: s_14
var_label: s_14
var_index: 2
var_neighbours: Any["Transition_15", "Transition_17"]
var_neighbours[1]: Transition_15
in_neighbor_label: s_10
out_neighbor_label: s_14
var_neighbours[2]: Transition_17
in_neighbor_label: s_14
out_neighbor_label: x_16
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(s₀_7, out, NodeData in context with properties name = s₀, index = nothing), (constvar_8, p, NodeData in context with properties name = constvar, index = nothing)]
out_var: s₀_7
obser_var_label_strs: ["x_12", "x_16"]
obser_var_label_strs[i]: x_12
var_neighbours[j]: Transition_13
in_neighbor_label: s_10
out_neighbor_label: x_12
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_12, out, NodeData in context with properties name = x, index = 1), (s_10, in, NodeData in context with properties name = s, index = 1), (A_4, a, NodeData in context with properties name = A, index = nothing)]
obser_var_label_strs[i]: x_16
var_neighbours[j]: Transition_17
in_neighbor_label: s_14
out_neighbor_label: x_16
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_16, out, NodeData in context with properties name = x, index = 2), (s_14, in, NodeData in context with properties name = s, index = 2), (A_4, a, NodeData in context with properties name = A, index = nothing)]
var_neighbours[1]: MatrixDirichlet_6
in_neighbor_label: constvar_5
out_neighbor_label: A_4
inifac_out_neighbor_str: A_4
inifac_str: MatrixDirichlet_6
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(A_4, out, NodeData in context with properties name = A, index = nothing), (constvar_5, a, NodeData in context with properties name = constvar, index = nothing)]
out_var: A_4
hmm_tikz =generate_tikz( heading ="Hidden Markov Model", Model = hmm_gppl_model, layers = layers) ## fac layers, i.e. not var (vars are handled with assoced layer)## print(hmm_tikz)show_tikz(hmm_tikz); ## write to file rather than show in notebook
meta_graph = lar_meta_graph## Shorten some labels to make graph more readable# str_labels = [string(lab) for lab in labels(meta_graph)]# replacements = # Pair("MvNormalMeanCovariance", "Nmc"), # Pair("MvNormalMeanPrecision", "Nmp"), # Pair("constvar", "cv"),# Pair("x", "XXXXX") ## make more obvious# short_labels = [replace(s, replacements...) for s in str_labels]GraphPlot.gplot( ## existing plotting functionality meta_graph, layout=spring_layout, nodelabel=collect(labels(meta_graph)),## nodelabel=short_labels, nodelabelsize=0.1, NODESIZE=0.02, ## diameter of the nodes NODELABELSIZE=1.5,# nodelabelc="white", nodelabelc="green", nodelabeldist=0.0, nodefillc=nothing, ## "cyan" edgestrokec="red",## ImageSize = (800, 800) ##- does not work)
var_neighbours[1]: NormalMeanPrecision_8
in_neighbor_label: constvar_6
out_neighbor_label: θ_5
inifac_out_neighbor_str: θ_5
inifac_str: NormalMeanPrecision_8
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(θ_5, out, NodeData in context with properties name = θ, index = nothing), (constvar_6, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_7, τ, NodeData in context with properties name = constvar, index = nothing)]
out_var: θ_5
var_neighbours[1]: GammaShapeRate_4
in_neighbor_label: constvar_2
out_neighbor_label: γ_1
inifac_out_neighbor_str: γ_1
inifac_str: GammaShapeRate_4
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(γ_1, out, NodeData in context with properties name = γ, index = nothing), (constvar_2, α, NodeData in context with properties name = constvar, index = nothing), (constvar_3, β, NodeData in context with properties name = constvar, index = nothing)]
out_var: γ_1
var_neighbours[1]: NormalMeanPrecision_12
in_neighbor_label: constvar_10
out_neighbor_label: s₀_9
inifac_out_neighbor_str: s₀_9
inifac_str: NormalMeanPrecision_12
var: (s₀_9, out, NodeData in context with properties name = s₀, index = nothing)
=== NEXT var node is: s₀_9
var_label: s₀_9
var_index: 0
var_neighbours: Any["NormalMeanPrecision_12", "AR_14"]
var_neighbours[1]: NormalMeanPrecision_12
in_neighbor_label: constvar_10
out_neighbor_label: s₀_9
var_neighbours[2]: AR_14
in_neighbor_label: s₀_9
out_neighbor_label: s_13
=== NEXT fac node is: AR_14
var: (s_13, y, NodeData in context with properties name = s, index = 1)
=== NEXT var node is: s_13
var_label: s_13
var_index: 1
var_neighbours: Any["AR_14", "*_17", "AR_22"]
var_neighbours[1]: AR_14
in_neighbor_label: s₀_9
out_neighbor_label: s_13
var_neighbours[2]: *_17
in_neighbor_label: constvar_16
out_neighbor_label: anonymous_var_graphppl_15
var_neighbours[3]: AR_22
in_neighbor_label: s_13
out_neighbor_label: s_21
=== NEXT fac node is: AR_22
var: (s_21, y, NodeData in context with properties name = s, index = 2)
=== NEXT var node is: s_21
var_label: s_21
var_index: 2
var_neighbours: Any["AR_22", "*_25", "AR_30"]
var_neighbours[1]: AR_22
in_neighbor_label: s_13
out_neighbor_label: s_21
var_neighbours[2]: *_25
in_neighbor_label: constvar_24
out_neighbor_label: anonymous_var_graphppl_23
var_neighbours[3]: AR_30
in_neighbor_label: s_21
out_neighbor_label: s_29
=== NEXT fac node is: AR_30
var: (s_29, y, NodeData in context with properties name = s, index = 3)
=== NEXT var node is: s_29
var_label: s_29
var_index: 3
var_neighbours: Any["AR_30", "*_33"]
var_neighbours[1]: AR_30
in_neighbor_label: s_21
out_neighbor_label: s_29
var_neighbours[2]: *_33
in_neighbor_label: constvar_32
out_neighbor_label: anonymous_var_graphppl_31
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(s₀_9, out, NodeData in context with properties name = s₀, index = nothing), (constvar_10, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_11, τ, NodeData in context with properties name = constvar, index = nothing)]
out_var: s₀_9
obser_var_label_strs: ["x_18", "x_26", "x_34"]
obser_var_label_strs[i]: x_18
var_neighbours[j]: NormalMeanPrecision_20
in_neighbor_label: anonymous_var_graphppl_15
out_neighbor_label: x_18
obser_var_label_strs[i]: x_26
var_neighbours[j]: NormalMeanPrecision_28
in_neighbor_label: anonymous_var_graphppl_23
out_neighbor_label: x_26
obser_var_label_strs[i]: x_34
var_neighbours[j]: NormalMeanPrecision_36
in_neighbor_label: anonymous_var_graphppl_31
out_neighbor_label: x_34
lar_tikz =generate_tikz( heading ="Time-Varying Autoregressive Model (Univariate)", Model = lar_gppl_model, layers = layers) ## fac layers, i.e. not var (vars are handled with assoced layer)## print(lar_tikz)show_tikz(lar_tikz); ## write to file rather than show in notebook
To infer goal-driven (i.e. purposeful) behavior, we add prior beliefs \(p^+(\mathbf{x})\) about desired future observations. This leads to an extended agent model:
\[p(\mathbf{x}_k \mid \mathbf{s}_k) = \mathcal{N}(\mathbf{x}_k \mid \mathbf{s}_k,\,\mathbf\Theta)\] where \(\mathbf{x}_k = (\chi_{1k}, \chi_{2k}, ...)\) denotes observations of the agent after interacting with the environment.
This means we set a vague prior for the initial state.
4.5.3.1 Generative Model for the Drone
The code in the next block defines the agent’s internal beliefs over the external dynamics and its probabilistic model of the environment, which correspond accurately by directly using the functions defined above. We use the @model macro from RxInfer to define the probabilistic model and the meta block to define approximation methods for the nonlinear state-transition functions.
In the model specification we in addition to the current state of the agent we include the beliefs over its future states (up to T steps ahead):
@modelfunctiondronenav_model(x, mᵤ, Vᵤ, mₓ, Vₓ, mₛ₍ₜ₋₁₎, Vₛ₍ₜ₋₁₎, T, Rᵃ)## Transition function g = (sₜ₋₁::AbstractVector) ->begin sₜ =similar(sₜ₋₁) ## Next state sₜ =Aᵃ(sₜ₋₁, 1.0) + sₜ₋₁return sₜend## Function for modeling turn/yaw control h = (u::AbstractVector) ->Rᵃ(u[1]) Γ =_γ*diageye(4) ## Transition precision 𝚯 =_ϑ*diageye(4) ## Observation variance## sₜ₋₁ ~ MvNormal(mean=mₛ₍ₜ₋₁₎, cov=Vₛ₍ₜ₋₁₎) s₀ ~MvNormal(mean=mₛ₍ₜ₋₁₎, cov=Vₛ₍ₜ₋₁₎)## sₖ₋₁ = sₜ₋₁ sₖ₋₁ = s₀local sfor k in1:T## Control u[k] ~MvNormal(mean=mᵤ[k], cov=Vᵤ[k]) hIuI[k] ~h(u[k]) where { meta=DeltaMeta(method=Unscented()) }## State transition gIsI[k] ~g(sₖ₋₁) where { meta=DeltaMeta(method=Unscented()) } ghSum[k] ~ gIsI[k] + hIuI[k]#. s[k] ~MvNormal(mean=ghSum[k], precision=Γ)## Likelihood of future observations x[k] ~MvNormal(mean=s[k], cov=𝚯)## Target/Goal prior x[k] ~MvNormal(mean=mₓ[k], cov=Vₓ[k]) sₖ₋₁ = s[k]endreturn (s, )end
meta_graph = drone_meta_graph## Shorten some labels to make graph more readablestr_labels = [string(lab) for lab inlabels(meta_graph)]replacements =Pair("MvNormalMeanCovariance", "Nmc"), Pair("MvNormalMeanPrecision", "Nmp"), Pair("constvar", "cv"),Pair("x", "XXXXX") ## make more obviousshort_labels = [replace(s, replacements...) for s in str_labels]GraphPlot.gplot( ## existing plotting functionality meta_graph, layout=spring_layout,## nodelabel=collect(labels(meta_graph)), nodelabel=short_labels, nodelabelsize=0.1, NODESIZE=0.02, ## diameter of the nodes NODELABELSIZE=1.5,# nodelabelc="white", nodelabelc="green", nodelabeldist=0.0, nodefillc=nothing, ## "cyan" edgestrokec="red",## ImageSize = (800, 800) ##- does not work)
cntrl_var_label_strs: ["u_5", "u_24", "u_43"]
cntrl_var_label_strs[i]: u_5
var_neighbours[j]: MvNormalMeanCovariance_8
in_neighbor_label: constvar_6
out_neighbor_label: u_5
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(u_5, out, NodeData in context with properties name = u, index = 1), (constvar_6, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_7, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: #70_10
in_neighbor_label: u_5
out_neighbor_label: hIuI_9
cntrl_var_label_strs[i]: u_24
var_neighbours[j]: MvNormalMeanCovariance_27
in_neighbor_label: constvar_25
out_neighbor_label: u_24
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(u_24, out, NodeData in context with properties name = u, index = 2), (constvar_25, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_26, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: #70_29
in_neighbor_label: u_24
out_neighbor_label: hIuI_28
cntrl_var_label_strs[i]: u_43
var_neighbours[j]: MvNormalMeanCovariance_46
in_neighbor_label: constvar_44
out_neighbor_label: u_43
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(u_43, out, NodeData in context with properties name = u, index = 3), (constvar_44, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_45, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: #70_48
in_neighbor_label: u_43
out_neighbor_label: hIuI_47
var_neighbours[1]: MvNormalMeanCovariance_4
in_neighbor_label: constvar_2
out_neighbor_label: s₀_1
inifac_out_neighbor_str: s₀_1
inifac_str: MvNormalMeanCovariance_4
var: (s₀_1, out, NodeData in context with properties name = s₀, index = nothing)
=== NEXT var node is: s₀_1
var_label: s₀_1
var_index: 0
var_neighbours: Any["MvNormalMeanCovariance_4", "#69_12"]
var_neighbours[1]: MvNormalMeanCovariance_4
in_neighbor_label: constvar_2
out_neighbor_label: s₀_1
var_neighbours[2]: #69_12
in_neighbor_label: s₀_1
out_neighbor_label: gIsI_11
Take this var_label as next fac because we ONLY have 2 var_neighbours
=== NEXT fac node is: #69_12
var: (gIsI_11, out, NodeData in context with properties name = gIsI, index = 1)
=== NEXT var node is: gIsI_11
var_label: gIsI_11
var_index: 1
var_neighbours: Any["#69_12", "+_14"]
var_neighbours[1]: #69_12
in_neighbor_label: s₀_1
out_neighbor_label: gIsI_11
var_neighbours[2]: +_14
in_neighbor_label: gIsI_11
out_neighbor_label: ghSum_13
Take this var_label as next fac because we ONLY have 2 var_neighbours
=== NEXT fac node is: +_14
var: (ghSum_13, out, NodeData in context with properties name = ghSum, index = 1)
=== NEXT var node is: ghSum_13
var_label: ghSum_13
var_index: 1
var_neighbours: Any["+_14", "MvNormalMeanPrecision_17"]
var_neighbours[1]: +_14
in_neighbor_label: gIsI_11
out_neighbor_label: ghSum_13
var_neighbours[2]: MvNormalMeanPrecision_17
in_neighbor_label: ghSum_13
out_neighbor_label: s_15
Take this var_label as next fac because we ONLY have 2 var_neighbours
=== NEXT fac node is: MvNormalMeanPrecision_17
var: (s_15, out, NodeData in context with properties name = s, index = 1)
=== NEXT var node is: s_15
var_label: s_15
var_index: 1
var_neighbours: Any["MvNormalMeanPrecision_17", "MvNormalMeanCovariance_20", "#69_31"]
var_neighbours[1]: MvNormalMeanPrecision_17
in_neighbor_label: ghSum_13
out_neighbor_label: s_15
var_neighbours[2]: MvNormalMeanCovariance_20
in_neighbor_label: s_15
out_neighbor_label: x_18
var_neighbours[3]: #69_31
in_neighbor_label: s_15
out_neighbor_label: gIsI_30
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(s₀_1, out, NodeData in context with properties name = s₀, index = nothing), (constvar_2, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_3, Σ, NodeData in context with properties name = constvar, index = nothing)]
out_var: s₀_1
obser_var_label_strs: ["x_18", "x_37", "x_56"]
obser_var_label_strs[i]: x_18
var_neighbours[j]: MvNormalMeanCovariance_20
in_neighbor_label: s_15
out_neighbor_label: x_18
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_18, out, NodeData in context with properties name = x, index = 1), (s_15, μ, NodeData in context with properties name = s, index = 1), (constvar_19, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: MvNormalMeanCovariance_23
in_neighbor_label: constvar_21
out_neighbor_label: x_18
obser_var_label_strs[i]: x_37
var_neighbours[j]: MvNormalMeanCovariance_39
in_neighbor_label: s_34
out_neighbor_label: x_37
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_37, out, NodeData in context with properties name = x, index = 2), (s_34, μ, NodeData in context with properties name = s, index = 2), (constvar_38, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: MvNormalMeanCovariance_42
in_neighbor_label: constvar_40
out_neighbor_label: x_37
obser_var_label_strs[i]: x_56
var_neighbours[j]: MvNormalMeanCovariance_58
in_neighbor_label: s_53
out_neighbor_label: x_56
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_56, out, NodeData in context with properties name = x, index = 3), (s_53, μ, NodeData in context with properties name = s, index = 3), (constvar_57, Σ, NodeData in context with properties name = constvar, index = nothing)]
var_neighbours[j]: MvNormalMeanCovariance_61
in_neighbor_label: constvar_59
out_neighbor_label: x_56
obser_var_label_strs: ["x_18", "x_37", "x_56"]
obser_var_label_strs[i]: x_18
var_neighbours[j]: MvNormalMeanCovariance_20
in_neighbor_label: s_15
out_neighbor_label: x_18
var_neighbours[j]: MvNormalMeanCovariance_23
in_neighbor_label: constvar_21
out_neighbor_label: x_18
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_18, out, NodeData in context with properties name = x, index = 1), (constvar_21, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_22, Σ, NodeData in context with properties name = constvar, index = nothing)]
obser_var_label_strs[i]: x_37
var_neighbours[j]: MvNormalMeanCovariance_39
in_neighbor_label: s_34
out_neighbor_label: x_37
var_neighbours[j]: MvNormalMeanCovariance_42
in_neighbor_label: constvar_40
out_neighbor_label: x_37
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_37, out, NodeData in context with properties name = x, index = 2), (constvar_40, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_41, Σ, NodeData in context with properties name = constvar, index = nothing)]
obser_var_label_strs[i]: x_56
var_neighbours[j]: MvNormalMeanCovariance_58
in_neighbor_label: s_53
out_neighbor_label: x_56
var_neighbours[j]: MvNormalMeanCovariance_61
in_neighbor_label: constvar_59
out_neighbor_label: x_56
neighbors: Tuple{GraphPPL.NodeLabel, GraphPPL.EdgeLabel, GraphPPL.NodeData}[(x_56, out, NodeData in context with properties name = x, index = 3), (constvar_59, μ, NodeData in context with properties name = constvar, index = nothing), (constvar_60, Σ, NodeData in context with properties name = constvar, index = nothing)]
drone_tikz =generate_tikz( heading ="Drone Flying to Target", Model = drone_gppl_model, layers = layers) ## fac layers, i.e. not var (vars are handled with assoced layer)## print(drone_tikz)show_tikz(drone_tikz); ## write to file rather than show in notebook