# Conditional Independence¶

Conditional independence is the property of a TPM that *each node’s state at
time* \(t+1\) *must be independent of the state of the others, given the state of
the network at time* \(t\):

This example explores the assumption of conditional independence, and the behaviour of the program when it is not satisfied.

Every state-by-node TPM corresponds to a unique state-by-state TPM which
satisfies the conditional independence property (see Transition probability matrix conventions for
a discussion of the different TPM forms). If a state-by-node TPM is given as
input for a `Network`

, PyPhi assumes that it is from a system with the
corresponding conditionally independent state-by-state TPM.

When a state-by-state TPM is given as input for a `Network`

, the state-by-state
TPM is first converted to a state-by-node TPM. PyPhi then assumes that the
system corresponds to the unique conditionally independent representation of
the state-by-node TPM.

Note

Every **deterministic** state-by-state TPM satisfies the conditional
independence property.

Consider a system of two binary nodes (\(A\) and \(B\)) which do not change if they have the same value, but flip with probability 50% if they have different values.

We’ll load the state-by-state TPM for such a system from the `examples`

module:

```
>>> import pyphi
>>> tpm = pyphi.examples.cond_depend_tpm()
>>> print(tpm)
[[1. 0. 0. 0. ]
[0. 0.5 0.5 0. ]
[0. 0.5 0.5 0. ]
[0. 0. 0. 1. ]]
```

This system does not satisfy the conditional independence property; given a
previous state of `(1, 0)`

, the current state of node \(A\) depends on whether
or not \(B\) has flipped.

If a conditionally dependent TPM is used to create a `Network`

, PyPhi will
raise an error:

```
>>> network = pyphi.Network(tpm)
Traceback (most recent call last):
...
pyphi.exceptions.ConditionallyDependentError: TPM is not conditionally independent.
See the conditional independence example in the documentation for more info.
```

To see the conditionally independent TPM that corresponds to the conditionally dependent TPM, convert it to state-by-node form and then back to state-by-state form:

```
>>> sbn_tpm = pyphi.convert.state_by_state2state_by_node(tpm)
>>> print(sbn_tpm)
[[[0. 0. ]
[0.5 0.5]]
[[0.5 0.5]
[1. 1. ]]]
>>> sbs_tpm = pyphi.convert.state_by_node2state_by_state(sbn_tpm)
>>> print(sbs_tpm)
[[1. 0. 0. 0. ]
[0.25 0.25 0.25 0.25]
[0.25 0.25 0.25 0.25]
[0. 0. 0. 1. ]]
```

A system which does not satisfy the conditional independence property exhibits “instantaneous causality.” In such situations, there must be additional exogenous variable(s) which explain the dependence.

Now consider the above example, but with the addition of a third node (\(C\)) which is equally likely to be ON or OFF, and such that when nodes \(A\) and \(B\) are in different states, they will flip when \(C\) is ON, but stay the same when \(C\) is OFF.

```
>>> tpm2 = pyphi.examples.cond_independ_tpm()
>>> print(tpm2)
[[0.5 0. 0. 0. 0.5 0. 0. 0. ]
[0. 0.5 0. 0. 0. 0.5 0. 0. ]
[0. 0. 0.5 0. 0. 0. 0.5 0. ]
[0. 0. 0. 0.5 0. 0. 0. 0.5]
[0.5 0. 0. 0. 0.5 0. 0. 0. ]
[0. 0. 0.5 0. 0. 0. 0.5 0. ]
[0. 0.5 0. 0. 0. 0.5 0. 0. ]
[0. 0. 0. 0.5 0. 0. 0. 0.5]]
```

The resulting state-by-state TPM now satisfies the conditional independence property.

```
>>> sbn_tpm2 = pyphi.convert.state_by_state2state_by_node(tpm2)
>>> print(sbn_tpm2)
[[[[0. 0. 0.5]
[0. 0. 0.5]]
[[0. 1. 0.5]
[1. 0. 0.5]]]
[[[1. 0. 0.5]
[0. 1. 0.5]]
[[1. 1. 0.5]
[1. 1. 0.5]]]]
```

The node indices are `0`

and `1`

for \(A\) and \(B\), and `2`

for \(C\):

```
>>> AB = [0, 1]
>>> C = [2]
```

From here, if we marginalize out the node \(C\);

```
>>> tpm2_marginalizeC = sbn_tpm2.marginalize_out(C)
```

And then restrict the purview to only nodes \(A\) and \(B\);

```
>>> import numpy as np
>>> tpm2_purviewAB = np.squeeze(tpm2_marginalizeC[:,:,:,AB])
```

We get back the original state-by-node TPM from the system with just \(A\) and \(B\).

```
>>> np.all(tpm2_purviewAB == sbn_tpm)
True
```