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.
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: