3. BBN Generation

BBN generation is available. We support singly- and multi-connected BBNs.

3.1. Singly-connected BBN

Use generate_singly_bbn() to generate a singly-connected BBN and then use create_reasoning_model() to create a reasoning model.

This example uses max_iter=20 so the generated tree is more illustrative than the initial ordered tree.

[1]:
from pybbn.factory import create_reasoning_model
from pybbn.generator import generate_singly_bbn
import numpy as np

g, p = generate_singly_bbn(
    n=10, max_iter=20, max_values=2, max_alpha=10, rng=np.random.default_rng(seed=37)
)

model = create_reasoning_model(g, p)

The singly-connected BBN graph looks like the following.

[2]:
from help.viz import get_graph_layout
import networkx as nx
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(5, 5))

pos = get_graph_layout(g, seed=37)
nx.draw(g, pos=pos, with_labels=True, node_color="#e0e0e0")

fig.tight_layout()
_images/generate_4_0.png

Posterior queries proceed as usual.

[3]:
import pandas as pd

q1 = model.pquery(easy=True)

observed = list(model.d.nodes())[4]
states = model.domains[observed]

e = {observed: model.create_observation_evidences(observed, states[0])}
q2 = model.pquery(easy=True, evidences=e)

e = {observed: model.create_observation_evidences(observed, states[1])}
q3 = model.pquery(easy=True, evidences=e)

p1 = pd.Series({n: q1[n].iloc[0]["__p__"] for n in model.d.nodes()})
p2 = pd.Series({n: q2[n].iloc[0]["__p__"] for n in model.d.nodes()})
p3 = pd.Series({n: q3[n].iloc[0]["__p__"] for n in model.d.nodes()})

pd.DataFrame([p1, p2, p3]).T.rename(columns={0: "q1", 1: "q2", 2: "q3"})
[3]:
q1 q2 q3
X0 0.122739 0.018840 0.269416
X1 0.115061 0.032858 0.231107
X2 0.627763 0.627688 0.627870
X3 0.210991 0.210991 0.210991
X4 0.585357 1.000000 0.000000
X5 0.699483 0.876250 0.449937
X6 0.612394 0.617927 0.604582
X7 0.505644 0.507679 0.502771
X8 0.394267 0.392686 0.396498
X9 0.684198 0.684198 0.684198
[4]:
from pathlib import Path
from pybbn.serde import model_to_dict
import json
import tempfile

output_dir = Path(tempfile.gettempdir()) / "pybbn-docs"
output_dir.mkdir(exist_ok=True)

with (output_dir / "singly-connected.json").open("w") as fp:
    json.dump(model_to_dict(model), fp, indent=1)

3.2. Multi-connected BBN

Use generate_multi_bbn() to generate a multi-connected BBN and then use create_reasoning_model() to create a reasoning model.

This example also uses max_iter=20 so the generated DAG moves farther from the initial ordered tree and better shows the additional connectivity.

[5]:
from pybbn.generator import generate_multi_bbn

g, p = generate_multi_bbn(
    n=10, max_iter=20, max_values=2, max_alpha=10, rng=np.random.default_rng(seed=37)
)

model = create_reasoning_model(g, p)
[6]:
from help.viz import get_graph_layout

fig, ax = plt.subplots(figsize=(5, 5))

pos = get_graph_layout(g, seed=37)
nx.draw(g, pos=pos, with_labels=True, node_color="#e0e0e0")

fig.tight_layout()
_images/generate_10_0.png
[7]:
import pandas as pd

q1 = model.pquery(easy=True)

observed = list(model.d.nodes())[0]
states = model.domains[observed]

e = {observed: model.create_observation_evidences(observed, states[0])}
q2 = model.pquery(easy=True, evidences=e)

e = {observed: model.create_observation_evidences(observed, states[1])}
q3 = model.pquery(easy=True, evidences=e)

p1 = pd.Series({n: q1[n].iloc[0]["__p__"] for n in model.d.nodes()})
p2 = pd.Series({n: q2[n].iloc[0]["__p__"] for n in model.d.nodes()})
p3 = pd.Series({n: q3[n].iloc[0]["__p__"] for n in model.d.nodes()})

pd.DataFrame([p1, p2, p3]).T.rename(columns={0: "q1", 1: "q2", 2: "q3"})
[7]:
q1 q2 q3
X0 0.690304 1.000000 0.000000
X1 0.709774 0.686770 0.761050
X2 0.254686 0.246655 0.272586
X3 0.191932 0.156421 0.271086
X4 0.544433 0.545465 0.542131
X5 0.315296 0.315987 0.313756
X6 0.671777 0.664806 0.687315
X7 0.496768 0.498473 0.492967
X8 0.367806 0.367324 0.368879
X9 0.498058 0.499395 0.495079
[8]:
with (output_dir / "multi-connected.json").open("w") as fp:
    json.dump(model_to_dict(model), fp, indent=1)