Skip to content

Fire Protection Credal Network ​

This example models fire safety in a building. A fire may produce smoke and set off an alarm; the alarm can also be triggered by tampering. Smoke prompts the occupants to leave, and neighbours may report the accident. Every conditional probability is known only up to an interval rather than a single number, so the model is a CredalNetwork (CN) and inference returns probability bounds instead of point values.

The network and the probability intervals below are the benchmark of Estrada-Lugo, Tolo, De Angelis & Patelli [17] (their "small-interval" parameterisation), which lets us check the bounds computed here against their published results.

julia
using EnhancedBayesianNetworks

Root nodes ​

Both roots are imprecise: a recent tampering event, and an actual fire.

julia
T = DiscreteNode(:Tampering)
T[:Tampering => :YesT] = Interval(0.00889, 0.01001)
T[:Tampering => :NoT] = Interval(0.98999, 0.99111)

F = DiscreteNode(:Fire)
F[:Fire => :YesF] = Interval(0.040011, 0.041022)
F[:Fire => :NoF] = Interval(0.958978, 0.959989)
[0.958978, 0.959989]

Alarm ​

The alarm depends on both Tampering and Fire.

julia
A = DiscreteNode(:Alarm, [:Tampering, :Fire])
A[:Tampering => :YesT, :Fire => :YesF, :Alarm => :YesA] = Interval(0.564106, 0.6)
A[:Tampering => :YesT, :Fire => :YesF, :Alarm => :NoA] = Interval(0.4, 0.435894)
A[:Tampering => :YesT, :Fire => :NoF, :Alarm => :YesA] = Interval(0.880001, 0.9)
A[:Tampering => :YesT, :Fire => :NoF, :Alarm => :NoA] = Interval(0.1, 0.119999)
A[:Tampering => :NoT, :Fire => :YesF, :Alarm => :YesA] = Interval(0.987342, 0.99)
A[:Tampering => :NoT, :Fire => :YesF, :Alarm => :NoA] = Interval(0.01, 0.012658)
A[:Tampering => :NoT, :Fire => :NoF, :Alarm => :YesA] = Interval(0.000003, 0.0002)
A[:Tampering => :NoT, :Fire => :NoF, :Alarm => :NoA] = Interval(0.9998, 0.999997)
[0.9998, 0.999997]

Smoke, Leaving and Report ​

The remaining nodes form a chain: Fire → Smoke, and Alarm → Leaving → Report.

julia
S = DiscreteNode(:Smoke, [:Fire])
S[:Fire => :YesF, :Smoke => :YesS] = Interval(0.89, 0.91)
S[:Fire => :YesF, :Smoke => :NoS] = Interval(0.09, 0.11)
S[:Fire => :NoF, :Smoke => :YesS] = Interval(0.01, 0.102469)
S[:Fire => :NoF, :Smoke => :NoS] = Interval(0.897531, 0.915557)

L = DiscreteNode(:Leaving, [:Alarm])
L[:Alarm => :YesA, :Leaving => :YesL] = Interval(0.870001, 0.9)
L[:Alarm => :YesA, :Leaving => :NoL] = Interval(0.1, 0.129999)
L[:Alarm => :NoA, :Leaving => :YesL] = Interval(0.400001, 0.414423)
L[:Alarm => :NoA, :Leaving => :NoL] = Interval(0.585577, 0.599999)

R = DiscreteNode(:Report, [:Leaving])
R[:Leaving => :YesL, :Report => :YesR] = Interval(0.75, 0.759989)
R[:Leaving => :YesL, :Report => :NoR] = Interval(0.240011, 0.25)
R[:Leaving => :NoL, :Report => :YesR] = Interval(0.171101, 0.190012)
R[:Leaving => :NoL, :Report => :NoR] = Interval(0.809988, 0.828899)
[0.809988, 0.828899]

Assembling the Credal Network ​

Because the nodes are imprecise, they are wrapped in a CredalNetwork rather than a BayesianNetwork.

julia
cn = CredalNetwork([T, F, A, S, L, R])
add_child!(cn, :Tampering, :Alarm)
add_child!(cn, :Fire, :Alarm)
add_child!(cn, :Fire, :Smoke)
add_child!(cn, :Alarm, :Leaving)
add_child!(cn, :Leaving, :Report)
order!(cn)

gplot(cn, background_color = "white", legend = true, label_size = 10, legend_x = 14.5, legend_y = 13.5)

Inference without evidence ​

For a Credal Network infer returns bounds: the lowest and highest probability of each state across the whole family of networks.

julia
infer(cn, [:Smoke], Evidence())
CredalPosterior P(Smoke)

Smoke	Interval
------------------------
YesS	[0.116674, 0.135596]
NoS	[0.864404, 0.883326]

Extreme posteriors: 4096
julia
infer(cn, [:Report], Evidence())
CredalPosterior P(Report)

Report	Interval
------------------------
YesR	[0.415413, 0.439863]
NoR	[0.560137, 0.584587]

Extreme posteriors: 4096
julia
infer(cn, [:Alarm], Evidence())
CredalPosterior P(Alarm)

Alarm	Interval
------------------------
YesA	[0.046867, 0.0492809]
NoA	[0.950719, 0.953133]

Extreme posteriors: 4096
julia
infer(cn, [:Leaving], Evidence())
CredalPosterior P(Leaving)

Leaving	Interval
------------------------
YesL	[0.422029, 0.438353]
NoL	[0.561647, 0.577971]

Extreme posteriors: 4096

Inference with evidence ​

Conditioning shifts and tightens the bounds. Given that a fire has broken out, how likely are the occupants to leave, and to report?

julia
infer(cn, [:Leaving], Evidence(:Fire => :YesF))
CredalPosterior P(Leaving | Fire=YesF)

Leaving	Interval
------------------------
YesL	[0.862061, 0.893461]
NoL	[0.106539, 0.137939]

Extreme posteriors: 4096
julia
infer(cn, [:Report], Evidence(:Fire => :YesF))
CredalPosterior P(Report | Fire=YesF)

Report	Interval
------------------------
YesR	[0.670147, 0.699264]
NoR	[0.300736, 0.329853]

Extreme posteriors: 4096
julia
infer(cn, [:Report], Evidence(:Alarm => :YesA))
CredalPosterior P(Report | Alarm=YesA)

Report	Interval
------------------------
YesR	[0.674744, 0.702991]
NoR	[0.297009, 0.325256]

Extreme posteriors: 4096

Reasoning diagnostically: having observed the occupants leaving, what are the bounds on there being a fire?

julia
infer(cn, [:Fire], Evidence(:Leaving => :YesL))
CredalPosterior P(Fire | Leaving=YesL)

Fire	Interval
------------------------
YesF	[0.0790652, 0.0864259]
NoF	[0.913574, 0.920935]

Extreme posteriors: 4096

Validation ​

These are exactly the eight queries reported by Estrada-Lugo et al. [17] (four without evidence, four with), whose "Exact" bounds the values above reproduce.

Timing ​

As a rough indication, the wall-clock time to answer one query after compilation — measured on the machine building these docs, so treat it as indicative rather than a controlled benchmark:

julia
infer(cn, [:Fire], Evidence(:Leaving => :YesL))   # warm up (trigger compilation)
elapsed = @elapsed infer(cn, [:Fire], Evidence(:Leaving => :YesL))
println("query solved in ", round(elapsed * 1.0e3; digits = 3), " ms")
query solved in 474.121 ms

This page was generated using Literate.jl.