See the Wikipedia page on Bayesian networks

Alarm Example

Imports

import edu.uci.ics.jung.graph.DirectedSparseGraph
import cats.implicits._
import spire.math._
import spire.implicits._
import axle._
import axle.algebra.DirectedGraph
import axle.stats._
import axle.pgm._
import axle.jblas._
import axle.jung._

Setup

val bools = Vector(true, false)
// bools: scala.collection.immutable.Vector[Boolean] = Vector(true, false)

val B = UnknownDistribution0[Boolean, Rational](bools, "Burglary")
// B: axle.stats.UnknownDistribution0[Boolean,spire.math.Rational] = UnknownDistribution0(Vector(true, false),Burglary)

val E = UnknownDistribution0[Boolean, Rational](bools, "Earthquake")
// E: axle.stats.UnknownDistribution0[Boolean,spire.math.Rational] = UnknownDistribution0(Vector(true, false),Earthquake)

val A = UnknownDistribution0[Boolean, Rational](bools, "Alarm")
// A: axle.stats.UnknownDistribution0[Boolean,spire.math.Rational] = UnknownDistribution0(Vector(true, false),Alarm)

val J = UnknownDistribution0[Boolean, Rational](bools, "John Calls")
// J: axle.stats.UnknownDistribution0[Boolean,spire.math.Rational] = UnknownDistribution0(Vector(true, false),John Calls)

val M = UnknownDistribution0[Boolean, Rational](bools, "Mary Calls")
// M: axle.stats.UnknownDistribution0[Boolean,spire.math.Rational] = UnknownDistribution0(Vector(true, false),Mary Calls)

val bFactor =
  Factor(Vector(B), Map(
    Vector(B is true) -> Rational(1, 1000),
    Vector(B is false) -> Rational(999, 1000)))
// bFactor: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Burglary)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true)) -> 1/1000, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),false)) -> 999/1000))

val eFactor =
  Factor(Vector(E), Map(
    Vector(E is true) -> Rational(1, 500),
    Vector(E is false) -> Rational(499, 500)))
// eFactor: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Earthquake)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true)) -> 1/500, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),false)) -> 499/500))

val aFactor =
  Factor(Vector(B, E, A), Map(
    Vector(B is false, E is false, A is true) -> Rational(1, 1000),
    Vector(B is false, E is false, A is false) -> Rational(999, 1000),
    Vector(B is true, E is false, A is true) -> Rational(940, 1000),
    Vector(B is true, E is false, A is false) -> Rational(60, 1000),
    Vector(B is false, E is true, A is true) -> Rational(290, 1000),
    Vector(B is false, E is true, A is false) -> Rational(710, 1000),
    Vector(B is true, E is true, A is true) -> Rational(950, 1000),
    Vector(B is true, E is true, A is false) -> Rational(50, 1000)))
// aFactor: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Burglary), UnknownDistribution0(Vector(true, false),Earthquake), UnknownDistribution0(Vector(true, false),Alarm)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),false), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false)) -> 71/100, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false)) -> 1/20, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), Ca...

val jFactor =
  Factor(Vector(A, J), Map(
    Vector(A is true, J is true) -> Rational(9, 10),
    Vector(A is true, J is false) -> Rational(1, 10),
    Vector(A is false, J is true) -> Rational(5, 100),
    Vector(A is false, J is false) -> Rational(95, 100)))
// jFactor: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Alarm), UnknownDistribution0(Vector(true, false),John Calls)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),true), CaseIs(UnknownDistribution0(Vector(true, false),John Calls),true)) -> 9/10, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),true), CaseIs(UnknownDistribution0(Vector(true, false),John Calls),false)) -> 1/10, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false), CaseIs(UnknownDistribution0(Vector(true, false),John Calls),true)) -> 1/20, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false), CaseIs(UnknownDistribution0(Vector(true, false),John Calls),false)) -> 19/20))

val mFactor =
  Factor(Vector(A, M), Map(
    Vector(A is true, M is true) -> Rational(7, 10),
    Vector(A is true, M is false) -> Rational(3, 10),
    Vector(A is false, M is true) -> Rational(1, 100),
    Vector(A is false, M is false) -> Rational(99, 100)))
// mFactor: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Alarm), UnknownDistribution0(Vector(true, false),Mary Calls)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),true), CaseIs(UnknownDistribution0(Vector(true, false),Mary Calls),true)) -> 7/10, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),true), CaseIs(UnknownDistribution0(Vector(true, false),Mary Calls),false)) -> 3/10, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false), CaseIs(UnknownDistribution0(Vector(true, false),Mary Calls),true)) -> 1/100, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false), CaseIs(UnknownDistribution0(Vector(true, false),Mary Calls),false)) -> 99/100))

// edges: ba, ea, aj, am
val bn = BayesianNetwork.withGraphK2[Boolean, Rational, DirectedSparseGraph](
  "A sounds (due to Burglary or Earthquake) and John or Mary Call",
  Map(B -> bFactor,
    E -> eFactor,
    A -> aFactor,
    J -> jFactor,
    M -> mFactor))
// bn: axle.pgm.BayesianNetwork[Boolean,spire.math.Rational,edu.uci.ics.jung.graph.DirectedSparseGraph[axle.pgm.BayesianNetworkNode[Boolean,spire.math.Rational],axle.pgm.Edge]] = BayesianNetwork(A sounds (due to Burglary or Earthquake) and John or Mary Call,Map(UnknownDistribution0(Vector(true, false),Alarm) -> Factor(Vector(UnknownDistribution0(Vector(true, false),Burglary), UnknownDistribution0(Vector(true, false),Earthquake), UnknownDistribution0(Vector(true, false),Alarm)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),false), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false)) -> 71/100, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(...

Create an SVG visualization

import axle.visualize._
// import axle.visualize._

import axle.web._
// import axle.web._

svg(BayesianNetworkVisualization(bn, 1000, 1000, 20), "alarmbayes.svg")

alarm bayes network

The network can be used to compute the joint probability table:

val jpt = bn.jointProbabilityTable
// jpt: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Burglary), UnknownDistribution0(Vector(true, false),Mary Calls), UnknownDistribution0(Vector(true, false),Earthquake), UnknownDistribution0(Vector(true, false),John Calls), UnknownDistribution0(Vector(true, false),Alarm)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),false), CaseIs(UnknownDistribution0(Vector(true, false),Mary Calls),true), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),John Calls),false), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),true)) -> 202797/5000000000, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(Vec...

string(jpt)
// res2: String =
// Burglary Mary Calls Earthquake John Calls Alarm
// true     true       true       true       true  1197/1000000000
// true     true       true       true       false 1/20000000000
// true     true       true       false      true  133/1000000000
// true     true       true       false      false 19/20000000000
// true     true       false      true       true  1477539/2500000000
// true     true       false      true       false 1497/50000000000
// true     true       false      false      true  164171/2500000000
// true     true       false      false      false 28443/50000000000
// true     false      true       true       true  513/1000000000
// true     false      true       true       false 99/20000000000
// true     false      true       false      true  57/1000000000
// true     false      true      ...

Variables can be summed out of the factor:

jpt.Σ(M).Σ(J).Σ(A).Σ(B).Σ(E)
// res3: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(),Map(Vector() -> 1))
jpt.sumOut(M).sumOut(J).sumOut(A).sumOut(B).sumOut(E)
// res4: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(),Map(Vector() -> 1))

Multiplication of factors also works:

val f = (bn.cpt(A) * bn.cpt(B)) * bn.cpt(E)
// f: axle.stats.Factor[Boolean,spire.math.Rational] = Factor(Vector(UnknownDistribution0(Vector(true, false),Burglary), UnknownDistribution0(Vector(true, false),Earthquake), UnknownDistribution0(Vector(true, false),Alarm)),Map(Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),false), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false)) -> 70929/50000000, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),true), CaseIs(UnknownDistribution0(Vector(true, false),Alarm),false)) -> 1/10000000, Vector(CaseIs(UnknownDistribution0(Vector(true, false),Burglary),true), CaseIs(UnknownDistribution0(Vector(true, false),Earthquake),t...

string(f)
// res5: String =
// Burglary Earthquake Alarm
// true     true       true  19/10000000
// true     true       false 1/10000000
// true     false      true  23453/25000000
// true     false      false 1497/25000000
// false    true       true  28971/50000000
// false    true       false 70929/50000000
// false    false      true  498501/500000000
// false    false      false 498002499/500000000

Markov assumptions:

string(bn.markovAssumptionsFor(M))
// res6: String = I({Mary Calls}, {Alarm}, {Burglary, Earthquake, John Calls})

This is read as “M is independent of E, B, and J given A”.