in src/beanmachine/ppl/compiler/support.py [0:0]
def _product(self, f: Callable, *nodes: bn.BMGNode) -> SetOfTensors:
# * We have a sequence of nodes n[0], n[1], ... n[k-1].
#
# * Each of those nodes has possible values t[x][0], t[x][1] ...
# for x from 0 to k-1.
#
# * We have a function f which takes k tensors and returns a tensor.
#
# * We wish to compute the set:
#
# {
# f(t[0][0], t[1][0], ... t[k-1][0]),
# f(t[0][1], t[1][0], ... t[k-1][0]),
# ...
# }
#
# That is, the Cartesian product of all possible combinations of
# values of each node, with each element of the product run through
# function f.
#
# However, we have some problems:
#
# * The support of a node might be infinite.
# * The support of a node might be unknown.
# * The support of a node might be finite but large.
# * The size of the product might be finite but large.
#
# In those cases we want to return special values Infinite, Unknown
# or TooBig rather than wasting time and memory to compute the set.
#
# ####
#
# First thing to do is determine the *approximate* size of the
# Cartesian product of possible values.
#
# TODO: This approximation is an over-estimate; for instance, when
# multiplying {0 or 1} by { n elements} we assume that the resulting
# set has up to 2n elements, when in fact it only has n or n+1 elements.
# Would it be simpler and more accurate to instead make a loop, accumulate
# the result into a mutable deduplicating set, and if the set ever gets
# too big, bail out then?
size = functools.reduce(
lambda acc, node: _set_product_approximate_size(acc, self[node]), nodes, 1.0
)
if size == positive_infinity:
return Infinite
if isnan(size):
return Unknown
if size >= _limit:
return TooBig
# If we've made it here then every node had a small support
# and the product of the approximate sizes was small too.
# We can just take the Cartesian product and call f.
return SetOfTensors(f(c) for c in itertools.product(*(self[n] for n in nodes)))