Real-time inference optimisation
geobn includes two tiers of optimisation for repeated or latency-sensitive inference, such as continuously updating a dashboard as sensor values change.
The problem
By default, every call to bn.infer() fetches, aligns, and discretises all
inputs, then runs one pgmpy VariableElimination query per unique evidence
combination. For a 1000×1000 grid with terrain sources that never change, this
is wasteful: DEM fetch + reprojection + discretisation can take several seconds
even though the result is the same every time.
Tier 1 — Freeze static nodes
Use freeze() to mark nodes whose input data
will not change between infer() calls:
bn.freeze("slope_angle", "aspect") # declare terrain as static
result = bn.infer(query=["avalanche_risk"])
# First call: fetches terrain normally, caches the discrete index array.
bn.set_input("recent_snow", geobn.ConstantSource(35.0))
result = bn.infer(query=["avalanche_risk"])
# Second call: terrain array reused from cache — no fetch, no reprojection.
What is cached after the first call:
- The discrete integer index array for every frozen node.
- The reference
GridSpec(so it is not re-derived from sources). - The pgmpy
VariableEliminationengine (constructed once, reused).
If the underlying data of a frozen node actually changes (e.g. you swap out the
DEM source), call clear_cache() to
discard the stale arrays before the next infer().
Tier 2 — Precompute all evidence combinations
When all inputs are effectively static and only a few dynamic scalar inputs change, pre-run the entire state-space combinatorial product once and store the results as a numpy lookup table:
bn.precompute(query=["avalanche_risk"])
# One-time cost: ∏ n_states_i pgmpy queries.
# For a 3×2×3×3 state space this is 54 queries (< 1 second).
result = bn.infer(query=["avalanche_risk"])
# Zero pgmpy calls — O(H×W) numpy fancy indexing only.
After precompute(), each infer() call replaces every pgmpy query with a
single numpy index operation: the (H, W) discrete index grids are used directly
to look up the pre-filled probability table.
Note
The table path activates automatically when infer() is called with the same
query list that was passed to precompute(). Calling infer() with a
different query falls back to the normal VE path.
Offline persistence — save and load the table
For robotics and edge-deployment workflows where pgmpy must not run at deploy time, you can build the lookup table on a workstation and ship only the numpy archive:
Offline (workstation):
import geobn
bn = geobn.load("model.bif")
bn.set_input("slope", geobn.RasterSource("slope.tif"))
bn.set_input("rainfall", geobn.ConstantSource(50.0))
bn.set_discretization("slope", [0, 10, 30, 90], ["flat", "moderate", "steep"])
bn.set_discretization("rainfall", [0, 25, 75, 200], ["low", "medium", "high"])
bn.precompute(query=["fire_risk"]) # one-time cost: all state combos
bn.save_precomputed("fire_risk_table.npz") # portable .npz — ship this file
Runtime (robot / edge device):
import geobn
bn = geobn.load("model.bif")
bn.set_input("slope", geobn.RasterSource("slope.tif"))
bn.set_input("rainfall", geobn.ConstantSource(50.0))
bn.set_discretization("slope", [0, 10, 30, 90], ["flat", "moderate", "steep"])
bn.set_discretization("rainfall", [0, 25, 75, 200], ["low", "medium", "high"])
bn.load_precomputed("fire_risk_table.npz") # no pgmpy inference — loads numpy archive
result = bn.infer(query=["fire_risk"]) # O(H×W) table lookup, zero pgmpy calls
Note
load_precomputed() validates that the file's node order and array shapes
match the current BN and discretization configuration. A ValueError is
raised if there is a mismatch; a FileNotFoundError is raised if the file
does not exist.
Combining both tiers
The tiers stack naturally:
bn.freeze("slope_angle", "aspect")
bn.precompute(query=["avalanche_risk"])
# Subsequent infer() calls:
# - Skip fetch/align/discretise for frozen terrain nodes (Tier 1)
# - Skip all pgmpy queries (Tier 2)
result = bn.infer(query=["avalanche_risk"])
Cache lifecycle
| Trigger | Effect |
|---|---|
bn.freeze(*nodes) called with a different node set |
clear_cache() is called automatically |
bn.set_grid(...) |
Cache cleared automatically |
bn.clear_cache() |
All caches discarded (frozen arrays, VE engine, inference table) |
API reference
See the full method signatures in the GeoBayesianNetwork reference: