def simplex_grid(length, subdivisions, using=None, inplace=False): """Returns a generator over distributions, determined by a grid. The grid is "triangular" in Euclidean space. The total number of points on the grid is:: (subdivisions + length - 1)! / (subdivisions)! / (length-1)! and is equivalent to the total number of ways ``n`` indistinguishable items can be placed into ``k`` distinguishable slots, where n=`subdivisions` and k=`length`. Parameters ---------- length : int The number of elements in each distribution. The dimensionality of the simplex is length-1. subdivisions : int The number of subdivisions for the interval [0, 1]. Each component will take on values at the boundaries of the subdivisions. For example, one subdivision means each component can take the values 0 or 1 only. Two subdivisions corresponds to :math:`[[0, 1/2], [1/2, 1]]` and thus, each component can take the values 0, 1/2, or 1. A common use case is to exponentially increase the number of subdivisions at each level. That is, subdivisions would be: 2**0, 2**1, 2**2, 2**3, ... using : None, callable, or distribution If None, then scalar distributions on integers are yielded. If `using` is a distribution, then each yielded distribution is a copy of `using` with its pmf set appropriately. For other callables, a tuple of the pmf is passed to the callable and then yielded. inplace : bool If `True`, then each yielded distribution is the same Python object, but with a new probability mass function. If `False`, then each yielded distribution is a unique Python object and can be safely stored for other calculations after the generator has finished. This keyword has an effect only when `using` is None or some distribution. Examples -------- >>> list(dit.simplex_grid(2, 2, using=tuple)) [(0.0, 1.0), (0.25, 0.75), (0.5, 0.5), (0.75, 0.25), (1.0, 0.0)] """ from dit.math.combinatorics import slots if subdivisions < 1: raise ditException('`subdivisions` must be greater than or equal to 1') elif length < 1: raise ditException('`length` must be greater than or equal to 1') gen = slots(int(subdivisions), int(length), normalized=True) if using is None: using = random_scalar_distribution(length) if using is tuple: for pmf in gen: yield pmf elif not isinstance(using, BaseDistribution): for pmf in gen: yield using(pmf) else: if length != len(using.pmf): raise Exception('`length` must match the length of pmf') if inplace: d = using for pmf in gen: d.pmf[:] = pmf yield d else: for pmf in gen: d = using.copy() d.pmf[:] = pmf yield d
def test_slots1(): x = list(slots(3, 2)) x_ = [(0, 3), (1, 2), (2, 1), (3, 0)] assert x == x_
def test_slots2(): x = np.asarray(list(slots(3, 2, normalized=True))) x_ = np.asarray([(0, 1), (1 / 3, 2 / 3), (2 / 3, 1 / 3), (1, 0)]) assert np.allclose(x, x_)
def test_slots2(): x = np.asarray(list(slots(3, 2, normalized=True))) x_ = np.asarray([(0, 1), (1/3, 2/3), (2/3, 1/3), (1, 0)]) assert np.allclose(x, x_)
def test_slots1(): x = list(slots(3, 2)) x_ = [(0, 3), (1, 2), (2, 1), (3, 0)] assert_equal(x, x_)