# Probabilistic Inference¶

The probabilistic inference algorithm used by py-bbn is an exact inference algorithm. Let’s go through an example on how to conduct exact inference.

## Huang Graph¶

Below is the code to create the Huang Graph [HD99]. Note the typical procedure as follows.

• create a Bayesian Belief Network (BBN)

• create a junction tree from the graph

• assert evidence

• print out the marginal probabilities

``` 1from pybbn.graph.dag import Bbn
2from pybbn.graph.edge import Edge, EdgeType
3from pybbn.graph.jointree import EvidenceBuilder
4from pybbn.graph.node import BbnNode
5from pybbn.graph.variable import Variable
6from pybbn.pptc.inferencecontroller import InferenceController
7
8# create the nodes
9a = BbnNode(Variable(0, 'a', ['on', 'off']), [0.5, 0.5])
10b = BbnNode(Variable(1, 'b', ['on', 'off']), [0.5, 0.5, 0.4, 0.6])
11c = BbnNode(Variable(2, 'c', ['on', 'off']), [0.7, 0.3, 0.2, 0.8])
12d = BbnNode(Variable(3, 'd', ['on', 'off']), [0.9, 0.1, 0.5, 0.5])
13e = BbnNode(Variable(4, 'e', ['on', 'off']), [0.3, 0.7, 0.6, 0.4])
14f = BbnNode(Variable(5, 'f', ['on', 'off']), [0.01, 0.99, 0.01, 0.99, 0.01, 0.99, 0.99, 0.01])
15g = BbnNode(Variable(6, 'g', ['on', 'off']), [0.8, 0.2, 0.1, 0.9])
16h = BbnNode(Variable(7, 'h', ['on', 'off']), [0.05, 0.95, 0.95, 0.05, 0.95, 0.05, 0.95, 0.05])
17
18# create the network structure
19bbn = Bbn() \
37
38# convert the BBN to a join tree
39join_tree = InferenceController.apply(bbn)
40
41# insert an observation evidence
42ev = EvidenceBuilder() \
43    .with_node(join_tree.get_bbn_node_by_name('a')) \
44    .with_evidence('on', 1.0) \
45    .build()
46join_tree.set_observation(ev)
47
48# print the posterior probabilities
49for node, posteriors in join_tree.get_posteriors().items():
50    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
51    print(f'{node} : {p}')
```

A Bayesian Belief Network (BBN) is defined as a pair, `G, P`, where

• `G` is a directed acylic graph (DAG)

• `P` is a joint probability distribution

• and `G` satisfies the Markov Condition (nodes are conditionally independent of non-descendants given its parents)

Ideally, the API should force the user to define `G` and `P` separately. However, there will be a bit of `cognitive friction` with this API as we define nodes associated with their local probability models (conditional probability tables) and then the structure afterwards. But this approach seems a bit more concise, no?

## Updating Conditional Probability Tables¶

Sometimes, you may want to preserve the join tree structure and just update the condtional probability tables (CPTs). Here’s how to do so.

``` 1from pybbn.graph.dag import Bbn
2from pybbn.graph.edge import EdgeType, Edge
3from pybbn.graph.node import BbnNode
4from pybbn.graph.variable import Variable
5from pybbn.pptc.inferencecontroller import InferenceController
6
7# you have built a BBN
8a = BbnNode(Variable(0, 'a', ['t', 'f']), [0.2, 0.8])
9b = BbnNode(Variable(1, 'b', ['t', 'f']), [0.1, 0.9, 0.9, 0.1])
12
13# you have built a junction tree from the BBN
14# let's call this "original" junction tree the left-hand side (lhs) junction tree
15lhs_jt = InferenceController.apply(bbn)
16
17# you may just update the CPTs with the original junction tree structure
18# the algorithm to find/build the junction tree is avoided
19# the CPTs are updated
20rhs_jt = InferenceController.reapply(lhs_jt, {0: [0.3, 0.7], 1: [0.2, 0.8, 0.8, 0.2]})
21
22# let's print out the marginal probabilities and see how things changed
23# print the marginal probabilities for the lhs junction tree
24print('lhs probabilities')
25# print the posterior probabilities
26for node, posteriors in lhs_jt.get_posteriors().items():
27    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
28    print(f'{node} : {p}')
29
30# print the marginal probabilities for the rhs junction tree
31print('rhs probabilities')
32for node, posteriors in rhs_jt.get_posteriors().items():
33    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
34    print(f'{node} : {p}')
```

Note that we use `InferenceController.reapply(...)` to apply the new CPTs to a previous one and that we get a new junction tree as an output.