def test_op_add_num(self) -> None: p_d6 = P(6) p_d6_plus = P(H(range(2, 8))) p_d8 = P(8) p_d8_plus = P(H(range(2, 10))) assert 1 + p_d6 == p_d6_plus assert p_d8_plus == p_d8 + 1
def test_op_sub_num(self) -> None: p_d6 = P(6) p_minus_d6 = P(H(range(0, -6, -1))) p_d8 = P(8) p_d8_minus = P(H(range(0, 8))) assert 1 - p_d6 == p_minus_d6 assert p_d8_minus == p_d8 - 1
def do_it(_: str) -> None: import matplotlib.pyplot p_4d6 = 4 @ P(6) res1 = p_4d6.h(slice(1, None)) d6_reroll_first_one = H(6).substitute(lambda h, outcome: H(6) if outcome == 1 else outcome) p_4d6_reroll_first_one = 4 @ P(d6_reroll_first_one) res2 = p_4d6_reroll_first_one.h(slice(1, None)) p_4d6_reroll_all_ones = 4 @ P(H(range(2, 7))) res3 = p_4d6_reroll_all_ones.h(slice(1, None)) matplotlib.pyplot.plot( *res1.distribution_xy(), marker=".", label="Discard lowest", ) matplotlib.pyplot.plot( *res2.distribution_xy(), marker=".", label="Re-roll first 1; discard lowest", ) matplotlib.pyplot.plot( *res3.distribution_xy(), marker=".", label="Re-roll all 1s; discard lowest", ) matplotlib.pyplot.legend() # Should match the corresponding img[alt] text matplotlib.pyplot.title(r"Comparing various take-three-of-4d6 methods")
def test_getitem_slice(self) -> None: d4n = H(-4) d8 = H(8) p_3d4n_3d8 = 3 @ P(d4n, d8) assert p_3d4n_3d8[:] == p_3d4n_3d8 assert p_3d4n_3d8[:0] == P() assert p_3d4n_3d8[6:] == P() assert p_3d4n_3d8[2:4] == P(d4n, d8)
def test_init_multiple_histograms(self) -> None: d6 = H(6) p_d6 = P(6) p_2d6 = P(d6, d6) assert p_2d6.h() == H(sum(v) for v in itertools.product(d6, d6)) assert P(p_d6, p_d6) == p_2d6 assert P(p_d6, d6) == p_2d6 assert P(d6, p_d6) == p_2d6
def test_op_sub_h(self) -> None: d2 = H(2) d3 = H(3) assert d2 - d3 == {-2: 1, -1: 2, 0: 2, 1: 1} assert d3 - d2 == {-1: 1, 0: 2, 1: 2, 2: 1} assert d2 - H({}) == d2 assert H({}) - d3 == -d3
def test_op_unary(self) -> None: p = P(H(-v if v % 2 else v for v in range(10, 20))) assert isinstance(+p, type(p)) assert (+p) == P(H((10, -11, 12, -13, 14, -15, 16, -17, 18, -19))) assert isinstance(-p, type(p)) assert (-p) == P(H((-10, 11, -12, 13, -14, 15, -16, 17, -18, 19))) assert isinstance(abs(p), type(p)) assert abs(p) == P(H((10, 11, 12, 13, 14, 15, 16, 17, 18, 19)))
def test_h_flatten(self) -> None: r_d6 = range(1, 7) r_d8 = range(1, 9) d6_d8 = H(sum(v) for v in itertools.product(r_d6, r_d8) if v) p_d6 = P(6) p_d8 = P(8) p_d6_d8 = P(p_d6, p_d8) assert p_d6_d8.h() == d6_d8 assert P().h() == H({})
def test_len_and_counts(self) -> None: d0 = H({}) d6_d8 = H(6) + H(8) assert len(d0) == 0 assert sum(d0.counts()) == 0 assert len(d6_d8) == 13 # num distinct values assert sum(d6_d8.counts()) == 48 # combinations assert len((d6_d8 + d6_d8)) == 25 assert sum((d6_d8 + d6_d8).counts()) == 2304
def test_op_add_h(self) -> None: d2 = H(2) d3 = H(3) assert d2 + d3 == {2: 1, 3: 2, 4: 2, 5: 1} assert d3 + d2 == {2: 1, 3: 2, 4: 2, 5: 1} assert d2 + d3 == d3 + d2 assert d2 + H({}) == d2 assert H({}) + d3 == d3
def test_map(self) -> None: d6 = H(6) d8 = H(8) within_filter = _within(-1, 1) d8_v_d6 = d8.map(within_filter, d6) assert d8_v_d6 == {-1: 10, 0: 17, 1: 21} within_filter = _within(7, 9) d6_2_v_7_9 = (2 @ d6).map(within_filter, 0) assert d6_2_v_7_9 == {-1: 15, 0: 15, 1: 6}
def test_op_matmul(self) -> None: d6 = H(6) d6_2 = d6 + d6 d6_3 = d6_2 + d6 assert 0 @ d6 == H({}) assert H({}) == d6 @ 0 assert 2 @ d6 == d6_2 assert d6_2 == d6 @ 2 assert d6_3 == 3 @ d6 assert 4 @ d6 == d6 @ 2 @ 2
def test_getitem_int(self) -> None: d4n = H(-4) d8 = H(8) p_3d4n_3d8 = 3 @ P(d4n, d8) assert p_3d4n_3d8[0] == d4n assert p_3d4n_3d8[2] == d4n assert p_3d4n_3d8[-3] == d8 assert p_3d4n_3d8[-1] == d8 with pytest.raises(IndexError): _ = p_3d4n_3d8[6]
def test_format(self) -> None: assert H((1, 2, 3, 1, 2, 1)).format(width=115) == os.linesep.join(( "avg | 1.67", "std | 0.75", "var | 0.56", " 1 | 50.00% |##################################################", " 2 | 33.33% |#################################", " 3 | 16.67% |################", )) assert (H((1, 2, 3, 1, 2, 1)).format( width=0) == "{avg: 1.67, 1: 50.00%, 2: 33.33%, 3: 16.67%}")
def do_it(_: str) -> None: import matplotlib.pyplot save_roll = H(20) burning_arch_damage = 10 @ H(6) + 10 pass_save = save_roll.ge(10) damage_half_on_save = burning_arch_damage // (pass_save + 1) outcomes, probabilities = damage_half_on_save.distribution_xy() matplotlib.pyplot.plot(outcomes, probabilities, marker=".") # Should match the corresponding img[alt] text matplotlib.pyplot.title( r"Expected outcomes for attack with saving throw for half damage")
def test_op_truediv_h(self) -> None: d2 = H(2) d3n = H(-3) p_d2 = P(d2) p_d3n = P(d3n) d2_truediv_d3n = d2 / d3n d3n_truediv_d2 = d3n / d2 assert p_d2 / p_d3n == d2_truediv_d3n assert p_d2 / d3n == d2_truediv_d3n assert d2 / p_d3n == d2_truediv_d3n assert p_d3n / p_d2 == d3n_truediv_d2 assert p_d3n / d2 == d3n_truediv_d2 assert d3n / p_d2 == d3n_truediv_d2 assert p_d2 / p_d3n != p_d3n / p_d2
def do_it(_: str) -> None: import matplotlib.pyplot p_2d6 = 2 @ P(H(6)) outcomes, probabilities = p_2d6.h(0).distribution_xy() matplotlib.pyplot.bar( [v - 0.125 for v in outcomes], probabilities, alpha=0.75, width=0.5, label="Lowest", ) outcomes, probabilities = p_2d6.h(-1).distribution_xy() matplotlib.pyplot.bar( [v + 0.125 for v in outcomes], probabilities, alpha=0.75, width=0.5, label="Highest", ) matplotlib.pyplot.legend() # Should match the corresponding img[alt] text matplotlib.pyplot.title(r"Taking the lowest or highest die of 2d6")
def test_display_burst_outer(self): _, ax = dyce_plt.matplotlib.pyplot.subplots() d6_2 = 2 @ H(6) dyce_plt.display_burst(ax, d6_2, dyce_plt.labels_cumulative(d6_2)) wedge_labels = [ w.get_label() for w in ax.get_children()[:22] if isinstance(w, matplotlib.patches.Wedge) ] assert len(wedge_labels) == 22 assert wedge_labels == [ "2 2.78%; ≥2.78%; ≤100.00%", "3 5.56%; ≥8.33%; ≤97.22%", "4 8.33%; ≥16.67%; ≤91.67%", "5 11.11%; ≥27.78%; ≤83.33%", "6 13.89%; ≥41.67%; ≤72.22%", "7 16.67%; ≥58.33%; ≤58.33%", "8 13.89%; ≥72.22%; ≤41.67%", "9 11.11%; ≥83.33%; ≤27.78%", "10 8.33%; ≥91.67%; ≤16.67%", "11 5.56%; ≥97.22%; ≤8.33%", "12 2.78%; ≥100.00%; ≤2.78%", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", ]
def test_display_burst(self): _, ax = dyce_plt.matplotlib.pyplot.subplots() d6_2 = 2 @ H(6) dyce_plt.display_burst(ax, d6_2) wedge_labels = [ w.get_label() for w in ax.get_children()[:22] if isinstance(w, matplotlib.patches.Wedge) ] assert len(wedge_labels) == 22 assert wedge_labels == [ "2.78%", "5.56%", "8.33%", "11.11%", "13.89%", "16.67%", "13.89%", "11.11%", "8.33%", "5.56%", "2.78%", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", ]
def test_rolls_with_counts_take_homogeneous_dice_vs_known_correct( self) -> None: p_df = P(H((-1, 0, 1))) p_4df = 4 @ p_df for which in ( # All outcomes slice(None), slice(0, 4), ): karonen, multinomial_coefficient = _rwc_validation_helper( p_4df, which) karonen.assert_not_called() multinomial_coefficient.assert_called() for which in ( # 1 Outcome slice(0, 1), slice(1, 2), slice(2, 3), slice(3, 4), ): karonen, multinomial_coefficient = _rwc_validation_helper( p_4df, which) karonen.assert_called() multinomial_coefficient.assert_not_called() for which in ( # No outcomes slice(0, 0), ): karonen, multinomial_coefficient = _rwc_validation_helper( p_4df, which) karonen.assert_not_called() multinomial_coefficient.assert_not_called()
def test_h_take_homogeneous_dice_vs_known_correct(self) -> None: # Use the brute-force mechanism to validate our harder-to-understand # implementation p_df = P(H((-1, 0, 1))) p_4df = 4 @ p_df for which in ( slice(0, 0), slice(2, 3), slice(1, 2), slice(0, 1), ): assert p_4df.h(which) == H( (sum(roll), count) for roll, count in _brute_force_combinations_with_counts( tuple(p_4df), which))
def test_op_truediv_num(self) -> None: p_d10 = P(10) p1 = P(H(range(100, 0, -10))) assert p_d10 == p1 / 10 assert (2 * 2 * 2 * 3 * 3 * 5 * 7) / p_d10 == H({ 252.0: 1, 280.0: 1, 315.0: 1, 360.0: 1, 420.0: 1, 504.0: 1, 630.0: 1, 840.0: 1, 1260.0: 1, 2520.0: 1, })
def do_it(style: str) -> None: import matplotlib.pyplot single_attack = 2 @ H(6) + 5 def gwf(h: H, outcome): return h if outcome in (1, 2) else outcome great_weapon_fighting = 2 @ (H(6).substitute(gwf)) + 5 fig = matplotlib.pyplot.figure() fig.set_size_inches(10, 10, forward=True) plot_ax = matplotlib.pyplot.subplot2grid((2, 2), (0, 0), colspan=2) sa_burst_ax = matplotlib.pyplot.subplot2grid((2, 2), (1, 0), colspan=1) gwf_burst_ax = matplotlib.pyplot.subplot2grid((2, 2), (1, 1), colspan=1) sa_label = "Normal attack" plot_ax.plot( *single_attack.distribution_xy(), color="lightgreen" if style == "dark" else "tab:green", label=sa_label, marker=".", ) gwf_label = "“Great Weapon Fighting”" plot_ax.plot( *great_weapon_fighting.distribution_xy(), color="lightblue" if style == "dark" else "tab:blue", label=gwf_label, marker=".", ) plot_ax.legend() # Should match the corresponding img[alt] text plot_ax.set_title(r"Comparing a normal attack to an enhanced one") display_burst( sa_burst_ax, single_attack, desc=sa_label, graph_color="RdYlGn_r", text_color="white" if style == "dark" else "black", ) display_burst( gwf_burst_ax, great_weapon_fighting, desc=gwf_label, graph_color="RdYlBu_r", text_color="white" if style == "dark" else "black", )
def test_variance(self) -> None: h = H((i, i) for i in range(10)) assert math.isclose( h.variance(), statistics.pvariance( itertools.chain(*(itertools.repeat(outcome, count) for outcome, count in h.items()))), )
def test_op_sub_h(self) -> None: d2 = H(2) d3n = H(-3) p_d2 = P(d2) p_d3n = P(d3n) d2_sub_d3n = d2 - d3n d3n_sub_d2 = d3n - d2 assert p_d2 - p_d3n == d2_sub_d3n assert p_d2 - d3n == d2_sub_d3n assert d2 - p_d3n == d2_sub_d3n assert p_d3n - p_d2 == d3n_sub_d2 assert p_d3n - d2 == d3n_sub_d2 assert d3n - p_d2 == d3n_sub_d2 assert p_d2 - p_d3n != p_d3n - p_d2 assert p_d2 - P() == p_d2 assert P() - p_d2 == -p_d2 assert P() - P() == P()
def test_op_floordiv_h(self) -> None: d2 = H(2) d3n = H(-3) p_d2 = P(d2) p_d3n = P(d3n) d2_floordiv_d3n = d2 // d3n d3n_floordiv_d2 = d3n // d2 assert p_d2 // p_d3n == d2_floordiv_d3n assert p_d2 // d3n == d2_floordiv_d3n assert d2 // p_d3n == d2_floordiv_d3n assert p_d3n // p_d2 == d3n_floordiv_d2 assert p_d3n // d2 == d3n_floordiv_d2 assert d3n // p_d2 == d3n_floordiv_d2 assert p_d2 // p_d3n != p_d3n // p_d2 assert p_d2 // P() == P() assert P() // p_d2 == P() assert P() // P() == P()
def test_op_mod_h(self) -> None: d2 = H(2) d3n = H(-3) p_d2 = P(d2) p_d3n = P(d3n) d2_mod_d3n = d2 % d3n d3n_mod_d2 = d3n % d2 assert p_d2 % p_d3n == d2_mod_d3n assert p_d2 % d3n == d2_mod_d3n assert d2 % p_d3n == d2_mod_d3n assert p_d3n % p_d2 == d3n_mod_d2 assert p_d3n % d2 == d3n_mod_d2 assert d3n % p_d2 == d3n_mod_d2 assert p_d2 % p_d3n != p_d3n % p_d2 assert p_d2 % P() == P() assert P() % p_d2 == P() assert P() % P() == P()
def test_op_pow_h(self) -> None: d2 = H(2) d3 = H(3) p_d2 = P(d2) p_d3 = P(d3) d2_pow_d3 = d2**d3 d3_pow_d2 = d3**d2 assert p_d2**p_d3 == d2_pow_d3 assert p_d2**d3 == d2_pow_d3 assert d2**p_d3 == d2_pow_d3 assert p_d3**p_d2 == d3_pow_d2 assert p_d3**d2 == d3_pow_d2 assert d3**p_d2 == d3_pow_d2 assert p_d2**p_d3 != p_d3**p_d2 assert p_d2**P() == P() assert P()**p_d2 == P() assert P()**P() == P()
def do_it(_: str) -> None: import matplotlib.pyplot res = (10 @ P(H(10).explode(max_depth=3))).h(slice(-3, None)) matplotlib.pyplot.plot(*res.distribution_xy(), marker=".") # Should match the corresponding img[alt] text matplotlib.pyplot.title( r"Modeling taking the three highest of ten exploding d10s")
def test_substitute_never_expand(self) -> None: def never_expand( d: H, # pylint: disable=unused-argument outcome: float, ) -> Union[float, H]: return outcome d20 = H(20) assert d20.substitute(never_expand) == d20 assert d20.substitute(never_expand, operator.add, 20) == d20