Skip to content

Aggregators

Creative Commons LicenseaGrUMinteractive 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.

import pyagrum as gum
import pyagrum.lib.notebook as gnb
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
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)
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)
nampl = bn.addAMPLITUDE(gum.RangeVariable("DELTA", "DELTA", 0, max_x - min_x))
bn.addArc(nmax, nampl)
bn.addArc(nmin, nampl)
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 !
nexists = bn.addEXISTS(gum.LabelizedVariable("EXISTS_0", "EXISTS"), 0)
bn.addArc(l[0], nexists)
bn.addArc(l[1], nexists)
bn.addArc(l[2], nexists)
nforall = bn.addFORALL(gum.LabelizedVariable("FORALL_1", "FORALL"), 1)
bn.addArc(l[3], nforall)
bn.addArc(l[4], nforall)
bn.addArc(l[5], nforall)
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)
for nod in l:
bn.cpt(nod).fillWith(1).normalize()
gnb.showInference(bn, size="13")

svg

## 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()

svg

## if the roots do not have uniform but random distribution
for nod in l:
bn.generateCPT(nod)
gnb.showInference(bn, size="13")

svg

gnb.showInference(bn, size="13", evs={"MEDIAN": [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0]})

svg

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.

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 104=1000010^4=10000 floats. Instead :

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