Skip to content

Symbolic outcomes

Thanks to optype, dyce offers typing support for arbitrary number-like outcomes, including primitives from symbolic expression packages such as SymPy.

1
2
3
4
5
6
7
>>> from dyce import H, P
>>> from dyce.d import d2, d3
>>> import sympy.abc
>>> d2_2x = d2 + 2 * sympy.abc.x
>>> d3_y = d3 + sympy.abc.y
>>> P(d2_2x, d3_y).h()
H({2*x + y + 2: 1, 2*x + y + 3: 2, 2*x + y + 4: 2, 2*x + y + 5: 1})

Miss you, Doris!

Note

Be aware that, depending on implementation, performance can suffer quite a bit when using symbolic primitives, especially with large histograms or pools.

For histograms and pools, dyce remains opinionated about ordering. For non-critical contexts where relative values are indeterminate, dyce will attempt a “natural” ordering based on the string representation of each outcome. This is to accommodate symbolic expressions whose relative values are often unknowable.

1
2
3
4
5
6
7
>>> expr = sympy.abc.x < sympy.abc.x * 3
>>> expr
x < 3*x
>>> bool(expr)  # nope
Traceback (most recent call last):
  ...
TypeError: cannot determine truth value of Relational...

In practice, that means that certain operations won’t work with symbolic expressions where correctness depends on ordering outcomes according to relative value (e.g., dice selection from pools).

Flattening pools (e.g., via P.h) works.

1
2
3
4
5
6
7
8
>>> d2_x = d2 * sympy.abc.x
>>> d2_x
H({2*x: 1, x: 1})
>>> p = P(d2_x / 3, (d2_x + 1) / 3, (d2_x + 2) / 3)
>>> p
P(H({2*x/3 + 1/3: 1, x/3 + 1/3: 1}), H({2*x/3 + 2/3: 1, x/3 + 2/3: 1}), H({2*x/3: 1, x/3: 1}))
>>> p.h()
H({2*x + 1: 1, 4*x/3 + 1: 3, 5*x/3 + 1: 3, x + 1: 1})

Enumerating rolls works.

1
2
>>> list(p.rolls_with_counts())
[((2*x/3, 2*x/3 + 1/3, 2*x/3 + 2/3), 1), ((2*x/3 + 1/3, 2*x/3 + 2/3, x/3), 1), ((2*x/3, 2*x/3 + 2/3, x/3 + 1/3), 1), ..., ((2*x/3, x/3 + 1/3, x/3 + 2/3), 1), ((x/3, x/3 + 1/3, x/3 + 2/3), 1)]

P.sample works.

1
2
>>> p.sample()
(2*x/3, 2*x/3 + 2/3, x/3 + 1/3)

Selection (e.g., via P.at) “works” (i.e., falls back to natural ordering of outcomes), but that is a deliberate compromise of convenience.

1
2
>>> p.at(0)
H({2*x/3: 4, 2*x/3 + 1/3: 2, 2*x/3 + 2/3: 1, x/3: 1})

P.apply_to_each_h can help pave the way back to concrete outcomes.

1
2
3
>>> f = lambda outcome: outcome.subs({sympy.abc.x: sympy.Rational(1, 3)})
>>> p.apply_to_each_h(f)
P(H({1/9: 1, 2/9: 1}), H({4/9: 1, 5/9: 1}), H({7/9: 1, 8/9: 1}))