Aggregators

Creative Commons License

aGrUM

interactive online version

Aggregators are special type of nodes that includes a generic CPT for any numbers of parents.

pyAgrum proposes a list of such aggregators. Some of then are used below.

In [1]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb
In [2]:
min_x = 0
max_x = 15

bn = gum.BayesNet()
l = [bn.add(gum.RangeVariable(item, item, min_x, max_x)) for item in ["a", "b", "c", "d", "e", "f"]]

gum.config["notebook", "histogram_line_threshold"] = 15
In [3]:
nmax = bn.addMAX(gum.RangeVariable("MAX", "MAX", min_x, max_x))
bn.addArc(l[0], nmax)
bn.addArc(l[1], nmax)
bn.addArc(l[2], nmax)
In [4]:
nmin = bn.addMIN(gum.RangeVariable("MIN", "MIN", min_x, max_x))
bn.addArc(l[3], nmin)
bn.addArc(l[4], nmin)
bn.addArc(l[5], nmin)
In [5]:
nampl = bn.addAMPLITUDE(gum.RangeVariable("DELTA", "DELTA", 0, max_x - min_x))
bn.addArc(nmax, nampl)
bn.addArc(nmin, nampl)
In [6]:
nmedian = bn.addMEDIAN(gum.RangeVariable("MEDIAN", "MEDIAN", min_x, max_x))
for n in [l[0], l[1], l[2], l[3]]:
  bn.addArc(n, nmedian)
# potential for median has a size : 16^5=2^20 double !
In [7]:
nexists = bn.addEXISTS(gum.LabelizedVariable("EXISTS_0", "EXISTS"), 0)
bn.addArc(l[0], nexists)
bn.addArc(l[1], nexists)
bn.addArc(l[2], nexists)
In [8]:
nforall = bn.addFORALL(gum.LabelizedVariable("FORALL_1", "FORALL"), 1)
bn.addArc(l[3], nforall)
bn.addArc(l[4], nforall)
bn.addArc(l[5], nforall)
In [9]:
ncount = bn.addCOUNT(gum.RangeVariable("COUNT_1", "COUNT_1,", 0, 3), 1)
bn.addArc(l[0], ncount)
bn.addArc(l[1], ncount)
bn.addArc(l[2], ncount)
In [10]:
for nod in l:
  bn.cpt(nod).fillWith(1).normalize()
In [11]:
gnb.showInference(bn, size="13")
../_images/notebooks_94-Tools_aggregators_13_0.svg
In [12]:
# dot | neato | fdp | sfdp | twopi | circo | osage | patchwork
gum.config["notebook", "graph_rankdir"] = "LR"
gnb.showInference(bn, size="13", evs={"MEDIAN": [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0]})
gum.config.reset()
../_images/notebooks_94-Tools_aggregators_14_0.svg
In [13]:
# if the roots do not have uniform but random distribution
for nod in l:
  bn.generateCPT(nod)

gnb.showInference(bn, size="13")
../_images/notebooks_94-Tools_aggregators_15_0.svg
In [14]:
gnb.showInference(bn, size="13", evs={"MEDIAN": [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0]})
../_images/notebooks_94-Tools_aggregators_16_0.svg

Input/Output

Aggregator (and ICI model) nodes have no stored CPT — the probability is computed on the fly from the aggregator’s rule. Saving to most BN file formats (BIF, BIFXML, DSL, XDSL, net, UAI, O3PRM) flattens that computed table into a full dense CPT before writing it out. Round-tripping through one of these formats therefore loses the aggregator: reloading the file gives back a plain node with a dense CPT, not an aggregator (bad round trip).

Only aGrUM’s native jgum (JSON) and bgum (binary) formats preserve the aggregator/ICI type exactly. Instead of a flat array of probabilities, the cpt entry for such a node is a small object describing its type and parameters, e.g. {"kind": "aggregator", "name": "forall", "value": 1} for a FORALL node, or {"kind": "ici", "name": "MultiDimNoisyORCompound", ...} for a Noisy-Or node.

In [15]:
import json
import os
import tempfile

demo = gum.BayesNet()
ps = [demo.add(gum.RangeVariable(f"P{i}", "", 0, 9)) for i in range(4)]
for i in range(4):
  demo.cpt(f"P{i}").randomCPT()
nsum = demo.addSUM(gum.RangeVariable("SUM", "", 0, 9))
for p in ps:
  demo.addArc(p, nsum)

with tempfile.TemporaryDirectory() as d:
  bif_path = os.path.join(d, "demo.bif")
  jgum_path = os.path.join(d, "demo.jgum")
  gum.saveBN(demo, bif_path)
  gum.saveBN(demo, jgum_path)
  bif_size = os.path.getsize(bif_path)
  jgum_size = os.path.getsize(jgum_path)
  with open(jgum_path) as f:
    jgum_content = json.load(f)

print(f"BIF  (dense CPT)         : {bif_size:>8} bytes")
print(f"jgum (compact aggregator): {jgum_size:>8} bytes")
print()
print(json.dumps(jgum_content, indent=2))
BIF  (dense CPT)         :   371379 bytes
jgum (compact aggregator):     1176 bytes

{
  "type": "BN",
  "GumJsonVersion": "1.0",
  "nodes": [
    "P0[10]",
    "P1[10]",
    "P2[10]",
    "P3[10]",
    "SUM[10]"
  ],
  "parents": {
    "P0": [],
    "P1": [],
    "P2": [],
    "P3": [],
    "SUM": [
      "P0",
      "P1",
      "P2",
      "P3"
    ]
  },
  "cpt": {
    "P0": [
      0.041534047697483666,
      0.30354711427410097,
      0.013285607869804095,
      0.04649931056197004,
      0.1916200355854209,
      0.2294082863043041,
      0.003181428251623264,
      0.012228270478849446,
      0.13747521464730572,
      0.021220684329137818
    ],
    "P1": [
      0.007526694046102483,
      0.14764054419103842,
      0.051923935684058564,
      0.13298046897004068,
      0.08585622728872372,
      0.10119553889644778,
      0.01803484437163927,
      0.13493739063328558,
      0.10917532904586857,
      0.21072902687279493
    ],
    "P2": [
      0.0033006997002650758,
      0.15596241228190494,
      0.04311938547689714,
      0.02371039329531166,
      0.14004542721348606,
      0.26035599870667114,
      0.004945723144842429,
      0.12610728825832662,
      0.031124541490831104,
      0.2113281304314638
    ],
    "P3": [
      0.025519186146852467,
      0.3146990536221461,
      0.31186305212214427,
      0.0004965699225364384,
      0.12762955717599866,
      0.11211823440944402,
      0.019636778605034655,
      0.021011839114818298,
      0.03889227733480638,
      0.028133451546218713
    ],
    "SUM": {
      "kind": "aggregator",
      "name": "sum"
    }
  },
  "properties": {
    "software": "aGrUM 3.1.0",
    "creation": "2026-08-18 10:00:21.010",
    "lastModification": "2026-08-18 10:00:21.027"
  }
}

Indeed, the cpt for the node SUM should have a size of \(10^4=10000\) floats. Instead :

In [16]:
print(json.dumps(jgum_content['cpt']['SUM'], indent=2))
{
  "kind": "aggregator",
  "name": "sum"
}
In [ ]: